Aronsson Datateknik

Major och minor modes

av Lars Aronsson, december 1991

Detta LysKOM-inlägg från 1991 återges enbart av nostalgiska skäl. Det skrevs i en tidsålder då man gick över från 7-bitskoderna ASCII och ISO 646 till 8-bitskoden ISO 8859-1 och hade problem att få alla program att fungera med detta. Författaren har använt GNU Emacs dagligen sedan 1989, och gamla TECO Emacs dessförinnan.


153741 1991-12-05  04:04  /197 rader/ Lars Aronsson Lysator
Mottagare: Emacs erfarenhetsutbyte <805>
Markerad av dig och 2 andra.
Ärende:  Det här kunde nästan platsa i GARB...
------------------------------------------------------------
Jag har nu skrivit en nätt liten swedish-keys-mode, som liknar den
swedish-iso-mode som Staflin med flera bidragit till. Skillnaden är
att min är en minor mode och den andra är en major mode. Jag tänkte
berätta lite hur den ser ut och hur man kan skriva egna minor modes.


			MAJOR OCH MINOR MODES

Alla kanske inte vet skillnad på major och minor modes, så jag tar det
helt kort. Båda slagen av moder kan binda tangenter till magiska
funktioner (så att LINEFEED auto-indenterar C-program, eller C-c C-c
skickar in ett inlägg i LysKOM-edit-mode).

En major mode börjar dagen med att kasta ut allt gammalt badvatten och
slår sedan fast sina egna tangentbindningar. Dessa gäller i bufferten
ända tills man byter major mode igen. Typiska major modes är c-mode
och fundamental-mode.

En minor mode kan slås på och av flera gånger under det att man har en
och samma major mode. När man slår av en minor mode, så måste man
naturligtvis sopa igen alla spår efter sig. Detta betyder att man inte
kan kasta ut gammalt badvatten när man slår på den, för man riskerar
då att slänga ut eventuella barn också. En minor mode måste kunna
samsas med andra moder, både major och minor. Typisk minor mode är
auto-fill-mode, som normalt körs under lyskom-edit-mode.


		  SWEDISH-ISO-MODE ÄR EN MAJOR MODE

Swedish-iso-mode är en major mode som sätter amerikanska ][\ till att
skapa svenska ÅÄÖ, vilket är behändigt om man har amerikanskt
tangentbord. Om man skriver C-kod och vill skriva lite svensk text i
kommentarerna, så kan man slå över till swedish-iso-mode och sedan gå
tillbaks till c-mode när man är klar.

Men om man kör outline-mode, en major mode för att strukturredigera
stora rapporter, så är det väldigt obekvämt att byta till en annan
major mode. Det var därför jag valde att skriva en minor mode, som
kunde samsas med t ex outline-mode.

Swedish-iso-mode är egentligen två moder. Den andra heter
indented-swedish-iso-mode. Båda dessa inkluderar funktionalitet från
vanliga text-mode, som också är en major mode, nämligen center-line
(ESC s), center-paragraph (ESC S) och indentering (TAB). Detta behövs
inte när min swedish-keys-mode är en minor mode. Den samsas nämligen
utmärkt med vanliga text-mode.


		    HUR DU KAN SKRIVA MINOR MODES

Det är några saker du bör tänka på när du skriver en minor mode. Jag
visste inte om det här innan och allt står inte i dokumentationen till
GNU Emacs Lisp.

Det enda exemplet jag har hittills på en minor-mode är auto-fill-mode.
Auto-fill-mode vänder till ny rad när man trycker mellanslag bortom
fyll-positionen (normalt 70). Det är alltså bara _en_ tangent som
påverkas, mellanslag. Dessutom binds den inte om (bara en gång). Det
är _en_ funktion som tar hand om mellanslag oavsett om auto-fill-mode
är på eller inte. Den funktionen kollar sedan om den buffert-lokala
flaggan auto-fill-hook (illa valt namn) är på eller inte. Bindningen
av mellanslagstangenten kan alltså vara global, det räcker om flaggan
är buffert-lokal.

När jag gjorde swedish-keys-mode, så ville jag inte göra som
auto-fill-mode. Enkelt, tänker ni, bara att lokalt binda om dom sex
tangenterna [\]{|}. Funktionen "local-set-key: Give KEY a local
definition of COMMAND" borde duga. Trodde jag också, tills jag läste
fortsättningen: "The definition goes in the current buffer's local
map, which is shared with other buffers in the same major mode". Vad
sägs om det? Ni kommer alltså att samtidigt slå på eller av moden i
samtliga buffertar som kör samma major mode! Det duger inte.


		    EN BUFFERT-LOKAL TANGENTKARTA

Vi måste veta att den här bufferten har en egen lokal tangentkarta.
Det gör vi genom att skaffa en buffert-lokal variabel och en funktion
(dom har samma namn, vilket går bra i Emacs Lisp):

	(defvar aronsson-buffer-local-map nil
	  "Pointer to buffer-local keymap.")

	(make-variable-buffer-local 'aronsson-buffer-local-map)

	(defun aronsson-buffer-local-map ()
	  "Return a buffer-local keymap.
	So-called local keymaps tend to be major-mode-local,
	not buffer-local."
	  (cond ((not aronsson-buffer-local-map)
		 (setq aronsson-buffer-local-map (current-local-map))
		 (if (not aronsson-buffer-local-map)
		     (setq aronsson-buffer-local-map (make-keymap))
		   (setq aronsson-buffer-local-map
			 (copy-keymap aronsson-buffer-local-map)))
		 (use-local-map aronsson-buffer-local-map)))
	  aronsson-buffer-local-map)       

Om det är första gången vi efterfrågar en buffert-lokal tangentkarta i
den här bufferten, så exekveras första cond-grenen och en helt ny
karta skapas. Vid upprepade anrop returneras den befintliga variabeln.
Funktions- och variabelnamnet har tills vidare förledet "aronsson-"
eftersom jag använder den i flera moder.


		     SPARA UNDAN GAMLA BINDNINGAR

Antag nu att vi redigerar C-kod med c-mode och slår på
swedish-keys-mode för att skriva nåra kommentarer. När vi sedan slår
av swedish-keys-mode, så ska ju c-modes bindningar av { och }
(auto-indenterande) komma tillbaka. Innan vi gör define-key måste vi
alltså spara undan de befintliga bindningarna i en buffert-lokal
variabel.

	(defvar swedish-keys-old-bindings nil
	  "Buffer-local store for hidden key bindings.")


		     SLÅ PÅ OCH AV EN MINOR MODE

En minor mode består, förutom av tangentbindningarna, av en funktion
som slår på och av moden. Det finns en hel vetenskap för hur detta ska
fungera. Det finns emellertid ännu ingen minor mode som helt följer
alla reglerna (så vitt jag vet). Jag gör likadant som auto-fill-mode
och det betyder att utan argument till funktionen swedish-keys-mode,
så slås den omväxlande på eller av (togglas). Om ett argument finns,
och är ett positivt heltal, så slås moden på (oavsett om den redan var
på) och för andra argument slås den av. Detta implementeras som så
här. Vi har en buffert-lokal flagga som vet om moden var på- eller
avslagen:

	(defvar swedish-keys-mode nil
	  "If this buffer-local variable is true,
	   Swedish keys are used.")

	(defun swedish-keys-mode (&optional arg)
	  "Toggle Swedish keys.
	With arg, turn Swedish keys on iff arg is positive.
	This minor mode binds keys [\\]{|} to insert Swedish letters.
	Unfortunately, this applies only to the local buffer, and not
	to the mini buffer used by search commands."
	  (interactive "P")
	  (make-local-variable 'swedish-keys-mode)
	  (make-local-variable 'swedish-keys-old-bindings)
	  (cond
	   ((equal swedish-keys-mode
		   (if arg (> (prefix-numeric-value arg) 0)
		     (not swedish-keys-mode)))
	    )
	   ((not swedish-keys-mode)             ; Turn on
	    (swedish-keys-on))
	   (swedish-keys-mode                   ; Turn off
	    (swedish-keys-off))))

Det finns en global associationslista som heter minor-mode-alist. Den
håller reda på vad som ska skrivas i Emacs mode-rad när vi kör en
speciell mode:

	(setq minor-mode-alist
	      (append minor-mode-alist '((swedish-keys-mode " ÅÄÖ" ))))


			 BIND OM TANGENTERNA

Så slutligen till själva koden som binder om tangenterna. Den är rätt
så enkel och rakt på:

	(defconst swedish-keys-list
	  '(("[" . "\C-q\304") ("\\". "\C-q\326")
	    ("]" . "\C-q\305") ("{" . "\C-q\344")
	    ("|" . "\C-q\366") ("}" . "\C-q\345"))
	  "A list of key bindings used for Swedish keys.
	In each element of the list, CAR is the key
	and CDR is the binding.")

	(defun swedish-keys-on ()
	  "Turn on Swedish keys. Internal function."
	  (setq swedish-keys-mode t)
	  (let ((r swedish-keys-list)
	        (m (8859-buffer-local-map))
	        (old nil))
            (while r
	      (setq old (cons (key-binding (car (car r))) old))
	      (define-key m (car (car r)) (cdr (car r)))
	      (setq r (cdr r)))
            (setq swedish-keys-old-bindings (reverse old))))
   
	(defun swedish-keys-off ()
	  "Turn on Swedish keys. Internal function."
          (setq swedish-keys-mode nil)
          (let ((r swedish-keys-list)
	        (m (8859-buffer-local-map))
                (old swedish-keys-old-bindings))
            (while r
	      (define-key m (car (car r)) (car old))
              (setq r (cdr r))
              (setq old (cdr old)))))
(153741) -----------------------------------
Kommentar i text 153749 av Marwin


Valid HTML 4.0! Updated November 19, 2008