8000 GitHub - imtan/darger: A small GPU-rendered Emacs-style text editor in Nim, with built-in SKK and a Lisp extension language · GitHub
[go: up one dir, main page]

Skip to content
imtanPublic

About

A small GPU-rendered Emacs-style text editor in Nim, with built-in SKK and a Lisp extension language

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

138 Commits

Folders and files

Repository files navigation

darger

日本語版: README.ja.md

A small GPU text editor for Windows 11 and Linux, built with Nim 2.2.10+. Windy owns the window, OpenGL context, input and clipboard; Pixie rasterizes glyphs into Boxy's GPU atlas. macOS compiles but is untested; agent-command can use claude -p --output-format text unchanged.

C-c a (agent-prompt) sends the current file and an instruction through agent-command for a one-shot edit, then opens a diff review. C-g cancels. C-c c (agent-chat) instead prompts Agent chat (<root directory name>): for a multi-turn conversation in the project's top directory: the nearest ancestor holding .git, or the file's directory if none exists. From a chat buffer it uses that chat's root; from any other path-less buffer it searches from the working directory, falling back to that directory.

Each root has an in-memory session and a Markdown transcript named *agent <root directory name>*, reachable with C-x b. C-c c Enter with no text opens the existing transcript without sending anything. Messages go unchanged to stdin; replies keep Markdown and code fences. Only one agent can run at a time across both commands. C-g records cancellation; failures appear in the transcript. A reply does not switch away from another buffer. Killing a transcript forgets the conversation and drops any pending reply. Transcripts never require saving, and no chat history is saved to disk. M-x agent-chat-new empties the current root's transcript and resets its session (refused while that conversation is running).

Chat replies appear live, with tool names and short summaries as Markdown quote lines and ... at the end while the process runs. Thinking and tool results are hidden. Only complete output lines are displayed; ordinary plain-text agent commands also work. A cancellation or failure keeps the output received so far. The cursor starts at the bottom and follows new output while it stays on the last line; move up to read without following. Updates do not add undo history. Both chat and one-shot agents show elapsed time (Agent running 1:23) in the echo line, with the latest tool activity for chat.

The defaults (override them in ~/.darger.el) are:

(setq agent-chat-command "claude -p --output-format stream-json --verbose --session-id {id}")
(setq agent-chat-resume-command "claude -p --output-format stream-json --verbose --resume {id}")

Every {id} is replaced with the session's random UUID. The resume command is used only after a successful reply; a failed first message retries the first command. With these defaults, claude -p can read the project, and refuses to edit files or run commands unless your Claude Code settings already allow them, since nobody can be asked. Adding --permission-mode acceptEdits to both variables allows edits. A buffer without edits of its own follows its file: when the agent (or anything else) changes it on disk, darger reads it again within a second (Reverted NAME, one undo step brings the old text back); a buffer you have edited keeps your text, and C-x C-s over a file changed on disk first asks NAME changed on disk; save anyway? (y or n). M-x revert-buffer reads the file again over your edits.

nimble build -d:release
.\darger.exe [file...]
nim r tests/tbuffer.nim
nimble build -d:release       # or: nimble install -d && nim c -d:release --outdir:. src/darger.nim
./darger [file...]
nim r tests/tbuffer.nim
nim r tests/tfileio.nim   # POSIX save behaviour
nim r tests/trecent.nim
nim r tests/tbuffers.nim
nim r tests/tcomplete.nim
nim r tests/tsyntax.nim
nim r tests/thtml.nim     # HTML layout for the web viewer
nim r tests/tfeed.nim     # RSS / Atom parsing
nim r tests/tfetch.nim    # curl downloads (needs curl; uses file:// URLs)
nim r tests/tupdate.nim   # self-update against a local bare repository (needs git)
nim r tests/torg.nim      # Org structure and org-roam notes
nim r tests/tterm.nim     # shell output text and the pty (needs /bin/sh)
nim r tests/tscreen.nim   # the terminal screen model and key sequences
nim r tests/tpanes.nim    # window layout
nim r tests/tshader.nim   # background shader sources
nim r tests/tgrep.nim     # ripgrep / grep output parsing and a real search
nim r tests/teagle.nim    # Eagle API parsing and link paths
nim r tests/tproject.nim  # project file listing (git and plain) and known projects
nim r tests/tmigemo.nim   # romaji -> kana -> dictionary words for migemo
nim r tests/tpictures.nim # PNG conversion cache for pictures pixie cannot decode
nim r tests/tchanges.nim  # files changed under a project between two snapshots
nim r tests/texecpath.nim # the login shell's PATH merged for GUI launches
nim r tests/tplaces.nim   # grep-mode style places lists
nim r tests/tledger.nim   # understanding ledger entries and the glossary
nim r tests/tfontinstall.nim # offline font catalog, SHA-256, verified ZIP installation
nim r tests/tnovel.nim    # vertical columns, kinsoku, scrolling and the font's vert table
nim r tests/tmonkey.nim   # fuzz: random input into Buffer, SKK, Lisp, HTML, the terminal, panes
tests/editormonkey/run.sh 1 16 300   # whole editor: random command sequences per seed (opens windows)

darger.app on macOS

nimble app builds the release binary and assembles ~/Applications/darger.app around a copy of it, registered with LaunchServices, so darger opens from Spotlight, the Dock and Finder's "Open With" (plain text, source code, Markdown and .org files) and with open -a darger file.org. The binary is copied rather than run from the checkout because macOS lets LaunchServices start an executable that lives under ~/Documents (or Desktop, Downloads) only after a privacy prompt that never comes for an unsigned build: the launch hangs before the first instruction, while the same binary runs fine from a terminal. The bundle's darger is a stub that records the checkout in DARGER_SOURCE and runs the copied darger-bin, so M-x darger-update still finds the checkout, and started from the bundle it rebuilds with nimble -y app, refreshing the copy; after a manual nimble build run nimble app again. Files opened this way arrive as an Apple event, not as arguments (AppKit also reports the command-line arguments that way; those are dropped); a patched copy of windy's macOS backend (patches/windy/macos/platform.nim) collects them and the editor visits them, into the running darger when there is one, instead of the dashboard. Launched this way the window also takes focus, which a bare ./darger started from another program does not get on recent macOS; for a script, open -n -W -a darger --args FILE starts a new instance with FILE and waits until it closes.

When started from the Finder, Spotlight or the Dock, darger takes the PATH of the login shell ($SHELL -l -c 'printf %s "$PATH"') and adds the usual tool directories (Homebrew, ~/.local/bin, ~/.bun/bin, …) that exist, so agent-command (claude), rg and git are found as in a terminal; macOS otherwise gives applications only the bare system PATH.

Updating darger

M-x darger-update checks GitHub for new commits and, when there are any, pulls them and rebuilds the editor in the background. It works on the checkout holding the running executable (override with (setq darger-source-directory "~/src/darger")), which must be a git clone with darger.nimble. The steps are git fetch, a comparison with the upstream branch, git merge --ff-only and darger-build-command (default nimble -y build -d:release; set it to the nim c line above if your nimble is too old). The echo line shows Updating darger checking GitHub 0:03 C-g:cancel (then pulling, building); C-g stops the running step. Every step's command and output goes to a *darger update* buffer (C-x b), without switching to it. The result is one of darger is up to date, darger updated to <hash> (N commits): restart to use it, or darger update failed: .... Local commits that GitHub lacks are never merged over: with no new upstream commits they count as up to date, otherwise the update stops with local commits diverge from GitHub. Git never prompts for credentials (GIT_TERMINAL_PROMPT=0). On Windows the running darger.exe is renamed to darger.old.exe before the build, moved back if the build fails, and deleted at the next start. (darger-update) in ~/.darger.el checks at every start and reload.

Nimble older than 0.24 (e.g. the 0.22.x bundled with Nim 2.2.10) fails with cannot open file: opengl; use the nim c line instead.

On Linux windy uses X11 + GLX only, so Wayland sessions need XWayland. libX11, libXext, libXcursor and libGL.so.1 are loaded with dlopen at startup (ldd does not list them): Arch libx11 libxext libxcursor libglvnd mesa xorg-xwayland, Debian libx11-6 libxext6 libxcursor1 libgl1. config.nims swaps in patched copies of windy 0.5.0's X11 backend from patches/windy via patchFile on Linux only (see patches/windy/README.md); windy stays pinned to 0.5.0.

The window has no system title bar or frame; darger draws its own title bar, one text row high: the buffer name in the middle and minimize, maximize and close buttons (on the left on macOS, on the right elsewhere). Dragging anywhere with the left button moves the window, dragging its right or bottom edge (6 px) or its bottom right corner (32 px) resizes it, and a double click on the title bar toggles maximized, which covers the whole screen including the taskbar (on macOS, the screen beside the menu bar and the Dock). A click in the text selects that window and puts the cursor there (the mark is deactivated); the wheel scrolls the window under the pointer three lines a notch, moving its cursor only as far as needed to keep it in view, as Emacs does. The mouse does nothing else. On macOS, windy 0.5.0 registers its borderless window's canBecomeKeyWindow override under the wrong selector, so such a window never takes keyboard focus; the patched backend above registers the real one (re-check on any windy bump). macOS itself takes C-SPC for "Select the previous input source" (System Settings > Keyboard > Keyboard Shortcuts > Input Sources) and C-arrows for Mission Control; turn those off, or bind mark elsewhere in ~/.darger.el. Option types special characters on many keys there, so on macOS the command key is Meta (meta-modifier is "cmd"; the system still keeps Cmd-Tab and Cmd-Space); (setq meta-modifier "alt") in ~/.darger.el makes Option Meta, as elsewhere. C-x C-c quits. On Windows the build is a GUI program (--app:gui in config.nims), so no console opens behind it and messages on stderr are not shown.

Without a file, the editor opens *scratch* with a dashboard shown over it, in the spirit of nvim's start screens: the DARGER logo (drawn as filled cells with a shadow, so it looks the same in every font) with a line under it, a centred menu of one-key commands with their real bindings at the right (j journal entry, J journal quick add (q closes the dashboard, so the quick line takes the capital), t capture a task, a agenda, u understanding, f find file, p projects, g agent chat, s shell, m manual), up to 10 recent files labelled 1-9 and 0 (HOME shown as ~, long paths cut from the left), and a footer with the date, the file count and the understanding debt. The layout follows the window's size. C-n / C-p, arrows or k / j-less moves select an entry (the selected line is highlighted), Enter runs or opens it, a label or menu key does so at once (a file that no longer exists is reported and the dashboard stays), q, C-g or Escape close it to *scratch*, and any other bound key (C-c u, C-c p, C-x g, M-x, ...) closes the dashboard and runs as usual. M-x dashboard opens it and a key bound to dashboard also closes it; none is bound by default. The list lives in ~/.darger-recent (one absolute UTF-8 path per line, newest first, at most 30, written atomically): every existing file visited from the command line, C-x C-f or find-file, and every file saved, is moved to the top. *scratch* is never recorded, and errors reading or writing the list are ignored.

F1 (or M-x help) toggles the built-in manual (Japanese) outside prompts and review. In it, navigation keys and C-v / M-v / PageDown / PageUp / C-l scroll; q, Enter, Escape, C-g or F1 close it and C-x C-c quits. The edited buffer is never touched.

UTF-8 files retain CRLF/LF on save; finding a nonexistent file creates an empty buffer bound to that path. A UTF-8 BOM is kept as a file prefix, not an editable character. Save writes a unique temporary file beside the target, checks and flushes it, then replaces the target; on any failure the temporary file is removed, the original stays intact and the buffer stays modified. Directory targets are rejected. On Linux/macOS read-only files are refused, a symlink is written through to its target, the file mode (and owner, when permitted) is kept, and ~ expands in the find/write prompts.

Set $env:DARGER_FONT = 'C:\path\to\monospace.ttf' (POSIX: DARGER_FONT=/path/mono.ttf DARGER_FALLBACK_FONTS=/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc ./darger) to choose fonts. Default primary: Iosevka Nerd Font Mono first on every OS (IosevkaNerdFontMono-ExtraLight.ttf, then -Light, then -Regular; in /usr/share/fonts/TTF and ~/.local/share/fonts on Linux, the per-user and C:\Windows\Fonts folders on Windows, ~/Library/Fonts on macOS), then per-user HackGen Console NF, Cascadia Mono, Consolas, Courier New (Windows), then DejaVu Sans Mono, Liberation Mono, Noto Sans Mono, Adwaita Mono (Linux) and Menlo paths; on Linux fc-match monospace is the last resort. DARGER_FALLBACK_FONTS accepts TTF/OTF/TTC paths separated by ; on every OS; by default the editor tries BIZ UD Gothic, Yu Gothic, MS Gothic, Malgun Gothic, Segoe UI Emoji and Segoe UI Symbol on Windows, and Noto Sans CJK, Noto Sans Symbols 2 and Hiragino elsewhere, plus fc-match picks for Japanese and Korean on Linux. Colour emoji fonts (CBDT, sbix) are unsupported. Unreadable fonts are skipped with a message on stderr. TTC files use their first face. ~/.darger.el can choose fonts too: (set-font "path") makes the file the primary font (over DARGER_FONT and the defaults), keeping the fallbacks, and (set-fallback-fonts "path;path") replaces the fallbacks; both work at any time (M-: too), and a font that cannot be loaded leaves the current fonts in place and reports Cannot load font … in the echo area.

Text is 16 pixels times the window DPI scale times the zoom, with aligned fallback baselines. DARGER_SCALE (e.g. 2) overrides the DPI scale; on Linux, when windy reports 1.0 (X11 without Xft.dpi, XWayland), GDK_SCALE is used if set. F2 starts a sticky zoom (like Emacs hydra-zoom): g zooms in (x1.1), l out, 0 resets, and the keys repeat without F2 until another key leaves the zoom and runs normally (C-g just leaves). The zoom stays within 50%..400% and the echo area shows it. Like Emacs' internal-border-width 32, the text area, mode line and echo line are inset by a 32-pixel-times-DPI-scale border (not zoomed) that shrinks in tiny windows.

The default colours are modus-vivendi (black background, as in Emacs' modus-themes); (load-theme "catppuccin") switches to the previous Catppuccin Mocha colours and (load-theme "modus-vivendi") back, at any time (M-: or ~/.darger.el). Unknown names report No such theme: ….

For a file inside a Git repository, the selected window’s mode line also shows git:<branch> (a short commit hash for detached HEAD). Branch information is checked at most once a second, without starting Git, including in worktrees.

Syntax highlighting is built in (no external grammars or libraries, so it works the same on Windows). The language comes from the file extension, case-insensitively, or a #! line for Python, Ruby and shell scripts, and is shown in the mode line: Nim (.nim .nims .nimble), GLSL (.glsl .frag .vert .fs .vs), Python (.py .pyi), Ruby (.rb .rake .jbuilder .rabl .gemspec .ru .thor, Gemfile, Rakefile, Capfile, Guardfile, .pryrc .irbrc), GDScript (.gd), JS/TS (.js .mjs .ts .tsx .jsx), C/C++ (.c .h .cpp .hpp .cc), Rust (.rs), Go (.go), Shell (.sh .bash .zsh), Lisp (.el .lisp .scm .clj, .emacs), JSON, TOML, YAML (.yaml .yml), Markdown (.md .markdown), ERB (.erb .rhtml: HTML tags, attributes and comments, with the Ruby inside <% %>, <%= %> and <%# %> highlighted as Ruby; also .yml.erb), HTML (.html .htm .xhtml), Vue (.vue), Svelte (.svelte), XML/SVG/plist (.xml .svg .plist .xsd .xsl .xslt), CSS (.css), SCSS (.scss), Sass (.sass), Less (.less), SQL (.sql, case-insensitive keywords), Haml (.haml), Slim (.slim), Dockerfile (Dockerfile, Dockerfile.*, *.dockerfile), Makefile (Makefile, makefile, GNUmakefile, *.mk), INI/conf/env (.ini .cfg .conf .env .env.* .properties, .editorconfig .gitconfig .npmrc .yarnrc), Procfile (INI-like keys), GraphQL (.graphql .gql) and Org (.org); Vue/Svelte use HTML highlighting without script/style sub-highlighting. Gemfile.lock and .rspec remain Text; anything else, and *scratch*, is Text. It is re-detected after visiting or writing a file. Comments, strings, keywords, function and variable names, types, constants/numbers and builtins take the theme's faces (for modus-vivendi: fg-dim, blue-warmer, magenta-cooler, magenta, cyan, cyan-cooler, blue-cooler and magenta-warmer, as in Emacs). The tokenizer is table-driven and approximate: block comments, multi-line strings, Markdown fences and Org #+begin_src blocks carry across lines, but there is no real parsing. The whole buffer is re-highlighted after each change (a few ms for 3000 lines); the help, dashboard and review overlays stay plain.

Like show-paren-mode, the bracket after point, or else the closing bracket before it, is highlighted together with its partner: (), [], {} and ()「」『』【】. Brackets in strings and comments only pair with each other. Each kind is counted on its own, so a mismatched ( [ ) ] still pairs, and the search stops 5000 lines away.

File buffers and *scratch* show line numbers in a gutter on the left: right-aligned, at least 3 digits wide, dim (the theme's lineNo) with the cursor's line bright (lineNoNow), then 2 empty cells. The overlays (help, dashboard, buffer list, filer, web page, RSS list, review) have none.

CJK and the supported emoji ranges occupy two cells; combining marks, variation selectors, joiners and skin-tone modifiers occupy zero cells. U+2600..27BF stays one cell like Emacs. Emoji use monochrome outlines without shaping: ZWJ sequences, flags and keycaps render as their parts. Missing glyphs appear as hollow boxes. File tabs display at eight-cell stops; Tab inserts spaces to the next four-cell stop. Long lines scroll horizontally, keeping the entire cursor block visible. IME composition appears inline with a highlighted background and moves the displayed suffix right, without changing the buffer until text is committed. Native IME works on Windows/macOS only: windy's X11 backend has no XIM, so fcitx/ibus, dead keys and Compose do not work on Linux; use the built-in SKK (C-\ or C-x C-j).

C- means Ctrl, M- means Alt; either left or right modifier works.

Action Keys
Character / line movement C-f / C-b / C-n / C-p, arrows
Line start / end C-a / C-e, Home / End
Words / indentation M-f / M-b, M-m
Buffer start / end M-< / M->, C-Home / C-End
Pages / recenter C-v / M-v, PageDown / PageUp; C-l cycles center/top/bottom
Go to line M-g g / M-g M-g
Jump to a matching line (consult-line) M-g l
Language server: completion / hover C-M-i (also automatic) / C-c h
Language server: definition / back / references M-. / M-, / M-?
Language server: format the buffer C-c =
Insert / delete Enter / C-m / C-j, Tab / C-i, Backspace / C-h, C-d / Delete
Open line / transpose C-o / C-t
Kill line / word / backward word C-k / M-d / M-Backspace
Word upper / lower / capitalize M-u / M-l / M-c
Toggle mark / exchange / select all C-SPC or C-@ / C-x C-x / C-x h
Kill / copy region C-w / M-w
Yank / rotate last yank C-y / M-y
Undo C-/, C-_ (Ctrl+Shift+Minus), C-x u
Find / save / save as / exit C-x C-f / C-x C-s / C-x C-w / C-x C-c
Split window below / right C-x 2 / C-x 3
Other window / delete window / delete other windows C-x o / C-x 0 / C-x 1
Toggle file tree / select tree or return to file C-x t t / M-0
Enlarge window / wider / narrower / balance C-x ^ / C-x } / C-x { / C-x +
Buffer / file in the other window C-x 4 b / C-x 4 f
Window in a direction (windmove) C-c left / right / up / down
Incremental search forward / backward C-s / C-r; repeat for next / previous
Cancel C-g
SKK Japanese input on / off C-\ or C-x C-j
Manual (help) on / off F1
Dashboard (recent files) on / off M-x dashboard
Pull and rebuild from GitHub M-x darger-update
Zoom in / out / reset (sticky) F2 g / l / 0
Open the init file ~/.darger.el C-c ,
Web browser (eww) / RSS reader C-c w / C-c r
GLSL buffer: shader apply / picture only / clear C-c C-c / C-c C-o / C-c C-k
Shell buffer / interrupt / end-of-file C-c s / C-c C-c / C-d
Claude Code: start or focus / toggle window / new session C-c C c / C-c C t / C-c C n
Claude Code: send region / send file C-c C r / C-c C f
Org: TODO / checkbox / new item C-c C-t / C-c C-c / M-Enter
Org: promote / demote / move subtree up / down M-Left / M-Right / M-Up / M-Down
Org: headline list / open link / timestamp C-c C-j / C-c C-o / C-c .
Org: fold the headline / the whole file TAB / Shift-TAB
Org: refile / schedule / deadline / archive C-c C-w / C-c C-s / C-c C-d / C-c C-x C-a
Org: priority up / down (on a headline) Shift-Up / Shift-Down
Org: journal entry / quick add / capture C-c j / C-c q / C-c t
Org: image from Eagle by keyword / by folder C-M-e / C-c e
Org: paste the clipboard image C-M-y
org-roam: find / insert link / backlinks / today C-c n f / C-c n i / C-c n l / C-c n d
org-roam: today's daily with a time entry C-c n j
org-roam: capture a note / add a file tag C-c n c / C-c n t
org-roam: graph in the browser C-c n g
Understanding buffer (debt per branch) C-c u
Grep the Org directory (consult-ripgrep) C-c n s
Project: find file / switch project / project buffers C-x p f / C-x p p / C-x p b
Projectile hydra (f p b s r v d l q) C-c p
Ask the agent where (places with a preview) C-c l

Search ignores case for all-lowercase queries. Typing extends the current match; a search that hits the buffer edge shows Failing I-search, and repeating C-s/C-r then wraps (Wrapped). Repeats never overlap the current match; C-s/C-r with an empty query reuses the last search. Backspace returns to the previous search state. Enter accepts the match, any other command ends the search at the match and runs, and C-g restores the original point. Prompts other than incremental search float in a popup near the top of the window (M-g l's at the bottom; on the bottom line when the window is too small); messages stay on the bottom line. Minibuffers support insertion, Backspace, C-a/C-e/C-f/C-b, C-k, C-y, Enter and C-g; the Find/Write/Agent/buffer/line prompts and incremental search also take C-\ or C-x C-j (any key bound to skk-mode) to toggle SKK. Answer confirmation prompts with y or n, then Enter. Backspace and Delete remove an active region without killing it. Killing/copying updates the clipboard; C-y pastes the clipboard when it holds text copied elsewhere since the editor last set or took it (as Emacs does: the text joins the 60-entry kill ring, so M-y still reaches the earlier kills), else the kill ring; C-c C-y (clipboard-yank) always pastes the clipboard. Undo keeps 200 snapshots and groups runs of non-whitespace self-inserts. Snapshot undo and flattened edits favor small files.

Writing with the agent

C-c G sends the active region (otherwise the whole buffer) to agent-command. s b always uses the whole buffer. Auto translation chooses English when the text contains hiragana, katakana or kanji, and Japanese otherwise.

Key Command Action
C-c G t t agent-translate-auto Auto Japanese ↔ English
C-c G t j agent-translate-to-japanese Translate to Japanese
C-c G t e agent-translate-to-english Translate to English
C-c G p p agent-proofread Proofread and refine
C-c G p e agent-proofread-en Proofread English
C-c G p f agent-rewrite-formal Formal business tone
C-c G p c agent-rewrite-casual Casual, friendly tone
C-c G s r agent-summarize-region Bullet summary
C-c G s b agent-summarize-buffer Summarize the whole buffer
C-c G s k agent-extract-keywords Comma-separated keywords
C-c G d t agent-suggest-title Three title candidates
C-c G d n agent-draft-blog-from-notes Markdown blog draft from notes
C-c G d d agent-draft-blog-interactive Ask for a topic, then draft
C-c G g agent-chat Existing agent chat

Translations, proofreading and tone changes propose replacing the selected text. Summaries, keywords and titles propose insertion immediately after it (at the end for a whole buffer). Both open an Agent writing review: y takes a hunk, k keeps the original, and q applies decisions. Blog drafts open in *agent draft* in the other window without changing the source; d d uses the entered topic and selected text as context. If the source changes while waiting, the result is saved in *agent draft* instead of opening a stale review. Commands refuse to start while an agent is running or when agent-command is empty.

Agent prose in *agent draft* and *explain* wraps to the destination window’s width when shown (minimum 20 cells). Japanese characters count as two cells; wrapping respects Japanese opening/closing punctuation, preserves blank lines, and gives - , * and numbered list items a hanging indent. The original text is retained; resizing alone does not reflow an already displayed buffer.

Review

A review shows the buffer with another version's changes as hunks, the way the agent's edits are reviewed: removed lines in red, added in green, the current hunk brighter. Keys: y takes the proposed version of the hunk, k keeps the buffer's, u undoes the decision, n / p move, l lists the hunks (3 -1 +2 [taken] first line) to jump to one, a takes every pending hunk and finishes, A keeps every pending one and finishes, w applies the decisions made so far and keeps reviewing the rest, q or Enter applies the decisions (pending hunks stay as they are), C-g cancels, ? lists the keys. Within a pending hunk, the k-th removed and added lines are compared and the part that differs is highlighted brighter, like diff-refine-hunk. The echo line names the source, the hunk and its -removed +added line counts.

| toggles two read-only panes: *review: before* on the left and *review: after* on the right, with blank padding to align the lines. n / p move both panes to the current hunk; y / k / u decide, a / A / q finish, C-g cancels, and ? shows help. Press | again to return to the unified review. A split made by this toggle is closed when leaving; existing panes are restored.

e opens the current hunk's proposed lines as plain text in an editable *hunk* buffer in the other pane. C-c C-c replaces those lines and restarts the decisions; C-c C-k cancels the edit. Both return to the review. An empty replacement removes the additions. This key is unavailable in read-only comparisons.

r asks the agent for a change to the current hunk: the instruction goes to agent-command with that hunk and the whole proposal, and the agent's complete new text replaces the proposal (decisions start over, the hunk position is kept), so a review goes round review, feedback, new proposal without leaving the buffer; (review-request-changes "…") does the same from Lisp. Besides the agent (C-c a), these open a review:

  • M-x diff-buffer-with-file: the buffer against its file on disk, so an agent's or another editor's write is merged hunk by hunk with your own edits. Saving over a file that changed on disk now offers r for the same review.
  • M-x vc-review: the buffer against the file as committed (git show HEAD:…); taking a hunk reverts it to the committed version.
  • (review-file "other.txt"), or M-x review-file with a prompt: the buffer against another file, like ediff-buffers.
  • When a file changed on disk while its buffer has edits, the echo line says so once and offers that review; with (setq auto-revert-review t) the review opens by itself when that buffer is current and nothing else is open. Buffers without edits still follow the file.
  • After an agent chat turn (C-c c), the files it changed under the chat's root are counted in the echo line, and M-x agent-changes lists them: Enter opens the file in a review against the buffer's own text when it differs from the disk, else against git HEAD, so an agent's edits across the project are gated the same way.

Understanding ledger

Code taken from an agent without understanding it is a debt; the review keeps the books. x asks the agent to explain the current hunk (what it changes, why, what to watch out for, and the domain or technical terms it relies on as - term :: definition items); the explanation shows in *explain*, wrapped to the other window’s width, and the hunk counts as understood. When a review of an agent's or the disk's changes ends, every hunk taken becomes an Org entry in understanding-ledger (default ~/.darger-understanding.org, nil to disable): * TODO file:line taken without explanation :debt:project: with the hunk as a diff block, or * DONE … :understood: with the explanation, so the agenda and TODO tools track what is still to be understood. M-x understanding-debt lists the open entries and Enter visits the spot (M-x understanding-ledger opens the file). The terms from explanations collect in glossary-file (default ~/.darger-glossary.org, one - term :: definition per line, existing terms kept) and M-x glossary looks one up. X asks the agent a question of your own about the hunk (the explanation so far is sent along); the answer joins the explanation in *explain* and in the ledger, so a hunk can be understood through a short dialogue. U attests that you understand the hunk without an explanation; it is recorded as DONE too. Nothing is ever demanded: the ledger is a self-kept account of the work you still want to understand.

M-x understanding-repay explains the nearest open debt for the current file, across all branches, using its recorded hunk and the current file context (40 lines either side). The answer appears in *explain*, the entry becomes DONE with the explanation, and its terms join the glossary. In *understanding*, x visits and repays a debt row; Enter still just visits it.

Entries carry the git branch (:BRANCH:, also a tag) and the file's last-change commit (:REV:), so the debt is kept per branch: M-x understanding-debt lists the current branch's open entries (understanding-debt-all every branch's). M-x branch-debt turns the branch's own work into debt: the files changed since the merge base with the base branch (main, master or develop, or understanding-base-branch) that no DONE entry of this branch attests to at their current commit, with their +/- counts and (uncommitted); Enter opens the file in a comparison read base to branch (removed lines are the base's, added ones yours), where nothing is taken: x explains, X asks, U attests, and q records every hunk (understood or not) in the ledger. Run it again and the attested files are gone; before a merge it answers "what did I do here that I do not yet understand?".

C-c u (understanding) opens the *understanding* buffer, the ledger as a view to work from (a magit status for understanding debt), an ordinary read-only buffer with its own keys, so C-x o, splits and C-x b work as usual: the branch against its base with the files still to understand (+n -m, (uncommitted)) and the ones understood, the open debt entries on the branch, and the glossary size. Enter on a file opens the base comparison and the buffer comes back when it closes; Enter on a debt entry goes to the spot; U marks a whole file understood; o opens the ledger file; g rebuilds; q leaves it. The first time in a repository the buffer asks which branch to understand the work against (local and remote-tracking branches, the usual main / master / develop first) instead of silently comparing with master; the choice is remembered per repository in ~/.darger-understanding-bases, b changes it, and (setq understanding-base-branch "name") fixes it for every repository. The popup lists (understanding-debt, branch-debt) stay for quick jumps.

The review's keys are also commands (review-take, review-keep, review-next, review-previous, review-finish, review-explain-hunk, review-attest), so an init file or an agent can drive a review.

Git (magit, minimal)

C-x g (magit-status) opens the read-only *magit* buffer for the current file's repository: HEAD, upstream ahead/behind counts, untracked / unstaged / staged files, and the ten recent commits. n / p move, g refreshes, q leaves (the other window, or the previous buffer), and ? lists the keys.

  • s / u: stage / unstage the file. On an untracked or unstaged heading, s stages all changes (git add -A); on the staged heading, u unstages all.
  • Enter: visit the file; files with unstaged changes open a review against HEAD (git HEAD, as in vc-review). Accepting a hunk restores its HEAD version in the buffer. Untracked and staged-only files are just visited.
  • k: discard an unstaged file's changes after a y/n prompt (restore the index version).
  • c: prompt for a commit message; P: push; F: pull with --ff-only.

Git operations run synchronously on saved files; save edits before staging. The status refreshes after operations and keeps the cursor row where possible.

Remote files over ssh (TRAMP)

Use C-x C-f with /ssh:HOST:/absolute/path or /ssh:user@host:~/file. C-x C-s and C-x C-w save remotely, preserving UTF-8 BOM and CRLF; saves use an adjacent temporary file and rename, preserving existing file permissions. Directories open in Dired; completion caches remote names for three seconds. The mode line shows ssh:HOST, and recent files include remote paths. Remote Dired supports browsing, opening, parent navigation and refresh; its mkdir, rename and trash operations are unavailable. Auto-revert, local Git lookup, project enumeration and local language servers skip remote buffers.

SSH runs synchronously in batch mode: configure keys/ssh-agent and host keys before opening files. Connections use ControlMaster with a 120-second lifetime and an 8-second connection timeout. Errors appear in the echo area; failed saves leave edits marked modified. (setq tramp-ssh-options "-p 2222") adds SSH options; M-x tramp-cleanup closes control masters used by this session. Directory listings require common BSD/GNU ls -lA output; filenames containing newlines and symlink directory completion are not supported. Remote search and remote LSP are planned for a later slice.

File tree (treemacs)

C-x t t (treemacs) toggles a left file tree, keeping the file window selected. M-0 (treemacs-select-window) selects the tree; M-0 or q in the tree returns to the file window. The root is the current file's project root (nearest .git), or its directory, or the working directory. SSH paths are not supported.

In the tree, n / p move, Enter / f open a file in another window or toggle a directory, and Tab / Space toggle a directory. ^ / u move to the parent row; at the root they move the root up (treemacs-root-up). C-c C-r makes the selected directory the root. g refreshes, H toggles hidden entries, and ? shows help. Directories come first, with case-insensitive sorting. Single-directory chains are displayed as a/b/c; actions on that row target the last directory.

Settings in ~/.darger.el: treemacs-width defaults to 35 cells, treemacs-follow-mode to t (reveal the current file), treemacs-collapse-dirs to 3 (maximum directory names per compressed row; 0 disables compression), treemacs-show-hidden and treemacs-stay-on-open to nil. Set the latter to t to keep the tree selected after opening a file. treemacs-is-never-other-window defaults to t: other-window and other-window file/buffer commands skip the tree when another destination exists.

With a Nerd Font as the editor font (Iosevka Nerd Font Mono is picked up from ~/Library/Fonts when installed) every row carries a file-type icon: folders by state, Nim, Ruby, ERB, JavaScript, TypeScript, JSON, YAML, Markdown, Org, HTML, CSS, shell, SQL, Docker, images, archives and more, coloured by type. (setq treemacs-icons nil) turns them off; a font without the glyphs falls back to the plain ▸ / ▾ markers.

Filer (Dired)

C-x d prompts for a directory, starting in the current file's directory (or the working directory for scratch), and so does M-x dired; (dired "path") and directory command-line arguments open the filer directly. In the C-x d prompt Enter opens the directory typed so far, so C-x d Enter lists the current one; Tab completes. In C-x C-f, Enter on a directory candidate keeps descending as before, and only a directory with nothing to pick opens the filer.

The read-only overlay includes hidden files, with directories first and names sorted case-insensitively. Rows show type (d, l, -), size, modification time and name; directory names have a trailing / and are highlighted.

  • n / C-n / down and p / C-p / up move between entries; C-v / M-v page, M-< / M-> jump to the first / last entry.
  • Enter / f opens a file or enters a directory; ^ goes to the parent and selects the directory just left. g refreshes, retaining the selected name.
    • creates a directory (including parents); R renames or moves an entry, refusing an existing destination. Both use path-completing prompts.
  • D asks Trash NAME? (y or n); y uses the Windows Recycle Bin or Linux gio trash. Errors are shown in the echo area; there is no permanent-delete fallback. Trash is unsupported on other platforms.
  • q / C-g / Escape closes the filer. C-g / Escape cancels a create/rename prompt and returns to the listing.

Shell

C-c s (M-x shell) opens *shell*, a comint-style shell buffer (macOS / Linux): shell-file-name (default $SHELL, else /bin/sh) runs on a pseudo-terminal in the current file's directory with TERM=dumb, INSIDE_DARGER=1 and PAGER=cat. Its output, with escape sequences, carriage returns and backspaces applied, fills the top of the buffer in the eight ANSI foreground colours (ESC[31m … ESC[37m, bright 90-97, 38;5;n for the first 16; other attributes and backgrounds are dropped, true colour shows in the default colour) from the theme's ansi palette; what you type after it is a command, sent with Enter (the pty does not echo, so the line stays as typed and the output follows), C-c C-c interrupts (SIGINT), and C-d at an empty command sends end-of-file. The process learns the size of the window showing *shell* ($COLUMNS, $LINES) and follows splits, resizes and zooms. The output keeps about 400 KB. The buffer never needs saving; killing it, or quitting, hangs the shell up, and C-c s after the shell exited (Process shell finished) starts a new one.

M-x term is the other kind of terminal, like Emacs' term in char mode: *terminal* shows a real screen (src/termscreen.nim: cursor addressing, erasing, scrolling regions, the alternate screen, colours, with the lines that scrolled off above it) of the size of its window, running the shell, or (term "command args") that program, with TERM=xterm-256color, so full-screen programs such as claude, top or vim work. Every key goes to the program as a terminal would send it (Enter, arrows, function keys, C-a … C-z, M-x as ESC x, Escape, Shift-Tab), and typed characters too, so SKK is out of the way there; only the editor's prefixes C-x and C-c (and M-x, M-:, C-g) stay with darger, so C-x o, C-x b and C-c s still work, and C-c C-c sends a C-c to the program. C-c C-j (term-line-mode) switches to line mode as in Emacs term: the buffer is ordinary text, so every editing key and SKK work and the scrollback can be searched and copied; what is typed after the screen's text is kept across new output and Enter sends it to the program (which echoes it). C-c C-k (term-char-mode) gives the keys back to the program, dropping unsent input; the mode line shows [Line] or [Char], and (term-send-input) sends the typed input from Lisp. An init file that opens a file or a terminal is no longer covered by the dashboard. The buffer's cursor is the terminal's; wide (CJK) runes take two cells; the eight ANSI colours show as foreground and background, underline as an underline, inverse video by swapping the two. Bold text uses the primary font's Bold weight when installed (SemiBold, then Medium as fallbacks); without one (SF Mono, fallback fonts) the glyph is filled twice a pixel apart, as terminals synthesise bold. The terminal has no line-number gutter, so the program owns every column of its window. A program that asks for mouse reports (?1000, ?1002, ?1003, SGR ?1006) gets clicks, releases and wheel notches on its cells instead of darger's cursor placement and scrolling; on the alternate screen without reports, the wheel sends arrow keys, as terminals do for less or vim. Killing the buffer or quitting hangs the program up.

Claude Code runs in such a terminal with the bindings of the claude-code.el setup it replaces: C-c C c (claude-code) starts claude-command (default claude) in the project root (the nearest .git above the file) in *claude-code: <project>*, shown in a window on the right (splitting when there is one window) and selected, or just goes there when it already runs; C-c C t (claude-code-toggle) hides that window or shows it again; C-c C n (claude-code-new) kills the session and starts a fresh one; C-c C r (claude-code-send-region) pastes the region into it (bracketed paste, so its newlines do not submit) and presses Enter; C-c C f (claude-code-send-file) sends このファイルを見てください: <path relative to the project> and Enter.

Window transparency

(setq frame-alpha 0.85) makes the whole window that opaque, text included, like Emacs' alpha frame parameter (1 is opaque, the lowest accepted value 0.1); it takes effect at once, also when changed in ~/.darger.el while darger runs. macOS only so far (the window's alpha value); on Linux and Windows the setting is read and ignored. For a see-thr 8 ough bac 10BC8 kground with crisp text, the background shader's dim below is the other way.

Background shader

A GLSL fragment shader can run under the text, as the NeoEmacs frame shader did: (background-shader "~/.emacs.d/bg-shader.glsl") in ~/.darger.el or at M-: compiles the file and draws it over the whole window every frame, with the theme's background laid over it at background-shader-dim opacity (default 0.6; 0 shows the picture alone). The file is either Shadertoy-style, a mainImage(out vec4 fragColor, in vec2 fragCoord) with iResolution, iTime, iMouse and iFrame available, or a whole #version 410 core fragment shader with its own main and out. In a GLSL buffer (.glsl .frag .vert .fs .vs, highlighted) C-c C-c applies the buffer's text, C-c C-o toggles between the picture alone (no text, mode lines or echo line, only the title bar; keys still work, so C-c C-o brings the text back) and the dimmed background, and C-c C-k clears it; M-x background-shader (the buffer), background-shader-toggle and background-shader-clear do the same anywhere. Saving the applied file, or changing it on disk, re-applies it, so a shader can be written while it runs. A compile error leaves the previous shader running and puts GL's log, with the file's line numbers, in the echo line and a *shader* buffer (C-x b). While a shader runs darger redraws every frame instead of only on input. shaders/ holds three samples to start from: gradient.glsl, plasma.glsl and stars.glsl.

Web browser (eww)

C-c w (eww) prompts URL or search: . A URL is loaded as typed; a word with a dot (example.com) gets https://; anything else is searched with eww-search-prefix (default DuckDuckGo's HTML version). Enter with an empty prompt reopens the last page. (eww "url") and M-x eww do the same from Lisp; C-c w on a page closes it.

Pages are downloaded with fetch-command (default curl -sSL --max-time 30 --max-redirs 10 --compressed -A darger/0.1; curl is part of Windows 10/11, macOS and nearly every Linux) in the background, so the editor stays responsive and Loading url 0:03 C-g:cancel ticks in the echo line. The body goes to a temporary file, not a pipe; downloads stop at 20 MB. There is no cookie jar, JavaScript, form submission or image display.

The page is laid out by a built-in tolerant HTML tokenizer (no external libraries): paragraphs, headings (coloured like function names), lists (•, 1.), <pre> kept verbatim, block quotes indented, tables as rows with two spaces between cells, images as [alt], <hr> as a rule, scripts and styles dropped. Text wraps to the window width (at most 100 cells) at spaces and between CJK characters, and is laid out again when the window or zoom changes. Links are blue (the string face). The first row shows the URL; the mode line shows the <title>. The charset comes from the Content-Type header, a BOM, <meta charset> or the XML declaration (Shift_JIS, EUC-JP, ISO-2022-JP, GBK, Big5, EUC-KR, Windows-125x ... via the OS converters); invalid bytes become U+FFFD. A feed URL (RSS/Atom) is shown as a list of its entries; text/plain and JSON appear as is; other types (images, PDF) are not shown (& opens them outside). An HTTP error page is shown with HTTP 404 in the echo line.

  • C-n / C-p / n / p / arrows move by line; C-f / C-b within it; C-v / M-v / SPC / Backspace page; M-< / M-> jump to the top / bottom; C-l recenters.
  • Tab / Shift-Tab move to the next / previous link. Enter / f follows the link under the cursor, or else the first link on the cursor's line.
  • l / r go back / forward (pages are fetched again, the cursor line is kept); g reloads; G prompts for another URL or search.
  • & opens the link at point (else the page) in the system browser; w copies that URL to the kill ring and clipboard; v opens the raw HTML in a *web source* buffer.
  • q / Escape / C-g close the page (C-g first cancels a download); F1 opens the manual; C-x C-c quits. The page stays and C-c w Enter brings it back.

RSS

C-c r (rss) opens the reader, an elfeed-style list of every subscription's entries, newest first: date feed title, with read entries dimmed. Subscriptions come from ~/.darger-feeds (one URL per line, optionally followed by a title; # comments) and from (rss-feed "url" ["title"]) in ~/.darger.el. The first opening fetches every feed with fetch-command, up to all of them at once; the list fills in as they arrive and the header shows fetching 3/7, then 12 new or up to date and the first failure (host: HTTP 404, not a feed, ...). A site URL whose page advertises its feed (<link rel=alternate type=application/rss+xml>) is followed once. RSS 2.0, Atom and RSS 1.0 (RDF) are parsed with the standard library's tolerant XML parser: title, link, guid/id, pubDate/published/updated/dc:date, author/dc:creator, and content:encoded (else the description/summary) as HTML, including Atom XHTML content. Entries are not cached on disk: each session fetches the feeds again.

  • n / p / C-n / C-p / arrows move; C-v / M-v / SPC / Backspace page; M-< / M->.
  • Enter / f marks the entry read and shows it with the web viewer: the title links to the article, then feed, date and author, then the body. Links inside work as on any page; q (or l with no history) returns to the list.
  • & / b open the article in the system browser; w copies its URL.
  • r / u mark the entry read / unread (r also moves down); R marks every listed entry read. Read keys (guid, else link) are kept in ~/.darger-rss-read, the newest 5000.
  • g fetches every feed again. a prompts for a feed URL, appends it to ~/.darger-feeds and fetches it. s filters the list orderless-style on feed and title (empty resets).
  • q / Escape / C-g close the list (fetches continue; RSS: 3 new (C-c r) is echoed when they finish). F1 opens the manual; C-x C-c quits.

Windows

Like Emacs, the frame can show several windows at once: C-x 2 (split-window-below) and C-x 3 (split-window-right) split the selected window in two, both showing its buffer at the same place; C-x o (other-window) selects the next one (left to right, top to bottom, wrapping), a mouse click selects the one under it, C-x 0 (delete-window) removes the selected window and gives its space to its neighbour, and C-x 1 (delete-other-windows) keeps only the selected one. C-x ^ (enlarge-window, shrink-window by M-x) gives the selected window a row more from the window above or below it, C-x } / C-x { (enlarge-window-horizontally / shrink-window-horizontally) a column more or less from the window beside it, and C-x + (balance-windows) shares every split by the number of windows on each side. C-x 4 b and C-x 4 f (switch-to-buffer-other-window, find-file-other-window) ask for a buffer or a file and show it in the next window, splitting when there is only one. C-c left / right / up / down (windmove-left …) select the window in that direction from the cursor, as windmove does. Each window has its own buffer, point and scroll (a buffer shown in two windows has a point in each), and its own mode line; the selected window's is bright, the others dimmed, and a │ column separates side-by-side windows. A split is refused when a half would be smaller than 3 rows or 10 columns. The selected window is the one every command, prompt, overlay (dired, eww, the manual, a review) and the cursor box act in; the others just show their buffer with its highlighting and folds. Killing a buffer shown in another window puts the most recently used buffer there. The prompt popup and the echo line belong to the frame, not to a window.

Buffers

Every file opens in its own buffer; the other buffers keep their text, point, undo, scroll position and language. Every command-line argument is opened and the first is shown (one that fails to load is reported in the echo area and skipped), next to the initial *scratch*. Visiting a file that is already open (C-x C-f, find-file, the dashboard, C-c ,) switches to its buffer and keeps its edits.

  • C-x b (switch-to-buffer) prompts for a buffer name; Enter alone switches to the most recently used other buffer (shown as the default), and an unknown name creates an empty buffer with that name. (switch-to-buffer "name") does the same from Lisp.
  • C-x k (kill-buffer) prompts for a name (default: the current buffer) and asks Buffer NAME modified; kill anyway? (y or n) for a modified one. Killing the current buffer shows the previous one; killing the last leaves a fresh *scratch*.
  • C-x right / C-x left (next-buffer / previous-buffer) cycle in opening order.
  • C-x C-b (list-buffers) lists the buffers, most recently used first, as CRM Name Path rows (. marks the current buffer, * a modified one). C-n / C-p / n / p move, Enter switches, k kills the buffer on the line (asking y/n inline when it is modified), q, C-g or Escape close the list.

Names follow Emacs: a file's name is its basename; buffers sharing one get their parent directory appended (darger.nim<src>, darger.nim<tests>, deepening to <a/src> when needed), path-less buffers are *scratch*, *scratch*<2>, ... or the name typed at C-x b. The mode line and window title show this name. C-x C-s saves only the current buffer; C-x C-c asks before exiting when any buffer is modified. The kill ring is shared: it follows you to the buffer you switch to. Buffers follow their files (see the agent section above): an unedited buffer whose file changed on disk is re-read, an edited one asks before saving over the change.

Completion

The Find file / Write file (C-x C-f, C-x C-w), M-x, C-x b, C-x k, M-g l, M-g f and M-? prompts list candidates under the input, vertico-style: up to 10 rows (fewer in a short window, so some text stays visible), the selected one highlighted, and N/M (selected / matching) at the right of the prompt. Matching is orderless-style: the input is split on spaces and every term must occur somewhere in the candidate, in any order, ignoring case (Japanese included). Candidates that start with the first term come first, otherwise the source order is kept.

  • The first candidate is preselected. C-n / C-p (Down / Up) move and wrap around; C-v / M-v (PageDown / PageUp) move by 10.
  • TAB inserts the selected candidate into the input; Enter accepts it. M-Enter accepts the input exactly as typed, e.g. to create foo next to an existing foobar, or a new buffer whose name is a prefix of another. With no matches, Enter uses the typed input too.
  • Files: the candidates are the entries of the directory typed so far (up to the last /, ~ expanded), directories with a trailing /, sorted ignoring case; the rest of the input filters them. Dot-files appear once the rest starts with .. TAB or Enter on a directory descends into it instead of opening it.
  • M-x lists every command: buffer commands, primitives callable without arguments and parameterless defuns from ~/.darger.el. C-x b lists buffers most recently used first with the default first; C-x k starts with the current buffer.
  • M-g l (consult-line) lists every line as number text (text cut at 200 characters) and filters on the text. Moving the selection previews that line, centred and highlighted; Enter stays there, C-g returns to where it started. Its list opens at the bottom of the window, above the mode line, so the previewed line stays visible above it.

Searching files

M-x consult-ripgrep asks Grep in <dir>: for a query and searches the project (the nearest .git above the file, else its directory) with ripgrep (rg --vimgrep --smart-case, 200 matches per file at most; grep -rni without rg), then lists the matches as path:line: text, consult-style: typing narrows them, moving the selection previews the spot, Enter jumps there (M-, comes back) and C-g returns. (consult-ripgrep "dir") searches that directory, and C-c n s (consult-ripgrep-org) the org-roam-directory, as a consult-ripgrep wrapper over the notes would. The search runs to completion before the list opens.

Asking the agent where

C-c l (agent-locate) asks agent-command a question about the project ("where is the retry logic?", "どこで日報のテンプレートを埋めている?") and opens the answer as a *grep* buffer in Grep mode, the way Emacs shows a grep buffer: the agent is told to reply with path:line: note lines, grouped under == 1. Heading lines when that helps. It is an ordinary buffer in its window (C-x o, splits and C-x b work as usual), read-only with its own keys: n / p move between the places while the other window follows to each spot (the line there and the row here are highlighted), Enter goes there and selects that window, o makes the list editable (M-x places toggles back), g re-reads it, q leaves it, ? lists the keys. (agent-locate "question") does it from Lisp. M-x agent-references lists every path:line the current buffer mentions the same way.

Any text in that shape works: a file whose first line is the Emacs cookie -*- mode: grep; default-directory: "/the/project/" -*- (Claude Code can write one as a trail through a question) becomes a grep buffer when visited, and so does a file that reads like grep output (rg / grep -n saved to a file: at least two path:line lines naming existing files, on at least half of its lines); (setq grep-auto-open nil) turns that off, and M-x grep-mode makes the current buffer one by hand. M-x rgrep (or (rgrep "query")) searches the project like consult-ripgrep but lists the matches in a *grep* buffer instead of the popup, for walking through them with n / p. The mode line shows (Grep). Relative paths resolve under the cookie's directory, else the file's own.

Migemo

As with migemo in Emacs, a romaji query also finds the Japanese it spells, in isearch (C-s / C-r), consult-line, consult-ripgrep's narrowing and every completion prompt: kensaku matches けんさく, ケンサク and the SKK dictionary's words read that way (検索, 研削, …; a reading prefix counts, so kens already finds 検索), besides kensaku itself. The match found sets the highlighted length. It needs no cmigemo: the SKK dictionary and romaji rules are used, loaded on first use. On by default ((setq migemo-mode nil) or M-x migemo-mode turns it off).

While a prompt is open, (insert "text") types into the minibuffer (a one-key prompt takes the first character) and (exit-minibuffer) accepts it as Enter does, so an init file or an agent can drive prompts. (type-string "n") types as the keyboard would, through the rune filters, so the keys of read-only buffers (places, understanding, magit) and terminals can be driven too, and (press-key "C-c u") presses a chord through the same dispatch as the keyboard.

Projects

Like Emacs' project.el: C-x p f (project-find-file) lists every file of the project, the nearest .git above the current file (tracked and untracked files with .gitignore applied, through git ls-files) or else the file's directory (hidden and build directories skipped), as relative paths to narrow and open. C-x p p (project-switch-project) offers the projects visited before (kept in ~/.darger-projects, newest first; any directory can be typed) and then a file in the chosen one; C-x p b (project-buffers) lists the open buffers under the project. C-c p opens a projectile hydra over them: f find file, p switch project, b buffers, s grep (consult-ripgrep), r rgrep into a *grep* buffer, v magit, d the root in dired (project-dired), l ask the agent where, q quit; a head that opens a prompt ends the hydra. Redefine it with defhydra in ~/.darger.el to change the heads.

Language servers

A file whose language has a server gets diagnostics from it over LSP. The server is started the first time a buffer of that language is shown (one per language, rooted at the nearest directory above the file holding .git, else the file's directory) and stopped on exit. darger sends the whole text 150 ms after the last edit, and on save.

Language Server (the first one installed is used)
Nim nimlangserver
Python pyright-langserver --stdio, pylsp
Ruby solargraph stdio, ruby-lsp
GDScript tcp://127.0.0.1:6005 (the Godot editor's server)
JS/TS typescript-language-server --stdio
C/C++ clangd
Rust rust-analyzer
Go gopls
Shell bash-language-server start
JSON vscode-json-language-server --stdio
YAML yaml-language-server --stdio
Markdown marksman server

When none is found the echo area says so once, e.g. No language server for C/C++ (clangd not found). Override a language's command (split on spaces; tcp://host:port connects instead of spawning) in ~/.darger.el:

(lsp-server "C/C++" "clangd --log=error")
(lsp-server "Python" "")   ; no server for Python
  • Diagnostics underline their range (error red, warning yellow, info blue, hint grey); the mode line shows E:n W:m after the language name. Resting the cursor 500 ms on one shows error: message in the echo area when it is empty.
  • M-g f (consult-flymake) lists them as line:col E|W|I|H message at the bottom of the window, sorted by line; moving the selection previews the spot, Enter stays there, C-g returns.
  • M-x lsp-restart restarts the current buffer's server; M-x lsp-log opens the log, point at its end: darger-lsp-<language>-<pid>.log in the temp directory (the server's stderr and messages, restarted past 4 MB and removed on exit).
  • Completion (company-style): a popup under the cursor lists the server's candidates, each with a kind letter (f function, v variable, m method, c class/struct, k keyword, t type, s snippet, o other) and its detail. It opens by itself once you have typed two identifier characters and paused 300 ms, or right after one of the server's trigger characters (such as .); C-M-i (completion-at-point) asks at once. C-n / C-p (or the arrows) select, Enter / TAB insert (snippet placeholders are reduced to their text, and the item's extra edits, such as clangd's #include, go in too), C-g / Escape close, and typing narrows the list (asking the server again after a 300 ms pause when it said the list was cut short); any other key closes it and runs as usual. (setq lsp-auto-complete nil) in ~/.darger.el keeps only C-M-i.
  • C-c h (lsp-hover) shows the server's description of the symbol at point in a box under the cursor (up to 12 rows; C-n / C-p / C-v / M-v scroll a longer one); any other key closes it.
  • M-. (lsp-definition) jumps to the definition, opening its file when needed, and M-, (lsp-back) returns; the last 50 origins are kept. M-x lsp-type-definition and lsp-implementation jump the same way. M-? (lsp-references) lists the references as path:line: text at the bottom of the window, grouped by file; moving the selection previews each one, Enter stays there (M-, comes back), C-g returns.
  • C-c = (lsp-format-buffer) formats the buffer with the server (4-space indents); C-/ undoes the whole format in one step.
  • A request the server leaves unanswered for 10 s is dropped with LSP: <method> timed out; the editor does not block waiting for an answer, and text a busy server has not read yet is queued (on Windows, written 4 KB at a time).

Org and org-roam

In an Org buffer (.org), C-c C-t cycles the headline at or above point through TODO, DONE and none; C-c C-c toggles a list item's [ ] / [X]; M-Enter adds a headline or list item of the same kind below the line (numbers advance, a checkbox comes empty); M-Left / M-Right promote and demote the headline; M-Up / M-Down swap its subtree with the sibling above or below. C-c C-j lists the headlines like M-g l (the preview follows the selection, C-g returns, M-, comes back after a jump), C-c C-o follows the [[link]] at point (id: opens the note, file: and plain paths open files, URLs go to the system browser, *Headline jumps), and C-c . inserts <2026-10-07 Wed>.

(setq org-todo-keywords '("TODO(t)" "NEXT(n)" "WAITING(w)" "|" "DONE(d)")) sets the keyword sequence (default TODO DONE): C-c C-t cycles through it and back to none, the words after | count as done (the last word without a |) for the agenda, and headlines colour them (active words red, yellow, blue, magenta, cyan in turn, done words dimmed). With selection keys like (t), C-c C-t instead asks TODO: [t] TODO [n] NEXT … and takes one key, SPC for none, as Emacs' fast TODO selection does.

C-c n c (org-roam-capture) asks for a title and makes the note in org-roam-directory from org-roam-capture-templates, a list of (key description file head): with several templates one key picks one, as in Emacs' capture. file and head may use ${title}, ${slug}, ${id}, %<FORMAT> and %T; the ID drawer is added when the head has none. For example (setq org-roam-capture-templates '(("z" "🌱 Zettelkasten" "%<%Y%m%d%H%M%S>.org" "\n#+date: %T\n#+title:${title}\n#+filetags:\n") ("w" "📝 Wiki" "%<%Y%m%d%H%M%S>.org" "#+title:📝${title}\n#+filetags: :WIKI:\n"))). C-c n t (org-roam-tag-add) adds a tag to the file's #+filetags: (the notes' existing tags are offered; the line is created after #+title: when missing).

C-c n g (org-roam-graph) opens the notes and their id: links as a graph in the browser (a page with its own force layout, no Graphviz needed): the current note is red, hovering shows a note's title, tags and link count, drag moves, the wheel zooms.

C-M-y (org-insert-clipboard-image) saves the clipboard's image as img/YYYY-mm-dd-HH-MM-SS.png next to the file (the buffer must have one) and inserts [[file:img/…]], like the usual Emacs paste-image commands, with no pngpaste needed. This works on macOS and Windows; on Linux the command reports that clipboard images are not supported, because windy's X11 backend has no image clipboard.

Image links show inline, as org-display-inline-images: a line with [[file:…png]] (also jpg, gif, webp, bmp; ~ and paths relative to the file work; WebP through macOS' sips) gets the picture in the rows under it, scaled to the text width and at most org-image-max-rows rows (default 15). A picture running off the pane's bottom is cropped, and the wheel, C-v / M-v and recentring count its rows. Files are reloaded when they change; a click on a picture puts point at the end of its line. M-x org-toggle-inline-images hides and shows them.

M-x olivetti-mode toggles a centred writing column for the current buffer. Set olivetti-body-width (default 100 cells) to choose the body width; wider panes add a left margin, with line numbers beside the text, while narrower panes keep their usual layout. Set (setq olivetti-languages '("Org" "Markdown")) to enable it automatically when visiting those languages (default nil).

Installing fonts

M-x font-install offers RinMincho and Nerd Fonts Symbols with installed/missing status. Downloads use pinned HTTPS URLs and SHA-256 checks for both the ZIP and each selected font/license file. Files go to font-install-dir (default ~/.darger-fonts/); set it to ~/Library/Fonts on macOS to share them with other apps. Fonts are not vendored. font-auto-install defaults to 'ask: a missing catalog novel font prompts once per session; t downloads automatically and nil disables the prompt. Installed fonts take effect without restarting; C-g cancels a running download. Any failure installs nothing and says why in the echo line. Tree icons use nerd-symbols as a fallback; missing icons show a one-time installation hint. Only catalog entries are accepted, not arbitrary URLs.

M-x novel-mode toggles a per-buffer novel mode: the text is drawn in 倫明朝 (RinMincho, https://rinmincho.pages.dev/, SIL OFL 1.1; not bundled; use M-x font-install RET rinmincho) and, by default, in Japanese vertical writing: each line flows top to bottom, wraps into further columns with kinsoku, and columns go right to left. Other buffers keep their font; characters RinMincho lacks come from the usual fonts, and without RinMincho the mode works with those fonts and the echo line says why.

  • Keys in a vertical pane: Down / Up move to the next / previous character, Left / Right to the next / previous column, C-v / M-v page by columns, C-l puts the cursor's column in the middle, rightmost, then leftmost. C-n / C-p stay logical lines. The wheel scrolls columns and a click puts point on the character under it. Line numbers are hidden.
  • Vertical forms come from the font's own OpenType vert substitutions (vrt2 in a font without vert). A font with neither gets brackets, dashes and ellipses rotated 90°, and 、。,. and small kana moved to the upper right.
  • Tate-chu-yoko keeps !!, !?, ?!, ?? and exactly two-digit numbers upright side by side in one cell; …… and ―― pairs never split (except the no-legal-break fallback). Other half-width letters and digits stand upright, one to a cell, centred across the column.
  • Vertical panes apply these rules to the display only (novel-display-rules, default t): digits, ASCII, ... and 15:30 stay exactly as typed in the buffer and on disk. Set (setq novel-display-rules nil) for the raw display; M-x novel-format remains available to rewrite the text. A rewritten group shares one source cursor position: clicks land at its start and Down / Up skip the group. The IME composition line stays raw.
  • M-x novel-format normalises the active region, or the whole buffer, in one undo step, even outside novel mode: full-width ASCII letters/punctuation (preserving tate-chu-yoko), kanji numbers except two-digit runs (years and long numbers digit by digit), paired ellipses and dashes, removal of 。 before 」』, and a full-width space after exclamation/question marks except at line ends or before closing brackets or further such marks; times written hh:mm become hh時mm分 (10:00 → 10時, 3:05 → 三時五分). Spaces elsewhere, ー and a single - stay unchanged. Based on 翔夜/エス's 「縦書き小説の見栄えを良くする七つのルール」.
  • Settings: novel-font and novel-bold-font (a file name looked for in the font directories, or a path), novel-vertical (default t; nil is horizontal, centred in novel-body-width cells, default 80), novel-column-spacing (default 1.7 ems), novel-font-scale (default 1.25, vertical only), novel-languages and novel-file-suffixes (automatic activation, default nil).
  • Not drawn in vertical panes yet: folds (hidden lines show), inline pictures, review bands and paren highlights. The horizontal layout does not soft-wrap yet.

Images from Eagle (eagle.cool), as an Emacs helper for its local API would: C-M-e (org-insert-eagle-image) asks for a keyword (empty for the newest items) and lists the matching images from Eagle's local API as name.ext [tags]; C-c e (org-insert-eagle-image-from-folder) lists Eagle's folders (parent/child) first. Enter inserts a link to the library file itself, [[file:~/…/Library.library/images/ ID.info/name.ext]], written with ~ so it resolves on every machine that keeps the library under HOME. Eagle must be running (eagle-api-base, default http://localhost:41595); nothing is copied. The highlighted item's thumbnail is previewed at the top right while the list is open.

Folding works as in Emacs: TAB on a headline cycles FOLDED (the subtree hidden, the headline ending in ...), CHILDREN (its text and its child headlines, folded) and SUBTREE; Shift-TAB cycles the whole file through OVERVIEW (top-level headlines only), CONTENTS (every headline, no text) and SHOW ALL, and #+startup: folded (or overview, content) opens the file that way. TAB elsewhere still indents. C-n / C-p, C-v / M-v and C-l count only shown lines; a search, M-g g, a link or an edit that lands in hidden text opens the folds down to it, keeping the sibling subtrees folded. Edits above a fold move it; a folded headline that is deleted simply shows its text again. M-x org-cycle and org-shifttab are the commands.

C-c j (org-journal-new-entry) keeps a journal like org-journal: today's file is org-journal-dir / org-journal-file-format (defaults ~/org-journal, %Y%m%d.org), created with org-journal-file-header and a date headline (org-journal-date-prefix + org-journal-date-format, defaults * and %A, %d %B %Y), then a time headline (org-journal-time-prefix + org-journal-time-format, defaults ** and %R ) is added at the end with the cursor after it. Before org-extend-today-until o'clock (default "0") the entry still belongs to yesterday. org-journal-dir may be a remote directory in the TRAMP form, /ssh:HOST:~/Dropbox/Org: the day's file is then read, created (the directory with mkdir -p) and saved over SSH, as with C-x C-f. A new file gets the contents of org-journal-daily-template-file (default none) under the date headline, so the day's sections are there before the first entry, and an ID property drawer at the top when org-journal-add-id is t (default nil), making the day an org-roam node. With org-journal-condition-prompt t (default nil) a new entry asks 体調 [1-5] with one key: a digit puts that many ★ after the time and leaves the cursor after them, any other key skips, as an org-journal hook would. The formats take strftime's %Y %y %m %d %e %H %M %S %R %A %a %B %b %j %%; (setq system-time-locale "C") forces English names and "ja_JP.UTF-8" Japanese ones; without it, under a Japanese locale (LANG=ja_JP.UTF-8) %A and %B give 火曜日 and 10月, as Emacs does. A day's file written by another tool is brought into shape on the next C-c j / C-c q: the header is added when its #+title: is missing, and an existing top-level headline counts as the date headline, so none is added twice. C-c q (org-journal-quick-add) adds one line to the same day's file without leaving the current buffer: it prompts Journal: and Enter adds org-journal-time-prefix + the time + the text: on the same line when org-journal-time-format has no trailing newline (** 14:30 手書きの方が残る), under the time headline when it ends with one, as C-c j lays entries out (*** =14:30= then the text on the next line). Empty input adds nothing, C-g cancels. The file is created as C-c j creates it, but no 体調 is asked. The line goes at the end of the file, or with org-journal-quick-add-headline (default "") at the end of that headline's section (above its trailing blank lines; the headline is added at the end when missing): "** 今日の出来事" reproduces yuruorg's 日報クイック追記. A buffer visiting the file is edited (undoable) and saved, otherwise the file is written directly (or over SSH); (org-journal-quick-add "text") adds without asking, and a multi-line text puts the lines after the first under the entry. C-c t (org-capture) prompts Capture: and files the text, through org-capture-template (default * TODO %?\n; %? is the text, %t / %u today's active / inactive timestamp, %T / %U with the time), at the end of the subtree of org-capture-headline in org-capture-file (defaults "", meaning the end of the file, and ~/org/inbox.org), creating the headline or the file when missing. An open buffer of that file is edited and saved; otherwise the file is written directly, and you stay where you were. M-x org-agenda reads org-agenda-files (a path, a list of paths, or directories of .org files; default none) and lists, consult-style with a preview, what Emacs' day agenda would show for today: TODO headlines scheduled today or earlier (Scheduled: / Sched. 3x:), deadlines overdue or within 14 days (Deadline: / In 3 d.: / 3 d. ago:), headlines with today's timestamp, then the undated TODOs (TODO:), each with its file's name; Enter goes to the headline (M-, comes back), C-g returns. DONE items and headlines dated for other days are left out. Bind it yourself, e.g. (global-set-key "C-c a" 'org-agenda) replaces the agent prompt.

org-super-agenda-groups sorts the agenda into named groups, like org-super-agenda: each (:name "NAME" KEY VALUE) takes the items matching :priority "A", :todo "NEXT", :tag "game" or :scheduled today, first match wins, and the rest go under "その他". The default follows a Daily dashboard (最重要 / 次にやる / 今日やる / tags / 待ち / 未整理). Headlines carry priorities [#A] [#B] [#C] in colour; M-x org-priority asks for one (a / b / c, SPC removes it) and Shift-Up / Shift-Down on a headline steps it (elsewhere they still select).

C-c C-w (org-refile) moves the subtree at point under a headline picked by its file.org/Parent/Child path from the current buffer and org-agenda-files (org-refile-targets, :maxlevel 3 by default), re-levelling it; a parent that does not exist is created after a confirmation. C-c C-s / C-c C-d set SCHEDULED: / DEADLINE: from 2026-11-10, +3, fri or an empty answer (today). C-c C-x C-a moves the subtree to FILE_archive with Emacs' ARCHIVE_TIME / ARCHIVE_FILE / ARCHIVE_OLPATH / ARCHIVE_TODO properties; M-x org-archive-done-tasks archives every DONE subtree.

org-modern-mode (on by default; M-x org-modern-mode toggles it) draws TODO keywords as badges coloured by org-todo-keyword-faces, #+begin_src / #+end_src as thin rules keeping the language, checkboxes as □ ✓ ■ and - / + bullets as •. Only the display changes: the cursor's line shows the text as it is.

org-roam notes are Org files under org-roam-directory (default ~/org-roam, (setq org-roam-directory "~/notes") in ~/.darger.el) with an ID property drawer and a #+title:; the directory is rescanned at each command, without a database. C-c n f prompts for a title: Enter on a listed note opens it, and Enter on a new title (or M-Enter to take the typed title while matches are listed) creates <timestamp>-<slug>.org and opens it. C-c n i inserts [[id:…][Title]] at point (an active region becomes the title and is replaced). C-c n l lists the notes linking to the current one with the linking line, previewed like references. C-c n d opens today's daily/YYYY-MM-DD.org, creating it; C-c n j (org-roam-dailies-capture-today) opens it and adds a * HH:MM headline at the end. M-x org-id-get-create gives any Org file an ID, making it a note.

Architecture

src/core.nim is the editor: the buffer registry, the window and renderer, key dispatch, prompts, overlays and the Lisp interpreter, plus the API everything else plugs into. src/darger.nim only owns the window (title bar, mouse, keyboard callbacks) and the main loop. Every feature is an extension under src/ext/, a Nim module that imports core and registers itself when imported from darger.nim: org (Org and org-roam), web (eww), rss, dired, help, dash, blist, agent (one-shot edits, review and chat), selfupdate and langserver. The pure logic each one builds on (parsing, protocols, file work) stays in its own module in src/ with tests.

The API, in core.nim under "The extension API": defineMode registers a view (a Mode: the buffer it shows, its faces, mode-line tag, echo-line message, key handler, close and relayout procs); definePrompt registers a minibuffer prompt (a Prompt: candidates, whether they preview positions or complete paths, which view it floats over, and onAccept, which may keep the prompt open); interp.defPrimitive adds a command for M-x and global-set-key; defaults.add supplies Lisp evaluated after the built-in bindings and before ~/.darger.el. Hooks: tickHooks (polled every loop), messageHooks (echo-line overrides), keyFilters, runeHooks, insertHooks, imeHooks, escapeHooks, busyHooks, cancelHooks (C-g), killHooks, saveHooks, modifiedIgnore, exitHooks, langHints, and the single providers dirVisitor, marksHook, boxHook, modeLineHook, startScreen. Views reach other features through the keymap (dispatch(full)) rather than by calling them, so an extension can be left out by removing its import.

Init file

~/.darger.el is evaluated at startup after the default bindings, and again whenever it changes on disk (checked twice a second), so saving it applies it at once: the default bindings are restored first, so a global-set-key, hydra or setq dropped from the file goes back to its default, while feeds, LSP servers and defuns already added stay until restart. The echo line reports Loaded ~/.darger.el or the error, and No ~/.darger.el (C-c , creates it) at every start without one; M-x load-init reloads it by hand. C-c , opens it ((find-file "~/.darger.el"), which switches to its buffer when it is already open). Besides global-set-key, setq, message, insert and command, it can use (load-theme "catppuccin"), (set-font "path"), (set-fallback-fonts "path;path"), (find-file "path") (~ expands), save-buffer, kill-emacs, skk-mode, zoom-in / zoom-out / zoom-reset, (eww "url"), (background-shader "path"), (setq background-shader-dim 0.6), (rss-feed "url"), (setq fetch-command "..."), (setq eww-search-prefix "..."), and (ignore-errors ...), which evaluates its body and gives nil instead of stopping the file when it fails, (hydra "PREFIX" "hint"), which makes a one-key prefix sticky: after a bound PREFIX x command the prefix stays active and the hint shows in the echo area, and defhydra, Emacs' hydra in one form:

(defhydra "C-c w" "windows"
  '(("o" other-window "other")
    ("2" split-window-below "split below")
    ("3" split-window-right "split right")
    ("1" delete-other-windows "only" :exit)
    ("q" nil "quit")))

The prefix (a chord like C-c w works too) becomes sticky and each head a binding under it; while it is active a box above the mode line lists the heads as [key] hint in columns, with a last line saying which keys leave. A head with :exit runs its command and leaves, a nil command just leaves, any other unbound key leaves and runs as usual. (hydra-enter "C-c w") opens a hydra from Lisp or from a plain key bound to it. The default zoom is one: (defhydra "f2" "zoom" '(("g" zoom-in "in") ("l" zoom-out "out") ("0" zoom-reset "reset"))).

(global-set-key "f2 r" 'zoom-reset)
(load-theme "catppuccin")

SKK input

C-\ or C-x C-j (skk-mode) toggles a built-in DDSKK-style SKK (turning it off commits anything in progress). The mode line shows [かな], [カナ] or [SKK] (latin); nothing when off. Pending romaji, ▽reading and ▼candidate are drawn inline at the cursor like an IME composition; the buffer changes only when kana complete or a conversion is committed, through the normal insert path (a commit is one undo step).

  • Direct input follows the DDSKK romaji rules: nn/n' → ん, a doubled consonant → っ, x + vowel / xtu / xya / xwa for small kana, - ー, ~ 〜, ! ? : [ ] full-width, z/ z. z, z- zh zj zk zl z[ z] → ・ … ‥ 〜 ← ↓ ↑ → 『 』. . and , give 。 and 、 as DDSKK's skk-kutouten-type "jp" (the default); "en" gives .,, "jp-en" 。,, "en-jp" .、, "ascii" leaves them as typed. Digits and space stay ASCII. ( inserts () with the cursor inside; ) steps over a following ). q toggles katakana, l switches to latin, L to full-width ASCII ([全英]: Abc 1 → Abc 1), C-j returns to hiragana from either.
  • ▽: an uppercase letter or ; starts a reading; a second uppercase letter (or ;) marks the okurigana (OkuRi → 送り, converted as soon as the kana is complete). SPC converts, Enter / C-j commits the reading without a newline, q commits it as katakana, Backspace deletes the last kana, C-g cancels. Enter, Backspace and Tab mean the same as C-m, C-h and C-i here. (setq skk-egg-like-newline nil) makes Enter in ▽ / ▼ commit and then insert the newline, as DDSKK does by default; the default t only commits. Tab completes the reading like skk-comp: it becomes the next dictionary key starting with what was typed (user dictionary entries first, then the main one, alphabetically; okuri-ari keys skipped), another Tab the next, and after the last Tab brings the typed text back; typing continues from the completion and SPC converts it.
  • / starts an abbrev reading (▽ typed as ASCII, no romaji; uppercase and ; are literal): /dog SPC looks up the key dog, so dog /犬/ in the user dictionary gives 犬, Enter commits the ASCII as typed, Tab completes among ASCII keys, and a miss registers under the ASCII key. Kana input resumes afterwards.
  • ▼: SPC / x step through candidates 1-4, and C-n / C-p (or the arrows) one candidate at a time anywhere; from the 5th the current page of seven hangs from the cursor as a list with its pick keys a s d f j k l and the selected one highlighted (in a prompt, as A:… ▶S:… D:… [残り N] on the echo line). SPC / x change page, C-p before the first candidate returns to ▽. Enter / C-j commits, Backspace or C-g returns to ▽, and any other kana key commits and continues.
  • A reading with no candidates, or SPC past the last one, opens DDSKK's registration prompt 辞書登録 「よみ」: (SKK works in it too): Enter with a word puts it in the user dictionary and inserts it (with the okurigana), Enter alone or C-g returns to ▽ with the reading. Inside another prompt the reading just comes back as ▽ with No candidates for ….
  • Any other command (C-a, C-f, M-x, C-x …) first commits what SKK has in progress, as DDSKK does, so ▽ / ▼ never stays behind at the old cursor. SKK stays out of an active Windows IME composition. Find-file, write-file, agent, buffer, consult-line and isearch minibuffers use SKK as well; y/n and goto-line prompts bypass it.
  • The main dictionary is $env:DARGER_SKK_JISYO, else the first existing of ~/Dropbox/.emacs.d/skk-get-jisyo/SKK-JISYO.L, ~/.emacs.d/skk-get-jisyo/SKK-JISYO.L, ~/.skk/SKK-JISYO.L and /usr/share/skk/SKK-JISYO.L (EUC-JP when line 1 has a coding: euc-jp cookie, UTF-8 otherwise). It loads on the first toggle and reports SKK: N entries loaded (t ms); without it kana input still works and conversion says No dictionary.
  • Each commit from ▼ moves the choice to the front and rewrites ~/.darger-skk-jisyo (UTF-8 SKK format, searched first, keys sorted rather than by recency). Emacs' ~/.skk-jisyo is never touched.
  • Dictionary candidates keep their annotations (仮名;note): ▼ and kakutei use the word, the candidate box shows the note beside it. (concat "a\057b") candidates (SKK's way to write / or ;) are evaluated, so a/b is shown and inserted; a registered word containing / or ; is written that way, and word;note registers with its note.
  • Not implemented: l-prefixed small kana (l is the latin-mode key, as in DDSKK: use x) and okuri […] blocks.

Windy 0.5.0 forwards repeated Windows WM_KEYDOWN events to onButtonPress, so held command keys use native OS repeat. onRune handles printable text only.

In a sandbox where Nimble cannot update ~/.nimble/nimbledata2.json, c 54EB opy the installed pkgs2 directory and *.json files from ~/.nimble into nimcache/nimble, then use:

nimble --nimbleDir:nimcache/nimble --offline build -d:release

All compiler cache files remain under the project nimcache/ directory.

About

A small GPU-rendered Emacs-style text editor in Nim, with built-in SKK and a Lisp extension language

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0