* Начало На хабре уже была [[http://habrahabr.ru/post/28943/][обзорная статья]] по jabber.el - jabber-клиенту для emacs. Решив попробовать этот клиент после pidgin, я наткнулся на отсутствие таких привычных уже вещей, как история ввода или форматированные сообщения. К сожалению, emacs-jabber не развивается так быстро, как хотелось бы. К счастью, возможности emacs по настройке практически безграничны, поэтому добавить нужное несложно. В этой статье я расскажу, как реализовал историю ввода. Если эта тема заинтересует общественность, в дальнейшем опишу отправку форматированных сообщений (html) и некоторые другие плюшки. #+DOCBOOK: #+DOCBOOK: Всё описанное делалось для себя. Я использую [[https://github.com/bbatsov/prelude][prelude]], поэтому некоторые функции могут быть из сторонних библиотек и отсутствовать в чистом емаксе. Также я пользуюсь только чатами, для конференций указанный код нужно будет подправить. #+DOCBOOK: * История ввода Очень удобная штука в pidgin - история ввода. По ctrl+up можно идти назад по истории отправленных сообщений, а по ctrl+down вперёд. Добавим этот функционал в emacs-jabber. Нам понадобятся три переменные: список введённых фраз, текущая позиция в этом списке и последний введённый, но ещё не отправленный текст. #+begin_src emacs-lisp :tangle yes (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) #+end_src При отправке сообщения добавляем его в список: #+begin_src emacs-lisp :tangle yes (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) #+end_src Важно: my-jabber-input-history-position указывает не на последний элемент истории, а за него (нумерация с нуля). Это логично, ведь мы ещё не ходили по списку. Функция для прохода назад по списку: #+begin_src emacs-lisp :tangle yes (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))))) #+end_src Всё просто, единственное, на что стоит обратить внимание - сохранение текущего введённого текста. Если пользователь передумает пользоваться историей, мы сможем восстановить то, что он успел ввести. Функция для прохода вперёд по списку: #+begin_src emacs-lisp :tangle yes (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)))))) #+end_src Здесь логика хитрее. Если мы на последнем элементе в списке (len - 1), то вставлять надо предварительно сохранённый пользовательский ввод. Но позицию по-прежнему увеличиваем, чтоб в следующий раз сработало второе условие. Вешаем эти функции на удобные кнопкосочетания: #+begin_src emacs-lisp :tangle yes (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) #+end_src Готово. Теперь у нас есть тот же функционал, что и в pidgin + извещения о достижении начала и конца списка + история без дублирующихся сообщений (благодаря add-to-list). Любителям ido-mode может пригодиться такая функция: #+begin_src emacs-lisp :tangle yes (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))) #+end_src Поскольку это ido, то работает поиск в списке по мере ввода текста (даже нечёткий, если установлена переменная ido-enable-flex-matching) и перебор вариантов по C-s/C-r. * Конец Текст подготовлен в emacs с использованием org-mode и экспорта в docbook (С-c C-e D). * Ссылки [[https://github.com/ineu/habr/blob/master/emacs-jabber/input-history.el][Лиспокод]] [[https://raw.github.com/ineu/habr/master/emacs-jabber/input-history.org][Исходник этой статьи]] [[https://github.com/ineu/habr/blob/master/org.xsl][XSLT для преобразования докбукового XML в понятный хабру формат.]]