Emacs aanpassen

Emacs is zo groot, en zo complex, dat het zelfs een eigen programmeertaal kent! Ik maak geen grapje: om Emacs werkelijk aan je eigen wensen aan te passen, moet je programma's in deze taal schrijven. Het wordt Emacs Lisp genoemd, en het is een Lisp-dialect, dus als je voorgaande ervaring hebt in Lisp, blijkt het zeer gebruiksvriendelijk. Maak je er geen zorgen om als je hier nog geen ervaring mee hebt: Ik zal het niet diepgaand behandelen, omdat het beslist het beste te leren is door het te doen. Raadpleeg de Infopages over Emacs-Lisp en lees veel broncode in Emacs-Lisp als je echt in Emacs wilt leren programmeren.

De meeste functionaliteit van Emacs is gedefinieerd in Emacs-Lisp [1] code. De meeste bestanden worden gedistribueerd met Emacs en staan collectief bekend als de "Emacs Lisp library". De lokatie van deze library is afhankelijk van de wijze waarop Emacs op je systeem werd geïnstalleerd. Gebruikelijke lokaties zijn /usr/lib/emacs/lisp, /usr/lib/emacs/19.19/lisp/, etc. De "19.19" is het versienummer van Emacs, en dit kan op jouw systeem een andere versie zijn.

Je hoeft op je systeem niet op zoek te gaan naar de Lisp library, omdat Emacs deze informatie intern heeft opgeslagen, in een variabele met de naam load-path. Om achter de waarde van deze variabele te komen, is het nodig het te evalueren. Evalueren wil zeggen de Emacs's lisp interpreter de waarde op te laten halen. Er is een speciale modus voor het evalueren van Lisp-expressies in Emacs, genaamd de lisp-interaction-mode. Gewoonlijk bestaat een buffer met de naam "*scratch*" die zich reeds in deze modus bevindt. Maak een nieuwe buffer met een willekeurige naam aan als je er geen kunt vinden en typ in deze buffer de opdracht M-x lisp-interaction-mode.

Nu heb je een werkruimte voor uitwisseling met de Emacs Lisp interpreter. Typ het volgende:


load-path

en druk als laatste op C-j. In lisp-interaction-mode, is C-j gekoppeld aan eval-print-last-sexp. Een "sexp is een "s-expressie", welk uit een gebalanceerde groep haakjes bestaat, inclusief none. Daarmee is het wat vereenvoudigd, maar je zult er gevoel voor ontwikkelen wat het is onderwijl je met Emacs-Lisp werkt. Hoe dan ook, met het evalueren van load-path zou je iets moeten krijgen als:


load-path{C-j} 
("/usr/lib/emacs/site-lisp/vm-5.35" "/home/kfogel/elithp" 
 "/usr/lib/emacs/site-lisp" "/usr/lib/emacs/19.19/lisp")

Natuurlijk ziet het er op elk systeem niet hetzelfde uit, aangezien dit afhankelijk is van de wijze waarop Emacs werd geïnstalleerd. Het bovenstaande voorbeeld is afkomstig van mijn 386 PC waarop Linux draait. Zoals het bovenstaande aanduidt, is load-path een lijst bestaande uit tekenreeksen. Elke tekenreeks duidt een directory aan waarin Emacs Lisp bestanden kunnen staan. Wanneer Emacs een bestand met Lisp code moet laden, zoekt het dit bestand in één van deze directory's in de opgegeven volgorde. Als een directory wordt genoemd welk in werkelijkheid niet op het bestandssysteem voorkomt, dan negeert Emacs dit gewoon.

Wanneer Emacs wordt opgestart, probeert het automatisch het bestand .emacs uit je homedirectory te laden. Daarom moet je persoonlijke aanpassingen aan Emacs in .emacs plaatsen. De meest gebruikelijke aanpassingen zijn toetsenbordkoppelingen die je als volgt definieert:


(global-set-key "\C-cl" 'goto-line)

global-set-key is een functie met twee argumenten: de te koppelen toets, en de functie die eraan moet worden gekoppeld. Het woord "global" betekent dat deze toetsenbordkoppeling in alle major modi van toepassing zal zijn (er bestaat nog een functie, local-set-key, waarbij een toets wordt gekoppeld aan een enkele buffer). Hiervoor koppelde ik C-c l aan de functie goto-line. De toets wordt middels een tekenreeks beschreven. De speciale syntax "\C-<char>" betekent de ingedrukte Ctrl toets terwijl de toets <char> wordt ingedrukt. Op vergelijkbare wijze duidt, "\M-<char>" de Meta-toets aan.

Alles goed en wel, maar hoe wist ik dat de naam van de functie "goto-line" was? Wellicht dat ik weet dat ik C-c l wil koppelen aan een functie die vraagt om een regelnummer en de cursor dan naar die regel verplaatst, maar hoe kom ik achter de naam van die functie?

Hier komen Emacs online hulpfaciliteiten bij om de hoek kijken. Zodra je weet naar welk type functie je zoekt, kun je Emacs gebruiken om de exacte naam te achterhalen. Hier is een snelle en slordige manier: aangezien Emacs functienamen aanvult, typ je gewoon C-h f (ter herinnering, dit is describe-function), en druk dan zonder nog iets in te tikken op de Tab-toets. Hiermee vraag je Emacs voltooiing uit te voeren op de lege string, met andere woorden, de voltooiing zal overeenkomen met elke functie! Het kan even duren eer de voltooiingslijst is opgebouwd, gezien Emacs nogal wat interne functies kent, maar het zal er zoveel van tonen als op het scherm past wanneer het zover is.

Geef van daaruit de opdracht C-g om de describe-function te verlaten. Er is nu een buffer met de naam "*Completions*", met daarin de voltooiingslijst die je zojuist hebt gegenereerd. Schakel naar die buffer over. Nu kun je C-s isearch gebruiken om naar waarschijnlijke functies te zoeken. Je kunt bijvoorbeeld gerust veronderstellen dat de tekenreeks "line" in de naam van de functie voorkomt dat vraagt om een regelnummer en dan naar die regel gaat. Start daarom gewoon met het zoeken naar de tekenreeks "line" en je zult eventueel vinden waarnaar je op zoek was.

Voor een andere methode kun je gebruik maken van C-h a, command-apropos, voor het tonen van alle functies waarvan in de naam de opgegeven tekenreeks voorkomt. De uitvoer van command-apropos is naar mijn mening wat moeilijker te doorzoeken dan een voltooiingslijst, maar wellicht dat jij er anders over denkt. Probeer beide methoden en kijk wat je er zelf van vindt.

Er bestaat altijd nog een kans dat Emacs niet over een voorgedefinieerde functie beschikt die doet waarnaar je op zoek bent. In deze situatie moet je de functie zelf schrijven. Ik ga je niet voorschrijven hoe je dit doet. Hiervoor zou je de Emacs Lisp library kunnen bekijken op voorbeelden van functiedefinities en de Infopages lezen over Emacs-Lisp. Mocht je een plaatselijke Emacs-goeroe kennen, vraag hem/haar dan hoe dit te doen. Het definiëren van je eigen Emacs functies stelt niet zoveel voor --- om je een indruk te geven heb ik er in het laatste jaar zo'n 131 geschreven. Het vraagt om wat oefening, maar het leerproces is helemaal niet moeilijk.

Mensen gebruiken het bestand .emacs ook vaak voor het instellen van bepaalde variabelen op voorkeurswaarden. Plaats bijvoorbeeld het volgende in .emacs en start Emacs dan opnieuw op:


(setq inhibit-startup-message t)

Emacs controleert de waarde van de variabele inhibit-startup-message om te besluiten bepaalde informatie over de versie en het ontbreken van garantie wanneer het opstart wel of niet weer te geven. In de bovenstaande Lisp-expressie wordt gebruik gemaakt van de opdracht setq om deze variabele in te stellen op de waarde `t'. Dit is een speciale Lisp-waarde met als betekenis true. Het tegengestelde van `t is `nil', wat de toegekende false waarde is in Emacs Lisp. Hier zijn nog twee regels in mijn .emacs bestand die je wellicht van nut vindt:


;; bij het zoeken maakt het niet uit of 
;; hoofdletters of kleine letters worden opgegeven 
(setq case-fold-search nil) 
;; Maakt dat C-programma's zo inspringen als ik dat wil: 
(setq c-indent-level 2)

De eerste expressie maakt dat bij zoekopdrachten (inclusief isearch) geen onderscheid wordt gemaakt in hoofdletters of kleine letters; dat wil zeggen dat er bij het zoeken zowel een overeenkomst wordt gevonden als de opgegeven zoekstring in kleine letters als in hoofdletters werd gevonden, ook als deze slechts werd ingevoerd in kleine letters. De tweede expressie stelt de standaardinspringing wat kleiner in dan normaal voor de statements van de programmeertaal C. Dit is slechts mijn persoonlijke voorkeur; Ik vind dat het de C-code leesbaarder maakt.

Het commentaarteken in Lisp is ";". Emacs negeert alles wat erop volgt, tenzij het onderdeel uitmaakt van een letterlijke tekenreeks, zoals in


;; deze twee regels zullen door de 
;; Lisp-interpreter worden genegeerd, 
;; maar de s-expressie daaropvolgend 
;; zal volledig worden geevalueerd: 
(setq some-literal-string "Een vreemde pause; zonder doel.")

Je doet er goed aan je wijzigingen aan Lisp-bestanden te becommentariëren, omdat je zes maanden later niet meer zult weten wat je gedachte erbij was toen je ze aanpaste. Als de commentaarregel op een eigen regel komt, dan kun je het laten voorafgaan door twee puntkomma's. Dit draagt ertoe bij dat Emacs de inspringingen in Lisp-bestanden correct afhandelt.

Je kunt op dezelfde wijze informatie verkrijgen over Emacs variabelen zoals met functies. Gebruik C-h v, describe-variable voor de aanmaak van een voltooiingslijst, of gebruik C-h C-a, apropos. apropos verschilt in die zin van C-h a, command-apropos, dat het functies en variabelen laat zien in plaats van slechts functies.

De standaardextensie voor Emacs Lisp bestanden is ".el", als in "c-mode.el". Om Lisp-code echter sneller uit te voeren, biedt Emacs de mogelijkheid tot compilatie en deze bestanden met gecompileerde Lisp-code eindigen op ".elc" in plaats van ".el". De uitzondering hierop vormt het bestand .emacs, welk de extensie .el niet nodig heeft, omdat Emacs weet dat het bij het opstarten hiernaar moet zoeken.

Gebruik de opdracht M-x load-file voor het interactief laden van een bestand met Lisp-code. Het zal je vragen om de naam van het bestand. Voor het laden van Lisp-bestanden vanuit andere Lisp-bestanden doe je het volgende:


(load "c-mode") ; dwing Emacs de inhoud van 
                ; c-mode.el of .elc te laden

Emacs zal eerst de extensie .elc aan de bestandsnaam toevoegen en het ergens proberen op te zoeken in het load-path. Als dit niet lukt, probeert het 't met de extensie .el; mocht dat niet lukken dan gebruikt het de letterlijke tekenreeks zoals doorgegeven aan load. Je kunt een bestand compileren met de opdracht M-x byte-compile-file, maar als je het bestand vaak aanpast, dan is dit het waarschijnlijk niet waard. Pas byte-compile-file echter nooit toe op het bestand .emacs, en geef het ook niet de extensie .el.

Nadat .emacs werd geladen, zoekt Emacs naar een te laden bestand met de naam default.el. Gewoonlijk is het te vinden in een directory in load-path met de naam site-lisp of local-elisp of iets dergelijks (zie het voorbeeld van load-path dat ik eerder gaf). Mensen die Emacs op multi-user systemen beheren gebruiken default.el vaak om wijzigingen aan te brengen die van invloed zijn op ieders Emacs, aangezien ieders Emacs het laadt na hun persoonlijke bestand .emacs. Ook op default.el mag de functie byte-compile-file niet worden toegepast, aangezien het meestal tamelijk vaak wordt gewijzigd.

Staan er fouten in iemands .emacs bestand, dan zal Emacs default.el niet trachten te laden, maar in plaats daarvan gewoon stoppen en de melding "Error in init file." of iets dergelijks vluchtig weergeven. Als je deze melding ziet, dan betekent dit waarschijnlijk dat er iets mis is met het bestand .emacs.

Er is nog een type expressie die in .emacs wordt geplaatst. De Emacs Lisp library biedt soms meerdere pakketten om hetzelfde op verschillende manieren te doen. Dit betekent dat je moet opgeven welke je wilt gebruiken (of je krijgt het standaardpakket, wat niet voor alle doeleinden de beste is). Dit vindt bijvoorbeeld plaats bij de interactiefeatures van Emacs's Scheme. Er bestaan twee verschillende Scheme interfaces die met Emacs worden gedistribueerd (tenminste in versie 19): xscheme en cmuscheme.


prompt> ls /usr/lib/emacs/19.19/lisp/*scheme*
/usr/lib/emacs/19.19/lisp/cmuscheme.el
/usr/lib/emacs/19.19/lisp/cmuscheme.elc
/usr/lib/emacs/19.19/lisp/scheme.el
/usr/lib/emacs/19.19/lisp/scheme.elc
/usr/lib/emacs/19.19/lisp/xscheme.el
/usr/lib/emacs/19.19/lisp/xscheme.elc

Ik vind de interface die door cmuscheme wordt geboden veel beter dan die van xscheme, maar standaard maakt Emacs gebruik van xscheme. Hoe kan ik ervoor zorgen dat Emacs handelt in overeenstemming met mijn voorkeur? Ik plaats dit in .emacs:


;; merk op hoe de expressie over twee regels kan worden verdeeld. 
;; Lisp negeert over het algemeen witruimte: 
(autoload 'run-scheme "cmuscheme" 
   "Start een inferieur Scheme, zoals ik dat prettig vind." t)

De functie autoload accepteert de naam van een functie (omsloten door "'", om redenen die te maken hebben met hoe Lisp werkt) en geeft aan Emacs door dat deze functie is gedefinieerd in een bepaald bestand. Het bestand is het tweede argument, een tekenreeks (zonder de ".el" of ".elc" extensie) de naam van het bestand aanduidend waarnaar te zoeken in het load-path.

De resterende argumenten zijn optioneel, maar in dit geval nodig. Het derde argument bestaat uit een documentatiestring voor de functie, zodat je er describe-function op toepassend, wat nuttige informatie over krijgt. Het vierde argument vertelt Emacs dat deze automatisch laadbare functie interactief kan worden aangeroepen (dat wil zeggen met M-x). In dit geval is dat van groot belang, omdat men M-x run-scheme moet kunnen intikken om een schemeproces draaiend onder Emacs te starten.

Wat gebeurt er als ik M-x run-scheme intik, nu run-scheme is gedefinieerd als een automatisch laadbare functie? Emacs zoekt naar de functie run-scheme, ziet dat het is ingesteld om automatisch te worden geladen, en laadt het bestand genoemd door de autoload (in dit geval, "cmuscheme"). Het gecompileerde bestand cmuscheme.elc bestaat, dus Emacs laadt dit. Dat bestand moet de functie run-scheme definiëren, anders ontstaat een autoload error. Gelukkig definieert het run-scheme dus verloopt alles vlekkeloos, en krijg ik mijn voorkeurs Scheme-interface [2]

Een autoload is als een belofte aan Emacs dat, wanneer het zover is, het de opgegeven functie in het opgegeven bestand kan vinden. Hiervoor krijg je enige controle over wat er wordt geladen. Bovendien helpt het automatisch laden ervoor de omvang van Emacs in het geheugen te beperken, door bepaalde features pas te laden wanneer ze nodig zijn. Veel opdrachten zijn niet echt gedefinieerd als functies wanneer Emacs opstart. In plaats daarvan zijn ze ingesteld als automatisch te laden vanuit een bepaald bestand. Hoef je de opdracht nooit aan te roepen, dan zal het ook nooit worden geladen. Deze ruimtebesparing is in wezen vitaal voor het functioneren van Emacs: als het elk beschikbaar bestand in de Lisp-library zou laden, dan zou het wel twintig minuten duren eer het zou zijn opgestart, en dit zou wel eens al je beschikbare geheugen op je computer in beslag kunnen nemen. Je hoeft deze automatisch laadbare functies niet in .emacs in te stellen; hier werd bij de samenstelling van Emacs al voor gezorgd.

Noten

[1]

Soms wordt het onofficieel "Elisp" genoemd.

[2]

Tussen twee haakjes, de interface waar ik het eerder over had, was cmuscheme, in de sectie over het werken met Scheme, dus als je iets wilt gebruiken uit die tutorial, moet je er voor zorgen dat je cmuscheme draait.