Начало
На хабре уже была обзорная статья по jabber.el — jabber-клиенту для emacs. Решив попробовать этот клиент после pidgin, я наткнулся на отсутствие таких привычных уже вещей, как история ввода или форматированные сообщения. К сожалению, emacs-jabber не развивается так быстро, как хотелось бы. К счастью, возможности emacs по настройке практически безграничны, поэтому добавить нужное несложно. В этой статье я расскажу, как реализовал историю ввода. Если эта тема заинтересует общественность, в дальнейшем опишу отправку форматированных сообщений (html) и некоторые другие плюшки.
История ввода
Очень удобная штука в pidgin — история ввода. По ctrl+up можно идти назад по истории отправленных сообщений, а по ctrl+down вперёд. Добавим этот функционал в emacs-jabber. Нам понадобятся три переменные: список введённых фраз, текущая позиция в этом списке и последний введённый, но ещё не отправленный текст.
(defvar my-jabber-input-history '() "Variable that holds input history")
(make-variable-buffer-local 'my-jabber-input-history)
(defvar my-jabber-input-history-position 0 "Current position in input history")
(make-variable-buffer-local 'my-jabber-input-history-position)
(defvar my-jabber-input-history-current nil "Current input value")
(make-variable-buffer-local 'my-jabber-input-history-current)
При отправке сообщения добавляем его в список:
(defun my-jabber-input-history-hook (body id)
(add-to-list 'my-jabber-input-history body t)
(setq my-jabber-input-history-position (length my-jabber-input-history)))
(add-hook 'jabber-chat-send-hooks 'my-jabber-input-history-hook)
Важно: my-jabber-input-history-position указывает не на последний элемент истории, а за него (нумерация с нуля). Это логично, ведь мы ещё не ходили по списку.
Функция для прохода назад по списку:
(defun my-jabber-previous-input ()
(interactive)
(let (current-input (pos my-jabber-input-history-position) (len (length my-jabber-input-history)))
(if (= pos 0)
(message "%s" "No previous input")
(setq current-input (delete-and-extract-region jabber-point-insert (point-max)))
(when (= pos len) ; running first time, save current input
(setq my-jabber-input-history-current current-input))
(decf my-jabber-input-history-position)
(insert (nth my-jabber-input-history-position my-jabber-input-history)))))
Всё просто, единственное, на что стоит обратить внимание — сохранение текущего введённого текста. Если пользователь передумает пользоваться историей, мы сможем восстановить то, что он успел ввести.
Функция для прохода вперёд по списку:
(defun my-jabber-next-input ()
(interactive)
(let ((pos my-jabber-input-history-position) (len (length my-jabber-input-history)))
(cond
((= pos (1- len)) ; pointing at the last element, insert saved input
(incf my-jabber-input-history-position)
(delete-region jabber-point-insert (point-max))
(insert my-jabber-input-history-current)
(setq my-jabber-input-history-current nil))
((= pos len) ; pointing beyound last element, notify user
(message "%s" "No next input"))
(t ; insert next history item
(incf my-jabber-input-history-position)
(delete-region jabber-point-insert (point-max))
(insert (nth my-jabber-input-history-position my-jabber-input-history))))))
Здесь логика хитрее. Если мы на последнем элементе в списке (len — 1), то вставлять надо предварительно сохранённый пользовательский ввод. Но позицию по-прежнему увеличиваем, чтоб в следующий раз сработало второе условие.
Вешаем эти функции на удобные кнопкосочетания:
(define-key jabber-chat-mode-map (kbd "M-p") 'my-jabber-previous-input)
(define-key jabber-chat-mode-map (kbd "M-n") 'my-jabber-next-input)
Готово. Теперь у нас есть тот же функционал, что и в pidgin + извещения о достижении начала и конца списка + история без дублирующихся сообщений (благодаря add-to-list).
Любителям ido-mode может пригодиться такая функция:
(defun my-jabber-input-history-choose ()
(interactive)
(let ((choice (ido-completing-read "Select history item: " (reverse my-jabber-input-history))))
(delete-region jabber-point-insert (point-max))
(insert choice)))
Поскольку это ido, то работает поиск в списке по мере ввода текста (даже нечёткий, если установлена переменная ido-enable-flex-matching) и перебор вариантов по C-s/C-r.
Конец
Текст подготовлен в emacs с использованием org-mode и экспорта в docbook (С-c C-e D).
Ссылки
XSLT для преобразования докбукового XML в понятный хабру формат.
Автор: EvilShadow