Mercurial > hg > xemacs-beta
diff lisp/utils/text-props.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/utils/text-props.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,579 @@ +;;; text-props.el --- implements properties of characters + +;; Copyright (C) 1993, 1994 Free Software Foundation, Inc. +;; Copyright (C) 1995 Amdahl Corporation. +;; Copyright (C) 1995 Ben Wing. + +;; Keywords: extensions, wp, faces +;; Author: Jamie Zawinski <jwz@lucid.com> +;; Modified: Ben Wing <wing@666.com> -- many of the Lisp functions below +;; were completely broken. +;; +;; This file is part of XEmacs. + +;; XEmacs 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 2, or (at your option) +;; any later version. + +;; XEmacs 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 XEmacs; see the file COPYING. If not, write to +;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Synched up with: Not in FSF. + +;;; Commentary: + +;;; This is a nearly complete implementation of the FSF19 text properties API. +;;; Please let me know if you notice any differences in behavior between +;;; this implementation and the FSF implementation. +;;; +;;; However, keep in mind that this interface has been implemented because it +;;; is useful. Compatibility with code written for FSF19 is a secondary goal +;;; to having a clean and useful interface. +;;; +;;; The cruftier parts of the FSF API, such as the special handling of +;;; properties like `mouse-face', `front-sticky', and other properties whose +;;; value is a list of names of *other* properties set at this position, are +;;; not implemented. The reason for this is that if you feel you need that +;;; kind of functionality, it's a good hint that you should be using extents +;;; instead of text properties. +;;; +;;; When should I use Text Properties, and when should I use Extents? +;;; ================================================================== +;;; +;;; If you are putting a `button' or `hyperlink' of some kind into a buffer, +;;; the most natural interface is one which deals with properties of regions +;;; with explicit endpoints that behave more-or-less like markers. That is +;;; what `make-extent', `extent-at', and `extent-property' are for. +;;; +;;; If you are dealing with styles of text, where things do not have explicit +;;; endpoints (as is done in font-lock.el and shell-font.el) or if you want to +;;; partition a buffer (that is, change some attribute of a range from one +;;; value to another without disturbing the properties outside of that range) +;;; then an interface that deals with properties of characters may be most +;;; natural. +;;; +;;; Another way of thinking of it is, do you care where the endpoints of the +;;; region are? If you do, then you should use extents. If it's ok for the +;;; region to become divided, and for two regions with identical properties to +;;; be merged into one region, then you might want to use text properties. +;;; +;;; Some applications want the attributes they add to be copied by the killing +;;; and yanking commands, and some do not. This is orthogonal to whether text +;;; properties or extents are used. Remember that text properties are +;;; implemented in terms of extents, so anything you can do with one you can +;;; do with the other. It's just a matter of which way of creating and +;;; managing them is most appropriate to your application. +;;; +;;; Implementation details: +;;; ======================= +;;; +;;; This package uses extents with a non-nil 'text-prop property. It assumes +;;; free reign over the endpoints of any extent with that property. It will +;;; not alter any extent which does not have that property. +;;; +;;; Right now, the text-property functions create one extent for each distinct +;;; property; that is, if a range of text has two text-properties on it, there +;;; will be two extents. As the set of text-properties is going to be small, +;;; this is probably not a big deal. It would be possible to share extents. +;;; +;;; One tricky bit is that undo/kill/yank must be made to not fragment things: +;;; these extents must not be allowed to overlap. We accomplish this by using +;;; a custom `paste-function' property on the extents. +;;; +;;; shell-font.el and font-lock.el could put-text-property to attach fonts to +;;; the buffer. However, what these packages are interested in is the +;;; efficient extent partitioning behavior which this code exhibits, not the +;;; duplicability aspect of it. In fact, either of these packages could be be +;;; implemented by creating a one-character non-expandable extent for each +;;; character in the buffer, except that that would be extremely wasteful of +;;; memory. (Redisplay performance would be fine, however.) +;;; +;;; If these packages were to use put-text-property to make the extents, then +;;; when one copied text from a shell buffer or a font-locked source buffer +;;; and pasted it somewhere else (a sendmail buffer, or a buffer not in +;;; font-lock mode) then the fonts would follow, and there's no easy way to +;;; get rid of them (other than pounding out a call to put-text-property by +;;; hand.) This is annoying. Maybe it wouldn't be so annoying if there was a +;;; more general set of commands for handling styles of text (in fact, if +;;; there were such a thing, copying the fonts would probably be exactly what +;;; one wanted) but we aren't there yet. So these packages use the interface +;;; of `put-nonduplicable-text-property' which is the same, except that it +;;; doesn't make duplicable extents. +;;; +;;; `put-text-property' and `put-nonduplicable-text-property' don't get along: +;;; they will interfere with each other, reusing each others' extents without +;;; checking that the "duplicableness" is correct. This is a bug, but it's +;;; one that I don't care enough to fix this right now. + + +;;; Code: + + +;; The following functions were ported to C for speed; the overhead of doing +;; this many full lisp function calls was not small. + +;; #### The C functions have changed since then; the Lisp equivalents +;; should be updated. + +;(defun put-text-property (start end prop value &optional buffer) +; "Adds the given property/value to all characters in the specified region. +;The property is conceptually attached to the characters rather than the +;region. The properties are copied when the characters are copied/pasted." +; (put-text-property-1 start end prop value buffer t) +; prop) +; +;(defun put-nonduplicable-text-property (start end prop value &optional buffer) +; "Adds the given property/value to all characters in the specified region. +;The property is conceptually attached to the characters rather than the +;region, however the properties will not be copied the characters are copied." +; (put-text-property-1 start end prop value buffer nil) +; prop) +; +;(defun put-text-property-1 (start end prop value buffer duplicable) +; ;; returns whether any property of a character was changed +; (if (= start end) +; nil +; (save-excursion +; (and buffer (set-buffer buffer)) +; (let ((the-extent nil) +; (changed nil)) +; ;; prop, value, the-extent, start, end, and changed are of dynamic +; ;; scope. changed and the-extent are assigned. +; (map-extents (function put-text-property-mapper) nil +; (max 1 (1- start)) +; (min (buffer-size) (1+ end))) +; +; ;; If we made it through the loop without reusing an extent +; ;; (and we want there to be one) make it now. +; (cond ((and value (not the-extent)) +; (setq the-extent (make-extent start end)) +; (set-extent-property the-extent 'text-prop prop) +; (set-extent-property the-extent prop value) +; (setq changed t) +; (cond (duplicable +; (set-extent-property the-extent 'duplicable t) +; (set-extent-property the-extent 'paste-function +; 'text-prop-extent-paste-function))) +; )) +; changed)))) +; +;(defun put-text-property-mapper (e ignore) +; ;; prop, value, the-extent, start, end, and changed are of dynamic scope. +; ;; changed and the-extent are assigned. +; (let ((e-start (extent-start-position e)) +; (e-end (extent-end-position e)) +; (e-val (extent-property e prop))) +; (cond ((not (eq (extent-property e 'text-prop) prop)) +; ;; It's not for this property; do nothing. +; nil) +; +; ((and value +; (not the-extent) +; (eq value e-val)) +; ;; we want there to be an extent here at the end, and we haven't +; ;; picked one yet, so use this one. Extend it as necessary. +; ;; We only reuse an extent which has an EQ value for the prop in +; ;; question to avoid side-effecting the kill ring (that is, we +; ;; never change the property on an extent after it has been +; ;; created.) +; (cond +; ((or (/= e-start start) (/= e-end end)) +; (set-extent-endpoints e (min e-start start) (max e-end end)) +; (setq changed t))) +; (setq the-extent e)) +; +; ;; Even if we're adding a prop, at this point, we want all other +; ;; extents of this prop to go away (as now they overlap.) +; ;; So the theory here is that, when we are adding a prop to a +; ;; region that has multiple (disjoint) occurences of that prop +; ;; in it already, we pick one of those and extend it, and remove +; ;; the others. +; +; ((eq e the-extent) +; ;; just in case map-extents hits it again (does that happen?) +; nil) +; +; ((and (>= e-start start) +; (<= e-end end)) +; ;; extent is contained in region; remove it. Don't destroy or +; ;; modify it, because we don't want to change the attributes +; ;; pointed to by the duplicates in the kill ring. +; (setq changed t) +; (detach-extent e)) +; +; ((and the-extent +; (eq value e-val) +; (<= e-start end) +; (>= e-end start)) +; ;; this extent overlaps, and has the same prop/value as the +; ;; extent we've decided to reuse, so we can remove this existing +; ;; extent as well (the whole thing, even the part outside of the +; ;; region) and extend the-extent to cover it, resulting in the +; ;; minimum number of extents in the buffer. +; (cond +; ((and (/= (extent-start-position the-extent) e-start) +; (/= (extent-end-position the-extent) e-end)) +; (set-extent-endpoints the-extent +; (min (extent-start-position the-extent) +; e-start) +; (max (extent-end-position the-extent) +; e-end)) +; (setq changed t))) +; (detach-extent e)) +; +; ((<= (extent-end-position e) end) +; ;; extent begins before start but ends before end, +; ;; so we can just decrease its end position. +; (if (and (= (extent-start-position e) e-start) +; (= (extent-end-position e) start)) +; nil +; (set-extent-endpoints e e-start start) +; (setq changed t))) +; +; ((>= (extent-start-position e) start) +; ;; extent ends after end but begins after start, +; ;; so we can just increase its start position. +; (if (and (= (extent-start-position e) end) +; (= (extent-start-position e) e-end)) +; nil +; (set-extent-endpoints e end e-end) +; (setq changed t))) +; +; (t +; ;; Otherwise, the extent straddles the region. +; ;; We need to split it. +; (set-extent-endpoints e e-start start) +; (setq e (copy-extent e)) +; (set-extent-endpoints e end e-end) +; (setq changed t)))) +; ;; return nil to continue mapping over region. +; nil) +; +; +;(defun text-prop-extent-paste-function (extent from to) +; ;; Whenever a text-prop extent is pasted into a buffer (via `yank' or +; ;; `insert' or whatever) we attach the properties to the buffer by calling +; ;; `put-text-property' instead of by simply alowing the extent to be copied +; ;; or re-attached. Then we return nil, telling the C code not to attach +; ;; it again. By handing the insertion hackery in this way, we make kill/yank +; ;; behave consistently iwth put-text-property and not fragment the extents +; ;; (since text-prop extents must partition, not overlap.) +; (let* ((prop (or (extent-property extent 'text-prop) +; (error "internal error: no text-prop on %S" extent))) +; (val (or (extent-property extent prop) +; (error "internal error: no text-prop %S on %S" +; prop extent)))) +; (put-text-property from to prop val) +; nil)) +; +;(defun add-text-properties (start end props &optional buffer) +; "Add properties to the characters from START to END. +;The third argument PROPS is a property list specifying the property values +;to add. The optional fourth argument, OBJECT, is the buffer containing the +;text. Returns t if any property was changed, nil otherwise." +; (let ((changed nil)) +; (while props +; (setq changed +; (or (put-text-property-1 start end (car props) (car (cdr props)) +; buffer t) +; changed)) +; (setq props (cdr (cdr props)))) +; changed)) +; +;(defun remove-text-properties (start end props &optional buffer) +; "Remove the given properties from all characters in the specified region. +;PROPS should be a plist, but the values in that plist are ignored (treated +;as nil.) Returns t if any property was changed, nil otherwise." +; (let ((changed nil)) +; (while props +; (setq changed +; (or (put-text-property-1 start end (car props) nil buffer t) +; changed)) +; (setq props (cdr (cdr props)))) +; changed)) +; + +(defun set-text-properties (start end props &optional buffer-or-string) + "You should NEVER use this function. It is ideologically blasphemous. +It is provided only to ease porting of broken FSF Emacs programs. + +Completely replace properties of text from START to END. +The third argument PROPS is the new property list. +The optional fourth argument, BUFFER-OR-STRING, +is the string or buffer containing the text." + (map-extents #'(lambda (extent ignored) + (remove-text-properties start end + (list (extent-property extent + 'text-prop) + nil) + buffer-or-string)) + buffer-or-string start end nil nil 'text-prop) + (add-text-properties start end props buffer-or-string)) + + +;;; The following functions can probably stay in lisp, since they're so simple. + +;(defun get-text-property (pos prop &optional buffer) +; "Returns the value of the PROP property at the given position." +; (let ((e (extent-at pos buffer prop))) +; (if e +; (extent-property e prop) +; nil))) + +(defun extent-properties-at-1 (position buffer-or-string text-props-only) + (let ((extent nil) + (props nil) + new-props) + (while (setq extent (extent-at position buffer-or-string + (if text-props-only 'text-prop nil) + extent)) + (if text-props-only + ;; Only return the one prop which the `text-prop' property points at. + (let ((prop (extent-property extent 'text-prop))) + (setq new-props (list prop (extent-property extent prop)))) + ;; Return all the properties... + (setq new-props (extent-properties extent)) + ;; ...but! Don't return the `begin-glyph' or `end-glyph' properties + ;; unless the position is exactly at the appropriate endpoint. Yeah, + ;; this is kind of a kludge. + ;; #### Bug, this doesn't work for end-glyphs (on end-open extents) + ;; because we've already passed the extent with the glyph by the time + ;; it's appropriate to return the glyph. We could return the end + ;; glyph one character early I guess... But then next-property-change + ;; would have to stop one character early as well. It could back up + ;; when it hit an end-glyph... + ;; #### Another bug, if there are multiple glyphs at the same position, + ;; we only see the first one. + (cond ((or (extent-begin-glyph extent) (extent-end-glyph extent)) + (if (/= position (if (extent-property extent 'begin-glyph) + (extent-start-position extent) + (extent-end-position extent))) + (let ((rest new-props) + prev) + (while rest + (cond ((or (eq (car rest) 'begin-glyph) + (eq (car rest) 'end-glyph)) + (if prev + (setcdr prev (cdr (cdr rest))) + (setq new-props (cdr (cdr new-props)))) + (setq rest nil))) + (setq prev rest + rest (cdr rest)))))))) + (cond ((null props) + (setq props new-props)) + (t + (while new-props + (or (getf props (car new-props)) + (setq props (cons (car new-props) + (cons (car (cdr new-props)) + props)))) + (setq new-props (cdr (cdr new-props))))))) + props)) + +(defun extent-properties-at (position &optional object) + "Returns the properties of the character at the given position +in OBJECT (a string or buffer) by merging the properties of overlapping +extents. The returned value is a property list, some of which may be +shared with other structures. You must not modify it. + +If POSITION is at the end of OBJECT, the value is nil. + +This returns all properties on all extents. +See also `text-properties-at'." + (extent-properties-at-1 position object nil)) + +(defun text-properties-at (position &optional object) + "Returns the properties of the character at the given position +in OBJECT (a string or buffer) by merging the properties of overlapping +extents. The returned value is a property list, some of which may be +shared with other structures. You must not modify it. + +If POSITION is at the end of OBJECT, the value is nil. + +This returns only those properties added with `put-text-property'. +See also `extent-properties-at'." + (extent-properties-at-1 position object t)) + +(defun text-property-any (start end prop value &optional buffer-or-string) + "Check text from START to END to see if PROP is ever `eq' to VALUE. +If so, return the position of the first character whose PROP is `eq' +to VALUE. Otherwise return nil. +The optional fifth argument, BUFFER-OR-STRING, is the buffer or string +containing the text and defaults to the current buffer." + (while (and start (< start end) + (not (eq value (get-text-property start prop buffer-or-string)))) + (setq start (next-single-property-change start prop buffer-or-string end))) + ;; we have to insert a special check for end due to the illogical + ;; definition of next-single-property-change (blame FSF for this). + (if (eq start end) nil start)) + +(defun text-property-not-all (start end prop value &optional buffer-or-string) + "Check text from START to END to see if PROP is ever not `eq' to VALUE. +If so, return the position of the first character whose PROP is not +`eq' to VALUE. Otherwise, return nil. +The optional fifth argument, BUFFER-OR-STRING, is the buffer or string +containing the text and defaults to the current buffer." + (if (not (eq value (get-text-property start prop buffer-or-string))) + start + (let ((retval (next-single-property-change start prop + buffer-or-string end))) + ;; we have to insert a special check for end due to the illogical + ;; definition of previous-single-property-change (blame FSF for this). + (if (eq retval end) nil retval)))) + +;; Older versions that only work sometimes (when VALUE is non-nil +;; for text-property-any, and maybe only when VALUE is nil for +;; text-property-not-all). They might be faster in those cases, +;; but that's not obvious. + +;(defun text-property-any (start end prop value &optional buffer) +; "Check text from START to END to see if PROP is ever `eq' to VALUE. +;If so, return the position of the first character whose PROP is `eq' +;to VALUE. Otherwise return nil." +; ;; #### what should (text-property-any x y 'foo nil) return when there +; ;; is no foo property between x and y? Either t or nil seems sensible, +; ;; since a character with a property of nil is indistinguishable from +; ;; a character without that property set. +; (map-extents +; #'(lambda (e ignore) +; (if (eq value (extent-property e prop)) +; ;; return non-nil to stop mapping +; (max start (extent-start-position e)) +; nil)) +; nil start end buffer)) +; +;(defun text-property-not-all (start end prop value &optional buffer) +; "Check text from START to END to see if PROP is ever not `eq' to VALUE. +;If so, return the position of the first character whose PROP is not +;`eq' to VALUE. Otherwise, return nil." +; (let (maxend) +; (map-extents +; #'(lambda (e ignore) +; ;;### no, actually, this is harder. We need to collect all props +; ;; for a given character, and then determine whether no extent +; ;; contributes the given value. Doing this without consing lots +; ;; of lists is the tricky part. +; (if (eq value (extent-property e prop)) +; (progn +; (setq maxend (extent-end-position e)) +; nil) +; (max start maxend))) +; nil start end buffer))) + +(defun next-property-change (pos &optional buffer-or-string limit) + "Return the position of next property change. +Scans forward from POS in BUFFER-OR-STRING (defaults to the current buffer) + until it finds a change in some text property, then returns the position of + the change. +Returns nil if the properties remain unchanged all the way to the end. +If the value is non-nil, it is a position greater than POS, never equal. +If the optional third argument LIMIT is non-nil, don't search + past position LIMIT; return LIMIT if nothing is found before LIMIT. +If two or more extents with conflicting non-nil values for a property overlap + a particular character, it is undefined which value is considered to be + the value of the property. (Note that this situation will not happen if + you always use the text-property primitives.)" + (let ((limit-was-nil (null limit))) + (or limit (setq limit (if (bufferp buffer-or-string) + (point-max buffer-or-string) + (length buffer-or-string)))) + (let ((value (extent-properties-at pos buffer-or-string))) + (while + (and (< (setq pos (next-extent-change pos buffer-or-string)) limit) + (plists-eq value (extent-properties-at pos buffer-or-string))))) + (if (< pos limit) pos + (if limit-was-nil nil + limit)))) + +(defun previous-property-change (pos &optional buffer-or-string limit) + "Return the position of previous property change. +Scans backward from POS in BUFFER-OR-STRING (defaults to the current buffer) + until it finds a change in some text property, then returns the position of + the change. +Returns nil if the properties remain unchanged all the way to the beginning. +If the value is non-nil, it is a position less than POS, never equal. +If the optional third argument LIMIT is non-nil, don't search back + past position LIMIT; return LIMIT if nothing is found until LIMIT. +If two or more extents with conflicting non-nil values for a property overlap + a particular character, it is undefined which value is considered to be + the value of the property. (Note that this situation will not happen if + you always use the text-property primitives.)" + (let ((limit-was-nil (null limit))) + (or limit (setq limit (if (bufferp buffer-or-string) + (point-min buffer-or-string) + 0))) + (let ((value (extent-properties-at (1- pos) buffer-or-string))) + (while + (and (> (setq pos (previous-extent-change pos buffer-or-string)) + limit) + (plists-eq value (extent-properties-at (1- pos) + buffer-or-string))))) + (if (> pos limit) pos + (if limit-was-nil nil + limit)))) + +(defun text-property-bounds (pos prop &optional object at-flag) + "Return the bounds of property PROP at POS. +This returns a cons (START . END) of the largest region of text containing +POS which has a non-nil value for PROP. The return value is nil if POS +does not have a non-nil value for PROP. OBJECT specifies the buffer +or string to search in. Optional arg AT-FLAG controls what \"at POS\" +means, and has the same meaning as for `extent-at'." + (or object (setq object (current-buffer))) + (and (get-char-property pos prop object at-flag) + (let ((begin (if (stringp object) 0 (point-min object))) + (end (if (stringp object) (length object) (point-max object)))) + (cons (previous-single-property-change (1+ pos) prop object begin) + (next-single-property-change pos prop object end))))) + +(defun next-text-property-bounds (count pos prop &optional object) + "Return the COUNTth bounded property region of property PROP after POS. +If COUNT is less than zero, search backwards. This returns a cons +\(START . END) of the COUNTth maximal region of text that begins after POS +\(starts before POS) and has a non-nil value for PROP. If there aren't +that many regions, nil is returned. OBJECT specifies the buffer or +string to search in." + (or object (setq object (current-buffer))) + (let ((begin (if (stringp object) 0 (point-min object))) + (end (if (stringp object) (length object) (point-max object)))) + (catch 'hit-end + (if (> count 0) + (progn + (while (> count 0) + (if (>= pos end) + (throw 'hit-end nil) + (and (get-char-property pos prop object) + (setq pos (next-single-property-change pos prop + object end))) + (setq pos (next-single-property-change pos prop object end))) + (setq count (1- count))) + (and (< pos end) + (cons pos (next-single-property-change pos prop object end)))) + (while (< count 0) + (if (<= pos begin) + (throw 'hit-end nil) + (and (get-char-property (1- pos) prop object) + (setq pos (previous-single-property-change pos prop + object begin))) + (setq pos (previous-single-property-change pos prop object + begin))) + (setq count (1+ count))) + (and (> pos begin) + (cons (previous-single-property-change pos prop object begin) + pos)))))) + +;(defun detach-all-extents (&optional buffer) +; (map-extents #'(lambda (x i) (detach-extent x) nil) +; buffer)) + + +(provide 'text-props) + +;;; text-props.el ends here