Продвинутому в мире программирования человеку, коим скорее всего являешься ты, хабравчанин, пояснять, что такое язык 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)))))
Это минимум для работы.
;; ========== Цветовая схема
(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.
Для большего понимания рекомендую копировать построчно и разбираться отдельно, что из этого получилось.
Выслушаю любые вопросы и предложения.
Автор: Бог сервера