Editor indentation for Emacs and Sublime Text; container guides point to editor support (from dev 5d35f256476e)
This commit is contained in:
@@ -39,7 +39,7 @@ are regenerated on each release — patches cannot be merged directly.
|
|||||||
Report problems (or send patches) to the author; accepted changes are
|
Report problems (or send patches) to the author; accepted changes are
|
||||||
applied to the development tree and appear in a following snapshot.
|
applied to the development tree and appear in a following snapshot.
|
||||||
|
|
||||||
This snapshot was assembled from development commit `c08eb1bbfa4c`.
|
This snapshot was assembled from development commit `5d35f256476e`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ and load the mode. Add to `~/.emacs.d/init.el`:
|
|||||||
```elisp
|
```elisp
|
||||||
(add-to-list 'load-path "full-pathname-of-the-emacs-directory")
|
(add-to-list 'load-path "full-pathname-of-the-emacs-directory")
|
||||||
(require 'klammertext-mode)
|
(require 'klammertext-mode)
|
||||||
|
(require 'klammertext-indent) ; optional, experimental: TAB indentation
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `full-pathname-of-the-emacs-directory` with the full path to the
|
Replace `full-pathname-of-the-emacs-directory` with the full path to the
|
||||||
directory that contains `klammertext-mode.el`.
|
directory that contains `klammertext-mode.el`. The second require loads the
|
||||||
|
experimental indentation support (see "Indentation" below); it is a separate
|
||||||
|
unit — comment the line out to disable indentation entirely.
|
||||||
|
|
||||||
The mode auto-activates for `.kt` and `.k` files. (The `.k` / `.kt` distinction
|
The mode auto-activates for `.kt` and `.k` files. (The `.k` / `.kt` distinction
|
||||||
is a filing convention, not a lexical one — the same mode serves both.) You can
|
is a filing convention, not a lexical one — the same mode serves both.) You can
|
||||||
@@ -153,6 +156,46 @@ opening `@name`. It uses the same matcher as the highlighting. The starting
|
|||||||
position is pushed to the mark ring, so `C-u C-SPC` jumps back. (Also available
|
position is pushed to the mark ring, so `C-u C-SPC` jumps back. (Also available
|
||||||
as `M-x klammertext-jump-to-match`.)
|
as `M-x klammertext-jump-to-match`.)
|
||||||
|
|
||||||
|
## Indentation (experimental)
|
||||||
|
|
||||||
|
With `klammertext-indent.el` loaded (the optional require above), **TAB**
|
||||||
|
indents the current line — and `indent-region` a selection — to reflect the
|
||||||
|
klammer nesting, two spaces per level:
|
||||||
|
|
||||||
|
```
|
||||||
|
@ol
|
||||||
|
Item one
|
||||||
|
| Item two
|
||||||
|
@ol
|
||||||
|
Embedded item one
|
||||||
|
| Embedded item two
|
||||||
|
@
|
||||||
|
| Item three
|
||||||
|
@
|
||||||
|
```
|
||||||
|
|
||||||
|
The rule: a line indents to 2 × depth; a line *beginning* with a bar run
|
||||||
|
(`|`, `||`, …) or a closing delimiter sits one level less — at its owner's
|
||||||
|
opening column. So the bars and the close line up under the `@` of the list
|
||||||
|
they belong to, and `| ` (bar + space) puts item text exactly at the content
|
||||||
|
column. The bar rule is dimension-independent: `||` table rows drop to the
|
||||||
|
opener's column the same way. All three `@`-tiers indent uniformly.
|
||||||
|
|
||||||
|
Exceptions, all deliberate:
|
||||||
|
|
||||||
|
- Klammers in `klammertext-transparent-klammers` (default: `document`)
|
||||||
|
contribute no level, so a document's ordinary paragraphs stay 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`
|
||||||
|
(default: `eval` — inline Python is indentation-sensitive), are never
|
||||||
|
touched. Neither are removed regions (`#[ ... ]#`, after `##`).
|
||||||
|
|
||||||
|
Nothing reformats automatically — whitespace is content in Klammertext, so
|
||||||
|
indentation happens only when you ask for it (TAB, `indent-region`). The
|
||||||
|
offset is `klammertext-indent-offset` (default 2); all three variables are
|
||||||
|
customizable in the `klammertext-indent` group.
|
||||||
|
|
||||||
## Literal klammers
|
## Literal klammers
|
||||||
|
|
||||||
Inside a `literal` argument — for example the body of `@code ... code@` — `#`
|
Inside a `literal` argument — for example the body of `@code ... code@` — `#`
|
||||||
|
|||||||
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
|
||||||
@@ -123,11 +123,12 @@ Register one with `klammertext-add-literal-klammer', e.g. in your init file:
|
|||||||
|
|
||||||
;; SYNC: the Sublime Text port in doc/sublime/ duplicates this list statically
|
;; SYNC: the Sublime Text port in doc/sublime/ duplicates this list statically
|
||||||
;; (a Sublime syntax/plugin cannot read this Emacs defcustom). When you add or
|
;; (a Sublime syntax/plugin cannot read this Emacs defcustom). When you add or
|
||||||
;; remove a literal klammer, mirror it in BOTH:
|
;; remove a literal klammer, mirror it in ALL of:
|
||||||
;; * LITERAL_KLAMMERS in doc/sublime/Klammertext.py
|
;; * LITERAL_KLAMMERS in doc/sublime/Klammertext.py
|
||||||
|
;; * LITERAL_KLAMMERS in doc/sublime/Klammertext_indent.py
|
||||||
;; * the @NAME literal rule + literal_NAME context in
|
;; * the @NAME literal rule + literal_NAME context in
|
||||||
;; doc/sublime/Klammertext.sublime-syntax
|
;; doc/sublime/Klammertext.sublime-syntax
|
||||||
;; All three are currently seeded with just "code".
|
;; All four are currently seeded with just "code".
|
||||||
|
|
||||||
(defun klammertext-add-literal-klammer (name)
|
(defun klammertext-add-literal-klammer (name)
|
||||||
"Register NAME as a klammer whose literal content must not be interpreted.
|
"Register NAME as a klammer whose literal content must not be interpreted.
|
||||||
|
|||||||
@@ -4,8 +4,13 @@
|
|||||||
// command) to Ctrl+M — Sublime's own "go to matching bracket" key, repurposed
|
// command) to Ctrl+M — Sublime's own "go to matching bracket" key, repurposed
|
||||||
// for klammers, since the built-in cannot match context-dependent @ pairs.
|
// for klammers, since the built-in cannot match context-dependent @ pairs.
|
||||||
//
|
//
|
||||||
// The "selector" context confines the binding to Klammertext files, so Ctrl+M
|
// Binds "reindent lines" (the companion Klammertext_indent.py command) to
|
||||||
// keeps its normal meaning everywhere else.
|
// Ctrl+Alt+I. Sublime's own Reindent (Edit > Line > Reindent, unbound by
|
||||||
|
// default) is driven by single-line regex patterns that cannot express
|
||||||
|
// Klammertext nesting, so reindentation is a plugin command here too.
|
||||||
|
//
|
||||||
|
// The "selector" contexts confine the bindings to Klammertext files, so both
|
||||||
|
// keys keep their normal meaning everywhere else.
|
||||||
//
|
//
|
||||||
// macOS users may prefer "super+m"; change the "keys" value below. This file
|
// macOS users may prefer "super+m"; change the "keys" value below. This file
|
||||||
// (no platform suffix) is loaded on all platforms.
|
// (no platform suffix) is loaded on all platforms.
|
||||||
@@ -16,5 +21,12 @@
|
|||||||
"context": [
|
"context": [
|
||||||
{ "key": "selector", "operator": "equal", "operand": "text.klammertext" }
|
{ "key": "selector", "operator": "equal", "operand": "text.klammertext" }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": ["ctrl+alt+i"],
|
||||||
|
"command": "klammertext_reindent",
|
||||||
|
"context": [
|
||||||
|
{ "key": "selector", "operator": "equal", "operand": "text.klammertext" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -52,13 +52,15 @@ import sublime_plugin
|
|||||||
|
|
||||||
# Klammer names whose content is a literal argument (verbatim interior).
|
# Klammer names whose content is a literal argument (verbatim interior).
|
||||||
#
|
#
|
||||||
# SYNC: this list is one of three copies that must agree. When you add or
|
# SYNC: this list is one of four copies that must agree. When you add or
|
||||||
# remove a literal klammer, mirror it in all three:
|
# remove a literal klammer, mirror it in all four:
|
||||||
# * klammertext-literal-klammers in doc/emacs/klammertext-mode.el (the source
|
# * klammertext-literal-klammers in doc/emacs/klammertext-mode.el (the source
|
||||||
# of truth; a Sublime syntax/plugin cannot read that Emacs defcustom)
|
# of truth; a Sublime syntax/plugin cannot read that Emacs defcustom)
|
||||||
# * LITERAL_KLAMMERS here
|
# * LITERAL_KLAMMERS here
|
||||||
|
# * LITERAL_KLAMMERS in Klammertext_indent.py (a deletable unit, so it does
|
||||||
|
# not import from this file)
|
||||||
# * the @NAME literal rule + literal_NAME context in Klammertext.sublime-syntax
|
# * the @NAME literal rule + literal_NAME context in Klammertext.sublime-syntax
|
||||||
# All three are currently seeded with just "code".
|
# All four are currently seeded with just "code".
|
||||||
LITERAL_KLAMMERS = set(["code"])
|
LITERAL_KLAMMERS = set(["code"])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ Emacs mode closely; where the two intentionally differ, the file headers say so.
|
|||||||
|------|---------|
|
|------|---------|
|
||||||
| `Klammertext.sublime-syntax` | Syntax highlighting. Colors the text-removal constructs (`#`, `##`, `#[...]#`) and the three `@`-tiers — application `@`, definition `@@`, system `@@@` — each as an opening vs. a close, plus `^`-escapes and verbatim `@code ... code@` spans. |
|
| `Klammertext.sublime-syntax` | Syntax highlighting. Colors the text-removal constructs (`#`, `##`, `#[...]#`) and the three `@`-tiers — application `@`, definition `@@`, system `@@@` — each as an opening vs. a close, plus `^`-escapes and verbatim `@code ... code@` spans. |
|
||||||
| `Klammertext.py` | Plugin with two features that share one context-sensitive matcher: jump between an opening and its close, and live highlighting of the matching delimiter as the caret moves (mismatched or unbalanced delimiters flag in red). |
|
| `Klammertext.py` | Plugin with two features that share one context-sensitive matcher: jump between an opening and its close, and live highlighting of the matching delimiter as the caret moves (mismatched or unbalanced delimiters flag in red). |
|
||||||
| `Default.sublime-keymap` | Binds jump-to-match to **Ctrl+M**, scoped to Klammertext files. |
|
| `Klammertext_indent.py` | **Experimental.** Reindentation per the Klammertext convention (see below). A separate unit: delete this one file to disable indentation; nothing else is affected. |
|
||||||
|
| `Default.sublime-keymap` | Binds jump-to-match to **Ctrl+M** and reindent to **Ctrl+Alt+I**, scoped to Klammertext files. |
|
||||||
| `Comments.tmPreferences` | Comment toggling: **Ctrl+/** inserts `# ` (line removal), **Ctrl+Shift+/** wraps in `#[ ... ]#` (block removal). |
|
| `Comments.tmPreferences` | Comment toggling: **Ctrl+/** inserts `# ` (line removal), **Ctrl+Shift+/** wraps in `#[ ... ]#` (block removal). |
|
||||||
| `Breakers` / `Celeste` / `Mariana` / `Monokai` / `Sixteen` `.sublime-color-scheme` | Color overrides for Sublime's five built-in schemes — one hue system, full intensity on the dark schemes, scaled down on the light ones. Additive: they recolor only the Klammertext delimiters and leave the rest of each scheme unchanged. |
|
| `Breakers` / `Celeste` / `Mariana` / `Monokai` / `Sixteen` `.sublime-color-scheme` | Color overrides for Sublime's five built-in schemes — one hue system, full intensity on the dark schemes, scaled down on the light ones. Additive: they recolor only the Klammertext delimiters and leave the rest of each scheme unchanged. |
|
||||||
| `Klammertext_in_Sublime_Text.md` | This file. |
|
| `Klammertext_in_Sublime_Text.md` | This file. |
|
||||||
@@ -48,11 +49,48 @@ adaptive `region.*` scopes, which were added in ST4.
|
|||||||
| caret on a klammer `@` | The matching delimiter boxes automatically; a name mismatch or unbalanced delimiter boxes in red with a status-bar message (equivalent of `show-paren-mode`) |
|
| caret on a klammer `@` | The matching delimiter boxes automatically; a name mismatch or unbalanced delimiter boxes in red with a status-bar message (equivalent of `show-paren-mode`) |
|
||||||
| **Ctrl+/** | Toggle line comment (`#`) |
|
| **Ctrl+/** | Toggle line comment (`#`) |
|
||||||
| **Ctrl+Shift+/** | Toggle block comment (`#[ ... ]#`) |
|
| **Ctrl+Shift+/** | Toggle block comment (`#[ ... ]#`) |
|
||||||
|
| **Ctrl+Alt+I** | Reindent the selected lines (the current line when there is just a caret) — experimental, see "Indentation" below |
|
||||||
|
|
||||||
Ctrl+M is Sublime's own "go to matching bracket" key, reused here because the
|
Ctrl+M is Sublime's own "go to matching bracket" key, reused here because the
|
||||||
built-in cannot match Klammertext's context-dependent `@`. macOS users who
|
built-in cannot match Klammertext's context-dependent `@`. macOS users who
|
||||||
prefer `super+m` can change it in `Default.sublime-keymap`.
|
prefer `super+m` can change it in `Default.sublime-keymap`.
|
||||||
|
|
||||||
|
## Indentation (experimental)
|
||||||
|
|
||||||
|
`Klammertext_indent.py` ports the Emacs mode's indentation
|
||||||
|
(`doc/emacs/klammertext-indent.el`): **Ctrl+Alt+I** reindents the line(s)
|
||||||
|
touched by the selection to reflect the klammer nesting, two spaces per level:
|
||||||
|
|
||||||
|
```
|
||||||
|
@ol
|
||||||
|
Item one
|
||||||
|
| Item two
|
||||||
|
@ol
|
||||||
|
Embedded item one
|
||||||
|
| Embedded item two
|
||||||
|
@
|
||||||
|
| Item three
|
||||||
|
@
|
||||||
|
```
|
||||||
|
|
||||||
|
The rule: a line indents to 2 × depth; a line *beginning* with a bar run
|
||||||
|
(`|`, `||`, …) or a closing delimiter sits one level less — at its owner's
|
||||||
|
opening column, so bars and closes line up under the `@` of the klammer they
|
||||||
|
belong to. All three `@`-tiers indent uniformly. Exceptions: `@document`
|
||||||
|
contributes no level (a document's paragraphs stay at the left margin); lines
|
||||||
|
inside verbatim `@code` content, inside `@eval` argument spans (inline Python
|
||||||
|
is indentation-sensitive), and inside removed regions are never touched. The
|
||||||
|
policy lists (`TRANSPARENT_KLAMMERS`, `CODE_KLAMMERS`, `INDENT_OFFSET`) are at
|
||||||
|
the top of `Klammertext_indent.py`, kept in sync with the Emacs defcustoms.
|
||||||
|
|
||||||
|
Sublime's own Reindent (Edit → Line → Reindent) is driven by single-line
|
||||||
|
regex patterns that cannot express Klammertext nesting, so this is a plugin
|
||||||
|
command instead. Nothing reformats automatically (no on-Enter auto-indent):
|
||||||
|
whitespace is content in Klammertext, so indentation happens only when you
|
||||||
|
ask for it. To disable the feature, delete `Klammertext_indent.py` and the
|
||||||
|
Ctrl+Alt+I entry in `Default.sublime-keymap` (or just the keymap entry, to
|
||||||
|
keep the command available from plugins).
|
||||||
|
|
||||||
## Colors
|
## Colors
|
||||||
|
|
||||||
Colors are installed automatically for all five of Sublime's built-in schemes.
|
Colors are installed automatically for all five of Sublime's built-in schemes.
|
||||||
@@ -75,16 +113,17 @@ exact values are in each file's header comment.
|
|||||||
|
|
||||||
## Keeping literal klammers in sync
|
## Keeping literal klammers in sync
|
||||||
|
|
||||||
Klammers whose content is verbatim (`@code ... code@`) are listed in three
|
Klammers whose content is verbatim (`@code ... code@`) are listed in four
|
||||||
places that must agree — a Sublime syntax/plugin cannot read the Emacs
|
places that must agree — a Sublime syntax/plugin cannot read the Emacs
|
||||||
defcustom, so the list is duplicated:
|
defcustom, so the list is duplicated:
|
||||||
|
|
||||||
- `klammertext-literal-klammers` in `doc/emacs/klammertext-mode.el` (the source of truth)
|
- `klammertext-literal-klammers` in `doc/emacs/klammertext-mode.el` (the source of truth)
|
||||||
- `LITERAL_KLAMMERS` in `Klammertext.py`
|
- `LITERAL_KLAMMERS` in `Klammertext.py`
|
||||||
|
- `LITERAL_KLAMMERS` in `Klammertext_indent.py`
|
||||||
- the `@code` rule and `literal_code` context in `Klammertext.sublime-syntax`
|
- the `@code` rule and `literal_code` context in `Klammertext.sublime-syntax`
|
||||||
|
|
||||||
All three are seeded with just `code`. When you add or remove a literal
|
All four are seeded with just `code`. When you add or remove a literal
|
||||||
klammer, change all three.
|
klammer, change all four.
|
||||||
|
|
||||||
## Not included
|
## Not included
|
||||||
|
|
||||||
|
|||||||
276
doc/edit/sublime/Klammertext_indent.py
Normal file
276
doc/edit/sublime/Klammertext_indent.py
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
# Klammertext_indent.py
|
||||||
|
#
|
||||||
|
# EXPERIMENTAL. Reindentation for Klammertext files — the Sublime Text port
|
||||||
|
# of doc/emacs/klammertext-indent.el. This file is a separate unit: delete it
|
||||||
|
# (or move it out of the package folder) to disable indentation entirely; the
|
||||||
|
# rest of the Klammertext package is unaffected.
|
||||||
|
#
|
||||||
|
# Command name (for keymaps / the command palette): klammertext_reindent
|
||||||
|
# Keybinding: Ctrl+Alt+I (in Default.sublime-keymap), scoped to Klammertext
|
||||||
|
# files. It reindents the line(s) touched by the selection — the current
|
||||||
|
# line when there is just a caret. Nothing reformats automatically (no
|
||||||
|
# on-Enter auto-indent), because whitespace is content in Klammertext.
|
||||||
|
#
|
||||||
|
# The convention (2026-07-27):
|
||||||
|
#
|
||||||
|
# @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 (a bare @-run or
|
||||||
|
# a named close) 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 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 CODE_KLAMMERS (seeded with
|
||||||
|
# "eval" -- inline Python is indentation-sensitive!) are NEVER touched.
|
||||||
|
# Removed regions (#[ ... ]#, everything after ##) are likewise left
|
||||||
|
# alone.
|
||||||
|
#
|
||||||
|
# Known limitation (shared with the Emacs scanner): a raw @ inside a ^'...'^
|
||||||
|
# literal region would confuse the depth scan.
|
||||||
|
#
|
||||||
|
# SYNC: the policy lists below must agree with the Emacs side:
|
||||||
|
# * LITERAL_KLAMMERS with klammertext-literal-klammers (also duplicated in
|
||||||
|
# Klammertext.py and Klammertext.sublime-syntax; all seeded "code")
|
||||||
|
# * TRANSPARENT_KLAMMERS with klammertext-transparent-klammers ("document")
|
||||||
|
# * CODE_KLAMMERS with klammertext-code-klammers ("eval")
|
||||||
|
# * INDENT_OFFSET with klammertext-indent-offset (2)
|
||||||
|
# The scanning helpers (name_char_p, escaped_p, block_end, at_run_end) are
|
||||||
|
# duplicated from Klammertext.py so this file stays a deletable unit with no
|
||||||
|
# import coupling.
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sublime
|
||||||
|
import sublime_plugin
|
||||||
|
_IN_SUBLIME = True
|
||||||
|
except ImportError: # standalone testing outside Sublime Text
|
||||||
|
_IN_SUBLIME = False
|
||||||
|
|
||||||
|
INDENT_OFFSET = 2
|
||||||
|
LITERAL_KLAMMERS = set(["code"])
|
||||||
|
TRANSPARENT_KLAMMERS = set(["document"])
|
||||||
|
CODE_KLAMMERS = set(["eval"])
|
||||||
|
|
||||||
|
|
||||||
|
# --- pure helpers (duplicated from Klammertext.py; see SYNC note above) -----
|
||||||
|
|
||||||
|
def name_char_p(ch):
|
||||||
|
"""True if CH can be part of a klammer name (letter, digit or _)."""
|
||||||
|
if ch is None:
|
||||||
|
return False
|
||||||
|
return (('a' <= ch <= 'z') or ('A' <= ch <= 'Z')
|
||||||
|
or ('0' <= ch <= '9') or ch == '_')
|
||||||
|
|
||||||
|
|
||||||
|
def escaped_p(s, pos):
|
||||||
|
"""True if the char at POS is escaped by an odd run of ^ before it."""
|
||||||
|
n = 0
|
||||||
|
i = pos - 1
|
||||||
|
while i >= 0 and s[i] == '^':
|
||||||
|
n += 1
|
||||||
|
i -= 1
|
||||||
|
return (n % 2) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def block_end(s, frm):
|
||||||
|
"""Index just after the ]# that closes a #[ block opened at FROM (the index
|
||||||
|
just after the opening #[). Counts nested #[ ... ]#; len(s) if unclosed."""
|
||||||
|
depth = 1
|
||||||
|
i = frm
|
||||||
|
n = len(s)
|
||||||
|
while depth > 0:
|
||||||
|
a = s.find('#[', i)
|
||||||
|
b = s.find(']#', i)
|
||||||
|
if a == -1 and b == -1:
|
||||||
|
return n
|
||||||
|
if b == -1 or (a != -1 and a < b):
|
||||||
|
depth += 1
|
||||||
|
i = a + 2
|
||||||
|
else:
|
||||||
|
depth -= 1
|
||||||
|
i = b + 2
|
||||||
|
return i
|
||||||
|
|
||||||
|
|
||||||
|
def at_run_end(s, pos):
|
||||||
|
"""Index just after the run of @ that begins at POS."""
|
||||||
|
p = pos
|
||||||
|
n = len(s)
|
||||||
|
while p < n and s[p] == '@':
|
||||||
|
p += 1
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
# --- the depth scanner (port of klammertext-indent--state-at) ---------------
|
||||||
|
|
||||||
|
def state_at(s, pos):
|
||||||
|
"""Scan s[0:POS] (POS a line beginning). Return (stack, opaque): STACK is
|
||||||
|
the list of names of the klammer applications, @@ definitions and @@@
|
||||||
|
commands open at POS, outermost first; OPAQUE is True 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)."""
|
||||||
|
stack = []
|
||||||
|
n = len(s)
|
||||||
|
i = 0
|
||||||
|
while i < pos:
|
||||||
|
j = i
|
||||||
|
while j < pos and s[j] != '@' and s[j] != '#':
|
||||||
|
j += 1
|
||||||
|
if j >= pos:
|
||||||
|
break
|
||||||
|
hit = j
|
||||||
|
i = hit + 1
|
||||||
|
if escaped_p(s, hit): # ^@ / ^# : plain text
|
||||||
|
continue
|
||||||
|
nxt = s[hit + 1] if hit + 1 < n else None
|
||||||
|
if s[hit] == '#':
|
||||||
|
if nxt == '#': # ## removes to end of buffer
|
||||||
|
return (stack, True)
|
||||||
|
elif nxt == '[': # #[ ... ]# (nestable)
|
||||||
|
end = block_end(s, hit + 2)
|
||||||
|
if pos < end:
|
||||||
|
return (stack, True)
|
||||||
|
i = end
|
||||||
|
elif nxt in ('+', '/', '-'): # whitespace operators
|
||||||
|
pass
|
||||||
|
else: # # to end of line
|
||||||
|
eol = s.find('\n', hit)
|
||||||
|
i = n if eol == -1 else eol
|
||||||
|
continue
|
||||||
|
# an @-run
|
||||||
|
run_end = at_run_end(s, hit)
|
||||||
|
run_len = run_end - hit
|
||||||
|
after = s[run_end] if run_end < n else None
|
||||||
|
if name_char_p(after):
|
||||||
|
# @name / @@name / @@@name : an opener (or, for a literal
|
||||||
|
# klammer, a verbatim span to step over).
|
||||||
|
k = run_end
|
||||||
|
while k < n and name_char_p(s[k]):
|
||||||
|
k += 1
|
||||||
|
name = s[run_end:k]
|
||||||
|
i = k
|
||||||
|
if run_len == 1 and name in LITERAL_KLAMMERS:
|
||||||
|
# Verbatim interior: find the closing NAME@ by name.
|
||||||
|
idx = s.find(name + '@', k)
|
||||||
|
if idx == -1: # never closed
|
||||||
|
return (stack, True)
|
||||||
|
close_end = idx + len(name) + 1
|
||||||
|
if pos < close_end:
|
||||||
|
return (stack, True)
|
||||||
|
i = close_end
|
||||||
|
elif run_len == 1 and k < n and s[k] == '-':
|
||||||
|
pass # @name-arg : opens no span
|
||||||
|
else:
|
||||||
|
stack.append(name)
|
||||||
|
else:
|
||||||
|
# a bare @-run, or the run of a named close: a close.
|
||||||
|
if stack:
|
||||||
|
stack.pop()
|
||||||
|
i = run_end
|
||||||
|
opaque = any(name in CODE_KLAMMERS for name in stack)
|
||||||
|
return (stack, opaque)
|
||||||
|
|
||||||
|
|
||||||
|
def depth(stack):
|
||||||
|
"""Number of indentation levels STACK contributes.
|
||||||
|
Transparent klammers contribute none."""
|
||||||
|
return sum(1 for name in stack if name not in TRANSPARENT_KLAMMERS)
|
||||||
|
|
||||||
|
|
||||||
|
def dedent_line_p(s, bol):
|
||||||
|
"""True when the line starting at BOL 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."""
|
||||||
|
n = len(s)
|
||||||
|
i = bol
|
||||||
|
while i < n and s[i] in ' \t':
|
||||||
|
i += 1
|
||||||
|
if i >= n:
|
||||||
|
return False
|
||||||
|
c = s[i]
|
||||||
|
if c == '|':
|
||||||
|
return True
|
||||||
|
if c == '@':
|
||||||
|
run_end = at_run_end(s, i)
|
||||||
|
return not name_char_p(s[run_end] if run_end < n else None)
|
||||||
|
if name_char_p(c):
|
||||||
|
# A named close: name chars followed by an @-run (an unescaped @ can
|
||||||
|
# only be a delimiter).
|
||||||
|
k = i
|
||||||
|
while k < n and name_char_p(s[k]):
|
||||||
|
k += 1
|
||||||
|
return k < n and s[k] == '@'
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def target_column(s, bol):
|
||||||
|
"""Column for the line starting at BOL, or None for lines that must not
|
||||||
|
be touched (verbatim, code, or removed content)."""
|
||||||
|
stack, opaque = state_at(s, bol)
|
||||||
|
if opaque:
|
||||||
|
return None
|
||||||
|
if dedent_line_p(s, bol) and stack:
|
||||||
|
stack = stack[:-1]
|
||||||
|
return INDENT_OFFSET * depth(stack)
|
||||||
|
|
||||||
|
|
||||||
|
def reindent_lines(s, bols):
|
||||||
|
"""Compute the edits that reindent the lines whose beginnings are BOLS.
|
||||||
|
Return a list of (start, end, replacement) triples over S, in ascending
|
||||||
|
order, replacing each line's leading whitespace; untouchable lines and
|
||||||
|
already-correct lines produce no edit. Pure function -- also used by the
|
||||||
|
standalone tests."""
|
||||||
|
n = len(s)
|
||||||
|
edits = []
|
||||||
|
for bol in bols:
|
||||||
|
tgt = target_column(s, bol)
|
||||||
|
if tgt is None:
|
||||||
|
continue
|
||||||
|
i = bol
|
||||||
|
while i < n and s[i] in ' \t':
|
||||||
|
i += 1
|
||||||
|
if s[bol:i] != ' ' * tgt:
|
||||||
|
edits.append((bol, i, ' ' * tgt))
|
||||||
|
return edits
|
||||||
|
|
||||||
|
|
||||||
|
# --- the command ------------------------------------------------------------
|
||||||
|
|
||||||
|
if _IN_SUBLIME:
|
||||||
|
|
||||||
|
class KlammertextReindentCommand(sublime_plugin.TextCommand):
|
||||||
|
"""Reindent the line(s) touched by the selection per the Klammertext
|
||||||
|
convention. Sublime equivalent of TAB in the Emacs mode (which has no
|
||||||
|
Sublime analogue: TAB there always inserts). Bound to Ctrl+Alt+I."""
|
||||||
|
|
||||||
|
def run(self, edit):
|
||||||
|
view = self.view
|
||||||
|
s = view.substr(sublime.Region(0, view.size()))
|
||||||
|
bols = []
|
||||||
|
seen = set()
|
||||||
|
for region in view.sel():
|
||||||
|
for line in view.lines(region):
|
||||||
|
if line.a not in seen:
|
||||||
|
seen.add(line.a)
|
||||||
|
bols.append(line.a)
|
||||||
|
# Compute all edits from the original text, then apply from the
|
||||||
|
# bottom up so earlier offsets stay valid.
|
||||||
|
for a, b, new in sorted(reindent_lines(s, bols), reverse=True):
|
||||||
|
view.replace(edit, sublime.Region(a, b), new)
|
||||||
|
|
||||||
|
def is_enabled(self):
|
||||||
|
return self.view.match_selector(0, "text.klammertext")
|
||||||
@@ -111,6 +111,20 @@ The Standard Klammer Set is loaded by default. To load a different klammer set,
|
|||||||
pass `-k PATH` (the klammer set's `.k` file). To run with only the three
|
pass `-k PATH` (the klammer set's `.k` file). To run with only the three
|
||||||
primitive klammers (`@read`, `@eval`, `@cond`), use `-k none`.
|
primitive klammers (`@read`, `@eval`, `@cond`), use `-k none`.
|
||||||
|
|
||||||
|
## Editor support (Emacs, Sublime Text)
|
||||||
|
|
||||||
|
Editing Klammertext is nicer with editor support: syntax highlighting,
|
||||||
|
delimiter matching, and indentation for Emacs and Sublime Text. It is not
|
||||||
|
inside the container image — it belongs on your machine, next to your editor.
|
||||||
|
Download it from either place:
|
||||||
|
|
||||||
|
- <https://andykopra.com/Klammertext_editing.zip> — unpacks to `emacs/` and
|
||||||
|
`sublime/` folders
|
||||||
|
- the Klammertext source repository,
|
||||||
|
<https://git.andykopra.com/ack/klammertext>, directory `doc/edit/`
|
||||||
|
|
||||||
|
Each package's README explains its installation.
|
||||||
|
|
||||||
## If something goes wrong
|
## If something goes wrong
|
||||||
|
|
||||||
- **`Cannot connect to the Docker daemon`** — the Docker service isn't running:
|
- **`Cannot connect to the Docker daemon`** — the Docker service isn't running:
|
||||||
|
|||||||
@@ -124,10 +124,12 @@ That's it — you're running Klammertext.
|
|||||||
together, and run `ktext` from that folder.
|
together, and run `ktext` from that folder.
|
||||||
- **Runs natively.** On Apple Silicon, `container` runs the native arm64 image
|
- **Runs natively.** On Apple Silicon, `container` runs the native arm64 image
|
||||||
with no Rosetta translation.
|
with no Rosetta translation.
|
||||||
- **Fonts.** The default fonts (Crimson Pro, Open Sans, Inconsolata) are built
|
- **Fonts.** The default fonts (Crimson Pro, Open Sans, Inconsolata, and
|
||||||
in, so PDFs work with no internet connection. If you ask for a different font
|
others) are built in, so PDFs work with no internet connection. Font
|
||||||
by name, Klammertext downloads it from Google Fonts the first time, which
|
resolution is entirely offline: asking for a font that isn't installed
|
||||||
needs an internet connection.
|
lists the available fonts, and additional fonts are installed from font
|
||||||
|
files you already have with `kdesc --font install <folder>` (`kdesc --font
|
||||||
|
help` explains).
|
||||||
- **Updating later.** When a new version is announced, run `klammertext-update`
|
- **Updating later.** When a new version is announced, run `klammertext-update`
|
||||||
in Terminal.
|
in Terminal.
|
||||||
- **If you also build Klammertext from source on this Mac.** Most people don't —
|
- **If you also build Klammertext from source on this Mac.** Most people don't —
|
||||||
@@ -143,6 +145,20 @@ That's it — you're running Klammertext.
|
|||||||
run `container system stop`; start it again with `container system start` next
|
run `container system stop`; start it again with `container system start` next
|
||||||
time.
|
time.
|
||||||
|
|
||||||
|
## Editor support (Emacs, Sublime Text)
|
||||||
|
|
||||||
|
Editing Klammertext is nicer with editor support: syntax highlighting,
|
||||||
|
delimiter matching, and indentation for Emacs and Sublime Text. It is not
|
||||||
|
inside the container image — it belongs on your Mac, next to your editor.
|
||||||
|
Download it from either place:
|
||||||
|
|
||||||
|
- <https://andykopra.com/Klammertext_editing.zip> — unpacks to `emacs/` and
|
||||||
|
`sublime/` folders
|
||||||
|
- the Klammertext source repository,
|
||||||
|
<https://git.andykopra.com/ack/klammertext>, directory `doc/edit/`
|
||||||
|
|
||||||
|
Each package's README explains its installation.
|
||||||
|
|
||||||
## If something goes wrong
|
## If something goes wrong
|
||||||
|
|
||||||
- **`command not found: ktext`** — you didn't open a new Terminal window after
|
- **`command not found: ktext`** — you didn't open a new Terminal window after
|
||||||
|
|||||||
Reference in New Issue
Block a user