Subject: bug#19359: Buttons in man pages

From: [email-masked] (Álvar Ibeas)
Date: Wed, 17 Dec 2014 23:15:56 +0100

Here is another suggestion that could serve as an approach to avoid
cross-references being interpreted as word-wrapped links, when the
previous line doesn't end with a hyphen. This one allows references to
span more than two lines.

The variable `Man-name-regexp' is changed to include line
breaks. Hopefully, this doesn't mess up (many) other things.

As an advantage, with this approach, a cross-reference outside the `SEE
ALSO' section that is split across two (or more) lines can be followed
by typing RET not only with point on the first of them.

diff --git a/lisp/man.el b/lisp/man.el
index a61524b..51e5b1b 100644
--- a/lisp/man.el
+++ b/lisp/man.el
@@ -275,7 +275,22 @@ Used in `bookmark-set' to get the default bookmark name."
   :type 'hook
   :group 'man)
 
-(defvar Man-name-regexp "[-a-zA-Z0-9_+][-a-zA-Z0-9_.:+]*"
+(defvar Man-name-leading-character "[-a-zA-Z0-9_+]"
+  "Regular expression describing the first character of the name of a manpage.")
+
+(defvar Man-name-character "[-a-zA-Z0-9_.:+]"
+  "Regular expression describing a generic character in the name of a manpage.")
+
+(defvar Man-hyphenation-added-character "[\u2010\u00AD]"
+  "Regular expression describing a character added to break a name.")
+
+(defvar Man-hyphenation-character
+  (concat "\\(?:-\\|" Man-hyphenation-added-character "\\)")
+  "Regular expression describing a character used for hyphenation.")
+
+(defvar Man-name-regexp
+  (concat "\\(\\_<" Man-name-leading-character "\\(" Man-name-character "*?"
+	  Man-hyphenation-character "\n[ \t]*\\)*" Man-name-character "*\\_>\\)")
   "Regular expression describing the name of a manpage (without section).")
 
 (defvar Man-section-regexp "[0-9][a-zA-Z0-9+]*\\|[LNln]"
@@ -304,9 +319,7 @@ This regexp should not start with a `^' character.")
 This regular expression should start with a `^' character.")
 
 (defvar Man-reference-regexp
-  (concat "\\(" Man-name-regexp
-	  "\\(\n[ \t]+" Man-name-regexp "\\)*\\)[ \t]*(\\("
-	  Man-section-regexp "\\))")
+  (concat Man-name-regexp "[ \t]*(\\(" Man-section-regexp "\\))")
   "Regular expression describing a reference to another manpage.")
 
 (defvar Man-apropos-regexp
@@ -707,7 +720,33 @@ a \"/\" as a local filename.  The function returns either `man-db'
                      'man))))
     Man-support-local-filenames))
 
-
+
+(defun Man-skip-name-backward ()
+  "Move point backward until the beginning of what seems to be a
+name.  Return the name length."
+  (let (travel)
+    (setq travel (skip-chars-backward "-a-zA-Z0-9_.:+"))
+    (while (looking-back (concat Man-hyphenation-character "\n[ \t]*"))
+      (end-of-line 0)
+      (backward-char)
+      (setq travel (+ travel
+		      (skip-chars-backward "-a-zA-Z0-9_.:+"))))
+    travel))
+
+(defun Man-skip-name-forward ()
+  "Move point forward until the end of what seems to be a name.
+Return the name length."
+  (let (travel)
+    (setq travel (skip-chars-forward "-a-zA-Z0-9_.:+"))
+    (while (or (and (looking-back "-")
+		    (looking-at "$"))
+	       (looking-at (concat Man-hyphenation-character "$")))
+      (beginning-of-line 2)
+      (skip-chars-forward "[ \t]*")
+      (setq travel (+ travel
+		      (skip-chars-forward "-a-zA-Z0-9_.:+"))))
+    travel))
+
 ;; ======================================================================
 ;; default man entry: get word near point
 
@@ -729,7 +768,7 @@ POS defaults to `point'."
 	       ;; We skipped a valid section number backwards, look at
 	       ;; preceding text.
 	       (or (and (skip-chars-backward ",; \t")
-			(not (zerop (skip-chars-backward "-a-zA-Z0-9._+:"))))
+			(not (zerop (Man-skip-name-backward))))
 		   ;; Not a valid entry, move POS after closing paren.
 		   (not (setq pos (match-end 0)))))
 	  ;; We have a candidate, make `start' record its starting
@@ -737,7 +776,7 @@ POS defaults to `point'."
 	  (setq start (point))
 	;; Otherwise look at char before POS.
 	(goto-char pos)
-	(if (not (zerop (skip-chars-backward "-a-zA-Z0-9._+:")))
+	(if (not (zerop (Man-skip-name-backward)))
 	    ;; Our candidate is just before or around POS.
 	    (setq start (point))
 	  ;; Otherwise record the current column and look backwards.
@@ -750,7 +789,7 @@ POS defaults to `point'."
 	    ;; Skip section number backwards.
 	    (goto-char (match-beginning 0))
 	    (skip-chars-backward " \t"))
-	  (if (not (zerop (skip-chars-backward "-a-zA-Z0-9._+:")))
+	  (if (not (zerop (Man-skip-name-backward)))
 	      (progn
 		;; We have a candidate before POS ...
 		(setq start (point))
@@ -768,17 +807,12 @@ POS defaults to `point'."
 	    (setq start (point)))))
       ;; We have found a suitable starting point, try to skip at least
       ;; one character.
-      (skip-chars-forward "-a-zA-Z0-9._+:")
+      (Man-skip-name-forward)
       (setq word (buffer-substring-no-properties start (point)))
-      ;; If there is a continuation at the end of line, check the
-      ;; following line too, eg:
-      ;;     see this-
-      ;;     command-here(1)
-      ;; Note: This code gets executed iff our entry is after POS.
-      (when (looking-at "[ \t\r\n]+\\([-a-zA-Z0-9._+:]+\\)([0-9])")
-	(setq word (concat word (match-string-no-properties 1)))
-	;; Make sure the section number gets included by the code below.
-	(goto-char (match-end 1)))
+      (setq word (replace-regexp-in-string
+		  (concat "\\([ \t\n]\\|"
+			  Man-hyphenation-added-character "\\)")
+		  "" word))
       (when (string-match "[-._]+$" word)
 	(setq word (substring word 0 (match-beginning 0))))
       ;; The following was commented out since the preceding code
@@ -786,14 +820,14 @@ POS defaults to `point'."
 ;;;       ;; If looking at something like *strcat(... , remove the '*'
 ;;;       (when (string-match "^*" word)
 ;;; 	(setq word (substring word 1)))
-	(concat
-	 word
-	 (and (not (string-equal word ""))
-	      ;; If looking at something like ioctl(2) or brc(1M),
-	      ;; include the section number in the returned value.
-	      (looking-at
-	       (concat "[ \t]*([ \t]*\\(" Man-section-regexp "\\)[ \t]*)"))
-	      (format "(%s)" (match-string-no-properties 1)))))))
+      (concat
+       word
+       (and (not (string-equal word ""))
+	    ;; If looking at something like ioctl(2) or brc(1M),
+	    ;; include the section number in the returned value.
+	    (looking-at
+	     (concat "[ \t]*([ \t]*\\(" Man-section-regexp "\\)[ \t]*)"))
+	    (format "(%s)" (match-string-no-properties 1)))))))
 
 
 ;; ======================================================================
@@ -1535,7 +1569,7 @@ The following key bindings are currently in effect in the buffer:
 	      (end (progn
 		     (Man-next-section 1)
 		     (point)))
-	      hyphenated
+	      ;; hyphenated
 	      (runningpoint -1))
 	  (save-restriction
 	    (narrow-to-region start end)
@@ -1543,18 +1577,22 @@ The following key bindings are currently in effect in the buffer:
 	    (back-to-indentation)
 	    (while (and (not (eobp)) (/= (point) runningpoint))
 	      (setq runningpoint (point))
-	      (if (re-search-forward Man-hyphenated-reference-regexp end t)
-		  (let* ((word (match-string 0))
-			 (len (1- (length word))))
-		    (if hyphenated
-			(setq word (concat hyphenated word)
-			      hyphenated nil
-			      ;; Update len, in case a reference spans
-			      ;; more than two lines (paranoia).
-			      len (1- (length word))))
-		    (if (memq (aref word len) '(?- ?))
-			(setq hyphenated (substring word 0 len)))
-		    (and (string-match Man-reference-regexp word)
+	      (if (re-search-forward Man-reference-regexp end t)
+		  (let* ((word (match-string 0)))
+			 ;; (len (1- (length word)))
+		    ;; (if hyphenated
+		    ;; 	(setq word (concat hyphenated word)
+		    ;; 	      hyphenated nil
+		    ;; 	      ;; Update len, in case a reference spans
+		    ;; 	      ;; more than two lines (paranoia).
+		    ;; 	      len (1- (length word))))
+		    ;; (if (memq (aref word len) '(?- ?))
+		    ;; 	(setq hyphenated (substring word 0 len)))
+		    (setq word (replace-regexp-in-string
+				(concat "\\([ \t\n]\\|"
+					Man-hyphenation-added-character "\\)")
+				"" word))
+		    (and ;(string-match Man-reference-regexp word)
                          (not (member word Man--refpages))
                          (push word Man--refpages))))
 	      (skip-chars-forward " \t\n,"))))))
@@ -1720,25 +1758,25 @@ Actually the section moved to is described by `Man-see-also-regexp'."
       (error "%s" (concat "No " Man-see-also-regexp
 		     " section found in the current manpage"))))
 
-(defun Man-possibly-hyphenated-word ()
-  "Return a possibly hyphenated word at point.
-If the word starts at the first non-whitespace column, and the
-previous line ends with a hyphen, return the last word on the previous
-line instead.  Thus, if a reference to \"tcgetpgrp(3V)\" is hyphenated
-as \"tcgetp-grp(3V)\", and point is at \"grp(3V)\", we return
-\"tcgetp-\" instead of \"grp\"."
-  (save-excursion
-    (skip-syntax-backward "w()")
-    (skip-chars-forward " \t")
-    (let ((beg (point))
-	  (word (current-word)))
-      (when (eq beg (save-excursion
-		      (back-to-indentation)
-		      (point)))
-	(end-of-line 0)
-	(if (eq (char-before) ?-)
-	    (setq word (current-word))))
-      word)))
+;; (defun Man-possibly-hyphenated-word ()
+;;   "Return a possibly hyphenated word at point.
+;; If the word starts at the first non-whitespace column, and the
+;; previous line ends with a hyphen, return the last word on the previous
+;; line instead.  Thus, if a reference to \"tcgetpgrp(3V)\" is hyphenated
+;; as \"tcgetp-grp(3V)\", and point is at \"grp(3V)\", we return
+;; \"tcgetp-\" instead of \"grp\"."
+;;   (save-excursion
+;;     (skip-syntax-backward "w()")
+;;     (skip-chars-forward " \t")
+;;     (let ((beg (point))
+;; 	  (word (current-word)))
+;;       (when (eq beg (save-excursion
+;; 		      (back-to-indentation)
+;; 		      (point)))
+;; 	(end-of-line 0)
+;; 	(if (eq (char-before) ?-)
+;; 	    (setq word (current-word))))
+;;       word)))
 
 (defvar Man--last-refpage nil)
 
@@ -1752,14 +1790,14 @@ Specify which REFERENCE to use; default is based on word at point."
       (let* ((default (or
 		       (car (all-completions
 			     (let ((word
-				    (or (Man-possibly-hyphenated-word)
-					"")))
+			      	    (or (Man-default-man-entry)
+			      		"")))
 			       ;; strip a trailing '-':
-			       (if (string-match "-$" word)
-				   (substring word 0
-					      (match-beginning 0))
-				 word))
-			     Man--refpages))
+			       ;; (if (string-match "-$" word)
+			       ;; 	   (substring word 0
+			       ;; 		      (match-beginning 0))
+			       ;; 	 word))
+			     Man--refpages)))
                        (if (member Man--last-refpage Man--refpages)
                            Man--last-refpage
                          (car Man--refpages))))