Skip to content

Commit

Permalink
Improve wordAtPoint
Browse files Browse the repository at this point in the history
Most of the implementation was done by @thomasjm in #119
  • Loading branch information
skovhus committed Mar 3, 2020
1 parent a46509c commit c1b8fc2
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 2 deletions.
14 changes: 13 additions & 1 deletion server/src/analyser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,13 +319,25 @@ export default class Analyzer {
const document = this.uriToTreeSitterTrees[uri]
const contents = this.uriToFileContent[uri]

const node = document.rootNode.namedDescendantForPosition({ row: line, column })
if (!document.rootNode) {
// Check for lacking rootNode (due to failed parse?)
return null
}

const point = { row: line, column }

const node = TreeSitterUtil.namedLeafDescendantForPosition(point, document.rootNode)

if (!node) {
return null
}

const start = node.startIndex
const end = node.endIndex
const name = contents.slice(start, end)

// Hack. Might be a problem with the grammar.
// TODO: Document this with a test case
if (name.endsWith('=')) {
return name.slice(0, name.length - 1)
}
Expand Down
55 changes: 54 additions & 1 deletion server/src/util/tree-sitter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Range } from 'vscode-languageserver/lib/main'
import { SyntaxNode } from 'web-tree-sitter'
import { Point, SyntaxNode } from 'web-tree-sitter'

export function forEach(node: SyntaxNode, cb: (n: SyntaxNode) => void) {
cb(node)
Expand Down Expand Up @@ -52,3 +52,56 @@ export function findParent(
}
return null
}

/**
* Given a tree and a point, try to find the named leaf node that the point corresponds to.
* This is a helper for wordAtPoint, useful in cases where the point occurs at the boundary of
* a word so the normal behavior of "namedDescendantForPosition" does not find the desired leaf.
* For example, if you do
* > (new Parser()).setLanguage(bash).parse("echo 42").rootNode.descendantForIndex(4).text
* then you get 'echo 42', not the leaf node for 'echo'.
*
* TODO: the need for this function might reveal a flaw in tree-sitter-bash.
*/
export function namedLeafDescendantForPosition(
point: Point,
rootNode: SyntaxNode,
): SyntaxNode | null {
const node = rootNode.namedDescendantForPosition(point)

if (node.childCount === 0) {
return node
} else {
// The node wasn't a leaf. Try to figure out what word we should use.
const nodeToUse = searchForLeafNode(point, node)
if (nodeToUse) {
return nodeToUse
} else {
return null
}
}
}

function searchForLeafNode(point: Point, parent: SyntaxNode): SyntaxNode | null {
let child: SyntaxNode = parent.firstNamedChild
while (child) {
if (
pointsEqual(child.startPosition, point) ||
pointsEqual(child.endPosition, point)
) {
if (child.childCount === 0) {
return child
} else {
return searchForLeafNode(point, child)
}
}

child = child.nextNamedSibling
}

return null
}

function pointsEqual(point1: Point, point2: Point) {
return point1.row === point2.row && point1.column === point2.column
}

0 comments on commit c1b8fc2

Please sign in to comment.