Skip to content
NightMachineryPublic

About

My somewhat modular emacs config.

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Latest commit

 

History

681 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

My Emacs Doom Config

Project files

TASKS.org appears in Projectile file pickers even when Git ignores it. See Projectile configuration and search limitations.

Dependencies

ugrep

fd

fzf

notmuch, isync (mbsync), msmtp, flock

For mail: brew install notmuch isync msmtp flock, then email/install-night-mail.zsh. See docs/email.md.

Environment Variables

nightNotes

Brish

night.sh

[help:night/codestral-key-get], etc. use z var-get

@todo
Create a helper function `night/z-var-get` which, when brish is available (`night/brish-p`), uses `z var-get`, but falls back on `getenv` when it's not. Use it everywhere we currently use `z var-get`.

[help:night/org-link-browser-current]

Converts Google search URLs to search: Org links, including UTF-8 queries such as Persian text. See docs/org-link-browser-current.md.

[help:night/arxiv-to-org]

Uses the shell converter through BrishGarden. Failed or empty arXiv API requests fall back to validated abstract-page metadata. See arXiv conversion.

[help:night/semantic-scholar-to-org]

Resolves arXiv links to Semantic Scholar automatically. The native Go helper tries keyless page data first, then API and extraction fallbacks. Notes include authors, a full abstract, and a supplied TLDR block before the abstract. See retrieval options.

[help:night/paste-yank-html]

[agfi:pbpaste-html]
[help:night/org-insert-and-fix-levels]
Falls back to doing nothing and returning the input as is

[help:night/pbpaste]

Wraps current-kill while locally clearing osx-clipboard-last-selected-text so terminal macOS clipboard sync is not suppressed as already seen.

[help:night/h-kill-skip-whitespace]

Wraps kill-new and must forward optional arguments with &rest so boolean REPLACE values from kill-append remain valid apply tails.

[help:night/paste-md2org-sentencecased]

Converts Markdown clipboard text to Org, sentence-cases it, and inserts it with Org level adjustment.
With a prefix argument, joins the clipboard’s lines with newline2space first.

[help:night/newline2space]

Text-in/text-out wrapper around the zsh newline2space filter, for callers that already hold the text. [help:night/p-newline2space] is the clipboard-reading command.

[help:night/org-paste-escaped-in-md-code-block]

Bound to leader , <. Pastes the clipboard as a Markdown fenced code block, the Emacs counterpart of the Hammerspoon pasteBlockified on hyper+=,=.
Comma-escapes lines starting with * or #+ only in Org buffers, where they would otherwise be parsed as a heading or a keyword. Elsewhere the content goes in byte for byte, since a comma would corrupt it.
[help:night/h-md-code-fence] drops trailing whitespace so the closing fence always starts its own line, and grows the fence past the longest backtick run in the content so that fenced text nests correctly. A newline is opened first when point is mid-line.

[help:night/smart-text-paste]

Bound to leader , s.
In Org buffers outside md=/=markdown source blocks, converts Markdown clipboard text to Org before sentence-cased insertion.
Elsewhere, sentence-cases and inserts the clipboard text directly.
C-u leader , s first joins the clipboard’s lines with newline2space, for text copied out of a hard-wrapped terminal or a PDF. It is opt-in because that filter also flattens lists and paragraph breaks; see docs/smart-text-paste.md.

macOS

[help:night/org-link-safari-current]

Uses Applescript directly

[help:night/hs-alert]

Asynchronous wrapper over [agfi:hs-alert]; a wedged Hammerspoon must never block Emacs.

Keywords cover the whole hs-alert-v2 surface: :dur, :flash, :pos, :id, :markup, :color. Anything omitted keeps the zsh-side default.

Notes

Bookmarks

SPC b m saves a file bookmark without a position. Use C-u SPC b m or M-x night/bookmark-set-with-position to save the current location. See bookmark behavior.

At-tags

Percent signs belong to tags such as @25% and @12.5%. See at-tag highlighting.

Org buffers opening without colors

Intermittent loss of coloring on open is under investigation. The September 24 special-tag rule is a candidate, but synthetic comparisons passed with both rules; a recorded jit-lock redisplay error has no captured stack yet.

Mobile line numbers

emc-mobile hides line numbers in marked terminal frames except at the final line; desktop buffer settings are preserved. See docs/mobile-line-numbers.md.

Copies from those frames use native OSC 52 to reach the terminal client’s clipboard. See docs/mobile-clipboard.md for the 6000-byte default limit and tmux setup.

emc-tealy and emc-tealy-tmux send oversized copies through SSH, with 15-minute successful readiness caching and 30-second failure caching. Clear only that host with emc-tealy-cache-clear or M-x night/mobile-clipboard-tealy-cache-clear; M-x night/mobile-clipboard-cache-clear clears all hosts.

Normal paste in those frames fetches fresh text through SSH and termux-clipboard-get, with a bounded wait and C-g cancellation. Desktop and plain mobile paste keep their existing behavior. See docs/mobile-clipboard.md.

M-x night/ssh-paste-toggle toggles automatic SSH reads independently of frame markers. night/ssh-paste and night/tealy-paste explicitly insert remote clipboard text even with automatic reads disabled.

Yasnippet

Stale active field state

Org LaTeX preview

[help:night/org-latex-fix-begin-env-bug]

Rewrites \[...\] display math that Org mis-parses into \begin{equation*}...\end{equation*}.

A line starting with \begin{...} opens a latex-environment element, which ends the paragraph and strands the closing \], so only the inner environment previews or exports. Hits math written straight into org files by coding agents, which never passes through [agfi:md2org].

Lazy previewing on large files

audiofile: links and the audio mpv server

Following a link to a missing file used to kill the mpv server, breaking every later hear-* call for the rest of the session.

[agfi:hear-start-server] must pass --idle=yes: loadfile ... replace discards the playlist first, so any failed load left mpv with nothing to play and it exited, taking $mpv_audio_ipc with it.

[agfi:hear-loadfile] is the chokepoint; it now rejects missing files and prunes dead entries out of playlists.

[help:night/hear] checks too, so something lands in the echo area; [help:night/path-checkable-p] decides what Emacs may check, excluding URLs and zsh named dirs such as ~mu/.

[help:night/org-link-audiofile-follow] calls [help:night/hear] directly, so following a link never visits a buffer. [help:night/file-extension-actions2] is now just the fallback for media opened some other way.

Redis from Emacs

eredis cannot AUTH on connect, returns error replies as ordinary strings, and never reconnects. So an unauthenticated Emacs did not fail, it lied: [help:night/path-unabbrev] returned another process’s path, the password cache handed back NOAUTH Authentication required. as a passphrase, and [help:night/redis-lock] never acquired while with-redis-lock ran its body anyway.

[help:night/redis-connect] connects, AUTHs from ~/.redis-auth and confirms with PING. [help:night/redis-connected-p] asks the process; it was a variable once, which nothing read and which reported t while the connection was dead.

The checked wrappers recover by themselves through [help:night/redis–call-with-reconnect]: a dead connection reconnects once and the call is retried once. Before that, a redis restart left every later call failing until someone ran [help:night/redis-reconnect] by hand, and an audiofile: link was usually how it got noticed, since [help:night/path-unabbrev] goes through redis.

The retry wraps only the raw eredis call, never the check around it. NOAUTH, WRONGPASS and WRONGTYPE arrive as ordinary strings rather than signals, so they never look retryable and signal [help:night/redis-error] outside its reach; wrapping the check too would loop on a wrong password. Exactly one retry, so a redis that is really down fails promptly.

[help:night/redis-reconnect] remains the remedy for what a retry cannot reach: a rotated password, and proving a connection works rather than inheriting a grandfathered one.

Prefer the checked wrappers [help:night/redis-set], [help:night/redis-setnx], [help:night/redis-expire], [help:night/redis-get] over raw eredis calls.

A daemon that connected before requirepass was set stays authenticated, so it proves nothing. Test in a freshly connected Emacs.

Pasting into a search minibuffer

[help:night/pns] quotes the clipboard so it matches itself, but the correct quoting depends on who reads the pattern, and the same keystroke feeds four consumers.

Doom sets evil-ex-search-vim-style-regexp to t, so evil rewrote the (){}|+ that regexp-quote deliberately leaves bare. Pasting \hat{t} into / turned the braces into an interval and the search died with “Invalid content of \{\}”. [help:night-evil] now sets it nil.

[help:night/h-regex-dialect] names the dialect: emacs for evil and consult-line, and for consult’s ripgrep builder too, since [help:consult–compile-regexp] converts to PCRE itself. Only [help:night/consult-ugrep] binds it, to ugrep-bool, as its builder hands the input to ugrep unconverted.

[help:night/regex-group-shy] spells the non-capturing group per dialect. [help:night/h-arxiv-regex-canonizer] used to hardcode // where \\ was meant, so every non-ugrep search for an arxiv URL matched nothing.

ivy-rg (remote dirs) stays broken: [help:counsel–elisp-to-pcre] drops one backslash of a literal \\, and no input quoting survives that.

Fill-in-the-middle completion

[help:night/llm-fim-providers] is the whole provider abstraction: every native FIM API takes the same body (model, prompt, suffix, max_tokens, stop), so an entry only names the endpoint, the key getter, the model, and whether the text is at choices[0].message.content (:extract chat, Mistral) or choices[0].text (:extract text, OpenAI-style). Configured: codestral (~0.3s), deepseek (deepseek-v4-pro, ~2s, better output), deepseek-flash (~1s).

[help:night/llm-fim-provider-select] at leader . f and [help:night/llm-fim-provider-show] at leader . F; f=/=F because p=/=P are taken by ellama-command-map. C-u M-. overrides the provider for one call only.

No FIM API exists from Google (Gemini is generateContent only; Codey code-gecko had real prefix=/=suffix but is PaLM-era and deprecated; CodeGemma is FIM-trained but self-hosted; Vertex’s answer is reselling Codestral), OpenRouter or Groq (chat-only), or Anthropic. So those keys are of no use here.

No automatic fallback to a second provider on error: a hotkey that silently switches models hides an expired key for weeks.

Not minuet-ai.el, which is the only real multi-provider FIM package but has no direct-insert command – its UX is ghost text plus minuet-accept-suggestion-line, and its failures go to a *minuet* buffer rather than the echo area. Worth installing separately on another key if ghost text is ever wanted.

[help:night/llm-fim-insert-at-point] used to say nothing at all. It now reports FIM: requesting … with a ⋯ indicator at the insertion point, then the outcome with its elapsed time.

Failures were doubly invisible. plz without :else hands the plz-error struct to :then instead of a body, so a 401 signalled wrong-type-argument inside the process sentinel; and the eight in-band error branches were (funcall callback (error "…")), where error signals during argument evaluation and the callback is never reached.

[help:night/llm-fim-get] takes :on-error and :finally alongside :callback, mirroring plz’s own then/else split. [help:night/h-llm-fim–api-message] tries Mistral’s detail, plain message, and DeepSeek’s OpenAI-shaped error.message, so a bad key reads FIM: HTTP 401 — Authentication Fails, Your api key: ****-key is invalid.

[help:night/h-llm-fim–cancel] drops the buffer-local pending slot before killing curl. Killing it makes plz report a curl failure, and that handler must already look stale or every cancellation would announce itself as an error.

[help:night/llm-fim-insert-at-point] claims that slot before sending, for the mirror reason: a missing key reports synchronously, and the guard has to see that report as current or it is swallowed as stale.

So a second M-. supersedes the first request rather than racing it, and C-g aborts. [help:night/h-llm-fim-escape] returns nil, because doom-escape-hook runs under run-hook-with-args-until-success and a non-nil return would swallow the quit.

[help:night/llm-fim-strip-leading-space] is nil. Dropping a leading space was unconditional, on the belief that Codestral prepended a stray one; over 29 contexts per provider all three do it at the same rate, and it is almost always right – ` 0’ after count =', or the whole indent when point is on an empty line. One sample in 87 was improved by stripping, three were broken by it. Binding it with =let does nothing, as the request is async; setq it.

A zsh twin lives on alt+.: [agfi:fim-get] in ~/scripts/zshlang/auto-load/others/fim.zsh, with the same provider table and the same request body, so the two want changing together. Callable from here as z fim-get <prefix> <suffix>.

What a command may read before sending it to a model

Four commands send buffer text to a third party: [help:night/llm-fim-insert-at-point] on M-., and [help:night/ellama-code-fill-in-the-middle] on leader . ., [help:night/ellama-code-complete], [help:night/ellama-complete]. Until now the path policy covered exactly one of them, so leader . . would happily stream a decrypted .gpg buffer that M-. refuses outright – and at ten times the window, +-10000 chars.

A scope says how much may be read: nearby (the character window), block (the Org block, the markdown fenced code block, or the enclosing defun elsewhere), subtree (the heading and its children, in Org and markdown). What is sent is always the scope intersected with the window, so a scope only ever narrows.

[help:night/llm-scope] sets it everywhere and [help:night/llm-scope-select] (leader . o) per buffer; leader . O is the global setter and leader . C-o reads the effective scope back with where it came from. A file can ask for its own scope with a night/llm-scope-file file-local, honoured only when it narrows – the content being protected must not be able to widen what is sent, and a discarded request is reported rather than silently ignored. Nothing else writes them – not the chooser, not the one-shot commands, not the privacy prompt – so what a keystroke sends never changes behind your back.

One-shot: leader . b block, leader . h subtree, leader . n nearby, leader . C-, asks first. alt+cmd+. completes within the current heading; kitty sends it as the xterm \x1b[27;35;46~ that [help:night/setup-input-decode] already decodes, rather than its own CSI-u spelling which nothing here reads.

A confirm rule now raises a chooser instead of a yes/no, so the answer to “may I send this?” can be “only this block”. Every candidate is highlighted at once rather than previewed one at a time: they nest, so a single rendering answers all three questions and the choice costs one keypress. Sizes go in the prompt because a subtree is routinely taller than the window.

The confirmation remembers the scope it was granted at, not a bare yes. Otherwise approving block once and pressing M-. again would hit the remembered approval and fall through to the buffer’s own scope – consent for a block silently spent on the whole window. A scope that stops resolving refuses rather than widening.

The shared machinery is in autoload/night-llm-context.el rather than either caller: night-llm-fim.el is wrapped in (after! (night-openai night/ellama) ...) and provides no feature, so its body evaluates strictly after night-ellama.el’s in the same provide cascade and nothing can depend on it. Every model-facing name now shares the night/llm- prefix, so M-x night/llm- completes to all of it; the obsolete aliases that carried the older night/fim-* and night/mistral-fim-* spellings have been deleted rather than re-pointed, as they were a day or two old with no consumers outside this directory.

[help:night/llm-context-line-tol] had never done anything. The test subtracted the line boundary from the window edge, where the difference can never be positive, so the window always rounded out to whole lines however long the line was – the opposite of what the tolerance is for. Measured: a 400-char line is now left alone at the default 200 and folded in at 900.

[help:night/ellama-code-complete] bound three of the four leading arguments of its response filter, so the closure ellama-stream called still wanted two and got one. It would have signalled wrong-number-of-arguments on the first chunk; latent only because its keybinding is commented out.

Copilot is gated too, but at mode activation, not at completion: copilot--mode-setup ends with a didOpen carrying copilot--get-source, so the buffer leaves the instant the mode turns on and every later edit streams a didChange delta. A completion request itself carries only a position and a path, so gating copilot-complete would have protected nothing.

Not copilot-disable-predicates, which looks like the supported hook – a defcustom, stable across upgrades – but is consulted only in copilot--post-command-debounce, which decides whether to request a completion and never touches the sync. It would read correctly and stop nothing.

A fourth scope, buffer, says what Copilot actually reads. Ranked widest, so consent stored for it covers a later FIM window while a FIM window’s consent does not cover Copilot. It carries :windowed nil: intersecting the whole file with the +-1000 window would show a reassuring lie. Its bounds are widened, because copilot--get-source widens – narrowing does not protect you, and copilot-max-char is 100000, so anything smaller goes whole.

(copilot-mode . t) in safe-local-variable-values is inert, not a leak: a file-local variable naming a minor mode sets the variable without running the mode body, so no hooks and no sync. It did break [help:night/copilot-ensure], which tested bound-and-true-p and skipped real activation; [help:night/h-copilot-active-p] asks whether copilot--mode-setup actually hooked the buffer.

Still ungated: the upstream ellama-command-map has ~17 more ellama-stream call sites, gptel sends the Org ancestry of point, and chatgpt-shell its prompt text.

The symbol at point in shell buffers

sh-mode-syntax-table gives symbol syntax to ! % : . ^ ~ ,, and - inherits it from the standard table, so the symbol at point inside ${alert_color:-red} was alert_color:-red.

That is not just a yio bug: * search ([help:evil-symbol-word-search] is t here), M-., company-dabbrev-code and [help:night/brishz-doc-at-point] all read the same table, so whichm was being asked about alert_color:-red too.

[help:night/sh-punctuation-chars] demotes : and % to punctuation in the table itself, which fixes all of those at once. - and . stay symbol constituents so night-foo, --color and a.txt remain whole; ! ^ ~ , are the next candidates if ${x^^} or {a,b} annoy.

sh-mode buffers share that table object, so mutating it reaches open buffers, and sh-set-shell only replaces it for shells listed in sh-mode-syntax-table-input (only rpm is).

SMIE tokenizes shell by syntax class, so this could have moved indentation. It does not: 100 real files under ~/scripts reindent identically under the old and new tables. Re-run that comparison if the char list grows.

- cannot go in that list – night-foo, --color and a-b need it – so ${alert_markup:-plain} still yanked -plain. A syntax table cannot express that; [help:night/sh-syntax-propertize-operators] does it per position instead, marking an operator that directly follows a : or a name inside ${}. Both are unambiguous: no construct has : followed directly by -+?= as part of a name, and a variable name cannot contain -.

Anchoring on : rather than on the surrounding construct is why ${(f)v:-w} works, which no bash parser could read. That, an uninstalled grammar, and the fact that *=/=M-.=/=whichm read syntax rather than a parse tree, are why this is not tree-sitter.

[help:night/sh-syntax-propertize-setup-h] must flush (`syntax-ppss-flush-cache’): if anything propertizes during mode setup, syntax-propertize--done is already at point-max and the rules silently never fire. It also runs over existing sh-mode buffers at load, since a mode hook alone leaves long-lived buffers on the old behaviour.

Proxies and the daemon’s frozen environment

A process’s environment is fixed when it is created, so a daemon started from a shell with a proxy exported keeps sending through that proxy for its whole life. Deactivating the proxy in a terminal cannot reach an Emacs that already exists.

plz shells out to curl, which reads http_proxy and friends from the environment, so FIM, ement and the llm package follow the frozen copy. url.el consults url-proxy-services and can lazily import environment proxy settings into that cache. Packages can configure their own transport, so a working request elsewhere does not establish that curl’s route is correct.

A dead local proxy commonly produces curl error 7 in 0.0s: connecting to a closed loopback port is refused immediately. Confirm the route and listener, since a remote refusal can also be immediate. FIM adds a hint to check [help:night/proxy-status] when a relevant proxy variable is configured.

The message names no host or port because plz discards curl’s stderr and substitutes a fixed string from its plz-curl-errors table. curl does say which address it could not reach; that text never survives.

[help:night/proxy-status] reports the variables this Emacs actually has, grouped by value; C-u writes all of them, set or not, to a buffer; from Lisp it returns an alist. Both letter cases are reported, since curl reads the lowercase forms and some tools only the uppercase, and nothing keeps them in step.

It does not probe liveness – a diagnostic that can hang is worse than one that reports less. Check with lsof -nP -iTCP:<port> -sTCP:LISTEN; nothing listening means every request through it fails in 0.0s.

Restore an intended proxy service and retry. To deliberately change routes, update the daemon’s environment and URL.el’s cached proxy settings, or restart it with the correct environment. There is no automatic direct retry.

Same shape as the redis connection: state captured once at startup, never refreshed, invisible afterwards. When something works everywhere except in a long-lived Emacs, suspect startup-frozen state first.

Email with notmuch

notmuch reads, mbsync keeps ~/Mail in step with the IMAP server, msmtp sends. SPC o m opens the inbox, SPC o M the hello screen, J (in normal state; notmuch’s own j otherwise) plus a saved search’s key jumps to it. HTML mail is converted by pandoc and shown as Org. The account’s identity lives in the private ~/.config/night-mail/identity.env and the password in the login Keychain; email/bin/night-mail-render fills the templates in email/templates/ from the former.

A sync is email/bin/night-mail-sync: a lock, then notmuch new, whose pre-new hook turns the deleted and inbox tags into folder moves and runs mbsync -a, and whose post-new hook turns the server’s moves back into tags and dumps all tags as a backup. A LaunchAgent runs one every five minutes.

New unread inbox mail produces desktop alerts from a dedicated Night Mail.app with an envelope icon, automatically bootstrapped by the email installer through the shared shell notification-app builder. Alerts show subject, sender, and a multiline body excerpt, with no repeated app-name heading. Enable Allow Notifications for Night Mail in macOS System Settings and select Banners or Alerts; the bootstrapper requests permission but cannot approve it for you. ~/.config/night-mail/notifications.json controls ordered sender/subject regex groups with first-match precedence and a final catch-all. Settings include enablement, preview limits (preview_chars, preview_lines), sound, DND bypass (off by default), and per-group burst limits. Expanded messages displayed on screen in Emacs dismiss their alerts, even when point is on another message in the thread; batch summaries disappear once all their messages have been opened. Set night/mail-dismiss-notifications-on-open to nil to opt out. Clicking an alert raises the GUI Emacs server and opens that message, or a search for a batch’s messages ([help:night/mail-open-from-notification]). Initial imports stay silent; settings apply on every sync. See desktop notification settings.

[help:night/mail-sync] requests a fresh sync asynchronously through the macOS Aqua LaunchAgent, so Keychain lookup uses the graphical login context even when GUI Emacs was started in a detached session. It refreshes the notmuch buffers on success or partial indexing success; fetch failures report fetch FAILED instead of done. notmuch-poll is advised to use it. Mail tools explicitly select the login Keychain when looking up the password. Re-run email/install-night-mail.zsh after updating the LaunchAgent template.

Messages inside a thread appear newest-first. Localleader s toggles the original reply tree (night/notmuch-toggle-thread-order). Quoted HTML history starts collapsed behind a Quoted history · N lines button; nested quotes use greater-than prefixes within one Org block. night/mail-collapse-history and night/notmuch-thread-order control these defaults.

notmuch.el is pinned to the tag of the Homebrew notmuch in packages.el; [help:night/h-notmuch-version-check] warns when they drift. Re-pin after brew upgrade notmuch.

Custom tags stay on this machine (mbsync does not sync IMAP keywords), but they are keyed on the Message-ID and survive every rename and move.

About

My somewhat modular emacs config.

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages