UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

338 lines (337 loc) 13.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isUri = isUri; exports.uriToPath = uriToPath; exports.pathToUri = pathToUri; exports.normalizePath = normalizePath; exports.normalizeFsPath = normalizeFsPath; exports.pathToRelativeFunctionName = pathToRelativeFunctionName; exports.uriInUserFunctions = uriInUserFunctions; exports.nodeToSymbolInformation = nodeToSymbolInformation; exports.nodeToDocumentSymbol = nodeToDocumentSymbol; exports.createRange = createRange; exports.toSelectionRange = toSelectionRange; exports.toTextEdit = toTextEdit; exports.toTextDocumentEdit = toTextDocumentEdit; exports.toFoldingRange = toFoldingRange; exports.toLspDocument = toLspDocument; exports.toSymbolKind = toSymbolKind; exports.symbolKindToString = symbolKindToString; exports.symbolKindsFromNode = symbolKindsFromNode; exports.isAutoloadedUriLoadsFunction = isAutoloadedUriLoadsFunction; exports.isAutoloadedUriLoadsFunctionName = isAutoloadedUriLoadsFunctionName; exports.shouldHaveAutoloadedFunction = shouldHaveAutoloadedFunction; exports.formatTextWithIndents = formatTextWithIndents; const vscode_languageserver_1 = require("vscode-languageserver"); const LSP = __importStar(require("vscode-languageserver")); const vscode_uri_1 = require("vscode-uri"); const node_types_1 = require("./node-types"); const document_1 = require("../document"); const tree_sitter_1 = require("./tree-sitter"); const LocationNamespace = __importStar(require("./locations")); const os_1 = __importDefault(require("os")); const builtins_1 = require("./builtins"); const RE_PATHSEP_WINDOWS = /\\/g; function isUri(stringUri) { const uri = vscode_uri_1.URI.parse(stringUri); return vscode_uri_1.URI.isUri(uri); } function uriToPath(stringUri) { const uri = vscode_uri_1.URI.parse(stringUri); return normalizeFsPath(uri.fsPath); } function pathToUri(filepath, documents) { // Yarn v2+ hooks tsserver and sends `zipfile:` URIs for Vim. Keep as-is. // Example: zipfile:///foo/bar/baz.zip::path/to/module if (filepath.startsWith('zipfile:')) { return filepath; } const fileUri = vscode_uri_1.URI.file(filepath); const normalizedFilepath = normalizePath(fileUri.fsPath); const document = documents && documents.get(normalizedFilepath); return document ? document.uri : fileUri.toString(); } /** * Normalizes the file system path. * * On systems other than Windows it should be an no-op. * * On Windows, an input path in a format like "C:/path/file.ts" * will be normalized to "c:/path/file.ts". */ function normalizePath(filePath) { const fsPath = vscode_uri_1.URI.file(filePath).fsPath; return normalizeFsPath(fsPath); } /** * Normalizes the path obtained through the "fsPath" property of the URI module. */ function normalizeFsPath(fsPath) { return fsPath.replace(RE_PATHSEP_WINDOWS, '/'); } function currentVersion(filepath, documents) { const fileUri = vscode_uri_1.URI.file(filepath); const normalizedFilepath = normalizePath(fileUri.fsPath); const document = documents && documents.get(normalizedFilepath); return document ? document.version : null; } function pathToRelativeFunctionName(uriPath) { const relativeName = uriPath.split('/').at(-1) || uriPath; return relativeName.replace('.fish', ''); } function uriInUserFunctions(uri) { const path = uriToPath(uri); return path?.startsWith(`${os_1.default.homedir}/.config/fish`) || false; } function nodeToSymbolInformation(node, uri) { let name = node.text; const kind = toSymbolKind(node); const range = (0, tree_sitter_1.getRange)(node); switch (kind) { case vscode_languageserver_1.SymbolKind.Namespace: name = pathToRelativeFunctionName(uri); break; case vscode_languageserver_1.SymbolKind.Function: case vscode_languageserver_1.SymbolKind.Variable: case vscode_languageserver_1.SymbolKind.File: case vscode_languageserver_1.SymbolKind.Class: case vscode_languageserver_1.SymbolKind.Null: default: break; } return vscode_languageserver_1.SymbolInformation.create(name, kind, range, uri); } function nodeToDocumentSymbol(node) { const name = node.text; let detail = node.text; const kind = toSymbolKind(node); let range = (0, tree_sitter_1.getRange)(node); const selectionRange = (0, tree_sitter_1.getRange)(node); const children = []; let parent = node.parent || node; switch (kind) { case vscode_languageserver_1.SymbolKind.Variable: parent = (0, node_types_1.findParentVariableDefinitionKeyword)(node) || node; detail = (0, tree_sitter_1.getPrecedingComments)(parent); range = (0, tree_sitter_1.getRange)(parent); break; case vscode_languageserver_1.SymbolKind.Function: detail = (0, tree_sitter_1.getPrecedingComments)(parent); range = (0, tree_sitter_1.getRange)(parent); break; case vscode_languageserver_1.SymbolKind.File: case vscode_languageserver_1.SymbolKind.Class: case vscode_languageserver_1.SymbolKind.Namespace: case vscode_languageserver_1.SymbolKind.Null: default: break; } return vscode_languageserver_1.DocumentSymbol.create(name, detail, kind, range, selectionRange, children); } function createRange(startLine, startCharacter, endLine, endCharacter) { return { start: { line: startLine, character: startCharacter, }, end: { line: endLine, character: endCharacter, }, }; } function toSelectionRange(range) { const span = LocationNamespace.Range.toTextSpan(range.range); return vscode_languageserver_1.SelectionRange.create(LocationNamespace.Range.fromTextSpan(span), range.parent ? toSelectionRange(range.parent) : undefined); } function toTextEdit(edit) { return { range: { start: LocationNamespace.Position.fromLocation(edit.start), end: LocationNamespace.Position.fromLocation(edit.end), }, newText: edit.newText, }; } function toTextDocumentEdit(change, documents) { return { textDocument: { uri: pathToUri(change.fileName, documents), version: currentVersion(change.fileName, documents), }, edits: change.textChanges.map(c => toTextEdit(c)), }; } function toFoldingRange(node, document) { let collapsedText = ''; let _kind = vscode_languageserver_1.FoldingRangeKind.Region; if ((0, node_types_1.isFunctionDefinition)(node) || (0, node_types_1.isFunctionDefinitionName)(node.firstNamedChild)) { collapsedText = node.firstNamedChild?.text || node.text.split(' ')[0]?.toString() || ''; } if ((0, node_types_1.isScope)(node)) { collapsedText = node.text; } if ((0, node_types_1.isVariableDefinition)(node)) { collapsedText = node.text; } if ((0, node_types_1.isComment)(node)) { collapsedText = node.text.slice(0, 10); if (node.text.length >= 10) { collapsedText += '...'; } _kind = vscode_languageserver_1.FoldingRangeKind.Comment; } const range = (0, tree_sitter_1.getRangeWithPrecedingComments)(node); const startLine = range.start.line; const endLine = range.end.line > 0 && document.getText(LSP.Range.create(LSP.Position.create(range.end.line, range.end.character - 1), range.end)) === 'end' ? Math.max(range.end.line + 1, range.start.line) : range.end.line; return { ...vscode_languageserver_1.FoldingRange.create(startLine, endLine), collapsedText: collapsedText, kind: vscode_languageserver_1.FoldingRangeKind.Region, }; } function toLspDocument(filename, content) { const doc = vscode_languageserver_1.TextDocumentItem.create(pathToUri(filename), 'fish', 0, content); return new document_1.LspDocument(doc); } function toSymbolKind(node) { if ((0, node_types_1.isVariable)(node)) { return vscode_languageserver_1.SymbolKind.Variable; } else if ((0, node_types_1.isFunctionDefinitionName)(node)) { // change from isFunctionDefinition(node) return vscode_languageserver_1.SymbolKind.Function; } else if ((0, node_types_1.isString)(node)) { return vscode_languageserver_1.SymbolKind.String; } else if ((0, node_types_1.isProgram)(node) || (0, node_types_1.isFunctionDefinition)(node) || (0, node_types_1.isStatement)(node)) { return vscode_languageserver_1.SymbolKind.Namespace; } else if ((0, builtins_1.isBuiltin)(node.text) || (0, node_types_1.isCommandName)(node) || (0, node_types_1.isCommand)(node)) { return vscode_languageserver_1.SymbolKind.Class; } return vscode_languageserver_1.SymbolKind.Null; } /** * Pretty much just for logging a symbol kind */ function symbolKindToString(kind) { switch (kind) { case vscode_languageserver_1.SymbolKind.Variable: return 'variable'; case vscode_languageserver_1.SymbolKind.Function: return 'function'; case vscode_languageserver_1.SymbolKind.String: return 'string'; case vscode_languageserver_1.SymbolKind.Namespace: return 'namespace'; case vscode_languageserver_1.SymbolKind.Class: return 'class'; case vscode_languageserver_1.SymbolKind.Null: return 'null'; default: return 'other'; } } /** * @param node - SyntaxNode toSymbolKind/symbolKindToString wrapper for both * `string` and `number` type * @returns { * kindType: toSymbolKind(node) -> 13 | 12 | 15 | 3 | 5 | 21 * kindString: symbolKindToString(kindType) -> number * } */ function symbolKindsFromNode(node) { const kindType = toSymbolKind(node); const kindString = symbolKindToString(kindType); return { kindType, kindString, }; } /** * Closure for checking if a documents `node.type === function_definition` is * autoloaded. Callback checks the `document.uri` for determining which * autoloaded type to check for. * ___ * @param document - LspDocument to check if it is autoloaded * @returns (n: SyntaxNode) => boolean - true if the document is autoloaded */ function isAutoloadedUriLoadsFunction(document) { const callbackmap = { 'conf.d': (node) => (0, node_types_1.isTopLevelFunctionDefinition)(node) && (0, node_types_1.isFunctionDefinition)(node), config: (node) => (0, node_types_1.isTopLevelFunctionDefinition)(node) && (0, node_types_1.isFunctionDefinition)(node), functions: (node) => { if ((0, node_types_1.isTopLevelFunctionDefinition)(node) && (0, node_types_1.isFunctionDefinition)(node)) { return node.firstChild?.text === document.getAutoLoadName(); } return false; }, completions: (_) => false, '': (_) => false, }; return callbackmap[document.getAutoloadType()]; } /** * The nodes that are considered autoloaded functions are the firstNamedChild of * a `function_definition` node. This is because the firstNamedChild is the * function's name (skipping the `function` keyword). * ___ * Closure for checking if a documents `node.parent.type === function_definition` * is autoloaded. Callback checks the `document.uri` for determining which * autoloaded type to check for. * ___ * @param document - LspDocument to check if it is autoloaded * @returns (n: SyntaxNode) => boolean - true if function name is autoloaded in the document */ function isAutoloadedUriLoadsFunctionName(document) { const callbackmap = { 'conf.d': (node) => (0, node_types_1.isTopLevelFunctionDefinition)(node) && (0, node_types_1.isFunctionDefinitionName)(node), config: (node) => (0, node_types_1.isTopLevelFunctionDefinition)(node) && (0, node_types_1.isFunctionDefinitionName)(node), functions: (node) => { if ((0, node_types_1.isTopLevelFunctionDefinition)(node) && (0, node_types_1.isFunctionDefinitionName)(node)) { return node?.text === document.getAutoLoadName(); } return false; }, completions: (_) => false, '': (_) => false, }; return callbackmap[document.getAutoloadType()]; } function shouldHaveAutoloadedFunction(document) { return 'functions' === document.getAutoloadType(); } function formatTextWithIndents(doc, line, text) { const indent = doc.getIndentAtLine(line); return text .split('\n') .map(line => indent + line) .join('\n'); }