1 ;; get rid of custom from my init file
2 (setq custom-file "~/.emacs.d/emacs-custom.el")
5 (setq-default abbrev-mode t)
7 (expand-file-name "~/dotsnew/emacs/abbrev_defs"))
9 (defconst op/backup-dir
10 (expand-file-name "backups" user-emacs-directory))
11 (unless (file-exists-p op/backup-dir)
12 (make-directory op/backup-dir))
13 (setq backup-directory-alist `(("." . ,op/backup-dir)))
15 (setq use-dialog-box nil
17 require-final-newline t
21 enable-recursive-minibuffers t
24 next-error-message-highlight t
25 read-minibuffer-restore-windows nil
26 isearch-allow-motion t
27 calc-make-windows-dedicated t
28 user-mail-address "op@omarpolo.com")
30 (setq completion-ignore-case t
31 read-file-name-completion-ignore-case t
32 read-buffer-completion-ignore-case t)
34 (define-key global-map (kbd "C-x C-b") #'ibuffer)
35 (define-key global-map (kbd "M-g i") #'imenu)
38 "Just like `imenu', but always flattened!"
42 (define-key minibuffer-mode-map (kbd "C-w") #'backward-kill-word)
44 (defun op/reverse-other-window ()
45 "Like `other-window', but reverse."
48 (define-key global-map (kbd "C-x O") #'op/reverse-other-window)
50 (define-key global-map (kbd "C-x v f") #'vc-pull)
52 (setq uniquify-buffer-name-style 'forward
53 uniquify-strip-common-suffix t)
55 (setq-default scroll-up-aggressively 0.0
56 scroll-down-aggressively 0.0
57 scroll-preserve-screen-position t
58 next-screen-context-lines 1)
60 (define-key global-map (kbd "M-z") #'zap-up-to-char)
63 (setq whitespace-style '(face trailing)
64 backward-delete-char-untabify-method 'hungry
65 tab-always-indent 'complete
67 sentence-end-double-space t)
68 (setq-default indent-tabs-mode t)
70 (defun op/enable-tabs ()
71 "Enable `indent-tabs-mode' in the current buffer."
73 (setq-local indent-tabs-mode t))
75 (defun op/disable-tabs ()
76 "Disable `indent-tabs-mode' in the current buffer."
78 (setq-local indent-tabs-mode nil))
80 (add-hook 'conf-mode-hook #'op/enable-tabs)
81 (add-hook 'text-mode-hook #'op/enable-tabs)
82 (add-hook 'prog-mode-hook #'op/enable-tabs)
83 (add-hook 'prog-mode-hook #'whitespace-mode)
84 (add-hook 'text-mode-hook #'whitespace-mode)
86 (dolist (hook '(emacs-lisp-mode-hook
88 clojurescript-mode-hook
91 (add-hook hook #'op/disable-tabs))
93 (with-eval-after-load 'log-edit
94 (add-hook 'log-edit-mode #'auto-fill-mode))
96 ;; free the c-z binding
97 (define-key global-map (kbd "C-z") nil)
98 (define-key global-map (kbd "C-z V") #'variable-pitch-mode)
99 (define-key global-map (kbd "C-z n") #'display-line-numbers-mode)
101 (define-key global-map (kbd "M-SPC") #'cycle-spacing)
102 (define-key global-map (kbd "M-u") #'upcase-dwim)
103 (define-key global-map (kbd "M-l") #'downcase-dwim)
104 (define-key global-map (kbd "M-c") #'capitalize-dwim)
106 (let ((font "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1"))
107 (set-frame-font font nil t)
108 (add-to-list 'default-frame-alist `(font . ,font)))
110 ;; fix the emojis too
111 (set-fontset-font t 'emoji '("Noto Emoji" . "iso10646-1")
117 (setq history-delete-duplicates t
119 savehist-save-minibuffer-history t)
120 (electric-pair-mode +1)
122 (define-key global-map (kbd "M-/") #'hippie-expand)
123 (setq hippie-expand-try-functions-list
125 try-expand-dabbrev-all-buffers
126 try-expand-dabbrev-from-kill
127 try-complete-file-name-partially
128 try-complete-file-name
129 try-expand-all-abbrevs
132 try-complete-lisp-symbol-partially
133 try-complete-lisp-symbol))
135 (setq isearch-lazy-count t
136 search-whitespace-regexp ".*?"
137 isearch-allow-scroll 'unlimited)
139 (defun op/buffer-to-side-window (place)
140 "Place the current buffer in the side window at PLACE."
141 (interactive (list (intern
142 (completing-read "Which side: "
143 '(top left right bottom)))))
144 (let ((buf (current-buffer)))
145 (display-buffer-in-side-window
146 buf `((window-height . 0.15)
149 (window-parameters . ((no-delete-other-windows . t)))))
152 (defun op/visit-new-migration-file (name)
153 "Visit a new SQL migration file named after NAME."
154 (interactive "Mname: ")
155 (let* ((name (replace-regexp-in-string " " "-" (string-trim name)))
156 (f (format "%s-%s.sql"
157 (format-time-string "%Y%m%d%H%M")
161 (defun op/fill-or-unfill (fn &optional justify region)
162 "Meant to be an adviced :around `fill-paragraph'.
163 FN is the original `fill-column'. If `last-command' is
164 `fill-paragraph', unfill it, fill it otherwise. Inspired from a
165 post on endless parentheses. Optional argument JUSTIFY and
166 REGION are passed to `fill-paragraph'."
168 (if (eq last-command 'fill-paragraph)
169 (progn (setq this-command nil)
172 (funcall fn justify region)))
173 (advice-add 'fill-paragraph :around #'op/fill-or-unfill)
175 (defmacro op/deftranspose (name scope key doc)
176 "Macro to produce transposition functions.
177 NAME is the function's symbol. SCOPE is the text object to
178 operate on. Optional DOC is the function's docstring.
180 Transposition over an active region will swap the object at
181 mark (region beginning) with the one at point (region end).
183 It can optionally define a key for the defined function in the
184 `global-map' if KEY is passed.
186 Originally from protesilaos' dotemacs."
187 (declare (indent defun))
192 (let ((x (intern (format "transpose-%s" ,scope))))
197 `(define-key global-map (kbd ,key) #',name))))
199 (op/deftranspose op/transpose-lines "lines" "C-x C-t"
200 "Transpose lines or swap over active region.")
202 (op/deftranspose op/transpose-paragraphs "paragraphs" "C-S-t"
203 "Transpose paragraph or swap over active region.")
205 (op/deftranspose op/transpose-sentences "sentences" "C-x M-t"
206 "Transpose sentences or swap over active region.")
208 (op/deftranspose op/transpose-sexps "sexps" "C-M-t"
209 "Transpose sexps or swap over active region.")
211 (op/deftranspose op/transpose-words "words" "M-t"
212 "Transpose words or swap over active region.")
214 (defun op/narrow-or-widen-dwim (p)
215 "Widen if the buffer is narrowed, narrow-dwim otherwise.
216 Dwim means: region, org-src-block, org-subtree or defun,
217 whichever applies first. Narrowing to org-src-blocks actually
218 calls `org-edit-src-code'.
220 With prefix P, don't widen, just narrow even if buffer is already
221 narrowed. With P being -, narrow to page instead of to defun.
223 Taken from endless parentheses."
225 (declare (interactive-only))
226 (cond ((and (buffer-narrowed-p) (not p)) (widen))
228 (narrow-to-region (region-beginning)
230 ((derived-mode-p 'org-mode)
231 ;; `org-edit-src-code' isn't a real narrowing
232 (cond ((ignore-errors (org-edit-src-code) t))
233 ((ignore-errors (org-narrow-to-block) t))
234 (t (org-narrow-to-subtree))))
235 ((eql p '-) (narrow-to-page))
236 (t (narrow-to-defun))))
238 (define-key global-map (kbd "C-c w") #'op/narrow-or-widen-dwim)
240 (with-eval-after-load 'dired
241 (add-hook 'dired-mode-hook #'dired-hide-details-mode)
242 (add-hook 'dired-mode-hook #'dired-omit-mode)
244 (define-key dired-mode-map (kbd "C-c w") #'wdired-change-to-wdired-mode)
247 (setq dired-listing-switches "-lahF"
249 dired-deletion-confirmer #'y-or-n-p
250 dired-do-revert-buffer t))
252 ;; just like telescope!
253 (with-eval-after-load 'diff-mode
254 (define-key diff-mode-map (kbd "M-SPC") #'scroll-down-command))
256 (with-eval-after-load 'elisp-mode
257 (add-hook 'emacs-lisp-mode-hook #'checkdoc-minor-mode)
258 (add-hook 'emacs-lisp-mode-hook #'prettify-symbols-mode)
259 (let ((map emacs-lisp-mode-map))
260 (define-key map (kbd "C-c C-k") #'eval-buffer)
261 (define-key map (kbd "C-c k") #'op/ert-all)
262 (define-key map (kbd "C-c C-z") #'op/ielm-repl)))
264 (with-eval-after-load 'help
265 (add-hook 'help-mode-hook #'visual-line-mode))
268 (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
270 ;; packages that i want to be installed
271 (dolist (pkg '(vc-got pdf-tools eglot nameless sly cider go-mode web-mode
272 lua-mode markdown-mode yaml-mode gemini-mode elfeed
273 form-feed shackle embark consult mct puni))
274 (unless (package-installed-p pkg)
275 (message "Installing %s" pkg)
276 (package-install pkg)))
278 (global-form-feed-mode +1)
280 (add-hook 'text-mode-hook #'puni-mode)
281 (add-hook 'prog-mode-hook #'puni-mode)
282 (define-key puni-mode-map (kbd "C-)") #'puni-slurp-forward)
283 (define-key puni-mode-map (kbd "C-(") #'puni-barf-forward)
285 (setq completion-styles '(basic substring initials flex partial-completion))
287 (require 'consult) ;; some stuff lacks an autoload and i don't want to debug it
289 (cl-loop for (key . func) in '(("C-x :" . consult-complex-command)
290 ("C-x b" . consult-buffer)
291 ("C-x 4 b" . consult-buffer-other-window)
292 ("C-x 5 b" . consult-buffer-other-frame)
293 ("C-x r b" . consult-bookmark)
294 ("C-x p b" . consult-project-buffer)
295 ("M-y" . consult-yank-pop)
296 ("M-g g" . consult-goto-line)
297 ("M-g M-g" . consult-goto-line)
298 ("M-g m" . consult-mark)
299 ("M-g i" . consult-imenu)
300 ("M-s l" . consult-line)
301 ("M-s L" . consult-line-multi)
302 ("M-s m" . consult-multi-occur))
303 do (define-key global-map (kbd key) func))
304 (add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode)
306 (setq completions-detailed t)
307 (mct-minibuffer-mode +1)
310 ;; override the binding for the annoying mct-backward-updir.
311 (define-key mct-minibuffer-local-filename-completion-map
312 (kbd "DEL") #'backward-delete-char)
314 (setq mct-remove-shadowed-file-names t
315 mct-completions-format 'one-column
316 mct-completion-passlist '(Info-goto-node
323 consult-project-buffer
327 (with-eval-after-load 'cider
328 (define-key cider-repl-mode-map (kbd "C-c M-o") #'cider-repl-clear-buffer))
330 (with-eval-after-load 'go-mode
331 (add-hook 'go-mode-hook #'subword-mode))
333 (with-eval-after-load 'eglot
334 (define-key eglot-mode-map (kbd "<f1>") #'eglot-code-actions)
335 (define-key eglot-mode-map (kbd "<f2>") #'eglot-format)
336 (add-to-list 'eglot-ignored-server-capabilites
337 :documentHighlightProvider)
338 (add-to-list 'eglot-server-programs
339 '(c-mode . ("clangd" "--header-insertion=never"))))
341 (add-hook 'emacs-lisp-mode #'nameless-mode)
342 (with-eval-after-load 'nameless
343 (setq nameless-private-prefix t
344 nameless-affect-indentation-and-filling nil)
345 (define-key emacs-lisp-mode-map (kbd "_") #'nameless-insert-name-or-self-insert))
347 (add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
348 (with-eval-after-load 'web-mode
349 (setq web-mode-markup-indent-offset 2
350 web-mode-css-indent-offset 2
351 web-mode-style-padding 0
352 web-mode-enable-engine-detection t)
353 (add-hook 'web-mode-hook #'op/disable-tabs)
355 ;; fix .dir-locals.el
356 (defun op/web-mode-fix-dir-locals ()
357 (when (derived-mode-p major-mode 'web-mode)
358 (web-mode-guess-engine-and-content-type)))
359 (add-hook 'hack-local-variables-hook #'op/web-mode-fix-dir-locals))
361 (with-eval-after-load 'css-mode
362 (add-hook 'css-mode-hook #'op/disable-tabs))
364 (with-eval-after-load 'flymake
365 (define-key prog-mode-map (kbd "C-c ! n") #'flymake-goto-next-error)
366 (define-key prog-mode-map (kbd "C-c ! p") #'flymake-goto-prev-error))
368 (with-eval-after-load 'cc-mode
369 (setq c-basic-offset 8
370 c-default-style "K&R")
371 (dolist (hook '(c-mode-hook c++-mode-hook))
372 (add-hook hook #'abbrev-mode)
373 (add-hook hook #'subword-mode))
374 (defun op/c-indent ()
376 (c-set-offset 'arglist-intro '+)
377 (c-set-offset 'arglist-cont-nonempty '*))
378 (add-hook 'c-mode-hook #'op/c-indent)
380 (defun op/c-add-include (path &optional localp)
381 "Include PATH at the start of the file.
382 If LOCALP is non-nil, the include will be \"local\"."
383 (interactive "Mheader to include: \nP")
386 "^#[ \t]*include[ \t]*\""
387 "^#[ \t]*include[ \t]*<"))
388 (ignore-re "^#include \"compat.h\"")
390 (goto-char (point-min))
391 (while (not (or (and (looking-at re)
392 (not (looking-at ignore-re)))
396 (error "Don't know where to insert the header"))
398 (insert "#include " (if localp "\"\"" "<>"))
401 (move-beginning-of-line 1)
404 (while (and (looking-at re)
407 (sort-lines nil start (point)))))
408 (define-key c-mode-map (kbd "C-c C-a") #'op/c-add-include))
410 (with-eval-after-load 'perl-mode
411 (setq perl-indent-level 8))
413 (with-eval-after-load 'sh-script
414 (setq sh-basic-offset 8
415 sh-indent-after-loop-construct 8
416 sh-indent-after-continuation nil))
420 (setq eshell-hist-ignoredups t)
422 (defun op/eshell-bufname (dir)
423 (concat "*eshell " (expand-file-name dir) "*"))
425 (defun op/eshell (arg)
426 "Run or jump to eshell in current project.
427 If called with prefix argument ARG always create a new eshell
430 (let* ((proj (project-current))
431 (dir (if (and proj (not arg))
434 (default-directory dir)
435 (eshell-buffer-name (let ((name (op/eshell-bufname dir)))
437 (generate-new-buffer name)
440 (define-key global-map (kbd "C-c e") #'op/eshell)
442 (with-eval-after-load 'eshell
443 (setq eshell-save-history-on-exit t
444 eshell-history-size 1024
446 eshell-compl-dir-ignore
447 "\\`\\(\\.\\.?\\|CVS\\|\\.svn\\|\\.git\\|\\.got\\)/\\'")
449 (defun op/eshell-after-cd (&rest _)
450 (rename-buffer (op/eshell-bufname default-directory) t))
452 (advice-add #'eshell/cd :after #'op/eshell-after-cd))
454 (with-eval-after-load 'esh-mode
455 (defun op/clear-eshell ()
457 (let ((inhibit-read-only t))
459 (eshell-send-input)))
461 (define-key eshell-command-map (kbd "M-o") #'op/clear-eshell))
465 (unless (package-installed-p 'sndio)
466 (package-install-file "~/w/sndio.el/sndio.el"))
470 (unless (package-installed-p 'saturn)
471 (package-install-file "~/w/saturn/GUI/saturn.el"))
475 (unless (package-installed-p 'simple-pass)
476 (package-install-file "~/.emacs.d/simple-pass.el"))
477 (define-key global-map (kbd "C-z p") #'simple-pass-copy)
483 (define-key global-map (kbd "C-x w") #'elfeed)
484 (with-eval-after-load 'elfeed
485 (define-key elfeed-show-mode-map (kbd "q") #'delete-window)
486 (define-key elfeed-show-mode-map (kbd "S-SPC") #'scroll-down-command)
487 (define-key elfeed-show-mode-map (kbd "M-SPC") #'scroll-down-command)
488 (setq elfeed-show-entry-switch #'pop-to-buffer
490 '("https://undeadly.org/cgi?action=rss&full=yes&items=10"
491 "http://www.tedunangst.com/flak/rss"
492 "https://www.dragonflydigest.com/feed"
493 "https://www.mirbsd.org/news.rss"
494 "https://www.mirbsd.org/announce.rss"
495 "https://bentsukun.ch/index.xml"
496 "https://drewdevault.com/feed.xml"
497 "https://www.cambus.net/atom.xml"
498 "https://dataswamp.org/~solene/rss.xml"
499 "https://briancallahan.net/blog/feed.xml"
500 "https://www.poolp.org/index.xml"
501 "https://jcs.org/rss"
502 "https://sanctum.geek.nz/arabesque/feed/"
503 "https://tech.toryanderson.com/"
504 "https://alexschroeder.ch/wiki?action=journal;search=-tag:rpg -tag:rsp;lang=en;title=English Diary without RPG Pages"
505 "http://boston.conman.org/bostondiaries.rss"
506 "https://emacsninja.com/feed.atom"
507 "https://bsdly.blogspot.com/feeds/posts/default"
508 "https://crawshaw.io/atom.xml"
509 "https://nullprogram.com/feed/"
510 "http://pragmaticemacs.com/feed/"
511 "https://emacsnotes.wordpress.com/feed/"
512 "https://metaredux.com/feed.xml"
513 "https://emacsredux.com/atom.xml"
514 "https://endlessparentheses.com/atom.xml"
515 "https://www.masteringemacs.org/feed"
516 "https://cestlaz.github.io/rss.xml"
517 "https://utcc.utoronto.ca/~cks/space/blog/?atom"
518 "https://irreal.org/blog/?feed=rss2"
519 "https://jao.io/blog/rss.xml"
520 "https://planet.lisp.org/rss20.xml"
521 "https://insideclojure.org/feed.xml"
522 "https://tech.toryanderson.com/index.xml"
523 "https://vermaden.wordpress.com/feed/"
524 "https://www.arp242.net/feed.xml"
525 "https://tymoon.eu/api/reader/atom"
526 "https://venam.nixers.net/blog/feed.xml"
527 "https://www.omarpolo.com/rss.xml"
528 "https://owarisubs.lacumpa.biz/feed/"
529 "https://asenshi.moe/feed/"
530 "https://godotengine.org/rss.xml"
532 "https://adventofcomputing.libsyn.com/rss"
534 "https://github.com/go-gitea/gitea/releases.atom"
536 "https://nitter.pussthecat.org/NanoRaptor/rss"
538 "https://github.com/yshui/picom/releases.atom"
539 "https://github.com/vslavik/poedit/releases.atom"
540 "https://github.com/TokTok/c-toxcore/releases.atom"
541 "https://github.com/alexander-akhmetov/python-telegram/releases.atom"
542 "https://github.com/paul-nameless/tg/releases.atom"
543 "https://github.com/YACReader/yacreader/releases.atom"
544 "https://github.com/luarocks/luarocks/releases.atom"
545 "https://github.com/okbob/pspg/releases.atom"
546 "https://github.com/taisei-project/taisei/releases.atom"
547 "https://github.com/recp/cglm/releases.atom"
548 "https://github.com/SCons/scons/releases.atom"
549 "https://git.sr.ht/~rjarry/aerc/refs/rss.xml"
551 "https://causal.agency/list/pounce.atom"
553 "https://www.crimsonmagic.me/feed/"
554 "https://fullybookedtls.wordpress.com/feed/"
556 "https://draculadaily.substack.com/feed")))
558 (setq shackle-default-rule nil
560 (let ((repls "\\*\\(cider-repl\\|sly-mrepl\\|ielm\\)")
561 (godot "\\*godot - .*\\*")
562 (vcs "\\*\\(Flymake\\|Package-Lint\\|vc-\\(git\\|got\\) :\\).*")
563 (elfeed "\\*elfeed-entry\\*")
564 (vmd "\\*vmd console .*"))
565 `((compilation-mode :noselect t
568 ("*Async Shell Command*" :ignore t)
575 (occur-mode :select t
578 (diff-mode :select t)
596 (define-key global-map (kbd "M-g e") #'embark-act)
598 (defun op/target-filename+line ()
599 "Target a file with optional line number: file[:number]."
601 (let* ((beg (progn (skip-chars-backward "^[:space:]\n")
603 (end (progn (skip-chars-forward "^[:space:]\n")
605 (str (buffer-substring-no-properties beg end)))
607 (when (and (progn (goto-char beg)
608 (ffap-file-at-point))
609 (string-match ".+\\(:[[:digit:]]+\\)?:?" str))
610 `(file ,str ,beg . ,end))))))
612 (add-to-list 'embark-target-finders #'op/target-filename+line)
614 (defun op/acme-find-file (filename)
615 "Visit FILENAME like `find-file', but also jump to line if provided."
617 (if (not (string-match "\\(.+\\):\\([[:digit:]]+\\)" filename))
619 (let ((path (match-string 1 filename))
620 (line (string-to-number (match-string 2 filename))))
621 (with-current-buffer (find-file path)
622 (goto-line line))))))
624 (define-key embark-file-map (kbd "RET") #'op/acme-find-file)