python

Emacs + удобный менеджер окон и буферов

  • среда, 19 июля 2017 г. в 03:12:56
https://habrahabr.ru/post/333640/
  • Python
  • Emacs


Привет, хабражители!

Недавно, около года назад, я начал увлекаться емаксом. Спасибо за это товарищу по работе, который много чего рассказал и влюбил меня в емакс.

Но, не хватало мне очень хорошего и удобного менеджера буферов, я начал искать и нашел emacs-purpose.

Это очень удобная система построения своей конфигурации буферов и их расположения на странице. Что интересно, что она подразумевает что у каждого буфера есть предназначение и соответственно целевое место в твоем layoutе. На основании этого extensionа даже возможно сделать свой собственный ide в emacs очень легко. Итак, давайте рассмотрим несколько шагов по построению своей версии IDE используя этот движок.

Для затравки, вот то как выглядит мой интерфейс емакса.

image

Мы будем строить свою версию IDE в несколько шагов:

  1. построение своей структуры расположения буферов
  2. назначение каждому буферу списка режимов, которые будут отображаться в этом буфере.
  3. упрощение переключения между буферами.
  4. другие улучшения юзабилити для нашего IDE.

1. Построение своей структуры расположения буферов


Это очень просто!

В файле window-purpose-x.el нужно поменять определение переменной purpose-x-code1—window-layout.
В идеале это можно сделать также поместив layout отдельно в папку ~/.emacs.d/layouts/ и затем подгрузив layout следующей функцией.

(purpose-load-window-layout-file "~/.emacs.d/layouts/full-ide.window-layout")

но пока почему то это не работает, отправил Pull request с новым layout автору (разбираемся почему не работает).

Пока это не пофикшено в основном репозитории, то мне пришлось поправить window-purpose-x.el. Вообще судя по тому что я понял, там проблема с новыми типами purposeов, которые я ввожу в layout, проблема по всей видимости в функции:
pull request

Вот моя конфигурация, которая выглядит так (рисунок был сверху):

elisp код
(nil
    (0 0 152 35)
    (t
     (0 0 29 35)
     (:purpose dired :purpose-dedicated t :width 0.16 :height 0.5 :edges
               (0.0 0.0 0.19333333333333333 0.5))
     (:purpose buffers :purpose-dedicated t :width 0.16 :height 0.4722222222222222 :edges
               (0.0 0.5 0.19333333333333333 0.9722222222222222)))
    (t
     (29 0 125 35)
    (:purpose edit :purpose-dedicated t :width 0.6 :height 0.85 :edges
              (0.19333333333333333 0.0 0.8266666666666667 0.85))
    (:purpose misc :purpose-dedicated t :width 0.6 :height 0.1 :edges
              (0.19333333333333333 0.8722222222222222 0.8266666666666667 0.9722222222222222))
    )
    (t
     (125 0 152 35)
    (:purpose ilist :purpose-dedicated t :width 0.15333333333333332 :height 0.6 :edges
              (0.82666666666666667 0.0 0.9722222222222222 0.6))
    (:purpose todo :purpose-dedicated t :width 0.15333333333333332 :height 0.372222222 :edges
              (0.8266666666666667 0.6 0.9722222222222222 0.9722222222222222))

     )
    )

А вообще достаточно этот текст будет поместить в ~/.emacs.d/layouts/full-ide.window-layout и добавить такую вещь в наш init.el

(defun load-purpose-mode ()
  (interactive)
  (purpose-load-window-layout-file "~/.emacs.d/layouts/full-ide.window-layout")
  (purpose-x-code1-setup)
)
(global-set-key (kbd "M-L") 'load-purpose-mode)

по нажатию Alt+Shift+L у меня загружается эта конфигурация. Кстати, подсказка: можно сделать кучу конфигураций. Например, если вы переходите в режим анализа изменений в гите, то можно сделать конфигурацию специально для анализа состояния репозитория.

Постепенно мы будем дополнять нашу функцию load-purpose-mode другими вкусностями.

2. Назначение каждому буферу списка режимов, которые будут отображаться в этом буфере


Для своего IDE я определил следующим режимам отображаться в буферах с определенными purposами.
misc purpose todo purpose edit purpose
inferior-python-mode org-mode css-mode
python-inferior-mode yaml-mode
gdb-inferior-io-mode conf-unix-mode
fundamental-mode *magit*
compilation-mode
shell-mode
eshell-mode
term-mode
Сделать это очень просто (один из вариантов):

(add-to-list 'purpose-user-mode-purposes
             '(YOUR_MODE . PURPOSE))

Для magit все немного сложней (из документации emacs-purpose):

(defvar purpose-x-magit-single-conf
    (purpose-conf "magit-single"
      :regexp-purposes '(("^\\*magit" . misc)))
      "Configuration that gives each magit major mode the same purpose.")
(purpose-x-magit-single-on)

После всех ваших изменений не забудьте выполнить следующую команду в вашем init.el:

(purpose-compile-user-configuration)

Тогда ваши изменения применятся.

3. Упрощение переключения между буферами


Много буферов? Но вроде бы они разделены по назначениям, но как между ними переключаться? Благодаря этой библиотеке – это достаточно легко.

Вот мой список команд для переключения на буферы с разными назначениями:

elisp код
;; helper для получения первого буфера с определенным purpose. нужна для прямого переключения на нужный буфер. (ie, на специальный purpose буфер)
(defun get-only-one-buffer-with-purpose (purpose)
  "Get buffers wih purpose"
  (buffer-name (nth 0 (purpose-buffers-with-purpose purpose)))
  )
;; переключитья на dired buffer, к сожалению, на каждую открытую директорию создается буфер, это значит что по прошествии нескольких часов у вас может быть открыто очень много dired буферов. Здесь есть один недостаток. А может можно проще ? Я не нашел как переключить просто на активный буфер с конкретным purpose. Итак, ты сможешь выбрать на какой буфер переключиться
(define-key purpose-mode-map (kbd "C-c C-f")
  (lambda () (interactive) (purpose-switch-buffer-with-some-purpose 'dired))
  )
;; переключиться на буфер со списком открытых файлов. Здесь все просто супер. Он один. Поэтому нет проблемы.
(define-key purpose-mode-map (kbd "C-c C-l")
  (lambda () (interactive) (purpose-switch-buffer (get-only-one-buffer-with-purpose 'buffers)))
  )
;; переключиться на один из файлов для редактирования. Та же проблемы что и с dired
(define-key purpose-mode-map (kbd "C-c C-c")
  (lambda () (interactive) (purpose-switch-buffer-with-some-purpose 'edit))
  )
;; переключиться на буфер с списком определений (функций, классов и тд и тп) в открытом файле для редактирования. Здесь все просто супер. Он один. Поэтому нет проблемы.
(define-key purpose-mode-map (kbd "C-c C-d")
  (lambda () (interactive)  (purpose-switch-buffer (get-only-one-buffer-with-purpose 'ilist)))
  )
;; переключиться на буфер с списком todo вещей. Об этом расскажу чуть позже. Полезная вещь. И он тоже один
(define-key purpose-mode-map (kbd "C-c C-t")
  (lambda () (interactive)  (purpose-switch-buffer (get-only-one-buffer-with-purpose 'todo)))
  )

4. Другие улучшения юзабилити для нашего IDE


Лично для меня важно еще видеть список todo в проекте, напоминает о каких-то важных вещах.

Поэтому я сделал специально буфер с purpose todo. И открываю в нем файл написанный для org-mode, в котором содержится список todo конкретного проекта.
Генерируется этот файлик автоматически при активации window-purpose layoutа

В функцию load-purpose-mode (приводилась выше) в конец нужно добавить (todo-mode-get-buffer-create) и вот кусочек elispа отвечающего за этот процесс

elisp код
(defconst todo-mode-buffer-name "*CodeTodo*"
  "Name of the buffer that is used to display todo entries.")

;; когда процесс создания todo файла закончился, выполнится эта функция, фактически эта функция откроет файл в read only mode с списком @todo. Можно будет перейти к месту @todo и посмотреть что там за вопрос не решен.
(defun on-org-mode-todo-file-built (process event)
  (find-file (concat (getenv "PWD") "/todo.org"))
  (call-interactively 'read-only-mode)
  )

;; функция отвечающая за генерацию todo файла для org-mode
(defun build-org-mode-file-for-todo ()
  (start-process "Building todo things" "*CodeTodo*" "bash" "-ic" "source ~/.bashrc; collecttodotags")
  (set-process-sentinel (get-process "Building todo things") 'on-org-mode-todo-file-built)
  )

;; создание буффера если его еще нет, запуск функций приведенных выше
(defun todo-mode-get-buffer-create ()
    "Return the todo-mode buffer.
If it doesn't exist, create it."
    (or (get-buffer todo-mode-buffer-name)
        (let ((buffer (get-buffer-create todo-mode-buffer-name)))
          (with-current-buffer buffer
            (org-mode)
            (build-org-mode-file-for-todo)
            (pop-to-buffer todo-mode-buffer-name))
          buffer)))

Команда collectotodotags это алиас на башевскую команду:

alias collecttodotags="find `pwd` -type d \( -name .git -o -name myworkenv -o -name node_modules \) -prune -o -type f \( -name todo.org \) -prune -o -type f -print -exec grep -n '@todo' '{}' \; | create_org_mode_todo_file.py > ./todo.org"

Эта команда находит список всех файлов с упоминанием места, где есть todo в коде и посылает список этих файлов с todo на вход следующему питоновскому файлу:
github

Причем обратите внимание, как можно указать, что какое-то todo важнее другого
github

Чем @todooo имеет больше «o» в конце, тем оно важней. Тут не совсем уверен как лучше было бы сделать, может кто подскажет?

И да, последняя вкусность. Смысл misc purposа в том, чтобы отображать там все промежуточные вещи, поиски и тд и тп, но буфер маленький, поэтому я добавил следующие shortcuts:

(global-set-key "\C-c+" (lambda () (interactive) (enlarge-window +20)))
(global-set-key "\C-c_" (lambda () (interactive) (enlarge-window -20)))

Один для увеличения буфера, другой для уменьшения, помогает, когда в misc оказывается много текста.

И в целом как вам этот emacs extension?
Спасибо за внимание!

P.S. Если интересна тема, то могу продолжить и рассказать другие моменты касательно моего конфига emacsа