Skip to content

sogaiu/janet-ts-mode

Repository files navigation

janet-ts-mode

A tree-sitter-based Emacs major mode for the Janet programming language

Highlighting Sample

Status

Author uses as primary means of editing Janet code.

Currently has more-or-less usable:

  • Highlighting
  • Indentation
  • Imenu
  • Navigation
  • Which-Func

See the "Things to try" section below for additional details.

Quick Taste

For a recent Emacs (>= 30.x) with the integrated tree-sitter support, janet-emacs-trial-kit may be a way of trying out janet-ts-mode along with some other janet-supporting packages without having to change any existing settings or manually download anything additional.

Dependencies

Emacs with tree-sitter support (master branch or >= emacs-30)

Note: janet-ts-mode was known to work with Emacs 29.x at one point, but the author currently uses 30.x and doesn't exercise the code with 29.x, YMMV.

Setup

  • If Emacs >= 30.x with tree-sitter support is in place [1], something like the following in your .emacs equivalent should arrange for the tree-sitter-janet-simple grammar to be available:

    (setq treesit-language-source-alist
          (if (eq 'windows-nt system-type)
              '((janet-simple
                 . ("https://github.com/sogaiu/tree-sitter-janet-simple"
                    nil nil "gcc.exe")))
            '((janet-simple
               . ("https://github.com/sogaiu/tree-sitter-janet-simple")))))
    
    (when (not (treesit-language-available-p 'janet-simple))
      (treesit-install-language-grammar 'janet-simple))
  • janet-ts-mode prep

    • Manual

      • cd ~/src # or some place appropriate
      • git clone https://github.com/sogaiu/janet-ts-mode
      • edit your .emacs equivalent so that:
        • load-path knows about janet-ts-mode
        • (require 'janet-ts-mode) is included
    • use-package

      (use-package janet-ts-mode
        :vc (:url "https://github.com/sogaiu/janet-ts-mode"
             :rev :newest))
    • Elpaca - add something like the following to your .emacs equivalent:

      (use-package janet-ts-mode
        :elpaca (:host github
                 :repo "sogaiu/janet-ts-mode"
                 :files ("*.el")))
    • Straight.el - add something like the following to your .emacs equivalent:

      (straight-use-package
       '(janet-ts-mode :host github
                       :repo "sogaiu/janet-ts-mode"
                       :files ("*.el")))
      
      (use-package janet-ts-mode
        :straight t)

Things to try

First, open a .janet file and check that the current major mode is janet-ts-mode (e.g. via C-h m). Try M-x janet-ts-mode if it isn't.

Basics

  • Syntax highlighting - if things are working appropriately, non-empty buffer content should be appropriately highlighted.

  • Indentation - type in some code and press the Tab key to indent the current line, or select a region and press the Tab key to indent the region [2].

    Note that by default, within multi-line long-strings, indentation might feel inconvenient or otherwise not to your liking [3]. See the Indentation within Multi-Line Long-Strings section for more details.

  • Imenu - to end up at a particular top-level definition, M-x imenu, and follow the prompts. Alternatively, look for a menu named Index and interact with that appropriately :)

  • Navigation - use M-x treesit-end-of-defun (C-M-e) to navigate to the end of the current defun (kind of tricky to explain, but something like "top-level" form for the case of a lispy language in Emacs), or M-x treesit-beginning-of-defun (C-M-a) to navigate to the beginning of the current defun, or if not within a defun, conveniently navigate through the buffer by repeatedly invoking M-x treesit-end-of-defun to go in the forward direction (toward the end of the buffer), or M-x treesit-beginning-of-defun to go in the reverse direction (toward the beginning of the buffer).

  • Which-Func - when within a top-level definition, to see a guessed name for the definition, M-x which-function-mode, and observe the mode line. If things went well, probably toward the right of the mode line there should be some text like: [nice-function-name] if point is within a definition with name nice-function-name.

Developer-ish Things

  • Tree-sitter's view of the source - M-x treesit-explore-mode and choose janet-simple to open a second buffer (named *tree-sitter explorer for ...*) that shows a view of what the tree-sitter grammar thinks the code in the buffer parses as. Likely there will be a fair number of instances of things like sym_lit, par_tup_lit, num_lit, etc. These are node names associated with the tree-sitter-janet-simple grammar. Clicking on node names in the newly opened buffer should cause point in the corresponding buffer with Janet source code in it to move accordingly.

  • Tree-sitter queries - M-: (treesit-query-validate ...) and M-: (treesit-query-string ...) ... it's kind of complicated...please see comments in janet-ts-mode.el (^^;

Experimental Things

There's a file named janet-ts-experiment.el that contains a variety of convenience commands. As the file name suggests, the content is experimental. Not sure what if anything I'll keep from it, but currently it has:

  • Selection

    • Select something relevant around point
    • Expand current selection
  • Formatting Helpers

    • Split across lines, content of selection at opening parens
    • Format pairs within selection to be on own lines
  • Wrapping

    • Wrap something at point in tracev call
    • Unwrap a tracev call that contains point
  • Comment and Long-String Folding

    • Fold aforementioned forms so they don't take much space
    • Unfold folded forms so the content can be seen
    • Toggle the folding of certain forms
  • Delimiters

    • Cycle delimiters: (...) -> [...] -> {...} and back to parens
    • Move right delimiter at point over the next thing to the right - if you're not a structural editor user and have found auto-balanced delimiters (particularly parens) to get in your way, this function might be appealing.

If the file is required, it should add various things to the Janet-TS menu. Alternatively, using the "Enable Experimental Features" menu item under the Janet-TS menu may work too.

There's another file named janet-ts-helpers.el that provides limited integration with a number of Janet-related command line utilities:

Again, if the file is required, it should add various things to the Janet-TS menu. Alternatively, using the "Enable Helpers Features" menu item under the Janet-TS menu may work too.

Indentation within Multi-Line Long-Strings

As mentioned elsewhere, by default janet-ts-mode does not modify whitespace within long-strings in response to the Tab key being pressed [3].

One user reported that customization similar to the following yielded behavior more to their tastes:

(add-hook
    'janet-ts-mode-hook
    (lambda ()
      (bind-key "C-<return>" (lambda ()
                               (interactive)
                               (electric-indent-just-newline nil)
                               (indent-relative t))
                             janet-ts-mode-map)
      (bind-key "C-<tab>" (lambda ()
                            (interactive)
                            (indent-relative t))
                          janet-ts-mode-map)))

Credits

  • ahungry - theme and other discussions
  • AlbertoGP - PR 12 to janet-mode
  • alternateved - report and suggestion
  • amano.kenji - testing and installation instruction suggestions
  • bakpakin and Janet contributors
  • bbatsov - clojure-ts-mode discussions
  • casouri and other Emacs contributors - Emacs tree-sitter integration
  • CosmicToast - use-package installation info
  • dannyfreeman - clojure-ts-mode and discussions
  • dgutov - ruby-ts--syntax-propertize work
  • Fanael - rainbow-delimiters and syntax table discussion
  • maxbrunsfeld and tree-sitter contributors - tree-sitter
  • ml-2 - suggestions and discussions
  • sbocq - discussion about treesit-*-of-defun
  • thechampagne - autoloads
  • ubolonton and elisp-tree-sitter contributors
  • yrns - PR 11 to janet-mode

Footnotes

[1] If building Emacs >= 30.x from source (typically to get it with tree-sitter support), links below might be of interest:

  • Debian / Ubuntu -- this should become easier once Emacs-tree-sitter integration ships.
  • Void -- the easiest way I've tried so far :)
  • These steps give instructions that are a bit less distribution-specific (though there is a trade-off regarding setup of things so that Emacs knows where to find the tree-sitter library).
  • How to Get Started with Tree-Sitter might be worth looking at, though I haven't examined the details.

[2] Please note that although the description uses the phrase "press the Tab key", what is actually meant is invoking the indent-for-tab-command function in Emacs (which is what Tab is bound to by default in many non-C programming-related major modes).

[3] The TLDR version of why the indentation works the way it does by default is:

  1. Most other major modes for lisp-like languages behave this way.

  2. An effort has been made to preserve the alleged intent of the author of the code.

See below for a more detailed explanation.

As mentioned above, most major modes for lisp-like languages (apart from Clojure) in Emacs, behave in the same way by default with respect to indentation, e.g. pressing Tab by itself does not result in any change to the text. (This can be easily experienced by interacting appropriately with docstrings for Emacs Lisp constructs.) So in that sense janet-ts-mode's current arrangement is not particularly divergent from most other similar efforts.

There is at least one other reason for the current behavior but it requires a bit of detailed explanation. It turns out that long-strings in Janet become ordinary strings after parsing and as is documented on the website:

Janet will automatically remove indentation (so-called "dedenting") for whitespace that appears before the column in which the long-string began.

That description seems to leave out a little bit, but the following examples should help with clarifying what happens:

(def a
  ``
  First line
 Second line
  ``)
# =>
"  First line\n Second line\n  "

(def b
  ``
  First line
  Second line
  ``)
# =>
"First line\nSecond line"

Evaluation has not resulted in the string associated with a to be different than what is visible in the code. For example, there are two leading spaces and there is a single space after the first newline. Further, the string has a second newline and ends with two spaces.

In contrast, the ordinary string that is associated with b after evaluation does not have any leading or trailing whitespace and there is only a single newline. The second case is the type of situation the quoted text above regarding "dedenting" refers to.

When parsed, multi-line long-string content is only modified if the first non-whitespace character on every line (after the first line) occurs at or after the column of the leftmost character of the opening delimiter of the long-string.

As can be seen in the example above for a, the "S" in "Second" occurs before the leftmost character of the opening delimiter of the long-string and as such, dedenting does not happen for any portion of the long-string.

Editor indentation features can lead to changes in whitespace and as such, depending on how that's implemented, can in some cases lead to undesirable / unintended (and undetected) changes in the intent reflected in the formatting of a multi-line long-string due to the "dedenting" feature described above.

Two situations in which this might happen are if large sections of one's buffer or the entire buffer is selected and indentation is requested. In such cases there may be long-strings that are not visible being changed and if so the user will not see them and/or be aware of them and thus fail to detect whether the existing intent has been modified in ways contrary to their desires. Note that there may be more situations, the ones described above are presented to give one some general sense of the situation.

A safe way to preserve an author's alleged intent is to not modify whitespace within a long-string. This is the default behavior in janet-ts-mode.

About

Tree-sitter-based Emacs Major Mode for the Janet Programming Language

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors