Улучшаем emacs-jabber

в 16:50, , рубрики: emacs, jabber, xmpp, метки: , ,

Начало

На хабре уже была обзорная статья по jabber.el — jabber-клиенту для emacs. Решив попробовать этот клиент после pidgin, я наткнулся на отсутствие таких привычных уже вещей, как история ввода или форматированные сообщения. К сожалению, emacs-jabber не развивается так быстро, как хотелось бы. К счастью, возможности emacs по настройке практически безграничны, поэтому добавить нужное несложно. В этой статье я расскажу, как реализовал историю ввода. Если эта тема заинтересует общественность, в дальнейшем опишу отправку форматированных сообщений (html) и некоторые другие плюшки.

Оговорка

Всё описанное делалось для себя. Я используюprelude, поэтому некоторые функции могут быть из сторонних библиотек и отсутствовать в чистом емаксе. Также я пользуюсь только чатами, для конференций указанный код нужно будет подправить.

История ввода

Очень удобная штука в 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js