1 ;;; tinylibt.el --- Library for handling text properties.
3 ;; This file is not part of Emacs
7 ;; Copyright (C) 1995-2007 Jari Aalto
8 ;; Keywords: extensions
10 ;; Maintainer: Jari Aalto
12 ;; To get information on this program, call M-x tinylibt-version.
13 ;; Look at the code with folding.el
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even tqhe implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
37 ;; ....................................................... &t-install ...
38 ;; Put this file on your Emacs-Lisp load path, add following into
39 ;; ~/.emacs startup file.
41 ;; (require 'tinylibt)
43 ;; No autoload is suggested, because almost every function would have
44 ;; to be in autoload state. It's easier to use require. Here are
45 ;; suggested keybings for interactive use.
47 ;; (global-unset-key "\C-z")
48 ;; (global-set-key "\C-ztm" 'ti::text-mark-region) ;; e.g. permanent 'mark'
49 ;; (global-set-key "\C-ztu" 'ti::text-unmark-region) ;; remove 'mark'
50 ;; (global-set-key "\C-ztc" 'ti::text-clear-buffer-properties)
51 ;; (global-set-key "\C-ztb" 'ti::text-buffer)
52 ;; (global-set-key "\C-ztU" 'ti::text-undo)
54 ;; If you have any questions or feedback, use this function
56 ;; M-x ti::text-submit-bug-report
61 ;; ..................................................... &t-commentary ...
65 ;; Overview of features
67 ;; o This package is primary aimed for programmers, but
68 ;; interactive users will also find handy functions.
69 ;; o Show matched text with color in the buffer.
70 ;; o This is *NOTHING* like `font-lock' or `lazy-lock'
71 ;; which are demand driven packages intended for certain major modes.
72 ;; Use this package to "manually" mark interesting things in
74 ;; o Examples: highlighting on/off tabs, Verifying PGP
75 ;; fingerprints against trusted key server list
76 ;; o UNDO: adjustable stack size. Stack is cleared if
77 ;; stack limit reached (stack 'wraps')
81 ;; Mostly this package is designed for programmers, who add
82 ;; some highlight commands in hooks. For quick text highlighting,
83 ;; you can use these interactive functions:
85 ;; ti::text-looking-at
86 ;; ti::text-buffer ;; Highlight in whole buffer area
87 ;; ti::text-re-search-forward
88 ;; ti::text-re-search-backward
91 ;; ti::text-clear-buffer-properties
92 ;; ti::text-clear-region-properties
94 ;; ti::text-mark-region
95 ;; ti::text-unmark-region
97 ;; ti::text-mouse-mark-region
98 ;; ti::text-mouse-unmark-region
100 ;; Setting different face (programming)
102 ;; If you want permanetly change the face, when marking text
105 ;; ti::text-search-face-set ;to set
106 ;; ti::text-search-face-reset ;to get default color back
108 ;; If you want temporarily use some face, supply direct FACE parameter
109 ;; when you call search functions, like:
111 ;; ti::text-re-search-forward (re &optional level face)
115 ;; This is for simple text highlighting only. Like finding certain items
116 ;; or marking something quickly and temporarily (great for text files)
118 ;; You can mix font-lock/hilit19 and TIMA package, but remember that
119 ;; these packages have different goals. Use TIMA only for finding
120 ;; things in buffer, or marking certain articles in gnus...
122 ;; Be carefull: if you use `ti::text-clear-buffer-properties', you will
123 ;; wipe out all text properties.
125 ;; Example: highlighting tabs
127 ;; (global-set-key "\C-ct" 'my-tabs-highligh-in-buffer)
129 ;; (defun my-tabs-highligh-in-buffer (&optional arg)
130 ;; "Toggless hilit/dehiliting tabs in buffer.
131 ;; If ARG is integer, force highlighting. If ARG is C-u, then
132 ;; force dehighlighting."
137 ;; (when (re-search-forward "\t" nil t)
138 ;; ;; is the tab marked?
139 ;; (setq prop (get-text-property (1- (point)) 'face))
141 ;; ((or (integerp arg) ;; Do highlighting
143 ;; (eq prop 'default)))
144 ;; (beginning-of-line)
145 ;; (ti::text-re-search-forward "\t+"))
147 ;; (beginning-of-line)
149 ;; (ti::text-re-search-forward "\t+" 0 'default )))))))
151 ;; Example: finding PGP key matches
153 ;; (defun my-pgp-fp-certify ()
154 ;; "To certify keys, E.g. get list of remailers
155 ;; from http://www.uit.no/
156 ;; - Display in window1 the UIT.NO result file
157 ;; - Put received key fingerprints in other window (pgp -ka
160 ;; Call this function in the Received keys buffer, and it'll
161 ;; highlight keys that match Fingerprint in uit.no window."
163 ;; (let* ((blist (ti::window-list 'buffers))
164 ;; (buffer (car (delq (current-buffer) blist)))
169 ;; (while (re-search-forward
170 ;; "Key fingerprint.*= +\\(.*\\)" nil t)
171 ;; (setq elt (match-string 1) ok nil)
174 ;; (set-buffer buffer)
176 ;; (setq ok (ti::text-re-search-forward elt)))
178 ;; (beginning-of-line)
179 ;; (ti::text-looking-at ".*"))
191 ;;; ......................................................... &require ...
194 (eval-when-compile (ti::package-use-dynamic-compilation))
196 (ti::package-defgroup-tiny TinylibText ti::text-: extensions
197 "Mark matched text in buffer with face.
200 o This package is primary aimed for elisp programmers, but
201 interactive users will also find handy functions.
202 o Shows matched text with color in the buffer.
203 o This is *NOTHING* like font-lock, lazy-lock or hilit19,
204 which are demand driven packages intended for certain major modes.
205 Use this package to manually mark interesting things in
207 o Examples: highlighting on/off tabs, Verifying PGP
208 fingerprints against trusted list like http://www.uit.no/
209 o UNDO: adjustable stack size. Stack is cleared if
210 stack limit reached (stack 'wraps')
214 ;;{{{ setup: variables
216 ;;; ......................................................... &v-hooks ...
218 (defcustom ti::text-:load-hook nil
219 "*Hook run when file has been loaded."
223 (defcustom ti::text-:stack-size 1000
224 "*How big undo history."
228 ;;; ....................................................... &v-private ...
230 (defvar ti::text-:stack-count nil
231 "Incremented after every search. Do not touch.")
233 (defvar ti::text-:stack-push-flag nil
234 "Non-nil means ('undo-func) do not record match data to stack.
235 If this variable has value 'undo-func then the next calls to
236 `ti::text-re-search' won't record data to stack.")
238 (defvar ti::text-:stack nil
239 "Private. Contain last search data.
240 This is actually property list stack so that undo can be done.
251 ;;; ........................................................ &v-public ...
252 ;;; user configurable
254 (defcustom ti::text-:face-tab-search-default 'highlight
255 "*Default face used when marking searched text."
256 :type '(symbol :tag "Face symbol")
259 ;;; For now, only search face is used, but maybe in the future the others..
261 (defcustom ti::text-:face-table
263 (cons 'search 'highlight)
264 (cons 'warn (if (ti::emacs-p) 'region 'bold)) ;XE doesn't have 'region face
266 "*Faces used for marking text."
269 (symbol :tag "symbolic face name"
270 (symbol :tag "Face name"))))
277 (ti::macrof-version-bug-report
281 "$Id: tinylibt.el,v 2.46 2007/05/06 23:15:20 jaalto Exp $"
282 '(ti::text-:version-id
285 ti::text-:stack-count
286 ti::text-:stack-push-flag
288 ti::text-:face-search-default
289 ti::text-:face-table)))
292 ;;{{{ code: misc funcs
294 ;;; ----------------------------------------------------------------------
296 (defmacro ti::text-search-face-reset ()
297 "Reset used face to the default value.
298 If you use many colours to highlight text. Remember to call this
299 when you're finished."
301 'setcdr (list 'assq ''search 'ti::text-:face-table)
302 'ti::text-:face-search-default))
304 ;;; ----------------------------------------------------------------------
306 (defmacro ti::text-search-face-set (face)
307 "Change search colour to FACE."
308 (list 'setcdr (list 'assq ''search 'ti::text-:face-table) face))
310 ;;; ----------------------------------------------------------------------
312 (defmacro ti::text-face (face)
313 "Return real face when logical FACE is given."
314 ;; This way the global variable does not float around the file
315 (list 'cdr (list 'assoc face 'ti::text-:face-table)))
317 ;;; ----------------------------------------------------------------------
319 (defsubst ti::text-stack-clear ()
321 (put 'ti::text-:stack 'definition-stack nil)
322 (setq ti::text-:stack-count 0
323 ti::text-:stack nil))
325 ;;; ----------------------------------------------------------------------
327 (defsubst ti::text-stack-length ()
328 "Return undo stack length."
329 (length (get 'ti::text-:stack 'definition-stack)))
331 ;;; ----------------------------------------------------------------------
333 (defsubst ti::text-stack-full-p ()
334 "Check if stack is full."
335 (eq (ti::text-stack-length) (1+ ti::text-:stack-size)))
337 ;;; ----------------------------------------------------------------------
339 (defsubst ti::text-stack-p ()
340 "Check if there is data in undo stack. nil means that stack is empty."
341 (or (get 'ti::text-:stack 'definition-stack)
342 ;; Make sure this is also zero because there is no data
343 (progn (setq ti::text-:stack-count 0) nil)))
345 ;;; ----------------------------------------------------------------------
347 (defun ti::text-save-data (re level func mode beg)
348 "Save search values RE LEVEL FUNC MODE BEG for undo.
349 If the stack is full, then Clear the stack before pushing to it."
350 (or (integerp ti::text-:stack-count)
351 (setq ti::text-:stack-count (ti::text-stack-length) ))
352 (when (and (stringp re)
353 (not (eq ti::text-:stack-push-flag 'undo-func)))
355 ;; The last entry in the stack is always nil, that's why
359 ;; 1st: '(mil) pushed last data; size 1
360 ;; 2nd '((..) nil) pushed next, size 2
362 ;; As you can see there actually is only one real data;
363 ;; the 1st push reads the current calues of ti::text-:stack
364 ;; which is nil; because it was the last element that was poped
365 (when (>= ti::text-:stack-count
366 (1+ ti::text-:stack-size))
367 (ti::text-stack-clear)
368 (setq ti::text-:stack-count 0))
369 (ti::push-definition 'ti::text-:stack)
370 (setq ti::text-:stack
377 (incf ti::text-:stack-count)))
379 ;;; ----------------------------------------------------------------------
381 (defun ti::text-undo ()
382 "Undo last highlighting.
383 `ti::text-:stack-push-flag' is set to 'undo-func while this function runs."
385 (let* ((ti::text-:stack-push-flag 'undo-func)
386 (prev ti::text-:stack)
392 ;; - Hm The stack values are empty now, try to pop last saved values
394 ;; - Actually there should be something in the variable is the stack is
395 ;; not empty and it is an erro condition is variable IS empty AND
396 ;; there is values in the stack! ... We'll we don't nag about that
397 ;; here. I just thought you should know about it.
399 (decf ti::text-:stack-count)
400 (ti::pop-definition 'ti::text-:stack)
401 (setq prev ti::text-:stack)) ;Maybe this time there is something
402 (if (not (and (ti::listp prev)
405 (ti::text-stack-clear)
406 (error "tinylibt: No more undo information in stack."))
407 (setq func (nth 0 prev)
412 (decf ti::text-:stack-count)
414 ;; - What if user has narrowed the buffer
415 ;; - Or he has deleted text
416 (or (ignore-errors (goto-char beg))
418 There is no such search point in the buffer any more? %s" beg))
420 ((eq func 'looking-at)
421 (ti::text-looking-at re level 'default))
425 (if (eq func 're-search-backward)
431 ;; UNDO done; now get next undo information
432 (ti::pop-definition 'ti::text-:stack))))
434 ;;; ----------------------------------------------------------------------
436 (defun ti::text-clear-buffer-properties (&optional propl)
437 "Remove all properties from buffer that match property list PROPL.
440 See function `ti::text-clear-region-properties'"
442 (ti::text-clear-region-properties (point-min) (point-max) propl)
443 (when (interactive-p)
445 (message "Properties cleared")))
447 ;;; ----------------------------------------------------------------------
449 (defun ti::text-clear-region-properties (beg end &optional propl)
450 "Remove properties from BEG END. Affects read only buffers too.
454 PROPL (PROP VAL PROP VAL ..) Property list to match.
455 If nil, then remove all properties."
457 (let* (buffer-read-only ;allow writing
464 rprop ;read property name
467 (with-buffer-modified
469 (set-text-properties beg end nil)
470 (ti::keep-lower-order beg end)
471 (setq prop (nth 0 propl) val (nth 1 propl))
472 (while (and (> (point-max) beg)
473 ;; The beg is advanced in loop
476 (setq point (text-property-any beg end prop val)))
477 (setq read-list (text-properties-at point)
481 (setq rprop (nth 0 list)
483 list (cdr (cdr list))) ;go 2 forward
484 ;; The memq return the rest of list
486 ;; '(owner me face highlight do-it nil)
487 ;; (memq 'face) --> '(face highlight do-it nil)
489 ;; So the (nth 1) gives the value 'highlight which we
491 (if (or (null (setq elt (memq rprop read-list)))
492 (not (eq (nth 1 elt) rval)))
493 ;; This doesn'tmatch, stop and cancel delete
494 (setq list nil delete nil)))
495 ;; Character by character, this is bit slow...
497 (set-text-properties point (1+ point) nil))
499 (setq beg (1+ point)))))))
501 ;;; ----------------------------------------------------------------------
503 (defun ti::text-get-mouse-property ()
504 "Check if the point has 'mouse-face text property.
505 notice that if value read from point is nil,
506 that means same as no `mouse-face' property exists.
510 nil no property at point found
511 prop `mouse-face' property value"
512 (let* ((prop (text-properties-at (point))))
513 (if (setq prop (memq 'mouse-face prop))
514 (cdr prop)))) ;return value, may be nil
516 ;;; ----------------------------------------------------------------------
518 (defun ti::text-match-level (&optional level face-or-propl beg end)
519 "Add to match LEVEL a FACE-OR-PROPL in region BEG END.
520 If no match in that level, do nothing. Property `rear-nonsticky' is
521 added to the end of match unless FACE-OR-PROPL contains it.
526 FACE-OR-PROPL Defaults to '(face highlight)
527 If symbol, must be face symbol.
528 Can also be property list '(PROP VAL PROP VAL ..))
530 BEG END If given, then these are the are matched."
532 (setq beg (or beg (match-beginning (or level 0)))
533 end (or end (match-end (or level 0))))
534 (when (and (and beg end)
535 (not (eq beg end))) ;Nothing to do
537 ((null face-or-propl)
538 (add-text-properties beg end '(face highlight)))
539 ((symbolp face-or-propl)
540 (add-text-properties beg end (list 'face face-or-propl)))
542 (setq add-flag (null (memq 'rear-nonsticky face-or-propl)))
543 (add-text-properties beg end face-or-propl)))
545 (if (eq end 1) (setq end 2)) ;(1- 1) = 0, invalid charpos
546 (add-text-properties (1- end) end '(rear-nonsticky t))))))
548 ;;; ----------------------------------------------------------------------
551 (defun ti::text-re-search
552 (re &optional direction level maxp face mode save-undo)
553 "Highlight found text with search face.
557 The beginning of match and end of match will have
558 property 'rear-nonsticky t, so that adding characters before of after
559 text, won't inherit the face.
564 DIRECTION bool non-nil means backward
565 LEVEL nbr which subexpression in re to highlight, default is 0
566 MAXP nbr last search point [default until bob/eob]
569 if symbol is 'null then set face to value nil
570 or if this is list; then it must be properly list
571 of format '(PROP PROP-VAL PROP PROP-VAL ..)
573 MODE nbr signify that function should highlight all matches
574 that occur within LEVEL..NBR
575 if you have lot's of xx(match)yy|zz(match)tt|
576 the subexpression are counted from left to to
578 SAVE-UNDO flag non-nil means that the highlight information is
579 recorded for undo. This flag in on if function is
580 called interactively. NOTE: the undo information is
581 saved only if something was matched.
586 nbr start of match at LEVEL."
587 (let* ((func (if direction
590 (start-point (point))
594 bp ep ;beg/end points
599 (with-buffer-modified
600 ;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. set defaults . .
612 (setq face (ti::text-face 'search))))
613 ;; Otherwise face is non-nil
614 (setq max-level (1+ (or mode level)))
615 ;; Make sure the property list has paired values if supplied
616 (if (and (ti::listp face)
617 (not (eq 0 (% (length face) 2))))
618 (error "face properties are not paired: prop val"))
619 ;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. do matching . .
620 (while (funcall func re maxp t)
622 (while (< count max-level)
623 (setq bp (match-beginning count)
624 ep (match-end count))
625 (if (and bp (null ret)) ;do only once
627 (when (and bp (not (eq bp ep))) ;empty string
631 (put-text-property bp ep 'face face))
635 ;; Read two values at time
637 (setq prop (car list) list (cdr list)
638 val (car list) list (cdr list))
639 (put-text-property bp ep prop val))))
640 ;; #todo: something is wrong here, investigate..
642 ;; If a character's `rear-nonsticky'
643 ;; property is `t', then none of its properties are rear-sticky.
645 ;; Hmm, doesn't affect 19.28; is there bug in this emacs?
646 ;; The highlight is still extended If one adds chars after
649 ;; The stickiness must be activated ONE before the character.
656 (add-text-properties beg ep '(rear-nonsticky t))))
657 (setq count (1+ count))))
658 ;; Saving the search values for possible undo.
659 (if (and save-undo ret)
660 (ti::text-save-data re level func mode start-point))
661 ;; Return success status
664 ;;; ----------------------------------------------------------------------
666 (defun ti::text-property-search-and-modify
667 (match-plist set-plist &optional beg end)
668 "Search all characters forward, whose text properties match MATCH-PLIST.
669 Set properties to SET-PLIST. The point moves along the search.
673 MATCH-PLIST property list '(prop val prop val ..)
674 SET-PLIST property list '(prop val prop val ..)
675 BEG start point of search; defaults to `point-min'
676 END end point of search; defaults to `point-max'"
677 (let* ((sprop (car match-plist)) ;serach property
678 (sval (car (cdr match-plist)))
683 (if (null match-plist)
684 (error "Invalid match-plist"))
685 (or beg (setq beg (point-min)))
686 (or end (setq end (point-max)))
687 (ti::keep-lower-order beg end)
689 (setq point (1- (point)))
690 (while (and (not (eobp))
692 (setq point (text-property-any (1+ point) end sprop sval)))
694 (setq plist (text-properties-at (point))
697 (while (and ok mlist)
698 ;; Select 1str PROP fro match-list and see if it is in read PLIST
699 ;; Continue until all MLIST properties are found from read PLIST
700 (setq elt (memq (car mlist) plist)
701 ;; first non-match terminates loop immediately
702 ok (and elt (eq (nth 1 elt) (nth 1 mlist)))
703 mlist (cdr (cdr mlist))))
705 (set-text-properties (point) (1+ (point)) set-plist)))))
708 ;;{{{ code: interactive
710 ;;; ----------------------------------------------------------------------
711 ;;; Mon, 12 Feb 1996, Tom Fontaine <fontaine@esd.ray.com>
712 ;;; Sent this piece of code. Thanks Tom!
714 (defun ti::text-read-regexp ()
715 "Read regexp using `regexp-history'."
717 ((default (car regexp-history))
719 (read-from-minibuffer
721 (format "Highlight matching regexp (default `%s'): " default)
722 "Highlight matching regexp: ")
725 (if (> (length input) 0) ;the return value
727 (setcar regexp-history default))))
729 ;;; ----------------------------------------------------------------------
732 (defun ti::text-looking-at (re &optional level face-or-plist )
733 "Highlight found RE at LEVEL with FACE-OR-PLIST.
734 The LEVEL is subexpression to highlight. PLIST means property list."
735 (interactive "slook at: ")
736 (let (buffer-read-only) ;allow writing
737 (with-buffer-modified
738 (setq level (or level 0)
739 face-or-plist (or face-or-plist (ti::text-face 'search)))
741 (when (and (looking-at re)
743 (ti::text-save-data re level 'looking-at nil (point))
744 (ti::text-match-level level face-or-plist)))))
746 ;;; ----------------------------------------------------------------------
749 (defun ti::text-buffer (re &optional level face mode save-undo)
750 "Highlight RE and sub LEVEL in whole buffer, starting from `point-min'.
753 See `ti::text-re-search' for descriptions of FACE MODE and SAVE-UNDO."
754 (interactive (list (ti::text-read-regexp) current-prefix-arg))
758 (goto-char (point-min))
759 (ti::text-re-search re nil level nil face mode save-undo)))
761 ;;; ----------------------------------------------------------------------
764 (defun ti::text-re-search-forward (re &optional level face mode save-undo)
765 "Search RE and highlight forward until `point-max'.
766 Optional prefix arg tells which subexpression LEVEL to match that
767 function should highlight. point is preserved during call.
769 See `ti::text-re-search' for descriptions of FACE MODE SAVE-UNDO."
770 (interactive (list (ti::text-read-regexp) current-prefix-arg))
774 (ti::text-re-search re nil level nil face mode save-undo)))
776 ;;; ----------------------------------------------------------------------
779 (defun ti::text-re-search-backward (re &optional level face mode save-undo)
780 "Search RE and highlight backward until `point-min'.
781 Optional prefix arg tells which subexpression LEVEL to match that
782 function should highlight. point is preserved during call.
784 See `ti::text-re-search' for descriptions of FACE MODE SAVE-UNDO."
785 (interactive (list (ti::text-read-regexp) current-prefix-arg))
789 (ti::text-re-search re 'back level nil face mode save-undo)))
791 ;;; ----------------------------------------------------------------------
792 ;;; - These are handy when you want to "mark" ceratin texts for quick ref.
795 (defun ti::text-mouse-mark-region (beg end event)
796 "Highlight region BEG END. EVENT is mouse event."
798 (ti::text-mark-region beg end))
800 ;;; ----------------------------------------------------------------------
803 (defun ti::text-mouse-unmark-region (beg end event)
804 "Remove highlight from region BEG END. EVENT is mouse event."
806 (ti::text-mark-region beg end 'remove))
808 ;;; - This is for keyboard users
811 (defun ti::text-unmark-region (beg end)
812 "Remove highlight from region BEG END."
814 (ti::text-mark-region beg end 'remove))
816 ;;; ----------------------------------------------------------------------
819 (defun ti::text-mark-region (beg end &optional remove face)
820 "Highlight BEG END. With optional prefix arg REMOVE all matching FACE."
822 (let* (buffer-read-only ;set this to nil
825 (or face (ti::text-face 'search)))))
826 (with-buffer-modified
827 (put-text-property beg end 'face face))))
832 (run-hooks 'ti::text-:load-hook)
834 ;;; tinylibt.el ends here