Compare commits

...

373 Commits

Author SHA1 Message Date
Case Duckworth
2673a4f1d3 Update README 2022-07-06 16:50:01 -05:00
Case Duckworth
58e163e03b apodsf8u 2022-07-06 16:47:51 -05:00
Case Duckworth
42947150ad Rename +compat.el to avoid collision; add a thing 2022-07-06 16:47:32 -05:00
Case Duckworth
4742605f98 Update readme 2022-06-15 15:25:25 -05:00
Case Duckworth
9b2324c6e0 Change readme...again 2022-06-15 10:28:01 -05:00
Case Duckworth
dbb6181a5d aodifu 2022-06-15 10:26:10 -05:00
Case Duckworth
21b5d80814 Fix startup complaining 2022-06-09 09:16:50 -05:00
Case Duckworth
af3eb37c8e blep 2022-06-08 17:59:53 -05:00
Case Duckworth
2c8a3306db Add link to new server 2022-05-27 13:40:30 -05:00
Case Duckworth
b6015fa0c2 Delete stuff 2022-05-27 13:34:03 -05:00
Case Duckworth
69a0c8c199 Change to readme.txt 2022-05-27 13:33:50 -05:00
Case Duckworth
80abceb212 asdpofiuasdpfoiasjdpfoiajsdf 2022-05-27 13:26:19 -05:00
Case Duckworth
41b2867466 Don't inhibit redisplay or message when debugging 2022-05-27 13:25:56 -05:00
Case Duckworth
3da59fb2f6 i'm doing a terrible job 2022-05-25 22:07:33 -05:00
Case Duckworth
dd3afe747e meh 2022-05-23 20:12:59 -05:00
Case Duckworth
02addc0aff Add timers 2022-05-12 22:38:33 -05:00
Case Duckworth
b3c2d9ded9 meh 2022-05-12 22:38:31 -05:00
Case Duckworth
97bbe5e322 Inhibit org-mode-hook in org-agenda 2022-05-12 22:38:01 -05:00
Case Duckworth
134409aa67 Modeline stuff! 2022-05-12 22:37:47 -05:00
Case Duckworth
1375113b0e meh 2022-05-10 13:57:28 -05:00
Case Duckworth
1492d153f0 Further mode line changes 2022-05-10 13:57:19 -05:00
Case Duckworth
a6085b58a2 Fix load order 2022-05-10 13:57:09 -05:00
Case Duckworth
f0a4617a52 Remove deprecated consult-completing-read-multiple and add indicator 2022-05-10 13:56:44 -05:00
Case Duckworth
a0b156b7e1 Merge branch 'main' of tildegit.org:acdw/emacs 2022-05-10 08:33:24 -05:00
Case Duckworth
ea979bdc41 meh 2022-05-10 08:33:14 -05:00
Case Duckworth
40c8fe07fa Add +modeline-spacer 2022-05-10 08:33:05 -05:00
Case Duckworth
4c4dd0e782 Merged 2022-05-09 20:37:17 -05:00
Case Duckworth
3cb2f98be4 meh 2022-05-09 20:36:32 -05:00
Case Duckworth
222a20c7c1 Further modeline changes 2022-05-06 13:16:16 -05:00
Case Duckworth
eaa1c58e1b Remove echo messages from jabber 2022-05-06 12:42:10 -05:00
Case Duckworth
1f8021f0ee Hook visual-fill-column-mode into org-mode 2022-05-06 12:42:01 -05:00
Case Duckworth
c66f44e360 indentation 2022-05-06 12:41:54 -05:00
Case Duckworth
2d69ea08d3 AdD sPoNgEbOb-CaSe 2022-05-06 12:41:31 -05:00
Case Duckworth
6e7d7fe47d Merge branch 'main' of tildegit.org:acdw/emacs 2022-05-06 10:23:57 -05:00
Case Duckworth
fcd47a9c3a meh 2022-05-06 10:23:02 -05:00
Case Duckworth
1df15735b2 Add +sort-lines 2022-05-06 10:21:51 -05:00
Case Duckworth
246990fd15 Make modes in which to ignore open-paragraph a customization option 2022-05-06 10:21:32 -05:00
Case Duckworth
e3356d3c39 Pull recipe repos as part of +straight-update-all 2022-05-06 10:21:12 -05:00
Case Duckworth
a8e71fa8c7 Add straight :needs 2022-05-06 10:21:02 -05:00
Case Duckworth
144e5244d2 Demote errors more better-er 2022-05-06 10:20:46 -05:00
Case Duckworth
bcf56eff21 Remove redundancy 2022-05-06 10:20:36 -05:00
Case Duckworth
46487b4a33 Add find-script.el 2022-05-06 10:20:14 -05:00
Case Duckworth
be0546d73c Don't query to install pdf-tools 2022-05-05 18:42:02 -05:00
Case Duckworth
f6512b78a2 Fix a subtle bug in +mapc-some-buffers 2022-05-05 18:41:48 -05:00
Case Duckworth
f0febf6814 Change modeline 2022-05-05 18:41:42 -05:00
Case Duckworth
517b999407 meh 2022-05-05 18:41:21 -05:00
Case Duckworth
f7099ebac6 Disable auto-insert-mode 2022-05-01 10:16:06 -05:00
Case Duckworth
5d4db143f2 Indentation 2022-05-01 09:36:27 -05:00
Case Duckworth
4e562b202c Add `+indent-rigidly'
This works line-wise if the region isn't active.
2022-05-01 09:36:07 -05:00
Case Duckworth
8360e66d11 Allow passing a list of modes to `+mapc-some-buffers'
Due to the way this is written, I can't pass one mode by itself---it must be a
list.
2022-05-01 09:26:59 -05:00
Case Duckworth
a6341764f3 Add geiser-guile 2022-05-01 09:25:49 -05:00
Case Duckworth
9a8043d49b Configure eros 2022-05-01 09:25:36 -05:00
Case Duckworth
23836a13f1 Take advantage of `eww-auto-rename-buffer' if it exists 2022-05-01 09:25:25 -05:00
Case Duckworth
13fed66dec Add bindings for consult functions 2022-05-01 09:25:12 -05:00
Case Duckworth
2cc59cf768 Add +org-wrap-on-hyphens 2022-05-01 09:24:57 -05:00
Case Duckworth
02b8882f54 Indentation 2022-05-01 09:24:44 -05:00
Case Duckworth
0730f1e1ac Add +compile-dispatch
At some point I probably should add a +recompile .. or something, I'm not sure
how the compile flow works in Emacs.
2022-05-01 09:21:51 -05:00
Case Duckworth
5c02bbc592 Remove (:quit)-ed setup forms 2022-05-01 09:21:39 -05:00
Case Duckworth
818cfc0380 Return point to the same point relative of defun in +init-sort 2022-05-01 09:11:41 -05:00
Case Duckworth
c822c9bfd1 Change org-mode pretty symbols 2022-04-28 15:50:11 -05:00
Case Duckworth
c2834c3511 Make hiding tracking-mode-line when clocked in configurable 2022-04-28 15:49:42 -05:00
Case Duckworth
7f7ede201b Dim .gitignore files (mostly) in dired 2022-04-28 15:49:22 -05:00
Case Duckworth
8565654433 Add setup forms to imenu /after/ init 2022-04-28 15:48:46 -05:00
Case Duckworth
9c5aa35b2a Move where I italicize comment face 2022-04-28 15:48:21 -05:00
Case Duckworth
bb7f256f59 Add long-s-mode 2022-04-28 15:47:34 -05:00
Case Duckworth
57f0dd43c4 bleh 2022-04-27 08:38:03 -05:00
Case Duckworth
0b573c7eba Add functions for org
These aren't bound or implemented but they're here in case I want them.
2022-04-27 08:37:11 -05:00
Case Duckworth
4b1eaab205 Add +org-show-mode
Toggle org-hide-emphasis-markers and org-link display
2022-04-27 08:36:35 -05:00
Case Duckworth
8edc3aa615 Also inhibit user-save-mode with predicates 2022-04-27 08:35:48 -05:00
Case Duckworth
340c8583cc Display fortunes in scratch buffer 2022-04-27 08:35:22 -05:00
Case Duckworth
3f925fc0e3 Add +org-export-pre-hook to prepare the buffer before export 2022-04-24 17:00:36 -05:00
Case Duckworth
3219bf88cc Correction: uh, not a typo but whatever 2022-04-24 15:03:04 -05:00
Case Duckworth
9b17702d3f Fix typo 2022-04-24 15:02:24 -05:00
Case Duckworth
47371e7484 Change prot's urls 2022-04-24 15:02:19 -05:00
Case Duckworth
0aab1430c8 Configure jabber 2022-04-24 15:02:12 -05:00
Case Duckworth
6d62265dd0 Add ement.el 2022-04-24 15:02:05 -05:00
Case Duckworth
f2b652dabc Bleh 2022-04-24 15:01:58 -05:00
Case Duckworth
57b0d19290 Refactor 2022-04-24 15:01:25 -05:00
Case Duckworth
30a7f9651b Add commentary to user-save.el 2022-04-24 15:01:13 -05:00
Case Duckworth
86c2a140a5 Change `+org-before-save@prettify-buffer' 2022-04-24 15:00:58 -05:00
Case Duckworth
d4c3d79770 Fix `+org-fix-blank-lines' 2022-04-24 15:00:41 -05:00
Case Duckworth
56b2c9fb54 Correct backward-kill-word behavior 2022-04-24 15:00:11 -05:00
Case Duckworth
d31baf887e Merge branch 'main' of tildegit.org:acdw/emacs 2022-04-20 10:47:36 -05:00
Case Duckworth
28d11fd0e8 bleh 2022-04-20 10:44:51 -05:00
Case Duckworth
a7ecf23377 Don't indicate buffer boundaries 2022-04-20 10:43:42 -05:00
Case Duckworth
1f7e7ebf24 Add org-drawer-list 2022-04-20 10:43:20 -05:00
Case Duckworth
088f933bb1 Properly set fixed-pitch height 2022-04-20 10:42:30 -05:00
Case Duckworth
53409a7eb2 Update snippets 2022-04-20 10:42:20 -05:00
Case Duckworth
5782c55e52 Enhance :straight setup form
:straight now takes care of :straight-when, :also-straight, and possibly others,
later.
2022-04-19 22:27:03 -05:00
Case Duckworth
791f486e1a Collapse redundancy 2022-04-19 22:26:26 -05:00
Case Duckworth
abce780f1b Just enable lin-global-mode 2022-04-19 22:26:11 -05:00
Case Duckworth
c4cbde3631 Customize Info+ 2022-04-19 22:25:52 -05:00
Case Duckworth
f8ce8b16f3 Remove org-contacts 2022-04-19 22:25:14 -05:00
Case Duckworth
1e11dad4a1 Indentation 2022-04-19 22:25:04 -05:00
Case Duckworth
7d71c6e5bc Add Info history commands to pulse commands 2022-04-19 22:24:16 -05:00
Case Duckworth
d26bfd92e1 Merge branch 'main' of tildegit.org:acdw/emacs 2022-04-13 10:16:19 -05:00
Case Duckworth
3c5c175b84 Configure org-mode ... some more 2022-04-13 10:15:59 -05:00
Case Duckworth
e3e399ac52 Don't indent notuch content 2022-04-13 10:15:44 -05:00
Case Duckworth
bb756ce658 Dang ol +shr.el file 2022-04-12 22:41:03 -05:00
Case Duckworth
3970a88dd9 Remove unused w3m setup form 2022-04-12 22:40:48 -05:00
Case Duckworth
ca1823d2b2 Add shr-heading from oantolin 2022-04-12 22:40:37 -05:00
Case Duckworth
b868fa30e8 Configure undoes 2022-04-12 22:40:28 -05:00
Case Duckworth
f93e8df8cf Bind org-store-link 2022-04-12 22:40:11 -05:00
Case Duckworth
a27eb917b8 Use ispell or aspell 2022-04-12 22:40:00 -05:00
Case Duckworth
4d83762191 Rebind M-u 2022-04-12 22:39:52 -05:00
Case Duckworth
35c88720df Fix requires 2022-04-12 22:39:44 -05:00
Case Duckworth
19cb761465 Require +chicken 2022-04-12 13:19:56 -05:00
Case Duckworth
c6ccd9151d Configure modus-themes 2022-04-12 13:19:49 -05:00
Case Duckworth
b391b15541 Add packages 2022-04-12 13:19:16 -05:00
Case Duckworth
e0e49f18fd Add a keybind for timer-list-cancel 2022-04-12 13:18:55 -05:00
Case Duckworth
298ebabb88 Configure tab-bar 2022-04-12 13:18:48 -05:00
Case Duckworth
7d720a4793 Configure org-mode 2022-04-12 13:18:41 -05:00
Case Duckworth
e64c1a2854 Change commenting form 2022-04-12 13:18:28 -05:00
Case Duckworth
869f2192bd Stop being compatible with Emacs < 28 2022-04-12 13:18:08 -05:00
Case Duckworth
c0a0df1e3c Add comment 2022-04-12 13:17:54 -05:00
Case Duckworth
6068ebf457 Require '_work
Might still need some ... work
2022-04-12 13:17:42 -05:00
Case Duckworth
a6db4c2295 Change auto-fill rules 2022-04-12 13:17:30 -05:00
Case Duckworth
6f1f0de1c1 Add dlet to compat.el 2022-04-12 13:16:49 -05:00
Case Duckworth
9ed685f740 Add +dired-goto-file
This function fixes dumbness in vertico-directory when jumping directories in
dired.
2022-04-12 13:16:15 -05:00
Case Duckworth
9b23b33921 Add +crux-kill-and-join-forward
This one is visual-line-mode aware
2022-04-12 13:15:36 -05:00
Case Duckworth
c0fa442767 Start the server if not already 2022-04-12 13:15:08 -05:00
Case Duckworth
f479cd9b30 Merge branch 'main' of tildegit.org:acdw/emacs 2022-04-02 13:58:17 -05:00
Case Duckworth
99ddcb1718 Experiment: remove most font-lock faces 2022-04-02 13:56:25 -05:00
Case Duckworth
94c3685e85 Remove binding for crux-visit-shell-buffer
This should've gone with eshell-whatever
2022-04-02 13:56:11 -05:00
Case Duckworth
755bd8b646 Fix arity of affe-orderless-regexp-compiler 2022-04-02 13:55:59 -05:00
Case Duckworth
191a223a1d Add $INFOPATH to Info-additional-directory-list 2022-04-02 13:55:44 -05:00
Case Duckworth
6db8a02175 Add C-x C-m binding for execute-extended-command
On recommendation of Steve Yegge
2022-04-02 13:54:52 -05:00
Case Duckworth
1d64079a52 Ignore errors in +tab-bar-notmuch-count 2022-04-02 13:54:43 -05:00
Case Duckworth
2e488c508f Add +org-unsmartify 2022-04-02 13:54:19 -05:00
Case Duckworth
2876e85cf6 Change notation of settings 2022-04-02 13:54:06 -05:00
Case Duckworth
8f8121e3a2 Add +eshell-here 2022-04-02 13:53:59 -05:00
Case Duckworth
abf24e71c7 Enable +elfeed-update-command to run only sometimes 2022-04-02 13:53:39 -05:00
Case Duckworth
8383d8cb8b Change default mpv viewer to cache 2022-04-02 13:52:59 -05:00
Case Duckworth
122ee05071 Add +chicken.el 2022-04-02 13:52:40 -05:00
Case Duckworth
2e46fd9241 Ignore scratch.el 2022-04-02 13:52:23 -05:00
Case Duckworth
548e9b8acc Add +scratch-buffer 2022-04-02 12:37:25 -05:00
Case Duckworth
a44a825f2c Merge branch 'main' of tildegit.org:acdw/emacs 2022-04-01 18:56:05 -05:00
Case Duckworth
9742b1a3c8 Only load notmuch tab-bar if notmuch is in $PATH 2022-04-01 18:55:19 -05:00
Case Duckworth
f75a2fc9e3 Fix arity of consult--orderless-regex-compiler
I might want to combine this and the one for affe.
2022-04-01 18:54:42 -05:00
Case Duckworth
173dd60dca Merge branch 'main' of tildegit.org:acdw/emacs 2022-03-30 18:16:40 -05:00
Case Duckworth
29c287a8c3 I waited way too long to make this commit 2022-03-30 18:14:56 -05:00
Case Duckworth
31f595fafa Merge branch 'main' of https://tildegit.org/acdw/emacs 2022-03-12 11:26:25 -06:00
Case Duckworth
b462cc8785 Purge org-contacts
... sort of
2022-03-12 11:23:38 -06:00
Case Duckworth
a11c0cdeb0 Fix affe-regexp-compiler arguments 2022-03-12 11:22:32 -06:00
Case Duckworth
979fa11e49 Add larry.el 2022-03-12 11:21:29 -06:00
Case Duckworth
f6512fe1bd Uh 2022-03-11 20:04:05 -06:00
Case Duckworth
8eda323c31 Add snippets 2022-03-11 20:03:38 -06:00
Case Duckworth
b867ee1889 Change org-export-clip-subtree-to-html 2022-02-28 09:40:33 -06:00
Case Duckworth
f65f4387b8 Fix elfeed-update-async niceness 2022-02-28 09:40:24 -06:00
Case Duckworth
b0346f6283 Add input method to the modeline 2022-02-28 09:40:18 -06:00
Case Duckworth
b454114819 Add org-taskwise 2022-02-28 09:39:58 -06:00
Case Duckworth
1ecd585824 Whitespace and bugginess 2022-02-28 09:39:35 -06:00
Case Duckworth
3d7fd0f224 Add clean-kill-ring 2022-02-28 09:39:24 -06:00
Case Duckworth
dcbc12ed9f Hook hl-line-mode on timer-list 2022-02-28 09:39:12 -06:00
Case Duckworth
10515f443e Bind org-clock-in and org-clock-out 2022-02-28 09:39:01 -06:00
Case Duckworth
45d179a4c3 Remove :local-repo directive
It just doesn't seem to work the way I'm using it.
2022-02-23 10:16:48 -06:00
Case Duckworth
49bd36e850 Reorganize notmuch lists 2022-02-23 10:15:50 -06:00
Case Duckworth
4326b4327f Further configure mail 2022-02-21 22:40:35 -06:00
Case Duckworth
147f94f5d2 Try to improve bongo tab-bar width 2022-02-21 21:02:08 -06:00
Case Duckworth
277dfcc6cd Add +concat and +file-string 2022-02-18 18:25:33 -06:00
Case Duckworth
5b35d5c91b Changes 2022-02-18 18:25:27 -06:00
Case Duckworth
b05b4c8107 Add diff-hl
Not sure if I'm going to keep it
2022-02-18 18:25:07 -06:00
Case Duckworth
0194e2ea2d Allow :require to take no arguments 2022-02-18 18:24:52 -06:00
Case Duckworth
977ff8c2fe Move :quit around 2022-02-18 18:20:38 -06:00
Case Duckworth
9019eb07f2 Fix +org-return-dwim to work on definition lists 2022-02-18 18:18:37 -06:00
Case Duckworth
0064d11659 Stub out +link-hint-define-keyword
I think this is still not quite right
2022-02-18 18:18:20 -06:00
Case Duckworth
7f8a95ea03 Fix some typo stuff 2022-02-18 18:18:03 -06:00
Case Duckworth
c031326367 Notmuch and stuff 2022-02-16 23:19:27 -06:00
Case Duckworth
a2dac68a29 Uhhhhh 2022-02-16 23:19:16 -06:00
Case Duckworth
dbe26df4d4 Uh 2022-02-16 23:19:11 -06:00
Case Duckworth
51f004efdf Unrequire `dash' 2022-02-16 23:19:04 -06:00
Case Duckworth
55857d7441 Customize elfeed 2022-02-16 23:18:55 -06:00
Case Duckworth
a9d596fe80 Set `find-file-visit-truename' to t
I don't know why this might be a bad idea
2022-02-16 23:18:38 -06:00
Case Duckworth
434e105886 Change,,, whatever 2022-02-16 23:18:28 -06:00
Case Duckworth
f0b220a7f4 Add +link-hint-open-chrome
I need to write a macro
2022-02-16 23:18:02 -06:00
Case Duckworth
fae8e9168f Change modeline 2022-02-16 23:17:56 -06:00
Case Duckworth
f5fcd05944 Change org capture template 2022-02-16 23:17:13 -06:00
Case Duckworth
5666ae8631 Fix org-attach
I should send a bug report...
2022-02-16 23:17:01 -06:00
Case Duckworth
2254c5c6c6 Change the dang ol' org font stuff 2022-02-16 23:16:50 -06:00
Case Duckworth
88ada4f1b9 Change notmuch whatever 2022-02-16 23:16:31 -06:00
Case Duckworth
914e5ff25a Break out dired packages into their own things 2022-02-16 23:15:47 -06:00
Case Duckworth
4219f9702b Add more domains to browse-url
Honestly, this could be ... bettered.
2022-02-16 23:14:52 -06:00
Case Duckworth
82869c1f4f Add ecomplete 2022-02-16 23:12:43 -06:00
Case Duckworth
017f1c065c Use better org faces
These work better with mixed italic, bold, and regular faces
2022-02-16 23:12:02 -06:00
Case Duckworth
90b28afdf1 Coupla typos 2022-02-16 23:11:57 -06:00
Case Duckworth
ceff2ca8f1 Add tel: and mailto: link types 2022-02-16 23:11:25 -06:00
Case Duckworth
b835fb6f90 Add :quit local macro 2022-02-16 23:10:51 -06:00
Case Duckworth
83de113f4e Add bunches of tab-bar stuff 2022-02-16 23:06:05 -06:00
Case Duckworth
21741b85e5 Add chat-disconnect 2022-02-16 23:05:55 -06:00
Case Duckworth
5738c05f5b Fix fonts 2022-02-16 23:05:47 -06:00
Case Duckworth
fe8985ae47 Write +{forward,backward}-paragraph 2022-02-08 20:59:33 -06:00
Case Duckworth
265119b750 Don't display slack team name 2022-02-08 20:59:23 -06:00
Case Duckworth
7167cd4962 Change crux-shell-buffer-name 2022-02-08 20:59:13 -06:00
Case Duckworth
a733d45b24 Add consult-notmuch 2022-02-08 20:59:02 -06:00
Case Duckworth
3b34e8cf35 Add corfu 2022-02-08 14:17:05 -06:00
Case Duckworth
62e37fdee6 Hook visual-fill-column-mode into markdown-mode 2022-02-08 14:16:40 -06:00
Case Duckworth
77626da370 Add yaoddmuse 2022-02-08 14:16:36 -06:00
Case Duckworth
5dad1aee40 Fix or comment bugs 2022-02-08 14:16:28 -06:00
Case Duckworth
2a16bbd8db Change logic in +elfeed 2022-02-08 14:15:43 -06:00
Case Duckworth
269d8f687e Make +tab-bar-{emms,bongo} work when those libraries aren't loaded 2022-02-08 14:15:03 -06:00
Case Duckworth
6d2d31a40e Don't pass the buffer to PREDICATE in `+mapc-some-buffers' 2022-02-08 14:14:34 -06:00
Case Duckworth
e0187eea1e Add variable-pitch face 2022-02-08 14:14:21 -06:00
Case Duckworth
635eee323d Customize backups 2022-02-07 17:09:45 -06:00
Case Duckworth
801ee6ec24 Add +bongo-radio 2022-02-07 17:09:41 -06:00
Case Duckworth
e8bcf53e55 Remove emms 2022-02-07 17:09:20 -06:00
Case Duckworth
5efe60c74b Undo common-lisp-indent-function
oops
2022-02-07 17:09:04 -06:00
Case Duckworth
aa7d1157b7 Change sorting to user-save-hook 2022-02-07 17:07:37 -06:00
Case Duckworth
ea5eea7046 Change lisp-indent-function to common-lisp-indent-function 2022-02-07 13:16:09 -06:00
Case Duckworth
50d7ff8077 Add :load-from 2022-02-07 13:16:04 -06:00
Case Duckworth
88e218faf2 Add bongo 2022-02-07 13:16:00 -06:00
Case Duckworth
c921132330 Add notmuch 2022-02-07 13:14:29 -06:00
Case Duckworth
3b9b91686f Unglobify user-save-mode 2022-02-07 13:14:10 -06:00
Case Duckworth
e76a106571 Add gdrive.el, which is unfinished 2022-02-06 22:18:39 -06:00
Case Duckworth
b0abd8aca6 Meh 2022-02-06 22:18:36 -06:00
Case Duckworth
f8e5e64308 Add other link types 2022-02-06 22:17:44 -06:00
Case Duckworth
a43c72e5c8 Modularize tab-bar more 2022-02-06 22:17:34 -06:00
Case Duckworth
c3ff6dc08c Add chat 2022-02-06 22:17:25 -06:00
Case Duckworth
a1be3555bc Add org sms support 2022-02-06 22:17:02 -06:00
Case Duckworth
57c91fea2f Correct elfeed-async script 2022-02-06 22:16:26 -06:00
Case Duckworth
ed8cce06cf Add +browse-url-other-window
Though I'm not sure if it works very well...
2022-02-06 22:16:07 -06:00
Case Duckworth
aa99e1b33e Sort alias file ... ? 2022-02-06 22:15:56 -06:00
Case Duckworth
9cf098bfb7 Update early-init 2022-02-06 22:15:41 -06:00
Case Duckworth
f4e794e9b0 Change fonts 2022-02-06 22:15:17 -06:00
Case Duckworth
16604de818 Change compat logic 2022-02-06 22:14:50 -06:00
Case Duckworth
4178ff9c18 Fonts 2022-02-02 18:28:50 -06:00
Case Duckworth
5455025f1b more dwimmy 2022-02-02 18:28:45 -06:00
Case Duckworth
5eea6b4919 meh 2022-02-02 18:28:39 -06:00
Case Duckworth
89a12141dc speed up init 2022-02-02 18:28:33 -06:00
Case Duckworth
3c89c1ee06 Fix spacing with +modeline-file-percentage 2022-02-01 15:29:39 -06:00
Case Duckworth
27616fa08a Change default fonts on bob 2022-02-01 15:06:16 -06:00
Case Duckworth
9b4e6f10d3 Parameterize +tab-bar-menu-bar-icon 2022-02-01 15:06:03 -06:00
Case Duckworth
2525937bc4 Remove font-lock-comment-face from mode-line things 2022-02-01 15:05:51 -06:00
Case Duckworth
1293e0d071 Hide presence notifications ??
Not sure if this is working
2022-02-01 15:05:25 -06:00
Case Duckworth
981f581188 meh 2022-02-01 15:05:20 -06:00
Case Duckworth
cd7f941ff8 Setup mode-line-bell 2022-02-01 15:05:07 -06:00
Case Duckworth
3a69dcdd37 Build geiser info 2022-02-01 15:05:01 -06:00
Case Duckworth
67cff9a99d Change a e s t h e t i c 2022-02-01 15:04:45 -06:00
Case Duckworth
7ffde2d9bb Changes 2022-01-31 17:27:39 -06:00
Case Duckworth
1b881512d1 Add flyspell to vertico-multiform-categories
NOTE: this requires my fork of flyspell-correct that sets up the proper
annotations to the completions.
2022-01-31 17:26:45 -06:00
Case Duckworth
fe79856349 Add slack 2022-01-31 17:26:39 -06:00
Case Duckworth
b19c1f98fa Update `+eshell-quit-or-delete-char' 2022-01-31 13:55:31 -06:00
Case Duckworth
5bb0040b8a Add `+org-define-capture-template' 2022-01-31 13:55:00 -06:00
Case Duckworth
f37cb20764 Rename ponder.jpg -> ponder.png 2022-01-31 13:54:40 -06:00
Case Duckworth
195618bcf3 Random stuff 2022-01-31 00:54:53 -06:00
Case Duckworth
90e13f3aac Add secret-source as an auth-source
TODO: This still needs /lots/ of work.
2022-01-31 00:54:22 -06:00
Case Duckworth
e57943cfa2 Setup bookmark.el 2022-01-31 00:54:15 -06:00
Case Duckworth
8f3631016d Add an org-capture template 2022-01-31 00:54:05 -06:00
Case Duckworth
843a784d56 Change tab-bar display 2022-01-31 00:53:58 -06:00
Case Duckworth
e8589cec6c Change face-setting logic 2022-01-31 00:53:30 -06:00
Case Duckworth
6807db4e4a gah 2022-01-29 20:05:59 -06:00
Case Duckworth
8e122e335b Move to README.md 2022-01-29 19:40:25 -06:00
Case Duckworth
304e85be3a Correct image ... ? 2022-01-29 19:37:28 -06:00
Case Duckworth
86d79d55a0 Add README and ponder.jpg 2022-01-29 19:35:11 -06:00
Case Duckworth
cd60a835bf Add plancat 2022-01-29 19:34:08 -06:00
Case Duckworth
301cd5df28 Configure emms 2022-01-29 19:33:54 -06:00
Case Duckworth
726d90d708 Remove auto-insert 2022-01-29 19:33:43 -06:00
Case Duckworth
702502bbdd Allow +open-paragraph to open a paragraph above 2022-01-28 17:25:16 -06:00
Case Duckworth
e559dd7880 Don't pad region 2022-01-28 17:25:07 -06:00
Case Duckworth
aba16d7e16 Move org-download 2022-01-28 17:25:02 -06:00
Case Duckworth
20ff854f41 Enable/disable packages and stuff 2022-01-28 17:24:53 -06:00
Case Duckworth
26b816767b Add ebuku
Also added buku and bukubrowse
2022-01-28 17:24:20 -06:00
Case Duckworth
e0b879deae Truncate lines in eshell 2022-01-28 17:24:13 -06:00
Case Duckworth
1e5c351253 Whose birthday is it? 2022-01-27 19:26:33 -06:00
Case Duckworth
10622662b8 Add bmp to images 2022-01-27 19:26:13 -06:00
Case Duckworth
55c716cbe8 Add emms 2022-01-25 16:58:26 -06:00
Case Duckworth
025c1af7f3 Various what/ever 2022-01-25 16:58:22 -06:00
Case Duckworth
97687e8d01 Don't eval after stuff 2022-01-25 16:58:11 -06:00
Case Duckworth
da29487a9b Change initial-major-mode 2022-01-25 16:58:04 -06:00
Case Duckworth
181f3530f3 Add capture templates 2022-01-25 16:57:56 -06:00
Case Duckworth
3b6dcdc3bd Add +jabber
Oops, should've been earlier /shurg
2022-01-25 16:57:38 -06:00
Case Duckworth
995f998872 Fix finger 2022-01-25 16:57:30 -06:00
Case Duckworth
3586cecd8b Allow saving ispell-local-words in .dir-locals.el
TODO: Automatically move local words to .dir-locals on save
2022-01-25 16:56:48 -06:00
Case Duckworth
d799b1cded Move truncate-lines setting to a mode-hook 2022-01-25 16:56:33 -06:00
Case Duckworth
6b355568b0 Customize jabber 2022-01-25 16:55:44 -06:00
Case Duckworth
3c30daf8a5 Add function `font-lock-todo-insinuate' 2022-01-25 16:54:47 -06:00
Case Duckworth
44ecd3b272 Here we go again 2022-01-24 13:25:41 -06:00
Case Duckworth
6de50bb864 Mark browse-url-browser-function safe 2022-01-24 13:25:05 -06:00
Case Duckworth
dbc6934ae6 Add variable +org-before-save-prettify-buffer 2022-01-24 13:24:50 -06:00
Case Duckworth
2d5cee697e Add jabber 2022-01-24 13:24:39 -06:00
Case Duckworth
10a2e1a0f4 Add machine 2022-01-24 13:24:30 -06:00
Case Duckworth
dbe223f794 Add font-lock for keywords TODO, FIXME, BUG 2022-01-24 13:24:20 -06:00
Case Duckworth
f7622fc591 Separate out package-lint-flymake setup form 2022-01-24 07:58:12 -06:00
Case Duckworth
48316556b1 Ignore eshell/* but aliases
Oops.
2022-01-21 17:47:10 -06:00
Case Duckworth
d00598afe6 Add linebreak 2022-01-21 17:41:57 -06:00
Case Duckworth
00f639319c Update elfeed-update.el 2022-01-21 17:41:51 -06:00
Case Duckworth
81fb787be4 Comment out md4rd 2022-01-21 16:41:32 -06:00
Case Duckworth
3ec991d541 Merge branch 'main' of tildegit.org:acdw/emacs 2022-01-21 16:40:36 -06:00
Case Duckworth
2b99cc25d3 Change system to machine 2022-01-21 16:40:25 -06:00
Case Duckworth
fbc03a1cdf Merge branch 'main' of https://tildegit.org/acdw/emacs 2022-01-21 16:39:12 -06:00
Case Duckworth
adf815b61b Two out of three ain't bad 2022-01-21 16:34:55 -06:00
Case Duckworth
44741fed54 Merge branch 'main' of tildegit.org:acdw/emacs 2022-01-21 09:30:31 -06:00
Case Duckworth
40a6acaf56 Being working on +elfeed-update-async 2022-01-21 09:29:49 -06:00
Case Duckworth
6ae55c4a36 Change display of tab-bar buttons 2022-01-21 09:28:52 -06:00
Case Duckworth
ef5719915d Add logging to CANCELED state
Maybe?  This isn't working right for /some reason/
2022-01-21 09:28:33 -06:00
Case Duckworth
04e917c834 Fix mode-line-bell recipe 2022-01-20 08:29:04 -06:00
Case Duckworth
2341b8279d Don't hide markdown markup
It makes it harder to read and edit.
2022-01-20 08:28:49 -06:00
Case Duckworth
043a186158 Load +straight.el 2022-01-20 08:28:37 -06:00
Case Duckworth
81c6f17854 Hm. 2022-01-18 18:16:32 -06:00
Case Duckworth
6852a7307a Ahh 2022-01-18 18:16:01 -06:00
Case Duckworth
efc08126f7 Um 2022-01-18 17:18:06 -06:00
Case Duckworth
c3601eaf2f Modify org-file-apps after loading org 2022-01-18 10:30:02 -06:00
Case Duckworth
4236a39dfb Add unworking sketch for fluid-width tabs 2022-01-17 17:13:10 -06:00
Case Duckworth
298fd65a4c Fix function calls 2022-01-17 17:13:03 -06:00
Case Duckworth
21d53603b1 Add describe-symbol to vertico-multi-.. 2022-01-17 17:12:44 -06:00
Case Duckworth
309a34cea5 Open local HTML files with `browse-url' 2022-01-17 17:12:34 -06:00
Case Duckworth
1394b10658 So! Many! Changes!! 2022-01-17 13:45:32 -06:00
Case Duckworth
ee1720b8ad Add sort-setq 2022-01-17 01:19:43 -06:00
Case Duckworth
5fe7d70d08 Add ytdious 2022-01-17 01:10:20 -06:00
Case Duckworth
0fb567e1b3 Add newer features section and page breaks 2022-01-16 23:43:22 -06:00
Case Duckworth
6d2391e0e9 Remove eshell-banner-message 2022-01-16 23:31:16 -06:00
Case Duckworth
c42eb9d62d Add menu keys for buffer and file 2022-01-16 23:31:07 -06:00
Case Duckworth
596a21b6a0 Merge branch 'main' of https://tildegit.org/acdw/emacs 2022-01-16 23:13:30 -06:00
Case Duckworth
2243b1e4ef Dammit, lots of changes 2022-01-16 23:13:11 -06:00
Case Duckworth
0a0c898a28 Un-propertize +tab-bar-misc-info 2022-01-16 16:31:17 -06:00
Case Duckworth
dba542cc18 Define `+tab-bar-extra' face
I don't think this allows any reface-whatevers.
2022-01-16 16:19:45 -06:00
Case Duckworth
84c320cda4 Make tab bar more better 2022-01-16 15:54:00 -06:00
Case Duckworth
35fbd99419 Fix typo 2022-01-16 15:53:44 -06:00
Case Duckworth
4362009bad Unignore private.el 2022-01-15 22:06:02 -06:00
Case Duckworth
7f14098962 Add web-mode 2022-01-15 22:05:50 -06:00
Case Duckworth
6f20710673 Turn and face the strange 2022-01-14 21:03:59 -06:00
Case Duckworth
0495456fb9 Ch ch ch changes 2022-01-14 21:03:36 -06:00
Case Duckworth
4b2e57e396 David Bowie 2022-01-14 17:20:23 -06:00
Case Duckworth
6ffc1e7e42 Merge branch 'main' of https://tildegit.org/acdw/emacs 2022-01-13 22:00:09 -06:00
Case Duckworth
b495c3b20c Add snippets/ 2022-01-13 21:59:30 -06:00
Case Duckworth
e037d2fb71 Whatever 2022-01-13 18:01:12 -06:00
Case Duckworth
caf8d7aa54 Add remember.el (WIP) 2022-01-12 17:40:53 -06:00
Case Duckworth
125a82251c Coupla small changes 2022-01-12 17:40:43 -06:00
Case Duckworth
2a119625e6 Bindings and stuff 2022-01-11 22:28:32 -06:00
Case Duckworth
e4f7ed9609 Lots o changes at work
I need to fix +circe-define-filter or something
2022-01-11 16:01:03 -06:00
Case Duckworth
1c6d042d2f Merge branch 'main' of https://tildegit.org/acdw/emacs 2022-01-10 23:53:12 -06:00
Case Duckworth
0e43d013ea Bleh 2022-01-10 23:52:43 -06:00
Case Duckworth
e91bb5a1be Add music to browse-url-with-mpv 2022-01-10 23:52:31 -06:00
Case Duckworth
4d1605ce45 Hook reading-mode into view-mode
I need to fix reading-mode, however.
2022-01-10 23:52:11 -06:00
Case Duckworth
dcf3a3aa02 Fix bug 2022-01-10 23:52:02 -06:00
Case Duckworth
3379638199 Change mode-line and tab-bar 2022-01-10 23:44:45 -06:00
Case Duckworth
2918cb39a2 Merge branch 'main' of https://tildegit.org/acdw/emacs 2022-01-10 08:33:43 -06:00
Case Duckworth
0adf0814ee Add markdown 2022-01-10 08:33:38 -06:00
Case Duckworth
eb40045abd Add a few packages and .. stuff 2022-01-09 23:58:33 -06:00
Case Duckworth
b7295426c9 Weekend, babee 2022-01-09 20:52:07 -06:00
Case Duckworth
9360a54e62 Gah, so many changes 2022-01-07 17:30:46 -06:00
Case Duckworth
04a90b906c Add apheleia 2022-01-07 17:30:27 -06:00
Case Duckworth
1eb9b6cb39 Make elfeed load the database before opening 2022-01-07 00:05:45 -06:00
Case Duckworth
f0f031fafe Begin customizing embark 2022-01-06 23:21:59 -06:00
Case Duckworth
f0294f7fb4 Lots of other changes 2022-01-06 22:55:26 -06:00
Case Duckworth
9b9d026b61 Add god-mode
I'm trying this out ... again
2022-01-06 22:54:56 -06:00
Case Duckworth
1aa0df7631 Merge branch 'main' of https://tildegit.org/acdw/emacs 2022-01-06 18:04:18 -06:00
Case Duckworth
6c2b242a8a Fixup +defvar to be in line with defvar 2022-01-06 18:01:56 -06:00
Case Duckworth
00da04a0fd Add +org-horizontal-rule 2022-01-06 18:01:27 -06:00
Case Duckworth
e4ce00f4d5 Add /POKE stub command
TODO : I've got to write these commands!!!
2022-01-06 16:43:31 -06:00
Case Duckworth
13cbd6644a Add option to user-save buffers when quitting Emacs 2022-01-06 16:43:16 -06:00
Case Duckworth
be15049058 Add some extra functions 2022-01-06 15:49:32 -06:00
Case Duckworth
4d2a9603ad Disable electric-pair-mode in lui-mode, and define 'irc alias 2022-01-06 15:49:01 -06:00
Case Duckworth
75e03850c0 Fix naming bug
I also added aliases just in case I forget the proper names for these maps again.
2022-01-06 15:48:30 -06:00
Case Duckworth
4dee486f1f Add +kill-word-backward-or-region 2022-01-06 15:48:00 -06:00
Case Duckworth
84139db9a8 Add user-save-mode 2022-01-06 15:47:20 -06:00
Case Duckworth
81152ca242 Add +flyspell-correct-buffer 2022-01-06 15:46:50 -06:00
Case Duckworth
5630465877 Add find-variable to +lookup-map 2022-01-06 08:55:43 -06:00
Case Duckworth
58d096ff3c Fix :setup and :setup-when
Now we have better warnings and messages too
2022-01-05 18:42:02 -06:00
Case Duckworth
de01982128 Immediately load custom-file 2022-01-05 18:39:08 -06:00
Case Duckworth
5ccf1ad613 Require exec-path-from-shell 2022-01-05 18:20:40 -06:00
Case Duckworth
724c0a6fce Fix :setup form
I had to end the `ignore-errors' form with t
2022-01-05 18:19:47 -06:00
Case Duckworth
9e46efac61 Lots of changes, maybe breaking something
I have to do a big debugging tonight.  Keybinds aren't getting picked up, idk
what's going on.
2022-01-05 17:12:32 -06:00
Case Duckworth
084618a930 Break function into library 2022-01-05 17:12:03 -06:00
Case Duckworth
2c86771b1d Add form-feed 2022-01-05 11:31:12 -06:00
Case Duckworth
1b9d1c5844 Add +defvar
For those lazy times when I want to reset a variable that's defvared
2022-01-05 11:26:05 -06:00
Case Duckworth
3962ebb9bc Improve +sunrise-sunset
TODO: At some point, I should break this into a standalone package.
2022-01-05 11:25:37 -06:00
Case Duckworth
9ba030fff8 Fix +Emacs lisp auto-insert 2022-01-05 11:25:16 -06:00
Case Duckworth
461e98df20 Add dictionary lookup to +lookup-map 2022-01-05 11:25:00 -06:00
Case Duckworth
f66b316f8a Add +link-hint-map 2022-01-05 11:24:46 -06:00
Case Duckworth
b2980816c5 Don't ensure functions in :+key, :+leader, :+menu 2022-01-05 11:24:22 -06:00
Case Duckworth
a3fc41f61b Change +casing keymap 2022-01-05 11:24:05 -06:00
Case Duckworth
a59178fc7b Add +lookup 2022-01-05 11:23:47 -06:00
Case Duckworth
710dfe7cd5 Add wrap-region and modify expand-region to match 2022-01-05 11:23:26 -06:00
86 changed files with 7877 additions and 1265 deletions

9
.gitignore vendored
View File

@ -11,9 +11,14 @@ feeds.txt
gnus/
old/
pkg/
private.el
racket-mode/
server/
straight/
transient/
var/
var/
eshell/*
!eshell/aliases
url/
# put random stuff in here
scratch.el

View File

@ -21,29 +21,50 @@
;;; Code:
(define-advice load (:before (feature &rest _))
"Message the user when loading a library."
(with-temp-message (format "Now loading: '%s'" feature)))
;;; Speed up init
(setq gc-cons-threshold most-positive-fixnum)
;; Restore things after init
(defvar +emacs--startup-restore-alist nil
"Variables and values to restore after init.")
(add-hook 'emacs-startup-hook
(defun emacs-startup@restore-values ()
"Restore values set during early-init for speed."
(setq gc-cons-threshold 134217728 ; 128mb
;; I don't do the common `file-name-handler-alist' thing here
;; because of a weirdness where my Emacs doesn't know how to
;; load bookmark.el.gz when initializing.
)))
"Restore values set during init.
This applies values in `+emacs--startup-restore-alist'."
(dolist (a +emacs--startup-restore-alist)
(set (car a) (cdr a)))))
(defun +set-during-startup (variable value &optional restore)
"Set VARIABLE to VALUE during startup, but restore to RESTORE.
If RESTORE is nil or not passed, save the original value and
restore that."
(unless after-init-time
(setf (alist-get variable +emacs--startup-restore-alist)
(or restore (symbol-value variable)))
(set-default variable value)))
;; Garbage collection
(+set-during-startup 'gc-cons-threshold most-positive-fixnum)
(add-hook 'minibuffer-setup-hook (defun garbage-collect@minibuffer-enter ()
(setq gc-cons-threshold most-positive-fixnum)))
(add-hook 'minibuffer-exit-hook (defun garbage-collect@minibuffer-exit ()
(setq gc-cons-threshold 800000)))
;; Don't prematurely re-display
(unless debug-on-error
(+set-during-startup 'inhibit-redisplay t)
(+set-during-startup 'inhibit-message t))
;; Debug during init
(unless (eq debug-on-error 'startup)
(+set-during-startup 'debug-on-error 'init))
;;; Set up extra load paths and functionality
(push (locate-user-emacs-file "lisp") load-path)
(add-to-list 'load-path (locate-user-emacs-file "lisp/compat") :append)
(require 'acdw)
(require 'compat)
(+define-dir .etc (locate-user-emacs-file ".etc")
"Directory for all of Emacs's various files.
@ -52,13 +73,6 @@ See `no-littering' for examples.")
(+define-dir sync/ (expand-file-name "~/Sync")
"My Syncthing directory.")
;; Load system-specific changes.
(progn (require 'system)
(setq system-default-font "DejaVu Sans Mono"
system-variable-pitch-font "DejaVu Sans")
(setq system-load-directory (sync/ "emacs/systems/" t))
(system-settings-load nil :nowarn))
;;; Default frame settings
(setq default-frame-alist '((tool-bar-lines . 0)
@ -70,38 +84,22 @@ See `no-littering' for examples.")
window-resize-pixelwise t
inhibit-x-resources t
indicate-empty-lines nil
indicate-buffer-boundaries '((top . right)
(bottom . right)))
indicate-buffer-boundaries nil
;; '((top . right)
;; (bottom . right))
)
;;; Fonts
;;; No littering!
;; We install `no-littering' package below, but we can set the variables now.
;; Set default faces
(setq no-littering-etc-directory .etc
no-littering-var-directory .etc
straight-base-dir .etc)
(let ((font-name system-default-font)
(font-size system-default-height)
(variable-font-name system-variable-pitch-font)
(variable-font-size system-variable-pitch-height))
(set-face-attribute 'default nil :family system-default-font
:height font-size :weight 'book)
(set-face-attribute 'italic nil :family font-name
:height font-size :slant 'italic)
(set-face-attribute 'variable-pitch nil :family variable-font-name
:height variable-font-size))
;; https://github.com/emacscollective/no-littering/wiki/Setting-gccemacs'-eln-cache
;; Emoji fonts
(let ((ffl (font-family-list)))
(dolist (font '("Noto Color Emoji"
"Noto Emoji"
"Segoe UI Emoji"
"Apple Color Emoji"
"FreeSans"
"FreeMono"
"FreeSerif"
"Unifont"
"Symbola"))
(when (member font ffl)
(set-fontset-font t 'symbol (font-spec :family font) nil :append))))
(when (boundp 'comp-eln-load-path)
(setcar comp-eln-load-path (expand-file-name (.etc "eln-cache" t))))
;;; Packages
@ -112,28 +110,25 @@ See `no-littering' for examples.")
straight-check-for-modifications '(check-on-save
find-when-checking))
(setq no-littering-etc-directory .etc
no-littering-var-directory .etc
straight-base-dir .etc)
;; Bootstrap straight.el
;; https://github.com/raxod502/straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name
"straight/repos/straight.el/bootstrap.el"
straight-base-dir))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
(concat "https://raw.githubusercontent.com/"
"raxod502/straight.el/develop/install.el")
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(+with-message "Bootstrapping straight"
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name
"straight/repos/straight.el/bootstrap.el"
straight-base-dir))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
(concat "https://raw.githubusercontent.com/"
"raxod502/straight.el/develop/install.el")
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage)))
;; Early-loaded packages -- those that, for some reason or another,
;; need to be ensured to be loaded first.
@ -142,14 +137,20 @@ See `no-littering' for examples.")
(dolist (pkg '(el-patch
no-littering
setup))
setup
straight ; already installed, but what the hell
))
(straight-use-package pkg)
(require pkg)
(require (intern (format "+%s" pkg)) nil :noerror))
;; Setup `setup'
(add-to-list 'setup-modifier-list 'setup-wrap-to-demote-errors)
(add-to-list 'setup-modifier-list '+setup-wrap-to-demote-errors)
(unless (memq debug-on-error '(nil init))
(define-advice setup (:around (fn head &rest args) +setup-report)
(+with-progress ((format "[Setup] %S..." head))
(apply fn head args))))
;;; Appendix

4
eshell/aliases Normal file
View File

@ -0,0 +1,4 @@
alias sudo eshell/sudo $*
alias ff find-file $1
alias e find-file $1
alias edit find-file $1

2139
init.el

File diff suppressed because it is too large Load Diff

View File

@ -13,5 +13,72 @@ arg reversed."
(interactive "P" Info-mode)
(Info-copy-current-node-name (unless arg 0)))
(defun +Info-modeline-breadcrumbs ()
(let ((nodes (Info-toc-nodes Info-current-file))
(node Info-current-node)
(crumbs ())
(depth Info-breadcrumbs-depth-internal)
(text ""))
;; Get ancestors from the cached parent-children node info
(while (and (not (equal "Top" node)) (> depth 0))
(setq node (nth 1 (assoc node nodes)))
(when node (push node crumbs))
(setq depth (1- depth)))
;; Add bottom node.
(setq crumbs (nconc crumbs (list Info-current-node)))
(when crumbs
;; Add top node (and continuation if needed).
(setq crumbs (cons "Top" (if (member (pop crumbs) '(nil "Top"))
crumbs
(cons nil crumbs))))
(dolist (node crumbs)
(let ((crumbs-map (make-sparse-keymap))
(menu-map (make-sparse-keymap "Breadcrumbs in Mode Line")))
(define-key crumbs-map [mode-line mouse-3] menu-map)
(when node
(define-key menu-map [Info-prev]
`(menu-item "Previous Node" Info-prev
:visible ,(Info-check-pointer "prev[ious]*") :help "Go to the previous node"))
(define-key menu-map [Info-next]
`(menu-item "Next Node" Info-next
:visible ,(Info-check-pointer "next") :help "Go to the next node"))
(define-key menu-map [separator] '("--"))
(define-key menu-map [Info-breadcrumbs-in-mode-line-mode]
`(menu-item "Toggle Breadcrumbs" Info-breadcrumbs-in-mode-line-mode
:help "Toggle displaying breadcrumbs in the Info mode-line"
:button (:toggle . Info-breadcrumbs-in-mode-line-mode)))
(define-key menu-map [Info-set-breadcrumbs-depth]
`(menu-item "Set Breadcrumbs Depth" Info-set-breadcrumbs-depth
:help "Set depth of breadcrumbs to show in the mode-line"))
(setq node (if (equal node Info-current-node)
(propertize
(replace-regexp-in-string "%" "%%" Info-current-node)
'face 'mode-line-buffer-id
'help-echo "mouse-1: Scroll back, mouse-2: Scroll forward, mouse-3: Menu"
'mouse-face 'mode-line-highlight
'local-map
(progn
(define-key crumbs-map [mode-line mouse-1] 'Info-mouse-scroll-down)
(define-key crumbs-map [mode-line mouse-2] 'Info-mouse-scroll-up)
crumbs-map))
(propertize
node
'local-map (progn (define-key crumbs-map [mode-line mouse-1]
`(lambda () (interactive) (Info-goto-node ,node)))
(define-key crumbs-map [mode-line mouse-2]
`(lambda () (interactive) (Info-goto-node ,node)))
crumbs-map)
'mouse-face 'mode-line-highlight
'help-echo "mouse-1, mouse-2: Go to this node; mouse-3: Menu")))))
(let ((nodetext (if (not (equal node "Top"))
node
(concat (format "(%s)" (if (stringp Info-current-file)
(file-name-nondirectory Info-current-file)
;; Some legacy code can still use a symbol.
Info-current-file))
node))))
(setq text (concat text (if (equal node "Top") "" " > ") (if node nodetext "...")))))
text)))
(provide '+Info)
;;; +Info.el ends here

View File

@ -10,15 +10,21 @@
;; This is stolen from ace-window.el but with the mode-line stuff ripped out.
:global t
(if +ace-window-display-mode
(progn
(progn ; Enable
(aw-update)
(force-mode-line-update t)
(add-hook 'window-configuration-change-hook 'aw-update)
(add-hook 'after-make-frame-functions 'aw--after-make-frame t)
(advice-add 'aw--lead-overlay :override 'ignore))
(remove-hook 'window-configuration-change-hook 'aw-update)
(remove-hook 'after-make-frame-functions 'aw--after-make-frame)
(advice-remove 'aw--lead-overlay 'ignore)))
(progn ; Disable
(remove-hook 'window-configuration-change-hook 'aw-update)
(remove-hook 'after-make-frame-functions 'aw--after-make-frame)
(advice-remove 'aw--lead-overlay 'ignore))))
;; (defun +ace-window--mode-line-hint (path leaf)
;; (let ((wnd (cdr leaf)))
;; (with-selected-window wnd
;; ())))
;;;###autoload
(defun +ace-window-or-switch-buffer (arg)
@ -30,8 +36,5 @@ Switch to most recent buffer otherwise."
(switch-to-buffer nil)
(ace-window arg)))
(defun +ace-window@disable-overlay (_fn &rest _args)
"ADVICE for FN `aw--lead-overlay' (and ARGS) to not show overlays.")
(provide '+ace-window)
;;; +ace-window.el ends here

17
lisp/+apheleia.el Normal file
View File

@ -0,0 +1,17 @@
;;; +apheleia.el -*- lexical-binding: t; -*-
;;; Code:
(require 'cl-lib)
;; https://github.com/raxod502/apheleia/pull/63#issue-1077529623
(cl-defun +apheleia-indent-region (&key buffer scratch formatter callback &allow-other-keys)
(with-current-buffer scratch
(setq-local indent-line-function
(buffer-local-value 'indent-line-function buffer))
(indent-region (point-min)
(point-max))
(funcall callback)))
(provide '+apheleia)
;;; +apheleia.el ends here

View File

@ -17,5 +17,81 @@
(cdr (ring-ref avy-ring 0))))
t)
;;; Remove `buffer-face-mode' when avy is active.
(defcustom +avy-buffer-face-functions '(avy-goto-char
avy-goto-char-in-line
avy-goto-char-2
avy-goto-char-2-above
avy-goto-char-2-below
avy-goto-word-0
avy-goto-whitespace-end
avy-goto-word-0-above
avy-goto-word-0-below
avy-goto-whitespace-end-above
avy-goto-whitespace-end-below
avy-goto-word-1
avy-goto-word-1-above
avy-goto-word-1-below
avy-goto-symbol-1
avy-goto-symbol-1-above
avy-goto-symbol-1-below
avy-goto-subword-0
avy-goto-subword-1
avy-goto-word-or-subword-1
avy-goto-line
avy-goto-line-above
avy-goto-line-below
avy-goto-end-of-line
avy-goto-char-timer)
"Functions to disable `buffer-face-mode' during.")
(defvar-local +avy-buffer-face-mode-face nil
"The state of `buffer-face-mode' before calling `avy-with'.")
;;; XXX: Doesn't switch back if avy errors out or quits
(defun +avy@un-buffer-face (win)
"BEFORE advice on `avy-with' to disable `buffer-face-mode'."
(with-current-buffer (window-buffer win)
(when buffer-face-mode
(setq +avy-buffer-face-mode-face buffer-face-mode-face)
(buffer-face-mode -1))))
(defun +avy@re-buffer-face (win)
"AFTER advice on `avy-with' to re-enable `buffer-face-mode'."
(with-current-buffer (window-buffer win)
(when +avy-buffer-face-mode-face
(setq buffer-face-mode-face +avy-buffer-face-mode-face)
(buffer-face-mode +1)))
(let ((bounds (bounds-of-thing-at-point 'symbol)))
(when (and (car bounds)
(cdr bounds))
(pulse-momentary-highlight-region (car bounds) (cdr bounds)))))
(defun +avy@buffer-face (fn &rest r)
"AROUND advice for avy to dis/enable `buffer-face-mode'."
(if avy-all-windows
(walk-windows #'+avy@un-buffer-face nil (eq avy-all-windows 'all-frames)))
(condition-case e
(apply fn r)
((quit error) (message "Avy: %S" e) nil)
(:sucess e))
(if avy-all-windows
(walk-windows #'+avy@re-buffer-face nil (eq avy-all-windows 'all-frames))))
(define-minor-mode +avy-buffer-face-mode
"Turn off `buffer-face-mode' before doing Avy selections.
Restore the mode after the selection."
:lighter ""
:global t
(setq +avy-buffer-face-mode-face nil)
(cond
(+avy-buffer-face-mode
(dolist (fn +avy-buffer-face-functions)
(advice-add fn :around #'+avy@buffer-face)))
(t (dolist (fn +avy-buffer-face-functions)
(advice-remove fn #'+avy@buffer-face)))))
(provide '+avy)
;;; avy.el ends here

60
lisp/+bongo.el Normal file
View File

@ -0,0 +1,60 @@
;;; +bongo.el --- customizations in bongo -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(defgroup +bongo nil
"Extra customization for `bongo'."
:group 'bongo)
(defun +bongo-notify ()
(notifications-notify
:title "Now Playing"
:body (let ((bongo-field-separator "
"))
(substring-no-properties (bongo-formatted-infoset)))
:urgency 'low
:transient t))
(defun +bongo-stop-all ()
"Ensure only one bongo playlist is playing at a time.
This is intended to be :before advice to `bongo-play'."
(mapc (lambda (b)
(with-current-buffer b
(when-let* ((modep (derived-mode-p
'bongo-playlist-mode))
(bongo-playlist-buffer b)
(playingp (bongo-playing-p)))
(bongo-stop))))
(buffer-list)))
;;; Bongo Radio
(defcustom +bongo-radio-stations nil
"Stations to play using `+bongo-radio'.")
(defcustom +bongo-radio-buffer-name "*Bongo Radio*"
"Name of the buffer that holds all bongo radio stations."
:type 'string)
(defun +bongo-radio ()
(interactive)
(switch-to-buffer (or (get-buffer +bongo-radio-buffer-name)
(+bongo-radio-init))))
(defun +bongo-radio-init ()
(interactive)
(let ((bongo-playlist-buffer (get-buffer-create +bongo-radio-buffer-name))
(bongo-confirm-flush-playlist nil))
(with-bongo-playlist-buffer
(bongo-playlist-mode)
(bongo-flush-playlist :delete-all)
(cl-loop for (name . url) in +bongo-radio-stations
do (bongo-insert-uri url name)))
(prog1 (switch-to-buffer bongo-playlist-buffer)
(goto-char (point-min)))))
(provide '+bongo)
;;; +bongo.el ends here

View File

@ -11,21 +11,25 @@
;;; URL Handlers
(defun +browse-url-set-handlers (handlers)
"Set handlers for `browse-url'.
(defun +browse-url-set-handlers (&optional handlers)
"Set HANDLERS for `browse-url'.
Set `browse-url-handlers', if they exist; else
`browse-url-browser-function'. The reason for this switch is
that the latter is deprecated in Emacs 28+."
(set-default (if (boundp 'browse-url-handlers)
'browse-url-handlers
'browse-url-browser-function)
handlers))
that the latter is deprecated in Emacs 28+.
If HANDLERS is absent or nil, recompute handlers. This can be
useful when changing the default browser."
(let ((h (if (boundp 'browse-url-handlers)
'browse-url-handlers
'browse-url-browser-function)))
(set-default h (or handlers (symbol-value h)))))
(cl-defmacro +browse-url-make-external-viewer-handler
(viewer default-args &optional (prompt "URL: ")
&key
(custom-group '+browse-url)
(name (format "+browse-url-with-%s" viewer)))
(name (format "+browse-url-with-%s" viewer))
(fallback #'browse-url-generic))
"Create a `browse-url' handler function that calls VIEWER on the url.
Also create a `customize' setting in CUSTOM-GROUP for VIEWER's
arguments. DEFAULT-ARGS specifies the default arguments that
@ -33,7 +37,10 @@ setting should have. PROMPT will be shown to user in the
function's `interactive' spec, as an argument to
`browse-url-interactive-arg'. The resulting function will be
named NAME, defaulting to \"+browse-url-with-VIEWER\", and the variable
\"NAME-args\"."
\"NAME-args\".
If FALLBACK is non-nil, it's a function to fallback on if the
`start-process' call fails in anyway."
(declare (indent 1))
`(progn
(defcustom ,(intern (format "%s-args" name))
@ -41,19 +48,24 @@ named NAME, defaulting to \"+browse-url-with-VIEWER\", and the variable
,(format "Arguments to pass to %s in `%s'." viewer name)
:type '(repeat :tag "Command-line argument" string)
:group ',custom-group)
(defun ,(intern name) (url &optional _new-window)
(defun ,(intern name) (url &optional new-window)
,(format "Open URL in %s." viewer)
(interactive (browse-url-interactive-arg ,prompt))
(let* ((url (browse-url-encode-url url))
(process-environment (browse-url-process-environment)))
(message ,(format "Opening %%s in %s..." viewer) url)
(apply #'start-process
(concat ,viewer " " url) nil
,viewer
(append ,(intern (format "%s-args" name)) (list url)))))))
(unless (ignore-errors
(apply #'start-process
(concat ,viewer " " url) nil
,viewer
(append ,(intern (format "%s-args" name))
(list url))))
(funcall fallback url new-window))))))
;; Reference implementation: mpv
(+browse-url-make-external-viewer-handler "mpv" nil "Video URL: ")
(+browse-url-make-external-viewer-handler "mpv" '("--cache-pause-wait=30"
"--cache-pause-initial=yes")
"Video URL: ")
;; And feh too
(+browse-url-make-external-viewer-handler "feh" '("--auto-zoom"
"--geometry" "800x600"))
@ -108,9 +120,9 @@ ARGS are ignored here, but passed on for later processing."
;; along with the rest of the args, in a list to the original caller (probably
;; `browse-url'.)
(apply 'list
(cl-loop with url = (substring-no-properties
(if (consp url) (car url) url))
for (regex . transformation) in +browse-url-transformations
(cl-loop with url = (substring-no-properties
(if (consp url) (car url) url))
for (regex . transformation) in +browse-url-transformations
if (string-match regex url)
return (replace-match transformation nil nil url)
;; else
@ -134,5 +146,11 @@ When using this mode, ensure that the transformed URL is also in
(define-global-minor-mode +browse-url-transform-url-global-mode
+browse-url-transform-url-mode +browse-url-transform-url-mode)
(defun +browse-url-other-window (&rest args)
"Browse URL in the other window."
(let ((browsed (apply #'browse-url args)))
(when (bufferp browsed)
(switch-to-buffer-other-window browsed))))
(provide '+browse-url)
;;; +browse-url.el ends here

View File

@ -4,13 +4,6 @@
(require 'thingatpt)
(defvar +casing-map (let ((map (make-sparse-keymap)))
(define-key map "u" #'+upcase-dwim)
(define-key map "l" #'+downcase-dwim)
(define-key map "c" #'+capitalize-dwim)
map)
"Keymap for word-casing.")
;;;###autoload
(defun +upcase-dwim (arg)
"Upcase words in the region, or upcase word at point.
@ -24,9 +17,10 @@ Otherwise, it calls `upcase-word' on the word at point (using
(word-bound (save-excursion
(skip-chars-forward "^[:word:]")
(bounds-of-thing-at-point 'word))))
(upcase-region (car word-bound) (cdr word-bound))
(goto-char (cdr word-bound))
(upcase-word following))))
(when (and (car word-bound) (cdr word-bound))
(upcase-region (car word-bound) (cdr word-bound))
(goto-char (cdr word-bound))
(upcase-word following)))))
;;;###autoload
(defun +downcase-dwim (arg)
@ -41,9 +35,10 @@ Otherwise, it calls `downcase-word' on the word at point (using
(word-bound (save-excursion
(skip-chars-forward "^[:word:]")
(bounds-of-thing-at-point 'word))))
(downcase-region (car word-bound) (cdr word-bound))
(goto-char (cdr word-bound))
(downcase-word following))))
(when (and (car word-bound) (cdr word-bound))
(downcase-region (car word-bound) (cdr word-bound))
(goto-char (cdr word-bound))
(downcase-word following)))))
;;;###autoload
(defun +capitalize-dwim (arg)
@ -58,11 +53,30 @@ Otherwise, it calls `capitalize-word' on the word at point (using
(word-bound (save-excursion
(skip-chars-forward "^[:word:]")
(bounds-of-thing-at-point 'word))))
(capitalize-region (car word-bound) (cdr word-bound))
(goto-char (cdr word-bound))
(capitalize-word following))))
(when (and (car word-bound) (cdr word-bound))
(capitalize-region (car word-bound) (cdr word-bound))
(goto-char (cdr word-bound))
(capitalize-word following)))))
;; Later on, I'll add repeat maps and stuff in here...
(defvar +casing-map (let ((map (make-sparse-keymap)))
(define-key map "u" #'+upcase-dwim)
(define-key map (kbd "M-u") #'+upcase-dwim)
(define-key map "l" #'+downcase-dwim)
(define-key map (kbd "M-l") #'+downcase-dwim)
(define-key map "c" #'+capitalize-dwim)
(define-key map (kbd "M-c") #'+capitalize-dwim)
map)
"Keymap for case-related twiddling.")
(define-minor-mode +casing-mode
"Enable easy case-twiddling commands."
:lighter " cC"
:global t
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd "M-c") +casing-map)
map))
(provide '+casing)
;;; +casing.el ends here

22
lisp/+chicken.el Normal file
View File

@ -0,0 +1,22 @@
;;; +chicken.el --- Chicken Scheme additions -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
;; Reload [[https://wiki.call-cc.org/eggref/5/awful][awful]] with a keybinding
(defun +chicken-awful-reload ()
"Reload awful by visiting /reload."
(interactive)
(save-buffer)
(condition-case e
(url-retrieve-synchronously "http://localhost:8080/reload")
(file-error (progn
(message "Couldn't ping awful's server. Starting...")
(start-process "awful" (generate-new-buffer "*awful*")
"awful" "--development-mode" (buffer-file-name))))
(t (message "Some awful error occurred!"))))
(provide '+chicken)
;;; +chicken.el ends here

View File

@ -45,21 +45,27 @@
;;; Channel information
(defvar-local +circe-current-topic ""
"Cached topic of the buffer's channel.")
(defun +circe-current-topic (&optional message)
"Return the topic of the current channel.
When called with optional MESSAGE non-nil, or interactively, also
message the current topic."
(interactive "p")
(let ((topic
(save-excursion
(goto-char (point-max))
(or (re-search-backward
(rx (group "*** "
(or "Topic" "topic" "TOPIC")
(* (not ":")) ": ")
(group (+ nonl)))))
(buffer-substring-no-properties
(match-beginning 2) (match-end 2)))))
(or (save-excursion
(goto-char (point-max))
(and (re-search-backward
(rx (group "*** "
(or "Topic" "topic" "TOPIC")
(* (not ":")) ": ")
(group (+ nonl)))
nil t)
(buffer-substring-no-properties
(match-beginning 2) (match-end 2))))
+circe-current-topic)))
(setq +circe-current-topic topic)
(when message
(message "%s" topic))
topic))
@ -86,8 +92,8 @@ replace {nick} in the string with {NO-NICK}."
"Make a formatting regex for CHAR delimiters.
For entry into `lui-formatting-list'."
`(rx (or bol whitespace)
(group ,char (+? (not (any whitespace ,char))) ,char)
(or eol whitespace)))
(group ,char (+? (not (any whitespace ,char))) ,char)
(or eol whitespace)))
;;; Hooks & Advice
@ -101,9 +107,11 @@ For entry into `lui-formatting-list'."
(defun +circe-kill-buffer (&rest _)
"Kill a circe buffer without confirmation, and after a delay."
(let ((circe-channel-killed-confirmation nil)
(circe-server-killed-confirmation nil))
(run-with-timer 0.25 nil 'kill-buffer)))
(let ((circe-channel-killed-confirmation)
(circe-server-killed-confirmation))
(when (derived-mode-p 'lui-mode) ; don't spuriously kill
(ignore-errors
(kill-buffer)))))
(defun +circe-quit@kill-buffer (&rest _)
"ADVICE: kill all buffers of a server after `circe-command-QUIT'."
@ -115,9 +123,11 @@ For entry into `lui-formatting-list'."
(defun +circe-gquit@kill-buffer (&rest _)
"ADVICE: kill all Circe buffers after `circe-command-GQUIT'."
(dolist (buf (circe-server-buffers))
(with-current-buffer buf
(+circe-quit@kill-buffer))))
(let ((circe-channel-killed-confirmation)
(circe-server-killed-confirmation))
(dolist (buf (circe-server-buffers))
(with-current-buffer buf
(+circe-quit@kill-buffer)))))
(defun +circe-quit-all@kill-emacs ()
"Quit all circe buffers when killing Emacs."
@ -135,7 +145,7 @@ For entry into `lui-formatting-list'."
"What to do with `circe-server' buffers when created.")
(el-patch-defun circe (network-or-server &rest server-options)
"Connect to IRC.
"Connect to IRC.
Connect to the given network specified by NETWORK-OR-SERVER.
@ -153,36 +163,123 @@ All SERVER-OPTIONS are treated as variables by getting the string
locally in the server buffer.
See `circe-network-options' for a list of common options."
(interactive (circe--read-network-and-options))
(let* ((options (circe--server-get-network-options network-or-server
server-options))
(buffer (circe--server-generate-buffer options)))
(with-current-buffer buffer
(circe-server-mode)
(circe--server-set-variables options)
(circe-reconnect))
(el-patch-swap (pop-to-buffer-same-window buffer)
(funcall +circe-server-buffer-action buffer))))
(interactive (circe--read-network-and-options))
(let* ((options (circe--server-get-network-options network-or-server
server-options))
(buffer (circe--server-generate-buffer options)))
(with-current-buffer buffer
(circe-server-mode)
(circe--server-set-variables options)
(circe-reconnect))
(el-patch-swap (pop-to-buffer-same-window buffer)
(funcall +circe-server-buffer-action buffer))))
;;; Chat commands
(defun circe-command-SHORTEN (url)
"Shorten URL using `0x0-shorten-uri'.")
(defun circe-command-SLAP (nick)
"Slap NICK around a bit with a large trout.")
"Slap NICK around a bit with a large trout."
(interactive (list (completing-read "Nick to slap: "
(circe-channel-nicks)
nil t nil)))
(circe-command-ME (format "slaps %s about a bit with a large trout" nick)))
;;; Pure idiocy
;;; Filtering functions --- XXX: These don't work right.
;; Set `lui-input-function' to `+lui-filter', then add the filters you want to
;; `circe-channel-mode-hook'.
(define-minor-mode circe-cappy-hour-mode
(defvar +lui-filters nil
"Stack of input functions to apply.
This is an alist with cells of the structure (TAG . FN), so we
can easily remove elements.")
(make-variable-buffer-local '+lui-filters)
(defun +lui-filter (text &optional fn-alist)
(let ((fs (nreverse (purecopy (or fn-alist +lui-filters)))))
(while fs
(setq text (funcall (cdr (pop fs)) text)))
(circe--input text)))
(defmacro +circe-define-filter (name docstring &rest body)
"Define a filter for circe-inputted text."
(declare (doc-string 2)
(indent 1))
(let (plist)
(while (keywordp (car-safe body))
(push (pop body) plist)
(push (pop body) plist))
;; Return value
`(define-minor-mode ,name
,docstring
,@(nreverse plist)
(when (derived-mode-p 'circe-chat-mode)
(if ,name
(push '(,name . (lambda (it) ,@body)) +lui-filters)
(setq +lui-filters
(assoc-delete-all ',name +lui-filters)))))))
;; CAPPY HOUR! (Pure idiocy)
(+circe-define-filter +circe-cappy-hour-mode
"ENABLE CAPPY HOUR IN CIRCE!"
:lighter "CAPPY HOUR"
(when (derived-mode-p 'circe-chat-mode)
(if circe-cappy-hour-mode
(setq-local lui-input-function
(lambda (input) (circe--input (upcase input))))
;; XXX: It'd be better if this were more general, but whatever.
(setq-local lui-input-function #'circe--input))))
:lighter " CAPPY HOUR"
(upcase it))
;; URL Shortener
(+circe-define-filter +circe-shorten-url-mode
"Shorten long urls when chatting."
:lighter " c0x0"
(+circe-0x0-shorten-urls it))
(defvar +circe-0x0-max-length 20
"Maximum length of URLs before using a shortener.")
(defun +circe-0x0-shorten-urls (text)
"Find urls in TEXT and shorten them using `0x0'."
(require '0x0)
(require 'browse-url)
(let ((case-fold-search t))
(replace-regexp-in-string
browse-url-button-regexp
(lambda (match)
(if (> (length match) +circe-0x0-max-length)
(+with-message (format "Shortening URL: %s" match)
(0x0-shorten-uri (0x0--choose-server)
(substring-no-properties match)))
match))
text)))
(defun +circe-shorten-urls-all ()
"Turn on `+circe-shorten-url-mode' in all chat buffers."
(interactive)
(+mapc-some-buffers
(lambda () (+circe-shorten-url-mode +1))
(lambda (buf)
(derived-mode-p 'circe-chat-mode))))
;; Temperature conversion
(+circe-define-filter +circe-F/C-mode
"Convert degF to degF/degC for international chats."
:lighter " F/C"
(str-F/C it))
(defun fahrenheit-to-celsius (degf)
"Convert DEGF to Celsius."
(round (* (/ 5.0 9.0) (- degf 32))))
(defun celsius-to-fahrenheit (degc)
"Convert DEGC to Fahrenheit."
(round (+ 32 (* (/ 9.0 5.0) degc))))
(defun str-F/C (text)
(replace-regexp-in-string "[^.]\\([[:digit:]]+\\(?:\\.[[:digit:]]+\\)?[fF]\\)"
(lambda (match)
(format "%s/%dC" match
(fahrenheit-to-celsius
(string-to-number match))))
text
nil 1))
(provide '+circe)
;;; +circe.el ends here

64
lisp/+compat.el Normal file
View File

@ -0,0 +1,64 @@
;;; +compat.el --- Thin backward-compatibility shim -*- lexical-binding: t; -*-
;;; Commentary:
;; I use different versionso of Emacs. Sometimes I have to copy-paste functions
;; from newer Emacs to make my customizations work. This is that file.
;; This is probably ill-advised.
;;; Code:
;;; Load stuff in +compat/ subdirectory
(dolist (file (directory-files (locate-user-emacs-file "lisp/+compat") :full "\\.el\\'"))
(load file :noerror))
;;; Only define things if not already defined
(defmacro +compat-defun (name &rest args)
`(if (fboundp ',name)
(message "+compat: `%s' already bound." ',name)
(defun ,name ,@args)))
(defmacro +compat-defmacro (name &rest args)
`(if (fboundp ',name)
(message "+compat: `%s' already bound." ',name)
(defmacro ,name ,@args)))
;;; Single functions
(+compat-defmacro dlet (binders &rest body)
"Like `let' but using dynamic scoping."
(declare (indent 1) (debug let))
;; (defvar FOO) only affects the current scope, but in order for
;; this not to affect code after the main `let' we need to create a new scope,
;; which is what the surrounding `let' is for.
;; FIXME: (let () ...) currently doesn't actually create a new scope,
;; which is why we use (let (_) ...).
`(let (_)
,@(mapcar (lambda (binder)
`(defvar ,(if (consp binder) (car binder) binder)))
binders)
(let ,binders ,@body)))
;; https://git.savannah.gnu.org/cgit/emacs.git/diff/?id=772b189143453745a8e014e21d4b6b78f855bba3
(+compat-defun rename-visited-file (new-location)
"Rename the file visited by the current buffer to NEW-LOCATION.
This command also sets the visited file name. If the buffer
isn't visiting any file, that's all it does.
Interactively, this prompts for NEW-LOCATION."
(interactive
(list (if buffer-file-name
(read-file-name "Rename visited file to: ")
(read-file-name "Set visited file name: "
default-directory
(expand-file-name
(file-name-nondirectory (buffer-name))
default-directory)))))
(when (and buffer-file-name
(file-exists-p buffer-file-name))
(rename-file buffer-file-name new-location))
(set-visited-file-name new-location nil t))
(provide '+compat)
;;; +compat.el ends here

20
lisp/+compile.el Normal file
View File

@ -0,0 +1,20 @@
;;; +compile.el --- Extras for compile -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(defcustom +compile-function nil
"Function to run to \"compile\" a buffer."
:type 'function
:local t
:risky nil)
(defun +compile-dispatch (&optional arg)
"Run `+compile-function', if bound, or `compile'.
Any prefix ARG is passed to that function."
(interactive "P")
(call-interactively (or +compile-function #'compile)))
(provide '+compile)
;;; +compile.el ends here

View File

@ -5,14 +5,14 @@
(defun +consult-project-root ()
"Return either the current project, or the VC root, of current file."
(if (and (functionp 'project-current)
(project-current))
(project-current))
(car (project-roots (project-current)))
(vc-root-dir)))
;;; Cribbed functions
;; https://github.com/minad/consult/wiki
(defun consult--orderless-regexp-compiler (input type)
(defun consult--orderless-regexp-compiler (input type &rest _)
(setq input (orderless-pattern-compiler input))
(cons
(mapcar (lambda (r) (consult--convert-regexp r type)) input)

View File

@ -15,8 +15,10 @@ Copy from BEGIN to END using `kill-ring-save' if no argument was
passed, or with `crux-indent-rigidly-and-copy-to-clipboard' if
one was."
(interactive "r\nP")
(call-interactively (if arg #'kill-ring-save
#'crux-indent-rigidly-and-copy-to-clipboard)))
(call-interactively (if arg
#'crux-indent-rigidly-and-copy-to-clipboard
#'kill-ring-save))
(pulse-momentary-highlight-region begin end))
(defcustom +crux-default-date-format "%c"
"Default date format to use for `+crux-insert-date-or-time'.
@ -42,5 +44,15 @@ prompt for the time format."
(format-time-string +crux-alternate-date-format time))
(t (format-time-string (read-string "Time Format: ") time))))))
(defun +crux-kill-and-join-forward (&optional arg)
"If at end of line, join with following; else (visual)-kill line.
In `visual-line-mode', runs command `kill-visual-line'; in other
modes, runs command `kill-line'. Passes ARG to command when
provided. Deletes whitespace at join."
(interactive "P")
(if (and (eolp) (not (bolp)))
(delete-indentation 1)
(funcall (if visual-line-mode #'kill-visual-line #'kill-line) arg)))
(provide '+crux)
;;; +crux.el ends here

View File

@ -33,6 +33,9 @@
(defcustom +custom-variable-allowlist nil
"Variables to allow changing while loading the Custom file.")
(defcustom +custom-after-load-hook nil
"Functions to run after loading the custom file.")
(defun +custom-load-ignoring-most-customizations (&optional
error
nomessage
@ -50,12 +53,13 @@ pass t to it."
(cl-letf (((symbol-function 'custom-set-faces) 'ignore)
((symbol-function 'custom-set-variables)
(lambda (&rest args)
(apply 'custom-theme-set-variables 'user
(apply #'custom-theme-set-variables 'user
(seq-filter (lambda (el)
(memq (car el)
+custom-variable-allowlist))
args)))))
(load custom-file (not error) nomessage nosuffix must-suffix)))
(load custom-file (not error) nomessage nosuffix must-suffix))
(run-hooks '+custom-after-load-hook))
(defun +cus-edit-expand-widgets (&rest _)
"Expand descriptions in `Custom-mode' buffers."

View File

@ -2,7 +2,27 @@
;;; Code:
(with-eval-after-load 'vertico
(defun +dired-goto-file (file)
"ADVICE for `dired-goto-file' to make RET call `vertico-exit'."
(interactive ; stolen from `dired-goto-file'
(prog1
(list (dlet ((vertico-map (copy-keymap vertico-map)))
(define-key vertico-map (kbd "RET") #'vertico-exit)
(expand-file-name (read-file-name "Goto file: "
(dired-current-directory)))))
(push-mark)))
(dired-goto-file file)))
;;; [[https://www.reddit.com/r/emacs/comments/u2lf9t/weekly_tips_tricks_c_thread/i4n9aoa/?context=3][Dim files in .gitignore]]
(defun +dired-dim-git-ignores ()
"Dim out .gitignore contents"
(require 'vc)
(when-let ((ignores (vc-default-ignore-completion-table 'git ".gitignore"))
(exts (make-local-variable 'completion-ignored-extensions)))
(dolist (item ignores)
(add-to-list exts item))))
(provide '+dired)
;;; +dired.el ends here

45
lisp/+ecomplete.el Normal file
View File

@ -0,0 +1,45 @@
;;; +ecomplete.el --- ecomplete extras -*- lexical-binding: t; -*-
;;; Commentary:
;; see [[https://github.com/oantolin/emacs-config/blob/master/my-lisp/ecomplete-extras.el][oantolin's config]]
;;; Code:
(require 'ecomplete)
(defun +ecomplete--name+address (email)
"Return a pair of the name and address for an EMAIL."
(let (name)
(when (string-match "^\\(?:\\(.*\\) \\)?<\\(.*\\)>$" email)
(setq name (match-string 1 email)
email (match-string 2 email)))
(cons name email)))
(defun +ecomplete-add-email (email)
"Add email address to ecomplete's database."
(interactive "sEmail address: ")
(pcase-let ((`(,name . ,email) (+ecomplete--name+address email)))
(unless name (setq name (read-string "Name: ")))
(ecomplete-add-item
'mail email
(format (cond ((equal name "") "%s%s")
((string-match-p "^\\(?:[A-Za-z0-9 ]*\\|\".*\"\\)$" name)
"%s <%s>")
(t "\"%s\" <%s>"))
name email))
(ecomplete-save)))
(defun +ecomplete-remove-email (email)
"Remove email address from ecomplete's database."
(interactive
(list (completing-read "Email address: "
(ecomplete-completion-table 'mail))))
(when-let ((email (cdr (+ecomplete--name+address email)))
(entry (ecomplete-get-item 'mail email)))
(setf (cdr (assq 'mail ecomplete-database))
(remove entry (cdr (assq 'mail ecomplete-database))))
(ecomplete-save)))
(provide '+ecomplete)
;;; +ecomplete.el ends here

View File

@ -4,6 +4,7 @@
(require 'elfeed)
;; https://karthinks.com/software/lazy-elfeed/
(defun +elfeed-scroll-up-command (&optional arg)
"Scroll up or go to next feed item in Elfeed"
(interactive "^P")
@ -20,5 +21,165 @@
(scroll-down-command arg)
(error (elfeed-show-prev)))))
(defun +elfeed-search-browse-generic ()
"Browse a url with `browse-url-generic-browser'."
(interactive)
(elfeed-search-browse-url t))
(defun +elfeed-show-browse-generic ()
"Browse a url with `browse-url-generic-browser'."
(interactive)
(elfeed-show-visit t))
(defun +elfeed-show-mark-read-and-advance ()
"Mark an item as read and advance to the next item.
If multiple items are selected, don't advance."
(interactive)
(call-interactively #'elfeed-search-untag-all-unread)
(unless (region-active-p)
(call-interactively #'next-line)))
;;; Fetch feeds async
;; https://github.com/skeeto/elfeed/issues/367
(defun +elfeed--update-message ()
(message "[Elfeed] Update in progress")
'ignore)
(defvar +elfeed--update-running-p nil "Whether an update is currently running.")
(defvar +elfeed--update-count 0 "How many times `+elfeed-update-command' has run.")
(defcustom +elfeed-update-niceness 15
"How \"nice\" `+elfeed-update-command' should be."
:type 'integer
:group 'elfeed)
(defcustom +elfeed-update-lockfile
(expand-file-name "+elfeed-update-lock" (temporary-file-directory))
"The file to ")
(defun +elfeed-update-command ()
(interactive)
(unless (or +elfeed--update-running-p
(derived-mode-p 'elfeed-show-mode 'elfeed-search-mode))
(let ((script (expand-file-name "/tmp/elfeed-update.el"))
(update-message-format "[Elfeed] Background update: %s"))
(setq +elfeed--update-running-p t)
(elfeed-db-save)
(advice-add 'elfeed :override #'+elfeed--update-message)
(ignore-errors (kill-buffer "*elfeed-search*"))
(ignore-errors (kill-buffer "*elfeed-log*"))
(elfeed-db-unload)
(make-directory (file-name-directory script) :parents)
(with-temp-buffer
(insert
(let ((print-level nil)
(print-length nil))
(prin1-to-string ;; Print the following s-expression to a string
`(progn
;; Set up the environment
(setq lexical-binding t)
(load (locate-user-emacs-file "early-init"))
(dolist (pkg '(elfeed elfeed-org))
(straight-use-package pkg)
(require pkg))
;; Copy variables from current environment
(progn
,@(cl-loop for copy-var in '(rmh-elfeed-org-files
elfeed-db-directory
elfeed-curl-program-name
elfeed-use-curl
elfeed-curl-extra-arguments
elfeed-enclosure-default-dir)
collect `(progn (message "%S = %S" ',copy-var ',(symbol-value copy-var))
(setq ,copy-var ',(symbol-value copy-var)))))
;; Define new variables for this environment
(progn
,@(cl-loop for (new-var . new-val) in '((elfeed-curl-max-connections . 4))
collect `(progn (message "%S = %S" ',new-var ',new-val)
(setq ,new-var ',new-val))))
;; Redefine `elfeed-log' to log everything
(defun elfeed-log (level fmt &rest objects)
(princ (format "[%s] [%s]: %s\n"
(format-time-string "%F %T")
level
(apply #'format fmt objects))))
;; Run elfeed
(elfeed-org)
(elfeed)
(elfeed-db-load)
(elfeed-update)
;; Wait for `elfeed-update' to finish
(let ((q<5-count 0))
(while (and (> (elfeed-queue-count-total) 0)
(< q<5-count 5))
(sleep-for 5)
(message "Elfeed queue count total: %s" (elfeed-queue-count-total))
(when (< (elfeed-queue-count-total) 5)
(cl-incf q<5-count))
(accept-process-output)))
;; Garbage collect and save the database
(elfeed-db-gc)
(elfeed-db-save)
(princ (format ,update-message-format "done."))))))
(write-file script))
(chmod script #o777)
(message update-message-format "start")
(set-process-sentinel (start-process-shell-command
"Elfeed" "*+elfeed-update-background*"
(format "nice -n %d %s %s"
+elfeed-update-niceness
"emacs -Q --script"
script))
(lambda (proc stat)
(advice-remove 'elfeed #'+elfeed--update-message)
(setq +elfeed--update-running-p nil)
(unless (string= stat "killed")
(setq +elfeed--update-count (1+ +elfeed--update-count)))
(message update-message-format (string-trim stat)))))))
(defvar +elfeed--update-timer nil "Timer for `elfeed-update-command'.")
(defvar +elfeed--update-first-time 6 "How long to wait for the first time.")
(defvar +elfeed--update-repeat (* 60 15) "How long between updates.")
(defcustom +elfeed-update-proceed-hook nil
"Predicates to query before running `+elfeed-update-command'.
Each hook is passed no arguments."
:type 'hook)
(defun +elfeed-update-command-wrapper ()
"Run `+elfeed-update-command', but only sometimes.
If any of the predicates in `+elfeed-update-proceed-hook' return
nil, don't run `+elfeed-update-command'. If they all return
non-nil, proceed."
(when (run-hook-with-args-until-failure '+elfeed-update-proceed-hook)
(+elfeed-update-command)))
(defun +elfeed--cancel-update-timer ()
"Cancel `+elfeed--update-timer'."
(unless +elfeed--update-running-p
(ignore-errors (cancel-timer +elfeed--update-timer))
(setq +elfeed--update-timer nil)))
(defun +elfeed--reinstate-update-timer ()
"Reinstate `+elfeed--update-timer'."
;; First, unload the db
(setq +elfeed--update-timer
(run-at-time +elfeed--update-first-time
+elfeed--update-repeat
#'+elfeed-update-command-wrapper)))
(define-minor-mode +elfeed-update-async-mode
"Minor mode to update elfeed async-style."
:global t
(if +elfeed-update-async-mode
(progn ; enable
(+elfeed--reinstate-update-timer)
(advice-add 'elfeed :before '+elfeed--cancel-update-timer)
(advice-add 'elfeed-search-quit-window :after '+elfeed--reinstate-update-timer))
(progn ; disable
(advice-remove 'elfeed '+elfeed--cancel-update-timer)
(advice-remove 'elfeed-search-quit-window '+elfeed--reinstate-update-timer)
(+elfeed--cancel-update-timer))))
(provide '+elfeed)
;;; +elfeed.el ends here

View File

@ -25,6 +25,7 @@ Do this only if the buffer is not visiting a file."
(let ((buffer-file-name (buffer-name buf)))
(set-auto-mode))))
;;; General settings
(setq-default
@ -42,6 +43,7 @@ Do this only if the buffer is not visiting a file."
backup-by-copying t
backup-directory-alist `((".*" . ,(.etc "backup/" t)))
blink-cursor-blinks 1
comp-deferred-compilation nil
completion-category-defaults nil
completion-category-overrides '((file (styles . (partial-completion))))
completion-ignore-case t
@ -60,45 +62,52 @@ Do this only if the buffer is not visiting a file."
fast-but-imprecise-scrolling t
file-name-shadow-properties '(invisible t intangible t)
fill-column 80
find-file-visit-truename t
frame-resize-pixelwise t
global-auto-revert-non-file-buffers t
global-mark-ring-max 100
hscroll-margin 1
hscroll-step 1
imenu-auto-rescan t
image-use-external-converter (or (executable-find "convert")
(executable-find "gm")
(executable-find "ffmpeg"))
indent-tabs-mode nil
indicate-empty-lines nil
indicate-buffer-boundaries 'left
inhibit-startup-screen t
initial-buffer-choice t
kept-new-versions 6
kept-old-versions 2
kill-do-not-save-duplicates t
kill-read-only-ok t
kill-ring-max 500
kmacro-ring-max 20
load-prefer-newer t
load-prefer-newer noninteractive
major-mode '+set-major-mode-from-buffer-name
mark-ring-max 50
minibuffer-eldef-shorten-default t
minibuffer-prompt-properties (list 'read-only t
'cursor-intangible t
'face 'minibuffer-prompt)
'cursor-intangible t
'face 'minibuffer-prompt)
mode-require-final-newline 'visit-save
mouse-drag-copy-region t
mouse-wheel-progressive-speed nil
mouse-yank-at-point t
native-comp-async-report-warnings-errors 'silent
native-comp-deferred-compilation nil
read-answer-short t
read-buffer-completion-ignore-case t
read-extended-command-predicate (when (fboundp
'command-completion-default-include-p)
'command-completion-default-include-p)
;; read-extended-command-predicate
;; (when (fboundp
;; 'command-completion-default-include-p)
;; 'command-completion-default-include-p)
read-process-output-max (+bytes 1 :mib) ; Were in the future man. Set that to at least a megabyte
recenter-positions '(top middle bottom)
regexp-search-ring-max 100
regexp-search-ring-max 200
save-interprogram-paste-before-kill t
scroll-conservatively 101
scroll-down-aggressively 0.01
scroll-margin 1
scroll-margin 2
scroll-preserve-screen-position 1
scroll-step 1
scroll-up-aggressively 0.01
@ -112,7 +121,7 @@ Do this only if the buffer is not visiting a file."
show-paren-when-point-inside-paren t
;;show-trailing-whitespace t
tab-bar-show 1
tab-width 4
tab-width 8 ; so alignment expecting the default looks right
tramp-backup-directory-alist backup-directory-alist
undo-limit 100000000 ; 10 MB
use-dialog-box nil
@ -139,8 +148,12 @@ Do this only if the buffer is not visiting a file."
(when (version< emacs-version "28")
(fset 'yes-or-no-p 'y-or-n-p))
;;; Encodings
;; Allegedly, this is the only one you need...
(set-language-environment "UTF-8")
;; But I still set all of these, for fun.
(setq-default locale-coding-system 'utf-8-unix
coding-system-for-read 'utf-8-unix
coding-system-for-write 'utf-8-unix
@ -152,7 +165,6 @@ Do this only if the buffer is not visiting a file."
STRING))
(set-charset-priority 'unicode)
(set-language-environment "UTF-8")
(prefer-coding-system 'utf-8-unix)
(set-default-coding-systems 'utf-8-unix)
(set-terminal-coding-system 'utf-8-unix)
@ -166,34 +178,47 @@ Do this only if the buffer is not visiting a file."
(set-selection-coding-system 'utf-8)
(set-clipboard-coding-system 'utf-8)))
;;; Modes
(dolist (enable-mode '(global-auto-revert-mode
blink-cursor-mode
electric-pair-mode
show-paren-mode
global-so-long-mode
minibuffer-depth-indicate-mode
file-name-shadow-mode
minibuffer-electric-default-mode
delete-selection-mode
column-number-mode))
blink-cursor-mode
electric-pair-mode
show-paren-mode
global-so-long-mode
minibuffer-depth-indicate-mode
file-name-shadow-mode
minibuffer-electric-default-mode
delete-selection-mode
;; column-number-mode
))
(when (fboundp enable-mode)
(funcall enable-mode +1)))
(dolist (disable-mode '(tooltip-mode
tool-bar-mode
menu-bar-mode
scroll-bar-mode
horizontal-scroll-bar-mode))
menu-bar-mode
scroll-bar-mode
horizontal-scroll-bar-mode))
(when (fboundp disable-mode)
(funcall disable-mode -1)))
;;; Hooks
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)
(add-hook 'minibuffer-setup-hook 'cursor-intangible-mode)
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
(defun +auto-create-missing-dirs ()
"Automatically create missing directories when finding a file."
;; https://emacsredux.com/blog/2022/06/12/auto-create-missing-directories/
(let ((target-dir (file-name-directory buffer-file-name)))
(unless (file-exists-p target-dir)
(make-directory target-dir t))))
(add-hook 'find-file-not-found-functions #'+auto-create-missing-dirs)
;;; Better-default functions ...
(defun +cycle-spacing (&optional n preserve-nl-back mode)
@ -220,6 +245,43 @@ kill without asking."
(save-buffers-kill-emacs))
(delete-frame nil :force)))
(defun +kill-word-backward-or-region (&optional arg backward-kill-word-fn)
"Kill active region or ARG words backward.
BACKWARD-KILL-WORD-FN is the function to call to kill a word
backward. It defaults to `backward-kill-word'."
(interactive "P")
(call-interactively (if (region-active-p)
#'kill-region
(or backward-kill-word-fn #'backward-kill-word))))
(defun +backward-kill-word-wrapper (fn &optional arg)
"Kill backward using FN until the beginning of a word, smartly.
If point is on at the beginning of a line, kill the previous new
line. If the only thing before point on the current line is
whitespace, kill that whitespace.
With argument ARG: if ARG is a number, just call FN
ARG times. Otherwise, just call FN."
;; I want this to be a wrapper so that I can call other word-killing functions
;; with it. It's *NOT* advice because those functions probably use
;; `backward-kill-word' under the hood (looking at you, paredit), so advice
;; will make things weird.
(if (null arg)
(cond
((looking-back "^" 1)
(let ((delete-active-region nil))
(delete-backward-char 1)))
((looking-back "^[ ]*")
(delete-horizontal-space :backward-only))
(t (call-interactively fn)))
(funcall fn (if (listp arg) 1 arg))))
(defun +backward-kill-word (&optional arg)
"Kill word backward using `backward-kill-word'.
ARG is passed to `backward-kill-word'."
(interactive "P")
(+backward-kill-word-wrapper #'backward-kill-word arg))
;; ... and advice
;; Indent the region after a yank.
@ -229,6 +291,7 @@ kill without asking."
(advice-add #'yank :after #'+yank@indent)
(advice-add #'yank-pop :after #'+yank@indent)
;;; Bindings
;; I need to place these bindings under `+key-mode-map' so that they aren't
@ -243,19 +306,17 @@ kill without asking."
("C-s" . isearch-forward-regexp)
("C-r" . isearch-backward-regexp)
("C-M-s" . isearch-forward)
("C-M-r" . isearch-backward)
("M-u" . upcase-dwim)
("M-l" . downcase-dwim)
("M-c" . capitalize-dwim)))
(define-key +key-mode-map (kbd (car binding)) (cdr binding)))
("C-M-r" . isearch-backward)))
(define-key (current-global-map) (kbd (car binding)) (cdr binding)))
;;; Required libraries
(when (require 'uniquify nil :noerror)
(setq-default uniquify-buffer-name-style 'forward
uniquify-separator path-separator
uniquify-after-kill-buffer-p t
uniquify-ignore-buffers-re "^\\*"))
uniquify-separator path-separator
uniquify-after-kill-buffer-p t
uniquify-ignore-buffers-re "^\\*"))
(when (require 'goto-addr)
(if (fboundp 'global-goto-address-mode)
@ -264,36 +325,40 @@ kill without asking."
(when (require 'recentf nil :noerror)
(setq-default recentf-save-file (.etc "recentf.el")
recentf-max-menu-items 100
recentf-max-saved-items nil
recentf-auto-cleanup 'mode)
recentf-max-menu-items 100
recentf-max-saved-items nil
recentf-auto-cleanup 'mode)
(add-to-list 'recentf-exclude .etc)
(recentf-mode +1))
(when (require 'repeat nil :noerror)
(setq-default repeat-exit-key "g"
repeat-exit-timeout 5)
(when (fboundp 'repeat-mode)
;; `repeat-mode' is defined in repeat.el, which is an older library.
(repeat-mode +1)))
(when (require 'savehist nil :noerror)
(setq-default history-length t
history-delete-duplicates t
history-autosave-interval 60
savehist-file (.etc "savehist.el"))
history-delete-duplicates t
history-autosave-interval 60
savehist-file (.etc "savehist.el")
;; Other variables --- don't truncate any of these.
;; `add-to-history' uses the values of these variables unless
;; they're nil, in which case it falls back to `history-length'.
kill-ring-max 100
mark-ring-max 100
global-mark-ring-max 100
regexp-search-ring-max 100
search-ring-max 100
kmacro-ring-max 100
eww-history-limit 100)
(dolist (var '(extended-command-history
global-mark-ring
kill-ring
regexp-search-ring
search-ring
mark-ring))
global-mark-ring
mark-ring
kill-ring
kmacro-ring
regexp-search-ring
search-ring))
(add-to-list 'savehist-additional-variables var))
(savehist-mode +1))
(when (require 'saveplace nil :noerror)
(setq-default save-place-file (.etc "places.el")
save-place-forget-unreadable-files (eq system-type 'gnu/linux))
save-place-forget-unreadable-files (eq system-type 'gnu/linux))
(save-place-mode +1))
;; (when (require 'tramp)
@ -303,5 +368,17 @@ kill without asking."
;; (add-to-list 'tramp-default-proxies-alist
;; '((regexp-quote (system-name)) nil nil)))
;;; Newer features
;; These aren't in older version of Emacs, but they're so nice.
(when (fboundp 'repeat-mode)
(setq-default repeat-exit-key "g"
repeat-exit-timeout 5)
(repeat-mode +1))
(when (fboundp 'pixel-scroll-precision-mode)
(pixel-scroll-precision-mode +1))
(provide '+emacs)
;;; +emacs.el ends here

28
lisp/+embark.el Normal file
View File

@ -0,0 +1,28 @@
;;; +embark.el -*- lexical-binding: t; -*-
;;; Commentary:
;; https://github.com/oantolin/embark/wiki/Additional-Actions
;;; Code:
(require 'embark)
(embark-define-keymap embark-straight-map
("u" straight-visit-package-website)
("r" straight-get-recipe)
("i" straight-use-package)
("c" straight-check-package)
("F" straight-pull-package)
("f" straight-fetch-package)
("p" straight-push-package)
("n" straight-normalize-package)
("m" straight-merge-package))
(add-to-list 'embark-keymap-alist '(straight . embark-straight-map))
(with-eval-after-load 'marginalia
(add-to-list 'marginalia-prompt-categories '("recipe\\|package" . straight)))
(provide '+embark)
;;; +embark.el ends here

46
lisp/+emms.el Normal file
View File

@ -0,0 +1,46 @@
;;; +emms.el --- EMMS customizations -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'emms-player-mpv)
(require 'el-patch)
;; https://lists.gnu.org/archive/html/emms-help/2022-01/msg00006.html
(el-patch-feature emms-player-mpv)
(with-eval-after-load 'emms-player-mpv
(el-patch-defun emms-player-mpv-start (track)
(setq emms-player-mpv-stopped nil)
(emms-player-mpv-proc-playing nil)
(let
((track-name (emms-track-get track 'name))
(track-is-playlist (memq (emms-track-get track 'type)
'(streamlist playlist))))
(if (emms-player-mpv-ipc-fifo-p)
(progn
;; ipc-stop is to clear any buffered commands
(emms-player-mpv-ipc-stop)
(emms-player-mpv-proc-init (if track-is-playlist "--playlist" "--")
track-name)
(emms-player-started emms-player-mpv))
(let*
((play-cmd
`(batch
((,(el-patch-swap
(if track-is-playlist 'loadlist 'loadfile)
'loadfile)
,track-name replace))
((set pause no))))
(start-func
;; Try running play-cmd and retry it on connection failure, e.g. if mpv died
(apply-partially 'emms-player-mpv-cmd play-cmd
(lambda (_mpv-data mpv-error)
(when (eq mpv-error 'connection-error)
(emms-player-mpv-cmd play-cmd))))))
(if emms-player-mpv-ipc-stop-command
(setq emms-player-mpv-ipc-stop-command start-func)
(funcall start-func)))))))
(provide '+emms)
;;; +emms.el ends here

View File

@ -25,11 +25,37 @@ any directory proferred by `consult-dir'."
;;; Start and quit
;; from https://old.reddit.com/r/emacs/comments/1zkj2d/advanced_usage_of_eshell/
(defun +eshell-here ()
"Go to eshell and set current directory to current buffer's."
;; consider: make a new eshell buffer when given a prefix argument.
(interactive)
(let ((dir (file-name-directory (or (buffer-file-name)
default-directory))))
(eshell)
(eshell/pushd ".")
(cd dir)
(goto-char (point-max))
(eshell-kill-input)
(eshell-send-input)
(setq-local scroll-margin 0)
(recenter 0)))
(defun +eshell-quit-or-delete-char (arg)
"Delete the character to the right, or quit eshell on an empty line."
(interactive "p")
(if (and (eolp) (looking-back eshell-prompt-regexp))
(eshell-life-is-too-much)
(progn (eshell-life-is-too-much)
(when (and (<= 1 (count-windows))
;; TODO: This is not what I want. What I really want is
;; for an eshell-only frame (i.e., called from a
;; keybind) to delete itself, but a regular Emacs frame
;; with Eshell inside to stick around. I think I'll
;; need to make a frame-local (?) variable for that to
;; work.
(> (length (frame-list)) 2)
server-process)
(delete-frame)))
(delete-forward-char arg)))
;;; Insert previous arguments
@ -76,5 +102,25 @@ any directory proferred by `consult-dir'."
(add-hook 'eshell-post-command-hook #'eshell-record-args nil t)
(remove-hook 'eshell-post-command-hook #'eshell-record-args t)))
;;;###autoload
(defmacro +eshell-eval-after-load (&rest forms)
"Execute FORMS after Eshell is loaded.
If Eshell is already loaded in the session, immediately execute
forms.
I wrote this because Eshell doesn't properly do loading or
something, it's really annoying to work with."
(declare (indent 0))
`(progn
(defun +eshell@setup ()
"Setup the Eshell session."
,@forms)
(when (featurep 'eshell)
`(dolist (buf (buffer-list))
(with-current-buffer buf
(when (derived-mode-p 'eshell-mode)
(+eshell@setup)))))
(add-hook 'eshell-mode-hook #'+eshell@setup)))
(provide '+eshell)
;;; +eshell.el ends here

View File

@ -65,7 +65,7 @@
(defun +eww-bookmark-setup ()
"Setup eww bookmark integration."
(setq-local bookmark-make-record-function #'eww-bookmark--make))
(setq-local bookmark-make-record-function #'+eww-bookmark--make))
(provide '+eww)
;;; +eww.el ends here

24
lisp/+expand-region.el Normal file
View File

@ -0,0 +1,24 @@
;;; +expand-region.el -*- lexical-binding: t; -*-
;;; Commentary:
;;
;;; Code:
;; Because of `wrap-region', I can't use `expand-region-fast-keys-enabled'. So
;; instead of that, I'm adding this to the binding to C--, but I also want to be
;; able to use the negative argument. So there's this.
(defun +er/contract-or-negative-argument (arg)
"Contract the region if the last command expanded it.
Otherwise, pass the ARG as a negative argument."
(interactive "p")
(cond ((memq last-command '(er/expand-region
er/contract-region
+er/contract-or-negative-argument))
(er/contract-region arg))
(t (call-interactively #'negative-argument))))
(provide '+expand-region)
;;; +expand-region.el ends here

46
lisp/+finger.el Normal file
View File

@ -0,0 +1,46 @@
;;; +finger.el --- Finger bugfix -*- lexical-binding: t; -*-
;;; Commentary:
;; `net-utils' defines `finger', which purportedly consults
;; `finger-X.500-host-regexps' to determine what hosts to only send a username
;; to. I've found that that is not the case, and so I've patched it. At some
;; point I'll submit this to Emacs itself.
;;; Code:
(require 'net-utils) ; this requires everything else I'll need.
(require 'seq)
(defun finger (user host)
"Finger USER on HOST.
This command uses `finger-X.500-host-regexps'
and `network-connection-service-alist', which see."
;; One of those great interactive statements that's actually
;; longer than the function call! The idea is that if the user
;; uses a string like "pbreton@cs.umb.edu", we won't ask for the
;; host name. If we don't see an "@", we'll prompt for the host.
(interactive
(let* ((answer (read-from-minibuffer "Finger User: "
(net-utils-url-at-point)))
(index (string-match (regexp-quote "@") answer)))
(if index
(list (substring answer 0 index)
(substring answer (1+ index)))
(list answer
(read-from-minibuffer "At Host: "
(net-utils-machine-at-point))))))
(let* ((user-and-host (concat user "@" host))
(process-name (concat "Finger [" user-and-host "]"))
(regexps finger-X.500-host-regexps)
) ;; found
(when (seq-some (lambda (r) (string-match-p r host)) regexps)
(setq user-and-host user))
(run-network-program
process-name
host
(cdr (assoc 'finger network-connection-service-alist))
user-and-host)))
(provide '+finger)
;;; +finger.el ends here

24
lisp/+flyspell-correct.el Normal file
View File

@ -0,0 +1,24 @@
;;; +flyspell-correct.el --- -*- lexical-binding: t; -*-
;;; Code:
(require 'flyspell-correct)
(defun +flyspell-correct-buffer (&optional prefix)
"Run `flyspell-correct-wrapper' on all misspelled words in the buffer.
With PREFIX, prompt to change the current dictionary."
(interactive "P")
(flyspell-buffer)
(when prefix
(let ((current-prefix-arg nil))
(call-interactively #'ispell-change-dictionary)))
(+with-message "Checking spelling"
(flyspell-correct-move (point-min) :forward :rapid)))
(defun +flyspell-correct-buffer-h (&rest _)
"Run `+flyspell-correct-buffer'.
This is suitable for placement in a hook."
(+flyspell-correct-buffer))
(provide '+flyspell-correct)
;;; +flyspell-correct.el ends here

17
lisp/+god-mode.el Normal file
View File

@ -0,0 +1,17 @@
;;; +god-mode.el -*- lexical-binding: t; -*-
;;; Code:
(defun +god-mode-insert ()
"Leave `god-local-mode' at point."
(interactive)
(god-local-mode -1))
(defun +god-mode-append ()
"Leave `god-local-mode' after point."
(interactive)
(forward-char 1)
(god-local-mode -1))
(provide '+god-mode)
;;; +god-mode.el ends here

View File

@ -28,62 +28,79 @@ within that group, forms with a HEAD of `:require' are sorted
first, and `:straight' HEADs are sorted last. All other forms
are sorted lexigraphically."
(interactive)
(save-excursion
(save-restriction
(widen)
(+lisp-sort-sexps
(point-min) (point-max)
;; Key function
nil
;; Sort function
(lambda (s1 s2)
(let ((s1 (cdr s1)) (s2 (cdr s2)))
(cond
;; Sort everything /not/ `setup' /before/ `setup'
((and (+init--sexp-setup-p s1)
(not (+init--sexp-setup-p s2)))
nil)
((and (+init--sexp-setup-p s2)
(not (+init--sexp-setup-p s1)))
t)
;; otherwise...
(t (let ((s1-straight (+init--sexp-setup-p s1 :straight))
(s2-straight (+init--sexp-setup-p s2 :straight))
(s1-require (+init--sexp-setup-p s1 :require))
(s2-require (+init--sexp-setup-p s2 :require)))
(cond
;; `:straight' setups have extra processing
((and s1-straight s2-straight)
(let* ((r (rx (: ":straight" (? "-when") (* space) (? "("))))
(s1 (replace-regexp-in-string r "" s1))
(s2 (replace-regexp-in-string r "" s2)))
(string< s1 s2)))
;; `:require' setups go first
((and s1-require (not s2-require)) t)
((and s2-require (not s1-require)) nil)
;; `:straight' setups go last
((and s1-straight (not s2-straight)) nil)
((and s2-straight (not s1-straight)) t)
;; otherwise, sort lexigraphically
(t (string< s1 s2))))))))))))
;; I have to make my own "version" of `save-excursion', since the mark and
;; point are lost (I think that's the problem) when sorting the buffer.
(let* ((current-point (point))
(current-defun (beginning-of-defun))
(defun-point (- current-point (point)))
(current-defun-re (buffer-substring-no-properties (line-beginning-position)
(line-end-position))))
(widen) ; It makes no sense to `save-restriction'
(+lisp-sort-sexps
(point-min) (point-max)
;; Key function
nil
;; Sort function
(lambda (s1 s2)
(let ((s1 (cdr s1)) (s2 (cdr s2)))
(cond
;; Sort everything /not/ `setup' /before/ `setup'
((and (+init--sexp-setup-p s1)
(not (+init--sexp-setup-p s2)))
nil)
((and (+init--sexp-setup-p s2)
(not (+init--sexp-setup-p s1)))
t)
;; otherwise...
(t (let ((s1-straight (+init--sexp-setup-p s1 :straight))
(s2-straight (+init--sexp-setup-p s2 :straight))
(s1-require (+init--sexp-setup-p s1 :require))
(s2-require (+init--sexp-setup-p s2 :require)))
(cond
;; `:straight' setups have extra processing
((and s1-straight s2-straight)
(let* ((r (rx (: ":straight" (? "-when") (* space) (? "("))))
(s1 (replace-regexp-in-string r "" s1))
(s2 (replace-regexp-in-string r "" s2)))
(string< s1 s2)))
;; `:require' setups go first
((and s1-require (not s2-require)) t)
((and s2-require (not s1-require)) nil)
;; `:straight' setups go last
((and s1-straight (not s2-straight)) nil)
((and s2-straight (not s1-straight)) t)
;; otherwise, sort lexigraphically
(t (string< s1 s2)))))))))
;; Return to original point relative to the defun we were in
(ignore-errors (goto-char (point-min))
(re-search-forward current-defun-re)
(beginning-of-defun)
(goto-char (+ (point) defun-point)))))
(defun +init-sort-then-save ()
"Sort init.el, then save it."
(interactive)
(+init-sort)
(save-buffer))
(if (fboundp #'user-save-buffer)
(user-save-buffer)
(save-buffer)))
;;; Add `setup' forms to `imenu-generic-expression'
(defun +init-add-setup-to-imenu ()
"Recognize `setup' forms in `imenu'."
;; `imenu-generic-expression' automatically becomes buffer-local when set
(setf (alist-get "Setup" imenu-generic-expression nil nil 'string-equal)
(list
(rx (: bol (* space)
"(setup" (+ space)
(group (? "(") (* nonl))))
1)))
(setf (alist-get "Setup" imenu-generic-expression nil nil #'equal)
(list
(rx (: "(setup" (+ space)
(group (? "(") (* nonl))))
1))
(when (boundp 'consult-imenu-config)
(setf (alist-get ?s
(plist-get
(alist-get 'emacs-lisp-mode consult-imenu-config)
:types))
'("Setup"))))
;;; Major mode

97
lisp/+ispell.el Normal file
View File

@ -0,0 +1,97 @@
;;; +ispell.el --- Customizations for `ispell' -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'cl-lib)
(require 'seq)
;; Utility function TODO: move elsewhere
(defun +ispell-append-removing-duplicates (&rest lists)
"Append LISTS, removing duplicates from the result.
Any keyword arguments to `cl-remove-duplicates' should come
before the LISTS."
(let (cl-remove-duplicates-args)
(while (keywordp (car lists))
(push (pop lists) cl-remove-duplicates-args)
(push (pop lists) cl-remove-duplicates-args))
(apply #'cl-remove-duplicates (apply #'append lists)
(nreverse cl-remove-duplicates-args))))
;;; Ispell in .dir-locals
;; Let Emacs know a list of strings is safe
(defun +ispell-safe-local-p (list)
(and (listp list)
(seq-every-p #'stringp list)))
;; Can I instruct ispell to insert LocalWords in a different file?
;; https://emacs.stackexchange.com/q/31396/2264
;; How can I move all my file-local LocalWords to .dir-locals.el?
;; https://emacs.stackexchange.com/q/31419
;; Adapted from ispell.el:ispell-buffer-local-words
(defun +ispell-buffer-local-words-list ()
(let (words)
(or ispell-buffer-local-name
(setq ispell-buffer-local-name (buffer-name)))
(save-excursion
(goto-char (point-min))
(while (search-forward ispell-words-keyword nil t)
(let ((end (point-at-eol))
(ispell-casechars (ispell-get-casechars))
string)
(while (re-search-forward " *\\([^ ]+\\)" end t)
(setq string (match-string-no-properties 1))
(if (and (< 1 (length string))
(equal 0 (string-match ispell-casechars string)))
(push string words))))))
words))
;;;###autoload
(defun +ispell-move-buffer-words-to-dir-locals (&optional arg)
"Move the current buffer-local words to .dir-locals.el.
This function prompts the user to save .dir-locals.el, unless
prefix ARG is non-nil; then it just saves them."
(interactive "P")
(unless (buffer-file-name)
(user-error "Buffer not attached to file"))
(hack-dir-local-variables)
(let ((print-level nil)
(print-length nil))
(when-let ((new-words (cl-remove-if (lambda (el) (eq el '\.\.\.)) ; XXX: NO IDEA
; where this came from
(+ispell-append-removing-duplicates
:test #'string=
ispell-buffer-session-localwords
(alist-get 'ispell-buffer-session-localwords
dir-local-variables-alist)
(alist-get 'ispell-buffer-session-localwords
file-local-variables-alist)
(+ispell-buffer-local-words-list)))))
(save-excursion
(add-dir-local-variable
major-mode
'ispell-buffer-session-localwords
(setq ispell-buffer-session-localwords
new-words))
(when (or arg
(y-or-n-p "Save .dir-locals.el?"))
(save-buffer))
(bury-buffer))
(or ispell-buffer-local-name
(setq ispell-buffer-local-name (buffer-name)))
(save-excursion
(goto-char (point-min))
(while (search-forward ispell-words-keyword nil t)
(delete-region (point-at-bol) (1+ (point-at-eol))))))))
;;;###autoload
(defun +ispell-move-buffer-words-to-dir-locals-hook ()
"Convenience function for binding to a hook."
(+ispell-move-buffer-words-to-dir-locals t))
(provide '+ispell)
;;; +ispell.el ends here

278
lisp/+jabber.el Normal file
View File

@ -0,0 +1,278 @@
;;; +jabber.el --- Customizations for jabber.el -*- lexical-binding: t; -*-
;;; Commentary:
;; Most changes I want to PR and contribute, but a few don't make sense to
;; contribute upstream, at least not now.
;;; Code:
(require 'jabber)
(require 'tracking)
(defgroup +jabber nil
"Extra jabber.el customizations."
:group 'jabber)
(defcustom +jabber-ws-prefix 0
"Width to pad left side of chats."
:type 'string)
(defcustom +jabber-pre-prompt " \n"
"String to put before the prompt."
:type 'string)
(defvar +jabber-tracking-show-p #'jabber-activity-show-p-default
"Function that checks if the given JID should be shown in the mode line.
This does the same as `jabber-activity-show-p', but for the
`tracking-mode' mode-line.")
(defun +jabber-tracking-add (from buffer text proposed-alert)
"ADVICE to add jabber buffers to `tracking-buffers'."
(when (funcall +jabber-tracking-show-p from)
(tracking-add-buffer buffer 'jabber-activity-face)))
(defun +jabber-tracking-add-muc (nick group buffer text proposed-alert)
"ADVICE to add jabber MUC buffers to `tracking-buffers'."
(when (funcall +jabber-tracking-show-p group)
(tracking-add-buffer buffer 'jabber-activity-face)))
;;; Hiding presence messages:
;; https://paste.sr.ht/~hdasch/f0ad09fbcd08e940a4fda71c2f40abc1c4efd45f
;; Tame MUC presence notifications.
;; This patch hides or applies a face to MUC presence notifications in
;; the MUC chat buffer. To control its behavior, customize
;; jabber-muc-decorate-presence-patterns. By default it does nothing.
;; jabber-muc-decorate-presence-patterns is a list of pairs consisting
;; of a regular expression and a either a face or nil. If a the
;; regular expression matches a presence notification, then either:
;; - the specified face is applied to the notification message
;; - or if the second value of the pair is nil, the notification is
;; discarded
;; If no regular expression in the list of pairs matches the notification
;; message, the message is displayed unchanged.
;; For example, the customization:
;; '(jabber-muc-decorate-presence-patterns
;; '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$")
;; ("." . jabber-muc-presence-dim)))
;; hides participant enter/leave notifications. It also diminishes other
;; presence notification messages to make it easier to distinguish
;; between conversation and notifications.
(defface jabber-muc-presence-dim
'((t (:foreground "dark grey" :weight light :slant italic)))
"face for diminished presence notifications.")
(defcustom jabber-muc-decorate-presence-patterns nil
"List of regular expressions and face pairs.
When a presence notification matches a pattern, display it with
associated face. Ignore notification if face is nil."
:type '(repeat
:tag "Patterns"
(cons :format "%v"
(regexp :tag "Regexp")
(choice
(const :tag "Ignore" nil)
(face :tag "Face" :value jabber-muc-presence-dim))))
:group 'jabber-alerts)
(defun jabber-muc-maybe-decorate-presence (node)
"Filter presence notifications."
(cl-destructuring-bind (key msg &key time) node
(let* ((match (cl-find-if
(lambda (pair)
(string-match (car pair) msg))
jabber-muc-decorate-presence-patterns))
(face (cdr-safe match)))
(if match
(when face
(jabber-maybe-print-rare-time
(ewoc-enter-last
jabber-chat-ewoc
(list key
(propertize msg 'face face)
:time time))))
(jabber-maybe-print-rare-time
(ewoc-enter-last jabber-chat-ewoc node))))))
(defun jabber-muc-process-presence (jc presence)
(let* ((from (jabber-xml-get-attribute presence 'from))
(type (jabber-xml-get-attribute presence 'type))
(x-muc (cl-find-if
(lambda (x) (equal (jabber-xml-get-attribute x 'xmlns)
"http://jabber.org/protocol/muc#user"))
(jabber-xml-get-children presence 'x)))
(group (jabber-jid-user from))
(nickname (jabber-jid-resource from))
(symbol (jabber-jid-symbol from))
(our-nickname (gethash symbol jabber-pending-groupchats))
(item (car (jabber-xml-get-children x-muc 'item)))
(actor (jabber-xml-get-attribute (car (jabber-xml-get-children item 'actor)) 'jid))
(reason (car (jabber-xml-node-children (car (jabber-xml-get-children item 'reason)))))
(error-node (car (jabber-xml-get-children presence 'error)))
(status-codes (if error-node
(list (jabber-xml-get-attribute error-node 'code))
(mapcar
(lambda (status-element)
(jabber-xml-get-attribute status-element 'code))
(jabber-xml-get-children x-muc 'status)))))
;; handle leaving a room
(cond
((or (string= type "unavailable") (string= type "error"))
;; error from room itself? or are we leaving?
(if (or (null nickname)
(member "110" status-codes)
(string= nickname our-nickname))
;; Assume that an error means that we were thrown out of the
;; room...
(let* ((leavingp t)
(message (cond
((string= type "error")
(cond
;; ...except for certain cases.
((or (member "406" status-codes)
(member "409" status-codes))
(setq leavingp nil)
(concat "Nickname change not allowed"
(when error-node
(concat ": " (jabber-parse-error error-node)))))
(t
(concat "Error entering room"
(when error-node
(concat ": " (jabber-parse-error error-node)))))))
((member "301" status-codes)
(concat "You have been banned"
(when actor (concat " by " actor))
(when reason (concat " - '" reason "'"))))
((member "307" status-codes)
(concat "You have been kicked"
(when actor (concat " by " actor))
(when reason (concat " - '" reason "'"))))
(t
"You have left the chatroom"))))
(when leavingp
(jabber-muc-remove-groupchat group))
;; If there is no buffer for this groupchat, don't bother
;; creating one just to tell that user left the room.
(let ((buffer (get-buffer (jabber-muc-get-buffer group))))
(if buffer
(with-current-buffer buffer
(jabber-muc-maybe-decorate-presence
(list (if (string= type "error")
:muc-error
:muc-notice)
message
:time (current-time)))))
(message "%s: %s" (jabber-jid-displayname group) message))))
;; or someone else?
(let* ((plist (jabber-muc-participant-plist group nickname))
(jid (plist-get plist 'jid))
(name (concat nickname
(when jid
(concat " <"
(jabber-jid-user jid)
">")))))
(jabber-muc-remove-participant group nickname)
(with-current-buffer (jabber-muc-create-buffer jc group)
(jabber-muc-maybe-decorate-presence
(list :muc-notice
(cond
((member "301" status-codes)
(concat name " has been banned"
(when actor (concat " by " actor))
(when reason (concat " - '" reason "'"))))
((member "307" status-codes)
(concat name " has been kicked"
(when actor (concat " by " actor))
(when reason (concat " - '" reason "'"))))
((member "303" status-codes)
(concat name " changes nickname to "
(jabber-xml-get-attribute item 'nick)))
(t
(concat name " has left the chatroom")))
:time (current-time))))))
(t
;; someone is entering
(when (or (member "110" status-codes) (string= nickname our-nickname))
;; This is us. We just succeeded in entering the room.
;;
;; The MUC server is supposed to send a 110 code whenever this
;; is our presence ("self-presence"), but at least one
;; (ejabberd's mod_irc) doesn't, so check the nickname as well.
;;
;; This check might give incorrect results if the server
;; changed our nickname to avoid collision with an existing
;; participant, but even in this case the window where we have
;; incorrect information should be very small, as we should be
;; getting our own 110+210 presence shortly.
(let ((whichgroup (assoc group *jabber-active-groupchats*)))
(if whichgroup
(setcdr whichgroup nickname)
(add-to-list '*jabber-active-groupchats* (cons group nickname))))
;; The server may have changed our nick. Record the new one.
(puthash symbol nickname jabber-pending-groupchats))
;; Whoever enters, we create a buffer (if it didn't already
;; exist), and print a notice. This is where autojoined MUC
;; rooms have buffers created for them. We also remember some
;; metadata.
(let ((old-plist (jabber-muc-participant-plist group nickname))
(new-plist (jabber-muc-parse-affiliation x-muc)))
(jabber-muc-modify-participant group nickname new-plist)
(let ((report (jabber-muc-report-delta nickname old-plist new-plist
reason actor)))
(when report
(with-current-buffer (jabber-muc-create-buffer jc group)
(jabber-muc-maybe-decorate-presence
(list :muc-notice report
:time (current-time)))
;; Did the server change our nick?
(when (member "210" status-codes)
(ewoc-enter-last
jabber-chat-ewoc
(list :muc-notice
(concat "Your nick was changed to " nickname " by the server")
:time (current-time))))
;; Was this room just created? If so, it's a locked
;; room. Notify the user.
(when (member "201" status-codes)
(ewoc-enter-last
jabber-chat-ewoc
(list :muc-notice
(with-temp-buffer
(insert "This room was just created, and is locked to other participants.\n"
"To unlock it, ")
(insert-text-button
"configure the room"
'action (apply-partially 'call-interactively 'jabber-muc-get-config))
(insert " or ")
(insert-text-button
"accept the default configuration"
'action (apply-partially 'call-interactively 'jabber-muc-instant-config))
(insert ".")
(buffer-string))
:time (current-time))))))))))))
(defun +jabber-colors-update (&optional buffer)
"Update jabber colors in BUFFER, defaulting to the current."
(with-current-buffer (or buffer (current-buffer))
(when jabber-buffer-connection
(setq jabber-muc-participant-colors nil)
(cond (jabber-chatting-with
(jabber-chat-create-buffer jabber-buffer-connection
jabber-chatting-with))
(jabber-group
(jabber-muc-create-buffer jabber-buffer-connection
jabber-group))))))
(provide '+jabber)
;;; +jabber.el ends here

View File

@ -19,9 +19,9 @@
;; I need to define this map before the proper mode map.
(defvar +key-leader-map (let ((map (make-sparse-keymap))
(c-z (global-key-binding "\C-z")))
(define-key map "\C-z" c-z)
map)
(c-z (global-key-binding "\C-z")))
;;(define-key map "\C-z" c-z)
map)
"A leader keymap under the \"C-z\" bind.")
;; http://xahlee.info/emacs/emacs/emacs_menu_app_keys.html and
@ -83,15 +83,15 @@
`(define-key +key-mode-map ,key ,command))
:documentation "Bind KEY to COMMAND in `+key-mode-map'."
:debug '(form sexp)
:ensure '(kbd func)
:ensure '(kbd nil)
:repeatable t)
(setup-define :+leader
(lambda (key command)
`(define-key +key-leader-map ,key ,command))
:documentation "Bind KEY to COMMAND in `+key-leader-map'."
:debug '(form sexp)
:ensure '(kbd func)
:ensure '(kbd nil)
:repeatable t)
(setup-define :+menu
@ -99,7 +99,7 @@
`(define-key +key-menu-map ,key ,command))
:documentation "Bind KEY to COMMAND in `+key-leader-map'."
:debug '(form sexp)
:ensure '(kbd func)
:ensure '(kbd nil)
:repeatable t))
(provide '+key)

View File

@ -21,6 +21,7 @@
(defun +kmacro-change-mode-line (&rest _)
"Remap the mode-line face when recording a kmacro."
(add-to-list 'face-remapping-alist '(mode-line . +kmacro-modeline)))
(defun +kmacro-restore-mode-line (&rest _)

View File

@ -2,6 +2,7 @@
;;; Code:
(require 'cl-lib)
(require 'link-hint)
(defgroup +link-hint nil
@ -22,7 +23,71 @@
w3m-message-link)
"Link types to define `:open-secondary' for.")
(defun +link-hint-setup-open-secondary (&optional types)
(defvar +link-hint-map (make-sparse-keymap)
"Keymap for `link-hint' functionality.")
(cl-defmacro +link-hint-define-keyword (keyword handler docstring
&optional (types 'link-hint-types)
&rest rest
&key multiple &allow-other-keys)
"Set up a `link-hint' KEYWORD, with optional TYPES.
If TYPES is not present, use `link-hint-types'.
KEYWORD defines the link-hint type. It will be used to create a
function for opening links of the form \"link-hint-openKEYWORD\".
HANDLER is the function to open a link with.
DOCSTRING is the macro's documentation.
Keyword arguments are passed to `link-hint-define-type' prefixed
with the KEYWORD."
(declare (indent 2)
(doc-string 3))
(let ((types (symbol-value types))
(func-sym (intern (format "+link-hint-open%s" keyword)))
(mult-sym (intern (format "%s-multiple" keyword)))
(expr))
;; Define the type
(push `(dolist (type ',types)
(link-hint-define-type type
,keyword ,handler
,@(mapcar (lambda (el)
(if (eq el :multiple)
mult-sym
el))
rest)))
expr)
;; Define an opener
(push `(defun ,func-sym ()
,(format "%s\n\nDefined by `+link-hint-define'." docstring)
(interactive)
(avy-with link-hint-open-link
(link-hint--one ,keyword)))
expr)
;; Handle `:multiple'
(when multiple
(push `(defun ,(intern (format "+link-hint-open-multiple%s" keyword)) ()
,(format "Open multiple links with `%s'.\n\nDefined by `+link-hint-define'."
func-sym)
(avy-with link-hint-open-multiple-links
(link-hint--multiple ,keyword)))
expr)
(push `(defun ,(intern (format "+link-hint-open-all%s" keyword)) ()
,(format "Open all visible links with `%s'.\n\nDefined by `+link-hint-define'."
func-sym)
(avy-with link-hint-open-all-links
(link-hint--all ,keyword)))
expr))
;; Return the built expression
`(progn ,@(nreverse expr))))
(+link-hint-define-keyword :secondary browse-url-secondary-browser-function
"Open a link in the secondary browser."
+link-hint-open-secondary-types
:multiple t)
(defun +link-hint-open-secondary-setup (&optional types)
"Define the `:open-secondary' link-hint type for TYPES.
If TYPES is nil, define it for `+link-hint-open-secondary-types'."
(dolist (type (or types +link-hint-open-secondary-types))
@ -30,6 +95,36 @@ If TYPES is nil, define it for `+link-hint-open-secondary-types'."
:open-secondary browse-url-secondary-browser-function
:open-secondary-multiple t)))
(defun +link-hint-open-secondary ()
"Open a link in the secondary browser."
(interactive)
(avy-with link-hint-open-link
(link-hint--one :open-secondary)))
(defun +link-hint-open-chrome-setup (&optional types)
"Define the `:open-chrome' link-hint type for TYPES.
If TYPES is nil, define it for `+link-hint-open-secondary-types'."
(dolist (type (or types +link-hint-open-secondary-types))
(link-hint-define-type type
:open-chrome #'browse-url-chrome
:open-chrome-multiple t)))
(defun +link-hint-open-chrome ()
"Open a link with chrome."
(interactive)
(avy-with link-hint-open-link
(link-hint--one :open-chrome)))
;; (cl-defmacro +link-hint-add-type (keyword )
;; "Define link-hint type KEYWORD to operate on TYPES.
;; If TYPES is nil or absent, define KEYWORD for all
;; `link-hint-types'."
;; (let (forms)
;; (dolist (type (or types link-hint-types))
;; (push `(link-hint-define-type ,type ,keyword ,function) forms))
;; (push `(defun ,(intern (format "+link-hint%s" ,keyword))
;; ))))
(defun +link-hint-open-link (prefix)
"Open a link.
Without a PREFIX, open using `browse-url-browser-function'; with
@ -54,5 +149,21 @@ a PREFIX, use `browse-url-secondary-browser-function'."
(avy-with link-hint-open-all-links
(link-hint--one (if prefix :open-secondary :open))))
;;; Pocket-reader.el integration
(defun +link-hint-pocket-add-setup (&optional types)
"Define the `:pocket-add' link-hint type for TYPES.
If TYPES is nil, define it for `link-hint-types'."
(dolist (type (or types link-hint-types))
(link-hint-define-type type
:pocket-add #'pocket-reader-generic-add-link
:pocket-add-multiple t)))
(defun +link-hint-pocket-add ()
"Add a link to the Pocket reader."
(interactive)
(avy-with link-hint-open-link
(link-hint--one :pocket-add)))
(provide '+link-hint)
;;; +link-hint.el ends here

View File

@ -156,5 +156,40 @@ With a prefix argument N, (un)comment that many sexps."
(dotimes (_ (or n 1))
(+lisp-comment-sexp--raw))))
;;; Sort `setq' constructs
;;https://emacs.stackexchange.com/questions/33039/
(defun +lisp-sort-setq ()
(interactive)
(save-excursion
(save-restriction
(let ((sort-end (progn
(end-of-defun)
(backward-char)
(point-marker)))
(sort-beg (progn
(beginning-of-defun)
(or (re-search-forward "[ \\t]*(" (point-at-eol) t)
(point-at-eol))
(forward-sexp)
(or (re-search-forward "\\<" (point-at-eol) t)
(point-at-eol))
(point-marker))))
(narrow-to-region (1- sort-beg) (1+ sort-end))
(sort-subr nil #'+lisp-sort-setq-next-record
#'+lisp-sort-setq-end-record)))))
(defun +lisp-sort-setq-next-record ()
(condition-case nil
(progn
(forward-sexp 1)
(backward-sexp))
('scan-error (end-of-buffer))))
(defun +lisp-sort-setq-end-record ()
(condition-case nil
(forward-sexp 2)
('scan-error (end-of-buffer))))
(provide '+lisp)
;;; +lisp.el ends here

26
lisp/+message.el Normal file
View File

@ -0,0 +1,26 @@
;;; +message.el --- Extra message-mode functions -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
;; Thanks to Alex Schroeder for this!
;; https://www.emacswiki.org/emacs/Change_Signature_Dynamically
(defun +message-check-for-signature-change (&rest ignore)
"Check for a change in the To: or Cc: fields"
(when (and (message--in-tocc-p)
(not (buffer-narrowed-p)))
(save-excursion
(goto-char (point-max))
(let ((end (point)))
(when (re-search-backward message-signature-separator nil t)
(delete-region (1- (match-beginning 0)) end)))
(message-insert-signature))))
(defun +message-signature-setup ()
(make-local-variable 'after-change-functions)
(push '+message-check-for-signature-change after-change-functions))
(provide '+message)
;;; +message.el ends here

View File

@ -9,6 +9,7 @@
;;; Code:
(require '+util)
(require 'actually-selected-window)
(require 'simple-modeline)
(require 'minions)
@ -25,46 +26,90 @@ will default to this string.")
;;; Combinators
(defun +modeline-concat (segments &optional separator)
"Concatenate multiple `simple-modeline'-style SEGMENTS.
SEGMENTS is a list of either modeline segment-functions (see
`simple-modeline' functions for an example of types of
functions), though it can also contain cons cells of the
form (SEGMENT . PREDICATE).
"Concatenate multiple functional modeline SEGMENTS.
Each segment in SEGMENTS is a function returning a mode-line
construct.
Segments are separated from each other using SEPARATOR, which
defaults to a \" \". space. Only segments that evaluate to a
non-trivial string (that is, a string not equal to \"\") will be
separated, for a cleaner look.
Segments are separated using SEPARATOR, which defaults to
`+modeline-default-spacer'. Only segments that evaluate to a
non-zero-length string will be separated, for a cleaner look.
This function makes a lambda, so you can throw it straight into
`simple-modeline-segments'."
(setq separator (or separator +modeline-default-spacer))
(lambda ()
(apply #'concat
(let (this-sep result-list)
(dolist (segment segments)
(push (funcall (or (car-safe segment) segment)
this-sep)
result-list)
(if (or (cdr-safe segment)
(and (car result-list)
(not (equal (car result-list) ""))))
(setq this-sep separator)
(setq this-sep nil)))
(unless (seq-some #'null result-list)
(push +modeline-default-spacer result-list))
(nreverse result-list)))))
This function returns a lambda that should be `:eval'd or
`funcall'd in a mode-line context."
(let ((separator (or separator +modeline-default-spacer)))
(lambda ()
(let (this-sep result)
(dolist (segment segments)
(let ((segstr (funcall segment this-sep)))
(when (and segstr
(not (equal segstr "")))
(push segstr result)
(setq this-sep separator))))
(apply #'concat
(nreverse result))))))
(defun +modeline-spacer (&optional n spacer &rest strings)
"Make an N-length SPACER, or prepend SPACER to STRINGS.
When called with no arguments, insert `+modeline-default-spacer'.
N will repeat SPACER N times, and defaults to 1. SPACER defaults
to `+modeline-default-spacer', but can be any string. STRINGS
should form a mode-line construct when `concat'ed."
(declare (indent 2))
(let ((spacer (or spacer +modeline-default-spacer))
(n (or n 1))
(strings (cond((null strings) '(""))
((equal strings '("")) nil)
((atom strings) (list strings))
(t strings)))
r)
(when strings (dotimes (_ n) (push spacer r)))
(apply #'concat (apply #'concat r) strings)))
;;; Modeline segments
(defun +modeline-sanitize-string (string)
"Sanitize a string for `format-mode-line'."
(when string
(string-replace "%" "%%" string)))
(defcustom +modeline-buffer-name-max-length 0
"Maximum length of `+modeline-buffer-name'.
If > 0 and < 1, use that portion of the window's width. If > 1,
use that many characters. If anything else, don't limit. If the
buffer name is longer than the max length, it will be shortened
and appended with `truncate-string-ellipsis'."
:type '(choice (const :tag "No maximum length" 0)
(natnum :tag "Number of characters")
(float :tag "Fraction of window's width")))
(defcustom +modeline-buffer-position nil
"What to put in the `+modeline-buffer-name' position."
:type 'function
:local t)
(defun +modeline-buffer-name (&optional spacer) ; gonsie
"Display the buffer name."
(concat (or spacer +modeline-default-spacer)
(propertize
(+string-align (buffer-name) 20 :ellipsis nil)
'help-echo (or (buffer-file-name)
(buffer-name))
'mouse-face 'mode-line-highlight)))
(let ((bufname (string-trim (string-replace "%" "" (buffer-name)))))
(+modeline-spacer nil spacer
(if (and +modeline-buffer-position (fboundp +modeline-buffer-position))
(funcall +modeline-buffer-position)
(propertize (cond
((ignore-errors
(and (> +modeline-buffer-name-max-length 0)
(< +modeline-buffer-name-max-length 1)))
(truncate-string-to-width bufname
(* (window-total-width)
+modeline-buffer-name-max-length)
nil nil t))
((ignore-errors
(> +modeline-buffer-name-max-length 1))
(truncate-string-to-width bufname
+modeline-buffer-name-max-length
nil nil t))
(t bufname))
'help-echo (or (buffer-file-name)
(buffer-name))
'mouse-face 'mode-line-highlight)))))
(defcustom +modeline-minions-icon "&"
"The \"icon\" for `+modeline-minions' button."
@ -72,28 +117,61 @@ This function makes a lambda, so you can throw it straight into
(defun +modeline-minions (&optional spacer)
"Display a button for `minions-minor-modes-menu'."
(concat (or spacer +modeline-default-spacer)
(propertize
+modeline-minions-icon
'help-echo "Minor modes menu\nmouse-1: show menu."
'local-map (purecopy (simple-modeline-make-mouse-map
'mouse-1
(lambda (event)
(interactive "e")
(with-selected-window
(posn-window (event-start event))
(minions-minor-modes-menu)))))
'mouse-face 'mode-line-highlight)))
(+modeline-spacer nil spacer
(propertize
+modeline-minions-icon
'help-echo "Minor modes menu\nmouse-1: show menu."
'local-map (purecopy (simple-modeline-make-mouse-map
'mouse-1
(lambda (event)
(interactive "e")
(with-selected-window
(posn-window (event-start event))
(minions-minor-modes-menu)))))
'mouse-face 'mode-line-highlight)))
(defcustom +modeline-major-mode-faces '((text-mode . font-lock-string-face)
(prog-mode . font-lock-keyword-face)
(t . font-lock-warning-face))
"Mode->face mapping for `+modeline-major-mode'.
If the current mode is derived from the car of a cell, the face
in the cdr will be applied to the major-mode in the mode line."
:type '(alist :key-type function
:value-type face))
(defface +modeline-major-mode-face nil
"Face for modeline major-mode.")
(defun +modeline-major-mode (&optional spacer)
"Display the current `major-mode'."
(concat (or spacer +modeline-default-spacer)
(propertize (+string-truncate (format-mode-line mode-name) 12)
'face 'bold
'keymap mode-line-major-mode-keymap
'help-echo (concat (format-mode-line mode-name)
" mode\nmouse-1: show menu.")
'mouse-face 'mode-line-highlight)))
(+modeline-spacer nil spacer
"("
(propertize ;; (+string-truncate (format-mode-line mode-name) 16)
(format-mode-line mode-name)
'face (when (actually-selected-window-p)
;; XXX: This is probably really inefficient. I need to
;; simply detect which mode it's in when I change major
;; modes (`change-major-mode-hook') and change the face
;; there, probably.
;; (catch :done (dolist (cel +modeline-major-mode-faces)
;; (when (derived-mode-p (car cel))
;; (throw :done (cdr cel))))
;; (alist-get t +modeline-major-mode-faces))
'+modeline-major-mode-face)
'keymap (let ((map (make-sparse-keymap)))
(bindings--define-key map [mode-line down-mouse-1]
`(menu-item "Menu Bar" ignore
:filter ,(lambda (_) (mouse-menu-major-mode-map))))
(define-key map [mode-line mouse-2] 'describe-mode)
(bindings--define-key map [mode-line down-mouse-3]
`(menu-item "Minions" minions-minor-modes-menu))
map)
'help-echo (+concat (list (format-mode-line mode-name) " mode")
"mouse-1: show menu"
"mouse-2: describe mode"
"mouse-3: display minor modes")
'mouse-face 'mode-line-highlight)
")"))
(defcustom +modeline-modified-icon-alist '((ephemeral . "*")
(readonly . "=")
@ -124,95 +202,185 @@ The order of elements matters: whichever one matches first is applied."
(defun +modeline-modified (&optional spacer) ; modified from `simple-modeline-status-modified'
"Display a color-coded \"icon\" indicator for the buffer's status."
(let* ((icon (catch :icon
(dolist (cell +modeline-modified-icon-alist)
(when (pcase (car cell)
('ephemeral (not (buffer-file-name)))
('readonly buffer-read-only)
('modified (buffer-modified-p))
('special
(apply 'derived-mode-p
+modeline-modified-icon-special-modes))
('t t)
(_ nil))
(throw :icon (cdr cell)))))))
(concat (or spacer +modeline-default-spacer)
(propertize (or icon "")
'mouse-face 'mode-line-highlight))))
(defun +modeline-buffer-modes (&optional spacer)
"Display various buffer-specific stuff cleanly."
;; This is clunky and should probably be improved.
(concat (+modeline-reading-mode)
(+modeline-narrowed (when reading-mode ","))))
(dolist (cell +modeline-modified-icon-alist)
(when (pcase (car cell)
('ephemeral (not (buffer-file-name)))
('readonly buffer-read-only)
('modified (buffer-modified-p))
('special
(apply 'derived-mode-p
+modeline-modified-icon-special-modes))
('t t)
(_ nil))
(throw :icon cell))))))
(+modeline-spacer nil spacer
(propertize (or (cdr-safe icon) "")
'help-echo (format "Buffer \"%s\" is %s."
(buffer-name)
(pcase (car-safe icon)
('t "unmodified")
('nil "unknown")
(_ (car-safe icon))))))))
(defun +modeline-narrowed (&optional spacer)
"Display an indication that the buffer is narrowed."
(when (buffer-narrowed-p)
(concat (or spacer +modeline-default-spacer)
(propertize "N"
'help-echo (format "%s\n%s"
"Buffer is narrowed."
"mouse-2: widen buffer.")
'local-map (purecopy (simple-modeline-make-mouse-map
'mouse-2 'mode-line-widen))
'face 'font-lock-doc-face
'mouse-face 'mode-line-highlight))))
(+modeline-spacer nil spacer
(propertize "N"
'help-echo (format "%s\n%s"
"Buffer is narrowed."
"mouse-2: widen buffer.")
'local-map (purecopy (simple-modeline-make-mouse-map
'mouse-2 'mode-line-widen))
'face 'font-lock-doc-face
'mouse-face 'mode-line-highlight))))
(defun +modeline-reading-mode (&optional spacer)
"Display an indication that the buffer is in `reading-mode'."
(when reading-mode
(concat (or spacer +modeline-default-spacer)
(propertize
(concat "R" (when (bound-and-true-p +eww-readable-p) "w"))
'help-echo (format "%s\n%s"
"Buffer is in reading-mode."
"mouse-2: disable reading-mode.")
'local-map (purecopy
(simple-modeline-make-mouse-map
'mouse-2 (lambda (ev)
(interactive "e")
(with-selected-window
(posn-window
(event-start ev))
(reading-mode -1)
(force-mode-line-update)))))
'face 'font-lock-doc-face
'mouse-face 'mode-line-highlight))))
(+modeline-spacer nil spacer
(propertize
(concat "R" (when (bound-and-true-p +eww-readable-p) "w"))
'help-echo (format "%s\n%s"
"Buffer is in reading-mode."
"mouse-2: disable reading-mode.")
'local-map (purecopy
(simple-modeline-make-mouse-map
'mouse-2 (lambda (ev)
(interactive "e")
(with-selected-window
(posn-window
(event-start ev))
(reading-mode -1)
(force-mode-line-update)))))
'face 'font-lock-doc-face
'mouse-face 'mode-line-highlight))))
(define-minor-mode file-percentage-mode
"Toggle the percentage display in the mode line (File Percentage Mode)."
:init-value t :global t :group 'mode-line)
(defun +modeline-position (&optional _) ; adapted from `simple-modeline'
"Display the current cursor position."
(list '((line-number-mode
((column-number-mode
(column-number-indicator-zero-based
(9 " %l:%c")
(9 " %l:%C"))
(6 " %l:")))
((column-number-mode
(column-number-indicator-zero-based
(5 " :%c")
(5 " :%C"))))))
'(file-percentage-mode
((-3 "%p") "%% "))
(if (region-active-p)
(propertize (format "%s%-5d"
(if (and (mark) (< (point) (mark))) "-" "+")
(apply '+ (mapcar
(lambda (pos)
(- (cdr pos)
(car pos)))
(region-bounds))))
'font-lock-face 'font-lock-variable-name-face))))
(defun +modeline--percentage ()
"Return point's progress through current file as a percentage."
(let ((tot (count-screen-lines (point-min) (point-max) :ignore-invisible)))
(floor (* 100 (/ (float (line-number-at-pos)) tot)))))
(defun +modeline--buffer-contained-in-window-p ()
"Whether the buffer is totally contained within its window."
(let ((window-min (save-excursion (move-to-window-line 0) (point)))
(window-max (save-excursion (move-to-window-line -1) (point))))
(and (<= window-min (point-min))
(>= window-max (point-max)))))
(defun +modeline-file-percentage (&optional spacer)
"Display the position in the current file."
(when file-percentage-mode
;; (let ((perc (+modeline--percentage)))
;; (propertize (+modeline-spacer nil spacer
;; (cond
;; ((+modeline--buffer-contained-in-window-p) "All")
;; ((= (line-number-at-pos) (line-number-at-pos (point-min))) "Top")
;; ((= (line-number-at-pos) (line-number-at-pos (point-max))) "Bot")
;; ;; Why the 10 %s? Not sure. `format' knocks them
;; ;; down to 5, then `format-mode-line' kills all but
;; ;; two. If I use only 8, the margin is much too
;; ;; large. Something else is obviously going on, but
;; ;; I'm at a loss as to what it could be.
;; (t (format "%d%%%%%%%%%%" perc))))
;; ;; TODO: add scroll-up and scroll-down bindings.
;; ))
(let ((perc (format-mode-line '(-2 "%p"))))
(+modeline-spacer nil spacer
"/"
(pcase perc
("To" "Top")
("Bo" "Bot")
("Al" "All")
(_ (format ".%02d" (string-to-number perc))))))))
(defun +modeline-file-percentage-ascii-icon (&optional spacer)
(when file-percentage-mode
(+modeline-spacer nil spacer
(let ((perc (format-mode-line '(-2 "%p"))))
(pcase perc
("To" "/\\")
("Bo" "\\/")
("Al" "[]")
(_ (let ((vec (vector "/|" "//" "||" "\\\\" "\\|" "\\|"))
(perc (string-to-number perc)))
(aref vec (floor (/ perc 17))))))))))
(defun +modeline-file-percentage-icon (&optional spacer)
"Display the position in the current file as an icon."
(when file-percentage-mode
(let ((perc (+modeline--percentage)))
(propertize (+modeline-spacer nil spacer
(cond
((+modeline--buffer-contained-in-window-p) "111")
((= perc 0) "000")
((< perc 20) "001")
((< perc 40) "010")
((< perc 60) "011")
((< perc 80) "100")
((< perc 100) "101")
((>= perc 100) "110")))
'help-echo (format "Point is %d%% through the buffer."
perc)))))
(define-minor-mode region-indicator-mode
"Toggle the region indicator in the mode line."
:init-value t :global t :group 'mode-line)
(defun +modeline-region (&optional spacer)
"Display an indicator if the region is active."
(when (and region-indicator-mode
(region-active-p))
(+modeline-spacer nil spacer
(propertize (format "%d%s"
(apply '+ (mapcar (lambda (pos)
(- (cdr pos)
(car pos)))
(region-bounds)))
(if (and (< (point) (mark))) "-" "+"))
'font-lock-face 'font-lock-variable-name-face))))
(defun +modeline-line (&optional spacer)
(when line-number-mode
(+modeline-spacer nil spacer
"%3l")))
(defun +modeline-column (&optional spacer)
(when column-number-mode
(+modeline-spacer nil spacer
"|"
(if column-number-indicator-zero-based "%2c" "%2C"))))
(defcustom +modeline-position-function nil
"Function to use instead of `+modeline-position' in modeline."
:type '(choice (const :tag "Default" nil)
function)
:local t)
(defun +modeline-position (&optional spacer)
"Display the current cursor position.
See `line-number-mode', `column-number-mode', and
`file-percentage-mode'. If `+modeline-position-function' is set
to a function in the current buffer, call that function instead."
(cond ((functionp +modeline-position-function)
(when-let* ((str (funcall +modeline-position-function)))
(+modeline-spacer nil spacer str)))
(t (funcall (+modeline-concat '(+modeline-region
+modeline-line
+modeline-column
+modeline-file-percentage)
"")))))
(defun +modeline-vc (&optional spacer)
"Display the version control branch of the current buffer in the modeline."
;; from https://www.gonsie.com/blorg/modeline.html, from Doom
(if-let ((backend (vc-backend buffer-file-name)))
(concat (or spacer +modeline-default-spacer)
(substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2)))))
(when-let ((backend (vc-backend buffer-file-name)))
(+modeline-spacer nil spacer
(substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2)))))
(defun +modeline-track (&optional spacer)
"Display `tracking-mode' information."
@ -221,24 +389,99 @@ The order of elements matters: whichever one matches first is applied."
(defun +modeline-anzu (&optional spacer)
"Display `anzu--update-mode-line'."
(concat (or spacer +modeline-default-spacer)
(anzu--update-mode-line)))
(+modeline-spacer nil spacer
(anzu--update-mode-line)))
(defun +modeline-text-scale (&optional spacer)
"Display text scaling level."
;; adapted from https://github.com/seagle0128/doom-modeline
(when (and (boundp 'text-scale-mode-amount)
(/= text-scale-mode-amount 0))
(format (if (> text-scale-mode-amount 0) "%s(%+d)" "%s(%-d)")
(or spacer +modeline-default-spacer)
text-scale-mode-amount)))
(+modeline-spacer nil spacer
(concat (if (> text-scale-mode-amount 0) "+" "-")
(number-to-string text-scale-mode-amount)))))
(defun +modeline-ace-window-display (&optional spacer)
"Display `ace-window-display-mode' information in the modeline."
(when (and +ace-window-display-mode
ace-window-mode)
(concat (or spacer +modeline-default-spacer)
(window-parameter (selected-window) 'ace-window-path))))
(+modeline-spacer nil spacer
(window-parameter (selected-window) 'ace-window-path))))
(defun +modeline-god-mode (&optional spacer)
"Display an icon when `god-mode' is active."
(when (and (boundp 'god-local-mode) god-local-mode)
(+modeline-spacer nil spacer
(propertize "Ω"
'help-echo (concat "God mode is active."
"\nmouse-1: exit God mode.")
'local-map (purecopy
(simple-modeline-make-mouse-map
'mouse-1 (lambda (e)
(interactive "e")
(with-selected-window
(posn-window
(event-start e))
(god-local-mode -1)
(force-mode-line-update)))))
'mouse-face 'mode-line-highlight))))
(defun +modeline-input-method (&optional spacer)
"Display which input method is active."
(when current-input-method
(+modeline-spacer nil spacer
(propertize current-input-method-title
'help-echo (format
(concat "Current input method: %s\n"
"mouse-1: Describe current input method\n"
"mouse-3: Toggle input method")
current-input-method)
'local-map (purecopy
(let ((map (make-sparse-keymap)))
(define-key map [mode-line mouse-1]
(lambda (e)
(interactive "e")
(with-selected-window (posn-window (event-start e))
(describe-current-input-method))))
(define-key map [mode-line mouse-3]
(lambda (e)
(interactive "e")
(with-selected-window (posn-window (event-start e))
(toggle-input-method nil :interactive))))
map))
'mouse-face 'mode-line-highlight))))
(defface +modeline-kmacro-indicator '((t :foreground "Firebrick"))
"Face for the kmacro indicator in the modeline.")
(defun +modeline-kmacro-indicator (&optional spacer)
"Display an indicator when recording a kmacro."
(when defining-kbd-macro
(+modeline-spacer nil spacer
(propertize ""
'face '+modeline-kmacro-indicator
'help-echo (format (concat "Defining a macro\n"
"Current step: %d\n"
"mouse-1: Stop recording")
kmacro-counter)
'local-map (purecopy (simple-modeline-make-mouse-map
'mouse-1 (lambda (e)
(interactive "e")
(with-selected-window
(posn-window (event-start e))
(kmacro-end-macro nil)))))
'mouse-face 'mode-line-highlight))))
(defface +nyan-mode-line nil
"Face for nyan-cat in mode line.")
(defun +modeline-nyan-on-focused (&optional spacer)
"Display the cat from `nyan-mode', but only on the focused window."
(require 'nyan-mode)
(when (and (or nyan-mode (bound-and-true-p +nyan-local-mode))
(actually-selected-window-p))
(+modeline-spacer nil spacer
(propertize (nyan-create) 'face '+nyan-mode-line))))
(provide '+modeline)
;;; +modeline.el ends here

42
lisp/+mwim.el Normal file
View File

@ -0,0 +1,42 @@
;;; +mwim.el --- Extras -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'seq)
(defgroup +mwim nil
"Extra `mwim' customizations."
:group 'mwim)
(defcustom +mwim-passthrough-modes nil
"Modes to not move-where-I-mean."
:type '(repeat function))
(defun +mwim-beginning-maybe (&optional arg)
"Perform `mwim-beginning', maybe.
Will just do \\[beginning-of-line] in one of
`+mwim-passthrough-modes'."
(interactive)
(if (apply #'derived-mode-p +mwim-passthrough-modes)
(let ((this-mode-map (symbol-value (intern (format "%s-map" major-mode))))
(key "C-a"))
(call-interactively (or (keymap-lookup this-mode-map key t t)
(keymap-lookup (current-global-map) key t t))))
(call-interactively #'mwim-beginning)))
(defun +mwim-end-maybe (&optional arg)
"Perform `mwim-beginning', maybe.
Will just do \\[end-of-line] in one of
`+mwim-passthrough-modes'."
(interactive)
(if (apply #'derived-mode-p +mwim-passthrough-modes)
(let ((this-mode-map (symbol-value (intern (format "%s-map" major-mode))))
(key "C-e"))
(call-interactively (or (keymap-lookup this-mode-map key t t)
(keymap-lookup (current-global-map) key t t))))
(call-interactively #'mwim-end)))
(provide '+mwim)
;;; +mwim.el ends here

62
lisp/+notmuch.el Normal file
View File

@ -0,0 +1,62 @@
;;; +notmuch.el --- Notmuch extras -*- lexical-binding: t; -*-
;;; Commentary:
;; This is stuff that I suppose /could/ go in notmuch/init.el, but ... doesn't.
;;; Code:
(require 'cl-lib)
(require 'notmuch)
(defvar +notmuch-send-dispatch-rules nil
"Alist of from addresses and variables to set when sending.")
(defun +notmuch-query-concat (&rest queries)
"Concatenate notmuch queries."
(mapconcat #'identity queries " AND "))
(defun +send-mail-dispatch ()
"Dispatch mail sender, depending on account."
(let ((from (message-fetch-field "from")))
(dolist (vars (cl-loop for (addr . vars) in +notmuch-send-dispatch-rules
if (string-match-p addr from) return vars))
(set (car vars) (cdr vars)))))
(defun +notmuch-correct-tags (args)
(list (car args) (mapcar #'string-trim (cadr args))))
(defun +notmuch-goto (&optional prefix)
"Go straight to a `notmuch' search.
Without PREFIX argument, go to the first one in
`notmuch-saved-searches'; with a PREFIX argument, prompt the user
for which saved search to go to; with a double PREFIX
argument (\\[universal-argument] \\[universal-argument]), prompt
for search."
(interactive "P")
(pcase prefix
('nil (notmuch-search (plist-get (car notmuch-saved-searches) :query)))
('(4) (notmuch-search (plist-get (cl-find (completing-read "Saved Search: "
(mapcar (lambda (el)
(plist-get el :name))
notmuch-saved-searches))
notmuch-saved-searches
:key (lambda (el) (plist-get el :name))
:test #'equal)
:query)))
(_ (notmuch-search))))
;; Don't add an initial input when completing addresses
(el-patch-feature notmuch)
(with-eval-after-load 'notmuch
(el-patch-defun notmuch-address-selection-function (prompt collection initial-input)
"Call (`completing-read'
PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
(completing-read
prompt collection nil nil
(el-patch-swap initial-input
nil)
'notmuch-address-history)))
(provide '+notmuch)
;;; +notmuch.el ends here

42
lisp/+nyan-mode.el Normal file
View File

@ -0,0 +1,42 @@
;;; +nyan-mode.el --- Extras for nyan-mode -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
;;; Update even without line number in the mode line.
(defcustom +nyan-mode-update-functions
'( end-of-buffer beginning-of-buffer
next-line previous-line
org-next-visible-heading org-previous-visible-heading)
"Functions after which to force a mode-line update."
:type '(repeat function))
(defun +nyan-mode--fmlu (&rest _)
"Update the mode-line, advice-style."
(force-mode-line-update))
(defun +nyan-mode-advice (&rest _)
"Advise line-moving functions when in `nyan-mode'."
(dolist (fn +nyan-mode-update-functions)
(if nyan-mode
(advice-add fn :after #'+nyan-mode--fmlu)
(advice-remove fn #'+nyan-mode--fmlu))))
(defface +nyan-mode-line nil
"Face for the nyan-mode mode-line indicator.")
(define-minor-mode +nyan-local-mode
"My very own `nyan-mode' that isn't global and doesn't update the mode-line."
:global nil
:group 'nyan
(dolist (fn +nyan-mode-update-functions)
(if +nyan-local-mode
(advice-add fn :after #'+nyan-mode--fmlu)
(advice-remove fn #'+nyan-mode--fmlu))))
(define-globalized-minor-mode +nyan-mode +nyan-local-mode +nyan-local-mode)
(provide '+nyan-mode)
;;; +nyan-mode.el ends here

60
lisp/+orderless.el Normal file
View File

@ -0,0 +1,60 @@
;;; +orderless.el --- Mostly from minad -*- lexical-binding: t; -*-
;;; Commentary:
;; See https://github.com/minad/consult/wiki#minads-orderless-configuration
;;; Code:
(require 'orderless)
;;; Dispataching
(defvar +orderless-dispatch-alist '((?% . char-fold-to-regexp)
(?! . orderless-without-literal)
(?` . orderless-initialism)
(?= . orderless-literal)
(?~ . orderless-flex))
"Charcters to dispatch styles on orderless segments.")
(defun +orderless-dispatch (pattern index _total)
"Dispatch orderless segments of a search string.
Dispatchers are taken from `+orderless-dispatch-alist', and added
to the following defaults:
- regexp$ :: matches REGEXP at the end of the pattern.
- .ext :: matches EXT (at end of pattern)
Dispatch characters can be added at the beginning or ending of a
segment to make that segment match accordingly."
(cond
;; Ensure that $ works with Consult commands, which add disambiguation
;; suffixes
((string-suffix-p "$" pattern)
(cons 'orderless-regexp
(concat (substring pattern 0 -1) "[\x100000-\x10FFFD]*$")))
;; File extensions
((and
;; Completing filename or eshell
(or minibuffer-completing-file-name
(derived-mode-p 'eshell-mode))
;; File extension
(string-match-p "\\`\\.." pattern))
(cons 'orderless-regexp
(concat "\\." (substring pattern 1) "[\x100000-\x10FFFD]*$")))
;; Ignore single !
((string= "!" pattern) `(orderless-literal . ""))
;; Prefix and suffix
((if-let (x (assq (aref pattern 0) +orderless-dispatch-alist))
(cons (cdr x) (substring pattern 1))
(when-let (x (assq (aref pattern (1- (length pattern)))
+orderless-dispatch-alist))
(cons (cdr x) (substring pattern 0 -1)))))))
(orderless-define-completion-style +orderless-with-initialism
(orderless-matching-styles '(orderless-initialism
orderless-literal
orderless-regexp)))
(provide '+orderless)
;;; +orderless.el ends here

29
lisp/+org-attach.el Normal file
View File

@ -0,0 +1,29 @@
;;; +org-attach.el --- Fixes for org-attach -*- lexical-binding: t; -*-
;;; Commentary:
;; `org-attach-attach' doesn't fix the path name. Before I submit a bug, I'm
;; just fixing it by advising `org-attach-attach'.
;;; Code:
(defun +org-attach-attach-fix-args (args)
"ADVICE for `org-attach-attach' to normalize FILE first.
VISIT-DIR and METHOD are passed through unchanged.
This should be applied as `:filter-args' advice."
(cons (expand-file-name (car args)) (cdr args)))
(define-minor-mode +org-attach-fix-args-mode
"Fix the arguments passed to `org-attach-attach'.
This mode normalizes the filename passed to `org-attach-attach'
so that links can be properly made."
:lighter ""
:keymap nil
:global t ; I figure, what does this hurt?
(if +org-attach-fix-args-mode
(advice-add 'org-attach-attach :filter-args #'+org-attach-attach-fix-args)
(advice-remove 'org-attach-attach #'+org-attach-attach-fix-args)))
(provide '+org-attach)
;;; +org-attach.el ends here

View File

@ -85,5 +85,80 @@ properly process the variable."
;; Sort after, maybe
(when sort-after (+org-capture-sort list))))
(defun +org-template--ensure-path (keys &optional list)
"Ensure path of keys exists in `org-capture-templates'."
(unless list (setq list 'org-capture-templates))
(when (> (length key) 1)
;; Check for existence of groups.
(let ((expected (cl-loop for i from 1 to (1- (length key))
collect (substring key 0 i) into keys
finally return keys)))
(cl-loop for ek in expected
if (not (+org-capture--get ek (symbol-value list))) do
(setf (+org-capture--get ek (symbol-value list))
(list (format "(Group %s)" ek)))))))
(defcustom +org-capture-default-type 'entry
"Default template for `org-capture-templates'."
:type '(choice (const :tag "Entry" entry)
(const :tag "Item" item)
(const :tag "Check Item" checkitem)
(const :tag "Table Line" table-line)
(const :tag "Plain Text" plain)))
(defcustom +org-capture-default-target ""
"Default target for `org-capture-templates'."
;; TODO: type
)
(defcustom +org-capture-default-template nil
"Default template for `org-capture-templates'."
;; TODO: type
)
(defun +org-define-capture-templates-group (keys description)
"Add a group title to `org-capture-templates'."
(setf (+org-capture--get keys org-capture-templates)
(list description)))
;; [[https://github.com/cadadr/configuration/blob/39813a771286e542af3aa333172858532c3bb257/emacs.d/gk/gk-org.el#L1573][from cadadr]]
(defun +org-define-capture-template (keys description &rest args)
"Define a capture template and necessary antecedents.
ARGS is a plist, which in addition to the additional options
`org-capture-templates' accepts, takes the following and places
them accordingly: :type, :target, and :template. Each of these
corresponds to the same field in `org-capture-templates's
docstring, which see. Likewise with KEYS and DESCRIPTION, which
are passed separately to the function.
This function will also create all the necessary intermediate
capture keys needed for `org-capture'; that is, if KEYS is
\"wcp\", entries for \"w\" and \"wc\" will both be ensured in
`org-capture-templates'."
(declare (indent 2))
;; Check for existence of parent groups
(when (> (length keys) 1)
(let ((expected (cl-loop for i from 1 to (1- (length keys))
collect (substring 0 i) into keys
finally return keys)))
(cl-loop
for ek in expected
if (not (+org-capture--get ek org-capture-templates))
do (+org-define-capture-templates-group ek (format "(Group %s)" ek)))))
(if (null args)
;; Add the title
(+org-define-capture-templates-group keys description)
;; Add the capture template.
(setf (+org-capture--get keys org-capture-templates)
(append (list (or (plist-get args :type)
+org-capture-default-type)
(or ( plist-get args :target)
+org-capture-default-target)
(or (plist-get args :template)
+org-capture-default-template))
(cl-loop for (key val) on args by #'cddr
unless (member key '(:type :target :template))
append (list key val))))))
(provide '+org-capture)
;;; +org-capture.el ends here

47
lisp/+org-drawer-list.el Normal file
View File

@ -0,0 +1,47 @@
;;; +org-drawer-list.el --- Add stuff to org drawers easy-style -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'org)
(require '+org)
(require 'ol)
(require 'org-drawer-list)
(defcustom +org-drawer-list-resources-drawer "RESOURCES"
"Where to add links with `+org-drawer-list-add-resource'.")
(defun +org-drawer-list-add-resource (url &optional title)
"Add URL to the resource drawer of the current tree.
The resource drawer is given by the variable
`+org-drawer-list-resources-drawer'. If optional TITLE is given,
format the list item as an Org link."
(interactive
(let* ((clipboard-url (if (string-match-p (rx (sequence bos
(or "http"
"gemini"
"gopher"
"tel"
"mailto")))
(current-kill 0))
(string-trim (current-kill 0))
(read-string "Resource URL: ")))
(url-title (let ((clipboard-headings
(+org-insert--get-title-and-headings clipboard-url)))
(read-string "title (edit): "
(completing-read
"title: " clipboard-headings
nil nil nil nil (car clipboard-headings))))))
(list clipboard-url url-title)))
(let (current-visible-mode visible-mode)
;; XXX: This is not the "proper" way to fix the issue I was having --- I've
;; isolated the bug to somewhere in `org-insert-item', but this fix works
;; well enough™ for now.
(visible-mode +1)
(org-drawer-list-add +org-drawer-list-resources-drawer
(org-link-make-string url title))
(visible-mode (if current-visible-mode +1 -1))))
(provide '+org-drawer-list)
;;; +org-drawer-list.el ends here

112
lisp/+org-wc.el Normal file
View File

@ -0,0 +1,112 @@
;;; +org-wc.el --- org-wc in the modeline -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'org-wc)
(require '+modeline)
(require 'cl-lib)
(defgroup +org-wc nil
"Extra fast word-counting in `org-mode'"
:group 'org-wc
:group 'org)
(defvar-local +org-wc-word-count nil
"Running total of words in this buffer.")
(defcustom +org-wc-update-after-funcs '(org-narrow-to-subtree
org-narrow-to-block
org-narrow-to-element
org-capture-narrow)
"Functions after which to update the word count."
:type '(repeat function))
(defcustom +org-wc-deletion-idle-timer 0.25
"Length of time, in seconds, to wait before updating word-count."
:type 'number)
(defcustom +org-wc-huge-change 5000
"Number of characters that constitute a \"huge\" insertion."
:type 'number)
(defcustom +org-wc-huge-buffer 10000
"Number of words past which we're not going to try to count."
:type 'number)
(defvar +org-wc-correction -5
"Number to add to `+org-wc-word-count', for some reason?
`+org-wc-word-count' seems to consistently be off by 5. Thus
this correction. (At some point I should correct the underlying
code... probably).")
(defvar-local +org-wc-update-timer nil)
(defun +org-wc-delayed-update (&rest _)
(if +org-wc-update-timer
(setq +org-wc-update-timer nil)
(setq +org-wc-update-timer
(run-with-idle-timer +org-wc-deletion-idle-timer nil #'+org-wc-update))))
(defun +org-wc-force-update ()
(interactive)
(message "Counting words...")
(when (timerp +org-wc-update-timer)
(cancel-timer +org-wc-update-timer))
(+org-wc-update)
(message "Counting words...done"))
(defun +org-wc-update (&rest _) ; Needs variadic parameters, since it's advice
(dlet ((+org-wc-counting t))
(+org-wc-buffer)
(force-mode-line-update)
(setq +org-wc-update-timer nil)))
(defun +org-wc-changed (start end length)
(+org-wc-delayed-update))
(defun +org-wc-buffer ()
"Count the words in the buffer."
(when (and (derived-mode-p 'org-mode)
(not (eq +org-wc-word-count 'huge)))
(setq +org-wc-word-count
(cond
((> (count-words (point-min) (point-max))
+org-wc-huge-buffer)
'huge)
(t (org-word-count-aux (point-min) (point-max)))))))
(defvar +org-wc-counting nil
"Are we currently counting?")
(defun +org-wc-recount-widen (&rest _)
(when (and (not +org-wc-counting))
(+org-wc-update)))
(defun +org-wc-modeline ()
(cond
((eq +org-wc-word-count 'huge) "huge")
(+org-wc-word-count (format "%sw" (max 0 (+ +org-wc-word-count +org-wc-correction))))))
(define-minor-mode +org-wc-mode
"Count words in `org-mode' buffers in the mode-line."
:lighter ""
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-.") #'+org-wc-force-update)
map)
(if +org-wc-mode
(progn ; turn on
(+org-wc-buffer)
(add-hook 'after-change-functions #'+org-wc-delayed-update nil t)
(setq-local +modeline-position-function #'+org-wc-modeline)
(dolist (fn +org-wc-update-after-funcs)
(advice-add fn :after #'+org-wc-update)))
(progn ; turn off
(remove-hook 'after-change-functions #'+org-wc-delayed-update t)
(kill-local-variable '+modeline-position-function)
(dolist (fn +org-wc-update-after-funcs)
(advice-remove fn #'+org-wc-update)))))
(provide '+org-wc)
;;; +org-wc.el ends here

View File

@ -2,12 +2,12 @@
;;; Code:
(require 'el-patch)
(require 'org)
(require 'org-element)
(require 'ox)
;;; org-return-dwim - https://github.com/alphapapa/unpackaged.el
;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
;;; org-return-dwim - [[https://github.com/alphapapa/unpackaged.el][unpackaged]] and [[http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/][kitchin]]
(defun +org-element-descendant-of (type element)
"Return non-nil if ELEMENT is a descendant of TYPE.
@ -80,8 +80,13 @@ appropriate. In tables, insert a new row or end the table."
(let* ((context (org-element-context))
(first-item-p (eq 'plain-list (car context)))
(itemp (eq 'item (car context)))
(emptyp (eq (org-element-property :contents-begin context)
(org-element-property :contents-end context)))
(emptyp (or
;; Empty list item (regular)
(eq (org-element-property :contents-begin context)
(org-element-property :contents-end context))
;; Empty list item (definition)
;; This seems to work, with minimal testing. -- 2022-02-17
(looking-at " *::")))
(item-child-p
(+org-element-descendant-of 'item context)))
;; The original function from unpackaged just tested the (or ...) test
@ -92,7 +97,7 @@ appropriate. In tables, insert a new row or end the table."
;; for now, it works well enough.
(cond ((and itemp emptyp)
(delete-region (line-beginning-position) (line-end-position))
(insert "\n\n"))
(insert "\n"))
((or first-item-p
(and itemp (not emptyp))
item-child-p)
@ -139,38 +144,41 @@ N is passed to the functions."
Optional PREFIX argument operates on the entire buffer.
Drawers are included with their headings."
(interactive "P")
(org-map-entries (lambda ()
(org-with-wide-buffer
;; `org-map-entries' narrows the buffer, which
;; prevents us from seeing newlines before the
;; current heading, so we do this part widened.
(while (not (looking-back "\n\n" nil))
;; Insert blank lines before heading.
(insert "\n")))
(let ((end (org-entry-end-position)))
;; Insert blank lines before entry content
(forward-line)
(while (and (org-at-planning-p)
(< (point) (point-max)))
;; Skip planning lines
(forward-line))
(while (re-search-forward
org-drawer-regexp end t)
;; Skip drawers. You might think that
;; `org-at-drawer-p' would suffice, but for
;; some reason it doesn't work correctly when
;; operating on hidden text. This works, taken
;; from `org-agenda-get-some-entry-text'.
(re-search-forward "^[ \t]*:END:.*\n?" end t)
(goto-char (match-end 0)))
(unless (or (= (point) (point-max))
(org-at-heading-p)
(looking-at-p "\n"))
(insert "\n"))))
t
(if prefix
nil
'tree)))
(let ((org-element-use-cache nil))
(org-map-entries (lambda ()
(let ((beg (org-entry-beginning-position))
(end (org-entry-end-position)))
(org-with-wide-buffer
;; `org-map-entries' narrows the buffer, which
;; prevents us from seeing newlines before the
;; current heading, so we do this part widened.
(while (not (looking-back "\n\n" nil))
;; Insert blank lines before heading.
(insert "\n")))
;; Insert blank lines before entry content
(forward-line)
(while (and (org-at-planning-p)
(< (point) (point-max)))
;; Skip planning lines
(forward-line))
(while (re-search-forward
org-drawer-regexp end t)
;; Skip drawers. You might think that
;; `org-at-drawer-p' would suffice, but for
;; some reason it doesn't work correctly when
;; operating on hidden text. This works, taken
;; from `org-agenda-get-some-entry-text'.
(re-search-forward "^[ \t]*:END:.*\n?" end t)
(goto-char (match-end 0)))
(unless (or (= (point) (point-max))
(org-at-heading-p)
(looking-at-p "\n"))
(insert "\n"))))
t
(if prefix
nil
'tree))))
;;; org-count-words
@ -245,46 +253,61 @@ instead of the true count."
((use-region-p)
(message "%d words in region"
(+org-count-words-stupidly (region-beginning)
(region-end))))
(region-end))))
(t
(message "%d words in buffer"
(+org-count-words-stupidly (point-min)
(point-max))))))
(point-max))))))
;;; org-insert-link-dwim - https://xenodium.com/emacs-dwim-do-what-i-mean/
(defun +org-insert-link-dwim ()
(defun +org-insert--get-title-and-headings (url)
"Retrieve title and headings from URL.
Return as a list."
(with-current-buffer (url-retrieve-synchronously url)
(let ((dom (libxml-parse-html-region (point-min) (point-max))))
(cl-remove-if
(lambda (i) (string= i ""))
(apply #'append (mapcar (lambda (tag)
(mapcar #'dom-text
(dom-by-tag dom tag)))
'(title h1 h2 h3 h4 h5 h6)))))))
(defun +org-insert-link-dwim (&optional interactivep)
"Like `org-insert-link' but with personal dwim preferences."
(interactive)
(interactive '(t))
(let* ((point-in-link (org-in-regexp org-link-any-re 1))
(clipboard-url (when (string-match-p
(rx (sequence bos
(or "http"
"gemini"
"gopher")))
"gopher"
"tel"
"mailto")))
(current-kill 0))
(current-kill 0)))
(region-content (when (region-active-p)
(buffer-substring-no-properties (region-beginning)
(region-end)))))
(cond ((and region-content clipboard-url (not point-in-link))
(delete-region (region-beginning) (region-end))
(insert (org-link-make-string clipboard-url region-content)))
((and clipboard-url (not point-in-link))
(insert (org-link-make-string
clipboard-url
(read-string "title: "
(with-current-buffer
(url-retrieve-synchronously
clipboard-url)
(dom-text
(car
(dom-by-tag (libxml-parse-html-region
(point-min)
(point-max))
'title))))))))
(t
(call-interactively 'org-insert-link)))))
(region-end))))
(org-link (when (and clipboard-url (not point-in-link))
(org-link-make-string
(string-trim clipboard-url)
(or region-content
(let ((clipboard-headings
(+org-insert--get-title-and-headings clipboard-url)))
(read-string "title (edit): "
(completing-read
"title: " clipboard-headings
nil nil nil nil (car clipboard-headings)))))))))
(if interactivep
(cond ((and region-content clipboard-url (not point-in-link))
(delete-region (region-beginning) (region-end))
(insert org-link))
((and clipboard-url (not point-in-link))
(insert org-link))
(t
(call-interactively 'org-insert-link)))
org-link)))
;;; Navigate headings with widening
@ -309,12 +332,21 @@ instead of the true count."
;;; Hooks & Advice
(defvar +org-before-save-prettify-buffer t
"Prettify org buffers before saving.")
(put '+org-before-save-prettify-buffer 'safe-local-variable #'booleanp)
(defun +org-before-save@prettify-buffer ()
(save-mark-and-excursion
(mark-whole-buffer)
;;(org-fill-paragraph nil t)
(+org-fix-blank-lines t)
(org-align-tags t)))
(when +org-before-save-prettify-buffer
(save-mark-and-excursion
(+org-unsmartify)
(+org-fix-blank-lines t)
(org-align-tags t)
(when (buffer-narrowed-p)
(goto-char (point-min))
(forward-line 1)
(org-narrow-to-subtree)))))
(defun +org-delete-backward-char (N)
"Keep tables aligned while deleting N characters backward.
@ -343,7 +375,7 @@ the deletion might narrow the column."
;; `org-pretty-entities-include-sub-superscripts', which really does exactly
;; what I wanted.
(defface +org-script-markers '((t :inherit shadow))
(defface +org-script-markers '((t (:inherit shadow)))
"Face to be used for sub/superscripts markers i.e., ^, _, {, }.")
;; Hiding the super and subscript markers is extremely annoying
@ -383,7 +415,7 @@ the deletion might narrow the column."
;; (nth (if table-p 2 0) org-script-display)
(nth 2 org-script-display)))
(put-text-property (match-beginning 2) (match-end 2)
'face 'vz/org-script-markers)
'face '+org-script-markers)
(when (and (eq (char-after (match-beginning 3)) ?{)
(eq (char-before (match-end 3)) ?}))
(put-text-property (match-beginning 3) (1+ (match-beginning 3))
@ -398,5 +430,377 @@ the deletion might narrow the column."
"Notify the user of what phone NUMBER to call."
(message "Call: %s" number))
(defun +org-sms-open (number _)
"Notify the user of what phone NUMBER to text."
(message "SMS: %s" number))
;; Make a horizontal rule!
(defun +org-horizontal-rule ()
"Make a horizontal rule after the current line."
(interactive nil org-mode)
(unless (eq (line-beginning-position) (line-end-position))
(end-of-line)
(newline))
(dotimes (_ fill-column)
(insert "-")))
;; Follow links, DWIM style
(defun +org-open-at-point-dwim (&optional arg)
"Open thing at point, or if there isn't something, list things."
(interactive "P")
(save-excursion
(let* ((this-char-type (org-element-type (org-element-context)))
(prev-char-type (ignore-errors
(save-excursion
(backward-char)
(org-element-type (org-element-context)))))
(types '(citation citation-reference clock comment comment-block
footnote-definition footnote-reference headline
inline-src-block inlinetask keyword link
node-property planning src-block timestamp))
(type this-char-type))
(when (and (memq this-char-type types) (memq prev-char-type types))
(backward-char)
(setq type prev-char-type)) ; what the fuckckckckck
;; Okay, so this ^ is pretty janky and doesn't /really/ work that well,
;; especially on DEADLINE (and probably SCHEDULED) lines. However, since
;; I really just want to open the list of URLs /most of the time/, I'm
;; fixing it like this instead.
(unless (and (memq type types)
(ignore-errors (org-open-at-point arg)
t))
(while (not
(progn
(org-back-to-heading)
(car (org-offer-links-in-entry (current-buffer) (point) 1))))
(org-up-heading-all 1))
(org-open-at-point arg)))))
;;; Open local HTML files with `browse-url'
(defun +org-open-html (file-path link-string)
"Open FILE-PATH with `browse-url'.
This function is intended to use with `org-file-apps'. See the
documentation of that function for a description of the two
arguments here, FILE-PATH and LINK-STRING."
(message "Opening %s (%s)..." file-path link-string)
(browse-url file-path))
(defun +org-insert-horizontal-rule (prefix)
"Insert a horizontal rule (-----) after the current line.
With PREFIX, insert before the current line."
(interactive "P")
(if prefix
(move-beginning-of-line nil)
(move-end-of-line nil)
(forward-line 1))
(insert "-----\n"))
;;; Make code snippets in org-mode easier to type
;; http://mbork.pl/2022-01-17_Making_code_snippets_in_Org-mode_easier_to_type
(defun +org-insert-backtick ()
"Insert a backtick using `org-self-insert-command'."
(interactive)
(setq last-command-event ?`)
(call-interactively #'org-self-insert-command))
(defvar-local +org-insert-tilde-language nil
"Default language name in the current Org file.
If nil, `org-insert-tilde' after 2 tildes inserts an \"example\"
block. If a string, it inserts a \"src\" block with the given
language name.")
(defun +org-insert-tilde ()
"Insert a tilde using `org-self-insert-command'."
(interactive)
(if (string= (buffer-substring-no-properties (- (point) 3) (point))
"\n~~")
(progn (delete-char -2)
(if +org-insert-tilde-language
(insert (format "#+begin_src %s\n#+end_src"
+org-insert-tilde-language))
(insert "#+begin_example\n#+end_example"))
(forward-line -1)
(if (string= +org-insert-tilde-language "")
(move-end-of-line nil)
;;(org-edit-special) ; Useful really only with splits.
))
(setq last-command-event ?~)
(call-interactively #'org-self-insert-command)))
;;; Better org faces
;; see `org-emphasis-alist'
(defface org-bold '((t (:weight bold)))
"Bold face in `org-mode' documents.")
(defface org-italic '((t (:slant italic)))
"Italic face in `org-mode' documents.")
(defface org-underline '((t (:underline t)))
"Underline face in `org-mode' documents.")
(defface org-strikethrough '((t (:strike-through t)))
"Strike-through face for `org-mode' documents.")
;; `org-verbatim' and `org-code' are apparently already things, so we skip them
;; here.
;;; Copy org trees as HTML
;; Thanks to Oleh Krehel, via [[https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard][this StackExchange question]].
(defun +org-export-clip-to-html
(&optional async subtreep visible-only body-only ext-plist post-process)
"Export region to HTML, and copy it to the clipboard.
Arguments ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST,
and POST-PROCESS are passed to `org-export-to-file'."
(interactive) ; XXX: hould this be interactive?
(message "Exporting Org to HTML...")
(let ((org-tmp-file "/tmp/org.html"))
(org-export-to-file 'html org-tmp-file
async subtreep visible-only body-only ext-plist post-process)
(start-process "xclip" "*xclip*"
"xclip" "-verbose"
"-i" org-tmp-file
"-t" "text/html"
"-selection" "clipboard"))
(message "Exporting Org to HTML...done."))
;; Specialized functions
(defun +org-export-clip-subtree-to-html ()
"Export current subtree to HTML."
(interactive)
(+org-export-clip-to-html nil :subtree))
;;; Unsmartify quotes and dashes and stuff.
(defun +org-unsmartify ()
"Replace \"smart\" punctuation with their \"dumb\" counterparts."
(interactive)
(save-excursion
(goto-char (point-min))
(while (re-search-forward "[“”‘’–—]" nil t)
(let ((replace (pcase (match-string 0)
((or "" "") "\"")
((or "" "") "'")
("" "--")
("" "---"))))
(replace-match replace nil nil)))))
;;; go forward and backward in the tree, ~ cleanly ~
;; https://stackoverflow.com/a/25201697/10756297
(defun +org-show-next-heading-tidily ()
"Show next entry, keeping other entries closed."
(interactive)
(if (save-excursion (end-of-line) (outline-invisible-p))
(progn (org-show-entry) (show-children))
(outline-next-heading)
(unless (and (bolp) (org-on-heading-p))
(org-up-heading-safe)
(hide-subtree)
(user-error "Boundary reached"))
(org-overview)
(org-reveal t)
(org-show-entry)
(recenter-top-bottom)
(show-children)
(recenter-top-bottom 1)))
(defun +org-show-previous-heading-tidily ()
"Show previous entry, keeping other entries closed."
(interactive)
(let ((pos (point)))
(outline-previous-heading)
(unless (and (< (point) pos) (bolp) (org-on-heading-p))
(goto-char pos)
(hide-subtree)
(user-error "Boundary reached"))
(org-overview)
(org-reveal t)
(org-show-entry)
(recenter-top-bottom)
(show-children)
(recenter-top-bottom 1)))
;;; Make `org-flag-region' (which folds subtrees) recognize
;; [[https://teddit.net/r/orgmode/comments/u3du0v/how_to_make_orgcycle_respect_and_always_show_the/][from u/yantar92]]
;; (advice-add 'org-flag-region :around #'org-flag-region@unfold-page-breaks)
(defun org-flag-region@unfold-page-breaks (oldfun from to flag &optional spec)
"ADVICE to unfold all the page-break lines inside a folded region."
(funcall oldfun from to flag spec)
(when (and flag (not (eq 'visible spec)))
(org-with-point-at from
(while (re-search-forward "\n\u000c\n" to t)
(org-flag-region (match-beginning 0) (match-end 0) t 'visible)))))
;;; Emacs 28+: wrap on hyphens
;; https://emacs.stackexchange.com/a/71342/37239
(defcustom +org-category-table (let ((table (copy-category-table)))
(modify-category-entry ?- ?| table)
table)
"Character category table for `org-mode'."
:type 'sexp)
(defun +org-wrap-on-hyphens ()
"Soft-wrap `org-mode' buffers on spaces and hyphens."
(set-category-table +org-category-table)
(setq-local word-wrap-by-category t))
;;; Inhibit hooks on `org-agenda'
;; It's really annoying when I call `org-agenda' and five hundred Ispell
;; processes are created because I have `flyspell-mode' in the hook. This mode
;; inhibits those hooks when entering the agenda, but runs them when opening the
;; actual buffer.
(defun +org-agenda-inhibit-hooks (fn &rest r)
"Advice to inhibit hooks when entering `org-agenda'."
(dlet ((org-mode-hook nil)) ; I'm not sure if `dlet' is strictly needed
(apply fn r)))
(defvar-local +org-hook-has-run-p nil
"Whether `org-mode-hook' has run in the current buffer.")
(defun +org-agenda-switch-run-hooks (&rest _)
"Advice to run `org-mode-hook' when entering org-mode.
This should only fire when switching to a buffer from `org-agenda'."
(unless +org-hook-has-run-p
(run-mode-hooks 'org-mode-hook)
(setq +org-hook-has-run-p t)))
(define-minor-mode +org-agenda-inhibit-hooks-mode
"Inhibit `org-mode-hook' when opening `org-agenda'."
:lighter ""
:global t
(if +org-agenda-inhibit-hooks-mode
(progn ; Enable
(advice-add 'org-agenda :around #'+org-agenda-inhibit-hooks)
(advice-add 'org-agenda-switch-to :after #'+org-agenda-switch-run-hooks))
(progn ; Disable
(advice-remove 'org-agenda #'+org-agenda-inhibit-hooks)
(advice-remove 'org-agenda-switch-to #'+org-agenda-switch-run-hooks))))
;;; "Fix" `org-align-tags'
(el-patch-defun org-align-tags (&optional all)
"Align tags in current entry.
When optional argument ALL is non-nil, align all tags in the
visible part of the buffer."
(let ((get-indent-column
(lambda ()
(let ((offset (el-patch-swap
(if (bound-and-true-p org-indent-mode)
(* (1- org-indent-indentation-per-level)
(1- (org-current-level)))
0)
0)))
(+ org-tags-column
(if (> org-tags-column 0) (- offset) offset))))))
(if (and (not all) (org-at-heading-p))
(org--align-tags-here (funcall get-indent-column))
(save-excursion
(if all
(progn
(goto-char (point-min))
(while (re-search-forward org-tag-line-re nil t)
(org--align-tags-here (funcall get-indent-column))))
(org-back-to-heading t)
(org--align-tags-here (funcall get-indent-column)))))))
;;; Meta-return
(defun +org-meta-return (&optional arg)
"Insert a new line, or wrap a region in a table.
See `org-meta-return', but `+org-return-dwim' does most of the
stuff I would want out of that function already.
When called with a prefix ARG, will still unconditionally call
`org-insert-heading'."
(interactive "P")
(org-fold-check-before-invisible-edit 'insert)
(or (run-hook-with-args-until-success 'org-metareturn-hook) ; Allow customizations
(call-interactively (cond (arg #'org-insert-heading)
((org-at-table-p) #'org-table-wrap-region)
(t #'org-return)))))
;;; move org archives to a dedicated file
(defun +org-archive-monthwise (archive-file)
(if (file-exists-p archive-file)
(with-current-buffer (find-file-noselect archive-file)
(let ((dir (file-name-directory (file-truename archive-file)))
(prog (make-progress-reporter (format "Archiving from %s..." archive-file)))
(keep-going t))
(goto-char (point-min))
(while keep-going
(when-let* ((time (or (org-entry-get (point) "ARCHIVE_TIME")
(org-get-deadline-time (point))))
(parsed-time (and time
(org-parse-time-string time)))
(refile-target (format "%s%02d-%02d.org"
dir
(decoded-time-year parsed-time)
(decoded-time-month parsed-time)))
(title-str (format "#+title: Archive for %02d-%02d (%s)\n\n"
(decoded-time-year parsed-time)
(decoded-time-month parsed-time)
(file-truename archive-file))))
(unless (file-exists-p refile-target)
(with-current-buffer (find-file-noselect refile-target)
(insert title-str)
(save-buffer)))
(org-refile nil nil (list ""
refile-target
nil
0)))
(progress-reporter-update prog)
(org-next-visible-heading 1)
(when (>= (point) (point-max))
(setq keep-going nil)))))
(message "Archive file %s does not exist!" archive-file)))
;;; el-patch
(el-patch-defun org-format-outline-path (path &optional width prefix separator)
"Format the outline path PATH for display.
WIDTH is the maximum number of characters that is available.
PREFIX is a prefix to be included in the returned string,
such as the file name.
SEPARATOR is inserted between the different parts of the path,
the default is \"/\"."
(setq width (or width 79))
(setq path (delq nil path))
(unless (> width 0)
(user-error "Argument `width' must be positive"))
(setq separator (or separator "/"))
(let* ((org-odd-levels-only nil)
(fpath (concat
prefix (and prefix path separator)
(mapconcat
(lambda (s) (replace-regexp-in-string "[ \t]+\\'" "" s))
(cl-loop for head in path
for n from 0
collect (el-patch-swap
(org-add-props
head nil 'face
(nth (% n org-n-level-faces) org-level-faces))
head))
separator))))
(when (> (length fpath) width)
(if (< width 7)
;; It's unlikely that `width' will be this small, but don't
;; waste characters by adding ".." if it is.
(setq fpath (substring fpath 0 width))
(setf (substring fpath (- width 2)) "..")))
fpath))
(provide '+org)
;;; +org.el ends here

29
lisp/+ox.el Normal file
View File

@ -0,0 +1,29 @@
;;; +ox.el --- org-export helpers -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'ox)
;;; Run hooks before doing any exporting at all
(defcustom +org-export-pre-hook nil
"Functions to run /before/ `org-export-as' does anything.
These will run on the buffer about to be exported, NOT a copy."
:type 'hook)
(defun +org-export-pre-run-hooks (&rest _)
"Run hooks in `+org-export-pre-hook'."
(run-hooks '+org-export-pre-hook))
(defun +org-export-pre-hooks-insinuate ()
"Advise `org-export-as' to run `+org-export-pre-hook'."
(advice-add 'org-export-as :before #'+org-export-pre-run-hooks))
(defun +org-export-pre-hooks-remove ()
"Remove pre-hook advice on `org-export-as'."
(advice-remove 'org-export-as #'+org-export-pre-run-hooks))
(provide '+ox)
;;; +ox.el ends here

26
lisp/+paredit.el Normal file
View File

@ -0,0 +1,26 @@
;;; +paredit.el --- bespoke paredit stuffs -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require '+emacs) ; `+backward-kill-word-wrapper'
(defun +paredit--backward-kill-word (&optional n)
"Perform `paredit-backward-kill-word' N times."
(interactive "p")
(dotimes (_ (or n 1))
(paredit-backward-kill-word)))
(defun +paredit-backward-kill-word (&optional arg)
"Kill a word backward using `paredit-backward-kill-word'.
Wrapped in `+backward-kill-word-wrapper', which see.
Prefix ARG means to just call `paredit-backward-kill-word'."
;; Of course, `paredit-backward-kill-word' doesn't TAKE an argument ... :///
;; So I had to write the wrapper above.
(interactive)
(+backward-kill-word-wrapper #'+paredit--backward-kill-word arg))
(provide '+paredit)
;;; +paredit.el ends here

38
lisp/+pdf-tools.el Normal file
View File

@ -0,0 +1,38 @@
;;; +pdf-tools.el --- Extras for the excellent pdf-tools' -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
;; XXX: The way I'm dispatching browsers here is /very/ down-and-dirty. It
;; needs to be much improved.
(defun +pdf-view-open-all-pagelinks (&optional browse-url-func)
"Open all the links on this page of a PDF.
BROWSE-URL-FUNC overrides the default `browse-url'."
(interactive)
(let ((links (pdf-info-pagelinks (pdf-view-current-page)))
(browse-url-func (or browse-url-func #'browse-url))
(seen))
(dolist (link links)
(when-let* ((uri (alist-get 'uri link))
(_ (not (member uri seen))))
(push uri seen)
(funcall browse-url-func uri)))))
(defun +pdf-view-open-links-in-chrome ()
"Open all links on this PDF page in Chrome.
See also `+pdf-view-open-all-pagelinks'."
(interactive)
(+pdf-view-open-all-pagelinks #'browse-url-chrome))
(defun +pdf-view-position (&optional spacer)
"Return the page we're on for the modeline."
(when (derived-mode-p 'pdf-view-mode)
(format "%sp.%s/%s"
(or spacer (bound-and-true-p +modeline-default-spacer) " ")
(pdf-view-current-page)
(pdf-info-number-of-pages))))
(provide '+pdf-tools)
;;; +pdf-tools.el ends here

View File

@ -5,9 +5,10 @@
;;(require 'scratch)
(defun +scratch-immortal ()
"Bury, don't kill \"*scratc*\" buffer.
"Bury, don't kill \"*scratch*\" buffer.
For `kill-buffer-query-functions'."
(if (eq (current-buffer) (get-buffer "*scratch*"))
(if (or (eq (current-buffer) (get-buffer "*scratch*"))
(eq (current-buffer) (get-buffer "*text*")))
(progn (bury-buffer)
nil)
t))
@ -24,6 +25,53 @@ For `kill-buffer-query-functions'."
(next-line 2))
(rename-buffer (concat "*scratch<" mode ">*") t)))
(defun +scratch-fortune ()
(let* ((fmt (if (executable-find "fmt")
(format "| fmt -%d -s" (- fill-column 2))
""))
(s (string-trim
(if (executable-find "fortune")
(shell-command-to-string (concat "fortune -s" fmt))
"ABANDON ALL HOPE YE WHO ENTER HERE"))))
(concat (replace-regexp-in-string "^" ";; " s)
"\n\n")))
;; [[https://old.reddit.com/r/emacs/comments/ui1q41/weekly_tips_tricks_c_thread/i7ef4xg/][u/bhrgunatha]]
(defun +scratch-text-scratch ()
"Create a \"*text*\" scratch buffer in Text mode."
(with-current-buffer (get-buffer-create "*text*")
(text-mode)))
(defcustom +scratch-buffers '("*text*" "*scratch*")
"Scratch buffers.")
(defvar +scratch-last-non-scratch-buffer nil
"Last buffer that wasn't a scratch buffer.")
(defun +scratch-toggle (buffer)
"Switch to BUFFER, or to the previous (non-scratch) buffer."
(if (or (null +scratch-last-non-scratch-buffer)
(not (member (buffer-name (current-buffer)) +scratch-buffers)))
;; Switch to a scratch buffer
(progn
(setq +scratch-last-non-scratch-buffer (current-buffer))
(switch-to-buffer buffer))
;; Switch away from scratch buffer ...
(if (equal (get-buffer-create buffer) (current-buffer))
;; to the original buffer
(switch-to-buffer +scratch-last-non-scratch-buffer)
;; to another scratch
(switch-to-buffer buffer))))
(defun +scratch-switch-to-scratch ()
"Switch to scratch buffer."
(interactive)
(+scratch-toggle "*scratch*"))
(defun +scratch-switch-to-text ()
"Switch to text buffer."
(interactive)
(+scratch-toggle "*text*"))
(provide '+scratch)
;;; +scratch.el ends here

View File

@ -23,58 +23,194 @@
(require 'el-patch)
(require 'setup)
(require 'straight)
(require 'cl-lib)
(defun +setup-warn (message &rest args)
"Warn the user that something bad happened in `setup'."
(display-warning 'setup (format message args)))
(defun +setup-wrap-to-demote-errors (body name)
"Wrap BODY in a `with-demoted-errors' block.
This behavior is prevented if `setup-attributes' contains the
symbol `without-error-demotion'.
This function differs from `setup-wrap-to-demote-errors' in that
it includes the NAME of the setup form in the warning output."
(if (memq 'without-error-demotion setup-attributes)
body
`(with-demoted-errors ,(format "Error in setup form on line %d (%s): %%S"
(line-number-at-pos)
name)
,body)))
;;; New forms
(setup-define :quit
'setup-quit
:documentation "Quit the current `setup' form.
Good for commenting.")
(setup-define :face
(lambda (face spec)
`(custom-set-faces '(,face ,spec 'now "Customized by `setup'.")))
(lambda (face spec)
`(custom-set-faces (list ,face ,spec 'now "Customized by `setup'.")))
:documentation "Customize FACE with SPEC using `custom-set-faces'."
:repeatable t)
(setup-define :load-after
(lambda (&rest features)
(let ((body `(require ',(setup-get 'feature))))
(dolist (feature (nreverse features))
(setq body `(with-eval-after-load ',feature ,body)))
body))
(lambda (&rest features)
(let ((body `(require ',(setup-get 'feature))))
(dolist (feature (nreverse features))
(setq body `(with-eval-after-load ',feature ,body)))
body))
:documentation "Load the current feature after FEATURES.")
(setup-define :also-straight
(lambda (recipe) `(setup (:straight ,recipe)))
:documentation
"Install RECIPE with `straight-use-package', after loading FEATURE."
:repeatable t
:after-loaded t)
(setup-define :load-from
(lambda (path)
`(let ((path* (expand-file-name ,path)))
(if (file-exists-p path*)
(add-to-list 'load-path path*)
,(setup-quit))))
:documentation "Add PATH to load path.
This macro can be used as NAME, and it will replace itself with
the nondirectory part of PATH.
If PATH does not exist, abort the evaluation."
:shorthand (lambda (args)
(intern
(file-name-nondirectory
(directory-file-name (cadr args))))))
(setup-define :straight
(lambda (recipe)
`(unless (ignore-errors (straight-use-package ',recipe))
,(setup-quit)))
:documentation
"Install RECIPE with `straight-use-package'.
This macro can be used as HEAD, and will replace itself with the
first RECIPE's package."
:repeatable t
:shorthand (lambda (sexp)
(let ((recipe (cadr sexp)))
(if (consp recipe)
(car recipe)
recipe))))
(setup-define :straight-when
(lambda (recipe condition)
`(unless (and ,condition
(straight-use-package ',recipe))
(setup-define :needs
(lambda (executable)
`(unless (executable-find ,executable)
,(setup-quit)))
:documentation "If EXECUTABLE is not in the path, stop here."
:repeatable 1)
;;; Package integrations
;;; Straight.el
(defun setup--straight-handle-arg (arg var)
(cond
((and (boundp var) (symbol-value var)) t)
((keywordp arg) (set var t))
((functionp arg) (set var nil) (funcall arg))
((listp arg) (set var nil) arg)))
(with-eval-after-load 'straight
(setup-define :straight
(lambda (recipe &rest predicates)
(let* ((skp (make-symbol "straight-keyword-p"))
(straight-use-p
(cl-mapcar
(lambda (f) (setup--straight-handle-arg f skp))
predicates))
(form `(unless (and ,@straight-use-p
(condition-case e
(straight-use-package ',recipe)
(error
(+setup-warn ":straight error: %S"
',recipe)
,(setup-quit))
(:success t)))
,(setup-quit))))
;; Keyword arguments --- :quit is special and should short-circuit
(if (memq :quit predicates)
(setq form `,(setup-quit))
;; Otherwise, handle the rest of them ...
(when-let ((after (cadr (memq :after predicates))))
(setq form `(with-eval-after-load ,(if (eq after t)
(setup-get 'feature)
after)
,form))))
;; Finally ...
form))
:documentation "Install RECIPE with `straight-use-package'.
If PREDICATES are given, only install RECIPE if all of them return non-nil.
The following keyword arguments are also recognized:
- :quit --- immediately stop evaluating. Good for commenting.
- :after FEATURE --- only install RECIPE after FEATURE is loaded.
If FEATURE is t, install RECIPE after the current feature."
:repeatable nil
:indent 1
:shorthand (lambda (sexp)
(let ((recipe (cadr sexp)))
(or (car-safe recipe) recipe)))))
;;; Apheleia
(setup-define :apheleia
(lambda (name formatter &optional mode -pend)
(let* ((mode (or mode (setup-get 'mode)))
(current-formatters (and -pend
(alist-get mode apheleia-formatters))))
`(with-eval-after-load 'apheleia
(setf (alist-get ',name apheleia-formatters)
,formatter)
(setf (alist-get ',mode apheleia-mode-alist)
',(pcase -pend
(:append (append (ensure-list current-formatters)
(list name)))
(:prepend (cons name (ensure-list current-formatters)))
('nil name)
(_ (error "Improper `:apheleia' -PEND argument")))))))
:documentation
"Install RECIPE with `straight-use-package' when CONDITION is met.
If CONDITION is false, or if `straight-use-package' fails, stop
evaluating the body. This macro can be used as HEAD, and will
replace itself with the RECIPE's package."
:repeatable 2
:indent 1
:shorthand (lambda (sexp)
(let ((recipe (cadr sexp)))
(if (consp recipe) (car recipe) recipe))))
"Register a formatter to `apheleia''s lists.
NAME is the name given to the formatter in `apheleia-formatters'
and `apheleia-mode-alist'. FORMATTER is the command paired with
NAME in `apheleia-formatters'. MODE is the mode or modes to add
NAME to in `apheleia-mode-alist'. If MODE is not given or nil,
use the setup form's MODE. Optional argument -PEND can be one of
`:append' or `:prepend', and if given will append or prepend the
given NAME to the current formatters for the MODE in
`apheleia-mode-alist', rather than replace them (the default).
Example:
(setup
(:apheleia isort (\"isort\" \"--stdout\" \"-\")
python-mode))
; =>
(progn
(setf (alist-get 'isort apheleia-formatters)
'(\"isort\" \"--stdout\" \"-\"))
(setf (alist-get 'python-mode apheleia-mode-alist)
'isort))
This form cannot be repeated, and it cannot be used as HEAD.")
;;; Redefines of `setup' forms
(setup-define :bind-into
(lambda (feature-or-map &rest rest)
(cl-loop for f/m in (ensure-list feature-or-map)
collect (if (string-match-p "-map\\'" (symbol-name f/m))
`(:with-map ,f/m (:bind ,@rest))
`(:with-feature ,f/m (:bind ,@rest)))
into forms
finally return `(progn ,@forms)))
:documentation "Bind into keys into the map(s) of FEATURE-OR-MAP.
FEATURE-OR-MAP can be a feature or map name or a list of them.
The arguments REST are handled as by `:bind'."
:debug '(sexp &rest form sexp)
:indent 1)
(setup-define :require
(lambda (&rest features)
(require 'cl-lib)
(if features
`(progn ,@(cl-loop for feature in features collect
`(unless (require ',feature nil t)
,(setup-quit))))
`(unless (require ',(setup-get 'feature) nil t)
,(setup-quit))))
:documentation "Try to require FEATURE, or stop evaluating body.
This macro can be used as NAME, and it will replace itself with
the first FEATURE."
:repeatable nil
:shorthand #'cadr)
(provide '+setup)
;;; +setup.el ends here

51
lisp/+shr.el Normal file
View File

@ -0,0 +1,51 @@
;;; +shr.el --- SHR extras -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
;;; [[https://github.com/oantolin/emacs-config/blob/master/my-lisp/shr-heading.el][shr-heading]], by oantolin
(defun +shr-heading-next (&optional arg)
"Move forward by ARG headings (any h1-h4).
If ARG is negative move backwards, ARG defaults to 1."
(interactive "p")
(unless arg (setq arg 1))
(catch 'return
(dotimes (_ (abs arg))
(when (> arg 0) (end-of-line))
(if-let ((match
(funcall (if (> arg 0)
#'text-property-search-forward
#'text-property-search-backward)
'face '(shr-h1 shr-h2 shr-h3 shr-h4)
(lambda (tags face)
(cl-loop for x in (if (consp face) face (list face))
thereis (memq x tags))))))
(goto-char
(if (> arg 0) (prop-match-beginning match) (prop-match-end match)))
(throw 'return nil))
(when (< arg 0) (beginning-of-line)))
(beginning-of-line)
(point)))
(defun +shr-heading-previous (&optional arg)
"Move backward by ARG headings (any h1-h4).
If ARG is negative move forwards instead, ARG defaults to 1."
(interactive "p")
(+shr-heading-next (- (or arg 1))))
(defun +shr-heading--line-at-point ()
"Return the current line."
(buffer-substring (line-beginning-position) (line-end-position)))
(defun +shr-heading-setup-imenu ()
"Setup imenu for h1-h4 headings in eww buffer.
Add this function to appropriate major mode hooks such as
`eww-mode-hook' or `elfeed-show-mode-hook'."
(setq-local
imenu-prev-index-position-function #'+shr-heading-previous
imenu-extract-index-name-function #'+shr-heading--line-at-point))
(provide '+shr)
;;; +shr.el ends here

27
lisp/+slack.el Normal file
View File

@ -0,0 +1,27 @@
;;; +slack.el --- Slack customizations and extras -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'slack)
(defgroup +slack nil
"Extra slack customizations."
:group 'slack
:prefix "+slack-")
(defcustom +slack-teams nil
"Teams to register using `slack-register-team'.
This is a list of plists that are passed directly to
`slack-register-team'."
;;TODO: type
)
(defun +slack-register-teams ()
"Register teams in `+slack-teams'."
(dolist (team +slack-teams)
(apply #'slack-register-team team)))
(provide '+slack)
;;; +slack.el ends here

18
lisp/+sly.el Normal file
View File

@ -0,0 +1,18 @@
;;; +sly.el --- Sly customizations -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'sly)
(defun sly-mrepl-return-at-end ()
(interactive)
(if (<= (point-max) (point))
(sly-mrepl-return)
(if (bound-and-true-p paredit-mode)
(paredit-newline)
(electric-newline-and-maybe-indent))))
(provide '+sly)
;;; +sly.el ends here

42
lisp/+straight.el Normal file
View File

@ -0,0 +1,42 @@
;;; +straight.el --- Straight.el extras -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(defun +straight-update-package (package &optional recursive)
"Update PACKAGE using straight.
This pulls, rebuilds, and loads the updated PACKAGE."
(interactive (list (straight--select-package "Update package"
#'straight--installed-p)
current-prefix-arg))
(+with-message (format "Pulling package `%s'%s" package
(if recursive " and deps" ""))
(funcall (if recursive
#'straight-pull-package-and-deps
#'straight-pull-package)
package
:from-upstream))
(+with-message (format "Rebuilding package `%s'%s" package
(if recursive " and deps" ""))
(straight-rebuild-package package recursive))
(+with-message (format "Loading package `%s'%s" package
(if recursive " and deps" ""))
(ignore-errors (load-library (symbol-name package)))
(when recursive
(dolist (dep (straight--get-transitive-dependencies package))
(ignore-errors (load-library (symbol-name package)))))))
(defun +straight-update-all (from-upstream)
"Update all installed packages using straight.
This pulls and rebuilds all packages at once. It does not reload
all of them, for reasons that should be obvious.
With a prefix argument, it also pulls the packages FROM-UPSTREAM."
(interactive "P")
(straight-pull-recipe-repositories)
(straight-pull-all from-upstream)
(straight-rebuild-all))
(provide '+straight)
;;; +straight.el ends here

381
lisp/+tab-bar.el Normal file
View File

@ -0,0 +1,381 @@
;;; +tab-bar.el -*- lexical-binding: t; -*-
;;; Commentary:
;; Emacs 28 comes with an easy-to-use `tab-bar-format' option, but I still use
;; Emacs 27 on my Windows machine. Thus, the code in this file.
;;; Code:
(require 'acdw)
(require 'tab-bar)
(defface +tab-bar-extra
'((t :inherit (tab-bar font-lock-comment-face)))
"Tab bar face for extra information, like the menu-bar and time."
:group 'basic-faces)
;; Common
(defun +tab-bar-space (&optional n)
"Display a space N characters long, or 1."
`((space menu-item ,(+string-repeat (or n 1) " ") ignore)))
(defun +tab-bar-misc-info ()
"Display `mode-line-misc-info', formatted for the tab-bar."
`((misc-info menu-item ,(string-trim-right
(format-mode-line mode-line-misc-info))
ignore)))
(defcustom +tracking-hide-when-org-clocking nil
"Hide the `tracking-mode' information when clocked in."
:type 'boolean)
(defun +tab-bar-tracking-mode ()
"Display `tracking-mode-line-buffers' in the tab-bar."
;; TODO: write something to convert a mode-line construct to a tab-bar
;; construct.
(when (and (bound-and-true-p tracking-mode)
(not (and +tracking-hide-when-org-clocking
(bound-and-true-p org-clock-current-task))))
(cons (when (> (length tracking-mode-line-buffers) 0)
'(track-mode-line-separator menu-item " " ignore))
(cl-loop for i from 0 below (length tracking-mode-line-buffers)
as item = (nth i tracking-mode-line-buffers)
collect (append (list (intern (format "tracking-mode-line-%s" i))
'menu-item
(string-trim (format-mode-line item)))
(if-let ((keymap (plist-get item 'keymap)))
(list (alist-get 'down-mouse-1 (cdadr keymap)))
(list #'ignore))
(when-let ((help (plist-get item 'help-echo)))
(list :help help)))))))
(defun +tab-bar-timer ()
"Display `+timer-string' in the tab-bar."
(when (> (length (bound-and-true-p +timer-string)) 0)
`((timer-string menu-item
,(concat " " +timer-string)
(lambda (ev)
(interactive "e")
(cond ((not +timer-timer) nil)
((equal +timer-string +timer-running-string)
(popup-menu
'("Running timer"
["Cancel timer" +timer-cancel t])
ev))
(t (setq +timer-string ""))))))))
(defun +tab-bar-date ()
"Display `display-time-string' in the tab-bar."
(when display-time-mode
`((date-time-string menu-item
,(substring-no-properties (concat " " (string-trim display-time-string)))
(lambda (ev)
(interactive "e")
(popup-menu
(append '("Timer")
(let (r)
(dolist (time '(3 5 10))
(push (vector (format "Timer for %d minutes" time)
`(lambda () (interactive)
(+timer ,time))
:active t)
r))
(nreverse r))
'(["Timer for ..." +timer t]))
ev))
:help (discord-date-string)))))
(defun +tab-bar-notmuch-count ()
"Display a notmuch count in the tab-bar."
(when (and (executable-find "notmuch")
(featurep 'notmuch))
(let* ((counts (ignore-errors (notmuch-hello-query-counts notmuch-saved-searches)))
(next (cl-find "inbox+unread" counts :key (lambda (l) (plist-get l :name)) :test 'equal))
(next-count (plist-get next :count)))
(when (and next-count (> next-count 0))
`((notmuch-count menu-item
,(format " |%s|" next-count)
ignore
:help ,(format "%s mails requiring attention." next-count)))))))
(defun +tab-bar-org-clock ()
"Display `org-mode-line-string' in the tab-bar."
(when (and (fboundp 'org-clocking-p)
(org-clocking-p))
;; org-mode-line-string
`((org-clocking menu-item
,org-mode-line-string
(lambda (ev)
(interactive "e")
(let ((menu (make-sparse-keymap
(or org-clock-current-task "Org-Clock"))))
(map-keymap (lambda (key binding)
(when (consp binding)
(define-key-after menu (vector key)
(copy-sequence binding))))
(org-clock-menu))
(message "%S" ev)
(popup-menu menu ev)))
:help ,(or (replace-regexp-in-string
(rx "[[" (group (* (not "]")))
"][" (group (* (not "]")))
"]]")
"\\2"
org-clock-current-task)
"Org-Clock")))))
(defcustom +tab-bar-emms-max-length 24
"Maximum length of `+tab-bar-emms'."
:type 'number)
(defun +tab-bar-emms ()
"Display EMMS now playing information."
(when (and (bound-and-true-p emms-mode-line-mode)
emms-player-playing-p)
(let ((now-playing (+string-truncate (emms-mode-line-playlist-current)
(- +tab-bar-emms-max-length 2))))
`(emms-now-playing menu-item
,(concat "{" now-playing "}" " ")
emms-pause
( :help ,(emms-mode-line-playlist-current))))))
(defun +tab-bar-bongo ()
"Display Bongo now playing information."
(when-let ((modep (bound-and-true-p bongo-mode-line-indicator-mode))
(buf (cl-some (lambda (b)
(with-current-buffer b
(when-let* ((modep (derived-mode-p 'bongo-playlist-mode))
(bongo-playlist-buffer b)
(playingp (bongo-playing-p)))
b)))
(buffer-list))))
`((bongo-now-playing menu-item
,(concat "{"
(let ((bongo-field-separator ""))
(+string-truncate (replace-regexp-in-string
"\\(.*\\)\\(.*\\)\\(.*\\)"
"\\1: \\3"
(bongo-formatted-infoset))
;; This isn't right
(- (min 50 (/ (frame-width) 3 )) 2)))
"}")
(lambda () (interactive)
(let ((bongo-playlist-buffer
;; XXX: I'm sure this is terribly inefficient
(cl-some (lambda (b)
(with-current-buffer b
(when-let* ((modep (derived-mode-p
'bongo-playlist-mode))
(bongo-playlist-buffer b)
(playingp (bongo-playing-p)))
b)))
(buffer-list))))
(with-bongo-playlist-buffer
(bongo-pause/resume))))
:help ,(funcall bongo-header-line-function)))))
(defvar +tab-bar-show-original nil
"Original value of `tab-bar-show'.")
(defun +tab-bar-basename ()
"Generate the tab name from the basename of the buffer of the
selected window."
(let* ((tab-file-name (buffer-file-name (window-buffer
(minibuffer-selected-window)))))
(concat " "
(if tab-file-name
(file-name-nondirectory tab-file-name)
(+tab-bar-tab-name-truncated-left)))))
;;; FIXME this doesn't work...
;; (defvar +tab-bar-tab-min-width 8
;; "Minimum width of a tab on the tab bar.")
;; (defvar +tab-bar-tab-max-width 24
;; "Maximum width of a tab on the tab bar.")
;; (defun +tab-bar-fluid-calculate-width ()
;; "Calculate the width of each tab in the tab-bar."
;; (let* ((tab-bar-list (cdr (tab-bar-make-keymap-1)))
;; (tab-bar-avail-width (frame-width))
;; (tab-bar-tab-count (length (tab-bar-tabs)))
;; (tab-bar-close-button-char-width 1)
;; (tab-bar-add-tab-button-char-width 1)
;; (tab-bar-total-width
;; (length (mapconcat
;; (lambda (el)
;; (when-let ((str (car-safe (cdr-safe (cdr-safe el)))))
;; (substring-no-properties (eval str))))
;; tab-bar-list)))
;; (tab-bar-total-tab-width
;; (+ (* tab-bar-tab-count tab-bar-close-button-char-width)
;; tab-bar-add-tab-button-char-width
;; (length (mapconcat
;; (lambda (el)
;; (substring-no-properties (alist-get 'name el)))
;; (tab-bar-tabs)))))
;; (tab-bar-total-nontab-width (- tab-bar-total-width
;; tab-bar-total-tab-width)))
;; (min +tab-bar-tab-max-width
;; (max +tab-bar-tab-min-width
;; (/ (- tab-bar-avail-width
;; tab-bar-total-tab-width
;; tab-bar-total-nontab-width)
;; tab-bar-tab-count)))))
;; (defun +tab-bar-fluid-width ()
;; "Generate the tab name to fluidly fit in the given space."
;; (let* ((tab-file-name (buffer-file-name (window-buffer
;; (minibuffer-selected-window)))))
;; (format (format " %%s%%%ds" (+tab-bar-fluid-calculate-width))
;; (if tab-file-name
;; (file-name-nondirectory tab-file-name)
;; (+tab-bar-tab-name-truncated-left))
;; " ")))
(defun +tab-bar-tab-name-truncated-left ()
"Generate the tab name from the buffer of the selected window.
This is just like `tab-bar-tab-name-truncated', but truncates the
name to the left."
(let* ((tab-name (buffer-name (window-buffer (minibuffer-selected-window))))
(ellipsis (cond
(tab-bar-tab-name-ellipsis)
((char-displayable-p ?…) "")
("...")))
(l-ell (length ellipsis))
(l-name (length tab-name)))
(if (< (length tab-name) tab-bar-tab-name-truncated-max)
tab-name
(propertize (concat
(when (> (+ l-name l-ell) tab-bar-tab-name-truncated-max)
ellipsis)
(truncate-string-to-width tab-name l-name
(max 0 (- l-name tab-bar-tab-name-truncated-max l-ell))))
'help-echo tab-name))))
(defun +tab-bar-format-align-right ()
"Align the rest of tab bar items to the right, pixel-wise."
;; XXX: ideally, wouldn't require `shr' here
(require 'shr) ; `shr-string-pixel-width'
(let* ((rest (cdr (memq '+tab-bar-format-align-right tab-bar-format)))
(rest (tab-bar-format-list rest))
(rest (mapconcat (lambda (item) (nth 2 item)) rest ""))
(hpos (shr-string-pixel-width rest))
(str (propertize " " 'display `(space :align-to (- right (,hpos))))))
`((align-right menu-item ,str ignore))))
;;; Menu bar
;; stole from https://github.com/emacs-mirror/emacs/blob/master/lisp/tab-bar.el
(defun +tab-bar-menu-bar (event)
"Pop up the same menu as displayed by the menu bar.
Used by `tab-bar-format-menu-bar'."
(interactive "e")
(let ((menu (make-sparse-keymap (propertize "Menu Bar" 'hide t))))
(run-hooks 'activate-menubar-hook 'menu-bar-update-hook)
(map-keymap (lambda (key binding)
(when (consp binding)
(define-key-after menu (vector key)
(copy-sequence binding))))
(menu-bar-keymap))
(popup-menu menu event)))
(defcustom +tab-bar-menu-bar-icon " Emacs "
"The string to use for the tab-bar menu icon."
:type 'string)
(defun +tab-bar-format-menu-bar ()
"Produce the Menu button for the tab bar that shows the menu bar."
`((menu-bar menu-item (propertize +tab-bar-menu-bar-icon 'face '+tab-bar-extra)
+tab-bar-menu-bar :help "Menu Bar")))
;;; Tab bar format tabs
(require 'el-patch)
(el-patch-feature tab-bar)
(with-eval-after-load 'tab-bar
(el-patch-defun tab-bar--format-tab (tab i)
"Format TAB using its index I and return the result as a keymap."
(append
(el-patch-remove
`((,(intern (format "sep-%i" i)) menu-item ,(tab-bar-separator) ignore)))
(cond
((eq (car tab) 'current-tab)
`((current-tab
menu-item
,(funcall tab-bar-tab-name-format-function tab i)
ignore
:help "Current tab")))
(t
`((,(intern (format "tab-%i" i))
menu-item
,(funcall tab-bar-tab-name-format-function tab i)
,(alist-get 'binding tab)
:help "Click to visit tab"))))
(when (alist-get 'close-binding tab)
`((,(if (eq (car tab) 'current-tab) 'C-current-tab (intern (format "C-tab-%i" i)))
menu-item ""
,(alist-get 'close-binding tab)))))))
;; Emacs 27
(defun +tab-bar-misc-info-27 (output &rest _)
"Display `mode-line-misc-info' in the `tab-bar' on Emacs 27.
This is :filter-return advice for `tab-bar-make-keymap-1'."
(let* ((reserve (length (format-mode-line mode-line-misc-info)))
(str (propertize " "
'display `(space :align-to (- right (- 0 right-margin)
,reserve)))))
(prog1 (append output
`((align-right menu-item ,str nil))
(+tab-bar-misc-info)))))
;; Emacs 28
(defvar +tab-bar-format-original nil
"Original value of `tab-bar-format'.")
(defun +tab-bar-misc-info-28 ()
"Display `mode-line-misc-info', right-aligned, on Emacs 28."
(append (unless (memq 'tab-bar-format-align-right tab-bar-format)
'(tab-bar-format-align-right))
'(+tab-bar-misc-info)))
(define-minor-mode +tab-bar-misc-info-mode
"Show the `mode-line-misc-info' in the `tab-bar'."
:lighter ""
:global t
(if +tab-bar-misc-info-mode
(progn ; Enable
(setq +tab-bar-show-original tab-bar-show)
(cond
((boundp 'tab-bar-format) ; Emacs 28
(setq +tab-bar-format-original tab-bar-format)
(unless (memq '+tab-bar-misc-info tab-bar-format)
(setq tab-bar-format
(append tab-bar-format (+tab-bar-misc-info-28)))))
((fboundp 'tab-bar-make-keymap-1) ; Emacs 27
(advice-add 'tab-bar-make-keymap-1 :filter-return
'+tab-bar-misc-info-27)))
(setq tab-bar-show t))
(progn ; Disable
(setq tab-bar-show +tab-bar-show-original)
(cond
((boundp 'tab-bar-format) ; Emacs 28
(setq tab-bar-format +tab-bar-format-original))
((fboundp 'tab-bar-make-keymap-1) ; Emacs 27
(advice-remove 'tab-bar-make-keymap-1 '+tab-bar-misc-info-27))))))
(provide '+tab-bar)
;;; +tab-bar.el ends here

30
lisp/+titlecase.el Normal file
View File

@ -0,0 +1,30 @@
;;; +titlecase.el --- Titlecase extras -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(defun +titlecase-sentence-style-dwim (&optional arg)
"Titlecase a sentence.
With prefix ARG, toggle the value of
`titlecase-downcase-sentences' before sentence-casing."
(interactive "P")
(let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences)
titlecase-downcase-sentences)))
(titlecase-dwim 'sentence)))
(defun +titlecase-org-headings ()
(interactive)
(save-excursion
(goto-char (point-min))
;; See also `org-map-tree'. I'm not using that function because I want to
;; skip the first headline. A better solution would be to patch
;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc).
(let ((level (funcall outline-level)))
(while (and (progn (outline-next-heading)
(> (funcall outline-level) level))
(not (eobp)))
(titlecase-line)))))
(provide '+titlecase)
;;; +titlecase.el ends here

View File

@ -12,5 +12,13 @@
(unless (eq 1 (abs (- beg-index vertico--index)))
(ding))))
(defun +vertico-widen-or-complete ()
(interactive)
(if (or vertico-unobtrusive-mode
vertico-flat-mode)
(progn (vertico-unobtrusive-mode -1)
(vertico-flat-mode -1))
(call-interactively #'vertico-insert)))
(provide '+vertico)
;;; +vertico.el ends here

19
lisp/+vterm.el Normal file
View File

@ -0,0 +1,19 @@
;;; +vterm.el --- Vterm extras -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'vterm)
(defun +vterm-counsel-yank-pop-action (orig-fun &rest args)
(if (equal major-mode 'vterm-mode)
(let ((inhibit-read-only t)
(yank-undo-function (lambda (_start _end) (vterm-undo))))
(cl-letf (((symbol-function 'insert-for-yank)
(lambda (str) (vterm-send-string str t))))
(apply orig-fun args)))
(apply orig-fun args)))
(provide '+vterm)
;;; +vterm.el ends here

130
lisp/+window.el Normal file
View File

@ -0,0 +1,130 @@
;;; +window.el --- Fixes for Emacs's window.el -*- lexical-binding: t; -*-
;;; Commentary:
;; Do I want to propose this change in the Emacs ML?
;;; Code:
(require 'window)
;;; Split windows based on `window-total-width', not `window-width'
;; I have to just redefine these functions because the check is really deep in
;; there.
(defun window-splittable-p (window &optional horizontal)
"Return non-nil if `split-window-sensibly' may split WINDOW.
Optional argument HORIZONTAL nil or omitted means check whether
`split-window-sensibly' may split WINDOW vertically. HORIZONTAL
non-nil means check whether WINDOW may be split horizontally.
WINDOW may be split vertically when the following conditions
hold:
- `window-size-fixed' is either nil or equals `width' for the
buffer of WINDOW.
- `split-height-threshold' is an integer and WINDOW is at least as
high as `split-height-threshold'.
- When WINDOW is split evenly, the emanating windows are at least
`window-min-height' lines tall and can accommodate at least one
line plus - if WINDOW has one - a mode line.
WINDOW may be split horizontally when the following conditions
hold:
- `window-size-fixed' is either nil or equals `height' for the
buffer of WINDOW.
- `split-width-threshold' is an integer and WINDOW is at least as
wide as `split-width-threshold'.
- When WINDOW is split evenly, the emanating windows are at least
`window-min-width' or two (whichever is larger) columns wide."
(when (and (window-live-p window)
(not (window-parameter window 'window-side)))
(with-current-buffer (window-buffer window)
(if horizontal
;; A window can be split horizontally when its width is not
;; fixed, it is at least `split-width-threshold' columns wide
;; and at least twice as wide as `window-min-width' and 2 (the
;; latter value is hardcoded).
(and (memq window-size-fixed '(nil height))
;; Testing `window-full-width-p' here hardly makes any
;; sense nowadays. This can be done more intuitively by
;; setting up `split-width-threshold' appropriately.
(numberp split-width-threshold)
(>= (window-total-width window)
(max split-width-threshold
(* 2 (max window-min-width 2)))))
;; A window can be split vertically when its height is not
;; fixed, it is at least `split-height-threshold' lines high,
;; and it is at least twice as high as `window-min-height' and 2
;; if it has a mode line or 1.
(and (memq window-size-fixed '(nil width))
(numberp split-height-threshold)
(>= (window-height window)
(max split-height-threshold
(* 2 (max window-min-height
(if mode-line-format 2 1))))))))))
(defun split-window-sensibly (&optional window)
"Split WINDOW in a way suitable for `display-buffer'.
WINDOW defaults to the currently selected window.
If `split-height-threshold' specifies an integer, WINDOW is at
least `split-height-threshold' lines tall and can be split
vertically, split WINDOW into two windows one above the other and
return the lower window. Otherwise, if `split-width-threshold'
specifies an integer, WINDOW is at least `split-width-threshold'
columns wide and can be split horizontally, split WINDOW into two
windows side by side and return the window on the right. If this
can't be done either and WINDOW is the only window on its frame,
try to split WINDOW vertically disregarding any value specified
by `split-height-threshold'. If that succeeds, return the lower
window. Return nil otherwise.
By default `display-buffer' routines call this function to split
the largest or least recently used window. To change the default
customize the option `split-window-preferred-function'.
You can enforce this function to not split WINDOW horizontally,
by setting (or binding) the variable `split-width-threshold' to
nil. If, in addition, you set `split-height-threshold' to zero,
chances increase that this function does split WINDOW vertically.
In order to not split WINDOW vertically, set (or bind) the
variable `split-height-threshold' to nil. Additionally, you can
set `split-width-threshold' to zero to make a horizontal split
more likely to occur.
Have a look at the function `window-splittable-p' if you want to
know how `split-window-sensibly' determines whether WINDOW can be
split."
(let ((window (or window (selected-window))))
(or (and (window-splittable-p window)
;; Split window vertically.
(with-selected-window window
(split-window-below)))
(and (window-splittable-p window t)
;; Split window horizontally.
(with-selected-window window
(split-window-right)))
(and
;; If WINDOW is the only usable window on its frame (it is
;; the only one or, not being the only one, all the other
;; ones are dedicated) and is not the minibuffer window, try
;; to split it vertically disregarding the value of
;; `split-height-threshold'.
(let ((frame (window-frame window)))
(or
(eq window (frame-root-window frame))
(catch 'done
(walk-window-tree (lambda (w)
(unless (or (eq w window)
(window-dedicated-p w))
(throw 'done nil)))
frame nil 'nomini)
t)))
(not (window-minibuffer-p window))
(let ((split-height-threshold 0))
(when (window-splittable-p window)
(with-selected-window window
(split-window-below))))))))
(provide '+window)
;;; +window.el ends here

16
lisp/+xkcd.el Normal file
View File

@ -0,0 +1,16 @@
;;; +xkcd.el -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'xkcd)
(defun +xkcd-get-from-url (url &rest _)
"Open XKCD from URL."
(if (string-match "xkcd\\.com/\\([0-9]+\\)" url)
(xkcd-get (string-to-number (match-string 1 url)))
(funcall +browse-url-browser-function url)))
(provide '+xkcd)
;;; +xkcd.el ends here

21
lisp/+ytdious.el Normal file
View File

@ -0,0 +1,21 @@
;;; +ytdious.el --- Ytdious customizations -*- lexical-binding: t; -*-
;;; Commentary:
;; https://github.com/spiderbit/ytdious
;;; Code:
(defun +ytdious-watch ()
"Stream video at point in mpv."
(interactive)
(let* ((video (ytdious-get-current-video))
(id (ytdious-video-id-fun video)))
(start-process "ytdious mpv" nil
"mpv"
(concat "https://www.youtube.com/watch?v=" id))
"--ytdl-format=bestvideo[height<=?720]+bestaudio/best")
(message "Starting streaming..."))
(provide '+ytdious)
;;; +ytdious.el ends here

16
lisp/+zzz-to-char.el Normal file
View File

@ -0,0 +1,16 @@
;;; +zzz-to-char.el -*- lexical-binding: t; -*-
;;; Commentary:
;;
;;; Code:
(defun +zzz-to-char (prefix)
"Call `zzz-to-char' or `zzz-up-to-char' with PREFIX arg."
(interactive "P")
(call-interactively
(if prefix #'zzz-up-to-char #'zzz-to-char)))
(provide '+zzz-to-char)
;;; +zzz-to-char.el ends here

View File

@ -19,13 +19,17 @@
;;; Code:
(require 'diary-lib)
(require 'solar) ; for +sunrise-sunset
;;; Define a directory and an expanding function
(defmacro +define-dir (name directory &optional docstring inhibit-mkdir)
"Define a variable and function NAME expanding to DIRECTORY.
DOCSTRING is applied to the variable. Ensure DIRECTORY exists in
the filesystem, unless INHIBIT-MKDIR is non-nil."
(declare (indent 2))
(declare (indent 2)
(doc-string 3))
(unless inhibit-mkdir
(make-directory (eval directory) :parents))
`(progn
@ -37,12 +41,11 @@ the filesystem, unless INHIBIT-MKDIR is non-nil."
"If MKDIR is non-nil, the directory is created.\n"
"Defined by `/define-dir'.")
(let ((file-name (expand-file-name (convert-standard-filename file)
,name)))
,name)))
(when mkdir
(make-directory (file-name-directory file-name) :parents))
file-name))))
(defun +suppress-messages (oldfn &rest args) ; from pkal
"Advice wrapper for suppressing `message'.
OLDFN is the wrapped function, that is passed the arguments
@ -54,33 +57,6 @@ ARGS."
(when msg
(message "%s" msg)))))
(defun +sunrise-sunset (sunrise-command sunset-command &optional reset)
"Run SUNRISE-COMMAND at sunrise, and SUNSET-COMMAND at sunset.
With RESET, this function will call itself with its own
arguments. That's really only useful within this function
itself."
(let* ((times-regex (rx (* nonl)
(: (any ?s ?S) "unrise") " "
(group (repeat 1 2 digit) ":"
(repeat 1 2 digit)
(: (any ?a ?A ?p ?P) (any ?m ?M)))
(* nonl)
(: (any ?s ?S) "unset") " "
(group (repeat 1 2 digit) ":"
(repeat 1 2 digit)
(: (any ?a ?A ?p ?P) (any ?m ?M)))
(* nonl)))
(ss (+suppress-messages #'sunrise-sunset))
(_m (string-match times-regex ss))
(sunrise-time (match-string 1 ss))
(sunset-time (match-string 2 ss)))
(run-at-time sunrise-time nil sunrise-command)
(run-at-time sunset-time nil sunset-command)
(run-at-time "12:00am" nil sunset-command)
;; Reset everything at midnight
(run-at-time "12:00am" (* 60 60 24)
#'sunrise-sunset sunrise-command sunset-command t)))
(defun +ensure-after-init (function)
"Ensure FUNCTION runs after init, or now if already initialized.
If Emacs is already started, run FUNCTION. Otherwise, add it to
@ -89,11 +65,467 @@ If Emacs is already started, run FUNCTION. Otherwise, add it to
(funcall function)
(add-hook 'after-init-hook function)))
(defmacro +with-ensure-after-init (&rest body)
"Ensure BODY forms run after init.
Convenience macro wrapper around `+ensure-after-init'."
(declare (indent 0) (debug (def-body)))
`(+ensure-after-init (lambda () ,@body)))
(defun +remember-prefix-arg (p-arg P-arg)
"Display prefix ARG, in \"p\" and \"P\" `interactive' types.
I keep forgetting how they differ."
(interactive "p\nP")
(message "p: %S P: %S" p-arg P-arg))
(defmacro +defvar (var value &rest _)
"Quick way to `setq' a variable from a `defvar' form."
(declare (doc-string 3) (indent 2))
`(setq ,var ,value))
(defmacro +with-message (message &rest body)
"Execute BODY, with MESSAGE.
If body executes without errors, MESSAGE...Done will be displayed."
(declare (indent 1))
(let ((msg (gensym)))
`(let ((,msg ,message))
(condition-case e
(progn (message "%s..." ,msg)
,@body)
(:success (message "%s...done" ,msg))
(t (signal (car e) (cdr e)))))))
(defun +mapc-some-buffers (func &optional predicate)
"Perform FUNC on all buffers satisfied by PREDICATE.
By default, act on all buffers.
Both PREDICATE and FUNC are called with no arguments, but within
a `with-current-buffer' form on the currently-active buffer.
As a special case, if PREDICATE is a list, it will be interpreted
as a list of major modes. In this case, FUNC will only be called
on buffers derived from one of the modes in PREDICATE."
(let ((pred (or predicate t)))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (cond ((functionp pred)
(funcall pred))
((listp pred)
(apply #'derived-mode-p pred))
(t pred))
(funcall func))))))
;; https://github.com/cstby/emacs.d/blob/main/init.el#L67
(defun +clean-empty-lines (&optional begin end)
"Remove duplicate empty lines from BEGIN to END.
Called interactively, this function acts on the region, if
active, or else the entire buffer."
(interactive "*r")
(unless (region-active-p)
(setq begin (point-min)
end (save-excursion
(goto-char (point-max))
(skip-chars-backward "\n[:space:]")
(point))))
(save-excursion
(save-restriction
(narrow-to-region begin end)
(goto-char (point-min))
(while (re-search-forward "\n\n\n+" nil :move)
(replace-match "\n\n"))
;; Insert a newline at the end.
(goto-char (point-max))
(unless (or (buffer-narrowed-p)
(= (line-beginning-position) (line-end-position)))
(insert "\n")))))
(defcustom +open-paragraph-ignore-modes '(special-mode lui-mode comint-mode)
"Modes in which `+open-paragraph' makes no sense."
:type '(repeat function))
(defun +open-paragraph (&optional arg)
"Open a paragraph after paragraph at point.
A paragraph is defined as continguous non-empty lines of text
surrounded by empty lines, so opening a paragraph means to make
three blank lines, then place the point on the second one.
Called with prefix ARG, open a paragraph before point."
;; TODO: Take an integer as ARG, allowing for skipping paragraphs up and down.
(interactive "*P")
;; TODO: add `+open-paragraph-ignore-modes'
(unless (apply #'derived-mode-p +open-paragraph-ignore-modes)
;; Go to next blank line. This /isn't/ `end-of-paragraph-text' because
;; that's weird with org, and I'm guessing other modes too.
(unless (looking-at "^$") (forward-line (if arg -1 +1)))
(while (and (not (looking-at "^$"))
(= 0 (forward-line (if arg -1 +1)))))
(newline)
(when arg (newline) (forward-line -2))
(delete-blank-lines)
(newline 2)
(previous-line)))
(defun +split-window-then (&optional where arg)
"Split the window into a new buffer.
With non-nil ARG (\\[universal-argument] interactively), don't
prompt for a buffer to switch to. This function will split the
window using `split-window-sensibly', or open the new window in
the direction specified by WHERE. WHERE is ignored when called
interactively; if you want specific splitting, use
`+split-window-right-then' or `+split-window-below-then'."
(interactive "i\nP")
;; TODO: Canceling at the switching phase leaves the point in the other
;; window. Ideally, the user would see this as one action, meaning a cancel
;; would return to the original window.
(pcase where
;; These directions are 'backward' to the OG Emacs split-window commands,
;; because by default Emacs leaves the cursor in the original window. Most
;; users probably expect a switch to the new window, at least I do.
((or 'right :right) (split-window-right) (other-window 1))
((or 'left :left) (split-window-right))
((or 'below :below) (split-window-below) (other-window 1))
((or 'above :above) (split-window-below))
((pred null)
(or (split-window-sensibly)
(if (< (window-height) (window-width))
(split-window-below)
(split-window-right)))
(other-window 1))
(_ (user-error "Unknown WHERE paramater: %s" where)))
(unless arg
(condition-case nil
(call-interactively
(pcase (read-char "(B)uffer or (F)ile?")
(?b (if (fboundp #'consult-buffer)
#'consult-buffer
#'switch-to-buffer))
(?f #'find-file)
(_ #'ignore)))
(quit (delete-window)))))
(defun +split-window-right-then (&optional arg)
"Split window right, then prompt for a new buffer.
With optional ARG (\\[universal-argument]), just split."
(interactive "P")
(+split-window-then :right arg))
(defun +split-window-below-then (&optional arg)
"Split window below, then prompt for a new buffer.
With optional ARG (\\[universal-argument]), just split."
(interactive "P")
(+split-window-then :below arg))
(defun +bytes (number unit)
"Convert NUMBER UNITs to bytes.
UNIT can be one of :kb, :mb, :gb, :tb, :pb, :eb, :zb, :yb; :kib, :mib, :gib,
:tib, :pib, :eib, :zib, :yib."
(* number (pcase unit
;; Base 10 units
(:kb 1000)
(:mb (* 1000 1000))
(:gb (* 1000 1000 1000))
(:tb (* 1000 1000 1000 1000))
(:pb (* 1000 1000 1000 1000 1000))
(:eb (* 1000 1000 1000 1000 1000 1000))
(:zb (* 1000 1000 1000 1000 1000 1000 1000))
(:yb (* 1000 1000 1000 1000 1000 1000 1000 1000))
;; Base 2 units
(:kib 1024)
(:mib (* 1024 1024))
(:gib (* 1024 1024 1024))
(:tib (* 1024 1024 1024 1024))
(:pib (* 1024 1024 1024 1024 1024))
(:eib (* 1024 1024 1024 1024 1024 1024))
(:zib (* 1024 1024 1024 1024 1024 1024 1024))
(:yib (* 1024 1024 1024 1024 1024 1024 1024 1024)))))
;;; Font lock TODO keywords
(defcustom font-lock-todo-keywords '("TODO" "XXX" "FIXME" "BUG")
"Keywords to highlight with `font-lock-todo-face'.")
(defface font-lock-todo-face '((t :inherit font-lock-comment-face
:background "yellow"))
;; TODO: XXX: FIXME: BUG: testing :)
"Face for TODO keywords.")
(defun font-lock-todo-insinuate ()
(let ((keyword-regexp
(rx bow (group (eval (let ((lst '(or)))
(dolist (kw font-lock-todo-keywords)
(push kw lst))
(nreverse lst))))
":")))
(font-lock-add-keywords
nil
`((,keyword-regexp 1 'font-lock-todo-face prepend)))))
;; I don't use this much but I always forget the exact implementation, so this
;; is more to remember than anything else.
(defmacro setc (&rest vars-and-vals)
"Set VARS-AND-VALS by customizing them or using set-default.
Use like `setq'."
`(progn ,@(cl-loop for (var val) on vars-and-vals by #'cddr
if (null val) return (user-error "Not enough arguments")
collecting `(funcall (or (get ',var 'custom-get)
#'set-default)
',var ',val)
into ret
finally return ret)))
(defun +set-faces (specs)
"Set fonts to SPECS.
Specs is an alist: its cars are faces and its cdrs are the plist
passed to `set-face-attribute'. Note that the FRAME argument is
always nil; this function is mostly intended for use in init."
(dolist (spec specs)
(apply #'set-face-attribute (car spec) nil (cdr spec))))
(defcustom chat-functions '(+irc
jabber-connect-all
;; slack-start
)
"Functions to start when calling `chat'."
:type '(repeat function)
:group 'applications)
(defun +string-repeat (n str)
"Repeat STR N times."
(let ((r ""))
(dotimes (_ n)
(setq r (concat r str)))
r))
(defun chat-disconnect ()
"Disconnect from all chats."
(interactive)
(+with-progress "Quitting circe..."
(ignore-errors
(circe-command-GQUIT "peace love bread")
(cancel-timer (irc-connection-get conn :flood-timer))))
(+with-progress "Quitting jabber..."
(ignore-errors
(jabber-disconnect)))
(when (boundp '+slack-teams)
(+with-progress "Quitting-slack..."
(dolist (team +slack-teams)
(ignore-errors
(slack-team-disconnect team)))
(ignore-errors (slack-ws-close))))
(+with-progress "Killing buffers..."
(ignore-errors
(+mapc-some-buffers (lambda () "Remove the buffer from tracking and kill it unconditionally."
(let ((kill-buffer-query-functions nil))
(tracking-remove-buffer (current-buffer))
(kill-buffer)))
(lambda () "Return t if derived from the following modes."
(derived-mode-p 'lui-mode
'jabber-chat-mode
'jabber-roster-mode
'jabber-browse-mode
'slack-mode))))))
;; I can never remember all the damn chat things I run, so this just does all of em.
(defun chat (&optional arg)
"Initiate all chat functions.
With optional ARG, kill all chat-related buffers first."
(interactive "P")
(when arg (chat-disconnect))
(dolist-with-progress-reporter (fn chat-functions)
"Connecting to chat..."
(call-interactively fn)))
(defun +forward-paragraph (arg)
"Move forward ARG (simple) paragraphs.
A paragraph here is simply defined: it's a block of buffer that's
separated from others by two newlines."
(interactive "p")
(let ((direction (/ arg (abs arg))))
(forward-line direction)
(while (not (or (bobp)
(eobp)
(= arg 0)))
(if (looking-at "^[ \f\t]*$")
(setq arg (- arg direction))
(forward-line direction)))))
(defun +backward-paragraph (arg)
"Move backward ARG (simple) paragraphs.
See `+forward-paragraph' for the behavior."
(interactive "p")
(+forward-paragraph (- arg)))
(defun +concat (&rest strings)
"Concat STRINGS separated by SEPARATOR.
Each item in STRINGS is either a string or a list or strings,
which is concatenated without any separator.
SEPARATOR defaults to the newline (\\n)."
(let (ret
;; I don't know why a `cl-defun' with
;; (&rest strings &key (separator "\n")) doesn't work
(separator (or (cl-loop for i from 0 upto (length strings)
if (eq (nth i strings) :separator)
return (nth (1+ i) strings))
"\n")))
(while strings
(let ((string (pop strings)))
(cond ((eq string :separator) (pop strings))
((listp string) (push (apply #'concat string) ret))
((stringp string) (push string ret)))))
(mapconcat #'identity (nreverse ret) separator)))
(defun +file-string (file)
"Fetch the contents of FILE and return its string."
(with-current-buffer (find-file-noselect file)
(buffer-string)))
(defmacro +with-progress (pr-args &rest body)
"Perform BODY wrapped in a progress reporter.
PR-ARGS is the list of arguments to pass to
`make-progress-reporter'; it can be a single string for the
message, as well. If you want to use a formatted string, wrap
the `format' call in a list."
(declare (indent 1))
(let ((reporter (gensym))
(pr-args (if (listp pr-args) pr-args (list pr-args))))
`(let ((,reporter (make-progress-reporter ,@pr-args)))
(prog1 (progn ,@body)
(progress-reporter-done ,reporter)))))
(defmacro +with-eval-after-loads (features &rest body)
"Execute BODY after all FEATURES are loaded."
(declare (indent 1) (debug (form def-body)))
(unless (listp features)
(setq features (list features)))
(if (null features)
(macroexp-progn body)
(let* ((this (car features))
(rest (cdr features)))
`(with-eval-after-load ',this
(+with-eval-after-loads ,rest ,@body)))))
(defun +scratch-buffer (&optional nomode)
"Create a new scratch buffer and switch to it.
If the region is active, paste its contents into the scratch
buffer. The scratch buffer inherits the mode of the current
buffer unless NOMODE is non-nil. When called interactively,
NOMODE will be set when called with \\[universal-argument]."
(interactive "P")
(let* ((mode major-mode)
(bufname (generate-new-buffer-name (format "*scratch (%s)*" mode)))
(paste (and (region-active-p)
(prog1
(buffer-substring (mark t) (point))
(deactivate-mark)))))
(when (and (not nomode)
(bound-and-true-p ess-dialect)) ; Not sure what `ess-dialect' is
(setq mode (intern-soft (concat ess-dialect "-mode"))))
;; Set up buffer
(switch-to-buffer (get-buffer-create bufname))
(when (and (not nomode) mode)
(ignore-errors (funcall mode)))
(insert (format "%s Scratch buffer for %s%s\n\n"
comment-start mode comment-end))
(when paste (insert paste))
(get-buffer bufname)))
(defun +indent-rigidly (arg &optional interactive)
"Indent all lines in the region, or the current line.
This calls `indent-rigidly' and passes ARG to it."
(interactive "P\np")
(unless (region-active-p)
(push-mark)
(push-mark (line-beginning-position) nil t)
(goto-char (line-end-position)))
(call-interactively #'indent-rigidly))
(defun +sort-lines (reverse beg end)
"Sort lines in region, ignoring leading whitespace.
REVERSE non-nil means descending order; interactively, REVERSE is
the prefix argument, and BEG and END are the region. The
variable `sort-fold-case' determines whether case affects the
sort order."
(interactive "P\nr")
(save-excursion
(save-restriction
(narrow-to-region beg end)
(goto-char (point-min))
(let ((inhibit-field-text-motion t))
(sort-subr reverse
#'forward-line
#'end-of-line
#'beginning-of-line-text)))))
(defun +crm-indicator (args)
"AROUND advice for `completing-read-multiple'."
;; [[https://github.com/minad/vertico/blob/8ab2cddf3a1fb8799611b1d35118bf579aaf3154/README.org][from vertico's README]]
(cons (format "[CRM%s] %s"
(replace-regexp-in-string
"\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
crm-separator)
(car args))
(cdr args)))
;;; Timers!
;; inspired by [[https://git.sr.ht/~protesilaos/tmr/tree/main/item/tmr.el][prot's tmr.el package]]
(defvar +timer-string nil)
(defvar +timer-timer nil)
(defcustom +timer-running-string ""
"What to display when the timer is running."
:type 'string)
(defcustom +timer-done-string ""
"What to display when the timer is done."
:type 'string)
(defun +timer (time)
"Set a timer for TIME."
(interactive (list (read-string "Set a timer for how long? ")))
(let ((secs (cond ((natnump time) (* time 60))
((and (stringp time)
(string-match-p "[0-9]\\'" time))
(* (string-to-number time) 60))
(t (let ((secs 0)
(time time))
(save-match-data
(while (string-match "\\([0-9.]+\\)\\([hms]\\)" time)
(cl-incf secs
(* (string-to-number (match-string 1 time))
(pcase (match-string 2 time)
("h" 3600)
("m" 60)
("s" 1))))
(setq time (substring time (match-end 0)))))
secs)))))
(message "Setting timer for \"%s\" (%S seconds)..." time secs)
(setq +timer-string +timer-running-string)
(setq +timer-timer (run-with-timer secs nil
(lambda ()
(message "%S-second timer DONE!" secs)
(setq +timer-string +timer-done-string)
(let ((visible-bell t)
(ring-bell-function nil))
(ding))
(ding))))))
(defun +timer-cancel ()
"Cancel the running timer."
(interactive)
(cond ((not +timer-timer)
(message "No timer found!"))
(t
(cancel-timer +timer-timer)
(message "Timer canceled.")))
(setq +timer-string nil))
(defun +switch-to-last-buffer ()
"Switch to the last-used buffer in this window."
(interactive)
(switch-to-buffer nil))
(provide 'acdw)
;;; acdw.el ends here

74
lisp/dawn.el Normal file
View File

@ -0,0 +1,74 @@
;;; dawn.el --- Do things at dawn (and dusk) -*- lexical-binding: t; -*-
;;; Commentary:
;; There is also circadian.el, but it doesn't quite work for me.
;; This code comes mostly from https://gnu.xyz/auto_theme.html, but also
;; somewhere else (which I've forgotten) and my own brain :)
;;; Code:
(require 'calendar)
(require 'cl-lib)
(require 'solar)
(defvar dawn--dawn-timer nil
"Timer for dawn-command.")
(defvar dawn--dusk-timer nil
"Timer for dusk-command.")
(defvar dawn--reset-timer nil
"Timer to reset dawn at midnight.")
(defun dawn-encode-time (f)
"Encode fractional time F."
(let ((hhmm (cl-floor f))
(date (cdddr (decode-time))))
(encode-time
(append (list 0
(round (* 60 (cadr hhmm)))
(car hhmm)
)
date))))
(defun dawn-midnight ()
"Return the time of the /next/ midnight."
(let ((date (cdddr (decode-time))))
(encode-time
(append (list 0 0 0 (1+ (car date))) (cdr date)))))
(defun dawn-sunrise ()
"Return the time of today's sunrise."
(dawn-encode-time (caar (solar-sunrise-sunset (calendar-current-date)))))
(defun dawn-sunset ()
"Return the time of today's sunset."
(dawn-encode-time (caadr (solar-sunrise-sunset (calendar-current-date)))))
(defun dawn-schedule (dawn-command dusk-command)
"Run DAWN-COMMAND at sunrise, and DUSK-COMMAND at dusk.
RESET is an argument for internal use."
(let ((dawn (dawn-sunrise))
(dusk (dawn-sunset)))
(cond
((time-less-p nil dawn)
;; If it isn't dawn yet, it's still dark. Run DUSK-COMMAND and schedule
;; DAWN-COMMAND and DUSK-COMMAND for later.
(funcall dusk-command)
(run-at-time dawn nil dawn-command)
(run-at-time dusk nil dusk-command))
((time-less-p nil dusk)
;; If it isn't dusk yet, it's still light. Run DAWN-COMMAND and schedule
;; DUSK-COMMAND.
(funcall dawn-command)
(run-at-time dusk nil dusk-command))
(t ;; Otherwise, it's past dusk, so run DUSK-COMMAND.
(funcall dusk-command)))
;; Schedule a reset at midnight, to re-calculate dawn/dusk times.
;(unless reset)
(run-at-time (dawn-midnight) nil
#'dawn-schedule dawn-command dusk-command)))
(provide 'dawn)
;;; dawn.el ends here

58
lisp/elephant.el Normal file
View File

@ -0,0 +1,58 @@
;;; elephant.el --- Remember variables and modes -*- lexical-binding: t; -*-
;;; Code:
(defmacro elephant-remember (alist)
"Setup a closure remembering symbols to apply with
`remember-reset'. The variables will be renamed using TEMPLATE.
ALIST contains cells of the form (SYMBOL . NEW-VALUE), where
SYMBOL is a variable or mode name, and its value is what to set
after `remember-set'."
(unless lexical-binding
(user-error "`elephant' requires lexical binding."))
(let* ((template (format "elephant--%s-%%s" (gensym)))
(reset-fn (intern (format template "reset"))))
(cl-destructuring-bind (let-list fn-set-list fn-reset-list)
(cl-loop
for (sym . val) in (if (symbolp alist) (symbol-value alist) alist)
as rem = (intern (format template sym))
collect (list rem sym)
into let-list
collect (cond ((eq val 'enable)
`(,sym +1))
((eq val 'disable)
`(,sym -1))
(t `(setq-local ,sym ,val)))
into fn-set-list
collect (cond ((memq val '(enable disable))
`(progn (,sym (if ,rem +1 -1))
(fmakunbound ',rem)))
(t `(progn (setq-local ,sym ,rem)
(makunbound ',rem))))
into fn-reset-list
finally return (list let-list
fn-set-list
fn-reset-list))
`(progn
(defvar-local ,reset-fn nil
"Function to recall values from `elephant-remember'.")
(let ,let-list
(setf (symbol-function ',reset-fn)
(lambda ()
,@fn-reset-list
(redraw-display)
(fmakunbound ',reset-fn))))
,@fn-set-list
',reset-fn))))
(defun elephant-forget ()
"Forget all symbols generated by `elephant-remember'."
)
(provide 'elephant)
;;; elephant.el ends here

36
lisp/find-script.el Normal file
View File

@ -0,0 +1,36 @@
;;; find-script.el --- Find a script in $PATH -*- lexical-binding: t; -*-
;;; Commentary:
;; This package makes it easier to find a script to edit in $PATH. The initial
;; `rehash-exes' is slow, but it's stored in `*exes*' as a caching mechanism.
;; However, I'm sure it could be improved.
;; In addition, `*exes*' currently contains /all/ executables in $PATH, which
;; ... maybe only the ones stored in some text format should be shown.
;;; Code:
(defvar *exes* nil
"All the exectuables in $PATH.
Run `rehash-exes' to refresh this variable.")
(defun rehash-exes ()
"List all the executables in $PATH.
Also sets `*exes*' parameter."
(setq *exes*
(cl-loop for dir in exec-path
append (file-expand-wildcards (concat dir "*"))
into exes
finally return exes)))
;;;###autoload
(defun find-script (script)
"Find a file in $PATH."
(interactive
(list (let ((exes (or *exes* (rehash-exes))))
(completing-read "Script> " exes nil t))))
(find-file script))
(provide 'find-script)
;;; find-script.el ends here

130
lisp/gdrive.el Normal file
View File

@ -0,0 +1,130 @@
;;; gdrive.el --- Gdrive integration -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Case Duckworth
;; Author: Case Duckworth <case@bob>
;; Keywords: convenience, data, docs
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; [[https://github.com/prasmussen/gdrive][gdrive]] is a Go program to interface with Google Drive. This library connects
;; that to Emacs.
;;; Code:
(require 'cl-lib)
(defgroup gdrive nil
"Customizations for Emacs's gdrive module."
:group 'applications
:prefix "gdrive-")
(defcustom gdrive-bin (executable-find "gdrive")
"Where gdrive binary is located."
:type 'string)
(defcustom gdrive-buffer "*gdrive*"
"Default buffer for gdrive output."
:type 'string)
;;; Global flags
;;;; -c, --config <configDir>
;;;;; Application path, default: /Users/<user>/.gdrive
(defcustom gdrive-config-dir nil
"Application path.")
;;;; --refresh-token <refreshToken>
;;;;; Oauth refresh token used to get access token (for advanced users)
(defcustom gdrive-refresh-token nil
"Oauth refresh token used to get access token.
(For advanced users).")
;;;; --access-token <accessToken>
;;;;; Oauth access token, only recommended for short-lived requests because of
;;;;; short lifetime (for advanced users)
(defcustom gdrive-access-token nil
"Oauth access token.
Only recommended for short-lived requests because of short
lifetime (for advanced users).")
;;;; --service-account <accountFile>
;;;;; Oauth service account filename, used for server to server communication
;;;;; without user interaction (file is relative to config dir)
(defcustom gdrive-service-account nil
"Oauth service account filename.
Used for server to server communication without user
interaction (file is relative to config dir).")
(defun gdrive--global-arguments ()
"Build global arguments for gdrive."
(append
(when gdrive-config-dir (list "--config" gdrive-config-dir))
(when gdrive-refresh-token (list "--refresh-token" gdrive-refresh-token))
(when gdrive-access-token (list "--access-token" gdrive-access-token))
(when gdrive-service-account (list "--service-account" gdrive-service-account))))
;;; List files
;; gdrive [global] list [options]
;;;; -m, --max <maxFiles>
;;;; Max files to list, default: 30
;;;; -q, --query <query>
;;;;; Default query: "trashed = false and 'me' in owners". See https://developers.google.com/drive/search-parameters
;;;; --order <sortOrder>
;;;;; Sort order. See https://godoc.org/google.golang.org/api/drive/v3#FilesListCall.OrderBy
;;;; --name-width <nameWidth>
;;;;; Width of name column, default: 40, minimum: 9, use 0 for full width
;; NOTE: gdrive-list will pass 0 for this argument.
;;;; --absolute Show absolute path to file (will only show path from first parent)
;;;; --no-header Dont print the header
;; NOTE: gdrive-list will always pass this argument.
;;;; --bytes Size in bytes
(cl-defun gdrive-list (&key max query order absolute no-header bytes)
"Run the \"gdrive list\" command.
MAX is the max files to list; it defaults to 30. QUERY is the
query to pass; the default is \"trashed = false and 'me' in
owners\"."
(gdrive--run (append (gdrive--global-arguments)
(list "list")
(when max (list "--max" max))
(when query (list "--query" query))
(when order (list "--order" order))
(list "--name-width" "0")
(when absolute (list "--absolute"))
(when no-header (list "--no-header"))
(when bytes (list "--bytes")))))
(defmacro gdrive-query)
(defun gdrive--build-command-name (command)
"INTERNAL: Build a string name for COMMAND."
(concat "gdrive-" (car command)))
(defun gdrive--run (command &optional buffer)
"Run 'gdrive COMMAND', collecting results in BUFFER.
COMMAND, if not a list, will be made a list and appended to
`gdrive-bin'.
BUFFER defaults to `gdrive-buffer'."
(let ((command (if (listp command) command (list command)))
(buffer (or buffer gdrive-buffer)))
(make-process :name (gdrive--build-command-name command)
:buffer buffer
:command (cons gdrive-bin command))))
(provide 'gdrive)
;;; gdrive.el ends here

67
lisp/long-s-mode.el Normal file
View File

@ -0,0 +1,67 @@
;;; long-s-mode.el --- Proper typography for Emacs -*- lexical-binding: t; -*-
;;; Commentary:
;; from Catie on #emacs
;;; Code:
(define-minor-mode long-s-mode
"Minor mode for inserting 'ſ' characters")
(defconst +long-s+ ?ſ)
(defconst +short-s+ ?s)
(defun long-s-p (char)
(char-equal char +long-s+))
(defun short-s-p (char)
(or (char-equal char +short-s+)))
(defun s-char-p (char)
(or (long-s-p char)
(short-s-p char)))
(defun alpha-char-p (char)
(memq (get-char-code-property char 'general-category)
'(Ll Lu Lo Lt Lm Mn Mc Me Nl)))
(defun long-s-insert-short-s ()
(interactive)
(if (long-s-p (preceding-char))
(insert-char +short-s+)
(insert-char +long-s+)))
(defun long-s-insert-space ()
(interactive)
(if (long-s-p (preceding-char))
(progn (delete-backward-char 1)
(insert-char +short-s+))
(save-excursion
(while (not (alpha-char-p (preceding-char)))
(backward-char))
(when (long-s-p (preceding-char))
(delete-backward-char 1)
(insert-char +short-s+))))
(insert-char ?\ ))
(defvar long-s-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map (current-global-map))
(define-key map (kbd "s") #'long-s-insert-short-s)
(define-key map (kbd "SPC") #'long-s-insert-space)
map))
(setq long-s-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "s") #'long-s-insert-short-s)
(define-key map (kbd "SPC") #'long-s-insert-space)
map))
(unless (seq-some #'(lambda (x) (eq (car x) 'long-s-mode))
minor-mode-map-alist)
(push (cons 'long-s-mode long-s-mode-map)
minor-mode-map-alist))
(provide 'long-s-mode)
;;; long-s-mode.el ends here

23
lisp/private.el Normal file
View File

@ -0,0 +1,23 @@
;;; private.el -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'acdw)
(defgroup private nil
"Private things are private. Shhhhh....")
;; Private directory
(+define-dir private/ (sync/ "emacs/private")
"Private secretive secrets inside.")
(add-to-list 'load-path private/)
;; Load random private stuff
(require '_acdw)
(provide 'private)
;;; private.el ends here

View File

@ -58,9 +58,7 @@ FUNC should be a function with the signature (THING REMEMBERED-SETTING)."
;;;###autoload
(define-minor-mode reading-mode
"A mode for reading."
:init-value nil
:lighter " Read"
:keymap (make-sparse-keymap)
(if reading-mode
;; turn on
(progn

View File

@ -1,11 +1,17 @@
;;; system.el --- System-specific configuration -*- lexical-binding: t; -*-
;;; system.el --- Load system-dependendant settings -*- lexical-binding: t; -*-
;;; Commentary:
;; When using Emacs on separate computers, some variables need different
;; settings. This library contains functions and variables to work with
;; When using Emacs on multiple computers, some variables and functions need
;; different definitions. This library is built to assist in working with
;; different system configurations for Emacs.
;;; TODO:
;; machine.el
;; machine-case to switch on machine
;;
;;; Code:
(require 'cl-lib)
@ -15,145 +21,159 @@
:group 'emacs
:prefix "system-")
;;; Variables
;;; Settings
(defcustom system-load-alist '((system-microsoft-p . windows)
(system-linux-p . linux))
"Alist describing which system Emacs is on.
Each cell is of the form (PREDICATE . SYSTEM), where PREDICATE is
a function of no arguments and SYSTEM is a string or symbol that
will be passed to `system-settings-load'.
This list need not be exhaustive; see `system-settings-load' for
more details on what happens if this alist is exhausted."
:type '(alist :key-type function :value-type (choice string symbol)))
(defcustom system-load-directory (locate-user-emacs-file "systems")
"The directory from which to load system-specific configurations."
(defcustom system-load-directory (locate-user-emacs-file "systems"
"~/.emacs-systems")
"The directory where system-specific configurations live."
:type 'file)
;; `defcustoms' defined here are best-guess defaults.
;; These `defcustom's are best-guess defaults.
(defcustom system-default-font (pcase system-type
((or 'ms-dos 'windows-nt)
"Consolas")
(_ "monospace"))
"The font used for the `default' face."
(defcustom system-default-font (cond
((memq system-type '(ms-dos windows-nt))
"Consolas")
(t "monospace"))
"The font used for the `default' face.
Set this in your system files."
:type 'string)
(defcustom system-default-height 100
"The height used for the `default' face."
"The height used for the `default' face.
Set this in your system files."
:type 'number)
(defcustom system-variable-pitch-font (pcase system-type
((or 'ms-dos 'windows-nt)
"Arial")
(_ "sans-serif"))
"The font used for the `variable-pitch' face."
(defcustom system-variable-pitch-font (cond
((memq system-type '(ms-dos windows-nt))
"Arial")
(t "sans-serif"))
"The font used for the `variable-pitch' face.
Set this in your system files."
:type 'string)
(defcustom system-variable-pitch-height 1.0
"The height used for the `variable-pitch' face.
A floating-point number is recommended, since that makes it
relative to the `default' face height."
relative to the `default' face height.
Set this in your system files."
:type 'number)
(defcustom system-files-order '(:type :name :user)
"The order to load `system-files' in.
The elements of this list correspond to the keys in
`system-system'."
:type '(list (const :tag "System type" :type)
(const :tag "System name" :name)
(const :tag "Current user" :user)))
;;; Variables
(defvar system-system nil
"The current system's symbol.
Do not edit this by hand. Instead, call `system-get-system'.")
"Plist of systems that Emacs is in.
The keys are as follows:
(defvar system-file nil
"The current system's file for system-specific configuration.
Do not edit this by hand. Instead, call `system-get-system-file'.")
- :name - `system-name'
- :type - `system-type'
- :user - `user-login-name'
Each value is made safe to be a file name by passing through
`system--safe'.
Do not edit this by hand. Instead, call `system-get-systems'.")
(defvar system-files nil
"List of files to load for system-specific configuration.
Do not edit this by hand. Instead, call `system-get-system-files'.")
;;; Functions
;; Convenience functions for systems
(defun system-microsoft-p ()
"Return non-nil if running in a Microsoft system."
(memq system-type '(ms-dos windows-nt)))
(defun system-linux-p ()
"Return non-nil if running on a Linux system."
(memq system-type '(gnu/linux)))
(defun system-warn (message &rest args)
"Display a wraning message made from (format-message MESSAGE ARGS...).
This function is like `warn', except it uses the `system' type."
(defun system--warn (message &rest args)
"Display a system-file warning message.
This function is like `warn', except it uses a `system' type."
(display-warning 'system (apply #'format-message message args)))
(defun system-get-system ()
"Determine the current system."
(cl-loop for (p . s) in system-load-alist
if (with-demoted-errors (format "Problem running function `%s'" p)
(funcall p))
return (setq system-system s)))
(defun system--safe (str)
"Make STR safe for a file name."
(let ((bad-char-regexp ))
(downcase (string-trim
(replace-regexp-in-string "[#%&{}\$!'\":@<>*?/ \r\n\t+`|=]+"
"-" str)
"-" "-"))))
(defun system-get-system-file (&optional system refresh-cache set-system-file-p)
"Determine the current system's system-specific file.
The current system's file will be returned. The value of
`system-file' is set, /unless/ the parameter SYSTEM was passed to
this function and SET-SYSTEM-FILE-P is nil. If both SYSTEM and
SET-SYSTEM-FILE-P are non-nil, this function will still set
`system-file'.
(defun system-get-systems ()
"Determine the current system(s).
This system updates `system-system', which see."
;; Add system-name
(setf (plist-get system-system :name)
(intern (system--safe (system-name))))
;; Add system-type
(setf (plist-get system-system :type)
(intern (system--safe (symbol-name system-type))))
;; Add current user
(setf (plist-get system-system :user)
;; Use `user-real-login-name' in case Emacs gets called under su.
(intern (system--safe (user-real-login-name))))
system-system)
If SYSTEM is not passed, and `system-file' is set, simply return
its value /unless/ REFRESH-CACHE is non-nil, in which case
`system-load-alist' will be looped through to find the
appropriate system by testing the car of each cell there. When
one matches, use the cdr of that cell as SYSTEM. If none
matches, return nil.
(defun system-get-files ()
"Determine the current systems' load-files.
The system load-files should live in `system-load-directory', and
named using either the raw name given by the values of
`system-system', or that name prepended with the type, e.g.,
\"name-bob.el\", for a system named \"bob\".
This function will only look for system-specific files in
`system-load-directory'."
(let* ((system* (or system
(and system-file (not refresh-cache))
(system-get-system)))
(file (expand-file-name (format "%s" system*) system-load-directory)))
(when (or (not system)
(and system set-system-file-p))
(setq system-file file))
file))
The second form of file-name is to work around name collisions,
e.g. if a there's a user named \"bob\" and a system named
\"bob\".
This function updates `system-files'."
;; Get systems
(system-get-systems)
;; Re-set `system-files'
(setq system-files nil)
(let (ret)
(dolist (key (reverse system-files-order))
(let* ((val (plist-get system-system key))
(key-val (intern (system--safe (format "%s-%s" key val)))))
(push (list key-val val) ret)))
;; Update `system-files'.
(setq system-files ret)))
;;;###autoload
(defun system-settings-load (&optional system error nomessage)
"Load system settings.
Load settings from `system-file', or the `system-file' as
determined by SYSTEM, if passed. See `system-get-system-file' for
details on how the `system-file' is determined.
(defun system-settings-load (&optional error nomessage)
"Load system settings from `system-files'.
Each list in `system-files' will be considered item-by-item; the
first found file in each will be loaded.
ERROR determines how to deal with errors: if nil, warn the user
when `system-file' can't be found or when the system being used
can't be determined. If t, those are elevated to errors. If any
other value, the errors are completely ignored.
when no system-files can be found or when the system being used
cannot be determined. If t, these warnings are elevated to
errors. Any other value ignores the warnings completely.
NOMESSAGE is passed directly to `load'."
(let ((file (system-get-system-file system)))
(if file
(condition-case e
(load file nil nomessage)
(t (cond ((eq error t) (signal (car e) (cdr e)))
((null error) (system-warn "Couldn't find file `%s'."
file)))))
(funcall (cond ((eq error t) #'error)
((null error) #'system-warn)
(t #'ignore))
"Could not determine the system being used."))))
;;;###autoload
(defun system-find-system-file (&optional system)
"Find the current system's system-file."
(interactive (list (completing-read "System file: "
(mapcar (lambda (a) (format "%s" (cdr a)))
system-load-alist)
nil t nil nil
(format "%s" (system-get-system)))))
(find-file (cl-loop with file = (system-get-system-file system)
for cand in (list file
(concat file ".el"))
if (file-exists-p cand)
return cand
finally return cand)))
(system-get-files)
(if system-files
(let (files-loaded)
(dolist (ss system-files)
(catch :done
(dolist (s ss)
(let ((fn (expand-file-name (format "%s" s)
system-load-directory)))
(when (load fn t nomessage)
(push fn files-loaded)
(throw :done nil))))))
(unless files-loaded
(cond ((eq error t) (error "Error loading system-files.")
(null error) (system--warn "Couldn't load system-files."))))
files-loaded)
(funcall (cond ((eq error t) #'error)
((null error) #'system--warn)
(t #'ignore))
"Couldn't determine the system being used.")))
(provide 'system)
;;; system.el ends here

View File

@ -1,197 +0,0 @@
;;; titlecase.el --- title-case phrases -*- lexical-binding: t; -*-
;;; Commentary:
;; adapted from https://hungyi.net/posts/programmers-way-to-title-case/
;;; Code:
(require 'seq)
(defgroup titlecase nil
"Customizations for titlecasing phrases."
:prefix "titlecase-"
:group 'text)
;;; Lists of words /never/ to capitalize
(defvar titlecase-prepositions
'("'thout" "'tween" "aboard" "about" "above"
"abreast" "absent" "abt." "across" "after" "against" "ago" "aloft" "along"
"alongside" "amid" "amidst" "among" "amongst" "anti" "apart" "apropos"
"around" "as" "aside" "aslant" "astride" "at" "atop" "away" "before"
"behind" "below" "beneath" "beside" "besides" "between" "beyond" "but" "by"
"c." "ca." "circa" "come" "concerning" "contra" "counting" "cum" "despite"
"down" "during" "effective" "ere" "except" "excepting" "excluding" "failing"
"following" "for" "from" "hence" "in" "including" "inside" "into" "less"
"like" "mid" "midst" "minus" "mod" "modulo" "near" "nearer" "nearest"
"neath" "next" "notwithstanding" "o'" "o'er" "of" "off" "offshore" "on"
"onto" "ontop" "opposite" "out" "outside" "over" "pace" "past" "pending"
"per" "plus" "post" "pre" "pro" "qua" "re" "regarding" "respecting" "round"
"sans" "save" "saving" "short" "since" "sub" "t'" "than" "through"
"throughout" "thru" "thruout" "till" "times" "to" "toward" "towards" "under"
"underneath" "unlike" "until" "unto" "up" "upon" "v." "versus" "via"
"vis-à-vis" "vs." "w." "w/" "w/i" "w/o" "wanting" "with" "within"
"without")
"List of prepositions in English.
This list is, by necessity, incomplete, even though prepositions
are a closed lexical group in the English language. This list
was pulled and culled from
https://en.wikipedia.org/wiki/List_of_English_prepositions.")
(defvar titlecase-articles '("a" "an" "the")
"List of articles in English.")
(defvar titlecase-coordinating-conjunctions '("for" "and" "nor" "but" "or"
"yet" "so")
"List of coordinating conjunctions in English.")
(defvar titlecase-lowercase-chicago (append titlecase-articles
titlecase-prepositions
titlecase-coordinating-conjunctions)
"Words to lowercase in Chicago Style.
Include: articles, coordinating conjunctions, prepositions, and
\"to\" in an infinitive (though that's caught as a preposition).")
(defvar titlecase-lowercase-apa (append titlecase-articles
(seq-filter (lambda (p)
(< (length p) 4))
titlecase-prepositions))
"Words to lowercase in APA Style.")
(defvar titlecase-lowercase-mla (append titlecase-articles
titlecase-prepositions
titlecase-coordinating-conjunctions)
"Words to lowercase in MLA Style.")
(defvar titlecase-lowercase-ap (append titlecase-articles
(seq-filter (lambda (p)
(< (length p) 4))
titlecase-prepositions)
(seq-filter
(lambda (p)
(< (length p) 4))
titlecase-coordinating-conjunctions))
"Words to lowercase in AP Style.")
(defvar titlecase-lowercase-bluebook (append titlecase-articles
titlecase-coordinating-conjunctions
(seq-filter
(lambda (p)
(< (length p) 4))
titlecase-prepositions))
"Words to lowercase in Bluebook Style.")
(defvar titlecase-lowercase-ama (append titlecase-articles
titlecase-coordinating-conjunctions
(seq-filter (lambda (p)
(< (length p) 4))
titlecase-prepositions))
"Words to lowercase in AMA Style.")
(defvar titlecase-lowercase-nyt (append titlecase-articles
titlecase-prepositions
titlecase-coordinating-conjunctions)
"Words to lowercase in New York Times Style.")
(defvar titlecase-lowercase-wikipedia
(append titlecase-articles
(seq-filter (lambda (p) (< (length p) 5)) titlecase-prepositions)
titlecase-coordinating-conjunctions)
"Words to lowercase in Wikipedia Style.")
(defcustom titlecase-style 'chicago
"Which style to use when titlecasing."
:type '(choice (const :tag "Chicago Style" chicago)
(const :tag "APA Style" apa)
(const :tag "MLA Style" mla)
(const :tag "AP Style" ap)
(const :tag "Bluebook Style" bluebook)
(const :tag "AMA Style" ama)
(const :tag "New York Times Style" nyt)
(const :tag "Wikipedia Style" wikipedia)))
(defun titlecase--normalize (begin end)
"Normalize region from BEGIN to END."
(goto-char begin)
(unless (re-search-forward "[a-z]" end :noerror)
(downcase-region begin end)))
(defun titlecase--capitalize-first-word (begin end)
"Capitalize the first word of region from BEGIN to END."
(goto-char begin)
(capitalize-word 1))
(defun titlecase--capitalize-last-word (begin end)
"Capitalize the last word of region from BEGIN to END."
(goto-char end)
(backward-word 1)
(when (and (>= (point) begin))
(capitalize-word 1)))
(defun titlecase-region-with-style (begin end style)
"Titlecase the region of English text from BEGIN to END, using STYLE."
(interactive "*r")
(save-excursion
(goto-char begin)
;; If the region is in ALL-CAPS, normalize it first
(unless (re-search-forward "[a-z]" end :noerror)
(downcase-region begin end))
(goto-char begin) ; gotta go back to the beginning
(let (;; Constants during this function's runtime
(case-fold-search nil)
(downcase-word-list (symbol-value
(intern (format "titlecase-lowercase-%s"
style))))
;; State variables
(this-word (current-word))
(force-capitalize t))
;; And loop over the rest
(while (< (point) end)
(setq this-word (current-word))
(cond
;; Skip ALL-CAPS words
((string-match "^[A-Z]+$" this-word) (forward-word 1))
;; Force capitalization if `force-capitalize' is t
(force-capitalize (progn (capitalize-word 1)
(setq force-capitalize nil)))
;; Special rules for different styles
((and (memq style '(ap))
(> (length this-word) 3))
(capitalize-word 1))
;; Downcase words that should be
((member (downcase this-word) downcase-word-list)
(downcase-word 1))
;; Otherwise, capitalize the word
(t (capitalize-word 1)))
;; If the word ends with a :, ., ?, newline, or carriage-return, force
;; the next word to be capitalized.
(when (looking-at "[:.?;\n\r]")
(setq force-capitalize t))
(skip-syntax-forward "^w" end))
;; Capitalize the last word, only in some styles
(when (memq style '(chicago ap bluebook ama nyt wikipedia))
(backward-word 1)
(when (and (>= (point) begin))
(capitalize-word 1))))))
;;;###autoload
(defun titlecase-region (begin end)
"Titlecase the region of English text from BEGIN to END.
Uses the style provided in `titlecase-style'."
(interactive "*r")
(titlecase-region-with-style begin end titlecase-style))
;;;###autoload
(defun titlecase-dwim ()
"Titlecase either the region, if active, or the current line."
(interactive)
(if (region-active-p)
(titlecase-region (region-beginning) (region-end))
(titlecase-region (point-at-bol) (point-at-eol))))
(provide 'titlecase)
;;; titlecase.el ends here

129
lisp/user-save.el Normal file
View File

@ -0,0 +1,129 @@
;;; user-save.el --- Do things when explicitly saving files -*- lexical-binding: t; -*-
;; Copyright (C) 2021--2022 Case Duckworth <acdw@acdw.net>
;; URL: ...
;; Version: 0.1.0
;; Package-Requires: ((emacs "24.3"))
;; Keywords: files
;;; Commentary:
;; Because `super-save-mode' automatically saves every time we move away from a
;; buffer, it tends to run a lot of `before-save-hook's that don't need to be
;; run that often. For that reason, I'm writing a mode where C-x C-s saves
;; /and/ runs all the "real" before-save-hooks, so that super-save won't
;; automatically do things like format the buffer all the time.
;;; Code:
(require 'cl-lib)
(defgroup user-save nil
"Group for `user-save-mode' customizations."
:group 'emacs
:prefix "user-save-")
(defcustom user-save-hook-into-kill-emacs nil
"Add a hook to perform `user-save' to `kill-emacs-hook'.
This option is only useful is `user-save-mode' is active when
Emacs is killed."
:type 'boolean)
(defcustom user-save-inhibit-modes '(special-mode)
"List of modes to inhibit `user-save-mode' from activation in."
:type '(repeat symbol))
(defcustom user-save-inhibit-predicates '(user-save-non-file-buffer-p)
"List of predicates to inhibit `user-save-mode' from activation.
Each predicate will be called with no arguments, and if it
returns t, will inhibit `user-save-mode' from activating."
:type '(repeat function))
(defvar user-save-hook nil
"Hook to run when the user, not Emacs, saves the buffer.")
(defvar user-save-mode-map (let ((map (make-sparse-keymap)))
(define-key map (kbd "C-x C-s") #'user-save-buffer)
(define-key map (kbd "C-x s") #'user-save-some-buffers)
map)
"Keymap for `user-save-mode'.
This map shadows the default map for `save-buffer'.")
(defun user-save-run-hooks (&rest _)
"Run the hooks in `user-save-hook'.
This does /not/ also save the buffer."
(with-demoted-errors "User-save-hook error: %S"
(run-hooks 'user-save-hook)))
(defun user-save-non-file-buffer-p (&optional buffer-or-name)
"Return whether BUFFER-OR-NAME is a non-file buffer.
BUFFER-OR-NAME, if omitted, defaults to the current buffer."
(with-current-buffer (or buffer-or-name (current-buffer))
(not (buffer-file-name))))
(defun user-save-buffer (&optional arg)
"Save current buffer in visited file if modified.
This function is precisely the same as `save-buffer', but with
one modification: it also runs functions in `user-save-hook'.
This means that if you have some functionality in Emacs to
automatically save buffers periodically, but have hooks you want
to automatically run when the buffer saves that are
computationally expensive or just aren't something you want to
run all the time, put them in `user-save-hook'.
ARG is passed directly to `save-buffer'."
(interactive '(called-interactively))
(message "User-Saving the buffer...")
(user-save-run-hooks)
(save-buffer arg)
(message "User-Saving the buffer...Done."))
(defun user-save-some-buffers (&optional pred)
"Save some buffers as though the user saved them.
This function does not ask the user about each buffer, but PRED
is used in almost the same way as `save-some-buffers': if it's
nil or t, it will save all file-visiting modified buffers; if
it's a zero-argument function, that will be called to determine
whether the buffer needs to be saved."
;; This could maybe be much better.
(interactive "P")
(unless pred (setq pred save-some-buffers-default-predicate))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (and (buffer-modified-p)
(buffer-file-name)
(or (null pred)
(if (functionp pred) (funcall pred) pred)))
(user-save-buffer)))))
;;;###autoload
(define-minor-mode user-save-mode
"Mode to enable an an extra user-save hook."
:lighter " US"
:keymap user-save-mode-map)
;;;###autoload
(defun user-save-mode-disable ()
"Turn off `user-save-mode' in the current buffer."
(user-save-mode -1))
;;;###autoload
(defun user-save-mode-in-some-buffers ()
"Enable `user-save-mode', but only in some buffers.
The mode will not be enabled in buffers derived from modes in
`user-save-inhibit-modes', those for which
`user-save-inhibit-predicates' return t, or in the minibuffer."
(unless (or (minibufferp)
(cl-some #'derived-mode-p user-save-inhibit-modes)
(run-hook-with-args-until-failure 'user-save-inhibit-predicates))
(user-save-mode +1)))
;;;###autoload
(define-globalized-minor-mode user-save-global-mode user-save-mode user-save-mode-in-some-buffers
(if user-save-global-mode
(when user-save-hook-into-kill-emacs
(add-hook 'kill-emacs-hook #'user-save-some-buffers))
(remove-hook 'kill-emacs-hook #'user-save-some-buffers)))
(provide 'user-save)
;;; user-save.el ends here

45
machines/bob.el Normal file
View File

@ -0,0 +1,45 @@
;;; bob.el --- Customizations for "bob" -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'acdw)
(require 'machine)
(defun +bob-set-faces (&rest _)
(let (;;(base-face "IBM Plex Mono")
;; (var-face "IBM Plex Sans")
;; (base-face "Iosevka Comfy Wide")
;; (var-face "Iosevka Comfy Duo")
(base-face "DejaVu Sans Mono")
(var-face "DejaVu Sans")
(base-size 100)
(var-size 1.0)
(italic-face nil)
;; (bold-face nil)
(mono-face nil))
(+set-faces
`((default
:family ,base-face
:height ,base-size
:weight regular)
(bold :family ,(or (bound-and-true-p bold-face) base-face)
:weight extra-bold)
(italic :family ,(or (bound-and-true-p italic-face) base-face)
:weight normal
:slant italic)
(fixed-pitch :family ,(or (bound-and-true-p mono-face) base-face)
:height 1.0)
(variable-pitch
:family ,(or var-face base-face)
:height ,var-size)
;; (org-italic
;; :family ,(or var-face base-face)
;; :slant italic)
))))
;; Other ideas: [[https://twitter.com/NPRougier/status/1488570192561160195][from Nic Rougier]]
(add-hook 'machine-after-load-theme-hook #'+bob-set-faces)
;; bob.el ends here (+bob-set-faces)

5
machines/gnu-linux.el Normal file
View File

@ -0,0 +1,5 @@
;;; linux.el -*- lexical-binding: t; -*-
(setq machine-default-height 105)
;;; linux.el ends here

13
machines/larry.el Normal file
View File

@ -0,0 +1,13 @@
;;; larry.el --- Customizations for "larry" -*- lexical-binding: t; -*-
;;; Code:
(require 'acdw)
(require 'machine)
(add-function :after machine-after-load-theme
(defun +larry-set-faces (&rest _)
(+set-faces
`((default :family "DejaVu Sans Mono")
(fixed-pitch :family "DejaVu Sans Mono")
(variable-pitch :family "DejaVu Sans")))))

23
machines/windows-nt.el Normal file
View File

@ -0,0 +1,23 @@
;;; windows.el --- Windows settings! -*- lexical-binding: t; -*-
;; Annoying gnu-tls bug; I "always" trust the certificate anyway, so let's be
;; insecure.
(setq network-security-level 'low
debug-on-error t)
;; Fonts
(setq machine-default-font "Cascadia Mono"
machine-default-height 90
machine-variable-pitch-font "Carlito"
machine-variable-pitch-height 1.2)
;; Add C:\Program Files\* and C:\Program Files (x86)\* to exec-path
(dolist (path (append (file-expand-wildcards "C:/Program Files/*")
(file-expand-wildcards "c:/Program Files (x86)/*")
;; Others...
(save-match-data
(split-string (getenv "PATH") ";" t))))
(add-to-list 'exec-path path :append))
;;; windows.el ends here

5
readme.md Normal file
View File

@ -0,0 +1,5 @@
# ~/.emacs
This is my Emacs config. There are many like it, but this one is mine.
(If you're reading this from tildegit, I've moved to [my own server now](https://git.acdw.net/emacs/).)

View File

@ -0,0 +1,14 @@
# -*- mode: snippet -*-
# name: +feature
# key: +f
# --
;;; `(file-name-nondirectory (buffer-file-name))` --- ${1:Title} -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
$0
(provide '`(file-name-nondirectory (file-name-sans-extension (buffer-file-name)))`)
;;; `(file-name-nondirectory (buffer-file-name))` ends here

View File

@ -0,0 +1,677 @@
# key: gpl3
# name: gpl3
# --
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) ${1:`(format-time-string "%Y")`} ${2:`user-full-name`} <${3:`user-mail-address`}>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
A fancy and fast mode-line inspired by minimalism design.
Copyright (C) 2018 Vincent Zhang <seagle0128@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
doom-modeline Copyright (C) 2018 Vincent Zhang <seagle0128@gmail.com>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

4
snippets/org-mode/sc Normal file
View File

@ -0,0 +1,4 @@
# key: sc
# name: sc
# --
[sc name="${1: $(yas-choose-value '("total-recovery" "br-location-page" "_locationnameslisted" "organizations-helped" "other-results" "truck-accident-results" "car-wreck-results" "personal-injury-results" "number-locations" "experience" "employees" "mon-number" "mon-address" "lc-number" "lc-address" "ham-number" "ham-address" "zac-number" "zac-address" "liv-number" "liv-address" "asc-number" "asc-address" "shrev-number" "shrev-address" "alx-address" "alx-number" "laf-number" "laf-address" "toll-free" "br-number" "br-address" "gmia" "g-guarantee" "ds-number"))}"][/sc] $0

View File

@ -0,0 +1,8 @@
# -*- mode: snippet -*-
# name: chicken
# key: chicken
# --
\#!/bin/sh
\#| -*- scheme -*-
exec csi -s $0 \"$@\"
|#

10
snippets/sh-mode/getopts Normal file
View File

@ -0,0 +1,10 @@
# -*- mode: snippet -*-
# name: getopts
# key: getopts
# --
while getopts ${1:h} opt; do
case "$opt" in
$0
esac
done
shift $(( OPTIND -1 ))