Jak dziedziczyć po trybie progresywnym, nadal obsługując starsze emacsen?

10

Piszę główny tryb dla języka programowania, ale chcę obsługiwać starsze wersje Emacsa. prog-modejest stosunkowo nowy. Chcę dziedziczyć, prog-modejeśli jest zdefiniowane, ale nadal robię coś sensownego w przeciwnym razie.

Jakie jest najlepsze podejście? Czy powinienem defalias prog-modeużywać starszego Emacsena, czy może to będzie kolidować z innymi trybami, jeśli robią to samo?

Wilfred Hughes
źródło
Radzę po prostu zrezygnować ze wsparcia dla Emacsa <24. Moim zdaniem nie jest to już warte wysiłku i będziesz musiał zrezygnować z ważniejszych funkcji niż prog-mode. W szczególności będziesz cierpieć z powodu braku wiązania leksykalnego.
lunaryorn
Wnoszę wkład w tryb Julii, a niektórzy z podstawowego zespołu używają starszej Emacsen i wolelibyśmy, żebyśmy go wspierali.
Wilfred Hughes
1
@lunaryorn Emacs 24 jest wciąż całkiem nowy. Emacs 23 to aktualna wersja dla wielu systemów operacyjnych. Emacs 22 jest wciąż aktualny na kilku. Nie wszyscy aktualizują swoje oprogramowanie jak szalone. Porzucenie wsparcia dla Emacsa 23 ograniczyłoby cię do niewielu użytkowników, którzy pragną przewagi.
Gilles „SO- przestań być zły”
1
Istnieje wiele powodów, aby używać starszych wersji Emacsa. Na przykład w systemie Windows Emacs 23 stał się bardzo powolny, więc zdecydowałem się trzymać Emacsa 22.
Lindydancer
@Gilles Wątpię, czy to tylko „kilku użytkowników”. Flycheck nigdy nie wspierał Emacsa 23 i stał się jednym z najpopularniejszych pakietów na MELPA…
lunaryorn

Odpowiedzi:

11

Kosztem dodatkowego wiązania symboli najwyższego poziomu jest bardzo fajne rozwiązanie, które pozwala uniknąć powtarzania define-derived-modeformy:

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

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

Działa dobrze w każdym Emacsie> = 23. Wymyśliłem to haml-modekilka lat temu IIRC i wydaje się, że rozprzestrzenił się stamtąd na kilka innych głównych trybów. Najważniejsze, co define-derived-moderobi makro z symbolem trybu nadrzędnego, to generowanie kodu, który wywołuje jego funkcję: w tym sensie defaliasnowa zmienna jest dokładnie równoważna funkcji aliasu.

Jednym zastrzeżeniem jest to, że może to mylić derived-mode-p, więc kod, który sprawdza, czy tryb, z którego wywodzisz się z trybu, prog-modemoże nie działać poprawnie. W praktyce nie spotkałem się z żadnymi problemami: zwykle taki kod się zaczepia prog-mode-hook, co wciąż się uruchamia.

(Jak zauważa Jorgen w komentarzach, define-derived-modeużywa również mode-classwłaściwości z symbolu trybu rodzica i defaliasnie skopiuje jej. W chwili pisania ta właściwość wydaje się być używana tylkospecial-mode .)

Aktualizacja: w dzisiejszych czasach po prostu sugeruję wymaganie co najmniej Emacsa 24, ponieważ starsze wersje są od dawna przestarzałe.

sanityinc
źródło
2
Fajne rozwiązanie! Tylko zastrzeżenie: Działa to prog-mode, ale nie działa dla każdego trybu. define-derived-modekopiuje mode-classwłaściwość symbolu do trybu potomnego. defaliasBędzie nie przekazać tę właściwość. Jeśli mode-classma to zastosowanie w twoim przypadku użycia, musisz skopiować / ustawić go ręcznie.
Jorgen Schäfer
Dzięki, że to zrozumiałeś, Jorgen - będę musiał przekopać się i dowiedzieć się więcej o tym, co mode-classoznacza nieruchomość.
sanityinc
3

tl; dr: Użyj ifi własnej funkcji init:

(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)))

Następnie wykonaj całą inicjalizację trybu w your-cool-init.

Dłuższe wyjaśnienie:

Problem polega na tym, że oficjalnym sposobem pisania pochodnego trybu głównego jest użycie define-derived-modemakra:

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

W starszych Emacsen (wcześniejszych niż 24) to się psuje, kiedy prog-mode. I nie możesz go użyć (if (fboundp 'prog-mode) ...), ponieważ makro oczekuje dosłownego symbolu i zacytuje go dla ciebie w rozszerzeniu.

define-derived-modeużywa rodzica na wiele sposobów. Aby z nich skorzystać, musisz skopiować wszystkie z nich we własnej definicji trybu, a to zarówno żmudne, jak i podatne na błędy.

Zatem jedynym sposobem jest użycie dwóch różnych define-derived-modeinstrukcji, w zależności od tego prog-mode, czy istnieje, czy nie. To pozostawia problem z dwukrotnym pisaniem kodu inicjalizacji. Co jest oczywiście złe, więc wyodrębnij to do jego własnej funkcji, jak opisano powyżej.

(Najlepszym rozwiązaniem jest oczywiście porzucenie wsparcia dla 23.x i użycie zakresu leksykalnego. Ale myślę, że już rozważałeś i zrezygnowałeś z tej opcji :-))

Jorgen Schäfer
źródło
Jaki jest najbliższy odpowiednik prog-modestarszych Emacsen? Byłoby sensu pochodzą z text-modelub fundamental-mode, jeśli prog-modenie jest dostępna?
Wilfred Hughes
@Jorgen Czy możemy wyprowadzić tryb pośredni, używając fboundpnajpierw, tylko z define-derived-modeinstrukcją? Czy zatem tryb rzeczywisty z pełną definicją można wyprowadzić z tego trybu pośredniego? W ten sposób cały tryb nie musi być definiowany dwukrotnie.
Kaushal Modi
1
@WilfredHughes, nie ma żadnych. Wyprowadzanie z fundamental-modejest równoważne wyprowadzaniu z nil(i faktycznie define-derived-modezastępuje fundamental-modeje nil), chociaż text-modenie jest właściwe, ponieważ kod programu nie jest tekstem. Większość ustawień domyślnych text-modenie ma sensu w trybach programowania poza komentarzami. Dlatego prog-modewprowadzono w Emacs 24.
Jorgen Schäfer
@kaushalmodi, możesz wyprowadzić tryb pośredni, ale nadal wymagałoby to dwóch define-derived-modedefinicji w ifformie, tylko dla trybu pośredniego zamiast trybu końcowego. Zastąpiłbyś defundla funkcji init define-derived-modetryb dla trybu końcowego. Nie sądzę, aby było to szczególnie korzystne. Możesz również zdefiniować prog-modesiebie, jak sugeruje oryginalne pytanie, ale może to łatwo pomylić inne tryby, które polegają na fboundpsprawdzaniu obecności tego trybu.
Jorgen Schäfer
Nie wierzę, że define-derived-modekonieczne są dwa różne stwierdzenia. Kilka lat temu wymyśliłem rozwiązanie, które opublikowałem jako osobną odpowiedź, i wydaje się, że działa dobrze zarówno w Emacs 23, jak i 24. Kod jest podobny do wielu popularnych głównych trybów.
sanityinc
0

Myślę, że testowanie przy użyciu fboundpma większy sens.

(if (fboundp 'prog-mode)
    ...
   ...)
Alex Schröder
źródło
0

Możesz zdefiniować makro opakowania dla define-derived-modeoceny jego argumentów.

(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))

(Ostrzeżenie: tylko minimalnie przetestowane.)

Gilles „SO- przestań być zły”
źródło