>From 2c82301209ce8a41e68c8c6d25393c01aea55cfd Mon Sep 17 00:00:00 2001 From: dickmao Date: Tue, 2 Nov 2021 14:43:22 -0400 Subject: [PATCH] tree-sitter-lock-mode Joint work with casouri@gmail.com. * configure.ac (WIDE_EMACS_INT): Add tree-sitter infra. * lisp/font-core.el (font-lock-default-function): Use if-then as it was intended. (turn-on-font-lock-if-desired): Font lock was being turned on in minibuffers. * lisp/font-lock.el (font-lock-initial-fontify): De-obfuscate. (font-lock-mode-internal): Use if-then as it was intended. (font-lock-support-mode): Pivot boolean. (tree-sitter-lock-mode): Forward declare. (tree-sitter-fontify-region): Forward declare. (font-lock-turn-on-thing-lock): De-obfuscate. (font-lock-turn-off-thing-lock): De-obfuscate. (font-lock-ensure-function): Was not ensuring. (font-lock-ensure): Set a flag that should really be eliminated. (font-lock-after-change-function): Use when. (font-lock-compile-keywords): Whitespace. (font-lock-set-defaults): De-obfuscate. * lisp/jit-lock.el (jit-lock-function): Demorgan. (jit-lock--run-functions): "Loose" was a noop. (jit-lock-fontify-now): "Loose" was a noop. (jit-lock-force-redisplay): Apparently, this was written before for-loops were invented. * lisp/loadup.el ("tree-sitter"): Register tree-sitter. * lisp/progmodes/cc-fonts.el (c-font-lock-complex-decl-prepare): c-beginning-of-syntax was a noop. (c-font-lock-ml-strings): Whitespace. (c-force-redisplay): c-beginning-of-syntax was a noop. * lisp/progmodes/cc-mode.el (c-font-lock-init): c-beginning-of-syntax was a noop. * lisp/ps-print.el (jit-lock-fontify-now): c-beginning-of-syntax was a noop. * src/Makefile.in (TREE_SITTER_LIBS): Add tree sitter infra. * src/alloc.c (cleanup_vector): Memory manage tree sitters. * src/casefiddle.c (casify_region): Update tree. * src/data.c (Ftype_of): Add tree sitter infra. (syms_of_data): Add tree sitter infra. * src/emacs.c (main): Add tree sitter infra. * src/eval.c (define_error): Allow defining errors at the C level. * src/fileio.c (write_region): Whitespace. * src/insdel.c (insert_1_both): Update tree. (insert_from_string_1): Update tree. (insert_from_gap): Update tree. (insert_from_buffer_1): Update tree. (adjust_after_replace): Update tree. (replace_range): Update tree. (del_range_2): Update tree. * src/lisp.h (DEFINE_GDB_SYMBOL_BEGIN): Add tree sitter infra. * src/print.c (print_vectorlike): Add tree sitter infra. * src/xdisp.c (handle_face_prop): Whitespace. (next_element_from_buffer): Demorgan. (syms_of_xdisp): Making the fontified property nonsticky saves you the contortions of having to remove it in jit-lock-after-change. * test/src/tree-sitter-resources/queries/c/highlights.scm ("case"): Test resource for tree-sitter. --- configure.ac | 19 + lisp/font-core.el | 40 +- lisp/font-lock.el | 157 ++-- lisp/jit-lock.el | 147 +--- lisp/loadup.el | 1 + lisp/progmodes/cc-engine.el | 38 +- lisp/progmodes/cc-fonts.el | 9 +- lisp/progmodes/cc-mode.el | 1 - lisp/ps-print.el | 2 +- lisp/tree-sitter.el | 141 ++++ src/Makefile.in | 11 +- src/alloc.c | 23 + src/casefiddle.c | 12 + src/data.c | 4 + src/editfns.c | 2 +- src/emacs.c | 7 + src/eval.c | 13 + src/fileio.c | 2 +- src/insdel.c | 31 + src/json.c | 16 - src/lisp.h | 11 + src/print.c | 14 + src/tree-sitter.c | 717 ++++++++++++++++++ src/tree-sitter.h | 63 ++ src/xdisp.c | 10 +- test/src/tree-sitter-resources/bin/c.so | Bin 0 -> 399152 bytes .../queries/c/highlights.scm | 133 ++++ test/src/tree-sitter-tests.el | 111 +++ 28 files changed, 1436 insertions(+), 299 deletions(-) create mode 100644 lisp/tree-sitter.el create mode 100644 src/tree-sitter.c create mode 100644 src/tree-sitter.h create mode 100755 test/src/tree-sitter-resources/bin/c.so create mode 100644 test/src/tree-sitter-resources/queries/c/highlights.scm create mode 100644 test/src/tree-sitter-tests.el diff --git a/configure.ac b/configure.ac index 33e7037afe..1ba9dfcb55 100644 --- a/configure.ac +++ b/configure.ac @@ -455,6 +455,7 @@ AC_DEFUN OPTION_DEFAULT_OFF([imagemagick],[compile with ImageMagick image support]) OPTION_DEFAULT_ON([native-image-api], [don't use native image APIs (GDI+ on Windows)]) OPTION_DEFAULT_IFAVAILABLE([json], [compile with native JSON support]) +OPTION_DEFAULT_OFF([tree-sitter], [compile with tree-sitter]) OPTION_DEFAULT_ON([xft],[don't use XFT for anti aliased fonts]) OPTION_DEFAULT_ON([harfbuzz],[don't use HarfBuzz for text shaping]) @@ -2997,6 +2998,23 @@ AC_DEFUN AC_SUBST(JSON_CFLAGS) AC_SUBST(JSON_OBJ) +HAVE_TREE_SITTER=no +TREE_SITTER_OBJ= + +if test "${with_tree_sitter}" == "yes"; then + EMACS_CHECK_MODULES([TREE_SITTER], [tree-sitter = 0.6.3alpha]) + if test "${HAVE_TREE_SITTER}" = yes; then + AC_DEFINE(HAVE_TREE_SITTER, 1, [Define if using tree-sitter.]) + TREE_SITTER_LIBS="-ltree-sitter -ltree_sitter_highlight" + TREE_SITTER_OBJ="tree-sitter.o" + fi +fi + +AC_SUBST(HAVE_TREE_SITTER) +AC_SUBST(TREE_SITTER_LIBS) +AC_SUBST(TREE_SITTER_CFLAGS) +AC_SUBST(TREE_SITTER_OBJ) + NOTIFY_OBJ= NOTIFY_SUMMARY=no @@ -5984,6 +6002,7 @@ AC_DEFUN Does Emacs use -lxft? ${HAVE_XFT} Does Emacs use -lsystemd? ${HAVE_LIBSYSTEMD} Does Emacs use -ljansson? ${HAVE_JSON} + Does Emacs use -ltree-sitter? ${HAVE_TREE_SITTER} Does Emacs use the GMP library? ${HAVE_GMP} Does Emacs directly use zlib? ${HAVE_ZLIB} Does Emacs have dynamic modules support? ${HAVE_MODULES} diff --git a/lisp/font-core.el b/lisp/font-core.el index 95bf46c9b8..307593765e 100644 --- a/lisp/font-core.el +++ b/lisp/font-core.el @@ -158,25 +158,22 @@ font-lock-defontify (defvar font-lock-set-defaults) (defun font-lock-default-function (mode) - ;; Turn on Font Lock mode. - (when mode - (setq-local char-property-alias-alist - (copy-tree char-property-alias-alist)) - ;; Add `font-lock-face' as an alias for the `face' property. - (let ((elt (assq 'face char-property-alias-alist))) - (if elt - (unless (memq 'font-lock-face (cdr elt)) - (setcdr elt (nconc (cdr elt) (list 'font-lock-face)))) - (push (list 'face 'font-lock-face) char-property-alias-alist)))) - ;; Turn off Font Lock mode. - (unless mode - ;; Remove `font-lock-face' as an alias for the `face' property. + (if mode + (progn + (setq-local char-property-alias-alist + (copy-tree char-property-alias-alist)) + ;; Add `font-lock-face' as an alias for the `face' property. + (let ((elt (assq 'face char-property-alias-alist))) + (if elt + (unless (memq 'font-lock-face (cdr elt)) + (setcdr elt (nconc (cdr elt) (list 'font-lock-face)))) + (push (list 'face 'font-lock-face) char-property-alias-alist)))) (setq-local char-property-alias-alist (copy-tree char-property-alias-alist)) (let ((elt (assq 'face char-property-alias-alist))) (when elt (setcdr elt (remq 'font-lock-face (cdr elt))) - (when (null (cdr elt)) + (unless (cdr elt) (setq char-property-alias-alist (delq elt char-property-alias-alist)))))) @@ -258,13 +255,14 @@ font-lock-global-modes :group 'font-lock) (defun turn-on-font-lock-if-desired () - (when (cond ((eq font-lock-global-modes t) - t) - ((eq (car-safe font-lock-global-modes) 'not) - (not (memq major-mode (cdr font-lock-global-modes)))) - (t (memq major-mode font-lock-global-modes))) - (let (inhibit-quit) - (turn-on-font-lock)))) + (unless (minibufferp) + (when (cond ((eq font-lock-global-modes t) + t) + ((eq (car-safe font-lock-global-modes) 'not) + (not (memq major-mode (cdr font-lock-global-modes)))) + (t (memq major-mode font-lock-global-modes))) + (let (inhibit-quit) + (turn-on-font-lock))))) (define-globalized-minor-mode global-font-lock-mode font-lock-mode turn-on-font-lock-if-desired diff --git a/lisp/font-lock.el b/lisp/font-lock.el index a4ab897f6f..f6743977f7 100644 --- a/lisp/font-lock.el +++ b/lisp/font-lock.el @@ -650,28 +650,24 @@ font-lock-specified-p (not (eq font-lock-major-mode major-mode))))) (defun font-lock-initial-fontify () - ;; The first fontification after turning the mode on. This must - ;; only be called after the mode hooks have been run. + "The :after-hook of `font-lock-mode' minor mode." (when (and font-lock-mode - (font-lock-specified-p t)) + (font-lock-specified-p t) + (not font-lock-fontified)) (let ((max-size (font-lock-value-in-major-mode font-lock-maximum-size))) - (cond (font-lock-fontified - nil) - ((or (null max-size) (> max-size (buffer-size))) - (with-no-warnings (font-lock-fontify-buffer))) - (font-lock-verbose - (message "Fontifying %s...buffer size greater than font-lock-maximum-size" - (buffer-name))))))) + (if (or (not (fixnump max-size)) (<= (buffer-size) max-size)) + (font-lock-ensure) + (when font-lock-verbose + (message "Fontifying %s...buffer size greater than font-lock-maximum-size" + (buffer-name))))))) (defun font-lock-mode-internal (arg) - ;; Turn on Font Lock mode. - (when arg - (add-hook 'after-change-functions #'font-lock-after-change-function t t) - (font-lock-set-defaults) - (font-lock-turn-on-thing-lock)) - ;; Turn off Font Lock mode. - (unless font-lock-mode - (remove-hook 'after-change-functions #'font-lock-after-change-function t) + "This was better embedded in a minor mode, obviously." + (if arg + (progn + (cl-assert font-lock-mode) + (font-lock-set-defaults) + (font-lock-turn-on-thing-lock)) (font-lock-unfontify-buffer) (font-lock-turn-off-thing-lock))) @@ -900,6 +896,7 @@ font-lock-support-mode (const :tag "fast lock" fast-lock-mode) (const :tag "lazy lock" lazy-lock-mode) (const :tag "jit lock" jit-lock-mode) + (const :tag "tree sitter" tree-sitter-lock-mode) (repeat :menu-tag "mode specific" :tag "mode specific" :value ((t . jit-lock-mode)) (cons :tag "Instance" @@ -910,14 +907,15 @@ font-lock-support-mode (const :tag "none" nil) (const :tag "fast lock" fast-lock-mode) (const :tag "lazy lock" lazy-lock-mode) - (const :tag "JIT lock" jit-lock-mode))) - )) + (const :tag "JIT lock" jit-lock-mode) + (const :tag "tree sitter" tree-sitter-lock-mode))))) :version "21.1" :group 'font-lock) (defvar fast-lock-mode) (defvar lazy-lock-mode) (defvar jit-lock-mode) +(defvar tree-sitter-lock-mode) (declare-function fast-lock-after-fontify-buffer "fast-lock") (declare-function fast-lock-after-unfontify-buffer "fast-lock") @@ -925,41 +923,48 @@ jit-lock-mode (declare-function lazy-lock-after-fontify-buffer "lazy-lock") (declare-function lazy-lock-after-unfontify-buffer "lazy-lock") (declare-function lazy-lock-mode "lazy-lock") +(declare-function tree-sitter-fontify-region "tree-sitter") +(declare-function tree-sitter-lock-mode "tree-sitter") (defun font-lock-turn-on-thing-lock () (pcase (font-lock-value-in-major-mode font-lock-support-mode) - ('fast-lock-mode (fast-lock-mode t)) - ('lazy-lock-mode (lazy-lock-mode t)) + ('fast-lock-mode + (add-hook 'after-change-functions #'font-lock-after-change-function t t) + (fast-lock-mode t)) + ('lazy-lock-mode + (add-hook 'after-change-functions #'font-lock-after-change-function t t) + (lazy-lock-mode t)) + ('tree-sitter-lock-mode + (tree-sitter-lock-mode t)) ('jit-lock-mode - ;; Prepare for jit-lock - (remove-hook 'after-change-functions - #'font-lock-after-change-function t) - (setq-local font-lock-flush-function #'jit-lock-refontify) - (setq-local font-lock-ensure-function #'jit-lock-fontify-now) - ;; Prevent font-lock-fontify-buffer from fontifying eagerly the whole - ;; buffer. This is important for things like CWarn mode which - ;; adds/removes a few keywords and does a refontify (which takes ages on - ;; large files). - (setq-local font-lock-fontify-buffer-function #'jit-lock-refontify) - ;; Don't fontify eagerly (and don't abort if the buffer is large). - (setq-local font-lock-fontified t) - ;; Use jit-lock. - (jit-lock-register #'font-lock-fontify-region - (not font-lock-keywords-only)) - ;; Tell jit-lock how we extend the region to refontify. + (setq-local font-lock-fontify-buffer-function #'jit-lock-refontify + font-lock-flush-function #'jit-lock-refontify + font-lock-ensure-function #'jit-lock-fontify-now + font-lock-fontified t) ;; ramifies in `font-lock-initial-fontify' + (jit-lock-register #'font-lock-fontify-region (not font-lock-keywords-only)) (add-hook 'jit-lock-after-change-extend-region-functions #'font-lock-extend-jit-lock-region-after-change nil t)))) (defun font-lock-turn-off-thing-lock () + (mapc #'kill-local-variable + '(font-lock-fontify-buffer-function + font-lock-fontify-region-function + font-lock-flush-function + font-lock-ensure-function)) (cond ((bound-and-true-p fast-lock-mode) - (fast-lock-mode -1)) - ((bound-and-true-p jit-lock-mode) - (jit-lock-unregister 'font-lock-fontify-region) - ;; Reset local vars to the non-jit-lock case. - (kill-local-variable 'font-lock-fontify-buffer-function)) - ((bound-and-true-p lazy-lock-mode) - (lazy-lock-mode -1)))) + (fast-lock-mode -1) + (remove-hook 'after-change-functions #'font-lock-after-change-function t)) + ((bound-and-true-p lazy-lock-mode) + (lazy-lock-mode -1) + (remove-hook 'after-change-functions #'font-lock-after-change-function t)) + ((bound-and-true-p tree-sitter-lock-mode) + (tree-sitter-lock-mode -1)) + ((bound-and-true-p jit-lock-mode) + (jit-lock-unregister 'font-lock-fontify-region) + (remove-hook 'jit-lock-after-change-extend-region-functions + #'font-lock-extend-jit-lock-region-after-change + t)))) (defun font-lock-after-fontify-buffer () (cond ((bound-and-true-p fast-lock-mode) @@ -988,45 +993,6 @@ font-lock-after-unfontify-buffer ;;; Fontification functions. -;; Rather than the function, e.g., `font-lock-fontify-region' containing the -;; code to fontify a region, the function runs the function whose name is the -;; value of the variable, e.g., `font-lock-fontify-region-function'. Normally, -;; the value of this variable is, e.g., `font-lock-default-fontify-region' -;; which does contain the code to fontify a region. However, the value of the -;; variable could be anything and thus, e.g., `font-lock-fontify-region' could -;; do anything. The indirection of the fontification functions gives major -;; modes the capability of modifying the way font-lock.el fontifies. Major -;; modes can modify the values of, e.g., `font-lock-fontify-region-function', -;; via the variable `font-lock-defaults'. -;; -;; For example, Rmail mode sets the variable `font-lock-defaults' so that -;; font-lock.el uses its own function for buffer fontification. This function -;; makes fontification be on a message-by-message basis and so visiting an -;; RMAIL file is much faster. A clever implementation of the function might -;; fontify the headers differently from the message body. (It should, and -;; correspondingly for Mail mode, but I can't be bothered to do the work. Can -;; you?) This hints at a more interesting use... -;; -;; Languages that contain text normally contained in different major modes -;; could define their own fontification functions that treat text differently -;; depending on its context. For example, Perl mode could arrange that here -;; docs are fontified differently from Perl code. Or Yacc mode could fontify -;; rules one way and C code another. Neat! -;; -;; A further reason to use the fontification indirection feature is when the -;; default syntactic fontification, or the default fontification in general, -;; is not flexible enough for a particular major mode. For example, perhaps -;; comments are just too hairy for `font-lock-fontify-syntactically-region' to -;; cope with. You need to write your own version of that function, e.g., -;; `hairy-fontify-syntactically-region', and make your own version of -;; `hairy-fontify-region' call that function before calling -;; `font-lock-fontify-keywords-region' for the normal regexp fontification -;; pass. And Hairy mode would set `font-lock-defaults' so that font-lock.el -;; would call your region fontification function instead of its own. For -;; example, TeX modes could fontify {\foo ...} and \bar{...} etc. multi-line -;; directives correctly and cleanly. (It is the same problem as fontifying -;; multi-line strings and comments; regexps are not appropriate for the job.) - (defvar-local font-lock-extend-after-change-region-function nil "A function that determines the region to refontify after a change. @@ -1094,7 +1060,7 @@ font-lock-flush (defvar font-lock-ensure-function (lambda (beg end) - (unless font-lock-fontified + (when (text-property-not-all beg end 'fontified t) (save-excursion (font-lock-fontify-region beg end)))) "Function to make sure a region has been fontified. @@ -1121,7 +1087,8 @@ font-lock-ensure (when (font-lock-specified-p t) (font-lock-set-defaults) (funcall font-lock-ensure-function - (or beg (point-min)) (or end (point-max))))) + (or beg (point-min)) (or end (point-max))) + (setq font-lock-fontified t))) (defun font-lock-update (&optional arg) "Update the syntax highlighting in this buffer. @@ -1291,9 +1258,9 @@ font-lock-after-change-function (save-excursion (let ((inhibit-point-motion-hooks t) (inhibit-quit t) - (region (if font-lock-extend-after-change-region-function - (funcall font-lock-extend-after-change-region-function - beg end old-len)))) + (region (when font-lock-extend-after-change-region-function + (funcall font-lock-extend-after-change-region-function + beg end old-len)))) (save-match-data (if region ;; Fontify the region the major mode has specified. @@ -1828,7 +1795,7 @@ font-lock-compile-keywords (0 (if (memq (get-text-property (match-beginning 0) 'face) '(font-lock-string-face font-lock-doc-face - font-lock-comment-face)) + font-lock-comment-face)) (list 'face font-lock-warning-face 'help-echo "Looks like a toplevel defun: escape the parenthesis")) prepend))))) @@ -1940,12 +1907,10 @@ font-lock-set-defaults '(1 2 3)) ;"." "w" "_" (setq font-lock--syntax-table-affects-ppss t)) )))) - ;; (nth 4 defaults) used to hold `font-lock-beginning-of-syntax-function', - ;; but that was removed in 25.1, so if it's a cons cell, we assume that - ;; it's part of the variable alist. - ;; Variable alist? - (dolist (x (nthcdr (if (consp (nth 4 defaults)) 4 5) defaults)) - (set (make-local-variable (car x)) (cdr x))) + ;; rest is (var . val) bindings + (dolist (x (nthcdr 4 defaults)) + (when (consp x) + (set (make-local-variable (car x)) (cdr x)))) ;; Set up `font-lock-keywords' last because its value might depend ;; on other settings. (setq-local font-lock-keywords diff --git a/lisp/jit-lock.el b/lisp/jit-lock.el index bb2df2b1ff..02a4033095 100644 --- a/lisp/jit-lock.el +++ b/lisp/jit-lock.el @@ -365,14 +365,11 @@ jit-lock-refontify ;;; On demand fontification. (defun jit-lock-function (start) - "Fontify current buffer starting at position START. -This function is added to `fontification-functions' when `jit-lock-mode' -is active." + "Workhorse called from handle_fontified_prop() in xdisp.c. +Registered in `font-lock-turn-on-thing-lock' when `font-lock-support-mode' +is `jit-lock-mode'." (when (and jit-lock-mode (not memory-full)) - (if (not (and jit-lock-defer-timer - (or (not (eq jit-lock-defer-time 0)) - (input-pending-p)))) - ;; No deferral. + (if (or (not jit-lock-defer-timer) (zerop jit-lock-defer-time)) (jit-lock-fontify-now start (+ start jit-lock-chunk-size)) ;; Record the buffer for later fontification. (unless (memq (current-buffer) jit-lock-defer-buffers) @@ -387,116 +384,42 @@ jit-lock-function 'fontified 'defer))))) (defun jit-lock--run-functions (beg end) - (let ((tight-beg nil) (tight-end nil) - (loose-beg beg) (loose-end end)) + (let ((tight-beg (point-min)) + (tight-end (point-max))) (run-hook-wrapped 'jit-lock-functions (lambda (fun) - (pcase-let* - ((res (funcall fun beg end)) - (`(,this-beg . ,this-end) - (if (eq (car-safe res) 'jit-lock-bounds) - (cdr res) (cons beg end)))) - ;; If all functions don't fontify the same region, we currently - ;; just try to "still be correct". But we could go further and for - ;; the chunks of text that was fontified by some functions but not - ;; all, we could add text-properties indicating which functions were - ;; already run to avoid running them redundantly when we get to - ;; those chunks. - (setq tight-beg (max (or tight-beg (point-min)) this-beg)) - (setq tight-end (min (or tight-end (point-max)) this-end)) - (setq loose-beg (min loose-beg this-beg)) - (setq loose-end (max loose-end this-end)) - nil))) - `(,(min tight-beg beg) ,(max tight-end end) ,loose-beg ,loose-end))) - -(defun jit-lock-fontify-now (&optional start end) - "Fontify current buffer from START to END. -Defaults to the whole buffer. END can be out of bounds." + (prog1 nil + (pcase-let* + ((res (funcall fun beg end)) + (`(,this-beg . ,this-end) + (if (eq (car-safe res) 'jit-lock-bounds) + (cdr res) (cons beg end)))) + (setq tight-beg (max tight-beg this-beg)) + (setq tight-end (min tight-end this-end)))))) + (cons (min tight-beg beg) (max tight-end end)))) + +(defun jit-lock-fontify-now (start end) + "One of about six levels of indirection leading up to `font-lock-fontify-region'." + (setq end (min (point-max) end)) (with-buffer-prepared-for-jit-lock (save-excursion - (unless start (setq start (point-min))) - (setq end (if end (min end (point-max)) (point-max))) - (let ((orig-start start) next) - (save-match-data - ;; Fontify chunks beginning at START. The end of a - ;; chunk is either `end', or the start of a region - ;; before `end' that has already been fontified. - (while (and start (< start end)) - ;; Determine the end of this chunk. - (setq next (or (text-property-any start end 'fontified t) - end)) - - ;; Avoid unnecessary work if the chunk is empty (bug#23278). - (when (> next start) - ;; Fontify the chunk, and mark it as fontified. - ;; We mark it first, to make sure that we don't indefinitely - ;; re-execute this fontification if an error occurs. - (put-text-property start next 'fontified t) - (pcase-let - ;; `tight' is the part we've fully refontified, and `loose' - ;; is the part we've partly refontified (some of the - ;; functions have refontified it but maybe not all). - ((`(,tight-beg ,tight-end ,loose-beg ,_loose-end) - (condition-case err - (jit-lock--run-functions start next) - ;; If the user quits (which shouldn't happen in normal - ;; on-the-fly jit-locking), make sure the fontification - ;; will be performed before displaying the block again. - (quit (put-text-property start next 'fontified nil) - (signal (car err) (cdr err)))))) - - ;; In case we fontified more than requested, take - ;; advantage of the good news. - (when (or (< tight-beg start) (> tight-end next)) - (put-text-property tight-beg tight-end 'fontified t)) - - ;; Make sure the contextual refontification doesn't re-refontify - ;; what's already been refontified. - (when (and jit-lock-context-unfontify-pos - (< jit-lock-context-unfontify-pos tight-end) - (>= jit-lock-context-unfontify-pos tight-beg) - ;; Don't move boundary forward if we have to - ;; refontify previous text. Otherwise, we risk moving - ;; it past the end of the multiline property and thus - ;; forget about this multiline region altogether. - (not (get-text-property tight-beg - 'jit-lock-defer-multiline))) - (setq jit-lock-context-unfontify-pos tight-end)) - - ;; The redisplay engine has already rendered the buffer up-to - ;; `orig-start' and won't notice if the above jit-lock-functions - ;; changed the appearance of any part of the buffer prior - ;; to that. So if `loose-beg' is before `orig-start', we need to - ;; cause a new redisplay cycle after this one so that the changes - ;; are properly reflected on screen. - ;; To make such repeated redisplay happen less often, we can - ;; eagerly extend the refontified region with - ;; jit-lock-after-change-extend-region-functions. - (when (< loose-beg orig-start) - (run-with-timer 0 nil #'jit-lock-force-redisplay - (copy-marker loose-beg) - (copy-marker orig-start))) - - ;; Skip to the end of the fully refontified part. - (setq start tight-end))) - ;; Find the start of the next chunk, if any. - (setq start - (text-property-any start end 'fontified nil)))))))) - -(defun jit-lock-force-redisplay (start end) - "Force the display engine to re-render START's buffer from START to END. -This applies to the buffer associated with marker START." - (when (marker-buffer start) - (with-current-buffer (marker-buffer start) - (with-buffer-prepared-for-jit-lock - (when (> end (point-max)) - (setq end (point-max) start (min start end))) - (when (< start (point-min)) - (setq start (point-min) end (max start end))) - ;; Don't cause refontification (it's already been done), but just do - ;; some random buffer change, so as to force redisplay. - (put-text-property start end 'fontified t))))) + (save-match-data + (cl-loop for istart = (text-property-any (or istart start) end 'fontified nil) + while istart + for iend = (or (text-property-any istart end 'fontified t) end) + do (pcase-let* ((`(,istart* . ,iend*) + (jit-lock--run-functions istart iend))) + (put-text-property istart* iend* 'fontified t) + + ;; Mirror progress for context-aware fontifier (obsolete?) + (when (and jit-lock-context-unfontify-pos + (< jit-lock-context-unfontify-pos iend*) + (>= jit-lock-context-unfontify-pos istart*) + (not (get-text-property istart* 'jit-lock-defer-multiline))) + (setq jit-lock-context-unfontify-pos iend*))) + do (setq istart iend)))))) + ;;; Stealth fontification. diff --git a/lisp/loadup.el b/lisp/loadup.el index e8ecb67d56..214eec7359 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -282,6 +282,7 @@ (load "replace") (load "emacs-lisp/tabulated-list") (load "buff-menu") +(load "tree-sitter") (if (fboundp 'x-create-frame) (progn diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index a4568bd4ef..858ea5aa6f 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -4747,42 +4747,6 @@ c-safe-position ;; level between the safe position and bufpos. (throw 'done (min (1+ elem) bufpos)))) (setq paren-state (cdr paren-state))))))) - -(defun c-beginning-of-syntax () - ;; This is used for `font-lock-beginning-of-syntax-function'. It - ;; goes to the closest previous point that is known to be outside - ;; any string literal or comment. `c-state-cache' is used if it has - ;; a position in the vicinity. - (let* ((paren-state c-state-cache) - elem - - (pos (catch 'done - ;; Note: Similar code in `c-safe-position'. The - ;; difference is that we accept a safe position at - ;; the point and don't bother to go forward past open - ;; parens. - (while paren-state - (setq elem (car paren-state)) - (if (consp elem) - (cond ((<= (cdr elem) (point)) - (throw 'done (cdr elem))) - ((<= (car elem) (point)) - (throw 'done (car elem)))) - (if (<= elem (point)) - (throw 'done elem))) - (setq paren-state (cdr paren-state))) - (point-min)))) - - (if (> pos (- (point) 4000)) - (goto-char pos) - ;; The position is far back. Try `c-beginning-of-defun-1' - ;; (although we can't be entirely sure it will go to a position - ;; outside a comment or string in current emacsen). FIXME: - ;; Consult `syntax-ppss' here. - (c-beginning-of-defun-1) - (if (< (point) pos) - (goto-char pos))))) - ;; Tools for scanning identifiers and other tokens. @@ -7329,7 +7293,7 @@ c-ml-string-opener-around-point (cons (match-beginning 1) (cons (match-end 1) (match-beginning 2)))) (goto-char here)))) - + (defun c-ml-string-opener-intersects-region (&optional start finish) ;; If any part of the region [START FINISH] is inside an ml-string opener, ;; return a dotted list of the start, end and double-quote position of that diff --git a/lisp/progmodes/cc-fonts.el b/lisp/progmodes/cc-fonts.el index 9355409b2a..465daa98e4 100644 --- a/lisp/progmodes/cc-fonts.el +++ b/lisp/progmodes/cc-fonts.el @@ -943,9 +943,7 @@ c-font-lock-complex-decl-prepare pos) limit 'c-type))) - ;; Update `c-state-cache' to the beginning of the region. This will - ;; make `c-beginning-of-syntax' go faster when it's used later on, - ;; and it's near the point most of the time. + ;; Update `c-state-cache' to the beginning of the region. (c-parse-state) ;; Check if the fontified region starts inside a declarator list so @@ -1937,7 +1935,7 @@ c-font-lock-ml-strings (cons (match-end 1) (match-beginning 2))) string-delims (cons open-delim (c-get-ml-closer open-delim))) (goto-char (caar string-delims)))) - + ;; Point is in the body of an ml string. ((and string-delims (>= (point) (cadar string-delims)) @@ -2425,10 +2423,9 @@ c-override-default-keywords ;; redisplay. (defvar c-re-redisplay-timer nil) -(defun c-force-redisplay (start end) +(defun c-force-redisplay (_start _end) ;; Force redisplay immediately. This assumes `font-lock-support-mode' is ;; 'jit-lock-mode. Set the variable `c-re-redisplay-timer' to nil. - (jit-lock-force-redisplay (copy-marker start) (copy-marker end)) (setq c-re-redisplay-timer nil)) (defun c-fontify-new-found-type (type) diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el index ee5872d761..4fde95ae41 100644 --- a/lisp/progmodes/cc-mode.el +++ b/lisp/progmodes/cc-mode.el @@ -2552,7 +2552,6 @@ c-font-lock-init "font-lock-keywords-2" "font-lock-keywords-3"))) nil nil ,c-identifier-syntax-modifications - c-beginning-of-syntax (font-lock-mark-block-function . c-mark-function))) diff --git a/lisp/ps-print.el b/lisp/ps-print.el index 0fc9554679..edc3464b11 100644 --- a/lisp/ps-print.el +++ b/lisp/ps-print.el @@ -6325,7 +6325,7 @@ ps-screen-to-bit-face (ps-face-background-name face)))) -(declare-function jit-lock-fontify-now "jit-lock" (&optional start end)) +(declare-function jit-lock-fontify-now "jit-lock" (start end)) (declare-function lazy-lock-fontify-region "lazy-lock" (beg end)) ;; to avoid compilation gripes diff --git a/lisp/tree-sitter.el b/lisp/tree-sitter.el new file mode 100644 index 0000000000..3929041d7a --- /dev/null +++ b/lisp/tree-sitter.el @@ -0,0 +1,141 @@ +;;; tree-sitter.el --- tree-sitter utilities -*- lexical-binding: t -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;;; Code: + +(require 'font-lock) +(declare-function tree-sitter-changed-range "tree-sitter.c") +(declare-function tree-sitter-highlight-region "tree-sitter.c") + +(defgroup tree-sitter + nil + "Tree-sitter is an incremental parser." + :group 'tools) + +(defcustom tree-sitter-mode-alist + '((c++-mode . "cpp") + (rust-mode . "rust") + (sh-mode . "bash") + (c-mode . "c") + (go-mode . "go") + (html-mode . "html") + (java-mode . "java") + (js-mode . "javascript") + (python-mode . "python") + (ruby-mode . "ruby")) + "Map prog-mode to tree-sitter grammar." + :group 'tree-sitter + :type '(alist :key-type (symbol :tag "Prog mode") + :value-type (string :tag "Tree-sitter symbol")) + :risky t + :version "28.1") + +(defcustom tree-sitter-highlight-alist + '(("constant" . font-lock-constant-face) + ("type.builtin" . font-lock-type-face) + ("operator" . font-lock-builtin-face) + ("variable.parameter" . font-lock-variable-name-face) + ("function.builtin" . font-lock-function-name-face) + ;; ("punctuation.delimiter" . font-lock-constant-face) + ("attribute" . font-lock-variable-name-face) + ;; ("punctuation.bracket" . font-lock-constant-face) + ("string" . font-lock-string-face) + ("variable.builtin" . font-lock-builtin-face) + ("comment" . font-lock-comment-face) + ("number" . font-lock-constant-face) + ("type" . font-lock-type-face) + ("embedded" . font-lock-builtin-face) + ("function" . font-lock-function-name-face) + ("keyword" . font-lock-keyword-face) + ("constructor" . font-lock-function-name-face) + ("property" . font-lock-variable-name-face) + ("tag" . font-lock-type-face) + ("string.special" . font-lock-string-face) + ("constant.builtin" . font-lock-constant-face)) + "Map tree-sitter highlight name to font lock face." + :group 'tree-sitter + :initialize 'custom-initialize-default + :set (lambda (symbol value) + (mapc (lambda (x) + (when (listp (cdr x)) + (setcdr x (cadr x)))) + value) + (if-let ((problem (seq-find (lambda (x) + (or (not (stringp (car x))) + (not (facep (cdr x))))) + value))) + (error "Bad setting %S" problem) + (set-default symbol value))) + :type '(alist :key-type (string :tag "Tree-sitter highlight") + :value-type (symbol :tag "Font lock face")) + :risky t + :version "28.1") + +(defun tree-sitter-do-fontify (pos) + "Analog to `jit-lock-fontify-now' without all the indirection." + (let ((end (or (text-property-any pos (point-max) 'fontified t) + (point-max)))) + (font-lock-fontify-region pos end font-lock-verbose))) + +(define-minor-mode tree-sitter-lock-mode + "Tree-sitter font-lock minor mode." + :lighter "" + (if tree-sitter-lock-mode + (progn + (setq-local font-lock-fontify-region-function #'tree-sitter-fontify-region) + (add-hook 'after-change-functions #'tree-sitter-fontify-refresh nil t)) + (kill-local-variable 'font-lock-fontify-region-function) + (remove-hook 'after-change-functions #'tree-sitter-fontify-refresh t))) + +(defun tree-sitter-fontify-region (beg end loudly) + "Presumably widened in `font-lock-fontify-region'." + (ignore loudly) + (let ((inhibit-point-motion-hooks t)) + (with-silent-modifications + (save-excursion + (save-match-data + (let* ((changed-range (tree-sitter-changed-range)) + (beg* (if changed-range + (min beg (cl-first changed-range)) + beg)) + (end* (if changed-range + (min (point-max) + (max (min (+ beg jit-lock-chunk-size) + (cl-second changed-range)) + end)) + end)) + (bounds (tree-sitter-highlight-region beg* end*)) + (leftmost (if bounds (min beg* (car bounds)) beg*)) + (rightmost (if bounds (max end* (cdr bounds)) end*))) + ;; (when loudly + ;; (princ (format "changed [%s %s], initial [%d %d], final [%d %d]\n" + ;; (cl-first (tree-sitter-changed-range)) + ;; (cl-second (tree-sitter-changed-range)) + ;; beg end leftmost rightmost) + ;; #'external-debugging-output)) + (put-text-property leftmost rightmost 'fontified t))))))) + +(defsubst tree-sitter-fontify-refresh (start &rest _args) + (tree-sitter-do-fontify start)) + +(provide 'tree-sitter) + +;;; tree-sitter.el ends here diff --git a/src/Makefile.in b/src/Makefile.in index 7c977e34ea..bd3f53995b 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -322,6 +322,10 @@ JSON_LIBS = JSON_CFLAGS = @JSON_CFLAGS@ JSON_OBJ = @JSON_OBJ@ +TREE_SITTER_LIBS = @TREE_SITTER_LIBS@ +TREE_SITTER_FLAGS = @TREE_SITTER_FLAGS@ +TREE_SITTER_OBJ = @TREE_SITTER_OBJ@ + INTERVALS_H = dispextern.h intervals.h composite.h GETLOADAVG_LIBS = @GETLOADAVG_LIBS@ @@ -375,7 +379,7 @@ EMACS_CFLAGS= $(WEBKIT_CFLAGS) $(LCMS2_CFLAGS) \ $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \ $(HARFBUZZ_CFLAGS) $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \ - $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) \ + $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) $(TREE_SITTER_CFLAGS) \ $(LIBGNUTLS_CFLAGS) $(NOTIFY_CFLAGS) $(CAIRO_CFLAGS) \ $(WERROR_CFLAGS) ALL_CFLAGS = $(EMACS_CFLAGS) $(WARN_CFLAGS) $(CFLAGS) @@ -409,7 +413,8 @@ base_obj = thread.o systhread.o \ $(if $(HYBRID_MALLOC),sheap.o) \ $(MSDOS_OBJ) $(MSDOS_X_OBJ) $(NS_OBJ) $(CYGWIN_OBJ) $(FONT_OBJ) \ - $(W32_OBJ) $(WINDOW_SYSTEM_OBJ) $(XGSELOBJ) $(JSON_OBJ) + $(W32_OBJ) $(WINDOW_SYSTEM_OBJ) $(XGSELOBJ) $(JSON_OBJ) \ + $(TREE_SITTER_OBJ) obj = $(base_obj) $(NS_OBJC_OBJ) ## Object files used on some machine or other. @@ -522,7 +527,7 @@ LIBES = $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(HARFBUZZ_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \ $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \ $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) $(LIBSYSTEMD_LIBS) \ - $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) + $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) $(TREE_SITTER_LIBS) ## FORCE it so that admin/unidata can decide whether this file is ## up-to-date. Although since charprop depends on bootstrap-emacs, diff --git a/src/alloc.c b/src/alloc.c index aa790d3afa..a04f09f4d0 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -50,6 +50,10 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2021 Free Software #include TERM_HEADER #endif /* HAVE_WINDOW_SYSTEM */ +#ifdef HAVE_TREE_SITTER +#include "tree-sitter.h" +#endif + #include #include #include /* For backtrace. */ @@ -3180,6 +3184,25 @@ cleanup_vector (struct Lisp_Vector *vector) xfree (subr->native_c_name[0]); } } +#ifdef HAVE_TREE_SITTER + else if (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_TREE_SITTER)) + { + struct Lisp_Tree_Sitter *lisp_parser + = PSEUDOVEC_STRUCT (vector, Lisp_Tree_Sitter); + if (lisp_parser->highlight_names != NULL) + xfree (lisp_parser->highlight_names); + if (lisp_parser->highlights_query != NULL) + xfree (lisp_parser->highlights_query); + if (lisp_parser->highlighter != NULL) + ts_highlighter_delete (lisp_parser->highlighter); + if (lisp_parser->tree != NULL) + ts_tree_delete(lisp_parser->tree); + if (lisp_parser->prev_tree != NULL) + ts_tree_delete(lisp_parser->prev_tree); + if (lisp_parser->parser != NULL) + ts_parser_delete(lisp_parser->parser); + } +#endif } /* Reclaim space used by unmarked vectors. */ diff --git a/src/casefiddle.c b/src/casefiddle.c index 81e9ed153f..29c25ae6b8 100644 --- a/src/casefiddle.c +++ b/src/casefiddle.c @@ -30,6 +30,10 @@ Copyright (C) 1985, 1994, 1997-1999, 2001-2021 Free Software Foundation, #include "composite.h" #include "keymap.h" +#ifdef HAVE_TREE_SITTER +#include "tree-sitter.h" +#endif + enum case_action {CASE_UP, CASE_DOWN, CASE_CAPITALIZE, CASE_CAPITALIZE_UP}; /* State for casing individual characters. */ @@ -530,6 +534,11 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e) modify_text (start, end); prepare_casing_context (&ctx, flag, true); +#ifdef HAVE_TREE_SITTER + ptrdiff_t start_byte = CHAR_TO_BYTE (start); + ptrdiff_t old_end_byte = CHAR_TO_BYTE (end); +#endif + ptrdiff_t orig_end = end; record_delete (start, make_buffer_string (start, end, true), false); if (NILP (BVAR (current_buffer, enable_multibyte_characters))) @@ -548,6 +557,9 @@ casify_region (enum case_action flag, Lisp_Object b, Lisp_Object e) { signal_after_change (start, end - start - added, end - start); update_compositions (start, end, CHECK_ALL); +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (start_byte, old_end_byte, CHAR_TO_BYTE (end)); +#endif } return orig_end + added; diff --git a/src/data.c b/src/data.c index 0d3376f090..3661fc7192 100644 --- a/src/data.c +++ b/src/data.c @@ -259,6 +259,8 @@ DEFUN ("type-of", Ftype_of, Stype_of, 1, 1, 0, return Qxwidget; case PVEC_XWIDGET_VIEW: return Qxwidget_view; + case PVEC_TREE_SITTER: + return Qtree_sitter_parser; /* "Impossible" cases. */ case PVEC_MISC_PTR: case PVEC_OTHER: @@ -4065,6 +4067,8 @@ #define PUT_ERROR(sym, tail, msg) \ DEFSYM (Qterminal, "terminal"); DEFSYM (Qxwidget, "xwidget"); DEFSYM (Qxwidget_view, "xwidget-view"); + DEFSYM (Qtree_sitter_parser, "tree-sitter-parser"); + DEFSYM (Qtree_sitter_node, "tree-sitter-node"); DEFSYM (Qdefun, "defun"); diff --git a/src/editfns.c b/src/editfns.c index c8219decb0..d064fb2a45 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -1574,7 +1574,7 @@ make_buffer_string (ptrdiff_t start, ptrdiff_t end, bool props) have them, if PROPS is true. We don't want to use plain old make_string here, because it calls - make_uninit_string, which can cause the buffer arena to be + make_uninit_string, which can cause the buffer area to be compacted. make_string has no way of knowing that the data has been moved, and thus copies the wrong data into the string. This doesn't effect most of the other users of make_string, so it should diff --git a/src/emacs.c b/src/emacs.c index 032b27fcf3..cc3a947b4d 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -81,6 +81,10 @@ #define MAIN_PROGRAM #include TERM_HEADER #endif /* HAVE_WINDOW_SYSTEM */ +#ifdef HAVE_TREE_SITTER +#include "tree-sitter.h" +#endif /* HAVE_TREE_SITTER */ + #include "bignum.h" #include "intervals.h" #include "character.h" @@ -2099,6 +2103,9 @@ main (int argc, char **argv) syms_of_floatfns (); syms_of_buffer (); + #ifdef HAVE_TREE_SITTER + syms_of_tree_sitter (); + #endif syms_of_bytecode (); syms_of_callint (); syms_of_casefiddle (); diff --git a/src/eval.c b/src/eval.c index 94ad060773..391e6da3be 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1973,6 +1973,19 @@ signal_error (const char *s, Lisp_Object arg) xsignal (Qerror, Fcons (build_string (s), arg)); } +void +define_error (Lisp_Object name, const char *message, Lisp_Object parent) +{ + eassert (SYMBOLP (name)); + eassert (SYMBOLP (parent)); + Lisp_Object parent_conditions = Fget (parent, Qerror_conditions); + eassert (CONSP (parent_conditions)); + eassert (!NILP (Fmemq (parent, parent_conditions))); + eassert (NILP (Fmemq (name, parent_conditions))); + Fput (name, Qerror_conditions, pure_cons (name, parent_conditions)); + Fput (name, Qerror_message, build_pure_c_string (message)); +} + /* Use this for arithmetic overflow, e.g., when an integer result is too large even for a bignum. */ void diff --git a/src/fileio.c b/src/fileio.c index 3c13d3fe41..e79de3bf20 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -5278,7 +5278,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename, unsavable chars (as was the case with X-Symbol). */ Vlast_coding_system_used = choose_write_coding_system (start, end, filename, - append, visit, lockname, &coding); + append, visit, lockname, &coding); if (open_and_close_file && !auto_saving) { diff --git a/src/insdel.c b/src/insdel.c index 40674e15e4..157981730e 100644 --- a/src/insdel.c +++ b/src/insdel.c @@ -31,6 +31,10 @@ #include "region-cache.h" #include "pdumper.h" +#ifdef HAVE_TREE_SITTER +#include "tree-sitter.h" +#endif + static void insert_from_string_1 (Lisp_Object, ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, bool, bool); static void insert_from_buffer_1 (struct buffer *, ptrdiff_t, ptrdiff_t, bool); @@ -940,6 +944,10 @@ insert_1_both (const char *string, set_text_properties (make_fixnum (PT), make_fixnum (PT + nchars), Qnil, Qnil, Qnil); +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (PT, PT, PT + nchars); +#endif + adjust_point (nchars, nbytes); check_markers (); @@ -1071,6 +1079,10 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte, graft_intervals_into_buffer (intervals, PT, nchars, current_buffer, inherit); +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (PT, PT, PT + nchars); +#endif + adjust_point (nchars, outgoing_nbytes); check_markers (); @@ -1137,6 +1149,10 @@ insert_from_gap (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail) current_buffer, 0); } +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (ins_charpos, ins_charpos, ins_charpos + nchars); +#endif + if (ins_charpos < PT) adjust_point (nchars, nbytes); @@ -1287,6 +1303,10 @@ insert_from_buffer_1 (struct buffer *buf, /* Insert those intervals. */ graft_intervals_into_buffer (intervals, PT, nchars, current_buffer, inherit); +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (PT, PT, PT + nchars); +#endif + adjust_point (nchars, outgoing_nbytes); } @@ -1340,6 +1360,9 @@ adjust_after_replace (ptrdiff_t from, ptrdiff_t from_byte, offset_intervals (current_buffer, from, len - nchars_del); +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (from, from + nchars_del, from + len); +#endif if (from < PT) adjust_point (len - nchars_del, len_byte - nbytes_del); @@ -1535,6 +1558,10 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new, graft_intervals_into_buffer (intervals, from, inschars, current_buffer, inherit); +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (from, from + nchars_del, from + inschars); +#endif + /* Relocate point as if it were a marker. */ if (from < PT) adjust_point ((from + inschars - (PT < to ? PT : to)), @@ -1892,6 +1919,10 @@ del_range_2 (ptrdiff_t from, ptrdiff_t from_byte, evaporate_overlays (from); +#ifdef HAVE_TREE_SITTER + tree_sitter_record_change (from, from + nchars_del, from); +#endif + return deletion; } diff --git a/src/json.c b/src/json.c index b0779b912a..914873b83f 100644 --- a/src/json.c +++ b/src/json.c @@ -1090,22 +1090,6 @@ DEFUN ("json-parse-buffer", Fjson_parse_buffer, Sjson_parse_buffer, return unbind_to (count, lisp); } -/* Simplified version of 'define-error' that works with pure - objects. */ - -static void -define_error (Lisp_Object name, const char *message, Lisp_Object parent) -{ - eassert (SYMBOLP (name)); - eassert (SYMBOLP (parent)); - Lisp_Object parent_conditions = Fget (parent, Qerror_conditions); - eassert (CONSP (parent_conditions)); - eassert (!NILP (Fmemq (parent, parent_conditions))); - eassert (NILP (Fmemq (name, parent_conditions))); - Fput (name, Qerror_conditions, pure_cons (name, parent_conditions)); - Fput (name, Qerror_message, build_pure_c_string (message)); -} - void syms_of_json (void) { diff --git a/src/lisp.h b/src/lisp.h index 31656bb3b1..5e873029b7 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -1070,6 +1070,7 @@ DEFINE_GDB_SYMBOL_END (PSEUDOVECTOR_FLAG) PVEC_CONDVAR, PVEC_MODULE_FUNCTION, PVEC_NATIVE_COMP_UNIT, + PVEC_TREE_SITTER, /* These should be last, for internal_equal and sxhash_obj. */ PVEC_COMPILED, @@ -1316,6 +1317,7 @@ #define XSETTHREAD(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_THREAD)) #define XSETMUTEX(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_MUTEX)) #define XSETCONDVAR(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_CONDVAR)) #define XSETNATIVE_COMP_UNIT(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_NATIVE_COMP_UNIT)) +#define XSETTREE_SITTER(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_TREE_SITTER)) /* Efficiently convert a pointer to a Lisp object and back. The pointer is represented as a fixnum, so the garbage collector @@ -4768,6 +4770,10 @@ maybe_disable_address_randomization (int argc, char **argv) extern void malloc_probe (size_t); extern void syms_of_profiler (void); +/* Defined in tree-sitter.c. */ +extern void tree_sitter_record_change (ptrdiff_t start_char, ptrdiff_t old_end_char, + ptrdiff_t new_end_char); +extern void syms_of_tree_sitter (void); #ifdef DOS_NT /* Defined in msdos.c, w32.c. */ @@ -5153,6 +5159,11 @@ maybe_gc (void) maybe_garbage_collect (); } +/* Simplified version of 'define-error' that works with pure + objects. */ +void +define_error (Lisp_Object name, const char *message, Lisp_Object parent); + INLINE_HEADER_END #endif /* EMACS_LISP_H */ diff --git a/src/print.c b/src/print.c index c13294c8e6..54319ed1ca 100644 --- a/src/print.c +++ b/src/print.c @@ -48,6 +48,10 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2021 Free Software # include /* for F_DUPFD_CLOEXEC */ #endif +#ifdef HAVE_TREE_SITTER +#include "tree-sitter.h" +#endif + struct terminal; /* Avoid actual stack overflow in print. */ @@ -1856,6 +1860,16 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag, printchar ('>', printcharfun); } break; +#endif +#ifdef HAVE_TREE_SITTER + case PVEC_TREE_SITTER: + print_c_string ("#progmode, printcharfun, escapeflag); + printchar (' ', printcharfun); + int len = sprintf (buf, "%p", XTREE_SITTER (obj)); + strout (buf, len, len, printcharfun); + printchar ('>', printcharfun); + break; #endif default: emacs_abort (); diff --git a/src/tree-sitter.c b/src/tree-sitter.c new file mode 100644 index 0000000000..40ab44035f --- /dev/null +++ b/src/tree-sitter.c @@ -0,0 +1,717 @@ +/* Tree-sitter integration for GNU Emacs. + +Copyright (C) 2021 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +#include + +#include "lisp.h" +#include "buffer.h" +#include "tree-sitter.h" + +#define BUFFER_TO_SITTER(byte) ((uint32_t) CHAR_TO_BYTE (byte) - 1) +#define SITTER_TO_BUFFER(byte) (BYTE_TO_CHAR ((EMACS_INT) byte) + 1) + +typedef TSLanguage *(*TSLanguageFunctor) (void); +typedef Lisp_Object (*HighlightsFunctor) (const TSHighlightEventSlice *, const TSNode *, const char **); + +static Lisp_Object +make_tree_sitter (TSParser *parser, TSTree *tree, Lisp_Object progmode_arg) +{ + struct Lisp_Tree_Sitter *sitter + = ALLOCATE_PSEUDOVECTOR + (struct Lisp_Tree_Sitter, progmode, PVEC_TREE_SITTER); + + CHECK_SYMBOL (progmode_arg); + + sitter->progmode = progmode_arg; + sitter->parser = parser; + sitter->prev_tree = NULL; + sitter->tree = tree; + sitter->highlighter = NULL; + sitter->highlight_names = NULL; + sitter->highlights_query = NULL; + return make_lisp_ptr (sitter, Lisp_Vectorlike); +} + +static TSLanguageFunctor +tree_sitter_language_functor (Lisp_Object progmode) +{ + static Lisp_Object cache = LISPSYM_INITIALLY (Qnil); + struct Lisp_Hash_Table *h; + Lisp_Object hash; + ptrdiff_t i; + + if (NILP (cache)) + { + cache = make_hash_table (hashtest_eq, DEFAULT_HASH_SIZE, + DEFAULT_REHASH_SIZE, DEFAULT_REHASH_THRESHOLD, + Qnil, false); + staticpro (&cache); + } + + CHECK_SYMBOL (progmode); + h = XHASH_TABLE (cache); + i = hash_lookup (h, progmode, &hash); + if (i < 0) + { + Lisp_Object language = + Fcdr_safe (Fassq (progmode, Fsymbol_value (Qtree_sitter_mode_alist))); + if (! NILP (language)) + { + Lisp_Object module_stem = concat2 (build_string ("bin/"), language); + Lisp_Object module = Flocate_file_internal (module_stem, + Vload_path, + list1 (Vmodule_file_suffix), + Qnil); + if (NILP (module)) + xsignal2 (Qtree_sitter_language_error, + module_stem, build_string ("Could not locate module")); + else + { + dynlib_handle_ptr handle = dynlib_open (SSDATA (module)); + if (handle == NULL) + xsignal2 (Qtree_sitter_language_error, + module, build_string (dynlib_error ())); + else + { + TSLanguageFunctor fn; + dynlib_error (); + fn = dynlib_sym (handle, + SSDATA (concat2 (build_string ("tree_sitter_"), + language))); + if (fn == NULL) + xsignal2 (Qtree_sitter_language_error, + module, build_string (dynlib_error ())); + else + i = hash_put (h, progmode, make_misc_ptr (fn), hash); + } + } + } + } + return i >= 0 ? (TSLanguageFunctor) (xmint_pointer (HASH_VALUE (h, i))) : NULL; +} + +static Lisp_Object +tree_sitter_create (Lisp_Object progmode) +{ + Lisp_Object tree_sitter = Qnil; + TSLanguageFunctor fn; + + CHECK_SYMBOL (progmode); + + fn = tree_sitter_language_functor (progmode); + if (fn != NULL) + { + TSParser *ts_parser = ts_parser_new (); + ts_parser_set_language (ts_parser, fn ()); + tree_sitter = make_tree_sitter (ts_parser, NULL, progmode); + } + return tree_sitter; +} + +static Lisp_Object +list_highlights (const TSHighlightEventSlice *slice, const TSNode *node, + const char **highlight_names) +{ + Lisp_Object + retval = Qnil, + alist = Fsymbol_value (Qtree_sitter_highlight_alist); + const EMACS_INT count = XFIXNUM (Flength (alist)); + const uint32_t offset = ts_node_start_byte (*node); + + for (int i=slice->len-1; i>=0; --i) + { + const TSHighlightEvent *ev = &(slice->arr[i]); + eassert (ev->index < count); + if (ev->index >= TSHighlightEventTypeStartMin) { + Lisp_Object name = make_string (highlight_names[ev->index], + strlen (highlight_names[ev->index])); + retval = Fcons (Fcdr (Fassoc (name, alist, Qnil)), retval); + } else if (ev->index == TSHighlightEventTypeSource) { + retval = + Fcons (Fcons + (make_fixnum (SITTER_TO_BUFFER (ev->start + offset)), + make_fixnum (SITTER_TO_BUFFER (ev->end + offset))), + retval); + } else if (ev->index == TSHighlightEventTypeEnd) { + retval = Fcons (Qnil, retval); + } + } + return retval; +} + +static Lisp_Object +highlight_region (const TSHighlightEventSlice *slice, + const TSNode *node, const char **highlight_names) +{ + Lisp_Object + alist = Fsymbol_value (Qtree_sitter_highlight_alist), + prevailing = Qnil; + const EMACS_INT count = XFIXNUM (Flength (alist)); + const uint32_t offset = ts_node_start_byte (*node); + ptrdiff_t smallest = PTRDIFF_MAX, biggest = PTRDIFF_MIN; + + for (int i=0; ilen; ++i) + { + const TSHighlightEvent *ev = &(slice->arr[i]); + eassert (ev->index < count); + if (ev->index >= TSHighlightEventTypeStartMin) { + Lisp_Object name = make_string (highlight_names[ev->index], + strlen (highlight_names[ev->index])); + prevailing = Fcdr (Fassoc (name, alist, Qnil)); + } else if (ev->index == TSHighlightEventTypeSource) { + ptrdiff_t beg = SITTER_TO_BUFFER (ev->start + offset), + end = SITTER_TO_BUFFER (ev->end + offset); + smallest = min (smallest, beg); + biggest = max (biggest, end); + if (NILP (prevailing)) + Fremove_text_properties (make_fixnum (beg), make_fixnum (end), + list2 (Qface, Qt), Qnil); + else + Fput_text_property (make_fixnum (beg), make_fixnum (end), + Qface, prevailing, Qnil); + + } else if (ev->index == TSHighlightEventTypeEnd) { + prevailing = Qnil; + } + } + return smallest > biggest + ? Qnil : list2 (make_fixnum (smallest), make_fixnum (biggest)); +} + +static TSHighlighter * +ensure_highlighter(Lisp_Object sitter) +{ + TSHighlighter *ret = XTREE_SITTER (sitter)->highlighter; + if (ret == NULL) + { + char *scope; + const char *error = NULL; + Lisp_Object + suberror = Qnil, highlights_scm, + alist = Fsymbol_value (Qtree_sitter_highlight_alist), + language = Fcdr_safe (Fassq (XTREE_SITTER (sitter)->progmode, + Fsymbol_value (Qtree_sitter_mode_alist))); + const EMACS_INT count = XFIXNUM (Flength (alist)); + XTREE_SITTER (sitter)->highlight_names = xmalloc(sizeof (char *) * count); + intptr_t i = 0; + FILE *fp = NULL; + + eassert (! NILP (language)); /* by tree_sitter_create() */ + + FOR_EACH_TAIL (alist) + { + CHECK_STRING (XCAR (XCAR (alist))); + XTREE_SITTER (sitter)->highlight_names[i++] = + SSDATA (XCAR (XCAR (alist))); + } + alist = Fsymbol_value (Qtree_sitter_highlight_alist); /* FOR_EACH_TAIL mucks */ + + USE_SAFE_ALLOCA; + scope = SAFE_ALLOCA (strlen ("scope.") + SCHARS (language) + 1); + sprintf (scope, "scope.%s", SSDATA (language)); + + ret = (XTREE_SITTER (sitter)->highlighter = + ts_highlighter_new (XTREE_SITTER (sitter)->highlight_names, + XTREE_SITTER (sitter)->highlight_names, + (uint32_t) count)); + + highlights_scm = + Flocate_file_internal (concat3 (build_string ("queries/"), + language, build_string ("/highlights.scm")), + Vload_path, Qnil, Qnil); + + if (NILP (highlights_scm)) + { + suberror = language; + error = "Could not locate highlights scm"; + } + else + { + long highlights_query_length; + fp = fopen (SSDATA (highlights_scm), "rb"); + if (! fp) { + suberror = highlights_scm; + error = "Cannot fopen"; + goto finally; + } + fseek (fp, 0L, SEEK_END); + highlights_query_length = ftell (fp); + rewind (fp); + + XTREE_SITTER (sitter)->highlights_query = + xzalloc(highlights_query_length + 1); + + if (1 != fread (XTREE_SITTER (sitter)->highlights_query, + highlights_query_length, 1, fp)) + { + suberror = highlights_scm; + error = "Cannot fread"; + } + else + { + TSHighlightError ts_highlight_error = + ts_highlighter_add_language + (ret, + scope, + NULL, + ts_parser_language (XTREE_SITTER (sitter)->parser), + XTREE_SITTER (sitter)->highlights_query, + "", + "", + strlen (XTREE_SITTER (sitter)->highlights_query), + 0, + 0); + if (ts_highlight_error != TSHighlightOk) + { + suberror = make_fixnum (ts_highlight_error); + error = "ts_highlighter_add_language non-Ok return"; + } + } + } + + finally: + SAFE_FREE (); + if (fp != NULL) + fclose (fp); + if (error != NULL) + xsignal2 (Qtree_sitter_error, build_string (error), suberror); + } + return ret; +} + +static Lisp_Object +do_highlights (Lisp_Object beg, Lisp_Object end, HighlightsFunctor fn) +{ + Lisp_Object retval = Qnil, sitter; + + CHECK_FIXNUM (beg); + CHECK_FIXNUM (end); + sitter = Ftree_sitter (Fcurrent_buffer ()); + if (! NILP (sitter)) + { + TSHighlighter *ts_highlighter = ensure_highlighter (sitter); + Lisp_Object language = Fcdr_safe (Fassq (XTREE_SITTER (sitter)->progmode, + Fsymbol_value (Qtree_sitter_mode_alist))); + char *scope; + + USE_SAFE_ALLOCA; + scope = SAFE_ALLOCA (strlen ("scope.") + SCHARS (language) + 1); + sprintf (scope, "scope.%s", SSDATA (language)); + + if (ts_highlighter) + { + for (TSNode node = ts_node_first_child_for_byte + (ts_tree_root_node (XTREE_SITTER (sitter)->tree), + BUFFER_TO_SITTER (XFIXNUM (beg))); + (! ts_node_is_null (node) + && ts_node_start_byte (node) < BUFFER_TO_SITTER (XFIXNUM (end))); + node = ts_node_next_sibling (node)) + { + Lisp_Object + node_start = make_fixnum (SITTER_TO_BUFFER (ts_node_start_byte (node))), + node_end = make_fixnum (SITTER_TO_BUFFER (ts_node_end_byte (node))), + source_code = Fbuffer_substring_no_properties (node_start, node_end); + TSHighlightEventSlice ts_highlight_event_slice = + (TSHighlightEventSlice) { NULL, 0 }; + TSHighlightBuffer *ts_highlight_buffer = ts_highlight_buffer_new (); + + /* source code is relative coords */ + uint32_t restore_start = node.context[0]; + node.context[0] = 0; + + ts_highlight_event_slice = + ts_highlighter_return_highlights (ts_highlighter, scope, + SSDATA (source_code), + (uint32_t) SBYTES (source_code), + &node, + ts_highlight_buffer); + + /* restore to absolute coords */ + node.context[0] = restore_start; + retval = nconc2 (fn (&ts_highlight_event_slice, &node, + XTREE_SITTER (sitter)->highlight_names), + retval); + ts_highlighter_free_highlights (ts_highlight_event_slice); + ts_highlight_buffer_delete (ts_highlight_buffer); + } + } + + SAFE_FREE (); + } + return retval; +} + +DEFUN ("tree-sitter--testable", + Ftree_sitter__testable, Stree_sitter__testable, + 1, 1, 0, + doc: /* Is the bundled dynamic library readable by my OS. */) + (Lisp_Object file) +{ + CHECK_STRING (file); + return dynlib_open (SSDATA (file)) ? Qt : Qnil; +} + +DEFUN ("tree-sitter-root-node", + Ftree_sitter_root_node, Stree_sitter_root_node, + 0, 1, 0, + doc: /* Return TSNode stash from BUFFER's sitter. */) + (Lisp_Object buffer) +{ + Lisp_Object retval = Qnil, sitter; + + if (NILP (buffer)) + buffer = Fcurrent_buffer (); + + CHECK_BUFFER (buffer); + sitter = Ftree_sitter (buffer); + + if (! NILP (sitter)) + { + const TSTree *tree = XTREE_SITTER (sitter)->tree; + if (tree != NULL) + retval = build_string (ts_node_string (ts_tree_root_node (tree))); + } + return retval; +} + +DEFUN ("tree-sitter-highlights", + Ftree_sitter_highlights, Stree_sitter_highlights, + 2, 2, "r", + doc: /* Return list of highlights from BEG to END. */) + (Lisp_Object beg, Lisp_Object end) +{ + return do_highlights (beg, end, &list_highlights); +} + +DEFUN ("tree-sitter-highlight-region", + Ftree_sitter_highlight_region, Stree_sitter_highlight_region, + 2, 2, "r", + doc: /* Highlight BEG to END. */) + (Lisp_Object beg, Lisp_Object end) +{ + Lisp_Object bounds = do_highlights (beg, end, &highlight_region); + return NILP (bounds) + ? bounds : Fcons (apply1 (intern ("min"), bounds), + apply1 (intern ("max"), bounds)); +} + +DEFUN ("tree-sitter-changed-range", + Ftree_sitter_changed_range, Stree_sitter_changed_range, + 0, 1, 0, + doc: /* Return list of BEG and END of TSRange. */) + (Lisp_Object buffer) +{ + Lisp_Object retval = Qnil, sitter; + + if (NILP (buffer)) + buffer = Fcurrent_buffer (); + + CHECK_BUFFER (buffer); + sitter = Ftree_sitter (buffer); + + if (! NILP (sitter)) + { + const TSTree *tree = XTREE_SITTER (sitter)->tree, + *prev_tree = XTREE_SITTER (sitter)->prev_tree; + if (tree != NULL && prev_tree != NULL) + { + uint32_t count; + TSRange *range = ts_tree_get_changed_ranges (prev_tree, tree, &count); + retval = list2 (make_fixnum (SITTER_TO_BUFFER (range->start_byte)), + make_fixnum (SITTER_TO_BUFFER (range->end_byte))); + free (range); + } + } + return retval; +} + +static const char* +tree_sitter_read_buffer (void *payload, uint32_t byte_index, + TSPoint position, uint32_t *bytes_read) +{ + static int thread_unsafe_last_scan_characters = -1; + static char *thread_unsafe_return_value = NULL; + EMACS_INT start = SITTER_TO_BUFFER (byte_index); + struct buffer *bp = (struct buffer *) payload; + ptrdiff_t pdl_count = SPECPDL_INDEX (); + + if (thread_unsafe_last_scan_characters != tree_sitter_scan_characters) + { + if (thread_unsafe_return_value == NULL) + thread_unsafe_return_value = xmalloc (tree_sitter_scan_characters + 1); + else + thread_unsafe_return_value = xrealloc (thread_unsafe_return_value, + tree_sitter_scan_characters + 1); + } + + if (! BUFFER_LIVE_P (bp)) + error ("Selecting deleted buffer"); + + record_unwind_protect (save_restriction_restore, save_restriction_save ()); + Fwiden (); + sprintf (thread_unsafe_return_value, "%s", + SSDATA (Fbuffer_substring_no_properties + (make_fixnum (start), + make_fixnum (min (start + tree_sitter_scan_characters, + BUF_Z (bp) - BUF_BEG (bp) + 1))))); + unbind_to (pdl_count, Qnil); + if (bytes_read) + *bytes_read = strlen (thread_unsafe_return_value); + return thread_unsafe_return_value; +} + +DEFUN ("tree-sitter", + Ftree_sitter, Stree_sitter, + 0, 1, 0, + doc: /* Return BUFFER's Lisp_Tree_Sitter. */) + (Lisp_Object buffer) +{ + Lisp_Object sitter; + if (NILP (buffer)) + buffer = Fcurrent_buffer (); + + CHECK_BUFFER (buffer); + + sitter = Fbuffer_local_value (Qtree_sitter_sitter, buffer); + if (NILP (sitter) + || ! EQ (XTREE_SITTER (sitter)->progmode, BVAR (XBUFFER (buffer), major_mode))) + { + sitter = Fset (Qtree_sitter_sitter, + tree_sitter_create (BVAR (XBUFFER (buffer), major_mode))); + } + + if (! NILP (sitter)) + { + if (XTREE_SITTER (sitter)->tree == NULL) + { + XTREE_SITTER (sitter)->tree = + ts_parser_parse (XTREE_SITTER (sitter)->parser, + XTREE_SITTER (sitter)->tree, + (TSInput) { + XBUFFER (buffer), + tree_sitter_read_buffer, + TSInputEncodingUTF8 + }); + if (XTREE_SITTER (sitter)->tree == NULL) + xsignal1 (Qtree_sitter_parse_error, BVAR (XBUFFER (buffer), name)); + } + } + return sitter; +} + +/* TODO buffers not utf-8-clean. */ +void +tree_sitter_record_change (ptrdiff_t start_char, ptrdiff_t old_end_char, + ptrdiff_t new_end_char) +{ + Lisp_Object sitter = Fbuffer_local_value (Qtree_sitter_sitter, Fcurrent_buffer ()); + if (! NILP (sitter)) + { + TSTree *tree = XTREE_SITTER (sitter)->tree; + if (tree != NULL) + { + static const TSPoint dummy_point = { 0, 0 }; + TSInputEdit edit = { + BUFFER_TO_SITTER (start_char), + BUFFER_TO_SITTER (old_end_char), + BUFFER_TO_SITTER (new_end_char), + dummy_point, + dummy_point, + dummy_point + }; + + if (XTREE_SITTER (sitter)->prev_tree != NULL) + ts_tree_delete (XTREE_SITTER (sitter)->prev_tree); + XTREE_SITTER (sitter)->prev_tree = ts_tree_copy (tree); + ts_tree_edit (tree, &edit); + XTREE_SITTER (sitter)->tree = + ts_parser_parse (XTREE_SITTER (sitter)->parser, + tree, + (TSInput) { + current_buffer, + tree_sitter_read_buffer, + TSInputEncodingUTF8 + }); + ts_tree_delete (tree); + } + else + { + xsignal1 (Qtree_sitter_error, BVAR (XBUFFER (Fcurrent_buffer ()), name)); + } + } +} + +DEFUN ("tree-sitter-expand-pattern", + Ftree_sitter_expand_pattern, + Stree_sitter_expand_pattern, 1, 1, 0, + doc: /* Expand PATTERN to its string form. + +PATTERN can be + + :anchor + :? + :* + :+ + (TYPE PATTERN...) + [PATTERN...] + FIELD-NAME: + @CAPTURE-NAME + (_) + _ + \"TYPE\" */) + (Lisp_Object pattern) +{ + if (EQ (pattern, intern_c_string (":anchor"))) + return build_pure_c_string("."); + if (EQ (pattern, intern_c_string (":?"))) + return build_pure_c_string("?"); + if (EQ (pattern, intern_c_string (":*"))) + return build_pure_c_string("*"); + if (EQ (pattern, intern_c_string (":+"))) + return build_pure_c_string("+"); + if (VECTORP (pattern) || CONSP (pattern)) + return concat3 (build_pure_c_string (VECTORP (pattern) ? "[" : "("), + Fmapconcat (intern_c_string + ("tree-sitter-expand-pattern"), + pattern, + build_pure_c_string (" ")), + build_pure_c_string (VECTORP (pattern) ? "]" : ")")); + return CALLN (Fformat, build_pure_c_string("%S"), pattern); +} + +DEFUN ("tree-sitter-query-capture", + Ftree_sitter_query_capture, + Stree_sitter_query_capture, 4, 4, 0, + doc: /* Query NODE with PATTERN. + +Returns a list of (CAPTURE_NAME . NODE). CAPTURE_NAME is the name +assigned to the node in PATTERN. NODE is the captured node. + +PATTERN is either a string containing one or more matching patterns, +or a list containing one or more s-expression matching patterns. + +BEG and END specify the query range. + +Raise an tree-sitter-query-error if PATTERN is malformed. See the +info node for how to read the error message. */) + (Lisp_Object node, Lisp_Object pattern, + Lisp_Object beg, Lisp_Object end) +{ + TSNode *ts_node = (TSNode *) xmint_pointer (node); + TSQueryError error_type; + TSQuery *query; + TSQueryCursor *cursor; + TSQueryMatch match; + uint32_t error_offset, start_byte0, end_byte0; + Lisp_Object result = Qnil; + TSLanguageFunctor fn = + tree_sitter_language_functor (BVAR (current_buffer, major_mode)); + + validate_region (&beg, &end); + start_byte0 = XFIXNUM (beg); + end_byte0 = XFIXNUM (end); + + if (CONSP (pattern)) + pattern = Ftree_sitter_expand_pattern (pattern); + CHECK_STRING (pattern); + + query = ts_query_new (fn (), SSDATA (pattern), strlen (SSDATA (pattern)), + &error_offset, &error_type); + + if (query == NULL) + xsignal1 (Qtree_sitter_query_error, make_fixnum (error_type)); + + cursor = ts_query_cursor_new (); + + ts_query_cursor_set_byte_range (cursor, start_byte0, end_byte0); + ts_query_cursor_exec (cursor, query, *ts_node); + + while (ts_query_cursor_next_match (cursor, &match)) + { + const TSQueryCapture *captures = match.captures; + for (int idx = 0; idx < match.capture_count; idx++) + { + TSQueryCapture capture; + Lisp_Object captured_node; + const char *capture_name; + Lisp_Object entry; + uint32_t capture_name_len; + + capture = captures[idx]; + /* captured_node = make_tree_sitter_node (lisp_parser, capture.node); */ + captured_node = Qnil; + capture_name = ts_query_capture_name_for_id + (query, capture.index, &capture_name_len); + entry = Fcons (intern_c_string (capture_name), captured_node); + result = Fcons (entry, result); + } + } + ts_query_delete (query); + ts_query_cursor_delete (cursor); + return Freverse (result); +} + +void +syms_of_tree_sitter (void) +{ + DEFSYM (Qtree_sitter_error, "tree-sitter-error"); + DEFSYM (Qtree_sitter_parse_error, "tree-sitter-parse-error"); + DEFSYM (Qtree_sitter_query_error, "tree-sitter-query-error"); + DEFSYM (Qtree_sitter_language_error, "tree-sitter-language-error"); + DEFSYM (Qtree_sitterp, "tree-sitterp"); + + define_error (Qtree_sitter_error, "Generic tree-sitter exception", Qerror); + define_error (Qtree_sitter_parse_error, "Parse error", + Qtree_sitter_error); + define_error (Qtree_sitter_query_error, "Query pattern is malformed", + Qtree_sitter_error); + define_error (Qtree_sitter_language_error, "Cannot load language", + Qtree_sitter_error); + + DEFVAR_INT ("tree-sitter-scan-characters", tree_sitter_scan_characters, + doc: /* Number of characters to read per tree sitter scan. */); + tree_sitter_scan_characters = 1024; + + DEFSYM (Qtree_sitter_mode_alist, "tree-sitter-mode-alist"); + DEFSYM (Qtree_sitter_highlight_alist, "tree-sitter-highlight-alist"); + DEFSYM (Qtree_sitter_sitter, "tree-sitter-sitter"); + Fmake_variable_buffer_local (Qtree_sitter_sitter); + + defsubr (&Stree_sitter); + defsubr (&Stree_sitter_root_node); + defsubr (&Stree_sitter_highlights); + defsubr (&Stree_sitter_highlight_region); + defsubr (&Stree_sitter_changed_range); + defsubr (&Stree_sitter__testable); + + /* defsubr (&Stree_sitter_node_type); */ + /* defsubr (&Stree_sitter_node_start); */ + /* defsubr (&Stree_sitter_node_end); */ + /* defsubr (&Stree_sitter_node_string); */ + /* defsubr (&Stree_sitter_node_parent); */ + /* defsubr (&Stree_sitter_node_child); */ + /* defsubr (&Stree_sitter_node_check); */ + /* defsubr (&Stree_sitter_node_child_by_field_name); */ + /* defsubr (&Stree_sitter_node_next_sibling); */ + /* defsubr (&Stree_sitter_node_prev_sibling); */ + /* defsubr (&Stree_sitter_node_first_child_for_pos); */ + /* defsubr (&Stree_sitter_node_eq); */ + defsubr (&Stree_sitter_expand_pattern); + defsubr (&Stree_sitter_query_capture); +} diff --git a/src/tree-sitter.h b/src/tree-sitter.h new file mode 100644 index 0000000000..2022a5016c --- /dev/null +++ b/src/tree-sitter.h @@ -0,0 +1,63 @@ +/* Header file for the tree-sitter integration. + +Copyright (C) 2021 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or (at +your option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see . */ + +#ifndef EMACS_TREE_SITTER_H +#define EMACS_TREE_SITTER_H + +#include "lisp.h" + +#include +#include + +INLINE_HEADER_BEGIN + +struct Lisp_Tree_Sitter +{ + union vectorlike_header header; + Lisp_Object progmode; + TSParser *parser; + TSTree *prev_tree; + TSTree *tree; + TSHighlighter *highlighter; + const char **highlight_names; + char *highlights_query; +}; + +INLINE bool +TREE_SITTERP (Lisp_Object x) +{ + return PSEUDOVECTORP (x, PVEC_TREE_SITTER); +} + +INLINE struct Lisp_Tree_Sitter * +XTREE_SITTER (Lisp_Object a) +{ + eassert (TREE_SITTERP (a)); + return XUNTAG (a, Lisp_Vectorlike, struct Lisp_Tree_Sitter); +} + +INLINE void +CHECK_TREE_SITTER (Lisp_Object sitter) +{ + CHECK_TYPE (TREE_SITTERP (sitter), Qtree_sitterp, sitter); +} + +INLINE_HEADER_END + +#endif /* EMACS_TREE_SITTER_H */ diff --git a/src/xdisp.c b/src/xdisp.c index aa01db210b..9f670059a7 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -4515,7 +4515,6 @@ handle_face_prop (struct it *it) && IT_CHARPOS (*it) > BEG) { const int prev_face_id = face_before_it_pos (it); - old_face = FACE_FROM_ID_OR_NULL (it->f, prev_face_id); } @@ -8903,9 +8902,9 @@ next_element_from_buffer (struct it *it) success_p = false; } } - else if (!(!it->bidi_p - || BIDI_AT_BASE_LEVEL (it->bidi_it) - || IT_CHARPOS (*it) == it->stop_charpos)) + else if (it->bidi_p + && ! BIDI_AT_BASE_LEVEL (it->bidi_it) + && IT_CHARPOS (*it) != it->stop_charpos) { /* With bidi non-linear iteration, we could find ourselves far beyond the last computed stop_charpos, with several @@ -35057,6 +35056,9 @@ syms_of_xdisp (void) DEFSYM (Qfontified, "fontified"); DEFSYM (Qfontification_functions, "fontification-functions"); + Vtext_property_default_nonsticky + = Fcons (Fcons (Qfontified, Qt), Vtext_property_default_nonsticky); + /* Name of the symbol which disables Lisp evaluation in 'display' properties. This is used by enriched.el. */ DEFSYM (Qdisable_eval, "disable-eval"); diff --git a/test/src/tree-sitter-resources/bin/c.so b/test/src/tree-sitter-resources/bin/c.so new file mode 100755 index 0000000000000000000000000000000000000000..e61700cdf2bed6d1f5a9277c36241fe829978c9a GIT binary patch literal 399152 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 {p>C@NLAuCwKwj4T8DsD}uRUPYLGKJt&wv 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" + "!" "||" "&&" + "->" + "==" "!=" "<" ">" "<=" ">=" + "=" "+=" "-=" "*=" "/=" "%=" "|=" "&=" + "++" "--" +] @operator + +(conditional_expression ["?" ":"] @operator) + +["(" ")" "[" "]" "{" "}"] @punctuation.bracket + +["." "," ";"] @punctuation.delimiter + +;;; ---------------------------------------------------------------------------- +;;; Functions. + +(call_expression + function: [(identifier) @function.call + (field_expression field: (_) @method.call)]) + +(function_declarator + declarator: [(identifier) @function + (parenthesized_declarator + (pointer_declarator (field_identifier) @function))]) + +(preproc_function_def + name: (identifier) @function) + +;;; ---------------------------------------------------------------------------- +;;; Types. + +[(primitive_type) + (sized_type_specifier)] @type.builtin + +(type_identifier) @type + +;;; ---------------------------------------------------------------------------- +;;; Variables. + +(declaration declarator: [(identifier) @variable + (_ (identifier) @variable)]) + +(parameter_declaration declarator: [(identifier) @variable.parameter + (_ (identifier) @variable.parameter)]) + +(init_declarator declarator: [(identifier) @variable + (_ (identifier) @variable)]) + +(assignment_expression + left: [(identifier) @variable + (field_expression field: (_) @variable) + (subscript_expression argument: (identifier) @variable) + (pointer_expression (identifier) @variable)]) + +(update_expression + argument: (identifier) @variable) + +(preproc_def name: (identifier) @variable.special) + +(preproc_params + (identifier) @variable.parameter) + +;;; ---------------------------------------------------------------------------- +;;; Properties. + +(field_declaration + declarator: [(field_identifier) @property.definition + (pointer_declarator (field_identifier) @property.definition) + (pointer_declarator (pointer_declarator (field_identifier) @property.definition))]) + +(enumerator name: (identifier) @property.definition) + +(field_identifier) @property + +;;; ---------------------------------------------------------------------------- +;;; Misc. + +((identifier) @constant + (.match? @constant "^[A-Z_][A-Z_\\d]*$")) + +[(null) (true) (false)] @constant.builtin + +[(number_literal) + (char_literal)] @number + +(statement_identifier) @label + +;;; ---------------------------------------------------------------------------- +;;; Strings and comments. + +(comment) @comment + +[(string_literal) + (system_lib_string)] @string diff --git a/test/src/tree-sitter-tests.el b/test/src/tree-sitter-tests.el new file mode 100644 index 0000000000..0c620d562a --- /dev/null +++ b/test/src/tree-sitter-tests.el @@ -0,0 +1,111 @@ +;;; tree-sitter-tests.el --- tests for src/tree-sitter.c -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc. + +;; This file is NOT part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert) +(require 'tree-sitter) + +(defsubst tree-sitter-testable () + (when-let ((dylib (locate-file "bin/c" load-path (split-string ".so")))) + (tree-sitter--testable dylib))) + +(defmacro tree-sitter-tests-with-load-path (&rest body) + (declare (indent defun)) + `(let ((load-path (cons (expand-file-name "src/tree-sitter-resources") + load-path))) + (skip-unless (tree-sitter-testable)) + ,@body)) + +(defmacro tree-sitter-tests-doit (ext text &rest body) + "font-lock setup is highly circular, redundant, and difficult to isolate." + (declare (indent defun)) + `(tree-sitter-tests-with-load-path + (let ((dir (make-temp-file "tree-sitter-tests" t)) + (text (replace-regexp-in-string "^\n" "" ,text))) + (unwind-protect + (let ((font-lock-support-mode 'tree-sitter-lock-mode) + (file-name (expand-file-name (concat "tree-sitter-tests" ,ext) dir)) + font-lock-global-modes ;; only works when not interactive + enable-dir-local-variables) + (with-temp-file file-name (insert text)) + (find-file file-name) + (let (noninteractive) + (turn-on-font-lock)) + (should font-lock-mode) + (should tree-sitter-lock-mode) + (should-not (text-property-any (point-min) (point-max) 'fontified nil)) + ,@body) + (let (kill-buffer-query-functions) + (kill-buffer)) + (delete-directory dir t))))) + +(ert-deftest tree-sitter-basic-parsing () + "Test basic parsing routines." + (let ((text " +void main (void) { + return 0; +} +")) + (tree-sitter-tests-doit ".c" text + (should (equal (tree-sitter-highlights (point-min) (point-max)) + '(font-lock-type-face (1 . 5) nil (5 . 6) font-lock-function-name-face (6 . 10) nil (10 . 12) font-lock-type-face (12 . 16) nil (16 . 22) font-lock-keyword-face (22 . 28) nil (28 . 29) font-lock-constant-face (29 . 30) nil (30 . 33)))) + (goto-char (point-min)) + (forward-line 1) + (insert "\n printf(\"hello world\");\n") + (should (equal (tree-sitter-highlights (point-min) (point-max)) + '(font-lock-type-face (1 . 5) nil (5 . 6) font-lock-function-name-face (6 . 10) nil (10 . 12) font-lock-type-face (12 . 16) nil (16 . 23) font-lock-function-name-face (23 . 29) nil (29 . 30) font-lock-string-face (30 . 43) nil (43 . 48) font-lock-keyword-face (48 . 54) nil (54 . 55) font-lock-constant-face (55 . 56) nil (56 . 59))))))) + +(ert-deftest tree-sitter-how-fast () + "How fast can it fontify xdisp.c" + (tree-sitter-tests-with-load-path + (cl-flet ((bench + (file mode reps unfontify fontify) + (save-window-excursion + (find-file-literally file) + (let (font-lock-maximum-size + (font-lock-support-mode mode) + enable-dir-local-variables + font-lock-global-modes + font-lock-fontified) + (set-auto-mode) + (cl-letf (((symbol-function 'font-lock-initial-fontify) #'ignore)) + (let (noninteractive) + (turn-on-font-lock))) + (cl-assert (null (text-property-any (point-min) (point-max) 'fontified t))) + (unwind-protect + (benchmark-run reps + (funcall unfontify (point-min) (point-max)) + (funcall fontify (point-min) (point-max)) + (cl-assert (null (text-property-any (point-min) (point-max) 'fontified nil)))) + (let (kill-buffer-query-functions) + (kill-buffer))))))) + (let ((fast (car (bench (expand-file-name "src/xdisp.c" "..") + 'tree-sitter-lock-mode 1 + #'font-lock-unfontify-region + #'font-lock-fontify-region))) + (slow (car (bench (expand-file-name "src/xdisp.c" "..") + 'jit-lock-mode 1 + #'jit-lock-refontify + #'jit-lock-fontify-now)))) + (message "tree-sitter-how-fast: %s versus %s" fast slow) + (should (< fast (/ slow 3))))))) + +(provide 'tree-sitter-tests) +;;; tree-sitter-tests.el ends here -- 2.26.2