Skip to content

Commit 05aead4

Browse files
committed
Merge branch 'main' into pr/chenyenchung/113
2 parents 2fef24f + 94079b7 commit 05aead4

11 files changed

+269
-124
lines changed

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,11 @@ private static ServerCapabilities serverCapabilities() {
164164
var documentLinkOptions = new DocumentLinkOptions(false);
165165
result.setDocumentLinkProvider(documentLinkOptions);
166166
result.setDocumentSymbolProvider(true);
167-
var executeCommandOptions = new ExecuteCommandOptions(List.of("nextflow.server.previewDag"));
167+
var commands = List.of(
168+
"nextflow.server.previewDag",
169+
"nextflow.server.previewWorkspace"
170+
);
171+
var executeCommandOptions = new ExecuteCommandOptions(commands);
168172
result.setExecuteCommandProvider(executeCommandOptions);
169173
result.setHoverProvider(true);
170174
result.setReferencesProvider(true);
@@ -452,7 +456,6 @@ public void didChangeConfiguration(DidChangeConfigurationParams params) {
452456
withDefault(JsonUtils.getBoolean(settings, "nextflow.formatting.harshilAlignment"), configuration.harshilAlignment()),
453457
withDefault(JsonUtils.getBoolean(settings, "nextflow.formatting.maheshForm"), configuration.maheshForm()),
454458
withDefault(JsonUtils.getInteger(settings, "nextflow.completion.maxItems"), configuration.maxCompletionItems()),
455-
withDefault(JsonUtils.getBoolean(settings, "nextflow.files.scanWorkspace"), configuration.scanWorkspace()),
456459
withDefault(JsonUtils.getBoolean(settings, "nextflow.formatting.sortDeclarations"), configuration.sortDeclarations()),
457460
withDefault(JsonUtils.getBoolean(settings, "nextflow.typeChecking"), configuration.typeChecking())
458461
);
@@ -546,14 +549,21 @@ public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
546549
cancelChecker.checkCanceled();
547550
var command = params.getCommand();
548551
var arguments = params.getArguments();
549-
if( !"nextflow.server.previewDag".equals(command) || arguments.size() != 2 )
550-
return null;
551-
var uri = JsonUtils.getString(arguments.get(0));
552-
log.debug(String.format("textDocument/executeCommand %s %s", command, arguments.toString()));
553-
var service = getLanguageService(uri);
554-
if( service == null )
555-
return null;
556-
return service.executeCommand(command, arguments);
552+
if( "nextflow.server.previewDag".equals(command) && arguments.size() == 2 ) {
553+
log.debug(String.format("textDocument/previewDag %s", arguments.toString()));
554+
var uri = JsonUtils.getString(arguments.get(0));
555+
var service = getLanguageService(uri);
556+
if( service != null )
557+
return service.executeCommand(command, arguments);
558+
}
559+
if( "nextflow.server.previewWorkspace".equals(command) && arguments.size() == 1 ) {
560+
log.debug(String.format("textDocument/previewWorkspace %s", arguments.toString()));
561+
var name = JsonUtils.getString(arguments.get(0));
562+
var service = scriptServices.get(name);
563+
if( service != null )
564+
return service.executeCommand(command, arguments);
565+
}
566+
return null;
557567
});
558568
}
559569

src/main/java/nextflow/lsp/ast/ASTNodeCache.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,24 +115,22 @@ public Set<URI> update(Set<URI> uris, FileCache fileCache) {
115115
// perform additional ast analysis
116116
var changedUris = analyze(uris, fileCache);
117117

118-
// update ast parents cache
119-
for( var sourceUnit : sources ) {
120-
var uri = sourceUnit.getSource().getURI();
118+
// update ast lookup, diagnostics cache
119+
for( var uri : changedUris ) {
120+
var sourceUnit = sourcesByUri().get(uri);
121+
if( sourceUnit == null )
122+
continue;
123+
124+
// -- update ast lookup
121125
var parents = visitParents(sourceUnit);
122126
nodesByURI.put(uri, parents.keySet());
123127

124128
for( var node : parents.keySet() ) {
125129
var parent = parents.get(node);
126130
lookup.put(node, new LookupData(uri, parent));
127131
}
128-
}
129-
130-
// update diagnostics cache
131-
for( var uri : changedUris ) {
132-
var sourceUnit = sourcesByUri().get(uri);
133-
if( sourceUnit == null )
134-
continue;
135132

133+
// -- update errors
136134
var errors = new ArrayList<SyntaxException>();
137135
var errorMessages = sourceUnit.getErrorCollector().getErrors();
138136
if( errorMessages != null ) {
@@ -143,6 +141,7 @@ public Set<URI> update(Set<URI> uris, FileCache fileCache) {
143141
}
144142
errorsByUri.put(uri, errors);
145143

144+
// -- update warnings
146145
var warnings = new ArrayList<WarningMessage>();
147146
var warningMessages = sourceUnit.getErrorCollector().getWarnings();
148147
if( warningMessages != null ) {

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public record LanguageServerConfiguration(
2525
boolean harshilAlignment,
2626
boolean maheshForm,
2727
int maxCompletionItems,
28-
boolean scanWorkspace,
2928
boolean sortDeclarations,
3029
boolean typeChecking
3130
) {
@@ -39,7 +38,6 @@ public static LanguageServerConfiguration defaults() {
3938
false,
4039
100,
4140
false,
42-
false,
4341
false
4442
);
4543
}

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

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import nextflow.script.control.RelatedInformationAware;
4343
import nextflow.script.formatter.FormattingOptions;
4444
import nextflow.util.PathUtils;
45-
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
4645
import org.eclipse.lsp4j.CallHierarchyIncomingCall;
4746
import org.eclipse.lsp4j.CallHierarchyItem;
4847
import org.eclipse.lsp4j.CallHierarchyOutgoingCall;
@@ -121,51 +120,26 @@ public LanguageService() {
121120

122121
private volatile boolean initialized;
123122

123+
private volatile boolean scanned;
124+
125+
private volatile String rootUri;
126+
124127
private volatile LanguageServerConfiguration configuration;
125128

126129
public void initialize(String rootUri, LanguageServerConfiguration configuration) {
127130
synchronized (this) {
128131
this.initialized = false;
132+
this.scanned = false;
133+
this.rootUri = rootUri;
129134
this.configuration = configuration;
130135

131-
var astCache = getAstCache();
132-
133-
if( configuration.scanWorkspace() ) {
134-
var uris = getWorkspaceFiles(rootUri);
135-
astCache.clear();
136-
var changedUris = astCache.update(uris, fileCache);
137-
publishDiagnostics(changedUris);
138-
}
139-
else {
140-
clearDiagnostics();
141-
astCache.clear();
142-
}
136+
clearDiagnostics();
137+
getAstCache().clear();
143138

144139
this.initialized = true;
145140
}
146141
}
147142

148-
protected Set<URI> getWorkspaceFiles(String rootUri) {
149-
if( rootUri == null ) {
150-
return fileCache.getOpenFiles();
151-
}
152-
try {
153-
var result = new HashSet<URI>();
154-
PathUtils.visitFiles(
155-
Path.of(URI.create(rootUri)),
156-
(path) -> !PathUtils.isExcluded(path, configuration.excludePatterns()),
157-
(path) -> {
158-
if( matchesFile(path.toString()) )
159-
result.add(path.toUri());
160-
});
161-
return result;
162-
}
163-
catch( IOException e ) {
164-
log.error("Failed to query workspace files: " + rootUri + " -- cause: " + e.toString());
165-
return Collections.emptySet();
166-
}
167-
}
168-
169143
public void connect(LanguageClient client) {
170144
this.client = client;
171145
}
@@ -348,14 +322,7 @@ protected void awaitUpdate() {
348322
*/
349323
protected void update() {
350324
synchronized (this) {
351-
if( initialized ) {
352-
var uris = fileCache.removeChangedFiles();
353-
354-
log.debug("update " + DefaultGroovyMethods.join(uris, " , "));
355-
var astCache = getAstCache();
356-
var changedUris = astCache.update(uris, fileCache);
357-
publishDiagnostics(changedUris);
358-
}
325+
update0();
359326
}
360327

361328
updateLock.lock();
@@ -368,6 +335,65 @@ protected void update() {
368335
}
369336
}
370337

338+
private void update0() {
339+
if( !initialized )
340+
return;
341+
342+
var astCache = getAstCache();
343+
var uris = fileCache.removeChangedFiles();
344+
345+
if( !scanned ) {
346+
if( uris.isEmpty() ) {
347+
uris = getWorkspaceFiles();
348+
astCache.clear();
349+
this.scanned = true;
350+
}
351+
else {
352+
updateLater();
353+
}
354+
}
355+
356+
if( log.isDebugEnabled() ) {
357+
var builder = new StringBuilder();
358+
builder.append("update\n");
359+
uris.forEach((uri) -> {
360+
builder.append("- ");
361+
builder.append(uri.toString().replace(rootUri, "."));
362+
builder.append('\n');
363+
});
364+
log.debug(builder.toString());
365+
}
366+
367+
var changedUris = astCache.update(uris, fileCache);
368+
publishDiagnostics(changedUris);
369+
}
370+
371+
/**
372+
* Scan the workspace for files that can be managed by the language service.
373+
*
374+
* If there is no workspace root, use the set of open files instead.
375+
*/
376+
protected Set<URI> getWorkspaceFiles() {
377+
if( rootUri == null ) {
378+
return fileCache.getOpenFiles();
379+
}
380+
try {
381+
var result = new HashSet<URI>();
382+
PathUtils.visitFiles(
383+
Path.of(URI.create(rootUri)),
384+
(path) -> !PathUtils.isExcluded(path, configuration.excludePatterns()),
385+
(path) -> {
386+
if( matchesFile(path.toString()) )
387+
result.add(path.toUri());
388+
});
389+
return result;
390+
}
391+
catch( IOException e ) {
392+
log.error("Failed to query workspace files: " + rootUri + " -- cause: " + e.toString());
393+
return Collections.emptySet();
394+
}
395+
}
396+
371397
/**
372398
* Publish diagnostics for a set of compilation errors.
373399
*
@@ -390,7 +416,7 @@ protected void publishDiagnostics(Set<URI> changedUris) {
390416

391417
var diagnostic = new Diagnostic(range, message, DiagnosticSeverity.Error, "nextflow");
392418
if( error instanceof RelatedInformationAware ria )
393-
diagnostic.setRelatedInformation(getRelatedInformation(ria, uri));
419+
diagnostic.setRelatedInformation(relatedInformation(ria, uri));
394420
diagnostics.add(diagnostic);
395421
}
396422

@@ -407,7 +433,7 @@ protected void publishDiagnostics(Set<URI> changedUris) {
407433

408434
var diagnostic = new Diagnostic(range, message, DiagnosticSeverity.Warning, "nextflow");
409435
if( warning instanceof RelatedInformationAware ria )
410-
diagnostic.setRelatedInformation(getRelatedInformation(ria, uri));
436+
diagnostic.setRelatedInformation(relatedInformation(ria, uri));
411437
diagnostics.add(diagnostic);
412438
}
413439

@@ -416,7 +442,7 @@ protected void publishDiagnostics(Set<URI> changedUris) {
416442
});
417443
}
418444

419-
private List<DiagnosticRelatedInformation> getRelatedInformation(RelatedInformationAware ria, URI uri) {
445+
private static List<DiagnosticRelatedInformation> relatedInformation(RelatedInformationAware ria, URI uri) {
420446
var otherNode = ria.getOtherNode();
421447
if( otherNode != null ) {
422448
var result = new ArrayList<DiagnosticRelatedInformation>();
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2024-2025, Seqera Labs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package nextflow.lsp.services.script;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import nextflow.script.ast.ProcessNode;
22+
import nextflow.script.ast.WorkflowNode;
23+
import org.codehaus.groovy.ast.CodeVisitorSupport;
24+
import org.codehaus.groovy.ast.MethodNode;
25+
import org.codehaus.groovy.ast.expr.MethodCallExpression;
26+
27+
/**
28+
* Query the list of outgoing calls made by a workflow, process, or function.
29+
*
30+
* @author Ben Sherman <bentshermann@gmail.com>
31+
*/
32+
class OutgoingCallsVisitor extends CodeVisitorSupport {
33+
34+
private List<MethodCallExpression> outgoingCalls;
35+
36+
public List<MethodCallExpression> apply(MethodNode node) {
37+
outgoingCalls = new ArrayList<>();
38+
if( node instanceof ProcessNode pn )
39+
visit(pn.exec);
40+
else if( node instanceof WorkflowNode wn )
41+
visit(wn.main);
42+
else
43+
visit(node.getCode());
44+
return outgoingCalls;
45+
}
46+
47+
@Override
48+
public void visitMethodCallExpression(MethodCallExpression node) {
49+
visit(node.getObjectExpression());
50+
visit(node.getArguments());
51+
52+
if( node.isImplicitThis() )
53+
outgoingCalls.add(node);
54+
}
55+
}

src/main/java/nextflow/lsp/services/script/ScriptAstCache.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,17 @@ public void initialize(String rootUri, LanguageServerConfiguration configuration
9090
@Override
9191
protected Set<URI> analyze(Set<URI> uris, FileCache fileCache) {
9292
// recursively load included modules
93+
var changedUris = new HashSet<>(uris);
94+
9395
for( var uri : uris ) {
9496
var source = compiler().getSource(uri);
95-
new ModuleResolver(compiler()).resolve(source, (includeUri) -> compiler().createSourceUnit(includeUri, fileCache));
97+
new ModuleResolver(compiler()).resolve(source, (newUri) -> {
98+
changedUris.add(newUri);
99+
return compiler().createSourceUnit(newUri, fileCache);
100+
});
96101
}
97102

98103
// phase 2: include checking
99-
var changedUris = new HashSet<>(uris);
100-
101104
for( var sourceUnit : getSourceUnits() ) {
102105
var visitor = new ResolveIncludeVisitor(sourceUnit, compiler(), uris);
103106
visitor.visit();

0 commit comments

Comments
 (0)