Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ private static record LabeledLocations(String label,
return null;
}

final int offset = region.getOffset();
// Normalize cache key to the start of the word to avoid cache misses when the
// mouse moves within the same symbol.
final int cacheKeyOffset = findWord(document, region).getOffset();

final CompletableFuture<List<LSBasedHyperlink>> request = CACHE.computeIfAbsent(document, offset, () -> {
final CompletableFuture<List<LSBasedHyperlink>> request = CACHE.computeIfAbsent(document, cacheKeyOffset, () -> {
final var definitions = LanguageServers.forDocument(document)
.withCapability(ServerCapabilities::getDefinitionProvider)
.collectAll(ls -> ls.getTextDocumentService().definition(LSPEclipseUtils.toDefinitionParams(params))
Expand All @@ -98,7 +100,7 @@ private static record LabeledLocations(String label,
.thenApply(l -> new LabeledLocations(Messages.implementationHyperlinkLabel, l))
.exceptionally(err -> new LabeledLocations(Messages.implementationHyperlinkLabel, null)));

CompletableFuture<List<LabeledLocations>> combined = LanguageServers.addAll(
final CompletableFuture<List<LabeledLocations>> combined = LanguageServers.addAll(
LanguageServers.addAll(LanguageServers.addAll(definitions, declarations), typeDefinitions),
implementations);
return combined.thenApply(locations -> toHyperlinks(document, region, locations));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
import static org.eclipse.lsp4e.internal.NullSafetyHelper.lateNonNull;

import java.net.URI;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

import org.eclipse.core.runtime.ICoreRunnable;
Expand Down Expand Up @@ -51,6 +53,7 @@
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.LanguageServerPlugin;
import org.eclipse.lsp4e.LanguageServers;
import org.eclipse.lsp4e.internal.DocumentOffsetAsyncCache;
import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightKind;
import org.eclipse.lsp4j.DocumentHighlightParams;
Expand All @@ -77,6 +80,16 @@ public class HighlightReconcilingStrategy
private @Nullable IDocument document;
private @Nullable Job highlightJob;

// Debounce to avoid flooding requests while the user moves the caret/mouse.
private static final int HIGHLIGHT_DEBOUNCE_MS = 75;

// Short-lived cache for highlights per document+normalized offset.
private static final DocumentOffsetAsyncCache<List<? extends DocumentHighlight>> HIGHLIGHT_CACHE =
Comment thread
sebthom marked this conversation as resolved.
new DocumentOffsetAsyncCache<>(Duration.ofSeconds(10));

// Track the last normalized cache key to avoid canceling identical in-flight work.
private int lastCacheKeyOffset = -1;

/**
* Holds the current occurrence annotations.
*/
Expand Down Expand Up @@ -119,7 +132,8 @@ private void updateHighlights(ISelection selection) {
}
highlightJob = Job.createSystem("LSP4E Highlight", //$NON-NLS-1$
(ICoreRunnable)(monitor -> collectHighlights(textSelection.getOffset(), monitor)));
highlightJob.schedule();
// Debounce scheduling slightly to coalesce rapid selection changes
highlightJob.schedule(HIGHLIGHT_DEBOUNCE_MS);
}
}

Expand Down Expand Up @@ -188,9 +202,20 @@ private void collectHighlights(int caretOffset, @Nullable IProgressMonitor monit
if (sourceViewer == null || document == null || !enabled || monitor != null && monitor.isCanceled()) {
return;
}
cancel();

// Normalize the cache key to the start of the word/symbol.
final int cacheKeyOffset = normalizedOffset(document, caretOffset);

// Only cancel previous requests if the target symbol actually changed.
if (cacheKeyOffset != lastCacheKeyOffset) {
cancel();
lastCacheKeyOffset = cacheKeyOffset;
}

Position position;
try {
// Send the original caret offset to the LS to preserve behavior
// expected by tests and servers that distinguish positions within a word.
position = LSPEclipseUtils.toPosition(caretOffset, document);
} catch (BadLocationException e) {
LanguageServerPlugin.logError(e);
Expand All @@ -202,14 +227,22 @@ private void collectHighlights(int caretOffset, @Nullable IProgressMonitor monit
}
final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(uri);
final var params = new DocumentHighlightParams(identifier, position);
requests = LanguageServers.forDocument(document)
.withCapability(ServerCapabilities::getDocumentHighlightProvider)
.computeAll(languageServer -> languageServer.getTextDocumentService().documentHighlight(params));
requests.forEach(request -> request.thenAcceptAsync(highlights -> {

// Use cache to deduplicate requests to the same symbol for a short period.
final CompletableFuture<List<? extends DocumentHighlight>> request = HIGHLIGHT_CACHE.computeIfAbsent(document,
cacheKeyOffset, () -> {
final var reqs = requests = LanguageServers.forDocument(document)
.withCapability(ServerCapabilities::getDocumentHighlightProvider)
.computeAll(ls -> ls.getTextDocumentService().documentHighlight(params));
return CompletableFuture.supplyAsync(() -> reqs.stream().map(CompletableFuture::join) //
.filter(Objects::nonNull).flatMap(List::stream).toList());
});

request.thenAcceptAsync(highlights -> {
if (monitor == null || !monitor.isCanceled()) {
updateAnnotations(highlights, sourceViewer.getAnnotationModel());
}
}));
});
}

/**
Expand Down Expand Up @@ -273,6 +306,28 @@ private Object getLockObject(IAnnotationModel annotationModel) {
return annotationModel;
}

/**
* Compute a stable cache key for the symbol at the given caret offset by
* normalizing to the start of the Unicode identifier under the caret.
*/
private static int normalizedOffset(final IDocument document, final int offset) {
try {
// Walk left to the first non-identifier part, then move right one.
int pos = Math.max(0, offset - 1);
final int docLen = document.getLength();
while (pos >= 0 && pos < docLen) {
if (!Character.isUnicodeIdentifierPart(document.getChar(pos))) {
break;
}
pos--;
}
return Math.min(docLen, pos + 1);
} catch (final BadLocationException ex) {
LanguageServerPlugin.logError(ex.getMessage(), ex);
return offset;
Comment thread
sebthom marked this conversation as resolved.
}
}

void removeOccurrenceAnnotations() {
final var sourceViewer = this.sourceViewer;
if(sourceViewer == null)
Expand Down
Loading