emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/rec-mode f35bf065e8 1/3: Refactoring and xref support.


From: ELPA Syncer
Subject: [elpa] externals/rec-mode f35bf065e8 1/3: Refactoring and xref support. Docstring fixes.
Date: Mon, 20 Jun 2022 03:57:53 -0400 (EDT)

branch: externals/rec-mode
commit f35bf065e8293d0dc120d2824d61382f5454f1f3
Author: Antoine Kalmbach <ane@iki.fi>
Commit: Antoine Kalmbach <ane@iki.fi>

    Refactoring and xref support. Docstring fixes.
    
    * rec-mode.el: Update year to 2022.
    cl-seq is now comptime required.
    (rec-mode-map): Custom xref forward/back commands.
    (rec-parse-comment): Ditch EIEIO. Use cl-defstruct for
    speed and performance reasons. We don't need classes,
    for plain generics structs are fine, classes become useful
    when doing metaclass stuff.
    (rec-parse-field): Ditto.
    (rec-parse-record): Ditto.
    (rec-comment): Ditto.
    (rec-field): Ditto.
    (rec-narrow-record): Ditto.  Also return nil when the record
    cannot be narrowed to a descriptor.
    (rec-cmd-xref-go-back): Custom jump widens before jumping back.
    (rec-cmd-xref-go-forward): Vice versa, but forward.
    (xref-backend-references): Support XREF with back-references.
    (xref-backend-definitions): Support goto definition with foreign keys.
    (rec--xref-summary-for-record): Improve summary generation.
---
 rec-mode.el | 609 +++++++++++++++++++++++++++++++++++-------------------------
 1 file changed, 357 insertions(+), 252 deletions(-)

diff --git a/rec-mode.el b/rec-mode.el
index e13e298f31..03fca1e2a5 100644
--- a/rec-mode.el
+++ b/rec-mode.el
@@ -1,6 +1,6 @@
 ;;; rec-mode.el --- Major mode for viewing/editing rec files  -*- 
lexical-binding: t; -*-
 
-;; Copyright (C) 2009-2021  Free Software Foundation, Inc.
+;; Copyright (C) 2009-2022  Free Software Foundation, Inc.
 
 ;; Author: Jose E. Marchesi <jemarch@gnu.org>
 ;; Maintainer: Antoine Kalmbach <ane@iki.fi>
@@ -45,12 +45,14 @@
 
 (require 'compile)
 (eval-when-compile (require 'cl-lib))
+(eval-when-compile (require 'cl-seq))
 (require 'calendar)
 (require 'hl-line)
 (require 'tabulated-list)
 (eval-when-compile (require 'subr-x))
 (require 'seq)
 (require 'eieio)
+(require 'xref)
 
 ;;;; Customization
 
@@ -90,17 +92,20 @@ The default is t."
 ;;;; Faces and variables
 
 (defvar rec-max-lines-in-fields 15
-  "Values of fields having more than the specified lines will be hidden by 
default in navigation mode.")
+  "Truncate displaying lines exceeding this limit.
+
+Values of fields having more than the specified lines will be
+hidden by default in navigation mode.")
 (put 'rec-max-lines-in-fields 'safe-local-variable 'numberp)
 
 (defvar rec-recsel "recsel"
-  "Name of the 'recsel' utility from the GNU recutils.")
+  "Name of the `recsel' utility from the GNU recutils.")
 
 (defvar rec-recinf "recinf"
-  "Name of the 'recinf' utility from the GNU recutils.")
+  "Name of the `recinf' utility from the GNU recutils.")
 
 (defvar rec-recfix "recfix"
-  "Name of the 'recfix' utility from the GNU recutils.")
+  "Name of the `recfix' utility from the GNU recutils.")
 
 (defface rec-field-name-face '((t :inherit font-lock-variable-name-face))
   "Face for field names in record entries.")
@@ -281,6 +286,8 @@ The default is t."
     (define-key map (kbd "TAB") 'rec-cmd-goto-next-field)
     (define-key map (kbd "SPC") 'rec-cmd-toggle-field-visibility)
     (define-key map (kbd "b") 'rec-cmd-jump-back)
+    (define-key map [remap xref-go-back] 'rec-cmd-xref-go-back)
+    (define-key map [remap xref-go-forward] 'rec-cmd-xref-go-forward)
     map)
   "Keymap for `rec-mode'.")
 
@@ -293,7 +300,7 @@ The default is t."
 (defun rec-parse-comment ()
   "Parse and return a comment starting at point.
 
-Return a list whose first element is the symbol 'comment and the
+Return a list whose first element is the symbol \\='comment and the
 second element is the string with the contents of the comment,
 including the leading #:
 
@@ -302,9 +309,9 @@ including the leading #:
 If the point is not at the beginning of a comment then return nil"
   (when (and (equal (current-column) 0)
              (looking-at rec-comment-re))
-    (let ((comment (rec-comment :position (point)
-                                :value (buffer-substring-no-properties 
(match-beginning 0)
-                                                                       
(match-end 0)))))
+    (let ((comment (make-rec-comment :position (point)
+                                     :value (buffer-substring-no-properties 
(match-beginning 0)
+                                                                            
(match-end 0)))))
       (goto-char (match-end 0))
       ;; Skip a newline if needed
       (when (eolp) (forward-line 1))
@@ -346,7 +353,7 @@ nil"
       val)))
 
 (defun rec-parse-field ()
-  "Return a `rec-field' describing the field starting from the pointer.
+  "Return a field struct describing the field starting from the pointer.
 
 If the pointer is not at the beginning of a field descriptor then
 return nil."
@@ -356,105 +363,64 @@ return nil."
                (setq field-value (rec-parse-field-value)))
       ;; Skip a newline if needed
       (when (looking-at "\n") (goto-char (match-end 0)))
-      (rec-field :position there
-                 :name field-name
-                 :value field-value))))
+      (make-rec-field :position there
+                      :name field-name
+                      :value field-value))))
 
 (defun rec-parse-record ()
   "Return a structure describing the record starting from the pointer.
 
-The returned structure is a list of fields preceded by the symbol
-'record':
-
-   (record POSITION (FIELD-1 FIELD-2 ... FIELD-N))
+Returns either an object `rec-record' or `rec-record-descriptor' depending
+whether the current record is a plain record or a record
+descriptor.
 
 If the pointer is not at the beginning of a record, then return
-nil"
+nil."
   (let ((there (point))
         (fields ()) field-or-comment)
     (while (setq field-or-comment (or (rec-parse-field)
                                       (rec-parse-comment)))
       (push field-or-comment fields))
-    
-    (let* ((record (rec-record :position there
-                               :fields (reverse fields))))
-      (or (rec-record-to-descriptor record) record))))
+
+    (let ((record (rec-make-record there (reverse fields))))
+      (or (rec-narrow-record record) record))))
 
 
 ;;;; Operations on record structures
 ;;
 ;; Those functions retrieve or set properties of field structures.
 
-(defclass rec-record ()
-  ((position :initarg :position
-             :documentation "The position of the record in the recfile.")
-   (fields :initarg :fields
-           :documentation "The fields of the record."))
-  "A recfile record.")
-
-(defclass rec-record-descriptor (rec-record)
-  ((type :initarg :type
-         :documentation "The type described by the descriptor.")
-   (key :initarg :key
-        :initform nil
-        :documentation "The key field of the descriptor.")
-   (auto :initarg :auto
-         :initform nil
-         :documentation "The %auto field of the descriptor.")
-   (doc :initarg :doc
-        :initform ""
-        :documentation "The descriptor's %doc field."))
-  "A record descriptor.")
-
-(defclass rec-record-element ()
-  ((position :initarg :position)
-   (value :initarg :value))
-  "A record element, either a comment or field.")
-
-(cl-defgeneric rec-element-position (element)
-  "Return the position of ELEMENT.")
-
-(cl-defgeneric rec-element-value (element)
-  "Return the value of ELEMENT.")
-
-(cl-defmethod rec-element-position ((element rec-record-element))
-  "Return the position of ELEMENT."
-  (slot-value element 'position))
-
-(cl-defmethod rec-element-value ((element rec-record-element))
-  "Return the value of ELEMENT."
-  (slot-value element 'value))
-
-(defclass rec-comment (rec-record-element) ()
-  "A record comment.")
+(cl-defstruct (rec-record
+               (:constructor rec-make-record (position fields)))
+  "A record."
+  position fields)
 
-(defclass rec-field (rec-record-element)
-  ((name :initarg :name)))
+(cl-defstruct (rec-record-descriptor (:include rec-record))
+  "A record descriptor."
+  type types key auto doc)
 
-(defun rec-field-name (field)
-  (when (rec-field-p field)
-    (slot-value field 'name)))
+(cl-defstruct rec-record-element
+  "A record element, either a comment or a field."
+  position value)
 
-(defun rec-field-position (field)
-  (when (rec-field-p field)
-    (rec-element-position field)))
+(cl-defstruct (rec-comment (:include rec-record-element))
+  "A record comment.")
 
-(defun rec-field-value (field)
-  (when (rec-field-p field)
-    (rec-element-value field)))
+(cl-defstruct (rec-field (:include rec-record-element))
+  name)
 
 (defun rec-map-fields (fun record)
   "Map function FUN over the fields in RECORD."
-  (cl-loop for field in (slot-value record 'fields)
+  (cl-loop for field in (rec-record-fields record)
            when (rec-field-p field)
            collect (funcall fun field)))
 
 (cl-defmethod rec-record-assoc (name (record rec-record))
   "Get a list with the values of the fields in RECORD named NAME.
 
-NAME shall be a field name.
-If no such field exists in RECORD then nil is returned."
-  (cl-loop for field in (slot-value record 'fields)
+NAME shall be a field name.  If no such field exists in RECORD
+then nil is returned."
+  (cl-loop for field in (rec-record-fields record)
            when (and (rec-field-p field)
                      (equal name (rec-field-name field)))
            collect (rec-field-value field)))
@@ -464,7 +430,7 @@ If no such field exists in RECORD then nil is returned."
 
 (cl-defmethod rec-record-names ((record rec-record))
   "Get a list of the field names in the RECORD."
-  (cl-loop for field in (slot-value record 'fields)
+  (cl-loop for field in (rec-record-fields record)
            when (rec-field-p field)
            collect (rec-field-name field)))
 
@@ -485,7 +451,7 @@ If no such field exists in RECORD then nil is returned."
 
 (cl-defmethod rec-insert ((comment rec-comment))
   "Insert the written form of COMMENT in the current buffer."
-  (insert (rec-element-value comment) "\n"))
+  (insert (rec-record-element-value comment) "\n"))
 
 (defun rec-insert-field-name (field-name)
   "Insert the written form of FIELD-NAME in the current buffer."
@@ -503,15 +469,14 @@ If no such field exists in RECORD then nil is returned."
 
 (cl-defmethod rec-insert ((field rec-field))
   "Insert the written form of FIELD in the current buffer."
-  (with-slots (name value) field
-    (when (rec-insert-field-name name)
-      (insert " ")
-      (rec-insert-field-value value))))
+  (when (rec-insert-field-name (rec-field-name field))
+    (insert " ")
+    (rec-insert-field-value (rec-field-value field))))
 
 (cl-defmethod rec-insert ((record rec-record))
   "Insert the written form of RECORD in the current buffer."
-  (mapc #'rec-insert (slot-value record 'fields)))
-4
+  (mapc #'rec-insert (rec-record-fields record)))
+
 ;;;; Operations on field structures
 ;;
 ;; Those functions retrieve or set properties of field structures.
@@ -545,7 +510,9 @@ If no such field exists in RECORD then nil is returned."
 ;; under the pointer then nil is returned.
 
 (defun rec-beginning-of-field-pos ()
-  "Return the position of the beginning of the current field, or nil if the 
pointer is not on a field."
+  "Return the position of the beginning of the current field.
+
+Return nil if the pointer is not on a field."
   (save-excursion
     (beginning-of-line)
     (let (res)
@@ -563,7 +530,9 @@ If no such field exists in RECORD then nil is returned."
       res)))
 
 (defun rec-end-of-field-pos ()
-  "Return the position of the end of the current field, or nil if the pointer 
is not on a field."
+  "Return the position of the end of the current field.
+
+Return nil if the pointer is not on a field."
   (let ((begin-pos (rec-beginning-of-field-pos)))
     (when begin-pos
       (save-excursion
@@ -677,7 +646,7 @@ The current record is the record where the pointer is"
 (make-variable-buffer-local 'rec-buffer-descriptors)
 
 (defun rec-buffer-valid-p ()
-  "Determine whether the current buffer contains valid rec data."
+  "Determine if the current buffer has valid rec data."
   (equal (call-process-region (point-min) (point-max)
                               rec-recinf
                               nil ; delete
@@ -717,35 +686,32 @@ DONT-GO-FUNDAMENTAL is non-nil, don't switch to 
fundamental."
       (message (concat (buffer-name) ": " errmsg))
       nil)))
 
-(cl-defgeneric rec-record-to-descriptor (record)
-  "Try casting RECORD into a descriptor.")
 
-(cl-defmethod rec-record-to-descriptor ((record rec-record))
-  "Try casting RECORD into a descriptor."
-  (let ((type (car-safe (rec-record-assoc "%rec" record))))
-    (if type
-        (with-slots (position fields) record
-          (rec-record-descriptor :position position
-                                 :fields fields
-                                 :type type
-                                 :key (car-safe (rec-record-assoc "%key" 
record))
-                                 :auto (car-safe (rec-record-assoc "%auto" 
record))
-                                 :doc (car-safe (rec-record-assoc "%doc" 
record)))))))
+(defun rec-narrow-record (record)
+  "Try making a record descriptor out of RECORD.
 
-(cl-defmethod rec-record-to-descriptor ((_record rec-record-descriptor))
-  rec-record-descriptor)
+If the record is a descriptor, it will be an instance of
+`rec-record-descriptor', otherwise nil.  This judgment is based
+on the existence of the existence of the \"%rec\" field.  If a record
+has this field, it is a descriptor."
+  (when-let ((type (car-safe (rec-record-assoc "%rec" record))))
+    (make-rec-record-descriptor :position (rec-record-position record)
+                                :fields (rec-record-fields record)
+                                :type type
+                                :types (rec-record-assoc "%type" record)
+                                :key (car-safe (rec-record-assoc "%key" 
record))
+                                :auto (car-safe (rec-record-assoc "%auto" 
record))
+                                :doc (car-safe (rec-record-assoc "%doc" 
record)))))
 
 (defun rec--parse-sexp-records (records)
   "Parse a recinf sexp record in RECORDS."
   (cl-loop for (nil pos fields) in records
            for parsed-fields = (cl-loop for (nil pos name value) in fields
-                                        collect (rec-field :position pos
-                                                           :name name
-                                                           :value value))
-           for record = (rec-record :position pos
-                                    :fields parsed-fields)
-           collect (or (rec-record-to-descriptor record)
-                       record)))
+                                        collect (make-rec-field :position pos
+                                                                :name name
+                                                                :value value))
+           for record = (rec-make-record pos parsed-fields)
+           collect (or (rec-narrow-record record) record)))
 
 (defun rec-update-buffer-descriptors ()
   "Get a list of the record descriptors in the current buffer.
@@ -818,10 +784,9 @@ this function returns nil."
           (descriptors rec-buffer-descriptors))
       (mapc
        (lambda (elem)
-         (with-slots ((rec-type type) position) elem
-           (when (equal rec-type type)
-             (setq found t)
-             (goto-char position))))
+         (when (equal type (rec-record-descriptor-type elem))
+           (setq found t)
+           (goto-char (rec-record-descriptor-position elem))))
        descriptors)
       found)))
 
@@ -965,9 +930,8 @@ Return nil otherwise."
   "Return the type of the record under point.
 
 If the record is of no known type, return nil."
-  (let ((descriptor (rec-current-record-descriptor)))
-    (when (rec-record-descriptor-p descriptor)
-      (slot-value descriptor 'type))))
+  (when-let ((descriptor (rec-current-record-descriptor)))
+    (rec-record-descriptor-type descriptor)))
 
 (defun rec-current-record-descriptor ()
   "Return the record descriptor of the record under point.
@@ -984,9 +948,9 @@ Return nil if the point is not on a record."
              for curr in descriptors and
              next in next-descriptors
              
-             if (and (>= point (slot-value curr 'position))
+             if (and (>= point (rec-record-descriptor-position curr))
                      (or (= index (- count 1))
-                         (< point (slot-value next 'position))))
+                         (< point (rec-record-descriptor-position next))))
              
              return curr)))
 
@@ -1011,7 +975,7 @@ Return nil if the point is not on a record."
 
 Returns nil if no key is declared."
   (when-let ((descr (rec-current-record-descriptor)))
-    (slot-value descr 'key)))
+    (rec-record-descriptor-key descr)))
 
 ;;;; Navigation
 
@@ -1072,7 +1036,7 @@ descriptor record.  If nil, the descriptor is skipped."
                  (let ((ov (make-overlay (match-beginning 0) (match-end 0))))
                    (overlay-put ov 'display '(space . (:width 
rec-continuation-line-markers-width)))
                    (push ov rec-continuation-line-markers-overlays)))))))
-       (slot-value record 'fields)))))
+       (rec-record-fields record)))))
 
 (defun rec-remove-continuation-line-marker-overlays ()
   "Delete all the continuation line markers overlays."
@@ -1102,7 +1066,7 @@ can then be used to toggle the visibility."
                  (goto-char (rec-field-position field))
                  (rec-fold-field))
                t))))
-       (slot-value record 'fields)))))
+       (rec-record-fields record)))))
 
 (defun rec-field-folded-p ()
   "Return whether the current field is folded."
@@ -1152,7 +1116,7 @@ can then be used to toggle the visibility."
          (save-excursion
            (goto-char (rec-field-position field))
            (rec-unfold-field))))
-     (slot-value record 'fields))))
+     (rec-record-fields record))))
 
 (defun rec-toggle-field-visibility ()
   "Toggle the visibility of the current field."
@@ -1285,6 +1249,7 @@ manual."
             nil)))))))
 
 (defun rec-field-type (field-name)
+
   "Return the type of FIELD-NAME in determined in the current record set.
 
 If the field has no type, i.e. it is an unrestricted field which
@@ -1292,23 +1257,23 @@ can contain any text, then nil is returned."
   (let (res-type)
     (when-let ((descriptor (rec-current-record-descriptor))
                (types      (rec-record-assoc "%type" descriptor)))
-        ;; Note that invalid %type entries are simply ignored.
-        (mapc
-         (lambda (type-descr)
-           (with-temp-buffer
-             (insert type-descr)
-             (goto-char (point-min))
-             (when (looking-at "[ 
\n\t]*\\([a-zA-Z%][a-zA-Z0-9_-]*\\(,[a-zA-Z%][a-zA-Z0-9_-]*\\)?\\)[ \n\t]*")
-               (let (;; (names (match-string 1))
-                     (begin-description (match-end 0)))
-                 (goto-char (match-beginning 1))
-                 (while (looking-at "\\([a-zA-Z%][a-zA-Z0-9_]*\\),?")
-                   (if (equal (match-string 1) field-name)
-                       (progn
-                         (goto-char begin-description)
-                         (setq res-type (rec-parse-type (buffer-substring 
(point) (point-max)))))
-                     (goto-char (match-end 0))))))))
-         types))
+      ;; Note that invalid %type entries are simply ignored.
+      (mapc
+       (lambda (type-descr)
+         (with-temp-buffer
+           (insert type-descr)
+           (goto-char (point-min))
+           (when (looking-at "[ 
\n\t]*\\([a-zA-Z%][a-zA-Z0-9_-]*\\(,[a-zA-Z%][a-zA-Z0-9_-]*\\)?\\)[ \n\t]*")
+             (let (;; (names (match-string 1))
+                   (begin-description (match-end 0)))
+               (goto-char (match-beginning 1))
+               (while (looking-at "\\([a-zA-Z%][a-zA-Z0-9_]*\\),?")
+                 (if (equal (match-string 1) field-name)
+                     (progn
+                       (goto-char begin-description)
+                       (setq res-type (rec-parse-type (buffer-substring 
(point) (point-max)))))
+                   (goto-char (match-end 0))))))))
+       types))
     res-type))
 
 ;;;; Mode line and Head line
@@ -1511,7 +1476,7 @@ Argument HEADERS specifies the headers to display."
                            &key (type nil) (join nil) (index nil) (sex nil)
                            (fast-string nil) (random nil) (fex nil) (password 
nil)
                            (group-by nil) (sort-by nil) (icase nil) (uniq nil) 
(no-sexps nil)
-                           (descriptor nil))
+                           (descriptor nil) (values nil))
   "Perform a query in the current buffer using recsel.
 
 ARGS contains the arguments to pass to the program.
@@ -1542,7 +1507,10 @@ Optional argument UNIQ when non-nil, returns only unique 
results.
 
 Optional argument NO-SEXPS when non-nil, returns the results in rec format.
 
-Optional argument DESCRIPTOR when non-nil, includes the record descriptor."
+Optional argument DESCRIPTOR when non-nil, includes the record descriptor.
+
+Optional argument VALUES when non-nil, returns only the values of the fields. 
+Requires NO-SEXPS with non-nil value to work properly."
   (let ((buffer (generate-new-buffer "Rec Sel "))
         args status)
     (save-restriction
@@ -1566,6 +1534,8 @@ Optional argument DESCRIPTOR when non-nil, includes the 
record descriptor."
               (setq args (cons "-m" (cons (number-to-string random) args))))
             (when (stringp fex)
               (setq args (cons "-p" (cons fex args))))
+            (when (stringp values)
+              (setq args (cons "-P" (cons values args))))
             (when (stringp password)
               (setq args (cons "-s" (cons password args))))
             (when (stringp group-by)
@@ -1610,7 +1580,123 @@ Optional argument DESCRIPTOR when non-nil, includes the 
record descriptor."
 
 (defun rec-mode--xref-widen-before-return ()
   "Widen the buffer before returning from xref."
-  (widen))
+  (unless (derived-mode-p 'rec-edit-mode)
+    (rec-show-record)))
+
+(defun rec-cmd-xref-go-back ()
+  "Go back in the XREF history.
+
+See `xref-go-back'."
+  (interactive)
+  (widen)
+  (xref-go-back)
+  (unless (derived-mode-p 'rec-edit-mode)
+    (rec-show-record)))
+
+(defun rec-cmd-xref-go-forward ()
+  "Go back in the XREF history.
+
+See `xref-go-forward'."
+  (interactive)
+  (widen)
+  (xref-go-forward)
+  (unless (derived-mode-p 'rec-edit-mode)
+    (rec-show-record)))
+
+
+(defun rec-mode--xref-backend ()
+  "Return the XREF backend for `rec-mode'."
+  'rec)
+
+(cl-defmethod xref-backend-identifier-at-point ((_backend (eql rec)))
+  "Return a cross referencable identifier for the current record field at 
point."
+  (when-let ((field (rec-current-field)))
+    (rec-field-name field)))
+
+(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql rec)))
+  (if-let* ((descriptor (rec-current-record-descriptor))
+            (key (rec-record-descriptor-key descriptor)))
+      (list key)
+    (user-error "Current record type has no %key and cannot be a foreign 
key")))
+
+(cl-defmethod xref-backend-references ((_backend (eql rec)) _identifier)
+  "Find references to the current field with value IDENTIFIER in the recfile."
+  (when-let* ((descriptor (rec-current-record-descriptor))
+              (key (rec-record-descriptor-key descriptor))
+              (type (rec-record-descriptor-type descriptor))
+              (value (car-safe (rec-record-assoc key (rec-current-record))))
+
+              ;; Find all records that have "%type: Xx rec FOO", meaning
+              ;; a field "Xx: ABC" refers to records of type FOO.
+              (descriptors rec-buffer-descriptors)
+              (references
+               (seq-remove
+                #'null
+                (seq-map
+                 (lambda (descr)
+                   (let ((types (rec-record-descriptor-types descr)))
+                     (seq-remove
+                      #'null
+                      (seq-map (lambda (typ)
+                                 (let ((elts (split-string typ " ")))
+                                   (and (eq 3 (length elts))
+                                        (string= "rec" (nth 1 elts))
+                                        (string= type (nth 2 elts))
+                                        (list (rec-record-descriptor-type 
descr) (cl-first elts)))))
+                               types))))
+                 descriptors)))
+
+              ;; Find those that refer to *this* FOO.
+              (matching-references (seq-map
+                                    (lambda (reference)
+                                      (cl-destructuring-bind (ftype field) 
(car reference)
+                                        (cons
+                                         ftype
+                                         (rec--parse-sexp-records
+                                          (rec-query :sex (format "%s = '%s'" 
field value)
+                                                     :descriptor nil
+                                                     :fex field
+                                                     :type ftype)))))
+                                    references)))
+    (seq-mapcat
+     (lambda (matching-reference)
+       (cl-destructuring-bind (source-type . records) matching-reference
+         (seq-map (lambda (record)
+                    (rec-record-to-xref record source-type (current-buffer) 
(cons 'sex "bogus")))
+                  records)))
+     matching-references)))
+
+(cl-defmethod xref-backend-definitions ((_backend (eql rec)) _value)
+  "Find the definition of record referenced by the field, if available.
+
+If the VALUE is a foreign key to another record, jump to it.  If not,
+does nothing. The referent record type must have %key for that to work."
+  (when-let* ((type (rec-current-field-type))
+              (source (rec-field-value (rec-current-field))))
+    (if (eq 'rec (nth 1 type))
+        (let* ((reference (nth 3 type))
+               (results (rec--parse-sexp-records
+                         (rec-query :descriptor t
+                                    :type reference)))
+               (descriptor (seq-find #'rec-record-descriptor-p results)))
+          (when descriptor
+            (if-let* ((key (rec-record-descriptor-key descriptor))
+                      (sex (format "%s = '%s'" key source))
+                      (target (car-safe
+                               (rec--parse-sexp-records
+                                (rec-query :descriptor nil
+                                           :type reference
+                                           :sex sex
+                                           :fex key))))
+                      (field (seq-find (lambda (field)
+                                         (string= key (rec-field-name field)))
+                                       (rec-record-fields target))))
+                (list
+                 (xref-make
+                  (rec--xref-summary-for-record target reference (cons 'sex 
sex))
+                  (rec-xref-make-location (current-buffer) (rec-field-position 
field))))
+              (user-error "Impossible reference: target record type '%s' has 
no '%%key' defined" reference))))
+      (user-error "Field '%s' does not refer to anything" (rec-field-name 
(rec-current-field))))))
 
 ;;;; Selection of records
 ;;
@@ -1627,7 +1713,7 @@ Optional argument DESCRIPTOR when non-nil, includes the 
record descriptor."
       (message "No current selection")
     (widen)
     (let* ((first-record (car rec-current-selection))
-           (pos (slot-value first-record 'position)))
+           (pos (rec-record-position first-record)))
       (goto-char pos)
       (rec-show-record))))
 
@@ -1684,7 +1770,7 @@ Argument SEX is the selection expression to use."
           (run-hooks 'hack-local-variables-hook))
         (rec-update-buffer-descriptors)
         (switch-to-buffer buf))
-    (user-error "No results.")))
+    (user-error "No results.?")))
 
 (defun rec-cmd-new-buffer-from-sex (sex)
   "Query the current buffer using SEX and insert the result into a new buffer."
@@ -1702,7 +1788,9 @@ Argument SEX is the selection expression to use."
 
 
 (defun rec-cmd-new-buffer-from-fast-string (fast-string)
-  "Query the current buffer using FAST-STRING and insert the result into a new 
buffer."
+  "Query the current buffer using FAST-STRING.
+
+Inserts the result into a new buffer."
   (interactive
    (list (read-string "Fast string search: "
                       nil
@@ -1730,49 +1818,44 @@ Optionally select only the fields in FEX.")
   "Return a string representation of SELECTION.")
 
 (cl-defgeneric rec-selection-expr (selection)
-  "Return the actual expression used in the selection.")
+  "Return the actual expression used in the selection of SELECTION.")
 
-(defclass rec-selection ()
-  ((type :initarg :type
-         :initform nil)
-   (icase :initarg :icase))
-  "A query to restrict candidates for the current buffer.")
+(cl-defstruct rec-selection
+  "A query to restrict candidates for the current buffer."
+  type icase)
 
-(defclass rec-selection-fast (rec-selection)
-  ((fast :initarg :fast)))
+(cl-defstruct (rec-selection-sex (:include rec-selection))
+  "A selection based on selection expressions."
+  sex)
+
+(cl-defstruct (rec-selection-fast (:include rec-selection))
+  "A fast string search selection."
+  fast)
 
 (cl-defmethod rec-selection-expr ((selection rec-selection-fast))
-  (slot-value selection 'fast))
+  (rec-selection-fast-fast selection))
 
 (cl-defmethod rec-selection-stringify ((selection rec-selection-fast))
-  (with-slots (type fast) selection
-    (format "%s[%s]" type fast)))
+  (format "%s[%s]" (rec-selection-type selection) (rec-selection-fast-fast 
selection)))
 
 (cl-defmethod rec-selection-query ((selection rec-selection-fast) &optional 
fex)
-  "Query records using a fast string search."
-  (with-slots (type icase fast) selection
-    (rec-query :type type
-               :fex fex
-               :icase icase
-               :fast-string fast)))
-
-(defclass rec-selection-sex (rec-selection)
-  ((sex :initarg :sex)))
+  (rec-query :type (rec-selection-type selection)
+             :fex fex
+             :icase (rec-selection-icase selection)
+             :fast-string (rec-selection-fast-fast selection)))
 
 (cl-defmethod rec-selection-expr ((selection rec-selection-sex))
-  (slot-value selection 'sex))
+  (rec-selection-sex-sex selection))
 
 (cl-defmethod rec-selection-stringify ((selection rec-selection-sex))
-  (with-slots (type sex) selection
-    (format "%s / %s" type sex)))
+  (format "%s / %s" (rec-selection-type selection) (rec-selection-sex-sex 
selection)))
 
 (cl-defmethod rec-selection-query ((selection rec-selection-sex) &optional fex)
   "Query records using a selection expression."
-  (with-slots (type icase sex) selection
-    (rec-query :type type
-               :fex fex
-               :icase icase
-               :sex sex)))
+  (rec-query :type (rec-selection-type selection)
+             :fex fex
+             :icase (rec-selection-icase selection)
+             :sex (rec-selection-sex-sex selection)))
 
 ;;;;;; Variables for containing the selectionk
 
@@ -1829,9 +1912,9 @@ See `rec-selection-mode'."
             nil 'rec-selection-sex-history prev))))
   (when (not (equal sex ""))
     (rec-begin-selection
-     (rec-selection-sex :sex sex
-                        :icase prefix
-                        :type (rec-record-type)))))
+     (make-rec-selection-sex :sex sex
+                             :icase prefix
+                             :type (rec-record-type)))))
 
 (defvar rec-selection-fast-history nil
   "The history of record selection history (fast search).")
@@ -1855,9 +1938,9 @@ See `rec-selection-mode'."
             nil 'rec-selection-fast-history prev))))
   (when (not (equal fast-string ""))
     (rec-begin-selection
-     (rec-selection-fast :fast fast-string
-                         :type (rec-record-type)
-                         :icase prefix))))
+     (make-rec-selection-fast :fast fast-string
+                              :type (rec-record-type)
+                              :icase prefix))))
 
 (defun rec-cmd-exit-selection ()
   "Exit `rec-selection-mode'."
@@ -1880,16 +1963,16 @@ Prefix arguments N moves next by N records."
   (interactive "P")
   (if rec-current-selection
       (let* ((record (rec-current-record))
-             (pos (slot-value record 'position))
+             (pos (rec-record-position record))
              (where-am-i
               (cl-position-if
                (lambda (rec)
-                 (= pos (byte-to-position (slot-value rec 'position))))
+                 (= pos (byte-to-position (rec-record-position rec))))
                rec-current-selection))
              (next (if (numberp where-am-i)
                        (nth (+ where-am-i (or n 1)) rec-current-selection)
                      (car rec-current-selection))))
-        (if (and next (or (/= pos (slot-value next 'position)) (zerop n)))
+        (if (and next (or (/= pos (rec-record-position next)) (zerop n)))
             (rec-goto-record next)
           (user-error
            (if rec-selection-current-selection
@@ -1911,7 +1994,30 @@ Prefix arguments N moves next by N records."
 ;;;;; Selection cross reference
 
 (cl-defgeneric rec--xref-summary-for-record (record type kind)
-  "Return a formated summary line for RECORD of type TYPE.")
+  "Return a formated summary line for RECORD of type TYPE using KIND."
+  (let* ((pos (byte-to-position (rec-record-position record)))
+         (line-number (number-to-string
+                       (save-restriction
+                         (widen)
+                         (line-number-at-pos pos t))))
+         (heading (concat (propertize type 'face 'font-lock-type-face)
+                          " at line "
+                          line-number)))
+    
+    (add-face-text-property 0 (length heading) 'bold nil heading)
+    (format "%s\n%s"
+            heading
+            (rec--xref-truncate-fields record kind))))
+
+(defun rec-record-to-xref (record type buffer kind)
+  "Make an xref object out of a record structure.
+
+If TYPE is nil, the summary line will show just 'Record'.  BUFFER is the buffer
+from which to display results.  The KIND determines" 
+  (xref-make
+   (rec--xref-summary-for-record record type kind)
+   (rec-xref-make-location buffer (or (byte-to-position (rec-record-position 
record)) 0))))
+
 
 (cl-defgeneric rec--xref-truncate-fields (record kind)
   "Truncate fields of RECORD of search KIND.")
@@ -1921,7 +2027,7 @@ Prefix arguments N moves next by N records."
 
 Takes up to the first three elements of a record and displays them, padded
 with four spaces."
-  (let* ((rec-fields (slot-value record 'fields))
+  (let* ((rec-fields (rec-record-fields record))
          (fields (mapconcat
                   (lambda (field)
                     (concat
@@ -1930,7 +2036,7 @@ with four spaces."
                        (rec-insert field)
                        (string-trim-right
                         (rec-mode--syntax-highlight (buffer-string))))))
-                  (cl-subseq rec-fields 0 3 )
+                  (cl-subseq rec-fields 0 (min (length rec-fields) 3))
                   "\n")))
     (if (< 3 (length rec-fields))
         (concat fields "\n    ...")
@@ -1938,11 +2044,10 @@ with four spaces."
 
 (cl-defgeneric rec--xref-truncate-fields (record (kind (head fast)))
   "Truncate fields for KIND fast string searches in RECORD."
-  (let* ((fields (slot-value record 'fields))
+  (let* ((fields (rec-record-fields record))
          (matching (seq-filter
                     (lambda (field)
-                      (string= (slot-value field 'value)
-                               (cdr kind)))
+                      (cl-search (cdr kind) (rec-field-value field)))
                     fields)))
     (mapconcat
      (lambda (field)
@@ -1964,18 +2069,14 @@ with four spaces."
      matching
      "\n")))
 
-(defun rec--xref-summary-for-record (record type kind)
-  "Base class method to do the rest of the formating."
-  (let* ((pos (byte-to-position (slot-value record 'position)))
-         (line-number (number-to-string (line-number-at-pos pos t)))
-         (heading (concat (propertize type 'face 'font-lock-type-face)
-                          " at line "
-                          line-number)))
-    
-    (add-face-text-property 0 (length heading) 'bold nil heading)
-    (format "%s\n%s"
-            heading
-            (rec--xref-truncate-fields record kind))))
+(defun rec-xref-make-location (buffer position)
+  "Make an XREF object out of BUFFER and POSITION.
+
+Aims to be backwards compatible with Emacs versions
+28 and below."
+  (if (fboundp 'xref-make-buffer-location)
+      (xref-make-buffer-location buffer position)
+    (xref-buffer-location buffer :position position)))
 
 (defun rec--xref-query (query kind)
   "Make a XREF results list using QUERY identified by KIND."
@@ -1993,8 +2094,8 @@ with four spaces."
         (lambda (record)
           (xref-make
            (rec--xref-summary-for-record record type kind)
-           (xref-buffer-location :buffer (current-buffer)
-                                 :position (byte-to-position (slot-value 
record 'position)))))
+           (rec-xref-make-location (current-buffer)
+                                   (byte-to-position (rec-record-position 
record)))))
         data)
        nil))))
 
@@ -2050,11 +2151,13 @@ in the current buffer matching the fast string search."
 (make-variable-buffer-local 'rec-prev-bufffer)
 
 (defvar rec-pointer nil
-  "The previous position in `rec-prev-buffer' we were at, before jumping into 
`rec-edit-field-mode'.")
+  "The previous position in `rec-prev-buffer' we were at.
+
+The position is recorded before jumping into `rec-edit-field-mode'.")
 (make-variable-buffer-local 'rec-point)
 
 (defvar rec-prev-window-configuration nil
-  "The window configuration that was active before jumping into 
`rec-edit-field-mode'.")
+  "The window configuration before jumping into `rec-edit-field-mode'.")
 (make-variable-buffer-local 'rec-prev-window-configuration)
 
 (defconst rec-cmd-edit-field-message
@@ -2124,9 +2227,9 @@ will be used for fields of any type."
                 (rec-delete-field)
                 (save-excursion
                   (rec-insert
-                   (rec-field :position 0
-                              :name field-name
-                              :value new-value)))
+                   (make-rec-field :position 0
+                                   :name field-name
+                                   :value new-value)))
                 (rec-finish-editing-move)))))
          ((and (equal field-type-kind 'date) rec-popup-calendar
                (null n))
@@ -2138,37 +2241,37 @@ will be used for fields of any type."
                 (map (make-sparse-keymap)))
             (set-keymap-parent map calendar-mode-map)
             (define-key map "q"
-              (lambda () (interactive)
-                (use-local-map old-map)
-                (calendar-exit)))
+                        (lambda () (interactive)
+                          (use-local-map old-map)
+                          (calendar-exit)))
             (define-key map "t"
-              (lambda () (interactive)
-                (use-local-map old-map)
-                (calendar-exit)
-                (set-buffer rec-prev-buffer)
-                (let ((inhibit-read-only t))
-                  (rec-delete-field)
-                  (save-excursion
-                    (rec-insert
-                     (rec-field :position 0
-                                :name rec-field-name
-                                :value (format-time-string 
rec-time-stamp-format))))
-                  (rec-finish-editing-move))))
+                        (lambda () (interactive)
+                          (use-local-map old-map)
+                          (calendar-exit)
+                          (set-buffer rec-prev-buffer)
+                          (let ((inhibit-read-only t))
+                            (rec-delete-field)
+                            (save-excursion
+                              (rec-insert
+                               (make-rec-field :position 0
+                                               :name rec-field-name
+                                               :value (format-time-string 
rec-time-stamp-format))))
+                            (rec-finish-editing-move))))
             (define-key map (kbd "RET")
-              (lambda () (interactive)
-                (let* ((date (calendar-cursor-to-date))
-                       (time (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 
2 date))))
-                  (use-local-map old-map)
-                  (calendar-exit)
-                  (set-buffer rec-prev-buffer)
-                  (let ((inhibit-read-only t))
-                    (rec-delete-field)
-                    (save-excursion
-                      (rec-insert
-                       (rec-field :position 0
-                                  :name rec-field-name
-                                  :value (format-time-string "%Y-%m-%d" 
time))))
-                    (rec-finish-editing-move)))))
+                        (lambda () (interactive)
+                          (let* ((date (calendar-cursor-to-date))
+                                 (time (encode-time 0 0 0 (nth 1 date) (nth 0 
date) (nth 2 date))))
+                            (use-local-map old-map)
+                            (calendar-exit)
+                            (set-buffer rec-prev-buffer)
+                            (let ((inhibit-read-only t))
+                              (rec-delete-field)
+                              (save-excursion
+                                (rec-insert
+                                 (make-rec-field :position 0
+                                                 :name rec-field-name
+                                                 :value (format-time-string 
"%Y-%m-%d" time))))
+                              (rec-finish-editing-move)))))
             (use-local-map map)
             (message "[RET]: Select date [t]: Time-stamp     [q]: Exit")))
          (t
@@ -2378,7 +2481,7 @@ Optional argument N specifies number of records to skip."
 (defvar-local rec-edit-mode-type nil
   "The kind of thing we are navigating.
 
-One of ‘buffer‘, ‘record‘ or ‘type‘.")
+One of `buffer', `record' or `type'.")
 
 (defun rec-edit-record ()
   "Go to the record edition mode."
@@ -2663,7 +2766,7 @@ This command is especially useful with enumerated types."
 (defun rec-summary-move-to-record (record)
   "Move the cursor in the summary buffer to the position of RECORD."
   (when (buffer-live-p rec-summary-buffer)
-    (let ((target (slot-value record 'position))
+    (let ((target (rec-record-position record))
           (rec-summary-inhibit-sync t)
           where)
       (with-current-buffer rec-summary-buffer
@@ -2722,11 +2825,11 @@ active selection in `rec-selection-current-selection'."
                     (mapcar (lambda (rec)
                               (let* ((entry-marker (make-marker)))
                                 (set-marker entry-marker
-                                            (byte-to-position (slot-value rec 
'position)))
+                                            (byte-to-position 
(rec-record-position rec)))
                                 (list entry-marker
                                       (vconcat
                                        (cl-loop for field in summary-fields
-                                                for value = (car 
(rec-record-assoc field rec ))
+                                                for value = (string-join 
(rec-record-assoc field rec) ",")
                                                 collect (or value ""))))))
                             (rec--parse-sexp-records query))))
               ;; Create the summary window if it does not exist and populate
@@ -2793,7 +2896,7 @@ summary buffer."
 
 The record is assumed to have its position in bytes, not
 characters."
-  (rec-goto-position (slot-value record 'position)))
+  (rec-goto-position (rec-record-position record)))
 
 
 ;;;; Interacting with other modes
@@ -2811,8 +2914,8 @@ function returns nil."
           (let ((values (rec-record-assoc key record)))
             (if values
                 (car values)
-              (rec-field-value (car (slot-value record 'fields)))))
-        (rec-field-value (car (slot-value record 'fields)))))))
+              (rec-field-value (car (rec-record-fields record)))))
+        (rec-field-value (car (rec-record-fields record)))))))
 
 
 ;;;; Flymake support
@@ -2851,7 +2954,7 @@ function returns nil."
 
 ;;;###autoload
 (defun rec-mode-flymake-recfix (report-fn &rest _args)
-  "A Flymake backend for recfile compilation. 
+  "A Flymake backend for recfile compilation.
 
 Defers to `recfix' for checking the buffer, calling REPORT-FN
 to report the errors."
@@ -2916,7 +3019,7 @@ to report the errors."
         (current (rec-current-record)))
     (if type
         (cond ((rec-record-descriptor-p current)
-               (propertize (format "%%%s" type) 'face 'font-lock-keyword-face))
+              (propertize (format "%%%s" type) 'face 'font-lock-keyword-face))
               ((not (null (rec-key)))
                (let ((key-value (car-safe (rec-record-assoc
                                            (rec-key)
@@ -2951,7 +3054,7 @@ onto the chosen record."
     ["Jump back"               rec-cmd-jump-back rec-jump-back]
     ["Next record"             rec-cmd-goto-next-rec
      :help "Go to the next record of the same type."]
-    ["Previous record"         rec-cmd-goto-previous-rec 
+    ["Previous record"         rec-cmd-goto-previous-rec
      :help "Go to the previous record of the same type."]
     ["Next field"              rec-cmd-goto-next-field t]
     ["Go to record descriptor" rec-cmd-show-descriptor t]
@@ -2977,7 +3080,7 @@ onto the chosen record."
      
      ["For selection expression..." rec-cmd-xref-sex
       :help "Run a selection expression on the buffer and make an XREF list 
out of it."]
-     ["For fast string search..." rec-cmd-occur-from-sex
+     ["For fast string search..." rec-cmd-xref-fast-string
       :help "Run a fast string search and copy the matching lines into a new 
buffer."])
     
     "---"
@@ -3012,8 +3115,10 @@ onto the chosen record."
   (setq-local end-of-defun-function #'rec-end-of-record)
   (add-to-invisibility-spec '(rec-hide-field . "..."))
 
+  (setq-local xref-prompt-for-identifier nil)
   (add-hook 'xref-after-jump-hook #'rec-mode--xref-after-jump-hook nil t)
-  (add-hook 'xref-after-return-hook #'rec-mode--xref-after-return-hook nil t)
+  (add-hook 'xref-after-return-hook #'rec-mode--xref-widen-before-return nil t)
+  (add-hook 'xref-backend-functions #'rec-mode--xref-backend nil t)
 
   ;; Run some code later (i.e. after running the mode hook and setting the
   ;; file-local variables).
@@ -3079,7 +3184,7 @@ minor mode is entered.  This minor mode alters the 
behaviour of
 the standard bindings of `rec-cmd-goto-next-rec' and
 `rec-cmd-goto-previous-rec'.  In the minor mode, only the records
 matching the currently active selection are available for
-navigation. The minor mode can be exited using
+navigation.  The minor mode can be exited using
 `rec-selection-exit', bound to `\\[rec-cmd-exit-selection]'.
 
 \\{rec-selection-mode-map}."



reply via email to

[Prev in Thread] Current Thread [Next in Thread]