Настройка Emacs для последователей Dlang

в 1:39, , рубрики: D, emacs, метки: , , , , , , ,

Продвинутому в мире программирования человеку, коим скорее всего являешься ты, хабравчанин, пояснять, что такое язык D, нет смысла — о нем, хоть краем уха, но слышать был должен.

Dlang — это настоящее благословение для бородатых мальчиков-программистов C++. И эта статья предназначена как раз им, программистам, открывшим для себя этот язык, но вставших на перепутье тернистого выбора той самой IDE, посредством которой они будут познавать как «ходить» без костылей.

Я предпринимал несколько попыток пересесть на Emacs и, наконец, это случилось.

Ни для кого не секрет, что с IDE для Dlang всё очень туго.

  • MonoD
  • VisualD
  • Плагин для Eclipse
  • CodeBlocks
  • были плагины для QtCreator (вроде они перестали работать с последним QtCreator)
  • Coedit
  • плагин для SublimeText
  • DlangIde
  • ...

Из перечисленного в списке я пробовал всё, кроме VisualD, но каждый из них _какбе_намекает_: «Используй emacs». Отмечу, что DlangIde радует тем, что она написана на языке D, и можно участвовать в разработке, используя родной Dlang.

Так как я слишком привык к автодополнению кода, то этот пункт для меня основополагающий. Многие, казалось бы, умеют реализовывать его, но как-то не убедительно.

Итак, рецепт init.el

Первым делом хорошо было бы подключить пакетный менеджер. Он сам установит все необходимые плагины и т.п., если в cfg-var:packages добавить необходимый пакет.

;; ========== Автоустановка пакетов
(require 'cl) ;; common lisp
(require 'package) ;; пакетный менеджер

(defvar cfg-var:packages '(
    d-mode ;; подсветка Dlang
	ac-dcd ;; автодополнение Dlang
    nav    ;; навигация по файловой системе
    auto-complete ;; общее автодополнение
    flycheck ;; проверка синтаксиса
    autopair ;; авто скобки
    ))

(defun cfg:install-packages ()
    (let ((pkgs (remove-if #'package-installed-p cfg-var:packages)))
        (when pkgs
            (message "%s" "Emacs refresh packages database...")
            (package-refresh-contents)
            (message "%s" " done.")
            (dolist (p cfg-var:packages)
                (package-install p)))))

(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(package-initialize)
(cfg:install-packages)

Настройка пакета d-mode.

Основное — это конечно подсветка синтаксиса. Для удобной компиляции я написал функции rdmd() и dub(). Их можно привязать к клавишам f5, f6 только для режима d-mode.

(require 'd-mode)
(add-to-list 'auto-mode-alist '("\.d\'" . d-mode)) ; автозапуск d-mode для файлов .d

(defun rdmd() ;; запуск rdmd для текущего файла
  "Run rdmd for current file.d with unittests"
  (interactive)
  
  (setq rdmd-cmd (concat "rdmd -unittest " buffer-file-name))
  (save-buffer)
  (async-shell-command rdmd-cmd)
  )

(defun dub() ;; запуск dub для текущего проекта
  "Run dub for current project" ;; от ткущего файла спускается в низ по директориям 
  (interactive)                 ;; пока не найдёт dub.json,
  (save-buffer)                 ;; но не дальше 6ти директорий
  (setq str "./")
  (while (eq (file-exists-p (concat str "dub.json")) (eq str "./../../../../../../"))
    (setq str(concat str "../")))
  (setq str (concat "cd " str " && dub"))
  (async-shell-command str)
)

(define-key d-mode-map (kbd "<f6>") 'rdmd) ;; привязка клавиш к функциям
(define-key d-mode-map (kbd "<f5>") 'dub)  ;; rdmd и dub

;; отключаем вопросы при выходе по поводу запущенного dcd-server
(defadvice save-buffers-kill-emacs (around no-query-kill-emacs activate)
  "Prevent annoying "Active processes exist" query when you quit Emacs."
  (cl-letf (((symbol-function #'process-list) (lambda ())))
    ad-do-it))

То самое автодополнение.

За автодополнение отвечает плагин ac-dcd. Для работы плагина необходим сервер DCD в видимости системной переменной PATH. DCD можно собрать из исходников или установить из репозитория вашей системы.

;; ========== ac-dcd
(require 'ac-dcd)
(add-hook 'd-mode-hook
  (lambda ()
      (auto-complete-mode t)
      (when (featurep 'yasnippet) (yas-minor-mode-on))
      (ac-dcd-maybe-start-server)
      (ac-dcd-add-imports)
      (autopair-mode t)
      (ac-dcd--find-all-project-imports)
      (add-to-list 'ac-sources 'ac-source-dcd)
      (define-key d-mode-map (kbd "C-c ?") 'ac-dcd-show-ddoc-with-buffer)
      (define-key d-mode-map (kbd "C-c .") 'ac-dcd-goto-definition)
      (define-key d-mode-map (kbd "C-c ,") 'ac-dcd-goto-def-pop-marker)
      (define-key d-mode-map (kbd "C-c s") 'ac-dcd-search-symbol)          
	  
      (when (featurep 'popwin)
          (add-to-list 'popwin:special-display-config
                     `(,ac-dcd-error-buffer-name :noselect t))
          (add-to-list 'popwin:special-display-config
                     `(,ac-dcd-document-buffer-name :position right :width 80))
          (add-to-list 'popwin:special-display-config
                     `(,ac-dcd-search-symbol-buffer-name :position bottom :width 5)))))

Это минимум для работы.

Для наглядности покажу свой файл init.el полностью



;; ========== Цветовая схема
(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(ansi-color-faces-vector
   [default default default italic underline success warning error])
 '(custom-enabled-themes (quote (wombat))))
(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 )


;; ========== Автоустановка пакетов
(require 'cl) ;; common lisp
(require 'package) ;; пакетный менеджер

(defvar cfg-var:packages '(
    d-mode ;; подсветка Dlang
	ac-dcd ;; автодополнение Dlang
    nav    ;; навигация по файловой системе
    auto-complete ;; общее автодополнение
    flycheck ;; проверка синтаксиса
    autopair ;; авто скобки
    ))

(defun cfg:install-packages ()
    (let ((pkgs (remove-if #'package-installed-p cfg-var:packages)))
        (when pkgs
            (message "%s" "Emacs refresh packages database...")
            (package-refresh-contents)
            (message "%s" " done.")
            (dolist (p cfg-var:packages)
                (package-install p)))))

(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(package-initialize)
(cfg:install-packages)

;; ========== Хоткеи на русской раскладке
    ;; должна быть еще строчка в конце файла
(defun cfg:reverse-input-method (input-method)
  "Build the reverse mapping of single letters from INPUT-METHOD."
  (interactive
   (list (read-input-method-name "Use input method (default current): ")))
  (if (and input-method (symbolp input-method))
      (setq input-method (symbol-name input-method)))
  (let ((current current-input-method)
        (modifiers '(nil (control) (meta) (control meta))))
    (when input-method
      (activate-input-method input-method))
    (when (and current-input-method quail-keyboard-layout)
      (dolist (map (cdr (quail-map)))
        (let* ((to (car map))
               (from (quail-get-translation
                      (cadr map) (char-to-string to) 1)))
          (when (and (characterp from) (characterp to))
            (dolist (mod modifiers)
              (define-key local-function-key-map
                (vector (append mod (list from)))
                (vector (append mod (list to)))))))))
    (when input-method
      (activate-input-method current))))

 ;; ========== Общие нестройки
(server-start) ;; запуск в режиме сервера
(cua-mode t) ;; работа с буфером обмена по-человечески
(defalias 'yes-or-no-p 'y-or-n-p) ;; укорачиваем вопросы

;(set user-full-name "Vasya-Pupkin") ;; привязываем личность
;(set user-mail-address "pupkincore@mail.ru")

(setq inhibit-splash-screen t) ;; прячем экран приветсвия
(setq inhibit-startup-message t) ;; его можно вызвать C-h C-a

(show-paren-mode t) ;; выделяем выражения в скобках
(setq show-paren-style 'expression) ;; выделяем цветом

(delete-selection-mode t) ;; удаляем выделенный текст, когда пишем поверх

(setq make-backup-files nil) ;; отключаем автоматические сохранения
(setq auto-save-default nil)
(setq auto-save-list-file-name nil)

(require 'linum) ;; модуль нумерации строк
(line-number-mode t) ;; показывает номер строки в mode-line
(global-linum-mode t) ;; номера строк во всех буферах
(column-number-mode t) ;; показывать номер столбца в mode-line
(setq linum-format "%d") ;; задаём формат нумерации строк

;(fringe-mode '(8.0)) ;; ограничитель текста только слева
(setq-default indicate-empty-lines t) ;; отсутсвие строки выделить глифами рядом с полоской с номером строки
(setq-default indicate-buffer-boundaries 'left) ;; индикация только слева

(setq visible-bell t) ;; отключаем писк при ошибках. Вместо этого мигает в строке статуса

;(set-face-attribute 'default nil :font "Terminus-12")

(tool-bar-mode -1) ;; отключаем лишние панели
(menu-bar-mode -1)
(scroll-bar-mode -1)

(iswitchb-mode 1) ;; интерактивный режим переключения буферов 

(setq word-wrap t) ;; автоперенос по словам
(global-visual-line-mode t)

(require 'ido) ;; интерактивный режим открытия файлов
(ido-mode t)
(icomplete-mode t)
(ido-everywhere t)
(setq ido-virtual-buffers t)
(setq ido-enable-flex-matching t)

(require 'bs) ;; быстрая навигация между буферами
(require 'ibuffer)
(defalias 'list-buffers 'ibuffers) ;; отдельный список буферов C-x C-b
(global-set-key (kbd "<f2>") 'bs-show) ;; запуск buffer-selection по F2

(setq scroll-step 1) ;; скролинг по 1 строке
(setq scroll-margin 10) ;; сдвигать вверх вниз когда курсор в 10 строках
;(setq scroll-conservatively 10000) ;; ???

(setq x-select-enable-clipboard t) ;; общий буфер обмена с ОС

(setq search-highlight t) ;; Выделять результаты поиска
(setq query-replace-highlight t)

(global-set-key (kbd "<C-Tab>") 'other-window) ;; переключение окон

;; запилить закладки

;; ========== NAV
(require 'nav)
(nav-disable-overeager-window-splitting)
(global-set-key (kbd "<f8>") 'nav-toggle)

(defun nav-mode-hl-hook ()
  (local-set-key (kbd "<right>") 'nav-open-file-under-cursor)
  (local-set-key (kbd "<left>")  'nav-go-up-one-dir))

(add-hook 'nav-mode-hook 'nav-mode-hl-hook)

;; ========== autopair-mode
(require 'autopair) ;; автовставка скобок
(autopair-mode t)

;; ========== D-MODE
(require 'd-mode)
(add-to-list 'auto-mode-alist '("\.d\'" . d-mode)) ; автозапуск d-mode для файлов .d

(defun rdmd() ;; запуск rdmd для текущего файла
  "Run rdmd for current file.d with unittests"
  (interactive)
  
  (setq rdmd-cmd (concat "rdmd -unittest " buffer-file-name))
  (save-buffer)
  (async-shell-command rdmd-cmd)
  )

(defun dub() ;; запуск dub для текущего проекта
  "Run dub for current project" ;; от текущего файла спускается в низ по директориям 
  (interactive)                 ;; пока не найдёт dub.json,
  (save-buffer)                 ;; но не дальше 6ти директорий
  (setq str "./")
  (while (eq (file-exists-p (concat str "dub.json")) (eq str "./../../../../../../"))
    (setq str(concat str "../")))
  (setq str (concat "cd " str " && dub"))
  (async-shell-command str)
)

(define-key d-mode-map (kbd "<f6>") 'rdmd) ;; привязка клавиш к функциям
(define-key d-mode-map (kbd "<f5>") 'dub)  ;; rdmd и dub

;; отключаем вопросы при выходе по поводу запущенного dcd-server
(defadvice save-buffers-kill-emacs (around no-query-kill-emacs activate)
  "Prevent annoying "Active processes exist" query when you quit Emacs."
  (cl-letf (((symbol-function #'process-list) (lambda ())))
    ad-do-it))

;; ========== ac-dcd
(require 'ac-dcd)
(add-hook 'd-mode-hook
  (lambda ()
      (auto-complete-mode t)
      (when (featurep 'yasnippet) (yas-minor-mode-on))
      (ac-dcd-maybe-start-server)
      (ac-dcd-add-imports)
      (autopair-mode t)
      (ac-dcd--find-all-project-imports)
      (add-to-list 'ac-sources 'ac-source-dcd)
      (define-key d-mode-map (kbd "C-c ?") 'ac-dcd-show-ddoc-with-buffer)
      (define-key d-mode-map (kbd "C-c .") 'ac-dcd-goto-definition)
      (define-key d-mode-map (kbd "C-c ,") 'ac-dcd-goto-def-pop-marker)
      (define-key d-mode-map (kbd "C-c s") 'ac-dcd-search-symbol)          
	  
      (when (featurep 'popwin)
          (add-to-list 'popwin:special-display-config
                     `(,ac-dcd-error-buffer-name :noselect t))
          (add-to-list 'popwin:special-display-config
                     `(,ac-dcd-document-buffer-name :position right :width 80))
          (add-to-list 'popwin:special-display-config
                     `(,ac-dcd-search-symbol-buffer-name :position bottom :width 5)))))

;; ========== Отступы
(setq-default indent-tabs-mode nil) ; не использовать символ Tab для отсутпа

(setq tab-width 4 ; ширина таба
      c-default-style "stroustrup" ; отступ в CC mode
      js-indent-level 4 ; indentation level in JS mode
      css-indent-offset 4) ; indentation level in CSS mode

(add-hook 'd-mode-hook
          (lambda ()
            (setq tab-wdth 4)
            ))

(add-hook 'text-mode-hook
          (lambda ()
            (setq tab-wdth 4)
            ))

(global-set-key (kbd "TAB") 'self-insert-command) ;; indent
(setq-default c-basic-offset 4) 
(setq-default tab-width 4)

;; ========== Хоткеи на русской раскладке
;; А вот эта строка должна быть в самом конце
(cfg:reverse-input-method 'russian-computer)

или на GitHub.

Для большего понимания рекомендую копировать построчно и разбираться отдельно, что из этого получилось.

Выслушаю любые вопросы и предложения.

Автор: Бог сервера

Источник


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


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