e: Een volledig aanpasbare, zelfbewuste Emacs-achtige editor geschreven in Chez Scheme

De editor is een Scheme-systeem. Alles is een R6RS-bibliotheek: een minimale kern met een gepubliceerde, onveranderlijke API, en extensiemodules die hierop zijn gebouwd (de syntaxis-modi, haakjes-matching, de bewerkingshulpmiddelen en M-x zelf). De kern handhaaft zijn grenzen via de taal in plaats van via discipline: interne zaken zijn onzichtbaar en exports kunnen niet opnieuw worden toegewezen.

M-x evalueert Scheme tegen het actieve top-level van de editor, inclusief symboolaanvulling, parameter-hints tijdens het typen, geschiedenis en een transcriptie-buffer.

Modules kunnen hot-reloaden. Het opslaan van de broncode van een module binnen de editor laadt deze opnieuw in de actieve sessie; bronnen die elders bewerkt zijn, worden opgepikt met reload-module!. Registraties worden vervangen, afhankelijke modules worden opnieuw gecompileerd en buffers blijven behouden. e wordt ontwikkeld vanuit zichzelf.

Er zijn geen afhankelijkheden buiten Chez Scheme en een Unix-achtige terminal. Er is geen build- of installatiestap: een checkout kan direct worden uitgevoerd. De eerste start compileert de bibliotheken; latere starts duren ongeveer 100 ms.

De Undo-functie rapporteert wat hij ongedaan maakt: bijvoorbeeld Undo insert "hello" of Undo (replace-all! "xx" "yy"). Getypte tekens worden samengevoegd tot reeksen, een plakactie is één stap, en een M-x-commando is één gelabelde stap.

Snelstart

Als dagelijkse editor, gekloond als ~/.e:

git clone https://github.com/paveluv/e ~/.e
~/.e/e file.txt        # voeg ~/.e toe aan PATH voor simpelweg `e`

Of als onderdeel van een project, zodat iedereen die het project kloont een werkende editor heeft:

git clone https://github.com/paveluv/e ~/git/your_project/.e
rm -rf ~/git/your_project/.e/.git    # maak er gewone bestanden van in je repo
~/git/your_project/.e/e file.txt

Elke installatie is zelfvoorzienend: de loader gebruikt strikt de lib/ naast het script zelf, gecompileerde objecten blijven lokaal in eo/, en bronnen worden automatisch opnieuw gecompileerd wanneer zij (of iets wat ze importeren) wijzigen. Projectspecifieke modules in de lib/ van een vendored checkout blijven lokaal voor dat project.

De terminalgrootte wordt gedetecteerd via ioctl en bijgehouden bij wijzigingen; als dat niet beschikbaar is, stel dan LINES en COLUMNS in. Op FreeBSD, waar de Chez-port de scheme-script interpreter hernoemt, wijzig de shebang naar #!/usr/bin/env -S chez-scheme --script of voer uit: chez-scheme --script e.

Toetsenbordcombinaties

Bestanden en afsluiten

ToetsActie
C-x C-sOpslaan (vraagt om een naam in een onbenoemde buffer)
C-x C-wOpslaan als: vraagt om een pad, de buffer bezoekt dit
C-x C-fBezoek een bestand in zijn eigen buffer (maakt deze aan indien nodig)
C-x C-cAfsluiten (vraagt alleen als een buffer verschilt van het bestand)

Buffers en vensters

ToetsActie
C-x bBuffer wisselen (standaard: meest recente andere; nieuwe naam maakt een buffer aan)
C-x C-bOpen buffers: beweeg met Up/Down, Enter om te selecteren; klikken selecteert direct
M-UpSchakel naar de vorige buffer (alfabetisch)
M-DownSchakel naar de volgende buffer (alfabetisch)
C-x kEen buffer verwijderen (standaard: huidige; vraagt bij wijzigingen)
C-x 2Huidige venster splitsen in tweeën (gestapeld)
C-x 3Huidige venster splitsen in tweeën (naast elkaar)
C-x 1Alle andere vensters verwijderen
C-x 0Huidige venster verwijderen
C-x oGa naar het volgende venster
C-x tSoft-wrapping van lange regels in dit venster aan/uitzetten

Elk venster heeft zijn eigen statusregel, cursorpositie (point) en scrollpositie; dezelfde buffer kan in meerdere vensters tegelijk worden getoond. Buffers hebben per-buffer geschiedenis en markeringen; vensters hebben onafhankelijke cursorposities, scrolling, wrapping en status. Een scrollbalk aan de rechterkant wordt standaard getoond; C-x l schakelt een regelnummer-gutter aan/uit.

Beweging

ToetsActie
C-b / C-f / pijlenEén teken naar links / rechts
C-p / C-n / pijlenEén regel omhoog / omlaag
C-a / C-e, Home/EndBegin / einde van de regel
C-v / M-v, PgDn/PgUpPagina omlaag / omhoog
M-< / M->Begin / einde van de buffer

Bewerken

ToetsActie
C-d, DeleteVerwijder het teken na de cursor
BackspaceVerwijder het teken voor de cursor
C-oOpen een regel eronder, laat cursor op huidige plek
C-kVerwijder tot het einde van de regel (herhalingen accumuleren)
C-@ (C-Space)Markering zetten
C-wVerwijder de regio tussen markering en cursor
C-yPlak (yank) de laatste verwijdering
C-_Ongedaan maken (Undo)
C-M-_Herstellen (Redo)
TABRegel inspringen volgens de regels van de huidige modus
M-%Query replace vanaf cursor: y/SPC vervangt, n/DEL slaat over, q/RET/C-g stopt
M-.Beschrijf het symbool op de cursor (alleen Scheme buffers)
C-x C-eEvalueer de huidige buffer als Scheme
C-lScherm opnieuw tekenen en grootte opnieuw lezen
C-gAnnuleren (prompt, zoeken, markering, actieve evaluatie)
C-h kBeschrijf een toets en waar de bindingen vandaan komen

Aanpassing van toetsen

Alle toetsenbordcommando's kunnen worden hergebonden in config.e. Gebruikersbindingen overschrijven de standaardwaarden van de editor en modules. C-h k beschrijft een toets, de bron ervan en de contextuele betekenissen.

Voorbeeld:

(bind-key! "C-c s" save!!)
(unbind-key! "C-v")

Zoeken

C-s start een incrementele zoekopdracht: typ om de zoekterm uit te breiden, C-s opnieuw om naar de volgende match te springen (met wrap-around), Backspace om de term te verkorten, RET/ESC om stilzwijgend te accepteren, en C-g om te annuleren en de cursor terug te zetten naar het beginpunt. Bij een nieuwe, lege zoekopdracht roept C-s de vorige zoekterm op.

De matches in het huidige venster worden gemarkeerd tijdens het zoeken. De hoofdlettergevoeligheid werkt slim (zoals in Emacs): het negeert hoofdletters alleen als de zoekterm volledig uit kleine letters bestaat; zodra er één hoofdletter wordt getypt, wordt het een exacte zoekopdracht (de prompt toont dan I-search (exact):). M-c wisselt tussen deze modi, en (search-fold-case #f) in config.e maakt elke zoekopdracht exact. Alles wat niet incrementeel is — inclusief M-% query replace — matcht altijd exact.

Inspringen en opmaak

Inspringen is het enige dat strikt wordt afgedwongen — spaties binnen een regel zijn de keuze van de auteur. Regels worden nooit automatisch samengevoegd of gesplitst. De Scheme-regels zijn Emacs-achtig: body-forms (define, lambda, de let-familie, when, ...) springen hun bodies twee kolommen in ten opzichte van de opener. Een eenzame closer staat direct onder de opener.

TAB springt de huidige regel in, waarbij wordt gecycled tussen de beschikbare stops. Modes kunnen een eigen indenter registreren; scheme-mode doet dit. indent-region! en indent-buffer! springen regel voor regel in vanaf de bovenkant als één undo-stap.

format-region! en format-buffer! doen daarnaast het volgende:

  • Breiden tabs buiten strings uit naar spaties (instelbaar via (scheme-tab-width 2)).
  • Verwijderen trailing whitespace.
  • Verwijderen overbodige lege regels aan het einde van het bestand (zodat het bestand met exact één newline eindigt).
  • Passen haakjes-conventies toe: [ ] voor bindingen van de let-familie, do, parameterize en with-syntax, en voor clausules van cond, case, case-lambda, guard, syntax-rules en syntax-case; ( ) voor al het andere. Dit kan worden uitgeschakeld via scheme-format-brackets.

De Scheme-engine is de pure (scheme-format) bibliotheek, die ook de tool tools/scheme-format aanstuurt. Met (scheme-format-on-save #t) (standaard aan) wordt elke Scheme-buffer geformatteerd via een pre-save hook voor het opslaan.

Prompts

Input in prompts is regel-bewerkbaar met de gebruikelijke bindingen (C-a/C-e, pijlen, Home/End, C-k/C-y) en ondersteunt aanvulling via TAB (bestandsnamen in bestandsprompts, buffernamen in bufferprompts). TAB breidt uit tot het langste gemeenschappelijke prefix; als er geen uitbreiding mogelijk is, opent een tweede TAB een completions venster.

Input die breder is dan het scherm wordt gewrapt naar vervolgregels, gemarkeerd met een trailing \. Bij acht regels begint het prompt-gebied te scrollen.

Scheme Evalueren

M-x evalueert Scheme in het actieve top-level van de editor, waar de gepubliceerde API en geladen modules beschikbaar zijn. C-x C-e evalueert de huidige buffer. Resultaten verschijnen in de echo area en de gestructureerde log.

Voorbeeld: M-x (buffer-name (current-buffer)) eval: (buffer-name (current-buffer)) => "sa.txt"

Echo area en log

De echo area is een tijdelijke weergave van de log. Elk bericht wordt op een eigen regel geplaatst, voorafgegaan door het component in het grijs. Berichten gemarkeerd als progress vervangen de nieuwste regel van hun component in plaats van een nieuwe regel te stapelen.

Alles wat door de echo area gaat, belandt in de log buffer — het syslog van de editor met records van tijd (nanoseconde-precisie), component en tekst. De log buffer is een read-only weergave die automatisch wordt ververst. Met (log-view 'eval) kan een gefilterde weergave worden gemaakt (log eval).

Algemene bewerkingshulpmiddelen

Generieke bewerkingshulpmiddelen bevinden zich in de (edit) module en accepteren een optioneel where argument. Als dit wordt weggelaten, betekent dit de geselecteerde regio of de gehele huidige buffer.

Voorbeelden:

  • M-x (replace-all! "xx" "yy") — vervangt in de huidige buffer.
  • M-x (replace-all! "xx" "yy" buffer-file) — vervangt in elke file-buffer.
  • M-x (count-matches "xx" "notes.md") — telt matches zonder te bewerken.

Documentatie en de Scheme Manual

De Scheme-handleiding is beschikbaar in de editor:

  • M-x (describe eq-hashtable-ref)
  • C-h f (describe!!) vraagt om een gedocumenteerde functienaam.

Het opent een describe buffer met informatie over vormen, retourwaarden, bibliotheken, bron en volledige tekst. Als de naam een commando is, worden ook alle gebonden toetsen getoond. De data omvat R6RS (van TSPL4) en Chez-extensies. Voer één keer M-x (fetch-describe-data!) uit om de data (ca. 1.400 vermeldingen) te downloaden.

In een Scheme-buffer beschrijft M-. het symbool waar de cursor op staat. Modules kunnen eigen pagina's toevoegen via register-descriptions!.

Pretty Parens

M-x (pretty-scheme-clusters!) schakelt een modus in waarbij haakjes van constructen worden weergegeven als Unicode-paren, terwijl het bestand zelf ASCII blijft:

  • Definities: 「 」
  • Lambdas: <0xE2><0xA6><0x91> <0xE2><0xA6><0x92>
  • Let-familie: ⟨ ⟩
  • Conditionals: <0xE2><0xA6><0x85> <0xE2><0xA6><0x86>
  • Control: ⟦ ⟧
  • Iteratie: <0xE2><0x9F><0x85> <0xE2><0x9F><0x86>
  • Syntax: <0xE2><0xA7><0xBC> <0xE2><0xA7><0xBD>
  • Modules: ⸨ ⸩
  • Quoting: ‹ ›
  • set!: <0xE2><0xA7><0x98> <0xE2><0xA7><0x99>
  • delay: ⌊ ⌋

Applicaties en clausules blijven gewone haakjes, zodat de structuur zichtbaar is zonder deze te overspoelen. Er zijn ook varianten: pretty-scheme-depth! (rotatie op basis van diepte) en pretty-scheme-rainbow! (kleuren per diepteniveau).

Integriteit

De editor vergelijkt bestandsinhoud vóór het bewerken en opslaan, canonicaliseert bezochte paden en overschrijft externe wijzigingen nooit stilzwijgend. Het biedt opties voor overschrijven, opnieuw lezen of een three-way merge.

Configuratie

config.e is een eenvoudig Scheme-bestand zonder bibliotheek of shebang. Elke expressie wordt geëvalueerd in het top-level van de editor. Het wordt geladen bij opstarten en na elke module-reload.

config.e staat niet in versiebeheer. Er wordt een config.template.e meegeleverd met alle opties als commentaar. Kopieer deze naar config.e om te beginnen:

cp config.template.e config.e

Architectuur en extensiemodules

Alles in lib/ is een bibliotheek met de extensie .e.

  • Core: De generieke kernel (buffers, vensters, bewerkingen, rendering, prompts).
  • Sys: De systeemlaag (libc, termios, ioctl, signalen) en de enige bibliotheek met foreign procedures.

De exports van de core vormen de gepubliceerde API. Internen en muteerbare staat zijn onzichtbaar buiten de core. De API bevat commando-procedures, read-only state accessors en extensie-hooks (bind-key!, register-mode!, etc.).

Naamgevingsconventies (Bangs)

  • !! (twee bangs): Wacht op input van de gebruiker (prompt, bevestiging). Neemt geen vereiste argumenten en retourneert niets.
  • ! (één bang): Acteert direct op wat wordt meegegeven, zelfs als er iets wordt getoond (bijv. list-buffers!).

Extensiemodules

Een extensiemodule is een bibliotheek die (core) importeert en een init! exporteert voor registraties. Bij opstarten laadt de core elke lib/*.e bibliotheek en roept init! aan.

Modules kunnen worden herladen zonder de editor te herstarten. Het opslaan van een .e bestand in lib/ laadt dit direct opnieuw. Voor externe bewerkingen gebruik je M-x (reload-module! "naam").

Syntaxis-highlighting

Highlighting wordt verzorgd door modes, geregistreerd via register-mode!. Er worden drie modi meegeleverd: scheme-mode.e, c-mode.e en md-mode.e (Markdown).

Contextuele highlighting (afhankelijk van de cursorpositie) wordt verzorgd door highlighters via add-highlighter!. Voorbeelden hiervan zijn haakjes-matching (paren.e) en incrementeel zoeken (search.e).

Overige details

Muisgebruik

  • Klikken: Focus op venster en cursor plaatsen.
  • Slepen: Selecteren (werkt samen met C-w, C-y).
  • Dubbelklik: Selecteer woord.
  • Slepen statusbar: Vensters redimensioneren.
  • Wiel: Scrollen (1/8e van de hoogte) of horizontaal bewegen.

Native terminal-selectie vereist dat Shift wordt ingedrukt.

Overige functionaliteiten

  • Bracketed paste: Plakacties worden als één edit en één undo-stap behandeld.
  • Kill ring: Verwijderingen accumuleren bij opeenvolgende C-k commando's.
  • Incrementele updates: Alleen gewijzigde regels worden opnieuw getekend; styling is gememoiseerd.

Beperkingen

  • De vensterindeling is simpel: horizontale banden kunnen zij-aan-zij kolommen bevatten, maar het is geen algemeen tiling-systeem.
  • Tabs en andere controletekens worden weergegeven als een enkele spatie.
  • Input wordt gelezen als UTF-8, maar elk teken wordt geacht één terminalkolom in beslag te nemen.

Licentie

MIT