GitHub - paveluv/e: A fully customizable self-aware Emacs-like editor written in Chez Scheme.
Pangram verdict · v3.3
We believe that this text is a mix of AI and human-written content.
AI likelihood · overall
MixedArticle text · 1,415 words · 5 segments analyzed
A tiny console text editor written in Chez Scheme. It is Emacs-like in spirit—buffers, windows, a kill ring, incremental search, and a Scheme top level—but it is not an Emacs clone. It borrows the ideas that stay small and stops there. The editor is a Scheme system. Everything is an R6RS library: a minimal core with a published, immutable API, and extension modules built on it (the syntax modes, bracket matching, the editing helpers, M-x itself). The core enforces its boundaries with the language rather than with discipline: internals are invisible, exports cannot be reassigned. M-x evaluates Scheme against the editor's live top level, with symbol completion, parameter hints as you type, history, and a transcript buffer. Modules hot-reload. Saving a module's source inside the editor reloads it into the running session; sources edited elsewhere are picked up with reload-module!. Registrations are replaced, dependent modules recompile, buffers stay put. e is developed from inside itself. No dependencies beyond Chez Scheme and a Unix-like terminal. There is no build or installation step: a checkout runs in place. The first start compiles the libraries; later starts take about 100 ms. Undo reports what it undoes: Undo insert "hello", Undo (replace-all! "xx" "yy"). Typed characters coalesce into runs, a paste is a single step, an M-x command is one labeled step. Quick start As your everyday editor, cloned as ~/.e: git clone https://github.com/paveluv/e ~/.e ~/.e/e file.txt # add ~/.e to PATH for plain `e` Or vendored inside a project, so anyone who clones the project gets a working editor with it: git clone https://github.com/paveluv/e ~/git/your_project/.e rm -rf ~/git/your_project/.e/.git # make it ordinary files of your repo ~/git/your_project/.e/e file.txt Every installation is self-contained: the loader uses strictly the lib/ next to the script itself, compiled objects stay local in eo/, and sources recompile automatically when they (or anything they import) change. Project-specific modules dropped into a vendored checkout's lib/ stay local to that project. The terminal size is detected via ioctl and tracked across resizes; if that is unavailable, set LINES and COLUMNS. On FreeBSD, whose Chez port renames the scheme-script interpreter, change the shebang to #!/usr/bin/env -S chez-scheme --script or run chez-scheme --script e (details in the loader's header comment).
Key bindings Files and exiting Key Action C-x C-s Save (prompts for a name in an unnamed buffer) C-x C-w Save as: prompt for a path, the buffer visits it C-x C-f Visit a file in its own buffer (creating if needed) C-x C-c Quit (asks only if some buffer differs from its file) Buffers and windows Key Action C-x b Switch buffer (default: most recent other; new name creates a buffer) C-x C-b Open *buffers*: move with Up/Down, Enter to select; click selects immediately M-Up Switch to the previous buffer alphabetically M-Down Switch to the next buffer alphabetically C-x k Kill a buffer (default: current; asks if modified) C-x 2 Split the current window in two (stacked) C-x 3 Split the current window in two (side by side) C-x 1 Delete all other windows C-x 0 Delete the current window C-x o Move to the next window C-x t Toggle soft-wrapping of long lines in this window Each window has its own status line, point, and scroll position; the same buffer can be shown in several windows at once.
Buffers have per-buffer history and marks; windows have independent point, scrolling, wrapping, and status. A right-side scrollbar is shown by default; C-x l toggles a buffer-owned line-number gutter. See Buffers for switching, *buffers*, file integrity, target windows, mouse behavior, scrollbars, wrapping, splits, and the public API.
Movement Key Action C-b / C-f / arrows Left / right one character C-p / C-n / arrows Up / down one line C-a / C-e, Home/End Beginning / end of line C-v / M-v, PgDn/PgUp Page down / up M-< / M-> Beginning / end of buffer Editing Key Action C-d, Delete Delete the character after point Backspace Delete the character before point C-o Open a line below, leaving point in place C-k Kill to end of line (repeats accumulate) C-@ (C-Space) Set the mark C-w Kill the region between mark and point C-y Yank the last kill C-_ Undo C-M-_ Redo TAB Indent the line by its mode's rules M-% Query replace from point: y/SPC replaces, n/DEL skips, q/RET/C-g stops M-. Describe the symbol at point (Scheme buffers; see below) C-x C-e Evaluate the current buffer as Scheme C-l Repaint the screen and re-read its size C-g Cancel (prompt, search, mark, running evaluation) C-h k Describe a key and where its bindings came from Key customization All keyboard commands can be rebound in config.e. User bindings override built-in and module defaults, and C-h k describes a key, its source, and its contextual meanings: (bind-key! "C-c s" save!!) (unbind-key! "C-v") See Key binding configuration for key syntax, context maps, available actions, precedence, unbinding, and module defaults. Search C-s starts an incremental search: type to extend the needle, C-s again jumps to the next match (wrapping around), Backspace shortens the needle, RET/ESC accepts silently, C-g cancels and returns point to where the search began. In a new, empty search, C-s recalls and searches for the previous needle.
Point rides just past the current match, so a mark set before searching leaves the found text inside the region. The needle's matches in the current window are highlighted while searching. Matching folds case the smart way, as in Emacs: it ignores case only while the needle is all lowercase, and one typed capital makes it exact (the prompt then reads I-search (exact):). M-c toggles the current search either way, and (search-fold-case #f) in config.e makes every search exact. Everything else—including M-% query replace—always matches exactly. Indentation and formatting Indentation is the one thing enforced—spacing within a line is the author's (tables, alignment, and hand layout communicate structure), and lines are never joined or split. The Scheme rules are Emacs-like: body forms (define, lambda, the let family, when, ...) indent their bodies two columns past the opener, a lone closer sits under its opener, and an application's continuation lines have two stops: two past the opener, or aligned under the first argument when it shares the opener's line. The author picks: (very-long-function-name param1 param2 param3) (very-long-function-name param1 param2 param3) TAB indents the current line, cycling through its stops -- the nearest stop to the right, wrapping around -- and puts point on the indentation (a blank line pads out to it); modes opt in when they register an indenter, and scheme-mode does. indent-region! and indent-buffer! indent line by line from the top as one undo step, each line settling on the stop nearest its current indentation: on a stop it stays, before the first it takes the first, past the last the last. format-region! and format-buffer! additionally widen tabs outside strings to spaces ((scheme-tab-width 2); #f keeps tabs), trim trailing whitespace, drop trailing blank lines so the file ends with exactly one newline, and apply the bracket conventions -- [ ] for the bindings of the let family, do, parameterize, and with-syntax, and for the clauses of cond, case, case-lambda, guard, syntax-rules, and syntax-case; ( ) everywhere else -- the scheme-format-brackets parameter turns that off. A region format only rewrites bracket pairs it wholly contains, so it can never unbalance the buffer; string interiors and margin comments are never touched. Modules provide the rules per mode with register-indenter! and register-formatter!. The Scheme engine is the pure (scheme-format) library, which also drives tools/scheme-format: the same treatment from the shell with scheme-format [-i] [file ...], or stdin to stdout without arguments. With (scheme-format-on-save #t) (on by default) every save of a Scheme buffer formats it first through a pre-save hook; modules add their own with add-pre-save-hook! and add-post-save-hook! (the module reload is a post-save hook). Prompts Prompt input is line-editable with the usual bindings (C-a/C-e, arrows, Home/End, C-k/C-y through the shared kill ring), and completes with TAB: file names in the file prompts, buffer names in the buffer prompts. TAB extends to the longest common prefix; when nothing extends, a second TAB pops up a *completions* window, which disappears when the prompt finishes. Input longer than the screen is wide wraps onto continuation lines, marked with a trailing \ and indented under the prompt text, the windows above shrinking to make room; at eight lines the prompt area scrolls instead. Long echo-area messages wrap the same way. In a wrapped prompt the arrows move between visual lines, turning into history navigation at the top and bottom edges.