| git.druid.rocks | index | druid520 | radium | USAGE |
USAGE
radium - my emacs, tty only, no lsp, gtags does the work
this is my whole emacs setup: the config in this repo, the project
dsl (cfg.rp), and the keys i actually press. it runs on netbsd 11 as
a daemon + emacsclient, on the console (TERM=wsvt25), in 256 colors.
there is no lsp and there never will be - everything lsp claims to
do, gnu global (gtags) does, and everything else is small suckless-ish
packages plus my own elisp.
## install
git clone git://git.druid.rocks/druid520/radium.git
cp -r emacs ~/.emacs.d
emacs --daemon
emacsclient -nw
first boot installs every package off elpa/nongnu/melpa, so it wants
network + tls once. pkgsrc's emacs without builtin gnutls still works
(it falls back to gnutls-cli), and netbsd's cert paths are wired in,
so install mozilla-rootcerts and forget about it. TERM=wsvt25 gets
treated as xterm-256color automatically so the console isnt stuck on
16 colors.
in emacsclient: C-x C-c closes the client (the daemon stays up),
C-x # tells the daemon you're done with a server buffer.
radium-server.el is what makes any of this work without the explicit
`emacs --daemon` above: it starts the server itself (server-start,
guarded by server-running-p so a second `emacs` doesn't error trying
to start a second one) the moment any radium config loads, daemon or
not - a plain `emacs` (no --daemon) already leaves a socket for
emacsclient to attach to. server-client-instructions is off: no
"You can now switch to buffer..." banner every time a client attaches.
everything that's runtime state (elpa/, backups/, autosaves/, recentf,
history, places, desktop) is gitignored - the repo root *is*
~/.emacs.d once copied. none of my elisp gets byte-compiled, on
purpose.
## the config, how its built
init.el just puts itself on load-path and require's every radium-*
module, each wrapped so one module failing (no network, missing
binary, w/e) prints an "err:" line in *messages* and the rest still
load. modules are self-contained, most provide their own keys.
radium-bootstrap does package.el + use-package; radium-defaults does
all the stock emacs tuning (fonts are on, backups/savehist/recentf/
saveplace/desktop persist across daemon restarts, repeat-mode,
delete-selection, smooth tty scrolling, tabs + 78 cols). indentation is
tabs everywhere, 8 wide, no matter what - enforced in defaults, TAB
only ever indents (no snippet expansion, no completion cycling).
~/.radium.el, if it exists, loads dead last - after every radium-*
module above and its own hooks are already in place, so it can freely
override anything (a keybinding, a face, a hook), and a mistake in it
reports itself instead of crashing the daemon. this is where a real
machine's own settings that don't belong in a public git repo at all
go - an api key path, a font size, one extra package only this box
wants - not a fork of this repo, just one untracked file next to it.
## cfg.rp, the project dsl
per-project config lives in cfg.rp at the project root. suckless
`key = value` lines under [sections], toml without the ceremony.
values are raw strings to end of line - no quotes, no types. comments
(; or #) only on their own lines, because shell commands are legal
values and inline comments would fight them. cfg.rp itself is the
strongest project-root marker, so it wins over mk.conf detection.
a line that's none of blank/comment/[section]/key=value (an inline
comment tacked onto a [section] line, say) is a real, loud *Messages*
warning naming the file and line number, not a silent no-op - a line
like that used to just vanish, leaving every key after it silently
misattributed to whichever section came before, no error at all.
the sections:
[build]
tool = mk | make | gmake | samu | muon | gnat ; override autodetect
build = b ; C-c b b goal, beats mk.conf target_build
clean = c
run = r
install = i
test = t
run_cmd = ./bin/serve -v ; full command, beats the run goal for C-c b r/R
[tags]
gtags = yes ; auto-build the gtags db in the background when missing
[push]
remote = origin
branch = trunk
deploy = git push {remote} {branch} && ssh box 'do-deploy'
; any other key here is a named push template, {remote}/{branch}
; get substituted. C-c v p completes over the names; typing a bare
; branch name pushes that branch, empty RET pushes the default.
unknown sections n keys are ignored, so it grows freely. without the
file nothing changes - every lookup falls back to the old behavior
(mk.conf letters, marker detection, plain git push).
## project.el, not projectile
radium-project.el is emacs's own built-in project.el, not projectile -
C-x p f/g/p/b (find file/grep/switch project/list buffers) and the
rest of its own standard keymap, for free, once a root is found.
project-vc-extra-root-markers is set to the exact same marker list
radium-build-root already uses (radium-build.el's own
radium-build-markers - one shared list, not two separately
maintained ones that can drift out of sync with each other, which
they genuinely had: a project keyed only by cfg.rp or a bare mk/
directory used to build fine via C-c b b while C-x p f/project-
switch-project didn't recognize it as a project at all).
## keys, the radium ones
all mine live under C-c plus a few:
build (radium-build) - picks mk/make/gmake/samu/muon/gnat by project,
errors jumpable in the compile buffer:
C-c b b build
C-c b c clean
C-c b r run (plain output)
C-c b R run in a real terminal (curses, repls, interactive stuff)
C-c b i install
C-c b t test
C-c b T prompt for a goal, remembered per project, completion from
every target_* in mk.conf
C-c b o one-off goal, not remembered
C-c b g recompile last command
C-c b k kill the running build/check
tags (radium-tags) - gnu global:
C-c t g (re)build the gtags db at the project root
C-c t r rename a symbol project-wide (global -r + query-replace
over every file that references it)
M-. ggtags' dwim finder (definition/reference), results pop
into a clickable buffer at the bottom. falls back to
dumb-jump (grep-based, pinned to plain grep rather than
silently preferring rg/ag if either's on $PATH) when
ggtags has no answer
git (radium-git) - vc, not magit:
C-c v s vc-dir status (stage, commit, everything there)
C-c v d diff
C-c v l log
C-c v a annotate
C-c v p push (cfg.rp [push] templates, see above)
C-c v c next action (commit etc.)
plus diff-hl shows every modified line in the left margin and
changed files get marked in dired
sign/verify (radium-sign) - ssh-keygen -Y or signify, whichever
radium-sign-method names if it's actually installed, else whichever
of the two is (a real error only if neither is):
C-c v S sign this file - writes file.sig next to it
C-c v V verify file.sig against this file
both ask which key every time (completing-read over ~/.ssh/*.pub
for ssh, or radium-sign-signify-key-dir's *.sec/*.pub for signify) -
which key is exactly the kind of per-use decision worth asking, not
defaulting. ssh signing uses ssh-agent when the matching private key
is loaded there (falls back to the adjacent private key file
otherwise - ssh-keygen's own behavior, not something wired up here),
under the "file" namespace (radium-sign-ssh-namespace) - openssh's
own manpage example for signing a plain file, not a git object.
editing:
C-; iedit (edit every occurrence of the symbol, live)
C-c m c multiple cursors: one per line
C-c m a multiple cursors: every match of the word at point
C-> C-< multiple cursors: next/prev match
C-= expand-region
C-x u vundo, the visual undo tree (plain undo is gone, and
good riddance)
M-n M-p symbol-overlay: next/prev occurrence of the symbol under
point (all of them stay highlighted)
C-c o r symbol-overlay: rename the symbol in this buffer
C-c o t symbol-overlay: toggle in-scope vs whole-buffer
C-c o k symbol-overlay: clear
C-c C-e in grep/occur buffers: wgrep, edit results in place and
C-x C-s writes back to every real file. in dired it's
wdired, the same trick for file names
completion (radium-completion) - vertico + orderless + marginalia +
corfu + cape + embark:
C-c f consult-find
C-c g consult-grep
C-c r consult-recent-file
C-c i consult-imenu
C-x b consult-buffer
M-y consult-yank-pop
M-/ hippie-expand (dabbrev, files, kill ring, lisp symbols)
C-. embark: a menu of actions for whatever is at point or
selected in the minibuffer
TAB completes; typing completes in orderless style (any order,
space separated). corfu pops up in buffers on its own after
2 chars.
prototypes (radium-proto + radium-stub):
C-c c c insert a call with real param names, pulled from the
gtags db, one tabstop per param. works in c (foo(a, b)),
ada/spark (same positional form), scheme ((foo a b)),
perl (foo()).
C-c c s the function filler: turn every c/ada/perl prototype line
in the region (or whole buffer) into a stub block in my
style, point landing in the first body
C-c c S same, for just the prototype line under point
qemu + gdb (radium-qemu) - cfg.rp's own [qemu] section:
[qemu]
kernel = out/kaboom.elf
disk = out/disk.img ; optional
memory = 128 ; optional, mb, default 128
args = -smp 2 ; optional, raw extra flags
C-c q q boot -nographic in a real comint buffer (same "run it
through a real process, not a black box" idea as C-c b R)
C-c q g the same boot, plus -s -S: qemu halts before the first
real instruction, gdbserver stub on :1234
C-c q d `gdb -i=mi' on the [qemu] kernel, auto-attaching to a
-s -S qemu already running under this same project root
via `target remote :1234' the instant gdb's first real
prompt appears - a plain M-x gdb otherwise
electric fence / lmdbg (item 18's other half, in radium-build.el
instead - a run-command concern, not a qemu one): cfg.rp's [run]
section, efence = yes and/or lmdbg = yes, wraps C-c b r/R's own run
command (env LD_PRELOAD=libefence.so for efence, `lmdbg -o
ROOT/lmdbg.out' wrapping the whole thing for lmdbg - netbsd base's
own malloc/leak tracer). disclosed plainly: neither tool exists in
the sandbox this was built in, so this half is written against each
tool's own documented usage, not independently verified running.
binary tools (radium-binary) - hex editing, radare2, objdump:
C-c x h toggle hexl-mode on this buffer (emacs's own built-in hex
editor - .bin/.o already auto-open in it)
C-c x r radare2 on this buffer's file (or prompts), a real comint
repl - r2's own command set (aa, pdf, V, ...) already
does the real work, nothing here wraps it further
C-c x d objdump -d -M att on this buffer's file (or prompts), AT&T
syntax matching this config's own asm convention, shown
in a real asm-mode buffer (q to close)
item 22 (capstone/keystone/unicorn) has no further integration
beyond python.el itself, above - none of the three ship a
standalone cli tool, only a library to import from real python.
macros (radium-macros) - text macros that fire as i type:
![date] 2026-09-01 (or any strftime: ![date:%Y])
![time] ![datetime] ![uuid] ![wid] (host:pid) ![host] ![user]
![env:VAR] ![file] ![dir] ![basename] ![path] ![line] ![lang]
![guard] header guard for this file
![spaces:12] that many spaces
![epoch] the current unix timestamp
![rand:N] a random integer in [0,N), 1000000 if N is missing
![branch] the current git branch, empty outside a repo
![proto:name] auto-fills a call like C-c c c
![help] lists every real macro with a one-line description
C-c e expand the macro before point by hand (also reaches the
confirm-gated ones: ![shell:cmd], ![gitlog])
C-c h same listing as ![help], straight to the keybinding
the instant i type the closing ], it expands - except in kona, where
k's own ![] would collide. paredit is told to leave [ and ] alone
for the same reason.
C-c k n name the last keyboard macro
C-c k s name + save it to macros.el (loaded on startup, macros
survive the daemon)
C-c p process the whole buffer through a real script - "like
babel" but for any file, not just an org #+begin_src block.
prompts for a language (perl/elisp/scheme/commonlisp) and
opens a real edit buffer for the script in that language's
own major mode; C-c C-c there runs it against the buffer
C-c p was called from and replaces that buffer's whole
content with the result, C-c C-k cancels. perl gets the
whole buffer as $_ (real perl one-liner style, -0777 -pe -
a plain s/.../.../ or $_ = ... works); elisp needs no
subprocess at all, just write real elisp whose last form
evaluates to the new text, with `radium-proc-text' bound to
the old text; scheme/commonlisp are real external processes
(chez/sbcl), so they get the old text as a bound variable
named `text' too, same shape as elisp, and are expected to
leave their own last form's value to become the new text.
C-c k e show the last macro as elisp (elmacro)
checks (radium-checkonsave):
every save silently syntax-checks the file for its language (gcc,
nscc, gnatmake, as, perl -c, sh -n, python3's ast.parse, byte-compile
for elisp) in the background; failures pop a jumpable buffer, M-g n
/ M-g p walk the errors. per-file buffers, a re-save kills the stale
run.
shells n docs:
C-c s e eshell
C-c s s shell
C-c s t ansi-term (/bin/sh)
C-c d man (the real netbsd man, mandoc output)
C-c j j avy jump to any char on screen
C-c j l avy jump to line
C-c j w avy jump to word
M-o other window (windmove meta-arrows too)
languages:
C-c C-c kona: send region to the repl (or the current line when
nothing is selected) - global, so a kona one-liner works
from inside any file
C-c C-l kona: send line - global, and taken back from the cc/sh/
ada/forth maps so every code buffer gets it (scheme keeps
geiser's own eval/repl keys, M-` keeps module completion)
(its electric-toggle) so c buffers get it too
C-c C-z kona: open the repl - global
C-c C-r crontab-mode: preview the schedule's next 5 runs
C-c C-p ada-mode: run gnatprove (spark proof) on a .gpr here
C-c a w one-time: build ada-mode's bundled wisi parser
gptel - any openai-compatible api in a buffer, straight to the api,
no separate daemon:
C-c ? c open/switch to the *gptel* chat buffer
C-c ? s send (region if active, else the whole buffer up to point)
C-c ? m the model/backend/params menu
C-c ? r rewrite the region via a prompt, in place
which provider: radium-gptel-provider is nil (unconfigured) in this
repo on purpose - this config makes no assumption about which api
im on today, deepseek or otherwise (i move between them). set it,
and call (radium-gptel-apply-provider), from ~/.radium.el - the
same "real per-machine setting, not tracked here" spot item 13's
own api-key-path precedent already is:
(setq radium-gptel-provider
(list :name "OpenRouter" :host "openrouter.ai"
:endpoint "/api/v1/chat/completions"
:key-file "~/.openrouter"
:models '(anthropic/claude-opus-5.5 openai/gpt-5)
:model 'anthropic/claude-opus-5.5))
(radium-gptel-apply-provider)
left unset, gptel-backend just stays nil - gptel's own real
default too (checked directly against its source), not a gap this
config leaves open.
auth is always a plain api key, read fresh from the provider's own
:key-file on every request (that file holds nothing but the raw
key) - never in this repo, never in an env var. missing file -> a
normal 401, not an elisp error mid-request.
tools are on: the model can read_file/edit_region/write_file/
list_directory/run_shell_command mid-conversation, not just talk.
read/list never ask; write_file/edit_region/run_shell_command always
pop the proposed change for me to approve first, and edited files
pop into a real window so the change is visible in the actual
editor, not just written to disk behind my back.
mcp: set mcp-hub-servers (see mcp.el's own docs for the
(NAME . (:command ... :args ...)) / (NAME . (:url ...)) shape) to
whatever mcp servers i want - their tools merge into gptel-tools
automatically once they connect. unset by default: nothing starts,
nothing to configure if i dont want it.
org (radium-org) - notes live under ~/org, created if it isnt there:
todo.org (tasks), notes.org (everything else), journal.org (dated
entries via a datetree). C-c a/C-c c are already radium prefixes (ada
wisi-build, proto/stub), so org gets its own C-c n prefix instead of
the textbook global bindings from its own manual:
C-c n a org-agenda
C-c n c org-capture - "t" files a TODO under todo.org's inbox,
"n" a plain note under notes.org's inbox, "j" a dated entry
in journal.org
C-c n l org-store-link
C-c n r org-refile
deliberately no org-modern/org-superstar/org-bullets - they replace
headline stars and tags with unicode glyphs by default, and this
whole config stays plain-ascii on a tty. stars stay stars.
babel (C-c C-c on a #+begin_src block) is wired for shell, emacs-lisp,
scheme, perl, and C - the languages i actually use that org has a
working backend for (nothing for asm/kona/forth/ada, no ob- backend
exists for those). still asks before running a block every time
(org-confirm-babel-evaluate's own default) - same "ask before running
embedded code" shape as ![shell:...] and gptel's write/edit/shell
tools. perl blocks show what they actually print by default (ob-perl's
own default shows the last expression's return value instead, which
isnt what i want).
org-roam (item 2) lives under ~/org/roam, not a second notes root -
a real zettelkasten layer on the same tree todo/notes/journal.org
already use:
C-c n f find (or create) a node by title
C-c n i insert a link to a node at point
C-c n b toggle the backlinks buffer for the node at point
verified for real, not just loaded: created a real node, synced
org-roam's own real sqlite db, and inserted a real [[id:...]] link
from a second note back to it, the actual point of the feature.
## latex (radium-lang-latex)
.tex - auctex, the real, established latex package (not org's own
narrower latex-fragment preview). tabs, this config's own policy
applied consistently. no checkonsave: a real one-shot check would
mean chktex (bundled inside the full texlive distribution, no
standalone package) or a genuine multi-pass pdflatex compile,
neither of which is a quick syntax check the way perl -c/sh -n
are - same "no one-shot checker" category scheme/forth/kona/cron
already document.
## rd, and ~/.radium.ipc (radium-ipc)
every one of rd's own subcommands is spelled with a leading '-' -
on purpose, so a bare `rd' (or `rd FILE...') can never collide with
one, however many files i throw at it - except a filename that
itself starts with `-' (rd -- forces wrapper mode past that, same
end-of-options convention getopt/many unix tools already use):
rd [FILE...] the emacs wrapper: daemon already up, emacsclient
$RADIUM_CLIENT_ARGS FILE... ($RADIUM_CLIENT_ARGS is
-nw unless overridden, see below). not up: errors,
doesnt silently start one for you
rd -- [FILE...] same, forced - for a FILE that itself starts with `-'
rd -start emacs --daemon
rd -stop kill-emacs on the running daemon
rd -status up/down, exit code matches (0 up, 1 down)
rd -eval EXPR emacsclient --eval EXPR - synchronous, prints the
real return value
rd -ipc FORM fire-and-forget: appends FORM (one line) to
~/.radium.ipc, radium-ipc.el reads and evals it in the
daemon, the result goes to *Messages* - nothing comes
back down this pipe, by design (rd -eval already
covers the synchronous/has-a-reply case). a keybind
trigger is just another elisp form, no separate
mini-language: `rd -ipc "(call-interactively 'save-buffer)"'.
rd - (a lone dash, no subcommand name after it) errors -
same as any other unrecognized subcommand
$RADIUM_CLIENT_ARGS controls what the plain wrapper hands
emacsclient - -nw (a terminal frame) unless overridden, e.g.
`export RADIUM_CLIENT_ARGS=-c' for a real graphical frame instead.
necessarily shell-side config, not ~/.radium.el: rd is its own,
separate process, already running (and already needing these flags)
before it ever talks to the daemon that loads ~/.radium.el at all.
install: rd isnt copied into ~/.emacs.d by the normal install step
(that directory usually isnt on $PATH) - symlink it separately,
`ln -s ~/.emacs.d/bin/rd.sh ~/bin/rd' or wherever else is on $PATH.
the fifo path is configurable, one setting shared by both halves:
$RADIUM_IPC (falls back to ~/.radium.ipc if unset) - rd itself reads
it directly, and radium-ipc.el reads the same env var when it first
loads. to change it after the daemon is already up instead (from
~/.radium.el, say), `(setq radium-ipc-path "...")' then
`(radium-ipc-start)' to make the running daemon pick it up - safe to
call again anytime, it always moves the live reader to wherever
radium-ipc-path now points rather than leaving it on the old one.
~/.radium.ipc is a real fifo, mode 600 (owner-only - the same trust
boundary ~/.emacs.d itself already has, not a new one), created by
radium-ipc.el the moment the daemon starts. a plain shell redirect
into a fifo closes its write end the instant the write finishes,
which means the reader sees eof and exits after every single
command, not just at shutdown - radium-ipc respawns its reader
immediately every time so the channel stays open for the next one.
lib/radium.lisp and lib/radium.scm are that same channel's far end
for a real slime or geiser repl: `(load "~/.emacs.d/lib/radium.lisp")'
(sbcl) or the .scm one (chez) defines one function, `(radium-ipc
"FORM")', appending FORM (one real elisp form, as a string) to
~/.radium.ipc - genuinely extending the running daemon from cl or
scheme code, not just printing to their own repl buffer, since
there's no other bridge into emacs's own display/keymap/buffer
internals from outside elisp at all. fire-and-forget, same as
rd -ipc above and for the same reason - a synchronous reply would
need a second channel this doesn't have, and the daemon's own
*Messages* already shows the result if that's ever worth checking.
## sc, and dtc (radium-sc)
sc (druid520/sc.git) is a suckless ./configure generator: a
hand-written sc.conf (bin=/libs=/oss=/hard_cflags=/... plus any
custom target functions) becomes mk.conf + config.h + makefile +
mk/*.sh (b/c/dm/dh/d/dc) - the exact same convention every real
sc-based project this whole config already builds shares (kaboom,
nscc, jury, mp, forthc, btf itself). dtc ("devel toolchain") scaffolds
and grows an sc(+btf)-based project on top of it.
C-c b s run `sc' at this project's root in a real compile
buffer - one line per cflag/lib/header/ldflag/func
check as it runs
M-x radium-dtc-new scaffold a brand-new sc(+btf) project. dtc's
own remaining prompts (binary name(s), oses,
docs/ folder) are genuinely interactive (a
plain read -r in dtc.sh), so this hands off
to a real ansi-term rather than a compile
buffer, which cant answer them
M-x radium-dtc-doctor checks sc/btf2html/btf2man/mk/cc/perl/
pkg-config are all really on $PATH
M-x radium-dtc-addbin add a binary (c/s/S/pl/sh/bin) to this
project's own sc.conf bin=, with a real
starter source file
M-x radium-dtc-newdoc add NAME.btft to this project's own docs/
M-x radium-dtc-port scaffold an mp package definition
(category/name) from this project's own
sc.conf
a freshly-scaffolded, not-yet-configured project only has sc.conf on
disk (mk.conf/mk/ dont exist until sc itself runs) - "sc.conf" joins
radium-build-markers (and project-vc-extra-root-markers, updated
separately - add-to-list doesnt mutate a list a second var already
points at) so C-x p f/project-switch-project/radium-build-root all
agree on that project root too, before sc has ever run there.
verified for real, not just loaded: a real sc.conf + src/hello.c ->
sc generated config.h/makefile/mk.conf/mk/*.sh, and the result
actually built and ran (`hello`). dtc new/doctor/addbin/newdoc/port
all run for real too - addbin appends to a live sc.conf and drops a
real src/foo.c, newdoc drops a real docs/howto.btft, port scaffolds
a real port/hello/pkg.conf and honestly says so when no git remote
exists to fill pkg_fetch from.
## keys, plain emacs ones i use
C-x C-f find file C-x C-s save
C-x C-w write as C-x k kill buffer
C-x 2/3 split window C-x 0/1 unsplit
C-x o other window C-x b buffer (consult)
C-s C-r search fwd/back M-% query replace
C-g abort C-u numeric prefix
C-k kill line C-w kill region
M-w copy region C-y yank
C-a C-e line start/end C-n C-p next/prev line
M-f M-b word fwd/back M-a M-e sentence
M-< M-> buffer start/end C-l recenter
C-M-a/e function start/end (defun-ish nav)
M-! run shell command M-| pipe region through command
M-x any command C-h k/f/v help (key, function, var)
M-g n/p next/prev compile error C-x ` next error
TAB complete/indent M-p M-n minibuffer history
q quit most modes (dired, compile, ggtags popups, vundo)
RET/click jump to source in compile/ggtags/grep buffers
C-x C-e eval last sexp (elisp) C-c C-c eval defun
repeat-mode is on, so after any repeatable key (C-n, C-x o, M-n, ...)
the follow-up keys keep working without the prefix.
which-key pops up the available continuations 0.4s after any prefix
key (C-x, C-c, ...) - a cheatsheet, not a behavior change.
## what file becomes what
.c .h c89 style, tabs. gtags + checkonsave + proto + stub +
autoinsert (main/header guard) + snippets. typing the
closing `;' on a bare declaration (`int i;') rewrites
it in place to a zero-initialized one (`int i = 0;',
NULL for a pointer, {0} for an array/struct/union,
'\0' for char, 0.0 for float/double) - i almost never
want an uninitialized local
.adb .ads ada/spark via gnu elpa ada-mode. same
feature set, ada stub style, autoinsert body/spec
.pl .pm cperl, tab indented. checkonsave + gtags + proto +
stub (sub foo($$); keeps its prototype) + autoinsert
.scm .ss chez scheme via geiser (its own repl, eval keys, M-. and
completion - gtags stays out of scheme entirely).
gtags + proto with lisp call form + paredit + autoinsert.
completion is connection-aware, same real bug/fix as
common lisp above: geiser's own completion only runs
once a real repl is actually connected, falling back
to cape/dabbrev before that so corfu never errors
meanwhile (see radium-lang-scheme.el's own header)
.el emacs-lisp. checkonsave byte-compiles it,
gtags + paredit + autoinsert
.s at&t asm, tabs. gtags + checkonsave (as) + autoinsert
.sh posix sh (scripts all say /usr/bin/env sh).
checkonsave (sh -n) + gtags + autoinsert. TAB is a
literal self-insert here, not indent-for-tab-command
(which was a silent no-op on an already-correctly-
indented top-level line, column 0) - every other mode
above still recalculates indent on TAB
.fth .fs forth. autoinsert word + snippet (forthc is a one-shot
compiler, so no repl wiring)
.k .ks kona (my own major mode). repl + autoinsert + snippet
.cron crontab-mode + next-runs preview
.nsc .nsh nsc (druid520/nscc.git), c-mode derived - same tabs/
allman style as .c for free (c-mode-hook fires for a
derived mode too), plus nsc's own explicit-width types
and global/include highlighted. checkonsave runs nscc
itself (a .nsh compiles standalone same as a .nsc, no
-x-c-style flag needed) + gtags + autoinsert
.lisp .lsp common lisp via slime + sbcl (slime, not sly - same
.cl .asd established-over-newer call as vc over magit). own
keys (C-c C-c compile-defun, C-c C-l load-file - both
slime-mode's own, kona and gtags both stay out).
`M-x slime' is the actual command that starts sbcl and
connects - C-c C-z (slime-switch-to-output-buffer)
only switches to an already-connected repl, unlike
scheme's own geiser C-c C-z, and errors "Not
connected." if you press it first. completion is
connection-aware: slime's own completion only runs
once actually connected, cape/dabbrev fills in before
that so corfu never errors meanwhile (see
radium-lang-lisp.el's own header for the real bug this
fixes). no checkonsave - real cl has no one-shot
syntax check the way perl -c/sh -n do
.py python.el, tabs (this config's own policy, not pep8's -
still standard-legal: python only forbids MIXING tabs
and spaces within one block, tabs alone are fine).
checkonsave via python3's own ast.parse (a real syntax-
only check, no .pyc bytecode left behind the way
py_compile would) - the real prerequisite for item 22
(capstone/keystone/unicorn): none of the three ship a
standalone cli tool at all, they're consumed by
writing real python that imports them as a library,
checked directly (all three installed fresh in this
same sandbox specifically to check, no cstool-like
binary appeared on $PATH for any of them)
.tex auctex, tabs. no checkonsave (chktex/pdflatex are too
heavy/no standalone package - see below)
.btft .btfs btf (druid520/btf.git, my own markup format - one
grammar, both extensions: a .btfs stylesheet's
[rule sel=...]/[prop n=...] are ordinary tags under the
same [name attr="val"]...[e] shape .btft pages use).
my own major mode (no package covers it), fontification
only - no gtags/checkonsave/proto, nothing there to
index or syntax-check
everywhere: syntax highlighting (256-color), symbol-overlay, vundo,
diff-hl margins, multiple cursors, embark, the macro engine, wgrep.
scheme/forth/kona/cron have no one-shot syntax checker on netbsd, so
they just arent in checkonsave. languages without a
prototype/definition split (scheme, sh, asm, forth, kona, elisp) have
no stub either - nothing to fill from.
## things worth knowing
- completion in c/ada/scheme/perl hits the gtags db first (with real
param autofill), and falls through to ggtags/cape/dabbrev when the
db has nothing. a machine-wide db over /usr/include is optional:
mkdir -p ~/.cache/gtags-sys && cd /usr/include && gtags ~/.cache/gtags-sys
then printf and friends complete too.
- the macro confirm-gated commands (shell, gitlog) only run via C-c e
and always ask first. everything else in the safe table is pure
text.
- checkonsave runs on save, not on type, and never blocks - its
buffer only pops when something actually fails.