blob: 9d8e81fb869fd59ec36968dccd4c1e1d88ba72ae (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
;;; ss-org.el --- Base Org configuration -*- lexical-binding: t; -*-
;;; Commentary:
;; Shared Org setup and note-opening helpers.
;;; Code:
(require 'org)
(require 'ss-core)
(defvar ss-navigation-back-stack nil
"Stack of older locations for `ss-jump-back'.")
(defvar ss-navigation-forward-stack nil
"Stack of newer locations for `ss-jump-forward'.")
(defvar ss-navigation--inhibit-recording nil
"When non-nil, suppress navigation history recording.")
(defun ss-navigation--current-location ()
"Return the current location as a marker-backed plist."
(unless (minibufferp (current-buffer))
(list :marker (copy-marker (point-marker))
:window-start
(when (window-live-p (selected-window))
(copy-marker (window-start (selected-window)))))))
(defun ss-navigation--location-valid-p (location)
"Return non-nil when LOCATION still points to a live buffer position."
(when-let ((marker (plist-get location :marker)))
(and (markerp marker)
(marker-buffer marker)
(marker-position marker))))
(defun ss-navigation--same-location-p (left right)
"Return non-nil when LEFT and RIGHT identify the same buffer position."
(and (ss-navigation--location-valid-p left)
(ss-navigation--location-valid-p right)
(eq (marker-buffer (plist-get left :marker))
(marker-buffer (plist-get right :marker)))
(= (marker-position (plist-get left :marker))
(marker-position (plist-get right :marker)))))
(defun ss-navigation--prune-stack (stack)
"Return STACK with dead or duplicate locations removed."
(let (pruned previous)
(dolist (location stack (nreverse pruned))
(when (and (ss-navigation--location-valid-p location)
(not (ss-navigation--same-location-p location previous)))
(push location pruned)
(setq previous location)))))
(defun ss-navigation--push-location (stack-symbol location &optional clear-forward)
"Push LOCATION onto STACK-SYMBOL unless it duplicates the top entry.
When CLEAR-FORWARD is non-nil, reset `ss-navigation-forward-stack'."
(when (ss-navigation--location-valid-p location)
(set stack-symbol (ss-navigation--prune-stack (symbol-value stack-symbol)))
(unless (ss-navigation--same-location-p location (car (symbol-value stack-symbol)))
(set stack-symbol (cons location (symbol-value stack-symbol))))
(when clear-forward
(setq ss-navigation-forward-stack nil))
t))
(defun ss-navigation-push-current-location ()
"Push the current location onto the back stack.
This is for significant navigation points only and clears forward history."
(interactive)
(ss-navigation--push-location
'ss-navigation-back-stack
(ss-navigation--current-location)
'clear-forward))
(defun ss-navigation--restore-location (location)
"Restore LOCATION, returning non-nil when successful."
(when (ss-navigation--location-valid-p location)
(let* ((marker (plist-get location :marker))
(buffer (marker-buffer marker))
(window-start (plist-get location :window-start)))
(switch-to-buffer buffer)
(goto-char marker)
(when (and (markerp window-start)
(eq (marker-buffer window-start) buffer)
(window-live-p (selected-window)))
(set-window-start (selected-window)
(marker-position window-start)
t))
t)))
(defun ss-navigation--jump-from-stack (stack-symbol target-symbol)
"Restore the next location from STACK-SYMBOL and push current onto TARGET-SYMBOL."
(set stack-symbol (ss-navigation--prune-stack (symbol-value stack-symbol)))
(when-let ((target (car (symbol-value stack-symbol))))
(let ((current (ss-navigation--current-location)))
(set stack-symbol (cdr (symbol-value stack-symbol)))
(when (ss-navigation--location-valid-p current)
(ss-navigation--push-location target-symbol current))
(let ((ss-navigation--inhibit-recording t))
(ss-navigation--restore-location target)))))
(defun ss-navigation--jump-via-command (command)
"Use COMMAND as a mark-ring fallback jump, recording forward history."
(when (fboundp command)
(let ((before (ss-navigation--current-location))
after)
(let ((ss-navigation--inhibit-recording t))
(condition-case nil
(call-interactively command)
(error nil)))
(setq after (ss-navigation--current-location))
(when (and (ss-navigation--location-valid-p before)
(ss-navigation--location-valid-p after)
(not (ss-navigation--same-location-p before after)))
(ss-navigation--push-location 'ss-navigation-forward-stack before)
t))))
(defun ss-jump-back ()
"Move backward through navigation history."
(interactive)
(or (ss-navigation--jump-from-stack
'ss-navigation-back-stack
'ss-navigation-forward-stack)
(ss-navigation--jump-via-command 'pop-global-mark)
(ss-navigation--jump-via-command 'pop-to-mark-command)
(progn
(message "No back location available")
nil)))
(defun ss-jump-forward ()
"Move forward through navigation history."
(interactive)
(or (ss-navigation--jump-from-stack
'ss-navigation-forward-stack
'ss-navigation-back-stack)
(progn
(message "No forward location available")
nil)))
(defun ss-navigation--record-jump (original &rest args)
"Record the pre-jump location around ORIGINAL with ARGS."
(if ss-navigation--inhibit-recording
(apply original args)
(let ((before (ss-navigation--current-location))
result)
(setq result (apply original args))
(let ((after (ss-navigation--current-location)))
(when (and (ss-navigation--location-valid-p before)
(ss-navigation--location-valid-p after)
(not (ss-navigation--same-location-p before after)))
(ss-navigation--push-location
'ss-navigation-back-stack
before
'clear-forward)))
result)))
(defun ss-navigation--advise-command (command)
"Wrap COMMAND so significant jumps record navigation history."
(unless (advice-member-p #'ss-navigation--record-jump command)
(advice-add command :around #'ss-navigation--record-jump)))
(defun ss-navigation-setup ()
"Install navigation history advice for note-related jump commands."
(dolist (command '(ss-open-journal
ss-open-journal-today-session
ss-open-journal-full
ss-open-moc))
(ss-navigation--advise-command command))
(with-eval-after-load 'org
(ss-navigation--advise-command 'org-open-at-point))
(with-eval-after-load 'org-agenda
(dolist (command '(org-agenda-goto
org-agenda-switch-to
org-agenda-open-link))
(ss-navigation--advise-command command)))
(with-eval-after-load 'denote
(ss-navigation--advise-command 'denote-open-or-create))
(with-eval-after-load 'ss-crm
(dolist (command '(ss-crm-find
ss-crm-open
ss-crm-overview))
(ss-navigation--advise-command command))))
(defvar ss-journal-session-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-c") #'ss-journal-session-save-and-dismiss)
(define-key map (kbd "C-c C-k") #'ss-journal-session-dismiss)
map)
"Keymap for focused journal editing sessions.")
(defconst ss-journal-session-header-line
"Journal session: C-c C-c save and dismiss, C-c C-k dismiss"
"Header line shown during focused journal editing sessions.")
(define-minor-mode ss-journal-session-mode
"Minor mode for focused journal editing sessions."
:lighter " Journal-Session"
:keymap ss-journal-session-mode-map
(if ss-journal-session-mode
(setq-local header-line-format ss-journal-session-header-line)
(kill-local-variable 'header-line-format)))
(defun ss-journal-session-dismiss ()
"End the focused journal session without saving automatically."
(interactive)
(widen)
(ss-journal-session-mode -1)
(quit-window nil (selected-window)))
(defun ss-journal-session-save-and-dismiss ()
"Save the journal buffer, then end the focused journal session."
(interactive)
(save-buffer)
(ss-journal-session-dismiss))
(defun ss-open-journal ()
"Open today's journal entry in a focused session, creating it when needed."
(interactive)
(ss-open-journal-today-session))
(defun ss-open-journal-today-session ()
"Open today's journal entry in a focused, dismissable session."
(interactive)
(find-file (ss-require-existing-file ss-journal-file))
(widen)
(unless (fboundp 'ss-journal-goto-date)
(user-error "Journal date navigation is unavailable"))
(ss-journal-goto-date nil 'create)
(when (fboundp 'ss-journal-ensure-day-sections)
(ss-journal-ensure-day-sections))
(org-fold-show-entry)
(org-fold-show-subtree)
(org-narrow-to-subtree)
(ss-journal-session-mode 1))
(defun ss-open-journal-full ()
"Open `ss-journal-file' with the full buffer visible."
(interactive)
(find-file (ss-require-existing-file ss-journal-file))
(widen))
(defun ss-open-moc ()
"Open the central MOC note."
(interactive)
(find-file (ss-require-existing-file ss-moc-file)))
(defun ss-org-setup ()
"Initialize base Org configuration."
(use-package org
:ensure nil
:config
(setq org-directory ss-org-directory
org-hide-emphasis-markers t
org-agenda-search-headline-for-time t
org-agenda-custom-commands
'(("c" "Clarify items" todo "CLARIFY"))
org-todo-keywords
'((sequence "TODO(t)" "CLARIFY(c)" "WAIT(w@/!)" "|"
"DONE(d)" "CANCELLED(x@)"))
org-log-done 'note
org-log-into-drawer t)
(add-hook 'org-mode-hook
(lambda ()
(setq-local org-hide-emphasis-markers t)
(when (fboundp 'olivetti-mode)
(olivetti-mode 1))
(font-lock-flush)
(font-lock-ensure))))
(use-package git-auto-commit-mode
:ensure t
:pin melpa
:commands (git-auto-commit-mode)
:init
(setq gac-shell-and
(if (string-match-p "fish\\'" shell-file-name)
" ; and "
" && ")))
(add-hook 'emacs-startup-hook
(lambda ()
(find-file (ss-require-existing-file ss-moc-file))))
(ss-navigation-setup))
(provide 'ss-org)
;;; ss-org.el ends here
|