From: Theodor Thornhill <theo@thornhill.no>
To: emacs-devel@gnu.org
Cc: eliz@gnu.org, casouri@gmail.com
Subject: Plug treesit.el into other emacs constructs
Date: Mon, 12 Dec 2022 15:33:33 +0100 [thread overview]
Message-ID: <87wn6whete.fsf@thornhill.no> (raw)
[-- Attachment #1: Type: text/plain, Size: 2717 bytes --]
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
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-WIP-forward-implementation.patch --]
[-- Type: text/x-diff, Size: 7670 bytes --]
From e67e26f6d4dc39436b169b4fc026376d9f74b6b9 Mon Sep 17 00:00:00 2001
From: Theodor Thornhill <theo@thornhill.no>
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
next reply other threads:[~2022-12-12 14:33 UTC|newest]
Thread overview: 54+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-12-12 14:33 Theodor Thornhill [this message]
2022-12-12 14:45 ` Plug treesit.el into other emacs constructs Eli Zaretskii
2022-12-13 18:17 ` Theodor Thornhill
2022-12-12 15:46 ` Stefan Monnier
2022-12-13 18:27 ` Theodor Thornhill
2022-12-13 19:37 ` Stefan Monnier
2022-12-13 19:53 ` Yuan Fu
2022-12-13 20:06 ` Perry Smith
2022-12-13 23:19 ` Stefan Monnier
2022-12-14 8:14 ` Yuan Fu
2022-12-14 8:42 ` Theodor Thornhill
2022-12-14 14:01 ` Stefan Monnier
2022-12-14 16:24 ` Theodor Thornhill
2022-12-14 17:46 ` Stefan Monnier
2022-12-14 18:07 ` Theodor Thornhill
2022-12-14 19:25 ` Stefan Monnier
2022-12-14 19:35 ` Stefan Monnier
2022-12-14 20:04 ` Theodor Thornhill
2022-12-14 20:50 ` Stefan Monnier
2022-12-14 21:15 ` Theodor Thornhill
2022-12-14 21:34 ` Stefan Monnier
2022-12-15 19:37 ` Theodor Thornhill
2022-12-15 19:56 ` Stefan Monnier
2022-12-15 20:03 ` Theodor Thornhill
2022-12-15 20:33 ` Theodor Thornhill
2022-12-15 20:57 ` Theodor Thornhill
2022-12-24 7:00 ` Eli Zaretskii
2022-12-24 8:44 ` Yuan Fu
2022-12-24 14:01 ` Stefan Monnier
2022-12-24 14:15 ` Theodor Thornhill
2022-12-26 19:11 ` Theodor Thornhill
2022-12-26 22:46 ` Stefan Monnier
2022-12-26 22:51 ` Stefan Monnier
2022-12-27 22:15 ` Theodor Thornhill via Emacs development discussions.
2022-12-28 0:12 ` Stefan Monnier
2022-12-28 9:26 ` Theodor Thornhill via Emacs development discussions.
2022-12-28 18:01 ` Stefan Monnier
2022-12-28 18:27 ` Theodor Thornhill
2022-12-26 22:56 ` Theodor Thornhill
2022-12-27 15:46 ` Lynn Winebarger
2022-12-14 23:31 ` Yuan Fu
2022-12-15 0:05 ` Yuan Fu
2022-12-15 7:09 ` Eli Zaretskii
2022-12-15 7:14 ` Theodor Thornhill
2022-12-15 4:37 ` Stefan Monnier
2022-12-15 5:59 ` Theodor Thornhill
2022-12-15 21:23 ` Yuan Fu
2022-12-15 21:28 ` Theodor Thornhill
2022-12-13 20:02 ` Theodor Thornhill
2022-12-13 23:10 ` Stefan Monnier
2022-12-14 23:32 ` Stephen Leake
2022-12-16 10:02 ` Kévin Le Gouguec
2022-12-16 11:54 ` [SPAM UNSURE] " Stephen Leake
2022-12-17 15:30 ` Kévin Le Gouguec
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87wn6whete.fsf@thornhill.no \
--to=theo@thornhill.no \
--cc=casouri@gmail.com \
--cc=eliz@gnu.org \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).