mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Fixed: Invalid change delta in Java compiler
This commit is contained in:
+1
@@ -240,6 +240,7 @@ class PartialReparserImpl : PartialReparser {
|
||||
l.useSource(prevLogged)
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
log.error("An error occurred while reparsing method", err)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ object ProjectManager : EventReceiver {
|
||||
if (subModule !is ModuleProject) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if (subModule.onDocumentChanged(event)) {
|
||||
break
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+60
-65
@@ -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<Path> 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<Range, TreePath> 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;
|
||||
}
|
||||
}
|
||||
|
||||
+15
-2
@@ -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<CompileTask> 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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user