summaryrefslogtreecommitdiff
path: root/hbut.el
diff options
context:
space:
mode:
authorStefan Monnier <monnier@iro.umontreal.ca>2019-08-14 04:29:57 -0400
committerStefan Monnier <monnier@iro.umontreal.ca>2019-08-14 04:29:57 -0400
commit6e555e763567c66ad8e50724a7dd5e286dbb1e65 (patch)
tree86fb29daa274d8007063babec826719154bf087e /hbut.el
parent98a5ecb3bf80f2b53523c769459d1a1a49491125 (diff)
parent332ef336a7ad87e25c0563bfeaf0e6758d52c59c (diff)
Merge remote-tracking branch 'hyperbole/master' into externals/hyperbolescratch/hyperbole-lexbind
Diffstat (limited to 'hbut.el')
-rw-r--r--hbut.el1049
1 files changed, 705 insertions, 344 deletions
diff --git a/hbut.el b/hbut.el
index 8198af7..cb4816b 100644
--- a/hbut.el
+++ b/hbut.el
@@ -33,8 +33,7 @@ Nil disables saving.")
(defconst ebut:max-len 100
"Maximum length of a hyper-button label.")
-
-(defun ebut:act (label)
+(defun ebut:act (label)
"Activates Hyperbole explicit button with LABEL from the current buffer."
(interactive (list (hargs:read-match "Activate explicit button labeled: "
(ebut:alist)
@@ -46,9 +45,9 @@ Nil disables saving.")
(error "(ebut:act): No explicit button labeled: %s" label))))
(defun ebut:alist (&optional file)
- "Returns alist with each element a list containing a button label.
-For use as a completion table. Gets labels from optional FILE or current
-buffer."
+ "Returns alist of ebuts in FILE or the current buffer.
+Each element is a list of just an explicit button label. For use
+as a completion table."
(mapcar 'list (ebut:list file)))
(defun ebut:at-p (&optional start-delim end-delim)
@@ -57,7 +56,8 @@ Assumes point is within first line of button label, if at all.
Optional START-DELIM and END-DELIM are strings that override default
button delimiters."
(let ((key (ebut:label-p nil start-delim end-delim)))
- (and key (ebut:get key))))
+ (when key
+ (ebut:get key))))
(defun ebut:create (&optional but-sym)
"Creates Hyperbole explicit button based on optional BUT-SYM.
@@ -95,60 +95,58 @@ Returns entry deleted (a list of attribute values) or nil."
(defun ebut:get (&optional lbl-key buffer key-src)
"Returns explicit Hyperbole button symbol given by LBL-KEY and BUFFER.
-KEY-SRC is given when retrieving global buttons and is full source pathname.
+KEY-SRC is given when retrieving global buttons and is the full source pathname.
+
Retrieves button data, converts into a button object and returns a symbol
which references the button.
-All arguments are optional. When none are given, returns symbol for
-button that point is within or nil. BUFFER defaults to the current
+All arguments are optional. When none are given, returns a symbol for
+the button that point is within or nil. BUFFER defaults to the current
buffer."
(hattr:clear 'hbut:current)
(save-excursion
(let ((key-file) (key-dir) (but-data) (actype))
- (or lbl-key (setq lbl-key (ebut:label-p)))
- (if buffer
- (if (bufferp buffer) (set-buffer buffer)
+ (unless lbl-key
+ (setq lbl-key (ebut:label-p)))
+ (when buffer
+ (if (bufferp buffer)
+ (set-buffer buffer)
(error "(ebut:get): Invalid buffer argument: %s" buffer)))
- (if key-src
- nil
- (if (equal lbl-key (ebut:label-p))
- nil
+ (when (not key-src)
+ (when (not (equal lbl-key (ebut:label-p)))
(goto-char (point-min))
(ebut:next-occurrence lbl-key))
- (if (setq key-src (ebut:key-src 'full))
- ;; `ebut:key-src' sets current buffer to key-src buffer.
- (setq buffer (current-buffer)))
- )
- (if (and (stringp lbl-key) key-src)
- (progn
- (if (stringp key-src)
- (setq key-dir (file-name-directory key-src)
- key-file (file-name-nondirectory key-src)))
- (setq but-data (and key-src
- (hbdata:get-entry lbl-key (or key-file key-src)
- key-dir)))
- (if (null but-data)
- nil
- (hattr:set 'hbut:current 'lbl-key lbl-key)
- (hattr:set 'hbut:current 'loc key-src)
- (hattr:set 'hbut:current 'categ 'explicit)
- (hattr:set 'hbut:current 'action nil)
- (hattr:set 'hbut:current 'actype
- (intern (setq actype (hbdata:actype but-data))))
- ;; Hyperbole V1 referent compatibility
- (if (= (length actype) 2)
- (hattr:set 'hbut:current 'referent
- (hbdata:referent but-data)))
- (hattr:set 'hbut:current 'args (hbdata:args but-data))
- (hattr:set 'hbut:current 'creator (hbdata:creator but-data))
- (hattr:set 'hbut:current
- 'create-time (hbdata:create-time but-data))
- (hattr:set 'hbut:current
- 'modifier (hbdata:modifier but-data))
- (hattr:set 'hbut:current
- 'mod-time (hbdata:mod-time but-data))
- 'hbut:current)
- )))))
+ (when (setq key-src (ebut:key-src 'full))
+ ;; `ebut:key-src' sets current buffer to key-src buffer.
+ (setq buffer (current-buffer))))
+ (when (and (stringp lbl-key) key-src)
+ (when (stringp key-src)
+ (setq key-dir (file-name-directory key-src)
+ key-file (file-name-nondirectory key-src)))
+ (setq but-data (and key-src
+ (hbdata:get-entry lbl-key (or key-file key-src)
+ key-dir)))
+ (when but-data
+ (hattr:set 'hbut:current 'lbl-key lbl-key)
+ (hattr:set 'hbut:current 'loc key-src)
+ (hattr:set 'hbut:current 'categ 'explicit)
+ (hattr:set 'hbut:current 'action nil)
+ (hattr:set 'hbut:current 'actype
+ (intern (setq actype (hbdata:actype but-data))))
+ ;; Hyperbole V1 referent compatibility
+ (if (= (length actype) 2)
+
+ (hattr:set 'hbut:current 'referent
+ (hbdata:referent but-data)))
+ (hattr:set 'hbut:current 'args (hbdata:args but-data))
+ (hattr:set 'hbut:current 'creator (hbdata:creator but-data))
+ (hattr:set 'hbut:current
+ 'create-time (hbdata:create-time but-data))
+ (hattr:set 'hbut:current
+ 'modifier (hbdata:modifier but-data))
+ (hattr:set 'hbut:current
+ 'mod-time (hbdata:mod-time but-data))
+ 'hbut:current)))))
(defun ebut:is-p (object)
"Returns non-nil if OBJECT denotes an explicit Hyperbole button."
@@ -160,131 +158,40 @@ buffer."
(and (stringp key) (stringp label)
(equal key (downcase (ebut:label-to-key label)))))
-(defun ebut:key-src (&optional full)
- "Returns key source (usually unqualified) for current Hyperbole button.
-Also sets current buffer to key source.
-With optional FULL when source is a pathname, the full pathname is returned."
- (let ((src (cond ((hmail:mode-is-p) (current-buffer))
- ;; If buffer represents the output of a document
- ;; formatter, e.g. an Info document produced from a
- ;; Texinfo source, then return the Texinfo source
- ;; file, for example.
- ((ebut:key-src-fmt))
- ;; Handle directory movement within `make' output.
- ((save-excursion
- (and (re-search-backward
- "^[a-z]*make[^a-z]+\\(Entering\\|Leaving\\) directory `\\([^']+\\)'" nil t)
- (string-equal "Entering"
- (buffer-substring (match-beginning 1)
- (match-end 1)))))
- (let ((limit (match-end 2))
- ;; Latest working directory that `make' reported
- (wd (buffer-substring (match-beginning 2)
- (match-end 2)))
- cd)
- ;; But another cd or pushd command may have been issued.
- ;; Return the closest directory from the make output.
- (if (re-search-backward
- "\\<\\(cd\\|pushd\\)\\s +[\"\']?\\([^;\"\'\n\r\^L\\]+\\)"
- limit t)
- (progn (setq cd (buffer-substring (match-beginning 2)
- (match-end 2)))
- ;; Eliminate any trailing whitespace.
- (setq cd (substring
- cd 0 (string-match "\\s +\\'" cd)))
- (expand-file-name cd wd))
- wd)))
- (buffer-file-name
- (if full
- buffer-file-name
- (file-name-nondirectory buffer-file-name)))
- ;; Handle any preceding @loc hyp-source implicit button location references.
- ;; This is used in report buffers of explicit buttons, i.e. hui:hbut-report.
- ((save-excursion
- (save-restriction
- (widen)
- (end-of-visible-line)
- (if (and (search-backward hbut:source-prefix nil t)
- (or (memq (preceding-char) '(?\n ?\r))
- (= (point) (point-min))))
- (hbut:source full)))))
- (t (current-buffer)))))
- (cond ((null src) nil)
- ((bufferp src)
- (set-buffer src)
- src)
- ((file-directory-p src)
- (file-name-as-directory src))
- ((file-readable-p src)
- (set-buffer (find-file-noselect src))
- src)
- ((file-readable-p (setq src (hpath:symlink-referent src)))
- (set-buffer (find-file-noselect src))
- src))))
-
-(defun ebut:key-src-fmt ()
- "Returns unformatted filename associated with formatted current buffer.
-This is used to obtain the source of explicit buttons for buffers that
-represent the output of particular document formatters."
- (and (or (eq major-mode 'Info-mode)
- (string-match "\\.info\\(-[0-9]+\\)?$" (buffer-name)))
- (let ((src (and buffer-file-name
- (substring
- buffer-file-name
- 0 (string-match "\\.[^.]+$" buffer-file-name)))))
- (cond ((file-exists-p (concat src ".texi"))
- (concat src ".texi"))
- ((file-exists-p (concat src ".texinfo"))
- (concat src ".texinfo"))
- ((current-buffer))))))
-
-(defun ebut:key-to-label (lbl-key)
- "Unnormalizes LBL-KEY and returns a label string approximating actual label."
- (if lbl-key
- (let* ((pos 0) (len (length lbl-key)) (lbl) c)
- (while (< pos len)
- (setq c (aref lbl-key pos)
- lbl (concat lbl
- (if (eq c ?_)
- (if (or (= (1+ pos) len)
- (not (eq (aref lbl-key (1+ pos)) ?_)))
- " "
- (setq pos (1+ pos))
- "_")
- (char-to-string c)))
- pos (1+ pos)))
- lbl)))
+(defalias 'ebut:key-src 'hbut:key-src)
+(defalias 'ebut:key-src-set-buffer 'hbut:key-src-set-buffer)
+(defalias 'ebut:key-src-fmt 'hbut:key-src-fmt)
+(defalias 'ebut:key-to-label 'hbut:key-to-label)
(defun ebut:label-p (&optional as-label start-delim end-delim pos-flag two-lines-flag)
- "Returns key for Hyperbole button label that point is within.
-Returns nil if not within a label. Assumes point is within first line
- of button label, if at all.
-All following arguments are optional. If AS-LABEL is non-nil, label
-is returned rather than the key derived from the label. START-DELIM
-and END-DELIM are strings that override default button delimiters.
-With POS-FLAG non-nil, returns list of label-or-key,
-but-start-position, but-end-position. Positions include delimiters.
-With TWO-LINES-FLAG non-nil, constrains label search to two lines."
+ "Returns key for the Hyperbole explicit button label that point is within, else nil.
+Assumes point is within the first line of any button label. All
+following arguments are optional. If AS-LABEL is non-nil, label
+is returned rather than the key derived from the label.
+START-DELIM and END-DELIM are strings that override default
+button delimiters. With POS-FLAG non-nil, returns list of
+label-or-key, but-start-position, but-end-position. Positions
+include delimiters. With TWO-LINES-FLAG non-nil, constrains
+label search to two lines."
(let ((opoint (point))
- (npoint)
(quoted "\\(^\\|[^\\{]\\)")
- (start)
(ebut:max-len ebut:max-len)
- lbl-key end but-start but-end)
- (or start-delim (setq start-delim ebut:start))
- (or end-delim (setq end-delim ebut:end))
- (setq npoint (+ opoint (length start-delim)))
- ;; Ensure label is not blank
+ npoint start lbl-key end but-start but-end start-regexp end-regexp)
+ (unless start-delim (setq start-delim ebut:start))
+ (unless end-delim (setq end-delim ebut:end))
+ (setq start-regexp (regexp-quote start-delim)
+ end-regexp (regexp-quote end-delim)
+ npoint (+ opoint (length start-delim)))
+ ;; Ensure label is not blank and point is within matching delimiters
(save-excursion
- (beginning-of-line)
+ (forward-line 0)
(while (and (progn
- (while (re-search-forward
- (concat quoted (regexp-quote start-delim))
- npoint t)
+ (while (and (< (point) npoint)
+ (re-search-forward (concat quoted start-regexp) npoint t))
(setq start t))
start)
- (re-search-forward (concat "[^\\{]" (regexp-quote end-delim))
- npoint t))
+ (< (point) opoint)
+ (re-search-forward (concat "[^\\{]" end-regexp) opoint t))
(setq start nil))
(when start
(setq start (point)
@@ -302,124 +209,51 @@ With TWO-LINES-FLAG non-nil, constrains label search to two lines."
(forward-line 2)
(setq ebut:max-len (- (point) start))))
(and (< (point) (+ start ebut:max-len))
- (re-search-forward (concat quoted (regexp-quote end-delim))
- (+ start ebut:max-len) t)
+ (re-search-forward (concat quoted end-regexp) (+ start ebut:max-len) t)
(setq but-end (point)
end (- (point) (length end-delim))
- lbl-key (ebut:label-to-key (buffer-substring start end)))
+ lbl-key (ebut:label-to-key (buffer-substring-no-properties start end)))
(cond (pos-flag
(if as-label
(list (ebut:key-to-label lbl-key) but-start but-end)
(list lbl-key but-start but-end)))
(t (if as-label (ebut:key-to-label lbl-key) lbl-key))))))))
-(defun ebut:label-regexp (lbl-key &optional no-delim)
- "Unnormalizes LBL-KEY. Returns regular expr matching delimited but label.
-Optional NO-DELIM leaves off delimiters and leading and trailing space."
- (if lbl-key
- (let* ((pos 0)
- (len (length lbl-key))
- (c)
- (sep0 "[ \t\n\r]*")
- (sep "[ \t\n\r]+")
- (regexp (if no-delim "" (concat (regexp-quote ebut:start) sep0)))
- (case-fold-search))
- (while (< pos len)
- (setq c (aref lbl-key pos)
- regexp (concat regexp
- (if (eq c ?_)
- (if (or (= (1+ pos) len)
- (not (eq (aref lbl-key (1+ pos)) ?_)))
- sep
- (setq pos (1+ pos))
- "_")
- (regexp-quote (char-to-string c))))
- pos (1+ pos)))
- (if no-delim regexp
- (setq regexp (concat regexp sep0 (regexp-quote ebut:end)))))))
+(defalias 'ebut:label-regexp 'hbut:label-regexp)
-(defun ebut:label-to-key (label)
- "Normalizes LABEL for use as a Hyperbole button key and returns key.
-Eliminates any fill prefix in the middle of the label, replaces `_' with
-`__', removes leading and trailing whitespace and replaces each other
-whitespace sequence with `_'."
- (if (null label)
- nil
- (setq label (hbut:fill-prefix-remove label)
- ;; Remove leading and trailing space.
- label (hypb:replace-match-string "\\`[ \t\n\r]+\\|[ \t\n\r]+\\'"
- label "" t)
- label (hypb:replace-match-string "_" label "__" t))
- (hypb:replace-match-string "[ \t\n\r]+" label "_" t)))
+(defalias 'ebut:label-to-key 'hbut:label-to-key)
(defun ebut:list (&optional file loc-p)
- "Returns list of button labels from given FILE or current buffer.
+ "Returns list of button labels from in FILE or the current buffer.
Removes duplicate labels if optional LOC-P is omitted. With LOC-P, returns
list of elements (label start end) where start and end are the buffer
-positions at which the starting button delimiter begins and ends."
+positions at which the button delimiter begins and ends."
(interactive)
(setq file (if file (and (file-exists-p file) (find-file-noselect file))
(current-buffer)))
- (if file
- (progn
- (set-buffer file)
- (let ((buts (ebut:map (if loc-p
- (lambda (lbl start end)
- ;; Normalize label spacing
- (list (ebut:key-to-label
- (ebut:label-to-key lbl))
- start end))
- (lambda (lbl start end)
- ;; Normalize label spacing
- (ebut:key-to-label
- (ebut:label-to-key lbl)))))))
- (if loc-p buts (if buts (apply #'set:create buts)))))))
-
-(defalias 'map-ebut 'ebut:map)
-(defun ebut:map (but-func &optional start-delim end-delim
- regexp-match include-delims)
- "Applies BUT-FUNC to buttons delimited by optional START-DELIM and END-DELIM.
+ (when file
+ (set-buffer file)
+ (let ((buts (ebut:map (if loc-p
+ (lambda (lbl start end)
+ ;; Normalize label spacing
+ (list (ebut:key-to-label (ebut:label-to-key lbl))
+ start end))
+ (lambda (lbl start end)
+ ;; Normalize label spacing
+ (ebut:key-to-label (ebut:label-to-key lbl)))))))
+ (if loc-p buts (when buts (apply #'set:create buts))))))
+
+(defalias 'map-ebut 'ebut:map)
+
+(defun ebut:map (but-func &optional regexp-match include-delims)
+ "Applies BUT-FUNC to the explicit buttons in the visible part of the current buffer.
If REGEXP-MATCH is non-nil, only buttons which match this argument are
considered.
-Maps over portion of buffer visible under any current restriction.
+
BUT-FUNC must take precisely three arguments: the button label, the
start position of the delimited button label and its end position (positions
-include delimiters when INCLUDE-DELIMS is non-nil).
-If END-DELIM is a symbol, e.g. t, then START-DELIM is taken as a regular
-expression which matches an entire button string."
- (or start-delim (setq start-delim ebut:start))
- (or end-delim (setq end-delim ebut:end))
- (let* ((regexp (symbolp end-delim))
- (end-sym (or regexp (substring end-delim -1)))
- (rtn)
- (ignore)
- start end but lbl)
- (save-excursion
- (goto-char (point-min))
- (setq include-delims (if include-delims 0 1))
- (while (re-search-forward
- (if regexp start-delim
- (concat (regexp-quote start-delim)
- "\\([^" end-sym "\"][^" end-sym "]*\\)"
- (regexp-quote end-delim)))
- nil t)
- (setq start (match-beginning include-delims)
- end (match-end include-delims)
- but (buffer-substring (match-beginning 0) (match-end 0))
- lbl (buffer-substring (match-beginning 1) (match-end 1))
- ;; If within a programming language buffer, ignore matches outside comments.
- ignore (and (derived-mode-p 'prog-mode)
- ;; Match is outside of a programming language comment
- (not (nth 4 (syntax-ppss)))))
- (save-excursion
- (goto-char start)
- ;; Ignore matches with quoted delimiters.
- (or ignore (setq ignore (memq (preceding-char) '(?\\ ?\{)))))
- (cond (ignore (setq ignore nil))
- ((or (not regexp-match)
- (string-match regexp-match but))
- (setq rtn (cons (funcall but-func lbl start end) rtn))))))
- (nreverse rtn)))
+include delimiters when INCLUDE-DELIMS is non-nil)."
+ (hbut:map but-func ebut:start ebut:end regexp-match include-delims))
(defun ebut:modify (&optional lbl-key but-sym)
"Modifies existing Hyperbole button from optional LBL-KEY and BUT-SYM.
@@ -451,12 +285,12 @@ move to the first occurrence of the button."
(goto-char (+ (match-beginning 0) (length ebut:start)))))
(defun ebut:operate (curr-label new-label)
- "Operates on and modifies properties of a new or existing Hyperbole button given by CURR-LABEL.
+ "Operates on and modifies properties of a new or existing explicit button given by CURR-LABEL.
When NEW-LABEL is non-nil, this is substituted for CURR-LABEL and the
associated button is modified. Otherwise, a new button is created.
-Returns instance string appended to label to form unique label, nil if
-label is already unique. Signals an error when no such button is found
-in the current buffer."
+Returns instance string appended to label to form a per-buffer unique
+label; nil if label is already unique. Signals an error when no such
+button is found in the current buffer."
(let* ((lbl-key (ebut:label-to-key curr-label))
(lbl-regexp (ebut:label-regexp lbl-key))
(modify new-label)
@@ -474,10 +308,10 @@ in the current buffer."
(let* ((but-key-and-pos (ebut:label-p nil nil nil 'pos))
(at-but (equal (car but-key-and-pos)
(ebut:label-to-key new-label))))
- (if at-but
- (ebut:delimit (nth 1 but-key-and-pos)
- (nth 2 but-key-and-pos)
- instance-flag))
+ (when at-but
+ (ebut:delimit (nth 1 but-key-and-pos)
+ (nth 2 but-key-and-pos)
+ instance-flag))
(cond ((ebut:map
(lambda (lbl start end)
(delete-region start end)
@@ -485,7 +319,7 @@ in the current buffer."
(point)
(progn (insert new-label) (point))
instance-flag))
- nil nil lbl-regexp 'include-delims))
+ lbl-regexp 'include-delims))
(at-but)
((hypb:error "(ebut:operate): No button matching: %s" curr-label))))
;; Add a new button.
@@ -496,7 +330,7 @@ in the current buffer."
buf-lbl (buffer-substring start end))
(equal buf-lbl curr-label))
nil)
- ((progn (if start (goto-char start))
+ ((progn (when start (goto-char start))
(looking-at (regexp-quote curr-label)))
(setq start (point)
end (match-end 0)))
@@ -516,15 +350,15 @@ in the current buffer."
(re-search-backward regexp nil t)))
(goto-char (+ (match-beginning 0) (length ebut:start))))))
;; instance-flag might be 't which we don't want to return.
- (if (stringp instance-flag) instance-flag))
+ (when (stringp instance-flag) instance-flag))
(hypb:error
"(ebut:operate): Operation failed. Check button attribute permissions: %s"
hattr:filename))))
(defun ebut:search (string out-buf &optional match-part)
"Writes explicit button lines matching STRING to OUT-BUF.
-Uses Hyperbole space into which user has written buttons for the search.
-By default, only matches for whole button labels are found, optional MATCH-PART
+Searches across all files into which the user has previously saved explicit buttons.
+By default, only matches for whole button labels are found; optional MATCH-PART
enables partial matches."
(let* ((buffers (mapcar (lambda (dir)
(expand-file-name hattr:filename dir))
@@ -600,6 +434,32 @@ enables partial matches."
(if kill-buf (kill-buffer currbuf)))))))))))
total))
+(defun ebut:to (lbl-key)
+ "Finds the nearest explicit button with LBL-KEY (a label or label key) within the visible portion of the current buffer.
+Leaves point inside the button label. Returns the symbol for the button, else nil."
+ ;; Handle a label given rather than a label key
+ (if (string-match-p "\\s-" lbl-key)
+ (setq lbl-key (ebut:label-to-key lbl-key)))
+ (let ((regexp (hbut:label-regexp lbl-key t))
+ pos
+ found)
+ (save-excursion
+ ;; Since point might be in the middle of the matching button,
+ ;; move to the start of line to ensure don't miss it when
+ ;; searching forward.
+ (forward-line 0)
+ ;; re-search forward
+ (while (and (not found) (re-search-forward regexp nil t))
+ (setq pos (match-beginning 0)
+ found (equal (ebut:label-p nil nil nil nil t) lbl-key)))
+ ;; re-search backward
+ (while (and (not found) (re-search-backward regexp nil t))
+ (setq pos (match-beginning 0)
+ found (equal (ebut:label-p nil nil nil nil t) lbl-key))))
+ (when found
+ (goto-char pos)
+ (ebut:at-p))))
+
;;; ------------------------------------------------------------------------
(defun ebut:delimit (start end instance-str)
"Delimits button label spanning region START to END in current buffer.
@@ -616,7 +476,7 @@ Inserts INSTANCE-STR after END, before ending delimiter."
(insert ebut:start)
(goto-char end)
(insert instance-str ebut:end)
- ;; Insert any comment before the start marker.
+ ;; Insert any comment delimiter before the start marker.
(set-marker-insertion-type start t)
(hbut:comment start end)
(if (fboundp 'hproperty:but-add)
@@ -638,9 +498,9 @@ Inserts INSTANCE-STR after END, before ending delimiter."
"\\)" match-part (regexp-quote ebut:end)))
(defconst ebut:start "<("
- "String matching the start of a hyper-button.")
+ "String matching the start of a Hyperbole explicit hyper-button.")
(defconst ebut:end ")>"
- "String matching the end of a hyper-button.")
+ "String matching the end of a Hyperbole explicit hyper-button.")
(defconst ebut:instance-sep ":"
"String of one character, separates an ebut label from its instance num.")
@@ -648,25 +508,36 @@ Inserts INSTANCE-STR after END, before ending delimiter."
;;; gbut class - Global Hyperbole buttons - activated by typing label name
;;; ========================================================================
-(defvar gbut:file (expand-file-name hbmap:filename hbmap:dir-user)
+(defvar gbut:file (expand-file-name hbmap:filename hbmap:dir-user)
"File that stores globally accessible Hyperbole buttons, accessed by name.")
-(defun gbut:act (label)
+(defun gbut:act (label)
"Activates Hyperbole global button with LABEL."
(interactive (list (hargs:read-match "Activate global button labeled: "
(mapcar 'list (gbut:label-list))
- nil t nil 'ebut)))
+ nil t nil 'gbut)))
(cond ((null label)
(error "(gbut:act): You have not created any global buttons"))
((equal label "")
(error "(gbut:act): Please try again and type ? for a list of existing global button names"))
(t (let* ((lbl-key (hbut:label-to-key label))
- (but (ebut:get lbl-key nil gbut:file)))
+ (but (gbut:get lbl-key)))
(if but
(hbut:act but)
- (error "(gbut:act): No global button labeled: %s" label))))))
+ (error "(gbut:act): No global button found for label: %s" label))))))
+
+(defun gbut:get (&optional lbl-key)
+ "Returns global Hyperbole button symbol given by optional LBL-KEY if found in gbut:file.
-(defun gbut:help (label)
+Retrieves any button data, converts into a button object and returns a symbol
+which references the button.
+
+All arguments are optional. When none are given, returns a symbol for
+the button that point is within or nil."
+ (or (ebut:get lbl-key nil gbut:file)
+ (ibut:get lbl-key nil gbut:file)))
+
+(defun gbut:help (label)
"Displays help for Hyperbole global button with LABEL."
(interactive (list (hargs:read-match "Report on global button labeled: "
(mapcar 'list (gbut:label-list))
@@ -677,18 +548,39 @@ Inserts INSTANCE-STR after END, before ending delimiter."
(hbut:report but)
(error "(gbut:help): No global button labeled: %s" label))))
-(defun gbut:label-list ()
+(defun gbut:label-list ()
"Returns list of global button labels."
(mapcar 'hbut:key-to-label (gbut:key-list)))
+
+(defun gbut:to (lbl-key)
+ "Finds the global button with LBL-KEY (a label or label key) within the visible portion of the global button file.
+Leaves point inside the button label, if it has one.
+Returns the symbol for the button, else nil."
+ (when (file-readable-p gbut:file)
+ (let ((obuf (current-buffer))
+ (opoint (point))
+ found)
+ (set-buffer (find-file-noselect gbut:file))
+ (setq found (or (ebut:to lbl-key) (ibut:to lbl-key)))
+ (if found
+ (hpath:display-buffer (current-buffer) 'this-window)
+ (set-buffer obuf)
+ (goto-char opoint))
+ found)))
+
;;; ------------------------------------------------------------------------
-(defun gbut:key-list ()
+(defun gbut:key-list ()
"Returns list of global button label keys."
+ (nconc (gbut:ebut-key-list) (gbut:ibut-key-list)))
+
+(defun gbut:ebut-key-list ()
+ "Returns a list of explicit button label keys from the global button file."
(save-excursion
(if (hbdata:to-entry-buf gbut:file)
- (let ((gbuts))
+ (let (gbuts)
(save-restriction
- (narrow-to-region (point) (if (search-forward "\^L" nil t)
+ (narrow-to-region (point) (if (search-forward "\f" nil t)
(point) (point-max)))
(goto-char (point-min))
(condition-case ()
@@ -696,6 +588,15 @@ Inserts INSTANCE-STR after END, before ending delimiter."
(error nil))
gbuts)))))
+(defun gbut:ibut-key-list ()
+ "Returns a list of implicit button label keys from the global button file."
+ (when (file-readable-p gbut:file)
+ (save-excursion
+ (set-buffer (find-file-noselect gbut:file))
+ (save-restriction
+ (widen)
+ (ibut:label-map #'(lambda (label start end) (ibut:label-to-key label)))))))
+
;;; ========================================================================
;;; hattr class
;;; ========================================================================
@@ -807,10 +708,10 @@ Suitable for use as part of `write-file-functions'."
"Sets OBJ-SYMBOL's attribute ATTR-SYMBOL to ATTR-VALUE and returns ATR-VALUE."
(put obj-symbol attr-symbol attr-value))
-(defalias 'hattr:summarize 'hattr:report)
+(defalias 'hattr:summarize 'hattr:report)
(defvar hattr:filename
- (if hyperb:microcruft-os-p "_hypb" ".hypb")
+ (if hyperb:microsoft-os-p "_hypb" ".hypb")
"Per directory file name in which explicit button attributes are stored.
If you change its value, you will be unable to use buttons created by
others who use a different value!")
@@ -914,12 +815,115 @@ Ignores email-related buffers."
hbut:fill-prefix-regexps))
label)
+(defun hbut:get (&optional lbl-key buffer key-src)
+ "Returns explicit or labeled implicit Hyperbole button symbol given by LBL-KEY and BUFFER.
+KEY-SRC is given when retrieving global buttons and is the full source pathname.
+
+Returns a symbol which references the button.
+
+All arguments are optional. When none are given, returns a
+symbol for the button or button label that point is within or
+nil. BUFFER defaults to the current buffer."
+ (or (ebut:get lbl-key buffer key-src) (ibut:get lbl-key buffer key-src)))
+
(defun hbut:is-p (object)
"Returns non-nil if object denotes a Hyperbole button."
- (and (symbolp object) (hattr:get object 'categ)))
+ (and (symbolp object) (hattr:get object 'categ)))
+
+(defun hbut:key-src (&optional full)
+ "Returns key source (usually unqualified) for current Hyperbole button.
+Also sets current buffer to key source.
+With optional FULL when source is a pathname, the full pathname is returned."
+ (let ((src (cond ((hmail:mode-is-p) (current-buffer))
+ ;; If buffer represents the output of a document
+ ;; formatter, e.g. an Info document produced from a
+ ;; Texinfo source, then return the Texinfo source
+ ;; file, for example.
+ ((hbut:key-src-fmt))
+ ;; Handle directory movement within `make' output.
+ ((save-excursion
+ (and (re-search-backward
+ "^[a-z]*make[^a-z]+\\(Entering\\|Leaving\\) directory `\\([^']+\\)'" nil t)
+ (string-equal "Entering" (match-string 1))))
+ (let ((limit (match-end 2))
+ ;; Latest working directory that `make' reported
+ (wd (match-string 2))
+ cd)
+ ;; But another cd or pushd command may have been issued.
+ ;; Return the closest directory from the make output.
+ (if (re-search-backward
+ "\\<\\(cd\\|pushd\\)\\s +[\"\']?\\([^;\"\'\n\r\^L\\]+\\)"
+ limit t)
+ (progn (setq cd (match-string 2))
+ ;; Eliminate any trailing whitespace.
+ (setq cd (substring
+ cd 0 (string-match "\\s +\\'" cd)))
+ (expand-file-name cd wd))
+ wd)))
+ (buffer-file-name
+ (if full
+ buffer-file-name
+ (file-name-nondirectory buffer-file-name)))
+ ;; Handle any preceding @loc hyp-source implicit button location references.
+ ;; This is used in report buffers of explicit buttons, i.e. hui:hbut-report.
+ ((save-excursion
+ (save-restriction
+ (widen)
+ (end-of-visible-line)
+ (if (and (search-backward hbut:source-prefix nil t)
+ (or (memq (preceding-char) '(?\n ?\r))
+ (= (point) (point-min))))
+ (hbut:source full)))))
+ (t (current-buffer)))))
+ (hbut:key-src-set-buffer src)))
-(defalias 'hbut:key-src 'ebut:key-src)
-(defalias 'hbut:key-to-label 'ebut:key-to-label)
+(defun hbut:key-src-fmt ()
+ "Returns unformatted filename associated with formatted current buffer.
+This is used to obtain the source of Hyperbole buttons for buffers that
+represent the output of particular document formatters."
+ (and (or (eq major-mode 'Info-mode)
+ (string-match "\\.info\\(-[0-9]+\\)?$" (buffer-name)))
+ (let ((src (and buffer-file-name
+ (substring
+ buffer-file-name
+ 0 (string-match "\\.[^.]+$" buffer-file-name)))))
+ (cond ((file-exists-p (concat src ".texi"))
+ (concat src ".texi"))
+ ((file-exists-p (concat src ".texinfo"))
+ (concat src ".texinfo"))
+ ((current-buffer))))))
+
+(defun hbut:key-src-set-buffer (src)
+ "Set buffer to SRC, a buffer, file, directory or symlink and return SRC or nil if invalid."
+ (cond ((null src) nil)
+ ((bufferp src)
+ (set-buffer src)
+ src)
+ ((file-directory-p src)
+ (file-name-as-directory src))
+ ((file-readable-p src)
+ (set-buffer (find-file-noselect src))
+ src)
+ ((file-readable-p (setq src (hpath:symlink-referent src)))
+ (set-buffer (find-file-noselect src))
+ src)))
+
+(defun hbut:key-to-label (lbl-key)
+ "Unnormalizes LBL-KEY and returns a label string for display."
+ (if lbl-key
+ (let* ((pos 0) (len (length lbl-key)) (lbl) c)
+ (while (< pos len)
+ (setq c (aref lbl-key pos)
+ lbl (concat lbl
+ (if (eq c ?_)
+ (if (or (= (1+ pos) len)
+ (not (eq (aref lbl-key (1+ pos)) ?_)))
+ " "
+ (setq pos (1+ pos))
+ "_")
+ (char-to-string c)))
+ pos (1+ pos)))
+ lbl)))
(defun hbut:label (hbut)
"Returns the label for Hyperbole button symbol HBUT."
@@ -928,8 +932,111 @@ Ignores email-related buffers."
(error "(hbut:label): Argument is not a Hyperbole button symbol, `%s'"
hbut)))
-(defalias 'hbut:label-p 'ebut:label-p)
-(defalias 'hbut:label-to-key 'ebut:label-to-key)
+(defun hbut:label-p (&optional as-label start-delim end-delim pos-flag two-lines-flag)
+ "Returns key for the Hyperbole button label that point is within, else nil.
+Assumes point is within the first line of any button label. All
+following arguments are optional. If AS-LABEL is non-nil, label
+is returned rather than the key derived from the label.
+START-DELIM and END-DELIM are strings that override default
+button delimiters. With POS-FLAG non-nil, returns list of
+label-or-key, but-start-position, but-end-position. Positions
+include delimiters. With TWO-LINES-FLAG non-nil, constrains
+label search to two lines."
+ (or (ebut:label-p as-label start-delim end-delim pos-flag two-lines-flag)
+ (ibut:label-p as-label start-delim end-delim pos-flag two-lines-flag)))
+
+(defun hbut:label-regexp (lbl-key &optional no-delim start-delim end-delim)
+ "Unnormalizes LBL-KEY. Returns regular expr matching delimited button label.
+Optional NO-DELIM leaves off delimiters and leading and trailing space.
+Optional START-DELIM and END-DELIM are added around the returned
+label; these default to `ebut:start' and `ebut:end'."
+ (when lbl-key
+ (let* ((pos 0)
+ (len (length lbl-key))
+ (c)
+ (sep0 "[ \t\n\r]*")
+ (sep "[ \t\n\r]+")
+ (regexp (if no-delim "" (concat (regexp-quote (or start-delim ebut:start)) sep0)))
+ (case-fold-search))
+ (while (< pos len)
+ (setq c (aref lbl-key pos)
+ regexp (concat regexp
+ (if (eq c ?_)
+ (if (or (= (1+ pos) len)
+ (not (eq (aref lbl-key (1+ pos)) ?_)))
+ sep
+ (setq pos (1+ pos))
+ "_")
+ (regexp-quote (char-to-string c))))
+ pos (1+ pos)))
+ (if no-delim
+ regexp
+ (setq regexp (concat regexp sep0 (regexp-quote (or end-delim ebut:end))))))))
+
+(defun hbut:label-to-key (label)
+ "Normalizes LABEL for use as a Hyperbole button key and returns key.
+Eliminates any fill prefix in the middle of the label, replaces `_' with
+`__', removes leading and trailing whitespace and replaces each other
+whitespace sequence with `_'."
+ (when label
+ (setq label (hbut:fill-prefix-remove label)
+ ;; Remove leading and trailing space.
+ label (hypb:replace-match-string "\\`[ \t\n\r]+\\|[ \t\n\r]+\\'"
+ label "" t)
+ label (hypb:replace-match-string "_" label "__" t))
+ (hypb:replace-match-string "[ \t\n\r]+" label "_" t)))
+
+(defun hbut:map (but-func &optional start-delim end-delim
+ regexp-match include-delims)
+ "Applies BUT-FUNC to a set of Hyperbole buttons in the visible part of the current buffer.
+The set of buttons are those whose labels are delimited by optional START-DELIM and
+END-DELIM and that match any optional REGEXP-MATCH.
+
+START-DELIM defaults to ebut:start; END-DELIM defaults to ebut:end.
+If END-DELIM is a symbol, e.g. t, then START-DELIM is taken as a regular
+expression which matches an entire button string.
+
+BUT-FUNC must take precisely three arguments: the button label, the
+start position of the delimited button label and its end position (positions
+include delimiters when INCLUDE-DELIMS is non-nil)."
+ (or start-delim (setq start-delim ebut:start))
+ (or end-delim (setq end-delim ebut:end))
+ (let* ((regexp (symbolp end-delim))
+ (end-sym (or regexp (substring end-delim -1)))
+ (rtn)
+ (ignore)
+ start end but lbl)
+ (save-excursion
+ (goto-char (point-min))
+ (setq include-delims (if include-delims 0 1))
+ (while (re-search-forward
+ (if regexp start-delim
+ (concat (regexp-quote start-delim)
+ "\\([^" end-sym "\"][^" end-sym "]*\\)"
+ (regexp-quote end-delim)))
+ nil t)
+ (setq start (match-beginning include-delims)
+ end (match-end include-delims)
+ but (match-string 0)
+ lbl (match-string 1)
+ ;; If within a programming language buffer, ignore matches outside comments.
+ ignore (hbut:outside-comment-p))
+ (save-excursion
+ (goto-char start)
+ ;; Ignore matches with quoted delimiters.
+ (or ignore (setq ignore (memq (preceding-char) '(?\\ ?\{)))))
+ (cond (ignore (setq ignore nil))
+ ((or (not regexp-match)
+ (string-match regexp-match but))
+ (setq rtn (cons (funcall but-func lbl start end) rtn))))))
+ (nreverse rtn)))
+
+(defun hbut:outside-comment-p ()
+ "Returns t if within a programming language buffer and prior regexp match is outside a comment, else nil."
+ (when (and (derived-mode-p 'prog-mode)
+ (not (eq major-mode 'lisp-interaction-mode)))
+ ;; Match is outside of a programming language comment
+ (not (nth 4 (syntax-ppss)))))
(defun hbut:report (&optional arg)
"Pretty prints the attributes of a button or buttons.
@@ -1003,17 +1110,17 @@ Returns number of buttons reported on or nil if none."
If a file, always returns a full path if optional FULL is non-nil."
(goto-char (match-end 0))
(cond ((looking-at "#<buffer \"?\\([^\n\"]+\\)\"?>")
- (get-buffer (buffer-substring (match-beginning 1)
- (match-end 1))))
+ (get-buffer (match-string 1)))
((looking-at "\".+\"")
- (let* ((file (buffer-substring (1+ (match-beginning 0))
- (1- (match-end 0))))
+ (let* ((file (buffer-substring-no-properties
+ (1+ (match-beginning 0))
+ (1- (match-end 0))))
(absolute (file-name-absolute-p file)))
(if (and full (not absolute))
(expand-file-name file default-directory)
file)))))
-(defalias 'hbut:summarize 'hbut:report)
+(defalias 'hbut:summarize 'hbut:report)
(defvar hbut:current nil
"The currently selected Hyperbole button. Available to action routines.")
@@ -1023,44 +1130,157 @@ If a file, always returns a full path if optional FULL is non-nil."
This expression should be followed immediately by a file-name indicating the
source file for the buttons in the menu, if any.")
+(defun hbut:label-list ()
+ "Returns list of current buffer's Hyperbole button labels."
+ (mapcar 'hbut:key-to-label (hbut:key-list)))
+
+;;; ------------------------------------------------------------------------
+
+(defun hbut:key-list ()
+ "Returns list of global button label keys."
+ (nconc (hbut:ebut-key-list) (hbut:ibut-key-list)))
+
+(defun hbut:ebut-key-list (&optional key-src)
+ "Returns a list of explicit button label keys from optional KEY-SRC or the current buffer."
+ (save-excursion
+ (if (hbdata:to-entry-buf (or key-src (buffer-file-name)))
+ (let (hbuts)
+ (save-restriction
+ (narrow-to-region (point) (if (search-forward "\f" nil t)
+ (point) (point-max)))
+ (goto-char (point-min))
+ (condition-case ()
+ (while (setq hbuts (cons (car (read (current-buffer))) hbuts)))
+ (error nil))
+ hbuts)))))
+
+(defun hbut:ibut-key-list (&optional key-src)
+ "Returns a list of implicit button label keys from optional KEY-SRC or the current buffer."
+ (save-excursion
+ (when (hbut:key-src-set-buffer (or key-src (current-buffer)))
+ (save-restriction
+ (widen)
+ (ibut:label-map #'(lambda (label start end) (ibut:label-to-key label)))))))
+
;;; ========================================================================
;;; ibut class - Implicit Hyperbole Buttons
;;; ========================================================================
+(defun ibut:alist (&optional file)
+ "Returns alist of labeled ibuts in FILE or the current buffer.
+Each element is a list of just an implicit button label. For use
+as a completion table."
+ (mapcar 'list (ibut:list file)))
+
(defun ibut:at-p (&optional key-only)
"Returns symbol for implicit button at point, else nil.
-With optional KEY-ONLY, returns only the label key for button."
- (let ((types (htype:category 'ibtypes))
- ;; Global var used in (hact) function, don't delete.
- (hrule:action 'actype:identity)
- (itype)
- (args)
- (is-type))
- (or key-only (hattr:clear 'hbut:current))
- (while (and (not is-type) types)
- (setq itype (car types))
- (if (setq args (funcall itype))
- (setq is-type itype)
- (setq types (cdr types))))
- (if is-type
- (if key-only
- (hattr:get 'hbut:current 'lbl-key)
- (hattr:set 'hbut:current 'loc (save-excursion
- (hbut:key-src 'full)))
- (hattr:set 'hbut:current 'categ is-type)
- (or (hattr:get 'hbut:current 'args)
- (not (listp args))
- (progn
- (hattr:set 'hbut:current 'actype
- (or
- ;; Hyperbole action type
- (intern-soft (concat "actypes::"
- (symbol-name (car args))))
- ;; Regular Emacs Lisp function symbol
- (car args)
- ))
- (hattr:set 'hbut:current 'args (cdr args))))
- 'hbut:current))))
+Point may be on the implicit button or its optional preceding label.
+With optional KEY-ONLY, returns only the label key for button.
+
+Any labeled implicit button must contain at least two characters,
+excluding delimiters, not just one."
+ (let* ((opoint (point))
+ (label-key-start-end (ibut:label-p nil nil nil t t))
+ (lbl-key (car label-key-start-end)))
+ (unwind-protect
+ (when (not (hbut:outside-comment-p))
+ ;; Skip past any optional label and separators
+ (when label-key-start-end
+ (goto-char (nth 2 label-key-start-end))
+ (when (looking-at ibut:label-separator-regexp)
+ ;; Move past up to 2 possible characters of ibut
+ ;; delimiters; this prevents recognizing labeled,
+ ;; delimited ibuts of a single character but no one
+ ;; should need that.
+ (goto-char (min (+ 2 (match-end 0)) (point-max)))))
+
+ ;; Check for an implicit button at current point, record its
+ ;; attributes and return a button symbol for it.
+ (let ((types (htype:category 'ibtypes))
+ ;; Global var used in (hact) function, don't delete.
+ (hrule:action 'actype:identity)
+ (itype)
+ (args)
+ (is-type))
+ (unless key-only
+ (hattr:clear 'hbut:current))
+ (while (and (not is-type) types)
+ (setq itype (car types))
+ (if (setq args (funcall itype))
+ (setq is-type itype)
+ (setq types (cdr types))))
+ (when is-type
+ (when lbl-key
+ (hattr:set 'hbut:current 'lbl-key lbl-key))
+ (if key-only
+ (hattr:get 'hbut:current 'lbl-key)
+ (hattr:set 'hbut:current 'loc (save-excursion
+ (hbut:key-src 'full)))
+ (hattr:set 'hbut:current 'categ is-type)
+ (or (hattr:get 'hbut:current 'args)
+ (not (listp args))
+ (progn
+ (hattr:set 'hbut:current 'actype
+ (or
+ ;; Hyperbole action type
+ (intern-soft (concat "actypes::"
+ (symbol-name (car args))))
+ ;; Regular Emacs Lisp function symbol
+ (car args)))
+ (hattr:set 'hbut:current 'args (cdr args))))
+ 'hbut:current))))
+ (goto-char opoint))))
+
+(defun ibut:at-type-p (ibut-type-symbol)
+ "Returns non-nil if point is on a button of type `ibut-type-symbol`.
+Point must be on the button itself and not its label, if any.
+
+The return value is a list of the type's action type symbol and
+associated arguments from the button."
+ (when (and ibut-type-symbol (symbolp ibut-type-symbol))
+ (let ((type-name (symbol-name ibut-type-symbol)))
+ (unless (string-match "::" type-name)
+ (setq ibut-type-symbol (intern-soft (concat "ibtypes::" type-name))))
+ (when ibut-type-symbol
+ (let ((types (htype:category 'ibtypes))
+ ;; Global var used in (hact) function, don't delete.
+ (hrule:action 'actype:identity))
+ (funcall ibut-type-symbol))))))
+
+(defun ibut:get (&optional lbl-key buffer key-src)
+ "Returns implicit Hyperbole button symbol given by LBL-KEY and BUFFER.
+KEY-SRC is given when retrieving global buttons and is the full source pathname.
+
+Returns a symbol which references the button.
+
+All arguments are optional. When none are given, returns a
+symbol for the button or button label that point is within or
+nil. BUFFER defaults to the current buffer."
+ (hattr:clear 'hbut:current)
+ (save-excursion
+ (let ((key-file) (key-dir) (but-data) (actype))
+ (unless lbl-key
+ (setq lbl-key (ibut:label-p nil nil nil nil t)))
+ (when buffer
+ (if (bufferp buffer)
+ (set-buffer buffer)
+ (error "(ibut:get): Invalid buffer argument: %s" buffer)))
+ (when (not key-src)
+ (when (not (equal lbl-key (ibut:label-p nil nil nil nil t)))
+ (goto-char (point-min))
+ (ibut:next-occurrence lbl-key))
+ (when (setq key-src (hbut:key-src 'full))
+ ;; `hbut:key-src' sets current buffer to key-src buffer.
+ (setq buffer (current-buffer))))
+ (when (and (stringp lbl-key) key-src)
+ (when (stringp key-src)
+ (setq key-dir (file-name-directory key-src)
+ key-file (file-name-nondirectory key-src)))
+ (set-buffer (find-file-noselect key-src))
+ (goto-char (point-min))
+ (ibut:next-occurrence lbl-key)
+ ;; Build and return button symbol with button properties
+ (ibut:at-p)))))
(defun ibut:is-p (object)
"Returns non-nil if object denotes an implicit Hyperbole button."
@@ -1068,16 +1288,66 @@ With optional KEY-ONLY, returns only the label key for button."
(let ((categ (hattr:get object 'categ)))
(and categ (string-match "^ibtypes::" (symbol-name categ))))))
-(defun ibut:label-p ()
- "Returns key for Hyperbole implicit button label that point is on or nil."
- (ibut:at-p 'key-only))
+(defun ibut:label-map (but-func &optional start-delim end-delim
+ regexp-match include-delims)
+ "Applies BUT-FUNC to buttons delimited by optional START-DELIM and END-DELIM.
+START-DELIM defaults to ibut:label-start; END-DELIM defaults to ibut:label-end.
+If REGEXP-MATCH is non-nil, only buttons which match this argument are
+considered.
+
+Maps over portion of buffer visible under any current restriction.
+BUT-FUNC must take precisely three arguments: the button label, the
+start position of the delimited button label and its end position (positions
+include delimiters when INCLUDE-DELIMS is non-nil).
+If END-DELIM is a symbol, e.g. t, then START-DELIM is taken as a regular
+expression which matches an entire button string."
+ (hbut:map but-func ibut:label-start ibut:label-end))
+
+(defun ibut:rename (old-lbl new-lbl)
+ "Modifies a label preceding a Hyperbole implicit button in the current buffer given by LBL-KEY.
+Signals an error when no such button is found in the current buffer.
+
+Leaves point at the start of the button label which may be elsewhere
+than the current point; callers should use `save-excursion` to retain
+current."
+ ;; !! Need to handle adding instances to labels, similar to ebut:operate.
+ (cond ((or (not (stringp new-lbl)) (< (length new-lbl) 1))
+ (error "(ibut:rename): Invalid 'new-lbl' argument: \"%s\"" new-lbl))
+ ((or (not (stringp old-lbl)) (< (length old-lbl) 1))
+ (error "(ibut:rename): Invalid 'old-lbl' argument: \"%s\"" old-lbl))
+ ((ibut:to old-lbl)
+ (delete-region (point) (search-forward ibut:label-end nil t))
+ (save-excursion (insert new-lbl ibut:label-end)))
+ (t (error "(ibut:rename): Button '%s' not found in visible portion of buffer." old-lbl))))
+
+(defun ibut:label-p (&optional as-label start-delim end-delim pos-flag two-lines-flag)
+ "Returns key for the Hyperbole implicit button label that point is within, else nil.
+This is an optional label that may precede an implicit button.
+Use `ibut:at-p' instead to test if point is on either the
+implicit button itself or the label. Assumes point is within the
+first line of any button label.
+
+All following arguments are optional. If AS-LABEL is non-nil,
+label is returned rather than the key derived from the label.
+START-DELIM and END-DELIM are strings that override default
+button delimiters. With POS-FLAG non-nil, returns list of
+label-or-key, but-label-start-position, but-label-end-position.
+Positions include delimiters. With TWO-LINES-FLAG non-nil,
+constrains label search to two lines."
+ (ebut:label-p as-label (or start-delim ibut:label-start)
+ (or end-delim ibut:label-end) pos-flag two-lines-flag))
+
+(defun ibut:label-regexp (lbl-key &optional no-delim)
+ "Unnormalizes ibutton LBL-KEY. Returns regular expr matching delimited button label.
+Optional NO-DELIM leaves off delimiters and leading and trailing space."
+ (hbut:label-regexp lbl-key no-delim ibut:label-start ibut:label-end))
(defun ibut:label-set (label &optional start end)
- "Sets current implicit button attributes from LABEL and START, END position.
-Returns label. START and END are optional. When given, they specify the
-region in the buffer to flash when this implicit button is activated or
-queried for its attributes. If LABEL is a list, it is assumed to contain all
-arguments."
+ "Sets current implicit button attributes from LABEL and optional START, END positions.
+Returns label. When START and END are given, they specify the
+region in the buffer to flash when this implicit button is
+activated or queried for its attributes. If LABEL is a list, it
+is assumed to contain all arguments."
(cond ((stringp label)
(hattr:set 'hbut:current 'lbl-key (hbut:label-to-key label))
(and start (hattr:set 'hbut:current 'lbl-start start))
@@ -1089,12 +1359,103 @@ arguments."
(t (error "(ibut:label-set): Invalid label arg: `%s'" label)))
label)
+(defun ibut:list (&optional file loc-p)
+ "Returns list of labels of labeled ibuts in FILE or the current buffer.
+Removes duplicate labels if optional LOC-P is omitted. With LOC-P, returns
+list of elements (label start end) where start and end are the buffer
+positions at which the button label delimiter begins and ends."
+ (interactive)
+ (setq file (if file (and (file-exists-p file) (find-file-noselect file))
+ (current-buffer)))
+ (when file
+ (set-buffer file)
+ (let ((buts (ibut:map (if loc-p
+ (lambda (lbl start end)
+ ;; Normalize label spacing
+ (list (ibut:key-to-label (ibut:label-to-key lbl))
+ start end))
+ (lambda (lbl start end)
+ ;; Normalize label spacing
+ (ibut:key-to-label (ibut:label-to-key lbl)))))))
+ (if loc-p buts (when buts (apply #'set:create buts))))))
+
+(defalias 'ibut:key-src 'hbut:key-src)
+(defalias 'ibut:key-to-label 'hbut:key-to-label)
+(defalias 'ibut:label-to-key 'hbut:label-to-key)
+(defalias 'map-ibut 'ibut:map)
+
+(defun ibut:map (but-func &optional start-delim end-delim
+ regexp-match include-delims)
+ "Applies BUT-FUNC to the labeled implicit buttons in the visible part of the current buffer.
+If REGEXP-MATCH is non-nil, only buttons which match this argument are
+considered.
+
+BUT-FUNC must take precisely three arguments: the button label, the
+start position of the delimited button label and its end position (positions
+include delimiters when INCLUDE-DELIMS is non-nil)."
+ (hbut:map but-func ibut:label-start ibut:label-end regexp-match include-delims))
+
+(defun ibut:next-occurrence (lbl-key &optional buffer)
+ "Moves point to next occurrence of a labeled implicit button with LBL-KEY in optional BUFFER.
+BUFFER defaults to current buffer. It may be a buffer name.
+Returns non-nil iff occurrence is found.
+
+Remember to use (goto-char (point-min)) before calling this in order to
+move to the first occurrence of the button."
+ (if buffer
+ (if (not (or (bufferp buffer)
+ (and (stringp buffer) (get-buffer buffer))))
+ (error "(ibut:next-occurrence): Invalid buffer arg: %s" buffer)
+ (switch-to-buffer buffer)))
+ (when (re-search-forward (ibut:label-regexp lbl-key) nil t)
+ (goto-char (+ (match-beginning 0) (length ibut:label-start)))))
+
+(defalias 'ibut:summarize 'hbut:report)
+
+(defun ibut:to (lbl-key)
+ "Finds the nearest implicit button with LBL-KEY (a label or label key) within the visible portion of the current buffer.
+Leaves point inside the button text or its optional label, if it has one.
+Returns the symbol for the button, else nil."
+ ;; Handle a label given rather than a label key
+ (if (string-match-p "\\s-" lbl-key)
+ (setq lbl-key (ibut:label-to-key lbl-key)))
+ (let ((regexp (hbut:label-regexp lbl-key t))
+ pos
+ found)
+ (save-excursion
+ ;; Since point might be in the middle of the matching button,
+ ;; move to the start of line to ensure don't miss it when
+ ;; searching forward.
+ (forward-line 0)
+ ;; re-search forward
+ (while (and (not found) (re-search-forward regexp nil t))
+ (setq pos (match-beginning 0)
+ found (equal (ibut:label-p nil nil nil nil t) lbl-key)))
+ ;; re-search backward
+ (while (and (not found) (re-search-backward regexp nil t))
+ (setq pos (match-beginning 0)
+ found (equal (ibut:label-p nil nil nil nil t) lbl-key))))
+ (when found
+ (goto-char pos)
+ (ibut:at-p))))
+
+;;; ------------------------------------------------------------------------
+(defconst ibut:label-start "<["
+ "String matching the start of a Hyperbole implicit button label.")
+(defconst ibut:label-end "]>"
+ "String matching the end of a Hyperbole implicit button label.")
+(defvar ibut:label-separator " "
+ "Regular expression that separates an implicit button label from its implicit button text.")
+
+(defvar ibut:label-separator-regexp "\\s-*[-:=]*\\s-+"
+ "Regular expression that separates an implicit button label from its implicit button text.")
+
;;; ========================================================================
;;; ibtype class - Implicit button types
;;; ========================================================================
-(defalias 'defib 'ibtype:create)
-(put 'ibtype:create 'lisp-indent-function 'defun)
+(defalias 'defib 'ibtype:create)
+(put 'ibtype:create 'lisp-indent-function 'defun)
(defmacro ibtype:create (type params doc at-p &optional to-p style)
"Creates Hyperbole implicit button TYPE (unquoted sym) with PARAMS, described by DOC.
PARAMS are presently ignored.