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

Improve handling of qualified names when renaming #531

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
78368fc
Prepare qualified names for renaming.
toinehartman Nov 19, 2024
a5e9641
Perform validity checks outside of changed file iteration.
toinehartman Nov 26, 2024
6a27810
Implement renaming fully qualified names.
toinehartman Nov 26, 2024
951d17a
Fix bug where qualified prefix would be renamed based on length only.
toinehartman Nov 26, 2024
3a78abb
Find imports by import path instead of module name.
toinehartman Nov 26, 2024
c57048f
Add self-qualified test.
toinehartman Nov 26, 2024
7b62cb2
Do not support renaming external imports.
toinehartman Nov 27, 2024
d571b20
Implement Rascal workspace service.
toinehartman Nov 28, 2024
9f8d64b
Implement Java-based will-rename-files callback.
toinehartman Nov 28, 2024
2cbe317
Pair services in language server.
toinehartman Dec 2, 2024
081c880
Move module rename analysis from Java to Rascal.
toinehartman Dec 2, 2024
c2f7645
Fix lint error.
toinehartman Dec 2, 2024
be73a8d
Merge remote-tracking branch 'origin/main' into fix/rename-refactorin…
toinehartman Dec 2, 2024
d5ea288
Fix import resolution when renaming module.
toinehartman Dec 4, 2024
2eb84b2
Escape qualified names, unescape file names.
toinehartman Dec 4, 2024
9dc8e7d
Only consider imports in projects that can import the renamed module.
toinehartman Dec 4, 2024
292acd6
Use didRenameFiles callback.
toinehartman Dec 5, 2024
d39ed23
Fix code style.
toinehartman Dec 5, 2024
7d23b17
Add license.
toinehartman Dec 5, 2024
3f80f36
UI test for renaming from explorer callback.
toinehartman Dec 6, 2024
d7865dc
Move executor service to base class.
toinehartman Dec 9, 2024
b918896
Only set file operation capabilities for Rascal.
toinehartman Dec 9, 2024
e4e0956
Improve exception handling.
toinehartman Dec 9, 2024
dd91729
Fix small review suggestions.
toinehartman Dec 9, 2024
a62056b
Move rename files callback to prevent multiple evaluators.
toinehartman Dec 9, 2024
841ef40
Fixes after PR review.
toinehartman Dec 9, 2024
d931b5d
Use for loop instead of streaming.
toinehartman Dec 9, 2024
85b32f3
Reformat for readability.
toinehartman Dec 9, 2024
06d9d24
Remove unused method.
toinehartman Dec 9, 2024
b30ecff
Use tree from editor when possible.
toinehartman Dec 10, 2024
1c8b52f
Fix LSP restart in loop.
toinehartman Dec 10, 2024
4ffc1e7
Fix debug log message.
toinehartman Dec 10, 2024
af85b8a
Simplify exceptions.
toinehartman Dec 10, 2024
fc17d67
Handle interrupt in Rascal callback.
toinehartman Dec 10, 2024
496f17a
Improve `readFile`
toinehartman Dec 11, 2024
15482f2
Do all work inside futures.
toinehartman Dec 11, 2024
d6b64f3
Parallellize path config computation.
toinehartman Dec 11, 2024
1374f6a
Inline for readability.
toinehartman Dec 11, 2024
c4f8d60
Fix indentation.
toinehartman Dec 11, 2024
2257dac
Remove parse tree caching.
toinehartman Dec 12, 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 @@ -41,7 +41,10 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand Down Expand Up @@ -149,17 +152,23 @@ public void write(JsonWriter target, IValue value) throws IOException {
}

@SuppressWarnings({"java:S2189", "java:S106"})
public static void startLanguageServer(Supplier<IBaseTextDocumentService> service, int portNumber) {
public static void startLanguageServer(ExecutorService threadPool, Function<ExecutorService, IBaseTextDocumentService> docServiceProvider, BiFunction<ExecutorService, IBaseTextDocumentService, BaseWorkspaceService> workspaceServiceProvider, int portNumber) {
logger.info("Starting Rascal Language Server: {}", getVersion());

if (DEPLOY_MODE) {
startLSP(constructLSPClient(capturedIn, capturedOut, new ActualLanguageServer(() -> System.exit(0), service.get())));
var docService = docServiceProvider.apply(threadPool);
var wsService = workspaceServiceProvider.apply(threadPool, docService);
docService.pair(wsService);
startLSP(constructLSPClient(capturedIn, capturedOut, new ActualLanguageServer(() -> System.exit(0), docService, wsService)));
}
else {
try (ServerSocket serverSocket = new ServerSocket(portNumber, 0, InetAddress.getByName("127.0.0.1"))) {
logger.info("Rascal LSP server listens on port number: {}", portNumber);
while (true) {
startLSP(constructLSPClient(serverSocket.accept(), new ActualLanguageServer(() -> {}, service.get())));
var docService = docServiceProvider.apply(threadPool);
var wsService = workspaceServiceProvider.apply(threadPool, docService);
docService.pair(wsService);
startLSP(constructLSPClient(serverSocket.accept(), new ActualLanguageServer(() -> {}, docService, wsService)));
toinehartman marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (IOException e) {
logger.fatal("Failure to start TCP server", e);
Expand Down Expand Up @@ -206,10 +215,10 @@ private static class ActualLanguageServer implements IBaseLanguageServerExtensi
private final Runnable onExit;
private IDEServicesConfiguration ideServicesConfiguration;

private ActualLanguageServer(Runnable onExit, IBaseTextDocumentService lspDocumentService) {
private ActualLanguageServer(Runnable onExit, IBaseTextDocumentService lspDocumentService, BaseWorkspaceService lspWorkspaceService) {
this.onExit = onExit;
this.lspDocumentService = lspDocumentService;
this.lspWorkspaceService = new BaseWorkspaceService(lspDocumentService);
this.lspWorkspaceService = lspWorkspaceService;
reg.registerLogical(new ProjectURIResolver(this::resolveProjectLocation));
reg.registerLogical(new TargetURIResolver(this::resolveProjectLocation));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;

import com.google.gson.JsonPrimitive;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.eclipse.lsp4j.ClientCapabilities;
Expand All @@ -50,13 +52,15 @@ public class BaseWorkspaceService implements WorkspaceService, LanguageClientAwa
public static final String RASCAL_META_COMMAND = "rascal-meta-command";
public static final String RASCAL_COMMAND = "rascal-command";

private final ExecutorService ownExecuter;

private final IBaseTextDocumentService documentService;
private final CopyOnWriteArrayList<WorkspaceFolder> workspaceFolders = new CopyOnWriteArrayList<>();


BaseWorkspaceService(IBaseTextDocumentService documentService) {
protected BaseWorkspaceService(ExecutorService exec, IBaseTextDocumentService documentService) {
this.documentService = documentService;
documentService.pair(this);
this.ownExecuter = exec;
}


Expand Down Expand Up @@ -120,6 +124,9 @@ public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
}


protected final ExecutorService getExecuter() {
return ownExecuter;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@
*/
package org.rascalmpl.vscode.lsp;

import java.util.Set;
import java.util.concurrent.CompletableFuture;

import org.eclipse.lsp4j.RenameFilesParams;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter;
import org.rascalmpl.vscode.lsp.util.locations.LineColumnOffsetMap;

import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IValue;

Expand All @@ -45,4 +49,5 @@ public interface IBaseTextDocumentService extends TextDocumentService {
CompletableFuture<IValue> executeCommand(String languageName, String command);
LineColumnOffsetMap getColumnMap(ISourceLocation file);

default void didRenameFiles(RenameFilesParams params, Set<ISourceLocation> workspaceFolders) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
*/
package org.rascalmpl.vscode.lsp.parametric;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.rascalmpl.vscode.lsp.BaseLanguageServer;
import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.LanguageParameter;

import com.google.gson.GsonBuilder;

public class ParametricLanguageServer extends BaseLanguageServer {
Expand All @@ -43,9 +43,10 @@ public static void main(String[] args) {
dedicatedLanguage = null;
}

startLanguageServer(() -> {
ExecutorService threadPool = Executors.newCachedThreadPool();
return new ParametricTextDocumentService(threadPool, dedicatedLanguage);
}, 9999);
startLanguageServer(Executors.newCachedThreadPool()
, threadPool -> new ParametricTextDocumentService(threadPool, dedicatedLanguage)
, ParametricWorkspaceService::new
, 9999
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public LineColumnOffsetMap getColumnMap(ISourceLocation file) {
return columns.get(file);
}

private String getContents(ISourceLocation file) {
public String getContents(ISourceLocation file) {
file = file.top();
TextDocumentState ideState = files.get(file);
if (ideState != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2018-2023, NWO-I CWI and Swat.engineering
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.rascalmpl.vscode.lsp.parametric;

import java.util.concurrent.ExecutorService;

import org.rascalmpl.vscode.lsp.BaseWorkspaceService;
import org.rascalmpl.vscode.lsp.IBaseTextDocumentService;

public class ParametricWorkspaceService extends BaseWorkspaceService {
ParametricWorkspaceService(ExecutorService exec, IBaseTextDocumentService docService) {
super(exec, docService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
*/
package org.rascalmpl.vscode.lsp.rascal;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.logging.log4j.LogManager;
Expand All @@ -36,10 +35,7 @@
public class RascalLanguageServer extends BaseLanguageServer {
public static void main(String[] args) {
try {
startLanguageServer(() -> {
ExecutorService threadPool = Executors.newCachedThreadPool();
return new RascalTextDocumentService(threadPool);
}, 8888);
startLanguageServer(Executors.newCachedThreadPool(), RascalTextDocumentService::new, RascalWorkspaceService::new, 8888);
}
catch (Throwable e) {
final Logger logger = LogManager.getLogger(RascalLanguageServer.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,29 @@
import static org.rascalmpl.vscode.lsp.util.EvaluatorUtil.runEvaluator;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.eclipse.lsp4j.FileRename;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
Expand All @@ -54,15 +61,19 @@
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.functions.IFunction;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.values.parsetrees.TreeAdapter;
import org.rascalmpl.vscode.lsp.BaseWorkspaceService;
import org.rascalmpl.vscode.lsp.IBaseLanguageClient;
import org.rascalmpl.vscode.lsp.RascalLSPMonitor;
import org.rascalmpl.vscode.lsp.TextDocumentState;
import org.rascalmpl.vscode.lsp.util.EvaluatorUtil;
import org.rascalmpl.vscode.lsp.util.RascalServices;
import org.rascalmpl.vscode.lsp.util.Versioned;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;
import org.rascalmpl.vscode.lsp.util.locations.ColumnMaps;
import org.rascalmpl.vscode.lsp.util.locations.Locations;
Expand Down Expand Up @@ -235,6 +246,84 @@ public InterruptibleFuture<ITuple> getRename(ITree module, Position cursor, Set<
}, VF.tuple(VF.list(), VF.map()), exec, false, client);
}

private ISourceLocation sourceLocationFromUri(String uri) {
try {
return URIUtil.createFromURI(uri);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

private Optional<ISourceLocation> findContainingWorkspaceFolder(ISourceLocation loc, List<ISourceLocation> workspaceFolders) {
return workspaceFolders.stream()
.filter(folderLoc -> URIUtil.isParentOf(folderLoc, loc))
.findFirst();
}

private ISet qualfiedNameChangesFromRenames(List<FileRename> renames, Set<ISourceLocation> workspaceFolders, Function<ISourceLocation, PathConfig> getPathConfig) {
// Sort workspace folders so we get the most specific folders first
List<ISourceLocation> sortedWorkspaceFolders = workspaceFolders.stream()
.sorted((o1, o2) -> o1.toString().compareTo(o2.toString()))
.collect(Collectors.toList());

return renames.parallelStream()
.map(rename -> {
ISourceLocation currentLoc = sourceLocationFromUri(rename.getOldUri());
ISourceLocation newLoc = sourceLocationFromUri(rename.getNewUri());

ISourceLocation currentWsFolder = findContainingWorkspaceFolder(currentLoc, sortedWorkspaceFolders)
.orElseThrow(() -> new RuntimeException(String.format("Cannot automatically change uses of %s, since it is outside the current workspace.", currentLoc)));
Copy link
Member

Choose a reason for hiding this comment

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

nit: if we move this orElseThrow into the findContainingWorkspaceFolder we don't have to repeat it at the call site. (since it happens twice)


ISourceLocation newWsFolder = findContainingWorkspaceFolder(newLoc, sortedWorkspaceFolders)
.orElseThrow(() -> new RuntimeException(String.format("Cannot automatically change uses of %s, since it is outside the current workspace.", newLoc)));

if (!currentWsFolder.equals(newWsFolder)) {
String commonProjPrefix = StringUtils.getCommonPrefix(currentWsFolder.toString(), newWsFolder.toString());
String currentProject = StringUtils.removeStart(currentWsFolder.toString(), commonProjPrefix);
String newProject = StringUtils.removeStart(newWsFolder.toString(), commonProjPrefix);

throw new RuntimeException(String.format("Cannot automatically change uses of %s, since moving files between projects (from %s to %s) is not supported", currentLoc, currentProject, newProject));
}

PathConfig pcfg = getPathConfig.apply(currentWsFolder);
try {
IString currentName = VF.string(pcfg.getModuleName(currentLoc));
IString newName = VF.string(pcfg.getModuleName(newLoc));

return VF.tuple(currentName, newName, addResources(pcfg));
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
})
.collect(VF.setWriter());
}

private static String readFile(ISourceLocation loc) {
URIResolverRegistry reg = URIResolverRegistry.getInstance();
Copy link
Member

Choose a reason for hiding this comment

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

nit: reg can be inlined.

try (Reader reader = reg.getCharacterReader(loc)) {
return IOUtils.toString(reader);
} catch (IOException e) {
throw new RuntimeException(String.format("Error reading file %s", loc));
}
}

public InterruptibleFuture<ITuple> getModuleRenames(List<FileRename> fileRenames, Set<ISourceLocation> workspaceFolders, Function<ISourceLocation, PathConfig> getPathConfig, Map<ISourceLocation, TextDocumentState> documents) {
var emptyResult = VF.tuple(VF.list(), VF.map());
if (fileRenames.isEmpty()) {
return InterruptibleFuture.completedFuture(emptyResult);
}

return runEvaluator("Rascal module rename", semanticEvaluator, eval -> {
IFunction rascalGetPathConfig = eval.getFunctionValueFactory().function(getPathConfigType, (t, u) -> addResources(getPathConfig.apply((ISourceLocation) t[0])));
ISet qualifiedNameChanges = qualfiedNameChangesFromRenames(fileRenames, workspaceFolders, getPathConfig);
Copy link
Member

Choose a reason for hiding this comment

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

ideally we keep the work inside the eval lock as small as possible. especially since the qualifiedNameChangesFromRenames can end up callin maven (where there is some different locking happening in the latest version of rascal) we would hate to see a deadlock there.

So I would prefer if we can move this code to a completable future "before" this one, and only after that compose that to the runEvaluator closure.

try {
return (ITuple) eval.call("rascalRenameModule", qualifiedNameChanges, VF.set(workspaceFolders.toArray(ISourceLocation[]::new)), rascalGetPathConfig);
} catch (Throw e) {
throw new RuntimeException(e.getMessage());
}
}, emptyResult, exec, false, client);
}


public CompletableFuture<ITree> parseSourceFile(ISourceLocation loc, String input) {
return CompletableFuture.supplyAsync(() -> RascalServices.parseRascalModule(loc, input.toCharArray()), exec);
Expand Down
Loading
Loading