Skip to content

Commit dd1e9b7

Browse files
authored
Semantic highlighting (#92)
Signed-off-by: Ben Sherman <bentshermann@gmail.com>
1 parent 96e73bc commit dd1e9b7

File tree

13 files changed

+637
-1
lines changed

13 files changed

+637
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The following language features are currently supported:
1010
- formatting
1111
- hover hints
1212
- rename
13+
- semantic highlighting
1314
- DAG preview for workflows
1415

1516
## Requirements

modules/compiler/src/main/java/config/parser/ConfigAstBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292

9393
import static nextflow.config.parser.ConfigParser.*;
9494
import static nextflow.script.parser.PositionConfigureUtils.ast;
95+
import static nextflow.script.parser.PositionConfigureUtils.tokenPosition;
9596
import static org.codehaus.groovy.ast.expr.VariableExpression.THIS_EXPRESSION;
9697
import static org.codehaus.groovy.ast.tools.GeneralUtils.*;
9798

@@ -1173,6 +1174,7 @@ private Parameter formalParameter(FormalParameterContext ctx) {
11731174
: null;
11741175
var result = ast( param(type, name, defaultValue), ctx );
11751176
checkInvalidVarName(name, result);
1177+
result.putNodeMetaData("_START_NAME", tokenPosition(ctx.identifier()));
11761178
return result;
11771179
}
11781180

modules/compiler/src/main/java/script/parser/PositionConfigureUtils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,17 @@ public static <T extends ASTNode> T ast(T astNode, ASTNode start, ASTNode stop)
135135

136136
return astNode;
137137
}
138+
139+
/**
140+
* Get the zero-based start position (line, character) of a token.
141+
*
142+
* @param ctx
143+
*/
144+
public static TokenPosition tokenPosition(ParserRuleContext ctx) {
145+
var token = ctx.getStart();
146+
return new TokenPosition(
147+
token.getLine() - 1,
148+
token.getCharPositionInLine()
149+
);
150+
}
138151
}

modules/compiler/src/main/java/script/parser/ScriptAstBuilder.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
import org.codehaus.groovy.syntax.Types;
100100

101101
import static nextflow.script.parser.PositionConfigureUtils.ast;
102+
import static nextflow.script.parser.PositionConfigureUtils.tokenPosition;
102103
import static nextflow.script.parser.ScriptParser.*;
103104
import static nextflow.script.ast.ASTHelpers.*;
104105
import static org.codehaus.groovy.ast.expr.VariableExpression.THIS_EXPRESSION;
@@ -339,7 +340,11 @@ private IncludeNode includeDeclaration(IncludeDeclarationContext ctx) {
339340
.map(it -> {
340341
var name = it.name.getText();
341342
var alias = it.alias != null ? it.alias.getText() : null;
342-
return ast( new IncludeVariable(name, alias), it );
343+
var result = new IncludeVariable(name, alias);
344+
result.putNodeMetaData("_START_NAME", tokenPosition(it.name));
345+
if( it.alias != null )
346+
result.putNodeMetaData("_START_ALIAS", tokenPosition(it.alias));
347+
return ast( result, it );
343348
})
344349
.collect(Collectors.toList());
345350

@@ -1525,6 +1530,7 @@ private Parameter formalParameter(FormalParameterContext ctx) {
15251530
: null;
15261531
var result = ast( param(type, name, defaultValue), ctx );
15271532
checkInvalidVarName(name, result);
1533+
result.putNodeMetaData("_START_NAME", tokenPosition(ctx.identifier()));
15281534
return result;
15291535
}
15301536

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2013-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.script.parser;
17+
18+
/**
19+
*
20+
* @author Ben Sherman <bentshermann@gmail.com>
21+
*/
22+
public record TokenPosition(
23+
int line, // 0-based
24+
int character // 0-based
25+
) {}

modules/language-server/src/main/java/nextflow/lsp/NextflowLanguageServer.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import nextflow.lsp.file.PathUtils;
3232
import nextflow.lsp.util.Logger;
3333
import nextflow.lsp.services.LanguageService;
34+
import nextflow.lsp.services.SemanticTokensVisitor;
3435
import nextflow.lsp.services.config.ConfigService;
3536
import nextflow.lsp.services.script.ScriptService;
3637
import nextflow.script.formatter.FormattingOptions;
@@ -76,6 +77,10 @@
7677
import org.eclipse.lsp4j.ProgressParams;
7778
import org.eclipse.lsp4j.ReferenceParams;
7879
import org.eclipse.lsp4j.RenameParams;
80+
import org.eclipse.lsp4j.SemanticTokens;
81+
import org.eclipse.lsp4j.SemanticTokensLegend;
82+
import org.eclipse.lsp4j.SemanticTokensParams;
83+
import org.eclipse.lsp4j.SemanticTokensWithRegistrationOptions;
7984
import org.eclipse.lsp4j.ServerCapabilities;
8085
import org.eclipse.lsp4j.SetTraceParams;
8186
import org.eclipse.lsp4j.SymbolInformation;
@@ -164,6 +169,14 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
164169
serverCapabilities.setExecuteCommandProvider(executeCommandOptions);
165170
serverCapabilities.setHoverProvider(true);
166171
serverCapabilities.setReferencesProvider(true);
172+
var semanticTokensOptions = new SemanticTokensWithRegistrationOptions(
173+
new SemanticTokensLegend(
174+
SemanticTokensVisitor.TOKEN_TYPES,
175+
Collections.emptyList()
176+
),
177+
true,
178+
false);
179+
serverCapabilities.setSemanticTokensProvider(semanticTokensOptions);
167180
serverCapabilities.setRenameProvider(true);
168181
serverCapabilities.setWorkspaceSymbolProvider(true);
169182

@@ -408,6 +421,19 @@ public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
408421
});
409422
}
410423

424+
@Override
425+
public CompletableFuture<SemanticTokens> semanticTokensFull(SemanticTokensParams params) {
426+
return CompletableFutures.computeAsync((cancelChecker) -> {
427+
cancelChecker.checkCanceled();
428+
var uri = params.getTextDocument().getUri();
429+
log.debug("textDocument/semanticTokens/full " + relativePath(uri));
430+
var service = getLanguageService(uri);
431+
if( service == null )
432+
return null;
433+
return service.semanticTokensFull(params);
434+
});
435+
}
436+
411437
// --- WorkspaceService
412438

413439
@Override

modules/language-server/src/main/java/nextflow/lsp/services/LanguageService.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
import org.eclipse.lsp4j.PublishDiagnosticsParams;
7474
import org.eclipse.lsp4j.ReferenceParams;
7575
import org.eclipse.lsp4j.RenameParams;
76+
import org.eclipse.lsp4j.SemanticTokens;
77+
import org.eclipse.lsp4j.SemanticTokensParams;
7678
import org.eclipse.lsp4j.SymbolInformation;
7779
import org.eclipse.lsp4j.TextEdit;
7880
import org.eclipse.lsp4j.WorkspaceEdit;
@@ -116,6 +118,7 @@ public LanguageService() {
116118
protected LinkProvider getLinkProvider() { return null; }
117119
protected ReferenceProvider getReferenceProvider() { return null; }
118120
protected RenameProvider getRenameProvider() { return null; }
121+
protected SemanticTokensProvider getSemanticTokensProvider() { return null; }
119122
protected SymbolProvider getSymbolProvider() { return null; }
120123

121124
private volatile boolean initialized;
@@ -295,6 +298,15 @@ public WorkspaceEdit rename(RenameParams params) {
295298
return provider.rename(params.getTextDocument(), params.getPosition(), params.getNewName());
296299
}
297300

301+
public SemanticTokens semanticTokensFull(SemanticTokensParams params) {
302+
var provider = getSemanticTokensProvider();
303+
if( provider == null )
304+
return null;
305+
306+
awaitUpdate();
307+
return provider.semanticTokensFull(params.getTextDocument());
308+
}
309+
298310
public List<? extends WorkspaceSymbol> symbol(WorkspaceSymbolParams params) {
299311
var provider = getSymbolProvider();
300312
if( provider == null )
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2024, 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;
17+
18+
import java.util.List;
19+
20+
import org.eclipse.lsp4j.SemanticTokens;
21+
import org.eclipse.lsp4j.TextDocumentIdentifier;
22+
23+
public interface SemanticTokensProvider {
24+
25+
SemanticTokens semanticTokensFull(TextDocumentIdentifier textDocument);
26+
27+
}

0 commit comments

Comments
 (0)