Mercurial > emacs
changeset 1029:425f62908a54
Initial revision
author | Joseph Arceneaux <jla@gnu.org> |
---|---|
date | Fri, 21 Aug 1992 23:45:45 +0000 |
parents | 508bb8dd1e88 |
children | 9934251d8219 |
files | src/textprop.c |
diffstat | 1 files changed, 722 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/textprop.c Fri Aug 21 23:45:45 1992 +0000 @@ -0,0 +1,722 @@ +/* Interface code for dealing with text properties. + Copyright (C) 1992 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 1, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "config.h" +#include "lisp.h" +#include "intervals.h" +#include "buffer.h" + + +/* NOTES: previous- and next- property change will have to skip + zero-length intervals if they are implemented. This could be done + inside next_interval and previous_interval. + + It is assumed that for any interval plist, a property appears + only once on the list. Although some code i.e., remove_properties (), + handles the more general case, the uniqueness of properties is + neccessary for the system to remain consistent. This requirement + is enforced by the subrs installing properties onto the intervals. */ + + +/* Types of hooks. */ +Lisp_Object Qmouse_left; +Lisp_Object Qmouse_entered; +Lisp_Object Qpoint_left; +Lisp_Object Qpoint_entered; +Lisp_Object Qmodification; + +/* Visual properties text (including strings) may have. */ +Lisp_Object Qforeground, Qbackground, Qfont, Qunderline, Qstipple; +Lisp_Object Qinvisible, Qread_only; + +/* Extract the interval at position BEGIN from OBJECT, a string + or buffer. Additionally, check that BEGIN and END are within + the bounds of OBJECT. + + Note that buffer points don't correspond to interval indices. + For example, point-max is 1 greater than the index of the last + character. This difference is handled in the caller, which uses + the validated points to determine a length, and operates on that. + Exceptions are Ftext_properties_at, Fnext_property_change, and + Fprevious_property_change which call this function with BEGIN == END. + Handle this case specially. + + If FORCE is soft (0), it's OK to return NULL_INTERVAL. Otherwise, + create an interval tree for OBJECT if one doesn't exist. */ + +#define soft 0 +#define hard 1 + +static INTERVAL +validate_interval_range (object, begin, end, force) + Lisp_Object object, *begin, *end; + int force; +{ + register INTERVAL i; + CHECK_STRING_OR_BUFFER (object, 0); + CHECK_NUMBER_COERCE_MARKER (*begin, 0); + CHECK_NUMBER_COERCE_MARKER (*end, 0); + + /* If we are asked for a point, but from a subr which operates + on a range, then return nothing. */ + if (*begin == *end && begin != end) + return NULL_INTERVAL; + + if (XINT (*begin) > XINT (*end)) + { + register int n; + n = XFASTINT (*begin); /* This is legit even if *begin is < 0 */ + *begin = *end; + XFASTINT (*end) = n; /* because this is all we do with n. */ + } + + if (XTYPE (object) == Lisp_Buffer) + { + register struct buffer *b = XBUFFER (object); + + /* If there's no text, there are no properties. */ + if (BUF_BEGV (b) == BUF_ZV (b)) + return NULL_INTERVAL; + + if (!(BUF_BEGV (b) <= XINT (*begin) && XINT (*begin) <= XINT (*end) + && XINT (*end) <= BUF_ZV (b))) + args_out_of_range (*begin, *end); + i = b->intervals; + + /* Special case for point-max: return the interval for the + last character. */ + if (*begin == *end && *begin == BUF_Z (b)) + *begin -= 1; + } + else + { + register struct Lisp_String *s = XSTRING (object); + + if (! (1 <= XINT (*begin) && XINT (*begin) <= XINT (*end) + && XINT (*end) <= s->size)) + args_out_of_range (*begin, *end); + i = s->intervals; + } + + if (NULL_INTERVAL_P (i)) + return (force ? create_root_interval (object) : i); + + return find_interval (i, XINT (*begin)); +} + +/* Validate LIST as a property list. If LIST is not a list, then + make one consisting of (LIST nil). Otherwise, verify that LIST + is even numbered and thus suitable as a plist. */ + +static Lisp_Object +validate_plist (list) +{ + if (NILP (list)) + return Qnil; + + if (CONSP (list)) + { + register int i; + register Lisp_Object tail; + for (i = 0, tail = list; !NILP (tail); i++) + tail = Fcdr (tail); + if (i & 1) + error ("Odd length text property list"); + return list; + } + + return Fcons (list, Fcons (Qnil, Qnil)); +} + +#define set_properties(list,i) (i->plist = Fcopy_sequence (list)) + +/* Return nonzero if interval I has all the properties, + with the same values, of list PLIST. */ + +static int +interval_has_all_properties (plist, i) + Lisp_Object plist; + INTERVAL i; +{ + register Lisp_Object tail1, tail2, sym1, sym2; + register int found; + + /* Go through each element of PLIST. */ + for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1))) + { + sym1 = Fcar (tail1); + found = 0; + + /* Go through I's plist, looking for sym1 */ + for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2))) + if (EQ (sym1, Fcar (tail2))) + { + /* Found the same property on both lists. If the + values are unequal, return zero. */ + if (! EQ (Fequal (Fcar (Fcdr (tail1)), Fcar (Fcdr (tail2))), + Qt)) + return 0; + + /* Property has same value on both lists; go to next one. */ + found = 1; + break; + } + + if (! found) + return 0; + } + + return 1; +} + +/* Return nonzero if the plist of interval I has any of the + properties of PLIST, regardless of their values. */ + +static INLINE int +interval_has_some_properties (plist, i) + Lisp_Object plist; + INTERVAL i; +{ + register Lisp_Object tail1, tail2, sym; + + /* Go through each element of PLIST. */ + for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1))) + { + sym = Fcar (tail1); + + /* Go through i's plist, looking for tail1 */ + for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2))) + if (EQ (sym, Fcar (tail2))) + return 1; + } + + return 0; +} + +/* Add the properties of PLIST to the interval I, or set + the value of I's property to the value of the property on PLIST + if they are different. + + Return nonzero if this changes I (i.e., if any members of PLIST + are actually added to I's plist) */ + +static INLINE int +add_properties (plist, i) + Lisp_Object plist; + INTERVAL i; +{ + register Lisp_Object tail1, tail2, sym1, val1; + register int changed = 0; + register int found; + + /* Go through each element of PLIST. */ + for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1))) + { + sym1 = Fcar (tail1); + val1 = Fcar (Fcdr (tail1)); + found = 0; + + /* Go through I's plist, looking for sym1 */ + for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2))) + if (EQ (sym1, Fcar (tail2))) + { + register Lisp_Object this_cdr = Fcdr (tail2); + + /* Found the property. Now check its value. */ + found = 1; + + /* The properties have the same value on both lists. + Continue to the next property. */ + if (Fequal (val1, Fcar (this_cdr))) + break; + + /* I's property has a different value -- change it */ + Fsetcar (this_cdr, val1); + changed++; + break; + } + + if (! found) + { + i->plist = Fcons (sym1, Fcons (val1, i->plist)); + changed++; + } + } + + return changed; +} + +/* For any members of PLIST which are properties of I, remove them + from I's plist. */ + +static INLINE int +remove_properties (plist, i) + Lisp_Object plist; + INTERVAL i; +{ + register Lisp_Object tail1, tail2, sym; + register Lisp_Object current_plist = i->plist; + register int changed = 0; + + /* Go through each element of plist. */ + for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1))) + { + sym = Fcar (tail1); + + /* First, remove the symbol if its at the head of the list */ + while (! NILP (current_plist) && EQ (sym, Fcar (current_plist))) + { + current_plist = Fcdr (Fcdr (current_plist)); + changed++; + } + + /* Go through i's plist, looking for sym */ + tail2 = current_plist; + while (! NILP (tail2)) + { + register Lisp_Object this = Fcdr (Fcdr (tail2)); + if (EQ (sym, Fcar (this))) + { + Fsetcdr (Fcdr (tail2), Fcdr (Fcdr (this))); + changed++; + } + tail2 = this; + } + } + + if (changed) + i->plist = current_plist; + return changed; +} + +/* Remove all properties from interval I. Return non-zero + if this changes the interval. */ + +static INLINE int +erase_properties (i) + INTERVAL i; +{ + if (NILP (i->plist)) + return 0; + + i->plist = Qnil; + return 1; +} + + +DEFUN ("text-properties-at", Ftext_properties_at, + Stext_properties_at, 1, 2, 0, + "Return the list of properties held by the character at POSITION\n\ +in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\ +defaults to the current buffer.") + (pos, object) + Lisp_Object pos, object; +{ + register INTERVAL i; + register int p; + + if (NILP (object)) + XSET (object, Lisp_Buffer, current_buffer); + + i = validate_interval_range (object, &pos, &pos, soft); + if (NULL_INTERVAL_P (i)) + return Qnil; + + return i->plist; +} + +DEFUN ("next-property-change", Fnext_property_change, + Snext_property_change, 2, 2, 0, + "Return the position after POSITION in OBJECT which has properties\n\ +different from those at POSITION. OBJECT may be a string or buffer.\n\ +Returns nil if unsuccessful.") + (pos, object) + Lisp_Object pos, object; +{ + register INTERVAL i, next; + + i = validate_interval_range (object, &pos, &pos, soft); + if (NULL_INTERVAL_P (i)) + return Qnil; + + next = next_interval (i); + while (! NULL_INTERVAL_P (next) && intervals_equal (i, next)) + next = next_interval (next); + + if (NULL_INTERVAL_P (next)) + return Qnil; + + return next->position; +} + +DEFUN ("previous-property-change", Fprevious_property_change, + Sprevious_property_change, 2, 2, 0, + "Return the position before POSITION in OBJECT which has properties\n\ +different from those at POSITION. OBJECT may be a string or buffer.\n\ +Returns nil if unsuccessful.") + (pos, object) + Lisp_Object pos, object; +{ + register INTERVAL i, previous; + + i = validate_interval_range (object, &pos, &pos, soft); + if (NULL_INTERVAL_P (i)) + return Qnil; + + previous = previous_interval (i); + while (! NULL_INTERVAL_P (previous) && intervals_equal (previous, i)) + previous = previous_interval (previous); + if (NULL_INTERVAL_P (previous)) + return Qnil; + + return previous->position + LENGTH (previous) - 1; +} + +DEFUN ("add-text-properties", Fadd_text_properties, + Sadd_text_properties, 4, 4, 0, + "Add the PROPERTIES (a property list) to the text of OBJECT\n\ +(a string or buffer) in the range START to END. Returns t if any change\n\ +was made, nil otherwise.") + (object, start, end, properties) + Lisp_Object object, start, end, properties; +{ + register INTERVAL i, unchanged; + register int s, len, modified; + + properties = validate_plist (properties); + if (NILP (properties)) + return Qnil; + + i = validate_interval_range (object, &start, &end, hard); + if (NULL_INTERVAL_P (i)) + return Qnil; + + s = XINT (start); + len = XINT (end) - s; + + /* If we're not starting on an interval boundary, we have to + split this interval. */ + if (i->position != s) + { + /* If this interval already has the properties, we can + skip it. */ + if (interval_has_all_properties (properties, i)) + { + int got = (LENGTH (i) - (s - i->position)); + if (got >= len) + return Qnil; + len -= got; + } + else + { + unchanged = i; + i = split_interval_right (unchanged, s - unchanged->position + 1); + copy_properties (unchanged, i); + if (LENGTH (i) > len) + { + i = split_interval_left (i, len + 1); + copy_properties (unchanged, i); + add_properties (properties, i); + return Qt; + } + + add_properties (properties, i); + modified = 1; + len -= LENGTH (i); + i = next_interval (i); + } + } + + /* We are at the beginning of an interval, with len to scan */ + while (1) + { + if (LENGTH (i) >= len) + { + if (interval_has_all_properties (properties, i)) + return modified ? Qt : Qnil; + + if (LENGTH (i) == len) + { + add_properties (properties, i); + return Qt; + } + + /* i doesn't have the properties, and goes past the change limit */ + unchanged = i; + i = split_interval_left (unchanged, len + 1); + copy_properties (unchanged, i); + add_properties (properties, i); + return Qt; + } + + len -= LENGTH (i); + modified += add_properties (properties, i); + i = next_interval (i); + } +} + +DEFUN ("set-text-properties", Fset_text_properties, + Sset_text_properties, 4, 4, 0, + "Make the text of OBJECT (a string or buffer) have precisely\n\ +PROPERTIES (a list of properties) in the range START to END.\n\ +\n\ +If called with a valid property list, return t (text was changed).\n\ +Otherwise return nil.") + (object, start, end, properties) + Lisp_Object object, start, end, properties; +{ + register INTERVAL i, unchanged; + register int s, len; + + properties = validate_plist (properties); + if (NILP (properties)) + return Qnil; + + i = validate_interval_range (object, &start, &end, hard); + if (NULL_INTERVAL_P (i)) + return Qnil; + + s = XINT (start); + len = XINT (end) - s; + + if (i->position != s) + { + unchanged = i; + i = split_interval_right (unchanged, s - unchanged->position + 1); + copy_properties (unchanged, i); + if (LENGTH (i) > len) + { + i = split_interval_left (i, len); + set_properties (properties, i); + return Qt; + } + + set_properties (properties, i); + len -= LENGTH (i); + i = next_interval (i); + } + + while (1) + { + if (LENGTH (i) >= len) + { + if (LENGTH (i) == len) + { + set_properties (properties, i); + return Qt; + } + + i = split_interval_left (i, len + 1); + set_properties (properties, i); + return Qt; + } + + len -= LENGTH (i); + set_properties (properties, i); + i = next_interval (i); + } + + return Qt; +} + +DEFUN ("remove-text-properties", Fremove_text_properties, + Sremove_text_properties, 4, 4, 0, + "Remove the PROPERTIES (a property list) from the text of OBJECT\n\ +(a string or buffer) in the range START to END. Returns t if any change\n\ +was made, nil otherwise.") + (object, start, end, properties) + Lisp_Object object, start, end, properties; +{ + register INTERVAL i, unchanged; + register int s, len, modified; + + i = validate_interval_range (object, &start, &end, soft); + if (NULL_INTERVAL_P (i)) + return Qnil; + + s = XINT (start); + len = XINT (end) - s; + if (i->position != s) + { + /* No properties on this first interval -- return if + it covers the entire region. */ + if (! interval_has_some_properties (properties, i)) + { + int got = (LENGTH (i) - (s - i->position)); + if (got >= len) + return Qnil; + len -= got; + } + /* Remove the properties from this interval. If it's short + enough, return, splitting it if it's too short. */ + else + { + unchanged = i; + i = split_interval_right (unchanged, s - unchanged->position + 1); + copy_properties (unchanged, i); + if (LENGTH (i) > len) + { + i = split_interval_left (i, len + 1); + copy_properties (unchanged, i); + remove_properties (properties, i); + return Qt; + } + + remove_properties (properties, i); + modified = 1; + len -= LENGTH (i); + i = next_interval (i); + } + } + + /* We are at the beginning of an interval, with len to scan */ + while (1) + { + if (LENGTH (i) >= len) + { + if (! interval_has_some_properties (properties, i)) + return modified ? Qt : Qnil; + + if (LENGTH (i) == len) + { + remove_properties (properties, i); + return Qt; + } + + /* i has the properties, and goes past the change limit */ + unchanged = split_interval_right (i, len + 1); + copy_properties (unchanged, i); + remove_properties (properties, i); + return Qt; + } + + len -= LENGTH (i); + modified += remove_properties (properties, i); + i = next_interval (i); + } +} + +DEFUN ("erase-text-properties", Ferase_text_properties, + Serase_text_properties, 3, 3, 0, + "Remove all text properties from OBJECT (a string or buffer), in the\n\ +range START to END. Returns t if any change was made, nil otherwise.") + (object, start, end) + Lisp_Object object, start, end; +{ + register INTERVAL i, unchanged; + register int s, len, modified; + + i = validate_interval_range (object, &start, &end, soft); + if (NULL_INTERVAL_P (i)) + return Qnil; + + s = XINT (start); + len = XINT (end) - s; + if (i->position != s) + { + int got = LENGTH (i) - (s - i->position); + + if (got > len) + { + if (NILP (i->plist)) + return Qnil; + + unchanged = i; + i = split_interval_right (unchanged, s - unchanged->position + 1); + i = split_interval_right (i, len + 1); + copy_properties (unchanged, i); + return Qt; + } + + if (! NILP (i->plist)) + { + i = split_interval_right (i, s - i->position + 1); + modified++; + } + + len -= got; + i = next_interval (i); + } + + /* We are starting at the beginning of an interval */ + while (len > 0) + { + if (LENGTH (i) > len) + { + if (NILP (i->plist)) + return modified ? Qt : Qnil; + + i = split_interval_left (i, len + 1); + return Qt; + } + + len -= LENGTH (i); + modified += erase_properties (i); + i = next_interval (i); + } + + return modified ? Qt : Qnil; +} + +void +syms_of_textprop () +{ + DEFVAR_INT ("interval-balance-threshold", &interval_balance_threshold, + "Threshold for rebalancing interval trees, expressed as the +percentage by which the left interval tree should not differ from the right."); + interval_balance_threshold = 8; + + /* Common attributes one might give text */ + + staticpro (&Qforeground); + Qforeground = intern ("foreground"); + staticpro (&Qbackground); + Qbackground = intern ("background"); + staticpro (&Qfont); + Qfont = intern ("font"); + staticpro (&Qstipple); + Qstipple = intern ("stipple"); + staticpro (&Qunderline); + Qunderline = intern ("underline"); + staticpro (&Qread_only); + Qread_only = intern ("read-only"); + staticpro (&Qinvisible); + Qinvisible = intern ("invisible"); + + /* Properties that text might use to specify certain actions */ + + staticpro (&Qmouse_left); + Qmouse_left = intern ("mouse-left"); + staticpro (&Qmouse_entered); + Qmouse_entered = intern ("mouse-entered"); + staticpro (&Qpoint_left); + Qpoint_left = intern ("point-left"); + staticpro (&Qpoint_entered); + Qpoint_entered = intern ("point-entered"); + staticpro (&Qmodification); + Qmodification = intern ("modification"); + + defsubr (&Stext_properties_at); + defsubr (&Snext_property_change); + defsubr (&Sprevious_property_change); + defsubr (&Sadd_text_properties); + defsubr (&Sset_text_properties); + defsubr (&Sremove_text_properties); + defsubr (&Serase_text_properties); +}