Skip to content

Commit 59df020

Browse files
committed
Add command to preview workspace
Signed-off-by: Ben Sherman <bentshermann@gmail.com>
1 parent 5189f2c commit 59df020

File tree

7 files changed

+182
-63
lines changed

7 files changed

+182
-63
lines changed

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

Lines changed: 20 additions & 9 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);
@@ -546,14 +550,21 @@ public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
546550
cancelChecker.checkCanceled();
547551
var command = params.getCommand();
548552
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);
553+
if( "nextflow.server.previewDag".equals(command) && arguments.size() == 2 ) {
554+
log.debug(String.format("textDocument/previewDag %s", arguments.toString()));
555+
var uri = JsonUtils.getString(arguments.get(0));
556+
var service = getLanguageService(uri);
557+
if( service != null )
558+
return service.executeCommand(command, arguments);
559+
}
560+
if( "nextflow.server.previewWorkspace".equals(command) && arguments.size() == 1 ) {
561+
log.debug(String.format("textDocument/previewWorkspace %s", arguments.toString()));
562+
var name = JsonUtils.getString(arguments.get(0));
563+
var service = scriptServices.get(name);
564+
if( service != null )
565+
return service.executeCommand(command, arguments);
566+
}
567+
return null;
557568
});
558569
}
559570

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/ScriptCallHierarchyProvider.java

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@
2525
import nextflow.lsp.services.CallHierarchyProvider;
2626
import nextflow.lsp.util.LanguageServerUtils;
2727
import nextflow.lsp.util.Logger;
28-
import nextflow.script.ast.ProcessNode;
29-
import nextflow.script.ast.WorkflowNode;
3028
import org.codehaus.groovy.ast.ASTNode;
31-
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
3229
import org.codehaus.groovy.ast.MethodNode;
3330
import org.codehaus.groovy.ast.expr.MethodCallExpression;
3431
import org.codehaus.groovy.ast.expr.VariableExpression;
@@ -187,12 +184,11 @@ public List<CallHierarchyOutgoingCall> outgoingCalls(CallHierarchyItem item) {
187184
return Collections.emptyList();
188185

189186
var sourceUnit = ast.getSourceUnit(uri);
190-
var visitor = new OutgoingCallsVisitor(sourceUnit);
191-
visitor.visit((MethodNode) fromNode);
187+
var calls = new OutgoingCallsVisitor().apply((MethodNode) fromNode);
192188

193189
var callsMap = new HashMap<String, MethodCallExpression>();
194190
var rangesMap = new HashMap<String, List<Range>>();
195-
for( var call : visitor.getOutgoingCalls() ) {
191+
for( var call : calls ) {
196192
var name = call.getMethodAsString();
197193
callsMap.put(name, call);
198194
if( !rangesMap.containsKey(name) )
@@ -220,42 +216,4 @@ public List<CallHierarchyOutgoingCall> outgoingCalls(CallHierarchyItem item) {
220216
return result;
221217
}
222218

223-
private static class OutgoingCallsVisitor extends ClassCodeVisitorSupport {
224-
225-
private SourceUnit sourceUnit;
226-
227-
private List<MethodCallExpression> outgoingCalls = new ArrayList<>();
228-
229-
public OutgoingCallsVisitor(SourceUnit sourceUnit) {
230-
this.sourceUnit = sourceUnit;
231-
}
232-
233-
@Override
234-
protected SourceUnit getSourceUnit() {
235-
return sourceUnit;
236-
}
237-
238-
public void visit(MethodNode node) {
239-
if( node instanceof ProcessNode pn )
240-
visit(pn.exec);
241-
else if( node instanceof WorkflowNode wn )
242-
visit(wn.main);
243-
else
244-
visit(node.getCode());
245-
}
246-
247-
@Override
248-
public void visitMethodCallExpression(MethodCallExpression node) {
249-
visit(node.getObjectExpression());
250-
visit(node.getArguments());
251-
252-
if( node.isImplicitThis() )
253-
outgoingCalls.add(node);
254-
}
255-
256-
public List<MethodCallExpression> getOutgoingCalls() {
257-
return outgoingCalls;
258-
}
259-
}
260-
261219
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import java.util.List;
2222
import java.util.Map;
2323

24-
import com.google.gson.JsonNull;
25-
import com.google.gson.JsonPrimitive;
2624
import nextflow.lsp.services.CodeLensProvider;
2725
import nextflow.lsp.services.script.dag.DataflowVisitor;
2826
import nextflow.lsp.services.script.dag.MermaidRenderer;
@@ -32,6 +30,8 @@
3230
import org.eclipse.lsp4j.Command;
3331
import org.eclipse.lsp4j.TextDocumentIdentifier;
3432

33+
import static nextflow.lsp.util.JsonUtils.asJson;
34+
3535
/**
3636
*
3737
* @author Ben Sherman <bentshermann@gmail.com>
@@ -62,8 +62,7 @@ public List<CodeLens> codeLens(TextDocumentIdentifier textDocument) {
6262
var range = LanguageServerUtils.astNodeToRange(wn);
6363
if( range == null )
6464
continue;
65-
var name = wn.isEntry() ? JsonNull.INSTANCE : new JsonPrimitive(wn.getName());
66-
var arguments = List.of(new JsonPrimitive(uri.toString()), (Object) name);
65+
var arguments = List.of(asJson(uri.toString()), asJson(wn.getName()));
6766
var command = new Command("Preview DAG", "nextflow.previewDag", arguments);
6867
result.add(new CodeLens(range, command, null));
6968
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,18 @@ protected SymbolProvider getSymbolProvider() {
117117

118118
@Override
119119
public Object executeCommand(String command, List<Object> arguments) {
120-
if( !"nextflow.server.previewDag".equals(command) || arguments.size() != 2 )
121-
return null;
122-
var uri = getJsonString(arguments.get(0));
123-
var name = getJsonString(arguments.get(1));
124-
var provider = new ScriptCodeLensProvider(astCache);
125-
return provider.previewDag(uri, name);
120+
updateNow();
121+
if( "nextflow.server.previewDag".equals(command) && arguments.size() == 2 ) {
122+
var uri = getJsonString(arguments.get(0));
123+
var name = getJsonString(arguments.get(1));
124+
var provider = new ScriptCodeLensProvider(astCache);
125+
return provider.previewDag(uri, name);
126+
}
127+
if( "nextflow.server.previewWorkspace".equals(command) ) {
128+
var provider = new WorkspacePreviewProvider(astCache);
129+
return provider.preview();
130+
}
131+
return null;
126132
}
127133

128134
private String getJsonString(Object json) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.net.URI;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.stream.Stream;
22+
23+
import nextflow.script.ast.ProcessNode;
24+
import nextflow.script.ast.WorkflowNode;
25+
import nextflow.script.types.TypeChecker;
26+
27+
/**
28+
*
29+
* @author Ben Sherman <bentshermann@gmail.com>
30+
*/
31+
public class WorkspacePreviewProvider {
32+
33+
private ScriptAstCache ast;
34+
35+
public WorkspacePreviewProvider(ScriptAstCache ast) {
36+
this.ast = ast;
37+
}
38+
39+
public Map<String,Object> preview() {
40+
System.err.println("uris: " + ast.getUris());
41+
var result = ast.getUris().stream()
42+
.flatMap(uri -> definitions(uri))
43+
.toList();
44+
return Map.of("result", result);
45+
}
46+
47+
private Stream<? extends Object> definitions(URI uri) {
48+
var processes = ast.getProcessNodes(uri).stream()
49+
.map(pn -> Map.of(
50+
"name", pn.getName(),
51+
"type", "process",
52+
"uri", uri.toString(),
53+
"line", pn.getLineNumber() - 1
54+
));
55+
var workflows = ast.getWorkflowNodes(uri).stream()
56+
.map(wn -> Map.of(
57+
"name", wn.isEntry() ? "<entry>" : wn.getName(),
58+
"type", "workflow",
59+
"uri", uri.toString(),
60+
"line", wn.getLineNumber() - 1,
61+
"children", children(wn)
62+
));
63+
return Stream.concat(processes, workflows);
64+
}
65+
66+
private List<? extends Object> children(WorkflowNode node) {
67+
return new OutgoingCallsVisitor().apply(node).stream()
68+
.map(call -> TypeChecker.inferMethodTarget(call))
69+
.filter(mn -> mn instanceof ProcessNode || mn instanceof WorkflowNode)
70+
.map(mn -> Map.of(
71+
"name", mn.getName(),
72+
"uri", ast.getURI(mn)
73+
))
74+
.toList();
75+
}
76+
77+
}

src/main/java/nextflow/lsp/util/JsonUtils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020

2121
import com.google.gson.JsonElement;
22+
import com.google.gson.JsonNull;
2223
import com.google.gson.JsonObject;
2324
import com.google.gson.JsonPrimitive;
2425

@@ -28,6 +29,18 @@
2829
*/
2930
public class JsonUtils {
3031

32+
public static Object asJson(Object value) {
33+
if( value == null )
34+
return JsonNull.INSTANCE;
35+
if( value instanceof Boolean b )
36+
return new JsonPrimitive(b);
37+
if( value instanceof Number n )
38+
return new JsonPrimitive(n);
39+
if( value instanceof String s )
40+
return new JsonPrimitive(s);
41+
return value;
42+
}
43+
3144
public static List<String> getStringArray(Object json, String path) {
3245
var value = getObjectPath(json, path);
3346
if( value == null || !value.isJsonArray() )

0 commit comments

Comments
 (0)