Mercurial > hg > xemacs-beta
diff lisp/hyperbole/hui.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 4103f0995bd7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/hyperbole/hui.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,917 @@ +;;!emacs +;; +;; FILE: hui.el +;; SUMMARY: GNU Emacs User Interface to Hyperbole +;; USAGE: GNU Emacs Lisp Library +;; KEYWORDS: hypermedia +;; +;; AUTHOR: Bob Weiner +;; ORG: Brown U. +;; +;; ORIG-DATE: 19-Sep-91 at 21:42:03 +;; LAST-MOD: 25-Aug-95 at 02:26:56 by Bob Weiner +;; +;; This file is part of Hyperbole. +;; Available for use and distribution under the same terms as GNU Emacs. +;; +;; Copyright (C) 1991-1995, Free Software Foundation, Inc. +;; Developed with support from Motorola Inc. +;; +;; DESCRIPTION: +;; DESCRIP-END. + +;;; ************************************************************************ +;;; Other required Elisp libraries +;;; ************************************************************************ + +(require 'hargs) (require 'set) (require 'hmail) + +;;; ************************************************************************ +;;; Public variables +;;; ************************************************************************ + +(defvar hui:ebut-delete-confirm-p t + "*Non-nil means prompt before interactively deleting explicit buttons.") + +;;; ************************************************************************ +;;; Public functions +;;; ************************************************************************ + +(defun hui:ebut-create (&optional start end) + "Creates an explicit but starting from label between optional START and END. +Indicates by delimiting and adding any necessary instance number of the button +label." + (interactive (list (and (marker-position (hypb:mark-marker t)) + (region-beginning)) + (and (marker-position (hypb:mark-marker t)) + (region-end)))) + (let ((default-lbl) lbl but-buf actype) + (save-excursion + (setq default-lbl + (hui:hbut-label-default start end (not (interactive-p))) + lbl (hui:hbut-label default-lbl "ebut-create")) + (if (not (equal lbl default-lbl)) (setq default-lbl nil)) + + (setq but-buf (if default-lbl (current-buffer) (hui:ebut-buf))) + (hui:buf-writable-err but-buf "ebut-create") + + (hattr:set 'hbut:current 'loc (hui:key-src but-buf)) + (hattr:set 'hbut:current 'dir (hui:key-dir but-buf)) + (setq actype (hui:actype)) + (hattr:set 'hbut:current 'actype actype) + (hattr:set 'hbut:current 'args (hargs:actype-get actype)) + (hattr:set 'hbut:current 'action + (and (boundp 'hui:ebut-prompt-for-action) + hui:ebut-prompt-for-action (hui:action actype))) + ) + (ebut:operate lbl nil))) + +(defun hui:ebut-delete (but-key &optional key-src) + "Deletes explicit Hyperbole button given by BUT-KEY in optional KEY-SRC. +KEY-SRC may be a buffer or a pathname, when nil the current buffer is used. +Returns t if button is deleted, nil if user chooses not to delete or signals +an error otherwise. If called interactively, prompts user whether to delete +and derives BUT-KEY from the button that point is within. +Signals an error if point is not within a button." + (interactive (list (if (ebut:at-p) + (hattr:get 'hbut:current 'lbl-key) + nil))) + (cond ((null but-key) + (hypb:error + "(ebut-delete): Point is not over the label of an existing button.")) + ((not (stringp but-key)) + (hypb:error + "(ebut-delete): Invalid label key argument: '%s'." but-key))) + (let ((interactive (interactive-p))) + (if (and hui:ebut-delete-confirm-p interactive) + (if (y-or-n-p (format "Delete button %s%s%s? " + ebut:start + (hbut:key-to-label but-key) ebut:end)) + (hui:ebut-delete-op interactive but-key key-src) + (message "")) + (hui:ebut-delete-op interactive but-key key-src)))) + +(defun hui:ebut-edit () + "Creates or modifies an explicit Hyperbole button when conditions are met. +A region must have been delimited with the action-key and point must now be +within it before this function is called or it will do nothing. The region +must be no larger than the size given by 'ebut:max-len'. It must be entirely +within or entirely outside of an existing explicit button. When region is +within the button, the button is interactively modified. Otherwise, a new +button is created interactively with the region as the default label." + (interactive) + (let ((m (marker-position (hypb:mark-marker t))) + (op action-key-depress-prev-point) (p (point)) (lbl-key)) + (if (and m (eq (marker-buffer m) (marker-buffer op)) + (< op m) (<= (- m op) ebut:max-len) + (<= p m) (<= op p)) + (progn + (if (setq lbl-key (ebut:label-p)) + (hui:ebut-modify lbl-key) + (hui:ebut-create op m)) + t)))) + +(defun hui:ebut-modify (lbl-key) + "Modifies an explicit Hyperbole button given by LBL-KEY. +Signals an error when no such button is found in the current buffer." + (interactive (list (save-excursion + (hui:buf-writable-err (current-buffer) "ebut-modify") + (or (ebut:label-p) + (ebut:label-to-key + (hargs:read-match "Button to modify: " + (ebut:alist) nil t + nil 'ebut)))))) + (let ((lbl (ebut:key-to-label lbl-key)) + (but-buf (current-buffer)) + actype but new-lbl) + (save-excursion + (or (interactive-p) + (hui:buf-writable-err but-buf "ebut-modify")) + + (or (setq but (ebut:get lbl-key but-buf)) + (progn (pop-to-buffer but-buf) + (hypb:error "(ebut-modify): Invalid button, no data for '%s'." lbl))) + + (setq new-lbl + (hargs:read + "Change button label to: " + (function + (lambda (lbl) + (and (not (string= lbl "")) (<= (length lbl) ebut:max-len)))) + lbl + (format "(ebut-modify): Enter a string of at most %s chars." + ebut:max-len) + 'string)) + + (hattr:set 'hbut:current 'loc (hui:key-src but-buf)) + (hattr:set 'hbut:current 'dir (hui:key-dir but-buf)) + (setq actype (hui:actype (hattr:get but 'actype))) + (hattr:set 'hbut:current 'actype actype) + (hattr:set 'hbut:current 'args (hargs:actype-get actype 'modifying)) + (hattr:set 'hbut:current 'action + (and (boundp 'hui:ebut-prompt-for-action) + hui:ebut-prompt-for-action (hui:action actype))) + ) + (ebut:operate lbl new-lbl))) + +(defun hui:ebut-rename (curr-label new-label) + "Renames explicit Hyperbole button given by CURR-LABEL to NEW-LABEL. +If called interactively when point is not within an explicit button: + prompts for old and new button label values and performs rename. +If called interactively when point is within an explicit button: + saves button label and tells user to edit label, then call again. + second call changes the button's name from the stored value to the + edited value. +Signals an error if any problem occurs." + (interactive + (save-excursion + (let (curr-label new-label) + (hui:buf-writable-err (current-buffer) "ebut-rename") + (if hui:ebut-label-prev + (setq curr-label hui:ebut-label-prev + new-label (ebut:label-p 'as-label)) + (setq new-label nil + curr-label + (or (ebut:label-p 'as-label) + (let ((buts (ebut:alist))) + (if (null buts) + (hypb:error "(ebut-rename): No explicit buttons in buffer.") + (prog1 (hargs:read-match + "Button label to rename: " + buts nil t nil 'ebut) + (setq new-label + (hargs:read + "Rename button label to: " + (function + (lambda (lbl) + (and (not (string= lbl "")) + (<= (length lbl) ebut:max-len)))) + curr-label + (format + "(ebut-rename): Use a quoted string of at most %s chars." + ebut:max-len) + 'string)))))))) + (list curr-label new-label)))) + + (save-excursion + (if (interactive-p) + nil + (hui:buf-writable-err (current-buffer) "ebut-rename") + (if (or (not (stringp curr-label)) (string= curr-label "")) + (hypb:error "(ebut-rename): 'curr-label' must be a non-empty string: %s" + curr-label)) + (and (stringp new-label) (string= new-label "") + (hypb:error "(ebut-rename): 'new-label' must be a non-empty string: %s" + new-label))) + (or (ebut:get (ebut:label-to-key curr-label)) + (hypb:error "(ebut-rename): Can't rename %s since no button data." + curr-label)) + ) + (cond (new-label + (ebut:operate curr-label new-label) + (setq hui:ebut-label-prev nil) + (message "Renamed from '%s' to '%s'." curr-label new-label)) + (curr-label + (setq hui:ebut-label-prev curr-label) + (message "Edit button label and use same command to finish rename.")) + (t (hypb:error "(ebut-rename): Move point to within a button label.")))) + +(defun hui:ebut-search (string &optional match-part) + "Shows lines of files/buffers containing an explicit but match for STRING. +Returns number of buttons matched and displayed. +By default, only matches for whole button labels are found, optional MATCH-PART +enables partial matches. The match lines are shown in a buffer which serves as +a menu to find any of the occurrences." + (interactive (list (read-string "Search for button string: ") + (y-or-n-p "Enable partial matches? "))) + (if (not (stringp string)) + (hypb:error "(ebut-search): String to search for is required.")) + (let* ((prefix (if (> (length string) 14) + (substring string 0 13) string)) + (out-buf (get-buffer-create (concat "*" prefix " Hypb Search*"))) + (total (ebut:search string out-buf match-part))) + (if (> total 0) + (progn + (set-buffer out-buf) + (moccur-mode) + (if (fboundp 'outline-minor-mode) + (and (progn (goto-char 1) + (search-forward "\C-m" nil t)) + (outline-minor-mode 1))) + (if (fboundp 'hproperty:but-create) + (hproperty:but-create nil nil (regexp-quote + (if match-part string + (concat ebut:start string ebut:end))))) + (goto-char (point-min)) + (pop-to-buffer out-buf) + (if (interactive-p) (message "%d match%s." total + (if (> total 1) "es" "")) + total)) + (if (interactive-p) (message "No matches.") + total)))) + +(defun hui:error (&rest args) + (hypb:error "(hui:error): Obsolete, use hypb:error instead.")) + +(defun hui:gbut-create (lbl) + "Creates Hyperbole global button with LBL." + (interactive "sCreate global button labeled: ") + (let (but-buf actype) + (save-excursion + (setq actype (hui:actype)) + (setq but-buf (set-buffer (find-file-noselect gbut:file))) + (hui:buf-writable-err but-buf "ebut-create") + ;; This prevents movement of point which might be useful to user. + (save-excursion + (goto-char (point-max)) + (hattr:set 'hbut:current 'loc (hui:key-src but-buf)) + (hattr:set 'hbut:current 'dir (hui:key-dir but-buf)) + (hattr:set 'hbut:current 'actype actype) + (hattr:set 'hbut:current 'args (hargs:actype-get actype)) + (hattr:set 'hbut:current 'action + (and (boundp 'hui:ebut-prompt-for-action) + hui:ebut-prompt-for-action (hui:action actype))) + (setq lbl (concat lbl (ebut:operate lbl nil))) + (goto-char (point-max)) + (insert "\n") + (save-buffer) + ) + (message "%s created." lbl) + ))) + +(defun hui:gbut-modify (lbl-key) + "Modifies a global Hyperbole button given by LBL-KEY. +Signals an error when no such button is found." + (interactive (list (save-excursion + (hui:buf-writable-err + (find-file-noselect gbut:file) "gbut-modify") + (hbut:label-to-key + (hargs:read-match "Global button to modify: " + (mapcar 'list (gbut:lbl-list)) + nil t nil 'ebut))))) + (let ((lbl (hbut:key-to-label lbl-key)) + (but-buf (find-file-noselect gbut:file)) + actype but new-lbl) + (save-excursion + (or (interactive-p) + (hui:buf-writable-err but-buf "gbut-modify")) + + (or (setq but (ebut:get lbl-key but-buf)) + (progn (pop-to-buffer but-buf) + (hypb:error + "(gbut-modify): Invalid button, no data for '%s'." lbl))) + + (setq new-lbl + (hargs:read + "Change global button label to: " + (function + (lambda (lbl) + (and (not (string= lbl "")) (<= (length lbl) ebut:max-len)))) + lbl + (format "(gbut-modify): Enter a string of at most %s chars." + ebut:max-len) + 'string)) + + (hattr:set 'hbut:current 'loc (hui:key-src but-buf)) + (hattr:set 'hbut:current 'dir (hui:key-dir but-buf)) + (setq actype (hui:actype (hattr:get but 'actype))) + (hattr:set 'hbut:current 'actype actype) + (hattr:set 'hbut:current 'args (hargs:actype-get actype 'modifying)) + (hattr:set 'hbut:current 'action + (and (boundp 'hui:ebut-prompt-for-action) + hui:ebut-prompt-for-action (hui:action actype))) + (set-buffer but-buf) + (ebut:operate lbl new-lbl)))) + +(defun hui:hbut-act (&optional but) + "Executes action for optional Hyperbole button symbol BUT in current buffer. +Default is the current button." + (interactive + (let ((but (hbut:at-p)) (lst)) + (list + (cond (but) + ((setq lst (ebut:alist)) + (ebut:get (ebut:label-to-key + (hargs:read-match "Button to execute: " lst nil t + (ebut:label-p 'as-label) 'ebut)))) + (t (hypb:error "(hbut-act): No explicit buttons in buffer.")))))) + (cond ((and (interactive-p) (null but)) + (hypb:error "(hbut-act): No current button to activate.")) + ((not (hbut:is-p but)) + (hypb:error "(hbut-act): Button is invalid; it has no attributes.")) + (t (or but (setq but 'hbut:current)) + (hui:but-flash) (hyperb:act but)))) + +(defun hui:hbut-help (&optional but) + "Checks for and explains an optional button given by symbol, BUT. +BUT defaults to the button whose label point is within." + (interactive) + (setq but (or but (hbut:at-p) + (ebut:get (ebut:label-to-key + (hargs:read-match "Help for button: " + (ebut:alist) nil t nil 'ebut))))) + (or but + (hypb:error "(hbut-help): Move point to a valid Hyperbole button.")) + (if (not (hbut:is-p but)) + (cond (but (hypb:error "(hbut-help): Invalid button.")) + (t (hypb:error + "(hbut-help): Not on an implicit button and no buffer explicit buttons.")))) + (let ((type-help-func (intern-soft + (concat + (htype:names 'ibtypes (hattr:get but 'categ)) + ":help")))) + (or (equal (hypb:indirect-function 'hui:but-flash) + (function (lambda nil))) + ;; Only flash button if point is on it. + (let ((lbl-key (hattr:get but 'lbl-key))) + (and lbl-key + (or (equal lbl-key (ebut:label-p)) + (equal lbl-key (ibut:label-p))) + (hui:but-flash)))) + (if type-help-func + (funcall type-help-func but) + (let ((total (hbut:report but))) + (if total (hui:help-ebut-highlight)))))) + +(defun hui:hbut-label (default-label func-name) + "Reads button label from user using DEFAULT-LABEL and caller's FUNC-NAME." + (hargs:read "Button label: " + (function + (lambda (lbl) + (and (not (string= lbl "")) (<= (length lbl) ebut:max-len)))) + default-label + (format "(%s): Enter a string of at most %s chars." + func-name ebut:max-len) + 'string)) + +(defun hui:hbut-label-default (start end &optional skip-len-test) + "Returns default label based on START and END region markers or points. +Optional SKIP-LEN-TEST means don't limit label to 'ebut:max-len' length. +Returns nil if START or END are invalid or if region fails length test. + +Also has side effect of moving point to start of default label, if any." + (if (markerp start) (setq start (marker-position start))) + (if (markerp end) (setq end (marker-position end))) + ;; Test whether to use region as default button label. + (if (and (integerp start) (integerp end) + (or skip-len-test + (<= (max (- end start) (- start end)) ebut:max-len))) + (progn (goto-char start) + (buffer-substring start end)))) + +(defun hui:hbut-report (&optional arg) + "Pretty prints attributes of current button, using optional prefix ARG. +See 'hbut:report'." + (interactive "P") + (if (and arg (symbolp arg)) + (hui:hbut-help arg) + (let ((total (hbut:report arg))) + (if total + (progn (hui:help-ebut-highlight) + (message "%d button%s." total (if (/= total 1) "s" ""))))))) + +(fset 'hui:hbut-summarize 'hui:hbut-report) + +(defun hui:link-directly () + "Creates a Hyperbole link button at depress point, linked to release point. +See also documentation for 'hui:link-possible-types'." + (let* ((link-types (hui:link-possible-types)) + (but-window action-key-depress-window) + (num-types (length link-types)) + (release-window (selected-window)) + (but-modify nil) + type-and-args lbl-key but-loc but-dir) + (select-window action-key-depress-window) + (hui:buf-writable-err (current-buffer) "link-directly") + (if (ebut:at-p) + (progn + (setq but-modify t + but-loc (hattr:get 'hbut:current 'loc) + but-dir (hattr:get 'hbut:current 'dir) + lbl-key (hattr:get 'hbut:current 'lbl-key))) + (setq but-loc (hui:key-src (current-buffer)) + but-dir (hui:key-dir (current-buffer)) + lbl-key (hbut:label-to-key + (hui:hbut-label + (if (marker-position (hypb:mark-marker t)) + (hui:hbut-label-default + (region-beginning) (region-end))) + "link-directly")))) + (select-window release-window) + + (cond ((= num-types 0) + (error "(link-directly): No possible link type to create.")) + ((= num-types 1) + (hui:link-create but-modify + but-window lbl-key but-loc but-dir + (setq type-and-args (car link-types)))) + (t;; more than 1 + (let ((item) + type) + (hui:link-create + but-modify but-window + lbl-key but-loc but-dir + (setq type-and-args + (hui:menu-select + (cons '("Link to>") + (mapcar + (function + (lambda (type-and-args) + (setq type (car type-and-args)) + (list + (capitalize + (if (string-match + "^\\(link-to\\|eval\\)-" + (setq item (symbol-name type))) + (setq item (substring + item (match-end 0))) + item)) + type-and-args + (documentation + (intern (concat "actypes::" + (symbol-name type))))))) + link-types)))))))) + (message "`%s' button %s type `%s'." + (hbut:key-to-label lbl-key) + (if but-modify "set to" "created with") + (car type-and-args)))) + +;;; ************************************************************************ +;;; Private functions +;;; ************************************************************************ + +(defun hui:action (actype &optional prompt) + "Prompts for and returns an action to override action from ACTYPE." + (and actype + (let* ((act) (act-str) + (params (actype:params actype)) + (params-str (and params (concat " " (prin1-to-string params)))) + ) + (while (progn + (while (and (setq act-str + (hargs:read + (or prompt (concat "Action" params-str + ": ")) nil nil + nil 'string)) + (not (string= act-str "")) + (condition-case () + (progn (setq act (read act-str)) nil) + (error + (beep) (message "Invalid action syntax.") + (sit-for 3) t)))) + (and (not (symbolp act)) + params + ;; Use the weak condition that action must + ;; involve at least one of actype's parameters + ;; or else we assume the action is invalid, tell + ;; the user and provide another chance for entry. + (not (memq t + (mapcar + (function + (lambda (param) + (setq param (symbol-name param)) + (and (string-match + (concat "[\( \t\n,']" + (regexp-quote param) + "[\(\) \t\n\"]") + act-str) + t))) + params))) + )) + (beep) (message "Action must use at least one parameter.") + (sit-for 3)) + (let (head) + (while (cond ((listp act) + (and act (setq head (car act)) + (not (or (eq head 'lambda) + (eq head 'defun) + (eq head 'defmacro))) + (setq act (list 'lambda params act)) + nil ;; terminate loop + )) + ((symbolp act) + (setq act (cons act params))) + ((stringp act) + (setq act (action:kbd-macro act 1))) + ;; Unrecognized form + (t (setq act nil)) + ))) + act))) + +(defun hui:actype (&optional default-actype prompt) + "Using optional DEFAULT-ACTYPE, PROMPTs for a button action type. +DEFAULT-ACTYPE may be a valid symbol or symbol-name." + (and default-actype (symbolp default-actype) + (progn + (setq default-actype (symbol-name default-actype)) + (if (string-match "actypes::" default-actype) + (setq default-actype (substring default-actype (match-end 0)))))) + (if (or (null default-actype) (stringp default-actype)) + (intern-soft + (concat "actypes::" + (hargs:read-match (or prompt "Button's action type: ") + (mapcar 'list (htype:names 'actypes)) + nil t default-actype 'actype))) + (hypb:error "(actype): Invalid default action type received.") + )) + +(defun hui:buf-writable-err (but-buf func-name) + "If BUT-BUF is read-only or is unwritable, signal an error from FUNC-NAME." + (let ((obuf (prog1 (current-buffer) (set-buffer but-buf))) + ;; (unwritable (and buffer-file-name + ;; (not (file-writable-p buffer-file-name)))) + (err)) + ;; (if unwritable + ;; Commented error out since some people want to be able to create + ;; buttons within files which they have purposely marked read-only. + ;; (setq err + ;; (format "(ebut-modify): You are not allowed to modify '%s'." + ;; (file-name-nondirectory buffer-file-name)))) + (if buffer-read-only + (setq err + (format + "Button buffer '%s' is read-only. Use %s to change it." + (buffer-name but-buf) + (hypb:cmd-key-string + (if (where-is-internal 'vc-toggle-read-only) + 'vc-toggle-read-only 'toggle-read-only)) + ))) + (set-buffer obuf) + (if err (progn (pop-to-buffer but-buf) (hypb:error err))))) + +(defun hui:ebut-buf (&optional prompt) + "Prompt for and return a buffer in which to place a button." + (let ((buf-name)) + (while + (progn + (setq buf-name + (hargs:read-match + (or prompt "Button's buffer: ") + (delq nil + (mapcar + (function + (lambda (buf) + (let ((b (buffer-name buf))) + (if (and (not (string-match "mail\\*" b)) + (not (string-match "\\*post-news\\*" b)) + (string-match "\\`[* ]" b)) + nil + (cons b nil))))) + (buffer-list))) + nil t (buffer-name) 'buffer)) + (or (null buf-name) (equal buf-name ""))) + (beep)) + (get-buffer buf-name))) + +(defun hui:ebut-delete-op (interactive but-key key-src) + "INTERACTIVEly or not deletes explicit Hyperbole button given by BUT-KEY in KEY-SRC. +KEY-SRC may be a buffer or a pathname, when nil the current buffer is used. +Returns t if button is deleted, signals error otherwise. If called +with INTERACTIVE non-nil, derives BUT-KEY from the button that point is +within." + (let ((buf (current-buffer)) (ebut)) + (if (if interactive + (ebut:delete) + (cond ((or (null key-src) (and (bufferp key-src) (setq buf key-src))) + (setq ebut (ebut:get but-key key-src))) + ((and (stringp key-src) + (setq buf (find-file-noselect key-src))) + (setq ebut (ebut:get but-key buf))) + (t (hypb:error "(ebut-delete): Invalid key-src: '%s'." key-src))) + (if ebut + (ebut:delete ebut) + (hypb:error "(ebut-delete): No valid %s button in %s." + (ebut:key-to-label but-key) buf)) + ) + (progn (set-buffer buf) + (if interactive + (progn + (call-interactively 'hui:ebut-unmark) + (message "Button deleted.")) + (hui:ebut-unmark but-key key-src)) + (if (hmail:reader-p) (hmail:msg-narrow)) + ) + (hypb:error "(ebut-delete): You may not delete buttons from this buffer.")))) + +(defun hui:ebut-delimit (start end instance-str) + (hypb:error "(hui:ebut-delimit): Obsolete, use ebut:delimit instead.")) + +(defun hui:ebut-operate (curr-label new-label) + (hypb:error "(hui:ebut-operate): Obsolete, use ebut:operate instead.")) + +(defun hui:ebut-unmark (&optional but-key key-src directory) + "Remove delimiters from button given by BUT-KEY in KEY-SRC of DIRECTORY. +All args are optional, the current button and buffer file are the defaults." + (interactive) + (let ((form (function + (lambda () + (let ((buffer-read-only) start end) + (setq start (match-beginning 0) + end (match-end 0)) + (and (fboundp 'hproperty:but-delete) + (hproperty:but-delete start)) + (skip-chars-backward " \t\n") + (skip-chars-backward "0-9") + (if (= (preceding-char) (string-to-char ebut:instance-sep)) + (setq start (1- (point)))) + (if (search-backward ebut:start (- (point) ebut:max-len) t) + (if current-prefix-arg + ;; Remove button label, delimiters and preceding + ;; space, if any. + (delete-region (max (point-min) + (1- (match-beginning 0))) + end) + ;; + ;; Remove button delimiters only. + ;; + ;; Remove button ending delimiter + (delete-region start end) + ;; Remove button starting delimiter + (delete-region (match-beginning 0) + (match-end 0))))))))) + (if (interactive-p) + (save-excursion + (if (search-forward ebut:end nil t) (funcall form))) + ;; Non-interactive invocation. + (let ((cur-p)) + (if (and (or (null key-src) (eq key-src buffer-file-name)) + (or (null directory) (eq directory default-directory))) + (setq cur-p t) + (set-buffer (find-file-noselect + (expand-file-name key-src directory)))) + (save-excursion + (goto-char (point-min)) + (if (re-search-forward (ebut:label-regexp but-key) nil t) + (progn (funcall form) + ;; If modified a buffer other than the current one, + ;; save it. + (or cur-p (save-buffer))))))))) + +(defun hui:file-find (file-name) + "If FILE-NAME is readable, finds it, else signals an error." + (if (and (stringp file-name) (file-readable-p file-name)) + (find-file file-name) + (hypb:error "(file-find): \"%s\" does not exist or is not readable." + file-name))) + +(defun hui:hbut-term-highlight (start end) + "For terminals only: Emphasize a button spanning from START to END." + (save-restriction + (save-excursion + (goto-char start) + (narrow-to-region (point-min) start) + (sit-for 0) + (setq inverse-video t) + (goto-char (point-min)) + (widen) + (narrow-to-region (point) end) + (sit-for 0) + (setq inverse-video nil) + ))) + +(defun hui:hbut-term-unhighlight (start end) + "For terminals only: Remove any emphasis from hyper-button at START to END." + (save-restriction + (save-excursion + (goto-char start) + (narrow-to-region (point-min) start) + (sit-for 0) + (setq inverse-video nil)))) + +(defun hui:help-ebut-highlight () + "Highlight any explicit buttons in help buffer associated with current buffer." + (if (fboundp 'hproperty:but-create) + (save-excursion + (set-buffer + (get-buffer (hypb:help-buf-name))) + (hproperty:but-create)))) + +(defun hui:htype-delete (htype-sym) + "Deletes HTYPE-SYM from use in current Hyperbole session. +HTYPE-SYM must be redefined for use again." + (and htype-sym (symbolp htype-sym) + (let ((type + (intern (hargs:read-match + (concat "Delete from " (symbol-name htype-sym) ": ") + (mapcar 'list (htype:names htype-sym)) + nil t nil htype-sym)))) + (htype:delete type htype-sym)))) + +(defun hui:htype-help (htype-sym &optional no-sort) + "Displays documentation for types from HTYPE-SYM which match to a regexp. +Optional NO-SORT means display in decreasing priority order (natural order)." + (and htype-sym (symbolp htype-sym) + (let* ((tstr (symbol-name htype-sym)) + (tprefix (concat tstr "::")) + (buf-name (hypb:help-buf-name (capitalize tstr))) + (temp-buffer-show-hook + (function + (lambda (buf) + (set-buffer buf) (goto-char (point-min)) + (replace-regexp "^" " ") (goto-char (point-min)) + (replace-string (concat " " tprefix) "") + (goto-char (point-min)) (set-buffer-modified-p nil) + (display-buffer buf nil)))) + (temp-buffer-show-function temp-buffer-show-hook) + (names (htype:names htype-sym)) + (term (hargs:read-match + (concat (capitalize tstr) + " to describe (RTN for all): ") + (mapcar 'list (cons "" names)) + nil t nil htype-sym)) + nm-list + doc-list) + (setq nm-list + (if (string= term "") + (let ((type-names + (mapcar (function (lambda (nm) (concat tprefix nm))) + names))) + (if no-sort type-names + (sort type-names 'string<))) + (cons (concat tprefix term) nil)) + doc-list (delq nil (mapcar + (function + (lambda (name) + (let ((doc (documentation + (intern-soft name)))) + (if doc (cons name doc))))) + nm-list))) + (with-output-to-temp-buffer buf-name + (mapcar (function (lambda (nm-doc-cons) + (princ (car nm-doc-cons)) (terpri) + (princ (cdr nm-doc-cons)) (terpri))) + doc-list))))) + +(defun hui:key-dir (but-buf) + "Returns button key src directory based on BUT-BUF, a buffer." + (if (bufferp but-buf) + (let ((file (buffer-file-name but-buf))) + (if file + (file-name-directory (hpath:symlink-referent file)) + (cdr (assq 'default-directory (buffer-local-variables but-buf))))) + (hypb:error "(hui:key-dir): '%s' is not a valid buffer."))) + +(defun hui:key-src (but-buf) + "Returns button key src location based on BUT-BUF, a buffer. +This is BUT-BUF when button data is stored in the buffer and the +button's source file name when the button data is stored externally." + (save-excursion + (set-buffer but-buf) + (cond ((hmail:mode-is-p) but-buf) + ((hpath:symlink-referent (buffer-file-name but-buf))) + (t but-buf)))) + +(defun hui:link-create (modify but-window lbl-key but-loc but-dir type-and-args) + "Creates or modifies a new Hyperbole button. +If MODIFY is non-nil, modifies button at point in BUT-WINDOW, +otherwise, prompts for button label and creates a button. +LBL-KEY is internal form of button label. BUT-LOC is file or buffer +in which to create button. BUT-DIR is directory of BUT-LOC. +TYPE-AND-ARGS is the action type for the button followed by any arguments it requires." + (hattr:set 'hbut:current 'loc but-loc) + (hattr:set 'hbut:current 'dir but-dir) + (hattr:set 'hbut:current 'actype (intern-soft + (concat "actypes::" + (symbol-name + (car type-and-args))))) + (hattr:set 'hbut:current 'args (cdr type-and-args)) + + (select-window but-window) + (let ((label (ebut:key-to-label lbl-key))) + (ebut:operate label (if modify label))) + ) + +(defun hui:link-possible-types () + "Returns list of possible link types for a Hyperbole button link to point. +Each list element is a list of the link type and any arguments it requires. + +The link types considered are fixed. Defining new link types will not alter +the possible types. The code must be changed to do that. + +Referent Context Possible Link Type Returned +---------------------------------------------------- +Explicit Button link-to-ebut + or +Info Node link-to-Info-node + or +Mail Reader Msg link-to-mail + +Outline Regexp Prefix link-to-string-match + or +Directory Name link-to-directory + or +File Name link-to-file + or +Koutline Cell link-to-kcell + or +Buffer attached to File link-to-file + or +Buffer without File link-to-buffer-tmp + +Elisp Buffer at Start +or End of Sexpression eval-elisp +" + (let (val) + (delq nil + (list (if (ebut:at-p) + (list 'link-to-ebut buffer-file-name (ebut:label-p))) + (cond ((eq major-mode 'Info-mode) + (let ((hargs:reading-p 'Info-node)) + (list 'link-to-Info-node (hargs:at-p)))) + ((hmail:reader-p) + (list 'link-to-mail + (list (rmail:msg-id-get) buffer-file-name))) + ) + (cond + ;; If link is within an outline-regexp prefix, use + ;; a link-to-string-match. + ((and (boundp 'outline-regexp) + (stringp outline-regexp) + (save-excursion + (<= (point) + (progn + (beginning-of-line) + (if (looking-at outline-regexp) + (match-end 0) + 0))))) + (save-excursion + (end-of-line) + (let ((heading (buffer-substring + (point) + (progn (beginning-of-line) (point)))) + (occur 1)) + (while (search-backward heading nil t) + (setq occur (1+ occur))) + (list 'link-to-string-match + heading occur buffer-file-name)))) + ((let ((hargs:reading-p 'directory)) + (setq val (hargs:at-p t))) + (list 'link-to-directory val)) + ((let ((hargs:reading-p 'file)) + (setq val (hargs:at-p t))) + (list 'link-to-file val (point))) + ((eq major-mode 'kotl-mode) + (list 'link-to-kcell buffer-file-name (kcell-view:idstamp))) + (buffer-file-name + (list 'link-to-file buffer-file-name (point))) + (t (list 'link-to-buffer-tmp (buffer-name))) + ) + (and (fboundp 'smart-emacs-lisp-mode-p) + (smart-emacs-lisp-mode-p) + (or (= (char-syntax (following-char)) ?\() + (= (char-syntax (preceding-char)) ?\))) + (setq val (hargs:sexpression-p)) + (list 'eval-elisp val)) + )))) + + +;;; ************************************************************************ +;;; Private variables +;;; ************************************************************************ + + +(defvar hui:ebut-label-prev nil + "String value of previous button name during an explicit button rename. +At other times, values must be nil.") + +(provide 'hui)