fish-lsp
Version:
LSP implementation for fish/fish-shell
338 lines (337 loc) • 13.9 kB
JavaScript
;
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');
}