The latest approach is to try to get different behavior based on whether the indentation is line-related or region-related.
Emacs provides two variables that can be set to functions that handle each type of indentation:
indent-region-function, andindent-line-function
The idea is to provide an appropriate function for each of these variables.
I've tried out the code below along with the following commands:
indent-for-tab-command(Tab)indent-region,prog-indent-sexp(C-M-q),prog-fill-reindent-defun(M-q)
and so far things seem to be mostly working for my usage.
If you have any other indentation-related functions that you use, please mention them.
If you wish to try things out, here are some instructions for use:
- Save the code at the end of this comment in a file named
janet-ts-custom-indent.eland make sure it is on theload-pathfor your Emacs setup (or change your Emacs settings so thatrequirewill work for it). - Add the following (or similar) to your
.emacs-equivalent file:
(require 'janet-ts-custom-indent)
;; choose ONE of the following:
;; 1. indent to align with opening delimiter
'(setq janet-ts-ml-long-string-anchor-fn
#'janet-ts-align-to-opening-delim-anchor
janet-ts-ml-long-string-offset-fn
#'janet-ts-align-to-opening-delim-offset)
;; 2. indent relative to "most recent" non-blank line
(setq janet-ts-ml-long-string-anchor-fn
#'janet-ts-relative-to-previous-anchor
janet-ts-ml-long-string-offset-fn
#'janet-ts-relative-to-previous-offset)
;; arrange to work when janet-ts-mode is enabled
(add-hook 'janet-ts-mode-hook
#'janet-ts-custom-ml-long-string-indent)Here is the content of janet-ts-custom-indent.el:
;;; janet-ts-custom-indent --- Custom indentation examples
;;; Commentary:
;; Example custom indentation code. Experimental. May change.
;; The basic idea is to line-indent differently within multi-line
;; docstrings than the default behavior of not doing anything.
;;
;; Two examples are provided.
;;
;; 1) Indents to match the opening delimiter of the long-string.
;;
;; 2) Indents to match the first non-blank line searching backwards.
;; This allows a little extra control over indentation.
;;
;; To use this file, ensure it's on `load-path' and include an
;; appropriately modified version of the following code in your
;; `.emacs'-equivalent file of choice:
;;
;; ;; make sure the code in this file can be used
;; (require 'janet-ts-custom-indent)
;;
;; ;; choose ONE of the following:
;;
;; ;; 1. indent to align with opening delimiter
;; (setq janet-ts-ml-long-string-anchor-fn
;; #'janet-ts-align-to-opening-delim-anchor
;; janet-ts-ml-long-string-offset-fn
;; #'janet-ts-align-to-opening-delim-offset)
;;
;; ;; 2. indent relative to "most recent" non-blank line
;; (setq janet-ts-ml-long-string-anchor-fn
;; #'janet-ts-relative-to-previous-anchor
;; janet-ts-ml-long-string-offset-fn
;; #'janet-ts-relative-to-previous-offset)
;;
;; ;; arrange to work when janet-ts-mode is enabled
;; (add-hook 'janet-ts-mode-hook
;; #'janet-ts-custom-ml-long-string-indent)
;;; Code:
(require 'treesit)
(require 'janet-ts-mode) ; for janet-ts--indent-rules
(defun janet-ts--node-is-multi-line (node)
"Check whether NODE is multi-line."
(let* ((n-start (treesit-node-start node))
(start-line (line-number-at-pos n-start))
(n-end (treesit-node-end node))
(end-line (line-number-at-pos n-end)))
(not (= start-line end-line))))
(defun janet-ts--in-multi-line-long-string-p ()
"Check whether point is in a multiline long-string."
(let* ((here (point))
;; treesit-node-at can give misleading answer:
;; immediately to the left of the opening delimiter or
;; immediately to the right of the closing delimiter
;; can give result that within long-string even if not
(here-node (treesit-node-on here here)))
(and (string= "long_str_lit" (treesit-node-type here-node))
(janet-ts--node-is-multi-line here-node))))
(defun janet-ts--indent-ml-long-string-align-to-opening-delim (node parent bol)
"If within a multi-line long-string, indent to align to delimiter.
More precisely, lines that appear after the long-string's opening
delimiter (but not on the same line as the delimiter) are indented to
match the left-most character of the long-string's opening delimiter.
For info on NODE, PARENT, and BOL, see `treesit-indent-function'."
(let* ((here (point))
(here-node (treesit-node-on here here))
(anchor (treesit-node-start here-node))
(start-line (line-number-at-pos anchor))
(current-line (line-number-at-pos here))
(offset nil))
(if (or (= start-line current-line)
(= here anchor))
;; hand off to the default
(cons nil nil)
(save-excursion
(goto-char anchor)
;; move to first non-whitespace character
(back-to-indentation)
;; compute indentation offset
(setq offset (- (point) anchor)))
(cons anchor offset))))
;; XXX: this approach is duplicated effort, but it's unclear how to
;; avoid that
(defun janet-ts-align-to-opening-delim-anchor (node parent bol)
"Anchor wrapper function.
Wraps `janet-ts--indent-ml-long-string-align-to-opening-delim' to get
its car, which corresponds to what an anchor function would have
produced.
For info on NODE, PARENT, and BOL, see `treesit-indent-function'."
(let ((result (janet-ts--indent-ml-long-string-align-to-opening-delim
node parent bol)))
(when result
(car result))))
(defun janet-ts-align-to-opening-delim-offset (node parent bol)
"Offset function.
Wraps `janet-ts--indent-ml-long-string-align-to-opening-delim' to get
its cdr, which corresponds to what an offset function would have
produced.
For info on NODE, PARENT, and BOL, see `treesit-indent-function'."
(let ((result (janet-ts--indent-ml-long-string-align-to-opening-delim
node parent bol)))
(when result
(cdr result))))
(defun janet-ts--indent-ml-long-string-relative-to-previous (node parent bol)
"If within a multi-line long-string, indent to match previous line.
More precisely, lines that appear after the long-string's opening
delimiter (but not on the same line as the delimiter) are indented to
match the first previous non-blank line searching backwards.
For info on NODE, PARENT, and BOL, see `treesit-indent-function'."
(let* ((start (point))
(start-node (treesit-node-on start start))
(close-line (line-number-at-pos (treesit-node-end start-node)))
(anchor (treesit-node-start start-node))
(start-line (line-number-at-pos anchor))
(current-line (line-number-at-pos start))
(current-line-beginning nil)
(offset nil))
(cond ((or (= start-line current-line)
(= start anchor))
;; hand off to the default
(cons nil nil))
;; arrange for closing delimiter with opening delimiter alignment
((= close-line current-line)
(let ((open-delim (treesit-node-start start-node)))
(save-excursion
(goto-char open-delim)
(beginning-of-line)
(setq current-line-beginning (point))
(back-to-indentation)
(setq offset (- (point) current-line-beginning)))
(cons current-line-beginning offset)))
;; look for relevant previous line and compute accordingly
(:else
(let* ((found nil)
(start-line-beginning nil))
(save-excursion
(beginning-of-line)
(setq start-line-beginning (point))
(goto-char start)
(while (and (not found)
(not (= start-line current-line)))
;; move to previous line
(forward-line -1)
;; check if line is blank
(when (not (looking-at-p "[[:blank:]]*$"))
(setq found t)
(when (not (= start-line current-line))
(setq current-line-beginning (point))
;; move to first non-whitespace character
(back-to-indentation)
;; compute indentation offset
(setq offset (- (point) current-line-beginning))))))
(if (= start-line current-line)
(cons nil nil)
(cons start-line-beginning offset)))))))
;; XXX: this approach is duplicated effort, but it's unclear how to
;; avoid that
(defun janet-ts-relative-to-previous-anchor (node parent bol)
"Anchor wrapper function.
Wraps `janet-ts--indent-ml-long-string-relative-to-previous` to get its
car, which corresponds to what an anchor function would have produced.
For info on NODE, PARENT, and BOL, see `treesit-indent-function'."
(let ((result (janet-ts--indent-ml-long-string-relative-to-previous
node parent bol)))
(when result
(car result))))
(defun janet-ts-relative-to-previous-offset (node parent bol)
"Offset function.
Wraps `janet-ts--indent-ml-long-string-relative-to-previous` to get its
cdr, which corresponds to what an offset function would have produced.
For info on NODE, PARENT, and BOL, see `treesit-indent-function'."
(let ((result (janet-ts--indent-ml-long-string-relative-to-previous
node parent bol)))
(when result
(cdr result))))
(defvar janet-ts-ml-long-string-anchor-fn
#'janet-ts-relative-to-previous-anchor
"Anchor function for multi-line long-string line indentation.
See `treesit-simple-indent-rules' for details.")
(defvar janet-ts-ml-long-string-offset-fn
#'janet-ts-relative-to-previous-offset
"Offset function for multi-line long-string line indentation.
See `treesit-simple-indent-rules' for details.")
(defun janet-ts-custom-ml-long-string-indent ()
"Hook function for specialized multi-line long-string indentation.
This function locally sets `indent-region-function' and
`indent-line-function' to functions that provide specialized
behavior for indentation such that multi-line long-strings are
handled appropriately in various contexts.
The purpose of the region-related function is to ensure that indenting
regions that contain multi-line long-strings don't adversely
modify the content of multi-line long-strings.
The purpose of the line-related function is to make it possible to have
indentation behave differently within multi-line long-strings when
line-indentation is requested for that context."
(setq-local
;; special handling for region indentation
indent-region-function
(lambda (beg end)
(if (janet-ts--in-multi-line-long-string-p)
(let ((treesit-simple-indent-rules janet-ts--indent-rules))
(treesit-indent-region beg end))
(treesit-indent-region beg end)))
;; special handling for line indentation
indent-line-function
(lambda ()
(if (janet-ts--in-multi-line-long-string-p)
(let ((treesit-simple-indent-rules
;; N.B. value is a slightly modified janet-ts--indent-rules
`((janet-simple
((parent-is "source")
parent-bol 0)
;; multi-line long-string - can be top-level or not
((parent-is "long_str_lit")
,janet-ts-ml-long-string-anchor-fn
,janet-ts-ml-long-string-offset-fn)
((or (parent-is "sqr_tup_lit") (parent-is "struct_lit"))
parent 1)
((or (parent-is "par_arr_lit") (parent-is "sqr_arr_lit")
(parent-is "tbl_lit"))
parent 2)
((parent-is "par_tup_lit")
janet-ts--anchor-for-par-tup-parent 0)))))
(treesit-indent))
(treesit-indent)))))
(provide 'janet-ts-custom-indent)
;;; janet-ts-custom-indent.el ends here