summaryrefslogtreecommitdiff
path: root/tools/astro-languageserver/src/index.ts
blob: c834beaf944d7670d8c4bd407672db8415722ef7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { RequestType, TextDocumentPositionParams, createConnection, ProposedFeatures, TextDocumentSyncKind, TextDocumentIdentifier } from 'vscode-languageserver';
import { Document, DocumentManager } from './core/documents';
import { ConfigManager } from './core/config';
import { PluginHost, 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.register(new AstroPlugin(docManager, configManager));
    pluginHost.register(new HTMLPlugin(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,
        completionProvider: {
          resolveProvider: false,
          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.onFoldingRanges((evt) => pluginHost.getFoldingRanges(evt.textDocument));
  connection.onRequest(TagCloseRequest, (evt: any) => pluginHost.doTagComplete(evt.textDocument, evt.position));

  connection.listen();
}