Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API and implementation of codefixes and code actions for Rascal itself #478

Merged
merged 39 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fac1420
scaffolded an initial implementation of codefixes and code actioons f…
jurgenvinju Oct 16, 2024
05c9f9a
added first example code action for Rascal
jurgenvinju Oct 16, 2024
f60cff5
added default
jurgenvinju Oct 16, 2024
62f627f
final fixes
jurgenvinju Oct 16, 2024
ea0991d
added sort imports action
jurgenvinju Oct 16, 2024
46f76ea
fixed broken indentation
jurgenvinju Oct 16, 2024
55f6845
improved asyncronous command handling
jurgenvinju Oct 16, 2024
f08530e
removed superfluous newlines in sorted imports and extends
jurgenvinju Oct 17, 2024
0d0dcac
better grouping for imports and extends
jurgenvinju Oct 17, 2024
6be8c16
syntax better spaced
jurgenvinju Oct 17, 2024
0ab364f
added \'add missing license\' action for Rascal
jurgenvinju Oct 23, 2024
4a83365
grammar rules have 1 line distance
jurgenvinju Oct 23, 2024
0eea6fc
added UI test for Rascal code actions; factored out common assertLine…
jurgenvinju Oct 23, 2024
d799dc2
changed order of imports for testing purposes
jurgenvinju Oct 23, 2024
1e074bc
use arrow keys to skip the other actions
jurgenvinju Oct 23, 2024
17953b9
fixed typo
jurgenvinju Oct 23, 2024
4eecdaa
Merge branch 'main' into code-actions-for-rascal
jurgenvinju Oct 23, 2024
214dd88
reintroduced method lost in merge
jurgenvinju Oct 23, 2024
ed75af4
changed test because the action is not currently focused, it is the t…
jurgenvinju Oct 23, 2024
31f88d8
bringing mo to the mountain, just test the first action instead of th…
jurgenvinju Oct 24, 2024
e853737
added LICENSE to test project for testing purposes of the code action…
jurgenvinju Oct 24, 2024
eec1297
added LICENSE to original files
jurgenvinju Oct 24, 2024
e021fa5
make sure to revert changes made by code actions before we continue t…
jurgenvinju Oct 24, 2024
78250cd
factoring common functionality between Rascal LSP and Parametric DSL …
jurgenvinju Oct 24, 2024
f8b0702
acted on suggestions in review by @davylandman
jurgenvinju Oct 24, 2024
c07ba2e
fixed bug with command going to the wrong LSP server
jurgenvinju Oct 24, 2024
58997ec
adding license action only appears on module name now
jurgenvinju Oct 24, 2024
0644f00
factoring constants
jurgenvinju Oct 24, 2024
bbba2cb
fixed commands for Rascal itself
jurgenvinju Oct 24, 2024
d5362ac
fixed indentation
jurgenvinju Oct 24, 2024
f38c062
factored offset moving and character-width fixing code into reusable …
jurgenvinju Oct 30, 2024
782a23e
factoring reusable code and wrapping more async code
jurgenvinju Oct 30, 2024
af3284d
factored reusable quick-fix action triggering code from two tests as …
jurgenvinju Oct 30, 2024
8ffab57
found another use for toRascalPosition
jurgenvinju Oct 30, 2024
a6019d9
found another use for toRascalPosition
jurgenvinju Oct 30, 2024
4cfe8df
fixed unused imports
jurgenvinju Oct 30, 2024
69e5563
forgot await
jurgenvinju Oct 30, 2024
88867b7
Update utils.ts
jurgenvinju Oct 30, 2024
4d98fc0
Merge branch 'main' into code-actions-for-rascal
jurgenvinju Oct 30, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ public interface IBaseTextDocumentService extends TextDocumentService {
void unregisterLanguage(LanguageParameter lang);
CompletableFuture<IValue> executeCommand(String languageName, String command);
LineColumnOffsetMap getColumnMap(ISourceLocation file);

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import java.io.IOException;
import java.io.Reader;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -50,7 +49,6 @@
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.CodeLensOptions;
Expand Down Expand Up @@ -90,7 +88,6 @@
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
Expand All @@ -108,11 +105,10 @@
import org.rascalmpl.vscode.lsp.TextDocumentState;
import org.rascalmpl.vscode.lsp.parametric.model.ParametricFileFacts;
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary;
import org.rascalmpl.vscode.lsp.parametric.model.RascalADTs;
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummary.SummaryLookup;
import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter;
import org.rascalmpl.vscode.lsp.util.CodeActions;
import org.rascalmpl.vscode.lsp.util.Diagnostics;
import org.rascalmpl.vscode.lsp.util.DocumentChanges;
import org.rascalmpl.vscode.lsp.util.FoldingRanges;
import org.rascalmpl.vscode.lsp.util.Outline;
import org.rascalmpl.vscode.lsp.util.SemanticTokenizer;
Expand All @@ -132,7 +128,6 @@
import io.usethesource.vallang.IString;
import io.usethesource.vallang.ITuple;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IWithKeywordParameters;
import io.usethesource.vallang.exceptions.FactParseError;

public class ParametricTextDocumentService implements IBaseTextDocumentService, LanguageClientAware {
Expand Down Expand Up @@ -387,80 +382,10 @@ private CodeLens locCommandTupleToCodeLense(String languageName, IValue v) {
ISourceLocation loc = (ISourceLocation) t.get(0);
IConstructor command = (IConstructor) t.get(1);

return new CodeLens(Locations.toRange(loc, columns), constructorToCommand(languageName, command), null);
return new CodeLens(Locations.toRange(loc, columns), CodeActions.constructorToCommand(dedicatedLanguageName, languageName, command), null);
}

private CodeAction constructorToCodeAction(String languageName, IConstructor codeAction) {
IWithKeywordParameters<?> kw = codeAction.asWithKeywordParameters();
IConstructor command = (IConstructor) kw.getParameter(RascalADTs.CodeActionFields.COMMAND);
IString title = (IString) kw.getParameter(RascalADTs.CodeActionFields.TITLE);
IList edits = (IList) kw.getParameter(RascalADTs.CodeActionFields.EDITS);
IConstructor kind = (IConstructor) kw.getParameter(RascalADTs.CodeActionFields.KIND);

// first deal with the defaults. Must mimick what's in util::LanguageServer with the `data CodeAction` declaration
if (title == null) {
if (command != null) {
title = (IString) command.asWithKeywordParameters().getParameter(RascalADTs.CommandFields.TITLE);
}

if (title == null) {
title = IRascalValueFactory.getInstance().string("");
}
}

CodeAction result = new CodeAction(title.getValue());

if (command != null) {
result.setCommand(constructorToCommand(languageName, command));
}

if (edits != null) {
result.setEdit(new WorkspaceEdit(DocumentChanges.translateDocumentChanges(this, edits)));
}

result.setKind(constructorToCodeActionKind(kind));

return result;
}

/**
* Translates `refactor(inline())` to `"refactor.inline"` and `empty()` to `""`, etc.
* `kind == null` signals absence of the optional parameter. This is factorede into
* this private function because otherwise every call has to check it.
*/
private String constructorToCodeActionKind(@Nullable IConstructor kind) {
if (kind == null) {
return CodeActionKind.QuickFix;
}

String name = kind.getName();

if (name.isEmpty()) {
return "";
}
else if (name.length() == 1) {
return name.toUpperCase();
}
else if ("empty".equals(name)) {
return "";
}
else {
var kw = kind.asWithKeywordParameters();
for (String kwn : kw.getParameterNames()) {
String nestedName = constructorToCodeActionKind((IConstructor) kw.getParameter(kwn));
name = name + (nestedName.isEmpty() ? "" : ("." + nestedName));
}
}

return name;
}

private Command constructorToCommand(String languageName, IConstructor command) {
IWithKeywordParameters<?> kw = command.asWithKeywordParameters();
IString possibleTitle = (IString) kw.getParameter(RascalADTs.CommandFields.TITLE);

return new Command(possibleTitle != null ? possibleTitle.getValue() : command.toString(), getRascalMetaCommandName(), Arrays.asList(languageName, command.toString()));
}

private void handleParsingErrors(TextDocumentState file) {
handleParsingErrors(file, file.getCurrentTreeAsync());
Expand Down Expand Up @@ -588,6 +513,7 @@ public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActio
logger.debug("codeActions: {}", params);

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

final var loc = Locations.toLoc(params.getTextDocument());
final var start = params.getRange().getStart();
// convert to Rascal 1-based line
Expand Down Expand Up @@ -629,15 +555,15 @@ public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActio
return codeActions.thenCombine(quickfixes, (actions, quicks) ->
Stream.concat(quicks, actions)
.map(IConstructor.class::cast)
.map(cons -> constructorToCodeAction(contribs.getName(), cons))
.map(cons -> CodeActions.constructorToCodeAction(this, dedicatedLanguageName, contribs.getName(), cons))
.map(cmd -> Either.<Command,CodeAction>forRight(cmd))
.collect(Collectors.toList())
);
}

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

if (!focus.isEmpty()) {
return contribs.codeActions(focus).get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static org.rascalmpl.vscode.lsp.util.EvaluatorUtil.runEvaluator;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -51,6 +52,7 @@
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.rascalmpl.exceptions.Throw;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.functions.IFunction;
Expand All @@ -59,17 +61,19 @@
import org.rascalmpl.vscode.lsp.BaseWorkspaceService;
import org.rascalmpl.vscode.lsp.IBaseLanguageClient;
import org.rascalmpl.vscode.lsp.RascalLSPMonitor;
import org.rascalmpl.vscode.lsp.util.EvaluatorUtil;
import org.rascalmpl.vscode.lsp.util.RascalServices;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;
import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps;

import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISet;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IString;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.exceptions.FactTypeUseException;
import io.usethesource.vallang.io.StandardTextReader;
import io.usethesource.vallang.type.Type;
import io.usethesource.vallang.type.TypeFactory;
import io.usethesource.vallang.type.TypeStore;
Expand All @@ -82,6 +86,9 @@ public class RascalLanguageServices {
private final CompletableFuture<Evaluator> outlineEvaluator;
private final CompletableFuture<Evaluator> semanticEvaluator;
private final CompletableFuture<Evaluator> compilerEvaluator;
private final CompletableFuture<Evaluator> actionEvaluator;

private final CompletableFuture<TypeStore> actionStore;

private final TypeFactory tf = TypeFactory.getInstance();
private final TypeStore store = new TypeStore();
Expand All @@ -100,6 +107,8 @@ public RascalLanguageServices(RascalTextDocumentService docService, BaseWorkspac
outlineEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal outline", monitor, null, false, "lang::rascal::lsp::Outline");
semanticEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal semantics", monitor, null, true, "lang::rascalcore::check::Summary", "lang::rascal::lsp::refactor::Rename");
compilerEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal compiler", monitor, null, true, "lang::rascalcore::check::Checker");
actionEvaluator = makeFutureEvaluator(exec, docService, workspaceService, client, "Rascal actions", monitor, null, true, "lang::rascal::lsp::Actions");
jurgenvinju marked this conversation as resolved.
Show resolved Hide resolved
actionStore = actionEvaluator.thenApply(e -> ((ModuleEnvironment) e.getModule("lang::rascal::lsp::Actions")).getStore());
}

public InterruptibleFuture<@Nullable IConstructor> getSummary(ISourceLocation occ, PathConfig pcfg) {
Expand All @@ -116,6 +125,8 @@ public RascalLanguageServices(RascalTextDocumentService docService, BaseWorkspac
}
}



private static IConstructor addResources(PathConfig pcfg) {
var result = pcfg.asConstructor();
return result.asWithKeywordParameters()
Expand Down Expand Up @@ -250,6 +261,49 @@ public List<CodeLensSuggestion> locateCodeLenses(ITree tree) {
return result;
}

public CompletableFuture<IList> parseCodeActions(String command) {
return actionStore.thenApply(commandStore -> {
try {
var TF = TypeFactory.getInstance();
return (IList) new StandardTextReader().read(VF, commandStore, TF.listType(commandStore.lookupAbstractDataType("CodeAction")), new StringReader(command));
} catch (FactTypeUseException | IOException e) {
// this should never happen as long as the Rascal code
// for creating errors is type-correct. So it _might_ happen
// when running the interpreter on broken code.
throw new IllegalArgumentException("The command could not be parsed", e);
}
});
}

public InterruptibleFuture<IValue> executeCommand(String command) {
logger.debug("executeCommand({}...) (full command value in TRACE level)", () -> command.substring(0, Math.min(10, command.length())));
logger.trace("Full command: {}", command);

return InterruptibleFuture.flatten(parseCommand(command).thenApply(cons ->
EvaluatorUtil.<IValue>runEvaluator(
"executeCommand",
actionEvaluator,
ev -> ev.call("evaluateRascalCommand", cons),
null,
jurgenvinju marked this conversation as resolved.
Show resolved Hide resolved
exec,
true,
client
)
), exec);
}

private CompletableFuture<IConstructor> parseCommand(String command) {
return actionStore.thenApply(commandStore -> {
try {
return (IConstructor) new StandardTextReader().read(VF, commandStore, commandStore.lookupAbstractDataType("Command"), new StringReader(command));
}
catch (FactTypeUseException | IOException e) {
logger.catching(e);
jurgenvinju marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalArgumentException("The command could not be parsed", e);
}
});
}

public static final class CodeLensSuggestion {
private final ISourceLocation line;
private final String commandName;
Expand All @@ -268,7 +322,6 @@ public List<Object> getArguments() {
return arguments;
}


public ISourceLocation getLine() {
return line;
}
Expand All @@ -281,6 +334,13 @@ public String getCommandName() {
public String getShortName() {
return shortName;
}
}

public InterruptibleFuture<IList> codeActions(IList focus, PathConfig pcfg) {
return runEvaluator("Rascal codeActions", actionEvaluator, eval -> {
Map<String,IValue> kws = Map.of("pcfg", pcfg.asConstructor());
return (IList) eval.call("rascalCodeActions", "lang::rascal::lsp::Actions", kws, focus);
},
null, exec, false, client);
jurgenvinju marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading
Loading