feat(users/Profpatsch): moving around via the tree-sitter parse tree

Has a little setup to get the cursor position and map it onto a tree
sitter node. The current node is saved in a cursor variable, and a
highlight overlay marks the range of the current node in the buffer.

Change-Id: I0af56115f928732e993fbefe978a246ca7c757ee
Reviewed-on: https://cl.tvl.fyi/c/depot/+/2258
Reviewed-by: lukegb <lukegb@tvl.fyi>
Reviewed-by: tazjin <mail@tazj.in>
Reviewed-by: Profpatsch <mail@profpatsch.de>
Tested-by: BuildkiteCI
This commit is contained in:
Profpatsch 2020-12-19 19:22:55 +01:00
parent c9b985e7bc
commit 806c281b34
6 changed files with 154 additions and 0 deletions

View file

@ -149,6 +149,7 @@ let
texlive
thttpd
tree
tree-sitter
unzip
which
writeShellScript

View file

@ -0,0 +1,3 @@
# nothing yet (TODO: expose shell & tool)
{...}:
{}

View file

@ -0,0 +1,16 @@
{ pkgs ? import ../../../third_party {}, ... }:
let
inherit (pkgs) lib;
treeSitterGrammars = pkgs.runCommandLocal "grammars" {} ''
mkdir -p $out/bin
${lib.concatStringsSep "\n"
(lib.mapAttrsToList (name: src: "ln -s ${src}/parser $out/bin/${name}.so") pkgs.tree-sitter.builtGrammars)};
'';
in pkgs.mkShell {
buildInputs = [
pkgs.tree-sitter.builtGrammars.python
];
TREE_SITTER_GRAMMAR_DIR = treeSitterGrammars;
}

View file

@ -0,0 +1,13 @@
(4 + 5 + 5)
def foo(a, b, c)
def bar(a, b):
4
4
4
[1, 4, 5, 10]
def foo():
pass

View file

@ -0,0 +1,13 @@
(tree-sitter-load
'python
(format "%s/bin/python"
(getenv "TREE_SITTER_GRAMMAR_DIR")))
(setq tree-sitter-major-mode-language-alist
'((python-mode . python)))
(define-key evil-normal-state-map (kbd "C-<right>") #'tree-sitter-move-right)
;; (define-key evil-normal-state-map (kbd "C-<left>") 'sp-backward-parallel-sexp)
;; (define-key evil-normal-state-map (kbd "C-<down>") 'sp-down-sexp)
;; (define-key evil-normal-state-map (kbd "C-<up>") 'sp-backward-up-sexp)

View file

@ -0,0 +1,108 @@
;; this is not an actual cursor, just a node.
;; Its not super efficient, but cursors cant be *set* to an arbitrary
;; subnode, because they cant access the parent otherwise.
;; Wed need a way to reset the cursor and walk down to the node?!
(defvar-local tree-sitter-move--cursor nil
"the buffer-local cursor used for movement")
(defvar-local tree-sitter-move--debug-overlay nil
"an overlay used to visually display the region currently marked by the cursor")
;;;;; TODO: should everything use named nodes? Only some things?
;;;;; maybe there should be a pair of functions for everything?
;;;;; For now restrict to named nodes.
(defun tree-sitter-move--setup ()
;; TODO
(progn
(tree-sitter-mode t)
(setq tree-sitter-move--cursor (tsc-root-node tree-sitter-tree))
(add-variable-watcher
'tree-sitter-move--cursor
#'tree-sitter-move--debug-overlay-update)))
(defun tree-sitter-move--debug-overlay-update (sym newval &rest _args)
"variable-watcher to update the debug overlay when the cursor changes"
(let ((start (tsc-node-start-position newval))
(end (tsc-node-end-position newval)))
(symbol-macrolet ((o tree-sitter-move--debug-overlay))
(if o
(move-overlay o start end)
(setq o (make-overlay start end))
(overlay-put o 'face 'highlight)
))))
(defun tree-sitter-move--debug-overlay-teardown ()
"Turn of the overlay visibility and delete the overlay object"
(when tree-sitter-move--debug-overlay
(delete-overlay tree-sitter-move--debug-overlay)
(setq tree-sitter-move--debug-overlay nil)))
(defun tree-sitter-move--teardown ()
(setq tree-sitter-move--cursor nil)
(tree-sitter-move--debug-overlay-teardown)
(tree-sitter-mode nil))
;; Get the syntax node the cursor is on.
(defun tsc-node-named-node-at-point ()
(let ((p (point)))
(tsc-get-named-descendant-for-position-range
(tsc-root-node tree-sitter-tree) p p)))
(defun tsc-get-node-at-point ()
(let ((p (point)))
(tsc-get-descendant-for-position-range
(tsc-root-node tree-sitter-tree) p p)))
(defun tsc-get-first-named-node-with-siblings-up (node)
"Returns the first 'upwards' node that has siblings. That includes the current
node, so if the given node has siblings, it is returned."
(let ((has-siblings-p
(lambda (n)
(> (tsc-count-named-children (tsc-get-parent n))
1)))
(res node))
(while (not (funcall has-siblings-p res))
;; TODO tsc-get-parent is called twice, nicer somehow?
(setq res (tsc-get-parent res)))
res))
(defun tree-sitter-move--set-cursor-to-node (node)
(setq tree-sitter-move--cursor node))
(defun tree-sitter-move--set-cursor-to-node-at-point ()
(tree-sitter-move--set-cursor-to-node (tsc-get-node-at-point)))
(defun tree-sitter-move--move-point-to-node (node)
(set-window-point
(selected-window)
(tsc-node-start-position node)))
;; interactive commands (“do what I expect” section)
(defun tree-sitter-move-right ()
"Moves to the next sibling. If the current node does not have siblings, go
upwards until something has siblings and then move right."
(interactive)
(tree-sitter-move--set-cursor-to-node-at-point)
(let ((next (tsc-get-next-named-sibling
(tsc-get-first-named-node-with-siblings-up tree-sitter-move--cursor))))
(when next
(tree-sitter-move--set-cursor-to-node next)
(tree-sitter-move--move-point-to-node next))))
; mostly stolen from tree-sitter-mode
;;;###autoload
(define-minor-mode tree-sitter-move-mode
"Minor mode to do cursor movements via tree-sitter"
:init-value nil
:lighter " tree-sitter-move"
(if tree-sitter-move-mode
(tree-sitter--error-protect
(progn
(tree-sitter-move--setup))
(setq tree-sitter-move-mode nil)
(tree-sitter-move--teardown))
(lambda ())
(tree-sitter-move--teardown)))