From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Theodor Thornhill Newsgroups: gmane.emacs.devel Subject: Plug treesit.el into other emacs constructs Date: Mon, 12 Dec 2022 15:33:33 +0100 Message-ID: <87wn6whete.fsf@thornhill.no> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="35545"; mail-complaints-to="usenet@ciao.gmane.io" Cc: eliz@gnu.org, casouri@gmail.com To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Mon Dec 12 15:34:45 2022 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1p4jtD-0008pq-Qg for ged-emacs-devel@m.gmane-mx.org; Mon, 12 Dec 2022 15:34:44 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1p4jsV-0003Au-Eg; Mon, 12 Dec 2022 09:34:00 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p4jsJ-00037Z-7v for emacs-devel@gnu.org; Mon, 12 Dec 2022 09:33:55 -0500 Original-Received: from out-137.mta0.migadu.com ([2001:41d0:1004:224b::89]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p4jsB-0002Ui-IZ for emacs-devel@gnu.org; Mon, 12 Dec 2022 09:33:46 -0500 X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=thornhill.no; s=key1; t=1670855614; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type; bh=aDVlDzeXJU+4ElrDmIaPWlHtf5IBBKB8duelIafO8IY=; b=CwfxMeu9toWYy0o5We2/FQZI4TM+vUIpT25mdfcl2eiypsnRVgZ/BVo8wGeIIhLeNbcxWx vzfBSfY6m9sQBYHA36q4HH5NPNiG0QgY/Ol0AF5y244CwcIamd1U6vkVb1xs4EaPF1x9id Esd7Z5ldbXRBpN9RZWQAoc6mqVKSbVZOTIARjGlZJcX5vz0JbwZheib8X6gfYnumPjOKg2 0mTjmr0lDM0z3/58NXk2vtwdWdPoOaB/HL9y51EQbpEQRP8mS+uXzEa51Q/MNknemSlE8W QRyNvX0hznpw7PgE2Lwml9i8ChmokXclWBleuUNFzDDtoH4Zd84eJNQ01tWnUw== X-Migadu-Flow: FLOW_OUT Received-SPF: pass client-ip=2001:41d0:1004:224b::89; envelope-from=theo@thornhill.no; helo=out-137.mta0.migadu.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:301234 Archived-At: --=-=-= Content-Type: text/plain Hello! I've started plugging tree-sitter support into other constructs in emacs, and I would love some feedback and suggestions for how to proceed. Emacs has some nice utilities we could piggyback on, namely: - forward-sexp - forward-sentence - forward-* What they provide is suddenly access to all of the transpose-* functions. I'll show with some examples, where | denotes point, and if there are two | it denotes active region, like this: |String foo|. The patch supplied below enables the following: * Navigation: ** Forward-sentence: Executing M-e repeatedly will go from: ``` public void foo() { |int x = 5; int x = 6; } ``` to ``` public void foo() { int x = 5;| int x = 6; } ``` then ``` public void foo() { int x = 5; int x = 6;| } ``` ** transpose-sentence: Executing M-x transpose-sentence repeatedly will go from: ``` public void foo() { int x = 5;| if (r) { } int x = 6; } ``` to ``` public void foo() { if (r) { } int x = 5;| int x = 6; } ``` then ``` public void foo() { if (r) { } int x = 6; int x = 5;| } ``` Further transpose-sentence will not go out of the method because there are no siblings ** kill-sentence: Executing M-k repeatedly will go from: ``` public void foo() { |int x = 6; if (foo) { } } ``` to ``` public void foo() { | if (foo) { } } ``` then ``` public void foo() { | } ``` ** Forward-sexp: Executing C-M-f repeatedly will go from: ``` public void foo(|String bar, String baz) {} ``` to ``` public void foo(String bar|, String baz) {} ``` and ``` public void foo(String bar, String baz|) {} ``` ** Mark-sexp: Executing C-M-@ repeatedly will go from: ``` public void foo(|String bar, String baz) {} ``` to ``` public void foo(|String bar|, String baz) {} ``` then ``` public void foo(|String bar, String baz|) {} ``` ** transpose-sexp: Executing C-M-t repeatedly will go from: ``` public void foo(int bar,| String baz) {} ``` to ``` public void foo(String baz, int bar|) {} ``` ** kill-sexp: Executing C-M-k repeatedly will go from: ``` public void foo(|int bar, String baz) {} ``` to ``` public void foo(|, int bar) {} ``` then ``` public void foo() {} ``` And more. The patch only adds a few things: - (defvar-local forward-sentence-function nil), the analog to forward-sexp-function and beginning-of-defun-function - treesit-sexp-type-regexp and treesit-sentence-type-regexp, both to be added to treesit-major-mode-setup, and added to each *-ts-mode. Is the general idea ok? Could we add these or similar changes to paragraphs.el and just piggyback on it inside of treesit? If we want this I can start working on preparing a patch for this on the master branch. Theo --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-WIP-forward-implementation.patch >From e67e26f6d4dc39436b169b4fc026376d9f74b6b9 Mon Sep 17 00:00:00 2001 From: Theodor Thornhill Date: Mon, 12 Dec 2022 15:28:05 +0100 Subject: [PATCH] WIP forward-* implementation --- lisp/progmodes/java-ts-mode.el | 10 +++++ lisp/textmodes/paragraphs.el | 73 +++++++++++++++++++--------------- lisp/treesit.el | 50 ++++++++++++++++++++++- 3 files changed, 100 insertions(+), 33 deletions(-) diff --git a/lisp/progmodes/java-ts-mode.el b/lisp/progmodes/java-ts-mode.el index d5f4f55fe0..e76380ec2a 100644 --- a/lisp/progmodes/java-ts-mode.el +++ b/lisp/progmodes/java-ts-mode.el @@ -336,6 +336,16 @@ java-ts-mode "package_declaration" "module_declaration"))) + (setq-local treesit-sentence-type-regexp + (regexp-opt '("declaration" + "statement"))) + + (setq-local treesit-sexp-type-regexp + (regexp-opt '("declaration" + "statement" + "formal_parameter" + "string_literal"))) + ;; Font-lock. (setq-local treesit-font-lock-settings java-ts-mode--font-lock-settings) (setq-local treesit-font-lock-feature-list diff --git a/lisp/textmodes/paragraphs.el b/lisp/textmodes/paragraphs.el index c500dc014f..b53fe66eee 100644 --- a/lisp/textmodes/paragraphs.el +++ b/lisp/textmodes/paragraphs.el @@ -441,6 +441,8 @@ end-of-paragraph-text (if (< (point) (point-max)) (end-of-paragraph-text)))))) +(defvar-local forward-sentence-function nil) + (defun forward-sentence (&optional arg) "Move forward to next end of sentence. With argument, repeat. When ARG is negative, move backward repeatedly to start of sentence. @@ -449,36 +451,40 @@ forward-sentence sentences. Also, every paragraph boundary terminates sentences as well." (interactive "^p") (or arg (setq arg 1)) - (let ((opoint (point)) - (sentence-end (sentence-end))) - (while (< arg 0) - (let ((pos (point)) - par-beg par-text-beg) - (save-excursion - (start-of-paragraph-text) - ;; Start of real text in the paragraph. - ;; We move back to here if we don't see a sentence-end. - (setq par-text-beg (point)) - ;; Start of the first line of the paragraph. - ;; We use this as the search limit - ;; to allow sentence-end to match if it is anchored at - ;; BOL and the paragraph starts indented. - (beginning-of-line) - (setq par-beg (point))) - (if (and (re-search-backward sentence-end par-beg t) - (or (< (match-end 0) pos) - (re-search-backward sentence-end par-beg t))) - (goto-char (match-end 0)) - (goto-char par-text-beg))) - (setq arg (1+ arg))) - (while (> arg 0) - (let ((par-end (save-excursion (end-of-paragraph-text) (point)))) - (if (re-search-forward sentence-end par-end t) - (skip-chars-backward " \t\n") - (goto-char par-end))) - (setq arg (1- arg))) - (let ((npoint (constrain-to-field nil opoint t))) - (not (= npoint opoint))))) + (cond + (forward-sentence-function + (funcall forward-sentence-function arg)) + (t + (let ((opoint (point)) + (sentence-end (sentence-end))) + (while (< arg 0) + (let ((pos (point)) + par-beg par-text-beg) + (save-excursion + (start-of-paragraph-text) + ;; Start of real text in the paragraph. + ;; We move back to here if we don't see a sentence-end. + (setq par-text-beg (point)) + ;; Start of the first line of the paragraph. + ;; We use this as the search limit + ;; to allow sentence-end to match if it is anchored at + ;; BOL and the paragraph starts indented. + (beginning-of-line) + (setq par-beg (point))) + (if (and (re-search-backward sentence-end par-beg t) + (or (< (match-end 0) pos) + (re-search-backward sentence-end par-beg t))) + (goto-char (match-end 0)) + (goto-char par-text-beg))) + (setq arg (1+ arg))) + (while (> arg 0) + (let ((par-end (save-excursion (end-of-paragraph-text) (point)))) + (if (re-search-forward sentence-end par-end t) + (skip-chars-backward " \t\n") + (goto-char par-end))) + (setq arg (1- arg))) + (let ((npoint (constrain-to-field nil opoint t))) + (not (= npoint opoint))))))) (defun count-sentences (start end) "Count sentences in current buffer from START to END." @@ -532,14 +538,17 @@ repunctuate-sentences (remove-function isearch-filter-predicate repunctuate-sentences-filter))))) - (defun backward-sentence (&optional arg) "Move backward to start of sentence. With ARG, do it ARG times. See `forward-sentence' for more information." (interactive "^p") (or arg (setq arg 1)) - (forward-sentence (- arg))) + (cond + (forward-sentence-function + (funcall forward-sentence-function (- arg))) + (t + (forward-sentence (- arg))))) (defun kill-sentence (&optional arg) "Kill from point to end of sentence. diff --git a/lisp/treesit.el b/lisp/treesit.el index 133564f6c8..4a1c10211e 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1603,6 +1603,50 @@ treesit--defun-maybe-top-level node) finally return node)))) +(defvar-local treesit-sexp-type-regexp "\\`field_declaration\\'" + "A regexp that matches the node type of sexp nodes.") + +(defun treesit-forward-sexp (&optional arg) + "Tree-sitter `beginning-of-defun' function. +ARG is the same as in `beginning-of-defun'." + (let ((arg (or arg 1)) + (node (treesit-node-at (point)))) + (while (and (> arg 0) + (setq node (treesit-search-forward-goto + node + treesit-sexp-type-regexp))) + (setq arg (1- arg)) + (goto-char (treesit-node-end node))) + (while (and (< arg 0) + (setq node (treesit-search-forward-goto + node + treesit-sexp-type-regexp t t))) + (setq arg (1+ arg)) + (goto-char (treesit-node-start node))) + t)) + +(defvar-local treesit-sentence-type-regexp "\\`field_declaration\\'" + "A regexp that matches the node type of textual nodes.") + +(defun treesit-forward-sentence (&optional arg) + "Tree-sitter `beginning-of-defun' function. +ARG is the same as in `beginning-of-defun'." + (let ((arg (or arg 1)) + (node (treesit-node-at (point)))) + (while (and (> arg 0) + (setq node (treesit-search-forward-goto + node + treesit-sentence-type-regexp))) + (setq arg (1- arg)) + (goto-char (treesit-node-end node))) + (while (and (< arg 0) + (setq node (treesit-search-forward-goto + node + treesit-sentence-type-regexp t t))) + (setq arg (1+ arg)) + (goto-char (treesit-node-start node))) + t)) + (defun treesit-beginning-of-defun (&optional arg) "Tree-sitter `beginning-of-defun' function. ARG is the same as in `beginning-of-defun'." @@ -1730,7 +1774,11 @@ treesit-major-mode-setup ;; Navigation. (when treesit-defun-type-regexp (setq-local beginning-of-defun-function #'treesit-beginning-of-defun) - (setq-local end-of-defun-function #'treesit-end-of-defun))) + (setq-local end-of-defun-function #'treesit-end-of-defun)) + (when treesit-sentence-type-regexp + (setq-local forward-sentence-function #'treesit-forward-sentence)) + (when treesit-sexp-type-regexp + (setq-local forward-sexp-function #'treesit-forward-sexp))) ;;; Debugging -- 2.34.1 --=-=-=--