summaryrefslogtreecommitdiff
path: root/tools/language-server/src/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tools/language-server/src/index.ts')
-rw-r--r--tools/language-server/src/index.ts115
1 files changed, 115 insertions, 0 deletions
diff --git a/tools/language-server/src/index.ts b/tools/language-server/src/index.ts
new file mode 100644
index 000000000..5e4c736a2
--- /dev/null
+++ b/tools/language-server/src/index.ts
@@ -0,0 +1,115 @@
+import { RequestType, TextDocumentPositionParams, createConnection, ProposedFeatures, TextDocumentSyncKind, TextDocumentIdentifier } from 'vscode-languageserver';
+import { Document, DocumentManager } from './core/documents';
+import { ConfigManager } from './core/config';
+import { PluginHost, CSSPlugin, HTMLPlugin, TypeScriptPlugin, AppCompletionItem, AstroPlugin } from './plugins';
+import { urlToPath } from './utils';
+
+const TagCloseRequest: RequestType<TextDocumentPositionParams, string | null, any> = new RequestType('html/tag');
+
+/**
+ * Starts `astro-languageservice`
+ */
+export function startServer() {
+ let connection = createConnection(ProposedFeatures.all);
+
+ const docManager = new DocumentManager(({ uri, text }: { uri: string; text: string }) => new Document(uri, text));
+ const configManager = new ConfigManager();
+ const pluginHost = new PluginHost(docManager);
+
+ connection.onInitialize((evt) => {
+ const workspaceUris = evt.workspaceFolders?.map((folder) => folder.uri.toString()) ?? [evt.rootUri ?? ''];
+
+ pluginHost.initialize({
+ filterIncompleteCompletions: !evt.initializationOptions?.dontFilterIncompleteCompletions,
+ definitionLinkSupport: !!evt.capabilities.textDocument?.definition?.linkSupport,
+ });
+ pluginHost.register(new AstroPlugin(docManager, configManager, workspaceUris));
+ pluginHost.register(new HTMLPlugin(docManager, configManager));
+ pluginHost.register(new CSSPlugin(docManager, configManager));
+ pluginHost.register(new TypeScriptPlugin(docManager, configManager, workspaceUris));
+ configManager.updateEmmetConfig(evt.initializationOptions?.configuration?.emmet || evt.initializationOptions?.emmetConfig || {});
+
+ return {
+ capabilities: {
+ textDocumentSync: TextDocumentSyncKind.Incremental,
+ foldingRangeProvider: true,
+ definitionProvider: true,
+ completionProvider: {
+ resolveProvider: true,
+ triggerCharacters: [
+ '.',
+ '"',
+ "'",
+ '`',
+ '/',
+ '@',
+ '<',
+
+ // Emmet
+ '>',
+ '*',
+ '#',
+ '$',
+ '+',
+ '^',
+ '(',
+ '[',
+ '@',
+ '-',
+ // No whitespace because
+ // it makes for weird/too many completions
+ // of other completion providers
+
+ // Astro
+ ':',
+ ],
+ },
+ },
+ };
+ });
+
+ // Documents
+ connection.onDidOpenTextDocument((evt) => {
+ docManager.openDocument(evt.textDocument);
+ docManager.markAsOpenedInClient(evt.textDocument.uri);
+ });
+
+ connection.onDidCloseTextDocument((evt) => docManager.closeDocument(evt.textDocument.uri));
+
+ connection.onDidChangeTextDocument((evt) => {
+ docManager.updateDocument(evt.textDocument.uri, evt.contentChanges);
+ });
+
+ connection.onDidChangeWatchedFiles((evt) => {
+ const params = evt.changes
+ .map((change) => ({
+ fileName: urlToPath(change.uri),
+ changeType: change.type,
+ }))
+ .filter((change) => !!change.fileName);
+
+ pluginHost.onWatchFileChanges(params);
+ });
+
+ // Config
+ connection.onDidChangeConfiguration(({ settings }) => {
+ configManager.updateEmmetConfig(settings.emmet);
+ });
+
+ // Features
+ connection.onCompletion((evt) => pluginHost.getCompletions(evt.textDocument, evt.position, evt.context));
+ connection.onCompletionResolve((completionItem) => {
+ const data = (completionItem as AppCompletionItem).data as TextDocumentIdentifier;
+
+ if (!data) {
+ return completionItem;
+ }
+
+ return pluginHost.resolveCompletion(data, completionItem);
+ });
+ connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position));
+ connection.onFoldingRanges((evt) => pluginHost.getFoldingRanges(evt.textDocument));
+ connection.onRequest(TagCloseRequest, (evt: any) => pluginHost.doTagComplete(evt.textDocument, evt.position));
+
+ connection.listen();
+}