Como construir um plugin para GNU Emacs
O que é GNU Emacs?
[editar | editar código-fonte]GNU Emacs é a variante mais utilizada de uma família de editores de texto que se destacam por sua extensibilidade, conhecidos como EMACS[1][2]. Desenvolvido em 1984 por Richard Stallman[3], o GNU Emacs se destaca dos demais de sua família por ser um marco no movimento do software livre e um componente central do projeto GNU.
O GNU Emacs, assim como outros da família, tem muitas de suas funcionalidades implementadas em um dialeto de Lisp, conhecido como Emacs Lisp[4]. Portanto, sua customização se dá por meio de código escrito nessa linguagem, que é primariamente interpretada, e o GNU Emacs possui um interpretador de GNU Emacs Lisp. Assim, o desenvolvedor tem à sua disposição uma linguagem de programação completa para desenvolver plugins para esse editor de texto.
Fundamentos de Lisp e GNU Emacs Lisp
[editar | editar código-fonte]Conceitos importantes de Lisp
[editar | editar código-fonte]A sintaxe de Lisp é principalmente composta de listas, símbolos e valores, e código pode também ser tratado como dados comuns. Por exemplo, a seguinte função:
(defun print-text(text)
message "%s" text))
Internamente é entendida como uma lista com os elementos defun print-text
, e com uma lista aninhada text
. Essa característica de Lisp possibilita metaprogramação, que é importante para a customização de funções do editor de texto.
Particularidades do Emacs Lisp
[editar | editar código-fonte]Primeiramente, é válido destacar os tipos específicos do GNU Emacs Lisp, tais como buffer, window, frame, thread, keymap, entre outros. Esses tipos, dos quais a maioria necessita de uma API para obtê-los, diferente dos tipos primitivos de Lisp, permitem modificar a interface do GNU Emacs — que por sua vez é um editor de texto com interface gráfica (GUI), diferente de outros como o vi e o vim.
Além disso, GNU Emacs Lisp possui uma particularidade de namespaces, possuindo um exclusivo para variáveis, e um exclusivo para funções, de modo que é possível termos uma variável e uma função com o mesmo nome nessa linguagem.
Buffers no GNU Emacs
[editar | editar código-fonte]Um buffer no GNU Emacs é um objeto que contém texto a ser exibido ou editado. O texto que está contido num buffer pode tanto ser originado de um arquivo quanto gerado dentro do próprio GNU Emacs (como o minibuffer, um buffer especial para executar comandos no GNU Emacs).
Além de texto, buffers também possuem parâmetros que controlam a cor do texto, fonte, tamanho, entre outros. Adicionalmente, buffers podem conter um conjunto próprio de variáveis que definem o comportamento de códigos que rodarem no contexto daquele buffer (esse conceito será importante para entender modes).
Avaliações
[editar | editar código-fonte]Lisp oferece um mecanismo para avaliar expressões, e cada elemento da linguagem é avaliado de uma maneira.
- Valores primitivos são autoavaliados:
10
"texto qualquer"
Ao avaliarmos as expressões acima — no GNU Emacs, isso pode ser feito com o comando C-x C-e
—, notamos que o resultado são elas mesmas.
- Símbolos retornam seu valor quando avaliados:
buffer-file-name
Ao avaliarmos o símbolo acima, obtemos seu valor, que no ambiente do GNU Emacs corresponde ao arquivo que está aberto no buffer atual. Símbolos podem ser valores (variáveis) ou funções.
- Listas são avaliadas de acordo com o primeiro símbolo que aparece nelas:
(+ 3 2)
Ao avaliarmos a lista acima, o interpretador primeiro avalia o primeiro símbolo +
, que corresponde a uma função, e então avalia essa função passando como parâmetro os valores seguintes 3 2
, retornando 5
.
Uma maneira rápida de escrever código em Emacs Lisp e avaliá-lo dentro do GNU Emacs é por meio do comando M-:
.
Construindo um plugin para GNU Emacs
[editar | editar código-fonte]Veremos agora um exemplo de como construir um plugin para GNU Emacs criando um minor mode.
Modes
[editar | editar código-fonte]Modes no GNU Emacs são modos do editor que oferecem ao usuário certas funcionalidades quando estão ativos, e podem ser divididos em dois tipos:
- Major modes: são o mode principal de um buffer, e apenas um pode estar ativo por vez (por exemplo:
org-mode
). - Minor modes: são modes que proveem funcionalidades extras em um buffer ou até mesmo globalmente no GNU Emacs. Mais de um pode estar ativo por vez (por exemplo:
auto-fill-mode
).
As funcionalidades que um mode provê podem ser:
- Um keymap que só é ativado se o mode em questão está ativo.
- Um indicador no mode line que mostra se o mode está ativo.
- Um hook, que executa outras funções ou modes quando o mode em questão é ativado.
- Código que é executado quando se ativa ou desativa o mode em questão.
Requisitos para criar um minor mode
[editar | editar código-fonte]Um minor mode precisa cumprir os seguintes requisitos para funcionar corretamente no GNU Emacs:
- Uma variável para indicar se o mode está ativo:
- O nome dessa variável deve terminar em
-mode
- O valor
nil
indica que o mode está desativado, e qualquer outro valor indica que está ativado.
- O nome dessa variável deve terminar em
- Um comando para habilitar e desabilitar o mode:
- Esse comando deve ter o mesmo nome da variável que indica se o mode está ativo ou não.
- O comando pode realizar quaisquer mudanças necessárias no estado local do buffer ou global do GNU Emacs para que o mode funcione como esperado.
- O comando deve previamente checar se o mode já está ativo ou não.
Opcionalmente, o minor mode pode também conter:
- Um keymap para o mode:
- Para criar um keymap específico desse mode, basta adicionar um par chave-valor no
minor-mode-map-alist
, que registra todos os keymaps de todos os modes do GNU Emacs.
- Para criar um keymap específico desse mode, basta adicionar um par chave-valor no
- Um indicador de que o mode está ativo:
- Para isso, basta registrar o mode no
minor-mode-alist
junto com a string a ser mostrada no mode line quando ele estiver ativo.
- Para isso, basta registrar o mode no
Exemplo
[editar | editar código-fonte]Iremos construir do zero um minor mode com uma única funcionalidade (um keymap), e registrá-lo na lista de minor modes do editor de texto.
- Primeiramente, criemos uma variável para representar se nosso minor mode está ativo:
(make-variable-buffer-local
(defvar example-mode nil
"Toggle example-mode."))
Note que aqui usamos make-variable-buffer-local
, de forma que permitimos valores diferentes para essa variável a depender do buffer em que estamos, o que nos permite utilizar esse mode apenas nos buffers que desejarmos. Além disso, estamos por padrão deixando o mode desativado (pois passamos o valor de inicialização nil
para a variável)
- Em seguida, criemos uma variável para o keymap do nosso mode:
(defvar example-mode-map (make-sparse-keymap)
"Keymap for test-mode.")
Aqui, utilizamos a função make-sparse-keymap
para criar um objeto do tipo keymap, específico do GNU Emacs.
- Agora, criemos uma keybinding que será acessível quando nosso mode estiver ativo:
(define-key example-mode-map (kbd "C-c C-p")
(lambda()
(interactive)
(message "hello, world!")))
Nesse trecho, relacionamos uma sequência de comandos de teclado com uma função lambda que imprime "hello, world!"
no minibuffer. Utilizamos a anotação (interactive)
para que o GNU Emacs reconheça essa função como um comando que pode ser executado via a keybinding definida, ou com M-x nome da função
, caso ela tenha um nome. No nosso caso, temos uma função anônima (lambda), e portanto ela somente pode ser executada via comandos de teclado.
- Como já definimos as funcionalidades do nosso minor mode, vamos registrá-lo para que o GNU Emacs o reconheça como um dos modes disponíveis:
(add-to-list 'minor-mode-alist '(example-mode " example"))
(add-to-list 'minor-mode-map-alist (cons 'example-mode example-mode-map))
Aqui registramos nosso mode no minor-mode-alist
junto com a string a ser mostrada no mode-line quando ele estiver ativo.
- Agora, definamos uma função para ativar ou desativar o mode que criamos:
(defun example-mode (&optional ARG)
(interactive (list 'toggle))
(setq example-mode
(if (eq ARG 'toggle)
(not example-mode)
(> ARG 0)))
Nesse trecho, definimos um argumento ARG
opcional para ativar ou desativar nosso mode — por convenção, valores maiores que 0 ativam um mode, e valores menores ou iguais a 0 desativam-no.
Em seguida, usamos novamente a anotação (interactive)
para que essa função possa ser executada via M-x
ou keybinding, e passamos o argumento padrão 'toggle
para ela caso seja invocada interativamente.
Por fim, definimos o valor da variável example-mode
baseado no argumento ARG
passado. Caso seja igual à 'toggle
, então o mode é desativado caso esteja ativado e vice-versa. Caso ARG
seja um número, utilizamos a convenção para ativarmos ou desativarmos o mode: a expressão (> ARG 0)
retorna t
caso verdadeira, e nil
, caso falsa.
- Para finalizar, definamos comportamentos para quando ativamos ou desativamos o mode:
(if example-mode
(message "mode activated!")
(message "mode deactivated...")))
Com isso, criamos do zero um minor mode para GNU Emacs que pode ser utilizado em qualquer buffer do editor.
Próximos passos
[editar | editar código-fonte]Para aprender mais sobre Emacs Lisp e construção de extensões para GNU Emacs, é recomendado estudar as implementações de comandos built-in do editor, que são, em sua maioria, implementados nessa linguagem.
Para isso, basta executar o comando:
C-h x nome-do-comando RET
Aqui um exemplo da implementação da função switch-to-buffer
:
(interactive
(let ((force-same-window
(unless switch-to-buffer-obey-display-actions
(cond
((window-minibuffer-p) nil)
((not (eq (window-dedicated-p) t)) 'force-same-window)
((pcase switch-to-buffer-in-dedicated-window
('nil (user-error
"Cannot switch buffers in a dedicated window"))
('prompt
(if (y-or-n-p
(format "Window is dedicated to %s; undedicate it?"
(window-buffer)))
(progn
(set-window-dedicated-p nil nil)
'force-same-window)
(user-error
"Cannot switch buffers in a dedicated window")))
('pop nil)
(_ (set-window-dedicated-p nil nil) 'force-same-window)))))))
(list (read-buffer-to-switch "Switch to buffer: ") nil force-same-window)))
(let ((buffer (window-normalize-buffer-to-switch-to buffer-or-name))
(set-window-start-and-point (not switch-to-buffer-obey-display-actions)))
(cond
;; Don't call set-window-buffer if it's not needed since it
;; might signal an error (e.g. if the window is dedicated).
((and (eq buffer (window-buffer))
;; pop-to-buffer-same-window might decide to display
;; the same buffer in another window
(not switch-to-buffer-obey-display-actions)))
((and (window-minibuffer-p)
(not switch-to-buffer-obey-display-actions))
(if force-same-window
(user-error "Cannot switch buffers in minibuffer window")
(pop-to-buffer buffer norecord)))
((and (eq (window-dedicated-p) t)
(not switch-to-buffer-obey-display-actions))
(if force-same-window
(user-error "Cannot switch buffers in a dedicated window")
(pop-to-buffer buffer norecord)))
(t
(when switch-to-buffer-obey-display-actions
(let ((selected-window (selected-window)))
(pop-to-buffer-same-window buffer norecord)
(when (eq (selected-window) selected-window)
(setq set-window-start-and-point t))))
(when set-window-start-and-point
(let* ((entry (assq buffer (window-prev-buffers)))
(preserve-win-point
(buffer-local-value 'switch-to-buffer-preserve-window-point
buffer))
(displayed (and (eq preserve-win-point 'already-displayed)
(get-buffer-window buffer 0))))
(set-window-buffer nil buffer)
(when (and entry (or (eq preserve-win-point t) displayed))
;; Try to restore start and point of buffer in the selected
;; window (Bug#4041).
(set-window-start (selected-window) (nth 1 entry) t)
(set-window-point nil (nth 2 entry)))))))
(unless norecord
(select-window (selected-window)))
(set-buffer buffer)))
Referências
[editar | editar código-fonte]- ↑ GNU Emacs FAQ. https://www.gnu.org/software/emacs/manual/html_mono/efaq.html#Origin-of-the-term-Emacs
- ↑ Cameron, Debra. GNU Emacs Pocket Reference. O'Reilly. https://www.oreilly.com/library/view/gnu-emacs-pocket/1565924967/
- ↑ About the GNU Project. https://web.archive.org/web/20240903201004/https://www.gnu.org/gnu/thegnuproject.html
- ↑ Emacs Lisp. https://en.wikipedia.org/wiki/Emacs_Lisp