aboutsummaryrefslogtreecommitdiff
path: root/org-drill.el
diff options
context:
space:
mode:
Diffstat (limited to 'org-drill.el')
-rw-r--r--org-drill.el127
1 files changed, 93 insertions, 34 deletions
diff --git a/org-drill.el b/org-drill.el
index 11f161e..8c895b6 100644
--- a/org-drill.el
+++ b/org-drill.el
@@ -5003,9 +5003,11 @@ an integer (0 when absent). AVG-QUALITY is DRILL_AVERAGE_QUALITY as a
float, or nil when absent. DAYS-SINCE-REVIEW is the integer day count
since DRILL_LAST_REVIEWED, or nil when never reviewed. DAYS-SINCE-ADDED
is the integer day count since DATE_ADDED, or nil when absent.
-TOTAL-REPEATS is DRILL_TOTAL_REPEATS as an integer (0 when absent)."
+TOTAL-REPEATS is DRILL_TOTAL_REPEATS as an integer (0 when absent).
+FILE is the file the entry's buffer visits, or nil for a fileless
+buffer; card links carry it so the dashboard can jump across files."
heading pos failure-count avg-quality
- days-since-review days-since-added total-repeats)
+ days-since-review days-since-added total-repeats file)
;; Short aliases for the long struct accessors, used by the sort
;; comparators below so the comparator lines stay readable.
@@ -5057,7 +5059,8 @@ via `org-get-heading', so the point must be on a drill heading."
(org-drill-statistics--days-since-org-timestamp last-raw today-day)
:days-since-added
(org-drill-statistics--days-since-org-timestamp added-raw today-day)
- :total-repeats (if repeats-raw (string-to-number repeats-raw) 0))))
+ :total-repeats (if repeats-raw (string-to-number repeats-raw) 0)
+ :file (buffer-file-name))))
(defun org-drill-statistics--days-since-org-timestamp (timestamp today-day)
"Return integer days from org TIMESTAMP string to TODAY-DAY.
@@ -5266,25 +5269,51 @@ with no SCHEDULED time are ignored."
;; each scan and cap) gives the pre-cap totals needed for the "+N more"
;; footers for free.
-(defun org-drill-statistics--card-link (heading pos)
+(defun org-drill-statistics--card-link (heading pos &optional file)
"Return an org bracket link to a drill card heading.
HEADING is the card's outline heading string, used as the link
-description. POS is the integer buffer position of the heading, carried
-in the link path so the dashboard's RET handler can jump to the card.
-The path has the form \"org-drill-card:POS\". Any closing bracket in
-HEADING is replaced so a literal \"]]\" cannot terminate the link early.
-An empty or nil HEADING falls back to a position-based description."
+description. POS is the integer buffer position of the heading. FILE
+is the file the card's buffer visits, or nil for a fileless buffer.
+The path has the form \"org-drill-card:POS@FILE\" (FILE may be empty),
+so the follow handler can jump to the card even across files. Any
+closing bracket in HEADING is replaced so a literal \"]]\" cannot
+terminate the link early. An empty or nil HEADING falls back to a
+position-based description."
(let* ((desc (if (and heading (not (string-empty-p heading)))
heading
(format "card at %d" pos)))
(safe (replace-regexp-in-string "]" "}" desc)))
- (format "[[org-drill-card:%d][%s]]" pos safe)))
+ (format "[[org-drill-card:%d@%s][%s]]" pos (or file "") safe)))
+
+(defun org-drill-statistics--follow-card-link (path &optional _prefix)
+ "Follow an org-drill-card link to its card.
+PATH is \"POS@FILE\" (FILE may be empty) or a legacy bare \"POS\".
+With a file, visit it in another window and move point to POS. Without
+one, fall back to the dashboard's stored source buffer. Signals a
+`user-error' when neither identifies a live target."
+ (let* ((at (string-match "@" path))
+ (pos (string-to-number (if at (substring path 0 at) path)))
+ (file (and at (substring path (1+ at))))
+ (source (and (boundp 'org-drill-statistics--source-buffer)
+ org-drill-statistics--source-buffer)))
+ (cond
+ ((and file (not (string-empty-p file)))
+ (find-file-other-window file)
+ (goto-char (min (max 1 pos) (point-max))))
+ ((buffer-live-p source)
+ (pop-to-buffer source)
+ (goto-char (min (max 1 pos) (point-max))))
+ (t (user-error
+ "Card buffer is gone; refresh the dashboard from a deck buffer")))))
+
+(org-link-set-parameters "org-drill-card"
+ :follow #'org-drill-statistics--follow-card-link)
(defun org-drill-statistics--render-attention-table
(title rows total empty-note)
"Render one needs-attention subsection as an org string.
TITLE is the third-level heading text for the subsection. ROWS is a
-list of (HEADING . POS) cons cells, already capped at
+list of (HEADING POS FILE) lists, already capped at
`org-drill-statistics-attention-row-limit' and already sorted. TOTAL is
the full count of matching cards before capping, used for the
\"+N more\" footer. EMPTY-NOTE is the line shown when ROWS is empty.
@@ -5304,7 +5333,8 @@ the number of rows shown."
(mapconcat
(lambda (row)
(format "| %s |"
- (org-drill-statistics--card-link (car row) (cdr row))))
+ (org-drill-statistics--card-link
+ (nth 0 row) (nth 1 row) (nth 2 row))))
rows
"\n")
"\n"
@@ -5368,8 +5398,9 @@ pre-cap totals are available for the footers without rescanning."
(lambda (structs)
(mapcar
(lambda (d)
- (cons (org-drill-statistics--entry-attention-data-heading d)
- (org-drill-statistics--entry-attention-data-pos d)))
+ (list (org-drill-statistics--entry-attention-data-heading d)
+ (org-drill-statistics--entry-attention-data-pos d)
+ (org-drill-statistics--entry-attention-data-file d)))
(org-drill-statistics--cap-rows structs)))))
(concat
"** Needs attention\n"
@@ -5634,6 +5665,14 @@ algorithms. Set by `org-drill-statistics' and rotated by
`org-drill-statistics-cycle-algorithm'. Read by the render helpers via
the filtered log they are handed.")
+(defvar-local org-drill-statistics--source-buffer nil
+ "Buffer-local deck buffer the dashboard's card scans run in.
+Set by `org-drill-statistics' to the buffer the command was invoked
+from and read by `org-drill-statistics--render' (via refresh and the
+cycle commands) so every card traversal sees the deck, not the
+dashboard's own text. Also the fallback jump target for card links
+whose path carries no file.")
+
(defun org-drill-statistics--range-cutoff-float (label)
"Return the float-time cutoff for range LABEL, or nil for all time.
LABEL is a key in `org-drill-statistics-range-presets'. When its DAYS
@@ -5720,24 +5759,34 @@ is put in `org-mode'; not intended to be toggled by hand."
:lighter " OrgDrillStats"
:keymap org-drill-statistics-mode-map)
-(defun org-drill-statistics--render (buffer scope range algorithm)
+(defun org-drill-statistics--render (buffer scope range algorithm
+ &optional source)
"Render the dashboard for SCOPE, RANGE, ALGORITHM into BUFFER.
-BUFFER is the target buffer. Its read-only state is lifted for the
-write, the assembled body replaces the contents, point returns to the
-top, and the buffer is left read-only. The three filter values are
-stored buffer-locally so refresh and the cycle commands can read and
-rotate them. Returns BUFFER."
- (with-current-buffer buffer
- (let ((inhibit-read-only t)
- (body (org-drill-statistics--render-all scope range algorithm)))
- (erase-buffer)
- (insert body)
- (goto-char (point-min)))
- (setq org-drill-statistics--scope scope
- org-drill-statistics--range range
- org-drill-statistics--algorithm algorithm)
- (setq buffer-read-only t)
- buffer))
+BUFFER is the target buffer. SOURCE is the deck buffer the card scans
+run in, defaulting to the current buffer, so the traversals see the
+deck rather than the dashboard's own text. A dead SOURCE signals a
+`user-error'. BUFFER's read-only state is lifted for the write, the
+assembled body replaces the contents, point returns to the top, and the
+buffer is left read-only. The filter values and SOURCE are stored
+buffer-locally so refresh and the cycle commands can read and rotate
+them. Returns BUFFER."
+ (let ((source (or source (current-buffer))))
+ (unless (buffer-live-p source)
+ (user-error
+ "The dashboard's deck buffer is gone; run org-drill-statistics from a deck"))
+ (let ((body (with-current-buffer source
+ (org-drill-statistics--render-all scope range algorithm))))
+ (with-current-buffer buffer
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (insert body)
+ (goto-char (point-min)))
+ (setq org-drill-statistics--scope scope
+ org-drill-statistics--range range
+ org-drill-statistics--algorithm algorithm
+ org-drill-statistics--source-buffer source)
+ (setq buffer-read-only t)
+ buffer))))
;;;###autoload
(defun org-drill-statistics ()
@@ -5748,13 +5797,14 @@ filter header and the five render sections, using the current
to it. Refresh and the s/r/a cycle commands re-render in place."
(interactive)
(let ((buffer (get-buffer-create org-drill-statistics--buffer-name))
+ (source (current-buffer))
(scope org-drill-scope)
(range (caar org-drill-statistics-range-presets))
(algorithm nil))
(with-current-buffer buffer
(org-mode)
(org-drill-statistics-mode 1))
- (org-drill-statistics--render buffer scope range algorithm)
+ (org-drill-statistics--render buffer scope range algorithm source)
(switch-to-buffer buffer)
buffer))
@@ -5767,7 +5817,8 @@ every section against the current log and card state. Bound to g."
(current-buffer)
org-drill-statistics--scope
org-drill-statistics--range
- org-drill-statistics--algorithm))
+ org-drill-statistics--algorithm
+ org-drill-statistics--source-buffer))
(defun org-drill-statistics-cycle-scope ()
"Cycle the dashboard scope filter and refresh.
@@ -5990,7 +6041,15 @@ do. Interactively, prompts for DIRECTORY."
(org-drill-statistics--write-csv
(expand-file-name "cards.csv" directory)
org-drill-statistics--cards-csv-header
- (org-drill-statistics--cards-rows scope))
+ (let ((source org-drill-statistics--source-buffer))
+ (cond
+ ((buffer-live-p source)
+ (with-current-buffer source
+ (org-drill-statistics--cards-rows scope)))
+ (source
+ (user-error
+ "The dashboard's deck buffer is gone; refresh from a deck first"))
+ (t (org-drill-statistics--cards-rows scope)))))
(org-drill-statistics--write-csv
(expand-file-name "daily.csv" directory)
org-drill-statistics--daily-csv-header