Skip to content

cpoile/jai-ts-mode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Tree-sitter driven indentation, syntax highlighting, and navigation

images/example-highlighting.png

It will treat `#if false {` blocks as comment-face for clarity:

images/static-if-false-example.png

Please let me know if you see anything wrong (with a bit of a repro code) and I’ll do my best to fix it.

jai-ts-mode

A Major mode for Emacs >= 29.1 powered by tree-sitter for editing Jai files.

Requirements

Emacs >= 29.1 compiled with tree-sitter.

The tree-sitter grammar can be found at https://github.com/constantitus/tree-sitter-jai

To install it you can add this line to your `treesit-language-source-alist`:

(defvar treesit-language-source-alist
  '(...
    (jai "https://github.com/constantitus/tree-sitter-jai")
    ...))

Afterwards run M-x treesit-install-language-grammar RET jai RET.

Installation

Manual Installation:

Clone this repo

git clone https://github.com/cpoile/jai-ts-mode.git

and add this to your init.el file:

(load-file "/path/to/jai-ts-mode.el")

use-package

You can also use use-package to install this package, simply add this to your init.el file (you can skip the usage step below with this):

(use-package jai-ts-mode
  :ensure (:host github :repo "cpoile/jai-ts-mode")
  :mode "\\.jai\\'")

Usage

Add this to your configuration:

(add-to-list 'auto-mode-alist '("\\.jai\\'" . jai-ts-mode))

Tips and tricks

Here are some conveniences that you might find useful.

Navigation using treesitter, and aligning structs

;; For Doom Emacs:
(map! :map jai-ts-mode-map
      "C-M-a" #'jai-ts-mode--prev-defun
      "C-M-e" #'jai-ts-mode--next-defun
      "C-M-l" #'align-regexp
      "C-M-S-l" #'jai-ts-mode--align-struct)

;; Or regular Emacs:
(define-key jai-ts-mode-map (kbd "C-M-a") 'jai-ts-mode--prev-defun)
(define-key jai-ts-mode-map (kbd "C-M-e") 'jai-ts-mode--next-defun)
(define-key jai-ts-mode-map (kbd "C-M-l") 'align-regexp)
(define-key jai-ts-mode-map (kbd "C-M-S-l") 'jai-ts-mode--align-struct)

Dumb-jump for Go to definition/reference

Dumb-jump seems to do almost everything I need an LSP for.

Setup steps:

  1. Install https://github.com/jacktasia/dumb-jump
  2. Add to your init.el:
    (with-eval-after-load dumb-jump
      (setq dumb-jump-prefer-searcher 'rg
            dumb-jump-force-searcher 'rg
            dumb-jump-rg-search-args "--pcre2 --type-add \"jai:*.jai\"")
      (add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
        
  3. put a `.dumbjump` file in your project root containing the paths to directories you want to include, e.g.:
    +~/Jai/modules
        
  4. The following are convenience functions; use if you’d like. When I press `M-.` I want it to jump to the definition of the symbol (not references), but if I am already at the definition I want it to show references.

    Sometimes it shows references before the definition, so I’m still working on it. Maybe it’s because `+lookup/references` and `+lookup/definition` sometimes don’t work? /shrug

    (defun treesit-enabled-p ()
      "Checks if the current buffer has a treesit parser."
      (and (fboundp 'treesit-available-p)
           (treesit-available-p)
           (treesit-language-at (point))))
    
    ;; Add language node types that are considered declarations:
    (setq declaration-node-types '("procedure_declaration" "variable_declaration" "struct_declaration" "function_declaration"))
    
    (defun string-contains-any-substring-p (haystack targets)
      "Check if HAYSTACK string contains any string from the TARGETS list.
    
    HAYSTACK is the string to search within.
    TARGETS is a list of strings to search for.
    
    Search is case-sensitive by default (respects `case-fold-search`).
    Target strings are treated literally (regex metacharacters are quoted).
    
    Returns t if any string in TARGETS is found as a substring within HAYSTACK,
    nil otherwise."
      (seq-some
       (lambda (target-string) (string-match-p (regexp-quote target-string) haystack))
       targets))
    
    (defun cp/check-inspect-name-against-declarations ()
      "Calls treesit-inspect-node-at-point and then checks if the
    internal variable treesit--inspect-name exactly matches any type
    in a predefined list."
      (interactive)
      (when (treesit-enabled-p)
        (call-interactively #'treesit-inspect-node-at-point)
        (if (boundp 'treesit--inspect-name)
            (string-contains-any-substring-p treesit--inspect-name declaration-node-types))))
    
    (defun cp/go-to-def-or-ref ()
      (interactive)
      (let ((cur (line-number-at-pos))
            (cur-pt (point)))
        (if (cp/check-inspect-name-against-declarations)
            (call-interactively '+lookup/references)
          (call-interactively '+lookup/definition))))
        
  5. Then I add that to my prog-mode-map:
    ;; For Doom Emacs:
    (map! :map prog-mode-map
          "M-."        #'cp/go-to-def-or-ref)
    
    ;; Or regular Emacs:
    (define-key prog-mode-map (kbd "M-.") 'cp/go-to-def-or-ref)
    
        

Topsy for sticky function headers

https://github.com/alphapapa/topsy.el

images/topsy-ex.png

(add-hook 'prog-mode-hook #'topsy-mode)

(defun topsy--jai-beginning-of-defun ()
  "Return the line moved to by `jai-ts-mode--prev-defun'."
  (when (> (window-start) 1)
    (save-excursion
      (goto-char (window-start))
      (jai-ts-mode--prev-defun)
      (font-lock-ensure (point) (pos-eol))
      (buffer-substring (point) (pos-eol)))))

(add-to-list 'topsy-mode-functions '(jai-ts-mode . topsy--jai-beginning-of-defun))

Multiple cursors to rename symbol within function

I often want to rename a variable, but only within the current function. This is tedious, but with multiple cursors you can do it with some special logic. To set this up:

  1. Install https://github.com/magnars/multiple-cursors.el
  2. Put this in your `init.el`:
    (defun jai-narrow-to-defun ()
      "Narrow to the function/method definition at point using treesit."
      (let ((node (treesit-node-at (point))))
        (when-let ((defun-node (treesit-parent-until
                               node
                               (lambda (n)
                                 (member (treesit-node-type n)
                                        jai-ts-mode--defun-function-type-list)))))
          (narrow-to-region (treesit-node-start defun-node)
                           (treesit-node-end defun-node)))))
    
    (defun cp/mark-all-symbols-like-this-in-defun ()
      (interactive)
      (mc--select-thing-at-point-or-bark 'symbol)
      (if (eq major-mode 'jai-ts-mode)
          (save-restriction
          (widen)
          (jai-ts-mode--narrow-to-defun)
          (mc/mark-all-symbols-like-this))
        (save-restriction
          (widen)
          (narrow-to-defun)
          (mc/mark-all-symbols-like-this))))
    
    (global-set-key (kbd "C-c C-.") 'cp/mark-all-symbols-like-this-in-defun)
    
        

Roadmap? [3/5]

  • [-] Syntax Highlighting [2/3]
    • [X] Get something working
    • [X] Make things good enough
    • [ ] unknown unknowns
  • [-] Indentation [2/3]
    • [X] Get something working
    • [X] Make sure it’s good enough
    • [ ] unknown unknowns
  • [X] Imenu
  • [X] Forward/Backward defun
  • [X] Align struct fields with a keybinding

About

Jai emacs major mode using tree-sitter

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published