Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d0e14f2
Provide Groovy code completion
gselzer Oct 6, 2026
4e1b586
Build the analyzed source from a ScriptDocument
gselzer Oct 6, 2026
12446b7
Serve Groovy as an in-process language server
gselzer Oct 7, 2026
8b757ba
Move off scijava-code-api, now removed
gselzer Oct 7, 2026
2d98f0b
Type script bindings as last assigned
gselzer Oct 7, 2026
62b2a6a
Report problems in Groovy scripts
gselzer Oct 7, 2026
910870e
Ask the language servers through script sessions in tests
gselzer Oct 7, 2026
b6710f9
Follow code-lsp's packages by purpose
gselzer Oct 8, 2026
de1354b
Test signature order, not fit
gselzer Oct 8, 2026
ff2938a
Map diagnostics with ScriptDocument directly
gselzer Oct 9, 2026
b75a1d2
Serve plain Groovy: sessions declare the parameters
gselzer Oct 9, 2026
9df4493
Follow ScriptDialect and ScriptDocument into code-lsp's core package
gselzer Oct 9, 2026
fa20695
Gather completion into its package, and follow code-lsp
gselzer Oct 15, 2026
e63763c
Serve the script language by its name, as code-lsp now asks
gselzer Oct 16, 2026
495b9b9
Offer only a Groovy dialect: the server moves to code-lsp-groovy
gselzer Oct 16, 2026
818f326
Test only the dialect: the server's tests move to code-lsp-groovy
gselzer Oct 16, 2026
a06d9b6
Say what GroovyDialectTest's diagnostics test checks
gselzer Oct 16, 2026
d20b6f3
Test the dialect itself, and need the server only to test
gselzer Oct 16, 2026
d3b34d6
Follow scijava-code-lsp's ScriptDialect.header
gselzer Oct 16, 2026
2c4e64b
Follow scijava-code-lsp: sessions are made with the service
gselzer Oct 20, 2026
19bee5b
Follow scijava-code-lsp: problems arrive through the listener
gselzer Oct 21, 2026
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ in addition to being usable directly as a `javax.script.ScriptEngineFactory`,
it also provides some functionality on top, such as the ability to generate
lines of script code based on SciJava events.

## Code completion

The Script Editor completes code in Groovy scripts with
`scijava-code-lsp-groovy`'s language server, driven by Groovy's own
compiler; this plugin shows it the scripts as plain Groovy (`GroovyDialect`),
their `#@` parameters declared.

For a complete list of scripting languages available as part of the SciJava
platform, see the
[Scripting](https://github.com/scijava/scijava-common/wiki/Scripting) page on
Expand Down
19 changes: 19 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@
<license.excludes>**/script_templates/**</license.excludes>

<gpars.version>1.2.1</gpars.version>

<!-- NB: scijava-code-lsp is built against this scijava-common. -->
<scijava-common.version>2.101.0</scijava-common.version>
<scijava-code-lsp.version>0.1.0-SNAPSHOT</scijava-code-lsp.version>
<scijava-code-lsp-groovy.version>0.1.0-SNAPSHOT</scijava-code-lsp-groovy.version>
<!-- NB: As Fiji ships, for scijava-code-lsp (lsp4j). -->
<gson.version>2.14.0</gson.version>
</properties>

<dependencies>
Expand All @@ -103,6 +110,18 @@
<groupId>org.scijava</groupId>
<artifactId>scijava-common</artifactId>
</dependency>
<dependency>
<groupId>org.scijava</groupId>
<artifactId>scijava-code-lsp</artifactId>
<version>${scijava-code-lsp.version}</version>
</dependency>
<dependency>
<groupId>org.scijava</groupId>
<artifactId>scijava-code-lsp-groovy</artifactId>
<version>${scijava-code-lsp-groovy.version}</version>
<!-- NB: For integration tests; applications choose their servers. -->
<scope>test</scope>
</dependency>

<!-- Third-party dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* #%L
* JSR-223-compliant Groovy scripting language plugin.
* %%
* Copyright (C) 2014 - 2026 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/

package org.scijava.plugins.scripting.groovy.completion;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.scijava.code.lsp.ScriptDialect;
import org.scijava.plugin.Plugin;
import org.scijava.plugins.scripting.groovy.GroovyScriptLanguage;
import org.scijava.script.ScriptLanguage;

/**
* Describes how to turn Groovy scripts into plain Groovy code, for
* {@code groovy} language servers (e.g. scijava-code-lsp-groovy's).
*
* @author Gabriel Selzer
*/
@Plugin(type = ScriptDialect.class, name = "Groovy")
public class GroovyDialect implements ScriptDialect {

@Override
public boolean supports(final ScriptLanguage language) {
return language instanceof GroovyScriptLanguage;
}

@Override
public String languageId() {
return "groovy";
}

@Override
public Header header(final Map<String, Class<?>> parameters) {
// Declare the types of each parameter for type reasoning; Groovy gets
// the very Java objects, named in full, so nothing needs importing
final List<String> declarations = new ArrayList<>();
parameters.forEach((name, type) -> declarations.add(parameterType(
type) + " " + name));
// Combine into a Header
return new Header(String.join("; ", declarations), Collections
.emptySet());
}

/**
* @param type the Java type (of a SciJava Script parameter) declared in the
* script
* @return the Groovy type of the object given to the script: the Java type
* itself, by its full name (or {@code Object} for anonymous and
* local classes, which have no name to write)
*/
static String parameterType(final Class<?> type) {
final String name = type.getCanonicalName();
return name == null ? "Object" : name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* #%L
* JSR-223-compliant Groovy scripting language plugin.
* %%
* Copyright (C) 2014 - 2026 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/

package org.scijava.plugins.scripting.groovy.completion;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.lsp4j.Diagnostic;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.scijava.Context;
import org.scijava.code.lsp.LanguageServerService;
import org.scijava.code.lsp.ScriptDialect.Header;
import org.scijava.code.lsp.ScriptSession;
import org.scijava.script.ScriptLanguage;
import org.scijava.script.ScriptService;

/**
* Tests {@link GroovyDialect}
*
* @author Gabriel Selzer
*/
public class GroovyDialectTest {

private GroovyDialect dialect;
private Context context;
private ScriptLanguage groovy;

@Before
public void setUp() {
dialect = new GroovyDialect();
context = new Context(ScriptService.class, LanguageServerService.class);
groovy = context.service(ScriptService.class).getLanguageByName("Groovy");
}

@After
public void tearDown() {
context.dispose();
}

@Test
public void testScriptsLanguageAdherence() {
// Ensure the dialect supports the Groovy Script Language
assertTrue(dialect.supports(groovy));
}

@Test
public void testHeader() {
// SciJava Script parameters should be declared in Groovy code with
// their Java types, e.g. "java.util.List items"
final Map<String, Class<?>> parameters = new LinkedHashMap<>();
parameters.put("items", java.util.List.class);
parameters.put("count", int.class);
// Anonymous classes have no name to write.
parameters.put("thing", new Object() {}.getClass());
final Header header = dialect.header(parameters);
assertEquals("java.util.List items; int count; Object thing", header
.code());
// Nothing is imported.
assertTrue(header.hiddenNames().isEmpty());
}

@Test
public void testGroovyServerIntegration() throws Exception {
// With scijava-code-lsp-groovy's server: the parameters' declarations,
// and the blanked parameter lines, parse.
final CompletableFuture<List<Diagnostic>> heard =
new CompletableFuture<>();
try (ScriptSession session = new ScriptSession(context.service(
LanguageServerService.class), groovy))
{
session.setDiagnosticsListener(heard::complete);
session.update("#@ String name\n#@ int count\nprintln name * count\n");
final List<Diagnostic> problems = heard.get(20, TimeUnit.SECONDS);
assertTrue(problems.toString(), problems.isEmpty());
}
}
}
Loading