From 806c281b3491b165d01be561f005dada24107363 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Sat, 19 Dec 2020 19:22:55 +0100 Subject: 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 Reviewed-by: tazjin Reviewed-by: Profpatsch Tested-by: BuildkiteCI --- .../Profpatsch/emacs-tree-sitter-move/default.nix | 3 + users/Profpatsch/emacs-tree-sitter-move/shell.nix | 16 +++ users/Profpatsch/emacs-tree-sitter-move/test.py | 13 +++ users/Profpatsch/emacs-tree-sitter-move/tmp.el | 13 +++ .../emacs-tree-sitter-move/tree-sitter-move.el | 108 +++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 users/Profpatsch/emacs-tree-sitter-move/default.nix create mode 100644 users/Profpatsch/emacs-tree-sitter-move/shell.nix create mode 100644 users/Profpatsch/emacs-tree-sitter-move/test.py create mode 100644 users/Profpatsch/emacs-tree-sitter-move/tmp.el create mode 100644 users/Profpatsch/emacs-tree-sitter-move/tree-sitter-move.el (limited to 'users') 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 000000000000..fdc059c089b6 --- /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 000000000000..81d622ac73e5 --- /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 000000000000..0f57bae035da --- /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 000000000000..dcd17aa5757f --- /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-") #'tree-sitter-move-right) +;; (define-key evil-normal-state-map (kbd "C-") 'sp-backward-parallel-sexp) +;; (define-key evil-normal-state-map (kbd "C-") 'sp-down-sexp) +;; (define-key evil-normal-state-map (kbd "C-") '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 000000000000..37aafefb0e6e --- /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))) -- cgit 1.4.1