diff options
-rw-r--r-- | early-init.el | 10 | ||||
-rw-r--r-- | init.el | 174 | ||||
-rw-r--r-- | lisp/+emacs.el | 4 | ||||
-rw-r--r-- | lisp/+flyspell-correct.el | 24 | ||||
-rw-r--r-- | lisp/+org-capture.el | 164 | ||||
-rw-r--r-- | lisp/+org.el | 44 | ||||
-rw-r--r-- | lisp/+ox.el | 29 | ||||
-rw-r--r-- | lisp/+titlecase.el | 32 | ||||
-rw-r--r-- | lisp/acdw.el | 48 | ||||
-rw-r--r-- | lisp/private.el | 23 |
10 files changed, 524 insertions, 28 deletions
diff --git a/early-init.el b/early-init.el index 340cbf7..bc4ccdd 100644 --- a/early-init.el +++ b/early-init.el | |||
@@ -64,7 +64,7 @@ restore that." | |||
64 | (push (locate-user-emacs-file "lisp") load-path) | 64 | (push (locate-user-emacs-file "lisp") load-path) |
65 | (require 'acdw) | 65 | (require 'acdw) |
66 | 66 | ||
67 | (+define-dir .etc (locate-user-emacs-file ".etc") | 67 | (+define-dir .etc (locate-user-emacs-file "etc") |
68 | "Directory for all of Emacs's various files. | 68 | "Directory for all of Emacs's various files. |
69 | See `no-littering' for examples.") | 69 | See `no-littering' for examples.") |
70 | 70 | ||
@@ -81,13 +81,13 @@ See `no-littering' for examples.") | |||
81 | (yoke compat "https://git.sr.ht/~pkal/compat") | 81 | (yoke compat "https://git.sr.ht/~pkal/compat") |
82 | 82 | ||
83 | (yoke no-littering "https://github.com/emacscollective/no-littering" | 83 | (yoke no-littering "https://github.com/emacscollective/no-littering" |
84 | (require 'no-littering) | ||
85 | (setq no-littering-etc-directory .etc | 84 | (setq no-littering-etc-directory .etc |
86 | no-littering-var-directory .etc | 85 | no-littering-var-directory .etc |
87 | custom-file (.etc "custom.el")) | 86 | custom-file (.etc "custom.el")) |
87 | (require 'no-littering) | ||
88 | (when (boundp 'native-comp-eln-load-path) | ||
89 | (setcar native-comp-eln-load-path (expand-file-name (.etc "eln-cache" t)))) | ||
88 | (when (boundp 'comp-eln-load-path) | 90 | (when (boundp 'comp-eln-load-path) |
89 | (setcar comp-eln-load-path (expand-file-name (.etc "eln-cache" t)))) | 91 | (setcar comp-eln-load-path (expand-file-name (.etc "eln-cache" t)))) |
90 | (when (fboundp 'startup-redirect-eln-cache) | 92 | (when (fboundp 'startup-redirect-eln-cache) |
91 | (startup-redirect-eln-cache | 93 | (startup-redirect-eln-cache (convert-standard-filename (.etc "eln-cache/"))))) |
92 | (convert-standard-filename | ||
93 | (.etc "eln-cache/"))))) | ||
diff --git a/init.el b/init.el index 843fae9..3cbf3cc 100644 --- a/init.el +++ b/init.el | |||
@@ -25,7 +25,7 @@ | |||
25 | "M-/" #'hippie-expand | 25 | "M-/" #'hippie-expand |
26 | "M-=" #'count-words | 26 | "M-=" #'count-words |
27 | "C-x C-b" #'ibuffer | 27 | "C-x C-b" #'ibuffer |
28 | "C-x 4 n" #'clone-buffer | 28 | "C-x 4 n" #'clone-buffer |
29 | "S-<down-mouse-1>" #'mouse-set-mark | 29 | "S-<down-mouse-1>" #'mouse-set-mark |
30 | "C-x 0" #'+delete-window-or-bury-buffer | 30 | "C-x 0" #'+delete-window-or-bury-buffer |
31 | "M-j" nil | 31 | "M-j" nil |
@@ -42,7 +42,9 @@ | |||
42 | (let ((buffer-quit-function #'ignore)) | 42 | (let ((buffer-quit-function #'ignore)) |
43 | (apply fn r))) | 43 | (apply fn r))) |
44 | ;; Themes | 44 | ;; Themes |
45 | (load-theme 'modus-operandi)) | 45 | (load-theme 'modus-operandi) |
46 | (set-face-attribute 'default nil :family "Comic Code" :height 100) | ||
47 | (set-face-attribute 'variable-pitch nil :family "Comic Code" :height 100)) | ||
46 | 48 | ||
47 | (yoke isearch nil | 49 | (yoke isearch nil |
48 | (define-keys (current-global-map) | 50 | (define-keys (current-global-map) |
@@ -64,7 +66,7 @@ | |||
64 | tab-always-indent 'complete | 66 | tab-always-indent 'complete |
65 | completion-in-region-function #'consult-completion-in-region | 67 | completion-in-region-function #'consult-completion-in-region |
66 | consult-narrow-key "<" | 68 | consult-narrow-key "<" |
67 | consult--regexp-compiler #'consult--orderless-regexp-compiler) | 69 | consult--regexp-compiler #'consult--default-regexp-compiler) |
68 | (advice-add #'register-preview :override #'consult-register-window) | 70 | (advice-add #'register-preview :override #'consult-register-window) |
69 | (define-keys (current-global-map) | 71 | (define-keys (current-global-map) |
70 | ;; C-c bindings (mode-specific-map) | 72 | ;; C-c bindings (mode-specific-map) |
@@ -118,7 +120,7 @@ | |||
118 | (define-key org-mode-map (kbd "M-g o") #'consult-org-heading))) | 120 | (define-key org-mode-map (kbd "M-g o") #'consult-org-heading))) |
119 | 121 | ||
120 | (yoke orderless "https://github.com/oantolin/orderless" | 122 | (yoke orderless "https://github.com/oantolin/orderless" |
121 | (require 'orderless) | 123 | (require 'orderless) |
122 | (setq completion-styles '(substring orderless basic) | 124 | (setq completion-styles '(substring orderless basic) |
123 | completion-category-defaults nil | 125 | completion-category-defaults nil |
124 | completion-category-overrides '((file (styles basic partial-completion))) | 126 | completion-category-overrides '((file (styles basic partial-completion))) |
@@ -205,3 +207,167 @@ | |||
205 | (locate-user-emacs-file "yoke/with-editor/lisp"))) | 207 | (locate-user-emacs-file "yoke/with-editor/lisp"))) |
206 | (autoload #'transient--with-suspended-override "transient") | 208 | (autoload #'transient--with-suspended-override "transient") |
207 | (autoload #'magit "magit" nil :interactive)) | 209 | (autoload #'magit "magit" nil :interactive)) |
210 | |||
211 | (yoke visual-fill-column "https://codeberg.org/joostkremers/visual-fill-column" | ||
212 | (setq visual-fill-column-center-text t) | ||
213 | (add-hook* 'visual-fill-column-mode-hook #'visual-line-mode) | ||
214 | (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) | ||
215 | |||
216 | (yoke org "https://git.savannah.gnu.org/git/emacs/org-mode.git" | ||
217 | :load (locate-user-emacs-file "yoke/org/lisp/") | ||
218 | :depends ((org-contrib "https://git.sr.ht/~bzg/org-contrib" | ||
219 | (locate-user-emacs-file "yoke/org-contrib/lisp"))) | ||
220 | ;; DON'T load system org | ||
221 | (setq load-path | ||
222 | (cl-remove-if (lambda (path) (string-match-p "lisp/org\\'" path)) load-path)) | ||
223 | (setq org-adapt-indentation nil | ||
224 | org-auto-align-tags t | ||
225 | org-archive-mark-done t | ||
226 | org-fold-catch-invisible-edits 'show-and-error | ||
227 | org-clock-clocked-in-display 'mode-line | ||
228 | org-clock-frame-title-format (cons | ||
229 | '(t org-mode-line-string) | ||
230 | (cons " --- " frame-title-format)) | ||
231 | org-clock-string-limit 7 ; just the clock bit | ||
232 | ;; org-clock-string-limit 25 ; gives enough information | ||
233 | org-clock-persist nil | ||
234 | org-confirm-babel-evaluate nil | ||
235 | org-cycle-separator-lines 0 | ||
236 | org-directory (sync/ "org/" t) | ||
237 | org-ellipsis (or truncate-string-ellipsis "…") | ||
238 | org-fontify-done-headline t | ||
239 | org-fontify-quote-and-verse-blocks t | ||
240 | org-fontify-whole-heading-line t | ||
241 | org-hide-emphasis-markers t | ||
242 | org-html-coding-system 'utf-8-unix | ||
243 | org-image-actual-width (list (* (window-font-width) | ||
244 | (- fill-column 8))) | ||
245 | org-imenu-depth 3 | ||
246 | org-indent-indentation-per-level 0 | ||
247 | org-indent-mode-turns-on-hiding-stars nil | ||
248 | org-insert-heading-respect-content t | ||
249 | org-list-demote-modify-bullet '(("-" . "+") | ||
250 | ("+" . "-")) | ||
251 | org-log-done 'time | ||
252 | org-log-into-drawer t | ||
253 | org-num-skip-commented t | ||
254 | org-num-skip-unnumbered t | ||
255 | org-num-skip-footnotes t | ||
256 | org-outline-path-complete-in-steps nil | ||
257 | org-pretty-entities t | ||
258 | org-pretty-entities-include-sub-superscripts nil | ||
259 | org-refile-targets '((nil . (:maxlevel . 2)) | ||
260 | (org-agenda-files . (:maxlevel . 1))) | ||
261 | org-refile-use-outline-path 'file | ||
262 | org-special-ctrl-a/e t | ||
263 | org-special-ctrl-k t | ||
264 | org-src-fontify-natively t | ||
265 | org-src-tab-acts-natively t | ||
266 | org-src-window-setup 'current-window | ||
267 | org-startup-truncated nil | ||
268 | org-startup-with-inline-images t | ||
269 | org-tags-column -77 ;; (- (- fill-column 1 (length org-ellipsis))) | ||
270 | org-todo-keywords | ||
271 | '((sequence "TODO(t)" "WAIT(w@/!)" "ONGOING(o@)" | ||
272 | "|" "DONE(d!)" "ASSIGNED(a@/!)") | ||
273 | (sequence "|" "CANCELED(k@)") | ||
274 | (sequence "MEETING(m)")) | ||
275 | org-use-speed-commands t | ||
276 | org-emphasis-alist '(("*" org-bold) | ||
277 | ("/" org-italic) | ||
278 | ("_" org-underline) | ||
279 | ("=" org-verbatim) | ||
280 | ("~" org-code) | ||
281 | ("+" org-strikethrough))) | ||
282 | ;; (setq org-todo-keywords | ||
283 | ;; '((sequence | ||
284 | ;; "TODO(t)" | ||
285 | ;; "NEXT(n!)" ; next action | ||
286 | ;; "DONE(d)" ; done) | ||
287 | ;; (sequence | ||
288 | ;; "WAIT(w@)" ; waiting to be actionable again | ||
289 | ;; "HOLD(h@/!)" ; actinable, but will do later | ||
290 | ;; "IDEA(i)" ; maybe someday | ||
291 | ;; "KILL(k@/!)" ; cancelled, aborted or is no longer applicable | ||
292 | ;; )))))) | ||
293 | (add-hook* 'org-mode-hook | ||
294 | #'variable-pitch-mode | ||
295 | #'visual-fill-column-mode | ||
296 | #'turn-off-auto-fill | ||
297 | #'org-indent-mode | ||
298 | #'prettify-symbols-mode | ||
299 | #'abbrev-mode) | ||
300 | (eval-after org | ||
301 | (require '+org) | ||
302 | (define-keys org-mode-map | ||
303 | "C-M-k" #'kill-paragraph | ||
304 | "C-M-t" #'transpose-paragraphs) | ||
305 | (org-clock-persistence-insinuate))) | ||
306 | |||
307 | (yoke org-agenda nil | ||
308 | (setq org-agenda-skip-deadline-if-done t | ||
309 | org-agenda-skip-scheduled-if-done t | ||
310 | org-agenda-span 10 | ||
311 | org-agenda-block-separator ?─ | ||
312 | org-agenda-time-grid | ||
313 | '((daily today require-timed) | ||
314 | (800 1000 1200 1400 1600 1800 2000) | ||
315 | " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄") | ||
316 | org-agenda-current-time-string | ||
317 | "← now ─────────────────────────────────────────────────" | ||
318 | org-agenda-include-diary nil ; I use the org-diary features | ||
319 | org-agenda-todo-ignore-deadlines 'near | ||
320 | org-agenda-todo-ignore-scheduled 'future | ||
321 | org-agenda-include-deadlines t | ||
322 | org-deadline-warning-days 0 | ||
323 | org-agenda-show-future-repeats 'next | ||
324 | org-agenda-window-setup 'current-window) | ||
325 | (setq-local-hook org-agenda-mode-hook | ||
326 | truncate-lines t) | ||
327 | (add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree)) | ||
328 | |||
329 | (yoke ox nil ; org-export | ||
330 | (eval-after org (require 'ox)) | ||
331 | (eval-after ox | ||
332 | (require '+ox) | ||
333 | (require 'ox-md nil :noerror) | ||
334 | (+org-export-pre-hooks-insinuate)) | ||
335 | (setq org-export-coding-system 'utf-8-unix | ||
336 | org-export-headline-levels 8 | ||
337 | org-export-with-drawers nil | ||
338 | org-export-with-section-numbers nil | ||
339 | org-export-with-smart-quotes t | ||
340 | org-export-with-sub-superscripts t | ||
341 | org-export-with-toc nil)) | ||
342 | |||
343 | (yoke _work (sync/ "emacs/private") | ||
344 | :depends ((+org-capture (locate-user-emacs-file "lisp")) | ||
345 | (private (locate-user-emacs-file "lisp")) | ||
346 | (bbdb "https://git.savannah.nongnu.org/git/bbdb.git" | ||
347 | (locate-user-emacs-file "yoke/bbdb/lisp")) | ||
348 | (bbdb-vcard "https://github.com/tohojo/bbdb-vcard/")) | ||
349 | (require 'bbdb) | ||
350 | (require 'private) | ||
351 | (require '_work) | ||
352 | (bbdb-initialize 'gnus 'message) | ||
353 | (setq bbdb-complete-mail-allow-cycling t)) | ||
354 | |||
355 | (yoke org-taskwise "https://codeberg.org/acdw/org-taskwise.el") | ||
356 | |||
357 | (yoke titlecase "https://codeberg.org/acdw/titlecase.el" | ||
358 | (eval-after org (require 'titlecase)) | ||
359 | (eval-after titlecase | ||
360 | (require '+titlecase) | ||
361 | (add-to-list* 'titlecase-skip-words-regexps (rx word-boundary | ||
362 | (+ (any upper digit)) | ||
363 | word-boundary)))) | ||
364 | |||
365 | (yoke flyspell-correct "https://github.com/duckwork/flyspell-correct" | ||
366 | (eval-after flyspell | ||
367 | (require 'flyspell-correct) | ||
368 | (require '+flyspell-correct) | ||
369 | (define-keys flyspell-mode-map | ||
370 | "C-;" #'flyspell-correct-wrapper | ||
371 | "<f7>" #'+flyspell-correct-buffer)) | ||
372 | (add-hook 'org-mode-hook #'flyspell-mode) | ||
373 | (setq flyspell-correct--cr-key ";")) | ||
diff --git a/lisp/+emacs.el b/lisp/+emacs.el index 6f37b83..6f40cf0 100644 --- a/lisp/+emacs.el +++ b/lisp/+emacs.el | |||
@@ -301,8 +301,8 @@ ARG is passed to `backward-kill-word'." | |||
301 | (defun +yank@indent (&rest _) | 301 | (defun +yank@indent (&rest _) |
302 | "Indent the current region." | 302 | "Indent the current region." |
303 | (indent-region (min (point) (mark)) (max (point) (mark)))) | 303 | (indent-region (min (point) (mark)) (max (point) (mark)))) |
304 | (advice-add #'yank :after #'+yank@indent) | 304 | ;; (advice-add #'yank :after #'+yank@indent) |
305 | (advice-add #'yank-pop :after #'+yank@indent) | 305 | ;; (advice-add #'yank-pop :after #'+yank@indent) |
306 | 306 | ||
307 | 307 | ||
308 | ;;; Extra functions | 308 | ;;; Extra functions |
diff --git a/lisp/+flyspell-correct.el b/lisp/+flyspell-correct.el new file mode 100644 index 0000000..f4fc956 --- /dev/null +++ b/lisp/+flyspell-correct.el | |||
@@ -0,0 +1,24 @@ | |||
1 | ;;; +flyspell-correct.el --- -*- lexical-binding: t; -*- | ||
2 | |||
3 | ;;; Code: | ||
4 | |||
5 | (require 'flyspell-correct) | ||
6 | |||
7 | (defun +flyspell-correct-buffer (&optional prefix) | ||
8 | "Run `flyspell-correct-wrapper' on all misspelled words in the buffer. | ||
9 | With PREFIX, prompt to change the current dictionary." | ||
10 | (interactive "P") | ||
11 | (flyspell-buffer) | ||
12 | (when prefix | ||
13 | (let ((current-prefix-arg nil)) | ||
14 | (call-interactively #'ispell-change-dictionary))) | ||
15 | (+with-message "Checking spelling" | ||
16 | (flyspell-correct-move (point-min) :forward :rapid))) | ||
17 | |||
18 | (defun +flyspell-correct-buffer-h (&rest _) | ||
19 | "Run `+flyspell-correct-buffer'. | ||
20 | This is suitable for placement in a hook." | ||
21 | (+flyspell-correct-buffer)) | ||
22 | |||
23 | (provide '+flyspell-correct) | ||
24 | ;;; +flyspell-correct.el ends here | ||
diff --git a/lisp/+org-capture.el b/lisp/+org-capture.el new file mode 100644 index 0000000..7ed4e00 --- /dev/null +++ b/lisp/+org-capture.el | |||
@@ -0,0 +1,164 @@ | |||
1 | ;;; +org-capture.el -*- lexical-binding: t; -*- | ||
2 | |||
3 | ;;; Code: | ||
4 | |||
5 | (require 'cl-lib) | ||
6 | (require 'acdw) | ||
7 | ;; We don't require `org-capture' here because I'll have to require this library | ||
8 | ;; to init.el /before/ org-capture is fully needed. But I do need to declare | ||
9 | ;; `org-capture-templates'. | ||
10 | (defvar org-capture-templates nil) | ||
11 | |||
12 | (defun +org-capture--get (key &optional list) | ||
13 | "Find KEY in LIST, or return nil. | ||
14 | LIST defaults to `org-capture-templates'." | ||
15 | (alist-get key (or list org-capture-templates) nil nil #'equal)) | ||
16 | |||
17 | ;; Set it up as a generic value. Based on the one for `alist-get'. | ||
18 | (gv-define-expander +org-capture--get | ||
19 | (lambda (do key &optional alist) | ||
20 | (setq alist (or alist org-capture-templates)) | ||
21 | (macroexp-let2 macroexp-copyable-p k key | ||
22 | (gv-letplace (getter setter) alist | ||
23 | (macroexp-let2 nil p `(assoc ,k ,getter 'equal) | ||
24 | (funcall do `(cdr ,p) | ||
25 | (lambda (v) | ||
26 | (macroexp-let2 nil v v | ||
27 | (let ((set-exp | ||
28 | `(if ,p (setcdr ,p ,v) | ||
29 | ,(funcall setter | ||
30 | `(cons (setq ,p (cons ,k ,v)) | ||
31 | ,getter))))) | ||
32 | `(progn | ||
33 | ,set-exp | ||
34 | ,v)))))))))) | ||
35 | |||
36 | (defun +org-capture-sort (&optional list) | ||
37 | "Sort LIST by string keys. | ||
38 | LIST is a symbol and defaults to `org-capture-templates'." | ||
39 | (setq list (or list 'org-capture-templates)) | ||
40 | (set list (sort (symbol-value list) (lambda (a b) | ||
41 | (string< (car a) (car b)))))) | ||
42 | |||
43 | (defun +org-capture-sort-after-init (&optional list) | ||
44 | "Sort LIST with `+org-capture-sort' after Emacs init." | ||
45 | (+ensure-after-init #'+org-capture-sort)) | ||
46 | |||
47 | ;;;###autoload | ||
48 | (defun +org-capture-templates-setf (key value &optional list sort-after) | ||
49 | "Add KEY to LIST, using `setf'. | ||
50 | LIST is a symbol and defaults to `org-capture-templates' -- so | ||
51 | this function sets values on a list that's structured as such. | ||
52 | |||
53 | Thus, KEY is a string key. If it's longer than one character, | ||
54 | this function will search LIST for each successive run of | ||
55 | characters before the final, ensuring sub-lists exist of the | ||
56 | form (CHARS DESCRIPTION). | ||
57 | |||
58 | For example, if KEY is \"abc\", first a LIST item of the form (a | ||
59 | DESCRIPTION), if non-existant, will be added to the list (with a | ||
60 | default description), then an item of the | ||
61 | form (\"ab\" DESCRIPTION), before adding (KEY VALUE) to the LIST. | ||
62 | |||
63 | VALUE is the template or group header required for | ||
64 | `org-capture-templates', which see. | ||
65 | |||
66 | SORT-AFTER, when set to t, will call | ||
67 | `+org-capture-templates-sort' after setting, to ensure org can | ||
68 | properly process the variable." | ||
69 | ;; LIST defaults to `org-capture-templates' | ||
70 | (declare (indent 2)) | ||
71 | (unless list (setq list 'org-capture-templates)) | ||
72 | ;; Ensure VALUE is a list to cons properly | ||
73 | (unless (listp value) (setq value (list value))) | ||
74 | (when (> (length key) 1) | ||
75 | ;; Check for existence of groups. | ||
76 | (let ((expected (cl-loop for i from 1 to (1- (length key)) | ||
77 | collect (substring key 0 i) into keys | ||
78 | finally return keys))) | ||
79 | (cl-loop for ek in expected | ||
80 | if (not (+org-capture--get ek (symbol-value list))) do | ||
81 | (setf (+org-capture--get ek (symbol-value list)) | ||
82 | (list (format "(Group %s)" ek)))))) | ||
83 | (prog1 ;; Set KEY to VALUE | ||
84 | (setf (+org-capture--get key (symbol-value list)) value) | ||
85 | ;; Sort after, maybe | ||
86 | (when sort-after (+org-capture-sort list)))) | ||
87 | |||
88 | (defun +org-template--ensure-path (keys &optional list) | ||
89 | "Ensure path of keys exists in `org-capture-templates'." | ||
90 | (unless list (setq list 'org-capture-templates)) | ||
91 | (when (> (length key) 1) | ||
92 | ;; Check for existence of groups. | ||
93 | (let ((expected (cl-loop for i from 1 to (1- (length key)) | ||
94 | collect (substring key 0 i) into keys | ||
95 | finally return keys))) | ||
96 | (cl-loop for ek in expected | ||
97 | if (not (+org-capture--get ek (symbol-value list))) do | ||
98 | (setf (+org-capture--get ek (symbol-value list)) | ||
99 | (list (format "(Group %s)" ek))))))) | ||
100 | |||
101 | (defcustom +org-capture-default-type 'entry | ||
102 | "Default template for `org-capture-templates'." | ||
103 | :type '(choice (const :tag "Entry" entry) | ||
104 | (const :tag "Item" item) | ||
105 | (const :tag "Check Item" checkitem) | ||
106 | (const :tag "Table Line" table-line) | ||
107 | (const :tag "Plain Text" plain))) | ||
108 | |||
109 | (defcustom +org-capture-default-target "" | ||
110 | "Default target for `org-capture-templates'." | ||
111 | ;; TODO: type | ||
112 | ) | ||
113 | |||
114 | (defcustom +org-capture-default-template nil | ||
115 | "Default template for `org-capture-templates'." | ||
116 | ;; TODO: type | ||
117 | ) | ||
118 | |||
119 | (defun +org-define-capture-templates-group (keys description) | ||
120 | "Add a group title to `org-capture-templates'." | ||
121 | (setf (+org-capture--get keys org-capture-templates) | ||
122 | (list description))) | ||
123 | |||
124 | ;; [[https://github.com/cadadr/configuration/blob/39813a771286e542af3aa333172858532c3bb257/emacs.d/gk/gk-org.el#L1573][from cadadr]] | ||
125 | (defun +org-define-capture-template (keys description &rest args) | ||
126 | "Define a capture template and necessary antecedents. | ||
127 | ARGS is a plist, which in addition to the additional options | ||
128 | `org-capture-templates' accepts, takes the following and places | ||
129 | them accordingly: :type, :target, and :template. Each of these | ||
130 | corresponds to the same field in `org-capture-templates's | ||
131 | docstring, which see. Likewise with KEYS and DESCRIPTION, which | ||
132 | are passed separately to the function. | ||
133 | |||
134 | This function will also create all the necessary intermediate | ||
135 | capture keys needed for `org-capture'; that is, if KEYS is | ||
136 | \"wcp\", entries for \"w\" and \"wc\" will both be ensured in | ||
137 | `org-capture-templates'." | ||
138 | (declare (indent 2)) | ||
139 | ;; Check for existence of parent groups | ||
140 | (when (> (length keys) 1) | ||
141 | (let ((expected (cl-loop for i from 1 to (1- (length keys)) | ||
142 | collect (substring 0 i) into keys | ||
143 | finally return keys))) | ||
144 | (cl-loop | ||
145 | for ek in expected | ||
146 | if (not (+org-capture--get ek org-capture-templates)) | ||
147 | do (+org-define-capture-templates-group ek (format "(Group %s)" ek))))) | ||
148 | (if (null args) | ||
149 | ;; Add the title | ||
150 | (+org-define-capture-templates-group keys description) | ||
151 | ;; Add the capture template. | ||
152 | (setf (+org-capture--get keys org-capture-templates) | ||
153 | (append (list (or (plist-get args :type) | ||
154 | +org-capture-default-type) | ||
155 | (or ( plist-get args :target) | ||
156 | +org-capture-default-target) | ||
157 | (or (plist-get args :template) | ||
158 | +org-capture-default-template)) | ||
159 | (cl-loop for (key val) on args by #'cddr | ||
160 | unless (member key '(:type :target :template)) | ||
161 | append (list key val)))))) | ||
162 | |||
163 | (provide '+org-capture) | ||
164 | ;;; +org-capture.el ends here | ||
diff --git a/lisp/+org.el b/lisp/+org.el new file mode 100644 index 0000000..b17a1fa --- /dev/null +++ b/lisp/+org.el | |||
@@ -0,0 +1,44 @@ | |||
1 | ;;; +org.el --- -*- lexical-binding: t -*- | ||
2 | |||
3 | ;;; Copy org trees as HTML | ||
4 | |||
5 | ;; Thanks to Oleh Krehel, via [[https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard][this StackExchange question]]. | ||
6 | (defun +org-export-clip-to-html | ||
7 | (&optional async subtreep visible-only body-only ext-plist post-process) | ||
8 | "Export region to HTML, and copy it to the clipboard. | ||
9 | Arguments ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST, | ||
10 | and POST-PROCESS are passed to `org-export-to-file'." | ||
11 | (interactive) ; XXX: hould this be interactive? | ||
12 | (message "Exporting Org to HTML...") | ||
13 | (let ((org-tmp-file "/tmp/org.html")) | ||
14 | (org-export-to-file 'html org-tmp-file | ||
15 | async subtreep visible-only body-only ext-plist post-process) | ||
16 | (start-process "xclip" "*xclip*" | ||
17 | "xclip" "-verbose" | ||
18 | "-i" org-tmp-file | ||
19 | "-t" "text/html" | ||
20 | "-selection" "clipboard")) | ||
21 | (message "Exporting Org to HTML...done.")) | ||
22 | |||
23 | ;; Specialized functions | ||
24 | (defun +org-export-clip-subtree-to-html () | ||
25 | "Export current subtree to HTML." | ||
26 | (interactive) | ||
27 | (+org-export-clip-to-html nil :subtree)) | ||
28 | |||
29 | ;;; Unsmartify quotes and dashes and stuff. | ||
30 | |||
31 | (defun +org-unsmartify () | ||
32 | "Replace \"smart\" punctuation with their \"dumb\" counterparts." | ||
33 | (interactive) | ||
34 | (save-excursion | ||
35 | (goto-char (point-min)) | ||
36 | (while (re-search-forward "[“”‘’–—]" nil t) | ||
37 | (let ((replace (pcase (match-string 0) | ||
38 | ((or "“" "”") "\"") | ||
39 | ((or "‘" "’") "'") | ||
40 | ("–" "--") | ||
41 | ("—" "---")))) | ||
42 | (replace-match replace nil nil))))) | ||
43 | |||
44 | (provide '+org) | ||
diff --git a/lisp/+ox.el b/lisp/+ox.el new file mode 100644 index 0000000..8748a55 --- /dev/null +++ b/lisp/+ox.el | |||
@@ -0,0 +1,29 @@ | |||
1 | ;;; +ox.el --- org-export helpers -*- lexical-binding: t; -*- | ||
2 | |||
3 | ;;; Commentary: | ||
4 | |||
5 | ;;; Code: | ||
6 | |||
7 | (require 'ox) | ||
8 | |||
9 | ;;; Run hooks before doing any exporting at all | ||
10 | |||
11 | (defcustom +org-export-pre-hook nil | ||
12 | "Functions to run /before/ `org-export-as' does anything. | ||
13 | These will run on the buffer about to be exported, NOT a copy." | ||
14 | :type 'hook) | ||
15 | |||
16 | (defun +org-export-pre-run-hooks (&rest _) | ||
17 | "Run hooks in `+org-export-pre-hook'." | ||
18 | (run-hooks '+org-export-pre-hook)) | ||
19 | |||
20 | (defun +org-export-pre-hooks-insinuate () | ||
21 | "Advise `org-export-as' to run `+org-export-pre-hook'." | ||
22 | (advice-add 'org-export-as :before #'+org-export-pre-run-hooks)) | ||
23 | |||
24 | (defun +org-export-pre-hooks-remove () | ||
25 | "Remove pre-hook advice on `org-export-as'." | ||
26 | (advice-remove 'org-export-as #'+org-export-pre-run-hooks)) | ||
27 | |||
28 | (provide '+ox) | ||
29 | ;;; +ox.el ends here | ||
diff --git a/lisp/+titlecase.el b/lisp/+titlecase.el new file mode 100644 index 0000000..9266807 --- /dev/null +++ b/lisp/+titlecase.el | |||
@@ -0,0 +1,32 @@ | |||
1 | ;;; +titlecase.el --- Titlecase extras -*- lexical-binding: t; -*- | ||
2 | |||
3 | ;;; Commentary: | ||
4 | |||
5 | ;;; Code: | ||
6 | |||
7 | (require 'titlecase) | ||
8 | |||
9 | (defun +titlecase-sentence-style-dwim (&optional arg) | ||
10 | "Titlecase a sentence. | ||
11 | With prefix ARG, toggle the value of | ||
12 | `titlecase-downcase-sentences' before sentence-casing." | ||
13 | (interactive "P") | ||
14 | (let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences) | ||
15 | titlecase-downcase-sentences))) | ||
16 | (titlecase-dwim 'sentence))) | ||
17 | |||
18 | (defun +titlecase-org-headings () | ||
19 | (interactive) | ||
20 | (save-excursion | ||
21 | (goto-char (point-min)) | ||
22 | ;; See also `org-map-tree'. I'm not using that function because I want to | ||
23 | ;; skip the first headline. A better solution would be to patch | ||
24 | ;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc). | ||
25 | (let ((level (funcall outline-level))) | ||
26 | (while (and (progn (outline-next-heading) | ||
27 | (> (funcall outline-level) level)) | ||
28 | (not (eobp))) | ||
29 | (titlecase-line))))) | ||
30 | |||
31 | (provide '+titlecase) | ||
32 | ;;; +titlecase.el ends here | ||
diff --git a/lisp/acdw.el b/lisp/acdw.el index f972d08..444f249 100644 --- a/lisp/acdw.el +++ b/lisp/acdw.el | |||
@@ -45,23 +45,6 @@ Convenience wrapper around `define-key'." | |||
45 | map) | 45 | map) |
46 | key def))))) | 46 | key def))))) |
47 | 47 | ||
48 | (defmacro setq-local-hook (hook &rest args) | ||
49 | "Run `setq-local' on ARGS when running HOOK." | ||
50 | (declare (indent 1)) | ||
51 | (let ((fn (intern (format "%s-setq-local" hook)))) | ||
52 | (when (and (fboundp fn) | ||
53 | (functionp fn)) | ||
54 | (setq args (append (function-get fn 'setq-local-hook-settings) args))) | ||
55 | (unless (and (< 0 (length args)) | ||
56 | (zerop (mod (length args) 2))) | ||
57 | (user-error "Wrong number of arguments: %S" (length args))) | ||
58 | `(progn | ||
59 | (defun ,fn () | ||
60 | ,(format "Set local variables after `%s'." hook) | ||
61 | (setq-local ,@args)) | ||
62 | (function-put ',fn 'setq-local-hook-settings ',args) | ||
63 | (add-hook ',hook #',fn)))) | ||
64 | |||
65 | (unless (fboundp 'ensure-list) | 48 | (unless (fboundp 'ensure-list) |
66 | ;; Just in case we're using an old version of Emacs. | 49 | ;; Just in case we're using an old version of Emacs. |
67 | (defun ensure-list (object) | 50 | (defun ensure-list (object) |
@@ -89,3 +72,34 @@ form (FUNCTION &optional DEPTH LOCAL)." | |||
89 | (dolist (hook (ensure-list hooks)) | 72 | (dolist (hook (ensure-list hooks)) |
90 | (dolist (fn functions) | 73 | (dolist (fn functions) |
91 | (apply #'add-hook hook (ensure-list fn))))) | 74 | (apply #'add-hook hook (ensure-list fn))))) |
75 | |||
76 | ;;; Convenience macros | ||
77 | |||
78 | (defmacro setq-local-hook (hook &rest args) | ||
79 | "Run `setq-local' on ARGS when running HOOK." | ||
80 | (declare (indent 1)) | ||
81 | (let ((fn (intern (format "%s-setq-local" hook)))) | ||
82 | (when (and (fboundp fn) | ||
83 | (functionp fn)) | ||
84 | (setq args (append (function-get fn 'setq-local-hook-settings) args))) | ||
85 | (unless (and (< 0 (length args)) | ||
86 | (zerop (mod (length args) 2))) | ||
87 | (user-error "Wrong number of arguments: %S" (length args))) | ||
88 | `(progn | ||
89 | (defun ,fn () | ||
90 | ,(format "Set local variables after `%s'." hook) | ||
91 | (setq-local ,@args)) | ||
92 | (function-put ',fn 'setq-local-hook-settings ',args) | ||
93 | (add-hook ',hook #',fn)))) | ||
94 | |||
95 | (defmacro with-message (message &rest body) | ||
96 | "Execute BODY, with MESSAGE. | ||
97 | If body executes without errors, MESSAGE...Done will be displayed." | ||
98 | (declare (indent 1)) | ||
99 | (let ((msg (gensym))) | ||
100 | `(let ((,msg ,message)) | ||
101 | (condition-case e | ||
102 | (progn (message "%s..." ,msg) | ||
103 | ,@body) | ||
104 | (:success (message "%s...done" ,msg)) | ||
105 | (t (signal (car e) (cdr e))))))) | ||
diff --git a/lisp/private.el b/lisp/private.el new file mode 100644 index 0000000..4f6115e --- /dev/null +++ b/lisp/private.el | |||
@@ -0,0 +1,23 @@ | |||
1 | ;;; private.el -*- lexical-binding: t; -*- | ||
2 | |||
3 | ;;; Commentary: | ||
4 | |||
5 | ;;; Code: | ||
6 | |||
7 | (require 'acdw) | ||
8 | |||
9 | (defgroup private nil | ||
10 | "Private things are private. Shhhhh....") | ||
11 | |||
12 | ;; Private directory | ||
13 | |||
14 | (+define-dir private/ (sync/ "emacs/private") | ||
15 | "Private secretive secrets inside.") | ||
16 | (add-to-list 'load-path private/) | ||
17 | |||
18 | ;; Load random private stuff | ||
19 | |||
20 | (require '_acdw) | ||
21 | |||
22 | (provide 'private) | ||
23 | ;;; private.el ends here | ||