From 1846a7d981d22cd3b1311401324dbd94a5f8aeaa Mon Sep 17 00:00:00 2001
From: Johan Claesson <johanclaesson@bredband.net>
Date: Sat, 30 Mar 2019 19:43:44 +0100
Subject: Emacs: Refine xref etags matches

Fetch and present arity in the *xref* window (unless too many hits).

If we are reasonably sure which definition the user wants to go to
then jump there directly instead of popping to the *xref* window.
---
 lib/tools/emacs/erlang-test.el |  11 +-
 lib/tools/emacs/erlang.el      | 560 +++++++++++++++++++++++++++--------------
 lib/tools/test/emacs_SUITE.erl |  23 +-
 3 files changed, 393 insertions(+), 201 deletions(-)

(limited to 'lib')

diff --git a/lib/tools/emacs/erlang-test.el b/lib/tools/emacs/erlang-test.el
index 2ee584d11a..fbdd298da3 100644
--- a/lib/tools/emacs/erlang-test.el
+++ b/lib/tools/emacs/erlang-test.el
@@ -50,8 +50,15 @@
 ;; The -L option adds a directory to the load-path.  It should be the
 ;; directory containing erlang.el and erlang-test.el.
 ;;
-;; 3. Call the script test-erlang-mode in this directory.  This script
-;; use the second method.
+;; 3. Run the emacs_SUITE.  The testcases tests_interpreted/1 and
+;; tests_compiled/1 in this suite are using the second method.  One
+;; way to run this suite is with the ct_run tool, for example like the
+;; following when standing at the OTP repo top directory:
+;;
+;; ct_run -suite lib/tools/test/emacs_SUITE
+;;
+;; Note that this creates a lot of html log files in the current
+;; directory.
 
 ;;; Code:
 
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index 38c0eba92b..0b3a2319e2 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -4,7 +4,7 @@
 ;; Author:   Anders Lindgren
 ;; Keywords: erlang, languages, processes
 ;; Date:     2011-12-11
-;; Version:  2.8.1
+;; Version:  2.8.2
 ;; Package-Requires: ((emacs "24.1"))
 
 ;; %CopyrightBegin%
@@ -87,7 +87,7 @@
   "The Erlang programming language."
   :group 'languages)
 
-(defconst erlang-version "2.8.1"
+(defconst erlang-version "2.8.2"
   "The version number of Erlang mode.")
 
 (defcustom erlang-root-dir nil
@@ -502,6 +502,13 @@ regardless of where in the line point is when the TAB command is used."
   :type 'boolean
   :safe 'booleanp)
 
+(defcustom erlang-max-files-to-visit-for-refining-xrefs 32
+  "Upper limit how many files to visit for checking arity.
+When `nil' there is no limit."
+  :group 'erlang
+  :type '(restricted-sexp :match-alternatives (integerp 'nil))
+  :safe (lambda (val) (or (eq val nil) (integerp val))))
+
 (defvar erlang-man-inhibit (eq system-type 'windows-nt)
   "Inhibit the creation of the Erlang Manual Pages menu.
 
@@ -3689,10 +3696,13 @@ When an identifier is found return a list with 4 elements:
 module or nil.
 
 2. Module - Module name string or nil.  In case of a
-qualified-function a search fails if no entries with correct
-module are found.  For other kinds the module is just a
-preference.  If no matching entries are found the search will be
-retried without regard to module.
+qualified-function the module is explicitly specified (like
+module:fun()) and the search fails if no entries with correct
+module are found.  For other kinds the module is guessed: either
+fetched from import statements or it is assumed to be the local
+module.  In these cases the module is just a preference.  If no
+matching entries are found the search will be retried without
+regard to module.
 
 3. Name - String name of function, module, record or macro.
 
@@ -3704,18 +3714,22 @@ of arguments could be found, otherwise nil."
         (if (eq (char-syntax (following-char)) ? )
             (skip-chars-backward " \t"))
         (skip-chars-backward "[:word:]_:'")
-        (cond ((looking-at erlang-module-function-regexp)
+        (cond ((and (eq (preceding-char) ??)
+                    (looking-at (concat "\\(MODULE\\):" erlang-atom-regexp)))
+               (erlang-get-qualified-function-id-at-point (erlang-get-module)))
+              ((looking-at erlang-module-function-regexp)
                (erlang-get-qualified-function-id-at-point))
               ((looking-at (concat erlang-atom-regexp ":"))
                (erlang-get-module-id-at-point))
               ((looking-at erlang-name-regexp)
                (erlang-get-some-other-id-at-point)))))))
 
-(defun erlang-get-qualified-function-id-at-point ()
+(defun erlang-get-qualified-function-id-at-point (&optional module)
   (let ((kind 'qualified-function)
-        (module (erlang-remove-quotes
-                 (buffer-substring-no-properties
-                  (match-beginning 1) (match-end 1))))
+        (module (or module
+                    (erlang-remove-quotes
+                     (buffer-substring-no-properties
+                      (match-beginning 1) (match-end 1)))))
         (name (erlang-remove-quotes
                (buffer-substring-no-properties
                 (match-beginning (1+ erlang-atom-regexp-matches))
@@ -3825,7 +3839,8 @@ of arguments could be found, otherwise nil."
   (let ((case-fold-search nil)) ; force string matching to be case sensitive
     (if (and (stringp str)
              (not (string-match (eval-when-compile
-                                  (concat "\\`" erlang-atom-regexp "\\'")) str)))
+                                  (concat "\\`" erlang-atom-regexp "\\'"))
+                                str)))
         (progn
           (setq str (replace-regexp-in-string "'" "\\'" str t t ))
           (concat "'" str "'"))
@@ -4879,15 +4894,36 @@ about Erlang modules."
 ;; The backend below is a wrapper around the built-in etags backend.
 ;; It adds awareness of the module:tag syntax in a similar way that is
 ;; done above for the old etags commands.
+;;
+;; In addition arity is also considered when jumping to definitions.
+;; There is however currently no information about arity in the TAGS
+;; file.  Also two functions with the same name but different arity
+;; _sometimes_ get one TAGS entry each and sometimes are joined in one
+;; single entry.  If they are directly consecutive they will be
+;; joined.  If there are other functions etc in between then they will
+;; get one entry each.
+;;
+;; These limitations are present in both the etags program shipped
+;; with GNU Emacs and the tags.erl program in this repository.
+;;
+;; Therefore erlang.el must complement the information in TAGS by
+;; visiting files and checking arity.  When searching for popular
+;; function names (like init, handle_call etc) in a big TAGS file
+;; (like one indexing this repository) this may be quite
+;; time-consuming.  There exists therefore an upper limit for the
+;; number of files to visit (called
+;; `erlang-max-files-to-visit-for-refining-xrefs').
+;;
+;; As mentioned this xref implementation is based on the etags xref
+;; implementation.  But in the cases where arity is considered the
+;; etags information structures (class xref-etags-location) will be
+;; translated to our own structures which include arity (class
+;; erlang-xref-location).  This translation is started in the function
+;; `erlang-refine-xrefs'.
 
-(defvar erlang-current-arity nil
-  "The arity of the function currently being searched.
-
-There is no information about arity in the TAGS file.
-Consecutive functions with same name but different arity will
-only get one entry in the TAGS file.  Matching TAGS entries are
-therefore selected without regarding arity.  The arity is
-considered first when it is time to jump to the definition.")
+;; I mention this as a head up that some of the functions below deal
+;; with xref items with xref-etags-location and some deal with xref
+;; items with erlang-xref-location.
 
 (defun erlang-etags--xref-backend () 'erlang-etags)
 
@@ -4895,127 +4931,80 @@ considered first when it is time to jump to the definition.")
   (when (locate-library (symbol-name feature))
     (require feature)))
 
-(and (erlang-soft-require 'xref)
-     (erlang-soft-require 'cl-generic)
-     (erlang-soft-require 'eieio)
-     (erlang-soft-require 'etags)
-     ;; The purpose of using eval here is to avoid compilation
-     ;; warnings in emacsen without cl-defmethod etc.
-     (eval
-      '(progn
-         (cl-defmethod xref-backend-identifier-at-point
-             ((_backend (eql erlang-etags)))
-           (if (eq this-command 'xref-find-references)
-               (if (use-region-p)
-                   (buffer-substring-no-properties (region-beginning)
-                                                   (region-end))
-                 (thing-at-point 'symbol))
-             (erlang-id-to-string (erlang-get-identifier-at-point))))
-
-         (cl-defmethod xref-backend-definitions
-             ((_backend (eql erlang-etags)) identifier)
-           (erlang-xref-find-definitions identifier))
-
-         (cl-defmethod xref-backend-apropos
-             ((_backend (eql erlang-etags)) identifier)
-           (erlang-xref-find-definitions identifier t))
-
-         (cl-defmethod xref-backend-identifier-completion-table
-             ((_backend (eql erlang-etags)))
-           (let ((erlang-replace-etags-tags-completion-table t))
-             (tags-completion-table)))
-
-         (defclass erlang-xref-location (xref-etags-location) ())
-
-         (defun erlang-convert-xrefs (xrefs)
-           (mapcar (lambda (xref)
-                     (oset xref location (erlang-make-location
-                                          (oref xref location)))
-                     xref)
-                   xrefs))
-
-         (defun erlang-make-location (etags-location)
-           (with-slots (tag-info file) etags-location
-             (make-instance 'erlang-xref-location :tag-info tag-info
-                            :file file)))
-
-         (cl-defmethod xref-location-marker ((locus erlang-xref-location))
-           (with-slots (tag-info file) locus
-             (with-current-buffer (find-file-noselect file)
-               (save-excursion
-                 (or (erlang-goto-tag-location-by-arity tag-info)
-                     (etags-goto-tag-location tag-info))
-                 ;; Reset erlang-current-arity.  We want to jump to
-                 ;; correct arity in the first attempt.  That is now
-                 ;; done.  Possible remaining jumps will be from
-                 ;; entries in the *xref* buffer and then we want to
-                 ;; ignore the arity.  (Alternatively we could remove
-                 ;; all but one xref entry per file when we know the
-                 ;; arity).
-                 (setq erlang-current-arity nil)
-                 (point-marker)))))
-
-         (defun erlang-xref-context (xref)
-           (with-slots (tag-info) (xref-item-location xref)
-             (car tag-info))))))
-
-
-(defun erlang-goto-tag-location-by-arity (tag-info)
-  (when erlang-current-arity
-    (let* ((tag-text (car tag-info))
-           (tag-pos (cdr (cdr tag-info)))
-           (tag-line (car (cdr tag-info)))
-           (regexp (erlang-tag-info-regexp tag-text))
-           (startpos (or tag-pos
-                         (when tag-line
-                           (goto-char (point-min))
-                           (forward-line (1- tag-line))
-                           (point))
-                         (point-min))))
-      (setq startpos (max (- startpos 2000)
-                          (point-min)))
-      (goto-char startpos)
-      (let ((pos (or (erlang-search-by-arity regexp)
-                     (unless (eq startpos (point-min))
-                       (goto-char (point-min))
-                       (erlang-search-by-arity regexp)))))
-        (when pos
-          (goto-char pos)
-          t)))))
-
-(defun erlang-tag-info-regexp (tag-text)
-  (concat "^"
-          (regexp-quote tag-text)
-          ;; Erlang function entries in TAGS includes the opening
-          ;; parenthesis for the argument list.  Erlang macro entries
-          ;; do not.  Add it here in order to end up in correct
-          ;; position for erlang-get-arity.
-          (if (string-prefix-p "-define" tag-text)
-              "\\s-*("
-            "")))
-
-(defun erlang-search-by-arity (regexp)
-  (let (pos)
-    (while (and (null pos)
-                (re-search-forward regexp nil t))
-      (when (eq erlang-current-arity (save-excursion (erlang-get-arity)))
-        (setq pos (point-at-bol))))
-    pos))
-
-
+(when (and (erlang-soft-require 'xref)
+           (erlang-soft-require 'cl-generic)
+           (erlang-soft-require 'eieio)
+           (erlang-soft-require 'etags))
+  ;; The purpose of using eval here is to avoid compilation
+  ;; warnings in emacsen without cl-defmethod etc.
+  (eval
+   '(progn
+      (cl-defmethod xref-backend-identifier-at-point ((_backend
+                                                       (eql erlang-etags)))
+        (if (eq this-command 'xref-find-references)
+            (if (use-region-p)
+                (buffer-substring-no-properties (region-beginning)
+                                                (region-end))
+              (thing-at-point 'symbol))
+          (erlang-id-to-string (erlang-get-identifier-at-point))))
+
+      (cl-defmethod xref-backend-definitions ((_backend (eql erlang-etags))
+                                              identifier)
+        (erlang-xref-find-definitions identifier))
+
+      (cl-defmethod xref-backend-apropos ((_backend (eql erlang-etags))
+                                          identifier)
+        (erlang-xref-find-definitions identifier t))
+
+      (cl-defmethod xref-backend-identifier-completion-table
+        ((_backend (eql erlang-etags)))
+        (let ((erlang-replace-etags-tags-completion-table t))
+          (tags-completion-table)))
+
+      (defclass erlang-xref-location (xref-file-location)
+        ((arity :type fixnum :initarg :arity
+                :reader erlang-xref-location-arity))
+        :documentation "An erlang location is a file location plus arity.")
+
+      ;; This method definition only calls the superclass which is
+      ;; the default behaviour if it was not defined.  It is only
+      ;; needed for "upgrade" purposes.  In version 2.8.1 of
+      ;; erlang.el this method was defined differently and in case
+      ;; user switch to a new erlang.el without restarting Emacs
+      ;; this method needs to be redefined.
+      (cl-defmethod xref-location-marker ((locus erlang-xref-location))
+        (cl-call-next-method locus)))))
+
+;; If this function returns a single xref the user will jump to that
+;; directly.  If two or more xrefs are returned a *xref* window is
+;; displayed and the user can choose where to jump.  Hence we want to
+;; return a single xref when we are pretty sure that is where the user
+;; wants to go.  Otherwise return all possible xrefs but sort them so
+;; that xrefs in the local file is first and if arity is known sort
+;; the xrefs with matching arity before others.
+
+;; Note that the arity sorting work may partly be undone later when
+;; the hits are presented in the *xref* buffer since they then will be
+;; grouped together by file.  Ie when one file have one hit with
+;; correct arity and others with wrong arity these hits will be
+;; grouped together and may end up before hits with correct arity.
 (defun erlang-xref-find-definitions (identifier &optional is-regexp)
   (erlang-with-id (kind module name arity) identifier
-    (setq erlang-current-arity arity)
     (cond ((eq kind 'module)
            (erlang-xref-find-definitions-module name))
+          ((eq kind 'qualified-function)
+           (erlang-xref-find-definitions-qualified-function module
+                                                            name
+                                                            arity
+                                                            is-regexp))
           (module
-           (erlang-xref-find-definitions-module-tag module
+           (erlang-xref-find-definitions-module-tag kind
+                                                    module
                                                     name
-                                                    (eq kind
-                                                        'qualified-function)
+                                                    arity
                                                     is-regexp))
           (t
-           (erlang-xref-find-definitions-tag kind name is-regexp)))))
+           (erlang-xref-find-definitions-tag kind name arity is-regexp)))))
 
 (defun erlang-xref-find-definitions-module (module)
   (and (fboundp 'xref-make)
@@ -5040,65 +5029,252 @@ considered first when it is time to jump to the definition.")
                  (setq files (cdr files))))))
          (nreverse xrefs))))
 
-(defun erlang-visit-tags-table-buffer (cont cbuf)
-  (if (< emacs-major-version 26)
-      (visit-tags-table-buffer cont)
-    ;; Remove this with-no-warnings when Emacs 26 is the required
-    ;; version minimum.
-    (with-no-warnings
-      (visit-tags-table-buffer cont cbuf))))
-
-(defun erlang-xref-find-definitions-module-tag (module
+(defun erlang-xref-find-definitions-qualified-function (module
+                                                        tag
+                                                        arity
+                                                        is-regexp)
+  "Find definitions of TAG in MODULE preferably with arity ARITY.
+If one single perfect match was found return only that (ignoring
+other definitions matching TAG).  If IS-REGEXP is non-nil then
+TAG is a regexp."
+  (let* ((xrefs (when (fboundp 'etags--xref-find-definitions)
+                  (etags--xref-find-definitions tag is-regexp)))
+         (xrefs-split (erlang-split-xrefs-on-module xrefs module))
+         (module-xrefs (car xrefs-split))
+         (module-xrefs (erlang-refine-xrefs module-xrefs
+                                            'qualified-function
+                                            tag
+                                            is-regexp)))
+    (or (erlang-single-arity-match module-xrefs arity)
+        (erlang-sort-by-arity module-xrefs arity))))
+
+
+;; We will end up here when erlang-get-some-other-id-at-point either
+;; found module among the import statements or module is just the
+;; current local file.
+(defun erlang-xref-find-definitions-module-tag (kind
+                                                module
                                                 tag
-                                                is-qualified
+                                                arity
                                                 is-regexp)
-  "Find definitions of TAG and filter away definitions outside of
-MODULE.  If IS-QUALIFIED is nil and no definitions was found inside
-the MODULE then return any definitions found outside.  If
-IS-REGEXP is non-nil then TAG is a regexp."
-  (and (fboundp 'etags--xref-find-definitions)
-       (fboundp 'erlang-convert-xrefs)
-       (let ((xrefs (erlang-convert-xrefs
-                     (etags--xref-find-definitions tag is-regexp)))
-             xrefs-in-module)
-         (dolist (xref xrefs)
-           (when (string-equal module (erlang-xref-module xref))
-             (push xref xrefs-in-module)))
-         (cond (is-qualified xrefs-in-module)
-               (xrefs-in-module xrefs-in-module)
-               (t xrefs)))))
-
-(defun erlang-xref-find-definitions-tag (kind tag is-regexp)
-  "Find all definitions of TAG and reorder them so that
-definitions in the currently visited file comes first."
-  (and (fboundp 'etags--xref-find-definitions)
-       (fboundp 'erlang-convert-xrefs)
-       (let* ((current-file (and (buffer-file-name)
-                                 (file-truename (buffer-file-name))))
-              (regexp (erlang-etags-regexp kind tag is-regexp))
-              (xrefs (erlang-convert-xrefs
-                      (etags--xref-find-definitions regexp t)))
-              local-xrefs non-local-xrefs)
-         (while xrefs
-           (let ((xref (car xrefs)))
-             (if (string-equal (erlang-xref-truename-file xref)
-                               current-file)
-                 (push xref local-xrefs)
-               (push xref non-local-xrefs))
-             (setq xrefs (cdr xrefs))))
-         (append (reverse local-xrefs)
-                 (reverse non-local-xrefs)))))
+  "Find definitions of TAG preferably in MODULE and with arity ARITY.
+Return definitions outside MODULE if none are found inside.  If
+IS-REGEXP is non-nil then TAG is a regexp.
+
+If one single perfect match was found return only that (ignoring
+other definitions matching TAG)."
+  (let* ((xrefs (when (fboundp 'etags--xref-find-definitions)
+                  (etags--xref-find-definitions tag is-regexp)))
+         (xrefs-split (erlang-split-xrefs-on-module xrefs module))
+         (module-xrefs (car xrefs-split))
+         (module-xrefs (erlang-refine-xrefs module-xrefs
+                                            kind
+                                            tag
+                                            is-regexp)))
+    (or (erlang-single-arity-match module-xrefs arity)
+        (erlang-xref-find-definitions-tag kind tag arity is-regexp xrefs))))
+
+(defun erlang-xref-find-definitions-tag (kind
+                                         tag
+                                         arity
+                                         is-regexp
+                                         &optional xrefs)
+  "Find definitions of TAG preferably in local file and with arity ARITY.
+If one single perfect match was found return only that (ignoring
+other definitions matching TAG).  If no such local match was
+found then look for a matching BIF in the same way.  If IS-REGEXP
+is non-nil then TAG is a regexp."
+  (let* ((regexp (erlang-etags-regexp kind tag is-regexp))
+         (xrefs (or xrefs
+                    (when (fboundp 'etags--xref-find-definitions)
+                      (etags--xref-find-definitions regexp t))))
+         (xrefs-split (erlang-split-xrefs xrefs))
+         (local-xrefs (car xrefs-split))
+         (local-xrefs (erlang-refine-xrefs local-xrefs
+                                           kind
+                                           tag
+                                           is-regexp))
+         (bif-xrefs (cadr xrefs-split))
+         (other-xrefs (caddr xrefs-split)))
+    (or (erlang-single-arity-match local-xrefs arity)
+        ;; No local match, look for a matching BIF.
+        (progn
+          (setq bif-xrefs (erlang-refine-xrefs bif-xrefs
+                                               kind
+                                               tag
+                                               is-regexp))
+          (erlang-single-arity-match bif-xrefs arity))
+        (progn
+          (setq other-xrefs (erlang-refine-xrefs other-xrefs
+                                                 kind
+                                                 tag
+                                                 is-regexp))
+          (and (null local-xrefs)
+               (null bif-xrefs)
+               ;; No local of BIF matches at all.  Is there a single
+               ;; arity match among the rest?
+               (erlang-single-arity-match other-xrefs arity)))
+        (append (erlang-sort-by-arity local-xrefs arity)
+                (erlang-sort-by-arity bif-xrefs arity)
+                (erlang-sort-by-arity other-xrefs arity)))))
+
+
+(defun erlang-refine-xrefs (xrefs kind tag is-regexp)
+  (if (or (memq kind '(record module))
+          ;; No support for apropos here.
+          is-regexp
+          (erlang-too-many-files-in-xrefs xrefs))
+      xrefs
+    (when (and xrefs
+               (fboundp 'xref-item-location)
+               (fboundp 'xref-location-group)
+               (fboundp 'slot-value))
+      (let (files)
+        (cl-loop for xref in xrefs
+                 for loc = (xref-item-location xref)
+                 for file = (xref-location-group loc)
+                 do (pushnew file files :test 'string-equal))
+        (or (cl-loop for file in files
+                     append (erlang-xrefs-in-file file kind tag is-regexp))
+            ;; Failed for some reason.  Pretend like it is raining and
+            ;; return the unrefined xrefs.
+            xrefs)))))
+
+(defun erlang-too-many-files-in-xrefs (xrefs)
+  (and erlang-max-files-to-visit-for-refining-xrefs
+       (let ((files-to-visit (delete-dups
+                              (mapcar #'erlang-xref-truename-file
+                                      xrefs))))
+         (if (< (length files-to-visit)
+                erlang-max-files-to-visit-for-refining-xrefs)
+             nil
+           (message (concat "Too many hits to consider arity (see "
+                            "`erlang-max-files-to-visit-for-refining-xrefs')"))
+           t))))
+
+(defun erlang-xrefs-in-file (file kind tag is-regexp)
+  (when (fboundp 'make-instance)
+    (with-current-buffer (find-file-noselect file)
+      (save-excursion
+        (goto-char (point-min))
+        (let ((regexp (concat ; "^"
+                              (erlang-etags-regexp kind tag is-regexp)
+                              "\\s *("))
+              last-arity)
+          (cl-loop while (re-search-forward regexp nil t)
+                   for name = (match-string-no-properties 1)
+                   for arity = (save-excursion
+                                 (erlang-get-arity))
+                   for loc = (make-instance 'erlang-xref-location
+                                            :file file
+                                            :line (line-number-at-pos)
+                                            :column 0
+                                            :arity arity)
+                   for sum = (erlang-xref-summary kind name arity)
+                   when (and arity
+                             (not (eq arity last-arity)))
+                   collect (make-instance 'xref-item
+                                          :summary sum
+                                          :location loc)
+                   do (setq last-arity arity)))))))
+
+(defun erlang-xref-summary (kind tag arity)
+  (format "%s%s%s"
+          (if (memq kind '(record macro module))
+              (format "%s " kind)
+            "")
+          tag
+          (if arity (format "/%s" arity) "")))
+
+(defun erlang-single-arity-match (xrefs wanted-arity)
+  "Attempt to find one perfect match.
+
+If we have all information needed to consider arity then return a
+single perfect match or nothing.  If there are more than one
+match nothing is returned.
+
+If we don't have all information needed to consider arity just
+return XREFS as is."
+  (if (erlang-should-consider-arity-p xrefs wanted-arity)
+      (let ((nr-matches 0)
+            match)
+        (while (and xrefs
+                    (< nr-matches 2))
+          (let* ((xref (car xrefs))
+                 (arity (erlang-xref-arity xref)))
+            (when (eq arity wanted-arity)
+              (setq match xref
+                    nr-matches (1+ nr-matches)))
+            (setq xrefs (cdr xrefs))))
+        (when (eq nr-matches 1)
+          (list match)))
+    (when (eq (length xrefs) 1)
+      xrefs)))
+
+(defun erlang-sort-by-arity (xrefs wanted-arity)
+  (if (erlang-should-consider-arity-p xrefs wanted-arity)
+      (let (matches non-matches)
+        (while xrefs
+          (let* ((xref (car xrefs))
+                 (arity (erlang-xref-arity xref)))
+            (push xref (if (eq arity wanted-arity)
+                           matches
+                         non-matches))
+            (setq xrefs (cdr xrefs))))
+        (append (reverse matches) (reverse non-matches) xrefs))
+    xrefs))
+
+(defun erlang-should-consider-arity-p (xrefs wanted-arity)
+  (and wanted-arity
+       xrefs
+       (fboundp 'erlang-xref-location-p)
+       (fboundp 'xref-item-location)
+       (erlang-xref-location-p (xref-item-location (car xrefs)))))
 
 (defun erlang-etags-regexp (kind tag is-regexp)
-  (let ((tag-regexp (if is-regexp
-                        tag
-                      (regexp-quote tag))))
-    (cond ((eq kind 'record)
-           (concat "-record\\s-*(\\s-*" tag-regexp))
-          ((eq kind 'macro)
-           (concat "-define\\s-*(\\s-*" tag-regexp))
-          (t tag-regexp))))
-
+  (let ((tag-regexp (concat "\\("
+                            (if is-regexp
+                                tag
+                              (regexp-quote tag))
+                            "\\)")))
+    (concat (if is-regexp "" "^")
+            (cond ((eq kind 'record)
+                   (concat "-record\\s-*(\\s-*" tag-regexp))
+                  ((eq kind 'macro)
+                   (concat "-define\\s-*(\\s-*" tag-regexp))
+                  (t
+                   tag-regexp))
+            (if is-regexp "" "\\_>"))))
+
+(defun erlang-xref-arity (xref)
+  (and (fboundp 'erlang-xref-location-arity)
+       (fboundp 'xref-item-location)
+       (erlang-xref-location-arity (xref-item-location xref))))
+
+(defun erlang-split-xrefs-on-module (xrefs module)
+  (let (local-xrefs non-local-xrefs)
+    (dolist (xref xrefs)
+      (if (string-equal (erlang-xref-module xref)
+                        module)
+          (push xref local-xrefs)
+        (push xref non-local-xrefs)))
+    (cons (reverse local-xrefs)
+          (reverse non-local-xrefs))))
+
+(defun erlang-split-xrefs (xrefs)
+  (let ((current-file (and (buffer-file-name)
+                           (file-truename (buffer-file-name))))
+        local-xrefs bif-xrefs other-xrefs)
+    (dolist (xref xrefs)
+      (cond ((string-equal (erlang-xref-truename-file xref) current-file)
+             (push xref local-xrefs))
+            ((string-equal (erlang-xref-module xref) "erlang")
+             (push xref bif-xrefs))
+            (t
+             (push xref other-xrefs))))
+    (list (reverse local-xrefs)
+          (reverse bif-xrefs)
+          (reverse other-xrefs))))
 
 (defun erlang-xref-module (xref)
   (erlang-get-module-from-file-name (erlang-xref-file xref)))
@@ -5113,7 +5289,13 @@ definitions in the currently visited file comes first."
        (fboundp 'xref-item-location)
        (xref-location-group (xref-item-location xref))))
 
-
+(defun erlang-visit-tags-table-buffer (cont cbuf)
+  (if (< emacs-major-version 26)
+      (visit-tags-table-buffer cont)
+    ;; Remove this with-no-warnings when Emacs 26 is the required
+    ;; version minimum.
+    (with-no-warnings
+      (visit-tags-table-buffer cont cbuf))))
 
 ;;;
 ;;; Prepare for other methods to run an Erlang slave process.
diff --git a/lib/tools/test/emacs_SUITE.erl b/lib/tools/test/emacs_SUITE.erl
index a6d43d1816..8756a4e9b3 100644
--- a/lib/tools/test/emacs_SUITE.erl
+++ b/lib/tools/test/emacs_SUITE.erl
@@ -70,19 +70,20 @@ bif_highlight(Config) ->
 
 
 check_bif_highlight(Bin, Tag, Compare) ->
-    [_H,IntMatch,_T] =
+    [_H,Match,_T] =
         re:split(Bin,<<"defvar ",Tag/binary,
                        "[^(]*\\(([^)]*)">>,[]),
-    EmacsIntBifs = [list_to_atom(S) ||
-                  S <- string:tokens(binary_to_list(IntMatch)," '\"\n")],
+    EmacsBifs = [list_to_atom(S) ||
+                  S <- string:tokens(binary_to_list(Match)," '\"\n")],
 
-    ct:log("Emacs ~p",[EmacsIntBifs]),
-    ct:log("Int ~p",[Compare]),
+    ct:log("Comparing ~s", [Tag]),
+    ct:log("Emacs ~p",[EmacsBifs]),
+    ct:log("Erlang ~p",[Compare]),
 
-    ct:log("Diff1 ~p",[Compare -- EmacsIntBifs]),
-    ct:log("Diff2 ~p",[EmacsIntBifs -- Compare]),
-    [] = Compare -- EmacsIntBifs,
-    [] = EmacsIntBifs -- Compare.
+    ct:log("Only in Erlang ~p",[Compare -- EmacsBifs]),
+    ct:log("Only in Emacs ~p",[EmacsBifs -- Compare]),
+    [] = Compare -- EmacsBifs,
+    [] = EmacsBifs -- Compare.
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
@@ -188,7 +189,9 @@ diff(Orig, File) ->
     end.
 
 emacs_version_ok(AcceptVer) ->
-    case os:cmd("emacs --version | head -1") of
+    VersionLine = os:cmd("emacs --version | head -1"),
+    io:format("~s~n", [VersionLine]),
+    case VersionLine of
         "GNU Emacs " ++ Ver ->
             case string:to_float(Ver) of
                 {Vsn, _} when Vsn >= AcceptVer ->
-- 
cgit v1.2.3