Vierter Termin

Emacs-Workshop

1 2010-04-19 – Extensions nutzen    

1.1 Emacs Lisp – Nachtrag

1.1.1 Lokale Variablen

Lokale Variablen kann man mit let erzeugen. Sie verdecken globale
Variablen gleichen Namens.

(setq foo 'bar)
(let (foo)
  (setq foo 'quux)
  (message "local foo is %s" foo))
;; gibt "local foo is quux" aus

Lokale Variablen koennen mit let initialisiert werden:

(let ((foo 'quux)
      bar)
  (message "foo is %s" foo))
;; gibt "foo is quux" aus

Die Gueltigkeit von lokalen Variablen wird zur Laufzeit bestimmt
(“dynamic scope”), dies kann zu unvorhergesehenen Effekten fuehren und
erlaubt leider keine “closures”. In diesem Beispiel wurde das let
zum Zeitpunkt des Funktionsaufrufes verlassen und wirkt daher nicht
mehr:

(setq foo 'bar)
(fset 'my-function
      (let ((foo 'quux))
        (lambda () (message "foo ist %s" foo))))
(my-function) ; => "foo ist bar"

Hier ist das let Teil des Funktionskoerpers, so funktioniert’s:

(fset 'my-other-function
      (lambda () 
        (let ((foo 'quux))
          (message "foo ist %s" foo))))
(my-other-function) ; => "foo ist quux"

Das Makro lexical-let aus dem Paket “cl” simuliert einen “lexical
scope” wie man ihn aus Common Lisp, Scheme oder JavaScript kennt:

(fset 'my-lexical-function
      (lexical-let ((foo 'quux))
        (lambda () (message "foo ist %s" foo))))
(my-lexical-function) ; => "foo ist quux"

Dynamischer Scope hat den Vorteil, dass man globale Variablen leicht
voruebergehend auf einen neuen Wert setzen kann, ohne dass man sich
den alten Wert merken und wieder zuruecksetzen muss. Dies wird in
Emacs an vielen Stellen genutzt und ist ein uebliches Idiom.

Als praktisches Beispiel hier eine bessere Version von
fix-c-base-spelling, die beim suchen und ersetzen Gross- und
Kleinschreibung beachtet. Entscheidend ist hier die Variable
case-fold-search, die hier voruebergehend auf nil gesetzt wird:

(defun fix-c-base-spelling () 
  (save-excursion
    (goto-char (point-min))
    (let ((case-fold-search nil))
      (while (re-search-forward "C-Base" nil t)
        (replace-match "c-base" t)))))

1.1.2 buffer-local Scope

case-fold-search ist ausserdem ein Beispiel fuer eine Variable die
“buffer-local” ist. Eine solche Variable hat einen globalen
“default”-Wert der in einem Buffer ueberschrieben werden kann. Diesen
“default” kann man mit setq-default setzen:

(setq-default case-fold-search nil)

Ein einfaches setq setzt die Variable nur fuer den aktuellen Buffer.

1.1.3 Schleifen

Fuer Iteration bietet Emacs Lisp einige Konstrukte. Die einfachste ist while:

(setq i 0)
(while (< i 10)
  (insert "I will read M-x info before asking stupid questions.\n")
  (setq i (1+ i)))

Fuer einen einfachen Fall wie diesen ist dotimes praegnanter:

(dotimes (i 10)
  (insert "I will read M-x info before asking stupid questions.\n"))

Um ueber eine Liste zu iterieren benutzt man mapcar:

(mapcar '1+ '(1 2 3))
;; => (2 3 4)

(mapcar (lambda (i) (* i 2))
        '(1 2 3))
;; => (2 4 6)

(apply 'string 
  (mapcar '1+ "abc"))
;; => "bcd"

mapc ist sehr aehnlich, gibt aber als Rueckgabewert die Liste
unveraendert zurueck. Man verwendet mapc um deutlich zu machen, dass
man nur an den Seiteneffekten, in diesem Fall der Textausgabe,
interessiert ist.

(mapc (lambda (i) (insert (number-to-string (* i 2))))
      '(1 2 3))
;; => (1 2 3)

1.1.4 Bedingte Verzweigung

“if” wurde von Lisp erfunden. So sieht es in der Praxis aus:

(if (eq major-mode 'org-mode)
    (message "yay!")
  (message "nay."))

Es gibt keine syntaktischen Schluesselwoerter, die anzeigen muessten,
wo die Bedingung oder der THEN-Zweig enden, es ergibt sich aus der
Listenstruktur. Hier ein Beispiel aus der Praxis:

(add-hook 'ruby-mode-hook 
  (lambda () 
    (if (fboundp 'subword-mode) 
        (subword-mode 1)
      (c-subword-mode 1))))

Ein “if” hat auch immer einen Rueckgabewert und ist so dem ternaeren
Operator “?” in C und verwandten Sprachen vergleichbar:

(setq browse-url-browser-function
      (if window-system 'browse-url-mozilla 'w3m-browse-url))

if ist eine special form, wenn wir versuchen es als Funktion zu
schreiben, sehen wir, dass die Argumente evaluiert werden, bevor die
Funktion laeuft:

(defun my-if (cond then else)
  (if cond then else))

(my-if t (insert "foo") (insert "bar"))
;; fuegt "foobar" in den aktuellen Buffer ein

Der ELSE-Zweig kann mehrere Befehle (“forms”) hintereinander enthalten
(“implicit progn”):

(if (eq major-mode 'lisp-mode)
    (message "yay!")
  (setq my-message "What a pity! I love lisp!")
  (message my-message))

Fuer den THEN-Zweig braeuchte man hier ein progn:

(if (eq major-mode 'lisp-mode)
    (progn
      (set my-message "Happy happy, joy joy!")
      (message my-message))
  (setq my-message "What a pity! I love lisp!")
  (message my-message))

Wenn es nur den THEN-Zweig oder den ELSE-Zweig gibt, kann man
stattdessen die Funktionen “when” und “unless” verwenden:

(when window-system
  (color-theme-charcoal-black))

1.1.5 Praktisch: Zeitstempel einfuegen

(global-set-key (kbd "C-c m")
  (lambda () 
    (interactive)
    (insert
     (format "%s <%s>" 
             (format-time-string "%Y-%m-%d")
             user-mail-address))))

1.2 Was ist eine Extension?

Eine Emacs Extension fuegt neue Funktionalitaet hinzu oder
veraendert bestehende. Der Begriff Library wird ebenso benutzt und
bedeutet dasselbe. Beispiele:

  • erlang-mode
  • AucTeX
  • magit
  • w3m, jabber-el
  • nav, tabbar

Viele Extensions sind bereits Teil von Emacs, und es werden immer
neue aufgenommen, z.B. CEDET in Emacs 23.2, der bald (TM) erscheinen
wird. Dieser Prozess ist nicht so einfach, da dafuer – aus politischen
Gruenden – das Copyright der FSF ueberschrieben werden muss.

1.3 Extensions installieren

1.3.1 Extension mit einer Datei am Beispiel “Visible Bookmarks”

Erst mal sollten wir einen Blick auf die Datei werfen, die wir
heruntergeladen haben. Man beachte den standardisierten Dateikopf und
die Dokumentation darin. Fuer kleinere Pakete ist das auch die
Regel. Auch interessant ist das Ende der Datei.

Zum testen tut es ein

M-x load-file

Genauso gut ist

M-x eval-buffer

wenn man die Datei schon offen hat.

  • Laden einer Extension mit load und require

    Um diese Funktion permanent zur Verfuegung zu haben, muss sie in
    “bm.el” umbenannt und in ein bekanntes Verzeichnis verschoben
    werden. Der Name ist Geschmackssache: “.elisp”, “.emacs-lisp”,
    “.emacs-lisp.d” oder “.local-lisp” sind eine gute Wahl. Das
    Verzeichnis “.emacs.d” wird von verschiedenen Emacs-Paketen benutzt,
    abgesehen davon, dass es unuebersichtlich werden kann, spricht auch
    nichts dagegen, sein Elisp dort abzulegen.

    (add-to-list 'load-path "~/.elisp")
    (load "bm")
    

    Das funktioniert, hat aber den Nachteil, dass “bm.el” zwei mal geladen
    wird, wenn dieser Code noch einmal evaluiert wird. Besser ist es
    daher, die Funktion require zu benutzen, wie es der Kommentar auch
    empfiehlt:

    (require 'bm)
    

    require funktioniert nur, wenn die Datei mit provide endet. Das
    Symbol bm wird dadurch in die Liste features aufgenommen.

    features 
    ;; => (tooltip ediff-hook vc-hooks lisp-float-type mwheel...)
    
    (featurep 'bm)
    ;; => t, wenn bm geladen wurde
    
  • Grundkonfiguration

    Tastensequenzen der Form “C-c <Buchstabe>” und die Funktionstasten
    F5-F9 sind fuer den Benutzer reserviert. Natuerlich kann man sich auch
    dafuer entscheiden, bereits vorhandene Tastenbindungen zu
    ueberschreiben, wie es “bm.el” auch vorschlaegt:

    (global-set-key (kbd "<C-f2>") 'bm-toggle)
    (global-set-key (kbd "<f2>")   'bm-next)
    (global-set-key (kbd "<S-f2>") 'bm-previous)
    

    Die kbd-Funktion hilft uns dabei, die Tastenbindungen leserlich und
    portabel zu definieren.

  • Alternative zum Laden: autoload

    Wenn man viele Extensions in seiner .emacs laedt, verlaengert sich der
    Startprozess und der Speicherverbrauch. Dabei ist es unter Umstaenden
    gar nicht so entscheidend, ob all die Funktionalitaet schon von Anfang
    geladen ist, sie muss nur zum richtigen Zeitpunkt verfuegbar
    sein. Dabei hilft uns die Funktion autoload:

    (autoload FUNCTION FILE &optional DOCSTRING INTERACTIVE TYPE)
    

    In unserem Fall saehe dies dann so aus:

    (autoload 'bm-toggle "bm" "Toggle bookmark in current buffer." t)
    

    Ein weiterer Vorteil: wenn wir unsere Konfiguration mitnehmen und
    “bm.el” noch nicht installiert haben, bekommen wir beim Starten keinen
    Fehler.

  • Bytecompile

    Elisp-Code laedt und laeuft schneller, wenn er compiliert ist. Mit

    M-x byte-compile-file
    

    erzeugt man eine .elc-Datei, die dann von require und load
    bevorzugt geladen wird.

  • Bedingte Konfiguration

    Wenn die Extension staendig aktiv sein soll (z.B. persistente
    Bookmarks bei “bm”), hilft uns autoloading nicht. Wenn wir trotzdem
    eine universell einsetzbare .emacs haben wollen, koennen wir uns einen
    Parameter von require zunutze machen und ein wenig Logik schreiben:

    (if (require 'bm nil t)
        (progn
          (global-set-key (kbd "<C-f2>") 'bm-toggle)
          (global-set-key (kbd "<f2>")   'bm-next)
          (global-set-key (kbd "<S-f2>") 'bm-previous)))
    

    Das progn ist noetig, um die Anweisungen fuer den THEN-Zweig zu
    gruppieren, sonst wuerde die zweite und alle folgenden “Forms” dem
    ELSE-Zweig zugeordnet. when hat keine ELSE-Zweig und ermoeglicht uns
    eine etwas elegantere Schreibweise:

    (when (require 'bm nil t)
      (global-set-key (kbd "<C-f2>") 'bm-toggle)
      (global-set-key (kbd "<f2>")   'bm-next)
      (global-set-key (kbd "<S-f2>") 'bm-previous))
    
  • Verzoegerte Konfiguration

    Manche Einstellungen koennen erst vorgenommen werden, wenn ein Paket
    geladen ist: ein Hook ist vorher nicht definiert oder eine Liste, zu
    der wir ein Element hinzufuegen wollen, existiert noch nicht. Dies
    tritt natuerlich vor allem bei “autoloading” auf, wie es auch Emacs
    selbst benutzt, z.B. fuer die grep-Extension. Wenn ich z.B. nicht
    moechte, dass M-x rgrep Verzeichnisse ignoriert, die log heissen,
    kann ich das so konfigurieren:

    (eval-after-load "grep"
      '(add-to-list 'grep-find-ignored-directories "log"))
    

    Wichtig: ich uebergebe eine zitierte Liste, der Code wird erst spaeter
    (mit eval) ausgefuehrt. Wenn ich mehrere Kommandos ausfuehren will,
    brauche ich wieder ein progn:

    (eval-after-load "grep"
      '(progn
         (add-to-list 'grep-find-ignored-directories "log")
         (add-to-list 'grep-files-aliases ("r" . "*[.e]r[bh]*"))))
    

    Manchmal erledigt sich das Problem, wenn die Konfiguration waechst,
    manchmal nicht:

    (eval-after-load "grep"
      '(progn
         (mapc (lambda (i) 
                 (add-to-list 'grep-find-ignored-directories i))
               '("log" "downloads" "demo_content"))
         (mapc (lambda (e) (add-to-list 'grep-files-aliases e))
               '(("js" . "*.js") ("css" . "*.css*") ("r" . "*[.e]r[bh]*")))))
    
  • Befehle zum Umgang mit Libraries

    locate-library gibt den Pfad zu einer Library aus:

    M-x locate-library
    

    find-library oeffnet die Datei mit dem Quellcode:

    M-x find-library
    

    list-load-path-shadows zeigt, welche Libraries mehrfach vorhanden
    sind:

    M-x list-load-path-shadows
    

1.3.2 Extension mit vielen Dateien: Emacs-Jabber

Der Jabber-Client kann hier heruntergeladen werden: http://emacs-jabber.sourceforge.net/

Er wird standardmaessig mittels autoconf installiert, z.B.

./configure --prefix=/home/anselm/.local

Es ist in der Regel aber auch moeglich, ein groesseres Paket von Hand
zu installieren, in diesem Fall sind hierzu folgende Schritte notwendig:

  • die Elisp-Dateien in einen Ordner kopieren
  • diesen Ordner zum load-path hinzufuegen
  • die Dateien “byte-compilen”
  • optional die Dokumentation mit makeinfo aus Texinfo-Quellen
    uebersetzen, in das info-Verzeichnis kopieren, komprimieren und mit
    install-info in die Datei “dir” eintragen

1.3.3 Hilfe (?) bei der Konfiguration: customize

Das customize-System ist der Versuch, ein benutzerfreundliches
Konfigurationsinterface fuer Emacs zu schaffen. Ob man es mag oder
nicht, um sich einen Ueberblick ueber die vorhandenen
Konfigurationsmoeglichkeiten zu verschaffen, reicht es allemal. Die
volle Flexibilitaet erreicht man aber nur mit Elisp. Hier die
wichtigsten Befehle:

M-x customize
M-x customize-group
M-x customize-variable
M-x customize-saved

Customize speichert seine Daten direkt in der .emacs, kann aber auch
dazu angewiesen werden, sie in einer anderen Datei abzulegen. In
diesem Fall sind wir selbst dafuer verwantwortlich, diese auch zu
laden:

(setq custom-file "~/.emacs-custom.el")
(load (expand-file-name "~/.emacs-custom.el") t)

1.3.4 Hilfe bei der Installation: auto-install und ELPA

  • auto-install

    http://www.emacswiki.org/emacs/AutoInstall
    http://www.emacswiki.org/emacs/download/auto-install.el

    (add-to-list 'load-path "~/.emacs.d/auto-install")
    (mapc (lambda (source)
            (autoload (intern (format "auto-install-from-%s" source))
              "auto-install" nil t))
          '(buffer url emacswiki gist library directory dired))
    

    Oder einfach (require 'auto-install).

    Installiert doch mal anything aus dem EmacsWiki:

    M-x auto-install-from-emacswiki
    
  • ELPA

    ELPA ist ein Paketmanager fuer Emacs.

    In der naechsten Version von Emacs ist er moeglicherweise
    enthalten. XEmacs hat schon lange einen eigenen Paketmanager.

  • Distribution

    Linux-Distributionen bieten natuerlich auch Pakete von
    Emacs-Extensions an. Diese landen dann ueblicherweise in
    /usr/share/emacs/site-lisp.

    Bei Debian/Ubuntu landet die Konfiguration in /etc/emacs. Ebenfalls
    interessant sind die Skripte unter /var/lib/emacsen/