Editor indentation for Emacs and Sublime Text; container guides point to editor support (from dev 5d35f256476e)
This commit is contained in:
226
doc/edit/emacs/klammertext-indent.el
Normal file
226
doc/edit/emacs/klammertext-indent.el
Normal file
@@ -0,0 +1,226 @@
|
||||
;;; klammertext-indent.el --- TAB indentation for Klammertext -*- lexical-binding: t; -*-
|
||||
|
||||
;; EXPERIMENTAL. This file is a separate unit, loaded from the init file:
|
||||
;;
|
||||
;; (require 'klammertext-indent)
|
||||
;;
|
||||
;; Comment that line out to disable indentation entirely; klammertext-mode
|
||||
;; itself is untouched by this file.
|
||||
;;
|
||||
;; The convention (2026-07-26):
|
||||
;;
|
||||
;; @ol <- opener at its context's content column
|
||||
;; Item one <- content: opener column + 2
|
||||
;; | Item two <- bar run at the OPENER's column ("| " is two
|
||||
;; @ol characters, so item text aligns with "Item one")
|
||||
;; Embedded item one
|
||||
;; | Embedded item two
|
||||
;; @ <- close at its opener's column
|
||||
;; | Item four
|
||||
;; @
|
||||
;;
|
||||
;; Formal rule: a line indents to offset x (effective depth); a line that
|
||||
;; BEGINS with a bar run (|, ||, ...) or a closing delimiter (@ or name@)
|
||||
;; indents one level less, i.e. to its owner's opening column. The bar-run
|
||||
;; rule is dimension-independent: | (list items), || (table rows) and any
|
||||
;; longer run all drop to the opener's column. Effective depth counts every
|
||||
;; enclosing span uniformly -- applications (@), definitions (@@), and
|
||||
;; system commands (@@@) -- with these exceptions:
|
||||
;;
|
||||
;; * Klammers in `klammertext-transparent-klammers' (seeded with
|
||||
;; "document") contribute no level, so ordinary paragraphs of a document
|
||||
;; sit at the left margin.
|
||||
;; * Lines inside a literal klammer's verbatim content (@code ... code@)
|
||||
;; and inside the argument span of a klammer in
|
||||
;; `klammertext-code-klammers' (seeded with "eval" -- inline Python is
|
||||
;; indentation-sensitive!) are NEVER touched: TAB returns `noindent'.
|
||||
;; Removed regions (#[ ... ]#, everything after ##) are likewise left
|
||||
;; alone.
|
||||
;;
|
||||
;; Indentation happens only on explicit TAB / indent-region; nothing
|
||||
;; reformats automatically, because whitespace is content in Klammertext.
|
||||
;; The convention is nevertheless semantically free where it applies: list
|
||||
;; and table cell content is stripped by the SKS, leading whitespace
|
||||
;; collapses in the html/tex targets, and a blank line that acquires
|
||||
;; indentation spaces still separates paragraphs (the SKS paragraph
|
||||
;; separator is \n *\n ).
|
||||
;;
|
||||
;; Known limitation: a raw @ inside a ^'...'^ literal region would confuse
|
||||
;; the depth scan (the same limitation as the font-lock scanner).
|
||||
;;
|
||||
;; SYNC: the Sublime Text port doc/sublime/Klammertext_indent.py duplicates
|
||||
;; this file's policy (a Sublime plugin cannot read these defcustoms). When
|
||||
;; you change `klammertext-indent-offset', `klammertext-transparent-klammers'
|
||||
;; or `klammertext-code-klammers', mirror the change in that file's
|
||||
;; INDENT_OFFSET / TRANSPARENT_KLAMMERS / CODE_KLAMMERS.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'klammertext-mode)
|
||||
|
||||
(defgroup klammertext-indent nil
|
||||
"Indentation for Klammertext files."
|
||||
:group 'klammertext)
|
||||
|
||||
(defcustom klammertext-indent-offset 2
|
||||
"Number of columns per klammer nesting level."
|
||||
:type 'integer
|
||||
:group 'klammertext-indent)
|
||||
|
||||
(defcustom klammertext-transparent-klammers '("document")
|
||||
"Klammers whose span contributes no indentation level.
|
||||
The @document klammer is transparent so that the ordinary paragraphs of a
|
||||
document sit at the left margin; a future top-level peer (e.g. @jupyter)
|
||||
would be registered here too."
|
||||
:type '(repeat string)
|
||||
:group 'klammertext-indent)
|
||||
|
||||
(defcustom klammertext-code-klammers '("eval")
|
||||
"Klammers whose argument span holds code, never reindented.
|
||||
Lines inside such a span answer TAB with `noindent'. @eval is seeded
|
||||
because its content is Python, C++ or shell source -- Python in particular
|
||||
is indentation-sensitive. This mirrors the Klammermachine's own rule that
|
||||
@eval argument spans hold code, not writer text."
|
||||
:type '(repeat string)
|
||||
:group 'klammertext-indent)
|
||||
|
||||
;; --- Depth scanner ------------------------------------------------------
|
||||
|
||||
(defun klammertext-indent--state-at (pos)
|
||||
"Scan from `point-min' to POS (a line beginning).
|
||||
Return (STACK . OPAQUE): STACK is the list of names of the klammer
|
||||
applications, @@ definitions and @@@ commands open at POS, innermost
|
||||
first; OPAQUE is
|
||||
non-nil when POS lies inside content that indentation must not touch
|
||||
\(removed text, a literal klammer's verbatim span, or a code klammer's
|
||||
argument span). Reuses the mode's classification helpers, so escaped ^@ /
|
||||
^#, the abbreviated @name-arg form, and #+ #/ #- are all stepped over the
|
||||
same way the font-lock scanner steps over them."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(let ((stack nil) (opaque nil) (done nil))
|
||||
(while (and (not done)
|
||||
(re-search-forward "[@#]" pos t))
|
||||
(let ((hit (1- (point))))
|
||||
(cond
|
||||
((klammertext--escaped-p hit)) ; ^@ / ^# : plain text
|
||||
((eq (char-after hit) ?#)
|
||||
(let ((next (char-after (1+ hit))))
|
||||
(cond
|
||||
((eq next ?#) ; ## removes to end of buffer
|
||||
(setq opaque t done t))
|
||||
((eq next ?\[) ; #[ ... ]# (nestable)
|
||||
(let ((end (klammertext--block-end (+ hit 2))))
|
||||
(if (< pos end)
|
||||
(setq opaque t done t)
|
||||
(goto-char end))))
|
||||
((memq next '(?+ ?/ ?-))) ; whitespace operators
|
||||
(t (goto-char (line-end-position)))))) ; # to end of line
|
||||
(t ; an @-run
|
||||
(let* ((run-end (klammertext--at-run-end hit))
|
||||
(len (- run-end hit)))
|
||||
(cond
|
||||
;; @name / @@name / @@@name : an opener (or, for a literal
|
||||
;; klammer, a verbatim span to step over).
|
||||
((klammertext--name-char-p (char-after run-end))
|
||||
(goto-char run-end)
|
||||
(skip-chars-forward "A-Za-z0-9_")
|
||||
(let ((name (buffer-substring-no-properties run-end (point))))
|
||||
(cond
|
||||
((and (= len 1)
|
||||
(member name klammertext-literal-klammers))
|
||||
;; Verbatim interior: find the closing NAME@ by name.
|
||||
(if (re-search-forward
|
||||
(concat (regexp-quote name) "@") nil t)
|
||||
(when (< pos (point))
|
||||
(setq opaque t done t))
|
||||
(setq opaque t done t))) ; never closed
|
||||
((and (= len 1) (eq (char-after) ?-))) ; @name-arg : no span
|
||||
(t (push name stack)))))
|
||||
;; a bare @-run, or the run of a named close: a close.
|
||||
(t
|
||||
(pop stack)
|
||||
(goto-char run-end))))))))
|
||||
;; Inside the argument span of a code klammer (e.g. a multi-line @eval)?
|
||||
(unless opaque
|
||||
(let ((s stack))
|
||||
(while s
|
||||
(when (member (car s) klammertext-code-klammers)
|
||||
(setq opaque t s nil))
|
||||
(setq s (cdr s)))))
|
||||
(cons stack opaque))))
|
||||
|
||||
(defun klammertext-indent--depth (stack)
|
||||
"Number of indentation levels STACK contributes.
|
||||
Transparent klammers contribute none."
|
||||
(let ((d 0))
|
||||
(dolist (name stack d)
|
||||
(unless (member name klammertext-transparent-klammers)
|
||||
(setq d (1+ d))))))
|
||||
|
||||
;; --- Line classification ------------------------------------------------
|
||||
|
||||
(defun klammertext-indent--dedent-line-p ()
|
||||
"Non-nil when the current line begins with a token that sits at its
|
||||
owner's opening column: a bar run (|, ||, ...), a bare close run (@, @@,
|
||||
@@@), or a named close (name@, name@@, name@@@). A line beginning with an
|
||||
opener (@name, @@name, @@@name) is content-level."
|
||||
(save-excursion
|
||||
(back-to-indentation)
|
||||
(let ((c (char-after)))
|
||||
(cond
|
||||
((null c) nil)
|
||||
((eq c ?|) t)
|
||||
((eq c ?@)
|
||||
(not (klammertext--name-char-p
|
||||
(char-after (klammertext--at-run-end (point))))))
|
||||
((klammertext--name-char-p c)
|
||||
;; A named close: name chars followed by an @-run (an unescaped @
|
||||
;; can only be a delimiter).
|
||||
(skip-chars-forward "A-Za-z0-9_")
|
||||
(eq (char-after) ?@))
|
||||
(t nil)))))
|
||||
|
||||
;; --- The indent function ------------------------------------------------
|
||||
|
||||
(defun klammertext-indent--target-column ()
|
||||
"Column for the current line, or the symbol `noindent'."
|
||||
(let* ((state (klammertext-indent--state-at (line-beginning-position)))
|
||||
(stack (car state)))
|
||||
(if (cdr state)
|
||||
'noindent
|
||||
(* klammertext-indent-offset
|
||||
(klammertext-indent--depth
|
||||
(if (klammertext-indent--dedent-line-p) (cdr stack) stack))))))
|
||||
|
||||
(defun klammertext-indent-line ()
|
||||
"Indent the current line per the Klammertext convention.
|
||||
Content indents to `klammertext-indent-offset' x depth; a line beginning
|
||||
with a bar run or a closing delimiter aligns with its owner's opening
|
||||
column. Lines inside verbatim, code, or removed content are left alone."
|
||||
(interactive)
|
||||
(let ((target (klammertext-indent--target-column)))
|
||||
(if (eq target 'noindent)
|
||||
'noindent
|
||||
(if (> (current-column) (current-indentation))
|
||||
(save-excursion (indent-line-to target))
|
||||
(indent-line-to target)))))
|
||||
|
||||
;; --- Wiring -------------------------------------------------------------
|
||||
|
||||
(defun klammertext-indent-setup ()
|
||||
"Enable Klammertext indentation in the current buffer."
|
||||
(setq-local indent-line-function #'klammertext-indent-line)
|
||||
;; Klammer indentation columns are small and semantic; never use tabs.
|
||||
(setq-local indent-tabs-mode nil))
|
||||
|
||||
(add-hook 'klammertext-mode-hook #'klammertext-indent-setup)
|
||||
|
||||
;; Also enable in klammertext-mode buffers already open when this loads.
|
||||
(dolist (buf (buffer-list))
|
||||
(with-current-buffer buf
|
||||
(when (derived-mode-p 'klammertext-mode)
|
||||
(klammertext-indent-setup))))
|
||||
|
||||
(provide 'klammertext-indent)
|
||||
;;; klammertext-indent.el ends here
|
||||
Reference in New Issue
Block a user