aboutsummaryrefslogtreecommitdiffstats
path: root/src/textprop.c
diff options
context:
space:
mode:
authorJoseph Arceneaux <[email protected]>1992-08-21 23:45:45 +0000
committerJoseph Arceneaux <[email protected]>1992-08-21 23:45:45 +0000
commitd418ef42e13d633b305d351c7a4555052f732aa8 (patch)
tree2aca7522cb8723fa94352dc7e757cba92c979d58 /src/textprop.c
parentaf3890bd9016b6c7bd60cf620df4b2b3ba74d8fe (diff)
Initial revision
Diffstat (limited to 'src/textprop.c')
-rw-r--r--src/textprop.c722
1 files changed, 722 insertions, 0 deletions
diff --git a/src/textprop.c b/src/textprop.c
new file mode 100644
index 0000000000..2eae895c25
--- /dev/null
+++ b/src/textprop.c
@@ -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);
+}