UNPKG

svelte-language-server

Version:
434 lines 23.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startServer = startServer; const vscode_languageserver_1 = require("vscode-languageserver"); const node_1 = require("vscode-languageserver/node"); const DiagnosticsManager_1 = require("./lib/DiagnosticsManager"); const documents_1 = require("./lib/documents"); const semanticTokenLegend_1 = require("./lib/semanticToken/semanticTokenLegend"); const logger_1 = require("./logger"); const ls_config_1 = require("./ls-config"); const plugins_1 = require("./plugins"); const utils_1 = require("./utils"); const FallbackWatcher_1 = require("./lib/FallbackWatcher"); const configLoader_1 = require("./lib/documents/configLoader"); const importPackage_1 = require("./importPackage"); const CodeActionsProvider_1 = require("./plugins/typescript/features/CodeActionsProvider"); const service_1 = require("./plugins/css/service"); const FileSystemProvider_1 = require("./plugins/css/FileSystemProvider"); var TagCloseRequest; (function (TagCloseRequest) { TagCloseRequest.type = new vscode_languageserver_1.RequestType('html/tag'); })(TagCloseRequest || (TagCloseRequest = {})); /** * Starts the language server. * * @param options Options to customize behavior */ function startServer(options) { let connection = options?.connection; if (!connection) { if (process.argv.includes('--stdio')) { console.log = (...args) => { console.warn(...args); }; connection = (0, node_1.createConnection)(process.stdin, process.stdout); } else { connection = (0, node_1.createConnection)(new node_1.IPCMessageReader(process), new node_1.IPCMessageWriter(process)); } } if (options?.logErrorsOnly !== undefined) { logger_1.Logger.setLogErrorsOnly(options.logErrorsOnly); } const docManager = new documents_1.DocumentManager((textDocument) => new documents_1.Document(textDocument.uri, textDocument.text)); const configManager = new ls_config_1.LSConfigManager(); const pluginHost = new plugins_1.PluginHost(docManager); let sveltePlugin = undefined; let watcher; let pendingWatchPatterns = []; let watchDirectory = (patterns) => { pendingWatchPatterns = patterns; }; // Include Svelte files to better deal with scenarios such as switching git branches // where files that are not opened in the client could change const watchExtensions = ['.ts', '.js', '.mts', '.mjs', '.cjs', '.cts', '.json', '.svelte']; const nonRecursiveWatchPattern = '*.{' + watchExtensions.map((ext) => ext.slice(1)).join(',') + '}'; const recursiveWatchPattern = '**/' + nonRecursiveWatchPattern; connection.onInitialize((evt) => { const workspaceUris = evt.workspaceFolders?.map((folder) => folder.uri.toString()) ?? [ evt.rootUri ?? '' ]; logger_1.Logger.log('Initialize language server at ', workspaceUris.join(', ')); if (workspaceUris.length === 0) { logger_1.Logger.error('No workspace path set'); } if (!evt.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration) { const workspacePaths = workspaceUris.map(utils_1.urlToPath).filter(utils_1.isNotNullOrUndefined); watcher = new FallbackWatcher_1.FallbackWatcher(watchExtensions, workspacePaths); watcher.onDidChangeWatchedFiles(onDidChangeWatchedFiles); watchDirectory = (patterns) => { watcher?.watchDirectory(patterns); }; } const isTrusted = evt.initializationOptions?.isTrusted ?? true; configLoader_1.configLoader.setDisabled(!isTrusted); (0, importPackage_1.setIsTrusted)(isTrusted); configManager.updateIsTrusted(isTrusted); if (!isTrusted) { logger_1.Logger.log('Workspace is not trusted, running with reduced capabilities.'); } logger_1.Logger.setDebug((evt.initializationOptions?.configuration?.svelte || evt.initializationOptions?.config)?.['language-server']?.debug); // Backwards-compatible way of setting initialization options (first `||` is the old style) configManager.update(evt.initializationOptions?.configuration?.svelte?.plugin || evt.initializationOptions?.config || {}); configManager.updateTsJsUserPreferences(evt.initializationOptions?.configuration || evt.initializationOptions?.typescriptConfig || {}); configManager.updateTsJsFormateConfig(evt.initializationOptions?.configuration || evt.initializationOptions?.typescriptConfig || {}); configManager.updateEmmetConfig(evt.initializationOptions?.configuration?.emmet || evt.initializationOptions?.emmetConfig || {}); configManager.updatePrettierConfig(evt.initializationOptions?.configuration?.prettier || evt.initializationOptions?.prettierConfig || {}); // no old style as these were added later configManager.updateCssConfig(evt.initializationOptions?.configuration?.css); configManager.updateScssConfig(evt.initializationOptions?.configuration?.scss); configManager.updateLessConfig(evt.initializationOptions?.configuration?.less); configManager.updateHTMLConfig(evt.initializationOptions?.configuration?.html); configManager.updateClientCapabilities(evt.capabilities); pluginHost.initialize({ filterIncompleteCompletions: !evt.initializationOptions?.dontFilterIncompleteCompletions, definitionLinkSupport: !!evt.capabilities.textDocument?.definition?.linkSupport }); // Order of plugin registration matters for FirstNonNull, which affects for example hover info pluginHost.register((sveltePlugin = new plugins_1.SveltePlugin(configManager))); pluginHost.register(new plugins_1.HTMLPlugin(docManager, configManager)); const cssLanguageServices = (0, service_1.createLanguageServices)({ clientCapabilities: evt.capabilities, fileSystemProvider: new FileSystemProvider_1.FileSystemProvider() }); const workspaceFolders = evt.workspaceFolders ?? [{ name: '', uri: evt.rootUri ?? '' }]; pluginHost.register(new plugins_1.CSSPlugin(docManager, configManager, workspaceFolders, cssLanguageServices)); const normalizedWorkspaceUris = workspaceUris.map(utils_1.normalizeUri); pluginHost.register(new plugins_1.TypeScriptPlugin(configManager, new plugins_1.LSAndTSDocResolver(docManager, normalizedWorkspaceUris, configManager, { notifyExceedSizeLimit: notifyTsServiceExceedSizeLimit, onProjectReloaded: refreshCrossFilesSemanticFeatures, watch: true, nonRecursiveWatchPattern, watchDirectory: (patterns) => watchDirectory(patterns), reportConfigError(diagnostic) { connection?.sendDiagnostics(diagnostic); } }), normalizedWorkspaceUris, docManager)); const clientSupportApplyEditCommand = !!evt.capabilities.workspace?.applyEdit; const clientCodeActionCapabilities = evt.capabilities.textDocument?.codeAction; const clientSupportedCodeActionKinds = clientCodeActionCapabilities?.codeActionLiteralSupport?.codeActionKind.valueSet; return { capabilities: { textDocumentSync: { openClose: true, change: vscode_languageserver_1.TextDocumentSyncKind.Incremental, save: { includeText: false } }, hoverProvider: true, completionProvider: { resolveProvider: true, triggerCharacters: [ '.', '"', "'", '`', '/', '@', '<', // Emmet '>', '*', '#', '$', '+', '^', '(', '[', '@', '-', // No whitespace because // it makes for weird/too many completions // of other completion providers // Svelte ':', '|' ], completionItem: { labelDetailsSupport: true } }, documentFormattingProvider: true, colorProvider: true, documentSymbolProvider: true, definitionProvider: true, codeActionProvider: clientCodeActionCapabilities?.codeActionLiteralSupport ? { codeActionKinds: [ vscode_languageserver_1.CodeActionKind.QuickFix, vscode_languageserver_1.CodeActionKind.SourceOrganizeImports, CodeActionsProvider_1.SORT_IMPORT_CODE_ACTION_KIND, CodeActionsProvider_1.ADD_MISSING_IMPORTS_CODE_ACTION_KIND, CodeActionsProvider_1.REMOVE_UNUSED_IMPORTS_CODE_ACTION_KIND, ...(clientSupportApplyEditCommand ? [vscode_languageserver_1.CodeActionKind.Refactor] : []) ].filter(clientSupportedCodeActionKinds && evt.initializationOptions?.shouldFilterCodeActionKind ? (kind) => clientSupportedCodeActionKinds.includes(kind) : () => true), resolveProvider: true } : true, executeCommandProvider: clientSupportApplyEditCommand ? { commands: [ 'function_scope_0', 'function_scope_1', 'function_scope_2', 'function_scope_3', 'constant_scope_0', 'constant_scope_1', 'constant_scope_2', 'constant_scope_3', 'extract_to_svelte_component', 'migrate_to_svelte_5', 'Infer function return type' ] } : undefined, renameProvider: evt.capabilities.textDocument?.rename?.prepareSupport ? { prepareProvider: true } : true, referencesProvider: true, selectionRangeProvider: true, signatureHelpProvider: { triggerCharacters: ['(', ',', '<'], retriggerCharacters: [')'] }, semanticTokensProvider: { legend: (0, semanticTokenLegend_1.getSemanticTokenLegends)(), range: true, full: true }, linkedEditingRangeProvider: true, implementationProvider: true, typeDefinitionProvider: true, inlayHintProvider: true, callHierarchyProvider: true, foldingRangeProvider: true, codeLensProvider: { resolveProvider: true }, documentHighlightProvider: evt.initializationOptions?.configuration?.svelte?.plugin?.svelte ?.documentHighlight?.enable ?? true, workspaceSymbolProvider: true } }; }); connection.onInitialized(() => { if (watcher) { return; } const didChangeWatchedFiles = configManager.getClientCapabilities()?.workspace?.didChangeWatchedFiles; if (!didChangeWatchedFiles?.dynamicRegistration) { return; } // still watch the roots since some files might be referenced but not included in the project connection?.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, { watchers: [ { // Editors have exclude configs, such as VSCode with `files.watcherExclude`, // which means it's safe to watch recursively here globPattern: recursiveWatchPattern } ] }); if (didChangeWatchedFiles.relativePatternSupport) { watchDirectory = (patterns) => { connection?.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, { watchers: patterns.map((pattern) => ({ globPattern: pattern })) }); }; if (pendingWatchPatterns.length) { watchDirectory(pendingWatchPatterns); pendingWatchPatterns = []; } } }); function notifyTsServiceExceedSizeLimit() { connection?.sendNotification(vscode_languageserver_1.ShowMessageNotification.type, { message: 'Svelte language server detected a large amount of JS/Svelte files. ' + 'To enable project-wide JavaScript/TypeScript language features for Svelte files, ' + 'exclude large folders in the tsconfig.json or jsconfig.json with source files that you do not work on.', type: vscode_languageserver_1.MessageType.Warning }); } connection.onExit(() => { watcher?.dispose(); }); connection.onRenameRequest((req) => pluginHost.rename(req.textDocument, req.position, req.newName)); connection.onPrepareRename((req) => pluginHost.prepareRename(req.textDocument, req.position)); connection.onDidChangeConfiguration(({ settings }) => { configManager.update(settings.svelte?.plugin); configManager.updateTsJsUserPreferences(settings); configManager.updateTsJsFormateConfig(settings); configManager.updateEmmetConfig(settings.emmet); configManager.updatePrettierConfig(settings.prettier); configManager.updateCssConfig(settings.css); configManager.updateScssConfig(settings.scss); configManager.updateLessConfig(settings.less); configManager.updateHTMLConfig(settings.html); logger_1.Logger.setDebug(settings.svelte?.['language-server']?.debug); }); connection.onDidOpenTextDocument((evt) => { const document = docManager.openClientDocument(evt.textDocument); diagnosticsManager.scheduleUpdate(document); }); connection.onDidCloseTextDocument((evt) => docManager.closeDocument(evt.textDocument.uri)); connection.onDidChangeTextDocument((evt) => { diagnosticsManager.cancelStarted(evt.textDocument.uri); docManager.updateDocument(evt.textDocument, evt.contentChanges); pluginHost.didUpdateDocument(); }); connection.onHover((evt) => pluginHost.doHover(evt.textDocument, evt.position)); connection.onCompletion((evt, cancellationToken) => pluginHost.getCompletions(evt.textDocument, evt.position, evt.context, cancellationToken)); connection.onDocumentFormatting((evt) => pluginHost.formatDocument(evt.textDocument, evt.options)); connection.onRequest(TagCloseRequest.type, (evt) => pluginHost.doTagComplete(evt.textDocument, evt.position)); connection.onDocumentColor((evt) => pluginHost.getDocumentColors(evt.textDocument)); connection.onColorPresentation((evt) => pluginHost.getColorPresentations(evt.textDocument, evt.range, evt.color)); connection.onDocumentSymbol((evt, cancellationToken) => { if (configManager.getClientCapabilities()?.textDocument?.documentSymbol ?.hierarchicalDocumentSymbolSupport) { return pluginHost.getHierarchicalDocumentSymbols(evt.textDocument, cancellationToken); } else { return pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken); } }); connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position)); connection.onReferences((evt, cancellationToken) => pluginHost.findReferences(evt.textDocument, evt.position, evt.context, cancellationToken)); connection.onCodeAction((evt, cancellationToken) => pluginHost.getCodeActions(evt.textDocument, evt.range, evt.context, cancellationToken)); connection.onExecuteCommand(async (evt) => { const result = await pluginHost.executeCommand({ uri: evt.arguments?.[0] }, evt.command, evt.arguments); if (vscode_languageserver_1.WorkspaceEdit.is(result)) { const edit = { edit: result }; connection?.sendRequest(vscode_languageserver_1.ApplyWorkspaceEditRequest.type.method, edit); } else if (result) { connection?.sendNotification(vscode_languageserver_1.ShowMessageNotification.type.method, { message: result, type: vscode_languageserver_1.MessageType.Error }); } }); connection.onCodeActionResolve((codeAction, cancellationToken) => { const data = codeAction.data; return pluginHost.resolveCodeAction(data, codeAction, cancellationToken); }); connection.onCompletionResolve((completionItem, cancellationToken) => { const data = completionItem.data; if (!data) { return completionItem; } return pluginHost.resolveCompletion(data, completionItem, cancellationToken); }); connection.onSignatureHelp((evt, cancellationToken) => pluginHost.getSignatureHelp(evt.textDocument, evt.position, evt.context, cancellationToken)); connection.onSelectionRanges((evt) => pluginHost.getSelectionRanges(evt.textDocument, evt.positions)); connection.onImplementation((evt, cancellationToken) => pluginHost.getImplementation(evt.textDocument, evt.position, cancellationToken)); connection.onTypeDefinition((evt) => pluginHost.getTypeDefinition(evt.textDocument, evt.position)); connection.onFoldingRanges((evt) => pluginHost.getFoldingRanges(evt.textDocument)); connection.onCodeLens((evt) => pluginHost.getCodeLens(evt.textDocument)); connection.onCodeLensResolve((codeLens, token) => { const data = codeLens.data; if (!data) { return codeLens; } return pluginHost.resolveCodeLens(data, codeLens, token); }); connection.onDocumentHighlight((evt) => pluginHost.findDocumentHighlight(evt.textDocument, evt.position)); connection.onWorkspaceSymbol((evt, token) => pluginHost.getWorkspaceSymbols(evt.query, token)); const diagnosticsManager = new DiagnosticsManager_1.DiagnosticsManager(connection.sendDiagnostics, docManager, pluginHost.getDiagnostics.bind(pluginHost)); const refreshSemanticTokens = (0, utils_1.debounceThrottle)(() => { if (configManager?.getClientCapabilities()?.workspace?.semanticTokens?.refreshSupport) { connection?.sendRequest(vscode_languageserver_1.SemanticTokensRefreshRequest.method); } }, 1500); const refreshInlayHints = (0, utils_1.debounceThrottle)(() => { if (configManager?.getClientCapabilities()?.workspace?.inlayHint?.refreshSupport) { connection?.sendRequest(vscode_languageserver_1.InlayHintRefreshRequest.method); } }, 1500); const refreshCrossFilesSemanticFeatures = () => { diagnosticsManager.scheduleUpdateAll(); refreshInlayHints(); refreshSemanticTokens(); }; connection.onDidChangeWatchedFiles(onDidChangeWatchedFiles); function onDidChangeWatchedFiles(para) { const onWatchFileChangesParas = para.changes .map((change) => ({ fileName: (0, utils_1.urlToPath)(change.uri), changeType: change.type })) .filter((change) => !!change.fileName); pluginHost.onWatchFileChanges(onWatchFileChangesParas); refreshCrossFilesSemanticFeatures(); } connection.onDidSaveTextDocument(diagnosticsManager.scheduleUpdateAll.bind(diagnosticsManager)); connection.onNotification('$/onDidChangeTsOrJsFile', async (e) => { const path = (0, utils_1.urlToPath)(e.uri); if (path) { pluginHost.updateTsOrJsFile(path, e.changes); } refreshCrossFilesSemanticFeatures(); }); connection.onRequest(vscode_languageserver_1.SemanticTokensRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, undefined, cancellationToken)); connection.onRequest(vscode_languageserver_1.SemanticTokensRangeRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, evt.range, cancellationToken)); connection.onRequest(vscode_languageserver_1.LinkedEditingRangeRequest.type, async (evt) => await pluginHost.getLinkedEditingRanges(evt.textDocument, evt.position)); connection.onRequest(vscode_languageserver_1.InlayHintRequest.type, (evt, cancellationToken) => pluginHost.getInlayHints(evt.textDocument, evt.range, cancellationToken)); connection.onRequest(vscode_languageserver_1.CallHierarchyPrepareRequest.type, async (evt, token) => await pluginHost.prepareCallHierarchy(evt.textDocument, evt.position, token)); connection.onRequest(vscode_languageserver_1.CallHierarchyIncomingCallsRequest.type, async (evt, token) => await pluginHost.getIncomingCalls(evt.item, token)); connection.onRequest(vscode_languageserver_1.CallHierarchyOutgoingCallsRequest.type, async (evt, token) => await pluginHost.getOutgoingCalls(evt.item, token)); docManager.on('documentChange', diagnosticsManager.scheduleUpdate.bind(diagnosticsManager)); docManager.on('documentClose', (document) => diagnosticsManager.removeDiagnostics(document)); // The language server protocol does not have a specific "did rename/move files" event, // so we create our own in the extension client and handle it here connection.onRequest('$/getEditsForFileRename', async (fileRename) => pluginHost.updateImports(fileRename)); connection.onRequest('$/getFileReferences', async (uri) => { return pluginHost.fileReferences(uri); }); connection.onRequest('$/getComponentReferences', async (uri) => { return pluginHost.findComponentReferences(uri); }); connection.onRequest('$/getCompiledCode', async (uri) => { const doc = docManager.get(uri); if (!doc) { return null; } const compiled = await sveltePlugin.getCompiledResult(doc); if (compiled) { const js = compiled.js; const css = compiled.css; return { js, css }; } else { return null; } }); connection.listen(); } //# sourceMappingURL=server.js.map