-
Notifications
You must be signed in to change notification settings - Fork 0
/
init.el
1278 lines (1047 loc) · 47.2 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
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
;; Largely "inspired" by philc's .emacs: https://github.com/philc/emacs-config/blob/master/.emacs
;;
;; Package management
;;
(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/"))
(package-initialize)
(when (not package-archive-contents)
(package-refresh-contents))
(defvar my-packages '(
ace-link
ag
amx ; A fork of smex, which upgrades M-x
auto-complete
avy
browse-at-remote
buffer-move
cider
clojure-mode
color-theme-sanityinc-tomorrow
company
counsel
dash
dash-functional
diminish
doom-modeline
dumb-jump ; Go-to-definition for all languages, using ag
editorconfig ; Dependency of copilot
elisp-slime-nav
evil
evil-anzu
evil-args
evil-exchange
evil-leader
evil-matchit
evil-nerd-commenter
evil-numbers
evil-surround
evil-visualstar
;framemove ;; TODO(harry) No longer available on melpa?
go-mode
goto-last-change
haskell-mode
htmlize
ivy
less-css-mode
lua-mode
magit
markdown-mode
mustache-mode
noflet ; Replacement for the deprecated flet macro - see
; http://emacsredux.com/blog/2013/09/05/a-proper-replacement-for-flet/
org
org-download
org-mac-link
paradox ; Better package menu
projectile ; This is only used by counsel, which uses it to find the repo root directory
protobuf-mode
quelpa
quelpa-use-package
rainbow-delimiters
ruby-electric ; Insert matching delimiters; unindent end blocks after you type them.
scss-mode
smartparens
swiper
typescript-mode
undo-tree
wcheck-mode
web-mode
which-key
yaml-mode
))
(add-to-list 'package-pinned-packages '(cider . "melpa-stable") t)
(dolist (p my-packages)
(when (not (package-installed-p p))
(package-install p)))
(require 'use-package)
(require 'quelpa-use-package)
;;
;; General settings
;;
(require 'cl)
(add-to-list 'load-path "~/.emacs.d/elisp")
(require 'emacs-utils)
;; Turn off graphical toolbars.
(if (display-graphic-p) (menu-bar-mode 1) (menu-bar-mode -1))
(when (and (fboundp 'tool-bar-mode) tool-bar-mode) (tool-bar-mode -1))
(setq initial-scratch-message "") ; When opening a new buffer, don't show the scratch message.
;; Make it so that the scratch buffer uses markdown. By default it uses Emacs Lisp mode.
(setq initial-major-mode 'markdown-mode)
;; Sync environment variables.
;; NOTE(harry) On OSX, run the commands from https://gist.github.com/mcandre/7235205 to properly set the PATH
;; when launching from Spotlight, LaunchBar, etc.
(defun sync-env ()
(when (memq window-system '(mac ns x))
(let ((env-pair-re "^\\([^=[:space:]]+\\)=\\(.*\\)$"))
(with-temp-buffer
(shell-command (concat shell-file-name " -i -c env") t)
(goto-char (point-min))
(while (re-search-forward env-pair-re nil t)
(let ((name (match-string 1))
(val (match-string 2)))
(setenv name val)
(when (string-equal "PATH" name)
(setq eshell-path-env val
exec-path (append (parse-colon-path val) (list exec-directory))))))))))
(sync-env)
(global-auto-revert-mode t) ; Reload an open file from disk if it is changed outside of Emacs.
(setq inhibit-startup-message t)
(setq inhibit-startup-echo-area-message t)
(setq ring-bell-function 'ignore)
(setq mac-option-modifier 'alt)
(setq mac-command-modifier 'meta)
(add-to-list 'default-frame-alist '(ns-appearance . dark))
;; Require typing only "y" or "n" instead of the full "yes" to confirm destructive actions.
(defalias 'yes-or-no-p 'y-or-n-p)
(setq make-backup-files nil)
(setq auto-save-default nil)
(setq vc-follow-symlinks t) ; Don't ask confirmation to follow symlinks to edit files.
(savehist-mode t) ; Save your minibuffer history across Emacs sessions. UX win!
;; Include path information in duplicate buffer names (e.g. a/foo.txt b/foo.txt)
(setq uniquify-buffer-name-style 'forward)
;; Start scrolling the window when the cursor reaches its edge.
;; http://stackoverflow.com/questions/3631220/fix-to-get-smooth-scrolling-in-emacs
(setq scroll-margin 7
scroll-step 1
scroll-conservatively 10000
scroll-preserve-screen-position 1
;; Make touchpad scrolling on OSX less jerky
mouse-wheel-scroll-amount '(0.01))
;; The preference file for Emac's "Customize" system. `M-x customize` to access it.
(setq custom-file (expand-file-name "~/.emacs.d/custom.el"))
(load custom-file t)
;; Colorscheme
(load-theme 'sanityinc-tomorrow-bright t)
(set-face-attribute 'default nil :family "Consolas" :height 150)
;; Whitespace & line wrapping.
(global-whitespace-mode t)
(eval-after-load 'whitespace
'(progn
;; (setq whitespace-line-column 110) ; When text flows past 110 chars, highlight it.
; whitespace mode by default marks all whitespace. Show only tabs, trailing space, and trailing lines.
(setq whitespace-style '(face empty trailing tabs))))
(add-hook 'before-explicit-save-hook 'delete-trailing-whitespace)
(setq-default tab-width 2)
(setq-default evil-shift-width 2)
; Some modes have their own tab-width variables.
(setq-default css-indent-offset 2)
(setq-default fill-column 80)
(add-hook 'prog-mode-hook 'display-fill-column-indicator-mode)
(add-hook 'gfm-mode-hook 'display-fill-column-indicator-mode)
;; Visually wrap long lines on word boundaries. By default, Emacs will wrap mid-word.
(global-visual-line-mode t)
;; Highlight the line the cursor is on. This is mostly to make it easier to tell which split is active.
(global-hl-line-mode t)
;; Don't use tabs by default. Modes that really need tabs should enable indent-tabs-mode explicitly.
;; Makefile-mode already does that, for example. If indent-tabs-mode is off, untabify before saving.
(setq-default indent-tabs-mode nil)
(add-hook 'write-file-hooks
(lambda ()
(if (not indent-tabs-mode)
(untabify (point-min) (point-max)))
nil))
(defun backward-delete-word ()
"Deletes the word behind the cursor, and does not yank the contents to the clipboard."
; This implementation is based on backward-kill-word.
(interactive)
(delete-region (point) (progn (forward-word -1) (point))))
;; Enable the common Bash text-editing shortcuts in the minibuffer.
(util/define-keys minibuffer-local-map
(kbd "C-k") 'kill-line
(kbd "C-e") 'end-of-line
(kbd "C-d") 'delete-char
(kbd "C-w") 'backward-delete-word
(kbd "C-h") 'backward-delete-char)
;; Disable the prompt we get when killing a buffer with a process. This affects clojure mode in particular,
;; when we want to restart the nrepl process.
(setq kill-buffer-query-functions (remq 'process-kill-buffer-query-function kill-buffer-query-functions))
;; Use amx to show the M-x command prompt. It has better completion support than the default M-x.
(require 'amx)
(amx-mode 1)
;; RecentF mode is the Emacs minor mode used when opening files via C-x C-f.
(require 'recentf)
(define-key recentf-mode-map (kbd "C-w") 'backward-delete-word)
(define-key recentf-mode-map (kbd "C-h") 'backward-delete-char)
;; The poorly-named winner mode saves the history of your window splits, so you can undo and redo changes to
;; your window configuration.
(winner-mode t)
;; Save buffers whenever they lose focus.
;; This obviates the need to hit the Save key thousands of times a day. Inspired by http://goo.gl/2z0g5O.
(add-hook 'focus-out-hook 'util/save-buffer-if-dirty) ; This hook is only available in Emacs 24.4+.
(defadvice windmove-up (before other-window-now activate) (util/save-buffer-if-dirty))
(defadvice windmove-down (before other-window-now activate) (util/save-buffer-if-dirty))
(defadvice windmove-left (before other-window-now activate) (util/save-buffer-if-dirty))
(defadvice windmove-right (before other-window-now activate) (util/save-buffer-if-dirty))
; This is fired whenever the buffer list is updated, which is a reasonably robust way to detect that the
; window config has changed and the current buffer should be saved.
(add-hook 'buffer-list-update-hook 'util/save-buffer-if-dirty)
(setq create-lockfiles nil)
(setq eldoc-echo-area-use-multiline-p nil)
;;
;; Evil mode -- Vim keybindings for Emacs.
;;
(setq evil-want-C-u-scroll t)
(setq evil-undo-system 'undo-tree)
(require 'evil)
(require 'evil-nerd-commenter)
(require 'goto-last-change)
(require 'evil-leader) ; Provide configuration functions for assigning actions to a Vim leader key.
(setq evil-leader/leader "SPC")
;; Access leader with C-SPC in insert mode:
(setq evil-leader/in-all-states t)
;; Ensure evil-leader works in non-editing modes like magit. This is referenced from evil-leader's README.
(setq evil-leader/no-prefix-mode-rx '("magit-.*-mode"))
(global-evil-leader-mode)
(evil-mode t)
(require 'which-key)
(which-key-mode)
(setq which-key-allow-evil-operators t)
(setq which-key-show-operator-state-maps t)
;; When opening new lines, indent according to the previous line.
(setq evil-auto-indent t)
;; Move up and down through long, wrapped lines one visual line at a time.
(define-key evil-normal-state-map (kbd "j") 'evil-next-visual-line)
(define-key evil-normal-state-map (kbd "k") 'evil-previous-visual-line)
(define-key evil-normal-state-map (kbd "K") 'info-lookup-symbol)
;; I use this shortcut for manually splitting lines. Note that it does not put you in insert mode.
(define-key evil-normal-state-map (kbd "s") 'newline-and-indent)
;; When jumping back and forth between marks, recenter the screen on the cursor.
(define-key evil-normal-state-map (kbd "C-o")
(lambda () (interactive) (evil-jump-backward) (recenter-no-redraw)))
(define-key evil-normal-state-map (kbd "C-i")
(lambda () (interactive) (evil-jump-forward) (recenter-no-redraw)))
;; Evil uses the current file's mode's definition of a paragraph, which is often surprising. For instance, in
;; Markdown mode, a single item in a bullet list consistutes a paragraph. Instead, I've defined a paragraph to
;; be hunks of text separated by newlines. That's typically what I would expect of a paragraph. You can still
;; use Evil's paragraph definition using the text object "P" instead of "p".
(evil-define-text-object evil-paragraph-from-newlines (count &optional beg end type)
"Select a paragraph separated by newlines."
:type line
;; These two vars are set by the current programming mode. Set them to their default text mode values
;; temporarily while we select the paragraph. The implementation of evil-move-paragraph invokes
;; `forward-paragraph`, which uses these variables.
(let ((paragraph-start "\f\\|[ ]*$")
(paragraph-separate "[ ]*$"))
;; TODO(harry) Change to the following after upgrading evil:
;; (evil-select-an-object 'evil-paragraph beg end type count)))
(evil-an-object-range count beg end type #'evil-move-paragraph nil nil t)))
(define-key evil-outer-text-objects-map "p" 'evil-paragraph-from-newlines)
(define-key evil-outer-text-objects-map "P" 'evil-a-paragraph)
(evil-leader/set-key
"h" 'help
"SPC" 'amx
":" 'eval-expression
";" 'eval-expression
"b" 'ivy-switch-buffer
"t" 'counsel-fzf
"a" 'counsel-rg
"/" 'swiper
"u" 'universal-argument
"\\" (lambda () (interactive)
(split-window-horizontally)
(other-window 1)
(balance-windows))
"-" (lambda () (interactive)
(split-window-vertically)
(other-window 1)
(balance-windows))
"gs" (lambda() (interactive)
(util/save-buffer-if-dirty)
(magit-status-and-focus-unstaged))
"gl" 'magit-log-current
;; "v" is a mnemonic prefix for "view X".
"ve" (lambda () (interactive) (find-file "~/.emacs.d/init.el"))
"vh" (lambda () (interactive) (find-file "~/workspace/src/liftoff/haggler/src/haggler/handler.clj"))
"vk" (lambda () (interactive) (find-file "~/workspace/side_projects/qmk_firmware/keyboards/ergodox/keymaps/dvorak_harob/keymap.c"))
"vi" (lambda () (interactive) (find-file "~/Dropbox/notes/inbox.org") (org-mode))
"vs" (lambda () (interactive) (find-file "~/Dropbox/notes/scratch.org") (org-mode))
"vt" (lambda () (interactive) (find-file "~/Dropbox/notes/tasks.org") (org-mode))
"vz" (lambda () (interactive) (find-file "~/dotfiles/.zshrc")))
;; TODO(harry) Write a macro to prepend the evil-leader key instead of manually specifying SPC.
(which-key-add-key-based-replacements
"SPC c" "Comment"
"SPC e" "Evaluate"
"SPC g" "Git"
"SPC i" "Insert"
"SPC r" "Render"
"SPC v" "View"
"SPC w" "Window")
(eval-after-load 'evil
'(progn
(setq evil-default-cursor t)
;; Unbind these keys in evil so they can instead be used for code navigation.
(define-key evil-normal-state-map (kbd "M-,") nil)
(define-key evil-normal-state-map (kbd "M-.") nil)))
;; Enable the typical Bash/readline keybindings when in insert mode.
(util/define-keys evil-insert-state-map
(kbd "C-h") 'backward-delete-char
(kbd "C-k") 'kill-line
(kbd "C-a") 'beginning-of-line
(kbd "C-e") 'end-of-line
(kbd "C-d") 'delete-char
(kbd "C-w") 'backward-delete-word
(kbd "C-p") 'previous-line
(kbd "C-n") 'next-line)
(eval-after-load 'evil
'(progn
(define-key evil-normal-state-map " cc" 'evilnc-comment-or-uncomment-lines)
(define-key evil-visual-state-map " cc" 'evilnc-comment-operator)))
(require 'evil-surround)
(evil-define-key 'visual evil-surround-mode-map "s" 'evil-surround-region)
(evil-define-key 'visual evil-surround-mode-map "gs" 'evil-Surround-region)
(define-global-minor-mode global-surround-mode-with-exclusions global-evil-surround-mode
(lambda ()
(when (not (memq major-mode (list 'magit-status-mode)))
(evil-surround-mode 1))))
(global-surround-mode-with-exclusions 1)
(require 'evil-visualstar)
(global-evil-visualstar-mode)
(require 'evil-matchit)
(global-evil-matchit-mode 1)
(require 'evil-args)
(define-key evil-inner-text-objects-map "a" 'evil-inner-arg)
(define-key evil-outer-text-objects-map "a" 'evil-outer-arg)
;; gx to swap two text objects:
(require 'evil-exchange)
(evil-exchange-install)
(require 'evil-numbers)
(define-key evil-normal-state-map (kbd "C-a") 'evil-numbers/inc-at-pt)
(define-key evil-normal-state-map (kbd "C-S-a") 'evil-numbers/dec-at-pt)
;;
;; Window manipulation, switching, & management.
;;
;; Settings for window splits.
(setq split-height-threshold 40)
(setq split-width-threshold 200)
(setq split-window-preferred-function 'split-window-sensibly-reverse)
;; Ensure these open in the selected window, not a new popup.
(setq same-window-buffer-names '("*magit-log*"))
;; "I manage my windows in a 4x4 grid. I want ephemeral or status-based buffers to always show in the
;; lower-right or right window, in that order of preference."
(setq special-display-buffer-names '("*Help*" "*compilation*" "COMMIT_EDITMSG" "*Messages*"
"*magit-process*" "*magit-commit*" "*Compile-Log*" "*Gofmt Errors*"))
(setq special-display-regexps '("*cider.*" "*ag.*" "magit: .**"))
(setq special-display-function 'show-ephemeral-buffer-in-a-sensible-window)
;; A list of "special" (ephemeral) buffer names which should be focused after they are shown. Used by
;; show-ephemeral-buffer-in-a-sensible-window
(setq special-display-auto-focused-buffers '())
(defun switch-to-upper-left () (interactive) (select-window (frame-first-window)))
(defun switch-to-lower-left () (interactive) (switch-to-upper-left) (ignore-errors (windmove-down)))
(defun switch-to-upper-right () (interactive) (switch-to-upper-left) (ignore-errors (windmove-right 1)))
(defun switch-to-lower-right () (interactive) (switch-to-upper-right) (ignore-errors (windmove-down)))
;; References, for context:
;; http://snarfed.org/emacs_special-display-function_prefer-other-visible-frame
;; http://stackoverflow.com/questions/1002091/how-to-force-emacs-not-to-display-buffer-in-a-specific-window
;; The implementation of this function is based on `special-display-popup-frame` in window.el.
(defun show-ephemeral-buffer-in-a-sensible-window (buffer &optional buffer-data)
"Given a buffer, shows the window in a right-side split."
(let* ((original-window (selected-window))
(create-new-window (one-window-p))
(window (if create-new-window
(split-window-sensibly-reverse)
(save-excursion (switch-to-lower-right) (selected-window)))))
(display-buffer-record-window (if create-new-window 'window 'reuse) window buffer)
(set-window-buffer window buffer)
(when create-new-window (set-window-prev-buffers window nil))
(select-window original-window)
(when (member (buffer-name buffer) special-display-auto-focused-buffers)
(select-window window))
window))
(defun dismiss-ephemeral-windows ()
"Dismisses any visible windows in the current frame identifiedy by `special-display-buffer-names` and
`special-display-regexps`. I use this to quickly dismiss help windows, compile output, etc."
(interactive)
(save-excursion
(let ((original-window (selected-window)))
(dolist (window (window-list))
(let ((buffer (window-buffer window)))
(when (special-display-p (buffer-name buffer))
(quit-window nil window))))
(select-window original-window))))
(defun split-window-sensibly-reverse (&optional window)
"Identical to the built-in function split-window-sensibly, but prefers horizontal splits over vertical splits."
(let ((window (or window (selected-window))))
(or (and (window-splittable-p window t)
;; Split window horizontally.
(with-selected-window window
(split-window-right)))
(and (window-splittable-p window)
;; Split window vertically.(column-marker-1 80)
(with-selected-window window
(split-window-below)))
(and (eq window (frame-root-window (window-frame window)))
(not (window-minibuffer-p window))
;; If WINDOW is the only window on its frame and is not the
;; minibuffer window, try to split it vertically disregarding
;; the value of `split-height-threshold'.
(let ((split-height-threshold 0))
(when (window-splittable-p window)
(with-selected-window window
(split-window-below))))))))
;; Evil's window map is the set of keys which control window functions. All of its keys are prefixed with
;; <C-w>.
;; Undo the last change you made to your window configuration. Very handy as a method for temporarily
;; maximizing a window: first invoke delete-other-windows, and then invoke winner-undo..
(define-key evil-window-map (kbd "m") 'delete-other-windows)
(define-key evil-window-map (kbd "b") 'winner-undo)
(define-key evil-window-map (kbd "q") 'dismiss-ephemeral-windows)
;; Make it so Esc means quit, no matter the context.
;; http://stackoverflow.com/a/10166400/46237
;; Note that when Emacs becomes unresponsive (e.g. because I accidentally grepped my home directory), I might
;; still need to hold C-g (the Emacs esc/cancel key) to bring it back.
(defun minibuffer-keyboard-quit ()
"Abort recursive edit. In Delete Selection mode, if the mark is active, just deactivate it;
then it takes a second \\[keyboard-quit] to abort the minibuffer."
(interactive)
(if (and delete-selection-mode transient-mark-mode mark-active)
(setq deactivate-mark t)
(when (get-buffer "*Completions*") (delete-windows-on "*Completions*"))
(abort-recursive-edit)))
(define-key evil-normal-state-map [escape] 'keyboard-quit)
(define-key evil-visual-state-map [escape] 'keyboard-quit)
(define-key minibuffer-local-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-ns-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-completion-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-must-match-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-isearch-map [escape] 'minibuffer-keyboard-quit)
(global-set-key [escape] 'evil-exit-emacs-state)
;;
;; Incremental search (isearch)
;;
;; Make highlighting during incremental search feel snappier.
(setq case-fold-search t) ; Make searches case insensitive.
(setq lazy-highlight-initial-delay 0)
(setq lazy-highlight-cleanup nil)
(setq lazy-highlight-max-at-a-time nil)
;; Hitting escape aborts the search, restoring your cursor to the original position, as it does in Vim.
(define-key isearch-mode-map (kbd "<escape>") 'isearch-abort)
;; Make C-h act the same as backspace.
(define-key isearch-mode-map (kbd "C-h") 'isearch-delete-char)
;; Make M-v paste the clipboard's text into the search ring.
(define-key isearch-mode-map (kbd "M-v") 'isearch-yank-kill)
(define-key isearch-mode-map (kbd "C-w") 'isearch-del-word)
(defun trim-last-word-of-string (string)
"Removes the last word from the given string. Word separators are -, _ and spaces. This is designed to
perform the same function as kill-word, but on a string argument."
(lexical-let ((i 0))
(while (and (< i (length string))
(string-match "[-_ ]+" string i))
(setq i (second (match-data))))
(if (= i 0)
""
(substring string 0 (dec i)))))
(defun isearch-del-word (&optional arg)
"Delete word from end of search string and search again. If search string is empty, just beep.
This function definition is based on isearch-del-char, from isearch.el."
(interactive "p")
(if (= 0 (length isearch-string))
(ding)
(setq isearch-string (trim-last-word-of-string isearch-string)
isearch-message (mapconcat 'isearch-text-char-description
isearch-string "")))
;; Use the isearch-other-end as new starting point to be able
;; to find the remaining part of the search string again.
(when isearch-other-end (goto-char isearch-other-end))
(isearch-search)
(isearch-push-state)
(isearch-update))
;; When pressing enter to confirm a search, or jumping to the next result, scroll the result to the center of
;; the window. This solves the UX problem of the result appearing at the bottom of the screen, with little
;; context.
;; (defadvice evil-search-next (after isearch-recenter activate) (recenter-no-redraw))
;; (defadvice evil-search-previous (after isearch-recenter activate) (recenter-no-redraw))
;; (defadvice isearch-exit (before isearch-recenter activate) (recenter-no-redraw))
;; Taken from https://groups.google.com/forum/#!topic/gnu.emacs.help/vASrP0P-tXM
(defun recenter-no-redraw (&optional arg)
"Centers the viewport around the cursor."
(interactive "P")
(let ((recenter-redisplay nil))
(recenter arg)))
;;
;; Mac OS X keybindings minor mode.
;; Make it so the OSX keybindings you're used to always work in every mode in Emacs.
;; http://stackoverflow.com/questions/683425/globally-override-key-binding-in-emacs
;;
(defvar osx-keys-minor-mode-map (make-keymap) "osx-keys-minor-mode-keymap")
(util/define-keys osx-keys-minor-mode-map
(kbd "M-`") 'other-frame
(kbd "M-~") '(lambda () (interactive) (other-frame -1))
(kbd "M-w") 'vimlike-quit
(kbd "M-q") 'save-buffers-kill-terminal
(kbd "M-n") 'new-frame
(kbd "M-a") 'mark-whole-buffer
(kbd "M-s") 'explicitly-save-buffer
(kbd "M-v") 'clipboard-yank
(kbd "M-c") 'clipboard-kill-ring-save
(kbd "M-W") 'evil-quit ; Close all tabs in the current frame..
(kbd "M-A-h") 'mac-hide-others)
(define-minor-mode osx-keys-minor-mode
"A minor-mode for emulating osx keyboard shortcuts."
t " osx" osx-keys-minor-mode-map)
(osx-keys-minor-mode t)
(defadvice load (after give-osx-keybindings-priority activate)
"Try to ensure that osx keybindings always have priority."
(if (not (eq (car (car minor-mode-map-alist)) 'osx-keys-minor-mode))
(let ((osx-keys (assq 'osx-keys-minor-mode minor-mode-map-alist)))
(assq-delete-all 'osx-keys-minor-mode minor-mode-map-alist)
(add-to-list 'minor-mode-map-alist osx-keys))))
(ad-activate 'load)
(defun open-folder-in-finder ()
"Opens the folder of the current file in OSX's Finder."
(interactive)
(call-process-region nil nil "/usr/bin/open" nil nil nil "."))
(defun vimlike-quit ()
"Closes the current window, tab, or if there's only one tab, use the `:q` Evil
command. This simulates the `:q` behavior of Vim when used with tabs."
(interactive)
(let ((one-tab (= 1 (length (tab-bar-tabs))))
(one-window (one-window-p)))
(cond
; if current tab has split windows in it, close the current live window
((not one-window)
(delete-window) ; delete the current window
(balance-windows) ; balance remaining windows
)
; if there are multiple tabs, close the current tabs
((not one-tab)
(tab-close))
(one-tab
(evil-quit)))))
(defvar before-explicit-save-hook nil)
(defvar after-explicit-save-hook nil)
(defun explicitly-save-buffer ()
(interactive)
(run-hooks 'before-explicit-save-hook)
(save-buffer)
(run-hooks 'after-explicit-save-hook))
;; From http://emacs.stackexchange.com/a/18981
(defun mac-hide-others ()
"On a Mac, hide all applications other than Emacs."
(interactive)
(do-applescript (concat "tell application \"System Events\" to "
"set visible of every process whose visible is true "
"and name is not \"Emacs\" to "
"false")))
;;
;; Filename completions (i.e. CTRL-P or CMD-T in other editors)
;;
(require 'ivy)
(ivy-mode 1)
(setq ivy-use-virtual-buffers t)
(setq enable-recursive-minibuffers t)
(setq ivy-height 20)
(setq ivy-wrap t)
(setq ivy-re-builders-alist '((t . ivy--regex-ignore-order)))
(setq ivy-use-selectable-prompt t)
;; TODO(harry) Remove some of the default "^"'s in this var:
;; (setq ivy-initial-inputs-alist )
(define-key ivy-mode-map (kbd "C-h") 'backward-delete-char)
(define-key ivy-mode-map (kbd "C-w") 'backward-delete-word)
(require 'swiper)
(define-key ivy-minibuffer-map [escape] 'minibuffer-keyboard-quit)
(define-key swiper-map [escape] 'minibuffer-keyboard-quit)
(define-key swiper-map (kbd "C-h") 'backward-delete-char)
(define-key swiper-map (kbd "C-w") 'backward-delete-word)
(setq counsel-fzf-cmd "fzf --exact --filter=\"%s\"")
;;
;; Dired mode - using the Emacs file browser.
;;
(setq dired-recursive-copies (quote always))
(setq dired-recursive-deletes (quote top))
(put 'dired-find-alternate-file 'disabled nil) ; By default, the dired-find-alternative-file fn is disabled.
;; "go to dired, then call split-window-vertically, then go to another dired dir. Now, when you press C to
;; copy, the other dir in the split pane will be default destination. Same for R (rename; move)."
(setq dired-dwim-target t)
;; Use the same buffer for going into and up directories.
(evil-define-key 'normal dired-mode-map (kbd "gu") (lambda () (interactive) (find-alternate-file "..")))
(evil-define-key 'normal dired-mode-map "H" (lambda () (interactive) (find-alternate-file "..")))
(evil-define-key 'normal dired-mode-map (kbd "<return>")
'dired-find-alternate-file) ; was dired-advertised-find-file
(evil-define-key 'normal dired-mode-map "," nil) ; Ensure my evil-leader key works unhindered.
(evil-define-key 'normal dired-mode-map "cd" 'dired-create-directory)
(evil-define-key 'normal dired-mode-map "cf" 'dired-create-file)
(evil-define-key 'normal dired-mode-map "x" 'dired-mark)
(evil-define-key 'normal dired-mode-map "v" 'dired-details-toggle)
;; The "e" prefix is for execute.
(evil-define-key 'normal dired-mode-map "ed" 'dired-do-flagged-delete)
(evil-define-key 'normal dired-mode-map "em" 'dired-do-rename)
;; Taken from http://stackoverflow.com/a/18885461/46237.
(defun dired-create-file (file)
"Create a file called FILE, and recursively create any parent directories.
If FILE already exists, signal an error."
(interactive
(list (read-file-name "Create file: " (dired-current-directory))))
(let* ((expanded (expand-file-name file))
(try expanded)
(dir (directory-file-name (file-name-directory expanded)))
new)
(if (file-exists-p expanded)
(error "Cannot create file %s: file exists" expanded))
;; Find the topmost nonexistent parent dir (variable `new')
(while (and try (not (file-exists-p try)) (not (equal new try)))
(setq new try
try (directory-file-name (file-name-directory try))))
(when (not (file-exists-p dir))
(make-directory dir t))
(write-region "" nil expanded t)
(when new
(dired-add-file new)
(dired-move-to-filename))))
;;
;; Projectile (find file from the root of the current project).
;;
(require 'projectile)
(projectile-global-mode)
(setq projectile-completion-system 'ivy)
;;
;; Emacs Lisp (elisp)
;;
(add-hook 'emacs-lisp-mode-hook (lambda () (modify-syntax-entry ?- "w" emacs-lisp-mode-syntax-table)))
(evil-define-key 'normal emacs-lisp-mode-map
(kbd "M-h") 'shift-sexp-backward
(kbd "M-l") 'shift-sexp-forward
"K"'(lambda ()
(interactive)
;; Run `describe-function` and show its output in a help
;; window. Inspired from help-fns.el.
(with-help-window "*Help*"
(describe-function (intern (current-word))))))
(defun current-sexp ()
"Returns the text content of the sexp list around the cursor."
(let ((position (bounds-of-thing-at-point 'list)))
(buffer-substring-no-properties (car position) (cdr position))))
(defun elisp-eval-current-sexp ()
(interactive)
(message "%s" (eval (read (current-sexp)))))
(evil-leader/set-key-for-mode 'emacs-lisp-mode
; Note that I'm saving the buffer before each eval because otherwise, the buffer gets saved after the eval,
; (due to save-when-switching-windows setup) and the output from the buffer save overwrites the eval results
; in the minibuffer.
"eb" (lambda() (interactive) (util/save-buffer-if-dirty) (eval-buffer))
"es" (lambda () (interactive) (util/save-buffer-if-dirty) (elisp-eval-current-sexp))
"ex" (lambda () (interactive) (util/save-buffer-if-dirty) (call-interactively 'eval-defun))
"ee" 'view-echo-area-messages)
;; Indentation rules.
(put '-> 'lisp-indent-function nil)
(put '->> 'lisp-indent-function nil)
;;
;; Org mode, for TODOs and note taking.
;;
(require 'org-mode-personal)
;;
;; tab-bar-mode (tabs on the window).
;;
(setq tab-bar-select-tab-modifiers '(meta))
(setq tab-bar-tab-hints t)
(setq tab-bar-show 1) ;; hide bar if <= 1 tabs open
(setq tab-bar-close-button-show nil)
(tab-bar-mode 1)
(define-key evil-normal-state-map (kbd "M-t") 'tab-new)
(define-key evil-normal-state-map (kbd "M-}") 'tab-next)
(define-key evil-normal-state-map (kbd "M-{") 'tab-previous)
;;
;; Diminish - hide or shorten the names of minor modes in your modeline.
;; To see which minor modes you have loaded and what their modeline strings are: (message minor-mode-alist)
;;
(require 'diminish)
(diminish 'visual-line-mode "")
(diminish 'global-whitespace-mode "")
;(diminish 'global-visual-line-mode "")
(diminish 'auto-fill-function "")
(diminish 'yas-minor-mode "")
(diminish 'osx-keys-minor-mode "")
(diminish 'undo-tree-mode "")
(diminish 'ivy-mode "")
(diminish 'company-mode "")
;; Markdown
(setq markdown-command
(concat
"/opt/homebrew/bin/pandoc"
" --from markdown --to html"
" --metadata title='-'"
;; Use Gmail's default styling, so I can copy exported HTML into the Compose window with no reformatting:
" --include-in-header $HOME/.emacs.d/resources/gmail.css"))
(setq markdown-fontify-code-blocks-natively t)
;;
;; CSS
;;
(add-hook 'css-mode-hook (lambda ()
;; (autopair-mode 1) ; Auto-insert matching delimiters.
;; Properly unindent a closing brace after you type it and hit enter.
(electric-indent-mode)))
;;
;; Ruby
;;
(add-to-list 'auto-mode-alist '("\\.rake$" . ruby-mode))
(add-to-list 'auto-mode-alist '("\\.gemspec$" . ruby-mode))
(add-to-list 'auto-mode-alist '("\\.ru$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Rakefile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Gemfile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Capfile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Vagrantfile$" . ruby-mode))
;; Insert matching delimiters; unindent end blocks after you type them.
(add-hook 'ruby-mode-hook (lambda () (ruby-electric-mode)))
;;
;; Rainbow-delimiters: highlight parentheses in rainbow colors.
;;
(require 'rainbow-delimiters)
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)
;;
;; Smartparens utility functions
;;
(require 'smartparens)
(require 'smartparens-config)
(smartparens-global-mode t)
(sp-pair "'" nil :actions :rem)
(setq sp-autoescape-string-quote nil)
(sp-with-modes '(clojure-mode)
(sp-local-pair "`" "`" :when '(sp-in-string-p)))
(sp-with-modes '(org-mode)
(sp-local-pair "=" nil :actions :rem)
(sp-local-pair "~" nil :actions :rem))
(global-set-key (kbd "M-H") 'sp-forward-slurp-sexp)
(global-set-key (kbd "M-L") 'sp-forward-barf-sexp)
(defun shift-sexp-backward ()
(interactive)
(let* ((next (save-excursion (sp-forward-sexp)))
(prev (save-excursion (goto-char (sp-get next :beg-prf)) (sp-backward-sexp))))
(sp--transpose-objects prev next))
;; Focus the cursor correctly.
(sp-backward-sexp)
(sp-backward-sexp))
(defun shift-sexp-forward ()
(interactive)
(sp-forward-sexp)
(let* ((next (save-excursion (sp-forward-sexp)))
(prev (save-excursion (goto-char (sp-get next :beg-prf)) (sp-backward-sexp))))
(sp--transpose-objects prev next))
;; Focus the cursor correctly.
(sp-backward-sexp))
;;
;; Clojure
;;
(require 'clojure-mode)
(require 'clojure-mode-personal)
(require 'cider-test-personal)
(evil-leader/set-key-for-mode 'clojure-mode
"ett" 'cider-test/run-test-at-point
"etb" 'cider-test/run-tests-in-ns)
(which-key-add-major-mode-key-based-replacements 'clojure-mode
"SPC e t" "EvaluateTests")
(add-hook 'cider-mode-hook 'rainbow-delimiters-mode)
(add-hook 'cider-mode-hook 'eldoc-mode)
(add-hook 'cider-repl-mode-hook 'rainbow-delimiters-mode)
;;
;; HTML mode
;;
(add-to-list 'auto-mode-alist '("\\.erb$" . html-mode))
(defun preview-html ()
"Pipes the buffer's contents into a script which opens the HTML in a browser."
(interactive)
(call-process-region (point-min) (point-max) "/bin/bash" nil nil nil "-c" "bcat"))
(defun indent-html-buffer ()
(interactive)
;; html-beautify is a program defined here: https://github.com/beautify-web/js-beautify
;; To install: cd ~; npm install js-beautify; add ~/node_modules/.bin to your PATH.
;; I don't know why, but save-excursion does not maintain the cursor position.
;; (save-excursion
(let ((p (point))
(scroll-y (window-start)))
(call-process-region (point-min) (point-max) "html-beautify" t (buffer-name) t
"--file" "-" ; STDIN
"--indent-size" "2"
"--wrap-line-length" "110")
(set-window-start (selected-window) scroll-y)
(goto-char p)))
(evil-leader/set-key-for-mode 'html-mode
"ii" 'indent-html-buffer
"rr" 'preview-html)
;; TODO(harry) If this doesn't work out try multi-web-mode or mmm-mode. See:
;; http://www.emacswiki.org/emacs/MultipleModes
;; http://stackoverflow.com/questions/4462393/how-do-i-configure-emacs-for-editing-html-files-that-contain-javascript
(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
(defun my-web-mode-hook ()
(setq web-mode-markup-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2)
(setq web-mode-style-padding 2)
(setq web-mode-script-padding 2)
(setq web-mode-block-padding 2))
(add-hook 'web-mode-hook 'my-web-mode-hook)
;;
;; SCSS mode, for editing SCSS files.
;;
(setq scss-compile-at-save nil)
(add-to-list 'auto-mode-alist '("\\.scss\\'" . scss-mode))
;;
;; YAML mode, for editing YAML files
;;
(require 'yaml-mode)
(add-to-list 'auto-mode-alist '("\\.yml$" . yaml-mode))
;;
;; Go mode, for writing Go code
;;
(with-eval-after-load "go-mode"
(evil-define-key 'normal go-mode-map
"K" 'godef-describe))
(defun go-save-and-compile-fn (command)
"Returns a function for the purpose of binding to a key which saves the current buffer and then
runs the given command in the root of the go project."
(lexical-let ((command command))
#'(lambda ()
(interactive)
(go-save-and-compile command))))
;; Note that this function uses (projectile-project-root) to determine the directory to run `go` commands,
;; which requires that the go project have a .projectile file in it or that it be at the root of a git repo.
(defun go-save-and-compile (command)
"Saves the current buffer before invoking the given command."
(lexical-let ((has-makefile (file-exists-p (concat (projectile-project-root) "Makefile"))))
(save-buffer)
(message command)
(util/without-confirmation