Как мне наследовать от prog-mode, при этом поддерживая старый emacsen?

10

Я пишу основной режим для языка программирования, но я хочу поддерживать более старые версии Emacs. prog-modeотносительно новый. Я хочу унаследовать, prog-modeесли он определен, но все же сделать что-то разумное в противном случае.

Какой лучший подход? Должен ли я использовать defalias prog-modeболее старый Emacsen, или это будет мешать другим режимам, если они будут делать то же самое?

Уилфред Хьюз
источник
Я бы посоветовал просто отказаться от поддержки Emacs <24. По моему мнению, это больше не стоит усилий, и вам придется отказаться от более важных функций, чем prog-mode. Примечательно, что вы будете страдать от отсутствия лексической привязки.
lunaryorn
Я участвую в julia-mode, и некоторые из основной команды используют более старый Emacsen и предпочли бы, чтобы мы поддержали его.
Уилфред Хьюз
1
@lunaryorn Emacs 24 все еще довольно новый. Emacs 23 является текущей версией на многих ОС. Emacs 22 все еще актуален для немногих. Не каждый обновляет свое программное обеспечение как сумасшедший. Отказ от поддержки Emacs 23 ограничил бы вас несколькими пользователями, которые жаждут передового края.
Жиль "ТАК - перестань быть злым"
1
Есть много причин использовать старые версии Emacs. Например, в Windows Emacs 23 стал очень вялым, поэтому я решил придерживаться Emacs 22 там.
Lindydancer
@ Жиль Я сомневаюсь, что это всего лишь «несколько пользователей». Flycheck никогда прежде не поддерживал Emacs 23 и, тем не менее, стал одним из самых популярных пакетов на MELPA…
lunaryorn

Ответы:

11

За счет дополнительной привязки символов верхнего уровня, есть очень аккуратное решение, которое позволяет избежать повторения define-derived-modeформы:

(defalias 'my-fancy-parent-mode
  (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))

(define-derived-mode my-fancy-mode my-fancy-parent-mode
   ...)

Работает нормально в любом Emacs> = 23. Я придумал это haml-modeпару лет назад IIRC, и, похоже, он распространился оттуда на несколько других основных режимов. Главное, что define-derived-modeмакрос делает с символом родительского режима, - это генерирует код, который вызывает его функцию: в этом смысле defaliasновая переменная в точности эквивалентна псевдонимной функции.

Одно предостережение в том, что это может сбить с толку derived-mode-p, поэтому код, который проверяет, является ли ваш режим полученным, prog-modeможет работать некорректно. На практике я не сталкивался с какими-либо проблемами: для такого кода более привычно подключаться prog-mode-hook, который все еще запускается.

(Как указывает Йорген в комментариях, он define-derived-modeтакже использует mode-classсвойство из символа родительского режима и defaliasне будет копировать его. На момент написания этого свойства, кажется, его использовали только дляspecial-mode .)

Обновление: в эти дни я бы просто предложил требовать как минимум Emacs 24, так как старые версии давно устарели.

sanityinc
источник
2
Отличное решение! Просто предостережение: это работает prog-mode, но не будет работать для каждого режима. define-derived-modeкопирует mode-classсвойство символа в дочерний режим. defaliasБудет не передать это имущество. Если mode-classэто актуально для вашего варианта использования, вам нужно скопировать / установить его вручную.
Йорген Шефер
Спасибо, что поймали это, Йорген. Мне придется покопаться и узнать больше о том, что mode-classобозначает недвижимость.
sanityinc
3

tl; dr: Используйте ifи свою собственную функцию инициализации:

(if (fboundp 'prog-mode)
    (define-derived-mode your-cool-mode prog-mode "Cool"
      "Docstring"
      (your-cool--init))
  (define-derived-mode your-cool-mode nil "Cool"
    "Docstring"
    (your-cool--init)))

Затем выполните инициализацию всего режима в your-cool-init.

Более длинное объяснение:

Проблема в том, что официальным способом написания производного основного режима является использование define-derived-modeмакроса:

(define-derived-mode your-cool-mode prog-mode ...)

На старшем Emacsen (до 24) это ломается когда prog-mode. И вы не можете использовать его (if (fboundp 'prog-mode) ...)там, потому что макрос ожидает буквальный символ и процитирует его для вас в расширении.

define-derived-modeиспользует родителя множеством способов. Вам нужно будет скопировать все из них в своем собственном определении режима, чтобы использовать их, и это утомительно и подвержено ошибкам.

Таким образом, единственный способ - использовать два разных define-derived-modeутверждения, в зависимости от того, prog-modeсуществует или нет. Это оставляет вас с проблемой написания вашего кода инициализации дважды. Что, конечно, плохо, поэтому вы извлекаете это в свою собственную функцию, как описано выше.

(Лучшее решение - это, конечно, отказаться от поддержки 23.x и использовать лексическую область видимости. Но я думаю, что вы уже рассмотрели и отказались от этой опции. :-))

Йорген Шефер
источник
Что является самым близким эквивалентом prog-modeв старшем Emacsen? Имеет ли смысл выводить из text-modeили fundamental-modeесли prog-modeнет в наличии?
Уилфред Хьюз
@Jorgen Или мы можем вывести промежуточный режим, используя fboundpсначала только define-derived-modeутверждение? Тогда фактический режим с полным определением может быть получен из этого промежуточного режима? Таким образом, весь режим не должен быть определен дважды.
Каушал Моди
1
@ WilfredHughes, нет ни одного. Производное от fundamental-modeэквивалентно производному от nil(и действительно define-derived-modeзаменяет fundamental-modeна nil), но text-modeне подходит, поскольку программный код не является текстовым. Большинство настроек по умолчанию text-modeне имеют смысла в режимах программирования вне комментариев. Вот почему prog-modeбыл представлен в Emacs 24.
Йорген Шефер
@kaushalmodi, вы можете получить промежуточный режим, но для этого все равно потребуются два define-derived-modeопределения в ifформе, только для промежуточного режима вместо окончательного режима. Вы бы заменили defunдля функции init на define-derived-modeдля конечного режима. Я не думаю, что это особенно предпочтительно. Вы также можете определить prog-modeсебя, как предполагает первоначальный вопрос, но это может легко запутать другие режимы, которые fboundpпроверяют наличие этого режима.
Йорген Шефер
Я не верю, что необходимы два разных define-derived-modeутверждения. Пару лет назад я придумал решение, которое выложил в виде отдельного ответа, и, похоже, оно отлично работает в Emacs 23 и 24. Код, подобный тому, который используется в ряде популярных основных режимов.
sanityinc
0

Я думаю, что использование тестирования fboundpимеет больше смысла.

(if (fboundp 'prog-mode)
    ...
   ...)
Алекс Шредер
источник
0

Вы можете определить макрос-обертку, define-derived-modeкоторый оценивает его аргументы.

(defmacro define-derived-mode* (child parent name &optional docstring &rest body)
  (macroexpand `(define-derived-mode ,(eval child) ,(eval parent) ,(eval name)
                                     ,(eval docstring) . ,body)))
(define-derived-mode* 'toy-mode
  (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode)
  "Toy"
  "Major mode for my favorite toy language"
  (toy-mode-setup))

(Предупреждение: только минимально проверено.)

Жиль "ТАК - перестань быть злым"
источник