From mboxrd@z Thu Jan 1 00:00:00 1970 Path: main.gmane.org!not-for-mail From: "Stefan Monnier" Newsgroups: gmane.emacs.devel Subject: Re: Invisible colons in Emacs Info. Date: Wed, 25 Jun 2003 17:12:44 -0400 Sender: emacs-devel-bounces+emacs-devel=quimby.gnus.org@gnu.org Message-ID: <200306252112.h5PLCiko007276@rum.cs.yale.edu> References: <200306250234.h5P2YNY15932@eel.dms.auburn.edu> <200306251752.h5PHqcTi006187@rum.cs.yale.edu> NNTP-Posting-Host: main.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Trace: main.gmane.org 1056584524 28961 80.91.224.249 (25 Jun 2003 23:42:04 GMT) X-Complaints-To: usenet@main.gmane.org NNTP-Posting-Date: Wed, 25 Jun 2003 23:42:04 +0000 (UTC) Cc: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+emacs-devel=quimby.gnus.org@gnu.org Thu Jun 26 01:42:00 2003 Return-path: Original-Received: from quimby.gnus.org ([80.91.224.244]) by main.gmane.org with esmtp (Exim 3.35 #1 (Debian)) id 19VJto-0007Wi-00 for ; Thu, 26 Jun 2003 01:42:00 +0200 Original-Received: from monty-python.gnu.org ([199.232.76.173]) by quimby.gnus.org with esmtp (Exim 3.12 #1 (Debian)) id 19VJy3-00037M-00 for ; Thu, 26 Jun 2003 01:46:23 +0200 Original-Received: from localhost ([127.0.0.1] helo=monty-python.gnu.org) by monty-python.gnu.org with esmtp (Exim 4.20) id 19VJsZ-0002xm-Uo for emacs-devel@quimby.gnus.org; Wed, 25 Jun 2003 19:40:44 -0400 Original-Received: from list by monty-python.gnu.org with tmda-scanned (Exim 4.20) id 19VJqj-0002jo-SC for emacs-devel@gnu.org; Wed, 25 Jun 2003 19:38:49 -0400 Original-Received: from mail by monty-python.gnu.org with spam-scanned (Exim 4.20) id 19VIvo-0001bj-Rh for emacs-devel@gnu.org; Wed, 25 Jun 2003 18:40:13 -0400 Original-Received: from rum.cs.yale.edu ([128.36.229.169]) by monty-python.gnu.org with esmtp (Exim 4.20) id 19VHZO-0001kI-7j for emacs-devel@gnu.org; Wed, 25 Jun 2003 17:12:46 -0400 Original-Received: from rum.cs.yale.edu (localhost [127.0.0.1]) by rum.cs.yale.edu (8.12.8/8.12.8) with ESMTP id h5PLCiHl007278; Wed, 25 Jun 2003 17:12:44 -0400 Original-Received: (from monnier@localhost) by rum.cs.yale.edu (8.12.8/8.12.8/Submit) id h5PLCiko007276; Wed, 25 Jun 2003 17:12:44 -0400 X-Mailer: exmh version 2.4 06/23/2000 with nmh-1.0.4 Original-To: Luc Teirlinck Original-cc: karl@freefriends.org X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1b5 Precedence: list List-Id: Emacs development discussions. List-Help: List-Post: List-Subscribe: , List-Archive: List-Unsubscribe: , Errors-To: emacs-devel-bounces+emacs-devel=quimby.gnus.org@gnu.org Xref: main.gmane.org gmane.emacs.devel:15257 X-Report-Spam: http://spam.gmane.org/gmane.emacs.devel:15257 So it seems that the problem was introduced by revision 1.333 where we're trying to allow menu entry names with colons in them. The problem is that we skip `:' that could be part of the name without checking whether that is the last colon and is thus obviously not part of the name. Others will say the problem is simply that we allow `:', but assuming we still want to allow colon despite the obvious ambiguity, I think the patch below should help. It uses regexp matching rather than combinations of skip-chars-forward and stuff. The reason for the change is because, by using regexp-matching, we can take advantage of its backtracking to resolve the ambiguity. Problem is that it still fails on: * name1:foo:node. des:cription. because it thinks the menu entry name is "name1:foo:node. des" and the node name is "cription". I'm sure we can tune the disambiguation by saying that menu entries can have `:' but cannot have `.' after the `:' or some other such heuristic. If anybody wants to spice up the Info-menu-entry-name-re regexp accordingly... We really need a better solution. Stefan Index: info.el =================================================================== RCS file: /cvsroot/emacs/emacs/lisp/info.el,v retrieving revision 1.358 diff -u -b -r1.358 info.el --- info.el 19 Jun 2003 20:53:06 -0000 1.358 +++ info.el 25 Jun 2003 20:56:33 -0000 @@ -902,7 +902,9 @@ nodename end) (re-search-backward "^\^_") (search-forward "Node: ") - (setq nodename (Info-following-node-name)) + (setq nodename + (and (looking-at (Info-following-node-name-re)) + (match-string 1))) (search-forward "\n\^_" nil 'move) (beginning-of-line) (setq end (point)) @@ -1201,8 +1203,8 @@ nodename) (setq filename (if (= (match-beginning 1) (match-end 1)) "" - (substring nodename (match-beginning 2) (match-end 2))) - nodename (substring nodename (match-beginning 3) (match-end 3))) + (match-string 2 nodename)) + nodename (match-string 3 nodename)) (let ((trim (string-match "\\s *\\'" filename))) (if trim (setq filename (substring filename 0 trim)))) (let ((trim (string-match "\\s *\\'" nodename))) @@ -1385,35 +1387,33 @@ (defun Info-extract-pointer (name &optional errorname) "Extract the value of the node-pointer named NAME. If there is none, use ERRORNAME in the error message; -if ERRORNAME is nil, just return nil. -Bind this in case the user sets it to nil." +if ERRORNAME is nil, just return nil." + ;; Bind this in case the user sets it to nil. (let ((case-fold-search t)) (save-excursion - (save-restriction (goto-char (point-min)) (let ((bound (point))) (forward-line 1) - (cond ((re-search-backward (concat name ":") bound t) - (goto-char (match-end 0)) - (Info-following-node-name)) + (cond ((re-search-backward + (concat name ":" (Info-following-node-name-re)) bound t) + (match-string 1)) ((not (eq errorname t)) (error "Node has no %s" - (capitalize (or errorname name)))))))))) + (capitalize (or errorname name))))))))) -(defun Info-following-node-name (&optional allowedchars) - "Return the node name in the buffer following point. +(defun Info-following-node-name-re (&optional allowedchars) + "Return a regexp matching a node name. ALLOWEDCHARS, if non-nil, goes within [...] to make a regexp -saying which chars may appear in the node name." - (skip-chars-forward " \t") - (buffer-substring-no-properties - (point) - (progn - (while (looking-at (concat "[" (or allowedchars "^,\t\n") "]")) - (skip-chars-forward (concat (or allowedchars "^,\t\n") "(")) - (if (looking-at "(") - (skip-chars-forward "^)"))) - (skip-chars-backward " ") - (point)))) +saying which chars may appear in the node name. +Submatch 1 is the complete node name. +Submatch 2 if non-nil is the parenthesized file name part of the node name. +Submatch 3 is the local part of the node name. +End of submatch 0, 1, and 3 are the same, so you can safely concat." + (concat "[ \t]*" ;Skip leading space. + "\\(\\(([^)]+)\\)?" ;Node name can start with a file name. + "\\([" (or allowedchars "^,\t\n") "]*" ;Any number of allowed chars. + "[" (or allowedchars "^,\t\n") " ]" ;The last char can't be a space. + "\\|\\)\\)")) ;Allow empty node names. (defun Info-next () "Go to the next node of this node." @@ -1472,9 +1472,7 @@ (goto-char (point-min)) (while (re-search-forward "\\*note[ \n\t]*\\([^:]*\\):" nil t) - (setq str (buffer-substring-no-properties - (match-beginning 1) - (1- (point)))) + (setq str (match-string-no-properties 1)) ;; See if this one should be the default. (and (null default) (<= (match-beginning 0) start-point) @@ -1494,23 +1492,14 @@ (if (eq default t) (setq default str)) (if (eq alt-default t) (setq alt-default str)) ;; Don't add this string if it's a duplicate. - ;; We use a loop instead of "(assoc str completions)" because - ;; we want to do a case-insensitive compare. - (let ((tail completions) - (tem (downcase str))) - (while (and tail - (not (string-equal tem (downcase (car (car tail)))))) - (setq tail (cdr tail))) - (or tail - (setq completions - (cons (cons str nil) - completions)))))) + (or (assoc-string str completions t) + (push str completions)))) ;; If no good default was found, try an alternate. (or default (setq default alt-default)) ;; If only one cross-reference found, then make it default. (if (eq (length completions) 1) - (setq default (car (car completions)))) + (setq default (car completions))) (if completions (let ((input (completing-read (if default (concat @@ -1543,19 +1532,21 @@ (setq i (+ i 1))) (Info-goto-node target))) +(defconst Info-menu-entry-name-re "\\(?:[^:\n]\\|:[^,.;() \t\n]\\)*" + "Regexp that matches a menu entry name upto but not including the colon. +Because of ambiguities, this should be concatenated with something like +`:' and `Info-following-node-name-re'.") + (defun Info-extract-menu-node-name (&optional multi-line) (skip-chars-forward " \t\n") - (let ((beg (point)) - str) - (while (not (looking-at ":*[,.;() \t\n]")) - (skip-chars-forward "^:") - (forward-char 1)) - (setq str - (if (looking-at ":") - (buffer-substring-no-properties beg (1- (point))) - (skip-chars-forward " \t\n") - (Info-following-node-name (if multi-line "^.,\t" "^.,\t\n")))) - (replace-regexp-in-string "[ \n]+" " " str))) + (let (str) + (when (looking-at (concat Info-menu-entry-name-re ":\\(:\\|" + (Info-following-node-name-re + (if multi-line "^.,\t" "^.,\t\n")) "\\)")) + (setq str (or (match-string 2) + (buffer-substring (match-beginning 0) + (1- (match-beginning 1))))) + (replace-regexp-in-string "[ \n]+" " " str)))) ;; No one calls this. ;;(defun Info-menu-item-sequence (list) @@ -1567,7 +1558,8 @@ (defvar Info-complete-next-re nil) (defvar Info-complete-cache nil) -(defconst Info-node-spec-re "[^.,:(]*\\(([^)]*)[^.,:]*\\)?[,:.]" +(defconst Info-node-spec-re + (concat (Info-following-node-name-re "^.,:") "[,:.]") "Regexp to match the text after a : until the terminating `.'.") (defun Info-complete-menu-item (string predicate action) @@ -1594,7 +1586,7 @@ (concat "\n\\* +" (regexp-quote string) ":") nil t) (let ((pattern (concat "\n\\* +\\(" (regexp-quote string) - "[^\t\n]*?\\):" Info-node-spec-re)) + Info-menu-entry-name-re "\\):" Info-node-spec-re)) completions) ;; Check the cache. (if (and (equal (nth 0 Info-complete-cache) Info-current-file) @@ -2373,9 +2365,7 @@ (save-excursion (goto-char (point-min)) (while (re-search-forward "\\*note[ \n\t]*\\([^:]*\\):" nil t) - (setq str (buffer-substring - (match-beginning 1) - (1- (point)))) + (setq str (match-string 1)) (setq i 0) (while (setq i (string-match "[ \n\t]+" str i)) (setq str (concat (substring str 0 i) " " @@ -2798,7 +2788,7 @@ (let* ((nbeg (match-beginning 2)) (nend (match-end 2)) (tbeg (match-beginning 1)) - (tag (buffer-substring tbeg (match-end 1)))) + (tag (match-string 1))) (if (string-equal tag "Node") (put-text-property nbeg nend 'font-lock-face 'info-header-node) (put-text-property nbeg nend 'font-lock-face 'info-header-xref) @@ -2817,7 +2807,7 @@ ((equal tag "Up") Info-up-link-keymap)))))) (when Info-use-header-line (goto-char (point-min)) - (let ((header-end (save-excursion (end-of-line) (point))) + (let ((header-end (line-end-position)) header) ;; If we find neither Next: nor Prev: link, show the entire ;; node header. Otherwise, don't show the File: and Node: @@ -2829,7 +2819,7 @@ (progn (goto-char (match-beginning 1)) (setq header (buffer-substring (point) header-end))) - (if (re-search-forward "node:[ \t]*[^ \t]+[ \t]*" nil t) + (if (re-search-forward "node:[ \t]*[^ \t]+[ \t]*" header-end t) (setq header (concat "No next, prev or up links -- " (buffer-substring (point) header-end))) @@ -2933,9 +2923,9 @@ (< (- (point-max) (point)) Info-fontify-maximum-menu-size)) (let ((n 0) cont) - (while (re-search-forward (concat "^\\* +\\([^:\t\n]*\\)\\(:" - Info-node-spec-re - "\\([ \t]*\\)\\)") + (while (re-search-forward + (concat "^\\* +\\(" Info-menu-entry-name-re "\\)\\(:" + Info-node-spec-re "\\([ \t]*\\)\\)") nil t) (setq n (1+ n)) (if (and (<= n 9) (zerop (% n 3))) ; visual aids to help with 1-9 keys @@ -2952,11 +2942,11 @@ '(font-lock-face info-xref mouse-face highlight)))) (when (eq Info-hide-note-references t) - (put-text-property (match-beginning 2) (1- (match-end 4)) + (put-text-property (match-beginning 2) (1- (match-end 6)) 'invisible t) ;; We need a stretchable space like :align-to but with ;; a minimum value. - (put-text-property (1- (match-end 4)) (match-end 4) 'display + (put-text-property (1- (match-end 6)) (match-end 6) 'display (if (>= 22 (- (match-end 1) (match-beginning 0))) '(space :align-to 24)