unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#6644: `describe-variable' misses on certain permanent-locals [PATCH] (putative)
@ 2010-07-16  0:14 MON KEY
  2012-08-05  8:42 ` Chong Yidong
  0 siblings, 1 reply; 2+ messages in thread
From: MON KEY @ 2010-07-16  0:14 UTC (permalink / raw)
  To: 6644

[-- Attachment #1: Type: text/plain, Size: 24916 bytes --]

As currently written `describe-variable' will, for certain variables,
only inform if a variable is permanently local when it is set to have
a local value in current-buffer.

This happens for example with `kill-buffer-hook' which _is_ a
permanent-local. And in thes situations, `describe-variable' (and by
proxy "*Help*") can not tell us if a variable is permanent-local
unless the docstring explicitly states as such.

At first blush it would appear that `describe-variable's binding of
`locus' happens at the buffer level and therfor _should_ identify a
permanent-local per:

,----
| (with-current-buffer buffer
|   (setq val (symbol-value variable)
|         locus (variable-binding-locus variable)))
`----

This is because the following heuristics won't always recognize the
permanent-local property where a `locus' pivots on a `bufferp' check:

,----
|  (if valvoid
|      nil
|    (with-current-buffer standard-output
|      (setq val-start-pos (point))
|      (princ "value is ")
|      (terpri)
|      (let ((from (point)))
|        (pp val)
|        (if (< (point) (+ from 20))
|            (delete-region (1- from) from)))))
| 	    (terpri)
|  (when locus
|    (if (bufferp locus) ;<- A permanent-local's locus is elsewhere!
|        (princ (format "%socal in buffer %s; "
|                       (if (get variable 'permanent-local)
|                           "Permanently l" "L")
|                       (buffer-name)))
|      (princ (format "It is a frame-local variable; ")))
|    (if (not (default-boundp variable))
|        (princ "globally void")
|      (let ((val (default-value variable)))
|        (with-current-buffer standard-output
|          (princ "global value is ")
|          (terpri)
|          (let ((from (point)))
|            (pp val)
|            (if (< (point) (+ from 20))
|                (delete-region (1- from) from))))))
|    (terpri))
`----

W/re `describe-variable's current behavior around permanent-local it
does not serve its purpose well.

When a user asks that for the description of a variable variable that
variable has a permanent-local property it is important that a user be
informed as such.

Specifically that:

- a variable _is_ permanent-local;

- that though currently null, _if_ set it would be permanent-local in
  current-buffer;

- that if non-nil the value is permanent-local in current-buffer and
  will persist across invocations of `kill-all-local-variables' unless
  explicitly removed.

IOW, knowing that a variable is:

 "Local in buffer <SOME-BUFFER>;"

Is not the same as saying that a variable is;

 "Permanently local in <SOME-BUFFER>;"

Which isn't the same as saying either of following about a variable;

 "This variable is a permanent-local.
  It satisfies the predicate `local-variable-p' in <SOME-BUFFER>.
  This value will persist in current-buffer until explicitly removed;"

 "This variable is a permanent-local.
  Its current value is null in <SOME-BUFFER>.
  This variable satisfies the predicate `local-variable-if-set-p'.
  When set with `make-local-variable' the value will persist in
  current-buffer until explicitly removed;"

Following fncn `%describe-variable-miss-permanent-local-example'
illustrates what I perceive to be the "problem":

;;; ==============================
(defun %describe-variable-miss-permanent-local-example ()
  "Illustrate `describe-variable' not identifying permanent-local variables.
Display results in buffer \"*BUFFER-W-OUT-LOCAL-KB-HOOK*\"."
  (interactive)
  (let ((dvdr (concat "\n" (make-string 80 59) "\n"))
        (w-lcl "*BUFFER-W-LOCAL-KB-HOOK*")
        (w/o-lcl "*BUFFER-W-OUT-LOCAL-KB-HOOK*")
        kbh tmp-fncn)
    (with-current-buffer (get-buffer-create w-lcl)
      (fset 'tmp-fncn #'(lambda () "does nothing." nil))
      (add-hook 'kill-buffer-hook 'tmp-fncn nil t)
      (setq kbh (buffer-local-value 'kill-buffer-hook (current-buffer)))
      (with-current-buffer (get-buffer-create w/o-lcl)
        (erase-buffer)
        (save-excursion
          (insert (substring dvdr 1)
                  ";; Local value of `kill-buffer-hook' in buffer: "
w-lcl "\n;;\n"
                  (format "%S" kbh) dvdr)
          (setq kbh (buffer-local-value 'kill-buffer-hook (current-buffer)))
          (insert dvdr ";; Local value of `kill-buffer-hook' in
buffer: " w/o-lcl "\n;;\n"
                  (format "%S" kbh) dvdr dvdr
                  ";; Evaluating `describe-variable' for
`kill-buffer-hook' with buffer: "
                  w/o-lcl "\n;; NOTE, with `current-buffer' there is
no indication "
                  "of `kill-buffer-hook' being permanent-local" dvdr "\n"
                  (describe-variable 'kill-buffer-hook
(current-buffer)) "\n" dvdr
                  ";; Evaluating `describe-variable' for
`kill-buffer-hook' with buffer: "
                  w-lcl "\n;; NOTE, contrasted with above, now there
_is_ indication "
                  "of `kill-buffer-hook' as permanent-local.\n"
                  ";; Additionally, there is also indication that the "
                  "varaible's global value differs from the local." dvdr "\n"
                  (describe-variable 'kill-buffer-hook (get-buffer
w-lcl)) "\n" dvdr)))
      (prog1
          (unintern 'tmp-fncn)
        (kill-buffer (current-buffer))))
    (pop-to-buffer (get-buffer w/o-lcl))))
;;
;; (%describe-variable-miss-permanent-local-example)
;;
;; (unintern '%describe-variable-miss-permanent-local-example)

;;; ==============================
Following is a lightly tested modified version of `describe-variable'
tweaked to report more intelligently when variables are buffer-local
and/or permanent-local.  Following that is a patch to lisp/help-fns.el
current through Bzr-1000827:


;;;###autoload
(defun describe-variable (variable &optional buffer frame)
  "Display the full documentation of VARIABLE (a symbol).
Returns the documentation as a string, also.
If VARIABLE has a buffer-local value in BUFFER or FRAME
\(default to the current buffer and current frame),
it is displayed along with the global value."
  (interactive
   (let ((v (variable-at-point))
	 (enable-recursive-minibuffers t)
	 val)
     (setq val (completing-read (if (symbolp v)
				    (format
				     "Describe variable (default %s): " v)
				  "Describe variable: ")
				obarray
				'(lambda (vv)
				   (or (boundp vv)
				       (get vv 'variable-documentation)))
				t nil nil
				(if (symbolp v) (symbol-name v))))
     (list (if (equal val "")
	       v (intern val)))))
  (let (file-name
        (ploc (get variable 'permanent-local)))
    (unless (buffer-live-p buffer) (setq buffer (current-buffer)))
    (unless (frame-live-p frame) (setq frame (selected-frame)))
    (if (not (symbolp variable))
	(message "You did not specify a variable")
      (save-excursion
	(let ((valvoid (not (with-current-buffer buffer (boundp variable))))
	      val val-start-pos locus)
	  ;; Extract the value before setting up the output buffer,
	  ;; in case `buffer' *is* the output buffer.
	  (unless valvoid
	    (with-selected-frame frame
	      (with-current-buffer buffer
		(setq val (symbol-value variable)
		      locus (variable-binding-locus variable)))))
	  (help-setup-xref (list #'describe-variable variable buffer)
			   (called-interactively-p 'interactive))
	  (with-help-window (help-buffer)
	    (with-current-buffer buffer
	      (prin1 variable)
	      (setq file-name (find-lisp-object-file-name variable 'defvar))

	      (if file-name
		  (progn
		    (princ " is a variable defined in `")
		    (princ (if (eq file-name 'C-source)
			       "C source code"
			     (file-name-nondirectory file-name)))
		    (princ "'.\n")
		    (with-current-buffer standard-output
		      (save-excursion
			(re-search-backward "`\\([^`']+\\)'" nil t)
			(help-xref-button 1 'help-variable-def
					  variable file-name)))
		    (unless ploc
                      (if valvoid
                          (princ "It is void as a variable.")
                        (princ "Its "))))
                (unless ploc
                  (if valvoid
                      (princ " is void as a variable.")
                    (if (bufferp locus)
                        (when (null (buffer-local-value variable
(get-buffer buffer)))
                          (terpri))
                      (princ "'s "))))))

            (with-current-buffer standard-output
              (if valvoid
                  nil
                (progn
                  (setq val-start-pos (point))
                  (if (bufferp locus)
                      (if ploc
                          (if (buffer-local-value variable (get-buffer buffer))
                              (princ "Its local value is ")
                            (princ "Its default local value is "))
                        (princ "Its non-local global value is "))
                    (if ploc
                        (princ "Its value is ")
                      (princ "value is ")))
                  (terpri)
                  (let ((from (point)))
                    (pp val)
                    ;; Hyperlinks in variable's value are quite frequently
                    ;; inappropriate e.g C-h v <RET> features <RET>
                    ;; (help-xref-on-pp from (point))
                    (if (< (point)
                           (if ploc
                               (+ from 20)
                             (+ from 20)))
                        (delete-region (1- from) from)))))
              (when (and ploc (not (bufferp locus)))
                (terpri)
                (princ "It has a non-nil permanent-local property;")))
            (terpri)

            (progn
              (cond ((bufferp locus)
                     (princ (format "It is a %slocal variable in buffer %s;\n"
                                    (if ploc
                                        "permanent-" "buffer-")
                                    (buffer-name))))
                    ((framep locus)
                     (princ (format "It is a frame-local variable;")))
                    ((null locus)
                     "It is a global variable;"))
              (when (or (framep locus) (bufferp locus) ploc)
                (if (not (default-boundp variable))
                    (princ "Value as global: void")
                  (unless (null (buffer-local-value variable
(get-buffer buffer)))
                    (let ((val (default-value variable)))
                      (with-current-buffer standard-output
                        (princ "Value as global: ")
                        (terpri)
                        ;; Fixme: pp can take an age if you happen to
                        ;; ask for a very large expression.  We should
                        ;; probably print it raw once and check it's a
                        ;; sensible size before prettyprinting.  -- fx
                        (let ((from (point)))
                          (pp val)
                          ;; See previous comment for this function.
                          ;; (help-xref-on-pp from (point))
                          (if (< (point) (+ from 20))
                              (delete-region (1- from) from))))))))
              (terpri))

	    ;; If the value is large, move it to the end.
	    (with-current-buffer standard-output
	      (when (> (count-lines (point-min) (point-max)) 10)
		;; Note that setting the syntax table like below
		;; makes forward-sexp move over a `'s' at the end
		;; of a symbol.
		(set-syntax-table emacs-lisp-mode-syntax-table)
		(goto-char val-start-pos)
		;; The line below previously read as
		;; (delete-region (point) (progn (end-of-line) (point)))
		;; which suppressed display of the buffer local value for
		;; large values.
		(when (looking-at "value is") (replace-match ""))
		(save-excursion
		  (insert "\n\nValue:")
		  (set (make-local-variable 'help-button-cache)
		       (point-marker)))
		(insert "value is shown ")
		(insert-button "below"
			       'action help-button-cache
			       'follow-link t
			       'help-echo "mouse-2, RET: show value")
		(insert ".\n")))
            (terpri)

            (let* ((alias (condition-case nil
                              (indirect-variable variable)
                            (error variable)))
                   (obsolete (get variable 'byte-obsolete-variable))
		   (use (car obsolete))
		   (safe-var (get variable 'safe-local-variable))
                   (doc (or (documentation-property variable
'variable-documentation)
                            (documentation-property alias
'variable-documentation)))
                   (extra-line nil))
              ;; Add a note for variables that have been make-var-buffer-local.
              (when (and (local-variable-if-set-p variable)
                         (or (not (local-variable-p variable))
                             (with-temp-buffer
                               (local-variable-if-set-p variable))))
                (setq extra-line t)
                (princ "  Automatically becomes buffer-local when set
in any fashion.\n"))

              ;; Mention if it's an alias
              (unless (eq alias variable)
                (setq extra-line t)
                (princ (format "  This variable is an alias for
`%s'.\n" alias)))

              (when obsolete
                (setq extra-line t)
                (princ "  This variable is obsolete")
                (if (cdr obsolete) (princ (format " since %s" (cdr obsolete))))
		(princ (cond ((stringp use) (concat ";\n  " use))
			     (use (format ";\n  use `%s' instead." (car obsolete)))
			     (t ".")))
                (terpri))

	      (when (member (cons variable val) file-local-variables-alist)
		(setq extra-line t)
		(if (member (cons variable val) dir-local-variables-alist)
		    (let ((file (and (buffer-file-name)
				     (not (file-remote-p (buffer-file-name)))
				     (dir-locals-find-file (buffer-file-name)))))
		      (princ "  This variable is a directory local variable")
		      (when file
			(princ (concat "\n  from the file \""
				       (if (consp file)
					   (car file)
					 file)
				       "\"")))
		      (princ ".\n"))
		  (princ "  This variable is a file local variable.\n")))

	      (when (memq variable ignored-local-variables)
		(setq extra-line t)
		(princ "  This variable is ignored when used as a file local \
variable.\n"))

	      ;; Can be both risky and safe, eg auto-fill-function.
	      (when (risky-local-variable-p variable)
		(setq extra-line t)
		(princ "  This variable is potentially risky when used as a \
file local variable.\n")
		(when (assq variable safe-local-variable-values)
		  (princ "  However, you have added it to \
`safe-local-variable-values'.\n")))

	      (when safe-var
                (setq extra-line t)
		(princ "  This variable is safe as a file local variable ")
		(princ "if its value\n  satisfies the predicate ")
		(princ (if (byte-code-function-p safe-var)
			   "which is byte-compiled expression.\n"
			 (format "`%s'.\n" safe-var))))

              (if extra-line (terpri))
	      (princ "Documentation:\n")
	      (with-current-buffer standard-output
		(insert (or doc "Not documented as a variable."))))

	    ;; Make a link to customize if this variable can be customized.
	    (when (custom-variable-p variable)
	      (let ((customize-label "customize"))
		(terpri)
		(terpri)
		(princ (concat "You can " customize-label " this variable."))
		(with-current-buffer standard-output
		  (save-excursion
		    (re-search-backward
		     (concat "\\(" customize-label "\\)") nil t)
		    (help-xref-button 1 'help-customize-variable variable))))
	      ;; Note variable's version or package version
	      (let ((output (describe-variable-custom-version-info variable)))
		(when output
		  (terpri)
		  (terpri)
		  (princ output))))

	    (with-current-buffer standard-output
	      ;; Return the text we displayed.
	      (buffer-string))))))))

;;; ==============================
;;; PATCH to describe-variable as provided above in full

*** ediff7517N_J	2010-07-15 19:06:56.253304353 -0400
--- help-fns.el Bzr-100827	2010-07-15 19:06:56.304504327 -0400
***************
*** 20,27 ****
  				(if (symbolp v) (symbol-name v))))
       (list (if (equal val "")
  	       v (intern val)))))
!   (let (file-name
!         (ploc (get variable 'permanent-local)))
      (unless (buffer-live-p buffer) (setq buffer (current-buffer)))
      (unless (frame-live-p frame) (setq frame (selected-frame)))
      (if (not (symbolp variable))
--- 20,26 ----
  				(if (symbolp v) (symbol-name v))))
       (list (if (equal val "")
  	       v (intern val)))))
!   (let (file-name)
      (unless (buffer-live-p buffer) (setq buffer (current-buffer)))
      (unless (frame-live-p frame) (setq frame (selected-frame)))
      (if (not (symbolp variable))
***************
*** 55,87 ****
  			(re-search-backward "`\\([^`']+\\)'" nil t)
  			(help-xref-button 1 'help-variable-def
  					  variable file-name)))
! 		    (unless ploc
!                       (if valvoid
!                           (princ "It is void as a variable.")
!                         (princ "Its "))))
!                 (unless ploc
!                   (if valvoid
!                       (princ " is void as a variable.")
!                     (if (bufferp locus)
!                         (when (null (buffer-local-value variable
(get-buffer buffer)))
!                           (terpri))
!                       (princ "'s "))))))
!
!             (with-current-buffer standard-output
!               (if valvoid
!                   nil
!                 (progn
!                   (setq val-start-pos (point))
!                   (if (bufferp locus)
!                       (if ploc
!                           (if (buffer-local-value variable
(get-buffer buffer))
!                               (princ "Its local value is ")
!                             (princ "Its default local value is "))
!                         (princ "Its non-local global value is "))
!                     (if ploc
!                         (princ "Its value is ")
!                       (princ "value is ")))
!                   (terpri)
!                   (let ((from (point)))
!                     (pp val)
!                     ;; Hyperlinks in variable's value are quite frequently
--- 54,222 ----
  			(re-search-backward "`\\([^`']+\\)'" nil t)
  			(help-xref-button 1 'help-variable-def
  					  variable file-name)))
! 		    (if valvoid
! 			(princ "It is void as a variable.")
! 		      (princ "Its ")))
! 		(if valvoid
! 		    (princ " is void as a variable.")
! 		  (princ "'s "))))
! 	    (if valvoid
! 		nil
! 	      (with-current-buffer standard-output
! 		(setq val-start-pos (point))
! 		(princ "value is ")
! 		(terpri)
! 		(let ((from (point)))
! 		  (pp val)
! 		  ;; Hyperlinks in variable's value are quite frequently
! 		  ;; inappropriate e.g C-h v <RET> features <RET>
! 		  ;; (help-xref-on-pp from (point))
! 		  (if (< (point) (+ from 20))
! 		      (delete-region (1- from) from)))))
! 	    (terpri)
!
! 	    (when locus
! 	      (if (bufferp locus)
! 		  (princ (format "%socal in buffer %s; "
! 				 (if (get variable 'permanent-local)
! 				     "Permanently l" "L")
! 				 (buffer-name)))
! 		(princ (format "It is a frame-local variable; ")))
! 	      (if (not (default-boundp variable))
! 		  (princ "globally void")
! 		(let ((val (default-value variable)))
! 		  (with-current-buffer standard-output
! 		    (princ "global value is ")
! 		    (terpri)
! 		    ;; Fixme: pp can take an age if you happen to
! 		    ;; ask for a very large expression.  We should
! 		    ;; probably print it raw once and check it's a
! 		    ;; sensible size before prettyprinting.  -- fx
! 		    (let ((from (point)))
! 		      (pp val)
! 		      ;; See previous comment for this function.
! 		      ;; (help-xref-on-pp from (point))
! 		      (if (< (point) (+ from 20))
! 			  (delete-region (1- from) from))))))
!               (terpri))
!
! 	    ;; If the value is large, move it to the end.
! 	    (with-current-buffer standard-output
! 	      (when (> (count-lines (point-min) (point-max)) 10)
! 		;; Note that setting the syntax table like below
! 		;; makes forward-sexp move over a `'s' at the end
! 		;; of a symbol.
! 		(set-syntax-table emacs-lisp-mode-syntax-table)
! 		(goto-char val-start-pos)
! 		;; The line below previously read as
! 		;; (delete-region (point) (progn (end-of-line) (point)))
! 		;; which suppressed display of the buffer local value for
! 		;; large values.
! 		(when (looking-at "value is") (replace-match ""))
! 		(save-excursion
! 		  (insert "\n\nValue:")
! 		  (set (make-local-variable 'help-button-cache)
! 		       (point-marker)))
! 		(insert "value is shown ")
! 		(insert-button "below"
! 			       'action help-button-cache
! 			       'follow-link t
! 			       'help-echo "mouse-2, RET: show value")
! 		(insert ".\n")))
!             (terpri)
!
!             (let* ((alias (condition-case nil
!                               (indirect-variable variable)
!                             (error variable)))
!                    (obsolete (get variable 'byte-obsolete-variable))
! 		   (use (car obsolete))
! 		   (safe-var (get variable 'safe-local-variable))
!                    (doc (or (documentation-property variable
'variable-documentation)
!                             (documentation-property alias
'variable-documentation)))
!                    (extra-line nil))
!               ;; Add a note for variables that have been
make-var-buffer-local.
!               (when (and (local-variable-if-set-p variable)
!                          (or (not (local-variable-p variable))
!                              (with-temp-buffer
!                                (local-variable-if-set-p variable))))
!                 (setq extra-line t)
!                 (princ "  Automatically becomes buffer-local when
set in any fashion.\n"))
!
!               ;; Mention if it's an alias
!               (unless (eq alias variable)
!                 (setq extra-line t)
!                 (princ (format "  This variable is an alias for
`%s'.\n" alias)))
!
!               (when obsolete
!                 (setq extra-line t)
!                 (princ "  This variable is obsolete")
!                 (if (cdr obsolete) (princ (format " since %s" (cdr
obsolete))))
! 		(princ (cond ((stringp use) (concat ";\n  " use))
! 			     (use (format ";\n  use `%s' instead." (car obsolete)))
! 			     (t ".")))
!                 (terpri))
!
! 	      (when (member (cons variable val) file-local-variables-alist)
! 		(setq extra-line t)
! 		(if (member (cons variable val) dir-local-variables-alist)
! 		    (let ((file (and (buffer-file-name)
! 				     (not (file-remote-p (buffer-file-name)))
! 				     (dir-locals-find-file (buffer-file-name)))))
! 		      (princ "  This variable is a directory local variable")
! 		      (when file
! 			(princ (concat "\n  from the file \""
! 				       (if (consp file)
! 					   (car file)
! 					 file)
! 				       "\"")))
! 		      (princ ".\n"))
! 		  (princ "  This variable is a file local variable.\n")))
!
! 	      (when (memq variable ignored-local-variables)
! 		(setq extra-line t)
! 		(princ "  This variable is ignored when used as a file local \
! variable.\n"))
!
! 	      ;; Can be both risky and safe, eg auto-fill-function.
! 	      (when (risky-local-variable-p variable)
! 		(setq extra-line t)
! 		(princ "  This variable is potentially risky when used as a \
! file local variable.\n")
! 		(when (assq variable safe-local-variable-values)
! 		  (princ "  However, you have added it to \
! `safe-local-variable-values'.\n")))
!
! 	      (when safe-var
!                 (setq extra-line t)
! 		(princ "  This variable is safe as a file local variable ")
! 		(princ "if its value\n  satisfies the predicate ")
! 		(princ (if (byte-code-function-p safe-var)
! 			   "which is byte-compiled expression.\n"
! 			 (format "`%s'.\n" safe-var))))
!
!               (if extra-line (terpri))
! 	      (princ "Documentation:\n")
! 	      (with-current-buffer standard-output
! 		(insert (or doc "Not documented as a variable."))))
!
! 	    ;; Make a link to customize if this variable can be customized.
! 	    (when (custom-variable-p variable)
! 	      (let ((customize-label "customize"))
! 		(terpri)
! 		(terpri)
! 		(princ (concat "You can " customize-label " this variable."))
! 		(with-current-buffer standard-output
! 		  (save-excursion
! 		    (re-search-backward
! 		     (concat "\\(" customize-label "\\)") nil t)
! 		    (help-xref-button 1 'help-customize-variable variable))))
! 	      ;; Note variable's version or package version
! 	      (let ((output (describe-variable-custom-version-info variable)))
! 		(when output
! 		  (terpri)
! 		  (terpri)
! 		  (princ output))))
!
! 	    (with-current-buffer standard-output
! 	      ;; Return the text we displayed.
! 	      (buffer-string))))))))

[-- Attachment #2: describe-variable-mods-2010-07-15.el --]
[-- Type: application/octet-stream, Size: 25376 bytes --]

;;; :FILE-CREATED <Timestamp: #{2010-07-15T13:43:42-04:00Z}#{10284} - by MON>

;; `describe-variable' misses on certain permanent-locals [PATCH]

;; As currently written `describe-variable' will, for certain variables,
;; only inform if a variable is permanently local when it is set to have
;; a local value in current-buffer.

;; This happens for example with `kill-buffer-hook' which _is_ a
;; permanent-local. And in thes situations, `describe-variable' (and by
;; proxy "*Help*") can not tell us if a variable is permanent-local
;; unless the docstring explicitly states as such.

;; At first blush it would appear that `describe-variable's binding of
;; `locus' happens at the buffer level and therfor _should_ identify a
;; permanent-local per:

;; ,----
;; | (with-current-buffer buffer
;; |   (setq val (symbol-value variable)
;; |         locus (variable-binding-locus variable)))
;; `----

;; This is because the following heuristics won't always recognize the
;; permanent-local property where a `locus' pivots on a `bufferp' check:

;; ,----
;; |  (if valvoid
;; |      nil
;; |    (with-current-buffer standard-output
;; |      (setq val-start-pos (point))
;; |      (princ "value is ")
;; |      (terpri)
;; |      (let ((from (point)))
;; |        (pp val)
;; |        (if (< (point) (+ from 20))
;; |            (delete-region (1- from) from)))))
;; | 	    (terpri)
;; |  (when locus
;; |    (if (bufferp locus) ;<- A permanent-local's locus is elsewhere!
;; |        (princ (format "%socal in buffer %s; "
;; |                       (if (get variable 'permanent-local)
;; |                           "Permanently l" "L")
;; |                       (buffer-name)))
;; |      (princ (format "It is a frame-local variable; ")))
;; |    (if (not (default-boundp variable))
;; |        (princ "globally void")
;; |      (let ((val (default-value variable)))
;; |        (with-current-buffer standard-output
;; |          (princ "global value is ")
;; |          (terpri)
;; |          (let ((from (point)))
;; |            (pp val)
;; |            (if (< (point) (+ from 20))
;; |                (delete-region (1- from) from))))))
;; |    (terpri))
;; `----

;; W/re `describe-variable's current behavior around permanent-local it
;; does not serve its purpose well.

;; When a user asks that for the description of a variable variable that
;; variable has a permanent-local property it is important that a user be
;; informed as such.

;; Specifically that:

;; - a variable _is_ permanent-local;

;; - that though currently null, _if_ set it would be permanent-local in
;;   current-buffer;

;; - that if non-nil the value is permanent-local in current-buffer and
;;   will persist across invocations of `kill-all-local-variables' unless
;;   explicitly removed.

;; IOW, knowing that a variable is:

;;  "Local in buffer <SOME-BUFFER>;"

;; Is not the same as saying that a variable is; 
 
;;  "Permanently local in <SOME-BUFFER>;"

;; Which isn't the same as saying either of following about a variable;

;;  "This variable is a permanent-local.
;;   It satisfies the predicate `local-variable-p' in <SOME-BUFFER>.
;;   This value will persist in current-buffer until explicitly removed;"

;;  "This variable is a permanent-local. 
;;   Its current value is null in <SOME-BUFFER>.
;;   This variable satisfies the predicate `local-variable-if-set-p'.
;;   When set with `make-local-variable' the value will persist in
;;   current-buffer until explicitly removed;"

;; Following fncn `%describe-variable-miss-permanent-local-example'
;; illustrates what I perceive to be the "problem":

;;; ==============================
(defun %describe-variable-miss-permanent-local-example ()
  "Illustrate `describe-variable' not identifying permanent-local variables.
Display results in buffer \"*BUFFER-W-OUT-LOCAL-KB-HOOK*\"."
  (interactive)
  (let ((dvdr (concat "\n" (make-string 80 59) "\n"))
        (w-lcl "*BUFFER-W-LOCAL-KB-HOOK*")
        (w/o-lcl "*BUFFER-W-OUT-LOCAL-KB-HOOK*")
        kbh tmp-fncn)
    (with-current-buffer (get-buffer-create w-lcl)
      (fset 'tmp-fncn #'(lambda () "does nothing." nil))
      (add-hook 'kill-buffer-hook 'tmp-fncn nil t)
      (setq kbh (buffer-local-value 'kill-buffer-hook (current-buffer)))
      (with-current-buffer (get-buffer-create w/o-lcl)
        (erase-buffer)
        (save-excursion
          (insert (substring dvdr 1)
                  ";; Local value of `kill-buffer-hook' in buffer: " w-lcl "\n;;\n"
                  (format "%S" kbh) dvdr)
          (setq kbh (buffer-local-value 'kill-buffer-hook (current-buffer)))
          (insert dvdr ";; Local value of `kill-buffer-hook' in buffer: " w/o-lcl "\n;;\n"
                  (format "%S" kbh) dvdr dvdr
                  ";; Evaluating `describe-variable' for `kill-buffer-hook' with buffer: " 
                  w/o-lcl "\n;; NOTE, with `current-buffer' there is no indication "
                  "of `kill-buffer-hook' being permanent-local" dvdr "\n" 
                  (describe-variable 'kill-buffer-hook (current-buffer)) "\n" dvdr 
                  ";; Evaluating `describe-variable' for `kill-buffer-hook' with buffer: "
                  w-lcl "\n;; NOTE, contrasted with above, now there _is_ indication "
                  "of `kill-buffer-hook' as permanent-local.\n"
                  ";; Additionally, there is also indication that the "
                  "varaible's global value differs from the local." dvdr "\n" 
                  (describe-variable 'kill-buffer-hook (get-buffer w-lcl)) "\n" dvdr)))
      (prog1
          (unintern 'tmp-fncn)
        (kill-buffer (current-buffer))))
    (pop-to-buffer (get-buffer w/o-lcl))))
;;
;; (%describe-variable-miss-permanent-local-example)
;;
;; (unintern '%describe-variable-miss-permanent-local-example)

;;; ==============================
;; Following is a lightly tested modified version of `describe-variable'
;; tweaked to report more intelligently when variables are buffer-local
;; and/or permanent-local.  Following that is a patch to lisp/help-fns.el
;; current through Bzr-1000827:

;;;###autoload
(defun describe-variable (variable &optional buffer frame)
  "Display the full documentation of VARIABLE (a symbol).
Returns the documentation as a string, also.
If VARIABLE has a buffer-local value in BUFFER or FRAME
\(default to the current buffer and current frame),
it is displayed along with the global value."
  (interactive
   (let ((v (variable-at-point))
	 (enable-recursive-minibuffers t)
	 val)
     (setq val (completing-read (if (symbolp v)
				    (format
				     "Describe variable (default %s): " v)
				  "Describe variable: ")
				obarray
				'(lambda (vv)
				   (or (boundp vv)
				       (get vv 'variable-documentation)))
				t nil nil
				(if (symbolp v) (symbol-name v))))
     (list (if (equal val "")
	       v (intern val)))))
  (let (file-name
        (ploc (get variable 'permanent-local)))
    (unless (buffer-live-p buffer) (setq buffer (current-buffer)))
    (unless (frame-live-p frame) (setq frame (selected-frame)))
    (if (not (symbolp variable))
	(message "You did not specify a variable")
      (save-excursion
	(let ((valvoid (not (with-current-buffer buffer (boundp variable))))
	      val val-start-pos locus)
	  ;; Extract the value before setting up the output buffer,
	  ;; in case `buffer' *is* the output buffer.
	  (unless valvoid
	    (with-selected-frame frame
	      (with-current-buffer buffer
		(setq val (symbol-value variable)
		      locus (variable-binding-locus variable)))))
	  (help-setup-xref (list #'describe-variable variable buffer)
			   (called-interactively-p 'interactive))
	  (with-help-window (help-buffer)
	    (with-current-buffer buffer
	      (prin1 variable)
	      (setq file-name (find-lisp-object-file-name variable 'defvar))

	      (if file-name
		  (progn
		    (princ " is a variable defined in `")
		    (princ (if (eq file-name 'C-source)
			       "C source code"
			     (file-name-nondirectory file-name)))
		    (princ "'.\n")
		    (with-current-buffer standard-output
		      (save-excursion
			(re-search-backward "`\\([^`']+\\)'" nil t)
			(help-xref-button 1 'help-variable-def
					  variable file-name)))
		    (unless ploc
                      (if valvoid
                          (princ "It is void as a variable.")
                        (princ "Its "))))
                (unless ploc
                  (if valvoid
                      (princ " is void as a variable.")
                    (if (bufferp locus)
                        (when (null (buffer-local-value variable (get-buffer buffer)))
                          (terpri))
                      (princ "'s "))))))
              
            (with-current-buffer standard-output
              (if valvoid
                  nil
                (progn
                  (setq val-start-pos (point))
                  (if (bufferp locus)
                      (if ploc
                          (if (buffer-local-value variable (get-buffer buffer))
                              (princ "Its local value is ")
                            (princ "Its default local value is "))
                        (princ "Its non-local global value is "))
                    (if ploc
                        (princ "Its value is ")
                      (princ "value is ")))
                  (terpri)
                  (let ((from (point)))
                    (pp val)
                    ;; Hyperlinks in variable's value are quite frequently
                    ;; inappropriate e.g C-h v <RET> features <RET>
                    ;; (help-xref-on-pp from (point))
                    (if (< (point) 
                           (if ploc 
                               (+ from 20)
                             (+ from 20)))
                        (delete-region (1- from) from)))))
              (when (and ploc (not (bufferp locus)))
                (terpri)    
                (princ "It has a non-nil permanent-local property;")))
            (terpri)
            
            (progn
              (cond ((bufferp locus)
                     (princ (format "It is a %slocal variable in buffer %s;\n"
                                    (if ploc
                                        "permanent-" "buffer-")
                                    (buffer-name))))
                    ((framep locus) 
                     (princ (format "It is a frame-local variable;")))
                    ((null locus)
                     "It is a global variable;"))
              (when (or (framep locus) (bufferp locus) ploc)
                (if (not (default-boundp variable))
                    (princ "Value as global: void")
                  (unless (null (buffer-local-value variable (get-buffer buffer)))
                    (let ((val (default-value variable)))
                      (with-current-buffer standard-output
                        (princ "Value as global: ")
                        (terpri)
                        ;; Fixme: pp can take an age if you happen to
                        ;; ask for a very large expression.  We should
                        ;; probably print it raw once and check it's a
                        ;; sensible size before prettyprinting.  -- fx
                        (let ((from (point)))
                          (pp val)
                          ;; See previous comment for this function.
                          ;; (help-xref-on-pp from (point))
                          (if (< (point) (+ from 20))
                              (delete-region (1- from) from))))))))
              (terpri))
            
	    ;; If the value is large, move it to the end.
	    (with-current-buffer standard-output
	      (when (> (count-lines (point-min) (point-max)) 10)
		;; Note that setting the syntax table like below
		;; makes forward-sexp move over a `'s' at the end
		;; of a symbol.
		(set-syntax-table emacs-lisp-mode-syntax-table)
		(goto-char val-start-pos)
		;; The line below previously read as
		;; (delete-region (point) (progn (end-of-line) (point)))
		;; which suppressed display of the buffer local value for
		;; large values.
		(when (looking-at "value is") (replace-match ""))
		(save-excursion
		  (insert "\n\nValue:")
		  (set (make-local-variable 'help-button-cache)
		       (point-marker)))
		(insert "value is shown ")
		(insert-button "below"
			       'action help-button-cache
			       'follow-link t
			       'help-echo "mouse-2, RET: show value")
		(insert ".\n")))
            (terpri)
            
            (let* ((alias (condition-case nil
                              (indirect-variable variable)
                            (error variable)))
                   (obsolete (get variable 'byte-obsolete-variable))
		   (use (car obsolete))
		   (safe-var (get variable 'safe-local-variable))
                   (doc (or (documentation-property variable 'variable-documentation)
                            (documentation-property alias 'variable-documentation)))
                   (extra-line nil))
              ;; Add a note for variables that have been make-var-buffer-local.
              (when (and (local-variable-if-set-p variable)
                         (or (not (local-variable-p variable))
                             (with-temp-buffer
                               (local-variable-if-set-p variable))))
                (setq extra-line t)
                (princ "  Automatically becomes buffer-local when set in any fashion.\n"))

              ;; Mention if it's an alias
              (unless (eq alias variable)
                (setq extra-line t)
                (princ (format "  This variable is an alias for `%s'.\n" alias)))

              (when obsolete
                (setq extra-line t)
                (princ "  This variable is obsolete")
                (if (cdr obsolete) (princ (format " since %s" (cdr obsolete))))
		(princ (cond ((stringp use) (concat ";\n  " use))
			     (use (format ";\n  use `%s' instead." (car obsolete)))
			     (t ".")))
                (terpri))

	      (when (member (cons variable val) file-local-variables-alist)
		(setq extra-line t)
		(if (member (cons variable val) dir-local-variables-alist)
		    (let ((file (and (buffer-file-name)
				     (not (file-remote-p (buffer-file-name)))
				     (dir-locals-find-file (buffer-file-name)))))
		      (princ "  This variable is a directory local variable")
		      (when file
			(princ (concat "\n  from the file \""
				       (if (consp file)
					   (car file)
					 file)
				       "\"")))
		      (princ ".\n"))
		  (princ "  This variable is a file local variable.\n")))

	      (when (memq variable ignored-local-variables)
		(setq extra-line t)
		(princ "  This variable is ignored when used as a file local \
variable.\n"))

	      ;; Can be both risky and safe, eg auto-fill-function.
	      (when (risky-local-variable-p variable)
		(setq extra-line t)
		(princ "  This variable is potentially risky when used as a \
file local variable.\n")
		(when (assq variable safe-local-variable-values)
		  (princ "  However, you have added it to \
`safe-local-variable-values'.\n")))

	      (when safe-var
                (setq extra-line t)
		(princ "  This variable is safe as a file local variable ")
		(princ "if its value\n  satisfies the predicate ")
		(princ (if (byte-code-function-p safe-var)
			   "which is byte-compiled expression.\n"
			 (format "`%s'.\n" safe-var))))

              (if extra-line (terpri))
	      (princ "Documentation:\n")
	      (with-current-buffer standard-output
		(insert (or doc "Not documented as a variable."))))

	    ;; Make a link to customize if this variable can be customized.
	    (when (custom-variable-p variable)
	      (let ((customize-label "customize"))
		(terpri)
		(terpri)
		(princ (concat "You can " customize-label " this variable."))
		(with-current-buffer standard-output
		  (save-excursion
		    (re-search-backward
		     (concat "\\(" customize-label "\\)") nil t)
		    (help-xref-button 1 'help-customize-variable variable))))
	      ;; Note variable's version or package version
	      (let ((output (describe-variable-custom-version-info variable)))
		(when output
		  (terpri)
		  (terpri)
		  (princ output))))

	    (with-current-buffer standard-output
	      ;; Return the text we displayed.
	      (buffer-string))))))))

;;; ==============================

*** ediff7517N_J	2010-07-15 19:06:56.253304353 -0400
--- help-fns.el Bzr-100827	2010-07-15 19:06:56.304504327 -0400
***************
*** 20,27 ****
  				(if (symbolp v) (symbol-name v))))
       (list (if (equal val "")
  	       v (intern val)))))
!   (let (file-name
!         (ploc (get variable 'permanent-local)))
      (unless (buffer-live-p buffer) (setq buffer (current-buffer)))
      (unless (frame-live-p frame) (setq frame (selected-frame)))
      (if (not (symbolp variable))
--- 20,26 ----
  				(if (symbolp v) (symbol-name v))))
       (list (if (equal val "")
  	       v (intern val)))))
!   (let (file-name)
      (unless (buffer-live-p buffer) (setq buffer (current-buffer)))
      (unless (frame-live-p frame) (setq frame (selected-frame)))
      (if (not (symbolp variable))
***************
*** 55,87 ****
  			(re-search-backward "`\\([^`']+\\)'" nil t)
  			(help-xref-button 1 'help-variable-def
  					  variable file-name)))
! 		    (unless ploc
!                       (if valvoid
!                           (princ "It is void as a variable.")
!                         (princ "Its "))))
!                 (unless ploc
!                   (if valvoid
!                       (princ " is void as a variable.")
!                     (if (bufferp locus)
!                         (when (null (buffer-local-value variable (get-buffer buffer)))
!                           (terpri))
!                       (princ "'s "))))))
!               
!             (with-current-buffer standard-output
!               (if valvoid
!                   nil
!                 (progn
!                   (setq val-start-pos (point))
!                   (if (bufferp locus)
!                       (if ploc
!                           (if (buffer-local-value variable (get-buffer buffer))
!                               (princ "Its local value is ")
!                             (princ "Its default local value is "))
!                         (princ "Its non-local global value is "))
!                     (if ploc
!                         (princ "Its value is ")
!                       (princ "value is ")))
!                   (terpri)
!                   (let ((from (point)))
!                     (pp val)
!                     ;; Hyperlinks in variable's value are quite frequently
--- 54,222 ----
  			(re-search-backward "`\\([^`']+\\)'" nil t)
  			(help-xref-button 1 'help-variable-def
  					  variable file-name)))
! 		    (if valvoid
! 			(princ "It is void as a variable.")
! 		      (princ "Its ")))
! 		(if valvoid
! 		    (princ " is void as a variable.")
! 		  (princ "'s "))))
! 	    (if valvoid
! 		nil
! 	      (with-current-buffer standard-output
! 		(setq val-start-pos (point))
! 		(princ "value is ")
! 		(terpri)
! 		(let ((from (point)))
! 		  (pp val)
! 		  ;; Hyperlinks in variable's value are quite frequently
! 		  ;; inappropriate e.g C-h v <RET> features <RET>
! 		  ;; (help-xref-on-pp from (point))
! 		  (if (< (point) (+ from 20))
! 		      (delete-region (1- from) from)))))
! 	    (terpri)
! 
! 	    (when locus
! 	      (if (bufferp locus)
! 		  (princ (format "%socal in buffer %s; "
! 				 (if (get variable 'permanent-local)
! 				     "Permanently l" "L")
! 				 (buffer-name)))
! 		(princ (format "It is a frame-local variable; ")))
! 	      (if (not (default-boundp variable))
! 		  (princ "globally void")
! 		(let ((val (default-value variable)))
! 		  (with-current-buffer standard-output
! 		    (princ "global value is ")
! 		    (terpri)
! 		    ;; Fixme: pp can take an age if you happen to
! 		    ;; ask for a very large expression.  We should
! 		    ;; probably print it raw once and check it's a
! 		    ;; sensible size before prettyprinting.  -- fx
! 		    (let ((from (point)))
! 		      (pp val)
! 		      ;; See previous comment for this function.
! 		      ;; (help-xref-on-pp from (point))
! 		      (if (< (point) (+ from 20))
! 			  (delete-region (1- from) from))))))
!               (terpri))
! 
! 	    ;; If the value is large, move it to the end.
! 	    (with-current-buffer standard-output
! 	      (when (> (count-lines (point-min) (point-max)) 10)
! 		;; Note that setting the syntax table like below
! 		;; makes forward-sexp move over a `'s' at the end
! 		;; of a symbol.
! 		(set-syntax-table emacs-lisp-mode-syntax-table)
! 		(goto-char val-start-pos)
! 		;; The line below previously read as
! 		;; (delete-region (point) (progn (end-of-line) (point)))
! 		;; which suppressed display of the buffer local value for
! 		;; large values.
! 		(when (looking-at "value is") (replace-match ""))
! 		(save-excursion
! 		  (insert "\n\nValue:")
! 		  (set (make-local-variable 'help-button-cache)
! 		       (point-marker)))
! 		(insert "value is shown ")
! 		(insert-button "below"
! 			       'action help-button-cache
! 			       'follow-link t
! 			       'help-echo "mouse-2, RET: show value")
! 		(insert ".\n")))
!             (terpri)
! 
!             (let* ((alias (condition-case nil
!                               (indirect-variable variable)
!                             (error variable)))
!                    (obsolete (get variable 'byte-obsolete-variable))
! 		   (use (car obsolete))
! 		   (safe-var (get variable 'safe-local-variable))
!                    (doc (or (documentation-property variable 'variable-documentation)
!                             (documentation-property alias 'variable-documentation)))
!                    (extra-line nil))
!               ;; Add a note for variables that have been make-var-buffer-local.
!               (when (and (local-variable-if-set-p variable)
!                          (or (not (local-variable-p variable))
!                              (with-temp-buffer
!                                (local-variable-if-set-p variable))))
!                 (setq extra-line t)
!                 (princ "  Automatically becomes buffer-local when set in any fashion.\n"))
! 
!               ;; Mention if it's an alias
!               (unless (eq alias variable)
!                 (setq extra-line t)
!                 (princ (format "  This variable is an alias for `%s'.\n" alias)))
! 
!               (when obsolete
!                 (setq extra-line t)
!                 (princ "  This variable is obsolete")
!                 (if (cdr obsolete) (princ (format " since %s" (cdr obsolete))))
! 		(princ (cond ((stringp use) (concat ";\n  " use))
! 			     (use (format ";\n  use `%s' instead." (car obsolete)))
! 			     (t ".")))
!                 (terpri))
! 
! 	      (when (member (cons variable val) file-local-variables-alist)
! 		(setq extra-line t)
! 		(if (member (cons variable val) dir-local-variables-alist)
! 		    (let ((file (and (buffer-file-name)
! 				     (not (file-remote-p (buffer-file-name)))
! 				     (dir-locals-find-file (buffer-file-name)))))
! 		      (princ "  This variable is a directory local variable")
! 		      (when file
! 			(princ (concat "\n  from the file \""
! 				       (if (consp file)
! 					   (car file)
! 					 file)
! 				       "\"")))
! 		      (princ ".\n"))
! 		  (princ "  This variable is a file local variable.\n")))
! 
! 	      (when (memq variable ignored-local-variables)
! 		(setq extra-line t)
! 		(princ "  This variable is ignored when used as a file local \
! variable.\n"))
! 
! 	      ;; Can be both risky and safe, eg auto-fill-function.
! 	      (when (risky-local-variable-p variable)
! 		(setq extra-line t)
! 		(princ "  This variable is potentially risky when used as a \
! file local variable.\n")
! 		(when (assq variable safe-local-variable-values)
! 		  (princ "  However, you have added it to \
! `safe-local-variable-values'.\n")))
! 
! 	      (when safe-var
!                 (setq extra-line t)
! 		(princ "  This variable is safe as a file local variable ")
! 		(princ "if its value\n  satisfies the predicate ")
! 		(princ (if (byte-code-function-p safe-var)
! 			   "which is byte-compiled expression.\n"
! 			 (format "`%s'.\n" safe-var))))
! 
!               (if extra-line (terpri))
! 	      (princ "Documentation:\n")
! 	      (with-current-buffer standard-output
! 		(insert (or doc "Not documented as a variable."))))
! 
! 	    ;; Make a link to customize if this variable can be customized.
! 	    (when (custom-variable-p variable)
! 	      (let ((customize-label "customize"))
! 		(terpri)
! 		(terpri)
! 		(princ (concat "You can " customize-label " this variable."))
! 		(with-current-buffer standard-output
! 		  (save-excursion
! 		    (re-search-backward
! 		     (concat "\\(" customize-label "\\)") nil t)
! 		    (help-xref-button 1 'help-customize-variable variable))))
! 	      ;; Note variable's version or package version
! 	      (let ((output (describe-variable-custom-version-info variable)))
! 		(when output
! 		  (terpri)
! 		  (terpri)
! 		  (princ output))))
! 
! 	    (with-current-buffer standard-output
! 	      ;; Return the text we displayed.
! 	      (buffer-string))))))))

;;; ==============================
;;; EOF

^ permalink raw reply	[flat|nested] 2+ messages in thread

* bug#6644: `describe-variable' misses on certain permanent-locals [PATCH] (putative)
  2010-07-16  0:14 bug#6644: `describe-variable' misses on certain permanent-locals [PATCH] (putative) MON KEY
@ 2012-08-05  8:42 ` Chong Yidong
  0 siblings, 0 replies; 2+ messages in thread
From: Chong Yidong @ 2012-08-05  8:42 UTC (permalink / raw)
  To: MON KEY; +Cc: 6644

MON KEY <monkey@sandpframing.com> writes:

> As currently written `describe-variable' will, for certain variables,
> only inform if a variable is permanently local when it is set to have
> a local value in current-buffer.
>
> This happens for example with `kill-buffer-hook' which _is_ a
> permanent-local. And in thes situations, `describe-variable' (and by
> proxy "*Help*") can not tell us if a variable is permanent-local
> unless the docstring explicitly states as such.

Thanks.  I committed a slightly different fix than what you suggested.





^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2012-08-05  8:42 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-07-16  0:14 bug#6644: `describe-variable' misses on certain permanent-locals [PATCH] (putative) MON KEY
2012-08-05  8:42 ` Chong Yidong

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).