+(defvar info-xref-good 0
+ "Count of good cross references, during info-xref processing.")
+(defvar info-xref-bad 0
+ "Count of bad cross references, during info-xref processing.")
+(defvar info-xref-unavail 0
+ "Count of unavailable cross references, during info-xref processing.")
+
+(defvar info-xref-output-heading ""
+ "A heading string, during info-xref processing.
+This is shown if there's an error, but not if successful.")
+
+(defvar info-xref-filename nil
+ "The current buffer's filename, during info-xref processing.
+When looking at file contents in a temp buffer there's no
+`buffer-file-name', hence this variable.")
+
+(defvar info-xref-xfile-alist nil
+ "Info files found or not found, during info-xref processing.
+Key is \"(foo)\" etc and value nil or t according to whether info
+manual \"(foo)\" exists or not. This is used to suppress
+duplicate messages about foo not being available. (Duplicates
+within one top-level file that is.)")
+
+(defvar info-xref-in-progress nil)
+(defmacro info-xref-with-output (&rest body)
+ "Run BODY with an info-xref output buffer.
+This is meant to nest, so you can wrap it around a set of
+different info-xref checks and have them write to the one output
+buffer created by the outermost `info-xref-with-output', with an
+overall good/bad count summary inserted at the very end."
+
+ (declare (debug t))
+ `(save-excursion
+ (unless info-xref-in-progress
+ (display-buffer (get-buffer-create info-xref-output-buffer))
+ (set-buffer info-xref-output-buffer)
+ (setq buffer-read-only nil)
+ (fundamental-mode)
+ (erase-buffer)
+ (insert ";; info-xref output -*- mode: compilation -*-\n\n")
+ (compilation-mode)
+ (setq info-xref-good 0
+ info-xref-bad 0
+ info-xref-unavail 0
+ info-xref-xfile-alist nil))
+
+ (let ((info-xref-in-progress t)
+ (info-xref-output-heading ""))
+ ,@body)
+
+ (unless info-xref-in-progress
+ (info-xref-output "done, %d good, %d bad, %d unavailable"
+ info-xref-good info-xref-bad info-xref-unavail))))
+
+(defun info-xref-output (fmt &rest args)
+ "Emit a `format'-ed message FMT+ARGS to the `info-xref-output-buffer'."
+ (with-current-buffer info-xref-output-buffer
+ (save-excursion
+ (goto-char (point-max))
+ (let ((inhibit-read-only t))
+ (insert info-xref-output-heading
+ (apply 'format fmt args)
+ "\n")))
+ (setq info-xref-output-heading "")
+ ;; all this info-xref can be pretty slow, display now so the user sees
+ ;; some progress
+ (sit-for 0)))
+(put 'info-xref-output 'byte-compile-format-like t)
+
+(defun info-xref-output-error (fmt &rest args)
+ "Emit a `format'-ed error FMT+ARGS to the `info-xref-output-buffer'.
+The error is attributed to `info-xref-filename' and the current
+buffer's line and column of point."
+ (apply 'info-xref-output
+ (concat "%s:%s:%s: " fmt)
+ info-xref-filename
+ (1+ (count-lines (point-min) (line-beginning-position)))
+ (1+ (current-column))
+ args))
+(put 'info-xref-output-error 'byte-compile-format-like t)
+
+
+;;-----------------------------------------------------------------------------
+;; node checking
+
+;; When asking Info-goto-node to fork, *info* needs to be the current
+;; buffer, otherwise it seems to clone the current buffer but then do the
+;; goto-node in plain *info*.
+;;
+;; We only fork if *info* already exists, if it doesn't then can create and
+;; destroy just that instead of a new name.
+;;
+;; If Info-goto-node can't find the file, then no new buffer is created. If
+;; it finds the file but not the node, then a buffer is created. Handle
+;; this difference by checking before killing.
+;;
+(defun info-xref-goto-node-p (node)
+ "Return t if it's possible to go to the given NODE."
+ (let ((oldbuf (current-buffer)))
+ (save-excursion
+ (save-window-excursion
+ (prog1
+ (condition-case nil
+ (progn
+ (Info-goto-node node
+ (when (get-buffer "*info*")
+ (set-buffer "*info*")
+ "xref - temporary"))
+ t)
+ (error nil))
+ (unless (equal (current-buffer) oldbuf)
+ (kill-buffer)))))))
+
+(defun info-xref-check-node (node)
+
+ ;; Collapse spaces as per info.el and `help-make-xrefs'.
+ ;; Note defcustom :info-link nodes don't get this whitespace collapsing,
+ ;; they should be the exact node name ready to visit.
+ ;; `info-xref-check-all-custom' uses `info-xref-goto-node-p' and so
+ ;; doesn't come through here.
+ ;;
+ ;; Could use "[\t\n ]+" but try to avoid uselessly replacing " " with " ".
+ (setq node (replace-regexp-in-string "[\t\n][\t\n ]*\\| [\t\n ]+" " "
+ node t t))
+
+ (if (not (string-match "\\`([^)]*)" node))
+ (info-xref-output-error "no `(file)' part at start of node: %s\n" node)
+ (let ((file (match-string 0 node)))
+
+ (if (string-equal "()" file)
+ (info-xref-output-error "empty filename part: %s" node)
+
+ ;; see if the file exists, if haven't looked before
+ (unless (assoc file info-xref-xfile-alist)
+ (let ((found (info-xref-goto-node-p file)))
+ (push (cons file found) info-xref-xfile-alist)
+ (unless found
+ (info-xref-output-error "not available to check: %s\n (this reported once per file)" file))))
+
+ ;; if the file exists, try the node
+ (cond ((not (cdr (assoc file info-xref-xfile-alist)))
+ (cl-incf info-xref-unavail))
+ ((info-xref-goto-node-p node)
+ (cl-incf info-xref-good))
+ (t
+ (cl-incf info-xref-bad)
+ (info-xref-output-error "no such node: %s" node)))))))
+
+
+;;-----------------------------------------------------------------------------
+