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

Add support for multiple workspaces #160

Merged
merged 3 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
238 changes: 130 additions & 108 deletions src/main/java/software/amazon/smithy/lsp/SmithyLanguageServer.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@

package software.amazon.smithy.lsp.handler;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.lsp4j.DidChangeWatchedFilesRegistrationOptions;
import org.eclipse.lsp4j.FileSystemWatcher;
import org.eclipse.lsp4j.Registration;
import org.eclipse.lsp4j.Unregistration;
import org.eclipse.lsp4j.WatchKind;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import software.amazon.smithy.lsp.project.Project;
import software.amazon.smithy.lsp.project.ProjectConfigLoader;
import software.amazon.smithy.lsp.project.ProjectFilePatterns;

/**
* Handles computing the {@link Registration}s and {@link Unregistration}s for
Expand All @@ -40,48 +37,22 @@ public final class FileWatcherRegistrationHandler {
private static final String WATCH_BUILD_FILES_ID = "WatchSmithyBuildFiles";
private static final String WATCH_SMITHY_FILES_ID = "WatchSmithyFiles";
private static final String WATCH_FILES_METHOD = "workspace/didChangeWatchedFiles";
private static final List<Registration> BUILD_FILE_WATCHER_REGISTRATIONS;
private static final List<Unregistration> SMITHY_FILE_WATCHER_UNREGISTRATIONS;

static {
// smithy-build.json + .smithy-project.json + build exts
int buildFileWatcherCount = 2 + ProjectConfigLoader.SMITHY_BUILD_EXTS.length;
List<FileSystemWatcher> buildFileWatchers = new ArrayList<>(buildFileWatcherCount);
buildFileWatchers.add(new FileSystemWatcher(Either.forLeft(ProjectConfigLoader.SMITHY_BUILD)));
buildFileWatchers.add(new FileSystemWatcher(Either.forLeft(ProjectConfigLoader.SMITHY_PROJECT)));
for (String ext : ProjectConfigLoader.SMITHY_BUILD_EXTS) {
buildFileWatchers.add(new FileSystemWatcher(Either.forLeft(ext)));
}

BUILD_FILE_WATCHER_REGISTRATIONS = Collections.singletonList(new Registration(
WATCH_BUILD_FILES_ID,
WATCH_FILES_METHOD,
new DidChangeWatchedFilesRegistrationOptions(buildFileWatchers)));

SMITHY_FILE_WATCHER_UNREGISTRATIONS = Collections.singletonList(new Unregistration(
WATCH_SMITHY_FILES_ID,
WATCH_FILES_METHOD));
}
private static final List<Unregistration> SMITHY_FILE_WATCHER_UNREGISTRATIONS = List.of(new Unregistration(
WATCH_SMITHY_FILES_ID,
WATCH_FILES_METHOD));

private FileWatcherRegistrationHandler() {
}

/**
* @return The registrations to watch for build file changes
* @param projects The projects to get registrations for
* @return The registrations to watch for Smithy file changes across all projects
*/
public static List<Registration> getBuildFileWatcherRegistrations() {
return BUILD_FILE_WATCHER_REGISTRATIONS;
}

/**
* @param project The Project to get registrations for
* @return The registrations to watch for Smithy file changes
*/
public static List<Registration> getSmithyFileWatcherRegistrations(Project project) {
List<FileSystemWatcher> smithyFileWatchers = Stream.concat(project.sources().stream(),
project.imports().stream())
.map(FileWatcherRegistrationHandler::smithyFileWatcher)
.collect(Collectors.toList());
public static List<Registration> getSmithyFileWatcherRegistrations(Collection<Project> projects) {
List<FileSystemWatcher> smithyFileWatchers = projects.stream()
.flatMap(project -> ProjectFilePatterns.getSmithyFileWatchPatterns(project).stream())
.map(pattern -> new FileSystemWatcher(Either.forLeft(pattern), SMITHY_WATCH_FILE_KIND))
.toList();

return Collections.singletonList(new Registration(
WATCH_SMITHY_FILES_ID,
Expand All @@ -96,17 +67,19 @@ public static List<Unregistration> getSmithyFileWatcherUnregistrations() {
return SMITHY_FILE_WATCHER_UNREGISTRATIONS;
}

private static FileSystemWatcher smithyFileWatcher(Path path) {
String glob = path.toString();
if (!glob.endsWith(".smithy") && !glob.endsWith(".json")) {
// we have a directory
if (glob.endsWith("/")) {
glob = glob + "**/*.{smithy,json}";
} else {
glob = glob + "/**/*.{smithy,json}";
}
}
// Watch the absolute path, either a directory or file
return new FileSystemWatcher(Either.forLeft(glob), SMITHY_WATCH_FILE_KIND);
/**
* @param projects The projects to get registrations for
* @return The registrations to watch for build file changes across all projects
*/
public static List<Registration> getBuildFileWatcherRegistrations(Collection<Project> projects) {
List<FileSystemWatcher> watchers = projects.stream()
.map(ProjectFilePatterns::getBuildFilesWatchPattern)
.map(pattern -> new FileSystemWatcher(Either.forLeft(pattern)))
.toList();

return Collections.singletonList(new Registration(
WATCH_BUILD_FILES_ID,
WATCH_FILES_METHOD,
new DidChangeWatchedFilesRegistrationOptions(watchers)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.lsp.project;

import java.util.Set;

/**
* File changes to a {@link Project}.
*
* @param changedBuildFileUris The uris of changed build files
* @param createdSmithyFileUris The uris of created Smithy files
* @param deletedSmithyFileUris The uris of deleted Smithy files
*/
public record ProjectChanges(
Set<String> changedBuildFileUris,
Set<String> createdSmithyFileUris,
Set<String> deletedSmithyFileUris
) {
/**
* @return Whether there are any changed build files
*/
public boolean hasChangedBuildFiles() {
return !changedBuildFileUris.isEmpty();
milesziemer marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @return Whether there are any changed Smithy files
*/
public boolean hasChangedSmithyFiles() {
return !createdSmithyFileUris.isEmpty() || !deletedSmithyFileUris.isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.lsp.project;

import java.io.File;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Utility methods for creating file patterns corresponding to meaningful
* paths of a {@link Project}, such as sources and build files.
*/
public final class ProjectFilePatterns {
private static final int BUILD_FILE_COUNT = 2 + ProjectConfigLoader.SMITHY_BUILD_EXTS.length;

private ProjectFilePatterns() {
}

/**
* @param project The project to get watch patterns for
* @return A list of glob patterns used to watch Smithy files in the given project
*/
public static List<String> getSmithyFileWatchPatterns(Project project) {
return Stream.concat(project.sources().stream(), project.imports().stream())
.map(path -> getSmithyFilePattern(path, true))
.toList();
}

/**
* @param project The project to get a path matcher for
* @return A path matcher that can check if Smithy files belong to the given project
*/
public static PathMatcher getSmithyFilesPathMatcher(Project project) {
String pattern = Stream.concat(project.sources().stream(), project.imports().stream())
.map(path -> getSmithyFilePattern(path, false))
.collect(Collectors.joining(","));
return FileSystems.getDefault().getPathMatcher("glob:{" + pattern + "}");
}

/**
* @param project The project to get the watch pattern for
* @return A glob pattern used to watch build files in the given project
*/
public static String getBuildFilesWatchPattern(Project project) {
Path root = project.root();
String buildJsonPattern = escapeBackslashes(root.resolve(ProjectConfigLoader.SMITHY_BUILD).toString());
String projectJsonPattern = escapeBackslashes(root.resolve(ProjectConfigLoader.SMITHY_PROJECT).toString());

List<String> patterns = new ArrayList<>(BUILD_FILE_COUNT);
patterns.add(buildJsonPattern);
patterns.add(projectJsonPattern);
for (String buildExt : ProjectConfigLoader.SMITHY_BUILD_EXTS) {
patterns.add(escapeBackslashes(root.resolve(buildExt).toString()));
}

return "{" + String.join(",", patterns) + "}";
}

/**
* @param project The project to get a path matcher for
* @return A path matcher that can check if a file is a build file belonging to the given project
*/
public static PathMatcher getBuildFilesPathMatcher(Project project) {
// Watch pattern is the same as the pattern used for matching
String pattern = getBuildFilesWatchPattern(project);
return FileSystems.getDefault().getPathMatcher("glob:" + pattern);
}

// When computing the pattern used for telling the client which files to watch, we want
// to only watch .smithy/.json files. We don't need in the PathMatcher pattern (and it
// is impossible anyway because we can't have a nested pattern).
private static String getSmithyFilePattern(Path path, boolean isWatcherPattern) {
String glob = path.toString();
if (glob.endsWith(".smithy") || glob.endsWith(".json")) {
return escapeBackslashes(glob);
}

if (!glob.endsWith(File.separator)) {
glob += File.separator;
}
glob += "**";

if (isWatcherPattern) {
glob += ".{smithy,json}";
}

return escapeBackslashes(glob);
}

// In glob patterns, '\' is an escape character, so it needs to escaped
// itself to work as a separator (i.e. for windows)
private static String escapeBackslashes(String pattern) {
return pattern.replace("\\", "\\\\");
}
}
Loading
Loading