Skip to content

Commit 3e2ae19

Browse files
authored
Merge pull request #133 from xiliuya/add-gdscript-ts-mode
Add treesit Major mode `gdscript-ts-mode
2 parents cee6d61 + 812da6a commit 3e2ae19

File tree

3 files changed

+289
-1
lines changed

3 files changed

+289
-1
lines changed

README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ game engine in Emacs. It gives syntax highlighting and indentations.
1010
![](assets/emacs-gdscript-imenu.png)
1111

1212
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
13-
1413
**Table of Contents**
1514

1615
- [Features](#features)
@@ -21,17 +20,23 @@ game engine in Emacs. It gives syntax highlighting and indentations.
2120
- [Installing with `use-package` + `straight.el`](#installing-with-use-package--straightel)
2221
- [Installing manually](#installing-manually)
2322
- [Auto-completion with the Language Server Protocol (LSP)](#auto-completion-with-the-language-server-protocol-lsp)
23+
- [Known issues](#known-issues)
24+
- [Major mode with Treesit](#major-mode-with-treesit)
25+
- [Install treesit](#install-treesit)
26+
- [Install grammar](#install-grammar)
2427
- [How to use](#how-to-use)
2528
- [Opening the project in the editor](#opening-the-project-in-the-editor)
2629
- [Running Godot with visual debug options](#running-godot-with-visual-debug-options)
2730
- [Using Hydra](#using-hydra)
2831
- [Formatting code with gdformat](#formatting-code-with-gdformat)
2932
- [Browsing the Godot API with eww](#browsing-the-godot-api-with-eww)
33+
- [Using a local copy of the Godot docs](#using-a-local-copy-of-the-godot-docs)
3034
- [Keyboard shortcuts](#keyboard-shortcuts)
3135
- [Customization](#customization)
3236
- [Using the debugger](#using-the-debugger)
3337
- [Adding and removing breakpoints](#adding-and-removing-breakpoints)
3438
- [Running the project with the debugger active](#running-the-project-with-the-debugger-active)
39+
- [Multi-line display](#multi-line-display)
3540
- [Fetching an object's details](#fetching-an-objects-details)
3641
- [Debug Hydra](#debug-hydra)
3742
- [The `* Stack frame vars *` buffer](#the--stack-frame-vars--buffer)
@@ -59,6 +64,7 @@ This mode features all the essentials:
5964
- Auto-completion for all the keywords in the `gdscript-keywords.el` file.
6065
- Run or open the project and files with Godot.
6166
- Browsing the API reference in Emacs.
67+
- Add treesit major mode support `gdscript-ts-mode` .
6268

6369
![](assets/emacs-gdscript-code-folding.png)
6470

@@ -201,6 +207,41 @@ There are some known issues with the GDScript language server in Godot 3.2 due t
201207
(advice-add #'lsp--get-message-type :around #'lsp--gdscript-ignore-errors)
202208
```
203209

210+
## Major mode with Treesit
211+
212+
[Treesit](https://github.com/tree-sitter/tree-sitter) is an incremental parsing system for programming tools.
213+
214+
This package has a major mode (gdscript-ts-mode). That supports the use tree-sitter for font-lock, imenu, indentation, and navigation of `gdscript` files.
215+
216+
Emacs version 29 or higher is required to use this mode.
217+
218+
### Install treesit
219+
220+
We need to install tree-sitter library, When under Arch Linux :
221+
222+
```sh
223+
sudo pacman -S tree-sitter
224+
```
225+
226+
### Install grammar
227+
228+
To support Gdscript, we must install [gdscript-grammar](https://github.com/PrestonKnopp/tree-sitter-gdscript.git):
229+
230+
```sh
231+
git clone https://github.com/PrestonKnopp/tree-sitter-gdscript.git
232+
cd tree-sitter-gdscript/src
233+
cc -std=c99 -c parser.c
234+
cc -c scanner.cc
235+
cc -shared parser.o scanner.o -o libtree-sitter-gdscript.so
236+
```
237+
238+
Additional directories to look for tree-sitter language definitions. ( DIR is your working path )
239+
240+
```emacs-lisp
241+
(setq treesit-extra-load-path '("DIR/tree-sitter-gdscript/src/"))
242+
```
243+
enjoy.
244+
204245
## How to use
205246

206247
### Opening the project in the editor

gdscript-mode.el

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
(require 'gdscript-debug)
4545
(require 'gdscript-eglot)
4646

47+
(when (version< "29" emacs-version)
48+
(require 'gdscript-ts-mode))
49+
4750
;;;###autoload
4851
(add-to-list 'auto-mode-alist '("\\.gd\\'" . gdscript-mode))
4952
;;;###autoload

gdscript-ts-mode.el

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
;;; gdscript-ts-mode.el --- Summary -*- lexical-binding: t -*-
2+
3+
;; Copyright (C) 2023 GDQuest and contributors
4+
5+
;; Author: xiliuya <[email protected]>
6+
;; URL: https://github.com/godotengine/emacs-gdscript-mode/
7+
;; Version: 0.1.0
8+
;; Maintainer: xiliuya <[email protected]>
9+
;; Created: 2023-05-22 19:14:43
10+
11+
;; Keywords: languages
12+
;; Package-Requires: ((emacs "26.3"))
13+
14+
15+
;; This program is free software; you can redistribute it and/or modify
16+
;; it under the terms of the GNU General Public License as published by
17+
;; the Free Software Foundation, either version 3 of the License, or
18+
;; (at your option) any later version.
19+
20+
;; This program is distributed in the hope that it will be useful,
21+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+
;; GNU General Public License for more details.
24+
25+
;; You should have received a copy of the GNU General Public License
26+
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
27+
28+
;;; Commentary:
29+
;;
30+
;; Tree-sitter mode for Gdscript.(Refer to python-ts-mode)
31+
;; That supports the use tree-sitter for font-lock, imenu, indentation,
32+
;; and navigation of Gdscript files.
33+
34+
;;
35+
;;; Code:
36+
37+
(when (version< "29" emacs-version)
38+
(require 'treesit))
39+
40+
41+
;;; Imenu
42+
43+
(defvar gdscript-ts-imenu-format-item-label-function
44+
'gdscript-ts-imenu-format-item-label
45+
"Imenu function used to format an item label.
46+
It must be a function with two arguments: TYPE and NAME.")
47+
48+
(defvar gdscript-ts-imenu-format-parent-item-label-function
49+
'gdscript-ts-imenu-format-parent-item-label
50+
"Imenu function used to format a parent item label.
51+
It must be a function with two arguments: TYPE and NAME.")
52+
53+
(defvar gdscript-ts-imenu-format-parent-item-jump-label-function
54+
'gdscript-ts-imenu-format-parent-item-jump-label
55+
"Imenu function used to format a parent jump item label.
56+
It must be a function with two arguments: TYPE and NAME.")
57+
58+
(defun gdscript-ts-imenu-format-item-label (type name)
59+
"Return Imenu label for single node using TYPE and NAME."
60+
(format "%s (%s)" name type))
61+
62+
(defun gdscript-ts-imenu-format-parent-item-label (type name)
63+
"Return Imenu label for parent node using TYPE and NAME."
64+
(format "%s..." (gdscript-ts-imenu-format-item-label type name)))
65+
66+
(defun gdscript-ts-imenu-format-parent-item-jump-label (type _name)
67+
"Return Imenu label for parent node jump using TYPE and NAME."
68+
(if (string= type "class")
69+
"*class definition*"
70+
"*function definition*"))
71+
72+
;;; Keywords
73+
74+
(defvar gdscript-ts--treesit-keywords '("and" "as" "break" "class" "class_name"
75+
"const" "continue" "elif" "else" "enum" "export" "extends" "for" "func" "if" "in" "is"
76+
"master" "match" "not" "onready" "or" "pass" "puppet" "remote" "remotesync" "return" "setget" "signal"
77+
"var" "while"))
78+
79+
80+
;;; Setting
81+
82+
(defvar gdscript-ts--treesit-settings
83+
(treesit-font-lock-rules
84+
:language 'gdscript
85+
:feature 'comment
86+
'((comment) @font-lock-comment-face)
87+
88+
:language 'gdscript
89+
:feature 'definition
90+
'((function_definition (name) @font-lock-function-name-face)
91+
(class_definition
92+
(name) @font-lock-function-name-face)
93+
(parameters (identifier) @font-lock-variable-name-face))
94+
95+
:language 'gdscript
96+
:feature 'keyword
97+
`(([,@gdscript-ts--treesit-keywords] @font-lock-keyword-face)
98+
([(false) (true)] @font-lock-keyword-face))
99+
100+
:language 'gdscript
101+
:feature 'string
102+
'((string) @font-lock-string-face)
103+
104+
:language 'gdscript
105+
:feature 'type
106+
'(((type) @font-lock-type-face)
107+
(get_node) @font-lock-type-face)
108+
109+
:feature 'function
110+
:language 'gdscript
111+
'((call (identifier) @font-lock-function-call-face)
112+
(attribute_call (identifier) @font-lock-function-call-face))
113+
114+
:language 'gdscript
115+
:feature 'variable
116+
'((_ (name) @font-lock-variable-name-face))
117+
118+
:feature 'number
119+
:language 'gdscript
120+
'(([(integer) (float)] @font-lock-number-face))
121+
122+
:feature 'property
123+
:language 'gdscript
124+
'((attribute (identifier) (identifier) @font-lock-property-use-face))
125+
126+
:feature 'operator
127+
:language 'gdscript
128+
`(["+" "-" "*" "/" "^" ">" "<" "="] @font-lock-operator-face)))
129+
130+
131+
;;; Funtion
132+
133+
(defun gdscript-ts--treesit-defun-name (node)
134+
"Return the defun name of NODE."
135+
(treesit-node-text (treesit-search-subtree node "^name$" nil t) t))
136+
137+
(defun gdscript-ts--imenu-treesit-create-index-1 (node)
138+
"Given a sparse tree, create an imenu alist.
139+
140+
NODE is the root node of the tree returned by
141+
`treesit-induce-sparse-tree' (not a tree-sitter node, its car is
142+
a tree-sitter node). Walk that tree and return an imenu alist.
143+
144+
Return a list of ENTRY where
145+
146+
ENTRY := (NAME . MARKER)
147+
| (NAME . ((JUMP-LABEL . MARKER)
148+
ENTRY
149+
...)
150+
151+
NAME is the function/class's name, JUMP-LABEL is like \"*function
152+
definition*\"."
153+
(let* ((ts-node (car node))
154+
(children (cdr node))
155+
(subtrees (mapcan #'gdscript-ts--imenu-treesit-create-index-1
156+
children))
157+
(type (pcase (treesit-node-type ts-node)
158+
("function_definition" 'def)
159+
("export_variable_statement" 'e-var)
160+
("onready_variable_statement" 'o-var)
161+
("variable_statement" 'var)
162+
("class_definition" 'class)))
163+
;; The root of the tree could have a nil ts-node.
164+
(name (when ts-node
165+
(or (treesit-defun-name ts-node)
166+
"Anonymous")))
167+
(marker (when ts-node
168+
(set-marker (make-marker)
169+
(treesit-node-start ts-node)))))
170+
(cond
171+
((null ts-node)
172+
subtrees)
173+
(subtrees
174+
(let ((parent-label
175+
(funcall gdscript-ts-imenu-format-parent-item-label-function
176+
type name))
177+
(jump-label
178+
(funcall
179+
gdscript-ts-imenu-format-parent-item-jump-label-function
180+
type name)))
181+
`((,parent-label
182+
,(cons jump-label marker)
183+
,@subtrees))))
184+
(t (let ((label
185+
(funcall gdscript-ts-imenu-format-item-label-function
186+
type name)))
187+
(list (cons label marker)))))))
188+
189+
(defun gdscript-ts-imenu-treesit-create-index (&optional node)
190+
"Return tree Imenu alist for the current Gdscript buffer.
191+
192+
Change `gdscript-ts-imenu-format-item-label-function',
193+
`gdscript-ts-imenu-format-parent-item-label-function',
194+
`gdscript-ts-imenu-format-parent-item-jump-label-function' to
195+
customize how labels are formatted.
196+
197+
NODE is the root node of the subtree you want to build an index
198+
of. If nil, use the root node of the whole parse tree.
199+
200+
Similar to `gdscript-imenu-create-index' but use tree-sitter."
201+
(let* ((node (or node (treesit-buffer-root-node 'gdscript)))
202+
(tree (treesit-induce-sparse-tree
203+
node
204+
(rx (or (seq bol
205+
(or "onready_" "export_" "")
206+
"variable_statement"
207+
eol)
208+
(seq bol
209+
(or "function" "class")
210+
"_definition"
211+
eol)))
212+
nil 1000)))
213+
(gdscript-ts--imenu-treesit-create-index-1 tree)))
214+
215+
;;;###autoload
216+
(define-derived-mode gdscript-ts-mode gdscript-mode "Gdscript"
217+
"Major mode for editing gdscript files, using tree-sitter library.
218+
219+
\\{gdscript-ts-mode-map}"
220+
:syntax-table gdscript-mode-syntax-table
221+
(when (treesit-ready-p 'gdscript)
222+
(treesit-parser-create 'gdscript)
223+
(setq-local treesit-font-lock-feature-list
224+
'(( comment definition)
225+
( keyword string type)
226+
( function variable number property operator)))
227+
(setq-local treesit-font-lock-settings gdscript-ts--treesit-settings)
228+
;;; TODO: create-imenu
229+
(setq-local imenu-create-index-function
230+
#'gdscript-ts-imenu-treesit-create-index)
231+
(setq-local treesit-defun-type-regexp (rx (seq bol
232+
(or "function" "class")
233+
"_definition"
234+
eol)))
235+
(setq-local treesit-defun-name-function
236+
#'gdscript-ts--treesit-defun-name)
237+
(treesit-major-mode-setup)
238+
239+
240+
(add-to-list 'auto-mode-alist '("\\.gd\\'" . gdscript-ts-mode))
241+
(add-to-list 'interpreter-mode-alist '("gdscript[0-9.]*" . gdscript-ts-mode))))
242+
243+
(provide 'gdscript-ts-mode)
244+
;;; gdscript-ts-mode.el ends here

0 commit comments

Comments
 (0)