Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 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
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 @@ -153,6 +153,12 @@
<version>3.5.3</version>
</dependency>
</dependencies>
<configuration>
<systemPropertyVariables>
<forkMode>always</forkMode>
Copy link
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 @@ -34,6 +34,7 @@
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;

import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISet;
Expand All @@ -60,6 +61,7 @@ public interface ILanguageContributions {
public InterruptibleFuture<ISet> implementation(IList focus);
public InterruptibleFuture<IList> codeAction(IList focus);
public InterruptibleFuture<IList> selectionRange(IList focus);
public InterruptibleFuture<IList> formatting(IList input, IConstructor formattingOptions);

public InterruptibleFuture<ISourceLocation> prepareRename(IList focus);
public InterruptibleFuture<ITuple> rename(IList focus, String name);
Expand All @@ -81,6 +83,7 @@ public interface ILanguageContributions {
public CompletableFuture<Boolean> hasCodeAction();
public CompletableFuture<Boolean> hasDidRenameFiles();
public CompletableFuture<Boolean> hasSelectionRange();
public CompletableFuture<Boolean> hasFormatting();

public CompletableFuture<Boolean> specialCaseHighlighting();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -52,6 +53,7 @@
import org.rascalmpl.vscode.lsp.util.EvaluatorUtil;
import org.rascalmpl.vscode.lsp.util.EvaluatorUtil.LSPContext;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;

import io.usethesource.vallang.IBool;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
Expand Down Expand Up @@ -93,6 +95,7 @@ public class InterpretedLanguageContributions implements ILanguageContributions
private final CompletableFuture<@Nullable IFunction> rename;
private final CompletableFuture<@Nullable IFunction> didRenameFiles;
private final CompletableFuture<@Nullable IFunction> selectionRange;
private final CompletableFuture<@Nullable IFunction> formatting;

private final CompletableFuture<Boolean> hasAnalysis;
private final CompletableFuture<Boolean> hasBuild;
Expand All @@ -108,6 +111,7 @@ public class InterpretedLanguageContributions implements ILanguageContributions
private final CompletableFuture<Boolean> hasRename;
private final CompletableFuture<Boolean> hasDidRenameFiles;
private final CompletableFuture<Boolean> hasSelectionRange;
private final CompletableFuture<Boolean> hasFormatting;

private final CompletableFuture<Boolean> specialCaseHighlighting;

Expand Down Expand Up @@ -154,6 +158,7 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen
this.rename = getFunctionFor(contributions, LanguageContributions.RENAME);
this.didRenameFiles = getFunctionFor(contributions, LanguageContributions.DID_RENAME_FILES);
this.selectionRange = getFunctionFor(contributions, LanguageContributions.SELECTION_RANGE);
this.formatting = getFunctionFor(contributions, LanguageContributions.FORMATTING);

// assign boolean properties once instead of wasting futures all the time
this.hasAnalysis = nonNull(this.analysis);
Expand All @@ -170,6 +175,7 @@ public InterpretedLanguageContributions(LanguageParameter lang, IBaseTextDocumen
this.hasRename = nonNull(this.rename);
this.hasDidRenameFiles = nonNull(this.didRenameFiles);
this.hasSelectionRange = nonNull(this.selectionRange);
this.hasFormatting = nonNull(this.formatting);

this.specialCaseHighlighting = getContributionParameter(contributions,
LanguageContributions.PARSING,
Expand Down Expand Up @@ -389,6 +395,12 @@ public InterruptibleFuture<IList> selectionRange(IList focus) {
return execFunction(LanguageContributions.SELECTION_RANGE, selectionRange, VF.list(), focus);
}

@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 @@ -457,6 +469,11 @@ public CompletableFuture<Boolean> hasSelectionRange() {
return hasSelectionRange;
}

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

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

private volatile CompletableFuture<Boolean> hasAnalysis = failedInitialization();
private volatile CompletableFuture<Boolean> hasBuild = failedInitialization();
Expand All @@ -85,6 +86,7 @@ private static final <T> CompletableFuture<T> failedInitialization() {
private volatile CompletableFuture<Boolean> hasRename = failedInitialization();
private volatile CompletableFuture<Boolean> hasDidRenameFiles = failedInitialization();
private volatile CompletableFuture<Boolean> hasSelectionRange = failedInitialization();
private volatile CompletableFuture<Boolean> hasFormatting = failedInitialization();

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

Expand Down Expand Up @@ -162,6 +164,7 @@ private synchronized void calculateRouting() {
prepareRename = findFirstOrDefault(ILanguageContributions::hasRename);
didRenameFiles = findFirstOrDefault(ILanguageContributions::hasDidRenameFiles);
selectionRange = findFirstOrDefault(ILanguageContributions::hasSelectionRange);
formatting = findFirstOrDefault(ILanguageContributions::hasFormatting);

hasAnalysis = anyTrue(ILanguageContributions::hasAnalysis);
hasBuild = anyTrue(ILanguageContributions::hasBuild);
Expand All @@ -177,6 +180,7 @@ private synchronized void calculateRouting() {
hasDidRenameFiles = anyTrue(ILanguageContributions::hasDidRenameFiles);
hasCodeAction = anyTrue(ILanguageContributions::hasCodeAction);
hasSelectionRange = anyTrue(ILanguageContributions::hasSelectionRange);
hasFormatting = anyTrue(ILanguageContributions::hasFormatting);

// 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 @@ -337,6 +341,16 @@ 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 CompletableFuture<Boolean> hasFormatting() {
return hasFormatting;
}

@Override
public CompletableFuture<Boolean> hasCodeAction() {
return hasCodeAction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@
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.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 @@ -100,6 +103,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 @@ -110,6 +114,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.values.IRascalValueFactory;
import org.rascalmpl.values.parsetrees.ITree;
Expand Down Expand Up @@ -236,6 +241,8 @@ public void initializeServerCapabilities(ServerCapabilities result) {
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 @@ -407,7 +414,7 @@ private CompletableFuture<ISourceLocation> computeRenameRange(final ILanguageCon
public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
logger.trace("rename for: {}, new name: {}", params.getTextDocument().getUri(), params.getNewName());
final ILanguageContributions contribs = contributions(params.getTextDocument());
final Position rascalPos = Locations.toRascalPosition(params.getTextDocument(), params.getPosition(), columns);;
final Position rascalPos = Locations.toRascalPosition(params.getTextDocument(), params.getPosition(), columns);
return getFile(params.getTextDocument())
.getCurrentTreeAsync()
.thenApply(Versioned::get)
Expand Down Expand Up @@ -727,6 +734,67 @@ 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);

TextDocumentIdentifier uri = params.getTextDocument();
final ILanguageContributions contribs = contributions(uri);

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

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

TextDocumentIdentifier uri = 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
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()
.thenApply(Versioned::get)
.thenCompose(tree -> {
// just a range
var start = Locations.toRascalPosition(uri, range.getStart(), columns);
var end = Locations.toRascalPosition(uri, range.getEnd(), columns);
// compute the focus list at the end of the range
Copy link
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, start.getLine(), start.getCharacter(), end.getLine(), end.getCharacter());

var opts = getFormattingOptions(params.getOptions());
return contribs.formatting(focus, opts).get();
})
// convert the document changes
.thenApply(l -> DocumentChanges.translateTextEdits(this, l, Map.of())
.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 +786 to +787
Copy link
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 @@ -189,6 +189,16 @@ public InterruptibleFuture<IList> codeAction(IList focus) {
return InterruptibleFuture.completedFuture(VF.list());
}

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

@Override
public CompletableFuture<Boolean> hasFormatting() {
return CompletableFuture.completedFuture(false);
}

@Override
public InterruptibleFuture<ISet> implementation(IList focus) {
return InterruptibleFuture.completedFuture(VF.set());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private LanguageContributions () {}
public static final String IMPLEMENTATION = "implementation";
public static final String CODE_ACTION = "codeAction";
public static final String SELECTION_RANGE = "selectionRange";
public static final String FORMATTING = "formatting";

public static final String RENAME_SERVICE = "renameService";
public static final String PREPARE_RENAME_SERVICE = "prepareRenameService";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ public CompletableFuture<Either3<Range, PrepareRenameResult, PrepareRenameDefaul
IList focus = TreeSearch.computeFocusList(tr, rascalCursorPos.getLine(), rascalCursorPos.getCharacter());
return findQualifiedNameUnderCursor(focus);
})
.thenApply(cur -> DocumentChanges.locationToRange(this, TreeAdapter.getLocation(cur)))
.thenApply(TreeAdapter::getLocation)
.thenApply(loc -> Locations.toRange(loc, columns))
.thenApply(Either3::forFirst);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ public static WorkspaceEdit translateDocumentChanges(final IBaseTextDocumentServ
return wsEdit;
}

private static List<TextEdit> translateTextEdits(final IBaseTextDocumentService docService, IList edits, Map<String, ChangeAnnotation> changeAnnotations) {
public static List<TextEdit> translateTextEdits(final IBaseTextDocumentService docService, IList edits, Map<String, ChangeAnnotation> changeAnnotations) {
return edits.stream()
.map(IConstructor.class::cast)
.map(c -> {
var range = locationToRange(docService, (ISourceLocation) c.get("range"));
var loc = (ISourceLocation) c.get("range");
var range = Locations.toRange(loc, docService.getColumnMap(loc));
var replacement = ((IString) c.get("replacement")).getValue();
// Check annotation
var kw = c.asWithKeywordParameters();
Expand All @@ -125,11 +126,6 @@ private static List<TextEdit> translateTextEdits(final IBaseTextDocumentService
.collect(Collectors.toList());
}

public static Range locationToRange(final IBaseTextDocumentService docService, ISourceLocation loc) {
LineColumnOffsetMap columnMap = docService.getColumnMap(loc);
return Locations.toRange(loc, columnMap);
}

private static String getFileURI(IConstructor edit, String label) {
return ((ISourceLocation) edit.get(label)).getURI().toString();
}
Expand Down
Loading
Loading