Mercurial > hg > xemacs-beta
diff lisp/custom/wid-edit.el @ 173:8eaf7971accc r20-3b13
Import from CVS: tag r20-3b13
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:49:09 +0200 |
parents | 85ec50267440 |
children | 9ad43877534d |
line wrap: on
line diff
--- a/lisp/custom/wid-edit.el Mon Aug 13 09:47:55 2007 +0200 +++ b/lisp/custom/wid-edit.el Mon Aug 13 09:49:09 2007 +0200 @@ -4,7 +4,7 @@ ;; ;; Author: Per Abrahamsen <abraham@dina.kvl.dk> ;; Keywords: extensions -;; Version: 1.9940 +;; Version: 1.9951 ;; X-URL: http://www.dina.kvl.dk/~abraham/custom/ ;; This file is part of GNU Emacs. @@ -38,6 +38,7 @@ (eval-and-compile (autoload 'pp-to-string "pp") (autoload 'Info-goto-node "info") + (autoload 'finder-commentary "finder" nil t) (when (string-match "XEmacs" emacs-version) (condition-case nil @@ -101,27 +102,6 @@ (display-error obj buf) (buffer-string buf))))) -(when (let ((a "foo")) - (put-text-property 1 2 'foo 1 a) - (put-text-property 1 2 'bar 2 a) - (set-text-properties 1 2 nil a) - (text-properties-at 1 a)) - ;; XEmacs 20.2 and earlier had a buggy set-text-properties. - (defun set-text-properties (start end props &optional buffer-or-string) - "Completely replace properties of text from START to END. -The third argument PROPS is the new property list. -The optional fourth argument, BUFFER-OR-STRING, -is the string or buffer containing the text." - (map-extents #'(lambda (extent ignored) - (remove-text-properties - start end - (list (extent-property extent 'text-prop) - nil) - buffer-or-string) - nil) - buffer-or-string start end nil nil 'text-prop) - (add-text-properties start end props buffer-or-string))) - ;;; Customization. (defgroup widgets nil @@ -129,6 +109,7 @@ :link '(custom-manual "(widget)Top") :link '(url-link :tag "Development Page" "http://www.dina.kvl.dk/~abraham/custom/") + :link '(emacs-library-link :tag "Lisp File" "widget.el") :prefix "widget-" :group 'extensions :group 'hypermedia) @@ -157,6 +138,10 @@ :group 'widget-documentation :group 'widget-faces) +(defvar widget-button-face 'widget-button-face + "Face used for buttons in widges. +This exists as a variable so it can be set locally in certain buffers.") + (defface widget-button-face '((t (:bold t))) "Face used for widget buttons." :group 'widget-faces) @@ -236,7 +221,7 @@ :group 'widgets :type 'integer) -(defcustom widget-menu-minibuffer-flag nil +(defcustom widget-menu-minibuffer-flag (string-match "XEmacs" emacs-version) "*Control how to ask for a choice from the keyboard. Non-nil means use the minibuffer; nil means read a single character." @@ -299,31 +284,34 @@ ;; Define SPC as a prefix char to get to this menu. (define-key overriding-terminal-local-map " " (setq map (make-sparse-keymap title))) - (while items - (setq choice (car items) items (cdr items)) - (if (consp choice) - (let* ((name (car choice)) - (function (cdr choice)) - (character (aref name 0))) - ;; Pick a character for this choice; - ;; avoid duplication. - (when (lookup-key map (vector character)) - (setq character (downcase character)) - (when (lookup-key map (vector character)) - (setq character next-digit - next-digit (1+ next-digit)))) - (define-key map (vector character) - (cons (format "%c = %s" character name) function))))) - (define-key map [?\C-g] '("Quit" . keyboard-quit)) + (save-excursion + (set-buffer (get-buffer-create " widget-choose")) + (erase-buffer) + (insert "Available choices:\n\n") + (while items + (setq choice (car items) items (cdr items)) + (if (consp choice) + (let* ((name (car choice)) + (function (cdr choice))) + (insert (format "%c = %s\n" next-digit name)) + (define-key map (vector next-digit) function))) + ;; Allocate digits to disabled alternatives + ;; so that the digit of a given alternative never varies. + (setq next-digit (1+ next-digit))) + (insert "\nC-g = Quit")) + (define-key map [?\C-g] 'keyboard-quit) (define-key map [t] 'keyboard-quit) (setcdr map (nreverse (cdr map))) ;; Unread a SPC to lead to our new menu. (setq unread-command-events (cons ?\ unread-command-events)) ;; Read a char with the menu, and return the result ;; that corresponds to it. - (setq value - (lookup-key overriding-terminal-local-map - (read-key-sequence title) t)) + (save-window-excursion + (display-buffer (get-buffer " widget-choose")) + (let ((cursor-in-echo-area t)) + (setq value + (lookup-key overriding-terminal-local-map + (read-key-sequence title) t)))) (when (eq value 'keyboard-quit) (error "Canceled")) value)))) @@ -340,18 +328,6 @@ ;; ;; These functions are for specifying text properties. -(defun widget-specify-none (from to) - ;; Clear all text properties between FROM and TO. - (set-text-properties from to nil)) - -(defun widget-specify-text (from to) - ;; Default properties. - (add-text-properties from to (list 'read-only t - 'front-sticky t - 'rear-nonsticky nil - 'start-open nil - 'end-open nil))) - (defcustom widget-field-add-space (or (< emacs-major-version 20) (and (eq emacs-major-version 20) @@ -366,11 +342,11 @@ :group 'widgets) (defcustom widget-field-use-before-change - (or (> emacs-minor-version 34) - (> emacs-major-version 20) - (string-match "XEmacs" emacs-version)) + (and (or (> emacs-minor-version 34) + (> emacs-major-version 19)) + (not (string-match "XEmacs" emacs-version))) "Non-nil means use `before-change-functions' to track editable fields. -This enables the use of undo, but doesn'f work on Emacs 19.34 and earlier. +This enables the use of undo, but doesn't work on Emacs 19.34 and earlier. Using before hooks also means that the :notify function can't know the new value." :type 'boolean @@ -378,26 +354,22 @@ (defun widget-specify-field (widget from to) "Specify editable button for WIDGET between FROM and TO." - (put-text-property from to 'read-only nil) ;; Terminating space is not part of the field, but necessary in ;; order for local-map to work. Remove next sexp if local-map works ;; at the end of the overlay. (save-excursion (goto-char to) - (when widget-field-add-space - (insert-and-inherit " ")) + (cond ((null (widget-get widget :size)) + (forward-char 1)) + (widget-field-add-space + (insert-and-inherit " "))) (setq to (point))) - (if widget-field-add-space - (add-text-properties (1- to) to - '(front-sticky nil start-open t read-only to)) - (add-text-properties to (1+ to) - '(front-sticky nil start-open t read-only to))) - (add-text-properties (1- from) from - '(rear-nonsticky t end-open t read-only from)) (let ((map (widget-get widget :keymap)) (face (or (widget-get widget :value-face) 'widget-field-face)) (help-echo (widget-get widget :help-echo)) - (overlay (make-overlay from to nil nil t))) + (overlay (make-overlay from to nil + nil (or (not widget-field-add-space) + (widget-get widget :size))))) (unless (or (stringp help-echo) (null help-echo)) (setq help-echo 'widget-mouse-help)) (widget-put widget :field-overlay overlay) @@ -437,15 +409,17 @@ (defun widget-specify-sample (widget from to) ;; Specify sample for WIDGET between FROM and TO. - (let ((face (widget-apply widget :sample-face-get))) - (when face - (add-text-properties from to (list 'start-open t - 'end-open t - 'face face))))) + (let ((face (widget-apply widget :sample-face-get)) + (overlay (make-overlay from to nil t nil))) + (overlay-put overlay 'face face) + (widget-put widget :sample-overlay overlay))) + (defun widget-specify-doc (widget from to) ;; Specify documentation for WIDGET between FROM and TO. - (add-text-properties from to (list 'widget-doc widget - 'face widget-documentation-face))) + (let ((overlay (make-overlay from to nil t nil))) + (overlay-put overlay 'widget-doc widget) + (overlay-put overlay 'face widget-documentation-face) + (widget-put widget :doc-overlay overlay))) (defmacro widget-specify-insert (&rest form) ;; Execute FORM without inheriting any text properties. @@ -457,7 +431,6 @@ after-change-functions) (insert "<>") (narrow-to-region (- (point) 2) (point)) - (widget-specify-none (point-min) (point-max)) (goto-char (1+ (point-min))) (setq result (progn (,@ form))) (delete-region (point-min) (1+ (point-min))) @@ -870,8 +843,7 @@ before-change-functions after-change-functions (from (point))) - (apply 'insert args) - (widget-specify-text from (point)))) + (apply 'insert args))) (defun widget-convert-text (type from to &optional button-from button-to @@ -885,7 +857,6 @@ (let ((widget (apply 'widget-convert type :delete 'widget-leave-text args)) (from (copy-marker from)) (to (copy-marker to))) - (widget-specify-text from to) (set-marker-insertion-type from t) (set-marker-insertion-type to nil) (widget-put widget :from from) @@ -907,12 +878,18 @@ (let ((from (widget-get widget :from)) (to (widget-get widget :to)) (button (widget-get widget :button-overlay)) + (sample (widget-get widget :sample-overlay)) + (doc (widget-get widget :doc-overlay)) (field (widget-get widget :field-overlay)) (children (widget-get widget :children))) (set-marker from nil) (set-marker to nil) (when button (delete-overlay button)) + (when sample + (delete-overlay sample)) + (when doc + (delete-overlay doc)) (when field (delete-overlay field)) (mapcar 'widget-leave-text children))) @@ -1106,6 +1083,12 @@ widget)) nil))) +(defcustom widget-use-overlay-change (string-match "XEmacs" emacs-version) + "If non-nil, use overlay change functions to tab around in the buffer. +This is much faster, but doesn't work reliably on Emacs 19.34." + :type 'boolean + :group 'widgets) + (defun widget-move (arg) "Move point to the ARG next field or button. ARG may be negative to move backward." @@ -1116,9 +1099,12 @@ new) ;; Forward. (while (> arg 0) - (if (eobp) - (goto-char (point-min)) - (forward-char 1)) + (cond ((eobp) + (goto-char (point-min))) + (widget-use-overlay-change + (goto-char (next-overlay-change (point)))) + (t + (forward-char 1))) (and (eq pos (point)) (eq arg number) (error "No buttons or fields found")) @@ -1129,9 +1115,12 @@ (setq old new))))) ;; Backward. (while (< arg 0) - (if (bobp) - (goto-char (point-max)) - (backward-char 1)) + (cond ((bobp) + (goto-char (point-max))) + (widget-use-overlay-change + (goto-char (previous-overlay-change (point)))) + (t + (backward-char 1))) (and (eq pos (point)) (eq arg number) (error "No buttons or fields found")) @@ -1167,7 +1156,9 @@ (start (and field (widget-field-start field)))) (if (and start (not (eq start (point)))) (goto-char start) - (call-interactively 'beginning-of-line)))) + (call-interactively 'beginning-of-line))) + ;; XEmacs: preserve the region + (setq zmacs-region-stays t)) (defun widget-end-of-line () "Go to end of field or end of line, whichever is first." @@ -1176,7 +1167,9 @@ (end (and field (widget-field-end field)))) (if (and end (not (eq end (point)))) (goto-char end) - (call-interactively 'end-of-line)))) + (call-interactively 'end-of-line))) + ;; XEmacs: preserve the region + (setq zmacs-region-stays t)) (defun widget-kill-line () "Kill to end of field or end of line, whichever is first." @@ -1230,14 +1223,7 @@ (set-marker from nil) (set-marker to nil)))) (widget-clear-undo) - ;; We need to maintain text properties and size of the editing fields. - (make-local-variable 'after-change-functions) - (setq after-change-functions - (if widget-field-list '(widget-after-change) nil)) - (when widget-field-use-before-change - (make-local-variable 'before-change-functions) - (setq before-change-functions - (if widget-field-list '(widget-before-change) nil)))) + (widget-add-change)) (defvar widget-field-last nil) ;; Last field containing point. @@ -1261,7 +1247,8 @@ "Return the end of WIDGET's editing field." (let ((overlay (widget-get widget :field-overlay))) ;; Don't subtract one if local-map works at the end of the overlay. - (and overlay (if widget-field-add-space + (and overlay (if (or widget-field-add-space + (null (widget-get widget :size))) (1- (overlay-end overlay)) (overlay-end overlay))))) @@ -1281,13 +1268,29 @@ (setq found field)))) found)) -(defun widget-before-change (from &rest ignore) +(defun widget-before-change (from to) ;; This is how, for example, a variable changes its state to `modified'. ;; when it is being edited. - (condition-case nil - (let ((field (widget-field-find from))) - (widget-apply field :notify field)) - (error (debug "Before Change")))) + (let ((from-field (widget-field-find from)) + (to-field (widget-field-find to))) + (cond ((not (eq from-field to-field)) + (add-hook 'post-command-hook 'widget-add-change nil t) + (error "Change should be restricted to a single field")) + ((null from-field) + (add-hook 'post-command-hook 'widget-add-change nil t) + (error "Attempt to change text outside editable field")) + (widget-field-use-before-change + (condition-case nil + (widget-apply from-field :notify from-field) + (error (debug "Before Change"))))))) + +(defun widget-add-change () + (make-local-hook 'post-command-hook) + (remove-hook 'post-command-hook 'widget-add-change t) + (make-local-hook 'before-change-functions) + (add-hook 'before-change-functions 'widget-before-change nil t) + (make-local-hook 'after-change-functions) + (add-hook 'after-change-functions 'widget-after-change nil t)) (defun widget-after-change (from to old) ;; Adjust field size and text properties. @@ -1483,7 +1486,6 @@ (widget-apply widget :value-create))) (let ((from (copy-marker (point-min))) (to (copy-marker (point-max)))) - (widget-specify-text from to) (set-marker-insertion-type from t) (set-marker-insertion-type to nil) (widget-put widget :from from) @@ -1530,17 +1532,13 @@ (error "Unknown escape `%c'" escape))) (widget-put widget :buttons buttons))) -(defvar widget-button-face nil - "Face to use for buttons. -This is a variable so that it can be buffer-local.") - (defun widget-default-button-face-get (widget) ;; Use :button-face or widget-button-face (or (widget-get widget :button-face) (let ((parent (widget-get widget :parent))) (if parent (widget-apply parent :button-face-get) - 'widget-button-face)))) + widget-button-face)))) (defun widget-default-sample-face-get (widget) ;; Use :sample-face. @@ -1552,6 +1550,8 @@ (to (widget-get widget :to)) (inactive-overlay (widget-get widget :inactive)) (button-overlay (widget-get widget :button-overlay)) + (sample-overlay (widget-get widget :sample-overlay)) + (doc-overlay (widget-get widget :doc-overlay)) before-change-functions after-change-functions (inhibit-read-only t)) @@ -1560,6 +1560,10 @@ (delete-overlay inactive-overlay)) (when button-overlay (delete-overlay button-overlay)) + (when sample-overlay + (delete-overlay sample-overlay)) + (when doc-overlay + (delete-overlay doc-overlay)) (when (< from to) ;; Kludge: this doesn't need to be true for empty formats. (delete-region from to)) @@ -1782,6 +1786,36 @@ (require 'browse-url) (funcall browse-url-browser-function (widget-value widget))) +;;; The `file-link' Widget. + +(define-widget 'file-link 'link + "A link to a file." + :action 'widget-file-link-action) + +(defun widget-file-link-action (widget &optional event) + "Find the file specified by WIDGET." + (find-file (widget-value widget))) + +;;; The `emacs-library-link' Widget. + +(define-widget 'emacs-library-link 'link + "A link to an Emacs Lisp library file." + :action 'widget-emacs-library-link-action) + +(defun widget-emacs-library-link-action (widget &optional event) + "Find the Emacs Library file specified by WIDGET." + (find-file (locate-library (widget-value widget)))) + +;;; The `emacs-commentary-link' Widget. + +(define-widget 'emacs-commentary-link 'link + "A link to Commentary in an Emacs Lisp library file." + :action 'widget-emacs-commentary-link-action) + +(defun widget-emacs-commentary-link-action (widget &optional event) + "Find the Commentary section of the Emacs file specified by WIDGET." + (finder-commentary (widget-value widget))) + ;;; The `editable-field' Widget. (define-widget 'editable-field 'default @@ -2026,7 +2060,7 @@ (widget-get current :value))) (widget-setup) (widget-apply widget :notify widget event))) - (run-hooks 'widget-edit-hook)) + (run-hook-with-args 'widget-edit-functions widget)) (defun widget-choice-validate (widget) ;; Valid if we have made a valid choice. @@ -2082,7 +2116,7 @@ ;; Toggle value. (widget-value-set widget (not (widget-value widget))) (widget-apply widget :notify widget event) - (run-hooks 'widget-edit-hook)) + (run-hook-with-args 'widget-edit-functions widget)) ;;; The `checkbox' Widget. @@ -2569,8 +2603,6 @@ (when (< (widget-get child :entry-from) (widget-get widget :from)) (set-marker (widget-get widget :from) (widget-get child :entry-from))) - (widget-specify-text (widget-get child :entry-from) - (widget-get child :entry-to)) (if (eq (car children) before) (widget-put widget :children (cons child children)) (while (not (eq (car (cdr children)) before)) @@ -2644,7 +2676,6 @@ (widget-get widget :buttons)))) (let ((entry-from (copy-marker (point-min))) (entry-to (copy-marker (point-max)))) - (widget-specify-text entry-from entry-to) (set-marker-insertion-type entry-from t) (set-marker-insertion-type entry-to nil) (widget-put child :entry-from entry-from) @@ -2903,7 +2934,8 @@ "A regular expression." :match 'widget-regexp-match :validate 'widget-regexp-validate - :value-face 'widget-single-line-field-face + ;; Doesn't work well with terminating newline. + ;; :value-face 'widget-single-line-field-face :tag "Regexp") (defun widget-regexp-match (widget value) @@ -2929,7 +2961,8 @@ :complete-function 'widget-file-complete :prompt-value 'widget-file-prompt-value :format "%{%t%}: %v" - :value-face 'widget-single-line-field-face + ;; Doesn't work well with terminating newline. + ;; :value-face 'widget-single-line-field-face :tag "File") (defun widget-file-complete () @@ -3315,57 +3348,61 @@ ;;; The `color' Widget. -(define-widget 'color-item 'choice-item - "A color name (with sample)." - :format "%v (%{sample%})\n" - :sample-face-get 'widget-color-item-button-face-get) - -(defun widget-color-item-button-face-get (widget) - (let ((symbol (intern (concat "fg:" (widget-value widget))))) +(define-widget 'color 'editable-field + "Choose a color name (with sample)." + :format "%t: %v (%{sample%})\n" + :size 10 + :tag "Color" + :value "black" + :complete 'widget-color-complete + :sample-face-get 'widget-color-sample-face-get + :notify 'widget-color-notify + :action 'widget-color-action) + +(defun widget-color-complete (widget) + "Complete the color in WIDGET." + (let* ((prefix (buffer-substring-no-properties (widget-field-start widget) + (point))) + (list (widget-color-choice-list)) + (completion (try-completion prefix list))) + (cond ((eq completion t) + (message "Exact match.")) + ((null completion) + (error "Can't find completion for \"%s\"" prefix)) + ((not (string-equal prefix completion)) + (insert-and-inherit (substring completion (length prefix)))) + (t + (message "Making completion list...") + (let ((list (all-completions prefix list nil))) + (with-output-to-temp-buffer "*Completions*" + (display-completion-list list))) + (message "Making completion list...done"))))) + +(defun widget-color-sample-face-get (widget) + (let* ((value (condition-case nil + (widget-value widget) + (error (widget-get widget :value)))) + (symbol (intern (concat "fg:" value)))) (if (string-match "XEmacs" emacs-version) (prog1 symbol (or (find-face symbol) - (set-face-foreground (make-face symbol) (widget-value widget)))) + (set-face-foreground (make-face symbol) value))) (condition-case nil (facemenu-get-face symbol) (error 'default))))) -(define-widget 'color 'push-button - "Choose a color name (with sample)." - :format "%[%t%]: %v" - :tag "Color" - :value "black" - :value-create 'widget-color-value-create - :value-delete 'widget-children-value-delete - :value-get 'widget-color-value-get - :value-set 'widget-color-value-set - :action 'widget-color-action - :match 'widget-field-match - :tag "Color") - (defvar widget-color-choice-list nil) ;; Variable holding the possible colors. (defun widget-color-choice-list () (unless widget-color-choice-list (setq widget-color-choice-list - (mapcar '(lambda (color) (list color)) - (x-defined-colors)))) + (if (fboundp 'read-color-completion-table) + (read-color-completion-table) + (mapcar '(lambda (color) (list color)) + (x-defined-colors))))) widget-color-choice-list) -(defun widget-color-value-create (widget) - (let ((child (widget-create-child-and-convert - widget 'color-item (widget-get widget :value)))) - (widget-put widget :children (list child)))) - -(defun widget-color-value-get (widget) - ;; Pass command to first child. - (widget-apply (car (widget-get widget :children)) :value-get)) - -(defun widget-color-value-set (widget value) - ;; Pass command to first child. - (widget-apply (car (widget-get widget :children)) :value-set value)) - (defvar widget-color-history nil "History of entered colors") @@ -3373,19 +3410,32 @@ ;; Prompt for a color. (let* ((tag (widget-apply widget :menu-tag-get)) (prompt (concat tag ": ")) - (answer (cond ((string-match "XEmacs" emacs-version) - (read-color prompt)) - ((fboundp 'x-defined-colors) - (completing-read (concat tag ": ") - (widget-color-choice-list) - nil nil nil 'widget-color-history)) - (t - (read-string prompt (widget-value widget)))))) + (value (widget-value widget)) + (start (widget-field-start widget)) + (pos (cond ((< (point) start) + 0) + ((> (point) (+ start (length value))) + (length value)) + (t + (- (point) start)))) + (answer (if (commandp 'read-color) + (read-color prompt) + (completing-read (concat tag ": ") + (widget-color-choice-list) + nil nil + (cons value pos) + 'widget-color-history)))) (unless (zerop (length answer)) (widget-value-set widget answer) (widget-setup) (widget-apply widget :notify widget event)))) +(defun widget-color-notify (widget child &optional event) + "Update the sample, and notofy the parent." + (overlay-put (widget-get widget :sample-overlay) + 'face (widget-apply widget :sample-face-get)) + (widget-default-notify widget child event)) + ;;; The Help Echo (defun widget-echo-help-mouse ()