2.7. Использование Emacs в качестве среды разработки

2.7.1. Emacs

К сожалению, Unix-системы поставляются без какого бы то ни было включающего-все-что-вы-захотите-и-гораздо-больше одного-гигантского-пакета интегрированной среды разработки, которые имеются в других системах. [1] Однако имеется возможность настроить собственную среду. Она может быть не такой уж красивой, и не очень интегрированной, но вы можете настроить ее так, как вам этого хочется. И она бесплатна. И доступны ее исходный код.

И все это называется Emacs. Некоторые питают к нему отвращение, но многие его любят. Если вы из первых, то боюсь, что этот раздел мало вас заинтересует. Также вам потребуется некоторое количество памяти для его работы--я рекомендую 8 МБ для текстового режима и 16 МБ для X в качестве минимальных тербований для получения удовлетворительной производительности.

Вообще говоря, Emacs является редактором с очень широкими возможностями по его настройке--действительно, он устроен так, что больше похож на операционную систему, чем на редактор! На самом деле многие разрабочики и системные администраторы проводят практически все свое время в редакторе Emacs, выходя из него только для завершения работы с системой.

Здесь даже вкратце просто невозможно описать все, что может делать Emacs, но вот только некоторые из возможностей, представляющих интерес для разработчиков:

  • Очень мощный редактор, позволяющий выполнять поиск и замену как строк, так и регулярных выражений (шаблонов), переход к началу или концу блока, и так далее, и тому подобное.

  • Выпадающие меню и встроенная система помощи.

  • Синтаксическое выделение и формирование отступов в зависимости от языка.

  • Полная настраиваемость.

  • В Emacs вы можете компилировать и отлаживать программы.

  • При возникновении ошибки компиляции вы можете перейти к соответствующей строке исходного кода.

  • Дружественный интерфейс с программой info, используемой для чтения гипертекстовой документации GNU, включая документацию на сам Emacs.

  • Дружественный интерфейс с программой gdb, позволяющий перемещаться по исходному коду в соответствии с шагами выполнения программы.

  • Вы можете читать телеконференции Usenet и электронную почту во время компиляции программы.

И, несомненно, многое из того, что я не упомянул.

Emacs может быть установлен во FreeBSD как порт Emacs.

После того, как он будет установлен, запустите его и нажмите C-h t для изучения учебника по Emacs--это означает удерживание клавиши control и одновременным нажатием на клавишу h, с последующим отпусканием клавиши control и нажатием кнопки t. (Либо вы можете использовать мышь для выбора пункта Emacs Tutorial из меню Help).

Хотя в Emacs имеются меню, стоит изучить используемые клавиатурные команды, так как при редактировании чего-либо гораздо быстрее нажать несколько клавиш, чем искать мышь и потом щелкать в нужном месте. И, если мы поговорите с опытными пользователями Emacs, то вы обнаружите, что они часто иногда выдают фразы типа "M-x replace-s RET foo RET bar RET", так что полезно знать, что они означают. И, в любом случае, в Emacs имеется слишком много полезных функций, чтобы уместить их все в системе меню.

К счастью, достаточно легко найти клавиатурные сокращения, так как они выводятся рядом с пунктом меню. Я советую пользоваться пунктом меню, скажем, для открытия файла, до тех пор, пока вы не поймете, как это работает и почуствуете себя уверенно, и только затем попробуете выполнить C-x C-f. Когда вы будете этим удовлетворены, переходите к изучению следующей команды меню.

Если вы не можете хапомнить, что делает конкретная комбинация клавиш, выберите пункт Describe Key из меню Help и наберите ее--Emacs скажет, что она делает. Вы можете также воспользоваться пунктом меню Command Apropos для поиска всех команд, которые содержат некоторое слово, вместе с клавиатурной комбинацией.

Кстати, выражение выше означает удержание клавиши Meta, нажатие x, отпускание клавиши Meta, набор replace-s (сокращение для replace-string--еще одной особенностью Emacs является возможность сокращать команды). нажатие клавиши return, набор foo (строки, которую вы хотите заменить), нажатие клавиши return, набор bar (строки, которая заменит foo) и нажатие return снова. Emacs выполнит операцию по поиску и замене, которую вы только что запросили.

Если вы интересуетесь, в какой точке земного шаранаходится клавиша Meta, то это особая клавиша, которая имеется на многих рабочих станциях Unix. К несчастью, на PC их нет, поэтому обычно это клавиша alt (или, если вам уж совсем не повезло, клавиша escape).

Да, чтобы выйти из Emacs, нажмите C-x C-c (это означает удержание клавиши control, нажатие на x, нажатие на c и отпускание клавиши control). Если у вас есть открытые несохранненные файлы, Emacs выдаст запрос на их сохранение. (Не принимайте во внимание документацию, в которой говорится о C-z как об обычном способе выхода из Emacs--при этом Emacs остается работать в фоновом режиме и действительно необходима в случае работы с системой, но которой нет виртуальных терминалов).

2.7.2. Настройка Emacs

Emacs имеет огромные возможности; некоторые из них являются встроенными, некоторые нужно настроить.

Вместо того, чтобы использовать для настройки собственный макро-язык, в Emacs применяется версия Lisp, специально адаптированная для редакторов, известная под названием Emacs Lisp. Это может быть оказаться весьма полезным, если вы хотите начать изучать что-то типа Common Lisp, потому что он значительно меньше, чем Common Lisp (хотя все же весьма большой!).

Лучше всего изучать Emacs Lisp, скачав Учебник по Emacs

Однако для выполнения настройки Emacs нет нужды знать какой бы то ни было Lisp, так как я включил сюда пример файла .emacs, которого для начала должно хватить. Просто скопируйте его в свой домашний каталог и перезапустите Emacs, если он уже запущен; он считает команды из файла и (надеюсь) даст вам приемлимые начальные настройки.

2.7.3. Пример файла .emacs

К сожалению, здесь слишком много всего, чтобы объяснить подробно; однако на паре мест стоит остановиться подробнее.

  • Все, что начинается с символа ;, является комментарием и игнорируется Emacs.

  • В первой строке -*- Emacs-Lisp -*- нужна для того, чтобы мы могли редактировать сам файл .emacs в редакторе Emacs и имели все удобства редактирования текстов Emacs Lisp. Emacs обычно пытается угадать характер содержимого на основе имени файла, и этого корректно может не получиться в случае .emacs.

  • Клавиша tab в некоторых режимах работает как функция формирования отступа, поэтому при нажатия клавиши табуляции в текущей строке кода будет сформирован отступ. Если вы хотите разместить где-либо символ tab, то при нажатии клавиши tab удерживайте клавишу control.

  • This file supports syntax highlighting for C, C++, Perl, Lisp and Scheme, by guessing the language from the filename.

  • В Emacs уже имеется предопределенная функция с именем next-error. В окне вывода результата компиляции она позволяет вам передвигаться от одной ошибки компиляции к другой по нажатию M-n; мы определили дополнительную функцию previous-error, которая позволяет переходить к следующей ошибке по нажатию M-p. Самой приятной возможностью является то, что по нажатию C-c C-c файл с исходным текстом, в котором произошла ошибка, будет открыт на соответствующей строке.

  • Мы включили возможность Emacs работать как сервер, так что если вы делаете что-то вне Emacs и хотите отредактировать файл, то можете просто набрать

        % emacsclient filename
    
    

    и после этого сможете редактировать файл в вашем Emacs! [2]

Example 2-1. Примерный файл .emacs

    ;; -*-Emacs-Lisp-*-

    ;; This file is designed to be re-evaled; use the variable first-time
    ;; to avoid any problems with this.
    (defvar first-time t
      "Flag signifying this is the first time that .emacs has been evaled")

    ;; Meta
    (global-set-key "\M- " 'set-mark-command)
    (global-set-key "\M-\C-h" 'backward-kill-word)
    (global-set-key "\M-\C-r" 'query-replace)
    (global-set-key "\M-r" 'replace-string)
    (global-set-key "\M-g" 'goto-line)
    (global-set-key "\M-h" 'help-command)

    ;; Function keys
    (global-set-key [f1] 'manual-entry)
    (global-set-key [f2] 'info)
    (global-set-key [f3] 'repeat-complex-command)
    (global-set-key [f4] 'advertised-undo)
    (global-set-key [f5] 'eval-current-buffer)
    (global-set-key [f6] 'buffer-menu)
    (global-set-key [f7] 'other-window)
    (global-set-key [f8] 'find-file)
    (global-set-key [f9] 'save-buffer)
    (global-set-key [f10] 'next-error)
    (global-set-key [f11] 'compile)
    (global-set-key [f12] 'grep)
    (global-set-key [C-f1] 'compile)
    (global-set-key [C-f2] 'grep)
    (global-set-key [C-f3] 'next-error)
    (global-set-key [C-f4] 'previous-error)
    (global-set-key [C-f5] 'display-faces)
    (global-set-key [C-f8] 'dired)
    (global-set-key [C-f10] 'kill-compilation)

    ;; Keypad bindings
    (global-set-key [up] "\C-p")
    (global-set-key [down] "\C-n")
    (global-set-key [left] "\C-b")
    (global-set-key [right] "\C-f")
    (global-set-key [home] "\C-a")
    (global-set-key [end] "\C-e")
    (global-set-key [prior] "\M-v")
    (global-set-key [next] "\C-v")
    (global-set-key [C-up] "\M-\C-b")
    (global-set-key [C-down] "\M-\C-f")
    (global-set-key [C-left] "\M-b")
    (global-set-key [C-right] "\M-f")
    (global-set-key [C-home] "\M-<")
    (global-set-key [C-end] "\M->")
    (global-set-key [C-prior] "\M-<")
    (global-set-key [C-next] "\M->")

    ;; Mouse
    (global-set-key [mouse-3] 'imenu)

    ;; Misc
    (global-set-key [C-tab] "\C-q\t")   ; Control tab quotes a tab.
    (setq backup-by-copying-when-mismatch t)

    ;; Treat 'y' or <CR> as yes, 'n' as no.
    (fset 'yes-or-no-p 'y-or-n-p)
        (define-key query-replace-map [return] 'act)
        (define-key query-replace-map [?\C-m] 'act)

    ;; Load packages
    (require 'desktop)
    (require 'tar-mode)

    ;; Pretty diff mode
    (autoload 'ediff-buffers "ediff" "Intelligent Emacs interface to diff" t)
    (autoload 'ediff-files "ediff" "Intelligent Emacs interface to diff" t)
    (autoload 'ediff-files-remote "ediff"
      "Intelligent Emacs interface to diff")

    (if first-time
        (setq auto-mode-alist
          (append '(("\\.cpp$" . c++-mode)
                ("\\.hpp$" . c++-mode)
                        ("\\.lsp$" . lisp-mode)
                ("\\.scm$" . scheme-mode)
                ("\\.pl$" . perl-mode)
                ) auto-mode-alist)))

    ;; Auto font lock mode
    (defvar font-lock-auto-mode-list
      (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'lisp-mode 'perl-mode 'scheme-mode)
      "List of modes to always start in font-lock-mode")

    (defvar font-lock-mode-keyword-alist
      '((c++-c-mode . c-font-lock-keywords)
        (perl-mode . perl-font-lock-keywords))
      "Associations between modes and keywords")

    (defun font-lock-auto-mode-select ()
      "Automatically select font-lock-mode if the current major mode is
    in font-lock-auto-mode-list"
      (if (memq major-mode font-lock-auto-mode-list)
          (progn
        (font-lock-mode t))
        )
      )

    (global-set-key [M-f1] 'font-lock-fontify-buffer)

    ;; New dabbrev stuff
    ;(require 'new-dabbrev)
    (setq dabbrev-always-check-other-buffers t)
    (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
    (add-hook 'emacs-lisp-mode-hook
          '(lambda ()
             (set (make-local-variable 'dabbrev-case-fold-search) nil)
             (set (make-local-variable 'dabbrev-case-replace) nil)))
    (add-hook 'c-mode-hook
          '(lambda ()
             (set (make-local-variable 'dabbrev-case-fold-search) nil)
             (set (make-local-variable 'dabbrev-case-replace) nil)))
    (add-hook 'text-mode-hook
          '(lambda ()
             (set (make-local-variable 'dabbrev-case-fold-search) t)
             (set (make-local-variable 'dabbrev-case-replace) t)))

    ;; C++ and C mode...
    (defun my-c++-mode-hook ()
      (setq tab-width 4)
      (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
      (define-key c++-mode-map "\C-ce" 'c-comment-edit)
      (setq c++-auto-hungry-initial-state 'none)
      (setq c++-delete-function 'backward-delete-char)
      (setq c++-tab-always-indent t)
      (setq c-indent-level 4)
      (setq c-continued-statement-offset 4)
      (setq c++-empty-arglist-indent 4))

    (defun my-c-mode-hook ()
      (setq tab-width 4)
      (define-key c-mode-map "\C-m" 'reindent-then-newline-and-indent)
      (define-key c-mode-map "\C-ce" 'c-comment-edit)
      (setq c-auto-hungry-initial-state 'none)
      (setq c-delete-function 'backward-delete-char)
      (setq c-tab-always-indent t)
    ;; BSD-ish indentation style
      (setq c-indent-level 4)
      (setq c-continued-statement-offset 4)
      (setq c-brace-offset -4)
      (setq c-argdecl-indent 0)
      (setq c-label-offset -4))

    ;; Perl mode
    (defun my-perl-mode-hook ()
      (setq tab-width 4)
      (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent)
      (setq perl-indent-level 4)
      (setq perl-continued-statement-offset 4))

    ;; Scheme mode...
    (defun my-scheme-mode-hook ()
      (define-key scheme-mode-map "\C-m" 'reindent-then-newline-and-indent))

    ;; Emacs-Lisp mode...
    (defun my-lisp-mode-hook ()
      (define-key lisp-mode-map "\C-m" 'reindent-then-newline-and-indent)
      (define-key lisp-mode-map "\C-i" 'lisp-indent-line)
      (define-key lisp-mode-map "\C-j" 'eval-print-last-sexp))

    ;; Add all of the hooks...
    (add-hook 'c++-mode-hook 'my-c++-mode-hook)
    (add-hook 'c-mode-hook 'my-c-mode-hook)
    (add-hook 'scheme-mode-hook 'my-scheme-mode-hook)
    (add-hook 'emacs-lisp-mode-hook 'my-lisp-mode-hook)
    (add-hook 'lisp-mode-hook 'my-lisp-mode-hook)
    (add-hook 'perl-mode-hook 'my-perl-mode-hook)

    ;; Complement to next-error
    (defun previous-error (n)
      "Visit previous compilation error message and corresponding source code."
      (interactive "p")
      (next-error (- n)))

    ;; Misc...
    (transient-mark-mode 1)
    (setq mark-even-if-inactive t)
    (setq visible-bell nil)
    (setq next-line-add-newlines nil)
    (setq compile-command "make")
    (setq suggest-key-bindings nil)
    (put 'eval-expression 'disabled nil)
    (put 'narrow-to-region 'disabled nil)
    (put 'set-goal-column 'disabled nil)

    ;; Elisp archive searching
    (autoload 'format-lisp-code-directory "lispdir" nil t)
    (autoload 'lisp-dir-apropos "lispdir" nil t)
    (autoload 'lisp-dir-retrieve "lispdir" nil t)
    (autoload 'lisp-dir-verify "lispdir" nil t)

    ;; Font lock mode
    (defun my-make-face (face colour &optional bold)
      "Create a face from a colour and optionally make it bold"
      (make-face face)
      (copy-face 'default face)
      (set-face-foreground face colour)
      (if bold (make-face-bold face))
      )

    (if (eq window-system 'x)
        (progn
          (my-make-face 'blue "blue")
          (my-make-face 'red "red")
          (my-make-face 'green "dark green")
          (setq font-lock-comment-face 'blue)
          (setq font-lock-string-face 'bold)
          (setq font-lock-type-face 'bold)
          (setq font-lock-keyword-face 'bold)
          (setq font-lock-function-name-face 'red)
          (setq font-lock-doc-string-face 'green)
          (add-hook 'find-file-hooks 'font-lock-auto-mode-select)

          (setq baud-rate 1000000)
          (global-set-key "\C-cmm" 'menu-bar-mode)
          (global-set-key "\C-cms" 'scroll-bar-mode)
          (global-set-key [backspace] 'backward-delete-char)
                        ;      (global-set-key [delete] 'delete-char)
          (standard-display-european t)
          (load-library "iso-transl")))

    ;; X11 or PC using direct screen writes
    (if window-system
        (progn
          ;;      (global-set-key [M-f1] 'hilit-repaint-command)
          ;;      (global-set-key [M-f2] [?\C-u M-f1])
          (setq hilit-mode-enable-list
            '(not text-mode c-mode c++-mode emacs-lisp-mode lisp-mode
              scheme-mode)
            hilit-auto-highlight nil
            hilit-auto-rehighlight 'visible
            hilit-inhibit-hooks nil
            hilit-inhibit-rebinding t)
          (require 'hilit19)
          (require 'paren))
      (setq baud-rate 2400)         ; For slow serial connections
      )

    ;; TTY type terminal
    (if (and (not window-system)
         (not (equal system-type 'ms-dos)))
        (progn
          (if first-time
          (progn
            (keyboard-translate ?\C-h ?\C-?)
            (keyboard-translate ?\C-? ?\C-h)))))

    ;; Under UNIX
    (if (not (equal system-type 'ms-dos))
        (progn
          (if first-time
          (server-start))))

    ;; Add any face changes here
    (add-hook 'term-setup-hook 'my-term-setup-hook)
    (defun my-term-setup-hook ()
      (if (eq window-system 'pc)
          (progn
    ;;  (set-face-background 'default "red")
        )))

    ;; Restore the "desktop" - do this as late as possible
    (if first-time
        (progn
          (desktop-load-default)
          (desktop-read)))

    ;; Indicate that this file has been read at least once
    (setq first-time nil)

    ;; No need to debug anything now

    (setq debug-on-error nil)

    ;; All done
    (message "All done, %s%s" (user-login-name) ".")

2.7.4. Расширение количества языков, с которыми может работать Emacs

Итак, хорошо, если вы собираетесь программировать только на языках, уже описанных в файле .emacs (C, C++, Perl, Lisp и Scheme), но что случится, если вышел новый язык под названием "whizbang", в котором имеется масса привлекательных возможностей?

Первым делом нужно проверить, не поставляется ли с языком whizbang с файлами для Emacs, которые служат для описания языка. Обычно они имеют окончание .el, что означает "Emacs Lisp". Например, если whizbang является портом FreeBSDm мы можем поискать такие файлы командой

    % find /usr/ports/lang/whizbang -name "*.el" -print

и установить их, скопировав их в каталог Lisp для Emacs вашей системы. Во FreeBSD 2.1.0-RELEASE это каталог /usr/local/share/emacs/site-lisp.

Так, для примера, если результат выполнения команды поиска выглядит как

    /usr/ports/lang/whizbang/work/misc/whizbang.el

то мы выполним

    # cp /usr/ports/lang/whizbang/work/misc/whizbang.el /usr/local/share/emacs/site-lisp

Затем нам нужно решить, какое расширение имеют файлы с исходными текстами на языке whizbang. Пусть, для примера, они все оканчиваются на .wiz. Нам нужно добавить запись в наш файл .emacs, чтобы Emacs смог использовать информацию из whizbang.el.

Найдите строку auto-mode-alist в файле .emacs и добавьте строку для whizbang, такую, как:

    ...
    ("\\.lsp$" . lisp-mode)
    ("\\.wiz$" . whizbang-mode)
    ("\\.scm$" . scheme-mode)
    ...

Это означает, что Emacs будет автоматически переходить в режим whizbang-mode, когда вы редактируете файл, оканчивающийся на .wiz.

Чуть ниже вы найдете запись для font-lock-auto-mode-list. Добавьте к ней whizbang-mode следующего вида:

    ;; Auto font lock mode
    (defvar font-lock-auto-mode-list
      (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'whizbang-mode 'lisp-mode 'perl-mode 'scheme-mode)
      "List of modes to always start in font-lock-mode")

Это означает, что Emacs всегда будет использовать режим font-lock-mode (то есть выделение синтаксических конструкций) при редактировании файлов .wiz.

И это все, что нужно. Если есть что-то, что вы хотите делать автоматически при открытии файлов .wiz, то можете добавить строку whizbang-mode hook (смотрите my-scheme-mode-hook в качестве простого примера, который добавляет auto-indent).

Notes

[1]

По крайней мере, пока вы не готовы будете платить большие суммы денег.

[2]

Многие пользователи Emacs указывают emacsclient в качестве значения своей переменной окружения EDITOR, поэтому это происходит всякий раз, когда им нужно отредактировать файл.