* Abbrev should preserve case @ 2007-06-20 17:48 Andreas Röhler 2007-06-20 22:18 ` Glenn Morris 0 siblings, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-06-20 17:48 UTC (permalink / raw) To: emacs-devel Info emacs, Node: Expanding Abbrevs, says: ... ,---- | Abbrev expansion preserves case; thus, `foo' expands into `find | outer otter'; `Foo' into `Find outer otter', and `FOO' into `FIND OUTER | OTTER' `---- From there users may guess, it would be possible to define `foo', `Foo' and `FOO' as abbrev names likewise and altogether. Which don't work, because `add-abbrev' in line 289 abbrev.el down-cases every input, thus only down-cased names are registered. ,---- | (define-abbrev table (downcase name) exp)))) `---- Exists a reason for that? I would much appreciate, if I could mix freely up- and downcase chars. This would extend the range of possible abbrev-names considerably. The latter is of interest, if you don't use abbrev the common way, but for whole phrases, defined by machine. If no one objects, I would try to change that. Andreas Roehler ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-20 17:48 Abbrev should preserve case Andreas Röhler @ 2007-06-20 22:18 ` Glenn Morris 2007-06-21 3:47 ` Davis Herring 2007-06-21 17:32 ` Richard Stallman 0 siblings, 2 replies; 42+ messages in thread From: Glenn Morris @ 2007-06-20 22:18 UTC (permalink / raw) To: Andreas Röhler; +Cc: emacs-devel > ,---- > | Abbrev expansion preserves case; thus, `foo' expands into `find > | outer otter'; `Foo' into `Find outer otter', and `FOO' into `FIND OUTER > | OTTER' > `---- > > From there users may guess, it would be possible to define `foo', > `Foo' and `FOO' as abbrev names likewise and altogether. I don't understand what you mean. To me, the quoted text implies that case is irrelevant in abbrev names. Perhaps it could explicitly say something about this. > Which don't work, because `add-abbrev' in line 289 abbrev.el > down-cases every input, thus only down-cased names are registered. > > ,---- > | (define-abbrev table (downcase name) exp)))) > `---- > > Exists a reason for that? Because otherwise the abbrev mechanism could not work as described in your first quote. expand-abbrev in abbrev.c downcases words in the buffer before comparing against the defined abbrevs. If define-abbrev did allow you to define "FOO" as an abbrev, it would never be expanded. > I would much appreciate, if I could mix freely up- and downcase > chars. This would extend the range of possible abbrev-names > considerably. > > The latter is of interest, if you don't use abbrev the common way, > but for whole phrases, defined by machine. I don't understand what you mean. Perhaps an example would help. > If no one objects, I would try to change that. I don't know what you want to do. How would you tell the difference between `FOO' meaning "expand to the upper-case expansion of `foo'" (as it works now), and `FOO' a totally separate abbreviation? I guess you could try a case-insensitive match first, then a case-sensitive one if it fails. This would mean that `FOO' could expand to something different than `foo' only if `foo' were not defined as an abbrev. Seems a bit complex though. Admittedly, I think the way it works now is a bit odd, but it's always been like that. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-20 22:18 ` Glenn Morris @ 2007-06-21 3:47 ` Davis Herring 2007-06-21 4:14 ` Stefan Monnier 2007-06-21 7:00 ` Glenn Morris 2007-06-21 17:32 ` Richard Stallman 1 sibling, 2 replies; 42+ messages in thread From: Davis Herring @ 2007-06-21 3:47 UTC (permalink / raw) To: Glenn Morris; +Cc: Andreas Röhler, emacs-devel > How would you tell the difference between `FOO' meaning "expand to the > upper-case expansion of `foo'" (as it works now), and `FOO' a totally > separate abbreviation? I guess you could try a case-insensitive match > first, then a case-sensitive one if it fails. This would mean that > `FOO' could expand to something different than `foo' only if `foo' > were not defined as an abbrev. Seems a bit complex though. Surely you mean do a case-sensitive search first and then insensitive? Obviously the sensitive one can't work if the insensitive one fails. Then you go on to say that "FOO" could only be -different- if there is nothing than which to be different; instead we want to say "`FOO' can only expand as upcased `foo' if `FOO' is not its own abbrev", right? I'm not trying to be needlessly pedantic; rather I think what you probably meant to say made significantly more sense as a path forward than what you did say and so deserves consideration. Davis -- This product is sold by volume, not by mass. If it appears too dense or too sparse, it is because mass-energy conversion has occurred during shipping. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-21 3:47 ` Davis Herring @ 2007-06-21 4:14 ` Stefan Monnier 2007-06-21 7:10 ` Andreas Röhler 2007-06-21 7:00 ` Glenn Morris 1 sibling, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2007-06-21 4:14 UTC (permalink / raw) To: herring; +Cc: Glenn Morris, Andreas Röhler, emacs-devel >> How would you tell the difference between `FOO' meaning "expand to the >> upper-case expansion of `foo'" (as it works now), and `FOO' a totally >> separate abbreviation? I guess you could try a case-insensitive match >> first, then a case-sensitive one if it fails. This would mean that >> `FOO' could expand to something different than `foo' only if `foo' >> were not defined as an abbrev. Seems a bit complex though. > Surely you mean do a case-sensitive search first and then insensitive? > Obviously the sensitive one can't work if the insensitive one fails. Then > you go on to say that "FOO" could only be -different- if there is nothing > than which to be different; instead we want to say "`FOO' can only expand > as upcased `foo' if `FOO' is not its own abbrev", right? > I'm not trying to be needlessly pedantic; rather I think what you probably > meant to say made significantly more sense as a path forward than what you > did say and so deserves consideration. We could very easily make some abbrev-table case sensitive. I have reimplemented abbrevs in Elisp and have added some features such as case-fold properties on abbrev-tables as well as inheritance between abbrev-tables (those two properties are useful together to make it possible to mix case-sensitive and case-insensitive abbrevs). The port from C to Elisp is clean and should be pretty reliable (the new features OTOH are only partly implemented for now, the missing parts being mostly on the front of displaying/reading/saving those elements of abbrev tables). Recently someone brought to my attention that it is difficult to enable/disable abbreviations depending on the context (e.g. enable/disable abbrevs providing skeletons depending on whether point is in string/comment or not). So maybe a `predicate' would be a useful addition. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-21 4:14 ` Stefan Monnier @ 2007-06-21 7:10 ` Andreas Röhler 2007-06-21 8:01 ` Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-06-21 7:10 UTC (permalink / raw) To: emacs-devel; +Cc: Glenn Morris, Stefan Monnier ... > > We could very easily make some abbrev-table case sensitive. That's great. Might it not be the best solution to drop the down-case commands in abbrev.c? DEFUN ("define-global-abbrev", for example calls it ,---- | Fdefine_abbrev (Vglobal_abbrev_table, Fdowncase (abbrev), `---- line 181 Case handling should be done at a higher level AFAIU. > I have > reimplemented abbrevs in Elisp and have added some features such as > case-fold properties on abbrev-tables as well as inheritance between > abbrev-tables (those two properties are useful together to make it possible > to mix case-sensitive and case-insensitive abbrevs). The port from C to > Elisp is clean and should be pretty reliable (the new features OTOH are > only partly implemented for now, the missing parts being mostly on the > front of displaying/reading/saving those elements of abbrev tables). > > Recently someone brought to my attention that it is difficult to > enable/disable abbreviations depending on the context (e.g. enable/disable > abbrevs providing skeletons depending on whether point is in string/comment > or not). So maybe a `predicate' would be a useful addition. > > > Stefan As it seems the right time to consider several things, let me ask some more questions around abbrevs: Imaging the use of machine written abbrevs for NLP, context analyses etc., speed will matter. Therefore I suggest to do the work in C as far as possible, avoid re-implementations. (I intend to take part here as far as it's welcome and I'm able to.) Given all that would be done, another point arises: the volume of the abbrev_defs-file. Please consider if a derived mode must copy all the abbrevs. I'd say a derived mode should rather note the differences, but read the major-mode first. Then the abbrev-file, which already counts 500K here, would shrink a lot. Thanks all Andreas Roehler ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-21 7:10 ` Andreas Röhler @ 2007-06-21 8:01 ` Stefan Monnier 2007-06-21 9:00 ` Andreas Röhler 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2007-06-21 8:01 UTC (permalink / raw) To: Andreas Röhler; +Cc: Glenn Morris, emacs-devel > Might it not be the best solution to drop the down-case > commands in abbrev.c? The case-insensitivity (and magical treatment of case in general) is a feature, as evidenced by the amount of extra code in abbrev.c to implement it. So we do not want to just throw it all out. > Imaging the use of machine written abbrevs for NLP, context analyses etc., > speed will matter. Therefore I suggest to do the work in C as far as > possible, avoid re-implementations. (I intend to take part here as far as > it's welcome and I'm able to.) The expand-abbrev code has no loop. So there is no issue w.r.t performance (at least as long as we stick to the current constraint that abbreviations can only contain chars of word-syntax"): the code of expand-abbrev basically extracts the word before point, looks it up in a hash-table (actually, an obarray) and then uses the result to do the expansion (if any). The only part that will get slower with larger abbrev-tables is the hash-lookup which is coded in C anyway. > Please consider if a derived mode must copy all the > abbrevs. I'd say a derived mode should rather note the > differences, but read the major-mode first. Then the > abbrev-file, which already counts 500K here, would > shrink a lot. This may be solved by the use of inheritance. Although given the rather limited amount of derived major modes in use, I'm not sure your 500KB would really shrink that much. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-21 8:01 ` Stefan Monnier @ 2007-06-21 9:00 ` Andreas Röhler 0 siblings, 0 replies; 42+ messages in thread From: Andreas Röhler @ 2007-06-21 9:00 UTC (permalink / raw) To: emacs-devel; +Cc: Glenn Morris, Stefan Monnier Am Donnerstag, 21. Juni 2007 10:01 schrieb Stefan Monnier: > > Might it not be the best solution to drop the down-case > > commands in abbrev.c? > > The case-insensitivity (and magical treatment of case in general) is > a feature, as evidenced by the amount of extra code in abbrev.c to > implement it. So we do not want to just throw it all out. Hmm. To preserve the status quo quite often is a wise decision. Will see... > > Imaging the use of machine written abbrevs for NLP, context analyses > > etc., speed will matter. Therefore I suggest to do the work in C as far > > as possible, avoid re-implementations. (I intend to take part here as far > > as it's welcome and I'm able to.) > > The expand-abbrev code has no loop. So there is no issue > w.r.t performance (at least as long as we stick to the current constraint > that abbreviations can only contain chars of word-syntax"): the code of > expand-abbrev basically extracts the word before point, looks it up in > a hash-table (actually, an obarray) and then uses the result to do the > expansion (if any). The only part that will get slower with larger > abbrev-tables is the hash-lookup which is coded in C anyway. > > > Please consider if a derived mode must copy all the > > abbrevs. I'd say a derived mode should rather note the > > differences, but read the major-mode first. Then the > > abbrev-file, which already counts 500K here, would > > shrink a lot. > > This may be solved by the use of inheritance. I'm looking forward for that. > Although given the rather > limited amount of derived major modes in use, I'm not sure your 500KB would > really shrink that much. > You mentioned abbreviations depending on the context. That could explode the size with the current copying. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-21 3:47 ` Davis Herring 2007-06-21 4:14 ` Stefan Monnier @ 2007-06-21 7:00 ` Glenn Morris 1 sibling, 0 replies; 42+ messages in thread From: Glenn Morris @ 2007-06-21 7:00 UTC (permalink / raw) To: herring; +Cc: Andreas Röhler, emacs-devel "Davis Herring" wrote: > Surely you mean do a case-sensitive search first and then > insensitive? Obviously the sensitive one can't work if the > insensitive one fails. Then you go on to say that "FOO" could only > be -different- if there is nothing than which to be different; > instead we want to say "`FOO' can only expand as upcased `foo' if > `FOO' is not its own abbrev", right? It doesn't really matter what I meant if Stefan's already invented this particular wheel. I was using "case-insensitive" in a confusing (ie wrong) way. What I meant was: You have a word (FOO) in the buffer. Downcase it, and compare with the abbrev table. If you find a match (foo), return the expansion, with the existing abbrev case-fiddling applied. This is the same as happens as present. If you don't find a match (and if the word is not all lower-case), go on to try the un-downcased version (FOO) against the abbrev table. If you find a match, return the expansion. This would enable existing abbrevs to function as they always have, and allow for new mixed-case abbrevs so long as they don't conflict with old-style abbrevs. If you do it the other way round, you would break backwards compatibility. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-20 22:18 ` Glenn Morris 2007-06-21 3:47 ` Davis Herring @ 2007-06-21 17:32 ` Richard Stallman 2007-06-21 19:02 ` Stefan Monnier 1 sibling, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-06-21 17:32 UTC (permalink / raw) To: Glenn Morris; +Cc: andreas.roehler, emacs-devel The handling of case in abbrevs is designed to work well for abbreviations for words and phrases used in text. It is the right thing. It might be ok to allow separate definitions for non-lower-case abbrevs, and look first for them when the text to be expanded is not lower case. This would be ok provided that it has no effect on the handling of the sort of abbrevs that now exist. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-21 17:32 ` Richard Stallman @ 2007-06-21 19:02 ` Stefan Monnier 2007-06-22 16:25 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2007-06-21 19:02 UTC (permalink / raw) To: rms; +Cc: Glenn Morris, andreas.roehler, emacs-devel > The handling of case in abbrevs is designed to work well > for abbreviations for words and phrases used in text. > It is the right thing. Agreed. But it has since been (ab)used for things like skeletons in programming modes where typing "case SPC" would expand to case in *) ;; esac for those kinds of uses, the capitalization footwork of abbrev.el is rather harmful. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-21 19:02 ` Stefan Monnier @ 2007-06-22 16:25 ` Richard Stallman 2007-06-22 17:51 ` Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-06-22 16:25 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel Agreed. But it has since been (ab)used for things like skeletons in programming modes where typing "case SPC" would expand to case in *) ;; esac for those kinds of uses, the capitalization footwork of abbrev.el is rather harmful. In practice, how is it harmful? You would get undesirable results if you enter `CASE' or `Case', but that problem is easy to avoid: enter `case' instead. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-22 16:25 ` Richard Stallman @ 2007-06-22 17:51 ` Stefan Monnier 2007-06-22 21:53 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2007-06-22 17:51 UTC (permalink / raw) To: rms; +Cc: rgm, andreas.roehler, emacs-devel > Agreed. But it has since been (ab)used for things like skeletons in > programming modes where typing "case SPC" would expand to > case in > *) ;; > esac > for those kinds of uses, the capitalization footwork of abbrev.el is > rather harmful. > In practice, how is it harmful? Usually, not too severe. > You would get undesirable results if you enter `CASE' or `Case', but that > problem is easy to avoid: enter `case' instead. I'm not sure I understand the question: of course we know how to avoid the problem. And we know that it's really harmful if you happen to have variable names such as Case or CASE or cAse. I do not want to change the default case-insensitivity of abbrevs, but I think it'd be worthwhile to be able to specify some case-sensitive abbrevs as well. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev should preserve case 2007-06-22 17:51 ` Stefan Monnier @ 2007-06-22 21:53 ` Richard Stallman 2007-10-10 21:14 ` Abbrev tables in elisp with some extra stuff (was: Abbrev should preserve case) Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-06-22 21:53 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel I'm not sure I understand the question: of course we know how to avoid the problem. And we know that it's really harmful if you happen to have variable names such as Case or CASE or cAse. Now I see the point. Yes, making that abbrev case-sensitive would be a useful thing to do. I do not want to change the default case-insensitivity of abbrevs, but I think it'd be worthwhile to be able to specify some case-sensitive abbrevs as well. That is fine with me. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Abbrev tables in elisp with some extra stuff (was: Abbrev should preserve case) 2007-06-22 21:53 ` Richard Stallman @ 2007-10-10 21:14 ` Stefan Monnier 2007-10-11 19:57 ` Andreas Röhler 2007-10-12 15:59 ` Richard Stallman 0 siblings, 2 replies; 42+ messages in thread From: Stefan Monnier @ 2007-10-10 21:14 UTC (permalink / raw) To: rms; +Cc: rgm, andreas.roehler, emacs-devel > I'm not sure I understand the question: of course we know how to avoid the > problem. And we know that it's really harmful if you happen to have > variable names such as Case or CASE or cAse. > Now I see the point. Yes, making that abbrev case-sensitive would be > a useful thing to do. > I do not want to change the default case-insensitivity of abbrevs, but > I think it'd be worthwhile to be able to specify some case-sensitive > abbrevs as well. > That is fine with me. The patch below does the following: - reimplement the abbrev.c code in Elisp. abbrev.c can then be removed. - add a docstring arg to define-abbrev-table. - add property lists to abbrev-tables. Some properties have special meaning: - :parents contains a list of abbrev tables from which this table inherits abbreviations. - :case-preserve non-nil means that abbreviations are lookedup without case-folding, and the expansion is not capitalized/upcased. - :syntax-table holds the syntax table to use to find the relevant word. - :abbrev-before-point-function holds a function to use to find the abbrev at point. It should take no argument and return a list of the form (NAME SYM START END) where NAME is the abbrev name as found in the buffer, SYM is the abbrev, START and END are the buffer positions where NAME was found (i.e. NAME = (buffer-substring START END)). It should preserve point. - :enable-function can be set to a function of no argument which returns non-nil iff the abbrevs in this table should be used for this instance of `expand-abbrev'. Useful to disable skeleton-abbrevs in strings and comments. - allow local-abbrev-table to hold a list of abbrev tables so minor modes can add their own abbrev tables as well (useful for mailabbrev.el). - add a variable abbrev-auto-activated-tables that can be used to disable all but some specific tables. This can be used to implement mail-abbrevs-only. It also has a few more new features that I tried and then dropped or forgot about, so if/when this code is accepted, it'll need a bit of clean up before installation. Stefan --- orig/lisp/abbrev.el +++ mod/lisp/abbrev.el @@ -363,6 +363,504 @@ (if (or noquery (y-or-n-p (format "Expand `%s'? " string))) (expand-abbrev))))))) +;;; Abbrev properties. + + +;; Todo: +;; - abbrev table may be chosen based on context (e.g. mail-abbrev uses an +;; ugly pre-abbrev-expand-hook in order to do abbrev-expansion in the +;; header differently from abbrev expansion in the rest of the message). +;; - multi-word or non-word abbrevs? +;; - abbrevs could have a `predicate' so you can disable them in strings and +;; comments, for example. Maybe the predicate should be on the table +;; rather than on individual abbrevs. This may be enough to cover the +;; above request for context-dependent abbrevs. + +(defun abbrev-table-get (table prop) + "Get the PROP property of abbrev table TABLE." + (let ((sym (intern-soft "" table))) + (if sym (get sym prop)))) + +(defun abbrev-table-put (table prop val) + "Set the PROP property of abbrev table TABLE to VAL." + (let ((sym (intern "" table))) + (set sym nil) ; Make sure it won't be confused for an abbrev. + (put sym prop val))) + +(defun abbrev-get (sym prop) + (let ((plist (symbol-plist sym))) + (if (listp plist) + (plist-get plist prop) + (if (eq 'count prop) plist)))) + +(defun abbrev-put (sym prop val) + (let ((plist (symbol-plist sym))) + (if (consp plist) + (put sym prop val) + (setplist sym (if (eq 'count prop) val + (list 'count plist prop val)))))) + +(defmacro abbrev-with-wrapper-hook (var &rest body) + "Run BODY wrapped with the VAR hook. +VAR is a special hook: its functions are called with one argument which +is the \"original\" code, so the hook function can wrap the original function, +can call it several times, or even not call it at all. +This is similar to an `around' advice." + (declare (indent 1) (debug t)) + `(labels ((loop (--abrev-funs-- --abbrev-global--) + (lexical-let ((funs --abbrev-funs--) + (global --abbrev-global--)) + (if (consp funs) + (if (eq t (car funs)) + (loop (append global (cdr funs)) nil) + (funcall (car funs) + (lambda () (loop (cdr funs) global)))) + ,@body)))) + (loop ,var (if (local-variable-p ',var) (default-value ',var))))) + + +;;; Code that used to be implemented in src/abbrev.c + +(defvar abbrev-table-name-list '(fundamental-mode-abbrev-table + global-abbrev-table) + "List of symbols whose values are abbrev tables.") + +(defun make-abbrev-table (&optional props) + "Create a new, empty abbrev table object." + ;; The value 59 is an arbitrary prime number. + (let ((table (make-vector 59 0))) + (while (consp props) + (abbrev-table-put table (pop props) (pop props))) + table)) + +(defvar global-abbrev-table (make-abbrev-table) + "The abbrev table whose abbrevs affect all buffers. +Each buffer may also have a local abbrev table. +If it does, the local table overrides the global one +for any particular abbrev defined in both.") + +(defvar abbrev-minor-mode-tables nil + "List of additional abbrev tables.") +(make-variable-buffer-local 'abbrev-minor-mode-tables) + +(defvar fundamental-mode-abbrev-table + (let ((table (make-abbrev-table))) + ;; Set local-abbrev-table's default to be fundamental-mode-abbrev-table. + (setq-default local-abbrev-table table) + table) + "The abbrev table of mode-specific abbrevs for Fundamental Mode.") + +(defvar abbrevs-changed nil + "Set non-nil by defining or altering any word abbrevs. +This causes `save-some-buffers' to offer to save the abbrevs.") + +(defcustom abbrev-all-caps nil + "Set non-nil means expand multi-word abbrevs all caps if abbrev was so." + :type 'boolean + :group 'abbrev-mode) + +(defvar abbrev-start-location nil + "Buffer position for `expand-abbrev' to use as the start of the abbrev. +When nil, use the word before point as the abbrev. +Calling `expand-abbrev' sets this to nil.") + +(defvar abbrev-start-location-buffer nil + "Buffer that `abbrev-start-location' has been set for. +Trying to expand an abbrev in any other buffer clears `abbrev-start-location'.") + +(defvar last-abbrev nil + "The abbrev-symbol of the last abbrev expanded. See `abbrev-symbol'.") + +(defvar last-abbrev-text nil + "The exact text of the last abbrev expanded. +nil if the abbrev has already been unexpanded.") + +(defvar last-abbrev-location 0 + "The location of the start of the last abbrev expanded.") + +;; (defvar local-abbrev-table fundamental-mode-abbrev-table +;; "Local (mode-specific) abbrev table of current buffer.") +;; (make-variable-buffer-local 'local-abbrev-table) + +(defcustom pre-abbrev-expand-hook nil + "Function or functions to be called before abbrev expansion is done. +This is the first thing that `expand-abbrev' does, and so this may change +the current abbrev table before abbrev lookup happens." + :type 'hook + :group 'abbrev-mode) + +(defun clear-abbrev-table (table) + "Undefine all abbrevs in abbrev table TABLE, leaving it empty." + (setq abbrevs-changed t) + (dotimes (i (length table)) + (aset table i 0))) + +(defun define-abbrev (table name expansion &optional hook count system-flag + ;; In case the abbrev list passed to + ;; `define-abbrev-table' includes extra elements + ;; that we should ignore. + &rest ignore) + "Define an abbrev in TABLE named NAME, to expand to EXPANSION and call HOOK. +NAME must be a string, and should be lower-case. +EXPANSION should usually be a string. +To undefine an abbrev, define it with EXPANSION = nil. +If HOOK is non-nil, it should be a function of no arguments; +it is called after EXPANSION is inserted. +If EXPANSION is not a string, the abbrev is a special one, + which does not expand in the usual way but only runs HOOK. + +COUNT, if specified, gives the initial value for the abbrev's +usage-count, which is incremented each time the abbrev is used. +\(The default is zero.) + +SYSTEM-FLAG, if non-nil, says that this is a \"system\" abbreviation +which should not be saved in the user's abbreviation file. +Unless SYSTEM-FLAG is `force', a system abbreviation will not +overwrite a non-system abbreviation of the same name." + (unless count (setq count 0)) + (let ((sym (intern name table))) + ;; Don't override a prior user-defined abbrev with a system abbrev, + ;; unless system-flag is `force'. + (unless (and (not (memq system-flag '(nil force))) + (boundp sym) (symbol-value sym) + (not (abbrev-get sym 'system-flag))) + (unless (or system-flag + (and (boundp sym) (fboundp sym) + ;; load-file-name + (equal (symbol-value sym) expansion) + (equal (symbol-function sym) hook))) + (setq abbrevs-changed t)) + (set sym expansion) + (fset sym hook) + (setplist sym (if (null system-flag) count + (list 'count count 'system-flag system-flag)))) + name)) + +(defun abbrev--check-chars (abbrev global) + "Check if the characters in ABBREV have word syntax in either the +current (if global is nil) or standard syntax table." + (with-syntax-table + (cond ((null global) (standard-syntax-table)) + ;; ((syntax-table-p global) global) + (t (syntax-table))) + (when (string-match "\\W" abbrev) + (let ((badchars ()) + (pos 0)) + (while (string-match "\\W" abbrev pos) + (pushnew (aref abbrev (match-beginning 0)) badchars) + (setq pos (1+ pos))) + (error "Some abbrev characters (%s) are not word constituents %s" + (apply 'string (nreverse badchars)) + (if global "in the standard syntax" "in this mode")))))) + +(defun define-global-abbrev (abbrev expansion) + "Define ABBREV as a global abbreviation for EXPANSION. +The characters in ABBREV must all be word constituents in the standard +syntax table." + (interactive "sDefine global abbrev: \nsExpansion for %s: ") + (abbrev--check-chars abbrev 'global) + (define-abbrev global-abbrev-table (downcase abbrev) expansion)) + +(defun define-mode-abbrev (abbrev expansion) + "Define ABBREV as a mode-specific abbreviation for EXPANSION. +The characters in ABBREV must all be word-constituents in the current mode." + (interactive "sDefine mode abbrev: \nsExpansion for %s: ") + (unless local-abbrev-table + (error "Major mode has no abbrev table")) + (abbrev--check-chars abbrev nil) + (define-abbrev local-abbrev-table (downcase abbrev) expansion)) + +(defvar abbrev-auto-activated-tables t + ;; Could be expanded to be a predicate. + "List of abbrev tables that can be used when `expand-abbrev' is called implicitly. +If t, use all installed tables.") + +(defun abbrev-set-member (elem set) + (cond + ((functionp set) (funcall set elem)) + ((eq (car-safe set) 'not) (not (abbrev-set-member elem (cadr set)))) + (t (member elem set)))) + +(defun abbrev-active-tables (&optional tables) + (cond + ((consp tables) tables) + ((vectorp tables) (list tables)) + (t + (let ((tables (append abbrev-minor-mode-tables + (if (listp local-abbrev-table) + (append local-abbrev-table + (list global-abbrev-table)) + (list local-abbrev-table global-abbrev-table))))) + (if (or (eq t abbrev-auto-activated-tables) + (eq this-command 'expand-abbrev)) + tables + (dolist (table (prog1 tables (setq tables nil)) tables) + (if (abbrev-set-member table abbrev-auto-activated-tables) + (push table tables)))))))) + + +(defun abbrev-symbol (abbrev &optional table) + "Return the symbol representing abbrev named ABBREV. +This symbol's name is ABBREV, but it is not the canonical symbol of that name; +it is interned in an abbrev-table rather than the normal obarray. +The value is nil if that abbrev is not defined. +Optional second arg TABLE is abbrev table to look it up in. +The default is to try buffer's mode-specific abbrev table, then global table." + (let ((tables (abbrev-active-tables table)) + sym) + (while (and tables (not (symbol-value sym))) + (let ((table (pop tables)) + (case-fold (not (abbrev-table-get table :case-preserve)))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (setq sym (intern-soft (if case-fold (downcase abbrev) abbrev) table)) + (if (and (not case-fold) (symbol-value sym)) + ;; The :case-preserve property normally belongs to the + ;; abbrev-table, but the use of this abbrev needs to know if + ;; this abbrev came from a case preserving table or not, so we + ;; save a copy in the abbrev itself. + (abbrev-put sym :case-preserve t)))) + (if (symbol-value sym) + sym))) + + +(defun abbrev-expansion (abbrev &optional table) + "Return the string that ABBREV expands into in the current buffer. +Optionally specify an abbrev table as second arg; +then ABBREV is looked up in that table only." + (symbol-value (abbrev-symbol abbrev table))) + +(defun abbrev-before-point-exhaustive-search (table) + "Sample :abbrev-before-point-function." + (save-excursion + (skip-syntax-backward " ") + (let (res) + (mapatoms (lambda (sym) + (when (symbol-value sym) + (let ((name (symbol-name sym))) + (when (search-backward name (- (point) (length name)) t) + (setq res (list name sym + (match-beginning 0) (match-end 0))))))) + table) + res))) + +(defun abbrev--before-point () + "Try and find an abbrev before point. Return it if found, nil otherwise." + (unless (eq abbrev-start-location-buffer (current-buffer)) + (setq abbrev-start-location nil)) + + (let ((tables (abbrev-active-tables)) + (pos (point)) + start end sym) + + (if abbrev-start-location + (progn + (setq start abbrev-start-location) + (setq abbrev-start-location nil) + ;; Remove the hyphen inserted by `abbrev-prefix-mark'. + (if (and (< start (point-max)) + (eq (char-after start) ?-)) + (delete-region start (1+ start))) + (skip-syntax-backward " ") + (setq end (point)) + (setq name (buffer-substring start end)) + (setq sym (abbrev-symbol name tables)) + ;; Restore point. + (goto-char pos)) + + (while (and tables (not sym)) + (let* ((table (pop tables)) + (fun (abbrev-table-get table :abbrev-before-point-function)) + (enable-fun (abbrev-table-get table :enable-function))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (with-syntax-table (or (abbrev-table-get table :syntax-table) + (syntax-table)) + (if fun + ;; Use abbrev-before-point-function if provided. + (let ((res (funcall fun table))) + (setq name (pop res) sym (pop res) + start (pop res) end (pop res))) + (and (or (not enable-fun) (funcall enable-fun)) + (setq start (and (forward-word -1) (point))) + (setq end (and (forward-word 1) (min (point) pos))) + (setq name (buffer-substring start end)) + ;; This will also look it up in parent tables. This is + ;; not on purpose, but it seems harmless. + (setq sym (abbrev-symbol name table))) + ;; Restore point. + (goto-char pos)))))) + (if sym (list name sym start end)))) + + +(defun expand-abbrev () + "Expand the abbrev before point, if there is an abbrev there. +Effective when explicitly called even when `abbrev-mode' is nil. +Returns the abbrev symbol, if expansion took place." + (interactive) + (run-hooks 'pre-abbrev-expand-hook) + + (destructuring-bind (&optional name sym wordstart wordend) + (abbrev--before-point) + (when sym + (let ((value sym)) + (unless (or ;; executing-kbd-macro + noninteractive + (window-minibuffer-p (selected-window))) + ;; Add an undo boundary, in case we are doing this for + ;; a self-inserting command which has avoided making one so far. + (undo-boundary)) + ;; Now sym is the abbrev symbol. + (setq last-abbrev-text name) + (setq last-abbrev sym) + (setq last-abbrev-location wordstart) + ;; Increment use count. + (abbrev-put sym 'count (1+ (abbrev-get sym 'count))) + ;; If this abbrev has an expansion, delete the abbrev + ;; and insert the expansion. + (when (stringp (symbol-value sym)) + (goto-char wordend) + (insert (symbol-value sym)) + (delete-region wordstart wordend) + (when (and (not (abbrev-get sym :case-preserve)) + (string-match "[[:upper:]]" name)) + (if (not (string-match "[[:lower:]]" name)) + ;; Abbrev was all caps. If expansion is multiple words, + ;; normally capitalize each word. + (if (and (not abbrev-all-caps) + (save-excursion + (> (progn (backward-word 1) (point)) + (progn (goto-char wordstart) + (forward-word 1) (point))))) + (upcase-initials-region wordstart (point)) + (upcase-region wordstart (point))) + ;; Abbrev included some caps. Cap first initial of expansion. + (let ((end (point))) + ;; Find the initial. + (goto-char wordstart) + (skip-syntax-forward "^w" (1- end)) + ;; Change just that. + (upcase-initials-region (point) (1+ (point))))))) + (when (symbol-function sym) + (let* ((hook (symbol-function sym)) + (expanded + ;; If the abbrev has a hook function, run it. + (funcall hook))) + ;; In addition, if the hook function is a symbol with + ;; a non-nil `no-self-insert' property, let the value it + ;; returned specify whether we consider that an expansion took + ;; place. If it returns nil, no expansion has been done. + (if (and (symbolp hook) + (null expanded) + (get hook 'no-self-insert)) + (setq value nil)))) + value)))) + +(defun unexpand-abbrev () + "Undo the expansion of the last abbrev that expanded. +This differs from ordinary undo in that other editing done since then +is not undone." + (interactive) + (save-excursion + (unless (or (< last-abbrev-location (point-min)) + (> last-abbrev-location (point-max))) + (goto-char last-abbrev-location) + (when (stringp last-abbrev-text) + ;; This isn't correct if last-abbrev's hook was used + ;; to do the expansion. + (let ((val (symbol-value last-abbrev))) + (unless (stringp val) + (error "value of abbrev-symbol must be a string")) + (delete-region (point) (+ (point) (length val))) + ;; Don't inherit properties here; just copy from old contents. + (insert last-abbrev-text) + (setq last-abbrev-text nil)))))) + +(defun write--abbrev (sym) + (unless (or (null (symbol-value sym)) (abbrev-get sym 'system-flag)) + (insert " (") + (prin1 name) + (insert " ") + (prin1 (symbol-value sym)) + (insert " ") + (prin1 (symbol-function sym)) + (insert " ") + (prin1 (abbrev-get sym 'count)) + (insert ")\n"))) + +(defun describe--abbrev (sym) + (when (symbol-value sym) + (prin1 (symbol-name sym)) + (if (null (abbrev-get sym 'system-flag)) + (indent-to 15 1) + (insert " (sys)") + (indent-to 20 1)) + (prin1 (abbrev-get sym 'count)) + (indent-to 20 1) + (prin1 (symbol-value sym)) + (when (symbol-function sym) + (indent-to 45 1) + (prin1 (symbol-function sym))) + (terpri))) + +(defun insert-abbrev-table-description (name &optional readable) + "Insert before point a full description of abbrev table named NAME. +NAME is a symbol whose value is an abbrev table. +If optional 2nd arg READABLE is non-nil, a human-readable description +is inserted. Otherwise the description is an expression, +a call to `define-abbrev-table', which would +define the abbrev table NAME exactly as it is currently defined. + +Abbrevs marked as \"system abbrevs\" are omitted." + (let ((table (symbol-value name)) + (symbols ())) + (mapatoms (lambda (sym) (if (symbol-value sym) (push sym symbols))) table) + (setq symbols (sort symbols 'string-lessp)) + (let ((standard-output (current-buffer))) + (if readable + (progn + (insert "(") + (prin1 name) + (insert ")\n\n") + (mapc 'describe--abbrev symbols) + (insert "\n\n")) + (insert "(define-abbrev-table '") + (prin1 name) + (insert " '(") + (mapc 'write--abbrev symbols) + (insert " ))\n\n")) + nil))) + +(defun define-abbrev-table (tablename definitions + &optional docstring &rest props) + "Define TABLENAME (a symbol) as an abbrev table name. +Define abbrevs in it according to DEFINITIONS, which is a list of elements +of the form (ABBREVNAME EXPANSION HOOK USECOUNT SYSTEMFLAG). +\(If the list is shorter than that, omitted elements default to nil). +PROPS is a property list to apply to the table. +Properties with special meaning: +- :parents contains a list of abbrev tables from which this table inherits + abbreviations. +- :case-preserve non-nil means that abbreviations are lookedup without + case-folding, and the expansion is not capitalized/upcased. +- :syntax-table holds the syntax table to use for to find the relevant word. +- :abbrev-before-point-function holds a function to use to find the + abbrev at point. It should take no argument and return a list of the + form (NAME SYM START END) where NAME is the abbrev name as found in the + buffer, SYM is the abbrev, START and END are the buffer positions where + NAME was found (i.e. NAME = (buffer-substring START END)). It should + preserve point. +- :enable-function can be set to a function of no argument which returns + non-nil iff the abbrevs in this table should be used for this instance + of `expand-abbrev'." + (let ((table (if (boundp tablename) (symbol-value tablename)))) + (unless table + (setq table (make-abbrev-table props)) + (set tablename table) + (push tablename abbrev-table-name-list)) + (when (stringp docstring) + (put tablename 'variable-documentation docstring)) + (dolist (elt definitions) + (apply 'define-abbrev table elt)))) + (provide 'abbrev) ;; arch-tag: dbd6f3ae-dfe3-40ba-b00f-f9e3ff960df5 ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff (was: Abbrev should preserve case) 2007-10-10 21:14 ` Abbrev tables in elisp with some extra stuff (was: Abbrev should preserve case) Stefan Monnier @ 2007-10-11 19:57 ` Andreas Röhler 2007-10-12 15:59 ` Richard Stallman 1 sibling, 0 replies; 42+ messages in thread From: Andreas Röhler @ 2007-10-11 19:57 UTC (permalink / raw) To: emacs-devel; +Cc: Stefan Monnier, Richard Stallman That's fine, thanks. Works for me with GNU Emacs 23.0.50.1 (i686-pc-linux-gnu, GTK+ Version 2.8.3) of 2007-09-24 on Suse 10.0 Andreas Röhler Am Mittwoch, 10. Oktober 2007 23:14 schrieb Stefan Monnier: > > I'm not sure I understand the question: of course we know how to > > avoid the problem. And we know that it's really harmful if you happen to > > have variable names such as Case or CASE or cAse. > > > > Now I see the point. Yes, making that abbrev case-sensitive would be > > a useful thing to do. > > > > I do not want to change the default case-insensitivity of abbrevs, > > but I think it'd be worthwhile to be able to specify some case-sensitive > > abbrevs as well. > > > > That is fine with me. > > The patch below does the following: > - reimplement the abbrev.c code in Elisp. abbrev.c can then be removed. > - add a docstring arg to define-abbrev-table. > - add property lists to abbrev-tables. Some properties have special > meaning: - :parents contains a list of abbrev tables from which this table > inherits abbreviations. > - :case-preserve non-nil means that abbreviations are lookedup without > case-folding, and the expansion is not capitalized/upcased. > - :syntax-table holds the syntax table to use to find the relevant word. > - :abbrev-before-point-function holds a function to use to find the > abbrev at point. It should take no argument and return a list of the > form (NAME SYM START END) where NAME is the abbrev name as found in the > buffer, SYM is the abbrev, START and END are the buffer positions where > NAME was found (i.e. NAME = (buffer-substring START END)). It should > preserve point. > - :enable-function can be set to a function of no argument which returns > non-nil iff the abbrevs in this table should be used for this instance > of `expand-abbrev'. Useful to disable skeleton-abbrevs in strings and > comments. > - allow local-abbrev-table to hold a list of abbrev tables so minor > modes can add their own abbrev tables as well (useful for mailabbrev.el). > - add a variable abbrev-auto-activated-tables that can be used to disable > all but some specific tables. This can be used to implement > mail-abbrevs-only. > > It also has a few more new features that I tried and then dropped or > forgot about, so if/when this code is accepted, it'll need a bit of clean > up before installation. > > > Stefan > > > --- orig/lisp/abbrev.el > +++ mod/lisp/abbrev.el > @@ -363,6 +363,504 @@ > (if (or noquery (y-or-n-p (format "Expand `%s'? " string))) > (expand-abbrev))))))) > > +;;; Abbrev properties. > + > + > +;; Todo: > +;; - abbrev table may be chosen based on context (e.g. mail-abbrev uses an > +;; ugly pre-abbrev-expand-hook in order to do abbrev-expansion in the > +;; header differently from abbrev expansion in the rest of the message). > +;; - multi-word or non-word abbrevs? > +;; - abbrevs could have a `predicate' so you can disable them in strings > and +;; comments, for example. Maybe the predicate should be on the > table +;; rather than on individual abbrevs. This may be enough to cover > the +;; above request for context-dependent abbrevs. > + > +(defun abbrev-table-get (table prop) > + "Get the PROP property of abbrev table TABLE." > + (let ((sym (intern-soft "" table))) > + (if sym (get sym prop)))) > + > +(defun abbrev-table-put (table prop val) > + "Set the PROP property of abbrev table TABLE to VAL." > + (let ((sym (intern "" table))) > + (set sym nil) ; Make sure it won't be confused for an abbrev. > + (put sym prop val))) > + > +(defun abbrev-get (sym prop) > + (let ((plist (symbol-plist sym))) > + (if (listp plist) > + (plist-get plist prop) > + (if (eq 'count prop) plist)))) > + > +(defun abbrev-put (sym prop val) > + (let ((plist (symbol-plist sym))) > + (if (consp plist) > + (put sym prop val) > + (setplist sym (if (eq 'count prop) val > + (list 'count plist prop val)))))) > + > +(defmacro abbrev-with-wrapper-hook (var &rest body) > + "Run BODY wrapped with the VAR hook. > +VAR is a special hook: its functions are called with one argument which > +is the \"original\" code, so the hook function can wrap the original > function, +can call it several times, or even not call it at all. > +This is similar to an `around' advice." > + (declare (indent 1) (debug t)) > + `(labels ((loop (--abrev-funs-- --abbrev-global--) > + (lexical-let ((funs --abbrev-funs--) > + (global --abbrev-global--)) > + (if (consp funs) > + (if (eq t (car funs)) > + (loop (append global (cdr funs)) nil) > + (funcall (car funs) > + (lambda () (loop (cdr funs) global)))) > + ,@body)))) > + (loop ,var (if (local-variable-p ',var) (default-value ',var))))) > + > + > +;;; Code that used to be implemented in src/abbrev.c > + > +(defvar abbrev-table-name-list '(fundamental-mode-abbrev-table > + global-abbrev-table) > + "List of symbols whose values are abbrev tables.") > + > +(defun make-abbrev-table (&optional props) > + "Create a new, empty abbrev table object." > + ;; The value 59 is an arbitrary prime number. > + (let ((table (make-vector 59 0))) > + (while (consp props) > + (abbrev-table-put table (pop props) (pop props))) > + table)) > + > +(defvar global-abbrev-table (make-abbrev-table) > + "The abbrev table whose abbrevs affect all buffers. > +Each buffer may also have a local abbrev table. > +If it does, the local table overrides the global one > +for any particular abbrev defined in both.") > + > +(defvar abbrev-minor-mode-tables nil > + "List of additional abbrev tables.") > +(make-variable-buffer-local 'abbrev-minor-mode-tables) > + > +(defvar fundamental-mode-abbrev-table > + (let ((table (make-abbrev-table))) > + ;; Set local-abbrev-table's default to be > fundamental-mode-abbrev-table. + (setq-default local-abbrev-table table) > + table) > + "The abbrev table of mode-specific abbrevs for Fundamental Mode.") > + > +(defvar abbrevs-changed nil > + "Set non-nil by defining or altering any word abbrevs. > +This causes `save-some-buffers' to offer to save the abbrevs.") > + > +(defcustom abbrev-all-caps nil > + "Set non-nil means expand multi-word abbrevs all caps if abbrev was so." > + :type 'boolean > + :group 'abbrev-mode) > + > +(defvar abbrev-start-location nil > + "Buffer position for `expand-abbrev' to use as the start of the abbrev. > +When nil, use the word before point as the abbrev. > +Calling `expand-abbrev' sets this to nil.") > + > +(defvar abbrev-start-location-buffer nil > + "Buffer that `abbrev-start-location' has been set for. > +Trying to expand an abbrev in any other buffer clears > `abbrev-start-location'.") + > +(defvar last-abbrev nil > + "The abbrev-symbol of the last abbrev expanded. See `abbrev-symbol'.") > + > +(defvar last-abbrev-text nil > + "The exact text of the last abbrev expanded. > +nil if the abbrev has already been unexpanded.") > + > +(defvar last-abbrev-location 0 > + "The location of the start of the last abbrev expanded.") > + > +;; (defvar local-abbrev-table fundamental-mode-abbrev-table > +;; "Local (mode-specific) abbrev table of current buffer.") > +;; (make-variable-buffer-local 'local-abbrev-table) > + > +(defcustom pre-abbrev-expand-hook nil > + "Function or functions to be called before abbrev expansion is done. > +This is the first thing that `expand-abbrev' does, and so this may change > +the current abbrev table before abbrev lookup happens." > + :type 'hook > + :group 'abbrev-mode) > + > +(defun clear-abbrev-table (table) > + "Undefine all abbrevs in abbrev table TABLE, leaving it empty." > + (setq abbrevs-changed t) > + (dotimes (i (length table)) > + (aset table i 0))) > + > +(defun define-abbrev (table name expansion &optional hook count > system-flag + ;; In case the abbrev list passed > to > + ;; `define-abbrev-table' includes extra > elements + ;; that we should ignore. > + &rest ignore) > + "Define an abbrev in TABLE named NAME, to expand to EXPANSION and call > HOOK. +NAME must be a string, and should be lower-case. > +EXPANSION should usually be a string. > +To undefine an abbrev, define it with EXPANSION = nil. > +If HOOK is non-nil, it should be a function of no arguments; > +it is called after EXPANSION is inserted. > +If EXPANSION is not a string, the abbrev is a special one, > + which does not expand in the usual way but only runs HOOK. > + > +COUNT, if specified, gives the initial value for the abbrev's > +usage-count, which is incremented each time the abbrev is used. > +\(The default is zero.) > + > +SYSTEM-FLAG, if non-nil, says that this is a \"system\" abbreviation > +which should not be saved in the user's abbreviation file. > +Unless SYSTEM-FLAG is `force', a system abbreviation will not > +overwrite a non-system abbreviation of the same name." > + (unless count (setq count 0)) > + (let ((sym (intern name table))) > + ;; Don't override a prior user-defined abbrev with a system abbrev, > + ;; unless system-flag is `force'. > + (unless (and (not (memq system-flag '(nil force))) > + (boundp sym) (symbol-value sym) > + (not (abbrev-get sym 'system-flag))) > + (unless (or system-flag > + (and (boundp sym) (fboundp sym) > + ;; load-file-name > + (equal (symbol-value sym) expansion) > + (equal (symbol-function sym) hook))) > + (setq abbrevs-changed t)) > + (set sym expansion) > + (fset sym hook) > + (setplist sym (if (null system-flag) count > + (list 'count count 'system-flag system-flag)))) > + name)) > + > +(defun abbrev--check-chars (abbrev global) > + "Check if the characters in ABBREV have word syntax in either the > +current (if global is nil) or standard syntax table." > + (with-syntax-table > + (cond ((null global) (standard-syntax-table)) > + ;; ((syntax-table-p global) global) > + (t (syntax-table))) > + (when (string-match "\\W" abbrev) > + (let ((badchars ()) > + (pos 0)) > + (while (string-match "\\W" abbrev pos) > + (pushnew (aref abbrev (match-beginning 0)) badchars) > + (setq pos (1+ pos))) > + (error "Some abbrev characters (%s) are not word constituents %s" > + (apply 'string (nreverse badchars)) > + (if global "in the standard syntax" "in this mode")))))) > + > +(defun define-global-abbrev (abbrev expansion) > + "Define ABBREV as a global abbreviation for EXPANSION. > +The characters in ABBREV must all be word constituents in the standard > +syntax table." > + (interactive "sDefine global abbrev: \nsExpansion for %s: ") > + (abbrev--check-chars abbrev 'global) > + (define-abbrev global-abbrev-table (downcase abbrev) expansion)) > + > +(defun define-mode-abbrev (abbrev expansion) > + "Define ABBREV as a mode-specific abbreviation for EXPANSION. > +The characters in ABBREV must all be word-constituents in the current > mode." + (interactive "sDefine mode abbrev: \nsExpansion for %s: ") > + (unless local-abbrev-table > + (error "Major mode has no abbrev table")) > + (abbrev--check-chars abbrev nil) > + (define-abbrev local-abbrev-table (downcase abbrev) expansion)) > + > +(defvar abbrev-auto-activated-tables t > + ;; Could be expanded to be a predicate. > + "List of abbrev tables that can be used when `expand-abbrev' is called > implicitly. +If t, use all installed tables.") > + > +(defun abbrev-set-member (elem set) > + (cond > + ((functionp set) (funcall set elem)) > + ((eq (car-safe set) 'not) (not (abbrev-set-member elem (cadr set)))) > + (t (member elem set)))) > + > +(defun abbrev-active-tables (&optional tables) > + (cond > + ((consp tables) tables) > + ((vectorp tables) (list tables)) > + (t > + (let ((tables (append abbrev-minor-mode-tables > + (if (listp local-abbrev-table) > + (append local-abbrev-table > + (list global-abbrev-table)) > + (list local-abbrev-table > global-abbrev-table))))) + (if (or (eq t abbrev-auto-activated-tables) > + (eq this-command 'expand-abbrev)) > + tables > + (dolist (table (prog1 tables (setq tables nil)) tables) > + (if (abbrev-set-member table abbrev-auto-activated-tables) > + (push table tables)))))))) > + > + > +(defun abbrev-symbol (abbrev &optional table) > + "Return the symbol representing abbrev named ABBREV. > +This symbol's name is ABBREV, but it is not the canonical symbol of that > name; +it is interned in an abbrev-table rather than the normal obarray. > +The value is nil if that abbrev is not defined. > +Optional second arg TABLE is abbrev table to look it up in. > +The default is to try buffer's mode-specific abbrev table, then global > table." + (let ((tables (abbrev-active-tables table)) > + sym) > + (while (and tables (not (symbol-value sym))) > + (let ((table (pop tables)) > + (case-fold (not (abbrev-table-get table :case-preserve)))) > + (setq tables (append (abbrev-table-get table :parents) tables)) > + (setq sym (intern-soft (if case-fold (downcase abbrev) abbrev) > table)) + (if (and (not case-fold) (symbol-value sym)) > + ;; The :case-preserve property normally belongs to the > + ;; abbrev-table, but the use of this abbrev needs to know if > + ;; this abbrev came from a case preserving table or not, so we > + ;; save a copy in the abbrev itself. > + (abbrev-put sym :case-preserve t)))) > + (if (symbol-value sym) > + sym))) > + > + > +(defun abbrev-expansion (abbrev &optional table) > + "Return the string that ABBREV expands into in the current buffer. > +Optionally specify an abbrev table as second arg; > +then ABBREV is looked up in that table only." > + (symbol-value (abbrev-symbol abbrev table))) > + > +(defun abbrev-before-point-exhaustive-search (table) > + "Sample :abbrev-before-point-function." > + (save-excursion > + (skip-syntax-backward " ") > + (let (res) > + (mapatoms (lambda (sym) > + (when (symbol-value sym) > + (let ((name (symbol-name sym))) > + (when (search-backward name (- (point) (length > name)) t) + (setq res (list name sym > + (match-beginning 0) (match-end > 0))))))) + table) > + res))) > + > +(defun abbrev--before-point () > + "Try and find an abbrev before point. Return it if found, nil > otherwise." + (unless (eq abbrev-start-location-buffer (current-buffer)) > + (setq abbrev-start-location nil)) > + > + (let ((tables (abbrev-active-tables)) > + (pos (point)) > + start end sym) > + > + (if abbrev-start-location > + (progn > + (setq start abbrev-start-location) > + (setq abbrev-start-location nil) > + ;; Remove the hyphen inserted by `abbrev-prefix-mark'. > + (if (and (< start (point-max)) > + (eq (char-after start) ?-)) > + (delete-region start (1+ start))) > + (skip-syntax-backward " ") > + (setq end (point)) > + (setq name (buffer-substring start end)) > + (setq sym (abbrev-symbol name tables)) > + ;; Restore point. > + (goto-char pos)) > + > + (while (and tables (not sym)) > + (let* ((table (pop tables)) > + (fun (abbrev-table-get table > :abbrev-before-point-function)) + (enable-fun > (abbrev-table-get table :enable-function))) + (setq tables (append > (abbrev-table-get table :parents) tables)) + (with-syntax-table > (or (abbrev-table-get table :syntax-table) + > (syntax-table)) > + (if fun > + ;; Use abbrev-before-point-function if provided. > + (let ((res (funcall fun table))) > + (setq name (pop res) sym (pop res) > + start (pop res) end (pop res))) > + (and (or (not enable-fun) (funcall enable-fun)) > + (setq start (and (forward-word -1) (point))) > + (setq end (and (forward-word 1) (min (point) pos))) > + (setq name (buffer-substring start end)) > + ;; This will also look it up in parent tables. This is > + ;; not on purpose, but it seems harmless. > + (setq sym (abbrev-symbol name table))) > + ;; Restore point. > + (goto-char pos)))))) > + (if sym (list name sym start end)))) > + > + > +(defun expand-abbrev () > + "Expand the abbrev before point, if there is an abbrev there. > +Effective when explicitly called even when `abbrev-mode' is nil. > +Returns the abbrev symbol, if expansion took place." > + (interactive) > + (run-hooks 'pre-abbrev-expand-hook) > + > + (destructuring-bind (&optional name sym wordstart wordend) > + (abbrev--before-point) > + (when sym > + (let ((value sym)) > + (unless (or ;; executing-kbd-macro > + noninteractive > + (window-minibuffer-p (selected-window))) > + ;; Add an undo boundary, in case we are doing this for > + ;; a self-inserting command which has avoided making one so far. > + (undo-boundary)) > + ;; Now sym is the abbrev symbol. > + (setq last-abbrev-text name) > + (setq last-abbrev sym) > + (setq last-abbrev-location wordstart) > + ;; Increment use count. > + (abbrev-put sym 'count (1+ (abbrev-get sym 'count))) > + ;; If this abbrev has an expansion, delete the abbrev > + ;; and insert the expansion. > + (when (stringp (symbol-value sym)) > + (goto-char wordend) > + (insert (symbol-value sym)) > + (delete-region wordstart wordend) > + (when (and (not (abbrev-get sym :case-preserve)) > + (string-match "[[:upper:]]" name)) > + (if (not (string-match "[[:lower:]]" name)) > + ;; Abbrev was all caps. If expansion is multiple words, > + ;; normally capitalize each word. > + (if (and (not abbrev-all-caps) > + (save-excursion > + (> (progn (backward-word 1) (point)) > + (progn (goto-char wordstart) > + (forward-word 1) (point))))) > + (upcase-initials-region wordstart (point)) > + (upcase-region wordstart (point))) > + ;; Abbrev included some caps. Cap first initial of > expansion. + (let ((end (point))) > + ;; Find the initial. > + (goto-char wordstart) > + (skip-syntax-forward "^w" (1- end)) > + ;; Change just that. > + (upcase-initials-region (point) (1+ (point))))))) > + (when (symbol-function sym) > + (let* ((hook (symbol-function sym)) > + (expanded > + ;; If the abbrev has a hook function, run it. > + (funcall hook))) > + ;; In addition, if the hook function is a symbol with > + ;; a non-nil `no-self-insert' property, let the value it > + ;; returned specify whether we consider that an expansion took > + ;; place. If it returns nil, no expansion has been done. > + (if (and (symbolp hook) > + (null expanded) > + (get hook 'no-self-insert)) > + (setq value nil)))) > + value)))) > + > +(defun unexpand-abbrev () > + "Undo the expansion of the last abbrev that expanded. > +This differs from ordinary undo in that other editing done since then > +is not undone." > + (interactive) > + (save-excursion > + (unless (or (< last-abbrev-location (point-min)) > + (> last-abbrev-location (point-max))) > + (goto-char last-abbrev-location) > + (when (stringp last-abbrev-text) > + ;; This isn't correct if last-abbrev's hook was used > + ;; to do the expansion. > + (let ((val (symbol-value last-abbrev))) > + (unless (stringp val) > + (error "value of abbrev-symbol must be a string")) > + (delete-region (point) (+ (point) (length val))) > + ;; Don't inherit properties here; just copy from old contents. > + (insert last-abbrev-text) > + (setq last-abbrev-text nil)))))) > + > +(defun write--abbrev (sym) > + (unless (or (null (symbol-value sym)) (abbrev-get sym 'system-flag)) > + (insert " (") > + (prin1 name) > + (insert " ") > + (prin1 (symbol-value sym)) > + (insert " ") > + (prin1 (symbol-function sym)) > + (insert " ") > + (prin1 (abbrev-get sym 'count)) > + (insert ")\n"))) > + > +(defun describe--abbrev (sym) > + (when (symbol-value sym) > + (prin1 (symbol-name sym)) > + (if (null (abbrev-get sym 'system-flag)) > + (indent-to 15 1) > + (insert " (sys)") > + (indent-to 20 1)) > + (prin1 (abbrev-get sym 'count)) > + (indent-to 20 1) > + (prin1 (symbol-value sym)) > + (when (symbol-function sym) > + (indent-to 45 1) > + (prin1 (symbol-function sym))) > + (terpri))) > + > +(defun insert-abbrev-table-description (name &optional readable) > + "Insert before point a full description of abbrev table named NAME. > +NAME is a symbol whose value is an abbrev table. > +If optional 2nd arg READABLE is non-nil, a human-readable description > +is inserted. Otherwise the description is an expression, > +a call to `define-abbrev-table', which would > +define the abbrev table NAME exactly as it is currently defined. > + > +Abbrevs marked as \"system abbrevs\" are omitted." > + (let ((table (symbol-value name)) > + (symbols ())) > + (mapatoms (lambda (sym) (if (symbol-value sym) (push sym symbols))) > table) + (setq symbols (sort symbols 'string-lessp)) > + (let ((standard-output (current-buffer))) > + (if readable > + (progn > + (insert "(") > + (prin1 name) > + (insert ")\n\n") > + (mapc 'describe--abbrev symbols) > + (insert "\n\n")) > + (insert "(define-abbrev-table '") > + (prin1 name) > + (insert " '(") > + (mapc 'write--abbrev symbols) > + (insert " ))\n\n")) > + nil))) > + > +(defun define-abbrev-table (tablename definitions > + &optional docstring &rest props) > + "Define TABLENAME (a symbol) as an abbrev table name. > +Define abbrevs in it according to DEFINITIONS, which is a list of elements > +of the form (ABBREVNAME EXPANSION HOOK USECOUNT SYSTEMFLAG). > +\(If the list is shorter than that, omitted elements default to nil). > +PROPS is a property list to apply to the table. > +Properties with special meaning: > +- :parents contains a list of abbrev tables from which this table inherits > + abbreviations. > +- :case-preserve non-nil means that abbreviations are lookedup without > + case-folding, and the expansion is not capitalized/upcased. > +- :syntax-table holds the syntax table to use for to find the relevant > word. +- :abbrev-before-point-function holds a function to use to find the > + abbrev at point. It should take no argument and return a list of the + > form (NAME SYM START END) where NAME is the abbrev name as found in the + > buffer, SYM is the abbrev, START and END are the buffer positions where + > NAME was found (i.e. NAME = (buffer-substring START END)). It should + > preserve point. > +- :enable-function can be set to a function of no argument which returns > + non-nil iff the abbrevs in this table should be used for this instance > + of `expand-abbrev'." > + (let ((table (if (boundp tablename) (symbol-value tablename)))) > + (unless table > + (setq table (make-abbrev-table props)) > + (set tablename table) > + (push tablename abbrev-table-name-list)) > + (when (stringp docstring) > + (put tablename 'variable-documentation docstring)) > + (dolist (elt definitions) > + (apply 'define-abbrev table elt)))) > + > (provide 'abbrev) > > ;; arch-tag: dbd6f3ae-dfe3-40ba-b00f-f9e3ff960df5 > > > _______________________________________________ > Emacs-devel mailing list > Emacs-devel@gnu.org > http://lists.gnu.org/mailman/listinfo/emacs-devel ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff (was: Abbrev should preserve case) 2007-10-10 21:14 ` Abbrev tables in elisp with some extra stuff (was: Abbrev should preserve case) Stefan Monnier 2007-10-11 19:57 ` Andreas Röhler @ 2007-10-12 15:59 ` Richard Stallman 2007-10-12 21:26 ` Abbrev tables in elisp with some extra stuff Stefan Monnier 1 sibling, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-10-12 15:59 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel - :case-preserve non-nil means that abbreviations are lookedup without case-folding, and the expansion is not capitalized/upcased. It seems like a mistake to make this a per-table decision. In every abbrev table, abbrevs should preserve case by default. To define an abbrev that is only detected in a particular case is an exception, so each abbrev needs to be marked if it is to work that way. - :syntax-table holds the syntax table to use to find the relevant word. Why do we want this? - :abbrev-before-point-function holds a function to use to find the abbrev at point. Why do we want this? - :enable-function can be set to a function of no argument which returns non-nil iff the abbrevs in this table should be used for this instance of `expand-abbrev'. Useful to disable skeleton-abbrevs in strings and comments. That feature is useful, but shouldn't it be per-abbrev, not per-table? If we have some abbrevs that are from skeletons, and some abbrevs that are not, we don't want to have to put them in different abbrev tables. - allow local-abbrev-table to hold a list of abbrev tables so minor modes can add their own abbrev tables as well (useful for mailabbrev.el). The right way to do this is to have minor-mode-abbrev-table-alist which would work like minor-mode-map-alist. +(defvar abbrev-auto-activated-tables t + ;; Could be expanded to be a predicate. + "List of abbrev tables that can be used when `expand-abbrev' is called implicitly. +If t, use all installed tables.") Is this the best way to design the feature so that `mail-abbrevs-only' can use it? Ideally we want some hook function to test `mail-abbrevs-only' and DTRT, so that setting or binding `mail-abbrevs-only' in any fashion has the right effect. That is the case with the current code in mailabbrev.el. I don't want to take a step backwards. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-12 15:59 ` Richard Stallman @ 2007-10-12 21:26 ` Stefan Monnier 2007-10-13 6:41 ` Richard Stallman 2007-10-16 20:26 ` Stefan Monnier 0 siblings, 2 replies; 42+ messages in thread From: Stefan Monnier @ 2007-10-12 21:26 UTC (permalink / raw) To: rms; +Cc: rgm, andreas.roehler, emacs-devel > - :case-preserve non-nil means that abbreviations are lookedup without > case-folding, and the expansion is not capitalized/upcased. > It seems like a mistake to make this a per-table decision. > In every abbrev table, abbrevs should preserve case by default. > To define an abbrev that is only detected in a particular case > is an exception, so each abbrev needs to be marked if it is > to work that way. In my experience, this exception usually holds for groups of abbreviations or even for all abbrevs defined in a mode (typically for skeleton-abbrevs), so it seems convenient to set it once and for all for the whole group. Since we can have many abbrev-tables active at the same time, this is not a limitation. > - :syntax-table holds the syntax table to use to find the relevant word. > Why do we want this? So that we can define abbrevs which include "-" (for example) without changing the syntax of "-" in the normal syntax-table. Currently python.el uses an ugly pre-abbrev-expand-hook to cobble up some way to simulate this feature. mailabbrev.el also needs this. > - :abbrev-before-point-function holds a function to use to find the > abbrev at point. > Why do we want this? Mostly to provide other rules than "abbrev name = preceding word". E.g. there's a sample function in the code I sent to place on this hook which instead of looking at the preceding word just cycles through all the abbrevs in the table and checks if it matches text before point, so as to completely eliminate the "abbrev=word" limitation. Multi-word abbrevs are regularly requested on gnu.emacs.help (usually not very loudly, admittedly). > - :enable-function can be set to a function of no argument which returns > non-nil iff the abbrevs in this table should be used for this instance > of `expand-abbrev'. Useful to disable skeleton-abbrevs in strings and > comments. > That feature is useful, but shouldn't it be per-abbrev, not per-table? Same as :case-preserve, it tends to apply to groups of abbrevs. > If we have some abbrevs that are from skeletons, and some abbrevs that > are not, we don't want to have to put them in different abbrev tables. Why not? We can also combine those tables into a single one via inheritance, if desired. > - allow local-abbrev-table to hold a list of abbrev tables so minor > modes can add their own abbrev tables as well (useful for > mailabbrev.el). > The right way to do this is to have minor-mode-abbrev-table-alist > which would work like minor-mode-map-alist. We could do that as well, but it didn't seem to be much better and makes for more complex processing in expand-abbrev which is likely to be more often executed than enabling/disabling minor modes. As long as the minor mode is enabled/disabled via the function, we can provide the same behavior in either case. > +(defvar abbrev-auto-activated-tables t > + ;; Could be expanded to be a predicate. > + "List of abbrev tables that can be used when `expand-abbrev' is called implicitly. > +If t, use all installed tables.") > Is this the best way to design the feature so that `mail-abbrevs-only' > can use it? Ideally we want some hook function to test > `mail-abbrevs-only' and DTRT, so that setting or binding > `mail-abbrevs-only' in any fashion has the right effect. That is the > case with the current code in mailabbrev.el. I don't want to take > a step backwards. I'm not 100% satisfied with abbrev-auto-activated-tables either indeed. The problem is that the hook function would need to run *around* the expand-abbrev code (the current mailabbrev.el uses an big ugly hack to be able to wrap itself around expand-abbrev). I started writing abbrev-with-wrapper-hook for this purpose, actually, so that might be a better solution. > +(defmacro abbrev-with-wrapper-hook (var &rest body) > + "Run BODY wrapped with the VAR hook. > +VAR is a special hook: its functions are called with one argument which > +is the \"original\" code, so the hook function can wrap the original function, > What does "the original function" mean? There is no function > in the arguments. > I think it means the body. You're right, it means the body (potentially wrapped by other functions on the hook). > + `(labels ((loop (--abrev-funs-- --abbrev-global--) > Redefining `loop' is really confusing. It's only locally (lexically) bound, but I guess you're right that I should use another name. > Using `labels' in this way also requires an explanation > of why it is written this way. It's just so as to use lexical scoping, which is much cleaner than going through the gensym rigmarole. > Did you try to write it in a more straightforward way, without > `labels'? If so, what was the problem with that approach? lexical scoping *is* the straightforward way. > +(defvar abbrev-minor-mode-tables nil > + "List of additional abbrev tables.") > "Additional" does not explain clearly the difference > between these and others. This variable is a remnant from before local-abbrev-table could hold a list of tables. I'd remove it (or replace it with something else). > +(defcustom abbrev-all-caps nil > + "Set non-nil means expand multi-word abbrevs all caps if abbrev was so." > Please delete "Set". Sure (this docstring is copied straight from abbrev.c ;-) > + (dolist (table (prog1 tables (setq tables nil)) tables) > + (if (abbrev-set-member table abbrev-auto-activated-tables) > + (push table tables)))))))) > That always returns nil. No. But if this code confuses you, I'd better rewrite it as its equivalent (dolist (table (prog1 tables (setq tables nil))) (if (abbrev-set-member table abbrev-auto-activated-tables) (push table tables))) tables))))) > +(defun write--abbrev (sym) > This name should start with `internal-'. > +(defun describe--abbrev (sym) > Likewise. I like the "--" convention used in a few other packages, mostly because it still sticks to the normal prefix. But if you insist, I can use the longer and prefix-less-clean "internal-" thingy. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-12 21:26 ` Abbrev tables in elisp with some extra stuff Stefan Monnier @ 2007-10-13 6:41 ` Richard Stallman 2007-10-14 17:45 ` Andreas Röhler 2007-10-16 20:26 ` Stefan Monnier 1 sibling, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-10-13 6:41 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel In my experience, this exception usually holds for groups of abbreviations or even for all abbrevs defined in a mode (typically for skeleton-abbrevs), so it seems convenient to set it once and for all for the whole group. The simple, natural, and general interface is to specify this per abbrev. Please provide and document that method. Likewise for :enable-function. > If we have some abbrevs that are from skeletons, and some abbrevs that > are not, we don't want to have to put them in different abbrev tables. Why not? Because you should not have to know that a certain mode uses two abbrev tables and choose the right one. > The right way to do this is to have minor-mode-abbrev-table-alist > which would work like minor-mode-map-alist. We could do that as well, but it didn't seem to be much better and makes for more complex processing in expand-abbrev which is likely to be more often executed than enabling/disabling minor modes. As long as the minor mode is enabled/disabled via the function, we can provide the same behavior in either case. That is precisely what I want to avoid. It is unclean that minor modes have to be turned on or off by a function. Would you please implement minor-mode-abbrev-table-alist? > Using `labels' in this way also requires an explanation > of why it is written this way. It's just so as to use lexical scoping, which is much cleaner than going through the gensym rigmarole. > Did you try to write it in a more straightforward way, without > `labels'? If so, what was the problem with that approach? lexical scoping *is* the straightforward way. I had to struggle to understand it. Please see if this can be implemented straightforwardly in another way, without `labels'. > +(defun write--abbrev (sym) > This name should start with `internal-'. > +(defun describe--abbrev (sym) > Likewise. I like the "--" convention used in a few other packages, mostly because it still sticks to the normal prefix. Which packages do you have in mind? ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-13 6:41 ` Richard Stallman @ 2007-10-14 17:45 ` Andreas Röhler 2007-10-15 16:04 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-10-14 17:45 UTC (permalink / raw) To: rms; +Cc: Stefan Monnier, emacs-devel It was a request at http://lists.gnu.org/archive/html/help-gnu-emacs/2007-09/msg00438.html which I consider worthwhile to reflect in this context. XEmacs already is able to expand not only words but phrases. Thus you may have a phrase in one language which expands to another phrase in an other language. As XEmacs already does it, why not adapt the code from there? BTW: when doing translations it might be of interest to expand the word/phrase after point instead before. Thanks all Andreas Röhler ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-14 17:45 ` Andreas Röhler @ 2007-10-15 16:04 ` Richard Stallman 2007-10-15 18:04 ` Andreas Röhler 0 siblings, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-10-15 16:04 UTC (permalink / raw) To: Andreas Röhler; +Cc: monnier, emacs-devel BTW: when doing translations it might be of interest to expand the word/phrase after point instead before. I would rather not add a feature solely for the goal of maximum generality. So let's leave this out. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-15 16:04 ` Richard Stallman @ 2007-10-15 18:04 ` Andreas Röhler 2007-10-16 4:10 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-10-15 18:04 UTC (permalink / raw) To: rms; +Cc: Stefan Monnier, emacs-devel Am Montag, 15. Oktober 2007 18:04 schrieb Richard Stallman: > BTW: when doing translations it might be of interest to > expand the word/phrase after point instead before. > > I would rather not add a feature solely for the goal of maximum generality. > So let's leave this out. > It's not for the sake of generalisation. Below a first draft and example --with some explanation for the less experienced--: Put these definitions at the right place in your .abbrev_defs, M-x read-abbrev-file, evaluate the function, put the cursor at the beginning of the example sentence below: The first call of `expand-abbrev-after-point' should deliver "No expansion available" - because no translation/abbreviation was defined. But the cursor moved and with repeated calls that sentence is translated word by word. ("is" "ist" nil 0) ("the" "der" nil 0) ("extensible" "erweiterbare" nil 0) ("customizable" "einrichtbare" nil 0) ("self-documenting" "selbsterklärende" nil 0) ("real-time" "Echtzeit" nil 0) ("display" "Display" nil 0) ("editor" "Editor" nil 0) (defun expand-abbrev-after-point () "Expand abbrev after point if any. " (interactive "*") (forward-word 1) (or (expand-abbrev) (message "%s" "No translation available"))) Emacs is the extensible, customizable, self-documenting real-time display editor. Grüße Andreas Röhler ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-15 18:04 ` Andreas Röhler @ 2007-10-16 4:10 ` Richard Stallman 0 siblings, 0 replies; 42+ messages in thread From: Richard Stallman @ 2007-10-16 4:10 UTC (permalink / raw) To: Andreas Röhler; +Cc: monnier, emacs-devel > BTW: when doing translations it might be of interest to > expand the word/phrase after point instead before. > > I would rather not add a feature solely for the goal of maximum generality. > So let's leave this out. It seems to me that if you want to do something like this you can implement the mechanism easily enough on your own. But you wouldn't have to implement it yourself, because you could do this by expanding the word before point. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-12 21:26 ` Abbrev tables in elisp with some extra stuff Stefan Monnier 2007-10-13 6:41 ` Richard Stallman @ 2007-10-16 20:26 ` Stefan Monnier 2007-10-17 5:03 ` Richard Stallman ` (2 more replies) 1 sibling, 3 replies; 42+ messages in thread From: Stefan Monnier @ 2007-10-16 20:26 UTC (permalink / raw) To: rms; +Cc: rgm, andreas.roehler, emacs-devel >> - :case-preserve non-nil means that abbreviations are lookedup without >> case-folding, and the expansion is not capitalized/upcased. >> It seems like a mistake to make this a per-table decision. >> In every abbrev table, abbrevs should preserve case by default. >> To define an abbrev that is only detected in a particular case >> is an exception, so each abbrev needs to be marked if it is >> to work that way. > In my experience, this exception usually holds for groups of abbreviations > or even for all abbrevs defined in a mode (typically for skeleton-abbrevs), > so it seems convenient to set it once and for all for the whole group. > Since we can have many abbrev-tables active at the same time, this is not > a limitation. BTW, the code actually handles :case-preserve both on individual abbrevs and on abbrev-tables. >> - :syntax-table holds the syntax table to use to find the relevant word. >> Why do we want this? > So that we can define abbrevs which include "-" (for example) without > changing the syntax of "-" in the normal syntax-table. Currently python.el > uses an ugly pre-abbrev-expand-hook to cobble up some way to simulate > this feature. mailabbrev.el also needs this. I think I've changed my mind on this: instead of :syntax-table, I want to use :regexp where I can specify that my abbrevs can match anything else than a word. This works as well for python.el and mailabbrev.el and is more general. >> - :abbrev-before-point-function holds a function to use to find the >> abbrev at point. >> Why do we want this? > Mostly to provide other rules than "abbrev name = preceding word". > E.g. there's a sample function in the code I sent to place on this hook > which instead of looking at the preceding word just cycles through all the > abbrevs in the table and checks if it matches text before point, so as to > completely eliminate the "abbrev=word" limitation. Multi-word abbrevs are > regularly requested on gnu.emacs.help (usually not very loudly, admittedly). Now that we have :regexp, I'm not sure it's worth the trouble. >> - :enable-function can be set to a function of no argument which returns >> non-nil iff the abbrevs in this table should be used for this instance >> of `expand-abbrev'. Useful to disable skeleton-abbrevs in strings and >> comments. >> That feature is useful, but shouldn't it be per-abbrev, not per-table? > Same as :case-preserve, it tends to apply to groups of abbrevs. Actually, it's different from :case-preserve because its used earlier (before we've3 have found the abbrev): it's important that it be global to an abbrev-table because it determines whether or not to look for a word (i.e. it determines whether or not to search :regexp or to call :abbrev-before-point-function). Being called early also makes it possible to use it to refresh :regexp (in case you want to let :regexp adapt dynamically). I can easily add support for :enable-function to individual abbrevs if it is considered important, but several uses I can think of need it before we've found the abbrev, so abbrev-tables need them also. >> The right way to do this is to have minor-mode-abbrev-table-alist >> which would work like minor-mode-map-alist. Done. >> +(defmacro abbrev-with-wrapper-hook (var &rest body) >> + "Run BODY wrapped with the VAR hook. >> +VAR is a special hook: its functions are called with one argument which >> +is the \"original\" code, so the hook function can wrap the original function, >> What does "the original function" mean? There is no function >> in the arguments. I think it means the body. I've tried to improve the docstring. >> + `(labels ((loop (--abrev-funs-- --abbrev-global--) >> Redefining `loop' is really confusing. I've renamed it. >> Using `labels' in this way also requires an explanation >> of why it is written this way. I've added comments to try and make things more clear. >> Did you try to write it in a more straightforward way, without >> `labels'? If so, what was the problem with that approach? > lexical scoping *is* the straightforward way. Mostly, I need to create closures (the function arg passed to the hook functions) and the cleanest way to do that is via lexical-let (short of using Miles's lexical scoping branch that is ;-) If CL supported lexically scoped function arguments, the code would be even significantly cleaner. I attached the new version of the code. Stefan --- orig/lisp/abbrev.el +++ mod/lisp/abbrev.el @@ -363,6 +363,534 @@ (if (or noquery (y-or-n-p (format "Expand `%s'? " string))) (expand-abbrev))))))) +;;; Abbrev properties. + + +;; Todo: +;; - abbrev table may be chosen based on context (e.g. mail-abbrev uses an +;; ugly pre-abbrev-expand-hook in order to do abbrev-expansion in the +;; header differently from abbrev expansion in the rest of the message). +;; - multi-word or non-word abbrevs? +;; - abbrevs could have a `predicate' so you can disable them in strings and +;; comments, for example. Maybe the predicate should be on the table +;; rather than on individual abbrevs. This may be enough to cover the +;; above request for context-dependent abbrevs. + +(defun abbrev-table-get (table prop) + "Get the PROP property of abbrev table TABLE." + (let ((sym (intern-soft "" table))) + (if sym (get sym prop)))) + +(defun abbrev-table-put (table prop val) + "Set the PROP property of abbrev table TABLE to VAL." + (let ((sym (intern "" table))) + (set sym nil) ; Make sure it won't be confused for an abbrev. + (put sym prop val))) + +(defun abbrev-get (sym prop) + (let ((plist (symbol-plist sym))) + (if (listp plist) + (plist-get plist prop) + (if (eq 'count prop) plist)))) + +(defun abbrev-put (sym prop val) + (let ((plist (symbol-plist sym))) + (if (consp plist) + (put sym prop val) + (setplist sym (if (eq 'count prop) val + (list 'count plist prop val)))))) + +(defmacro abbrev-with-wrapper-hook (var &rest body) + "Run BODY wrapped with the VAR hook. +VAR is a special hook: its functions are called with one argument which +is the \"original\" code (the BODY), so the hook function can wrap the +original function, can call it several times, or even not call it at all. +VAR is normally a symbol (a variable) in which case it is treated like a hook, +with a buffer-local and a global part. But it can also be an arbitrary expression. +This is similar to an `around' advice." + (declare (indent 1) (debug t)) + ;; We need those two gensyms because CL's lexical scoping is not available + ;; for function arguments :-( + (let ((funs (make-symbol "funs")) + (global (make-symbol "global"))) + ;; The loop has to be done via recursion rather than a `while'. + `(labels ((runrestofhook (,funs ,global) + ;; `funs' holds the functions left on the hook and `global' + ;; holds the functions left on the global part of the hook + ;; (in case the hook is local). + (lexical-let ((funs ,funs) + (global ,global)) + (if (consp funs) + (if (eq t (car funs)) + (runrestofhook (append global (cdr funs)) nil) + (funcall (car funs) + (lambda () (runrestofhook (cdr funs) global)))) + ;; Once there are no more functions on the hook, run + ;; the original body. + ,@body)))) + (runrestofhook ,var + ;; The global part of the hook, if any. + ,(if (symbolp var) + `(if (local-variable-p ',var) + (default-value ',var))))))) + + +;;; Code that used to be implemented in src/abbrev.c + +(defvar abbrev-table-name-list '(fundamental-mode-abbrev-table + global-abbrev-table) + "List of symbols whose values are abbrev tables.") + +(defun make-abbrev-table (&optional props) + "Create a new, empty abbrev table object." + ;; The value 59 is an arbitrary prime number. + (let ((table (make-vector 59 0))) + ;; Each abbrev-table has a `modiff' counter which can be used to detect + ;; when an abbreviation was added. An example of use would be to + ;; construct :regexp dynamically as the union of all abbrev names, so + ;; `modiff' can let us detect that an abbrev was added and hence :regexp + ;; needs to be refreshed. + ;; The presence of `modiff' entry is also used as a tag indicating this + ;; vector is really an abbrev-table. + (abbrev-table-put table :abbrev-table-modiff 0) + (while (consp props) + (abbrev-table-put table (pop props) (pop props))) + table)) + +(defun abbrev-table-p (object) + (and (vectorp object) + (numberp (abbrev-table-get object :abbrev-table-modiff)))) + +(defvar global-abbrev-table (make-abbrev-table) + "The abbrev table whose abbrevs affect all buffers. +Each buffer may also have a local abbrev table. +If it does, the local table overrides the global one +for any particular abbrev defined in both.") + +(defvar abbrev-minor-mode-table-alist nil + "Alist of abbrev tables to use for minor modes. +Each element looks like (VARIABLE . ABBREV-TABLE); +ABBREV-TABLE is active whenever VARIABLE's value is non-nil.") + +(defvar fundamental-mode-abbrev-table + (let ((table (make-abbrev-table))) + ;; Set local-abbrev-table's default to be fundamental-mode-abbrev-table. + (setq-default local-abbrev-table table) + table) + "The abbrev table of mode-specific abbrevs for Fundamental Mode.") + +(defvar abbrevs-changed nil + "Set non-nil by defining or altering any word abbrevs. +This causes `save-some-buffers' to offer to save the abbrevs.") + +(defcustom abbrev-all-caps nil + "Non-nil means expand multi-word abbrevs all caps if abbrev was so." + :type 'boolean + :group 'abbrev-mode) + +(defvar abbrev-start-location nil + "Buffer position for `expand-abbrev' to use as the start of the abbrev. +When nil, use the word before point as the abbrev. +Calling `expand-abbrev' sets this to nil.") + +(defvar abbrev-start-location-buffer nil + "Buffer that `abbrev-start-location' has been set for. +Trying to expand an abbrev in any other buffer clears `abbrev-start-location'.") + +(defvar last-abbrev nil + "The abbrev-symbol of the last abbrev expanded. See `abbrev-symbol'.") + +(defvar last-abbrev-text nil + "The exact text of the last abbrev expanded. +nil if the abbrev has already been unexpanded.") + +(defvar last-abbrev-location 0 + "The location of the start of the last abbrev expanded.") + +;; (defvar local-abbrev-table fundamental-mode-abbrev-table +;; "Local (mode-specific) abbrev table of current buffer.") +;; (make-variable-buffer-local 'local-abbrev-table) + +(defcustom pre-abbrev-expand-hook nil + "Function or functions to be called before abbrev expansion is done. +This is the first thing that `expand-abbrev' does, and so this may change +the current abbrev table before abbrev lookup happens." + :type 'hook + :group 'abbrev-mode) + +(defun clear-abbrev-table (table) + "Undefine all abbrevs in abbrev table TABLE, leaving it empty." + (setq abbrevs-changed t) + (dotimes (i (length table)) + (aset table i 0))) + +(defun define-abbrev (table name expansion &optional hook count system-flag + ;; In case the abbrev list passed to + ;; `define-abbrev-table' includes extra elements + ;; that we should ignore. + &rest ignore) + "Define an abbrev in TABLE named NAME, to expand to EXPANSION and call HOOK. +NAME must be a string, and should be lower-case. +EXPANSION should usually be a string. +To undefine an abbrev, define it with EXPANSION = nil. +If HOOK is non-nil, it should be a function of no arguments; +it is called after EXPANSION is inserted. +If EXPANSION is not a string, the abbrev is a special one, + which does not expand in the usual way but only runs HOOK. + +COUNT, if specified, gives the initial value for the abbrev's +usage-count, which is incremented each time the abbrev is used. +\(The default is zero.) + +SYSTEM-FLAG, if non-nil, says that this is a \"system\" abbreviation +which should not be saved in the user's abbreviation file. +Unless SYSTEM-FLAG is `force', a system abbreviation will not +overwrite a non-system abbreviation of the same name." + (unless count (setq count 0)) + (let ((sym (intern name table))) + ;; Don't override a prior user-defined abbrev with a system abbrev, + ;; unless system-flag is `force'. + (unless (and (not (memq system-flag '(nil force))) + (boundp sym) (symbol-value sym) + (not (abbrev-get sym 'system-flag))) + (unless (or system-flag + (and (boundp sym) (fboundp sym) + ;; load-file-name + (equal (symbol-value sym) expansion) + (equal (symbol-function sym) hook))) + (setq abbrevs-changed t)) + (set sym expansion) + (fset sym hook) + (setplist sym (if (null system-flag) count + (list 'count count 'system-flag system-flag))) + (abbrev-table-put table :abbrev-table-modiff + (1+ (abbrev-table-get table :abbrev-table-modiff)))) + name)) + +(defun abbrev--check-chars (abbrev global) + "Check if the characters in ABBREV have word syntax in either the +current (if global is nil) or standard syntax table." + (with-syntax-table + (cond ((null global) (standard-syntax-table)) + ;; ((syntax-table-p global) global) + (t (syntax-table))) + (when (string-match "\\W" abbrev) + (let ((badchars ()) + (pos 0)) + (while (string-match "\\W" abbrev pos) + (pushnew (aref abbrev (match-beginning 0)) badchars) + (setq pos (1+ pos))) + (error "Some abbrev characters (%s) are not word constituents %s" + (apply 'string (nreverse badchars)) + (if global "in the standard syntax" "in this mode")))))) + +(defun define-global-abbrev (abbrev expansion) + "Define ABBREV as a global abbreviation for EXPANSION. +The characters in ABBREV must all be word constituents in the standard +syntax table." + (interactive "sDefine global abbrev: \nsExpansion for %s: ") + (abbrev--check-chars abbrev 'global) + (define-abbrev global-abbrev-table (downcase abbrev) expansion)) + +(defun define-mode-abbrev (abbrev expansion) + "Define ABBREV as a mode-specific abbreviation for EXPANSION. +The characters in ABBREV must all be word-constituents in the current mode." + (interactive "sDefine mode abbrev: \nsExpansion for %s: ") + (unless local-abbrev-table + (error "Major mode has no abbrev table")) + (abbrev--check-chars abbrev nil) + (define-abbrev local-abbrev-table (downcase abbrev) expansion)) + +(defun abbrev-set-member (elem set) + (cond + ((functionp set) (funcall set elem)) + ((eq (car-safe set) 'not) (not (abbrev-set-member elem (cadr set)))) + (t (member elem set)))) + +(defun abbrev--active-tables (&optional tables) + (cond + ((consp tables) tables) + ((vectorp tables) (list tables)) + (t + (let ((tables (if (listp local-abbrev-table) + (append local-abbrev-table + (list global-abbrev-table)) + (list local-abbrev-table global-abbrev-table)))) + ;; Add the minor-mode abbrev tables. + (dolist (x abbrev-minor-mode-table-alist) + (when (and (symbolp (car x)) (boundp (car x)) (symbol-value (car x))) + (setq tables + (if (listp (cdr x)) + (append (cdr x) tables) (cons (cdr x) tables))))) + tables)))) + + +(defun abbrev-symbol (abbrev &optional table) + "Return the symbol representing abbrev named ABBREV. +This symbol's name is ABBREV, but it is not the canonical symbol of that name; +it is interned in an abbrev-table rather than the normal obarray. +The value is nil if that abbrev is not defined. +Optional second arg TABLE is abbrev table to look it up in. +The default is to try buffer's mode-specific abbrev table, then global table." + (let ((tables (abbrev--active-tables table)) + sym) + (while (and tables (not (symbol-value sym))) + (let ((table (pop tables)) + (case-fold (not (abbrev-table-get table :case-preserve)))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (setq sym (intern-soft (if case-fold (downcase abbrev) abbrev) table)) + (if (and (not case-fold) (symbol-value sym)) + ;; The :case-preserve property normally belongs to the + ;; abbrev-table, but the use of this abbrev needs to know if + ;; this abbrev came from a case preserving table or not, so we + ;; save a copy in the abbrev itself. + (abbrev-put sym :case-preserve t)))) + (if (symbol-value sym) + sym))) + + +(defun abbrev-expansion (abbrev &optional table) + "Return the string that ABBREV expands into in the current buffer. +Optionally specify an abbrev table as second arg; +then ABBREV is looked up in that table only." + (symbol-value (abbrev-symbol abbrev table))) + +(defun abbrev-before-point-exhaustive-search (table) + "Sample :abbrev-before-point-function." + (save-excursion + (skip-syntax-backward " ") + (let (res) + (mapatoms (lambda (sym) + (when (symbol-value sym) + (let ((name (symbol-name sym))) + (when (search-backward name (- (point) (length name)) t) + (setq res (list name sym + (match-beginning 0) (match-end 0))))))) + table) + res))) + +(defun abbrev--before-point () + "Try and find an abbrev before point. Return it if found, nil otherwise." + (unless (eq abbrev-start-location-buffer (current-buffer)) + (setq abbrev-start-location nil)) + + (let ((tables (abbrev--active-tables)) + (pos (point)) + start end name res) + + (if abbrev-start-location + (progn + (setq start abbrev-start-location) + (setq abbrev-start-location nil) + ;; Remove the hyphen inserted by `abbrev-prefix-mark'. + (if (and (< start (point-max)) + (eq (char-after start) ?-)) + (delete-region start (1+ start))) + (skip-syntax-backward " ") + (setq end (point)) + (setq name (buffer-substring start end)) + (goto-char pos) ; Restore point. + (list name (abbrev-symbol name tables) start end)) + + (while (and tables (not res)) + (let* ((table (pop tables)) + (enable-fun (abbrev-table-get table :enable-function))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (setq res + (abbrev-with-wrapper-hook + (abbrev-table-get table :abbrev-before-point-function) + (and (or (not enable-fun) (funcall enable-fun)) + (looking-back (or (abbrev-table-get table :regexp) + "\\<\\(\\w+\\)\\W*") + (line-beginning-position)) + (setq start (match-beginning 1)) + (setq end (match-end 1)) + (setq name (buffer-substring start end)) + ;; This will also look it up in parent tables. + ;; This is not on purpose, but it seems harmless. + (list name (abbrev-symbol name table) start end)))) + ;; Restore point. + (goto-char pos))) + res))) + +(defvar abbrev-expand-function nil + "Wrapper hook around `expand-abbrev'. +The functions on this special hook are called with one argument: +a function that performs the abbrev expansion.") + +(defun expand-abbrev () + "Expand the abbrev before point, if there is an abbrev there. +Effective when explicitly called even when `abbrev-mode' is nil. +Returns the abbrev symbol, if expansion took place." + (interactive) + (run-hooks 'pre-abbrev-expand-hook) + (abbrev-with-wrapper-hook abbrev-expand-function + (destructuring-bind (&optional name sym wordstart wordend) + (abbrev--before-point) + (when sym + (let ((value sym)) + (unless (or ;; executing-kbd-macro + noninteractive + (window-minibuffer-p (selected-window))) + ;; Add an undo boundary, in case we are doing this for + ;; a self-inserting command which has avoided making one so far. + (undo-boundary)) + ;; Now sym is the abbrev symbol. + (setq last-abbrev-text name) + (setq last-abbrev sym) + (setq last-abbrev-location wordstart) + ;; Increment use count. + (abbrev-put sym 'count (1+ (abbrev-get sym 'count))) + ;; If this abbrev has an expansion, delete the abbrev + ;; and insert the expansion. + (when (stringp (symbol-value sym)) + (goto-char wordend) + (insert (symbol-value sym)) + (delete-region wordstart wordend) + (let ((case-fold-search nil)) + (when (and (not (abbrev-get sym :case-preserve)) + (string-match "[[:upper:]]" name)) + (if (not (string-match "[[:lower:]]" name)) + ;; Abbrev was all caps. If expansion is multiple words, + ;; normally capitalize each word. + (if (and (not abbrev-all-caps) + (save-excursion + (> (progn (backward-word 1) (point)) + (progn (goto-char wordstart) + (forward-word 1) (point))))) + (upcase-initials-region wordstart (point)) + (upcase-region wordstart (point))) + ;; Abbrev included some caps. Cap first initial of expansion. + (let ((end (point))) + ;; Find the initial. + (goto-char wordstart) + (skip-syntax-forward "^w" (1- end)) + ;; Change just that. + (upcase-initials-region (point) (1+ (point)))))))) + (when (symbol-function sym) + (let* ((hook (symbol-function sym)) + (expanded + ;; If the abbrev has a hook function, run it. + (funcall hook))) + ;; In addition, if the hook function is a symbol with + ;; a non-nil `no-self-insert' property, let the value it + ;; returned specify whether we consider that an expansion took + ;; place. If it returns nil, no expansion has been done. + (if (and (symbolp hook) + (null expanded) + (get hook 'no-self-insert)) + (setq value nil)))) + value))))) + +(defun unexpand-abbrev () + "Undo the expansion of the last abbrev that expanded. +This differs from ordinary undo in that other editing done since then +is not undone." + (interactive) + (save-excursion + (unless (or (< last-abbrev-location (point-min)) + (> last-abbrev-location (point-max))) + (goto-char last-abbrev-location) + (when (stringp last-abbrev-text) + ;; This isn't correct if last-abbrev's hook was used + ;; to do the expansion. + (let ((val (symbol-value last-abbrev))) + (unless (stringp val) + (error "value of abbrev-symbol must be a string")) + (delete-region (point) (+ (point) (length val))) + ;; Don't inherit properties here; just copy from old contents. + (insert last-abbrev-text) + (setq last-abbrev-text nil)))))) + +(defun write--abbrev (sym) + (unless (or (null (symbol-value sym)) (abbrev-get sym 'system-flag)) + (insert " (") + (prin1 name) + (insert " ") + (prin1 (symbol-value sym)) + (insert " ") + (prin1 (symbol-function sym)) + (insert " ") + (prin1 (abbrev-get sym 'count)) + (insert ")\n"))) + +(defun describe--abbrev (sym) + (when (symbol-value sym) + (prin1 (symbol-name sym)) + (if (null (abbrev-get sym 'system-flag)) + (indent-to 15 1) + (insert " (sys)") + (indent-to 20 1)) + (prin1 (abbrev-get sym 'count)) + (indent-to 20 1) + (prin1 (symbol-value sym)) + (when (symbol-function sym) + (indent-to 45 1) + (prin1 (symbol-function sym))) + (terpri))) + +(defun insert-abbrev-table-description (name &optional readable) + "Insert before point a full description of abbrev table named NAME. +NAME is a symbol whose value is an abbrev table. +If optional 2nd arg READABLE is non-nil, a human-readable description +is inserted. Otherwise the description is an expression, +a call to `define-abbrev-table', which would +define the abbrev table NAME exactly as it is currently defined. + +Abbrevs marked as \"system abbrevs\" are omitted." + (let ((table (symbol-value name)) + (symbols ())) + (mapatoms (lambda (sym) (if (symbol-value sym) (push sym symbols))) table) + (setq symbols (sort symbols 'string-lessp)) + (let ((standard-output (current-buffer))) + (if readable + (progn + (insert "(") + (prin1 name) + (insert ")\n\n") + (mapc 'describe--abbrev symbols) + (insert "\n\n")) + (insert "(define-abbrev-table '") + (prin1 name) + (insert " '(") + (mapc 'write--abbrev symbols) + (insert " ))\n\n")) + nil))) + +(defun define-abbrev-table (tablename definitions + &optional docstring &rest props) + "Define TABLENAME (a symbol) as an abbrev table name. +Define abbrevs in it according to DEFINITIONS, which is a list of elements +of the form (ABBREVNAME EXPANSION HOOK USECOUNT SYSTEMFLAG). +\(If the list is shorter than that, omitted elements default to nil). +PROPS is a property list to apply to the table. +Properties with special meaning: +- :parents contains a list of abbrev tables from which this table inherits + abbreviations. +- :case-preserve non-nil means that abbreviations are lookedup without + case-folding, and the expansion is not capitalized/upcased. +- :regexp describes the form of abbrevs. It defaults to \\<\\(\\w+\\)\\W* which + means that an abbrev can only be a single word. The submatch 1 is treated + as the potential name of an abbrev. +- :abbrev-before-point-function holds a function to use to find the + abbrev at point. It should take one argument (a function of no argument + which finds the abbrev using the default method) and return a list of the + form (NAME SYM START END) where NAME is the abbrev name as found in the + buffer, SYM is the abbrev, START and END are the buffer positions where + NAME was found (i.e. NAME = (buffer-substring START END)). +- :enable-function can be set to a function of no argument which returns + non-nil iff the abbrevs in this table should be used for this instance + of `expand-abbrev'." + (let ((table (if (boundp tablename) (symbol-value tablename)))) + (unless table + (setq table (make-abbrev-table props)) + (set tablename table) + (push tablename abbrev-table-name-list)) + (when (stringp docstring) + (put tablename 'variable-documentation docstring)) + (dolist (elt definitions) + (apply 'define-abbrev table elt)))) + (provide 'abbrev) ;; arch-tag: dbd6f3ae-dfe3-40ba-b00f-f9e3ff960df5 ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-16 20:26 ` Stefan Monnier @ 2007-10-17 5:03 ` Richard Stallman 2007-10-17 14:10 ` Richard Stallman 2007-10-17 20:48 ` Richard Stallman 2 siblings, 0 replies; 42+ messages in thread From: Richard Stallman @ 2007-10-17 5:03 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel BTW, the code actually handles :case-preserve both on individual abbrevs and on abbrev-tables. In that case, let's just make sure to document that it does. I think I've changed my mind on this: instead of :syntax-table, I want to use :regexp where I can specify that my abbrevs can match anything else than a word. This works as well for python.el and mailabbrev.el and is more general. I don't object. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-16 20:26 ` Stefan Monnier 2007-10-17 5:03 ` Richard Stallman @ 2007-10-17 14:10 ` Richard Stallman 2007-10-17 20:48 ` Richard Stallman 2 siblings, 0 replies; 42+ messages in thread From: Richard Stallman @ 2007-10-17 14:10 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel >> - :abbrev-before-point-function holds a function to use to find the >> abbrev at point. Now that we have :regexp, I'm not sure it's worth the trouble. That is good. >> - :enable-function can be set to a function of no argument which returns >> non-nil iff the abbrevs in this table should be used for this instance >> of `expand-abbrev'. Useful to disable skeleton-abbrevs in strings and >> comments. Actually, it's different from :case-preserve because its used earlier (before we've3 have found the abbrev): it's important that it be global to an abbrev-table because it determines whether or not to look for a word It is no disaster to look for a word, find it, see it is disabled, and not expand it. So please make this work for individual abbrevs even if it is not as efficient. Abbrev expansion will still be far faster than humans can type. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-16 20:26 ` Stefan Monnier 2007-10-17 5:03 ` Richard Stallman 2007-10-17 14:10 ` Richard Stallman @ 2007-10-17 20:48 ` Richard Stallman 2007-10-24 2:21 ` Stefan Monnier 2 siblings, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-10-17 20:48 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel +;; - abbrevs could have a `predicate' so you can disable them in strings and +;; comments, for example. Maybe the predicate should be on the table +;; rather than on individual abbrevs. This may be enough to cover the +;; above request for context-dependent abbrevs. If you implement that on individual abbrevs and on tables, it will do both jobs. +(defun abbrev-get (sym prop) + (let ((plist (symbol-plist sym))) + (if (listp plist) + (plist-get plist prop) + (if (eq 'count prop) plist)))) That needs a doc string, and the doc string needs to explain the various types of arguments that are allowed. + ;; The loop has to be done via recursion rather than a `while'. Please add an explanation of why. +(defun abbrev-set-member (elem set) That needs a doc string, and it should fully explain what SET can look like. +(defun abbrev--active-tables (&optional tables) That needs a doc string. It should state the purpose of having the argument TABLES. Why not just write (or tables (abbrev--active-tables))? (I don't mind using `abbrev--' to start a function name, because it has `abbrev' at the beginning.) +(defun write--abbrev (sym) I do dislike `--' here since it doesn't start with `abbrev'. Also please give this a doc string. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-17 20:48 ` Richard Stallman @ 2007-10-24 2:21 ` Stefan Monnier 2007-10-25 2:10 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2007-10-24 2:21 UTC (permalink / raw) To: rms; +Cc: rgm, andreas.roehler, emacs-devel > +;; - abbrevs could have a `predicate' so you can disable them in strings and > +;; comments, for example. Maybe the predicate should be on the table > +;; rather than on individual abbrevs. This may be enough to cover the > +;; above request for context-dependent abbrevs. > If you implement that on individual abbrevs and on tables, > it will do both jobs. Indeed. I've removed this todo item. > +(defun abbrev-get (sym prop) > + (let ((plist (symbol-plist sym))) > + (if (listp plist) > + (plist-get plist prop) > + (if (eq 'count prop) plist)))) > That needs a doc string, and the doc string needs > to explain the various types of arguments that are allowed. Added. > + ;; The loop has to be done via recursion rather than a `while'. > Please add an explanation of why. I tried. But really, it's pretty obvious if you try to write it. > +(defun abbrev-set-member (elem set) > That needs a doc string, and it should fully explain > what SET can look like. This function wasn't used, I removed it. > +(defun abbrev--active-tables (&optional tables) > That needs a doc string. > It should state the purpose of having the argument TABLES. > Why not just write (or tables (abbrev--active-tables))? Done. > (I don't mind using `abbrev--' to start a function name, > because it has `abbrev' at the beginning.) > +(defun write--abbrev (sym) > I do dislike `--' here since it doesn't start with `abbrev'. Duh, indeed. There was also describe--abbrev. I renamed both. Here is a the resulting version. Stefan --- orig/lisp/abbrev.el +++ mod/lisp/abbrev.el @@ -363,6 +363,510 @@ (if (or noquery (y-or-n-p (format "Expand `%s'? " string))) (expand-abbrev))))))) +;;; Abbrev properties. + +(defun abbrev-table-get (table prop) + "Get the PROP property of abbrev table TABLE." + (let ((sym (intern-soft "" table))) + (if sym (get sym prop)))) + +(defun abbrev-table-put (table prop val) + "Set the PROP property of abbrev table TABLE to VAL." + (let ((sym (intern "" table))) + (set sym nil) ; Make sure it won't be confused for an abbrev. + (put sym prop val))) + +(defun abbrev-get (sym prop) + "Get the property PROP of abbrev SYM." + (let ((plist (symbol-plist sym))) + (if (listp plist) + (plist-get plist prop) + (if (eq 'count prop) plist)))) + +(defun abbrev-put (sym prop val) + "Set the property PROP of abbrev SYM to value VAL. +See `define-abbrev' for the effect of some special properties." + (let ((plist (symbol-plist sym))) + (if (consp plist) + (put sym prop val) + (setplist sym (if (eq 'count prop) val + (list 'count plist prop val)))))) + +(defmacro abbrev-with-wrapper-hook (var &rest body) + "Run BODY wrapped with the VAR hook. +VAR is a special hook: its functions are called with one argument which +is the \"original\" code (the BODY), so the hook function can wrap the +original function, can call it several times, or even not call it at all. +VAR is normally a symbol (a variable) in which case it is treated like a hook, +with a buffer-local and a global part. But it can also be an arbitrary expression. +This is similar to an `around' advice." + (declare (indent 1) (debug t)) + ;; We need those two gensyms because CL's lexical scoping is not available + ;; for function arguments :-( + (let ((funs (make-symbol "funs")) + (global (make-symbol "global"))) + ;; Since the hook is a wrapper, the loop has to be done via + ;; recursion: a given hook function will call its parameter in order to + ;; continue looping. + `(labels ((runrestofhook (,funs ,global) + ;; `funs' holds the functions left on the hook and `global' + ;; holds the functions left on the global part of the hook + ;; (in case the hook is local). + (lexical-let ((funs ,funs) + (global ,global)) + (if (consp funs) + (if (eq t (car funs)) + (runrestofhook (append global (cdr funs)) nil) + (funcall (car funs) + (lambda () (runrestofhook (cdr funs) global)))) + ;; Once there are no more functions on the hook, run + ;; the original body. + ,@body)))) + (runrestofhook ,var + ;; The global part of the hook, if any. + ,(if (symbolp var) + `(if (local-variable-p ',var) + (default-value ',var))))))) + + +;;; Code that used to be implemented in src/abbrev.c + +(defvar abbrev-table-name-list '(fundamental-mode-abbrev-table + global-abbrev-table) + "List of symbols whose values are abbrev tables.") + +(defun make-abbrev-table (&optional props) + "Create a new, empty abbrev table object." + ;; The value 59 is an arbitrary prime number. + (let ((table (make-vector 59 0))) + ;; Each abbrev-table has a `modiff' counter which can be used to detect + ;; when an abbreviation was added. An example of use would be to + ;; construct :regexp dynamically as the union of all abbrev names, so + ;; `modiff' can let us detect that an abbrev was added and hence :regexp + ;; needs to be refreshed. + ;; The presence of `modiff' entry is also used as a tag indicating this + ;; vector is really an abbrev-table. + (abbrev-table-put table :abbrev-table-modiff 0) + (while (consp props) + (abbrev-table-put table (pop props) (pop props))) + table)) + +(defun abbrev-table-p (object) + (and (vectorp object) + (numberp (abbrev-table-get object :abbrev-table-modiff)))) + +(defvar global-abbrev-table (make-abbrev-table) + "The abbrev table whose abbrevs affect all buffers. +Each buffer may also have a local abbrev table. +If it does, the local table overrides the global one +for any particular abbrev defined in both.") + +(defvar abbrev-minor-mode-table-alist nil + "Alist of abbrev tables to use for minor modes. +Each element looks like (VARIABLE . ABBREV-TABLE); +ABBREV-TABLE is active whenever VARIABLE's value is non-nil.") + +(defvar fundamental-mode-abbrev-table + (let ((table (make-abbrev-table))) + ;; Set local-abbrev-table's default to be fundamental-mode-abbrev-table. + (setq-default local-abbrev-table table) + table) + "The abbrev table of mode-specific abbrevs for Fundamental Mode.") + +(defvar abbrevs-changed nil + "Set non-nil by defining or altering any word abbrevs. +This causes `save-some-buffers' to offer to save the abbrevs.") + +(defcustom abbrev-all-caps nil + "Non-nil means expand multi-word abbrevs all caps if abbrev was so." + :type 'boolean + :group 'abbrev-mode) + +(defvar abbrev-start-location nil + "Buffer position for `expand-abbrev' to use as the start of the abbrev. +When nil, use the word before point as the abbrev. +Calling `expand-abbrev' sets this to nil.") + +(defvar abbrev-start-location-buffer nil + "Buffer that `abbrev-start-location' has been set for. +Trying to expand an abbrev in any other buffer clears `abbrev-start-location'.") + +(defvar last-abbrev nil + "The abbrev-symbol of the last abbrev expanded. See `abbrev-symbol'.") + +(defvar last-abbrev-text nil + "The exact text of the last abbrev expanded. +nil if the abbrev has already been unexpanded.") + +(defvar last-abbrev-location 0 + "The location of the start of the last abbrev expanded.") + +;; (defvar local-abbrev-table fundamental-mode-abbrev-table +;; "Local (mode-specific) abbrev table of current buffer.") +;; (make-variable-buffer-local 'local-abbrev-table) + +(defcustom pre-abbrev-expand-hook nil + "Function or functions to be called before abbrev expansion is done. +This is the first thing that `expand-abbrev' does, and so this may change +the current abbrev table before abbrev lookup happens." + :type 'hook + :group 'abbrev-mode) + +(defun clear-abbrev-table (table) + "Undefine all abbrevs in abbrev table TABLE, leaving it empty." + (setq abbrevs-changed t) + (dotimes (i (length table)) + (aset table i 0))) + +(defun define-abbrev (table name expansion &optional hook count system-flag + ;; In case the abbrev list passed to + ;; `define-abbrev-table' includes extra elements + ;; that we should ignore. + &rest ignore) + "Define an abbrev in TABLE named NAME, to expand to EXPANSION and call HOOK. +NAME must be a string, and should be lower-case. +EXPANSION should usually be a string. +To undefine an abbrev, define it with EXPANSION = nil. +If HOOK is non-nil, it should be a function of no arguments; +it is called after EXPANSION is inserted. +If EXPANSION is not a string, the abbrev is a special one, + which does not expand in the usual way but only runs HOOK. + +COUNT, if specified, gives the initial value for the abbrev's +usage-count, which is incremented each time the abbrev is used. +\(The default is zero.) + +SYSTEM-FLAG, if non-nil, says that this is a \"system\" abbreviation +which should not be saved in the user's abbreviation file. +Unless SYSTEM-FLAG is `force', a system abbreviation will not +overwrite a non-system abbreviation of the same name." + (unless count (setq count 0)) + (let ((sym (intern name table))) + ;; Don't override a prior user-defined abbrev with a system abbrev, + ;; unless system-flag is `force'. + (unless (and (not (memq system-flag '(nil force))) + (boundp sym) (symbol-value sym) + (not (abbrev-get sym 'system-flag))) + (unless (or system-flag + (and (boundp sym) (fboundp sym) + ;; load-file-name + (equal (symbol-value sym) expansion) + (equal (symbol-function sym) hook))) + (setq abbrevs-changed t)) + (set sym expansion) + (fset sym hook) + (setplist sym (if (null system-flag) count + (list 'count count 'system-flag system-flag))) + (abbrev-table-put table :abbrev-table-modiff + (1+ (abbrev-table-get table :abbrev-table-modiff)))) + name)) + +(defun abbrev--check-chars (abbrev global) + "Check if the characters in ABBREV have word syntax in either the +current (if global is nil) or standard syntax table." + (with-syntax-table + (cond ((null global) (standard-syntax-table)) + ;; ((syntax-table-p global) global) + (t (syntax-table))) + (when (string-match "\\W" abbrev) + (let ((badchars ()) + (pos 0)) + (while (string-match "\\W" abbrev pos) + (pushnew (aref abbrev (match-beginning 0)) badchars) + (setq pos (1+ pos))) + (error "Some abbrev characters (%s) are not word constituents %s" + (apply 'string (nreverse badchars)) + (if global "in the standard syntax" "in this mode")))))) + +(defun define-global-abbrev (abbrev expansion) + "Define ABBREV as a global abbreviation for EXPANSION. +The characters in ABBREV must all be word constituents in the standard +syntax table." + (interactive "sDefine global abbrev: \nsExpansion for %s: ") + (abbrev--check-chars abbrev 'global) + (define-abbrev global-abbrev-table (downcase abbrev) expansion)) + +(defun define-mode-abbrev (abbrev expansion) + "Define ABBREV as a mode-specific abbreviation for EXPANSION. +The characters in ABBREV must all be word-constituents in the current mode." + (interactive "sDefine mode abbrev: \nsExpansion for %s: ") + (unless local-abbrev-table + (error "Major mode has no abbrev table")) + (abbrev--check-chars abbrev nil) + (define-abbrev local-abbrev-table (downcase abbrev) expansion)) + +(defun abbrev--active-tables (&optional tables) + "Return the list of abbrev tables currently active. +TABLES if non-nil overrides the usual rules. It can hold +either a single abbrev table or a list of abbrev tables." + ;; We could just remove the `tables' arg and let callers use + ;; (or table (abbrev--active-tables)) but then they'd have to be careful + ;; to treat the distinction between a single table and a list of tables. + (cond + ((consp tables) tables) + ((vectorp tables) (list tables)) + (t + (let ((tables (if (listp local-abbrev-table) + (append local-abbrev-table + (list global-abbrev-table)) + (list local-abbrev-table global-abbrev-table)))) + ;; Add the minor-mode abbrev tables. + (dolist (x abbrev-minor-mode-table-alist) + (when (and (symbolp (car x)) (boundp (car x)) (symbol-value (car x))) + (setq tables + (if (listp (cdr x)) + (append (cdr x) tables) (cons (cdr x) tables))))) + tables)))) + + +(defun abbrev-symbol (abbrev &optional table) + "Return the symbol representing abbrev named ABBREV. +This symbol's name is ABBREV, but it is not the canonical symbol of that name; +it is interned in an abbrev-table rather than the normal obarray. +The value is nil if that abbrev is not defined. +Optional second arg TABLE is abbrev table to look it up in. +The default is to try buffer's mode-specific abbrev table, then global table." + (let ((tables (abbrev--active-tables table)) + sym) + (while (and tables (not (symbol-value sym))) + (let ((table (pop tables)) + (case-fold (not (abbrev-table-get table :case-preserve)))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (setq sym (intern-soft (if case-fold (downcase abbrev) abbrev) table)) + (if (and (not case-fold) (symbol-value sym)) + ;; The :case-preserve property normally belongs to the + ;; abbrev-table, but the use of this abbrev needs to know if + ;; this abbrev came from a case preserving table or not, so we + ;; save a copy in the abbrev itself. + (abbrev-put sym :case-preserve t)))) + (if (symbol-value sym) + sym))) + + +(defun abbrev-expansion (abbrev &optional table) + "Return the string that ABBREV expands into in the current buffer. +Optionally specify an abbrev table as second arg; +then ABBREV is looked up in that table only." + (symbol-value (abbrev-symbol abbrev table))) + + +(defun abbrev--before-point () + "Try and find an abbrev before point. Return it if found, nil otherwise." + (unless (eq abbrev-start-location-buffer (current-buffer)) + (setq abbrev-start-location nil)) + + (let ((tables (abbrev--active-tables)) + (pos (point)) + start end name res) + + (if abbrev-start-location + (progn + (setq start abbrev-start-location) + (setq abbrev-start-location nil) + ;; Remove the hyphen inserted by `abbrev-prefix-mark'. + (if (and (< start (point-max)) + (eq (char-after start) ?-)) + (delete-region start (1+ start))) + (skip-syntax-backward " ") + (setq end (point)) + (setq name (buffer-substring start end)) + (goto-char pos) ; Restore point. + (list name (abbrev-symbol name tables) start end)) + + (while (and tables (not res)) + (let* ((table (pop tables)) + (enable-fun (abbrev-table-get table :enable-function))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (setq res + (and (or (not enable-fun) (funcall enable-fun)) + (looking-back (or (abbrev-table-get table :regexp) + "\\<\\(\\w+\\)\\W*") + (line-beginning-position)) + (setq start (match-beginning 1)) + (setq end (match-end 1)) + (setq name (buffer-substring start end)) + ;; This will also look it up in parent tables. + ;; This is not on purpose, but it seems harmless. + (list name (abbrev-symbol name table) start end))) + ;; Restore point. + (goto-char pos))) + res))) + +(defvar abbrev-expand-function nil + "Wrapper hook around `expand-abbrev'. +The functions on this special hook are called with one argument: +a function that performs the abbrev expansion.") + +(defun expand-abbrev () + "Expand the abbrev before point, if there is an abbrev there. +Effective when explicitly called even when `abbrev-mode' is nil. +Returns the abbrev symbol, if expansion took place." + (interactive) + (run-hooks 'pre-abbrev-expand-hook) + (abbrev-with-wrapper-hook abbrev-expand-function + (destructuring-bind (&optional name sym wordstart wordend) + (abbrev--before-point) + (when sym + (let ((value sym)) + (unless (or ;; executing-kbd-macro + noninteractive + (window-minibuffer-p (selected-window))) + ;; Add an undo boundary, in case we are doing this for + ;; a self-inserting command which has avoided making one so far. + (undo-boundary)) + ;; Now sym is the abbrev symbol. + (setq last-abbrev-text name) + (setq last-abbrev sym) + (setq last-abbrev-location wordstart) + ;; Increment use count. + (abbrev-put sym 'count (1+ (abbrev-get sym 'count))) + ;; If this abbrev has an expansion, delete the abbrev + ;; and insert the expansion. + (when (stringp (symbol-value sym)) + (goto-char wordend) + (insert (symbol-value sym)) + (delete-region wordstart wordend) + (let ((case-fold-search nil)) + (when (and (not (abbrev-get sym :case-preserve)) + (string-match "[[:upper:]]" name)) + (if (not (string-match "[[:lower:]]" name)) + ;; Abbrev was all caps. If expansion is multiple words, + ;; normally capitalize each word. + (if (and (not abbrev-all-caps) + (save-excursion + (> (progn (backward-word 1) (point)) + (progn (goto-char wordstart) + (forward-word 1) (point))))) + (upcase-initials-region wordstart (point)) + (upcase-region wordstart (point))) + ;; Abbrev included some caps. Cap first initial of expansion. + (let ((end (point))) + ;; Find the initial. + (goto-char wordstart) + (skip-syntax-forward "^w" (1- end)) + ;; Change just that. + (upcase-initials-region (point) (1+ (point)))))))) + (when (symbol-function sym) + (let* ((hook (symbol-function sym)) + (expanded + ;; If the abbrev has a hook function, run it. + (funcall hook))) + ;; In addition, if the hook function is a symbol with + ;; a non-nil `no-self-insert' property, let the value it + ;; returned specify whether we consider that an expansion took + ;; place. If it returns nil, no expansion has been done. + (if (and (symbolp hook) + (null expanded) + (get hook 'no-self-insert)) + (setq value nil)))) + value))))) + +(defun unexpand-abbrev () + "Undo the expansion of the last abbrev that expanded. +This differs from ordinary undo in that other editing done since then +is not undone." + (interactive) + (save-excursion + (unless (or (< last-abbrev-location (point-min)) + (> last-abbrev-location (point-max))) + (goto-char last-abbrev-location) + (when (stringp last-abbrev-text) + ;; This isn't correct if last-abbrev's hook was used + ;; to do the expansion. + (let ((val (symbol-value last-abbrev))) + (unless (stringp val) + (error "value of abbrev-symbol must be a string")) + (delete-region (point) (+ (point) (length val))) + ;; Don't inherit properties here; just copy from old contents. + (insert last-abbrev-text) + (setq last-abbrev-text nil)))))) + +(defun abbrev--write (sym) + "Write the abbrev in a `read'able form. +Only writes the non-system abbrevs. +Presumes that `standard-output' points to `current-buffer'." + (unless (or (null (symbol-value sym)) (abbrev-get sym 'system-flag)) + (insert " (") + (prin1 name) + (insert " ") + (prin1 (symbol-value sym)) + (insert " ") + (prin1 (symbol-function sym)) + (insert " ") + (prin1 (abbrev-get sym 'count)) + (insert ")\n"))) + +(defun abbrev--describe (sym) + (when (symbol-value sym) + (prin1 (symbol-name sym)) + (if (null (abbrev-get sym 'system-flag)) + (indent-to 15 1) + (insert " (sys)") + (indent-to 20 1)) + (prin1 (abbrev-get sym 'count)) + (indent-to 20 1) + (prin1 (symbol-value sym)) + (when (symbol-function sym) + (indent-to 45 1) + (prin1 (symbol-function sym))) + (terpri))) + +(defun insert-abbrev-table-description (name &optional readable) + "Insert before point a full description of abbrev table named NAME. +NAME is a symbol whose value is an abbrev table. +If optional 2nd arg READABLE is non-nil, a human-readable description +is inserted. Otherwise the description is an expression, +a call to `define-abbrev-table', which would +define the abbrev table NAME exactly as it is currently defined. + +Abbrevs marked as \"system abbrevs\" are omitted." + (let ((table (symbol-value name)) + (symbols ())) + (mapatoms (lambda (sym) (if (symbol-value sym) (push sym symbols))) table) + (setq symbols (sort symbols 'string-lessp)) + (let ((standard-output (current-buffer))) + (if readable + (progn + (insert "(") + (prin1 name) + (insert ")\n\n") + (mapc 'abbrev--describe symbols) + (insert "\n\n")) + (insert "(define-abbrev-table '") + (prin1 name) + (insert " '(") + (mapc 'abbrev--write symbols) + (insert " ))\n\n")) + nil))) + +(defun define-abbrev-table (tablename definitions + &optional docstring &rest props) + "Define TABLENAME (a symbol) as an abbrev table name. +Define abbrevs in it according to DEFINITIONS, which is a list of elements +of the form (ABBREVNAME EXPANSION HOOK USECOUNT SYSTEMFLAG). +\(If the list is shorter than that, omitted elements default to nil). +PROPS is a property list to apply to the table. +Properties with special meaning: +- `:parents' contains a list of abbrev tables from which this table inherits + abbreviations. +- `:case-preserve' non-nil means that abbreviations are lookedup without + case-folding, and the expansion is not capitalized/upcased. +- `:regexp' describes the form of abbrevs. It defaults to \\<\\(\\w+\\)\\W* which + means that an abbrev can only be a single word. The submatch 1 is treated + as the potential name of an abbrev. +- `:enable-function' can be set to a function of no argument which returns + non-nil iff the abbrevs in this table should be used for this instance + of `expand-abbrev'." + (let ((table (if (boundp tablename) (symbol-value tablename)))) + (unless table + (setq table (make-abbrev-table props)) + (set tablename table) + (push tablename abbrev-table-name-list)) + (when (stringp docstring) + (put tablename 'variable-documentation docstring)) + (dolist (elt definitions) + (apply 'define-abbrev table elt)))) + (provide 'abbrev) ;; arch-tag: dbd6f3ae-dfe3-40ba-b00f-f9e3ff960df5 Diffs between emacs@sv.gnu.org/emacs--devo--0 and workfile end here. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Abbrev tables in elisp with some extra stuff 2007-10-24 2:21 ` Stefan Monnier @ 2007-10-25 2:10 ` Richard Stallman 2007-10-26 5:44 ` Testing new abbrev tables in elisp Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-10-25 2:10 UTC (permalink / raw) To: Stefan Monnier; +Cc: rgm, andreas.roehler, emacs-devel Since you've dealt with all the changes I asked for, please install it. Please update the Lisp Manual as you install it. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Testing new abbrev tables in elisp 2007-10-25 2:10 ` Richard Stallman @ 2007-10-26 5:44 ` Stefan Monnier 2007-10-26 19:13 ` Andreas Röhler 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2007-10-26 5:44 UTC (permalink / raw) To: rms; +Cc: rgm, andreas.roehler, emacs-devel > Since you've dealt with all the changes I asked for, please install it. > Please update the Lisp Manual as you install it. OK. I have two problems, tho: - The code needs more testing. Could some of you try it out and confirm that they do not notice any difference? Stress testing would be great, especially if you use mailabbrev.el. - I need another name for the :case-preserve property because "preserve" is ambiguous: it can eoither refer to "not change case at all" or "propagate the case from the abbrev name to its expansion". Ideally, I'd like to use `:case-fold' except that it needs to default to nil and to case-folding, so maybe `:case-no-fold' ? -- Stefan --- orig/lisp/abbrev.el +++ mod/lisp/abbrev.el @@ -363,6 +363,524 @@ (if (or noquery (y-or-n-p (format "Expand `%s'? " string))) (expand-abbrev))))))) +;;; Abbrev properties. + +(defun abbrev-table-get (table prop) + "Get the PROP property of abbrev table TABLE." + (let ((sym (intern-soft "" table))) + (if sym (get sym prop)))) + +(defun abbrev-table-put (table prop val) + "Set the PROP property of abbrev table TABLE to VAL." + (let ((sym (intern "" table))) + (set sym nil) ; Make sure it won't be confused for an abbrev. + (put sym prop val))) + +(defun abbrev-get (sym prop) + "Get the property PROP of abbrev SYM." + (let ((plist (symbol-plist sym))) + (if (listp plist) + (plist-get plist prop) + (if (eq 'count prop) plist)))) + +(defun abbrev-put (sym prop val) + "Set the property PROP of abbrev SYM to value VAL. +See `define-abbrev' for the effect of some special properties." + (let ((plist (symbol-plist sym))) + (if (consp plist) + (put sym prop val) + (setplist sym (if (eq 'count prop) val + (list 'count plist prop val)))))) + +(defmacro abbrev-with-wrapper-hook (var &rest body) + "Run BODY wrapped with the VAR hook. +VAR is a special hook: its functions are called with one argument which +is the \"original\" code (the BODY), so the hook function can wrap the +original function, can call it several times, or even not call it at all. +VAR is normally a symbol (a variable) in which case it is treated like a hook, +with a buffer-local and a global part. But it can also be an arbitrary expression. +This is similar to an `around' advice." + (declare (indent 1) (debug t)) + ;; We need those two gensyms because CL's lexical scoping is not available + ;; for function arguments :-( + (let ((funs (make-symbol "funs")) + (global (make-symbol "global"))) + ;; Since the hook is a wrapper, the loop has to be done via + ;; recursion: a given hook function will call its parameter in order to + ;; continue looping. + `(labels ((runrestofhook (,funs ,global) + ;; `funs' holds the functions left on the hook and `global' + ;; holds the functions left on the global part of the hook + ;; (in case the hook is local). + (lexical-let ((funs ,funs) + (global ,global)) + (if (consp funs) + (if (eq t (car funs)) + (runrestofhook (append global (cdr funs)) nil) + (funcall (car funs) + (lambda () (runrestofhook (cdr funs) global)))) + ;; Once there are no more functions on the hook, run + ;; the original body. + ,@body)))) + (runrestofhook ,var + ;; The global part of the hook, if any. + ,(if (symbolp var) + `(if (local-variable-p ',var) + (default-value ',var))))))) + + +;;; Code that used to be implemented in src/abbrev.c + +(defvar abbrev-table-name-list '(fundamental-mode-abbrev-table + global-abbrev-table) + "List of symbols whose values are abbrev tables.") + +(defun make-abbrev-table (&optional props) + "Create a new, empty abbrev table object. +PROPS is a " + ;; The value 59 is an arbitrary prime number. + (let ((table (make-vector 59 0))) + ;; Each abbrev-table has a `modiff' counter which can be used to detect + ;; when an abbreviation was added. An example of use would be to + ;; construct :regexp dynamically as the union of all abbrev names, so + ;; `modiff' can let us detect that an abbrev was added and hence :regexp + ;; needs to be refreshed. + ;; The presence of `modiff' entry is also used as a tag indicating this + ;; vector is really an abbrev-table. + (abbrev-table-put table :abbrev-table-modiff 0) + (while (consp props) + (abbrev-table-put table (pop props) (pop props))) + table)) + +(defun abbrev-table-p (object) + (and (vectorp object) + (numberp (abbrev-table-get object :abbrev-table-modiff)))) + +(defvar global-abbrev-table (make-abbrev-table) + "The abbrev table whose abbrevs affect all buffers. +Each buffer may also have a local abbrev table. +If it does, the local table overrides the global one +for any particular abbrev defined in both.") + +(defvar abbrev-minor-mode-table-alist nil + "Alist of abbrev tables to use for minor modes. +Each element looks like (VARIABLE . ABBREV-TABLE); +ABBREV-TABLE is active whenever VARIABLE's value is non-nil.") + +(defvar fundamental-mode-abbrev-table + (let ((table (make-abbrev-table))) + ;; Set local-abbrev-table's default to be fundamental-mode-abbrev-table. + (setq-default local-abbrev-table table) + table) + "The abbrev table of mode-specific abbrevs for Fundamental Mode.") + +(defvar abbrevs-changed nil + "Set non-nil by defining or altering any word abbrevs. +This causes `save-some-buffers' to offer to save the abbrevs.") + +(defcustom abbrev-all-caps nil + "Non-nil means expand multi-word abbrevs all caps if abbrev was so." + :type 'boolean + :group 'abbrev-mode) + +(defvar abbrev-start-location nil + "Buffer position for `expand-abbrev' to use as the start of the abbrev. +When nil, use the word before point as the abbrev. +Calling `expand-abbrev' sets this to nil.") + +(defvar abbrev-start-location-buffer nil + "Buffer that `abbrev-start-location' has been set for. +Trying to expand an abbrev in any other buffer clears `abbrev-start-location'.") + +(defvar last-abbrev nil + "The abbrev-symbol of the last abbrev expanded. See `abbrev-symbol'.") + +(defvar last-abbrev-text nil + "The exact text of the last abbrev expanded. +nil if the abbrev has already been unexpanded.") + +(defvar last-abbrev-location 0 + "The location of the start of the last abbrev expanded.") + +;; (defvar local-abbrev-table fundamental-mode-abbrev-table +;; "Local (mode-specific) abbrev table of current buffer.") +;; (make-variable-buffer-local 'local-abbrev-table) + +(defcustom pre-abbrev-expand-hook nil + "Function or functions to be called before abbrev expansion is done. +This is the first thing that `expand-abbrev' does, and so this may change +the current abbrev table before abbrev lookup happens." + :type 'hook + :group 'abbrev-mode) +(make-obsolete-variable 'pre-abbrev-expand-hook 'abbrev-expand-functions "23.1") + +(defun clear-abbrev-table (table) + "Undefine all abbrevs in abbrev table TABLE, leaving it empty." + (setq abbrevs-changed t) + (dotimes (i (length table)) + (aset table i 0))) + +(defun define-abbrev (table name expansion &optional hook &rest props + ;; In case the abbrev list passed to + ;; `define-abbrev-table' includes extra elements + ;; that we should ignore. + &rest ignore) + "Define an abbrev in TABLE named NAME, to expand to EXPANSION and call HOOK. +NAME must be a string, and should be lower-case. +EXPANSION should usually be a string. +To undefine an abbrev, define it with EXPANSION = nil. +If HOOK is non-nil, it should be a function of no arguments; +it is called after EXPANSION is inserted. +If EXPANSION is not a string, the abbrev is a special one, + which does not expand in the usual way but only runs HOOK. + +PROPS is a property list. The following properties are special: +- `count': the value for the abbrev's usage-count, which is incremented each time + the abbrev is used (the default is zero). +- `system-flag': if non-nil, says that this is a \"system\" abbreviation + which should not be saved in the user's abbreviation file. + Unless `system-flag' is `force', a system abbreviation will not + overwrite a non-system abbreviation of the same name. +- `:case-preserve': non-nil means that abbreviations are looked up without + case-folding, and the expansion is not capitalized/upcased. +- `:enable-function': a function of no argument which returns non-nil iff the + abbrev should be used for a particular call of `expand-abbrev'. + +An obsolete but still supported calling form is: + +\(define-abbrev TABLE NAME EXPANSION &optional HOOK COUNT SYSTEM-FLAG)." + (when (and (consp props) (or (null (car props)) (numberp (car props)))) + ;; Old-style calling convention. + (setq props (list* 'count (car props) + (if (cadr props) (list 'system-flag (cadr props)))))) + (unless (plist-get props 'count) + (setq props (plist-put props 'count 0))) + (let ((system-flag (plist-get props 'system-flag)) + (sym (intern name table))) + ;; Don't override a prior user-defined abbrev with a system abbrev, + ;; unless system-flag is `force'. + (unless (and (not (memq system-flag '(nil force))) + (boundp sym) (symbol-value sym) + (not (abbrev-get sym 'system-flag))) + (unless (or system-flag + (and (boundp sym) (fboundp sym) + ;; load-file-name + (equal (symbol-value sym) expansion) + (equal (symbol-function sym) hook))) + (setq abbrevs-changed t)) + (set sym expansion) + (fset sym hook) + (setplist sym props) + (abbrev-table-put table :abbrev-table-modiff + (1+ (abbrev-table-get table :abbrev-table-modiff)))) + name)) + +(defun abbrev--check-chars (abbrev global) + "Check if the characters in ABBREV have word syntax in either the +current (if global is nil) or standard syntax table." + (with-syntax-table + (cond ((null global) (standard-syntax-table)) + ;; ((syntax-table-p global) global) + (t (syntax-table))) + (when (string-match "\\W" abbrev) + (let ((badchars ()) + (pos 0)) + (while (string-match "\\W" abbrev pos) + (pushnew (aref abbrev (match-beginning 0)) badchars) + (setq pos (1+ pos))) + (error "Some abbrev characters (%s) are not word constituents %s" + (apply 'string (nreverse badchars)) + (if global "in the standard syntax" "in this mode")))))) + +(defun define-global-abbrev (abbrev expansion) + "Define ABBREV as a global abbreviation for EXPANSION. +The characters in ABBREV must all be word constituents in the standard +syntax table." + (interactive "sDefine global abbrev: \nsExpansion for %s: ") + (abbrev--check-chars abbrev 'global) + (define-abbrev global-abbrev-table (downcase abbrev) expansion)) + +(defun define-mode-abbrev (abbrev expansion) + "Define ABBREV as a mode-specific abbreviation for EXPANSION. +The characters in ABBREV must all be word-constituents in the current mode." + (interactive "sDefine mode abbrev: \nsExpansion for %s: ") + (unless local-abbrev-table + (error "Major mode has no abbrev table")) + (abbrev--check-chars abbrev nil) + (define-abbrev local-abbrev-table (downcase abbrev) expansion)) + +(defun abbrev--active-tables (&optional tables) + "Return the list of abbrev tables currently active. +TABLES if non-nil overrides the usual rules. It can hold +either a single abbrev table or a list of abbrev tables." + ;; We could just remove the `tables' arg and let callers use + ;; (or table (abbrev--active-tables)) but then they'd have to be careful + ;; to treat the distinction between a single table and a list of tables. + (cond + ((consp tables) tables) + ((vectorp tables) (list tables)) + (t + (let ((tables (if (listp local-abbrev-table) + (append local-abbrev-table + (list global-abbrev-table)) + (list local-abbrev-table global-abbrev-table)))) + ;; Add the minor-mode abbrev tables. + (dolist (x abbrev-minor-mode-table-alist) + (when (and (symbolp (car x)) (boundp (car x)) (symbol-value (car x))) + (setq tables + (if (listp (cdr x)) + (append (cdr x) tables) (cons (cdr x) tables))))) + tables)))) + + +(defun abbrev-symbol (abbrev &optional table) + "Return the symbol representing abbrev named ABBREV. +This symbol's name is ABBREV, but it is not the canonical symbol of that name; +it is interned in an abbrev-table rather than the normal obarray. +The value is nil if that abbrev is not defined. +Optional second arg TABLE is abbrev table to look it up in. +The default is to try buffer's mode-specific abbrev table, then global table." + (let ((tables (abbrev--active-tables table)) + sym) + (while (and tables (not (symbol-value sym))) + (let ((table (pop tables)) + (case-fold (not (abbrev-table-get table :case-preserve)))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (setq sym (intern-soft (if case-fold (downcase abbrev) abbrev) table)) + (if (and (not case-fold) (symbol-value sym)) + ;; The :case-preserve property normally belongs to the + ;; abbrev-table, but the use of this abbrev needs to know if + ;; this abbrev came from a case preserving table or not, so we + ;; save a copy in the abbrev itself. + (abbrev-put sym :case-preserve t)))) + (if (symbol-value sym) + sym))) + + +(defun abbrev-expansion (abbrev &optional table) + "Return the string that ABBREV expands into in the current buffer. +Optionally specify an abbrev table as second arg; +then ABBREV is looked up in that table only." + (symbol-value (abbrev-symbol abbrev table))) + + +(defun abbrev--before-point () + "Try and find an abbrev before point. Return it if found, nil otherwise." + (unless (eq abbrev-start-location-buffer (current-buffer)) + (setq abbrev-start-location nil)) + + (let ((tables (abbrev--active-tables)) + (pos (point)) + start end name res) + + (if abbrev-start-location + (progn + (setq start abbrev-start-location) + (setq abbrev-start-location nil) + ;; Remove the hyphen inserted by `abbrev-prefix-mark'. + (if (and (< start (point-max)) + (eq (char-after start) ?-)) + (delete-region start (1+ start))) + (skip-syntax-backward " ") + (setq end (point)) + (setq name (buffer-substring start end)) + (goto-char pos) ; Restore point. + (list name (abbrev-symbol name tables) start end)) + + (while (and tables (not res)) + (let* ((table (pop tables)) + (enable-fun (abbrev-table-get table :enable-function))) + (setq tables (append (abbrev-table-get table :parents) tables)) + (setq res + (and (or (not enable-fun) (funcall enable-fun)) + (looking-back (or (abbrev-table-get table :regexp) + "\\<\\(\\w+\\)\\W*") + (line-beginning-position)) + (setq start (match-beginning 1)) + (setq end (match-end 1)) + (setq name (buffer-substring start end)) + ;; This will also look it up in parent tables. + ;; This is not on purpose, but it seems harmless. + (list name (abbrev-symbol name table) start end))) + ;; Restore point. + (goto-char pos))) + res))) + +(defvar abbrev-expand-function nil + "Wrapper hook around `expand-abbrev'. +The functions on this special hook are called with one argument: +a function that performs the abbrev expansion.") + +(defun expand-abbrev () + "Expand the abbrev before point, if there is an abbrev there. +Effective when explicitly called even when `abbrev-mode' is nil. +Returns the abbrev symbol, if expansion took place." + (interactive) + (run-hooks 'pre-abbrev-expand-hook) + (abbrev-with-wrapper-hook abbrev-expand-function + (destructuring-bind (&optional name sym wordstart wordend) + (abbrev--before-point) + (when sym + (let ((value sym)) + (unless (or ;; executing-kbd-macro + noninteractive + (window-minibuffer-p (selected-window))) + ;; Add an undo boundary, in case we are doing this for + ;; a self-inserting command which has avoided making one so far. + (undo-boundary)) + ;; Now sym is the abbrev symbol. + (setq last-abbrev-text name) + (setq last-abbrev sym) + (setq last-abbrev-location wordstart) + ;; Increment use count. + (abbrev-put sym 'count (1+ (abbrev-get sym 'count))) + ;; If this abbrev has an expansion, delete the abbrev + ;; and insert the expansion. + (when (stringp (symbol-value sym)) + (goto-char wordend) + (insert (symbol-value sym)) + (delete-region wordstart wordend) + (let ((case-fold-search nil)) + (when (and (not (abbrev-get sym :case-preserve)) + (string-match "[[:upper:]]" name)) + (if (not (string-match "[[:lower:]]" name)) + ;; Abbrev was all caps. If expansion is multiple words, + ;; normally capitalize each word. + (if (and (not abbrev-all-caps) + (save-excursion + (> (progn (backward-word 1) (point)) + (progn (goto-char wordstart) + (forward-word 1) (point))))) + (upcase-initials-region wordstart (point)) + (upcase-region wordstart (point))) + ;; Abbrev included some caps. Cap first initial of expansion. + (let ((end (point))) + ;; Find the initial. + (goto-char wordstart) + (skip-syntax-forward "^w" (1- end)) + ;; Change just that. + (upcase-initials-region (point) (1+ (point)))))))) + (when (symbol-function sym) + (let* ((hook (symbol-function sym)) + (expanded + ;; If the abbrev has a hook function, run it. + (funcall hook))) + ;; In addition, if the hook function is a symbol with + ;; a non-nil `no-self-insert' property, let the value it + ;; returned specify whether we consider that an expansion took + ;; place. If it returns nil, no expansion has been done. + (if (and (symbolp hook) + (null expanded) + (get hook 'no-self-insert)) + (setq value nil)))) + value))))) + +(defun unexpand-abbrev () + "Undo the expansion of the last abbrev that expanded. +This differs from ordinary undo in that other editing done since then +is not undone." + (interactive) + (save-excursion + (unless (or (< last-abbrev-location (point-min)) + (> last-abbrev-location (point-max))) + (goto-char last-abbrev-location) + (when (stringp last-abbrev-text) + ;; This isn't correct if last-abbrev's hook was used + ;; to do the expansion. + (let ((val (symbol-value last-abbrev))) + (unless (stringp val) + (error "value of abbrev-symbol must be a string")) + (delete-region (point) (+ (point) (length val))) + ;; Don't inherit properties here; just copy from old contents. + (insert last-abbrev-text) + (setq last-abbrev-text nil)))))) + +(defun abbrev--write (sym) + "Write the abbrev in a `read'able form. +Only writes the non-system abbrevs. +Presumes that `standard-output' points to `current-buffer'." + (unless (or (null (symbol-value sym)) (abbrev-get sym 'system-flag)) + (insert " (") + (prin1 name) + (insert " ") + (prin1 (symbol-value sym)) + (insert " ") + (prin1 (symbol-function sym)) + (insert " ") + (prin1 (abbrev-get sym 'count)) + (insert ")\n"))) + +(defun abbrev--describe (sym) + (when (symbol-value sym) + (prin1 (symbol-name sym)) + (if (null (abbrev-get sym 'system-flag)) + (indent-to 15 1) + (insert " (sys)") + (indent-to 20 1)) + (prin1 (abbrev-get sym 'count)) + (indent-to 20 1) + (prin1 (symbol-value sym)) + (when (symbol-function sym) + (indent-to 45 1) + (prin1 (symbol-function sym))) + (terpri))) + +(defun insert-abbrev-table-description (name &optional readable) + "Insert before point a full description of abbrev table named NAME. +NAME is a symbol whose value is an abbrev table. +If optional 2nd arg READABLE is non-nil, a human-readable description +is inserted. Otherwise the description is an expression, +a call to `define-abbrev-table', which would +define the abbrev table NAME exactly as it is currently defined. + +Abbrevs marked as \"system abbrevs\" are omitted." + (let ((table (symbol-value name)) + (symbols ())) + (mapatoms (lambda (sym) (if (symbol-value sym) (push sym symbols))) table) + (setq symbols (sort symbols 'string-lessp)) + (let ((standard-output (current-buffer))) + (if readable + (progn + (insert "(") + (prin1 name) + (insert ")\n\n") + (mapc 'abbrev--describe symbols) + (insert "\n\n")) + (insert "(define-abbrev-table '") + (prin1 name) + (insert " '(") + (mapc 'abbrev--write symbols) + (insert " ))\n\n")) + nil))) + +(defun define-abbrev-table (tablename definitions + &optional docstring &rest props) + "Define TABLENAME (a symbol) as an abbrev table name. +Define abbrevs in it according to DEFINITIONS, which is a list of elements +of the form (ABBREVNAME EXPANSION HOOK USECOUNT SYSTEMFLAG). +\(If the list is shorter than that, omitted elements default to nil). +PROPS is a property list to apply to the table. +Properties with special meaning: +- `:parents' contains a list of abbrev tables from which this table inherits + abbreviations. +- `:case-preserve' non-nil means that abbreviations are lookedup without + case-folding, and the expansion is not capitalized/upcased. +- `:regexp' describes the form of abbrevs. It defaults to \\<\\(\\w+\\)\\W* which + means that an abbrev can only be a single word. The submatch 1 is treated + as the potential name of an abbrev. +- `:enable-function' can be set to a function of no argument which returns + non-nil iff the abbrevs in this table should be used for this instance + of `expand-abbrev'." + (let ((table (if (boundp tablename) (symbol-value tablename)))) + (unless table + (setq table (make-abbrev-table props)) + (set tablename table) + (push tablename abbrev-table-name-list)) + (when (stringp docstring) + (put tablename 'variable-documentation docstring)) + (dolist (elt definitions) + (apply 'define-abbrev table elt)))) + (provide 'abbrev) ;; arch-tag: dbd6f3ae-dfe3-40ba-b00f-f9e3ff960df5 Diffs between emacs@sv.gnu.org/emacs--devo--0 and workfile end here. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-26 5:44 ` Testing new abbrev tables in elisp Stefan Monnier @ 2007-10-26 19:13 ` Andreas Röhler 2007-10-26 21:38 ` Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-10-26 19:13 UTC (permalink / raw) To: Stefan Monnier; +Cc: Richard Stallman, emacs-devel Am Freitag, 26. Oktober 2007 07:44 schrieben Sie: > > Since you've dealt with all the changes I asked for, please install it. > > Please update the Lisp Manual as you install it. > > OK. I have two problems, tho: > - The code needs more testing. Could some of you try it out and confirm > that they do not notice any difference? Stress testing would be great, > especially if you use mailabbrev.el. # patch -i patch.el abbrev.el patching file abbrev.el patch: **** malformed patch at line 4: ;;; (if (or noquery (y-or-n-p (format "Expand `%s'? " string))) Was my command wrong? Do you know a better way to read it in? Will report errors if any. Everything looks fine at the moment. > - I need another name for the :case-preserve property because "preserve" > is ambiguous: it can eoither refer to "not change case at all" or > "propagate the case from the abbrev name to its expansion". > Ideally, I'd like to use `:case-fold' except that it needs to default to > nil and to case-folding, so maybe `:case-no-fold' ? What about `case-sensitive'? For me as non-english speaker `folding' is not easy to follow because of the word-sence - as something knitting into. Sensitive as awareness is understood from the beginning. > > > -- Stefan > > > --- orig/lisp/abbrev.el > +++ mod/lisp/abbrev.el > @@ -363,6 +363,524 @@ > (if (or noquery (y-or-n-p (format "Expand `%s'? " string))) > (expand-abbrev))))))) > > +;;; Abbrev properties. > + > +(defun abbrev-table-get (table prop) > + "Get the PROP property of abbrev table TABLE." > + (let ((sym (intern-soft "" table))) > + (if sym (get sym prop)))) > + > +(defun abbrev-table-put (table prop val) > + "Set the PROP property of abbrev table TABLE to VAL." > + (let ((sym (intern "" table))) > + (set sym nil) ; Make sure it won't be confused for an abbrev. > + (put sym prop val))) > + > +(defun abbrev-get (sym prop) > + "Get the property PROP of abbrev SYM." > + (let ((plist (symbol-plist sym))) > + (if (listp plist) > + (plist-get plist prop) > + (if (eq 'count prop) plist)))) > + > +(defun abbrev-put (sym prop val) > + "Set the property PROP of abbrev SYM to value VAL. > +See `define-abbrev' for the effect of some special properties." > + (let ((plist (symbol-plist sym))) > + (if (consp plist) > + (put sym prop val) > + (setplist sym (if (eq 'count prop) val > + (list 'count plist prop val)))))) > + > +(defmacro abbrev-with-wrapper-hook (var &rest body) > + "Run BODY wrapped with the VAR hook. > +VAR is a special hook: its functions are called with one argument which > +is the \"original\" code (the BODY), so the hook function can wrap the > +original function, can call it several times, or even not call it at all. > +VAR is normally a symbol (a variable) in which case it is treated like a > hook, +with a buffer-local and a global part. But it can also be an > arbitrary expression. +This is similar to an `around' advice." > + (declare (indent 1) (debug t)) > + ;; We need those two gensyms because CL's lexical scoping is not > available + ;; for function arguments :-( > + (let ((funs (make-symbol "funs")) > + (global (make-symbol "global"))) > + ;; Since the hook is a wrapper, the loop has to be done via > + ;; recursion: a given hook function will call its parameter in order > to + ;; continue looping. > + `(labels ((runrestofhook (,funs ,global) > + ;; `funs' holds the functions left on the hook and > `global' + ;; holds the functions left on the global part > of the hook + ;; (in case the hook is local). > + (lexical-let ((funs ,funs) > + (global ,global)) > + (if (consp funs) > + (if (eq t (car funs)) > + (runrestofhook (append global (cdr funs)) nil) > + (funcall (car funs) > + (lambda () (runrestofhook (cdr funs) > global)))) + ;; Once there are no more functions on the > hook, run + ;; the original body. > + ,@body)))) > + (runrestofhook ,var > + ;; The global part of the hook, if any. > + ,(if (symbolp var) > + `(if (local-variable-p ',var) > + (default-value ',var))))))) > + > + > +;;; Code that used to be implemented in src/abbrev.c > + > +(defvar abbrev-table-name-list '(fundamental-mode-abbrev-table > + global-abbrev-table) > + "List of symbols whose values are abbrev tables.") > + > +(defun make-abbrev-table (&optional props) > + "Create a new, empty abbrev table object. > +PROPS is a " > + ;; The value 59 is an arbitrary prime number. > + (let ((table (make-vector 59 0))) > + ;; Each abbrev-table has a `modiff' counter which can be used to > detect + ;; when an abbreviation was added. An example of use would be > to + ;; construct :regexp dynamically as the union of all abbrev names, > so + ;; `modiff' can let us detect that an abbrev was added and hence > :regexp + ;; needs to be refreshed. > + ;; The presence of `modiff' entry is also used as a tag indicating > this + ;; vector is really an abbrev-table. > + (abbrev-table-put table :abbrev-table-modiff 0) > + (while (consp props) > + (abbrev-table-put table (pop props) (pop props))) > + table)) > + > +(defun abbrev-table-p (object) > + (and (vectorp object) > + (numberp (abbrev-table-get object :abbrev-table-modiff)))) > + > +(defvar global-abbrev-table (make-abbrev-table) > + "The abbrev table whose abbrevs affect all buffers. > +Each buffer may also have a local abbrev table. > +If it does, the local table overrides the global one > +for any particular abbrev defined in both.") > + > +(defvar abbrev-minor-mode-table-alist nil > + "Alist of abbrev tables to use for minor modes. > +Each element looks like (VARIABLE . ABBREV-TABLE); > +ABBREV-TABLE is active whenever VARIABLE's value is non-nil.") > + > +(defvar fundamental-mode-abbrev-table > + (let ((table (make-abbrev-table))) > + ;; Set local-abbrev-table's default to be > fundamental-mode-abbrev-table. + (setq-default local-abbrev-table table) > + table) > + "The abbrev table of mode-specific abbrevs for Fundamental Mode.") > + > +(defvar abbrevs-changed nil > + "Set non-nil by defining or altering any word abbrevs. > +This causes `save-some-buffers' to offer to save the abbrevs.") > + > +(defcustom abbrev-all-caps nil > + "Non-nil means expand multi-word abbrevs all caps if abbrev was so." > + :type 'boolean > + :group 'abbrev-mode) > + > +(defvar abbrev-start-location nil > + "Buffer position for `expand-abbrev' to use as the start of the abbrev. > +When nil, use the word before point as the abbrev. > +Calling `expand-abbrev' sets this to nil.") > + > +(defvar abbrev-start-location-buffer nil > + "Buffer that `abbrev-start-location' has been set for. > +Trying to expand an abbrev in any other buffer clears > `abbrev-start-location'.") + > +(defvar last-abbrev nil > + "The abbrev-symbol of the last abbrev expanded. See `abbrev-symbol'.") > + > +(defvar last-abbrev-text nil > + "The exact text of the last abbrev expanded. > +nil if the abbrev has already been unexpanded.") > + > +(defvar last-abbrev-location 0 > + "The location of the start of the last abbrev expanded.") > + > +;; (defvar local-abbrev-table fundamental-mode-abbrev-table > +;; "Local (mode-specific) abbrev table of current buffer.") > +;; (make-variable-buffer-local 'local-abbrev-table) > + > +(defcustom pre-abbrev-expand-hook nil > + "Function or functions to be called before abbrev expansion is done. > +This is the first thing that `expand-abbrev' does, and so this may change > +the current abbrev table before abbrev lookup happens." > + :type 'hook > + :group 'abbrev-mode) > +(make-obsolete-variable 'pre-abbrev-expand-hook 'abbrev-expand-functions > "23.1") + > +(defun clear-abbrev-table (table) > + "Undefine all abbrevs in abbrev table TABLE, leaving it empty." > + (setq abbrevs-changed t) > + (dotimes (i (length table)) > + (aset table i 0))) > + > +(defun define-abbrev (table name expansion &optional hook &rest props > + ;; In case the abbrev list passed to > + ;; `define-abbrev-table' includes extra > elements + ;; that we should ignore. > + &rest ignore) > + "Define an abbrev in TABLE named NAME, to expand to EXPANSION and call > HOOK. +NAME must be a string, and should be lower-case. > +EXPANSION should usually be a string. > +To undefine an abbrev, define it with EXPANSION = nil. > +If HOOK is non-nil, it should be a function of no arguments; > +it is called after EXPANSION is inserted. > +If EXPANSION is not a string, the abbrev is a special one, > + which does not expand in the usual way but only runs HOOK. > + > +PROPS is a property list. The following properties are special: > +- `count': the value for the abbrev's usage-count, which is incremented > each time + the abbrev is used (the default is zero). > +- `system-flag': if non-nil, says that this is a \"system\" abbreviation > + which should not be saved in the user's abbreviation file. > + Unless `system-flag' is `force', a system abbreviation will not > + overwrite a non-system abbreviation of the same name. > +- `:case-preserve': non-nil means that abbreviations are looked up without > + case-folding, and the expansion is not capitalized/upcased. > +- `:enable-function': a function of no argument which returns non-nil iff > the + abbrev should be used for a particular call of `expand-abbrev'. + > +An obsolete but still supported calling form is: > + > +\(define-abbrev TABLE NAME EXPANSION &optional HOOK COUNT SYSTEM-FLAG)." > + (when (and (consp props) (or (null (car props)) (numberp (car props)))) > + ;; Old-style calling convention. > + (setq props (list* 'count (car props) > + (if (cadr props) (list 'system-flag (cadr > props)))))) + (unless (plist-get props 'count) > + (setq props (plist-put props 'count 0))) > + (let ((system-flag (plist-get props 'system-flag)) > + (sym (intern name table))) > + ;; Don't override a prior user-defined abbrev with a system abbrev, > + ;; unless system-flag is `force'. > + (unless (and (not (memq system-flag '(nil force))) > + (boundp sym) (symbol-value sym) > + (not (abbrev-get sym 'system-flag))) > + (unless (or system-flag > + (and (boundp sym) (fboundp sym) > + ;; load-file-name > + (equal (symbol-value sym) expansion) > + (equal (symbol-function sym) hook))) > + (setq abbrevs-changed t)) > + (set sym expansion) > + (fset sym hook) > + (setplist sym props) > + (abbrev-table-put table :abbrev-table-modiff > + (1+ (abbrev-table-get table > :abbrev-table-modiff)))) + name)) > + > +(defun abbrev--check-chars (abbrev global) > + "Check if the characters in ABBREV have word syntax in either the > +current (if global is nil) or standard syntax table." > + (with-syntax-table > + (cond ((null global) (standard-syntax-table)) > + ;; ((syntax-table-p global) global) > + (t (syntax-table))) > + (when (string-match "\\W" abbrev) > + (let ((badchars ()) > + (pos 0)) > + (while (string-match "\\W" abbrev pos) > + (pushnew (aref abbrev (match-beginning 0)) badchars) > + (setq pos (1+ pos))) > + (error "Some abbrev characters (%s) are not word constituents %s" > + (apply 'string (nreverse badchars)) > + (if global "in the standard syntax" "in this mode")))))) > + > +(defun define-global-abbrev (abbrev expansion) > + "Define ABBREV as a global abbreviation for EXPANSION. > +The characters in ABBREV must all be word constituents in the standard > +syntax table." > + (interactive "sDefine global abbrev: \nsExpansion for %s: ") > + (abbrev--check-chars abbrev 'global) > + (define-abbrev global-abbrev-table (downcase abbrev) expansion)) > + > +(defun define-mode-abbrev (abbrev expansion) > + "Define ABBREV as a mode-specific abbreviation for EXPANSION. > +The characters in ABBREV must all be word-constituents in the current > mode." + (interactive "sDefine mode abbrev: \nsExpansion for %s: ") > + (unless local-abbrev-table > + (error "Major mode has no abbrev table")) > + (abbrev--check-chars abbrev nil) > + (define-abbrev local-abbrev-table (downcase abbrev) expansion)) > + > +(defun abbrev--active-tables (&optional tables) > + "Return the list of abbrev tables currently active. > +TABLES if non-nil overrides the usual rules. It can hold > +either a single abbrev table or a list of abbrev tables." > + ;; We could just remove the `tables' arg and let callers use > + ;; (or table (abbrev--active-tables)) but then they'd have to be careful > + ;; to treat the distinction between a single table and a list of tables. > + (cond > + ((consp tables) tables) > + ((vectorp tables) (list tables)) > + (t > + (let ((tables (if (listp local-abbrev-table) > + (append local-abbrev-table > + (list global-abbrev-table)) > + (list local-abbrev-table global-abbrev-table)))) > + ;; Add the minor-mode abbrev tables. > + (dolist (x abbrev-minor-mode-table-alist) > + (when (and (symbolp (car x)) (boundp (car x)) (symbol-value (car > x))) + (setq tables > + (if (listp (cdr x)) > + (append (cdr x) tables) (cons (cdr x) tables))))) > + tables)))) > + > + > +(defun abbrev-symbol (abbrev &optional table) > + "Return the symbol representing abbrev named ABBREV. > +This symbol's name is ABBREV, but it is not the canonical symbol of that > name; +it is interned in an abbrev-table rather than the normal obarray. > +The value is nil if that abbrev is not defined. > +Optional second arg TABLE is abbrev table to look it up in. > +The default is to try buffer's mode-specific abbrev table, then global > table." + (let ((tables (abbrev--active-tables table)) > + sym) > + (while (and tables (not (symbol-value sym))) > + (let ((table (pop tables)) > + (case-fold (not (abbrev-table-get table :case-preserve)))) > + (setq tables (append (abbrev-table-get table :parents) tables)) > + (setq sym (intern-soft (if case-fold (downcase abbrev) abbrev) > table)) + (if (and (not case-fold) (symbol-value sym)) > + ;; The :case-preserve property normally belongs to the > + ;; abbrev-table, but the use of this abbrev needs to know if > + ;; this abbrev came from a case preserving table or not, so we > + ;; save a copy in the abbrev itself. > + (abbrev-put sym :case-preserve t)))) > + (if (symbol-value sym) > + sym))) > + > + > +(defun abbrev-expansion (abbrev &optional table) > + "Return the string that ABBREV expands into in the current buffer. > +Optionally specify an abbrev table as second arg; > +then ABBREV is looked up in that table only." > + (symbol-value (abbrev-symbol abbrev table))) > + > + > +(defun abbrev--before-point () > + "Try and find an abbrev before point. Return it if found, nil > otherwise." + (unless (eq abbrev-start-location-buffer (current-buffer)) > + (setq abbrev-start-location nil)) > + > + (let ((tables (abbrev--active-tables)) > + (pos (point)) > + start end name res) > + > + (if abbrev-start-location > + (progn > + (setq start abbrev-start-location) > + (setq abbrev-start-location nil) > + ;; Remove the hyphen inserted by `abbrev-prefix-mark'. > + (if (and (< start (point-max)) > + (eq (char-after start) ?-)) > + (delete-region start (1+ start))) > + (skip-syntax-backward " ") > + (setq end (point)) > + (setq name (buffer-substring start end)) > + (goto-char pos) ; Restore point. > + (list name (abbrev-symbol name tables) start end)) > + > + (while (and tables (not res)) > + (let* ((table (pop tables)) > + (enable-fun (abbrev-table-get table :enable-function))) > + (setq tables (append (abbrev-table-get table :parents) tables)) > + (setq res > + (and (or (not enable-fun) (funcall enable-fun)) > + (looking-back (or (abbrev-table-get table :regexp) > + "\\<\\(\\w+\\)\\W*") > + (line-beginning-position)) > + (setq start (match-beginning 1)) > + (setq end (match-end 1)) > + (setq name (buffer-substring start end)) > + ;; This will also look it up in parent tables. > + ;; This is not on purpose, but it seems harmless. > + (list name (abbrev-symbol name table) start end))) > + ;; Restore point. > + (goto-char pos))) > + res))) > + > +(defvar abbrev-expand-function nil > + "Wrapper hook around `expand-abbrev'. > +The functions on this special hook are called with one argument: > +a function that performs the abbrev expansion.") > + > +(defun expand-abbrev () > + "Expand the abbrev before point, if there is an abbrev there. > +Effective when explicitly called even when `abbrev-mode' is nil. > +Returns the abbrev symbol, if expansion took place." > + (interactive) > + (run-hooks 'pre-abbrev-expand-hook) > + (abbrev-with-wrapper-hook abbrev-expand-function > + (destructuring-bind (&optional name sym wordstart wordend) > + (abbrev--before-point) > + (when sym > + (let ((value sym)) > + (unless (or ;; executing-kbd-macro > + noninteractive > + (window-minibuffer-p (selected-window))) > + ;; Add an undo boundary, in case we are doing this for > + ;; a self-inserting command which has avoided making one so > far. + (undo-boundary)) > + ;; Now sym is the abbrev symbol. > + (setq last-abbrev-text name) > + (setq last-abbrev sym) > + (setq last-abbrev-location wordstart) > + ;; Increment use count. > + (abbrev-put sym 'count (1+ (abbrev-get sym 'count))) > + ;; If this abbrev has an expansion, delete the abbrev > + ;; and insert the expansion. > + (when (stringp (symbol-value sym)) > + (goto-char wordend) > + (insert (symbol-value sym)) > + (delete-region wordstart wordend) > + (let ((case-fold-search nil)) > + (when (and (not (abbrev-get sym :case-preserve)) > + (string-match "[[:upper:]]" name)) > + (if (not (string-match "[[:lower:]]" name)) > + ;; Abbrev was all caps. If expansion is multiple > words, + ;; normally capitalize each word. > + (if (and (not abbrev-all-caps) > + (save-excursion > + (> (progn (backward-word 1) (point)) > + (progn (goto-char wordstart) > + (forward-word 1) (point))))) > + (upcase-initials-region wordstart (point)) > + (upcase-region wordstart (point))) > + ;; Abbrev included some caps. Cap first initial of > expansion. + (let ((end (point))) > + ;; Find the initial. > + (goto-char wordstart) > + (skip-syntax-forward "^w" (1- end)) > + ;; Change just that. > + (upcase-initials-region (point) (1+ (point)))))))) > + (when (symbol-function sym) > + (let* ((hook (symbol-function sym)) > + (expanded > + ;; If the abbrev has a hook function, run it. > + (funcall hook))) > + ;; In addition, if the hook function is a symbol with > + ;; a non-nil `no-self-insert' property, let the value it > + ;; returned specify whether we consider that an expansion > took + ;; place. If it returns nil, no expansion has been > done. + (if (and (symbolp hook) > + (null expanded) > + (get hook 'no-self-insert)) > + (setq value nil)))) > + value))))) > + > +(defun unexpand-abbrev () > + "Undo the expansion of the last abbrev that expanded. > +This differs from ordinary undo in that other editing done since then > +is not undone." > + (interactive) > + (save-excursion > + (unless (or (< last-abbrev-location (point-min)) > + (> last-abbrev-location (point-max))) > + (goto-char last-abbrev-location) > + (when (stringp last-abbrev-text) > + ;; This isn't correct if last-abbrev's hook was used > + ;; to do the expansion. > + (let ((val (symbol-value last-abbrev))) > + (unless (stringp val) > + (error "value of abbrev-symbol must be a string")) > + (delete-region (point) (+ (point) (length val))) > + ;; Don't inherit properties here; just copy from old contents. > + (insert last-abbrev-text) > + (setq last-abbrev-text nil)))))) > + > +(defun abbrev--write (sym) > + "Write the abbrev in a `read'able form. > +Only writes the non-system abbrevs. > +Presumes that `standard-output' points to `current-buffer'." > + (unless (or (null (symbol-value sym)) (abbrev-get sym 'system-flag)) > + (insert " (") > + (prin1 name) > + (insert " ") > + (prin1 (symbol-value sym)) > + (insert " ") > + (prin1 (symbol-function sym)) > + (insert " ") > + (prin1 (abbrev-get sym 'count)) > + (insert ")\n"))) > + > +(defun abbrev--describe (sym) > + (when (symbol-value sym) > + (prin1 (symbol-name sym)) > + (if (null (abbrev-get sym 'system-flag)) > + (indent-to 15 1) > + (insert " (sys)") > + (indent-to 20 1)) > + (prin1 (abbrev-get sym 'count)) > + (indent-to 20 1) > + (prin1 (symbol-value sym)) > + (when (symbol-function sym) > + (indent-to 45 1) > + (prin1 (symbol-function sym))) > + (terpri))) > + > +(defun insert-abbrev-table-description (name &optional readable) > + "Insert before point a full description of abbrev table named NAME. > +NAME is a symbol whose value is an abbrev table. > +If optional 2nd arg READABLE is non-nil, a human-readable description > +is inserted. Otherwise the description is an expression, > +a call to `define-abbrev-table', which would > +define the abbrev table NAME exactly as it is currently defined. > + > +Abbrevs marked as \"system abbrevs\" are omitted." > + (let ((table (symbol-value name)) > + (symbols ())) > + (mapatoms (lambda (sym) (if (symbol-value sym) (push sym symbols))) > table) + (setq symbols (sort symbols 'string-lessp)) > + (let ((standard-output (current-buffer))) > + (if readable > + (progn > + (insert "(") > + (prin1 name) > + (insert ")\n\n") > + (mapc 'abbrev--describe symbols) > + (insert "\n\n")) > + (insert "(define-abbrev-table '") > + (prin1 name) > + (insert " '(") > + (mapc 'abbrev--write symbols) > + (insert " ))\n\n")) > + nil))) > + > +(defun define-abbrev-table (tablename definitions > + &optional docstring &rest props) > + "Define TABLENAME (a symbol) as an abbrev table name. > +Define abbrevs in it according to DEFINITIONS, which is a list of elements > +of the form (ABBREVNAME EXPANSION HOOK USECOUNT SYSTEMFLAG). > +\(If the list is shorter than that, omitted elements default to nil). > +PROPS is a property list to apply to the table. > +Properties with special meaning: > +- `:parents' contains a list of abbrev tables from which this table > inherits + abbreviations. > +- `:case-preserve' non-nil means that abbreviations are lookedup without > + case-folding, and the expansion is not capitalized/upcased. > +- `:regexp' describes the form of abbrevs. It defaults to > \\<\\(\\w+\\)\\W* which + means that an abbrev can only be a single word. > The submatch 1 is treated + as the potential name of an abbrev. > +- `:enable-function' can be set to a function of no argument which returns > + non-nil iff the abbrevs in this table should be used for this instance > + of `expand-abbrev'." > + (let ((table (if (boundp tablename) (symbol-value tablename)))) > + (unless table > + (setq table (make-abbrev-table props)) > + (set tablename table) > + (push tablename abbrev-table-name-list)) > + (when (stringp docstring) > + (put tablename 'variable-documentation docstring)) > + (dolist (elt definitions) > + (apply 'define-abbrev table elt)))) > + > (provide 'abbrev) > > ;; arch-tag: dbd6f3ae-dfe3-40ba-b00f-f9e3ff960df5 > > > Diffs between emacs@sv.gnu.org/emacs--devo--0 and workfile end here. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-26 19:13 ` Andreas Röhler @ 2007-10-26 21:38 ` Stefan Monnier 2007-10-28 14:13 ` Andreas Röhler 0 siblings, 1 reply; 42+ messages in thread From: Stefan Monnier @ 2007-10-26 21:38 UTC (permalink / raw) To: Andreas Röhler; +Cc: Richard Stallman, emacs-devel > # patch -i patch.el abbrev.el Where does `patch.el' come from? I.e. how did you extract it from my email message? If you copy&paste it, then it's likely that trailing spaces got removed/messed up. Just pipe the whole raw email message to patch. > What about `case-sensitive'? For me as non-english > speaker `folding' is not easy to follow because of the > word-sence - as something knitting into. Sensitive as > awareness is understood from the beginning. Could work, but also suffers from the fact that the current behavior (which expands `sm' to `stefan monnier' but `Sm' to `Stefan Monnier'") can be considered as being "sensitive" to case. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-26 21:38 ` Stefan Monnier @ 2007-10-28 14:13 ` Andreas Röhler 2007-10-28 21:03 ` Stefan Monnier 0 siblings, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-10-28 14:13 UTC (permalink / raw) To: Stefan Monnier; +Cc: Glenn Morris, Richard Stallman, emacs-devel Am Freitag, 26. Oktober 2007 23:38 schrieben Sie: > > # patch -i patch.el abbrev.el > > Where does `patch.el' come from? I.e. how did you extract it from my > email message? If you copy&paste it, then it's likely that trailing spaces > got removed/messed up. Just pipe the whole raw email message to patch. Thanks. > > > What about `case-sensitive'? For me as non-english > > speaker `folding' is not easy to follow because of the > > word-sence - as something knitting into. Sensitive as > > awareness is understood from the beginning. > > Could work, but also suffers from the fact that the current behavior (which > expands `sm' to `stefan monnier' but `Sm' to `Stefan Monnier'") can be > considered as being "sensitive" to case. > Beside of case-sensitivity another theme is in the pipe: to allow multi-word abbrevs. XEmacs does but isn't perfect. As it's wanted for translations, cases in one language (abbrevs) should not predict i.e. force cases in expansion. Andreas Röhler ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-28 14:13 ` Andreas Röhler @ 2007-10-28 21:03 ` Stefan Monnier 2007-10-30 15:10 ` Andreas Röhler 2007-10-31 16:09 ` Juri Linkov 0 siblings, 2 replies; 42+ messages in thread From: Stefan Monnier @ 2007-10-28 21:03 UTC (permalink / raw) To: Andreas Röhler; +Cc: Glenn Morris, Richard Stallman, emacs-devel >> > What about `case-sensitive'? For me as non-english >> > speaker `folding' is not easy to follow because of the >> > word-sence - as something knitting into. Sensitive as >> > awareness is understood from the beginning. >> >> Could work, but also suffers from the fact that the current behavior (which >> expands `sm' to `stefan monnier' but `Sm' to `Stefan Monnier'") can be >> considered as being "sensitive" to case. >> > Beside of case-sensitivity another theme is in the > pipe: to allow multi-word abbrevs. Since I've installed my code this is now possible. Just set the :regexp property of the abbrev table accordingly. > As it's wanted for translations, cases in one language > (abbrevs) should not predict i.e. force cases in > expansion. It's easier to write ad-hoc code than to try and extend abbrevs to "do the right thing" for that kind of unusal situation. Stefan ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-28 21:03 ` Stefan Monnier @ 2007-10-30 15:10 ` Andreas Röhler 2007-10-31 7:47 ` Richard Stallman 2007-10-31 16:09 ` Juri Linkov 1 sibling, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-10-30 15:10 UTC (permalink / raw) To: emacs-devel; +Cc: Glenn Morris, Stefan Monnier, Richard Stallman [-- Attachment #1: Type: text/plain, Size: 1495 bytes --] Am Sonntag, 28. Oktober 2007 22:03 schrieb Stefan Monnier: > >> > What about `case-sensitive'? For me as non-english > >> > speaker `folding' is not easy to follow because of the > >> > word-sence - as something knitting into. Sensitive as > >> > awareness is understood from the beginning. > >> > >> Could work, but also suffers from the fact that the current behavior > >> (which expands `sm' to `stefan monnier' but `Sm' to `Stefan Monnier'") > >> can be considered as being "sensitive" to case. > > > > Beside of case-sensitivity another theme is in the > > pipe: to allow multi-word abbrevs. > > Since I've installed my code this is now possible. Just set the :regexp > property of the abbrev table accordingly. > > > As it's wanted for translations, cases in one language > > (abbrevs) should not predict i.e. force cases in > > expansion. > > It's easier to write ad-hoc code than to try and extend abbrevs to "do the > right thing" for that kind of unusal situation. > Attached a new abbrev.c with which you may choose the case for abbrevs freely: abbrev case takes no longer precedence for expansion for example wh => Whitehous Wh => whiteHouse WH => whiteHouse is possible at the same time. Some useless but harmless case-occurence counters are still in the code. Grüße Andreas Röhler (compiled with changed abbrev.c and CVS from october 28) GNU Emacs 23.0.50.2 (i686-pc-linux-gnu, GTK+ Version 2.10.6) of 2007-10-30 [-- Attachment #2: abbrev.c --] [-- Type: text/x-csrc, Size: 23939 bytes --] /* Primitives for word-abbrev mode. Copyright (C) 1985, 1986, 1993, 1996, 1998, 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. This file is part of GNU Emacs. GNU Emacs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Emacs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Emacs; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <config.h> #include <stdio.h> #include "lisp.h" #include "commands.h" #include "buffer.h" #include "window.h" #include "charset.h" #include "syntax.h" /* An abbrev table is an obarray. Each defined abbrev is represented by a symbol in that obarray whose print name is the abbreviation. The symbol's value is a string which is the expansion. If its function definition is non-nil, it is called after the expansion is done. The plist slot of the abbrev symbol is its usage count. */ /* List of all abbrev-table name symbols: symbols whose values are abbrev tables. */ Lisp_Object Vabbrev_table_name_list; /* The table of global abbrevs. These are in effect in any buffer in which abbrev mode is turned on. */ Lisp_Object Vglobal_abbrev_table; /* The local abbrev table used by default (in Fundamental Mode buffers) */ Lisp_Object Vfundamental_mode_abbrev_table; /* Set nonzero when an abbrev definition is changed */ int abbrevs_changed; int abbrev_all_caps; /* Non-nil => use this location as the start of abbrev to expand (rather than taking the word before point as the abbrev) */ Lisp_Object Vabbrev_start_location; /* Buffer that Vabbrev_start_location applies to */ Lisp_Object Vabbrev_start_location_buffer; /* The symbol representing the abbrev most recently expanded */ Lisp_Object Vlast_abbrev; /* A string for the actual text of the abbrev most recently expanded. This has more info than Vlast_abbrev since case is significant. */ Lisp_Object Vlast_abbrev_text; /* Character address of start of last abbrev expanded */ EMACS_INT last_abbrev_point; /* Hook to run before expanding any abbrev. */ Lisp_Object Vpre_abbrev_expand_hook, Qpre_abbrev_expand_hook; Lisp_Object Qsystem_type, Qcount, Qforce; \f DEFUN ("make-abbrev-table", Fmake_abbrev_table, Smake_abbrev_table, 0, 0, 0, doc: /* Create a new, empty abbrev table object. */) () { /* The value 59 is arbitrary chosen prime number. */ return Fmake_vector (make_number (59), make_number (0)); } DEFUN ("clear-abbrev-table", Fclear_abbrev_table, Sclear_abbrev_table, 1, 1, 0, doc: /* Undefine all abbrevs in abbrev table TABLE, leaving it empty. */) (table) Lisp_Object table; { int i, size; CHECK_VECTOR (table); size = XVECTOR (table)->size; abbrevs_changed = 1; for (i = 0; i < size; i++) XVECTOR (table)->contents[i] = make_number (0); return Qnil; } DEFUN ("define-abbrev", Fdefine_abbrev, Sdefine_abbrev, 3, 6, 0, doc: /* Define an abbrev in TABLE named NAME, to expand to EXPANSION and call HOOK. NAME must be a string, and should be lower-case. EXPANSION should usually be a string. To undefine an abbrev, define it with EXPANSION = nil. If HOOK is non-nil, it should be a function of no arguments; it is called after EXPANSION is inserted. If EXPANSION is not a string, the abbrev is a special one, which does not expand in the usual way but only runs HOOK. COUNT, if specified, gives the initial value for the abbrev's usage-count, which is incremented each time the abbrev is used. \(The default is zero.) SYSTEM-FLAG, if non-nil, says that this is a "system" abbreviation which should not be saved in the user's abbreviation file. Unless SYSTEM-FLAG is `force', a system abbreviation will not overwrite a non-system abbreviation of the same name. */) (table, name, expansion, hook, count, system_flag) Lisp_Object table, name, expansion, hook, count, system_flag; { Lisp_Object sym, oexp, ohook, tem; CHECK_VECTOR (table); CHECK_STRING (name); /* If defining a system abbrev, do not overwrite a non-system abbrev of the same name, unless 'force is used. */ if (!NILP (system_flag) && !EQ (system_flag, Qforce)) { sym = Fintern_soft (name, table); if (!NILP (SYMBOL_VALUE (sym)) && NILP (Fplist_get (XSYMBOL (sym)->plist, Qsystem_type))) return Qnil; } if (NILP (count)) count = make_number (0); else CHECK_NUMBER (count); sym = Fintern (name, table); oexp = SYMBOL_VALUE (sym); ohook = XSYMBOL (sym)->function; if (!((EQ (oexp, expansion) || (STRINGP (oexp) && STRINGP (expansion) && (tem = Fstring_equal (oexp, expansion), !NILP (tem)))) && (EQ (ohook, hook) || (tem = Fequal (ohook, hook), !NILP (tem)))) && NILP (system_flag)) abbrevs_changed = 1; Fset (sym, expansion); Ffset (sym, hook); if (! NILP (system_flag)) Fsetplist (sym, list4 (Qcount, count, Qsystem_type, system_flag)); else Fsetplist (sym, count); return name; } /* Check if the characters in ABBREV have word syntax in either the * current (if global == 0) or standard syntax table. */ static void abbrev_check_chars (abbrev, global) Lisp_Object abbrev; int global; { int i, i_byte, len, nbad = 0; int j, found, nuniq = 0; char *badchars, *baduniq; CHECK_STRING (abbrev); len = SCHARS (abbrev); badchars = (char *) alloca (len + 1); for (i = 0, i_byte = 0; i < len; ) { int c; FETCH_STRING_CHAR_ADVANCE (c, abbrev, i, i_byte); if (global) { /* Copied from SYNTAX in syntax.h, except using FOLLOW_PARENT. */ Lisp_Object syntax_temp = SYNTAX_ENTRY_FOLLOW_PARENT (Vstandard_syntax_table, c); if ( (CONSP (syntax_temp) ? (enum syntaxcode) (XINT (XCAR (syntax_temp)) & 0xff) : Swhitespace) != Sword ) badchars[nbad++] = c; } else if (SYNTAX (c) != Sword) badchars[nbad++] = c; } if (nbad == 0) return; baduniq = (char *) alloca (nbad + 1); for (i = 0; i < nbad; i++) { found = 0; for (j = 0; j < nuniq; j++) { if (badchars[i] == baduniq[j]) { found = 1; break; } } if (found) continue ; baduniq[nuniq++] = badchars[i]; } baduniq[nuniq] = '\0'; error ("Some abbrev characters (%s) are not word constituents %s", baduniq, global ? "in the standard syntax" : "in this mode" ); } DEFUN ("define-global-abbrev", Fdefine_global_abbrev, Sdefine_global_abbrev, 2, 2, "sDefine global abbrev: \nsExpansion for %s: ", doc: /* Define ABBREV as a global abbreviation for EXPANSION. The characters in ABBREV must all be word constituents in the standard syntax table. */) (abbrev, expansion) Lisp_Object abbrev, expansion; { abbrev_check_chars (abbrev, 1); Fdefine_abbrev (Vglobal_abbrev_table, abbrev, expansion, Qnil, make_number (0), Qnil); return abbrev; } DEFUN ("define-mode-abbrev", Fdefine_mode_abbrev, Sdefine_mode_abbrev, 2, 2, "sDefine mode abbrev: \nsExpansion for %s: ", doc: /* Define ABBREV as a mode-specific abbreviation for EXPANSION. The characters in ABBREV must all be word-constituents in the current mode. */) (abbrev, expansion) Lisp_Object abbrev, expansion; { if (NILP (current_buffer->abbrev_table)) error ("Major mode has no abbrev table"); abbrev_check_chars (abbrev, 0); Fdefine_abbrev (current_buffer->abbrev_table, abbrev, expansion, Qnil, make_number (0), Qnil); return abbrev; } DEFUN ("abbrev-symbol", Fabbrev_symbol, Sabbrev_symbol, 1, 2, 0, doc: /* Return the symbol representing abbrev named ABBREV. This symbol's name is ABBREV, but it is not the canonical symbol of that name; it is interned in an abbrev-table rather than the normal obarray. The value is nil if that abbrev is not defined. Optional second arg TABLE is abbrev table to look it up in. The default is to try buffer's mode-specific abbrev table, then global table. */) (abbrev, table) Lisp_Object abbrev, table; { Lisp_Object sym; CHECK_STRING (abbrev); if (!NILP (table)) sym = Fintern_soft (abbrev, table); else { sym = Qnil; if (!NILP (current_buffer->abbrev_table)) sym = Fintern_soft (abbrev, current_buffer->abbrev_table); if (NILP (SYMBOL_VALUE (sym))) sym = Qnil; if (NILP (sym)) sym = Fintern_soft (abbrev, Vglobal_abbrev_table); } if (NILP (SYMBOL_VALUE (sym))) return Qnil; return sym; } DEFUN ("abbrev-expansion", Fabbrev_expansion, Sabbrev_expansion, 1, 2, 0, doc: /* Return the string that ABBREV expands into in the current buffer. Optionally specify an abbrev table as second arg; then ABBREV is looked up in that table only. */) (abbrev, table) Lisp_Object abbrev, table; { Lisp_Object sym; sym = Fabbrev_symbol (abbrev, table); if (NILP (sym)) return sym; return Fsymbol_value (sym); } \f /* Expand the word before point, if it is an abbrev. Returns 1 if an expansion is done. */ DEFUN ("expand-abbrev", Fexpand_abbrev, Sexpand_abbrev, 0, 0, "", doc: /* Expand the abbrev before point, if there is an abbrev there. Effective when explicitly called even when `abbrev-mode' is nil. Returns the abbrev symbol, if expansion took place. */) () { register char *buffer, *p; int wordstart, wordend; register int wordstart_byte, wordend_byte, idx, idx_byte; int whitecnt; int uccount = 0, lccount = 0; register Lisp_Object sym; Lisp_Object expansion, hook, tem; Lisp_Object value; int multibyte = ! NILP (current_buffer->enable_multibyte_characters); value = Qnil; Frun_hooks (1, &Qpre_abbrev_expand_hook); wordstart = 0; if (!(BUFFERP (Vabbrev_start_location_buffer) && XBUFFER (Vabbrev_start_location_buffer) == current_buffer)) Vabbrev_start_location = Qnil; if (!NILP (Vabbrev_start_location)) { tem = Vabbrev_start_location; CHECK_NUMBER_COERCE_MARKER (tem); wordstart = XINT (tem); Vabbrev_start_location = Qnil; if (wordstart < BEGV || wordstart > ZV) wordstart = 0; if (wordstart && wordstart != ZV) { wordstart_byte = CHAR_TO_BYTE (wordstart); if (FETCH_BYTE (wordstart_byte) == '-') del_range (wordstart, wordstart + 1); } } if (!wordstart) wordstart = scan_words (PT, -1); if (!wordstart) return value; wordstart_byte = CHAR_TO_BYTE (wordstart); wordend = scan_words (wordstart, 1); if (!wordend) return value; if (wordend > PT) wordend = PT; wordend_byte = CHAR_TO_BYTE (wordend); whitecnt = PT - wordend; if (wordend <= wordstart) return value; p = buffer = (char *) alloca (wordend_byte - wordstart_byte); for (idx = wordstart, idx_byte = wordstart_byte; idx < wordend; ) { register int c; if (multibyte) { FETCH_CHAR_ADVANCE (c, idx, idx_byte); } else { c = FETCH_BYTE (idx_byte); idx++, idx_byte++; } /* ;; 2007-10-30 a.roehler@web.de changed section end */ /* if (UPPERCASEP (c)) */ /* c = DOWNCASE (c), uccount++; */ /* else if (! NOCASEP (c)) */ /* lccount++; */ if (multibyte) p += CHAR_STRING (c, p); else *p++ = c; } if (VECTORP (current_buffer->abbrev_table)) sym = oblookup (current_buffer->abbrev_table, buffer, wordend - wordstart, p - buffer); else XSETFASTINT (sym, 0); if (INTEGERP (sym) || NILP (SYMBOL_VALUE (sym))) sym = oblookup (Vglobal_abbrev_table, buffer, wordend - wordstart, p - buffer); if (INTEGERP (sym) || NILP (SYMBOL_VALUE (sym))) return value; if (INTERACTIVE && !EQ (minibuf_window, selected_window)) { /* Add an undo boundary, in case we are doing this for a self-inserting command which has avoided making one so far. */ SET_PT (wordend); Fundo_boundary (); } Vlast_abbrev_text = Fbuffer_substring (make_number (wordstart), make_number (wordend)); /* Now sym is the abbrev symbol. */ Vlast_abbrev = sym; value = sym; last_abbrev_point = wordstart; /* Increment use count. */ if (INTEGERP (XSYMBOL (sym)->plist)) XSETINT (XSYMBOL (sym)->plist, XINT (XSYMBOL (sym)->plist) + 1); else if (INTEGERP (tem = Fget (sym, Qcount))) Fput (sym, Qcount, make_number (XINT (tem) + 1)); /* If this abbrev has an expansion, delete the abbrev and insert the expansion. */ expansion = SYMBOL_VALUE (sym); if (STRINGP (expansion)) { SET_PT (wordstart); insert_from_string (expansion, 0, 0, SCHARS (expansion), SBYTES (expansion), 1); del_range_both (PT, PT_BYTE, wordend + (PT - wordstart), wordend_byte + (PT_BYTE - wordstart_byte), 1); SET_PT (PT + whitecnt); if (uccount && !lccount) { /* Abbrev was all caps */ /* If expansion is multiple words, normally capitalize each word */ /* This used to be if (!... && ... >= ...) Fcapitalize; else Fupcase but Megatest 68000 compiler can't handle that */ if (!abbrev_all_caps) if (scan_words (PT, -1) > scan_words (wordstart, 1)) { Fupcase_initials_region (make_number (wordstart), make_number (PT)); goto caped; } /* If expansion is one word, or if user says so, upcase it all. */ Fupcase_region (make_number (wordstart), make_number (PT)); caped: ; } else if (uccount) { /* Abbrev included some caps. Cap first initial of expansion */ int pos = wordstart_byte; /* Find the initial. */ while (pos < PT_BYTE && SYNTAX (*BUF_BYTE_ADDRESS (current_buffer, pos)) != Sword) pos++; /* Change just that. */ pos = BYTE_TO_CHAR (pos); Fupcase_initials_region (make_number (pos), make_number (pos + 1)); } } hook = XSYMBOL (sym)->function; if (!NILP (hook)) { Lisp_Object expanded, prop; /* If the abbrev has a hook function, run it. */ expanded = call0 (hook); /* In addition, if the hook function is a symbol with a non-nil `no-self-insert' property, let the value it returned specify whether we consider that an expansion took place. If it returns nil, no expansion has been done. */ if (SYMBOLP (hook) && NILP (expanded) && (prop = Fget (hook, intern ("no-self-insert")), !NILP (prop))) value = Qnil; } return value; } DEFUN ("unexpand-abbrev", Funexpand_abbrev, Sunexpand_abbrev, 0, 0, "", doc: /* Undo the expansion of the last abbrev that expanded. This differs from ordinary undo in that other editing done since then is not undone. */) () { int opoint = PT; int adjust = 0; if (last_abbrev_point < BEGV || last_abbrev_point > ZV) return Qnil; SET_PT (last_abbrev_point); if (STRINGP (Vlast_abbrev_text)) { /* This isn't correct if Vlast_abbrev->function was used to do the expansion */ Lisp_Object val; int zv_before; val = SYMBOL_VALUE (Vlast_abbrev); if (!STRINGP (val)) error ("Value of `abbrev-symbol' must be a string"); zv_before = ZV; del_range_byte (PT_BYTE, PT_BYTE + SBYTES (val), 1); /* Don't inherit properties here; just copy from old contents. */ insert_from_string (Vlast_abbrev_text, 0, 0, SCHARS (Vlast_abbrev_text), SBYTES (Vlast_abbrev_text), 0); Vlast_abbrev_text = Qnil; /* Total number of characters deleted. */ adjust = ZV - zv_before; } SET_PT (last_abbrev_point < opoint ? opoint + adjust : opoint); return Qnil; } \f static void write_abbrev (sym, stream) Lisp_Object sym, stream; { Lisp_Object name, count, system_flag; if (INTEGERP (XSYMBOL (sym)->plist)) { count = XSYMBOL (sym)->plist; system_flag = Qnil; } else { count = Fget (sym, Qcount); system_flag = Fget (sym, Qsystem_type); } if (NILP (SYMBOL_VALUE (sym)) || ! NILP (system_flag)) return; insert (" (", 5); name = SYMBOL_NAME (sym); Fprin1 (name, stream); insert (" ", 1); Fprin1 (SYMBOL_VALUE (sym), stream); insert (" ", 1); Fprin1 (XSYMBOL (sym)->function, stream); insert (" ", 1); Fprin1 (count, stream); insert (")\n", 2); } static void describe_abbrev (sym, stream) Lisp_Object sym, stream; { Lisp_Object one, count, system_flag; if (INTEGERP (XSYMBOL (sym)->plist)) { count = XSYMBOL (sym)->plist; system_flag = Qnil; } else { count = Fget (sym, Qcount); system_flag = Fget (sym, Qsystem_type); } if (NILP (SYMBOL_VALUE (sym))) return; one = make_number (1); Fprin1 (Fsymbol_name (sym), stream); if (!NILP (system_flag)) { insert_string (" (sys)"); Findent_to (make_number (20), one); } else Findent_to (make_number (15), one); Fprin1 (count, stream); Findent_to (make_number (20), one); Fprin1 (SYMBOL_VALUE (sym), stream); if (!NILP (XSYMBOL (sym)->function)) { Findent_to (make_number (45), one); Fprin1 (XSYMBOL (sym)->function, stream); } Fterpri (stream); } static void record_symbol (sym, list) Lisp_Object sym, list; { XSETCDR (list, Fcons (sym, XCDR (list))); } DEFUN ("insert-abbrev-table-description", Finsert_abbrev_table_description, Sinsert_abbrev_table_description, 1, 2, 0, doc: /* Insert before point a full description of abbrev table named NAME. NAME is a symbol whose value is an abbrev table. If optional 2nd arg READABLE is non-nil, a human-readable description is inserted. Otherwise the description is an expression, a call to `define-abbrev-table', which would define the abbrev table NAME exactly as it is currently defined. Abbrevs marked as "system abbrevs" are normally omitted. However, if READABLE is non-nil, they are listed. */) (name, readable) Lisp_Object name, readable; { Lisp_Object table; Lisp_Object symbols; Lisp_Object stream; CHECK_SYMBOL (name); table = Fsymbol_value (name); CHECK_VECTOR (table); XSETBUFFER (stream, current_buffer); symbols = Fcons (Qnil, Qnil); map_obarray (table, record_symbol, symbols); symbols = XCDR (symbols); symbols = Fsort (symbols, Qstring_lessp); if (!NILP (readable)) { insert_string ("("); Fprin1 (name, stream); insert_string (")\n\n"); while (! NILP (symbols)) { describe_abbrev (XCAR (symbols), stream); symbols = XCDR (symbols); } insert_string ("\n\n"); } else { insert_string ("(define-abbrev-table '"); Fprin1 (name, stream); insert_string (" '(\n"); while (! NILP (symbols)) { write_abbrev (XCAR (symbols), stream); symbols = XCDR (symbols); } insert_string (" ))\n\n"); } return Qnil; } \f DEFUN ("define-abbrev-table", Fdefine_abbrev_table, Sdefine_abbrev_table, 2, 2, 0, doc: /* Define TABLENAME (a symbol) as an abbrev table name. Define abbrevs in it according to DEFINITIONS, which is a list of elements of the form (ABBREVNAME EXPANSION HOOK USECOUNT SYSTEMFLAG). \(If the list is shorter than that, omitted elements default to nil). */) (tablename, definitions) Lisp_Object tablename, definitions; { Lisp_Object name, exp, hook, count; Lisp_Object table, elt, sys; CHECK_SYMBOL (tablename); table = Fboundp (tablename); if (NILP (table) || (table = Fsymbol_value (tablename), NILP (table))) { table = Fmake_abbrev_table (); Fset (tablename, table); Vabbrev_table_name_list = Fcons (tablename, Vabbrev_table_name_list); } CHECK_VECTOR (table); for (; CONSP (definitions); definitions = XCDR (definitions)) { elt = XCAR (definitions); name = Fcar (elt); elt = Fcdr (elt); exp = Fcar (elt); elt = Fcdr (elt); hook = Fcar (elt); elt = Fcdr (elt); count = Fcar (elt); elt = Fcdr (elt); sys = Fcar (elt); Fdefine_abbrev (table, name, exp, hook, count, sys); } return Qnil; } \f void syms_of_abbrev () { Qsystem_type = intern ("system-type"); staticpro (&Qsystem_type); Qcount = intern ("count"); staticpro (&Qcount); Qforce = intern ("force"); staticpro (&Qforce); DEFVAR_LISP ("abbrev-table-name-list", &Vabbrev_table_name_list, doc: /* List of symbols whose values are abbrev tables. */); Vabbrev_table_name_list = Fcons (intern ("fundamental-mode-abbrev-table"), Fcons (intern ("global-abbrev-table"), Qnil)); DEFVAR_LISP ("global-abbrev-table", &Vglobal_abbrev_table, doc: /* The abbrev table whose abbrevs affect all buffers. Each buffer may also have a local abbrev table. If it does, the local table overrides the global one for any particular abbrev defined in both. */); Vglobal_abbrev_table = Fmake_abbrev_table (); DEFVAR_LISP ("fundamental-mode-abbrev-table", &Vfundamental_mode_abbrev_table, doc: /* The abbrev table of mode-specific abbrevs for Fundamental Mode. */); Vfundamental_mode_abbrev_table = Fmake_abbrev_table (); current_buffer->abbrev_table = Vfundamental_mode_abbrev_table; buffer_defaults.abbrev_table = Vfundamental_mode_abbrev_table; DEFVAR_LISP ("last-abbrev", &Vlast_abbrev, doc: /* The abbrev-symbol of the last abbrev expanded. See `abbrev-symbol'. */); DEFVAR_LISP ("last-abbrev-text", &Vlast_abbrev_text, doc: /* The exact text of the last abbrev expanded. A value of nil means the abbrev has already been unexpanded. */); DEFVAR_INT ("last-abbrev-location", &last_abbrev_point, doc: /* The location of the start of the last abbrev expanded. */); Vlast_abbrev = Qnil; Vlast_abbrev_text = Qnil; last_abbrev_point = 0; DEFVAR_LISP ("abbrev-start-location", &Vabbrev_start_location, doc: /* Buffer position for `expand-abbrev' to use as the start of the abbrev. When nil, use the word before point as the abbrev. Calling `expand-abbrev' sets this to nil. */); Vabbrev_start_location = Qnil; DEFVAR_LISP ("abbrev-start-location-buffer", &Vabbrev_start_location_buffer, doc: /* Buffer that `abbrev-start-location' has been set for. Trying to expand an abbrev in any other buffer clears `abbrev-start-location'. */); Vabbrev_start_location_buffer = Qnil; DEFVAR_BOOL ("abbrevs-changed", &abbrevs_changed, doc: /* Set non-nil by defining or altering any word abbrevs. This causes `save-some-buffers' to offer to save the abbrevs. */); abbrevs_changed = 0; DEFVAR_BOOL ("abbrev-all-caps", &abbrev_all_caps, doc: /* *Set non-nil means expand multi-word abbrevs all caps if abbrev was so. */); abbrev_all_caps = 0; DEFVAR_LISP ("pre-abbrev-expand-hook", &Vpre_abbrev_expand_hook, doc: /* Function or functions to be called before abbrev expansion is done. This is the first thing that `expand-abbrev' does, and so this may change the current abbrev table before abbrev lookup happens. */); Vpre_abbrev_expand_hook = Qnil; Qpre_abbrev_expand_hook = intern ("pre-abbrev-expand-hook"); staticpro (&Qpre_abbrev_expand_hook); defsubr (&Smake_abbrev_table); defsubr (&Sclear_abbrev_table); defsubr (&Sdefine_abbrev); defsubr (&Sdefine_global_abbrev); defsubr (&Sdefine_mode_abbrev); defsubr (&Sabbrev_expansion); defsubr (&Sabbrev_symbol); defsubr (&Sexpand_abbrev); defsubr (&Sunexpand_abbrev); defsubr (&Sinsert_abbrev_table_description); defsubr (&Sdefine_abbrev_table); } /* arch-tag: b721db69-f633-44a8-a361-c275acbdad7d (do not change this comment) */ [-- Attachment #3: Type: text/plain, Size: 142 bytes --] _______________________________________________ Emacs-devel mailing list Emacs-devel@gnu.org http://lists.gnu.org/mailman/listinfo/emacs-devel ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-30 15:10 ` Andreas Röhler @ 2007-10-31 7:47 ` Richard Stallman 2007-11-03 20:29 ` Stephen J. Turnbull 0 siblings, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-10-31 7:47 UTC (permalink / raw) To: Andreas Röhler; +Cc: rgm, monnier, emacs-devel Moving the abbrev code to Lisp is a step forward; I don't want to go back to using C code. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-31 7:47 ` Richard Stallman @ 2007-11-03 20:29 ` Stephen J. Turnbull 2007-11-04 0:11 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Stephen J. Turnbull @ 2007-11-03 20:29 UTC (permalink / raw) To: rms; +Cc: rgm, Andreas Röhler, monnier, emacs-devel Richard Stallman writes: > Moving the abbrev code to Lisp is a step forward; I don't want to go back > to using C code. FWIW, I agree. XEmacs's C implementation should not be used as an excuse to go backwards here. In fact (Richard, would you please confirm?) it may be a good idea to use the Lisp implementation as a base to avoid legal issues if it looks "too much like" XEmacs code (the problem is that AFAIK you have looked at the XEmacs code, so couldn't swear that it's not an unintentional copy of someone else's code). ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-11-03 20:29 ` Stephen J. Turnbull @ 2007-11-04 0:11 ` Richard Stallman 2007-11-04 2:42 ` Stephen J. Turnbull 0 siblings, 1 reply; 42+ messages in thread From: Richard Stallman @ 2007-11-04 0:11 UTC (permalink / raw) To: Stephen J. Turnbull; +Cc: rgm, andreas.roehler, monnier, emacs-devel In fact (Richard, would you please confirm?) it may be a good idea to use the Lisp implementation as a base to avoid legal issues if it looks "too much like" XEmacs code (the problem is that AFAIK you have looked at the XEmacs code, so couldn't swear that it's not an unintentional copy of someone else's code). That is valid in general, but is it an issue here? Our old C code was written by me, mostly. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-11-04 0:11 ` Richard Stallman @ 2007-11-04 2:42 ` Stephen J. Turnbull 2007-11-04 21:06 ` Andreas Röhler 0 siblings, 1 reply; 42+ messages in thread From: Stephen J. Turnbull @ 2007-11-04 2:42 UTC (permalink / raw) To: rms; +Cc: rgm, andreas.roehler, monnier, emacs-devel Richard Stallman writes: > In fact (Richard, would you please confirm?) it may be a good idea to > use the Lisp implementation as a base to avoid legal issues if it > looks "too much like" XEmacs code (the problem is that AFAIK you have > looked at the XEmacs code, so couldn't swear that it's not an > unintentional copy of someone else's code). > > That is valid in general, but is it an issue here? Our old C code was > written by me, mostly. Well, of course any code in Emacs has all the necessary papers. But as I understand it, Andreas wants to code in C so he can borrow techniques he saw in the XEmacs code. It's fairly likely that code varies significantly from your code. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-11-04 2:42 ` Stephen J. Turnbull @ 2007-11-04 21:06 ` Andreas Röhler 2007-11-05 8:47 ` Richard Stallman 0 siblings, 1 reply; 42+ messages in thread From: Andreas Röhler @ 2007-11-04 21:06 UTC (permalink / raw) To: emacs-devel Cc: Glenn Morris, Richard Stallman, Juri Linkov, Stefan Monnier, Stephen Turnbull Am Sonntag, 4. November 2007 03:42 schrieb Stephen J. Turnbull: > Richard Stallman writes: > > In fact (Richard, would you please confirm?) it may be a good idea > > to use the Lisp implementation as a base to avoid legal issues if it > > looks "too much like" XEmacs code (the problem is that AFAIK you have > > looked at the XEmacs code, so couldn't swear that it's not an > > unintentional copy of someone else's code). > > > > That is valid in general, but is it an issue here? Our old C code was > > written by me, mostly. > > Well, of course any code in Emacs has all the necessary papers. > > But as I understand it, Andreas wants to code in C so he can borrow > techniques he saw in the XEmacs code. It's fairly likely that code > varies significantly from your code. > The head of abbrev.c shows remarks concerning authors and displays GPL, everything looks fine for me. Maybe exists a precise reason not to use that code for GNU Emacs? XEmacs don't rely on single word abbrevs to be expanded, but takes several words too. That's of interest with NLP/translations and it's coded in C. As it's at stake to look back or forward, speed difference between Lisp and C execution might be significant. At least that idea was in my head... Thanks all Andreas Röhler ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-11-04 21:06 ` Andreas Röhler @ 2007-11-05 8:47 ` Richard Stallman 0 siblings, 0 replies; 42+ messages in thread From: Richard Stallman @ 2007-11-05 8:47 UTC (permalink / raw) To: Andreas Röhler; +Cc: rgm, emacs-devel, juri, monnier, stephen We will not install C code for abbrevs. Please understand that this is a final decision. ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-28 21:03 ` Stefan Monnier 2007-10-30 15:10 ` Andreas Röhler @ 2007-10-31 16:09 ` Juri Linkov 2007-10-31 17:44 ` Stefan Monnier 1 sibling, 1 reply; 42+ messages in thread From: Juri Linkov @ 2007-10-31 16:09 UTC (permalink / raw) To: Stefan Monnier; +Cc: andreas.roehler, emacs-devel >> Beside of case-sensitivity another theme is in the >> pipe: to allow multi-word abbrevs. > > Since I've installed my code this is now possible. Just set the :regexp > property of the abbrev table accordingly. It's easy to write such code now to do this indeed, thanks. For example, (defvar dict '(("multi-word abbrev" "multi-word expansion") ;; ... )) (dolist (d dict) (define-abbrev global-abbrev-table (car d) (cadr d))) (abbrev-table-put global-abbrev-table :regexp (regexp-opt (mapcar 'car dict) 'words)) But maybe abbrev-mode should try to update :regexp automatically when the user interactively defines a new multi-word abbrev? So instead of giving the error message "Some abbrev characters are not word constituents" it could run something like: (let ((abbrevs ())) (mapatoms (lambda (sym) (if (symbol-value sym) (push (symbol-name sym) abbrevs))) global-abbrev-table) (abbrev-table-put global-abbrev-table :regexp (regexp-opt abbrevs 'words))) to rebuild :regexp from all abbrevs when at least one of them is not word constituent? -- Juri Linkov http://www.jurta.org/emacs/ ^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: Testing new abbrev tables in elisp 2007-10-31 16:09 ` Juri Linkov @ 2007-10-31 17:44 ` Stefan Monnier 0 siblings, 0 replies; 42+ messages in thread From: Stefan Monnier @ 2007-10-31 17:44 UTC (permalink / raw) To: Juri Linkov; +Cc: andreas.roehler, emacs-devel > But maybe abbrev-mode should try to update :regexp automatically when the > user interactively defines a new multi-word abbrev? So instead of giving > the error message "Some abbrev characters are not word constituents" it > could run something like: > (let ((abbrevs ())) > (mapatoms (lambda (sym) > (if (symbol-value sym) (push (symbol-name sym) abbrevs))) > global-abbrev-table) > (abbrev-table-put global-abbrev-table :regexp (regexp-opt abbrevs 'words))) > to rebuild :regexp from all abbrevs when at least one of them is not > word constituent? Indeed it could. It could even rebuild it dynamically from the abbrev table's :enable-function by looking at the :abbrev-table-modiff value to detect changes. Stefan PS: I believe I've provided enough hooks to do most such crazy things. My own personal interest in improving abbrevs was in their use for skeletons, so I'll let other people work on other aspects. ^ permalink raw reply [flat|nested] 42+ messages in thread
end of thread, other threads:[~2007-11-05 8:47 UTC | newest] Thread overview: 42+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2007-06-20 17:48 Abbrev should preserve case Andreas Röhler 2007-06-20 22:18 ` Glenn Morris 2007-06-21 3:47 ` Davis Herring 2007-06-21 4:14 ` Stefan Monnier 2007-06-21 7:10 ` Andreas Röhler 2007-06-21 8:01 ` Stefan Monnier 2007-06-21 9:00 ` Andreas Röhler 2007-06-21 7:00 ` Glenn Morris 2007-06-21 17:32 ` Richard Stallman 2007-06-21 19:02 ` Stefan Monnier 2007-06-22 16:25 ` Richard Stallman 2007-06-22 17:51 ` Stefan Monnier 2007-06-22 21:53 ` Richard Stallman 2007-10-10 21:14 ` Abbrev tables in elisp with some extra stuff (was: Abbrev should preserve case) Stefan Monnier 2007-10-11 19:57 ` Andreas Röhler 2007-10-12 15:59 ` Richard Stallman 2007-10-12 21:26 ` Abbrev tables in elisp with some extra stuff Stefan Monnier 2007-10-13 6:41 ` Richard Stallman 2007-10-14 17:45 ` Andreas Röhler 2007-10-15 16:04 ` Richard Stallman 2007-10-15 18:04 ` Andreas Röhler 2007-10-16 4:10 ` Richard Stallman 2007-10-16 20:26 ` Stefan Monnier 2007-10-17 5:03 ` Richard Stallman 2007-10-17 14:10 ` Richard Stallman 2007-10-17 20:48 ` Richard Stallman 2007-10-24 2:21 ` Stefan Monnier 2007-10-25 2:10 ` Richard Stallman 2007-10-26 5:44 ` Testing new abbrev tables in elisp Stefan Monnier 2007-10-26 19:13 ` Andreas Röhler 2007-10-26 21:38 ` Stefan Monnier 2007-10-28 14:13 ` Andreas Röhler 2007-10-28 21:03 ` Stefan Monnier 2007-10-30 15:10 ` Andreas Röhler 2007-10-31 7:47 ` Richard Stallman 2007-11-03 20:29 ` Stephen J. Turnbull 2007-11-04 0:11 ` Richard Stallman 2007-11-04 2:42 ` Stephen J. Turnbull 2007-11-04 21:06 ` Andreas Röhler 2007-11-05 8:47 ` Richard Stallman 2007-10-31 16:09 ` Juri Linkov 2007-10-31 17:44 ` Stefan Monnier
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).