diff --git a/build-tools/javac-services/src/main/java/com/itsaky/androidide/javac/services/partial/PartialReparserImpl.kt b/build-tools/javac-services/src/main/java/com/itsaky/androidide/javac/services/partial/PartialReparserImpl.kt index f87157c45..f5d05ccb2 100644 --- a/build-tools/javac-services/src/main/java/com/itsaky/androidide/javac/services/partial/PartialReparserImpl.kt +++ b/build-tools/javac-services/src/main/java/com/itsaky/androidide/javac/services/partial/PartialReparserImpl.kt @@ -240,6 +240,7 @@ class PartialReparserImpl : PartialReparser { l.useSource(prevLogged) } } catch (err: Throwable) { + log.error("An error occurred while reparsing method", err) return false } diff --git a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt index d3287e8f3..de7a37a08 100644 --- a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt +++ b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/ProjectManager.kt @@ -306,7 +306,7 @@ object ProjectManager : EventReceiver { if (subModule !is ModuleProject) { continue } - + if (subModule.onDocumentChanged(event)) { break } diff --git a/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar b/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar index 198a71da3..2a51bb96d 100644 Binary files a/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar and b/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar differ diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java index a873a7b55..3330f4b79 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java +++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.java @@ -125,15 +125,6 @@ public interface ILanguageServer { */ void applySettings(@Nullable IServerSettings settings); - /** - * Notify the language server that the project's configuration was changed. Language servers - * decide what type of object they want to receive as configuration. - * - * @param newConfiguration The new configuration object. Only a specific type of object might be - * accepted by observers. - */ - void configurationChanged(Object newConfiguration); - /** * Setup this language server with the given project. Servers are not expected to keep a reference * to the provided project. Instead, use {@link ProjectManager#getRootProject()} to obtain the diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaCompilerProvider.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaCompilerProvider.java index 2f2c3a327..642f13081 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaCompilerProvider.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaCompilerProvider.java @@ -44,6 +44,11 @@ public class JavaCompilerProvider { return JavaCompilerProvider.getInstance().forModule(module); } + /** For internal use only. */ + public JavaCompilerService getCompilerService() { + return compilerService; + } + public synchronized JavaCompilerService forModule(ModuleProject module) { // A module instance is set to the compiler only in case the project is initialized or // this method was called with other mdoule instance. diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java index fb8ab823a..8936798dc 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.java @@ -154,7 +154,7 @@ public class JavaLanguageServer implements ILanguageServer { capabilities.setSmartSelectionsEnabled(true); LSPEditorActions.ensureActionsMenuRegistered(JavaCodeActionsMenu.class); - if (EventBus.getDefault().isRegistered(this)) { + if (!EventBus.getDefault().isRegistered(this)) { EventBus.getDefault().register(this); } initialized = true; @@ -195,11 +195,13 @@ public class JavaLanguageServer implements ILanguageServer { this.settings = settings; } - @Override - public void configurationChanged(Object newConfiguration) {} - @Override public void setupWithProject(@NonNull final Project project) { + // Make sure we're registered with EventBus + if (!EventBus.getDefault().isRegistered(this)) { + EventBus.getDefault().register(this); + } + // Once we have project initialized // Destory the NO_MODULE_COMPILER instance JavaCompilerService.NO_MODULE_COMPILER.destroy(); @@ -306,6 +308,17 @@ public class JavaLanguageServer implements ILanguageServer { @Subscribe(threadMode = ThreadMode.ASYNC) @SuppressWarnings("unused") public void onContentChange(@NonNull DocumentChangeEvent event) { + if (!DocumentUtils.isJavaFile(event.getChangedFile())) { + return; + } + + // TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance + JavaCompilerService.NO_MODULE_COMPILER.onDocumentChange(event); + final JavaCompilerService compilerService = + JavaCompilerProvider.getInstance().getCompilerService(); + if (compilerService != null) { + compilerService.onDocumentChange(event); + } startOrRestartAnalyzeTimer(); } diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/JavaCompilerService.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/JavaCompilerService.java index 8f1753b1d..340061283 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/JavaCompilerService.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/JavaCompilerService.java @@ -21,11 +21,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Pair; +import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent; import com.itsaky.androidide.javac.services.compiler.ReusableCompiler; import com.itsaky.androidide.javac.services.partial.CompilationInfo; import com.itsaky.androidide.javac.services.partial.PartialReparser; import com.itsaky.androidide.javac.services.partial.PartialReparserImpl; import com.itsaky.androidide.lsp.java.models.CompilationRequest; +import com.itsaky.androidide.lsp.java.models.PartialReparseRequest; import com.itsaky.androidide.lsp.java.parser.ParseTask; import com.itsaky.androidide.lsp.java.parser.Parser; import com.itsaky.androidide.lsp.java.utils.Extractors; @@ -33,11 +35,10 @@ import com.itsaky.androidide.lsp.java.visitors.FindTypeDeclarations; import com.itsaky.androidide.models.Range; import com.itsaky.androidide.projects.ProjectManager; import com.itsaky.androidide.projects.api.ModuleProject; -import com.itsaky.androidide.projects.models.ActiveDocument; -import com.itsaky.androidide.utils.BootstrapClassesProvider; import com.itsaky.androidide.projects.util.Cache; import com.itsaky.androidide.projects.util.SourceClassTrie; import com.itsaky.androidide.projects.util.StringSearch; +import com.itsaky.androidide.utils.BootstrapClassesProvider; import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.StopWatch; import com.sun.source.tree.CompilationUnitTree; @@ -89,6 +90,7 @@ public class JavaCompilerService implements CompilerProvider { public ReusableCompiler compiler = new ReusableCompiler(); private CompileBatch cachedCompile; + private int changeDelta = 0; // The module project must not be null // It is marked as nullable just for some special cases like tests @@ -217,27 +219,6 @@ public class JavaCompilerService implements CompilerProvider { return candidates.toArray(new Path[0]); } - private boolean containsWord(Path file, String word) { - if (cacheContainsWord.needs(file, word)) { - cacheContainsWord.load(file, word, StringSearch.containsWord(file, word)); - } - return cacheContainsWord.get(file, word); - } - - private boolean containsImport(Path file, String className) { - String packageName = Extractors.packageName(className); - if (packageNameOrEmpty(file).equals(packageName)) { - return true; - } - String star = packageName + ".*"; - for (String i : readImports(file)) { - if (i.equals(className) || i.equals(star)) { - return true; - } - } - return false; - } - @Override public Path[] findMemberReferences(String className, String memberName) { List candidates = new ArrayList<>(); @@ -269,6 +250,52 @@ public class JavaCompilerService implements CompilerProvider { return compileBatch(request); } + public synchronized void close() { + if (cachedCompile != null) { + cachedCompile.close(); + cachedCompile.borrow.close(); + } + } + + public void destroy() { + synchronizedTask.post( + () -> { + close(); + cachedCompile = null; + cachedModified.clear(); + compiler = new ReusableCompiler(); + }); + } + + public SynchronizedTask getSynchronizedTask() { + return synchronizedTask; + } + + public void onDocumentChange(@NonNull DocumentChangeEvent event) { + this.changeDelta += event.getChangeDelta(); + } + + private boolean containsWord(Path file, String word) { + if (cacheContainsWord.needs(file, word)) { + cacheContainsWord.load(file, word, StringSearch.containsWord(file, word)); + } + return cacheContainsWord.get(file, word); + } + + private boolean containsImport(Path file, String className) { + String packageName = Extractors.packageName(className); + if (packageNameOrEmpty(file).equals(packageName)) { + return true; + } + String star = packageName + ".*"; + for (String i : readImports(file)) { + if (i.equals(className) || i.equals(star)) { + return true; + } + } + return false; + } + private SynchronizedTask compileBatch(CompilationRequest request) { synchronizedTask.post( () -> { @@ -321,7 +348,8 @@ public class JavaCompilerService implements CompilerProvider { private void tryReparse(@NonNull final CompilationRequest request) { // Satisfy lint - Objects.requireNonNull(request.partialRequest); + final PartialReparseRequest partialRequest = request.partialRequest; + Objects.requireNonNull(partialRequest); final StopWatch watch = new StopWatch("Method reparse"); final File file = new File(request.sources.iterator().next().toUri()); @@ -334,7 +362,7 @@ public class JavaCompilerService implements CompilerProvider { } final Pair currentMethod = - binarySearchCurrentMethod(positions, request.partialRequest.cursor); + binarySearchCurrentMethod(positions, partialRequest.cursor); if (currentMethod == null) { LOG.warn("Cannot perform reparse. Unable to find current method"); recompile(request); @@ -346,13 +374,6 @@ public class JavaCompilerService implements CompilerProvider { final MethodTree methodTree = (MethodTree) currentMethod.second.getLeaf(); LOG.debug("Trying to reparse method:", methodTree.getName()); - final int changeDelta; - final ActiveDocument document = ProjectManager.INSTANCE.getActiveDocument(file.toPath()); - if (document != null) { - changeDelta = document.getChangDelta(); - } else { - changeDelta = 0; - } final CompilationInfo info = new CompilationInfo( cachedCompile.task, cachedCompile.diagnosticListener, cachedCompile.roots.get(0)); @@ -360,26 +381,26 @@ public class JavaCompilerService implements CompilerProvider { final SourcePositions sourcePositions = Trees.instance(cachedCompile.task).getSourcePositions(); final int start = (int) sourcePositions.getStartPosition(info.cu, methodTree.getBody()); final int end = - (int) sourcePositions.getEndPosition(info.cu, methodTree.getBody()) + changeDelta; + (int) sourcePositions.getEndPosition(info.cu, methodTree.getBody()) + this.changeDelta; watch.lapFromLast("Found start and end positions of current method"); final PartialReparser reparser = new PartialReparserImpl(); - if (end < 0 || end > request.partialRequest.contents.length()) { + if (end < 0 || end > partialRequest.contents.length()) { LOG.warn( "Cannot reparse. Invalid change delta. end:", end, "changeDelta:", - changeDelta, + this.changeDelta, "content.length:", - request.partialRequest.contents.length()); + partialRequest.contents.length()); recompile(request); return; } - final String newBody = request.partialRequest.contents.substring(start, end); + final String newBody = partialRequest.contents.substring(start, end); final boolean reparsed = reparser.reparseMethod( - info, currentMethod.second, newBody, request.partialRequest.contents); + info, currentMethod.second, newBody, partialRequest.contents); if (!reparsed) { LOG.error("Failed to reparse"); recompile(request); @@ -390,10 +411,7 @@ public class JavaCompilerService implements CompilerProvider { LOG.info("Successfully reparsed method", methodTree.getName()); updateModificationCache(request); cachedCompile.updatePositions(info.cu, true); - - if (document != null) { - document.setChangDelta(0); - } + this.changeDelta = 0; } @Nullable @@ -457,25 +475,6 @@ public class JavaCompilerService implements CompilerProvider { return new CompileBatch(this, moreSources); } - public synchronized void close() { - if (cachedCompile != null && !cachedCompile.closed) { - cachedCompile.close(); - if (!cachedCompile.borrow.closed) { - cachedCompile.borrow.close(); - } - } - } - - public void destroy() { - synchronizedTask.post( - () -> { - close(); - cachedCompile = null; - cachedModified.clear(); - compiler = new ReusableCompiler(); - }); - } - private boolean containsType(Path file, String className) { if (cacheContainsType.needs(file, null)) { CompilationUnitTree root = parse(file).root; @@ -507,8 +506,4 @@ public class JavaCompilerService implements CompilerProvider { } return file; } - - public SynchronizedTask getSynchronizedTask() { - return synchronizedTask; - } } diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/SynchronizedTask.java b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/SynchronizedTask.java index aa8018fd7..ba6f07292 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/SynchronizedTask.java +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/compiler/SynchronizedTask.java @@ -36,8 +36,8 @@ package com.itsaky.androidide.lsp.java.compiler; import androidx.annotation.NonNull; -import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.lsp.java.CompilationCancellationException; +import com.itsaky.androidide.utils.ILogger; import java.util.concurrent.Semaphore; import java.util.function.Consumer; @@ -52,9 +52,16 @@ public class SynchronizedTask { private CompileTask task; public void run(@NonNull Consumer taskConsumer) { - semaphore.acquireUninterruptibly(); + try { + semaphore.acquire(); + } catch (InterruptedException e) { + throw new CompilationCancellationException(e); + } try { taskConsumer.accept(this.task); + } catch (Throwable err) { + LOG.error("An error occurred while working with compilation task", err); + throw err; } finally { semaphore.release(); } @@ -69,6 +76,9 @@ public class SynchronizedTask { try { return function.invoke(this.task); + } catch (Throwable err) { + LOG.error("An error occurred while working with compilation task", err); + throw err; } finally { semaphore.release(); } @@ -88,6 +98,9 @@ public class SynchronizedTask { this.task.close(); } run.run(); + } catch (Throwable err) { + LOG.error("An error occurred", err); + throw err; } finally { semaphore.release(); isCompiling = false; diff --git a/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt b/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt index 30fa0ee0e..2f3f00ffb 100644 --- a/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt +++ b/lsp/models/src/main/java/com/itsaky/androidide/lsp/models/Completions.kt @@ -205,8 +205,7 @@ open class CompletionItem( val start = getIdentifierStart(text.getLine(line), column) val shift = insertText.contains("$0") - - text.beginBatchEdit() + text.delete(line, start, line, column) if (text.contains("\n")) { @@ -234,7 +233,8 @@ open class CompletionItem( editor.text.delete(l, c, l, c + 2) } } - + + text.beginBatchEdit() if (additionalEditHandler != null) { additionalEditHandler!!.performEdits(editor, this) } else if (additionalTextEdits != null && additionalTextEdits!!.isNotEmpty()) { diff --git a/lsp/models/src/test/java/com/itsaky/androidide/lsp/models/ExampleUnitTest.kt b/lsp/models/src/test/java/com/itsaky/androidide/lsp/models/ExampleUnitTest.kt deleted file mode 100644 index 30d36dd6f..000000000 --- a/lsp/models/src/test/java/com/itsaky/androidide/lsp/models/ExampleUnitTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.androidide.lsp.models - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java index 1931a5ae2..8b822b346 100644 --- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java +++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/XMLLanguageServer.java @@ -127,9 +127,6 @@ public class XMLLanguageServer implements ILanguageServer { this.settings = settings; } - @Override - public void configurationChanged(Object newConfiguration) {} - @Override public void setupWithProject(@NonNull final Project project) {}