-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.el
2627 lines (2139 loc) · 86.9 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
;; -*- lexical-binding: t -*-
(use-package emacs
:custom
;; Support opening new minibuffers from inside existing minibuffers.
(enable-recursive-minibuffers t)
;; Emacs 28 and newer: Hide commands in M-x which do not work in the current
;; mode. Vertico commands are hidden in normal buffers. This setting is
;; useful beyond Vertico.
(read-extended-command-predicate #'command-completion-default-include-p)
;; TAB cycle if there are only few candidates
;; (completion-cycle-threshold 3)
;; Enable indentation+completion using the TAB key.
;; `completion-at-point' is often bound to M-TAB.
(tab-always-indent 'complete)
;; Emacs 30 and newer: Disable Ispell completion function. As an alternative,
;; try `cape-dict'.
(text-mode-ispell-word-completion nil)
;; Hide commands in M-x which do not apply to the current mode. Corfu
;; commands are hidden, since they are not used via M-x. This setting is
;; useful beyond Corfu.
(read-extended-command-predicate #'command-completion-default-include-p)
:init
;; Add prompt indicator to `completing-read-multiple'.
;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
(defun crm-indicator (args)
(cons (format "[CRM%s] %s"
(replace-regexp-in-string
"\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
crm-separator)
(car args))
(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)
;; Do not allow the cursor in the minibuffer prompt
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode))
;; Visuals
(menu-bar-mode -1)
(tool-bar-mode -1)
(setq minibuffer-message-timeout 0)
(setq inhibit-startup-screen t)
(global-display-line-numbers-mode 1)
(setq display-line-numbers 'visual
display-line-numbers-type 'relative)
(add-hook 'conf-mode-hook 'display-line-numbers-mode)
(add-hook 'conf-space-mode-hook 'display-line-numbers-mode)
(add-hook 'text-mode-hook 'display-line-numbers-mode)
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(defun enable-line-numbers-in-messages-buffer ()
(with-current-buffer "*Messages*"
(display-line-numbers-mode 1)))
(add-hook 'after-init-hook 'enable-line-numbers-in-messages-buffer)
(advice-add 'message :after
(lambda (&rest _)
(when (get-buffer "*Messages*")
(with-current-buffer "*Messages*"
(display-line-numbers-mode 1)))))
(defun my-mode-line-major-mode ()
"Returns a clean name of the current major mode."
(let ((mode (format "%s" major-mode)))
(replace-regexp-in-string "-mode$" "" mode)))
(defun my-vc-branch ()
"Get the current Git branch name, if any."
(unless (or (eq major-mode 'eshell-mode)
(eq major-mode 'special-mode) ; for *scratch*, *Messages*, etc
(string-prefix-p "*" (buffer-name))) ; any special buffer
(with-temp-buffer
(condition-case nil
(when (zerop (call-process "git" nil t nil "branch" "--show-current"))
(let ((branch (string-trim (buffer-string))))
(unless (string-empty-p branch)
(if (> (length branch) 40)
(concat (substring branch 0 37) "...")
branch))))
(error nil)))))
(defun my-window-number ()
"Get the current window number."
(let* ((windows (window-list-1 (frame-first-window) 'nomini t))
(num (cl-position (selected-window) windows)))
(format "%d" (1+ (or num 0)))))
(setq-default mode-line-format
'("%e"
(:eval (my-window-number))
"" ; Single space after the window number
mode-line-front-space
(:eval (if (buffer-file-name)
(abbreviate-file-name (buffer-file-name))
"%b"))
" | "
(:eval (my-mode-line-major-mode))
" | "
(:eval (or (my-vc-branch) ""))
(:eval (propertize " " 'display '(space :align-to (- right 12))))
mode-line-end-spaces))
;; ;; Tabs
(setq tab-bar-tab-name-format-function #'my-tab-bar-vim-name-format-function)
(setq tab-bar-format '(tab-bar-format-tabs tab-bar-separator))
(setq tab-bar-separator "\u200B") ;; Zero width space to fix color bleeding
(setq tab-bar-tab-hints nil) ;; Tab numbers of the left of the label
(setq tab-bar-new-button-show nil)
(setq tab-bar-close-button-show nil)
(setq tab-bar-auto-width nil)
(defun my-tab-name-format-function (tab i)
(defface my-active-tab-face
'((t :background "#2e2c3d" :foreground "#e0def4"))
"Face for the active tab.")
(defface my-inactive-tab-face
'((t :background "#1d1f21" :foreground "#6e6a86"))
"Face for the inactive tab.")
(let ((current-p (eq (car tab) 'current-tab))
(tab-name (format "%d %s" i (alist-get 'name (cdr tab)))))
;; Add padding around the tab name
(setq tab-name (format " %s " tab-name)) ;; Add a space before and after the tab name
(if current-p
(propertize tab-name 'face 'my-active-tab-face)
(propertize tab-name 'face 'my-inactive-tab-face))))
(setq tab-bar-tab-name-format-function #'my-tab-name-format-function)
(dotimes (i 9)
(let ((n (1+ i))) ; Tab numbers start from 1
(global-set-key (kbd (format "M-%d" n))
`(lambda () (interactive) (tab-bar-select-tab ,n)))))
;; System
(setq erc-nick "wurfkreuz")
(global-set-key (kbd "C-x u") 'windmove-up)
(save-some-buffers t)
(recentf-mode)
(setq vc-follow-symlinks t)
(setq dired-recursive-deletes 'always)
(setq desktop-load-locked-desktop t)
(setq backup-inhibited t)
(add-hook 'prog-mode-hook (show-paren-mode t))
;; Auto pairing
(add-hook 'prog-mode-hook (electric-pair-mode t))
;; I don't know what it does exactly, it's more like a test
(setq electric-pair-inhibit-predicate 'electric-pair-conservative-inhibit)
;; Don't pair '<'
(setq electric-pair-inhibit-predicate
`(lambda (c)
(if (char-equal c ?\<) t (,electric-pair-inhibit-predicate c))))
;; Break lines after a certain length
(setq sentence-end-double-space nil)
(auto-fill-mode 1)
(setq-default fill-column 80)
(add-hook 'text-mode-hook 'auto-fill-mode)
(add-hook 'go-ts-mode-hook 'auto-fill-mode)
(setq python-shell-interpreter "/usr/bin/python3")
(defalias 'yes-or-no-p 'y-or-n-p)
(setq-default indent-tabs-mode nil)
(use-package savehist
:ensure nil
:hook
(after-init . savehist-mode)
:config
(add-to-list 'savehist-additional-variables 'kill-ring)
(add-to-list 'savehist-additional-variables 'mark-ring)
(add-to-list 'savehist-additional-variables 'search-ring)
(add-to-list 'savehist-additional-variables 'regexp-search-ring))
;; ;; Executable on save if starts with '#!'
(add-hook 'after-save-hook
'executable-make-buffer-file-executable-if-script-p)
(make-directory (concat user-emacs-directory "auto-saves") t)
(setq auto-save-file-name-transforms
`((".*" ,(concat user-emacs-directory "auto-saves/") t)))
(setq auto-save-list-file-prefix (concat user-emacs-directory "auto-saves/.saves-"))
;; There was a situation where emacs created an autosave file in a directory
;; that i was currently for an eshell buffer.
(add-hook 'eshell-mode-hook
(lambda ()
(setq-local auto-save-default nil)))
(make-directory (concat user-emacs-directory "lock-files") t)
(setq lock-file-name-transforms
`((".*" ,(concat user-emacs-directory "lock-files/") t)))
(setq desktop-dirname (concat user-emacs-directory "desktop/"))
(make-directory (concat user-emacs-directory "backups") t)
(setq backup-directory-alist
`((".*" . ,(concat user-emacs-directory "backups/"))))
(defun fov/disable-backups-for-gpg ()
"Disable backups and autosaving for files ending in \".gpg\"."
(when (and (buffer-file-name)
(s-ends-with-p ".gpg" (buffer-file-name) t))
(setq-local backup-inhibited t)
(setq-local undo-tree-auto-save-history nil)
(auto-save-mode -1)))
(add-hook 'find-file-hook #'fov/disable-backups-for-gpg)
;; ;; First, disable auto-save globally
;; (setq auto-save-default nil)
;; (auto-save-mode -1)
;; ;; Then enable only for programming and text modes
;; (defun enable-auto-save-for-prog-and-text ()
;; "Enable auto-save for programming and text modes, except lisp-interaction-mode."
;; (when (or (and (derived-mode-p 'prog-mode)
;; (not (derived-mode-p 'lisp-interaction-mode)))
;; (derived-mode-p 'text-mode))
;; (setq-local auto-save-default t)
;; (auto-save-mode 1)))
;; (add-hook 'after-change-major-mode-hook #'enable-auto-save-for-prog-and-text)
;; ;; (defun disable-auto-save-for-eshell ()
;; ;; "Disable auto-save for eshell buffers."
;; ;; (when (eq major-mode 'eshell-mode)
;; ;; (setq-local auto-save-default nil)))
;; ;; (add-hook 'eshell-mode-hook #'disable-auto-save-for-eshell)
;; ;; (defun disable-auto-save-for-messages-buffer ()
;; ;; "Disable auto-save for the *Messages* buffer."
;; ;; (when (string= (buffer-name) "*Messages*")
;; ;; (setq-local auto-save-default nil)
;; ;; (auto-save-mode -1)))
;; ;; (add-hook 'after-change-major-mode-hook #'disable-auto-save-for-messages-buffer)
;; ;; (defun disable-auto-save-for-non-file-buffers ()
;; ;; "Disable auto-save for buffers not associated with a file."
;; ;; (unless (buffer-file-name)
;; ;; (setq-local auto-save-default nil)
;; ;; (auto-save-mode -1)))
;; ;; (add-hook 'after-change-major-mode-hook #'disable-auto-save-for-non-file-buffers)
;; ;; (defun disable-auto-save-for-ediff ()
;; ;; "Disable auto-save for ediff merge buffers."
;; ;; (when (string-match-p "\\`ediff" (buffer-name))
;; ;; (setq-local auto-save-default nil)
;; ;; (auto-save-mode -1)))
;; ;; (add-hook 'ediff-prepare-buffer-hook #'disable-auto-save-for-ediff)
;; Save sessions
(unless (file-exists-p desktop-dirname)
(make-directory desktop-dirname))
(desktop-save-mode 1)
(setq desktop-save 't)
(setq desktop-path (list desktop-dirname))
(setq desktop-auto-save-timeout 30)
(setq desktop-auto-save-timeout nil)
(auto-save-mode -1)
;; (auto-save-visited-mode 1)
;; (setq auto-save-visited-interval 30)
;; (setq auto-save-interval 1) ; Auto-save every 1 second
;; (setq auto-save-timeout 10) ; Auto-save after 10 seconds of idle time
;; (setq auto-save-no-message t)
(setq save-place-file (concat user-emacs-directory "saveplace/places"))
;; Save cursor position
(unless (file-exists-p (concat user-emacs-directory "saveplace/"))
(make-directory (concat user-emacs-directory "saveplace/")))
(save-place-mode 1)
(setq scroll-conservatively 101)
(setq scroll-margin 5)
;; (setq scroll-step 1)
;; I disabled '(setq scroll-step 1)' because i don't know the exact point of
;; why i was having this setting in the first place
(scroll-bar-mode -1)
(setq-default display-line-numbers-width 3)
(setq use-dialog-box nil)
(fringe-mode '(1 . 1))
(global-set-key (kbd "<escape>") 'keyboard-escape-quit)
(setq global-auto-revert-non-file-buffers t)
(global-auto-revert-mode 1)
(set-default 'truncate-lines t)
(add-hook 'special-mode-hook (lambda () (setq truncate-lines nil)))
;; List of modes that should have word-wrap enabled
(dolist (mode '(compilation-mode
;; Add other modes here
))
(add-hook (intern (concat (symbol-name mode) "-hook"))
(lambda () (setq-local truncate-lines nil))))
;; (add-hook 'prog-mode-hook (lambda ()
;; ;; (setq-local truncate-lines t)
;; (toggle-truncate-lines 1)))
;; ;; Specific for emacs-lisp-mode
;; (add-hook 'emacs-lisp-mode-hook
;; (lambda ()
;; (setq-local truncate-lines t)
;; (toggle-truncate-lines 1)))
;; ;; ;; Also, you can check what's changing the setting
;; ;; (add-variable-watcher 'truncate-lines
;; ;; (lambda (sym val op where)
;; ;; (message "truncate-lines changed to %s in %s" val where)))
;; (winner-mode 1)
(setq enable-local-variables t)
(setq enable-dir-local-variables t)
(let ((paths '("/home/wurfkreuz/.nix-profile/bin"
"/home/wurfkreuz/.ghcup/bin"
"/home/wurfkreuz/go/bin/"
"/home/wurfkreuz/test-dir/"
"/usr/bin")))
;; (setq exec-path (append paths exec-path))
(setenv "PATH" (concat (string-join paths ":")
":"
(getenv "PATH"))))
(require 'midnight)
(midnight-delay-set 'midnight-delay "10:00pm")
(setq auto-revert-verbose nil)
(setq display-buffer-base-action '(nil . ((some-window . mru))))
(minibuffer-regexp-mode 1)
(setq ielm-history-file-name "~/.emacs.d/.ielm-history")
(defun my-tramp-cleanup ()
"Clean up TRAMP buffers and connections on Emacs exit."
(when (fboundp 'tramp-cleanup-all-buffers)
(tramp-cleanup-all-buffers))
(when (fboundp 'tramp-cleanup-all-connections)
(tramp-cleanup-all-connections)))
(add-hook 'kill-emacs-hook #'my-tramp-cleanup)
;; I haven't tried them yet
(setq isearch-lazy-count t)
(setq isearch-lazy-highlight t)
;; Cursor
;; No delay when deleting pairs (i don't know what it does really, just testing)
(setopt delete-pair-blink-delay 0)
;; Same here
(setopt show-paren-context-when-offscreen 'overlay) ; Emacs 29
(blink-cursor-mode 0)
(setq show-paren-delay 0)
(show-paren-mode 1)
;; Theme
(add-to-list 'custom-theme-load-path (expand-file-name "themes" user-emacs-directory))(put 'eval 'safe-local-variable #'identity)
(load-theme 'rose-pine t)
;; SQL mode
(defun my-sql-mode-custom-faces ()
"Customize faces for SQL mode."
(face-remap-add-relative 'font-lock-builtin-face :foreground "#9ccfd8"))
(add-hook 'sql-mode-hook 'my-sql-mode-custom-faces)
(add-hook 'sql-interactive-mode-hook 'my-sql-mode-custom-faces)
;; Terraform mode
(defun my-terraform-mode-custom-faces ()
"Customize faces for terraform mode."
(face-remap-add-relative 'font-lock-type-face :foreground "#9ccfd8"))
(add-hook 'terraform-mode-hook 'my-terraform-mode-custom-faces)
(when (member "NotoSansM Nerd Font Mono" (font-family-list))
(set-face-attribute 'default nil :font "NotoSansM Nerd Font Mono-12:weight=medium")
;; Set a different font for italics
(set-face-attribute 'italic nil
:family "NotoSans Nerd Font"
:slant 'italic
:weight 'normal
:height 130)
(add-hook 'org-mode-hook
(lambda ()
(set-face-attribute 'org-verbatim nil
;; :family "NotoSerifNerdFontPropo-CondensedExtraLight"
:family "NotoSerifNerdFont"
:height 130
;; :foreground "#8bc34a" ; Adjust the color as desired
:weight 'normal))))
;; Emoji support
(when (member "Noto Color Emoji" (font-family-list))
(set-fontset-font t 'symbol (font-spec :family "Noto Color Emoji") nil 'prepend))
;; This is a code that tries to fix a sitaution where commented characters
;; inside single quotes have their own face
(defun in-string-or-sexp-or-keyword-p (pos)
"Return t if POS is inside a string, sexp, or keyword argument."
(save-excursion
(goto-char pos)
(let* ((ppss (syntax-ppss))
(in-string (nth 3 ppss))
(sexp-pos (nth 1 ppss)))
(or in-string
(and sexp-pos
(save-excursion
(goto-char sexp-pos)
(looking-at-p "(:.*"))))))) ; check if inside keyword list
(defun force-comment-face ()
"Force comment face for comments, handling both line comments and inline comments."
(font-lock-add-keywords
nil
`((,(lambda (limit)
(let (found-pos)
(while (and (not found-pos)
(re-search-forward ";.*$" limit t))
(let* ((semi-pos (- (match-beginning 0) 1))
(check-pos (1+ semi-pos))
(is-special (in-string-or-sexp-or-keyword-p check-pos)))
(when (not is-special)
(setq found-pos (point)))))
found-pos))
(0 font-lock-comment-face prepend)))
'append))
;; This is a code that tries to fix a sitaution where characters within double
;; quotes and inside single quotes have there own fontification characters
;; within double quotes and inside single quotes have there own face
(defun in-double-quotes-p (pos)
"Return t if POS is inside double quotes."
(save-excursion
(goto-char pos)
(let* ((state (syntax-ppss))
(in-string (nth 3 state))
(string-start (nth 8 state))
(double-quote (and string-start
(eq ?\" (char-after string-start)))))
(and in-string double-quote))))
(defun fix-quotes-in-string-face ()
"Force string face for quoted text inside strings."
(font-lock-add-keywords
nil
`((,(lambda (limit)
(let (found-pos)
(while (and (not found-pos)
(re-search-forward "'[^'\n]*'" limit t))
(let* ((quote-start (match-beginning 0))
(quote-end (match-end 0))
(in-double (in-double-quotes-p quote-start)))
(when in-double
(setq found-pos (point))
(remove-text-properties quote-start quote-end '(face nil))
(put-text-property quote-start quote-end 'face 'font-lock-string-face))))
found-pos))
(0 'font-lock-string-face keep)))
t))
(add-hook 'emacs-lisp-mode-hook 'fix-quotes-in-string-face)
(add-hook 'emacs-lisp-mode-hook 'force-comment-face) ;; aorisetn 'aorisetn'
(add-hook 'lisp-mode-hook 'force-comment-face)
;; Highlighting
(add-to-list 'auto-mode-alist '("sshd_config\\'" . conf-mode))
(add-to-list 'auto-mode-alist '("ssh_config\\'" . conf-mode))
;; Cron
;; For some reason doesn't want to load the downloaded package, so i donwloaded it with the macro, commented it out and then just load manually using add-to-list.
;; (use-package emacs-crontab-mode
;; :vc (:url "https://gitlab.com/Bacaliu/emacs-crontab-mode"
;; :rev :newest))
(add-to-list 'load-path (expand-file-name "emacs-crontab-mode" user-emacs-directory))
;; Treesitter
(setq treesit-font-lock-level 4) ;; The default value is 3
(use-package treesit-auto
:ensure t
:config
(global-treesit-auto-mode))
(use-package clojure-ts-mode)
(setq treesit-language-source-alist
'((lua "https://github.com/tree-sitter-grammars/tree-sitter-lua")
(zig "https://github.com/maxxnino/tree-sitter-zig")
(c3 "https://github.com/c3lang/tree-sitter-c3")))
(use-package zig-ts-mode
:vc (:url "https://codeberg.org/meow_king/zig-ts-mode"
:rev :newest))
(add-to-list 'auto-mode-alist '("\\.yaml\\'" . yaml-ts-mode))
(add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-ts-mode))
(add-hook 'yaml-ts-mode-hook (lambda ()
(auto-fill-mode -1)))
;; Undo fu
(use-package undo-fu
:config
(setq undo-limit 67108864)
(setq undo-tree-history-directory-alist `(("." . ,(concat user-emacs-directory
"undo-tree-history")))))
(use-package undo-fu-session
:config
(undo-fu-session-global-mode))
;; Create undo directory if it doesn't exist
(make-directory "~/.emacs.d/undo-tree-history" t)
(defvar-local change-history nil
"Ring of change positions for this buffer, most recent first.")
(defvar-local change-history-index 0
"Current index in change history for navigation.")
(defconst change-history-max 100
"Maximum number of change positions to remember.")
(defun record-change-position (beg end length)
"Record change position in history.
Added to `after-change-functions'."
(let ((pos (if (= length 0) end beg))) ; end for insertions, beg for deletions
(unless (and change-history
(= pos (car change-history)))
(push pos change-history)
(when (> (length change-history) change-history-max)
(setq change-history (butlast change-history)))
(setq change-history-index 0))))
(add-hook 'after-change-functions #'record-change-position)
(defun my/goto-last-change (&optional n)
"Move cursor through change history.
Without argument: go to previous change
With numeric prefix:
- Positive N: go N steps back in history
- Negative N: go N steps forward in history"
(interactive "P")
(if (null change-history)
(message "No change history available.")
(let* ((len (length change-history))
(n (or n 1))
(new-index (+ change-history-index n))
(new-index (max 0 (min new-index (1- len)))))
(setq change-history-index new-index)
(goto-char (nth new-index change-history))
(message "Position %d/%d" (1+ new-index) len))))
(defun goto-next-change ()
"Move forward through change history."
(interactive)
(my/goto-last-change -1))
(use-package goto-chg)
(defvar-local my-jump-ring '()
"Ring of positions from goto-last-change jumps.")
(defvar-local my-jump-index 0
"Current position in jump ring.")
(defun my-goto-last-change ()
"Wrapper for goto-last-change that stores jump positions."
(interactive)
(let ((old-pos (point)))
(message "Storing position: %d" old-pos)
(call-interactively 'goto-last-change)
(push old-pos my-jump-ring)
(setq my-jump-index 0)
(message "Jump ring now: %S" my-jump-ring)))
(defun my-goto-last-change-reverse ()
"Go back through stored jump positions."
(interactive)
(message "Current ring: %S, index: %d" my-jump-ring my-jump-index)
(if (null my-jump-ring)
(message "No previous jumps")
(let ((pos (nth my-jump-index my-jump-ring)))
(when pos
(goto-char pos)
(setq my-jump-index (1+ my-jump-index))
(when (>= my-jump-index (length my-jump-ring))
(setq my-jump-index 0))))))
;; Avy
(use-package avy
:ensure t
)
(defun avy-jump-to-window ()
"Use avy to jump to a specific window."
(interactive)
(let ((avy-all-windows 'all-frames))
(avy-with avy-jump-to-window
(avy--process
(mapcar (lambda (w)
(cons (window-start w) w))
(avy-window-list))
#'avy--overlay-post))))
(with-eval-after-load 'avy
(defun avy-action-copy-word (pt)
"Copy word at PT and paste at current point (like evil's iw)."
(let ((original-window (selected-window))
(original-point (point)))
(save-excursion
(goto-char pt)
(let ((bounds (evil-inner-word)))
(kill-ring-save (nth 0 bounds) (nth 1 bounds))))
(select-window original-window)
(goto-char original-point)
(yank))
t)
(defun avy-action-copy-WORD (pt)
"Copy WORD at PT and paste at current point (like evil's iW)."
(let ((original-window (selected-window))
(original-point (point)))
(save-excursion
(goto-char pt)
(let ((bounds (evil-inner-WORD)))
(kill-ring-save (nth 0 bounds) (nth 1 bounds))))
(select-window original-window)
(goto-char original-point)
(yank))
t)
(defun avy-action-copy-quoted (pt)
"Copy quoted text at PT and paste at current point."
(let ((original-window (selected-window))
(original-point (point)))
(save-excursion
(goto-char pt)
(let ((bounds (evil-select-quote ?\" t t)))
(kill-ring-save (nth 0 bounds) (nth 1 bounds))))
(select-window original-window)
(goto-char original-point)
(yank))
t)
;; Add to dispatch alist
(setf (alist-get ?w avy-dispatch-alist) 'avy-action-copy-word
(alist-get ?W avy-dispatch-alist) 'avy-action-copy-WORD
(alist-get ?\" avy-dispatch-alist) 'avy-action-copy-quoted))
;; Docker
(use-package docker
:ensure t
;; :config
;; It was defined in xterm block for some reason
;; (with-eval-after-load 'vterm
;; (setq docker-vterm-support t)
;; (setq docker-container-shell-file-name "vterm"))
)
(defun container-map-id (container-name)
"Display the UID and GID maps of a Docker container.
Ask for the name of a Docker container, retrieve its PID, and display the UID and GID maps."
(interactive "sContainer name: ")
(let* ((pid (string-trim (shell-command-to-string (format "docker inspect --format '{{.State.Pid}}' %s" container-name))))
(uid-map-file (format "/proc/%s/uid_map" pid))
(gid-map-file (format "/proc/%s/gid_map" pid)))
(if (and (not (string-empty-p pid))
(file-exists-p uid-map-file)
(file-exists-p gid-map-file))
(with-output-to-temp-buffer "*Docker ID Maps*"
(princ (format "UID and GID maps for container '%s' (PID: %s):\n\n" container-name pid))
(princ "UID map:\n")
(princ (with-temp-buffer
(insert-file-contents uid-map-file)
(buffer-string)))
(princ "\nGID map:\n")
(princ (with-temp-buffer
(insert-file-contents gid-map-file)
(buffer-string))))
(message "Failed to retrieve UID and/or GID maps for container '%s'" container-name))))
;; (defun docker-template ()
;; "Create docker.el windows with a specific layout"
;; (interactive)
;; (delete-other-windows)
;; (docker-images)
;; (docker-containers)
;; (transpose-frame)
;; (docker-volumes)
;; )
(defun docker-template ()
"Create docker.el windows with a specific layout"
(interactive)
(delete-other-windows)
(docker-images)
(docker-containers)
(transpose-frame)
(evil-window-move-very-bottom)
)
(defun toggle-docker-layout ()
"Toggle between docker layout and previous layout."
(interactive)
(let ((mode-name (symbol-name major-mode)))
;; (message "Current mode: %s, contains 'docker': %s"
;; mode-name
;; (if (string-match-p "docker" mode-name) "yes" "no"))
(condition-case nil
(if (not (get-register ?d))
;; First time setup
(progn
;; (message "First time setup: creating docker layout")
(window-configuration-to-register ?p)
(docker-template)
(window-configuration-to-register ?d))
;; Docker register exists - check if we're in docker mode
(if (string-match-p "docker" mode-name)
(progn
;; (message "In docker mode, switching to previous layout")
(jump-to-register ?p))
(progn
;; (message "Not in docker mode, switching to docker layout")
(window-configuration-to-register ?p)
(jump-to-register ?d))))
;; Handle invalid register by doing first-time setup
(error
;; (message "Invalid register detected, doing first-time setup")
(set-register ?p nil)
(set-register ?d nil)
(window-configuration-to-register ?p)
(docker-template)
(window-configuration-to-register ?d)))))
;; (defun my-docker-shell ()
;; (interactive)
;; (let ((container-id (read-string "Enter container ID: ")))
;; (comint-run (format "docker exec -it %s /bin/sh" container-id))))
;; Which-key
(which-key-mode)
(setq which-key-max-description-length 40)
;; Wgrep
(use-package wgrep
:ensure t)
;; Xterm-color
(require 'ansi-color)
(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)
;; Snippets
(defun my/select-placeholder ()
"Select the placeholder text on the current line."
(set-mark (point))
(end-of-line)
(point))
(use-package tempel
:ensure t
;; Require trigger prefix before template name when completing.
:custom
(tempel-trigger-prefix "<")
:bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
("M-*" . tempel-insert))
:init
;; Setup completion at point
(defun tempel-setup-capf ()
;; Add the Tempel Capf to `completion-at-point-functions'.
;; `tempel-expand' only triggers on exact matches. Alternatively use
;; `tempel-complete' if you want to see all matches, but then you
;; should also configure `tempel-trigger-prefix', such that Tempel
;; does not trigger too often when you don't expect it. NOTE: We add
;; `tempel-expand' *before* the main programming mode Capf, such
;; that it will be tried first.
(setq-local completion-at-point-functions
(cons #'tempel-expand
completion-at-point-functions)))
(add-hook 'conf-mode-hook 'tempel-setup-capf)
(add-hook 'prog-mode-hook 'tempel-setup-capf)
(add-hook 'text-mode-hook 'tempel-setup-capf)
;; Optionally make the Tempel templates available to Abbrev,
;; either locally or globally. `expand-abbrev' is bound to C-x '.
;; (add-hook 'prog-mode-hook #'tempel-abbrev-mode)
;; (global-tempel-abbrev-mode)
:config
(setq tempel-path "~/.emacs.d/other/templates")
)
(use-package tempel-collection
:ensure t)
;; Orderless
(use-package orderless
:ensure t
:init
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion)))))
;; Completion
;; ;; It makes completion-on-point to behave simpler, so that it doesn't takes into
;; ;; account what goes after the pointer.
;; (defun my-completion-at-point-advice (orig-fun &rest args)
;; "Insert a whitespace after the cursor before showing completion candidates, and clean it up afterward."
;; (let ((inserted-whitespace (not (eq (char-after) ?\s))))
;; (when inserted-whitespace
;; (save-excursion
;; (insert " ")))
;; (unwind-protect
;; (apply orig-fun args)
;; (when inserted-whitespace
;; (save-excursion
;; (delete-char 1))))))
;; (advice-add 'completion-at-point :around #'my-completion-at-point-advice)
;; ;; Corfu/Cape
(defun my-eshell-has-argument-p ()
"Check if the current Eshell input has an argument."
(let* ((input (eshell-get-old-input))
(trimmed-input (string-trim-right input))
(args (split-string trimmed-input " " t)))
(or (> (length args) 1)
(not (string-equal input trimmed-input)))))
(defun my-eshell-directory-completions ()
"Generate a list of all directories in the current working directory, including hidden ones."
(let ((current-dir (eshell/pwd)))
(cl-remove-if-not
#'file-directory-p
(directory-files current-dir t nil t))))
(defun my-eshell-completion-at-point ()
"Provide completion for Eshell using custom directory completions with a whitespace."
(unless (my-eshell-has-argument-p)
(let ((bounds (bounds-of-thing-at-point 'filename)))
(when bounds
(let* ((start (car bounds))
(end (cdr bounds))
(input (buffer-substring-no-properties start end))
(completions (my-eshell-directory-completions))
(matches (cl-remove-if-not
(lambda (dir)
(string-prefix-p input (file-name-nondirectory dir)))
completions)))
(when matches
(list start end
;; Add a space to each completion candidate
(mapcar (lambda (dir) (concat (file-name-nondirectory dir) " "))
matches)
:exclusive 'no)))))))
(defun my-eshell-setup ()
"Set up custom completions and key bindings for Eshell."
(add-to-list 'completion-at-point-functions 'my-eshell-completion-at-point))
(add-hook 'eshell-mode-hook 'my-eshell-setup)
;; Corfu setup
(use-package corfu
:ensure t
:init
(global-corfu-mode)
;; :custom
;; (corfu-auto nil)
;; (corfu-min-length 2)
:config
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent)
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify)
(corfu-echo-mode)
(corfu-history-mode)
(corfu-popupinfo-mode))
(with-eval-after-load 'corfu
(define-key corfu-map (kbd "RET") nil))
;; (defun my/dabbrev-capf ()
;; (let* ((bounds (or (bounds-of-thing-at-point 'filename)
;; (bounds-of-thing-at-point 'word)))
;; (start (if bounds (car bounds) (point)))
;; (end (if bounds (cdr bounds) (point)))
;; (current-word (when bounds (buffer-substring-no-properties start end))))
;; (when current-word
;; (list start
;; end
;; (completion-table-dynamic
;; (lambda (prefix)
;; (let ((case-fold-search t)
;; (words-list '()))
;; (save-excursion
;; (goto-char (point-min))
;; (while (re-search-forward (concat "\\<" (regexp-quote prefix) "\\w*") nil t)
;; (let ((found-word (match-string-no-properties 0)))
;; (unless (string= found-word current-word)
;; (push found-word words-list)))))
;; (delete-dups words-list))))))))
(defun buffer-words-completion ()
"Generate completion candidates from words of 4+ characters in the buffer."
(let ((words (list)))
(save-excursion
(goto-char (point-min))
(while (re-search-forward "\\_<[[:alnum:]_\\-]\\{4,\\}\\_>" nil t)
(push (match-string-no-properties 0) words)))
(delete-dups words)))
(defun my/buffer-words-capf ()
"Native CAPF for buffer words (4+ chars)."
(let* ((bounds (bounds-of-thing-at-point 'symbol))
(current-input (when bounds (buffer-substring-no-properties (car bounds) (cdr bounds)))))
(when bounds
(list (car bounds) ; Start position
(cdr bounds) ; End position
(lambda (string pred action)
(complete-with-action
action
(remove current-input (buffer-words-completion)) ; Remove self from completion list
string
pred))
:exclusive 'no)))) ; Allow combining with other CAPFs
(use-package cape
:ensure t
:after corfu
:config
(setq cape-dabbrev-check-other-buffers nil)
;; Default for all buffers
(setq completion-at-point-functions
(list #'tempel-complete ; or #'tempel-expand
#'cape-file))
;; For ALL buffers except elisp and eshell
(add-hook 'after-change-major-mode-hook
(lambda ()
(unless (or (derived-mode-p 'emacs-lisp-mode)
(derived-mode-p 'eshell-mode))
(setq-local completion-at-point-functions
(list #'tempel-complete ; or #'tempel-expand