[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Recent attempts at standardizing major mode definitions.
From: |
Luc Teirlinck |
Subject: |
Recent attempts at standardizing major mode definitions. |
Date: |
Sun, 1 Sep 2002 21:40:13 -0500 (CDT) |
Stefan Monnier wrote:
PS: The elisp manual clearly says in the "major mode conventions"
node to "Define a command whose name ends in `-mode'" so
ibuffer-mode should be interactive. I'd of course argue that it
should use `define-derived-mode' which would have made it
interactive as a matter of course without the programmer having to
think about it. As long as we don't use such a macro
systematically, we'll have to live with all those inconsistencies.
It seems to me that in the present 21.3.50 code, define-derived-mode
is already used pretty systematically. As I pointed out in another
message, this has lead to bugs.
To avoid confusion: I am not at all opposed to "using such a macro
systematically", but I am very much opposed to using define-derived-mode
in its present form. One reason is that the present use of this macro
has caused bugs in several modes. (See my previous message.) It is
not the only reason.
I believe that any macro that aims to standardize major mode definitions
should under no condition prevent the writer of a major mode to do
anything that is not only not "forbidden" by the guidelines in the
Elisp manual, but that is even explicitly allowed by these guidelines,
and is very often in the best interest of the user. Neither should
the macro make this unnecessarily difficult for him or her.
The ELisp manual clearly says that a major mode does not need to have
its own syntax-table or abbrev-table, but may share one with related
modes. Especially in the case of abbrev-tables, it is in the user's
interest to share local abbrev-tables as much as possible between
closely related modes. Yet, define-derived-mode makes it
unnecessarily tricky to share abbrev tables or syntax tables without
introducing the second type of bug I described in my previous message.
The Elisp manual also says that hooks of related modes may be ran just
before the mode's own hook OR that they may be ran earlier. It seems
to me that define-derived-mode tries to enforce the first type of
behavior.
Apart from the bug-related objections and the "enforcement of
unnecessary or even counterproductive rules" objections, I also do not
like the way define-derived-mode amalgamates two barely related
functionalities into one macro. Of course, one can always "save a
function definition" by combining two different functions into a
single macro that expands into the code of one or the other depending
on the value of some argument. Whether it makes sense to actually do
so is a different question.
define-derived-mode is currently used for two barely compatible, in
many respects diametrically opposite purposes:
A way to make a very specialized, but common, task easy for people to
do, namely defining a barely different derived mode from a parent
mode. I believe define-derived-mode serves this purpose well, except
for the fact that, as I pointed out in my previous message, it should
make the derived mode use the parent's abbrev table instead of
introducing bugs and confusion by trying to copy the parent's
abbrev-table.
The second, more recent purpose is as a "standard" way to define any
major mode whatsoever. This is a completely different task. I
believe that one should revert to the 21.2.90 behavior and no longer
make the code expand into something different when the parent mode is
fundamental-mode. Some modes could truly differ so little from
fundamental-mode that they are "truly" derived modes of fundamental
mode. I believe the current value of "nil" for PARENT should be
eliminated and replaced by a separate macro `define-major-mode', which
would be a true analogue of `define-minor-mode'. This would then
take over as the "standard" way to define a major mode.
I propose to define a macro that would be called like this:
(define-major-mode mymode name syntax-table abbrev-table mode-class
docstring body)
(A proposed expansion is included below.)
Here, syntax-table and abbrev-table could be another mode's syntax or
abbrev-tables, or nil, in which case the mode gets its own syntax or
abbrev table. This is one difference with the current code. Another
is that now the author decides when other mode's hooks are ran. Of
course, (s)he is perfectly free to choose the current solution in
body, (s)he is just not forced too. Note that my code still runs
run-mode-hooks instead of run-hooks, so that, if I understand things
correctly, authors using `define-major-mode' would still be supporting
other people's use of delay-mode-hooks, even if they would choose not
to use that machinery themselves. (I have not yet read through the
details of the source code of the functions involved in the
"delay-mode-hooks run-mode-hooks" machinery and their documentation
strings are imprecise and cryptic.)
There is still one thing I am hesitating about:
The Elisp manual says:
Major modes usually should have their own keymap...
There is a "usually" in this sentence. So maybe define-major-mode
should also take a keymap argument. This is a lot less clear than it
is for syntax-tables and abbrev-tables however.
To give a clearer idea of what I have in mind, here is what I envision
the macro (approximately) to expand into. (I have not yet even tried
to debug this, because at this stage it is just a proposal.) Note
that it is just an amended version of what define-derived-mode with a
PARENT of nil or fundamental-mode would expand into, but it eliminates
the second type of bug I reported in my previous message and it takes
care of all objections I formulated above.
(progn
(defvar mymode-map
(make-sparse-keymap))
(unless syntax-table
(defvar mymode-syntax-table
(make-syntax-table)))
(unless abbrev-table
(defvar mymode-abbrev-table
(progn
(define-abbrev-table 'mymode-abbrev-table nil)
mymode-abbrev-table)))
(if mode-class
(put 'mymode 'mode-class 'special))
(put 'mymode 'derived-mode-parent 'nil)
(defun mymode nil "docstring\n\nThis mode runs the hook
`mymode-hook', as the final step\nduring
initialization.\n\n\\{mymode-map}"
(interactive)
(kill-all-local-variables)
(setq major-mode 'mymode)
(setq mode-name name)
(use-local-map mymode-map)
(set-syntax-table (or syntax-table mymode-syntax-table))
(setq local-abbrev-table (or abbrev-table mymode-abbrev-table))
body
(run-mode-hooks 'my-mode-hook)))