Package: emacs;
Reported by: Juri Linkov <juri <at> linkov.net>
Date: Thu, 16 Jan 2025 17:50:01 UTC
Severity: wishlist
Tags: patch
Fixed in version 31.0.50
Done: Juri Linkov <juri <at> linkov.net>
Bug is archived. No further changes may be made.
To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 75609 in the body.
You can then email your comments to 75609 AT debbugs.gnu.org in the normal way.
Toggle the display of automated, internal messages from the tracker.
View this report as an mbox folder, status mbox, maintainer mbox
casouri <at> gmail.com, bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Thu, 16 Jan 2025 17:50:01 GMT) Full text and rfc822 format available.Juri Linkov <juri <at> linkov.net>
:casouri <at> gmail.com, bug-gnu-emacs <at> gnu.org
.
(Thu, 16 Jan 2025 17:50:01 GMT) Full text and rfc822 format available.Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
From: Juri Linkov <juri <at> linkov.net> To: bug-gnu-emacs <at> gnu.org Subject: Hideshow support for treesitter Date: Thu, 16 Jan 2025 19:45:11 +0200
[Message part 1 (text/plain, inline)]
Tags: patch Now that we have the new list thing in tree-sitter, it became possible to implement the hideshow support to hide the list things, i.e. exactly what hs-minor-mode did in non-ts modes:
[treesit-hideshow.patch (text/x-diff, inline)]
diff --git a/lisp/treesit.el b/lisp/treesit.el index ac34edaf84d..0e48fb91b44 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -3420,6 +3420,65 @@ treesit-outline-level (setq level (1+ level))) (if (zerop level) 1 level))) +;;; Hideshow mode + +(defun treesit-hs-block-end () + (let* ((pred 'list) + (thing (treesit-thing-at + (if (bobp) (point) (1- (point))) pred)) + (end (when thing (treesit-node-end thing))) + (last (when thing (treesit-node-child thing -1))) + (beg (if last (treesit-node-start last) + (if (bobp) (point) (1- (point)))))) + (when (and thing (eq (point) end)) + (set-match-data (list beg end)) + t))) + +(defun treesit-hs-find-block-beginning () + (let* ((pred 'list) + (thing (treesit-thing-at (point) pred)) + (thing (or thing (treesit-parent-until (treesit-node-at (point)) pred))) + (beg (when thing (treesit-node-start thing))) + (end (when thing (treesit-node-end thing)))) + (when thing + (goto-char beg) + (set-match-data (list beg end)) + t))) + +(defun treesit-hs-find-next-block (_regexp _maxp comments) + (let* ((pred (if comments '(or list "comment") 'list)) + ;; `treesit-navigate-thing' can't find a thing at bobp, + ;; so use `treesit-thing-at' to match at bobp. + (current (treesit-thing-at (point) pred)) + (beg (or (and current (eq (point) (treesit-node-start current)) (point)) + (treesit-navigate-thing (point) 1 'beg pred))) + (thing (when beg (treesit-thing-at beg pred))) + (end (when thing (treesit-node-end thing)))) + (when thing + (goto-char end) + (set-match-data + (if (and comments (equal (treesit-node-type thing) "comment")) + (list beg end nil nil beg end) + (list beg end beg end))) + t))) + +(defun treesit-hs-looking-at-block-start-p () + (let* ((pred 'list) + (thing (treesit-thing-at (point) pred)) + (beg (when thing (treesit-node-start thing))) + (first (when thing (treesit-node-child thing 0))) + (end (if first (treesit-node-end first) (1+ (point))))) + (when (and thing (eq (point) beg)) + (set-match-data (list beg end)) + t))) + +(defun treesit-hs-inside-comment-p () + (let ((thing (or (treesit-thing-at (point) "comment") + (unless (bobp) + (treesit-thing-at (1- (point)) "comment"))))) + (when thing + (list (treesit-node-start thing) (treesit-node-end thing))))) + ;;; Show paren mode (defun treesit-show-paren-data--categorize (pos &optional end-p) @@ -3603,7 +3662,17 @@ treesit-major-mode-setup (setq-local forward-list-function #'treesit-forward-list) (setq-local down-list-function #'treesit-down-list) (setq-local up-list-function #'treesit-up-list) - (setq-local show-paren-data-function 'treesit-show-paren-data)) + (setq-local show-paren-data-function #'treesit-show-paren-data) + (setq hs-block-start-regexp nil + hs-block-start-mdata-select 0 + hs-block-end-regexp #'treesit-hs-block-end + hs-c-start-regexp nil + hs-forward-sexp-func #'forward-list + hs-adjust-block-beginning nil + hs-find-block-beginning-func #'treesit-hs-find-block-beginning + hs-find-next-block-func #'treesit-hs-find-next-block + hs-looking-at-block-start-p-func #'treesit-hs-looking-at-block-start-p + hs-inside-comment-p-func #'treesit-hs-inside-comment-p)) (when (treesit-thing-defined-p 'sentence nil) (setq-local forward-sentence-function #'treesit-forward-sentence)) diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 823eb0527c6..ea7bc738a4d 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -259,14 +259,11 @@ hs-special-modes-alist ;; to the mode hierarchy. (mapcar #'purecopy '((c-mode "{" "}" "/[*/]" nil nil) - (c-ts-mode "{" "}" "/[*/]" nil nil) (c++-mode "{" "}" "/[*/]" nil nil) - (c++-ts-mode "{" "}" "/[*/]" nil nil) (bibtex-mode ("@\\S(*\\(\\s(\\)" 1)) (java-mode "{" "}" "/[*/]" nil nil) (java-ts-mode "{" "}" "/[*/]" nil nil) (js-mode "{" "}" "/[*/]" nil) - (js-ts-mode "{" "}" "/[*/]" nil) (lua-ts-mode "{\\|\\[\\[" "}\\|\\]\\]" "--" nil) (mhtml-mode "{\\|<[^/>]*?" "}\\|</[^/>]*[^/]>" "<!--" mhtml-forward nil) ;; Add more support here. @@ -481,6 +478,9 @@ hs-looking-at-block-start-p-func Python, where `looking-at' and `syntax-ppss' check is not enough to check if the point is at the block start.") +(defvar-local hs-inside-comment-p-func nil + "Function used to check if point is inside a comment.") + (defvar hs-headline nil "Text of the line where a hidden block begins, set during isearch. You can display this in the mode line by adding the symbol `hs-headline' @@ -625,9 +625,13 @@ hs-hide-block-at-point (setq p (line-end-position))) ;; `q' is the point at the end of the block (hs-forward-sexp mdata 1) - (setq q (if (looking-back hs-block-end-regexp nil) - (match-beginning 0) - (point))) + (setq q (cond ((and (stringp hs-block-end-regexp) + (looking-back hs-block-end-regexp nil)) + (match-beginning 0)) + ((functionp hs-block-end-regexp) + (funcall hs-block-end-regexp) + (match-beginning 0)) + (t (point)))) (when (and (< p q) (> (count-lines p q) 1)) (cond ((and hs-allow-nesting (setq ov (hs-overlay-at p))) (delete-overlay ov)) @@ -644,6 +648,9 @@ hs-inside-comment-p beginning. If we are inside of a comment but this condition is not met, we return a list having a nil as its car and the end of comment position as cdr." + (cond ((functionp hs-inside-comment-p-func) + (funcall hs-inside-comment-p-func)) + (t (save-excursion ;; the idea is to look backwards for a comment start regexp, do a ;; forward comment, and see if we are inside, then extend @@ -692,7 +699,7 @@ hs-inside-comment-p (skip-chars-backward " \t\n\f") (end-of-line) (when (>= (point) q) - (list (and hideable p) (point)))))))) + (list (and hideable p) (point)))))))))) (defun hs-grok-mode-type () "Set up hideshow variables for new buffers. @@ -704,7 +711,7 @@ hs-grok-mode-type (bound-and-true-p comment-end)) (let* ((lookup (assoc major-mode hs-special-modes-alist)) (start-elem (or (nth 1 lookup) "\\s("))) - (if (listp start-elem) + (if (consp start-elem) ;; handle (START-REGEXP MDATA-SELECT) (setq hs-block-start-regexp (car start-elem) hs-block-start-mdata-select (cadr start-elem)) @@ -850,14 +857,16 @@ hs-hide-all (syntax-propertize (point-max)) (let ((spew (make-progress-reporter "Hiding all blocks..." (point-min) (point-max))) - (re (concat "\\(" - hs-block-start-regexp - "\\)" - (if hs-hide-comments-when-hiding-all - (concat "\\|\\(" - hs-c-start-regexp - "\\)") - "")))) + (re (when (stringp hs-block-start-regexp) + (concat "\\(" + hs-block-start-regexp + "\\)" + (if (and hs-hide-comments-when-hiding-all + (stringp hs-c-start-regexp)) + (concat "\\|\\(" + hs-c-start-regexp + "\\)") + ""))))) (while (funcall hs-find-next-block-func re (point-max) hs-hide-comments-when-hiding-all) (if (match-beginning 1) @@ -869,7 +878,9 @@ hs-hide-all (hs-hide-block-at-point t)) ;; Go to end of matched data to prevent from getting stuck ;; with an endless loop. - (when (looking-at hs-block-start-regexp) + (when (if (stringp hs-block-start-regexp) + (looking-at hs-block-start-regexp) + (eq (point) (match-beginning 0))) (goto-char (match-end 0))))) ;; found a comment, probably (let ((c-reg (hs-inside-comment-p))) @@ -1008,7 +1019,8 @@ hs-minor-mode (setq hs-headline nil) (if hs-minor-mode (progn - (hs-grok-mode-type) + (unless (buffer-local-value 'hs-inside-comment-p-func (current-buffer)) + (hs-grok-mode-type)) ;; Turn off this mode if we change major modes. (add-hook 'change-major-mode-hook #'turn-off-hideshow
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Sat, 18 Jan 2025 07:41:02 GMT) Full text and rfc822 format available.Message #8 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Yuan Fu <casouri <at> gmail.com> To: Juri Linkov <juri <at> linkov.net> Cc: 75609 <at> debbugs.gnu.org Subject: Re: bug#75609: Hideshow support for treesitter Date: Fri, 17 Jan 2025 23:40:16 -0800
> On Jan 16, 2025, at 9:45 AM, Juri Linkov <juri <at> linkov.net> wrote: > > Tags: patch > > Now that we have the new list thing in tree-sitter, it became possible > to implement the hideshow support to hide the list things, i.e. > exactly what hs-minor-mode did in non-ts modes: Awesome! Thanks :-) Some comments below: > > diff --git a/lisp/treesit.el b/lisp/treesit.el > index ac34edaf84d..0e48fb91b44 100644 > --- a/lisp/treesit.el > +++ b/lisp/treesit.el > @@ -3420,6 +3420,65 @@ treesit-outline-level > (setq level (1+ level))) > (if (zerop level) 1 level))) > > +;;; Hideshow mode > + > +(defun treesit-hs-block-end () > + (let* ((pred 'list) > + (thing (treesit-thing-at > + (if (bobp) (point) (1- (point))) pred)) > + (end (when thing (treesit-node-end thing))) > + (last (when thing (treesit-node-child thing -1))) > + (beg (if last (treesit-node-start last) > + (if (bobp) (point) (1- (point)))))) > + (when (and thing (eq (point) end)) > + (set-match-data (list beg end)) > + t))) > + > +(defun treesit-hs-find-block-beginning () > + (let* ((pred 'list) > + (thing (treesit-thing-at (point) pred)) > + (thing (or thing (treesit-parent-until (treesit-node-at (point)) pred))) > + (beg (when thing (treesit-node-start thing))) > + (end (when thing (treesit-node-end thing)))) > + (when thing > + (goto-char beg) > + (set-match-data (list beg end)) > + t))) > + > +(defun treesit-hs-find-next-block (_regexp _maxp comments) > + (let* ((pred (if comments '(or list "comment") 'list)) > + ;; `treesit-navigate-thing' can't find a thing at bobp, > + ;; so use `treesit-thing-at' to match at bobp. > + (current (treesit-thing-at (point) pred)) > + (beg (or (and current (eq (point) (treesit-node-start current)) (point)) > + (treesit-navigate-thing (point) 1 'beg pred))) > + (thing (when beg (treesit-thing-at beg pred))) > + (end (when thing (treesit-node-end thing)))) > + (when thing > + (goto-char end) > + (set-match-data > + (if (and comments (equal (treesit-node-type thing) "comment")) > + (list beg end nil nil beg end) > + (list beg end beg end))) > + t))) > + > +(defun treesit-hs-looking-at-block-start-p () > + (let* ((pred 'list) > + (thing (treesit-thing-at (point) pred)) > + (beg (when thing (treesit-node-start thing))) > + (first (when thing (treesit-node-child thing 0))) > + (end (if first (treesit-node-end first) (1+ (point))))) > + (when (and thing (eq (point) beg)) > + (set-match-data (list beg end)) > + t))) > + > +(defun treesit-hs-inside-comment-p () > + (let ((thing (or (treesit-thing-at (point) "comment") > + (unless (bobp) > + (treesit-thing-at (1- (point)) "comment"))))) > + (when thing > + (list (treesit-node-start thing) (treesit-node-end thing))))) > + FYI some grammar calls comments line_comment and block_comment. Maybe use the comment thing first, and then match “comment” with the node type? > ;;; Show paren mode > > (defun treesit-show-paren-data--categorize (pos &optional end-p) > @@ -3603,7 +3662,17 @@ treesit-major-mode-setup > (setq-local forward-list-function #'treesit-forward-list) > (setq-local down-list-function #'treesit-down-list) > (setq-local up-list-function #'treesit-up-list) > - (setq-local show-paren-data-function 'treesit-show-paren-data)) > + (setq-local show-paren-data-function #'treesit-show-paren-data) > + (setq hs-block-start-regexp nil > + hs-block-start-mdata-select 0 > + hs-block-end-regexp #'treesit-hs-block-end > + hs-c-start-regexp nil > + hs-forward-sexp-func #'forward-list > + hs-adjust-block-beginning nil > + hs-find-block-beginning-func #'treesit-hs-find-block-beginning > + hs-find-next-block-func #'treesit-hs-find-next-block > + hs-looking-at-block-start-p-func #'treesit-hs-looking-at-block-start-p > + hs-inside-comment-p-func #'treesit-hs-inside-comment-p)) > > (when (treesit-thing-defined-p 'sentence nil) > (setq-local forward-sentence-function #'treesit-forward-sentence)) > diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el > index 823eb0527c6..ea7bc738a4d 100644 > --- a/lisp/progmodes/hideshow.el > +++ b/lisp/progmodes/hideshow.el > @@ -259,14 +259,11 @@ hs-special-modes-alist > ;; to the mode hierarchy. > (mapcar #'purecopy > '((c-mode "{" "}" "/[*/]" nil nil) > - (c-ts-mode "{" "}" "/[*/]" nil nil) > (c++-mode "{" "}" "/[*/]" nil nil) > - (c++-ts-mode "{" "}" "/[*/]" nil nil) > (bibtex-mode ("@\\S(*\\(\\s(\\)" 1)) > (java-mode "{" "}" "/[*/]" nil nil) > (java-ts-mode "{" "}" "/[*/]" nil nil) > (js-mode "{" "}" "/[*/]" nil) > - (js-ts-mode "{" "}" "/[*/]" nil) > (lua-ts-mode "{\\|\\[\\[" "}\\|\\]\\]" "--" nil) > (mhtml-mode "{\\|<[^/>]*?" "}\\|</[^/>]*[^/]>" "<!--" mhtml-forward nil) > ;; Add more support here. > @@ -481,6 +478,9 @@ hs-looking-at-block-start-p-func > Python, where `looking-at' and `syntax-ppss' check is not enough > to check if the point is at the block start.") > > +(defvar-local hs-inside-comment-p-func nil > + "Function used to check if point is inside a comment.") > + > (defvar hs-headline nil > "Text of the line where a hidden block begins, set during isearch. > You can display this in the mode line by adding the symbol `hs-headline' > @@ -625,9 +625,13 @@ hs-hide-block-at-point > (setq p (line-end-position))) > ;; `q' is the point at the end of the block > (hs-forward-sexp mdata 1) > - (setq q (if (looking-back hs-block-end-regexp nil) > - (match-beginning 0) > - (point))) > + (setq q (cond ((and (stringp hs-block-end-regexp) > + (looking-back hs-block-end-regexp nil)) > + (match-beginning 0)) > + ((functionp hs-block-end-regexp) > + (funcall hs-block-end-regexp) > + (match-beginning 0)) > + (t (point)))) > (when (and (< p q) (> (count-lines p q) 1)) > (cond ((and hs-allow-nesting (setq ov (hs-overlay-at p))) > (delete-overlay ov)) > @@ -644,6 +648,9 @@ hs-inside-comment-p > beginning. If we are inside of a comment but this condition is not met, > we return a list having a nil as its car and the end of comment position > as cdr." > + (cond ((functionp hs-inside-comment-p-func) > + (funcall hs-inside-comment-p-func)) > + (t Did you do this indentation on purpose to make a easier-to-read diff? > (save-excursion > ;; the idea is to look backwards for a comment start regexp, do a > ;; forward comment, and see if we are inside, then extend > @@ -692,7 +699,7 @@ hs-inside-comment-p > (skip-chars-backward " \t\n\f") > (end-of-line) > (when (>= (point) q) > - (list (and hideable p) (point)))))))) > + (list (and hideable p) (point)))))))))) > > (defun hs-grok-mode-type () > "Set up hideshow variables for new buffers. > @@ -704,7 +711,7 @@ hs-grok-mode-type > (bound-and-true-p comment-end)) > (let* ((lookup (assoc major-mode hs-special-modes-alist)) > (start-elem (or (nth 1 lookup) "\\s("))) > - (if (listp start-elem) > + (if (consp start-elem) > ;; handle (START-REGEXP MDATA-SELECT) > (setq hs-block-start-regexp (car start-elem) > hs-block-start-mdata-select (cadr start-elem)) > @@ -850,14 +857,16 @@ hs-hide-all > (syntax-propertize (point-max)) > (let ((spew (make-progress-reporter "Hiding all blocks..." > (point-min) (point-max))) > - (re (concat "\\(" > - hs-block-start-regexp > - "\\)" > - (if hs-hide-comments-when-hiding-all > - (concat "\\|\\(" > - hs-c-start-regexp > - "\\)") > - "")))) > + (re (when (stringp hs-block-start-regexp) > + (concat "\\(" > + hs-block-start-regexp > + "\\)" > + (if (and hs-hide-comments-when-hiding-all > + (stringp hs-c-start-regexp)) > + (concat "\\|\\(" > + hs-c-start-regexp > + "\\)") > + ""))))) > (while (funcall hs-find-next-block-func re (point-max) > hs-hide-comments-when-hiding-all) > (if (match-beginning 1) > @@ -869,7 +878,9 @@ hs-hide-all > (hs-hide-block-at-point t)) > ;; Go to end of matched data to prevent from getting stuck > ;; with an endless loop. > - (when (looking-at hs-block-start-regexp) > + (when (if (stringp hs-block-start-regexp) > + (looking-at hs-block-start-regexp) > + (eq (point) (match-beginning 0))) > (goto-char (match-end 0))))) > ;; found a comment, probably > (let ((c-reg (hs-inside-comment-p))) > @@ -1008,7 +1019,8 @@ hs-minor-mode > (setq hs-headline nil) > (if hs-minor-mode > (progn > - (hs-grok-mode-type) > + (unless (buffer-local-value 'hs-inside-comment-p-func (current-buffer)) > + (hs-grok-mode-type)) > ;; Turn off this mode if we change major modes. > (add-hook 'change-major-mode-hook > #'turn-off-hideshow
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Sat, 18 Jan 2025 17:45:02 GMT) Full text and rfc822 format available.Message #11 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Juri Linkov <juri <at> linkov.net> To: Yuan Fu <casouri <at> gmail.com> Cc: 75609 <at> debbugs.gnu.org Subject: Re: bug#75609: Hideshow support for treesitter Date: Sat, 18 Jan 2025 19:43:19 +0200
>> +(defun treesit-hs-inside-comment-p () >> + (let ((thing (or (treesit-thing-at (point) "comment") >> + (unless (bobp) >> + (treesit-thing-at (1- (point)) "comment"))))) >> + (when thing >> + (list (treesit-node-start thing) (treesit-node-end thing))))) >> + > > FYI some grammar calls comments line_comment and block_comment. > Maybe use the comment thing first, and then match “comment” with the node type? We need to introduce the new thing named "comment". It's necessary to add treesit support for 'forward-comment' that is used in many packages including 'hideshow'. This means adding the variable 'forward-comment-function' with the default value 'forward-comment-default-function'. Then will override it with 'treesit-forward-comment' that uses the comment thing. >> @@ -644,6 +648,9 @@ hs-inside-comment-p >> beginning. If we are inside of a comment but this condition is not met, >> we return a list having a nil as its car and the end of comment position >> as cdr." >> + (cond ((functionp hs-inside-comment-p-func) >> + (funcall hs-inside-comment-p-func)) >> + (t > > Did you do this indentation on purpose to make a easier-to-read diff? Indeed, this was to make it easier-to-read. And in the next patch I will just rename the default function to 'hs-inside-comment-p--default'.
Stefan Kangas <stefankangas <at> gmail.com>
to control <at> debbugs.gnu.org
.
(Tue, 21 Jan 2025 02:27:04 GMT) Full text and rfc822 format available.bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Thu, 23 Jan 2025 07:37:01 GMT) Full text and rfc822 format available.Message #16 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Juri Linkov <juri <at> linkov.net> To: Yuan Fu <casouri <at> gmail.com> Cc: 75609 <at> debbugs.gnu.org Subject: Re: bug#75609: Hideshow support for treesitter Date: Thu, 23 Jan 2025 09:34:34 +0200
[Message part 1 (text/plain, inline)]
>> Maybe use the comment thing first, and then match “comment” with the node type? > > We need to introduce the new thing named "comment". > It's necessary to add treesit support for 'forward-comment' > that is used in many packages including 'hideshow'. > > This means adding the variable 'forward-comment-function' > with the default value 'forward-comment-default-function'. > Then will override it with 'treesit-forward-comment' that > uses the comment thing. So here is the treesit implementation for the new variable forward-comment-function:
[treesit-forward-comment.patch (text/x-diff, inline)]
diff --git a/lisp/treesit.el b/lisp/treesit.el index 8d86d142e3f..894608bde81 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -2945,6 +2945,29 @@ treesit-forward-sentence (if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing) 'sentence (abs arg)))) +(defun treesit-forward-comment (&optional count) + "Tree-sitter `forward-comment-function' implementation. + +COUNT is the same as in `forward-comment'." + (let ((res t) thing) + (while (> count 0) + (skip-syntax-forward " >") + (setq thing (treesit-thing-at (point) 'comment)) + (if (and thing (eq (point) (treesit-node-start thing))) + (progn + (goto-char (min (1+ (treesit-node-end thing)) (point-max))) + (setq count (1- count))) + (setq count 0 res nil))) + (while (< count 0) + (skip-syntax-backward " >") + (setq thing (treesit-thing-at (max (1- (point)) (point-min)) 'comment)) + (if (and thing (eq (point) (treesit-node-end thing))) + (progn + (goto-char (treesit-node-start thing)) + (setq count (1+ count))) + (setq count 0 res nil))) + res)) + (defun treesit-default-defun-skipper () "Skips spaces after navigating a defun. This function tries to move to the beginning of a line, either by @@ -3654,6 +3736,9 @@ treesit-major-mode-setup (when (treesit-thing-defined-p 'sentence nil) (setq-local forward-sentence-function #'treesit-forward-sentence)) + (when (treesit-thing-defined-p 'comment nil) + (setq-local forward-comment-function #'treesit-forward-comment)) + ;; Imenu. (when (or treesit-aggregated-simple-imenu-settings treesit-simple-imenu-settings) diff --git a/src/syntax.c b/src/syntax.c index 6ffa8a94c7f..e25618ee03d 100644 --- a/src/syntax.c +++ b/src/syntax.c @@ -2434,6 +2434,9 @@ DEFUN ("forward-comment", Fforward_comment, Sforward_comment, 1, 1, 0, int dummy2; unsigned short int quit_count = 0; + if (!NILP (Vforward_comment_function)) + return calln (Vforward_comment_function, count); + CHECK_FIXNUM (count); count1 = XFIXNUM (count); stop = count1 > 0 ? ZV : BEGV; @@ -3796,6 +3799,11 @@ syms_of_syntax (void) DEFSYM (Qcomment_end_can_be_escaped, "comment-end-can-be-escaped"); Fmake_variable_buffer_local (Qcomment_end_can_be_escaped); + DEFVAR_LISP ("forward-comment-function", Vforward_comment_function, + doc: /* If non-nil, `forward-comment' delegates to this function. +Should take the same arguments and behave similarly to `forward-comment'. */); + Vforward_comment_function = Qnil; + defsubr (&Ssyntax_table_p); defsubr (&Ssyntax_table); defsubr (&Sstandard_syntax_table);
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Thu, 23 Jan 2025 09:21:02 GMT) Full text and rfc822 format available.Message #19 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Eli Zaretskii <eliz <at> gnu.org> To: Juri Linkov <juri <at> linkov.net>, Stefan Monnier <monnier <at> iro.umontreal.ca> Cc: 75609 <at> debbugs.gnu.org, casouri <at> gmail.com Subject: Re: bug#75609: Hideshow support for treesitter Date: Thu, 23 Jan 2025 11:19:44 +0200
> Cc: 75609 <at> debbugs.gnu.org > From: Juri Linkov <juri <at> linkov.net> > Date: Thu, 23 Jan 2025 09:34:34 +0200 > > +(defun treesit-forward-comment (&optional count) > + "Tree-sitter `forward-comment-function' implementation. > + > +COUNT is the same as in `forward-comment'." > + (let ((res t) thing) > + (while (> count 0) > + (skip-syntax-forward " >") > + (setq thing (treesit-thing-at (point) 'comment)) > + (if (and thing (eq (point) (treesit-node-start thing))) > + (progn > + (goto-char (min (1+ (treesit-node-end thing)) (point-max))) > + (setq count (1- count))) > + (setq count 0 res nil))) > + (while (< count 0) > + (skip-syntax-backward " >") > + (setq thing (treesit-thing-at (max (1- (point)) (point-min)) 'comment)) > + (if (and thing (eq (point) (treesit-node-end thing))) > + (progn > + (goto-char (treesit-node-start thing)) > + (setq count (1+ count))) > + (setq count 0 res nil))) > + res)) I'm curious: why do you have to use skip-syntax-forward/backward when tree-sitter already should know where a comment starts and ends? ideally, features that use tree-sitter should not need to consult any syntax tables set up by the major mode. Or what am I missing?
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Thu, 23 Jan 2025 17:41:01 GMT) Full text and rfc822 format available.Message #22 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Juri Linkov <juri <at> linkov.net> To: Eli Zaretskii <eliz <at> gnu.org> Cc: 75609 <at> debbugs.gnu.org, casouri <at> gmail.com, Stefan Monnier <monnier <at> iro.umontreal.ca> Subject: Re: bug#75609: Hideshow support for treesitter Date: Thu, 23 Jan 2025 19:39:17 +0200
>> +(defun treesit-forward-comment (&optional count) >> + "Tree-sitter `forward-comment-function' implementation. >> + >> +COUNT is the same as in `forward-comment'." >> + (let ((res t) thing) >> + (while (> count 0) >> + (skip-syntax-forward " >") >> + (setq thing (treesit-thing-at (point) 'comment)) >> + (if (and thing (eq (point) (treesit-node-start thing))) >> + (progn >> + (goto-char (min (1+ (treesit-node-end thing)) (point-max))) >> + (setq count (1- count))) >> + (setq count 0 res nil))) >> + (while (< count 0) >> + (skip-syntax-backward " >") >> + (setq thing (treesit-thing-at (max (1- (point)) (point-min)) 'comment)) >> + (if (and thing (eq (point) (treesit-node-end thing))) >> + (progn >> + (goto-char (treesit-node-start thing)) >> + (setq count (1+ count))) >> + (setq count 0 res nil))) >> + res)) > > I'm curious: why do you have to use skip-syntax-forward/backward when > tree-sitter already should know where a comment starts and ends? > ideally, features that use tree-sitter should not need to consult any > syntax tables set up by the major mode. Or what am I missing? tree-sitter has no information about whitespace, but whitespace is essential part of the behavior according to the docstring of 'forward-comment': Stop scanning if we find something other than a comment or whitespace. If COUNT comments are found as expected, with nothing except whitespace between them, return t; otherwise return nil. So treesit should be able to skip whitespace as well: default implementation = skip whitespace + check syntax table treesit implementation = skip whitespace + check syntax tree
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Thu, 23 Jan 2025 18:09:01 GMT) Full text and rfc822 format available.Message #25 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Eli Zaretskii <eliz <at> gnu.org> To: Juri Linkov <juri <at> linkov.net> Cc: 75609 <at> debbugs.gnu.org, casouri <at> gmail.com, monnier <at> iro.umontreal.ca Subject: Re: bug#75609: Hideshow support for treesitter Date: Thu, 23 Jan 2025 20:08:14 +0200
> From: Juri Linkov <juri <at> linkov.net> > Cc: Stefan Monnier <monnier <at> iro.umontreal.ca>, casouri <at> gmail.com, > 75609 <at> debbugs.gnu.org > Date: Thu, 23 Jan 2025 19:39:17 +0200 > > > I'm curious: why do you have to use skip-syntax-forward/backward when > > tree-sitter already should know where a comment starts and ends? > > ideally, features that use tree-sitter should not need to consult any > > syntax tables set up by the major mode. Or what am I missing? > > tree-sitter has no information about whitespace, but whitespace is > essential part of the behavior according to the docstring of > 'forward-comment': > > Stop scanning if we find something other than a comment or whitespace. > If COUNT comments are found as expected, with nothing except whitespace > between them, return t; otherwise return nil. > > So treesit should be able to skip whitespace as well: > > default implementation = skip whitespace + check syntax table > treesit implementation = skip whitespace + check syntax tree I'm confused: are you saying that (skip-syntax-forward " >") skips whitespace? AFAIU, it skips not just whitespace. And to skip whitespace we don't need syntax-tables, do we? I'm probably missing something.
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Thu, 23 Jan 2025 18:58:02 GMT) Full text and rfc822 format available.Message #28 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Juri Linkov <juri <at> linkov.net> To: Eli Zaretskii <eliz <at> gnu.org> Cc: 75609 <at> debbugs.gnu.org, casouri <at> gmail.com, monnier <at> iro.umontreal.ca Subject: Re: bug#75609: Hideshow support for treesitter Date: Thu, 23 Jan 2025 20:56:01 +0200
>> > I'm curious: why do you have to use skip-syntax-forward/backward when >> > tree-sitter already should know where a comment starts and ends? >> > ideally, features that use tree-sitter should not need to consult any >> > syntax tables set up by the major mode. Or what am I missing? >> >> tree-sitter has no information about whitespace, but whitespace is >> essential part of the behavior according to the docstring of >> 'forward-comment': >> >> Stop scanning if we find something other than a comment or whitespace. >> If COUNT comments are found as expected, with nothing except whitespace >> between them, return t; otherwise return nil. >> >> So treesit should be able to skip whitespace as well: >> >> default implementation = skip whitespace + check syntax table >> treesit implementation = skip whitespace + check syntax tree > > I'm confused: are you saying that (skip-syntax-forward " >") skips > whitespace? AFAIU, it skips not just whitespace. And to skip > whitespace we don't need syntax-tables, do we? I'm probably missing > something. Probably it would be fine to use just (skip-chars-forward " \t\n") if I correctly understand the documentation of 'forward-comment'.
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Mon, 27 Jan 2025 19:16:02 GMT) Full text and rfc822 format available.Message #31 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Juri Linkov <juri <at> linkov.net> To: Yuan Fu <casouri <at> gmail.com> Cc: 75609 <at> debbugs.gnu.org Subject: Re: bug#75609: Hideshow support for treesitter Date: Mon, 27 Jan 2025 21:11:59 +0200
[Message part 1 (text/plain, inline)]
>> +(defun treesit-hs-inside-comment-p () >> + (let ((thing (or (treesit-thing-at (point) "comment") >> + (unless (bobp) >> + (treesit-thing-at (1- (point)) "comment"))))) >> + (when thing >> + (list (treesit-node-start thing) (treesit-node-end thing))))) >> + > > FYI some grammar calls comments line_comment and block_comment. Maybe > use the comment thing first, and then match “comment” with the node type? Now this tries to use the comment thing first, and then falls back to “comment”.
[treesit-hideshow.patch (text/x-diff, inline)]
diff --git a/lisp/treesit.el b/lisp/treesit.el index e08aa52ca50..de86b847177 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -3489,6 +3489,82 @@ treesit-outline-level (setq level (1+ level))) (if (zerop level) 1 level))) +;;; Hideshow mode + +(defun treesit-hs-block-end () + "Tree-sitter implementation of `hs-block-end-regexp'." + (let* ((pred 'list) + (thing (treesit-thing-at + (if (bobp) (point) (1- (point))) pred)) + (end (when thing (treesit-node-end thing))) + (last (when thing (treesit-node-child thing -1))) + (beg (if last (treesit-node-start last) + (if (bobp) (point) (1- (point)))))) + (when (and thing (eq (point) end)) + (set-match-data (list beg end)) + t))) + +(defun treesit-hs-find-block-beginning () + "Tree-sitter implementation of `hs-find-block-beginning-func'." + (let* ((pred 'list) + (thing (treesit-thing-at (point) pred)) + (thing (or thing (treesit-parent-until (treesit-node-at (point)) pred))) + (beg (when thing (treesit-node-start thing))) + (end (when thing (treesit-node-end thing)))) + (when thing + (goto-char beg) + (set-match-data (list beg end)) + t))) + +(defun treesit-hs-find-next-block (_regexp _maxp comments) + "Tree-sitter implementation of `hs-find-next-block-func'." + (when (not comments) + (forward-comment (point-max))) + (let* ((comment-pred + (when comments + (if (treesit-thing-defined-p 'comment (treesit-language-at (point))) + 'comment "comment"))) + (pred (if comment-pred (append '(or list) (list comment-pred)) 'list)) + ;; `treesit-navigate-thing' can't find a thing at bobp, + ;; so use `treesit-thing-at' to match at bobp. + (current (treesit-thing-at (point) pred)) + (beg (or (and current (eq (point) (treesit-node-start current)) (point)) + (treesit-navigate-thing (point) 1 'beg pred))) + ;; Check if we found a list or a comment + (list-thing (when beg (treesit-thing-at beg 'list))) + (comment-thing (when beg (treesit-thing-at beg comment-pred))) + (comment-p (and comment-thing (eq beg (treesit-node-start comment-thing)))) + (thing (if comment-p comment-thing list-thing)) + (end (if thing (min (1+ (treesit-node-start thing)) (point-max))))) + (when end + (goto-char end) + (set-match-data + (if (and comments comment-p) + (list beg end nil nil beg end) + (list beg end beg end))) + t))) + +(defun treesit-hs-looking-at-block-start-p () + "Tree-sitter implementation of `hs-looking-at-block-start-p-func'." + (let* ((pred 'list) + (thing (treesit-thing-at (point) pred)) + (beg (when thing (treesit-node-start thing))) + (end (min (1+ (point)) (point-max)))) + (when (and thing (eq (point) beg)) + (set-match-data (list beg end)) + t))) + +(defun treesit-hs-inside-comment-p () + "Tree-sitter implementation of `hs-inside-comment-p-func'." + (let* ((comment-pred + (if (treesit-thing-defined-p 'comment (treesit-language-at (point))) + 'comment "comment")) + (thing (or (treesit-thing-at (point) comment-pred) + (unless (bobp) + (treesit-thing-at (1- (point)) comment-pred))))) + (when thing + (list (treesit-node-start thing) (treesit-node-end thing))))) + ;;; Show paren mode (defun treesit-show-paren-data--categorize (pos &optional end-p) @@ -3672,7 +3748,17 @@ treesit-major-mode-setup (setq-local forward-list-function #'treesit-forward-list) (setq-local down-list-function #'treesit-down-list) (setq-local up-list-function #'treesit-up-list) - (setq-local show-paren-data-function #'treesit-show-paren-data)) + (setq-local show-paren-data-function #'treesit-show-paren-data) + (setq-local hs-c-start-regexp nil + hs-block-start-regexp nil + hs-block-start-mdata-select 0 + hs-block-end-regexp #'treesit-hs-block-end + hs-forward-sexp-func #'forward-list + hs-adjust-block-beginning nil + hs-find-block-beginning-func #'treesit-hs-find-block-beginning + hs-find-next-block-func #'treesit-hs-find-next-block + hs-looking-at-block-start-p-func #'treesit-hs-looking-at-block-start-p + hs-inside-comment-p-func #'treesit-hs-inside-comment-p)) (when (treesit-thing-defined-p 'sentence nil) (setq-local forward-sentence-function #'treesit-forward-sentence)) diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index 823eb0527c6..d96efde991f 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -481,6 +481,9 @@ hs-looking-at-block-start-p-func Python, where `looking-at' and `syntax-ppss' check is not enough to check if the point is at the block start.") +(defvar-local hs-inside-comment-p-func nil + "Function used to check if point is inside a comment.") + (defvar hs-headline nil "Text of the line where a hidden block begins, set during isearch. You can display this in the mode line by adding the symbol `hs-headline' @@ -625,9 +628,13 @@ hs-hide-block-at-point (setq p (line-end-position))) ;; `q' is the point at the end of the block (hs-forward-sexp mdata 1) - (setq q (if (looking-back hs-block-end-regexp nil) - (match-beginning 0) - (point))) + (setq q (cond ((and (stringp hs-block-end-regexp) + (looking-back hs-block-end-regexp nil)) + (match-beginning 0)) + ((functionp hs-block-end-regexp) + (funcall hs-block-end-regexp) + (match-beginning 0)) + (t (point)))) (when (and (< p q) (> (count-lines p q) 1)) (cond ((and hs-allow-nesting (setq ov (hs-overlay-at p))) (delete-overlay ov)) @@ -644,6 +651,11 @@ hs-inside-comment-p beginning. If we are inside of a comment but this condition is not met, we return a list having a nil as its car and the end of comment position as cdr." + (if (functionp hs-inside-comment-p-func) + (funcall hs-inside-comment-p-func) + (hs-inside-comment-p--default))) + +(defun hs-inside-comment-p--default () (save-excursion ;; the idea is to look backwards for a comment start regexp, do a ;; forward comment, and see if we are inside, then extend @@ -704,7 +716,7 @@ hs-grok-mode-type (bound-and-true-p comment-end)) (let* ((lookup (assoc major-mode hs-special-modes-alist)) (start-elem (or (nth 1 lookup) "\\s("))) - (if (listp start-elem) + (if (consp start-elem) ;; handle (START-REGEXP MDATA-SELECT) (setq hs-block-start-regexp (car start-elem) hs-block-start-mdata-select (cadr start-elem)) @@ -850,14 +862,16 @@ hs-hide-all (syntax-propertize (point-max)) (let ((spew (make-progress-reporter "Hiding all blocks..." (point-min) (point-max))) - (re (concat "\\(" - hs-block-start-regexp - "\\)" - (if hs-hide-comments-when-hiding-all - (concat "\\|\\(" - hs-c-start-regexp - "\\)") - "")))) + (re (when (stringp hs-block-start-regexp) + (concat "\\(" + hs-block-start-regexp + "\\)" + (if (and hs-hide-comments-when-hiding-all + (stringp hs-c-start-regexp)) + (concat "\\|\\(" + hs-c-start-regexp + "\\)") + ""))))) (while (funcall hs-find-next-block-func re (point-max) hs-hide-comments-when-hiding-all) (if (match-beginning 1) @@ -869,7 +883,9 @@ hs-hide-all (hs-hide-block-at-point t)) ;; Go to end of matched data to prevent from getting stuck ;; with an endless loop. - (when (looking-at hs-block-start-regexp) + (when (if (stringp hs-block-start-regexp) + (looking-at hs-block-start-regexp) + (eq (point) (match-beginning 0))) (goto-char (match-end 0))))) ;; found a comment, probably (let ((c-reg (hs-inside-comment-p))) @@ -1008,7 +1024,10 @@ hs-minor-mode (setq hs-headline nil) (if hs-minor-mode (progn - (hs-grok-mode-type) + ;; Use such heuristics that if one buffer-local variable + ;; is already defined, don't overwrite other variables too. + (unless (buffer-local-value 'hs-inside-comment-p-func (current-buffer)) + (hs-grok-mode-type)) ;; Turn off this mode if we change major modes. (add-hook 'change-major-mode-hook #'turn-off-hideshow
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Tue, 28 Jan 2025 01:53:02 GMT) Full text and rfc822 format available.Message #34 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Yuan Fu <casouri <at> gmail.com> To: Juri Linkov <juri <at> linkov.net> Cc: 75609 <at> debbugs.gnu.org Subject: Re: bug#75609: Hideshow support for treesitter Date: Mon, 27 Jan 2025 17:52:06 -0800
> On Jan 27, 2025, at 11:11 AM, Juri Linkov <juri <at> linkov.net> wrote: > >>> +(defun treesit-hs-inside-comment-p () >>> + (let ((thing (or (treesit-thing-at (point) "comment") >>> + (unless (bobp) >>> + (treesit-thing-at (1- (point)) "comment"))))) >>> + (when thing >>> + (list (treesit-node-start thing) (treesit-node-end thing))))) >>> + >> >> FYI some grammar calls comments line_comment and block_comment. Maybe >> use the comment thing first, and then match “comment” with the node type? > > Now this tries to use the comment thing first, and then falls back to “comment”. FWIW I don’t have other comments :) Yuan
bug-gnu-emacs <at> gnu.org
:bug#75609
; Package emacs
.
(Tue, 28 Jan 2025 19:10:02 GMT) Full text and rfc822 format available.Message #37 received at 75609 <at> debbugs.gnu.org (full text, mbox):
From: Juri Linkov <juri <at> linkov.net> To: Yuan Fu <casouri <at> gmail.com> Cc: 75609 <at> debbugs.gnu.org Subject: Re: bug#75609: Hideshow support for treesitter Date: Tue, 28 Jan 2025 21:07:46 +0200
close 75609 31.0.50 thanks >>>> +(defun treesit-hs-inside-comment-p () >>>> + (let ((thing (or (treesit-thing-at (point) "comment") >>>> + (unless (bobp) >>>> + (treesit-thing-at (1- (point)) "comment"))))) >>>> + (when thing >>>> + (list (treesit-node-start thing) (treesit-node-end thing))))) >>>> + >>> >>> FYI some grammar calls comments line_comment and block_comment. Maybe >>> use the comment thing first, and then match “comment” with the node type? >> >> Now this tries to use the comment thing first, and then falls back to “comment”. > > FWIW I don’t have other comments :) Thanks, so now pushed to master and closed.
Juri Linkov <juri <at> linkov.net>
to control <at> debbugs.gnu.org
.
(Tue, 28 Jan 2025 19:10:03 GMT) Full text and rfc822 format available.Debbugs Internal Request <help-debbugs <at> gnu.org>
to internal_control <at> debbugs.gnu.org
.
(Wed, 26 Feb 2025 12:24:05 GMT) Full text and rfc822 format available.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.