diff --git a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
index 7313dba46..b671b9949 100644
--- a/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tests for language server bundle (Incubation)
Bundle-SymbolicName: org.eclipse.lsp4e.test;singleton:=true
-Bundle-Version: 0.16.12.qualifier
+Bundle-Version: 0.16.13.qualifier
Fragment-Host: org.eclipse.lsp4e
Bundle-Vendor: Eclipse LSP4E
Bundle-RequiredExecutionEnvironment: JavaSE-21
diff --git a/org.eclipse.lsp4e.test/pom.xml b/org.eclipse.lsp4e.test/pom.xml
index d8254b27c..96bc55b37 100644
--- a/org.eclipse.lsp4e.test/pom.xml
+++ b/org.eclipse.lsp4e.test/pom.xml
@@ -8,7 +8,7 @@
org.eclipse.lsp4e.test
eclipse-test-plugin
- 0.16.12-SNAPSHOT
+ 0.16.13-SNAPSHOT
diff --git a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/operations/inlayhint/LSPLineContentCodeMiningTest.java b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/operations/inlayhint/LSPLineContentCodeMiningTest.java
index 591035d1e..8a6099b0f 100644
--- a/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/operations/inlayhint/LSPLineContentCodeMiningTest.java
+++ b/org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/operations/inlayhint/LSPLineContentCodeMiningTest.java
@@ -34,6 +34,9 @@
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintLabelPart;
import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Display;
@@ -76,6 +79,29 @@ public void singleLabelPartCommand(MockLanguageServerFactory factory) throws Exc
assertEquals(MockLanguageServer.SUPPORTED_COMMAND_ID, executedCommand.getCommand());
assertEquals(command.getArguments(), executedCommand.getArguments());
}
+
+ @Test
+ void inlayHintWithTextEdit() throws Exception {
+ IFile file = TestUtils.createUniqueTestFile(project, "lspt", "x = [1, 2]");
+ ITextViewer textViewer = TestUtils.openTextViewer(file);
+ IDocument document = textViewer.getDocument();
+
+ final var provider = new InlayHintProvider();
+
+ // Create inlayhint with text edit
+ InlayHint inlayHint = new InlayHint(new Position(0, 0), Either.forLeft(": list[int]"));
+ inlayHint.setTextEdits(List.of(new TextEdit(new Range(new Position(0, 1), new Position(0, 1)), ": list[int]")));
+
+ // Simulate user clicking on inlayhint
+ LanguageServerWrapper wrapper = LanguageServiceAccessor.getLSWrapper(project,
+ LanguageServersRegistry.getInstance().getDefinition(MOCK_SERVER_ID));
+ final var sut = new LSPLineContentCodeMining(inlayHint, document, wrapper, provider);
+ MouseEvent mouseEvent = createMouseEvent();
+ sut.getAction().accept(mouseEvent);
+
+ // Text edit should be applied.
+ assertEquals("x: list[int] = [1, 2]", document.get());
+ }
private static InlayHintLabelPart createInlayLabelPart(String text, String commandID) {
final var labelPart = new InlayHintLabelPart(text);
diff --git a/org.eclipse.lsp4e/META-INF/MANIFEST.MF b/org.eclipse.lsp4e/META-INF/MANIFEST.MF
index 1168ef015..ebf5953a4 100644
--- a/org.eclipse.lsp4e/META-INF/MANIFEST.MF
+++ b/org.eclipse.lsp4e/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Language Server Protocol client for Eclipse IDE (Incubation)
Bundle-SymbolicName: org.eclipse.lsp4e;singleton:=true
-Bundle-Version: 0.19.13.qualifier
+Bundle-Version: 0.19.14.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-21
Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0",
org.eclipse.equinox.common;bundle-version="3.8.0",
diff --git a/org.eclipse.lsp4e/pom.xml b/org.eclipse.lsp4e/pom.xml
index 00bc8584b..a3acac8b6 100644
--- a/org.eclipse.lsp4e/pom.xml
+++ b/org.eclipse.lsp4e/pom.xml
@@ -10,7 +10,7 @@
org.eclipse.lsp4e
eclipse-plugin
- 0.19.13-SNAPSHOT
+ 0.19.14-SNAPSHOT
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/inlayhint/LSPLineContentCodeMining.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/inlayhint/LSPLineContentCodeMining.java
index f67fee6d5..2003103a1 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/inlayhint/LSPLineContentCodeMining.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/inlayhint/LSPLineContentCodeMining.java
@@ -26,6 +26,8 @@
import org.eclipse.lsp4e.LanguageServerWrapper;
import org.eclipse.lsp4e.LanguageServers;
import org.eclipse.lsp4e.command.CommandExecutor;
+import org.eclipse.lsp4e.internal.NullSafetyHelper;
+import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.InlayHint;
@@ -111,7 +113,7 @@ private static boolean canResolveInlayHint(@Nullable ServerCapabilities capabili
Either inlayProvider = capabilities.getInlayHintProvider();
if (inlayProvider != null && inlayProvider.isRight()) {
InlayHintRegistrationOptions options = inlayProvider.getRight();
- return options.getResolveProvider() != null && options.getResolveProvider().booleanValue();
+ return Boolean.TRUE.equals(options.getResolveProvider());
}
return false;
}
@@ -134,35 +136,67 @@ private static org.eclipse.jface.text.Position toPosition(Position position, IDo
@Override
public final @Nullable Consumer getAction() {
- return inlayHint.getLabel().map(l -> null, this::labelPartAction);
+ List parts = inlayHint.getLabel().map(l -> null, r -> r);
+ boolean hasPartWithCommand = parts != null
+ && parts.stream().map(InlayHintLabelPart::getCommand).anyMatch(Objects::nonNull);
+ boolean hasTextEdits = inlayHint.getTextEdits() != null && !inlayHint.getTextEdits().isEmpty();
+
+ if (!(hasPartWithCommand || hasTextEdits)) {
+ // Neither command nor text edits -> no action
+ return null;
+ }
+
+ return evt -> {
+ /*
+ * LSP does not define if commands or text edits take precedence. So for the
+ * sake of backwards compatibility, we first check for commands and then for
+ * text edits
+ */
+ if (hasPartWithCommand && handlePartWithCommand(evt, NullSafetyHelper.castNonNull(parts))) {
+ return;
+ }
+ if (hasTextEdits) {
+ // Apply text edits
+ try {
+ LSPEclipseUtils.applyEdits(document, inlayHint.getTextEdits());
+ // The text of the inlay hint is integrated into the document.
+ // To avoid a brief moment where both the inserted text and the inlay hint are
+ // rendered, we set an empty label for the hint.
+ setLabel(""); //$NON-NLS-1$
+ } catch (BadLocationException e) {
+ // Location to modify document was no longer valid -> Ignore
+ }
+ }
+ };
}
- private @Nullable Consumer labelPartAction(List labelParts) {
- String title = getLabel();
- if (title != null && !title.isEmpty()
- && labelParts.stream().map(InlayHintLabelPart::getCommand).anyMatch(Objects::nonNull)) {
- return me -> findLabelPart(me, labelParts) //
- .map(InlayHintLabelPart::getCommand) //
- .filter(Objects::nonNull) //
- .ifPresent(command -> {
- ServerCapabilities serverCapabilities = wrapper.getServerCapabilities();
- ExecuteCommandOptions provider = serverCapabilities == null ? null
- : serverCapabilities.getExecuteCommandProvider();
- String commandId = command.getCommand();
- if (provider != null && provider.getCommands().contains(commandId)) {
- LanguageServers.forDocument(document).computeAll((w, ls) -> {
- if (w == wrapper) {
- return ls.getWorkspaceService().executeCommand(
- new ExecuteCommandParams(commandId, command.getArguments()));
- }
- return CompletableFuture.completedFuture(null);
- });
- } else {
- CommandExecutor.executeCommandClientSide(command, document);
- }
- });
+ /**
+ * Returns true if the mouse event hit a label part with a command.
+ */
+ private boolean handlePartWithCommand(MouseEvent evt, List parts) {
+ // Figure out if the user clicked on a specific label part which has a command
+ Optional clickedPart = findLabelPart(evt, NullSafetyHelper.castNonNull(parts));
+ Optional commandOpt = clickedPart.map(InlayHintLabelPart::getCommand);
+ if (commandOpt.isPresent()) {
+ Command command = commandOpt.get();
+ ServerCapabilities serverCapabilities = wrapper.getServerCapabilities();
+ ExecuteCommandOptions provider = serverCapabilities == null ? null
+ : serverCapabilities.getExecuteCommandProvider();
+ String commandId = command.getCommand();
+ if (provider != null && provider.getCommands().contains(commandId)) {
+ LanguageServers.forDocument(document).computeAll((w, ls) -> {
+ if (w == wrapper) {
+ return ls.getWorkspaceService()
+ .executeCommand(new ExecuteCommandParams(commandId, command.getArguments()));
+ }
+ return CompletableFuture.completedFuture(null);
+ });
+ } else {
+ CommandExecutor.executeCommandClientSide(command, document);
+ }
+ return true;
}
- return null;
+ return false;
}
private Optional findLabelPart(MouseEvent me, List labelParts) {