Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
630ccc4
Rascal LSP formatting API and example.
toinehartman Jul 11, 2025
d3675c4
Provide overridable defaults for common string operations.
toinehartman Jul 14, 2025
b32d2f6
Align names with `DocumentFormattingParams`
toinehartman Jul 14, 2025
f16e17a
Implement parametric formatting.
toinehartman Jul 14, 2025
b749dcf
Ongoing design work.
toinehartman Jul 16, 2025
f1effe1
Experiment with a first formatting implementation using Box.
toinehartman Aug 6, 2025
6fc3acf
Simplify API and shift responsibilities towards DSL.
toinehartman Aug 7, 2025
bdb7989
Improve names in formatting library.
toinehartman Aug 7, 2025
a985a81
Add indentation conversion functions.
toinehartman Aug 7, 2025
0196d07
Do string modifications in Pico formatter.
toinehartman Aug 7, 2025
d1c4e3e
Update formatting API design.
toinehartman Aug 18, 2025
49b66d0
Add `mergeLines`
toinehartman Aug 18, 2025
998a115
Small fixes.
toinehartman Aug 18, 2025
551fe2a
Break new line ties by order.
toinehartman Aug 18, 2025
84c5a35
Document & fix string/formatting utils.
toinehartman Aug 18, 2025
c4b3561
Add rangeFormatting, reuse formatting.
toinehartman Aug 19, 2025
1748678
Match contribution sig in implementation.
toinehartman Aug 25, 2025
1d56010
Add missing license header.
toinehartman Aug 25, 2025
20a71ae
Document formatting service.
toinehartman Aug 25, 2025
cda1531
Use formatter from stdlib.
toinehartman Aug 25, 2025
206a57a
Change defaults based on discussion with @DavyLandman.
toinehartman Aug 27, 2025
6cdab51
Fix import to renamed module.
toinehartman Aug 28, 2025
05b2955
Format Pico examples.
toinehartman Aug 28, 2025
9561883
Inline function.
toinehartman Sep 3, 2025
7b92033
Use focus instead of Tree+loc.
toinehartman Sep 3, 2025
244da24
Implement range focus with special case for lists.
toinehartman Sep 8, 2025
69307a7
Fix comment about Rascal charcter encoding.
toinehartman Sep 8, 2025
c1b08a8
Add basic TreeSearch tests.
toinehartman Sep 10, 2025
6f5657f
Consider layout as well.
toinehartman Sep 10, 2025
49cf9be
Include partially selected element at end of range.
toinehartman Sep 10, 2025
b23a5ea
Simplify prepending element.
toinehartman Sep 10, 2025
ce46294
Test & fix list focus.
toinehartman Sep 15, 2025
f759c2e
Merge remote-tracking branch 'origin/main' into feature/lsp-formatting
toinehartman Apr 8, 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
6 changes: 6 additions & 0 deletions rascal-lsp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@
<version>3.5.5</version>
</dependency>
</dependencies>
<configuration>
<systemPropertyVariables>
<forkMode>always</forkMode>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this change in the pom.xml has to do with this PR?

<log4j2.configurationFactory>org.rascalmpl.vscode.lsp.log.LogRedirectConfiguration</log4j2.configurationFactory>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.rascalmpl</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public interface ILanguageContributions {
public InterruptibleFuture<IList> selectionRange(IList focus);
public InterruptibleFuture<IList> prepareCallHierarchy(IList focus);
public InterruptibleFuture<IList> incomingOutgoingCalls(IConstructor hierarchyItem, IConstructor direction);
public InterruptibleFuture<IList> formatting(IList input, IConstructor formattingOptions);

public InterruptibleFuture<ISourceLocation> prepareRename(IList focus);
public InterruptibleFuture<ITuple> rename(IList focus, String name);
Expand All @@ -88,6 +89,7 @@ public interface ILanguageContributions {
public CompletableFuture<Boolean> providesSelectionRange();
public CompletableFuture<Boolean> providesCallHierarchy();
public CompletableFuture<Boolean> providesCompletion();
public CompletableFuture<Boolean> providesFormatting();

public CompletableFuture<Boolean> specialCaseHighlighting();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public class InterpretedLanguageContributions implements ILanguageContributions
private final CompletableFuture<@Nullable IFunction> didRenameFiles;
private final CompletableFuture<@Nullable IFunction> selectionRange;
private final CompletableFuture<@Nullable IFunction> prepareCallHierarchy;
private final CompletableFuture<@Nullable IFunction> formatting;

private final CompletableFuture<@Nullable IFunction> callHierarchyService;
private final CompletableFuture<@Nullable IFunction> completion;
private final CompletableFuture<IList> completionTriggerCharacters;
Expand All @@ -119,6 +121,7 @@ public class InterpretedLanguageContributions implements ILanguageContributions
private final CompletableFuture<Boolean> providesSelectionRange;
private final CompletableFuture<Boolean> providesCallHierarchy;
private final CompletableFuture<Boolean> providesCompletion;
private final CompletableFuture<Boolean> providesFormatting;

private final CompletableFuture<Boolean> specialCaseHighlighting;

Expand Down Expand Up @@ -169,6 +172,7 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen
this.callHierarchyService = getFunctionFor(contributions, LanguageContributions.CALL_HIERARCHY, 1);
this.completion = getFunctionFor(contributions, LanguageContributions.COMPLETION);
this.completionTriggerCharacters = getContributionParameter(contributions, LanguageContributions.COMPLETION, LanguageContributions.COMPLETION_TRIGGER_CHARACTERS, VF.list(), IList.class);
this.formatting = getFunctionFor(contributions, LanguageContributions.FORMATTING);

// assign boolean properties once instead of wasting futures all the time
this.providesAnalysis = nonNull(this.analysis);
Expand All @@ -187,6 +191,7 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen
this.providesSelectionRange = nonNull(this.selectionRange);
this.providesCallHierarchy = nonNull(this.prepareCallHierarchy);
this.providesCompletion = nonNull(this.completion);
this.providesFormatting = nonNull(this.formatting);

this.specialCaseHighlighting = getContributionParameter(contributions,
LanguageContributions.PARSING,
Expand Down Expand Up @@ -486,6 +491,12 @@ public CompletableFuture<IList> completionTriggerCharacters() {
return completionTriggerCharacters;
}

@Override
public InterruptibleFuture<IList> formatting(IList focus, IConstructor formattingOptions) {
debug(LanguageContributions.FORMATTING, focus.size(), formattingOptions);
return execFunction(LanguageContributions.FORMATTING, formatting, VF.list(), focus, formattingOptions);
}

private void debug(String name, Object param) {
logger.debug("{}({})", name, param);
}
Expand Down Expand Up @@ -563,6 +574,11 @@ public CompletableFuture<Boolean> providesCompletion() {
return providesCompletion;
}

@Override
public CompletableFuture<Boolean> providesFormatting() {
return providesFormatting;
}

@Override
public CompletableFuture<Boolean> providesAnalysis() {
return providesAnalysis;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private static final <T> CompletableFuture<T> failedInitialization() {
private volatile CompletableFuture<ILanguageContributions> selectionRange = failedInitialization();
private volatile CompletableFuture<ILanguageContributions> callHierarchy = failedInitialization();
private volatile CompletableFuture<ILanguageContributions> completion = failedInitialization();
private volatile CompletableFuture<ILanguageContributions> formatting = failedInitialization();

private volatile CompletableFuture<Boolean> providesAnalysis = failedInitialization();
private volatile CompletableFuture<Boolean> providesBuild = failedInitialization();
Expand All @@ -94,6 +95,7 @@ private static final <T> CompletableFuture<T> failedInitialization() {
private volatile CompletableFuture<Boolean> providesSelectionRange = failedInitialization();
private volatile CompletableFuture<Boolean> providesCallHierarchy = failedInitialization();
private volatile CompletableFuture<Boolean> providesCompletion = failedInitialization();
private volatile CompletableFuture<Boolean> providesFormatting = failedInitialization();

private volatile CompletableFuture<Boolean> specialCaseHighlighting = failedInitialization();

Expand Down Expand Up @@ -172,6 +174,7 @@ private synchronized void calculateRouting() {
selectionRange = findFirstOrDefault(ILanguageContributions::providesSelectionRange, "selectionRange");
callHierarchy = findFirstOrDefault(ILanguageContributions::providesCallHierarchy, "callHierarchy");
completion = findFirstOrDefault(ILanguageContributions::providesCompletion, "completion");
formatting = findFirstOrDefault(ILanguageContributions::providesFormatting, "formatting");

providesAnalysis = anyTrue(ILanguageContributions::providesAnalysis);
providesBuild = anyTrue(ILanguageContributions::providesBuild);
Expand All @@ -189,6 +192,7 @@ private synchronized void calculateRouting() {
providesSelectionRange = anyTrue(ILanguageContributions::providesSelectionRange);
providesCallHierarchy = anyTrue(ILanguageContributions::providesCallHierarchy);
providesCompletion = anyTrue(ILanguageContributions::providesCompletion);
providesFormatting = anyTrue(ILanguageContributions::providesFormatting);

// Always use the special-case highlighting status of *the first*
// contribution (possibly using the default value in the Rascal ADT if
Expand Down Expand Up @@ -350,6 +354,11 @@ public InterruptibleFuture<IList> selectionRange(IList focus) {
return flatten(selectionRange, c -> c.selectionRange(focus));
}

@Override
public InterruptibleFuture<IList> formatting(IList focus, IConstructor formattingOptions) {
return flatten(formatting, c -> c.formatting(focus, formattingOptions));
}

@Override
public InterruptibleFuture<IList> completion(IList focus, IInteger cursorOffset, IConstructor trigger) {
return flatten(completion, c -> c.completion(focus, cursorOffset, trigger));
Expand Down Expand Up @@ -449,6 +458,11 @@ public CompletableFuture<Boolean> providesCompletion() {
return providesCompletion;
}

@Override
public CompletableFuture<Boolean> providesFormatting() {
return providesFormatting;
}

@Override
public CompletableFuture<Boolean> specialCaseHighlighting() {
return specialCaseHighlighting;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ public CompletableFuture<IList> completionTriggerCharacters() {
return completable(VF.list());
}

@Override
public InterruptibleFuture<IList> formatting(IList input, IConstructor formattingOptions) {
return interruptible(VF.list());
}

@Override
public CompletableFuture<Boolean> providesAnalysis() {
return falsy;
Expand Down Expand Up @@ -279,6 +284,11 @@ public CompletableFuture<Boolean> providesCompletion() {
return falsy;
}

@Override
public CompletableFuture<Boolean> providesFormatting() {
return falsy;
}

@Override
public CompletableFuture<Boolean> specialCaseHighlighting() {
return falsy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,16 @@
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.DocumentRangeFormattingParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.FileDelete;
import org.eclipse.lsp4j.FileRename;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.FoldingRangeRequestParams;
import org.eclipse.lsp4j.FormattingOptions;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.ImplementationParams;
Expand Down Expand Up @@ -117,6 +120,7 @@
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.WorkspaceFolder;
Expand All @@ -127,6 +131,7 @@
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.util.Ranges;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.util.locations.ColumnMaps;
Expand Down Expand Up @@ -279,6 +284,8 @@ public void initializeServerCapabilities(ClientCapabilities clientCapabilities,
result.setCodeLensProvider(new CodeLensOptions(false));
result.setRenameProvider(new RenameOptions(true));
result.setExecuteCommandProvider(new ExecuteCommandOptions(Collections.singletonList(getRascalMetaCommandName())));
result.setDocumentFormattingProvider(true);
result.setDocumentRangeFormattingProvider(true);
result.setInlayHintProvider(true);
result.setSelectionRangeProvider(true);
result.setFoldingRangeProvider(true);
Expand Down Expand Up @@ -802,6 +809,66 @@ public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActio
return CodeActions.mergeAndConvertCodeActions(this, dedicatedLanguageName, contribs.getName(), quickfixes, codeActions);
}

@Override
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
logger.debug("Formatting: {}", params);

var uri = Locations.toLoc(params.getTextDocument());
final ILanguageContributions contribs = contributions(uri);

// call the `formatting` implementation of the relevant language contribution
return getFile(uri)
.getCurrentTreeAsync(true)
.thenApply(Versioned::get)
.thenCompose(tree -> {
final var opts = getFormattingOptions(params.getOptions());
return contribs.formatting(VF.list(tree), opts).get();
})
.thenApply(l -> DocumentChanges.translateTextEdits(l, columns));
}

@Override
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
logger.debug("Formatting range: {}", params);

var uri = Locations.toLoc(params.getTextDocument());
Range range = params.getRange();
final ILanguageContributions contribs = contributions(uri);

// call the `formatting` implementation of the relevant language contribution
var fileState = getFile(uri);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fileState can be inlined in the next line.

return fileState
.getCurrentTreeAsync(true)
.thenApply(Versioned::get)
.thenCompose(tree -> {
// just a range
var r = Locations.setRange(uri, range, columns);
// compute the focus list at the end of the range
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is not accurate?

var focus = TreeSearch.computeFocusList(tree, r.getBeginLine(), r.getBeginColumn(), r.getEndLine(), r.getEndColumn());

var opts = getFormattingOptions(params.getOptions());
return contribs.formatting(focus, opts).get();
})
// convert the document changes
.thenApply(l -> DocumentChanges.translateTextEdits(l, columns)
.stream()
.filter(e -> Ranges.containsRange(range, e.getRange()))
.collect(Collectors.toList()));
}

private IConstructor getFormattingOptions(FormattingOptions options) {
var optionsType = tf.abstractDataType(typeStore, "FormattingOptions");
var consType = tf.constructor(typeStore, optionsType, "formattingOptions");
Comment on lines +860 to +861
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be stored in a field, and only looked-up at the start?

var opts = Map.of(
"tabSize", VF.integer(options.getTabSize()),
"insertSpaces", VF.bool(options.isInsertSpaces()),
"trimTrailingWhitespace", VF.bool(options.isTrimTrailingWhitespace()),
"insertFinalNewline", VF.bool(options.isInsertFinalNewline()),
"trimFinalNewlines", VF.bool(options.isTrimFinalNewlines())
);
return VF.constructor(consType, new IValue[0], opts);
}

private CompletableFuture<IList> computeCodeActions(final ILanguageContributions contribs, final int startLine, final int startColumn, ITree tree) {
IList focus = TreeSearch.computeFocusList(tree, startLine, startColumn);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private LanguageContributions () {}
public static final String CALL_HIERARCHY = "callHierarchy";
public static final String COMPLETION = "completion";
public static final String COMPLETION_TRIGGER_CHARACTERS = "additionalTriggerCharacters";
public static final String FORMATTING = "formatting";

public static final String RENAME_SERVICE = "renameService";
public static final String PREPARE_RENAME_SERVICE = "prepareRenameService";
Expand Down
Loading
Loading