-
Notifications
You must be signed in to change notification settings - Fork 3
/
init.el
870 lines (689 loc) · 32.4 KB
/
init.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
;; Emacs configuration for Brandon Rhodes, who does lots of Python.
;; Let's start with all the variables set through M-x customize-apropos.
;; I keep them here at the top of the file so that if a later error
;; prevents the rest of this file from being loaded, I at least get to
;; enjoy these settings while I fix things.
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(ag-arguments
'("--hidden" "--ignore" ".git" "--ignore" ".tox" "--smart-case" "--stats" "--width" "240"))
'(auto-save-default nil)
'(blacken-executable "~/.emacs.d/usr/bin/black")
'(blacken-fast-unsafe t)
'(blacken-skip-string-normalization t)
'(blink-cursor-mode nil)
'(c-default-style
'((c-mode . "k&r")
(c++-mode . "k&r")
(java-mode . "java")
(awk-mode . "awk")
(other . "gnu")))
'(column-number-mode t)
'(eldoc-echo-area-use-multiline-p nil)
'(fill-column 72)
'(help-at-pt-display-when-idle t nil (help-at-pt))
'(help-at-pt-timer-delay 0)
'(help-window-select t)
'(image-file-name-extensions
'("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm" "xpm" "pbm" "pgm" "ppm" "pnm"))
'(indent-tabs-mode nil)
'(inhibit-startup-screen t)
'(kill-do-not-save-duplicates t)
'(line-number-mode t)
'(longlines-show-hard-newlines t)
'(make-backup-files nil)
'(menu-bar-mode nil)
'(mode-line-format
'("%e" mode-line-front-space mode-line-mule-info mode-line-client mode-line-modified mode-line-frame-identification mode-line-buffer-identification " " mode-line-position " " mode-name mode-line-misc-info mode-line-end-spaces))
'(mouse-yank-at-point t)
'(org-clock-into-drawer nil)
'(org-clock-mode-line-total 'current)
'(org-duration-format 'h:mm)
'(org-startup-folded t)
'(org-startup-truncated nil)
'(recenter-positions '(middle))
'(safe-local-variable-values '((encoding . utf-8)))
'(scroll-preserve-screen-position t)
'(search-default-mode 'char-fold-to-regexp)
'(show-paren-delay 0)
'(show-paren-mode t)
'(show-trailing-whitespace t)
'(tool-bar-mode nil)
'(tooltip-mode nil)
'(uniquify-buffer-name-style 'forward nil (uniquify))
'(use-file-dialog nil)
'(vc-handled-backends nil))
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(escape-glyph ((t (:foreground "#d9c072"))))
'(highlight ((((class color) (min-colors 88)) :background "#eee8d5") (t :background "white")))
'(magit-item-highlight ((t nil)))
'(quote (mode-line-highlight ((t nil)))))
;; The escape-glyph color (above) was designed to make longlines mode
;; easier for me to read by making the visible newline characters blend
;; into the background instead of standing visually in the way of the
;; text. It was produced by chroma.js acting on Solarized colors:
;; > chroma.mix('#b58900', '#fdf6e3').hex()
;; < "#d9c072"
;; (add-to-list 'load-path "~/.emacs.d/site-lisp")
;; TODO: look back over the packages I previously had selected:
;; '(package-selected-packages
;; '(git-link importmagic multiple-cursors magit json-mode go-mode fzf edit-server browse-kill-ring ag))
;; Essential Mac OS X keybindings, put here at the top so that they
;; get installed even if something later in this file fails. My
;; wrists would not survive my career if I had to twist my fingers
;; away from home row for every Ctrl or Meta, so I put them at my
;; thumbs instead. Thumbs are orthogonal to the home-row fingers.
(when (eq system-type 'darwin)
(setq mac-command-modifier 'control)
(setq mac-right-command-modifier 'meta)
)
;; An experiment: it rarely seems like I want fundamental mode.
;; Instead, I'm always typing a quote character, and it comes out
;; straight instead of curly, and I have to manually switch into text
;; mode. So let's make text mode the default for un-extensioned files
;; and see how annoying the result is.
;;
;; This follows the original practice of Unix, where all files not
;; marked executable were text files (directories were identified by
;; starting with capital letters).
(setq-default major-mode 'text-mode)
;; Emacs had started hanging every time I started it, and I found this
;; recommendation at http://spacemacs.org/doc/FAQ#orgheadline14 - it
;; stops tramp mode (why does tramp mode, which I never use, get to run
;; at startup?) from invoking SSH on an unknown hostname that my ISP
;; apparently intercepts.
(setq tramp-ssh-controlmaster-options
"-o ControlMaster=auto -o ControlPath='tramp.%%C' -o ControlPersist=no")
;; Ctrl-Tab and Shift-Ctrl-Tab switch between tabs in my browser.
;; To re-use that muscle memory, make them switch buffers in Emacs.
(global-set-key [C-tab] 'other-window)
(global-set-key [(control iso-lefttab)] ; not C-S-tab or C-backtab?
(lambda () (interactive) (other-window -1)))
;; I have spent far too much of my life answering "y" to the prompt
;; "Active processes exist; kill them and exit anyway?"
(add-hook 'shell-mode-hook
(lambda ()
(process-kill-without-query
(get-buffer-process (current-buffer)) nil)))
;; C-z should never iconify Emacs, only suspend it when in a terminal.
;; I mean, who even iconifies programs any more? Not me.
(if (eq window-system 'x)
(global-set-key [(control z)] 'suspend-emacs))
;; "M-x date", which adds a simple header for diary-style text files.
(defun date ()
(interactive)
(insert (concat "======== "
(format-time-string "%Y %B %d %A")
" ========\n\n")))
;; Python
(add-hook 'python-mode-hook
(lambda ()
(define-key python-mode-map (kbd "M-[") #'python-indent-shift-left)
(define-key python-mode-map (kbd "M-]") #'python-indent-shift-right)
(setq outline-regexp " *\\(class\\|def\\) ")))
;; The "Eglot" language server library is now built-in to Emacs 29, so
;; let's try out "pylsp" from "python-lsp-server" (see SETUP.sh).
(add-hook 'python-mode-hook 'eglot-ensure)
(add-to-list 'exec-path "~/.emacs.d/venv/bin") ; so it finds `pylsp`
;; To debug `pylsp`, for instance after editing `~/.config/pycodestyle`:
;; (with-eval-after-load 'eglot
;; (add-to-list 'eglot-server-programs
;; '(python-mode . ("pylsp" "-vv"))))
;; But "Eglot" has a terrible behavior: every time you pause while
;; navigating the buffer, it computes and displays the documentation for
;; the symbol at point, which is both distracting and also burns your
;; laptop's CPU and battery to the ground. So let's disable its timers
;; and switch to manually invoking it via a keystroke.
;;(setq eldoc-display-functions '(eldoc-display-in-buffer) ; buffer only
(defun bcr-update-eglot-key-map ()
(define-key eglot-mode-map (kbd "C-h .")
'eldoc-print-current-symbol-info))
(defun bcr-remove-eldoc-hooks ()
(remove-hook 'post-command-hook #'eldoc-schedule-timer t)
(remove-hook 'pre-command-hook #'eldoc-pre-command-refresh-echo-area t))
(add-hook 'eglot-managed-mode-hook 'bcr-update-eglot-key-map)
(add-hook 'eldoc-mode-hook 'bcr-remove-eldoc-hooks)
;; Another very poor behavior of Eldoc: after splitting the window and
;; displaying its documentation buffer, it leaves your cursor stranded
;; over in the source code, so that closing the documentation requires
;; several keystrokes - `C-x o` then `q`. Let's instead move focus to
;; the buffer, so simply typing `q` will dismiss the docs.
(defun bcr-switch-to-eldoc-window (docs interactive)
(other-window 1))
(advice-add 'eldoc-display-in-buffer :after #'bcr-switch-to-eldoc-window)
;; Search with "ag".
(require 'thingatpt)
(defun pcre-quote (string)
(let* ((s string)
(s (replace-regexp-in-string "(" "\\(" s 'fixedcase 'literal))
(s (replace-regexp-in-string ")" "\\)" s 'fixedcase 'literal)))
s))
;; (pcre-quote "(a)")"\\(a\\)"
(defun pcre-word-delimit (string)
(let* ((s string)
(s (if (string-match-p "^[A-Za-z]" s)
(concat "\\b" s)
s))
(s (if (string-match-p "[A-Za-z]$" s)
(concat s "\\b")
s)))
s))
;; (pcre-word-delimit "a")"\\ba\\b"
;; (pcre-word-delimit ".a")".a\\b"
;; (pcre-word-delimit "a.")"\\ba."
;; (pcre-word-delimit ".a.")".a."
;; My own hand-tuned "ag" behavior: if the region is active, search for
;; the text in the region; otherwise search for the symbol at point;
;; otherwise prompt the user for a regular expression. In the first two
;; cases, if the pattern begins or ends with a letter than "\b" is
;; prefixed or suffixed to constrain the results.
(defun ag-word (word)
(ag-project-regexp (pcre-word-delimit (pcre-quote word)))
(other-window 1))
(defun ag-current-word ()
(interactive)
(if (use-region-p)
(ag-word (buffer-substring (region-beginning) (region-end)))
(let ((word (thing-at-point 'symbol)))
(if word
(ag-word word)
(progn
(call-interactively 'ag-project-regexp)
(other-window 1))))))
;; (global-set-key (kbd "M-a") 'ag-current-word)
;; I like jumping automatically to compilation errors (see the stanza
;; later in this file that mentions "recompile"), but I don't like
;; jumping to whatever happens to be the first search result when "ag"
;; uses compilation mode to present search results. So in that case
;; let's turn the setting off.
(add-hook 'ag-mode-hook
(lambda ()
(make-local-variable 'compilation-auto-jump-to-first-error)
(setq compilation-auto-jump-to-first-error nil)
))
(require 'compile)
;; Never auto-split a frame into a left and right window.
(setq split-width-threshold nil)
(setq split-height-threshold 0)
;; Multiple cursors.
;; (require 'multiple-cursors)
;; (global-set-key (kbd "M-n") 'mc/mark-next-like-this)
;; (global-set-key (kbd "M-p") 'mc/unmark-next-like-this)
;; Recognize Unicode curly apostrophe during spell checks, instead of
;; assuming it breaks contractions into two separate words.
(setq ispell-local-dictionary-alist
'(("english" "[[:alpha:]]" "[^[:alpha:]]" "['’]" t ("-d" "en") nil utf-8)
))
(setq ispell-dictionary "english")
;; I'm not sure why the following didn't work for recognizing curly
;; single quotes in ispell; the change it makes never seems to show up
;; in the alist? Leaving it here as a mystery so that I don't forget
;; the syntax:
;; (setf (alist-get "english" ispell-dictionary-alist nil nil 'equal)
;; '("[[:alpha:]]" "[^[:alpha:]]" "'’" t ("-d" "en") nil utf-8))
;; Flyspell to check my spelling and underline possible mistakes.
;; Thanks to http://stackoverflow.com/questions/8332163/
(if (executable-find "aspell")
(progn
(add-hook 'message-mode-hook 'turn-on-flyspell)
(add-hook 'rst-mode-hook 'turn-on-flyspell)
(add-hook 'text-mode-hook 'turn-on-flyspell)
;; (add-hook 'python-mode-hook (lambda () (flyspell-prog-mode)))
(add-hook 'js-mode-hook
(lambda ()
(when (string-match-p "^ [A-Za-z]" (buffer-string))
(make-variable-buffer-local 'js-indent-level)
(set-variable 'js-indent-level 2))
(flyspell-prog-mode)))))
;; Only 2 space indents for JSON. It is just data, after all.
(add-hook 'json-mode-hook
(lambda ()
(make-local-variable 'js-indent-level)
(setq js-indent-level 2)))
;;; https://www.emacswiki.org/emacs/UnfillParagraph
;;; Stefan Monnier <foo at acm.org>. It is the opposite of fill-paragraph
(defun unfill-paragraph (&optional region)
"Takes a multi-line paragraph and makes it into a single line of text."
(interactive (progn (barf-if-buffer-read-only) '(t)))
(let ((fill-column (point-max))
;; This would override `fill-column' if it's an integer.
(emacs-lisp-docstring-fill-column t))
(fill-paragraph nil region)))
(define-key global-map "\M-Q" 'unfill-paragraph)
;; Ispell should stop suggesting that it can cure a misspelling by
;; splitting a word into two quite different words.
(require 'cl) ; for Common List remove-if function
(defun contains-space-p (s)
"Determine whether a given string contains spaces."
(string-match-p " " s))
(defadvice ispell-parse-output (after remove-multi-words activate)
"Remove multi-word suggestions from ispell-style output."
(if (listp ad-return-value)
(setq ad-return-value
(list (nth 0 ad-return-value) ;; original word
(nth 1 ad-return-value) ;; offset in file
(remove-if 'contains-space-p (nth 2 ad-return-value))
(remove-if 'contains-space-p (nth 3 ad-return-value))
))))
;; Spell-check the whole buffer upon entry (thanks, Ryan McGuire!)
;; unless it is really huge.
(defadvice flyspell-mode
(after advice-flyspell-check-buffer-on-start activate)
(if (< (buffer-size) 40000)
(flyspell-buffer)))
;; Use an alternative dictionary, if available (see ./SETUP-spell.sh).
;; The "sug-mode" suggested by http://emacswiki.org/emacs/InteractiveSpell
(if (and (executable-find "aspell")
(file-exists-p "~/.emacs.d/aspell-huge"))
(progn
(setq ispell-program-name "aspell")
(setq ispell-extra-args
(list
;; The --local-data-dir option is necessary because of:
;; https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=772415
"--local-data-dir=/usr/lib/aspell"
(concat "--master=" (expand-file-name "~/.emacs.d/aspell-huge"))
" --sug-mode=ultra"))))
;; Pressing Enter should go ahead and indent the new line.
(global-set-key "\C-m" 'newline-and-indent)
;; Dedicated doctest mode.
(add-to-list 'auto-mode-alist '("\\.doctest$" . doctest-mode))
(autoload 'doctest-mode "doctest-mode" nil t)
;; Make the Tab key do something separate from C-i, which I want to keep
;; at its default definition of "indent".
;; (defun set-up-tabbing ()
;; (setq local-function-key-map (delq '(kp-tab . [9]) local-function-key-map))
;; (define-key (current-local-map) (kbd "<tab>") 'dabbrev-completion)
;; (define-key (current-local-map) (kbd "C-i") 'indent-for-tab-command))
;; (add-hook 'css-mode-hook 'set-up-tabbing)
;; (add-hook 'html-mode-hook 'set-up-tabbing)
;; (add-hook 'js-mode-hook 'set-up-tabbing)
;; (add-hook 'sh-mode-hook 'set-up-tabbing)
(defun set-up-tab-for-python ()
(define-key (current-local-map) [tab] 'completion-at-point))
(add-hook 'python-mode-hook 'set-up-tab-for-python)
;; Org mode should display totals in hours, not days-and-hours.
;; http://stackoverflow.com/questions/17929979/
(setq org-time-clocksum-format
'(:hours "%d" :require-hours t :minutes ":%02d" :require-minutes t))
;; Org mode reports should not use \emsp as their indent symbol.
;; http://emacs.stackexchange.com/questions/9528/
(defun my-org-clocktable-indent-string (level)
(if (= level 1)
""
(let ((str ". "))
(while (> level 2)
(setq level (1- level)
str (concat str ". ")))
(concat str "» "))))
(advice-add 'org-clocktable-indent-string
:override #'my-org-clocktable-indent-string)
;; Custom keywords.
(setq org-todo-keywords
'((sequence "TODO" "|" "DONE" "WONT")))
(setq org-todo-keyword-faces
'(("WONT" . "orange")))
;; Magit
;; (defun my-magit-blame ()
;; "Load magit and run its magnificent blame command."
;; (interactive)
;; (require 'magit) ;; only load magit in sessions where I use it
;; (magit-blame))
(defun my-blame ()
(interactive)
(start-process "blame" "*Messages*" ",blame"
(file-name-nondirectory (buffer-file-name))
(number-to-string (line-number-at-pos))))
(global-set-key (kbd "C-x v g") 'my-blame)
;; Colorize the diff that "git commit -v" (alias "git ci") includes in
;; the "COMMIT_EDITMSG" file; and, prevent "M-q" (fill-paragraph) from
;; mixing my text together with the git-generated comment that follows.
(defface commit-plus-face
'((((class color)) :foreground "dark green"))
"Diff lines preceded with a plus.")
(setq commit-highlights
'(("^+.*" . 'commit-plus-face)
("^-.*" . 'error)))
(define-derived-mode commit-mode fundamental-mode "Commit Mode"
(setq-local font-lock-defaults '(commit-highlights))
(setq-local paragraph-start "#")
(setq-local show-trailing-whitespace nil)
(activate-smart-quotes)
(auto-fill-mode))
(add-to-list 'auto-mode-alist '("COMMIT_EDITMSG$" . commit-mode))
;; A few other file extensions.
(add-to-list 'auto-mode-alist '("\\.scss\\'" . css-mode))
(add-to-list 'auto-mode-alist '("\\.mako\\'" . html-mode))
(add-to-list 'auto-mode-alist '("\\.md\\'" . text-mode))
;; Have dired hide irrelevant files by default; this can be toggled
;; interactively with M-o.
(require 'dired-x)
(setq dired-omit-files
(rx (or (seq bol (? ".") "#") ;; emacs autosave files
(seq "~" eol) ;; backup-files
(seq ".pyc" eol)
(seq ".pyo" eol)
)))
(setq dired-omit-extensions
(append dired-latex-unclean-extensions
dired-bibtex-unclean-extensions
dired-texinfo-unclean-extensions))
(add-hook 'dired-mode-hook (lambda () (dired-omit-mode 1)))
(put 'dired-find-alternate-file 'disabled nil)
(setq dired-auto-revert-buffer t)
;; Another dired-x feature: how to jump into dired at the current file,
;; which makes it easy to move or rename it.
(global-set-key (kbd "C-x d") 'dired-jump)
(global-set-key (kbd "C-x C-d") 'dired-jump)
;; Make it easy to jump between files inside a project.
(require 'fzf)
(global-set-key (kbd "C-x C-r") 'fzf-git-files)
;; I sometimes write presentations right in an Emacs buffer, with "^L"
;; separating the slides. By turning on "page-mode", I can move between
;; slides while staying in the same buffer with the "PageUp" and
;; "PageDn" keys.
(autoload 'page-mode "page-mode" nil t)
;; Fix the fact that Emacs misinterprets Shift-Up from an xterm.
;; http://lists.gnu.org/archive/html/help-gnu-emacs/2011-05/msg00211.html
;; I cannot get the above solution to work. A subsequent initialization
;; setup seems to erase my input-decode-map adjustment, even if I run it
;; in term-setup-hook! So I bind the offending key itself to the routine
;; that replaces its definition. I should learn how to also make itself
;; re-invoke the keystroke, but have spent enough time on this problem
;; for the evening.
(defun repair-shift-up ()
(interactive)
(define-key input-decode-map "\e[1;2A" [S-up]))
(global-set-key [select] 'repair-shift-up)
;; Allow for hand-written calls to customization functions; they must
;; live in a separate file, since the Customize sub-system sometimes
;; re-writes the custom-set-* calls here in init.el.
(load "~/.emacs.d/customizations.el")
;; Turn on TypeScript mode for .tsx files.
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-mode))
;; Load any local Emacs directives (such as extra packages, and
;; passwords that should not be stored in version control).
(if (file-exists-p "~/.emacs.d/local.el")
(load "~/.emacs.d/local.el"))
;; It may be obsolete, but it provides a combination of features I need.
(require 'longlines)
;; My own smart-quotes approach, based on Gareth Rees `smart-quotes.el`.
(setq smart-quotes-left-context "^\\|\\s-\\|\\s(\\|[‘“]")
(defun smart-quotes-insert-single (&optional arg)
"Insert U+2018 LEFT SINGLE QUOTATION MARK if point is preceded
by `smart-quotes-left-context'; U+2019 RIGHT SINGLE QUOTATION MARK
otherwise. If point is preceded by a single left or right quote,
insert a straight quote instead."
(interactive "P")
(insert-char
(or (if (or (= (preceding-char) #x2018)
(= (preceding-char) #x2019))
(progn (delete-char -1) #x0027))
(if (looking-back smart-quotes-left-context) #x2018 #x2019))))
(defun smart-quotes-insert-double (&optional arg)
"Insert U+201C LEFT DOUBLE QUOTATION MARK if point is preceded
by `smart-quotes-left-context'; U+201D RIGHT DOUBLE QUOTATION
MARK otherwise. If point is preceded by a double left or right quote,
insert straight double quotes instead."
(interactive "P")
(insert-char
(or (if (or (= (preceding-char) #x201C)
(= (preceding-char) #x201D))
(progn (delete-char -1) #x0022))
(if (looking-back smart-quotes-left-context) #x201C #x201D))))
(defun activate-smart-quotes ()
(interactive)
(local-set-key "'" 'smart-quotes-insert-single)
(local-set-key "\"" 'smart-quotes-insert-double))
(add-hook 'text-mode-hook 'activate-smart-quotes)
;; Prevent Emacs from constantly creating and deleting ".#filename"
;; symlinks (requires Emacs 24.3, which is not yet the Ubuntu default).
(setq create-lockfiles nil)
;; Format Go on each save.
;; TODO: for the moment I no longer install "go-mode". The next time I
;; need to develop in Go, I should try go-ts-mode, and figure out how to
;; get go-fmt hooked back up somehow as a save hook.
;;(add-hook 'before-save-hook #'gofmt-before-save)
;; Bind "recompile" to F5 and jump to the first error. If compilation
;; is producing paths relative to a directory, also set this locally:
;; (add-to-list 'compilation-search-path "/home/brhodes/livegrep")
(global-set-key (kbd "<f5>") 'recompile)
(setq compilation-auto-jump-to-first-error t)
(setq compilation-scroll-output 'first-error)
;; Hide the build buffer again immediately if it succeeds.
(add-hook 'compilation-finish-functions
(lambda (buf str)
(if (not (string-match-p ".*exited abnormally.*" str))
(progn
(delete-windows-on
(get-buffer-create "*compilation*"))
(message "Success")))))
;; Make it easy for me to edit projects that use Black.
;; (require 'blacken)
(defun black (&optional arg)
"Turn on blacken-mode in all Python buffers"
(interactive "P")
(add-hook 'python-mode-hook 'blacken-mode)
)
;; When selecting a file from dired, I'm usually there to just read, so
;; "view" mode is far more convenient (it's like less(1): the spacebar
;; pages down instead of adding a space to the file, et cetera). I can
;; press "e" to start editing if I really mean to modify the file.
;;(define-key dired-mode-map (kbd "RET") 'dired-view-file)
;; Set the executable bit automatically. This is going to save me SO
;; MUCH TIME!
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)
(setq js-indent-level 4)
(setq git-commit-filename-regexp "regex-that-never-matches-anything")
;; With much thanks to: https://www.emacswiki.org/emacs/RenumberList
(defun renumber-list (start end &optional num)
"Renumber the list items in the current START..END region.
If optional prefix arg NUM is given, start numbering from that number
instead of 1."
(interactive "*r\np")
(save-excursion
(goto-char start)
(setq num (or num 1))
(save-match-data
(while (re-search-forward "^[0-9]+" end t)
(replace-match (number-to-string num))
(setq num (1+ num))))))
;; Make it easy to delete all trailing whitespace. Based on M-\ key,
;; which is already by default bound to delete-horizontal-space.
(global-set-key (kbd "C-M-\\") 'delete-trailing-whitespace)
;; Prevent Emacs from scrolling the buffer in Org Mode when I press Tab
;; to cycle an item between hidden and visible.
;; https://emacs.stackexchange.com/questions/31276/
(remove-hook 'org-cycle-hook
#'org-optimize-window-after-visibility-change)
;; Auto-detect C identation per-file (useful when working maintanence
;; work in the fairly heterogeneous XEphem and PyEphem code bases).
(require 'dtrt-indent)
(dtrt-indent-global-mode)
;; Inline evaluation of math expressions, without my having to fill my
;; text files with the Emacs Lisp math syntax, of which I tire (and
;; which is also less powerful math-wise than the "units" tool).
(defun evaluate-region-using-units (start end)
"Evaluate the region as a 'units' expression and insert the result."
(interactive "r")
(let* ((region (buffer-substring start end))
;; Allow thousands commas in input, without "units" throwing an error.
(region (replace-regexp-in-string "," "" region))
;; Allow dollar signs (usually copy and pasted from financials).
(region (replace-regexp-in-string "\\$" "" region))
(command (concat "units -t '(" region ")'"))
(output-plus-newline (shell-command-to-string command))
(output (substring output-plus-newline 0 -1)))
(if (< (point) end) (exchange-point-and-mark))
(insert " = " output)))
(global-set-key (kbd "C-=") 'evaluate-region-using-units)
;; I noticed while writing my PyTexas talk that I do this a lot, so
;; let's make it an Emacs macro.
(defun comment-line-and-duplicate ()
"Comment out the current line and duplicate it on the next line."
(interactive)
(beginning-of-line)
(let ((b (point)))
(end-of-line)
(copy-region-as-kill b (point)))
(back-to-indentation)
(insert "#")
(end-of-line)
(insert "\n")
(yank)
(current-kill 1)
(back-to-indentation))
(global-set-key (kbd "M-#") 'comment-line-and-duplicate)
;; https://emacs.stackexchange.com/questions/54979/
(define-key global-map (kbd "C-x k")
(lambda () (interactive) (kill-buffer (current-buffer))))
;; Experiment with the built-in completion techniques before trying
;; something external like "orderless".
(setq completion-styles '(basic substring partial-completion flex)
read-file-name-completion-ignore-case t
read-buffer-completion-ignore-case t
completion-ignore-case t)
;; See the Vertico README.
(use-package vertico
:init
(vertico-mode))
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
(setq read-extended-command-predicate #'command-completion-default-include-p)
;; Adapted from the example configuration from the Consult README.
(use-package consult
;; Replace bindings. Lazily loaded due by `use-package'.
:bind (;; C-c bindings in `mode-specific-map'
("C-c M-x" . consult-mode-command)
("C-c h" . consult-history)
("C-c k" . consult-kmacro)
("C-c m" . consult-man)
("C-c i" . consult-info)
([remap Info-search] . consult-info)
;; C-x bindings in `ctl-x-map'
("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
("C-x b" . consult-buffer) ;; orig. switch-to-buffer
("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame
("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab
("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
;; Custom M-# bindings for fast register access
("M-#" . consult-register-load)
("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
("C-M-#" . consult-register)
;; Other custom bindings
("M-y" . consult-yank-pop) ;; orig. yank-pop
;; M-g bindings in `goto-map'
("M-g e" . consult-compile-error)
("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
("M-g g" . consult-goto-line) ;; orig. goto-line
("M-g M-g" . consult-goto-line) ;; orig. goto-line
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
("M-g m" . consult-mark)
("M-g k" . consult-global-mark)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)
;; M-s bindings in `search-map'
("M-s d" . consult-find) ;; Alternative: consult-fd
("M-s c" . consult-locate)
("M-s g" . consult-grep)
("M-s G" . consult-git-grep)
("M-s r" . consult-ripgrep)
("M-s l" . consult-line)
("M-s L" . consult-line-multi)
("M-s k" . consult-keep-lines)
("M-s u" . consult-focus-lines)
;; Isearch integration
("M-s e" . consult-isearch-history)
:map isearch-mode-map
("M-e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s l" . consult-line) ;; needed by consult-line to detect isearch
("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch
;; Minibuffer history
:map minibuffer-local-map
("M-s" . consult-history) ;; orig. next-matching-history-element
("M-r" . consult-history)) ;; orig. previous-matching-history-element
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
:hook (completion-list-mode . consult-preview-at-point-mode)
;; The :init configuration is always executed (Not lazy)
:init
;; Optionally configure the register formatting. This improves the register
;; preview for `consult-register', `consult-register-load',
;; `consult-register-store' and the Emacs built-ins.
(setq register-preview-delay 0.5
register-preview-function #'consult-register-format)
;; Optionally tweak the register preview window.
;; This adds thin lines, sorting and hides the mode line of the window.
(advice-add #'register-preview :override #'consult-register-window)
;; Configure other variables and modes in the :config section,
;; after lazily loading the package.
:config
;; Optionally configure preview. The default value
;; is 'any, such that any key triggers the preview.
;; (setq consult-preview-key 'any)
;; (setq consult-preview-key "M-.")
;; (setq consult-preview-key '("S-<down>" "S-<up>"))
;; For some commands and buffer sources it is useful to configure the
;; :preview-key on a per-command basis using the `consult-customize' macro.
(consult-customize
consult-theme :preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep
consult-bookmark consult-recent-file ;;consult-xref
consult--source-bookmark consult--source-file-register
consult--source-recent-file consult--source-project-recent-file
;; :preview-key "M-."
:preview-key '(:debounce 0.4 any))
;; Optionally configure the narrowing key.
;; Both < and C-+ work reasonably well.
(setq consult-narrow-key "<") ;; "C-+"
;; Optionally make narrowing help available in the minibuffer.
;; You may want to use `embark-prefix-help-command' or which-key instead.
;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help)
;; By default `consult-project-function' uses `project-root' from project.el.
;; Optionally configure a different project root function.
;;;; 1. project.el (the default)
;; (setq consult-project-function #'consult--default-project--function)
;;;; 2. vc.el (vc-root-dir)
;; (setq consult-project-function (lambda (_) (vc-root-dir)))
;;;; 3. locate-dominating-file
(setq consult-project-function
(lambda (_) (locate-dominating-file "." ".git")))
;;;; 4. projectile.el (projectile-project-root)
;; (autoload 'projectile-project-root "projectile")
;; (setq consult-project-function (lambda (_) (projectile-project-root)))
;;;; 5. No project support
;; (setq consult-project-function nil)
)
;; Use `consult-completion-in-region' if Vertico is enabled.
;; Otherwise use the default `completion--in-region' function.
(setq completion-in-region-function
(lambda (&rest args)
(apply (if vertico-mode
#'consult-completion-in-region
#'completion--in-region)
args)))
;; A random setting that I'm trying out: leave highlights sitting around
;; when a search is done, as they provide a bit of visual context about
;; how I 'got here' in the code. I used to find it intolerable to have
;; dangling highlighting, so I might have become less rigid over time.
(setq lazy-highlight-cleanup nil)
;; Don't create `~/.emacs.d/auto-save-list/`.
(setq auto-save-list-file-prefix nil)
;; I can never remember that `l` is the Back button in a *Help* buffer,
;; so this lets me instead use M-. and M-, to visit a link and then pop
;; back out to where I was reading.
(define-key help-mode-map (kbd "M-.") 'push-button)
(define-key help-mode-map (kbd "M-,") 'help-go-back)
;; Uncomment this line to receive a traceback on error:
;;(setq debug-on-error t)