aboutsummaryrefslogtreecommitdiff
path: root/modules/org-agenda-frame.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/org-agenda-frame.el')
-rw-r--r--modules/org-agenda-frame.el106
1 files changed, 97 insertions, 9 deletions
diff --git a/modules/org-agenda-frame.el b/modules/org-agenda-frame.el
index 1a19cc1b..f0de99ce 100644
--- a/modules/org-agenda-frame.el
+++ b/modules/org-agenda-frame.el
@@ -31,6 +31,7 @@
;; `with-eval-after-load' so a batch test-load runs no org-agenda side effects.
(defvar org-agenda-custom-commands)
(defvar org-agenda-finalize-hook)
+(defvar org-agenda-mode-map)
(defvar org-agenda-sticky)
(defvar org-agenda-window-setup)
(declare-function cj/build-org-agenda-list "org-agenda-config" (&optional force-rebuild))
@@ -44,10 +45,12 @@
(declare-function org-get-at-bol "org" (property))
(declare-function org-fold-show-context "org-fold" (&optional key))
(declare-function org-agenda "org-agenda" (&optional arg org-keys restriction))
-;; Forward references: defined later in this file (Phase 2 for -safe-redo,
-;; the lifecycle block for -delete).
-(declare-function cj/--agenda-frame-safe-redo "org-agenda-frame" ())
-(declare-function cj/--agenda-frame-delete "org-agenda-frame" ())
+;; No declare-function for -safe-redo / -delete: they are defined later in THIS
+;; file, and the byte-compiler resolves same-file forward references at end of
+;; compilation. A declare-function for a same-file function instead counts as a
+;; second definition ("defined multiple times") and, worse, its declared arglist
+;; overrides the real one for arg-count checking -- the empty () shadowed
+;; -safe-redo's actual (&optional frame), disabling that check.
(defconst cj/--agenda-frame-parameter 'cj/agenda-frame
"Frame parameter marking the dedicated agenda frame.
@@ -95,9 +98,18 @@ The existing top-level key is `d' (org-agenda-config.el:344), so `F' is
collision-free. The sticky buffer derives its name from this key
\(*Org Agenda(F)*).")
+(defvar cj/--agenda-frame-span 7
+ "Span, in days, of the agenda-frame view.
+The `F' custom command reads this via its `org-agenda-span' setting, which
+Org evaluates on every build and every redo, so `cj/--agenda-frame-day-view'
+\(span 1) and `cj/--agenda-frame-week-view' (span 7) change it and the next
+redo picks up the new span. Reset to 7 on each spawn so a fresh frame opens
+at the documented default.")
+
(defun cj/--agenda-frame-command ()
"Return the `org-agenda-custom-commands' entry for the agenda frame.
-A one-block agenda: a seven-day span anchored to today rather than
+A one-block agenda: a `cj/--agenda-frame-span'-day span anchored to today
+rather than
Monday (`org-agenda-list' otherwise anchors any seven-day span to the
week start in Org 9.7.11), rendered in the frame's sole window
\(`current-window', so Org's default `reorganize-frame' can't split it),
@@ -105,7 +117,7 @@ as its own sticky *Org Agenda(F)* buffer, with follow-mode forced off so
a non-nil global default can't open a second window at build time."
`(,cj/--agenda-frame-command-key "Agenda frame: 7-day today-anchored"
((agenda ""
- ((org-agenda-span 7)
+ ((org-agenda-span cj/--agenda-frame-span)
(org-agenda-start-day "0d")
(org-agenda-start-on-weekday nil)
(org-agenda-start-with-follow-mode nil)
@@ -195,7 +207,7 @@ frame stranded on a non-agenda buffer."
"Shown when a mutating or buffer-opening command is denied in the frame.")
(defconst cj/--agenda-frame-fixed-view-message
- "Agenda frame is fixed to the 7-day view"
+ "Agenda frame shows only the day (d) and week (w) views"
"Shown when a view-changing command is denied in the frame.")
(defun cj/--agenda-frame-denied-readonly ()
@@ -257,6 +269,10 @@ The default binding for every key not on the allowlist."
;; frame-scoped safe redo, same as r) rather than denying it as a
;; view-change. C-M-<f8> stays the force-rescan.
(define-key map (kbd "g") #'cj/--agenda-frame-safe-redo)
+ ;; d / w toggle the span (today's day vs the seven-day view) in place; the
+ ;; other view-changers stay denied to keep the today-anchored frame stable.
+ (define-key map (kbd "d") #'cj/--agenda-frame-day-view)
+ (define-key map (kbd "w") #'cj/--agenda-frame-week-view)
(define-key map (kbd "S-<f8>") #'cj/agenda-frame-toggle)
(define-key map (kbd "C-M-<f8>") #'cj/org-agenda-refresh-files)
;; C-x C-c means "close this frame" here. The global
@@ -284,8 +300,9 @@ The default binding for every key not on the allowlist."
;; its global binding and escapes the read-only frame into ai-term.
(dolist (key '("M-SPC" "M-S-SPC"))
(define-key map (kbd key) #'cj/--agenda-frame-denied-readonly))
- ;; View-changers get the distinct fixed-view message, not the read-only one.
- (dolist (key '("w" "d" "y" "f" "b" "j"))
+ ;; The remaining view-changers get the distinct fixed-view message, not the
+ ;; read-only one. d/w are handled above (they toggle the span in place).
+ (dolist (key '("y" "f" "b" "j"))
(define-key map (kbd key) #'cj/--agenda-frame-denied-fixed-view))
map)
"Keymap for `cj/agenda-frame-mode'.
@@ -304,6 +321,53 @@ of contract."
:lighter " AgendaFrame"
:keymap cj/agenda-frame-mode-map)
+(defun cj/--agenda-frame-shadow-mutations (&optional source-map prefix)
+ "Deny every SOURCE-MAP key sequence not on the frame map's allowlist.
+Walk SOURCE-MAP (default `org-agenda-mode-map') recursively. For each
+sequence it binds to a command, if `cj/agenda-frame-mode-map' doesn't already
+bind that sequence to a command or manage it as a prefix, add an explicit
+read-only deny.
+
+This closes the default-deny hole: a keymap's `[t]' default never shadows an
+explicit binding in a lower-priority map, so a single `[t]' catch-all denies
+only keys that are unbound everywhere. Every key `org-agenda-mode-map' binds
+\(t, I, k, z, s, ., the C-c mutators, C-x C-s, ...) would otherwise sail
+through the catch-all and mutate source files from the read-only frame.
+Explicitly denying each non-allowlisted sequence makes the catch-all's intent
+actually hold.
+
+PREFIX is the accumulated key vector during recursion (internal). Idempotent:
+re-running rebinds the same denials. Runs from `with-eval-after-load' once
+`org-agenda-mode-map' exists."
+ (let ((source (or source-map org-agenda-mode-map))
+ (prefix (or prefix [])))
+ (map-keymap
+ (lambda (event binding)
+ (unless (or (eq event t) (eq event 'menu-bar) (eq event 'remap)
+ (consp event))
+ (let ((seq (vconcat prefix (vector event))))
+ (cond
+ ((keymapp binding)
+ (cj/--agenda-frame-shadow-mutations binding seq))
+ ((commandp binding)
+ (let ((ours (lookup-key cj/agenda-frame-mode-map seq)))
+ ;; A command we allowlisted or a prefix we manage: leave it.
+ ;; Anything else (only the `[t]' default, or unbound under a
+ ;; shared prefix) escapes to org's command -- deny it here.
+ (unless (or (commandp ours) (keymapp ours))
+ (define-key cj/agenda-frame-mode-map seq
+ #'cj/--agenda-frame-denied-readonly))))))))
+ source)))
+
+;; The shadow walk is installed at the END of this file, not here: it reads
+;; `commandp' on each allowlisted binding to decide whether to keep it, and the
+;; view/redo handlers (day-view, week-view, safe-redo) are defined further down.
+;; If org-agenda is already loaded when this file loads (the normal startup order,
+;; and every reload), `with-eval-after-load' fires immediately -- so the walk must
+;; not run until those defuns exist, or it reads them as undefined, fails the
+;; commandp guard, and denies the very keys the allowlist grants. See the bottom
+;; of the file.
+
(defun cj/--agenda-frame-maybe-enable-mode ()
"Re-enable `cj/agenda-frame-mode' after an agenda build in the agenda frame.
Added to `org-agenda-finalize-hook'. `org-agenda-redo' rebuilds through
@@ -428,6 +492,9 @@ Returns the new agenda frame on success."
(condition-case err
(progn
(setq cj/--agenda-frame-launch-frame launch)
+ ;; A fresh frame opens at the documented seven-day default, even if a
+ ;; prior session left the span on the day view (d).
+ (setq cj/--agenda-frame-span 7)
(setq frame (make-frame (cj/--agenda-frame-make-parameters)))
(select-frame-set-input-focus frame)
;; Cached, non-forced: a frame spawned early after daemon startup
@@ -716,6 +783,21 @@ out-of-range window-start nor steals focus."
(when (window-live-p prev-window)
(select-window prev-window t)))))))
+(defun cj/--agenda-frame-day-view ()
+ "Shrink the Full Agenda frame to today's single-day view.
+Sets the span to 1 and refreshes. The redo re-evaluates the span, so the
+day view survives the wall-clock refresh tick until `w' widens it again."
+ (interactive)
+ (setq cj/--agenda-frame-span 1)
+ (cj/--agenda-frame-safe-redo))
+
+(defun cj/--agenda-frame-week-view ()
+ "Restore the Full Agenda frame to the seven-day today-anchored view.
+Sets the span back to 7 and refreshes."
+ (interactive)
+ (setq cj/--agenda-frame-span 7)
+ (cj/--agenda-frame-safe-redo))
+
(defun cj/--agenda-frame-start-timer (frame)
"Start FRAME's five-minute wall-clock refresh timer, unless one exists.
Idempotent: a frame already carrying a live timer keeps it (no duplicate).
@@ -755,5 +837,11 @@ force-refresh idea in the F8 family."
;; The public gesture appears only now that the feature is complete and live.
(cj/--agenda-frame-install-keys)
+;; Install the read-only shadow now that every allowlist handler above is
+;; defined, so the walk's `commandp' guard recognizes them and preserves the
+;; allowlist regardless of whether org-agenda loaded before or after this file.
+(with-eval-after-load 'org-agenda
+ (cj/--agenda-frame-shadow-mutations))
+
(provide 'org-agenda-frame)
;;; org-agenda-frame.el ends here