software.amazon.smithy.lsp.SmithyLanguageServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smithy-language-server Show documentation
Show all versions of smithy-language-server Show documentation
Language Server Protocol implementation for Smithy
The newest version!
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.smithy.lsp;
import static java.util.concurrent.CompletableFuture.completedFuture;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionOptions;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DefinitionParams;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.DocumentFilter;
import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.DocumentSymbol;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.ProgressParams;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.Registration;
import org.eclipse.lsp4j.RegistrationParams;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.eclipse.lsp4j.TextDocumentChangeRegistrationOptions;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentRegistrationOptions;
import org.eclipse.lsp4j.TextDocumentSaveRegistrationOptions;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.Unregistration;
import org.eclipse.lsp4j.UnregistrationParams;
import org.eclipse.lsp4j.WorkDoneProgressBegin;
import org.eclipse.lsp4j.WorkDoneProgressEnd;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.WorkspaceFoldersOptions;
import org.eclipse.lsp4j.WorkspaceServerCapabilities;
import org.eclipse.lsp4j.jsonrpc.CompletableFutures;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import software.amazon.smithy.lsp.codeactions.SmithyCodeActions;
import software.amazon.smithy.lsp.diagnostics.SmithyDiagnostics;
import software.amazon.smithy.lsp.document.Document;
import software.amazon.smithy.lsp.document.DocumentParser;
import software.amazon.smithy.lsp.document.DocumentShape;
import software.amazon.smithy.lsp.ext.OpenProject;
import software.amazon.smithy.lsp.ext.SelectorParams;
import software.amazon.smithy.lsp.ext.ServerStatus;
import software.amazon.smithy.lsp.ext.SmithyProtocolExtensions;
import software.amazon.smithy.lsp.handler.CompletionHandler;
import software.amazon.smithy.lsp.handler.DefinitionHandler;
import software.amazon.smithy.lsp.handler.HoverHandler;
import software.amazon.smithy.lsp.project.BuildFile;
import software.amazon.smithy.lsp.project.Project;
import software.amazon.smithy.lsp.project.ProjectAndFile;
import software.amazon.smithy.lsp.project.SmithyFile;
import software.amazon.smithy.lsp.protocol.LspAdapter;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.loader.IdlTokenizer;
import software.amazon.smithy.model.selector.Selector;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.syntax.Formatter;
import software.amazon.smithy.syntax.TokenTree;
import software.amazon.smithy.utils.IoUtils;
public class SmithyLanguageServer implements
LanguageServer, LanguageClientAware, SmithyProtocolExtensions, WorkspaceService, TextDocumentService {
private static final Logger LOGGER = Logger.getLogger(SmithyLanguageServer.class.getName());
private static final ServerCapabilities CAPABILITIES;
static {
ServerCapabilities capabilities = new ServerCapabilities();
capabilities.setCodeActionProvider(new CodeActionOptions(SmithyCodeActions.all()));
capabilities.setDefinitionProvider(true);
capabilities.setDeclarationProvider(true);
capabilities.setCompletionProvider(new CompletionOptions(true, null));
capabilities.setHoverProvider(true);
capabilities.setDocumentFormattingProvider(true);
capabilities.setDocumentSymbolProvider(true);
WorkspaceFoldersOptions workspaceFoldersOptions = new WorkspaceFoldersOptions();
workspaceFoldersOptions.setSupported(true);
capabilities.setWorkspace(new WorkspaceServerCapabilities(workspaceFoldersOptions));
CAPABILITIES = capabilities;
}
private SmithyLanguageClient client;
private final ServerState state = new ServerState();
private Severity minimumSeverity = Severity.WARNING;
private boolean onlyReloadOnSave = false;
private ClientCapabilities clientCapabilities;
SmithyLanguageServer() {
}
Project getFirstProject() {
return state.attachedProjects().values().stream().findFirst().orElse(null);
}
ServerState getState() {
return state;
}
@Override
public void connect(LanguageClient client) {
LOGGER.finest("Connect");
this.client = new SmithyLanguageClient(client);
String message = "smithy-language-server";
try {
Properties props = new Properties();
props.load(Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream("version.properties")));
message += " version " + props.getProperty("version");
} catch (IOException e) {
this.client.error("Failed to load smithy-language-server version: " + e);
}
this.client.info(message + " started.");
}
@Override
public CompletableFuture initialize(InitializeParams params) {
LOGGER.finest("Initialize");
Optional.ofNullable(params.getProcessId())
.flatMap(ProcessHandle::of)
.ifPresent(processHandle -> processHandle.onExit().thenRun(this::exit));
// TODO: Replace with a Gson Type Adapter if more config options are added beyond `logToFile`.
Object initializationOptions = params.getInitializationOptions();
if (initializationOptions instanceof JsonObject jsonObject) {
if (jsonObject.has("diagnostics.minimumSeverity")) {
String configuredMinimumSeverity = jsonObject.get("diagnostics.minimumSeverity").getAsString();
Optional severity = Severity.fromString(configuredMinimumSeverity);
if (severity.isPresent()) {
this.minimumSeverity = severity.get();
} else {
client.error(String.format("""
Invalid value for 'diagnostics.minimumSeverity': %s.
Must be one of %s.""", configuredMinimumSeverity, Arrays.toString(Severity.values())));
}
}
if (jsonObject.has("onlyReloadOnSave")) {
this.onlyReloadOnSave = jsonObject.get("onlyReloadOnSave").getAsBoolean();
client.info("Configured only reload on save: " + this.onlyReloadOnSave);
}
}
if (params.getWorkspaceFolders() != null && !params.getWorkspaceFolders().isEmpty()) {
Either workDoneProgressToken = params.getWorkDoneToken();
if (workDoneProgressToken != null) {
WorkDoneProgressBegin notification = new WorkDoneProgressBegin();
notification.setTitle("Initializing");
client.notifyProgress(new ProgressParams(workDoneProgressToken, Either.forLeft(notification)));
}
for (WorkspaceFolder workspaceFolder : params.getWorkspaceFolders()) {
state.loadWorkspace(workspaceFolder);
}
if (workDoneProgressToken != null) {
WorkDoneProgressEnd notification = new WorkDoneProgressEnd();
client.notifyProgress(new ProgressParams(workDoneProgressToken, Either.forLeft(notification)));
}
}
this.clientCapabilities = params.getCapabilities();
// We register for this capability dynamically otherwise
if (!isDynamicSyncRegistrationSupported()) {
CAPABILITIES.setTextDocumentSync(TextDocumentSyncKind.Incremental);
}
LOGGER.finest("Done initialize");
return completedFuture(new InitializeResult(CAPABILITIES));
}
private void tryInitProject(Path root) {
List loadErrors = state.tryInitProject(root);
if (!loadErrors.isEmpty()) {
String baseMessage = "Failed to load Smithy project at " + root;
StringBuilder errorMessage = new StringBuilder(baseMessage).append(":");
for (Exception error : loadErrors) {
errorMessage.append(System.lineSeparator());
errorMessage.append('\t');
errorMessage.append(error.getMessage());
}
client.error(errorMessage.toString());
String showMessage = baseMessage + ". Check server logs to find out what went wrong.";
client.showMessage(new MessageParams(MessageType.Error, showMessage));
}
}
private CompletableFuture registerSmithyFileWatchers() {
List registrations = FileWatcherRegistrations.getSmithyFileWatcherRegistrations(
state.attachedProjects().values());
return client.registerCapability(new RegistrationParams(registrations));
}
private CompletableFuture unregisterSmithyFileWatchers() {
List unregistrations = FileWatcherRegistrations.getSmithyFileWatcherUnregistrations();
return client.unregisterCapability(new UnregistrationParams(unregistrations));
}
private CompletableFuture registerWorkspaceBuildFileWatchers() {
var registrations = FileWatcherRegistrations.getBuildFileWatcherRegistrations(state.workspacePaths());
return client.registerCapability(new RegistrationParams(registrations));
}
private CompletableFuture unregisterWorkspaceBuildFileWatchers() {
var unregistrations = FileWatcherRegistrations.getBuildFileWatcherUnregistrations();
return client.unregisterCapability(new UnregistrationParams(unregistrations));
}
@Override
public void initialized(InitializedParams params) {
// We have to do this in `initialized` because we can't send dynamic registrations in `initialize`.
if (isDynamicSyncRegistrationSupported()) {
registerDocumentSynchronization();
}
registerWorkspaceBuildFileWatchers();
registerSmithyFileWatchers();
}
private boolean isDynamicSyncRegistrationSupported() {
return clientCapabilities != null
&& clientCapabilities.getTextDocument() != null
&& clientCapabilities.getTextDocument().getSynchronization() != null
&& clientCapabilities.getTextDocument().getSynchronization().getDynamicRegistration();
}
private void registerDocumentSynchronization() {
List buildDocumentSelector = List.of(
new DocumentFilter("json", "file", "**/{smithy-build,.smithy-project}.json"));
var openCloseBuildOpts = new TextDocumentRegistrationOptions(buildDocumentSelector);
var changeBuildOpts = new TextDocumentChangeRegistrationOptions(TextDocumentSyncKind.Incremental);
changeBuildOpts.setDocumentSelector(buildDocumentSelector);
var saveBuildOpts = new TextDocumentSaveRegistrationOptions();
saveBuildOpts.setDocumentSelector(buildDocumentSelector);
saveBuildOpts.setIncludeText(true);
client.registerCapability(new RegistrationParams(List.of(
new Registration("SyncSmithyBuildFiles/Open", "textDocument/didOpen", openCloseBuildOpts),
new Registration("SyncSmithyBuildFiles/Close", "textDocument/didClose", openCloseBuildOpts),
new Registration("SyncSmithyBuildFiles/Change", "textDocument/didChange", changeBuildOpts),
new Registration("SyncSmithyBuildFiles/Save", "textDocument/didSave", saveBuildOpts))));
DocumentFilter smithyFilter = new DocumentFilter();
smithyFilter.setLanguage("smithy");
smithyFilter.setScheme("file");
DocumentFilter smithyJarFilter = new DocumentFilter();
smithyJarFilter.setLanguage("smithy");
smithyJarFilter.setScheme("smithyjar");
List smithyDocumentSelector = List.of(smithyFilter);
var openCloseSmithyOpts = new TextDocumentRegistrationOptions(List.of(smithyFilter, smithyJarFilter));
var changeSmithyOpts = new TextDocumentChangeRegistrationOptions(TextDocumentSyncKind.Incremental);
changeSmithyOpts.setDocumentSelector(smithyDocumentSelector);
var saveSmithyOpts = new TextDocumentSaveRegistrationOptions();
saveSmithyOpts.setDocumentSelector(smithyDocumentSelector);
saveSmithyOpts.setIncludeText(true);
client.registerCapability(new RegistrationParams(List.of(
new Registration("SyncSmithyFiles/Open", "textDocument/didOpen", openCloseSmithyOpts),
new Registration("SyncSmithyFiles/Close", "textDocument/didClose", openCloseSmithyOpts),
new Registration("SyncSmithyFiles/Change", "textDocument/didChange", changeSmithyOpts),
new Registration("SyncSmithyFiles/Save", "textDocument/didSave", saveSmithyOpts))));
}
@Override
public WorkspaceService getWorkspaceService() {
return this;
}
@Override
public TextDocumentService getTextDocumentService() {
return this;
}
@Override
public CompletableFuture
© 2015 - 2025 Weber Informatics LLC | Privacy Policy