Skip to content

Commit e4debae

Browse files
sebthommickaelistria
authored andcommitted
feat: support dynamic registration for textDocument/completion
Apply dynamically registered CompletionOptions (including triggerCharacters) to server capabilities so content assist auto-activation and proposals reflect the server's runtime settings
1 parent 113e749 commit e4debae

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vegard IT GmbH and others.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Sebastian Thomschke (Vegard IT GmbH) - initial implementation
11+
*******************************************************************************/
12+
package org.eclipse.lsp4e.test.completion;
13+
14+
import static org.eclipse.lsp4e.test.utils.TestUtils.waitForAndAssertCondition;
15+
import static org.junit.Assert.*;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.eclipse.core.resources.IFile;
22+
import org.eclipse.jface.text.ITextViewer;
23+
import org.eclipse.jface.text.contentassist.ICompletionProposal;
24+
import org.eclipse.lsp4e.operations.completion.LSContentAssistProcessor;
25+
import org.eclipse.lsp4e.test.utils.AbstractTestWithProject;
26+
import org.eclipse.lsp4e.test.utils.TestUtils;
27+
import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
28+
import org.eclipse.lsp4j.CompletionItem;
29+
import org.eclipse.lsp4j.CompletionItemKind;
30+
import org.eclipse.lsp4j.CompletionList;
31+
import org.eclipse.lsp4j.CompletionOptions;
32+
import org.eclipse.lsp4j.Position;
33+
import org.eclipse.lsp4j.Range;
34+
import org.eclipse.lsp4j.Registration;
35+
import org.eclipse.lsp4j.RegistrationParams;
36+
import org.eclipse.lsp4j.TextEdit;
37+
import org.eclipse.lsp4j.jsonrpc.messages.Either;
38+
import org.eclipse.lsp4j.services.LanguageClient;
39+
import org.junit.Before;
40+
import org.junit.Test;
41+
42+
import com.google.gson.Gson;
43+
44+
/**
45+
* Verifies that dynamic registration of completion updates LSP4E server
46+
* capabilities and enables content assist proposals.
47+
*/
48+
public class DynamicCompletionRegistrationTest extends AbstractTestWithProject {
49+
50+
private LSContentAssistProcessor contentAssistProcessor;
51+
52+
@Before
53+
public void setup() {
54+
contentAssistProcessor = new LSContentAssistProcessor(true, false);
55+
}
56+
57+
@Test
58+
public void testDynamicCompletionRegistrationProvidesProposalsAndTriggers() throws Exception {
59+
// Prepare a file and open a viewer
60+
IFile file = TestUtils.createUniqueTestFile(project, "");
61+
ITextViewer viewer = TestUtils.openTextViewer(file);
62+
63+
// Ensure the mock LS is up
64+
waitForAndAssertCondition(5_000, () -> !MockLanguageServer.INSTANCE.getRemoteProxies().isEmpty());
65+
LanguageClient client = getMockClient();
66+
assertNotNull(client);
67+
68+
// Provide a simple completion item in the mock LS
69+
var items = new ArrayList<CompletionItem>();
70+
var item = new CompletionItem();
71+
item.setLabel("Alpha");
72+
item.setKind(CompletionItemKind.Text);
73+
item.setTextEdit(Either.forLeft(new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), "Alpha")));
74+
items.add(item);
75+
MockLanguageServer.INSTANCE.setCompletionList(new CompletionList(false, items));
76+
77+
// Dynamically register completion with trigger characters (server-like behavior)
78+
var registration = new Registration();
79+
registration.setId("test-completion-reg");
80+
registration.setMethod("textDocument/completion");
81+
var opts = new CompletionOptions();
82+
opts.setTriggerCharacters(List.of(".", "/", "#"));
83+
registration.setRegisterOptions(new Gson().toJsonTree(opts));
84+
client.registerCapability(new RegistrationParams(List.of(registration))).get(2, TimeUnit.SECONDS);
85+
86+
// Compute proposals (manual invocation). Should return the mock item.
87+
ICompletionProposal[] proposals = contentAssistProcessor.computeCompletionProposals(viewer, 0);
88+
assertEquals(1, proposals.length);
89+
assertEquals("Alpha", proposals[0].getDisplayString());
90+
91+
// Ask processor for auto-activation triggers and assert they include the registered ones
92+
char[] triggers = contentAssistProcessor.getCompletionProposalAutoActivationCharacters();
93+
String trig = new String(triggers != null ? triggers : new char[0]);
94+
assertTrue(trig.indexOf('.') >= 0);
95+
assertTrue(trig.indexOf('/') >= 0);
96+
assertTrue(trig.indexOf('#') >= 0);
97+
}
98+
99+
private LanguageClient getMockClient() {
100+
var proxies = MockLanguageServer.INSTANCE.getRemoteProxies();
101+
assertEquals(1, proxies.size());
102+
return proxies.get(0);
103+
}
104+
}

org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import org.eclipse.lsp4j.ClientCapabilities;
8484
import org.eclipse.lsp4j.ClientInfo;
8585
import org.eclipse.lsp4j.CodeActionOptions;
86+
import org.eclipse.lsp4j.CompletionOptions;
8687
import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams;
8788
import org.eclipse.lsp4j.DocumentFormattingOptions;
8889
import org.eclipse.lsp4j.DocumentOnTypeFormattingOptions;
@@ -1107,6 +1108,18 @@ public void registerCapability(RegistrationParams params) {
11071108
serverCapabilities.setCodeActionProvider(Boolean.TRUE);
11081109
addRegistration(reg, () -> serverCapabilities.setCodeActionProvider(beforeRegistration));
11091110
break;
1111+
case "textDocument/completion": { //$NON-NLS-1$
1112+
CompletionOptions previous = serverCapabilities.getCompletionProvider();
1113+
try {
1114+
final var completionOpts = new Gson().fromJson((JsonObject) reg.getRegisterOptions(),
1115+
CompletionOptions.class);
1116+
serverCapabilities.setCompletionProvider(completionOpts);
1117+
addRegistration(reg, () -> serverCapabilities.setCompletionProvider(previous));
1118+
} catch (final Exception ex) {
1119+
LanguageServerPlugin.logError(ex);
1120+
}
1121+
break;
1122+
}
11101123
case "workspace/symbol": //$NON-NLS-1$
11111124
final Either<Boolean, WorkspaceSymbolOptions> workspaceSymbolBeforeRegistration = serverCapabilities.getWorkspaceSymbolProvider();
11121125
serverCapabilities.setWorkspaceSymbolProvider(Boolean.TRUE);

org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public static TextDocumentClientCapabilities getTextDocumentClientCapabilities()
9696
"detail", //$NON-NLS-1$
9797
"additionalTextEdits"))); //$NON-NLS-1$
9898
final var completionCapabilities = new CompletionCapabilities(completionItemCapabilities);
99+
completionCapabilities.setDynamicRegistration(Boolean.TRUE);
99100
completionCapabilities.setContextSupport(true);
100101
completionCapabilities.setCompletionList(new CompletionListCapabilities(List.of( //
101102
"commitCharacters", //$NON-NLS-1$

0 commit comments

Comments
 (0)