UNPKG

fish-lsp

Version:

LSP implementation for fish/fish-shell

562 lines (561 loc) 19.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getChildNodes = getChildNodes; exports.getNamedChildNodes = getNamedChildNodes; exports.findChildNodes = findChildNodes; exports.getParentNodes = getParentNodes; exports.findFirstParent = findFirstParent; exports.getSiblingNodes = getSiblingNodes; exports.findFirstNamedSibling = findFirstNamedSibling; exports.findFirstSibling = findFirstSibling; exports.findEnclosingScope = findEnclosingScope; exports.getNodeText = getNodeText; exports.getNodesTextAsSingleLine = getNodesTextAsSingleLine; exports.firstAncestorMatch = firstAncestorMatch; exports.ancestorMatch = ancestorMatch; exports.descendantMatch = descendantMatch; exports.hasNode = hasNode; exports.getNamedNeighbors = getNamedNeighbors; exports.getRange = getRange; exports.findNodeAt = findNodeAt; exports.equalRanges = equalRanges; exports.containsRange = containsRange; exports.getNodeAt = getNodeAt; exports.getNodeAtRange = getNodeAtRange; exports.positionToPoint = positionToPoint; exports.pointToPosition = pointToPosition; exports.rangeToPoint = rangeToPoint; exports.getRangeWithPrecedingComments = getRangeWithPrecedingComments; exports.getPrecedingComments = getPrecedingComments; exports.isFishExtension = isFishExtension; exports.isPositionWithinRange = isPositionWithinRange; exports.isPositionAfter = isPositionAfter; exports.isNodeWithinRange = isNodeWithinRange; exports.nodesGen = nodesGen; exports.getLeafs = getLeafs; exports.getLastLeaf = getLastLeaf; exports.matchesArgument = matchesArgument; exports.getCommandArgumentValue = getCommandArgumentValue; exports.getNodeAtPosition = getNodeAtPosition; //import { existsSync } from 'fs' const path_1 = require("path"); //import { pathToFileURL, URL } from 'url' const vscode_languageserver_1 = require("vscode-languageserver"); // import { pathToFileURL } from 'url'; // typescript-language-server -> https://github.com/typescript-language-server/typescript-language-server/blob/master/src/document.ts // import vscodeUri from 'vscode-uri'; // typescript-language-server -> https://github.com/typescript-language-server/typescript-language-server/blob/master/src/document.ts // import { existsSync } from 'fs-extra'; const node_types_1 = require("./node-types"); /** * Returns an array for all the nodes in the tree (@see also nodesGen) * * @param {SyntaxNode} root - the root node to search from * @returns {SyntaxNode[]} all children of the root node (flattened) */ function getChildNodes(root) { const queue = [root]; const result = []; while (queue.length) { const current = queue.shift(); if (current) { result.push(current); } if (current && current.children) { queue.unshift(...current.children); } } return result; } function getNamedChildNodes(root) { const queue = [root]; const result = []; while (queue.length) { const current = queue.shift(); if (current && current.isNamed) { result.push(current); } if (current && current.children) { queue.unshift(...current.children); } } return result; } function findChildNodes(root, predicate) { const queue = [root]; const result = []; while (queue.length) { const current = queue.shift(); if (current && predicate(current)) { result.push(current); } if (current && current.children) { queue.unshift(...current.children); } } return result; } /** * Gets path to root starting where index 0 is child node passed in. * Format: [child, child.parent, ..., root] * * @param {SyntaxNode} child - the lowest child of root * @returns {SyntaxNode[]} an array of ancestors to the descendent node passed in. */ function getParentNodes(child) { const result = []; let current = child; while (current !== null) { // result.unshift(current); // unshift would be used for [root, ..., child] if (current) { result.push(current); } current = current?.parent || null; } return result; } function findFirstParent(node, predicate) { let current = node.parent; while (current !== null) { if (predicate(current)) { return current; } current = current.parent; } return null; } //const getSiblingFunc = (n: SyntaxNode, direction: 'before' | 'after') => { //if (direction === 'before') return n.nextNamedSibling //if (direction === 'after') return n.previousNamedSibling //return null //} /** * collects all siblings either before or after the current node. * * @param {SyntaxNode} node - the node to start from * @param {'forward' | 'backward'} [lookForward] - if 'backward' (DEFAULT), looks nodes after the current node. * otherwise if specified false, looks for nodes before the current node. * @returns {SyntaxNode[]} - an array of either previous siblings or next siblings. */ function getSiblingNodes(node, predicate, direction = 'before') { const siblingFunc = (n) => direction === 'before' ? n.previousNamedSibling : n.nextNamedSibling; let current = node; const result = []; while (current) { current = siblingFunc(current); if (current && predicate(current)) { result.push(current); } } return result; } /** * Similar to getSiblingNodes. Only returns first node matching the predicate */ function findFirstNamedSibling(node, predicate, direction = 'before') { const siblingFunc = (n) => direction === 'before' ? n.previousNamedSibling : n.nextNamedSibling; let current = node; while (current) { current = siblingFunc(current); if (current && predicate(current)) { return current; } } return null; } function findFirstSibling(node, predicate, direction = 'before') { const siblingFunc = (n) => direction === 'before' ? n.previousSibling : n.nextSibling; let current = node; while (current) { // console.log('curr: ', current.text); current = siblingFunc(current); if (current && predicate(current)) { return current; } } return null; } function findEnclosingScope(node) { let parent = node.parent || node; if ((0, node_types_1.isFunctionDefinitionName)(node)) { return findFirstParent(parent, n => (0, node_types_1.isFunctionDefinition)(n) || (0, node_types_1.isProgram)(n)) || parent; } else if (node.text === 'argv') { parent = findFirstParent(node, n => (0, node_types_1.isFunctionDefinition)(n) || (0, node_types_1.isProgram)(n)) || parent; return (0, node_types_1.isFunctionDefinition)(parent) ? parent.firstNamedChild || parent : parent; } else if ((0, node_types_1.isVariable)(node)) { parent = findFirstParent(node, n => (0, node_types_1.isScope)(n)) || parent; return (0, node_types_1.isForLoop)(parent) && (0, node_types_1.findForLoopVariable)(parent)?.text === node.text ? parent : findFirstParent(node, n => (0, node_types_1.isProgram)(n) || (0, node_types_1.isFunctionDefinitionName)(n)) || parent; } else if ((0, node_types_1.isCommandName)(node)) { return findFirstParent(node, n => (0, node_types_1.isProgram)(n)) || parent; } else { return findFirstParent(node, n => (0, node_types_1.isScope)(n)) || parent; } } // some nodes (such as commands) to get their text, you will need // the first named child. // other nodes (such as flags) need just the actual text. function getNodeText(node) { if (!node) { return ''; } if ((0, node_types_1.isFunctionDefinition)(node)) { return node.child(1)?.text || ''; } if ((0, node_types_1.isVariableDefinition)(node)) { const defVar = (0, node_types_1.findSetDefinedVariable)(node); return defVar.text || ''; } return node.text !== null ? node.text.trim() : ''; } function getNodesTextAsSingleLine(nodes) { let text = ''; for (const node of nodes) { text += ' ' + node.text.split('\n').map(n => n.split(' ').map(n => n.trim()).join(' ')).map(n => n.trim()).join(';'); if (!text.endsWith(';')) { text += ';'; } } return text.replaceAll(/;+/g, ';').trim(); } function firstAncestorMatch(start, predicate) { const ancestors = getParentNodes(start) || []; const root = ancestors[ancestors.length - 1]; //if (ancestors.length < 1) return root; for (const p of ancestors) { if (!predicate(p)) { continue; } return p; } return !!root && predicate(root) ? root : null; } /** * finds all ancestors (parent nodes) of a node that match a predicate * * @param {SyntaxNode} start - the leaf/deepest child node to start searching from * @param {(n: SyntaxNode) => boolean} predicate - a function that returns true if the node matches * @param {boolean} [inclusive] - if true, the start node can be included in the results * @returns {SyntaxNode[]} - an array of nodes that match the predicate */ function ancestorMatch(start, predicate, inclusive = true) { const ancestors = getParentNodes(start) || []; const searchNodes = []; for (const p of ancestors) { searchNodes.push(...getChildNodes(p)); } const results = searchNodes.filter(neighbor => predicate(neighbor)); return inclusive ? results : results.filter(ancestor => ancestor !== start); } /** * searches for all children nodes that match the predicate passed in * * @param {SyntaxNode} start - the root node to search from * @param {(n: SyntaxNode) => boolean} predicate - a function that returns a bollean * incating whether the node passed in matches the search criteria * @param {boolean} inclusive: boolean = true, * @returns {SyntaxNode[]} - all child nodes that match the predicate */ function descendantMatch(start, predicate, inclusive = true) { const descendants = []; descendants.push(...getChildNodes(start)); const results = descendants.filter(descendant => predicate(descendant)); return inclusive ? results : results.filter(r => r !== start); } function hasNode(allNodes, matchNode) { for (const node of allNodes) { if (node.equals(matchNode)) { return true; } } return false; } function getNamedNeighbors(node) { return node.parent?.namedChildren || []; } /** * uses nodesGen to build an array. * * @param {SyntaxNode} node - the root node of a document (where to begin search) * @returns {SyntaxNode[]} - all nodes seen in the document. */ //function getChildrenArray(node: SyntaxNode): SyntaxNode[] { // let root = nodesGen(node); // const result: SyntaxNode[] = []; // // var currNode = root.next(); // while (!currNode.done) { // if (currNode.value) { // result.push(currNode.value) // } // currNode = root.next() // } // return result //} // //function _findNodes(root: SyntaxNode): SyntaxNode[] { // let queue: SyntaxNode[] = [root] // let result: SyntaxNode[] = [] // // while (queue.length) { // let current : SyntaxNode | undefined = queue.pop(); // if (current && current.namedChildCount > 0) { // result.push(current) // queue.unshift(...current.namedChildren.filter(child => child)) // } else if (current && current.childCount > 0){ // result.push(current) // queue.unshift(...current.children) // } else { // continue // } // } // return result //} function getRange(node) { return vscode_languageserver_1.Range.create(node.startPosition.row, node.startPosition.column, node.endPosition.row, node.endPosition.column); } /** * findNodeAt() - handles moving backwards if the cursor is not currently on a node (safer version of getNodeAt) */ function findNodeAt(tree, line, column) { if (!tree.rootNode) { return null; } let currentCol = column; const currentLine = line; while (currentLine > 0) { const currentNode = tree.rootNode.descendantForPosition({ row: currentLine, column: currentCol }); if (currentNode) { return currentNode; } currentCol--; } return tree.rootNode.descendantForPosition({ row: line, column }); } function equalRanges(a, b) { return (a.start.line === b.start.line && a.start.character === b.start.character && a.end.line === b.end.line && a.end.character === b.end.character); } function containsRange(a, b) { return (a.start.line <= b.start.line && a.start.character <= b.start.character && a.end.line >= b.end.line && a.end.character >= b.end.character); } /** * getNodeAt() - handles moving backwards if the cursor i */ function getNodeAt(tree, line, column) { if (!tree.rootNode) { return null; } return tree.rootNode.descendantForPosition({ row: line, column }); } function getNodeAtRange(root, range) { return root.descendantForPosition(positionToPoint(range.start), positionToPoint(range.end)); } // export function getDependencyUrl(node: SyntaxNode, baseUri: string): URL { // let filename = node.children[1]?.text.replaceAll('"', '')! // // // if (!!filename && !filename.endsWith('.fish')) { // filename += '.fish' // } // // const paths = process.env.PATH?.split(':') || [] // // for (const p of paths) { // const url = pathToFileURL(join(p, filename)) // // // if (existsSync(url)) return new URL(url).toString() // } // // return new URL(filename, baseUri) // } function positionToPoint(pos) { return { row: pos.line, column: pos.character, }; } function pointToPosition(point) { return { line: point.row, character: point.column, }; } function rangeToPoint(range) { return { row: range.start.line, column: range.start.character, }; } function getRangeWithPrecedingComments(node) { let currentNode = node.previousNamedSibling; let previousNode = node; while (currentNode?.type === 'comment') { previousNode = currentNode; currentNode = currentNode.previousNamedSibling; } return vscode_languageserver_1.Range.create(pointToPosition(previousNode.startPosition), pointToPosition(node.endPosition)); } function getPrecedingComments(node) { if (!node) { return ''; } const comments = commentsHelper(node); if (!comments) { return node.text; } return [ commentsHelper(node), node.text, ].join('\n'); } function commentsHelper(node) { if (!node) { return ''; } const comment = []; let currentNode = node.previousNamedSibling; while (currentNode?.type === 'comment') { //comment.unshift(currentNode.text.replaceAll(/#+\s?/g, '')) comment.unshift(currentNode.text); currentNode = currentNode.previousNamedSibling; } return comment.join('\n'); } function isFishExtension(path) { const ext = (0, path_1.extname)(path).toLowerCase(); return ext === '.fish'; } function isPositionWithinRange(position, range) { const doesStartInside = position.line > range.start.line || position.line === range.start.line && position.character >= range.start.character; const doesEndInside = position.line < range.end.line || position.line === range.end.line && position.character <= range.end.character; return doesStartInside && doesEndInside; } function isPositionAfter(first, second) { return (first.line < second.line || first.line === second.line && first.character < second.character); } function isNodeWithinRange(node, range) { const doesStartInside = node.startPosition.row > range.start.line || node.startPosition.row === range.start.line && node.startPosition.column >= range.start.character; const doesEndInside = node.endPosition.row < range.end.line || node.endPosition.row === range.end.line && node.endPosition.column <= range.end.character; return doesStartInside && doesEndInside; } function* nodesGen(node) { const queue = [node]; while (queue.length) { const n = queue.shift(); if (!n) { return; } if (n.children.length) { queue.unshift(...n.children); } yield n; } } function getLeafs(node) { function gatherLeafs(node, leafs = []) { if (node.childCount === 0 && node.text !== '') { leafs.push(node); return leafs; } for (const child of node.children) { leafs = gatherLeafs(child, leafs); } return leafs; } return gatherLeafs(node); } function getLastLeaf(node, maxIndex = Infinity) { const allLeafs = getLeafs(node).filter(leaf => leaf.startPosition.column < maxIndex); return allLeafs[allLeafs.length - 1]; } function matchesArgument(node, argName) { const splitNode = node.text.slice(0, node.text.lastIndexOf('=')); if (argName.startsWith('-') && !argName.startsWith('--')) { return splitNode.startsWith('-') && splitNode.includes(argName.slice(1)); } if (argName.startsWith('--')) { return splitNode.startsWith('--') && splitNode.startsWith(argName.slice(2)); } return splitNode === argName; } /** * @param command - the command node to search it's children, accepts both command and command name nodes * @param argName - the name of the argument to search for * @returns the value of the argument if found, otherwise null */ function getCommandArgumentValue(command, argName) { function getCommand(node) { if (node.type === 'name' && node.parent) { return node.parent; } return node; } const arg = getCommand(command).children.find(child => matchesArgument(child, argName)); if (!arg) { return null; } const value = arg.text.includes('=') ? arg : arg.nextSibling; return value; } // Check out awk-language-server: // • https://github.com/Beaglefoot/awk-language-server/tree/master/server/src/utils.ts // • https://github.com/bash-lsp/bash-language-server/blob/main/server/src/util/tree-sitter.ts // //export function getQueriesList(queriesRawText: string): string[] { // const result: string[] = [] // // let openParenCount = 0 // let openBracketCount = 0 // let isQuoteCharMet = false // let isComment = false // let currentQuery = '' // // for (const char of queriesRawText) { // if (char === '"') isQuoteCharMet = !isQuoteCharMet // if (isQuoteCharMet) { // currentQuery += char // continue // } else if (!isQuoteCharMet && char === ';') isComment = true // else if (isComment && char !== '\n') continue // else if (char === '(') openParenCount++ // else if (char === ')') openParenCount-- // else if (char === '[') openBracketCount++ // else if (char === ']') openBracketCount-- // else if (char === '\n') { // isComment = false // // if (!openParenCount && !openBracketCount && currentQuery) { // result.push(currentQuery.trim()) // currentQuery = '' // } // // continue // } // // if (!isComment) currentQuery += char // } // // return result //} function getNodeAtPosition(tree, position) { return tree.rootNode.descendantForPosition({ row: position.line, column: position.character }); }