diff options
Diffstat (limited to 'lisp')
-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 |
8 files changed, 349 insertions, 19 deletions
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 | ||