Skip to content

Commit ef6358c

Browse files
gselzerclaude
andcommitted
Follow CompletionItems, and read name-first parameters
Function completions no longer carry a return type (LSP has no field for one): the detail, e.g. the full signature, is their short description. Parameter lists may be written name first, e.g. (a: float, b: int = 0), as ty writes them, as well as type first, e.g. (double a, double b). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent af6e480 commit ef6358c

4 files changed

Lines changed: 137 additions & 42 deletions

File tree

‎src/main/java/org/scijava/ui/swing/script/autocompletion/SciJavaCompletionProvider.java‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -442,11 +442,10 @@ private List<Completion> convert(final JTextComponent comp,
442442
text);
443443
final Completion c;
444444
if (params != null) {
445-
final String returnType = item.getLabelDetails() == null ? null : item
446-
.getLabelDetails().getDescription();
445+
// NB: No return type: LSP has no field for one. The detail (e.g.
446+
// the full signature) is the short description.
447447
final SciJavaFunctionCompletion fc = new SciJavaFunctionCompletion(this,
448-
name, returnType == null ? "" : returnType, edits);
449-
fc.setReturnValueDescription(returnType == null ? "" : returnType);
448+
name, null, edits);
450449
fc.setShortDescription(item.getDetail());
451450
fc.setDescription(description);
452451
fc.setParams(rstaParams(comp, name, params, overloads.get(name)));

‎src/main/java/org/scijava/ui/swing/script/autocompletion/SignatureLabel.java‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,21 +107,32 @@ else if (c == ',' && depth == 0) {
107107

108108
/**
109109
* A parameter (e.g. {@code java.lang.String name}) with simple type names
110-
* ({@code String name}).
110+
* ({@code String name}). One written name first (e.g.
111+
* {@code name: str}) is kept as it is.
111112
*/
112113
static String simple(final String parameter) {
114+
if (parameter.contains(":")) return parameter;
113115
final int space = parameter.lastIndexOf(' ');
114116
if (space < 0) return parameter;
115117
final String type = parameter.substring(0, space);
116118
return type.substring(type.lastIndexOf('.') + 1) + parameter.substring(
117119
space);
118120
}
119121

120-
/** Splits a parameter into its type (or null) and its name. */
122+
/**
123+
* Splits a parameter into its type (or null) and its name: written type
124+
* first (e.g. {@code double a}) or name first (e.g. {@code a: float}), and
125+
* without a default (e.g. {@code a: float = 1.0}).
126+
*/
121127
static String[] typeAndName(final String parameter) {
122-
final int space = parameter.lastIndexOf(' ');
123-
return space < 0 ? new String[] { null, parameter } : new String[] {
124-
parameter.substring(0, space).trim(), parameter.substring(space + 1) };
128+
final int eq = parameter.indexOf('=');
129+
final String p = (eq < 0 ? parameter : parameter.substring(0, eq)).trim();
130+
final int colon = p.indexOf(':');
131+
if (colon >= 0) return new String[] { p.substring(colon + 1).trim(), p
132+
.substring(0, colon).trim() };
133+
final int space = p.lastIndexOf(' ');
134+
return space < 0 ? new String[] { null, p } : new String[] { p.substring(0,
135+
space).trim(), p.substring(space + 1) };
125136
}
126137

127138
private static String label(final String signature,

‎src/test/java/org/scijava/ui/swing/script/autocompletion/SciJavaCompletionProviderTest.java‎

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.concurrent.atomic.AtomicInteger;
4444
import java.util.function.BiFunction;
4545
import java.util.function.Supplier;
46+
import java.util.stream.Collectors;
4647

4748
import javax.swing.JLabel;
4849
import javax.swing.JList;
@@ -52,18 +53,20 @@
5253
import org.eclipse.lsp4j.CompletionItemKind;
5354
import org.eclipse.lsp4j.CompletionList;
5455
import org.eclipse.lsp4j.CompletionParams;
56+
import org.eclipse.lsp4j.ParameterInformation;
5557
import org.eclipse.lsp4j.Range;
5658
import org.eclipse.lsp4j.SignatureHelp;
59+
import org.eclipse.lsp4j.SignatureInformation;
5760
import org.eclipse.lsp4j.TextEdit;
5861
import org.eclipse.lsp4j.jsonrpc.messages.Either;
5962
import org.fife.ui.autocomplete.Completion;
6063
import org.fife.ui.autocomplete.ParameterizedCompletion;
6164
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
6265
import org.junit.Test;
63-
import org.scijava.code.lsp.jvm.Callable;
6466
import org.scijava.code.lsp.Environment;
65-
import org.scijava.code.lsp.Positions;
6667
import org.scijava.code.lsp.jvm.AbstractInProcessLanguageServer;
68+
import org.scijava.code.lsp.CompletionItems;
69+
import org.scijava.code.lsp.Positions;
6770

6871
/**
6972
* Tests that {@link SciJavaCompletionProvider} faithfully bridges a language
@@ -140,17 +143,16 @@ public void testCallablesBecomeFunctionCompletionsWithChoices() {
140143

141144
// A callable completion taking one ij.gui.Roi parameter; and, inside its
142145
// argument list, an in-scope variable fitting it (and a method).
143-
final Callable addRoi = new Callable("addRoi", Collections.singletonList(
144-
new Callable.Param("roi", "ij.gui.Roi")), "void").kind(
145-
CompletionItemKind.Method);
146+
final CompletionItem addRoi = CompletionItems.methodSnippet("addRoi",
147+
Collections.singletonList("roi"), "(ij.gui.Roi roi)");
146148
final FakeServer server = new FakeServer();
147149
server.complete = (doc, caret) -> {
148150
if (!doc.lineTo(caret).endsWith("(")) {
149-
return FakeServer.items(doc, 0, caret, addRoi.toItem());
151+
return FakeServer.items(doc, 0, caret, addRoi);
150152
}
151153
final CompletionItem myRoi = new CompletionItem("myRoi");
152154
myRoi.setKind(CompletionItemKind.Variable);
153-
return FakeServer.items(doc, caret, caret, myRoi, addRoi.toItem());
155+
return FakeServer.items(doc, caret, caret, myRoi, addRoi);
154156
};
155157
final SciJavaCompletionProvider provider = new SciJavaCompletionProvider(
156158
FakeServers.session(server));
@@ -178,7 +180,7 @@ public void testCallablesRenderWithSignatures() {
178180

179181
final FakeServer server = new FakeServer();
180182
server.complete = (doc, caret) -> FakeServer.items(doc, 0, caret, method(
181-
completionText, "long[]").toItem());
183+
completionText, "long[]"));
182184
final SciJavaCompletionProvider provider = new SciJavaCompletionProvider(
183185
FakeServers.session(server));
184186

@@ -199,10 +201,10 @@ public void testParameterTooltipListsOtherOverloads() {
199201

200202
final FakeServer server = new FakeServer();
201203
server.complete = (doc, caret) -> FakeServer.items(doc, 0, caret, //
202-
method("img.getAt", "int[]").toItem(), //
203-
method("img.getAt", "net.imglib2.Localizable").toItem(), //
204-
method("img.getAt", "int[]").toItem(), // e.g. a bridge method
205-
method("img.getAtX", "long[]").toItem());
204+
method("img.getAt", "int[]"), //
205+
method("img.getAt", "net.imglib2.Localizable"), //
206+
method("img.getAt", "int[]"), // e.g. a bridge method
207+
method("img.getAtX", "long[]"));
206208
final SciJavaCompletionProvider provider = new SciJavaCompletionProvider(
207209
FakeServers.session(server));
208210
final List<Completion> completions = provider.getCompletionsImpl(area);
@@ -232,22 +234,20 @@ public void testParameterTooltipShowsSignatureHelp() {
232234
area.setText("a");
233235
area.setCaretPosition(1);
234236

235-
final Callable ints = method("a", "int", "int");
236-
final Callable strings = method("a", "java.lang.String");
237-
final Callable floats = method("a", "float", "float");
238-
final Callable longs = method("a", "long", "long");
239237
final String[] askedText = { null };
240238
final int[] askedCaret = { -1 };
241239
final FakeServer server = new FakeServer();
242-
server.complete = (doc, caret) -> FakeServer.items(doc, 0, caret, ints
243-
.toItem(), strings.toItem(), floats.toItem(), longs.toItem());
240+
server.complete = (doc, caret) -> FakeServer.items(doc, 0, caret, method(
241+
"a", "int", "int"), method("a", "java.lang.String"), method("a", "float",
242+
"float"), method("a", "long", "long"));
244243
server.help = (doc, caret) -> {
245244
askedText[0] = doc.text();
246245
askedCaret[0] = caret;
247246
// The server orders the signatures, best fits first: here, as for a
248247
// float typed as the first argument.
249-
return new SignatureHelp(Arrays.asList(floats.toSignature(), ints
250-
.toSignature(), longs.toSignature(), strings.toSignature()), 0, 1);
248+
return new SignatureHelp(Arrays.asList(signature("a", "float", "float"),
249+
signature("a", "int", "int"), signature("a", "long", "long"), signature(
250+
"a", "java.lang.String")), 0, 1);
251251
};
252252
final SciJavaCompletionProvider provider = new SciJavaCompletionProvider(
253253
FakeServers.session(server));
@@ -273,14 +273,12 @@ public void testParameterTooltipIgnoresOtherCalls() {
273273
final RSyntaxTextArea area = new RSyntaxTextArea();
274274
area.setText("a");
275275
area.setCaretPosition(1);
276-
final Callable ints = method("a", "int", "int");
277-
final Callable floats = method("a", "float", "float");
278276
final FakeServer server = new FakeServer();
279-
server.complete = (doc, caret) -> FakeServer.items(doc, 0, caret, ints
280-
.toItem(), floats.toItem());
277+
server.complete = (doc, caret) -> FakeServer.items(doc, 0, caret, method(
278+
"a", "int", "int"), method("a", "float", "float"));
281279
// The caret is in a call nested in a's arguments: b's signatures.
282280
server.help = (doc, caret) -> new SignatureHelp(Collections.singletonList(
283-
method("b", "double").toSignature()), 0, 0);
281+
signature("b", "double")), 0, 0);
284282
final SciJavaCompletionProvider provider = new SciJavaCompletionProvider(
285283
FakeServers.session(server));
286284
final ParameterizedCompletion intOverload =
@@ -300,9 +298,8 @@ public void testLazyDescriptions() {
300298
final AtomicInteger asked = new AtomicInteger();
301299
final FakeServer server = new FakeServer();
302300
server.complete = (doc, caret) -> FakeServer.items(doc, 3, caret, //
303-
server.described(new Callable("zeros", Collections.singletonList(
304-
new Callable.Param("shape", "int")), null).kind(
305-
CompletionItemKind.Method).toItem(), () -> {
301+
server.described(CompletionItems.methodSnippet("zeros",
302+
Collections.singletonList("shape"), "(int shape)"), () -> {
306303
asked.incrementAndGet();
307304
return "Return a new array of zeros.";
308305
}), //
@@ -413,15 +410,35 @@ public void testRequestCarriesScriptPath() {
413410

414411
// -- Helper methods --
415412

416-
private static Callable method(final String name,
413+
/** A method's completion, with parameters p0, p1, ... of the types. */
414+
private static CompletionItem method(final String name,
415+
final String... paramTypes)
416+
{
417+
final List<String> names = new ArrayList<>();
418+
for (int i = 0; i < paramTypes.length; i++) names.add("p" + i);
419+
return CompletionItems.methodSnippet(name, names, "(" + String.join(
420+
", ", params(paramTypes)) + ")");
421+
}
422+
423+
/** A method's signature, with parameters p0, p1, ... of the types. */
424+
private static SignatureInformation signature(final String name,
417425
final String... paramTypes)
418426
{
419-
final List<Callable.Param> params = new ArrayList<>();
427+
final List<String> params = params(paramTypes);
428+
final SignatureInformation s = new SignatureInformation(name + "(" + String
429+
.join(", ", params) + ") -> java.lang.Object");
430+
s.setParameters(params.stream().map(ParameterInformation::new).collect(
431+
Collectors.toList()));
432+
return s;
433+
}
434+
435+
/** E.g. {@code int p0}, {@code float p1}. */
436+
private static List<String> params(final String... paramTypes) {
437+
final List<String> out = new ArrayList<>();
420438
for (int i = 0; i < paramTypes.length; i++) {
421-
params.add(new Callable.Param("p" + i, paramTypes[i]));
439+
out.add(paramTypes[i] + " p" + i);
422440
}
423-
return new Callable(name, params, "java.lang.Object").kind(
424-
CompletionItemKind.Method);
441+
return out;
425442
}
426443

427444
/** "zeros", replacing what follows the dot, up to the end of the text. */
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* #%L
3+
* Script Editor and Interpreter for SciJava script languages.
4+
* %%
5+
* Copyright (C) 2009 - 2026 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.ui.swing.script.autocompletion;
31+
32+
import static org.junit.Assert.assertArrayEquals;
33+
import static org.junit.Assert.assertEquals;
34+
35+
import org.junit.Test;
36+
37+
/**
38+
* Tests {@link SignatureLabel}'s reading of parameters, whichever way a
39+
* language writes them.
40+
*
41+
* @author Gabriel Selzer
42+
*/
43+
public class SignatureLabelTest {
44+
45+
@Test
46+
public void testTypeAndName() {
47+
// Type first, e.g. Java's.
48+
assertArrayEquals(new String[] { "java.util.Map<K, V>", "m" },
49+
SignatureLabel.typeAndName("java.util.Map<K, V> m"));
50+
// Name first, e.g. Python's; with or without a default.
51+
assertArrayEquals(new String[] { "int | None", "indent" }, SignatureLabel
52+
.typeAndName("indent: int | None = None"));
53+
assertArrayEquals(new String[] { "float", "a" }, SignatureLabel
54+
.typeAndName("a: float"));
55+
// Untyped.
56+
assertArrayEquals(new String[] { null, "kw" }, SignatureLabel.typeAndName(
57+
"kw"));
58+
assertArrayEquals(new String[] { null, "kw" }, SignatureLabel.typeAndName(
59+
"kw=1"));
60+
}
61+
62+
@Test
63+
public void testSimple() {
64+
assertEquals("String s", SignatureLabel.simple("java.lang.String s"));
65+
// Name first: as it is.
66+
assertEquals("p: os.PathLike", SignatureLabel.simple("p: os.PathLike"));
67+
}
68+
}

0 commit comments

Comments
 (0)