-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.el
1681 lines (1437 loc) · 67.7 KB
/
config.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
;; TODO I renamed ~/.config/doom/modules/lang/org to org_bck
;; to allow Doom to launch. Figure out where the issue is so that
;; you can begin using org again.
;; TODO Backup files from previous doom installation are in
;; the backup subfolder
;; TODO Pressing M-TAB in a new heading completes todo keywords.
;; See https://orgmode.org/manual/Completion.html
;; TODO Every time you update Doom or its packages, you need to
;; go to
;; ~/.config/emacs/.local/straight/repos/smartparens/smartparens-ruby.el
;; and change ruby-base-mode to ruby-mode. Do this for Emacs 28
;; until you upgrade to Emacs 29. Then delete the .elc version of
;; the file, then run doom sync.
;; TODO Get lsp-mode working within org-mode buffers
;; TODO Compare bpython and ptpython to ipython
;; TODO Look into xonsh
;; TODO Learn how to align things with evil-lion.
;; TODO Look into using https://github.com/cyrus-and/zoom
;; TODO Replace dash-docs with devdocs for the +docsets flag. This will be a
;; big project, so in the meantime I am simply rebinding spc-s-k to
;; devdocs-peruse.
;; TODO Debug the font-lock-related issues that are causing emacs to hang on
;; both macos and linux. Someone on
;; https://www.reddit.com/r/emacs/comments/k7cku8/when_emacs_hangs_what_do_you_do/
;; recommended
;; https://github.com/alphapapa/emacs-package-dev-handbook?tab=readme-ov-file#tools-8
;; TODO Also use open-dribble-file to debug Emacs. Kill Emacs as soon as it
;; hangs, then examine the file for whatever keystrokes triggered the hang.
;; TODO Read the zhsell manual (not just the man pages)
;; TODO Look into https://github.com/tuh8888/chezmoi.el
;; TODO Setup https://docs.atuin.sh/ (recommended by chezmoi for
;; tracking shell history)
;; TODO Purchase https://www.monolisa.dev/buy
;; TODO When you edit the contents
;; of a collapsed org-src block from within an org src buffer, it expands the
;; block once you exit. It would be nice to close it again upon exiting back to
;; the org buffer.
;; TODO Look into using the following packages plus (custom?)
;; functions to send text to the buffer for execution in a shell, as an
;; alternative to comint: https://codeberg.org/akib/emacs-eat
;; https://github.com/szermatt/mistty (misty-send-string)
;; https://www.masteringemacs.org/article/running-shells-in-emacs-overview
;; TODO C-h v over a long function or variable n
;; See ~/.config/doom/modules/editor/file-templates/README.org
(setq user-full-name "Justin Kroes"
user-mail-address "[email protected]")
;; https://christiantietze.de/posts/2021/06/emacs-center-window-on-current-monitor/
;; On startup, set the font to a smaller size if working on the
;; laptop without external monitors
(setq doom-font (font-spec
:family "JuliaMono"
:size (startup-font-size)))
;; Do not prompt when killing Emacs
(setq confirm-kill-emacs nil)
;; Disable messages about available keybindings when using M-x
(setq suggest-key-bindings nil)
;; See lisp/doom-keybinds.el for additional settings
(setq which-key-idle-delay 0.3)
;; NOTE This is less useful for commands where Doom defines
;; descriptions that mask remaps
(setq which-key-compute-remaps t)
;; TODO Comment this out if you want to re-enable Doom's
;; descriptions of bindings
(setq which-key-replacement-alist nil)
;; Treat command key like control
(setq ns-command-modifier 'control
mac-command-modifier 'control)
;; Focus Emacs on MacOS
(select-frame-set-input-focus (selected-frame))
;; On MacOS the binding here should match the shortcut in System
;; Settings > Keyboard > Keyboard Shortcuts > Keyboard > Move
;; focus to next window
(map! "C-SPC" #'other-frame)
;; NOTE If using the default of M-SPC instead, in Windows
;; Terminal actions settings unbind alt+space and save settings.
;; (setq doom-leader-alt-key "C-SPC")
;; (setq doom-localleader-alt-key "C-SPC m")
;; The file where bookmarks are saved
(setq bookmark-default-file (concat-path doom-emacs-dir "etc" "bookmarks"))
;; Projectile caching is used with e.g. doom-project-find-file (SPC-f-F).
;; It's probably worth enabling for large projects, but for now it's
;; omitting file candidates that have been recently added to e.g. a
;; private module.
(setq projectile-enable-caching nil)
;; Enable which-key paging for help-map
(general-unbind help-map "C-h")
;; Makes inserting org footnotes easier. Type M-e or M-a to
;; forward- or backward-sentence, then SPC-m-f
(setq-hook! 'org-mode-hook
sentence-end "[.?!…,;:]")
;;; titlecase -----------------------------------------------------------------
(use-package titlecase)
;;; hl-line -------------------------------------------------------------------
(defun describe-face-under-hl-line ()
"The `hl-line' face obscures underlying faces and must be
disabled to get the underlying face. NOTE When mixing fixed- and
variable-pitch fonts within a buffer, this function will not
correctly display variable-pitch fonts. Instead use
`describe-char'."
(interactive)
(if hl-line-mode
(unwind-protect
(progn
(hl-line-mode -1)
(call-interactively #'describe-face))
(hl-line-mode))
(call-interactively #'describe-face)))
;; Inspired by ~/.config/emacs/modules/completion/ivy/config.el
(define-key! [remap describe-face] #'describe-face-under-hl-line)
;;; evil ----------------------------------------------------------------------
;; Easier evil "j" and "k", harder "gg" navigation
;; (setq display-line-numbers-type 'relative)
;;; edebug / messages -------------------------------------------------
(autoload #'edebug-instrument-function "edebug")
;; NOTE Uncomment this if not using evil-collection-edebug
;; As noted in
;; https://github.com/noctuid/evil-guide#why-dont-keys-defined-with-evil-define-key-work-immediately,
;; keymap normalization may be required in some cases. One seems to be use of
;; edebug-mode-map as an evil-intercept map. Without normalization, if in normal
;; mode SPC will trigger leader until you first switch to another evil state.
;; (add-hook 'edebug-mode-hook #'evil-normalize-keymaps)
(add-hook 'edebug-mode-hook
(defun edebug-enter-normal-state ()
(when edebug-mode (evil-normal-state))))
;; BUG When switching to buffer *Messages*, Emacs seems to go into an infinite
;; loop. It is caused by the following. Hang can even be triggered by consult
;; preview of *Messages* when within consult-buffer
;; Because this buffer is launched early, I have to use this instead of
;; `messages-buffer-mode-hook'
;; (with-current-buffer "*Messages*"
;; (+word-wrap-mode)
;; (display-line-numbers-mode))
;; Print full results to the messages buffer when evaluating expressions
(setq eval-expression-print-length nil
eval-expression-print-level nil
edebug-print-length 1000)
;;; lisp/lib/sessions.el ------------------------------------------------------
;; NOTE I use sessions.el to quickly save and load frame layouts (windows and
;; buffers) so that I can quickly restart Emacs while developing its config. It
;; may have effects beyond that, but I haven't seen any major bugs yet. I prefer
;; doom/quickload-session for one-time saves, and my/load-session for
;; sessions I plan to reopen multiple times.
;; HACK Fix for https://github.com/doomemacs/doomemacs/issues/7580
(setq desktop-base-file-name ".emacs_desktop")
(map! :map doom-leader-quit/session-map
:desc "Restore last session" "l" #'my/quickload-session
:desc "Save session to file" "L" #'my/load-session)
(defun my/quickload-session ()
(interactive)
"Quickload without a confirmation prompt."
(doom/quickload-session t))
(defun my/load-session ()
"Load session from file. Do not delete the session file after loading
(use `doom/quickload-session' if you want auto-deletion). Do not prompt for
confirmation."
(interactive)
(let* ((desktops (cl-reduce #'remove
;; Use separate session files from quickload
(list ".." "." desktop-base-file-name)
:initial-value (directory-files desktop-dirname)
:from-end t))
(original (if (length= desktops 1)
(expand-file-name (car desktops) desktop-dirname)
(read-file-name "Session to restore:"
(file-name-as-directory desktop-dirname)
nil
t)))
(backup (concat original "_bck")))
(copy-file original backup)
(doom/load-session original)
(copy-file backup original)
(delete-file backup)))
;;; undo ----------------------------------------------------------------------
;; BUG Even if `undo-no-redo' is non-nil, if you `undo' all edits in a buffer,
;; switch to a second window, then switch back, `undo' no longer reports "No
;; further undo information." It redoes the first edit in the buffer, then
;; undoes that redo, then reports the message. `vundo' does not have this same
;; issue.
(setq undo-no-redo t)
(after! vundo
(evil-collection-define-key 'normal 'vundo-mode-map
"d" 'vundo-diff))
;;; modeline ---------------------------------------------------
;; Letter between brackets is more visible than an icon for evil state
(setq doom-modeline-modal-icon nil)
;; TODO The first info buffer shows the modeline, but successive buffers do not.
;; Investigate the modeline rules for popups. In the meantime, disable modeline
;; hiding for popups.
(remove-hook '+popup-buffer-mode-hook #'+popup-set-modeline-on-enable-h)
;;; windows----------------------------------------------------------------
;; BUG When the top line of a window's buffer is blank, the background extends
;; to the entire line, or else the letter is invisible.
;; https://emacs.stackexchange.com/questions/45895/changing-faces-one-at-a-time-outside-customize
(custom-set-faces
'(aw-leading-char-face
((t (:foreground "white" :background "red" :height 200)))))
;; NOTE If we bind `other-window' directly, it will remap to `ace-window' when
;; the window-select module is active. If we want to circumvent remapping, wrap
;; the remapped command in a function call.
(map! "M-o" (lambda () (interactive) (call-interactively #'other-window)))
;;; default -------------------------------------------------------------------
(map! "M-RET" comment-line-break-function)
;; TODO This inserts a space even if one should not be inserted.
;; e.g. during ruby multiline comments.
;; https://emacs.stackexchange.com/questions/22746/add-a-space-after-the-comment-delimiter
;; (defun comment-indent-new-line--insert-a-space-after (&rest _)
;; "Ensure there is exactly one space after `comment-start'"
;; (just-one-space))
;; (advice-add 'comment-indent-new-line :after #'comment-indent-new-line--insert-a-space-after)
;;; scrolling -----------------------------------------------------------------
;; Scroll screen to right (`scroll-left') automatically when cursor moves off
;; screen. See `hscroll-step' and `hscroll-margin' for details.
(setq auto-hscroll-mode t) ; 'current-line
;; Easier hscroll mappings
(map! "M-h" (lambda () (interactive) (evil-scroll-column-left 20))
"M-l" (lambda () (interactive) (evil-scroll-column-right 20)))
;;; auto-fill -----------------------------------------------------------------
;; Within a comment, typing a nonspace character followed by a space beyond
;; column will cause Emacs to hard wrap your comment
(add-hook 'prog-mode-hook 'turn-on-auto-fill) ; https://www.gnu.org/software/emacs/manual/html_node/efaq/Turning-on-auto_002dfill-by-default.html
(setq comment-auto-fill-only-comments t)
(setq-default fill-column 65)
;; When auto-filling, automatically undo the effects of auto-hscroll-mode by
;; scrolling back again to the left.
;;
;; Stack trace:
;; yas--auto-fill
;; yas--original-auto-fill-function
;; do-auto-fill / normal-auto-fill-function
(advice-add 'do-auto-fill
:around (lambda (func &rest _)
(when (funcall func) (scroll-right))))
;; TODO Set regexp if you need to inhibit auto-fill in specific places
;; (setq auto-fill-inhibit-regexp "")
;;; vertico -------------------------------------------------------------------
;; Notes
;;
;; Pressing "o SPC" within consult-buffer will limit candidates to org buffers.
;; See +vertico--consult-org-source
;;
;; Switch to directory (consult-dir) and recursively find file within dir
;; (consult-dir-jump-file). In particular, use when within the minibuffer.
;; consult-dir will replace the prompt of any filepath-completing function
;; with the selected dir!
;; TODO The +icons flag to the vertico module loads package
;; all-the-icons-completion, which is used by adding
;; all-the-icons-completion-marginalia-setup to marginalia-mode-hook. It
;; creates a pseudo-issue:
;; Run spc-f-f within ~/org, then embark-act. Notice the extra space between
;; the last candidate and the bottom of the frame. This is because vertico and
;; embark use the same number of candidates but different line sizes due to
;; icons. Furthermore, if you decrease all-the-icons-scale-factor and/or
;; all-the-icons-default-adjust, then scroll down the list of org notes, you'll
;; notice that the icons for longer filenames are smaller.
;;
;; Once you have fixed this issue, re-enable the +icons flag.
;;
;; Alternatively, enable vertico-resize:
;; (after! vertico (setq vertico-resize t))
(after! embark
(map! :map embark-file-map
;; Replaces consult-file-externally
(:when IS-WSL "x" #'open-in-windows)
;; Adds file to bookmarks
"b" #'my/bookmark-set))
;; TODO Modify this so that it works without visiting file via find-file
(defun my/bookmark-set (file)
"For use with embark-file-map. Bookmark the selected file and
prompt for a name, using filename as default input"
(let ((curbuf (current-buffer)))
;; NOTE bookmark-set uses the current buffer. find-file opens directories
;; in dired. dired with e.g. /mnt/c/ throws permission errors that prevent
;; a bookmark's creation, but that's fine.
(find-file file)
(bookmark-set (read-from-minibuffer
"Bookmark name: "
(file-name-nondirectory
(if (f-dir-p file)
(directory-file-name file)
file))))
(kill-buffer (current-buffer))
(switch-to-buffer curbuf)))
(map! :map doom-leader-file-map :desc "Bookmark" "b" #'file-bookmark)
(defun file-bookmark ()
(interactive)
(my/bookmark-set
(car (find-file-read-args
"Find file: "
(confirm-nonexistent-file-or-buffer)))))
;; NOTE I implement bookmarks mainly so I can insert them into file completion
;; functions such as find-file via consult-dir (or directly calling
;; consult-dir). Calling bookmark-jump on a directory launches dired. Dired is
;; an issue for WSL traversing Windows shared drives. It is best to avoid
;; Dired wherever possible.
;;; completion ----------------------------------------------------------------
(use-package! corfu
:hook (doom-first-input . global-corfu-mode)
:init
(setq corfu-cycle nil
corfu-auto t
corfu-auto-prefix 1
;; What `corfu-insert-separator' (bound to M-SPC during completion)
;; insrts. (Note that Doom's default binding for doom-leader-alt-key
;; shadows the default binding for `corfu-insert-separator'.)
;; NOTE Does this need to be the same as `orderless-component-separator'?
corfu-separator ?\s
;; corfu normally quits if a word boundary (e.g., space) is inserted, but
;; a space is permitted if it was inserted by `corfu-insert-separator'.
corfu-quit-at-boundary 'separator
;; Keep corfu alive without a match, following `corfu-insert-separator'
corfu-quit-no-match 'separator
corfu-on-exact-match 'quit
;; Indent and complete on tab
tab-always-indent 'complete
corfu-popupinfo-delay 0.5
;; Less jarring to avoid hiding when `corfu-popupinfo-delay' is short
corfu-popupinfo-hide nil)
;; TODO Consider making this similar to minibuffer-map
(evil-define-key 'insert corfu-map
[tab] #'corfu-insert
(kbd "TAB") #'corfu-insert
(kbd "C-b") #'corfu-scroll-down
(kbd "C-f") #'corfu-scroll-up
(kbd "C-,") #'corfu-first
(kbd "C-.") #'corfu-last
;; This will shadow the binding to evil-escape in
;; ~/doom-emacs/modules/editor/evil/config.el
(kbd "C-g") #'corfu-abort
(kbd "C-SPC") #'corfu-insert-separator
(kbd "C-@") #'corfu-insert-separator ; For the terminal
;; You can also press existing bindings for scroll-other-window(-down)
(kbd "M-p") #'corfu-popupinfo-scroll-down
(kbd "M-n") #'corfu-popupinfo-scroll-up
(kbd "C-h") #'corfu-info-documentation ; Works with scroll-other-window(-down)
(kbd "C-n") #'corfu-next
(kbd "C-p") #'corfu-previous)
(advice-add 'corfu--setup :after 'evil-normalize-advice)
(advice-add 'corfu--teardown :after 'evil-normalize-advice)
:config
;;(corfu-popupinfo-mode) ; Documentation popup next to completion
(corfu-history-mode))
(defun evil-normalize-advice (&rest _) (evil-normalize-keymaps))
;; TODO evil-org interferes with RET. This doesn't seem to be an issue in the
;; company module. See the fix for #1335 in company/config.el.
;; Elsewhere, unbinding RET when `corfu-preselect-first' is enabled allows us to
;; insert a newline without also completing
(after! corfu (define-key corfu-map (kbd "RET") nil))
(defun corfu-abort ()
"Undo changes made while corfu was active and quit. Takes one keystroke compared
to two to three for corfu-reset but resets changes all at once rather than
incrementally."
(interactive)
(cancel-change-group corfu--change-group)
(corfu-quit))
;; HACK `corfu-info-documentation' spawns a help buffer, which the popup module
;; catches, but the two are not yet compatible. `scroll-other-window' will
;; not scroll the help buffer, and "q" will quit the original buffer. (The latter
;; behavior is likely tied to corfu restoring the window configuration via
;; pre-command-hook.)
;; (defadvice! +popup--ignore-corfu-info-documentation (fn &rest args)
;; :around #'corfu-info-documentation
;; (push '("^\\*\\([Hh]elp\\|Apropos\\)" nil) display-buffer-alist)
;; (apply fn args)
;; (pop display-buffer-alist))
;; Based on https://github.com/minad/corfu/wiki, but it actually works...
;; NOTE This setup will not be torn down properly if lsp-mode is disabled.
;; See the definition of lsp-completion-mode if you want to improve the
;; code
;; (unless (featurep! :completion company)
;; (setq lsp-completion-provider :none)
;; ;; OPTIONAL: The first word uses orderless-flex for filtering. This means the
;; ;; characters are matched in order but do not have to be consecutive. It
;; ;; returns more results than a pure orderless style would, at least initially
;; ;; (add-hook 'lsp-completion-mode-hook
;; ;; NOTE This also affects vertico; e.g., you can't use regex like "^" on the first word
;; ;; (lambda ()
;; ;; (add-to-list 'orderless-style-dispatchers
;; ;; (lambda (_pattern index _total)
;; ;; (and (eq index 0) 'orderless-flex)))))
;; ;; Make lsp completion use orderless
;; (add-hook 'lsp-completion-mode-hook
;; (lambda ()
;; (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
;; '(orderless))))
;; ;; Bust capf caches, refreshing candidates more often (e.g., on backspace)
;; (add-hook 'lsp-completion-mode-hook
;; (lambda ()
;; (setq-local completion-at-point-functions
;; (list (cape-capf-buster #'lsp-completion-at-point))))))
(use-package! cape)
(use-package! kind-icon
:after corfu
:init (setq kind-icon-default-face 'corfu-default)
:config (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
;; TODO Marginlia annotations don't seem to shift left to account for
;; left-truncated candidates.
;; Left-truncate matches/candidates in e.g. consult-line (spc s b) and
;; consult-recent-file (spc f r) and the recent file source for consult-buffer
;; (spc b b)
;; (use-package! vertico-truncate
;; :config
;; (vertico-truncate-mode))
;;; emacs lisp ----------------------------------------------------------------
;; modules/config/default/config.el binds this command to [C-return], which is
;; equivalent to "C-<return>" and hides any bindings to "C-RET"
(unbind-command global-map #'+default/newline-below)
(unbind-command evil-insert-state-map #'+default/newline-below)
(unbind-command evil-normal-state-map #'+default/newline-below)
(map! :map emacs-lisp-mode-map :gin "C-RET" #'eval-defun)
(map! :map emacs-lisp-mode-map :gin "C-<return>" #'eval-defun)
;;; org -----------------------------------------------------------------------
;; Where my org notes live
(setq org-directory (expand-file-name "~/org"))
;; Footnotes
(setq org-footnote-define-inline nil
org-footnote-auto-label t
org-footnote-auto-adjust t ; Like org-footnote-normalize
org-footnote-section "Footnotes")
;; Shrink tables on startup and show shrunk text in the echo area automatically
;; when cursor is over the ellipses that represent the shrunk text
(setq org-startup-shrink-all-tables t
help-at-pt-display-when-idle t
help-at-pt-timer-delay 0.25)
(help-at-pt-set-timer)
(when doom-variable-pitch-font
(add-hook 'org-mode-hook 'variable-pitch-mode)
;; NOTE Unlike the `line-spcing' text property, this works with visual lines
(setq-hook! 'org-mode-hook line-spacing 0.5))
;; The org-transclude manual recommends removing this advice
(after! org (advice-remove 'org-link-search '+org--recenter-after-follow-link-a))
;; TODO It is still a pain in the ass to define headings this way, as opposed
;; to just using a spreadsheet. There should be some way to interface between
;; the two. Spreadsheets are easier to edit, because you enter the headers
;; exactly once and don't need SPC-m-o to properly enter data.
(defun org-property-from-columns ()
"Add properties that start with a lowercase character and are
specified in a file-level COLUMNS property to the heading at
point."
(interactive)
(let ((columns (split-string (org-entry-get 0 "COLUMNS") " ")))
(dolist (col columns)
(setq col (replace-regexp-in-string "^%" "" col))
(when (and (s-lowercase-p (substring col 0 1))
(not (org-entry-get nil col)))
(org-set-property col "")))))
;; TODO Has org-attach always been scrolled to the bottom? File a bug report so
;; you can remove this hack eventually.
(advice-add 'org-attach :around #'test)
(defun test (fn &rest _)
(interactive)
(cl-letf (((symbol-function 'org-fit-window-to-buffer)
(lambda (&optional window max-height min-height shrink-only)
(cond ((not (window-full-width-p window))
;; Do nothing if another window would suffer.
)
((not shrink-only)
(fit-window-to-buffer window max-height min-height))
(t (shrink-window-if-larger-than-buffer window)))
(or window (selected-window))
;; HACK Display top of *Org-attach* buffers
(prin1 "hello")
(goto-line 1 (get-buffer "*Org Attach*")))))
(call-interactively fn)))
;; NOTE For this to work, the docs for `org-after-todo-statistics-hook' state that
;; the heading needs a statistics cookie. To insert a statistics cookie, manually
;; type "[/]" after e.g. a heading. As you toggle between todo and done for
;; subheadings, the cookie will update to display the fraction of entries marked
;; as done.
(add-hook 'org-after-todo-statistics-hook 'my/org-summary-todo)
(defun my/org-summary-todo (n-done n-not-done)
"Switch entry to DONE when all subentries are done, to TODO otherwise."
(let (org-log-done org-log-states) ; turn off logging
(org-todo (if (= n-not-done 0) "DONE" "TODO"))))
;; Insert a blank line when inserting a (non-sub)heading (see advice below), and
;; fold that line when cycling
(setq org-blank-before-new-entry '((heading . 1) (plain-list-item))
org-cycle-separator-lines 2)
(defadvice! +org/insert-item-below-a (fn &rest args)
"Still insert a list item when on a list, but use the
built-in org functions for inserting a heading, because
`+org/insert-item-below' does not respect `org-blank-before-new-entry'."
:around '+org/insert-item-below
(if (org-at-heading-p)
(call-interactively #'org-insert-heading-respect-content)
(apply fn args)))
(defadvice! org-insert-subheading-a (fn &rest args)
"Ignore `org-blank-before-new-entry when inserting a subheading
as the first child of a heading'"
:around #'org-insert-subheading
(let (org-blank-before-new-entry)
(apply fn args)))
;; When disabled, (C-)M-RET inserts a (sub)heading above
;; when called at beginning of line; else directly below
(after! org (setq org-insert-heading-respect-content nil))
;; C-RET (+org/insert-item-below) already enters insert state, so make M-RET
;; and C-M-RET do the same. We eschew evil-org-define-(b|e)ol-command so that
;; different behavior can be used at beg and end of line
(advice-add 'org-meta-return :after #'evil-insert-state)
(advice-add 'org-insert-subheading :after (lambda (&rest _) (evil-insert-state)))
;; NOTE +org/insert-item-below-a calls this function instead of
;; +org/insert-item-below-a when at a heading
(advice-add 'org-insert-heading-respect-content :after #'evil-insert-state)
(defadvice! org-insert-heading-a (&optional arg invisible-ok top)
"Make (C-)M-RET respect property drawers when inserting
(sub)heading below current heading, assuming that
`org-insert-heading-respect-content' is nil."
:override 'org-insert-heading
(interactive "P")
(let* ((blank? (org--blank-before-heading-p (equal arg '(16))))
(level (org-current-level))
(stars (make-string (if (and level (not top)) level 1) ?*)))
(cond
((or org-insert-heading-respect-content
(member arg '((4) (16)))
(and (not invisible-ok)
(invisible-p (max (1- (point)) (point-min)))))
;; Position point at the location of insertion. Make sure we
;; end up on a visible headline if INVISIBLE-OK is nil.
(org-with-limited-levels
(if (not level) (outline-next-heading) ;before first headline
(org-back-to-heading invisible-ok)
(when (equal arg '(16)) (org-up-heading-safe))
(org-end-of-subtree invisible-ok 'to-heading)))
;; At `point-max', if the file does not have ending newline,
;; create one, so that we are not appending stars at non-empty
;; line.
(unless (bolp) (insert "\n"))
(when (and blank? (save-excursion
(backward-char)
(org-before-first-heading-p)))
(insert "\n")
(backward-char))
(when (and (not level) (not (eobp)) (not (bobp)))
(when (org-at-heading-p) (insert "\n"))
(backward-char))
(unless (and blank? (org-previous-line-empty-p))
(org-N-empty-lines-before-current (if blank? 1 0)))
(insert stars " " "\n")
;; Move point after stars.
(backward-char)
;; When INVISIBLE-OK is non-nil, ensure newly created headline
;; is visible.
(unless invisible-ok
(if (eq org-fold-core-style 'text-properties)
(cond
((org-fold-folded-p
(max (point-min)
(1- (line-beginning-position)))
'headline)
(org-fold-region (line-end-position 0) (line-end-position) nil 'headline))
(t nil))
(pcase (get-char-property-and-overlay (point) 'invisible)
(`(outline . ,o)
(move-overlay o (overlay-start o) (line-end-position 0)))
(_ nil)))))
;; At a headline...
((org-at-heading-p)
(cond ((bolp)
(when blank? (save-excursion (insert "\n")))
(save-excursion (insert stars " \n"))
(unless (and blank? (org-previous-line-empty-p))
(org-N-empty-lines-before-current (if blank? 1 0)))
(end-of-line))
((and (org-get-alist-option org-M-RET-may-split-line 'headline)
(org-match-line org-complex-heading-regexp)
(org-pos-in-match-range (point) 4))
;; Grab the text that should moved to the new headline.
;; Preserve tags.
(let ((split (delete-and-extract-region (point) (match-end 4))))
(if (looking-at "[ \t]*$") (replace-match "")
(org-align-tags))
(end-of-line)
(when blank? (insert "\n"))
(insert "\n" stars " ")
(when (org-string-nw-p split) (insert split))))
(t
;; HACK Insert heading after property drawer
(re-search-forward org-property-end-re
(save-excursion
(outline-next-heading)
(point))
t)
(end-of-line)
(when blank? (insert "\n"))
(insert "\n" stars " "))))
;; On regular text, turn line into a headline or split, if
;; appropriate.
((bolp)
(insert stars " ")
(unless (and blank? (org-previous-line-empty-p))
(org-N-empty-lines-before-current (if blank? 1 0))))
(t
(unless (org-get-alist-option org-M-RET-may-split-line 'headline)
(end-of-line))
(insert "\n" stars " ")
(unless (and blank? (org-previous-line-empty-p))
(org-N-empty-lines-before-current (if blank? 1 0))))))
(run-hooks 'org-insert-heading-hook))
;; TODO Bind this to something
(defun insert-org-entity ()
"A dumb replacement for counsel-org-entity. See `org-pretty-entities'."
(interactive)
(let* ((str (completing-read
"Entity: "
(cl-loop for element in (append org-entities org-entities-user)
unless (or (stringp element)
(string-prefix-p "_" (car element))) ; some hspace elements
collect (cons
(format "%s | %s"
(cl-first element) ; name
(cl-seventh element)) ; utf-8
element))))
(latex (concat "\\" (nth 0 (split-string str "|" t " ")))))
(insert latex)))
;; TODO Replace this function with advice and remove the reference to it in
;; `+org-dwim-at-point'
;;
;; Note that this function is used by `+org/dwim-at-point' for links
;; when hitting RET
(defun my/org-open-at-point ()
"Use find-file with file link as initial input instead of opening file link
to a directory in dired. This is hacky but way faster than using dired on
shared drives."
(interactive)
(let* ((context
;; Only consider supported types, even if they are not the
;; closest one.
(org-element-lineage
(org-element-context)
'(citation citation-reference clock comment comment-block
footnote-definition footnote-reference headline
inline-src-block inlinetask keyword link node-property
planning src-block timestamp)
t)))
(if (and (eq (car context) 'link)
(string= (plist-get (car (cdr context)) :type) "file")
(f-dir-p (plist-get (car (cdr context)) :path)))
(let ((default-directory (plist-get (car (cdr context)) :path)))
(call-interactively #'find-file))
(call-interactively #'org-open-at-point))))
;;;; org-cycle / org-fold -----------------------------------------------------
;; Keep drawers open
(setq org-cycle-hide-drawer-startup nil)
;; Don't fold file-level drawer when cycling. Despite the changelog for org
;; v9.6, drawer folding state is not preserved for the file-level drawer.
(after! org-fold (fset 'org-fold-hide-drawer-all #'ignore))
(defun my/org-cycle ()
"Adapt org-cycle to fold the current code block if point is within
one. Useful for finding one's place within a large code block
without folding any headings."
(interactive)
(let* ((element (org-element-at-point))
(type (org-element-type element)))
(cond ((eq type 'src-block)
(let* ((post (org-element-property :post-affiliated element))
(start (save-excursion
(goto-char post)
(line-end-position)))
(end (save-excursion
(goto-char (org-element-property :end element))
(skip-chars-backward " \t\n")
(line-end-position))))
(when (let ((eol (line-end-position)))
(and (/= eol start) (/= eol end)))
(call-interactively #'org-previous-block)))))
(call-interactively #'org-cycle)))
;;;; org-file -----------------------------------------------------------------
;; NOTE This also affects org-attach-open
;; Open these non-text filetypes in Windows instead of WSL
(after! org
(when IS-WSL
(setq org-file-apps
'(("\\.pptx?\\'" . open-in-windows)
("\\.pdf?\\'" . open-in-windows)
("\\.docx?\\'" . open-in-windows)
("\\.txt?\\'" . open-in-windows)
("\\.xlsx?\\'" . open-in-windows)
("\\.csv?\\'" . open-in-windows)
("\\.png?\\'" . open-in-windows)
("\\.html?\\'" . open-in-windows)
(remote . emacs)
(auto-mode . emacs)
;; dired is unbelievably slow on Windows shared network drives
(directory . open-in-windows))
browse-url-generic-program "/mnt/c/Windows/System32/cmd.exe"
browse-url-generic-args '("/c" "start" "")
browse-url-browser-function 'browse-url-generic)))
;;;; org-src / org-babel ------------------------------------------------------
;; NOTE Large code blocks can slow down `org-cycle-global' noticeably when
;; code block native fontification is enabled
(setq org-src-fontify-natively t)
;; Edit org src block in the same window as org file
(after! org
(set-popup-rule! "^\\*Org Src" :ignore t)
(advice-remove 'org-edit-src-exit '+popup--org-edit-src-exit-a)
(setq org-src-window-setup 'current-window))
;; Normalize evil bindings in org src windows
(add-hook! 'org-src-mode-hook #'evil-normalize-keymaps)
;; Save edits and exit back to org file with "q"
(setq org-src-ask-before-returning-to-edit-buffer nil)
(map! :map org-src-mode-map :n "q" #'my/org-edit-src-save-and-exit)
(defun my/org-edit-src-save-and-exit ()
(interactive)
(org-edit-src-save)
(org-edit-src-exit)
;; Prevents accidental text insertion
(evil-normal-state)
;; TODO Make this respect current fold settings. Only re-fold
;; if it was previously folded.
;; Assume we want a folded block, because editing a block unfolds it. Since
;; we are exiting a src block, we don't need to test whether we are within a block.
;; (org-end-of-line)
;; (when (not (org-fold-folded-p))
;; (my/org-cycle))
)
;; BUG This advice was creating huge lags for org-babel-tangle
(after! org
(advice-remove 'org-export-to-file '+org--dont-trigger-save-hooks-a)
(advice-remove 'org-babel-tangle '+org--dont-trigger-save-hooks-a))
;;;; org-element --------------------------------------------------------------
;; TODO Prior to org v9.6, org-element threw a lot of errors about invalidated
;; cache. Disable the cache if you are still having issues. org v9.6 refactored
;; the cache and encouraged users to test it out. See
;; https://orgmode.org/Changes.html.
;;
;; (setq org-element-use-cache nil
;; org-element--cache-self-verify 'backtrace)
;;;; org-attach ---------------------------------------------------------------
(after! org
(when IS-WSL
;; Windows-based attachment directory for WSL Emacs
(setq org-attach-id-dir
"/mnt/c/Users/jkroes/OneDrive - California Department of Pesticide Regulation (1)/org-attach"))
;; When enabled, org-attach will add attachments to the nearest node with an
;; attachment directory, so you can e.g. add an attachment to an org-roam
;; file from anywhere in the file if none of the above headings have an
;; attachment dir. To create an attachment dir on the current heading, first
;; run `org-id-get-create'.
(setq org-attach-use-inheritance t)
;; When running org-attach, store a file link with absolute path to the
;; attached file. You can also store a link via embark-act, embark-copy-as-kill
;; (@w), then yank.
(setq org-attach-store-link-p 'attached))
;; Stack trace when following attachment links:
;; org-open-at-point
;; org-link-open
;; org-attach-follow
;; org-link-open-as-file(org-attach-expand)
;; org-open-file
;; (user-error "No such file: %s" file))
(defadvice! +org-attach-expand-a (file)
"HACK A version of org-attach-expand that actually will look through all
parent headings until it finds the linked attachment, to quote the docs for
`org-attach-use-inheritance'. Normally the search stops at the first heading for
which there is an attachment directory"
:override #'org-attach-expand
(let ((filepath (expand-file-name file (org-attach-dir))))
(if (and (org-attach-dir)
(file-exists-p filepath))
filepath
(if (= (point) (point-min))
;; Don't pass back control to org-attach-follow,
;; then org-link-open-as-file, then org-open-file.
;; If no file is found, exit immediately.
(user-error "No such file: %s" file)
(org-roam-up-heading-or-point-min)
(org-attach-expand file)))))
(defvar org-attach-ignore-regexp-list (list "." ".." ".DS_STORE")
"A list of filenames for org-attach to ignore")
(defadvice! +org-attach-file-list-a (directory)
"Return a list of files in the attachment DIRECTORY.
This ignores \".\", \"..\", \".DS_STORE\", and files ending in \"~\"."
:override #'org-attach-file-list
(delq nil
(mapcar (lambda (x)
(if (string-match
(concat "^"
(regexp-opt
org-attach-ignore-regexp-list)
"\\'")
x) nil x))
(directory-files directory nil "[^~]\\'"))))
(defun delete-empty-org-attach-id-dirs ()
"Delete empty directories within org-attach-id-dir."
(interactive)
(require 'dash)
;; Delete org-attach-id-dir sub-sub folders
(-each
(-filter
(lambda (file) (directory-empty-p file))
(directory-files-recursively org-attach-id-dir "" t))
#'delete-directory)
;; Delete org-attach-id-dir sub-folders. Some will be newly empty after the
;; last deletion.
(-each
(-filter
(lambda (file) (directory-empty-p file))
(directory-files org-attach-id-dir t))
#'delete-directory))
;;;; org-roam --------------------------------------------------
;; BUG If `org-roam-node-find' returns no nodes, it is probably an issue with
;; this file.
;; NOTE This must be a relative path. org-roam initializes this with
;; `org-attach-id-dir' but doesn't check whether it's a relative path!
;; See `org-roam-file-p'.
(after! org-roam
(setq org-roam-file-exclude-regexp (if (not IS-WSL)
(list (file-name-nondirectory (directory-file-name org-attach-id-dir)))
(list)))
;; TODO Remove this once you finish processing these files.
(push "reorg/" org-roam-file-exclude-regexp))
;; All of my org files are org-roam files
(setq org-roam-directory org-directory)
(after! org-roam
;; Only complete roam node titles within link brackets; otherwise, completion
;; interferes with normal typing
(setq org-roam-completion-everywhere nil)
;; NOTE If this isn't working, try running `org-roam-db-clear-all',then
;; `org-roam-db-sync'
(defvar org-roam-excluded-tags
(list (bound-and-true-p org-archive-tag)
(bound-and-true-p org-attach-auto-tag)
;; Omit vulpea tag. TODO Update this when you create a variable
;; to customize the vulpea tag
"project"))
(setq org-roam-db-node-include-function
(lambda ()
(not (-any (lambda (tag)
(member tag (org-get-tags nil)))
org-roam-excluded-tags)))))
;; Replace all roam: links generated by org-roam completion with id: links, on
;; file save.
(add-hook 'org-mode-hook
(lambda () (add-hook 'after-save-hook #'org-roam-link-replace-all nil t)))
;;;; consult-org-roam
(use-package! consult-org-roam
:after org-roam
:config
;; Annotate tags rather than including them in
;; org-roam-node-display-template. In the latter case, they are part of the
;; vertico candidate string, and vertico-insert (tab) inserts the tags as
;; well as the org-roam node string. Plus there's this unresolved bug:
;; https://github.com/org-roam/org-roam/issues/2066. On the flip side, you
;; can't search annotations.
;; TODO my/org-roam-node-read--annotation isn't working as of 2/4/24. I
;; (after! dendroam (setq org-roam-node-annotation-function #'my/org-roam-node-read--annotation))
;; Faster live preview
(setq consult-org-roam-grep-func #'consult-ripgrep)
;; Advise org-roam-node-read to use consult--read. This package uses
;; live previews by default (consult-org-roam--node-preview), but you
;; can suppress them via consult-customize.