Skip to content

Commit ed4301f

Browse files
authored
Resolve plugin definitions from registry (#141)
1 parent f15d57c commit ed4301f

21 files changed

+735
-48
lines changed

build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ test {
3030

3131
configurations {
3232
nextflowRuntime
33-
specImplementation.extendsFrom(nextflowRuntime)
3433
}
3534

3635
dependencies {
@@ -57,6 +56,7 @@ dependencies {
5756
testImplementation ('org.objenesis:objenesis:3.4')
5857
testImplementation ('net.bytebuddy:byte-buddy:1.14.17')
5958
testImplementation ('org.spockframework:spock-core:2.3-groovy-4.0') { exclude group: 'org.apache.groovy' }
59+
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
6060
}
6161

6262
sourceSets {
@@ -67,6 +67,10 @@ sourceSets {
6767
}
6868
}
6969

70+
configurations {
71+
specImplementation.extendsFrom(nextflowRuntime)
72+
}
73+
7074
task buildSpec(type: JavaExec) {
7175
description = 'Build spec file of core definitions'
7276
group = 'build'

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

src/main/java/nextflow/lsp/NextflowLanguageServer.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ public static void main(String[] args) {
121121
private LanguageClient client = null;
122122

123123
private Map<String, String> workspaceRoots = new HashMap<>();
124-
private Map<String, LanguageService> scriptServices = new HashMap<>();
125-
private Map<String, LanguageService> configServices = new HashMap<>();
124+
private Map<String, ConfigService> configServices = new HashMap<>();
125+
private Map<String, ScriptService> scriptServices = new HashMap<>();
126126

127127
private LanguageServerConfiguration configuration = LanguageServerConfiguration.defaults();
128128

@@ -461,6 +461,7 @@ public void didChangeConfiguration(DidChangeConfigurationParams params) {
461461
withDefault(JsonUtils.getBoolean(settings, "nextflow.formatting.harshilAlignment"), configuration.harshilAlignment()),
462462
withDefault(JsonUtils.getBoolean(settings, "nextflow.formatting.maheshForm"), configuration.maheshForm()),
463463
withDefault(JsonUtils.getInteger(settings, "nextflow.completion.maxItems"), configuration.maxCompletionItems()),
464+
withDefault(JsonUtils.getString(settings, "nextflow.pluginRegistryUrl"), configuration.pluginRegistryUrl()),
464465
withDefault(JsonUtils.getBoolean(settings, "nextflow.formatting.sortDeclarations"), configuration.sortDeclarations()),
465466
withDefault(JsonUtils.getBoolean(settings, "nextflow.typeChecking"), configuration.typeChecking())
466467
);
@@ -483,6 +484,7 @@ private <T> T withDefault(T value, T defaultValue) {
483484
private boolean shouldInitialize(LanguageServerConfiguration previous, LanguageServerConfiguration current) {
484485
return previous.errorReportingMode() != current.errorReportingMode()
485486
|| !DefaultGroovyMethods.equals(previous.excludePatterns(), current.excludePatterns())
487+
|| previous.pluginRegistryUrl() != current.pluginRegistryUrl()
486488
|| previous.typeChecking() != current.typeChecking();
487489
}
488490

@@ -500,8 +502,8 @@ private void initializeWorkspaces() {
500502
progress.update(progressMessage, count * 100 / total);
501503
count++;
502504

503-
scriptServices.get(name).initialize(configuration);
504505
configServices.get(name).initialize(configuration);
506+
scriptServices.get(name).initialize(configuration, configServices.get(name).getPluginSpecCache());
505507
}
506508

507509
progress.end();
@@ -519,16 +521,16 @@ public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) {
519521
var name = workspaceFolder.getName();
520522
log.debug("workspace/didChangeWorkspaceFolders remove " + name);
521523
workspaceRoots.remove(name);
522-
scriptServices.remove(name).clearDiagnostics();
523524
configServices.remove(name).clearDiagnostics();
525+
scriptServices.remove(name).clearDiagnostics();
524526
}
525527
for( var workspaceFolder : event.getAdded() ) {
526528
var name = workspaceFolder.getName();
527529
var uri = workspaceFolder.getUri();
528530
log.debug("workspace/didChangeWorkspaceFolders add " + name + " " + uri);
529531
addWorkspaceFolder(name, uri);
530-
scriptServices.get(name).initialize(configuration);
531532
configServices.get(name).initialize(configuration);
533+
scriptServices.get(name).initialize(configuration, configServices.get(name).getPluginSpecCache());
532534
}
533535
}
534536

@@ -620,13 +622,13 @@ public CompletableFuture<Either<List<? extends SymbolInformation>, List<? extend
620622
private void addWorkspaceFolder(String name, String uri) {
621623
workspaceRoots.put(name, uri);
622624

623-
var scriptService = new ScriptService(uri);
624-
scriptService.connect(client);
625-
scriptServices.put(name, scriptService);
626-
627625
var configService = new ConfigService(uri);
628626
configService.connect(client);
629627
configServices.put(name, configService);
628+
629+
var scriptService = new ScriptService(uri);
630+
scriptService.connect(client);
631+
scriptServices.put(name, scriptService);
630632
}
631633

632634
private String relativePath(String uri) {
@@ -654,12 +656,12 @@ private LanguageService getLanguageService(String uri) {
654656
return service;
655657
}
656658

657-
private LanguageService getLanguageService0(String uri, Map<String, LanguageService> services) {
659+
private LanguageService getLanguageService0(String uri, Map<String, ? extends LanguageService> services) {
658660
var service = workspaceRoots.entrySet().stream()
659661
.filter((entry) -> entry.getValue() != null && uri.startsWith(entry.getValue()))
660662
.findFirst()
661-
.map((entry) -> services.get(entry.getKey()))
662-
.orElse(services.get(DEFAULT_WORKSPACE_FOLDER_NAME));
663+
.map((entry) -> (LanguageService) services.get(entry.getKey()))
664+
.orElse((LanguageService) services.get(DEFAULT_WORKSPACE_FOLDER_NAME));
663665
if( service == null || !service.matchesFile(uri) )
664666
return null;
665667
return service;

src/main/java/nextflow/lsp/services/LanguageServerConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public record LanguageServerConfiguration(
2727
boolean harshilAlignment,
2828
boolean maheshForm,
2929
int maxCompletionItems,
30+
String pluginRegistryUrl,
3031
boolean sortDeclarations,
3132
boolean typeChecking
3233
) {
@@ -41,6 +42,7 @@ public static LanguageServerConfiguration defaults() {
4142
false,
4243
false,
4344
100,
45+
"https://registry.nextflow.io/api/",
4446
false,
4547
false
4648
);

src/main/java/nextflow/lsp/services/config/ConfigAstCache.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Set;
2222

2323
import groovy.lang.GroovyClassLoader;
24+
import nextflow.config.ast.ConfigNode;
2425
import nextflow.config.control.ConfigResolveVisitor;
2526
import nextflow.config.control.ResolveIncludeVisitor;
2627
import nextflow.config.parser.ConfigParserPluginFactory;
@@ -30,6 +31,7 @@
3031
import nextflow.lsp.compiler.LanguageServerErrorCollector;
3132
import nextflow.lsp.file.FileCache;
3233
import nextflow.lsp.services.LanguageServerConfiguration;
34+
import nextflow.lsp.spec.PluginSpecCache;
3335
import nextflow.script.control.PhaseAware;
3436
import nextflow.script.control.Phases;
3537
import nextflow.script.types.Types;
@@ -46,7 +48,7 @@ public class ConfigAstCache extends ASTNodeCache {
4648

4749
private LanguageServerConfiguration configuration;
4850

49-
private Map<String,SpecNode> spec = ConfigSpecFactory.defaultScopes();
51+
private PluginSpecCache pluginSpecCache;
5052

5153
public ConfigAstCache() {
5254
super(createCompiler());
@@ -65,8 +67,9 @@ private static CompilerConfiguration createConfiguration() {
6567
return config;
6668
}
6769

68-
public void initialize(LanguageServerConfiguration configuration) {
70+
public void initialize(LanguageServerConfiguration configuration, PluginSpecCache pluginSpecCache) {
6971
this.configuration = configuration;
72+
this.pluginSpecCache = pluginSpecCache;
7073
}
7174

7275
@Override
@@ -92,7 +95,7 @@ protected Set<URI> analyze(Set<URI> uris, FileCache fileCache) {
9295
continue;
9396
// phase 3: name checking
9497
new ConfigResolveVisitor(sourceUnit, compiler().compilationUnit(), Types.DEFAULT_CONFIG_IMPORTS).visit();
95-
new ConfigSpecVisitor(sourceUnit, spec, configuration.typeChecking()).visit();
98+
new ConfigSpecVisitor(sourceUnit, pluginSpecCache, configuration.typeChecking()).visit();
9699
if( sourceUnit.getErrorCollector().hasErrors() )
97100
continue;
98101
// phase 4: type checking
@@ -121,4 +124,8 @@ public boolean hasSyntaxErrors(URI uri) {
121124
.isPresent();
122125
}
123126

127+
public ConfigNode getConfigNode(URI uri) {
128+
return (ConfigNode) getSourceUnit(uri).getAST();
129+
}
130+
124131
}

src/main/java/nextflow/lsp/services/config/ConfigCompletionProvider.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import nextflow.config.ast.ConfigIncompleteNode;
2626
import nextflow.config.dsl.ConfigDsl;
2727
import nextflow.config.spec.SpecNode;
28-
import nextflow.lsp.ast.ASTNodeCache;
2928
import nextflow.lsp.ast.CompletionHelper;
3029
import nextflow.lsp.services.CompletionProvider;
3130
import nextflow.lsp.util.Logger;
@@ -62,14 +61,12 @@
6261
*/
6362
public class ConfigCompletionProvider implements CompletionProvider {
6463

65-
private static final List<CompletionItem> TOPLEVEL_ITEMS = topLevelItems();
66-
6764
private static Logger log = Logger.getInstance();
6865

69-
private ASTNodeCache ast;
66+
private ConfigAstCache ast;
7067
private CompletionHelper ch;
7168

72-
public ConfigCompletionProvider(ASTNodeCache ast, int maxItems) {
69+
public ConfigCompletionProvider(ConfigAstCache ast, int maxItems) {
7370
this.ast = ast;
7471
this.ch = new CompletionHelper(maxItems);
7572
}
@@ -86,17 +83,18 @@ public Either<List<CompletionItem>, CompletionList> completion(TextDocumentIdent
8683
return Either.forLeft(Collections.emptyList());
8784

8885
var nodeStack = ast.getNodesAtPosition(uri, position);
86+
var spec = ast.getConfigNode(uri).getSpec();
8987
if( nodeStack.isEmpty() )
90-
return Either.forLeft(TOPLEVEL_ITEMS);
88+
return Either.forLeft(topLevelItems(spec));
9189

9290
if( isConfigExpression(nodeStack) ) {
9391
addCompletionItems(nodeStack);
9492
}
9593
else {
9694
var names = currentConfigScope(nodeStack);
9795
if( names.isEmpty() )
98-
return Either.forLeft(TOPLEVEL_ITEMS);
99-
addConfigOptions(names);
96+
return Either.forLeft(topLevelItems(spec));
97+
addConfigOptions(names, spec);
10098
}
10199

102100
return ch.isIncomplete()
@@ -179,8 +177,8 @@ private static List<String> currentConfigScope(List<ASTNode> nodeStack) {
179177
return names;
180178
}
181179

182-
private void addConfigOptions(List<String> names) {
183-
var scope = SpecNode.ROOT.getScope(names);
180+
private void addConfigOptions(List<String> names, SpecNode.Scope spec) {
181+
var scope = spec.getScope(names);
184182
if( scope == null )
185183
return;
186184
scope.children().forEach((name, child) -> {
@@ -191,9 +189,9 @@ private void addConfigOptions(List<String> names) {
191189
});
192190
}
193191

194-
private static List<CompletionItem> topLevelItems() {
192+
private static List<CompletionItem> topLevelItems(SpecNode.Scope spec) {
195193
var result = new ArrayList<CompletionItem>();
196-
SpecNode.ROOT.children().forEach((name, child) -> {
194+
spec.children().forEach((name, child) -> {
197195
if( child instanceof SpecNode.Option option ) {
198196
result.add(configOption(name, option.description(), option.type()));
199197
}

src/main/java/nextflow/lsp/services/config/ConfigHoverProvider.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import nextflow.config.ast.ConfigAssignNode;
2424
import nextflow.config.ast.ConfigBlockNode;
2525
import nextflow.config.spec.SpecNode;
26-
import nextflow.lsp.ast.ASTNodeCache;
2726
import nextflow.lsp.ast.ASTNodeStringUtils;
2827
import nextflow.lsp.ast.LanguageServerASTUtils;
2928
import nextflow.lsp.services.HoverProvider;
@@ -49,9 +48,9 @@ public class ConfigHoverProvider implements HoverProvider {
4948

5049
private static Logger log = Logger.getInstance();
5150

52-
private ASTNodeCache ast;
51+
private ConfigAstCache ast;
5352

54-
public ConfigHoverProvider(ASTNodeCache ast) {
53+
public ConfigHoverProvider(ConfigAstCache ast) {
5554
this.ast = ast;
5655
}
5756

@@ -69,7 +68,7 @@ public Hover hover(TextDocumentIdentifier textDocument, Position position) {
6968

7069
var builder = new StringBuilder();
7170

72-
var content = getHoverContent(nodeStack);
71+
var content = getHoverContent(nodeStack, ast.getConfigNode(uri).getSpec());
7372
if( content != null ) {
7473
builder.append(content);
7574
builder.append('\n');
@@ -94,14 +93,14 @@ public Hover hover(TextDocumentIdentifier textDocument, Position position) {
9493
return new Hover(new MarkupContent(MarkupKind.MARKDOWN, value));
9594
}
9695

97-
protected String getHoverContent(List<ASTNode> nodeStack) {
96+
protected String getHoverContent(List<ASTNode> nodeStack, SpecNode.Scope spec) {
9897
var offsetNode = nodeStack.get(0);
9998
if( offsetNode instanceof ConfigAssignNode assign ) {
10099
var names = getCurrentScope(nodeStack);
101100
names.addAll(assign.names);
102101

103102
var fqName = String.join(".", names);
104-
var option = SpecNode.ROOT.getOption(names);
103+
var option = spec.getOption(names);
105104
if( option != null ) {
106105
var description = StringGroovyMethods.stripIndent(option.description(), true).trim();
107106
var builder = new StringBuilder();
@@ -120,7 +119,7 @@ else if( Logger.isDebugEnabled() ) {
120119
if( names.isEmpty() )
121120
return null;
122121

123-
var scope = SpecNode.ROOT.getChild(names);
122+
var scope = spec.getChild(names);
124123
if( scope != null ) {
125124
return StringGroovyMethods.stripIndent(scope.description(), true).trim();
126125
}

src/main/java/nextflow/lsp/services/config/ConfigService.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import nextflow.lsp.services.LanguageService;
2424
import nextflow.lsp.services.LinkProvider;
2525
import nextflow.lsp.services.SemanticTokensProvider;
26+
import nextflow.lsp.spec.PluginSpecCache;
2627

2728
/**
2829
* Implementation of language services for Nextflow config files.
@@ -31,6 +32,8 @@
3132
*/
3233
public class ConfigService extends LanguageService {
3334

35+
private PluginSpecCache pluginSpecCache;
36+
3437
private ConfigAstCache astCache;
3538

3639
public ConfigService(String rootUri) {
@@ -45,12 +48,21 @@ public boolean matchesFile(String uri) {
4548

4649
@Override
4750
public void initialize(LanguageServerConfiguration configuration) {
51+
initialize(configuration, new PluginSpecCache(configuration.pluginRegistryUrl()));
52+
}
53+
54+
public void initialize(LanguageServerConfiguration configuration, PluginSpecCache pluginSpecCache) {
4855
synchronized (this) {
49-
astCache.initialize(configuration);
56+
this.pluginSpecCache = pluginSpecCache;
57+
astCache.initialize(configuration, pluginSpecCache);
5058
}
5159
super.initialize(configuration);
5260
}
5361

62+
public PluginSpecCache getPluginSpecCache() {
63+
return pluginSpecCache;
64+
}
65+
5466
@Override
5567
protected ASTNodeCache getAstCache() {
5668
return astCache;

0 commit comments

Comments
 (0)