diff --git a/docs/modules.org b/docs/modules.org index 1a6a70372..7eddea11e 100644 --- a/docs/modules.org +++ b/docs/modules.org @@ -195,6 +195,7 @@ Small modules that give Emacs access to external tools & services. + [[file:../modules/tools/taskrunner/README.org][taskrunner]] - TODO + [[file:../modules/tools/terraform/README.org][terraform]] - TODO + tmux - TODO ++ [[file:../modules/tools/tree-sitter/README.org][tree-sitter]] - syntax and parsing, sitting in a tree... + [[file:../modules/tools/upload/README.org][upload]] - TODO * :ui diff --git a/init.example.el b/init.example.el index 07bcfaa6e..0ffd639db 100644 --- a/init.example.el +++ b/init.example.el @@ -105,6 +105,7 @@ ;;taskrunner ; taskrunner for all your projects ;;terraform ; infrastructure as code ;;tmux ; an API for interacting with tmux + ;;tree-sitter ; syntax and parsing, sitting in a tree... ;;upload ; map local to remote projects via ssh/ftp :os diff --git a/modules/editor/fold/autoload/fold.el b/modules/editor/fold/autoload/fold.el index 69726ebaa..2b757cc87 100644 --- a/modules/editor/fold/autoload/fold.el +++ b/modules/editor/fold/autoload/fold.el @@ -32,6 +32,11 @@ (end-of-line) (+fold--hideshow-fold-p)))))) +;; NOTE: does this need more? +(defun +fold--ts-fold-p () + (and (bound-and-true-p tree-sitter-mode) + (featurep 'ts-fold))) + (defun +fold--invisible-points (count) (let (points) (save-excursion @@ -62,7 +67,7 @@ (defun +fold/toggle () "Toggle the fold at point. -Targets `vimmish-fold', `hideshow' and `outline' folds." +Targets `vimmish-fold', `hideshow', `ts-fold' and `outline' folds." (interactive) (save-excursion (cond ((+fold--vimish-fold-p) (vimish-fold-toggle)) @@ -70,29 +75,32 @@ Targets `vimmish-fold', `hideshow' and `outline' folds." (cl-letf (((symbol-function #'outline-hide-subtree) (symbol-function #'outline-hide-entry))) (outline-toggle-children))) + ((+fold--ts-fold-p) (ts-fold-toggle)) ((+fold--hideshow-fold-p) (+fold-from-eol (hs-toggle-hiding)))))) ;;;###autoload (defun +fold/open () "Open the folded region at point. -Targets `vimmish-fold', `hideshow' and `outline' folds." +Targets `vimmish-fold', `hideshow', `ts-fold' and `outline' folds." (interactive) (save-excursion (cond ((+fold--vimish-fold-p) (vimish-fold-unfold)) ((+fold--outline-fold-p) (outline-show-children) (outline-show-entry)) + ((+fold--ts-fold-p) (ts-fold-open)) ((+fold--hideshow-fold-p) (+fold-from-eol (hs-show-block)))))) ;;;###autoload (defun +fold/close () "Close the folded region at point. -Targets `vimmish-fold', `hideshow' and `outline' folds." +Targets `vimmish-fold', `hideshow', `ts-fold' and `outline' folds." (interactive) (save-excursion (cond ((+fold--vimish-fold-p) (vimish-fold-refold)) + ((+fold--ts-fold-p) (ts-fold-close)) ((+fold--hideshow-fold-p) (+fold-from-eol (hs-hide-block))) ((+fold--outline-fold-p) (outline-hide-subtree))))) @@ -101,18 +109,20 @@ Targets `vimmish-fold', `hideshow' and `outline' folds." "Open folds at LEVEL (or all folds if LEVEL is nil)." (interactive (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg)))) - (when (featurep 'vimish-fold) - (vimish-fold-unfold-all)) - (save-excursion - (+fold--ensure-hideshow-mode) - (if (integerp level) - (progn - (outline-hide-sublevels (max 1 (1- level))) - (hs-life-goes-on - (hs-hide-level-recursive (1- level) (point-min) (point-max)))) - (hs-show-all) - (when (fboundp 'outline-show-all) - (outline-show-all))))) + (cond ((+fold--ts-fold-p) + (ts-fold-open-all)) + ((featurep 'vimish-fold) + (vimish-fold-unfold-all)) + ((save-excursion + (+fold--ensure-hideshow-mode) + (if (integerp level) + (progn + (outline-hide-sublevels (max 1 (1- level))) + (hs-life-goes-on + (hs-hide-level-recursive (1- level) (point-min) (point-max)))) + (hs-show-all) + (when (fboundp 'outline-show-all) + (outline-show-all))))))) ;;;###autoload (defun +fold/close-all (&optional level) @@ -120,13 +130,16 @@ Targets `vimmish-fold', `hideshow' and `outline' folds." (interactive (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg)))) (save-excursion - (when (featurep 'vimish-fold) - (vimish-fold-refold-all)) - (+fold--ensure-hideshow-mode) - (hs-life-goes-on - (if (integerp level) - (hs-hide-level-recursive (1- level) (point-min) (point-max)) - (hs-hide-all))))) + (if (+fold--ts-fold-p) + (ts-fold-close-all) + (progn + (when (featurep 'vimish-fold) + (vimish-fold-refold-all)) + (+fold--ensure-hideshow-mode) + (hs-life-goes-on + (if (integerp level) + (hs-hide-level-recursive (1- level) (point-min) (point-max)) + (hs-hide-all))))))) ;;;###autoload (defun +fold/next (count) @@ -142,7 +155,24 @@ Targets `vimmish-fold', `hideshow' and `outline' folds." (if (> count 0) (evil-vimish-fold/next-fold count) (evil-vimish-fold/previous-fold (- count)))) - (if (/= (point) orig-pt) (point)))) + (if (/= (point) orig-pt) (point))) + (lambda () + ;; ts-fold does not define movement functions so we need to do it ourselves + (when (+fold--ts-fold-p) + (let* ((arg-list (if (> count 0) ;; depending on direction we need to change the ranges + (list (point) (point-max)) + (list (point-min) (point)))) + (comp-fun (if (> count 0) ;; also depending on direction we need to change how we sort the list + #'< + #'>)) + (ovs (cl-remove-if-not + (lambda (ov) + (eq (overlay-get ov 'creator) 'ts-fold)) + ;; `overlays-in' does not provide a list that is sorted + ;; (in the way we need it atleast) so we need to sort it based on direction + (cl-sort (apply #'overlays-in arg-list) comp-fun :key #'overlay-start)))) + (if (and ovs (<= (abs count) (length ovs))) + (goto-char (overlay-start (nth (- (abs count) 1) ovs)))))))) if (save-excursion (funcall fn)) collect it into points finally do diff --git a/modules/editor/fold/config.el b/modules/editor/fold/config.el index 3dd9ed5c1..11be67aad 100644 --- a/modules/editor/fold/config.el +++ b/modules/editor/fold/config.el @@ -86,3 +86,16 @@ "zE" #'vimish-fold-delete-all) :config (vimish-fold-global-mode +1)) + +(use-package! ts-fold + :when (featurep! :tools tree-sitter) + :after tree-sitter + :config + ;; we want to use our own face so we nullify this one to have no effect and + ;; make it more similar to hideshows + (custom-set-faces! '(ts-fold-replacement-face :foreground nil + :box nil + :inherit font-lock-comment-face + :weight light)) + (setq ts-fold-replacement " [...] ") + (ts-fold-mode +1)) diff --git a/modules/editor/fold/packages.el b/modules/editor/fold/packages.el index ff2233c81..2867a322f 100644 --- a/modules/editor/fold/packages.el +++ b/modules/editor/fold/packages.el @@ -6,3 +6,6 @@ (package! vimish-fold :pin "a6501cbfe3db791f9ca17fd986c7202a87f3adb8") (when (featurep! :editor evil) (package! evil-vimish-fold :pin "b6e0e6b91b8cd047e80debef1a536d9d49eef31a")) +(when (featurep! :tools tree-sitter) + (package! ts-fold :pin "01d6485398a553a4fc4bbb3910edeb881c657f1f" + :recipe (:host github :repo "jcs090218/ts-fold"))) diff --git a/modules/lang/agda/config.el b/modules/lang/agda/config.el index 212a9b5e1..2920abd54 100644 --- a/modules/lang/agda/config.el +++ b/modules/lang/agda/config.el @@ -38,3 +38,9 @@ "h" #'agda2-display-implicit-arguments "q" #'agda2-quit "r" #'agda2-restart))) + +;; Tree Sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! '(agda-mode-local-vars-hook + agda2-mode-local-vars-hook) + #'tree-sitter!)) diff --git a/modules/lang/agda/doctor.el b/modules/lang/agda/doctor.el new file mode 100644 index 000000000..e5715325b --- /dev/null +++ b/modules/lang/agda/doctor.el @@ -0,0 +1,5 @@ +;;; lang/agda/doctor.el -*- lexical-binding: t; -*- + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/cc/config.el b/modules/lang/cc/config.el index f6ff67a0b..40fb8069e 100644 --- a/modules/lang/cc/config.el +++ b/modules/lang/cc/config.el @@ -304,3 +304,9 @@ If rtags or rdm aren't available, fail silently instead of throwing a breaking e "-isystem/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include" "-isystem/usr/local/include"] :resourceDir (cdr (doom-call-process "clang" "-print-resource-dir")))))))) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! '(c-mode-local-vars-hook + c++-mode-local-vars-hook) + #'tree-sitter!)) diff --git a/modules/lang/cc/doctor.el b/modules/lang/cc/doctor.el index 3c3facd0f..8cb6f9e87 100644 --- a/modules/lang/cc/doctor.el +++ b/modules/lang/cc/doctor.el @@ -5,6 +5,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (when (require 'rtags nil t) ;; rtags (when-let (bins (cl-remove-if #'rtags-executable-find diff --git a/modules/lang/csharp/config.el b/modules/lang/csharp/config.el index 4da8c94a3..cdd4bb6fd 100644 --- a/modules/lang/csharp/config.el +++ b/modules/lang/csharp/config.el @@ -72,3 +72,7 @@ or terminating simple string." (use-package! sln-mode :mode "\\.sln\\'") + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'csharp-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/csharp/doctor.el b/modules/lang/csharp/doctor.el index b9a56317f..35592ca3e 100644 --- a/modules/lang/csharp/doctor.el +++ b/modules/lang/csharp/doctor.el @@ -5,3 +5,7 @@ (let ((omnisharp-bin (or omnisharp-server-executable-path (omnisharp--server-installation-path t)))) (unless (file-exists-p omnisharp-bin) (warn! "Omnisharp server isn't installed, completion won't work")))) + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/elixir/config.el b/modules/lang/elixir/config.el index df6cbb60b..de356246b 100644 --- a/modules/lang/elixir/config.el +++ b/modules/lang/elixir/config.el @@ -99,3 +99,6 @@ "T" #'exunit-toggle-file-and-test "t" #'exunit-toggle-file-and-test-other-window "s" #'exunit-verify-single)) + +(eval-when! (featurep! +tree-sitter) + (add-hook! 'elixir-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/elixir/doctor.el b/modules/lang/elixir/doctor.el new file mode 100644 index 000000000..cb0e825e0 --- /dev/null +++ b/modules/lang/elixir/doctor.el @@ -0,0 +1,6 @@ +;; -*- lexical-binding: t; no-byte-compile: t; -*- +;;; lang/elixir/doctor.el + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/elm/config.el b/modules/lang/elm/config.el index 42b03352a..df263a0d7 100644 --- a/modules/lang/elm/config.el +++ b/modules/lang/elm/config.el @@ -21,3 +21,7 @@ :when (featurep! :checkers syntax) :after elm-mode :config (add-to-list 'flycheck-checkers 'elm)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'elm-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/elm/doctor.el b/modules/lang/elm/doctor.el new file mode 100644 index 000000000..73c309eed --- /dev/null +++ b/modules/lang/elm/doctor.el @@ -0,0 +1,5 @@ +;;; lang/elm/doctor.el -*- lexical-binding: t; -*- + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/go/config.el b/modules/lang/go/config.el index dd97318d8..7231839b0 100644 --- a/modules/lang/go/config.el +++ b/modules/lang/go/config.el @@ -76,3 +76,7 @@ (use-package! flycheck-golangci-lint :when (featurep! :checkers syntax) :hook (go-mode . flycheck-golangci-lint-setup)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'go-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/go/doctor.el b/modules/lang/go/doctor.el index 5a8c4ba3e..1f8c18ff7 100644 --- a/modules/lang/go/doctor.el +++ b/modules/lang/go/doctor.el @@ -5,6 +5,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (unless (executable-find "guru") (warn! "Couldn't find guru. Refactoring commands (go-guru-*) won't work")) diff --git a/modules/lang/java/config.el b/modules/lang/java/config.el index 1c6c8bac7..0b06a1e61 100644 --- a/modules/lang/java/config.el +++ b/modules/lang/java/config.el @@ -49,3 +49,7 @@ If the depth is 2, the first two directories are removed: net.lissner.game.") (set-docsets! 'groovy-mode "Groovy" "Groovy_JDK") (set-eval-handler! 'groovy-mode "groovy") (set-repl-handler! 'groovy-mode #'+java/open-groovy-repl)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'java-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/java/doctor.el b/modules/lang/java/doctor.el index 4a9372c23..13746350c 100644 --- a/modules/lang/java/doctor.el +++ b/modules/lang/java/doctor.el @@ -5,6 +5,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (unless (executable-find "javac") (warn! "Couldn't find the javac executable, are you sure the JDK is installed?")) diff --git a/modules/lang/javascript/config.el b/modules/lang/javascript/config.el index 6f3c13f6c..64eabf742 100644 --- a/modules/lang/javascript/config.el +++ b/modules/lang/javascript/config.el @@ -127,7 +127,16 @@ (define-derived-mode typescript-tsx-mode web-mode "TypeScript-TSX") (when (featurep! +lsp) (after! lsp-mode - (add-to-list 'lsp--formatting-indent-alist '(typescript-tsx-mode . typescript-indent-level))))) + (add-to-list 'lsp--formatting-indent-alist '(typescript-tsx-mode . typescript-indent-level)))) + (when (featurep! +tree-sitter) + (after! tree-sitter + (pushnew! tree-sitter-major-mode-language-alist '(typescript-tsx-mode . tsx)) + (pushnew! evil-textobj-tree-sitter-major-mode-language-alist '(typescript-tsx-mode . "tsx")) + + ;; HACK: the tsx grammer doesn't work with the hightlighting provided by + ;; font-lock-keywords. See emacs-tree-sitter/tree-sitter-langs#23 + (setq-hook! 'typescript-tsx-mode-hook + tree-sitter-hl-use-font-lock-keywords nil)))) (set-docsets! '(typescript-mode typescript-tsx-mode) :add "TypeScript" "AngularTS") @@ -311,3 +320,12 @@ to tide." (def-project-mode! +javascript-gulp-mode :when (locate-dominating-file default-directory "gulpfile.js")) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! '(js-mode-local-vars-hook + js2-mode-local-vars-hook + typescript-mode-local-vars-hook + typescript-tsx-mode-local-vars-hook + rjsx-mode-local-vars-hook) + #'tree-sitter!)) diff --git a/modules/lang/javascript/doctor.el b/modules/lang/javascript/doctor.el index e4c3dea63..087bde196 100644 --- a/modules/lang/javascript/doctor.el +++ b/modules/lang/javascript/doctor.el @@ -4,3 +4,7 @@ (assert! (or (not (featurep! +lsp)) (featurep! :tools lsp)) "This module requires (:tools lsp)") + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/json/config.el b/modules/lang/json/config.el index b96221d11..a088c54d2 100644 --- a/modules/lang/json/config.el +++ b/modules/lang/json/config.el @@ -29,3 +29,8 @@ :map json-mode-map :localleader "s" #'counsel-jq)) + +(eval-when! (featurep! +tree-sitter) + (add-hook! '(json-mode-local-vars-hook + jsonc-mode-local-vars-hook) + #'tree-sitter!)) diff --git a/modules/lang/json/doctor.el b/modules/lang/json/doctor.el index f1dc3009a..ce2226de7 100644 --- a/modules/lang/json/doctor.el +++ b/modules/lang/json/doctor.el @@ -3,3 +3,7 @@ (when (and (featurep! :completion ivy) (not (executable-find "jq"))) (warn! "Couldn't find jq. counsel-jq won't work." )) + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/julia/config.el b/modules/lang/julia/config.el index d97811284..bd4837c46 100644 --- a/modules/lang/julia/config.el +++ b/modules/lang/julia/config.el @@ -99,3 +99,7 @@ ;; Prevent timeout while installing LanguageServer.jl (setq-hook! 'julia-mode-hook eglot-connect-timeout (max eglot-connect-timeout 60)) :config (eglot-jl-init)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'julia-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/julia/doctor.el b/modules/lang/julia/doctor.el index ea64cf495..193faed09 100644 --- a/modules/lang/julia/doctor.el +++ b/modules/lang/julia/doctor.el @@ -4,6 +4,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (when (featurep! +lsp) (let ((args (cond ((require 'eglot-jl nil t) diff --git a/modules/lang/nix/config.el b/modules/lang/nix/config.el index b65f732ad..bda5cb76a 100644 --- a/modules/lang/nix/config.el +++ b/modules/lang/nix/config.el @@ -47,3 +47,7 @@ (use-package! nix-repl :commands nix-repl-show) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'nix-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/nix/doctor.el b/modules/lang/nix/doctor.el index 93f4de0c3..d79f06880 100644 --- a/modules/lang/nix/doctor.el +++ b/modules/lang/nix/doctor.el @@ -7,3 +7,6 @@ (unless (executable-find "nixfmt") (warn! "Couldn't find nixfmt. nix-format-buffer won't work.")) +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/ocaml/config.el b/modules/lang/ocaml/config.el index 696aa9822..001428d3f 100644 --- a/modules/lang/ocaml/config.el +++ b/modules/lang/ocaml/config.el @@ -118,3 +118,7 @@ ((equal ext ".eliomi") (setq-local ocamlformat-file-kind 'interface))))) (setq +format-with 'ocamlformat)))) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'tuareg-mode-local-vars-hook #'tree-sitter)) diff --git a/modules/lang/ocaml/doctor.el b/modules/lang/ocaml/doctor.el index 4e5445de5..712931aba 100644 --- a/modules/lang/ocaml/doctor.el +++ b/modules/lang/ocaml/doctor.el @@ -5,6 +5,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (unless (executable-find "ocamlmerlin") (warn! "Couldn't find ocamlmerlin. Lookup, completion and syntax checking won't work")) diff --git a/modules/lang/php/config.el b/modules/lang/php/config.el index 8eb917f63..c6f435b24 100644 --- a/modules/lang/php/config.el +++ b/modules/lang/php/config.el @@ -176,3 +176,7 @@ :on-exit (setq phpunit-args nil phpunit-executable nil)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'php-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/php/doctor.el b/modules/lang/php/doctor.el index 4e2ed45fc..95b984ce9 100644 --- a/modules/lang/php/doctor.el +++ b/modules/lang/php/doctor.el @@ -4,3 +4,7 @@ (assert! (or (not (featurep! +lsp)) (featurep! :tools lsp)) "This module requires (:tools lsp)") + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/python/config.el b/modules/lang/python/config.el index d7932e2ba..4f2b65f31 100644 --- a/modules/lang/python/config.el +++ b/modules/lang/python/config.el @@ -345,3 +345,7 @@ (use-package! lsp-pyright :when (featurep! +pyright) :after lsp-mode)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'python-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/python/doctor.el b/modules/lang/python/doctor.el index d1d2c08e4..b45f14f24 100644 --- a/modules/lang/python/doctor.el +++ b/modules/lang/python/doctor.el @@ -4,6 +4,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (if (not (or (executable-find "python") (executable-find "python3"))) (error! "Couldn't find python in your PATH") diff --git a/modules/lang/ruby/config.el b/modules/lang/ruby/config.el index c39686e1b..3ffec7bc0 100644 --- a/modules/lang/ruby/config.el +++ b/modules/lang/ruby/config.el @@ -195,3 +195,7 @@ (map! :localleader :map projectile-rails-mode-map "r" #'projectile-rails-command-map)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'ruby-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/ruby/doctor.el b/modules/lang/ruby/doctor.el index 5da563240..df74e9063 100644 --- a/modules/lang/ruby/doctor.el +++ b/modules/lang/ruby/doctor.el @@ -4,6 +4,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (unless (executable-find "ruby") (warn! "Ruby isn't installed.")) diff --git a/modules/lang/rust/config.el b/modules/lang/rust/config.el index b9971ad85..3fcab041a 100644 --- a/modules/lang/rust/config.el +++ b/modules/lang/rust/config.el @@ -81,3 +81,7 @@ (set-lookup-handlers! 'rustic-mode :definition '(racer-find-definition :async t) :documentation '+rust-racer-lookup-documentation)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'rustic-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/rust/doctor.el b/modules/lang/rust/doctor.el index 689d54285..70a5d2bc7 100644 --- a/modules/lang/rust/doctor.el +++ b/modules/lang/rust/doctor.el @@ -5,6 +5,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (unless (executable-find "rustc") (warn! "Couldn't find rustc binary")) diff --git a/modules/lang/scala/config.el b/modules/lang/scala/config.el index 470eb0dcc..0e427e909 100644 --- a/modules/lang/scala/config.el +++ b/modules/lang/scala/config.el @@ -50,3 +50,7 @@ (use-package! sbt-mode :after scala-mode :config (set-repl-handler! 'scala-mode #'+scala/open-repl :persist t)) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'scala-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/scala/doctor.el b/modules/lang/scala/doctor.el index 06a62720f..76d3c6a05 100644 --- a/modules/lang/scala/doctor.el +++ b/modules/lang/scala/doctor.el @@ -4,6 +4,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (if (and (featurep! +lsp) (not (executable-find "metals-emacs"))) (warn! "metals-emacs isn't installed")) diff --git a/modules/lang/sh/config.el b/modules/lang/sh/config.el index 16e23a610..684781bea 100755 --- a/modules/lang/sh/config.el +++ b/modules/lang/sh/config.el @@ -93,3 +93,7 @@ :config (when (featurep! +lsp) (add-hook 'powershell-mode-local-vars-hook #'lsp! 'append))) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'sh-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/sh/doctor.el b/modules/lang/sh/doctor.el index a25f3dfbc..0575489b3 100644 --- a/modules/lang/sh/doctor.el +++ b/modules/lang/sh/doctor.el @@ -3,3 +3,7 @@ (when (featurep! :checkers syntax) (unless (executable-find "shellcheck") (warn! "Couldn't find shellcheck. Shell script linting will not work"))) + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/swift/config.el b/modules/lang/swift/config.el index dc0fc6074..0de022376 100644 --- a/modules/lang/swift/config.el +++ b/modules/lang/swift/config.el @@ -31,3 +31,7 @@ "sourcekit" "/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/sourcekit-lsp" "/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/sourcekit")))) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! 'swift-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/swift/doctor.el b/modules/lang/swift/doctor.el new file mode 100644 index 000000000..c5efe98bc --- /dev/null +++ b/modules/lang/swift/doctor.el @@ -0,0 +1,5 @@ +;;; lang/swift/doctor.el -*- lexical-binding: t; -*- + +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") diff --git a/modules/lang/web/config.el b/modules/lang/web/config.el index 2185048c0..34ad2d069 100644 --- a/modules/lang/web/config.el +++ b/modules/lang/web/config.el @@ -55,3 +55,10 @@ (def-project-mode! +web-phaser-mode :modes '(+javascript-npm-mode) :when (+javascript-npm-dep-p '(or phaser phaser-ce)))) + +;; Tree sitter +(eval-when! (featurep! +tree-sitter) + (add-hook! '(html-mode-local-vars-hook + mhtml-mode-local-vars-hook + css-mode-local-vars-hook) + #'tree-sitter!)) diff --git a/modules/lang/web/doctor.el b/modules/lang/web/doctor.el index fa51a0d30..575bdccb4 100644 --- a/modules/lang/web/doctor.el +++ b/modules/lang/web/doctor.el @@ -4,6 +4,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (unless (executable-find "js-beautify") (warn! "Couldn't find js-beautify. Code formatting in JS/CSS/HTML modes will not work.")) diff --git a/modules/lang/zig/config.el b/modules/lang/zig/config.el index 23c7282d1..94b3b7355 100644 --- a/modules/lang/zig/config.el +++ b/modules/lang/zig/config.el @@ -30,3 +30,6 @@ "f" #'zig-format-buffer "r" #'zig-run "t" #'zig-test-buffer)) + +(eval-when! (featurep! +tree-sitter) + (add-hook! 'zig-mode-local-vars-hook #'tree-sitter!)) diff --git a/modules/lang/zig/doctor.el b/modules/lang/zig/doctor.el index ecbeca1a1..cfa477cdd 100644 --- a/modules/lang/zig/doctor.el +++ b/modules/lang/zig/doctor.el @@ -5,6 +5,10 @@ (featurep! :tools lsp)) "This module requires (:tools lsp)") +(assert! (or (not (featurep! +tree-sitter)) + (featurep! :tools tree-sitter)) + "This module requires (:tools tree-sitter)") + (unless (executable-find "zig") (warn! "Couldn't find zig binary")) diff --git a/modules/tools/tree-sitter/README.org b/modules/tools/tree-sitter/README.org new file mode 100644 index 000000000..0ae737029 --- /dev/null +++ b/modules/tools/tree-sitter/README.org @@ -0,0 +1,147 @@ +#+TITLE: tools/tree-sitter +#+DATE: August 17, 2021 +#+SINCE: 3.0.0 +#+STARTUP: inlineimages nofold + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#maintainers][Maintainers]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] +- [[#prerequisites][Prerequisites]] +- [[#features][Features]] + - [[#language-support][Language support]] + - [[#text-objects][Text Objects]] + - [[#goto-certain-nodes][Goto certain nodes]] +- [[#configuration][Configuration]] + - [[#disable-text-objects-for-certain-modes][Disable text objects for certain modes]] + - [[#rebinding-text-objects][Rebinding text objects]] + - [[#adding-your-own-text-objects][Adding your own text objects]] + - [[#disabling-highlighting-for-certain-modes][Disabling highlighting for certain modes]] +- [[#troubleshooting][Troubleshooting]] + - [[#error-bad-bounding-indices-0-1][=(error "Bad bounding indices: 0, 1")=]] + +* Description +This module adds [[https://tree-sitter.github.io/tree-sitter/][tree-sitter]] support to doom: + +#+begin_quote +Tree sitter is a parser generator tool and an incremental parsing library. It +can build a concrete syntax tree for a source file and efficiently update the +syntax tree as the source file is edited. This allows for features of the editor + to become syntax aware. +#+end_quote + +It includes: ++ Better syntax highlighting of supported languages ++ Structural text objects to manipulate functions statements and other code + structures like any other text object + +** Maintainers +- @jeetelongname + +** Module Flags +This module provides no flags. + +** Plugins ++ [[https://github.com/emacs-tree-sitter/elisp-tree-sitter][tree-sitter]] ++ [[https://github.com/emacs-tree-sitter/tree-sitter-langs][tree-sitter-langs]] ++ [[https://github.com/meain/evil-textobj-tree-sitter][evil-textobj-tree-sitter]]* (=:editor evil +everywhere=) + +* Prerequisites +This module has no prerequisites. + +* Features +** Language support +Currently Emacs tree sitter has got [[https://github.com/emacs-tree-sitter/tree-sitter-langs/tree/master/repos][parsers for these languages]] with syntax +highlighting support for [[https://emacs-tree-sitter.github.io/syntax-highlighting/][these languages]] as well as ~typescript-tsx-mode~ +To enable tree sitter for individual languages add the =+tree-sitter= flag. +Check the module readme of your language for support. + +** Text Objects +Not all language support all text objects (yet). [[https://github.com/nvim-treesitter/nvim-treesitter-textobjects#built-in-textobjects][Here is a table of the text +objects languages support]] +Note: only languages with parsers in emacs have text object support currently. +Currently text objects are bound to: +| key | text object | +|-----+---------------------| +| =A= | parameter list | +| =f= | function definition | +| =F= | function call | +| =C= | class | +| =c= | comment | +| =v= | conditional | +| =l= | loop | + +They are used in a container context (not =vf= but =vaf= or =vif=) + +** Goto certain nodes +you can also jump to the next / previous node type in a buffer by using =[g= +or =]g= respectfully, the following key will correspond to the text object you +want to jump to +Currently keys are bound to: +| key | text object | +|-----+----------------| +| =a= | parameter list | +| =f= | function | +| =F= | function call | +| =c= | comment | +| =C= | class | +| =v= | conditional | +| =l= | loop | + +* Configuration +** Disable text objects for certain modes +If you wish to disable tree sitter text objects then you can just remove +=+tree-sitter-keys-mode= from the language mode hook, for example if we did not +want it for ruby we would use this snippet +#+begin_src emacs-lisp +(remove-hook 'ruby-mode-hook #'+tree-sitter-keys-mode) +#+end_src + +** Rebinding text objects +Rebinding keys is the same as any other key but do notes they need to be bound +to the keymaps ~+tree-sitter-inner-text-object-map~ or +~+tree-sitter-outer-text-object-map~ +#+begin_src emacs-lisp +(map! (:map +tree-sitter-outer-text-objects-map + "f" nil + "f" (evil-textobj-tree-sitter-get-textobj "call.inner") + "F" nil + "F" (evil-textobj-tree-sitter-get-textobj "function.inner")) + (:map +tree-sitter-inner-text-objects-map + "f" nil + "f" (evil-textobj-tree-sitter-get-textobj "call.inner") + "F" nil + "F" (evil-textobj-tree-sitter-get-textobj "function.inner"))) +#+end_src + +** Adding your own text objects +If you wish to [[https://github.com/meain/evil-textobj-tree-sitter#custom-textobjects][add your own custom text objects]] then you need to bind them and +add them to the ~+tree-sitter-{inner, outer}-text-objects-map~ +for example: +#+begin_src emacs-lisp +(map! (:map +tree-sitter-outer-text-objects-map + "m" (evil-textobj-tree-sitter-get-textobj "import" + '((python-mode . [(import_statement) @import]) + (rust-mode . [(use_declaration) @import]))))) +#+end_src + +** Disabling highlighting for certain modes +If you want to disable highlighting by default you can add a +#+begin_src emacs-lisp +(after! MODE-PACKAGE + (tree-sitter-hl-mode -1)) +#+end_src + +If you only want it for certain modes then +#+begin_src emacs-lisp +(remove-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode) + +(add-hook 'MAJOR-MODE-HOOK #'tree-sitter-hl-mode) +#+end_src + +* Troubleshooting +** =(error "Bad bounding indices: 0, 1")= +This means that the text object does not have the underlying query needed, this can be +fixed by either adding in a custom query (which would override the current key +bound.) or [[https://github.com/nvim-treesitter/nvim-treesitter-textobjects/][contributing upstream!]] diff --git a/modules/tools/tree-sitter/autoload.el b/modules/tools/tree-sitter/autoload.el new file mode 100644 index 000000000..608ed77bf --- /dev/null +++ b/modules/tools/tree-sitter/autoload.el @@ -0,0 +1,22 @@ +;;; tools/tree-sitter/autoload.el -*- lexical-binding: t; -*- + +;;;###autodef +(defun tree-sitter! () + (interactive) + (turn-on-tree-sitter-mode)) + +;; HACK: Remove and refactor when `use-package' eager macro expansion is solved or `use-package!' is removed +;;;###autoload +(defun +tree-sitter-get-textobj (group &optional query) + "A wrapper around `evil-textobj-tree-sitter-get-textobj' to +prevent eager expansion." + (eval `(evil-textobj-tree-sitter-get-textobj ,group ,query))) + +;;;###autoload +(defun +tree-sitter-goto-textobj (group &optional previous end query) + "Thin wrapper that returns the symbol of a named function, used in keybindings." + (let ((sym (intern (format "+goto%s%s-%s" (if previous "-previous" "") (if end "-end" "") group)))) + (fset sym (lambda () + (interactive) + (evil-textobj-tree-sitter-goto-textobj group previous end query))) + sym)) diff --git a/modules/tools/tree-sitter/config.el b/modules/tools/tree-sitter/config.el new file mode 100644 index 000000000..a0ff3a3c0 --- /dev/null +++ b/modules/tools/tree-sitter/config.el @@ -0,0 +1,66 @@ +;;; tools/tree-sitter/config.el -*- lexical-binding: t; -*- + +(use-package! tree-sitter + :hook (tree-sitter-after-on . tree-sitter-hl-mode) + :config + (require 'tree-sitter-langs) + ;; This makes every node a link to a section of code + (setq tree-sitter-debug-jump-buttons t + ;; and this highlights the entire sub tree in your code + tree-sitter-debug-highlight-jump-region t)) + +(use-package! evil-textobj-tree-sitter + :when (featurep! :editor evil +everywhere) + :after tree-sitter + :config + (defvar +tree-sitter-inner-text-objects-map (make-sparse-keymap)) + (defvar +tree-sitter-outer-text-objects-map (make-sparse-keymap)) + (defvar +tree-sitter-goto-previous-map (make-sparse-keymap)) + (defvar +tree-sitter-goto-next-map (make-sparse-keymap)) + + (evil-define-key '(visual operator) 'tree-sitter-mode + "i" +tree-sitter-inner-text-objects-map + "a" +tree-sitter-outer-text-objects-map) + (evil-define-key 'normal 'tree-sitter-mode + "[g" +tree-sitter-goto-previous-map + "]g" +tree-sitter-goto-next-map) + + (map! (:map +tree-sitter-inner-text-objects-map + "A" (+tree-sitter-get-textobj '("parameter.inner" "call.inner")) + "f" (+tree-sitter-get-textobj "function.inner") + "F" (+tree-sitter-get-textobj "call.inner") + "C" (+tree-sitter-get-textobj "class.inner") + "v" (+tree-sitter-get-textobj "conditional.inner") + "l" (+tree-sitter-get-textobj "loop.inner")) + (:map +tree-sitter-outer-text-objects-map + "A" (+tree-sitter-get-textobj '("parameter.outer" "call.outer")) + "f" (+tree-sitter-get-textobj "function.outer") + "F" (+tree-sitter-get-textobj "call.outer") + "C" (+tree-sitter-get-textobj "class.outer") + "c" (+tree-sitter-get-textobj "comment.outer") + "v" (+tree-sitter-get-textobj "conditional.outer") + "l" (+tree-sitter-get-textobj "loop.outer")) + + (:map +tree-sitter-goto-previous-map + "a" (+tree-sitter-goto-textobj "parameter.outer" t) + "f" (+tree-sitter-goto-textobj "function.outer" t) + "F" (+tree-sitter-goto-textobj "call.outer" t) + "C" (+tree-sitter-goto-textobj "class.outer" t) + "c" (+tree-sitter-goto-textobj "comment.outer" t) + "v" (+tree-sitter-goto-textobj "conditional.outer" t) + "l" (+tree-sitter-goto-textobj "loop.outer" t)) + (:map +tree-sitter-goto-next-map + "a" (+tree-sitter-goto-textobj "parameter.outer") + "f" (+tree-sitter-goto-textobj "function.outer") + "F" (+tree-sitter-goto-textobj "call.outer") + "C" (+tree-sitter-goto-textobj "class.outer") + "c" (+tree-sitter-goto-textobj "comment.outer") + "v" (+tree-sitter-goto-textobj "conditional.outer") + "l" (+tree-sitter-goto-textobj "loop.outer"))) + + + (after! which-key + (setq which-key-allow-multiple-replacements t) + (pushnew! + which-key-replacement-alist + '(("" . "\\`+?evil-textobj-tree-sitter-function--\\(.*\\)\\(?:.inner\\|.outer\\)") . (nil . "\\1"))))) diff --git a/modules/tools/tree-sitter/doctor.el b/modules/tools/tree-sitter/doctor.el new file mode 100644 index 000000000..68026fce7 --- /dev/null +++ b/modules/tools/tree-sitter/doctor.el @@ -0,0 +1,4 @@ +;;; tools/treesitter/doctor.el -*- lexical-binding: t; -*- + +(unless (fboundp 'module-load) + (warn! "Emacs was not built with dynamic modules support. Tree sitter needs this to function")) diff --git a/modules/tools/tree-sitter/packages.el b/modules/tools/tree-sitter/packages.el new file mode 100644 index 000000000..f572ea6f9 --- /dev/null +++ b/modules/tools/tree-sitter/packages.el @@ -0,0 +1,12 @@ +;; -*- no-byte-compile: t; -*- +;;; tools/tree-sitter/packages.el + +(package! tree-sitter + :pin "3cfab8a0e945db9b3df84437f27945746a43cc71") + +(package! tree-sitter-langs + :pin "deb2d8674be8f777ace50e15c7c041aeddb1d0b2") + +(when (featurep! :editor evil +everywhere) + (package! evil-textobj-tree-sitter + :pin "0bf5bbbfecba95d49d40441ea54c6130e52bbeb1"))