about summary refs log tree commit diff
path: root/users/Profpatsch/emacs-tree-sitter-move
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2020-12-19T18·22+0100
committerProfpatsch <mail@profpatsch.de>2021-01-01T19·03+0000
commit806c281b3491b165d01be561f005dada24107363 (patch)
tree3cea1ceae3892dfa648a16c108aac1cfafcf4a07 /users/Profpatsch/emacs-tree-sitter-move
parentc9b985e7bc20c7dc90f14f47f8709864b05c032f (diff)
feat(users/Profpatsch): moving around via the tree-sitter parse tree r/2042
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
Diffstat (limited to 'users/Profpatsch/emacs-tree-sitter-move')
-rw-r--r--users/Profpatsch/emacs-tree-sitter-move/default.nix3
-rw-r--r--users/Profpatsch/emacs-tree-sitter-move/shell.nix16
-rw-r--r--users/Profpatsch/emacs-tree-sitter-move/test.py13
-rw-r--r--users/Profpatsch/emacs-tree-sitter-move/tmp.el13
-rw-r--r--users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el108
5 files changed, 153 insertions, 0 deletions
diff --git a/users/Profpatsch/emacs-tree-sitter-move/default.nix b/users/Profpatsch/emacs-tree-sitter-move/default.nix
new file mode 100644
index 0000000000..fdc059c089
--- /dev/null
+++ b/users/Profpatsch/emacs-tree-sitter-move/default.nix
@@ -0,0 +1,3 @@
+# nothing yet (TODO: expose shell & tool)
+{...}:
+{}
diff --git a/users/Profpatsch/emacs-tree-sitter-move/shell.nix b/users/Profpatsch/emacs-tree-sitter-move/shell.nix
new file mode 100644
index 0000000000..81d622ac73
--- /dev/null
+++ b/users/Profpatsch/emacs-tree-sitter-move/shell.nix
@@ -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;
+}
diff --git a/users/Profpatsch/emacs-tree-sitter-move/test.py b/users/Profpatsch/emacs-tree-sitter-move/test.py
new file mode 100644
index 0000000000..0f57bae035
--- /dev/null
+++ b/users/Profpatsch/emacs-tree-sitter-move/test.py
@@ -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
diff --git a/users/Profpatsch/emacs-tree-sitter-move/tmp.el b/users/Profpatsch/emacs-tree-sitter-move/tmp.el
new file mode 100644
index 0000000000..dcd17aa575
--- /dev/null
+++ b/users/Profpatsch/emacs-tree-sitter-move/tmp.el
@@ -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)
diff --git a/users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el b/users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el
new file mode 100644
index 0000000000..37aafefb0e
--- /dev/null
+++ b/users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el
@@ -0,0 +1,108 @@
+;; this is not an actual cursor, just a node.
+;; It’s not super efficient, but cursors can’t be *set* to an arbitrary
+;; subnode, because they can’t access the parent otherwise.
+;; We’d 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)))