#+TITLE: Emacs Config
#+AUTHOR: Joseph Ferano
#+PROPERTY: header-args:emacs-lisp :tangle ./init.el
#+STARTUP: overview
#+TOC: true
** Early Init
*** Filename handler alist
Skipping a bunch of regular expression searching in the file-name-handler-alist should improve start time.
#+begin_src emacs-lisp
(defvar default-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)
#+end_src
*** Garbage Collection
The default Garbage Collector is triggered at 800 KB, way too conservative,
let's bump to 512 MB. Garbage collection is a big contributor to startup times.
This fends it off, then is reset later by enabling `gcmh-mode'. Not resetting it
will cause stuttering/freezes.
#+begin_src emacs-lisp :tangle ./early-init.el
;; -*- lexical-binding: t -*-
(setq gc-cons-threshold (expt 2 32))
(add-hook 'emacs-startup-hook
(lambda ()
"Restore defalut values after init."
(setq file-name-handler-alist default-file-name-handler-alist)
(if (boundp 'after-focus-change-function)
(add-function :after after-focus-change-function
(lambda ()
(unless (frame-focus-state)
(garbage-collect))))
(add-hook 'focus-out-hook 'garbage-collect))))
(setq native-comp-async-report-warnings-errors nil)
#+end_src
Disabling these classic visual options during early-init seems to net a 0.04 ms boost in init time
#+begin_src emacs-lisp :tangle ./early-init.el
(setq max-specpdl-size 1200)
(setq max-lisp-eval-depth 800)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(menu-bar-mode -1)
(tooltip-mode -1)
#+end_src
*** Enhancements
Disable package.el, since we will be using elpaca.el. According to the elpaca.el
documentation;
#+BEGIN_SRC emacs-lisp :tangle ./early-init.el
(setq package-enable-at-startup nil)
#+END_SRC
Prioritize old byte-compiled source files over newer sources. It saves us a
little IO time to skip all the mtime checks on each lookup.
#+begin_src emacs-lisp :tangle ./early-init.el
(setq load-prefer-newer nil)
(setq safe-local-variable-values
'((org-src-preserve-indentation . t)
(eval add-hook 'after-save-hook
'(lambda nil
(org-babel-tangle))
nil t)))
;; (setq server-name (format "Emacs-%d" (emacs-pid)))
;; (add-hook 'after-init-hook #'server-start)
#+end_src
Prevent instructions on how to close an emacsclient frame.
#+begin_src emacs-lisp
(setq server-client-instructions nil)
#+end_src
Implicitly resizing the Emacs frame adds to init time. Fonts larger than the
system default can cause frame resizing, which adds to startup time.
#+begin_src emacs-lisp
(setq frame-inhibit-implied-resize t)
#+end_src
Ignore X resources.
#+begin_src emacs-lisp
(advice-add #'x-apply-session-resources :override #'ignore)
#+end_src
*** UTF-8 Support
#+begin_src emacs-lisp :tangle ./early-init.el
(setq default-input-method nil)
(setq utf-translate-cjk-mode nil) ; disable CJK coding/encoding (Chinese/Japanese/Korean characters)
(set-language-environment 'utf-8)
(set-keyboard-coding-system 'utf-8-mac) ; For old Carbon emacs on OS X only
(setq locale-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
#+end_src
Finish up
#+begin_src emacs-lisp
(provide 'early-init)
;;; early-init.el ends here
#+end_src
** COMMENT Elpaca
#+BEGIN_SRC emacs-lisp
;; -*- lexical-binding: t -*-
(defvar elpaca-installer-version 0.3)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
:ref nil
:files (:defaults (:exclude "extensions"))
:build (:not elpaca--activate-package)))
(let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory))
(build (expand-file-name "elpaca/" elpaca-builds-directory))
(order (cdr elpaca-order))
(default-directory repo))
(add-to-list 'load-path (if (file-exists-p build) build repo))
(unless (file-exists-p repo)
(make-directory repo t)
(condition-case-unless-debug err
(if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
((zerop (call-process "git" nil buffer t "clone"
(plist-get order :repo) repo)))
((zerop (call-process "git" nil buffer t "checkout"
(or (plist-get order :ref) "--"))))
(emacs (concat invocation-directory invocation-name))
((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
"--eval" "(byte-recompile-directory \".\" 0 'force)")))
((require 'elpaca))
((elpaca-generate-autoloads "elpaca" repo)))
(kill-buffer buffer)
(error "%s" (with-current-buffer buffer (buffer-string))))
((error) (warn "%s" err) (delete-directory repo 'recursive))))
(unless (require 'elpaca-autoloads nil t)
(require 'elpaca)
(elpaca-generate-autoloads "elpaca" repo)
(load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
(setq elpaca-queue-limit 30)
#+END_SRC
** Package Management
#+begin_src emacs-lisp
;; Also read:
(setq package-archives
'(("elpa" . "https://elpa.gnu.org/packages/")
("elpa-devel" . "https://elpa.gnu.org/devel/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")))
;; Proof-of-concept to install a list of packages
(mapc
(lambda (package)
(unless (package-installed-p package)
(package-install package)))
'(recentf
benchmark-init
;; dashboard
;; ligature
;; hydra
;; multiple-cursors
;; Text Editing
evil
evil-collection
evil-surround
evil-snipe
evil-commentary
evil-goggles
avy
all-the-icons-ibuffer
drag-stuff
;; ace-window
;; Mail
smtpmail
sendmail
age
popper
;; VEMCO
vertico
vertico-posframe
savehist
embark
embark-consult
marginalia
orderless
consult
;; LSP
consult-eglot
consult-lsp
lsp-mode
lsp-ui
flycheck
all-the-icons-completion
;; eat
;; Enhancements
olivetti
helpful
vterm
doom-modeline
undo-fu
undo-fu-session
vundo
which-key
mono-complete
beframe
harpoon
format-all
yasnippet
consult-yasnippet
;; company
;; lsp-mode
;; lsp-ui
;; flycheck
;; Org
org-bullets
org-kanban
org-fancy-priorities
org-roam
org-download
org-transclusion
valign
;; Programming Languages
sly
tuareg
dune
merlin
merlin-eldoc
utop
highlight-quoted
rustic
ob-rust
haskell-mode
clojure-mode
cider
fsharp-mode
go-mode
json-mode
markdown-mode
typescript-mode
elm-mode
gdscript-mode
;; Tools
mu4e-alert
dirvish
restclient
gptel
disaster
magit))
(setopt package-vc-selected-packages
'((dotenv :url "https://github.com/pkulev/dotenv.el")
(indent-bars :url "https://github.com/jdtsmith/indent-bars")
(doom-themes :url "https://github.com/JosephFerano/doom-themes")
(org-timeblock :url "https://github.com/ichernyshovvv/org-timeblock")
(org-roam-ui :url "https://github.com/org-roam/org-roam-ui")
(dape :url "https://github.com/svaante/dape")
(odin-mode :url "https://github.com/mattt-b/odin-mode")
(app-launcher :url "https://github.com/SebastienWae/app-launcher")))
(package-initialize)
#+end_src
** COMMENT Benchmarking
This is commented out since it adds ever so slightly to init time, but keep it around in case we
need to benchmark slow init times later.
#+begin_src emacs-lisp
(require 'benchmark-init)
(add-hook 'after-init-hook 'benchmark-init/deactivate)
#+end_src
** Misc Stuff
#+begin_SRC emacs-lisp
(setq default-directory "/home/joe/")
(setq vc-follow-symlinks t) ; Visit real file when editing a symlink without prompting.
(global-auto-revert-mode t) ; Revert buffer's file when the file changes on disk
(setq confirm-kill-emacs 'y-or-n-p)
(require 'recentf)
(recentf-mode t)
(setq recentf-auto-cleanup 10)
(setq recentf-keep '(file-remote-p file-readable-p))
(setq recentf-max-saved-items 1000)
(setq package-native-compile t)
#+end_src
This avoids those annoying *#backup#* files that get added and eventually slow down loading the file again.
#+begin_src emacs-lisp
(setq auto-save-default nil)
(setq create-lockfiles nil)
#+end_src
#+begin_src emacs-lisp
(setq use-dialog-box nil)
(fset 'yes-or-no-p 'y-or-n-p)
(setq large-file-warning-threshold 100000000)
(setq backup-directory-alist `((".*" . ,(expand-file-name "backups" user-emacs-directory))))
(setq backup-by-copying t
delete-old-versions t
kept-new-versions 6
kept-old-versions 2
version-control t)
#+END_SRC
I don't even know how you resume from GUI mode, we'll find a use for this keybinding later on
#+begin_src emacs-lisp
(when (display-graphic-p)
(global-unset-key (kbd "C-z")))
#+end_src
** Visuals
*** COMMENT Dashboard
Use Dashboard.el. First load `all-the-icons` for nicer rendering
#+begin_src emacs-lisp
;; (elpaca 'all-the-icons)
;; (package-vc-install (dashboard :ref)
(require 'dashboard)
(dashboard-setup-startup-hook)
(setq dashboard-items '((recents . 6)
(projects . 5)
(bookmarks . 5)))
(setq dashboard-startup-banner 'logo)
(setq dashboard-center-content t)
(setq dashboard-set-file-icons t)
(setq dashboard-set-navigator t)
(setq dashboard-set-heading-icons t)
(setq dashboard-projects-backend 'project-el)
(add-hook 'dashboard-mode-hook
(lambda ()
(setq-local line-spacing 12)
;; TODO: It's not jumping
(dashboard-jump-to-recents)))
;; (defun joe/launch-dashboard ()
;; "Jump to the dashboard buffer, if doesn't exists create one."
;; (interactive)
;; (switch-to-buffer dashboard-buffer-name)
;; (dashboard-mode)
;; (dashboard-insert-startupify-lists))
#+end_src
*** Olivetti
#+begin_src emacs-lisp
(require 'olivetti)
(if (equal "flowjoe-f37" (system-name))
(setq olivetti-minimum-body-width 100)
(setq olivetti-minimum-body-width 120))
(global-set-key (kbd "C-x x o") 'olivetti-mode)
(add-hook 'prog-mode-hook 'olivetti-mode)
#+end_src
Remove this hook from Olivetti so that lines can truncate [[https://github.com/rnkn/olivetti/issues/76][Github issue]]
#+begin_src emacs-lisp
(remove-hook 'olivetti-mode-on-hook 'visual-line-mode)
#+end_src
*** Themes
#+begin_src emacs-lisp
;; Small changes to my favorite themes
;; (load-theme 'doom-nord-light))
(setq custom-safe-themes t)
(custom-set-faces
'(dashboard-items-face ((t (:inherit widget-button :weight normal)))))
#+end_src
We want to add whatever custom theme we selected to the custom variables since
emacs will just automatically read it on startup but we don't want emacs to add
these settings to init.el, which in our case is worse since we have a literate
file. Send all custom variables to ~custom.el~
#+begin_src emacs-lisp
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file)
#+end_src
I copied this from consult so that I could add the customize-save-variable at
the end, so if I load another emacs process, it can have the same thing.
#+begin_src emacs-lisp
(defun joe/consult-theme (theme)
"Disable current themes and enable THEME from `consult-themes'.
The command supports previewing the currently selected theme."
(interactive
(list
(let* ((regexp (consult--regexp-filter
(mapcar (lambda (x) (if (stringp x) x (format "\\`%s\\'" x)))
consult-themes)))
(avail-themes (seq-filter
(lambda (x) (string-match-p regexp (symbol-name x)))
(cons 'default (custom-available-themes))))
(saved-theme (car custom-enabled-themes)))
(consult--read
(mapcar #'symbol-name avail-themes)
:prompt "Theme: "
:require-match t
:category 'theme
:history 'consult--theme-history
:lookup (lambda (selected &rest _)
(setq selected (and selected (intern-soft selected)))
(or (and selected (car (memq selected avail-themes)))
saved-theme))
:state (lambda (action theme)
(pcase action
('return (consult-theme (or theme saved-theme)))
((and 'preview (guard theme)) (consult-theme theme))))
:default (symbol-name (or saved-theme 'default))))))
(when (eq theme 'default) (setq theme nil))
(unless (eq theme (car custom-enabled-themes))
(mapc #'disable-theme custom-enabled-themes)
(when theme
(if (custom-theme-p theme)
(enable-theme theme)
(load-theme theme :no-confirm))
))
(customize-save-variable 'custom-enabled-themes custom-enabled-themes))
#+end_src
*** Other
Setup other stuff
#+begin_src emacs-lisp
(setq ring-bell-function 'ignore)
(when (equal "flowjoe-f37" (system-name))
(add-to-list 'default-frame-alist '(undecorated . t))
(add-to-list 'default-frame-alist '(fullscreen . maximized)))
(add-to-list 'default-frame-alist '(vertical-scroll-bars . nil))
;; (setq default-frame-alist '(
;; (vertical-scroll-bars)
;; (right-divider-width . 5)
;; (tab-bar-lines . 0)
;; (internal-border-width . 12)))
(add-hook 'text-mode-hook (lambda () (setq fill-column 80) (turn-on-auto-fill)))
;; (setq display-line-numbers 'relative)
(make-variable-buffer-local 'global-hl-line-mode)
(defun joe/set-display-line-numbers ()
(setq display-line-numbers 'relative))
(add-hook 'prog-mode-hook #'joe/set-display-line-numbers)
;; (defun joe/disable-line-numbers () (display-line-numbers-mode 0))
;; (dolist (mode '( dashboard-mode-hook org-mode-hook term-mode-hook eww-mode-hook eat-mode-hook
;; vterm-mode-hook dirvish-directory-view-mode-hook eshell-mode-hook lsp-ui-doc-frame-mode-hook
;; dired-mode-hook org-agenda-mode-hook shell-mode-hook magit-mode-hook compilation-mode-hook mu4e-headers-mode-hook mu4e-main-mode-hook))
;; (add-hook mode #'joe/disable-line-numbers))
(set-window-margins nil 0)
(setq-default right-fringe-width 10)
(setq scroll-margin 0
scroll-conservatively 100000
scroll-preserve-screen-position 1)
(global-hl-line-mode +1)
(column-number-mode +1)
(modify-all-frames-parameters
'((right-divider-width . 5)
(tab-bar-lines . 0)
(internal-border-width . 12)))
(when (>= emacs-major-version 29)
(pixel-scroll-precision-mode t))
(setq inhibit-startup-screen t)
;; Donβt compact font caches during GC, in case doom modeline gets laggy issue
(setq inhibit-compacting-font-caches t)
(require 'highlight-quoted)
(add-hook 'emacs-lisp-mode-hook 'highlight-quoted-mode)
(require 'doom-modeline)
(doom-modeline-mode)
(doom-modeline-def-modeline 'main
'(bar modals bar window-number matches buffer-info remote-host buffer-position word-count selection-info)
'(parrot objed-state misc-info battery grip irc mu4e gnus github debug repl lsp bar input-method indent-info buffer-encoding bar major-mode process))
;; Show the tab names, just put this at the car of the previous list
;; workspace-name
;; Set default mode-line
(add-hook 'doom-modeline-mode-hook
(lambda ()
(doom-modeline-set-modeline 'main 'default)))
;; TODO Likely not needed anymore
(dolist (mode '(dired-mode-hook lsp-help-mode-hook fundamental-mode-hook))
(add-hook mode (lambda () (setq truncate-lines t))))
#+end_src
Ligatures... are they that useful?
#+begin_src emacs-lisp :tangle no
(require 'ligature)
(global-ligature-mode)
#+end_src
** Random Functions
#+begin_src emacs-lisp
(defun joe/print-digit-as-ascii (digit)
(string-match-p "^\\(?:0\\|[1-9][0-9]*\\)$" digit))
(defun joe/get-ascii-in-region (beg end)
(interactive "r")
(if (use-region-p)
(message "%s"
(concat
(mapcar 'string-to-number
(split-string (buffer-substring-no-properties beg end) " "))))
(message "No region selected")))
(define-key ctl-x-x-map (kbd "a") #'joe/get-ascii-in-region)
#+end_src
** Text
#+begin_src emacs-lisp
;; (set-face-attribute 'default nil :family "Fira Code Nerd Font" :height 150)
(let ((height (if (equal "flowjoe-f37" (system-name))
115
105)))
(set-face-attribute 'default nil :family "JetBrainsMono Nerd Font Mono" :height height))
;; (set-face-attribute 'default nil :family "JetBrainsMono Nerd Font Mono" :height 115)
;; (set-face-attribute 'variable-pitch nil :family "Source Code Pro" :height 120)
(setq-default c-basic-offset 4) ;; This is annoying
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq-default line-spacing 5)
(setq indent-line-function #'indent-relative)
;; (add-hook 'before-save-hook 'whitespace-cleanup)
#+end_src
** Text Editor
Emacs is an great operating system, if only it had a good text editor...
*** COMMENT Hydra
#+begin_src emacs-lisp
(require 'hydra)
(defhydra hydra-navigate (global-map "")
"Window Navigation"
("q" nil "quit")
("d" joe/smooth-scroll-half-page-down "half page down")
("u" joe/smooth-scroll-half-page-up "half page up")
("e" joe/smooth-scroll-short-down "line down")
("y" joe/smooth-scroll-short-up "line up")
("n" next-line "line down")
("p" previous-line "line up")
("M-r" move-to-window-line-top-bottom "Reposition cursor"))
#+end_src
*** Text editing
#+begin_src emacs-lisp
;; TODO Find out what to do with this
(defun joe/bookmark-set-and-save ()
"Save the current buffer as a bookmark"
(interactive)
(bookmark-set)
(bookmark-save))
(global-set-key (kbd "M-z") #'zap-up-to-char)
(global-set-key (kbd "M-Z") #'zap-to-char)
(defun joe/duplicate-line-comment ()
(interactive)
(let ((col (current-column)))
(duplicate-line)
(comment-line 1)
(move-to-column col)))
(global-set-key (kbd "C-w") #'backward-kill-word)
(global-set-key (kbd "C-c d") 'duplicate-line)
(global-set-key (kbd "C-c C-;") 'joe/duplicate-line-comment)
(global-set-key (kbd "M-n") (kbd "C-u 1 C-v"))
(global-set-key (kbd "M-p") (kbd "C-u 1 M-v"))
(defun joe/insert-line-below ()
"Insert an empty line below the current line."
(interactive)
(save-excursion
(end-of-line)
(open-line 1)))
(defun joe/insert-line-above ()
"Insert an empty line above the current line."
(interactive)
(save-excursion
(end-of-line 0)
(open-line 1)))
(global-set-key (kbd "M-o") #'joe/insert-line-below)
(global-set-key (kbd "M-O") #'joe/insert-line-above)
(setq-default truncate-lines t)
#+end_src
Stole this from [[https://github.com/purcell/unfill/][Purcell]] but didn't feel like making it a package depencendy
#+begin_src emacs-lisp
(defun unfill-toggle ()
"Toggle filling/unfilling of the current region.
Operates on the current paragraph if no region is active."
(interactive)
(let (deactivate-mark
(fill-column
(if (eq last-command this-command)
(progn (setq this-command nil)
most-positive-fixnum)
fill-column)))
(call-interactively 'fill-paragraph)))
#+end_src
For the longest time I had no idea why the ~(~ and ~)~ vim motions for sentences
weren't working, until I randomly saw this in someone's init.el
#+begin_src emacs-lisp
(setq sentence-end-double-space nil)
#+end_src
~drag-stuff~ package to move lines around, here's a snippet that re-indents
#+begin_src emacs-lisp
(defun indent-region-advice (&rest ignored)
(let ((deactivate deactivate-mark))
(if (region-active-p)
(indent-region (region-beginning) (region-end))
(indent-region (line-beginning-position) (line-end-position)))
(setq deactivate-mark deactivate)))
(advice-add 'drag-stuff-up :after 'indent-region-advice)
(advice-add 'drag-stuff-down :after 'indent-region-advice)
(define-key prog-mode-map (kbd "M-j") #'drag-stuff-down)
(define-key prog-mode-map (kbd "M-k") #'drag-stuff-up)
#+end_src
Traditional vim indenting with =<<= and =>>= is too slow
#+begin_src emacs-lisp
;; (define-key prog-mode-map (kbd "M-l") 'indent-rigidly-right-to-tab-stop)
;; (define-key prog-mode-map (kbd "M-h") 'indent-rigidly-left-to-tab-stop)
#+end_src
Simple function more quickly align text
#+begin_src emacs-lisp
(defun joe/align-whitespace (beg end)
"Align column text in region by whitespace."
(interactive "r")
(align-regexp beg end "\\(\\s-*\\)\\s-" 1 0 t)
(indent-region beg end))
#+end_src
#+begin_src emacs-lisp
(add-hook 'csv-mode-hook #'csv-header-line)
(add-hook 'csv-mode-hook #'csv-align-mode)
(defun csv-align-visible ()
"Align visible fields."
(interactive)
(csv-align-fields nil (window-start) (window-end)))
(add-hook 'csv-mode-hook
(lambda ()
(define-key csv-mode-map (kbd "C-c C-a") 'csv-align-mode)))
#+end_src
Fill region is great, except when you don't need it...
#+begin_src emacs-lisp
(defun unfill-region (beg end)
"Unfill the region, joining text paragraphs into a single
logical line. This is useful, e.g., for use with
`visual-line-mode'."
(interactive "*r")
(let ((fill-column (point-max)))
(fill-region beg end)))
;; Handy key definition
(define-key global-map (kbd "C-M-q") #'unfill-region)
#+end_src
*** COMMENT Multiple Cursors
#+begin_src emacs-lisp
(require 'multiple-cursors)
#+end_src
*** COMMENT Meow
#+begin_src emacs-lisp
(elpaca 'meow)
(defun meow-setup ()
(setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
(meow-motion-overwrite-define-key
'("j" . meow-next)
'("k" . meow-prev)
'("" . ignore))
(meow-leader-define-key
;; SPC j/k will run the original command in MOTION state.
'("j" . "H-j")
'("k" . "H-k")
;; Use SPC (0-9) for digit arguments.
'("1" . meow-digit-argument)
'("2" . meow-digit-argument)
'("3" . meow-digit-argument)
'("4" . meow-digit-argument)
'("5" . meow-digit-argument)
'("6" . meow-digit-argument)
'("7" . meow-digit-argument)
'("8" . meow-digit-argument)
'("9" . meow-digit-argument)
'("0" . meow-digit-argument)
'("/" . meow-keypad-describe-key)
'("?" . meow-cheatsheet))
(meow-normal-define-key
'("0" . meow-expand-0)
'("9" . meow-expand-9)
'("8" . meow-expand-8)
'("7" . meow-expand-7)
'("6" . meow-expand-6)
'("5" . meow-expand-5)
'("4" . meow-expand-4)
'("3" . meow-expand-3)
'("2" . meow-expand-2)
'("1" . meow-expand-1)
'("-" . negative-argument)
'(";" . meow-reverse)
'("," . meow-inner-of-thing)
'("." . meow-bounds-of-thing)
'("[" . meow-beginning-of-thing)
'("]" . meow-end-of-thing)
'("a" . meow-append)
'("A" . meow-open-below)
'("b" . meow-back-word)
'("B" . meow-back-symbol)
'("c" . meow-change)
'("d" . meow-delete)
'("D" . meow-backward-delete)
'("e" . meow-next-word)
'("E" . meow-next-symbol)
'("f" . meow-find)
'("g" . meow-cancel-selection)
'("G" . meow-grab)
'("h" . meow-left)
'("H" . meow-left-expand)
'("i" . meow-insert)
'("I" . meow-open-above)
'("j" . meow-next)
'("J" . meow-next-expand)
'("k" . meow-prev)
'("K" . meow-prev-expand)
'("l" . meow-right)
'("L" . meow-right-expand)
'("m" . meow-join)
'("n" . meow-search)
'("o" . meow-block)
'("O" . meow-to-block)
'("p" . meow-yank)
'("q" . meow-quit)
'("Q" . meow-goto-line)
'("r" . meow-replace)
'("R" . meow-swap-grab)
'("s" . meow-kill)
'("t" . meow-till)
'("u" . meow-undo)
'("U" . meow-undo-in-selection)
'("v" . meow-visit)
'("w" . meow-mark-word)
'("W" . meow-mark-symbol)
'("x" . meow-line)
'("X" . meow-goto-line)
'("y" . meow-save)
'("Y" . meow-sync-grab)
'("z" . meow-pop-selection)
'("'" . repeat)
'("" . ignore)))
(require 'meow)
(meow-setup)
(meow-global-mode t)
(setq scroll-preserve-screen-position nil)
#+end_src
*** COMMENT expand-region
#+begin_src emacs-lisp
(evil-global-set-key 'insert (kbd "C-M-e") #'er/expand-region)
(evil-global-set-key 'insert (kbd "C-M-p") #'er/mark-inside-pairs)
(evil-global-set-key 'insert (kbd "C-M-f") #'er/mark-method-call)
#+end_src
*** Boon
#+begin_src emacs-lisp :tangle no
(defun joe/psp-scroll-down-half-page ()
(interactive)
(pixel-scroll-precision-scroll-down-page (/ (window-pixel-height) 2)))
(defun joe/psp-scroll-up-half-page ()
(interactive)
(pixel-scroll-precision-scroll-up-page (/ (window-pixel-height) 2)))
(elpaca 'boon
(require 'boon-qwerty)
(boon-mode)
(define-key boon-moves-map "h" 'backward-char)
(define-key boon-moves-map "j" 'next-line)
(define-key boon-moves-map "k" 'previous-line)
(define-key boon-moves-map "l" 'forward-char)
(define-key boon-moves-map "b" 'boon-smarter-backward)
(define-key boon-moves-map "w" 'boon-smarter-forward)
(define-key boon-moves-map "q" '("hop" . avy-goto-char-2))
(define-key boon-command-map (kbd "C-k") #'joe/scroll-down-line)
(define-key boon-command-map (kbd "C-j") #'joe/scroll-up-line)
(define-key boon-command-map (kbd "C-d") #'joe/psp-scroll-down-half-page)
(define-key boon-command-map (kbd "C-u") #'joe/psp-scroll-up-half-page)
(defun joe/scroll-up-line () (interactive) (scroll-up-line 2))
(defun joe/scroll-down-line () (interactive) (scroll-down-line 2))
(define-key boon-moves-map "H" 'backward-paragraph)
(define-key boon-moves-map "L" 'forward-paragraph)
(define-key boon-moves-map "K" 'boon-smarter-upward)
(define-key boon-moves-map "J" 'boon-smarter-downward)
(define-key boon-moves-map "o" 'boon-open-next-line-and-insert)
(define-key boon-moves-map "O" 'boon-open-line-and-insert)
(define-key boon-moves-map "i" 'boon-set-insert-like-state)
(define-key boon-moves-map "r" 'boon-replace-by-character)
(define-key boon-moves-map "y" 'boon-replace-by-character)
(define-key boon-moves-map "p" 'boon-splice)
(define-key boon-moves-map "y" 'boon-treasure-region)
(define-key ctl-x-map "s" 'save-buffer))
#+end_src
*** Evil
#+begin_src emacs-lisp
(setq evil-want-keybinding nil)
(setq evil-undo-system 'undo-fu)
(setq evil-want-C-u-scroll t)
(setq evil-want-Y-yank-to-eol t)
(setq evil-disable-insert-state-bindings t)
(setq evil-echo-state nil)
(require 'evil)
(evil-mode t)
;; (evil-global-set-key 'insert (kbd "C-w") #'evil-delete-backward-word)
;; vv to expand selection to line
(evil-global-set-key 'visual
(kbd "v")
(lambda ()
(interactive)
(evil-first-non-blank)
(exchange-point-and-mark)
(evil-end-of-line)))
(defvar joe/evil-space-mode-map (make-sparse-keymap)
"High precedence keymap.")
(define-minor-mode joe/evil-space-mode
"Global minor mode for higher precedence evil keybindings."
:global t)
(joe/evil-space-mode)
(dolist (state '(normal visual insert))
(evil-make-intercept-map
;; NOTE: This requires an evil version from 2018-03-20 or later
(evil-get-auxiliary-keymap joe/evil-space-mode-map state t t)
state))
(evil-define-key 'normal joe/evil-space-mode-map
(kbd "SPC t") tab-prefix-map
(kbd "SPC p") project-prefix-map
(kbd "SPC q") 'kill-buffer-and-window
(kbd "SPC h") 'help-command
(kbd "SPC hf") 'helpful-callable
(kbd "SPC hv") 'helpful-variable
(kbd "SPC hk") 'helpful-key
(kbd "SPC ho") 'helpful-symbol
(kbd "SPC hg") 'helpful-at-point
(kbd "SPC fb") 'bookmark-jump
(kbd "SPC fr") 'consult-recent-file
(kbd "SPC ff") 'project-find-file
(kbd "SPC fa") '(lambda () (interactive) (project-find-file t))
(kbd "SPC fi") 'joe/edit-init
(kbd "SPC bl") 'mode-line-other-buffer
(kbd "SPC ba") 'switch-to-buffer
(kbd "SPC bb") 'consult-buffer
(kbd "SPC bi") 'ibuffer
(kbd "SPC bu") 'recentf-open-most-recent-file
(kbd "SPC bm") 'joe/toggle-buffer-mode
(kbd "SPC br") 'joe/revert-buffer-no-confirm
(kbd "SPC gg") 'magit-status
(kbd "SPC gc") 'magit-clone
;; (kbd "SPC ss") 'eat
;; (kbd "SPC sv") 'eat-other-window
(kbd "SPC ss") 'joe/vterm-here
(kbd "SPC sv") 'vterm-other-window
(kbd "SPC Ba") 'joe/bookmark-set-and-save
(kbd "SPC Bd") 'bookmark-delete
(kbd "SPC mr") 'joe/compile-run
(kbd "SPC mc") 'joe/compile-comp
(kbd "SPC mm") 'mu4e
(kbd "SPC ct") 'joe/consult-theme
(kbd "SPC cl") 'consult-line
(kbd "SPC ci") 'consult-imenu
(kbd "SPC cy") 'consult-yank-from-kill-ring
(kbd "SPC cg") 'consult-ripgrep
(kbd "SPC cF") 'consult-find
(kbd "SPC co") 'consult-outline)
(define-key evil-window-map "u" #'winner-undo)
(define-key evil-window-map "U" #'winner-redo)
(defvar joe-mode-map
(let ((map (make-sparse-keymap)))
;; (define-key map (kbd "C-'") #'embark-act)
map)
"my-keys-minor-mode keymap.")
(evil-global-set-key 'normal (kbd "'") #'evil-goto-mark)
(define-key joe/evil-space-mode-map (kbd "M-'") #'embark-dwim)
(define-key joe/evil-space-mode-map (kbd "C-'") #'embark-act)
(define-key joe/evil-space-mode-map (kbd "C-/") #'comment-line)
(defun joe/scroll-up-line () (interactive) (scroll-up-line 2))
(defun joe/scroll-down-line () (interactive) (scroll-down-line 2))
(evil-global-set-key 'normal (kbd "C-e") #'joe/scroll-up-line)
(evil-global-set-key 'normal (kbd "C-y") #'joe/scroll-down-line)
(require 'evil-collection)
(evil-collection-init)
(require 'evil-surround)
(global-evil-surround-mode t)
(require 'evil-snipe)
(evil-snipe-override-mode +1)
(require 'evil-commentary)
(evil-commentary-mode t)
(require 'evil-goggles)
(evil-goggles-mode t)
(setq evil-goggles-duration 0.075)
(setq evil-goggles-pulse t)
(setq evil-goggles-async-duration 0.55)
#+end_src
** Buffers
#+begin_src emacs-lisp
(defun joe/kill-this-buffer-or-popup ()
(interactive)
"Kill the buffer normally, but if it's a popper popup, call the popper version"
(with-current-buffer (current-buffer)
(if (or (eq popper-popup-status nil)
(eq popper-popup-status 'raised))
(kill-this-buffer)
(popper-kill-latest-popup))))
(global-set-key (kbd "C-x k") #'joe/kill-this-buffer-or-popup)
(when (boundp 'evil-mode)
(evil-define-key 'normal joe/evil-space-mode-map
(kbd "SPC k") #'joe/kill-this-buffer-or-popup))
(global-set-key (kbd "C-x M-k") #'kill-buffer)
(require 'all-the-icons-ibuffer)
(add-hook 'ibuffer-mode-hook #'all-the-icons-ibuffer-mode)
(global-set-key [remap list-buffers] 'ibuffer)
(global-set-key (kbd "C-x B") 'ibuffer)
(global-set-key (kbd "C-x b") 'consult-project-buffer)
(global-set-key (kbd "C-x C-b") 'consult-buffer)
(defun joe/switch-other-buffer ()
"Switch to other buffer"
(interactive)
(switch-to-buffer (other-buffer)))
(global-set-key (kbd "C-x l") 'joe/switch-other-buffer)
(save-place-mode t)
(setq save-place-file (expand-file-name "places" user-emacs-directory))
#+end_src
The theme of `C-x 4` bindings is that they operate on other windows, so this function matches that behavior.
#+begin_src emacs-lisp
(defun joe/kill-other-buffer-and-window ()
"Kill other buffer and window"
(interactive)
(other-window 1)
(kill-buffer-and-window))
(global-set-key (kbd "C-x 4 0") 'joe/kill-other-buffer-and-window)
(global-set-key (kbd "C-x C-0") 'kill-buffer-and-window)
#+end_src
Harpoon lets you quickly switch between bookmarked buffers
#+begin_src emacs-lisp
(global-set-key (kbd "C-c h ") 'harpoon-add-file)
;; And the vanilla commands
(global-set-key (kbd "C-c h f") 'harpoon-toggle-file)
(global-set-key (kbd "C-c h h") 'harpoon-toggle-quick-menu)
(global-set-key (kbd "C-c h c") 'harpoon-clear)
(global-set-key (kbd "M-1") 'harpoon-go-to-1)
(global-set-key (kbd "M-2") 'harpoon-go-to-2)
(global-set-key (kbd "M-3") 'harpoon-go-to-3)
(global-set-key (kbd "M-4") 'harpoon-go-to-4)
(global-set-key (kbd "M-5") 'harpoon-go-to-5)
(global-set-key (kbd "C-c h 1") 'harpoon-go-to-1)
(global-set-key (kbd "C-c h 2") 'harpoon-go-to-2)
(global-set-key (kbd "C-c h 3") 'harpoon-go-to-3)
(global-set-key (kbd "C-c h 4") 'harpoon-go-to-4)
(global-set-key (kbd "C-c h 5") 'harpoon-go-to-5)
(global-set-key (kbd "C-c h 6") 'harpoon-go-to-6)
(global-set-key (kbd "C-c h 7") 'harpoon-go-to-7)
(global-set-key (kbd "C-c h 8") 'harpoon-go-to-8)
(global-set-key (kbd "C-c h 9") 'harpoon-go-to-9)
#+end_src
** Windows
*** Window Management
#+begin_src emacs-lisp
(add-hook 'after-init-hook (lambda () (winner-mode t)))
(setq joe/popper-side-toggle 'right)
(defun joe/window-split-vertical () (interactive) (set 'joe/popper-side-toggle 'right))
(defun joe/window-split-horizontal () (interactive) (set 'joe/popper-side-toggle 'below))
(define-key ctl-x-4-map (kbd "|") #'joe/window-split-vertical)
(define-key ctl-x-4-map (kbd "-") #'joe/window-split-horizontal)
(define-key ctl-x-4-map (kbd "t") #'rotate-window)
(defun joe/qtile-move-dir (dir)
(start-process "qtile-dir" nil "qtile" "cmd-obj" "-o" "layout" "-f" dir))
;; (start-process "qtile-dir" nil "qtile" "cmd-obj" "-o" "layout" "-f" "right"))
(defun joe/windmove-left ()
(interactive)
(if (window-in-direction 'left)
(windmove-left)
(joe/qtile-move-dir "left")))
(defun joe/windmove-right ()
(interactive)
(if (window-in-direction 'right)
(windmove-right)
(joe/qtile-move-dir "right")))
(defun joe/windmove-up ()
(interactive)
(if (window-in-direction 'up)
(windmove-up)
(joe/qtile-move-dir "up")))
(defun joe/windmove-down ()
(interactive)
(if (window-in-direction 'down)
(windmove-down)
(joe/qtile-move-dir "down")))
;; (global-set-key (kbd "s-h") #'joe/windmove-left)
;; (global-set-key (kbd "s-l") #'joe/windmove-right)
;; (global-set-key (kbd "s-k") #'joe/windmove-up)
;; (global-set-key (kbd "s-j") #'joe/windmove-down)
(global-set-key (kbd "s-h") #'windmove-left)
(global-set-key (kbd "s-l") #'windmove-right)
(global-set-key (kbd "s-k") #'windmove-up)
(global-set-key (kbd "s-j") #'windmove-down)
;; There's a bug in Gnome where frames are resized to the wrong dimensions
(defun joe/resize-frames ()
(interactive)
(dolist (frame (frame-list))
(toggle-frame-maximized frame)
(toggle-frame-maximized frame)))
(global-set-key (kbd "s-") #'joe/resize-frames)
#+end_src
**** COMMENT Unused for now
Ace Window will show a hint if there are more than 2 windows, but I don't really use it
#+begin_src emacs-lisp
(require 'ace-window)
(global-set-key (kbd "C-x o") #'ace-window)
(global-set-key (kbd "C-x C-o") #'ace-swap-window)
#+end_src
#+begin_src emacs-lisp
;; TODO Prot help improving workflow
(global-set-key (kbd "C-`") #'window-toggle-side-windows)
(defvar joe/side-window-buffers '("^\\*Flycheck errors\\*$"
"^\\*Completions\\*$"
"^\\*Occur\\*$"
"^\\*Help\\*$"
"^\\*Embark.*"
"^\\*helpful .*"
"^\\*Compilation\\*$"
"^\\*SQL.*"
"^\\*HTTP Response\\*$"
"^\\*grep\\*$"
"^\\*Colors\\*$"
"^\\*Async Shell Command\\*$"))
(dolist (bufname joe/side-window-buffers)
(add-to-list 'display-buffer-alist
`(,bufname
display-buffer-in-side-window
(side . right)
(window-width . 0.43)
(slot . 0)
(window-parameters
(no-delete-other-windows . t)))))
#+end_src
*** Beframe
#+begin_src emacs-lisp
(defvar consult-buffer-sources)
(declare-function consult--buffer-state "consult")
(with-eval-after-load 'consult
(defface beframe-buffer
'((t :inherit font-lock-string-face))
"Face for `consult' framed buffers.")
;; If you want to filter the current buffer you can use this and replace :items in the
;; beframe-consult-source var
;; (defun joe/consult-beframe-names-minus-current ()
;; (delete (buffer-name) (beframe-buffer-names)))
;; :items ,#'joe/consult-beframe-names-minus-current
(defvar beframe-consult-source
`( :name "Frame-specific buffers (current frame)"
:narrow ?F
:category buffer
:face beframe-buffer
:history beframe-history
:items ,#'beframe-buffer-names
:action ,#'switch-to-buffer
:state ,#'consult--buffer-state))
(add-to-list 'consult-buffer-sources 'beframe-consult-source))
(setq beframe-create-frame-scratch-buffer nil)
(beframe-mode +1)
(defun joe/beframe-switch-and-unassume ()
(interactive)
(let ((curr (current-buffer)))
(mode-line-other-buffer)
(beframe-unassume-current-frame-buffers-selectively (list curr))))
(evil-global-set-key 'normal (kbd "SPC b f") #'joe/beframe-switch-and-unassume)
#+end_src
*** Popper
#+begin_src emacs-lisp
(require 'popper)
(setq popper-reference-buffers
'("\\*compilation\\*" compilation-mode
"^\\*vterm\\*" vterm-mode
"^\\*Flymake.*" flymake-mode
"^\\*Flycheck.*" flycheck-error-list-mode
"^\\*Occur\\*$" occur-mode
"^\\*lsp-help\\*" lsp-help-mode
"^\\*eldoc\\*" special-mode
"^\\*godot.*" godot-mode
"^\\*ert\\*" ert-results-mode
"^\\*xref\\*" xref-mode
"^\\*HTTP Response\\*" javascript-mode
"^\\*SQL.*" sql-interactive-mode
"^\\*cargo-test\\*" cargo-test-mode
"^\\*cargo-run\\*" cargo-run-mode
"^\\*rustic-compilation\\*" rustic-compilation-mode
"^\\*ansi-term\\*$" term-mode
;; "^\\*Async Shell Command\\*$" shell-mode
"^\\*Async Shell Command\\*$"
("^\\*Warnings\\*$" . hide)
help-mode
helpful-mode))
(global-set-key (kbd "C-`") 'popper-toggle-latest)
(global-set-key (kbd "C-~") 'popper-cycle)
(global-set-key (kbd "C-M-`") 'popper-toggle-type)
(require 'popper-echo)
(popper-echo-mode t)
(defun joe/get-popper-dir ()
(with-current-buffer (current-buffer)
(if (or (> (window-width) 170) (eq olivetti-mode t))
joe/popper-side-toggle
'below)))
;; TODO Prot We need to revisit this function and change the rules; I
;; just saw that Popper uses side-window for it's internal popup at
;; the bottom function. I prefer the way Popper does it for anything
;; that is going to display horizontally at the bottom. However for
;; side splits I much prefer the functionality we have here where if
;; there's a window on the right, then overtake it and then when I
;; close it, keep whatever window was there open. The issue with
;; side-windows is that they keep the two window splits in tact and
;; that's not what I want. Sort of like how Magit does it, that's what
;; I want to copy. But like I have it here, it should respect my
;; "popper side" variable so I can toggle whether it appears at the
;; bottom or above.
(defun joe/popper-display-func (buffer &optional _alist)
(cond
((eq joe/popper-side-toggle 'below)
(popper-select-popup-at-bottom buffer _alist))
((when-let ((popup-buffer
(cl-find-if
#'popper-popup-p
(mapcar #'window-buffer (window-list)))))
(window--display-buffer
buffer (get-buffer-window popup-buffer) 'reuse
`((body-function . ,#'select-window)))))
((and (eq joe/popper-side-toggle 'right)
(window-in-direction 'left))
(window--display-buffer
buffer (get-buffer-window (current-buffer)) 'reuse))
((when-let ((right-window (window-in-direction 'right))
((eq joe/popper-side-toggle 'right)))
(window--display-buffer
buffer right-window 'reuse
`((body-function . ,#'select-window)))))
((and (not (window-in-direction 'right))
(not (window-in-direction 'left)))
(display-buffer-in-direction
buffer
`((window-height . 0.45)
(window-width . 0.45)
(direction . right)
(body-function . ,#'select-window))))
(t
(display-buffer-in-direction
buffer
`((window-height . 0.45)
(window-width . 0.45)
(direction . below)
(body-function . ,#'select-window))))))
(setq popper-display-function #'joe/popper-display-func)
(popper-mode t)
#+end_src
*** COMMENT Tab-bar & Tab-line
#+begin_src emacs-lisp
(global-set-key (kbd "s-n") #'tab-line-switch-to-next-tab)
(global-set-key (kbd "s-p") #'tab-line-switch-to-prev-tab)
#+end_src
*** COMMENT Scrolling
#+begin_src emacs-lisp
(require 'pixel-scroll)
(setq pixel-scroll-precision-large-scroll-height 10.0)
(setq pixel-scroll-precision-interpolation-factor 30)
(defun joe/pixel-scroll-lerp (amount direction)
(let ((half-height (* direction (/ (window-height) amount)))
(point-min-or-max (if (> direction 0) (point-min) (point-max))))
(when (or (and (pos-visible-in-window-p (point-min))
(< direction 0)))
(pixel-scroll-precision-interpolate (* 5 half-height)))
(pixel-scroll-precision-interpolate (* 5 half-height))))
(defun joe/smooth-scroll-half-page-down ()
"Smooth scroll down"
(interactive)
(joe/pixel-scroll-lerp 1.5 -1))
;; (pixel-scroll-kbd-up))
(defun joe/smooth-scroll-half-page-up ()
"Smooth scroll up"
(interactive)
(joe/pixel-scroll-lerp 1.5 1))
(defun joe/smooth-scroll-short-down ()
"Smooth scroll down"
(interactive)
(joe/pixel-scroll-lerp 8 -1))
(defun joe/smooth-scroll-short-up ()
"Smooth scroll down"
(interactive)
(joe/pixel-scroll-lerp 8 1))
;; scroll-up-command
(global-set-key (kbd "C-v") #'joe/smooth-scroll-half-page-down)
(global-set-key (kbd "M-v") #'joe/smooth-scroll-half-page-up)
(global-set-key (kbd "C-S-v") #'joe/smooth-scroll-short-down)
(global-set-key (kbd "M-S-v") #'joe/smooth-scroll-short-up)
(defun joe/scroll-other-half-down ()
(interactive)
(scroll-other-window 8))
(defun joe/scroll-other-half-up ()
(interactive)
(scroll-other-window -8))
(global-set-key (kbd "C-M-v") #'joe/scroll-other-half-down)
(global-set-key (kbd "C-M-S-V") #'joe/scroll-other-half-up)
(require 'topspace)
#+end_src
** Tabs/Workspaces
*** COMMENT Centaur Tabs
#+begin_src emacs-lisp
;; (require 'centaur-tabs)
(setq centaur-tabs-set-bar 'under)
(setq x-underline-at-descent-line t)
(setq centaur-tabs-set-close-button nil)
(setq centaur-tabs-set-icons t)
(setq centaur-tabs-show-navigation-buttons nil)
(setq centaur-tabs-set-close-button nil)
(setq centaur-tabs-set-modified-marker nil)
(setq centaur-tabs-show-new-tab-button nil)
(setq centaur-tabs-label-fixed-length 16)
(defun joe/forward-tab-or-popup ()
(interactive)
(if (popper-popup-p (current-buffer))
(popper-cycle)
(centaur-tabs-forward-tab)))
(defun joe/backward-tab-or-popup ()
(interactive)
(if (popper-popup-p (current-buffer))
(popper-cycle)
(centaur-tabs-backward-tab)))
;; (global-set-key (kbd "s-n") #'centaur-tabs-forward-tab)
;; (global-set-key (kbd "s-p") #'centaur-tabs-backward-tab)
(global-set-key (kbd "s-n") #'joe/forward-tab-or-popup)
(global-set-key (kbd "s-p") #'joe/backward-tab-or-popup)
(global-set-key (kbd "s-N") #'centaur-tabs-forward-group)
(global-set-key (kbd "s-P") #'centaur-tabs-backward-group)
(global-set-key (kbd "C-s-p") #'centaur-tabs-move-current-tab-to-left)
(global-set-key (kbd "C-s-n") #'centaur-tabs-move-current-tab-to-right)
(dolist (mode '(dashboard-mode-hook))
(add-hook mode 'centaur-tabs-local-mode))
(with-eval-after-load 'centaur-tabs
(defun joe/fix-centaur-tabs (THEME)
(centaur-tabs-mode -1)
(centaur-tabs-mode))
(advice-add 'consult-theme :after #'joe/fix-centaur-tabs)
(defun centaur-tabs-buffer-groups ()
"`centaur-tabs-buffer-groups' control buffers' group rules.
Group centaur-tabs with mode if buffer is derived from `eshell-mode' `emacs-lisp-mode' `dired-mode' `org-mode' `magit-mode'.
All buffer name start with * will group to \"Emacs\".
Other buffer group by `centaur-tabs-get-group-name' with project name."
(list
(cond
((or (derived-mode-p 'comint-mode)
(derived-mode-p 'sql-interactive-mode)
(string-match "*HTTP" (buffer-name))
(derived-mode-p 'compilation-mode))
"REPLs")
((or (and (string-equal "*" (substring (buffer-name) 0 1))
(not (string-match "*Org Src" (buffer-name))))
(memq major-mode '(magit-process-mode
magit-status-mode
magit-diff-mode
magit-log-mode
magit-file-mode
magit-blob-mode
magit-blame-mode)))
"*Buffers*")
(t
"Emacs")))))
(centaur-tabs-mode +1)
#+end_src
*** COMMENT iflipb
#+begin_src emacs-lisp
(global-set-key (kbd "s-n") #'iflipb-next-buffer)
(global-set-key (kbd "s-p") #'iflipb-previous-buffer)
(setq iflipb-permissive-flip-back t)
(setq iflipb-other-buffer-template " %s ")
(setq iflipb-current-buffer-template "<[%s]>")
(setq iflipb-buffer-list-function #'tabspaces--buffer-list)
#+end_src
*** COMMENT Tabspaces
#+begin_src emacs-lisp
(tabspaces-mode +1)
;; Filter Buffers for Consult-Buffer
(with-eval-after-load 'consult
;; hide full buffer list (still available with "b" prefix)
(consult-customize consult--source-buffer :hidden t :default nil)
;; set consult-workspace buffer list
(defvar consult--source-workspace
(list :name "Workspace Buffers"
:narrow ?w
:history 'buffer-name-history
:category 'buffer
:state #'consult--buffer-state
:default t
:items (lambda () (consult--buffer-query
:predicate #'tabspaces--local-buffer-p
:sort 'visibility
:as #'buffer-name)))
"Set workspace buffer list for consult-buffer.")
(add-to-list 'consult-buffer-sources 'consult--source-workspace))
#+end_src
*** COMMENT Tabs
#+begin_src emacs-lisp
;; (setq tab-bar-mode t)
;; (setq tab-bar-show nil)
;; (global-set-key (kbd "M-1") '(lambda () (interactive) (tab-bar-select-tab 1)))
;; (global-set-key (kbd "M-2") '(lambda () (interactive) (tab-bar-select-tab 2)))
;; (global-set-key (kbd "M-3") '(lambda () (interactive) (tab-bar-select-tab 3)))
;; (global-set-key (kbd "M-4") '(lambda () (interactive) (tab-bar-select-tab 4)))
;; (global-set-key (kbd "M-5") '(lambda () (interactive) (tab-bar-select-tab 5)))
;; (global-set-key (kbd "M-6") '(lambda () (interactive) (tab-bar-select-tab 6)))
;; (global-set-key (kbd "M-7") '(lambda () (interactive) (tab-bar-select-tab 7)))
;; (global-set-key (kbd "M-8") '(lambda () (interactive) (tab-bar-select-tab 8)))
;; (global-set-key (kbd "M-9") '(lambda () (interactive) (tab-bar-select-tab 9)))
;; (evil-global-set-key 'insert (kbd "M-1") '(lambda () (interactive) (tab-bar-select-tab 1)))
;; (evil-global-set-key 'insert (kbd "M-2") '(lambda () (interactive) (tab-bar-select-tab 2)))
;; (evil-global-set-key 'insert (kbd "M-3") '(lambda () (interactive) (tab-bar-select-tab 3)))
;; (evil-global-set-key 'insert (kbd "M-4") '(lambda () (interactive) (tab-bar-select-tab 4)))
;; (evil-global-set-key 'insert (kbd "M-5") '(lambda () (interactive) (tab-bar-select-tab 5)))
;; (evil-global-set-key 'insert (kbd "M-6") '(lambda () (interactive) (tab-bar-select-tab 6)))
;; (evil-global-set-key 'insert (kbd "M-7") '(lambda () (interactive) (tab-bar-select-tab 7)))
;; (evil-global-set-key 'insert (kbd "M-8") '(lambda () (interactive) (tab-bar-select-tab 8)))
;; (evil-global-set-key 'insert (kbd "M-9") '(lambda () (interactive) (tab-bar-select-tab 9)))
;; (evil-global-set-key 'normal (kbd "M-1") '(lambda () (interactive) (tab-bar-select-tab 1)))
;; (evil-global-set-key 'normal (kbd "M-2") '(lambda () (interactive) (tab-bar-select-tab 2)))
;; (evil-global-set-key 'normal (kbd "M-3") '(lambda () (interactive) (tab-bar-select-tab 3)))
;; (evil-global-set-key 'normal (kbd "M-4") '(lambda () (interactive) (tab-bar-select-tab 4)))
;; (evil-global-set-key 'normal (kbd "M-5") '(lambda () (interactive) (tab-bar-select-tab 5)))
;; (evil-global-set-key 'normal (kbd "M-6") '(lambda () (interactive) (tab-bar-select-tab 6)))
;; (evil-global-set-key 'normal (kbd "M-7") '(lambda () (interactive) (tab-bar-select-tab 7)))
;; (evil-global-set-key 'normal (kbd "M-8") '(lambda () (interactive) (tab-bar-select-tab 8)))
;; (evil-global-set-key 'normal (kbd "M-9") '(lambda () (interactive) (tab-bar-select-tab 9)))
#+end_src
*** Frames
#+begin_src emacs-lisp
(undelete-frame-mode)
(defun joe/select-frame ()
(interactive)
(let* ((frames (mapcar
(lambda (f) (cons (substring-no-properties
(cdr (assoc 'name (frame-parameters f))))
f))
(frame-list)))
(selected-frame-name (completing-read "Select Frame: " (mapcar #'car frames)))
(selected-frame (alist-get selected-frame-name frames "" nil 'string-equal)))
(select-frame-set-input-focus selected-frame)))
(define-key 'ctl-x-5-prefix (kbd "RET") #'joe/select-frame)
#+end_src
** Projects
Basic enhancements to project.el
#+begin_src emacs-lisp
(with-eval-after-load 'project
(setq project-vc-ignores '("target/" "bin/" "obj/"))
(add-to-list 'project-switch-commands '(magit-project-status "Magit" ?m))
;; TODO: This doesn't start in the correct working directory
(add-to-list 'project-switch-commands '(joe/vterm-here "VTerm" ?s))
(add-to-list 'project-vc-extra-root-markers ".dir-locals.el"))
#+end_src
These are functions to load a project specific file given the conventions I use.
#+begin_src emacs-lisp
(defun joe/project-open-project-file (FILENAME)
(when (project-current)
(let* ((proj-dir (project-root (project-current t)))
(file-path (expand-file-name FILENAME proj-dir)))
(if (file-exists-p file-path)
(find-file-other-window (expand-file-name FILENAME proj-dir))
(when (get-buffer FILENAME)
(switch-to-buffer (get-buffer FILENAME)))))))
(defun joe/project-open-project-todo ()
(interactive)
(joe/project-open-project-file "TODO.org"))
(defun joe/project-open-project-hours ()
(interactive)
(joe/project-open-project-file "HOURS.org"))
(defun joe/project-open-project-notes ()
(interactive)
(joe/project-open-project-file "NOTES.org"))
(defun joe/project-open-project-readme ()
(interactive)
(let* ((project (project-current))
(root (project-root project))
(README (cond ((file-exists-p (expand-file-name "README.org" root)) "README.org")
((file-exists-p (expand-file-name "README.md" root)) "README.md")
((file-exists-p (expand-file-name "README" root)) "README")
(t nil))))
(when README
(joe/project-open-project-file README))))
(defun joe/project-open-project-license ()
(interactive)
(joe/project-open-project-file "LICENSE"))
(defun joe/project-dirvish-dwim ()
(interactive)
(when (project-current)
(dirvish-dwim (project-root (project-current)))))
(evil-define-key 'normal joe/evil-space-mode-map (kbd "_") #'joe/project-dirvish-dwim)
(define-key project-prefix-map "t" #'joe/project-open-project-todo)
(define-key project-prefix-map "h" #'joe/project-open-project-hours)
(define-key project-prefix-map "n" #'joe/project-open-project-notes)
(define-key project-prefix-map "r" #'joe/project-open-project-readme)
(define-key project-prefix-map "l" #'joe/project-open-project-license)
(define-key project-prefix-map "s" #'eat-project)
(define-key project-prefix-map "S" #'eat-project-other-window)
;; Remape this
(define-key project-prefix-map "C-r" #'project-query-replace-regexp)
#+end_src
If you want to try and incorporate something a bit more robust, try this
#+begin_src emacs-lisp :tangle no
(defun +project-todo ()
"Edit the to-do file at the root of the current project."
(interactive)
(let* ((project (project-root (project-current t)))
(todo (car (directory-files project t "^TODO\\(\\..*\\)?$"))))
(cond ((and todo (file-exists-p todo)) (find-file todo))
(t (message "Project does not contain a TODO file.")))))
#+end_src
Stuff to immediately switch to Jetbrains for debugging
#+begin_src emacs-lisp
(global-set-key (kbd "C-M-r") #'joe/open-in-rider)
(defun joe/raise-frame-hook ()
(select-frame-set-input-focus (selected-frame)))
(add-hook 'server-switch-hook #'joe/raise-frame-hook)
(add-hook 'server-switch-hook #'raise-frame)
(defun joe/open-in-rider ()
(interactive)
(shell-command
(mapconcat #'shell-quote-argument
(list "rider.sh"
"--line"
(int-to-string (line-number-at-pos))
"--column"
(int-to-string (current-column))
buffer-file-name)
" ")))
#+end_src
** VEMCO
*** Vertico
#+begin_src emacs-lisp
(require 'all-the-icons-completion)
;; (require '(vertico :files (:defaults "extensions/*")
;; :includes (vertico-indexed
;; vertico-repeat
;; vertico-directory))
(require 'vertico)
(vertico-mode)
;; (elpaca 'vertico-posframe)
;; (define-key vertico-map (kbd "C-w") #'vertico-directory-delete-word)
;; (define-key vertico-map (kbd "C-r") #'vertico-repeat-)
;; (vertico-posframe-mode t)
;; (vertico-indexed-mode)
;; (setq vertico-posframe-parameters
;; '((left-fringe . 100)
;; (right-fringe . 100)))
;; (setq vertico-posframe-border-width 5)
;; (setq vertico-posframe-min-height 20)
(defun posframe-poshandler-slightly-below-top (info)
(cons (/ (- (plist-get info :parent-frame-width)
(plist-get info :posframe-width))
2)
180))
;; (setq vertico-posframe-poshandler #'posframe-poshandler-frame-center)
(setq vertico-posframe-poshandler #'posframe-poshandler-slightly-below-top)
(defun joe/consult-buffer-vertico-indexed (start-index)
(interactive)
(let ((vertico-count 12)
(vertico-posframe-width 110)
(vertico-posframe-height 20)
(vertico-group-format nil)
(vertico-indexed-start 1)
(vertico--index start-index)
(consult-buffer-sources '(beframe-consult-source)))
(consult-buffer)))
(defun joe/consult-buffer-vertico-next ()
(interactive)
(joe/consult-buffer-vertico-indexed 1))
(defun joe/consult-buffer-vertico-last ()
(interactive)
(joe/consult-buffer-vertico-indexed (1- (length (beframe-buffer-names)))))
(global-set-key (kbd "s-n") #'joe/consult-buffer-vertico-next)
(global-set-key (kbd "s-p") #'joe/consult-buffer-vertico-last)
(setq vertico-count 20
vertico-resize nil
vertico-cycle t)
(setq vertico-posframe-width 130)
(setq vertico-posframe-height 30)
(setq vertico-posframe-parameters
'((child-frame-border-width . 5)
(left-fringe . 30)
(right-fringe . 30)))
(require 'savehist)
(savehist-mode)
(add-hook 'minibuffer-setup-hook #'vertico-repeat-save)
(add-to-list 'savehist-additional-variables 'vertico-repeat-history)
(define-key vertico-map (kbd "C-M-n") #'vertico-next-group)
(define-key vertico-map (kbd "s-p") #'vertico-previous)
(define-key vertico-map (kbd "s-n") #'vertico-next)
;; #' "C-M-p" #'vertico-previous-group)
;; (elpaca 'vertico-directory)
;; :bind (:map vertico-map
;; ("RET" . vertico-directory-enter)
;; ("DEL" . vertico-directory-delete-char)
;; ("M-DEL" . vertico-directory-delete-word))
;; :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
#+end_src
*** Embark
#+begin_src emacs-lisp
(require 'embark)
#+end_src
Add a beframe keybinding to unassume a buffer with embark
#+begin_src emacs-lisp
(define-key embark-buffer-map (kbd "b") #'beframe-unassume-current-frame-buffers-selectively)
#+end_src
These two lines allow you to kill buffers without a confirmation and without
closing the mini-buffer so you can quickly kill multiple buffers if needed. The
odd looking ~'(t .t)~ is for specifying a default for all other actions.
#+begin_src emacs-lisp
(setq embark-quit-after-action '((t . t)
(kill-buffer . nil)
;; (beframe-unassume-current-frame-buffers-selectively . nil)))
(joe/beframe-unassume-and-refresh-consult . nil)))
(setf (alist-get 'kill-buffer embark-pre-action-hooks) nil)
#+end_src
#+begin_src emacs-lisp
(defun embark-which-key-indicator ()
"An embark indicator that displays keymaps using which-key.
The which-key help message will show the type and value of the
current target followed by an ellipsis if there are further
targets."
(lambda (&optional keymap targets prefix)
(if (null keymap)
(which-key--hide-popup-ignore-command)
(which-key--show-keymap
(if (eq (plist-get (car targets) :type) 'embark-become)
"Become"
(format "Act on %s '%s'%s"
(plist-get (car targets) :type)
(embark--truncate-target (plist-get (car targets) :target))
(if (cdr targets) "β¦" "")))
(if prefix
(pcase (lookup-key keymap prefix 'accept-default)
((and (pred keymapp) km) km)
(_ (key-binding prefix 'accept-default)))
keymap)
nil nil t (lambda (binding)
(not (string-suffix-p "-argument" (cdr binding))))))))
(setq embark-indicators
'(embark-which-key-indicator
embark-highlight-indicator
embark-isearch-highlight-indicator))
(defun embark-hide-which-key-indicator (fn &rest args)
"Hide the which-key indicator immediately when using the completing-read prompter."
(which-key--hide-popup-ignore-command)
(let ((embark-indicators
(remq #'embark-which-key-indicator embark-indicators)))
(apply fn args)))
(advice-add #'embark-completing-read-prompter
:around #'embark-hide-which-key-indicator)
(advice-remove #'embark-completing-read-prompter #'embark-hide-which-key-indicator)
#+end_src
*** Marginalia
#+begin_src emacs-lisp
(require 'marginalia)
(setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
(setq marginalia-align 'right)
(setq marginalia-max-relative-age most-positive-fixnum)
(marginalia-mode)
(define-key minibuffer-local-map (kbd "M-A") #'marginalia-cycle)
(require 'all-the-icons-completion)
(all-the-icons-completion-mode)
(all-the-icons-completion-marginalia-setup)
(add-hook 'marginalia-mode-hook #'all-the-icons-completion-marginalia-setup)
#+end_src
*** Consult
#+begin_src emacs-lisp
(require 'embark-consult)
(require 'consult)
;; (require 'consult-lsp)
(global-set-key (kbd "C-. C-l") 'consult-line)
(global-set-key (kbd "C-. C-i") 'consult-imenu)
(global-set-key (kbd "C-. C-t") 'joe/consult-theme)
(global-set-key (kbd "C-. C-r") 'consult-recent-file)
(global-set-key (kbd "C-. C-y") 'consult-yank-from-kill-ring)
#+end_src
*** Orderless
#+begin_src emacs-lisp
(require 'orderless)
(setq completion-styles '(orderless basic)
completion-category-overrides '((file (styles basic partial-completion))))
#+end_src
** App Launcher
#+begin_src emacs-lisp
(defun emacs-run-launcher ()
(interactive)
(unwind-protect
(app-launcher-run-app)
(delete-frame)))
#+end_src
** Dirvish/Dired
#+begin_src emacs-lisp
(require 'dirvish)
(with-eval-after-load 'dirvish
(dirvish-override-dired-mode)
(setq delete-by-moving-to-trash t)
(setq dired-dwim-target t)
(setq dirvish-reuse-session nil)
;; (add-hook 'dired-mode-hook 'centaur-tabs-local-mode)
(dirvish-define-preview exa (file)
"Use `exa' to generate directory preview."
:require ("exa") ; tell Dirvish to check if we have the executable
(when (file-directory-p file) ; we only interest in directories here
`(shell . ("exa" "--icons" "--color=always" "--no-user" "-al" "--group-directories-first" ,file))))
(add-to-list 'dirvish-preview-dispatchers 'exa)
(setq dired-listing-switches "-l --almost-all --human-readable --time-style=long-iso --group-directories-first --no-group")
(setq dirvish-preview-dispatchers (cl-substitute 'pdf-preface 'pdf dirvish-preview-dispatchers))
(setq dirvish-attributes '(all-the-icons file-size collapse subtree-state))
(defun joe/dirvish-find-directory (dir)
(interactive "FDirvish Directory:")
(dirvish-dwim dir))
(setq dirvish-quick-access-entries
'(("h" "~/" "Home")
("c" "~/.config" "Config")
("d" "~/Downloads/" "Downloads")
("D" "~/Documents/" "Documents")
("b" "~/Documents/Books/" "Books")
("p" "~/Development/" "Dev")
("t" "~/Tutorials/" "Tutorials")
("n" "~/Notes/" "OrgNotes")
("r" "~/Repositories" "Repos")
("B" "~/pCloudDrive/" "pCloud")))
(when (boundp 'evil-mode)
(evil-define-key 'normal dirvish-mode-map
(kbd "C-c f") #'dirvish-fd
(kbd "a") #'dirvish-quick-access
(kbd ".") #'dired-create-empty-file
(kbd "f") #'dirvish-file-info-menu
(kbd "y") #'dirvish-yank-menu
(kbd "h") #'dired-up-directory
(kbd "l") #'dired-find-file
(kbd "s") #'dirvish-quicksort
(kbd "v") #'dirvish-vc-menu
(kbd "q") #'dirvish-quit
(kbd "L") #'dirvish-history-go-forward
(kbd "H") #'dirvish-history-go-backward
(kbd "o") #'dired-open-file
(kbd "TAB") #'dirvish-subtree-toggle
(kbd "M-n") #'dirvish-narrow
(kbd "M-l") #'dirvish-ls-switches-menu
(kbd "M-m") #'dirvish-mark-menu
(kbd "M-t") #'dirvish-layout-toggle
(kbd "M-s") #'dirvish-setup-menu
(kbd "M-e") #'dirvish-emerge-menu
(kbd "M-j") #'dirvish-fd-jump)))
;; There's an issue with the keybinding precedence so we need to run this since
;; joe/evil-space-mode-map takes precedence above all else and '-' doesn't do
;; what it should
(defun joe/dirvish-up-dwim ()
(interactive)
(if (eq major-mode 'dired-mode)
(dired-up-directory)
(dirvish-dwim)))
(setq dired-omit-files "\\`[.]?#\\|\\`[.][.]?\\|\\.meta$")
(add-hook 'dired-mode-hook #'dired-omit-mode)
(when (boundp 'evil-mode)
(evil-define-key 'normal joe/evil-space-mode-map (kbd "-") #'joe/dirvish-up-dwim))
(global-set-key (kbd "C-x d") #'dirvish-dwim)
(global-set-key (kbd "C-x C-d") #'joe/dirvish-find-directory)
(defun dired-open-file ()
"In dired, open the file named on this line."
(interactive)
(let* ((file (dired-get-filename nil t)))
(message "Opening %s..." file)
(call-process "xdg-open" nil 0 nil file)
(message "Opening %s done" file)))
#+end_src
** Email
#+begin_src emacs-lisp
(when (file-exists-p "/usr/share/emacs/site-lisp/mu4e/")
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e/")
(require 'mu4e)
;; Attach files to a message composition buffer by going into `dired'
;; and doing C-c C-m C-a (M-x `gnus-dired-attach').
(require 'gnus-dired) ; does not require `gnus'
(add-hook 'dired-mode-hook #'gnus-dired-mode)
(add-hook 'mu4e-main-mode-hook 'olivetti-mode)
(add-to-list 'auto-mode-alist '("authinfo" . authinfo-mode))
(setq mu4e-get-mail-command "parallel mbsync -V \"-c ~/.config/mbsync/config\" ::: ferano.io.inbox gmail.allmail")
;;; Sending email (SMTP)
(require 'smtpmail)
(setq smtpmail-default-smtp-server "mail.gandi.net"
smtpmail-smtp-server "mail.gandi.net"
smtpmail-stream-type 'ssl
smtpmail-smtp-service 465
smtpmail-queue-mail nil)
(require 'sendmail)
(setq send-mail-function 'smtpmail-send-it)
(setq mu4e-update-interval 30)
(setq mu4e-hide-index-messages t)
(setq mu4e-completing-read-function 'completing-read)
(setq mu4e-context-policy 'pick-first)
(setq mu4e-compose-context-policy 'ask)
(setq mu4e-view-auto-mark-as-read nil)
;; (setq mu4e-sent-messages-behavior 'sent)
(setq message-kill-buffer-on-exit t)
(require 'age)
(setq age-default-identity '("~/.local/credentials/personal"))
(setq age-default-recipient '("~/.local/credentials/personal.pub"))
(setq auth-source-do-cache nil)
(defun joe/mu4e-auth-get-field (host prop)
"Find PROP in `auth-sources' for HOST entry."
(when-let ((source (auth-source-search :host host)))
(if (eq prop :secret)
(funcall (plist-get (car source) prop))
(plist-get (flatten-list source) prop))))
(setq auth-sources '("~/.local/credentials/authinfo.age"))
(setq mu4e-change-filenames-when-moving t)
(age-file-enable)
(age-encryption-mode +1)
(setq mu4e-bookmarks nil)
(setq mu4e-contexts
`(,(make-mu4e-context
:name "Ferano.io"
:enter-func (lambda () (mu4e-message "Entering ferano.io"))
:leave-func (lambda () (mu4e-message "Leaving ferano.io"))
:match-func (lambda (msg)
(when msg
(mu4e-message-contact-field-matches
msg :to (joe/mu4e-auth-get-field "mail.gandi.net" :user))))
:vars `((user-mail-address . ,(joe/mu4e-auth-get-field "mail.gandi.net" :user))
(user-full-name . "Joseph Ferano")
(mu4e-drafts-folder . "/ferano.io/Drafts/")
(mu4e-trash-folder . "/ferano.io/Trash/")
(mu4e-sent-folder . "/ferano.io/Sent/")))
,(make-mu4e-context
:name "Gmail"
:enter-func (lambda () (mu4e-message "Entering gmail"))
:leave-func (lambda () (mu4e-message "Leaving gmail"))
:match-func (lambda (msg)
(when msg
(mu4e-message-contact-field-matches
msg :to (joe/mu4e-auth-get-field "mail.gmail.com" :user))))
:vars `((user-mail-address . ,(joe/mu4e-auth-get-field "mail.gmail.com" :user))
(user-full-name . "Joseph Ferano")
(mu4e-drafts-folder . "/gmail/[Gmail]/Drafts/")
(mu4e-trash-folder . "/gmail/[Gmail]/Trash/")
(mu4e-sent-folder . "/gmail/[Gmail]/Sent Mail/")))))
(setq mu4e-maildir-shortcuts
'((:maildir "/ferano.io/Inbox" :key ?f)
(:maildir "/gmail/[Gmail]/All Mail" :key ?g)))
(mu4e 't)
(add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display))
;; (:name "Ferano.io Unread" :query "m:/ferano.io/Inbox AND g:unread" :key ?u)))
#+end_src
Fold threads. This is a gist provided by Rougier [[https://gist.github.com/rougier/98e83fb50e19fb73fe34a7ecc5fc1ccc][here]]. His other package is
[[https://github.com/rougier/mu4e-thread-folding][mu4e-thread-folding]] but they work slightly differently. Code for the latter will
be kept here commented out in case we want to try it again.
#+begin_src emacs-lisp
(when (file-exists-p "/usr/share/emacs/site-lisp/mu4e/")
(load-file "/home/joe/Dotfiles/.config/emacs/elisp/mu4e-fast-folding.el")
(evil-define-key 'normal mu4e-headers-mode-map (kbd "TAB")
#'mu4e-fast-folding-thread-toggle)
(evil-define-key 'normal mu4e-headers-mode-map (kbd "")
#'mu4e-fast-folding-thread-toggle-all))
;; (load-file "/home/joe/Dotfiles/.config/emacs/elisp/mu4e-thread-folding.el")
;; (require 'mu4e-fast-folding)
;; (add-to-list 'mu4e-header-info-custom
;; '(:empty . (:name "Empty"
;; :shortname ""
;; :function (lambda (msg) " "))))
;; (setq mu4e-headers-fields '((:empty . 2)
;; (:human-date . 12)
;; (:flags . 6)
;; (:mailing-list . 10)
;; (:from . 22)
;; (:subject . nil)))
;; (evil-define-key 'normal mu4e-headers-mode-map (kbd "TAB")
;; #'mu4e-headers-fold-at-point)
;; (evil-define-key 'normal mu4e-headers-mode-map (kbd "")
;; #'mu4e-headers-unfold-at-point)
;; (mu4e-thread-folding-mode +1)
#+end_src
** Avy
#+begin_src emacs-lisp
(require 'avy)
(setq avy-case-fold-search nil)
(setq avy-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l ?q ?w ?e ?r ?u ?i ?o ?p ?z ?x ?c ?v ?n ?m))
;; (define-key global-map (kbd "C-;") 'avy-goto-char) ;; I use this most frequently
(define-key global-map (kbd "M-s") 'avy-goto-char) ;; I use this most frequently
;; (define-key global-map (kbd "C-'") 'avy-goto-line) ;; Consistent with ivy-avy
(define-key global-map (kbd "M-g c") 'avy-goto-char)
(define-key global-map (kbd "M-g e") 'avy-goto-word-0) ;; lots of candidates
(define-key global-map (kbd "M-g g") 'avy-goto-line) ;; digits behave like goto-line
(define-key global-map (kbd "M-g w") 'avy-goto-word-1) ;; first character of the word
(define-key global-map (kbd "M-g P") 'avy-pop-mark)
#+end_src
** Helpful
#+begin_src emacs-lisp
(require 'helpful)
(global-set-key (kbd "C-h f") #'helpful-callable)
(global-set-key (kbd "C-h v") #'helpful-variable)
(global-set-key (kbd "C-h k") #'helpful-key)
(global-set-key (kbd "C-h C-d") #'helpful-at-point)
(global-set-key (kbd "C-h F") #'helpful-function)
(global-set-key (kbd "C-h C") #'helpful-command)
#+end_src
** Terminals/Shells
*** vterm
#+begin_src emacs-lisp
(require 'vterm)
(setq vterm-shell "/bin/fish")
(setq vterm-timer-delay 0.01)
(setq vterm-buffer-name-string "VTerm - %s")
;; (setq vterm-buffer-name-string nil)
(setq vterm-max-scrollback 100000)
(setq vterm-kill-buffer-on-exit t)
(defun joe/close-popup-buffer (vterm-buf event-msg)
(unless (or (eq popper-popup-status nil)
(eq popper-popup-status 'raised))
(popper-close-latest)))
(setq vterm-exit-functions '(joe/close-popup-buffer))
(defun joe/vterm-here ()
(interactive)
(let ((vterm-buf (vterm--internal #'switch-to-buffer)))
(with-current-buffer vterm-buf
(setq popper-popup-status 'raised))))
;; (global-set-key (kbd "C-c t") #'vterm)
;; (global-set-key (kbd "C-c T") #'joe/vterm-here)
;; (setq explicit-shell-file-name "~/Development/fell/fell")
(add-hook 'shell-mode (lambda () (setq-local global-hl-line-mode nil)))
(setq shell-kill-buffer-on-exit t)
(defun joe/vterm-mode-hook ()
(define-key vterm-mode-map (kbd "C-c C-x") #'vterm-send-C-x)
(when (boundp 'evil-mode)
(evil-define-key 'insert vterm-mode-map (kbd "C-f") (lambda () (interactive) (vterm-send-key (kbd "C-f"))))
(evil-define-key 'insert vterm-mode-map (kbd "C-w") (lambda () (interactive) (vterm-send-key (kbd "C-w"))))
(evil-define-key 'insert vterm-mode-map (kbd "") #'vterm-send-delete))
(setq-local global-hl-line-mode nil)
(setq buffer-face-mode-face '(:family "Fira Code Nerd Font"))
(buffer-face-mode))
(add-hook 'vterm-mode-hook #'joe/vterm-mode-hook)
#+end_src
VTerm is loading TRAMP along with it which slows down init time noticeably so call this after
startup. Reason we have to call this is so the vterm fucntion can call `vterm--internal`.
#+begin_src emacs-lisp :tangle no
;; (add-hook 'emacs-startup-hook (lambda () (require 'vterm)))
#+end_src
This bit of code is to achieve a vterm scratchpad for my window manager. In
order to know I'm closing the right frame, I'm going to use the frame's name to
close and remove the hook
#+begin_src emacs-lisp
(defun vterm--set-title (title)
"Use TITLE to set the buffer name according to `vterm-buffer-name-string'."
(when (and vterm-buffer-name-string (not (equal (buffer-name) "Scratch VTerm")))
(rename-buffer (format vterm-buffer-name-string title) t)))
(defun joe/kill-frame ()
(when (equal (buffer-name) "Scratch VTerm")
(remove-hook 'delete-frame-functions #'joe/kill-vterm-scratch)
(remove-hook 'kill-buffer-hook #'joe/kill-frame)
(delete-frame)))
(defun joe/kill-vterm-scratch (FRAME)
(let* ((kill-buffer-query-functions nil)
(scratch-framep (cdr (assoc 'scratch-frame (frame-parameters))))
(vterm-buf (get-buffer "Scratch VTerm")))
(when (and scratch-framep
vterm-buf)
(kill-buffer vterm-buf)
(remove-hook 'delete-frame-functions #'joe/kill-vterm-scratch)
(remove-hook 'kill-buffer-hook #'joe/kill-frame))))
(defun joe/vterm-scratch ()
(interactive)
(let* ((vterm-buf (vterm--internal #'switch-to-buffer "Scratch VTerm")))
(set-frame-parameter nil 'scratch-frame t)
(with-current-buffer vterm-buf
(setq mode-line-format nil)
(setq popper-popup-status 'raised)
(olivetti-mode)
(add-hook 'delete-frame-functions #'joe/kill-vterm-scratch)
(add-hook 'kill-buffer-hook #'joe/kill-frame))))
#+end_src
*** COMMENT eat
#+begin_src emacs-lisp
(defun joe/eat-mode-hook ()
(setq-local global-hl-line-mode nil)
(setq buffer-face-mode-face '(:family "Fira Code Nerd Font"))
(buffer-face-mode))
(add-hook 'eat-mode-hook #'joe/eat-mode-hook)
(evil-define-key 'insert eat-semi-char-mode-map (kbd "C-f") #'eat-self-input)
(evil-set-initial-state 'eat-mode 'normal)
(setq eat-kill-buffer-on-exit 't)
(eat-eshell-mode)
(eat-eshell-visual-command-mode)
#+end_src
** Undo Fu
#+begin_src emacs-lisp
(require 'undo-fu)
(undo-fu-session-global-mode +1)
(setq undo-limit 6710886400) ;; 64mb.
(setq undo-strong-limit 100663296) ;; 96mb.
(setq undo-outer-limit 1006632960) ;; 960mb.
(require 'undo-fu-session)
(setq undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'"))
(require 'vundo)
#+end_src
** Which Key
#+begin_src emacs-lisp
(require 'which-key)
(setq which-key-idle-delay 0.3)
(which-key-mode)
(when (boundp 'evil-mode)
(which-key-add-keymap-based-replacements evil-normal-state-map
"SPC f" '("Files")
"SPC b" '("Buffers")
"SPC B" '("Bookmarks")
"SPC c" '("Consult")
"SPC d" '("Dired")
"SPC g" '("Git")
"SPC m" '("Make")
"SPC t" '("Tabs")
"SPC p" '("Packages")
"SPC s" '("Shell (vterm)")
"SPC h" '("Help")))
#+end_src
** IDE Features
*** REPLs
#+begin_src emacs-lisp
(when (boundp 'evil-mode)
(evil-define-key 'insert comint-mode-map (kbd "C-n") 'comint-next-input)
(evil-define-key 'insert comint-mode-map (kbd "C-p") 'comint-previous-input))
#+end_src
*** Completion
#+begin_src emacs-lisp
;; (evil-global-set-key 'insert (kbd "C-f") #'mono-complete-expand)
(require 'mono-complete)
(evil-global-set-key 'insert (kbd "C-f") #'mono-complete-expand-or-fallback)
(setq mono-complete-backends (list 'dabbrev 'filesystem))
(setq mono-complete-backends (list 'capf 'dabbrev 'filesystem))
(setq mono-complete-backend-capf-complete-fn #'eglot-completion-at-point)
(setq mono-complete-preview-delay 0.1)
;; (add-to-list 'face-remapping-alist '(mono-complete-preview-face . shadow))
(set-face-attribute 'mono-complete-preview-face nil
:foreground 'unspecified
:background 'unspecified
:inherit 'shadow)
(mono-complete-mode +1)
(setq completion-in-region-function
(lambda (&rest args)
(apply (if vertico-mode
#'consult-completion-in-region
#'completion--in-region)
args)))
#+end_src
*** COMMENT Eglot
#+begin_src emacs-lisp
(with-eval-after-load 'eglot
;; (flymake-mode -1)
;; Disable it completely until we find out how the hell we can toggle it
(setq eglot-stay-out-of '(flymake))
;; (setq eglot-stay-out-of '())
(setq eldoc-echo-area-use-multiline-p nil)
(setq eldoc-idle-delay 0.15)
;; (setq eglot-stay-out-of '())
;; (add-hook 'eglot-managed-mode-hook (lambda () (flymake-mode -1)))
(evil-global-set-key 'normal (kbd "SPC li") 'eglot-inlay-hints-mode)
(evil-global-set-key 'normal (kbd "SPC cs") 'consult-eglot-symbols)
(evil-global-set-key 'normal (kbd "SPC cr") 'eglot-rename)
(evil-global-set-key 'normal (kbd "SPC ca") 'eglot-code-actions))
;; These don't work
;; (setq flymake-start-on-save-buffer nil)
;; (setq flymake-start-on-flymake-mode nil)
#+end_src
These help speed eglot up apparently [[https://www.reddit.com/r/emacs/comments/1b25904/is_there_anything_i_can_do_to_make_eglots/][Reddit Link]]
#+begin_src emacs-lisp
(fset #'jsonrpc--log-event #'ignore)
(setq eglot-events-buffer-size 0)
#+end_src
*** LSP
#+begin_src emacs-lisp
(require 'lsp-mode)
(setq lsp-enable-which-key-integration t)
(setq lsp-headerline-breadcrumb-enable nil)
(setq lsp-modeline-diagnostics-enable nil)
(setq lsp-modeline-code-actions-enable nil)
(setq lsp-lens-enable nil)
(setq lsp-signature-auto-activate nil)
(setq lsp-eldoc-enable-hover nil)
(setq eldoc-echo-area-use-multiline-p 'truncate-sym-name-if-fit)
(setq eldoc-idle-delay 0)
(setq lsp-eldoc-render-all t)
;; (setq lsp-ui-doc-enable t)
(defun joe/lsp-mode-hook ()
(flymake-mode -1))
(add-hook 'lsp-mode-hook #'joe/lsp-mode-hook)
(define-key global-map (kbd "C-c l l") #'lsp)
(setq lsp-ui-doc-position 'at-point)
(setq lsp-ui-doc-show-with-mouse nil)
;; All this changes because we are using eglot now
(when (boundp 'evil-mode)
(evil-global-set-key 'normal (kbd "M-d") #'lsp-ui-doc-glance)
(evil-global-set-key 'normal (kbd "M-r") #'lsp-rename)
(evil-global-set-key 'insert (kbd "M-i") #'lsp-signature-activate)
(evil-global-set-key 'normal (kbd "gD") #'xref-find-definitions-other-window)
(evil-global-set-key 'normal (kbd "SPC la") #'lsp-execute-code-action)
(evil-global-set-key 'normal (kbd "SPC lt") #'lsp-find-type-definition)
(evil-global-set-key 'normal (kbd "SPC lr") #'lsp-find-references)
(evil-global-set-key 'normal (kbd "SPC ld") #'lsp-find-definition)
(evil-global-set-key 'normal (kbd "SPC ls") #'consult-lsp-symbols)
(evil-global-set-key 'normal (kbd "SPC lD") #'consult-lsp-diagnostics)
(evil-global-set-key 'normal (kbd "SPC lf") #'consult-lsp-file-symbols))
#+end_src
*** Flycheck
#+begin_src emacs-lisp
(require 'flycheck)
(setq flycheck-check-syntax-automatically '())
(setq flycheck-keymap-prefix (kbd "C-c e"))
(defun joe/flycheck-buffer-and-next () (interactive) (flycheck-buffer) (flycheck-next-error))
(defun joe/flycheck-buffer-and-previous () (interactive) (flycheck-buffer) (flycheck-previous-error))
(defun joe/flycheck-clear ()
(interactive)
(when (boundp 'lsp-ui-mode)
(flycheck-clear)
(lsp-ui-sideline--erase)))
(add-hook 'after-save-hook #'joe/flycheck-clear)
(when (boundp 'evil-mode)
(evil-global-set-key 'normal (kbd "M-E") #'joe/flycheck-buffer-and-previous)
(evil-global-set-key 'normal (kbd "M-e") #'joe/flycheck-buffer-and-next)
(evil-global-set-key 'normal (kbd "C-M-e") #'joe/flycheck-clear)
(evil-global-set-key 'insert (kbd "C-M-e") #'joe/flycheck-clear))
#+end_src
*** Compilation
#+begin_src emacs-lisp
(setq compilation-auto-jump-to-first-error t)
(defun joe/save-then-recompile ()
"Save the buffer before recompiling"
(interactive)
(when (buffer-file-name)
(save-buffer))
(recompile))
(defun joe/save-if-file-ignore-args (&rest _)
"Save the buffer before recompiling"
(interactive)
(joe/save-if-file))
(defun joe/save-if-file ()
"Save buffer only if it has a backing file"
(interactive)
(when (buffer-file-name)
(save-buffer)))
(add-hook 'evil-insert-state-exit-hook #'joe/save-if-file)
;; (add-hook 'after-change-functions #'joe/save-if-file-ignore-args)
;; (define-key global-map (kbd "C-x C-s) #'save-buffer)
(define-key global-map (kbd "") #'joe/save-then-recompile)
(define-key global-map (kbd "") #'compile)
(defun joe/colorize-compilation-buffer ()
(ansi-color-apply-on-region compilation-filter-start (point)))
(add-hook 'compilation-filter-hook 'joe/colorize-compilation-buffer)
(defun joe/close-compilation-if-no-warn-err (buffer string)
"Bury a compilation buffer if succeeded without warnings "
(if (and
(string-match "compilation" (buffer-name buffer))
(string-match "finished" string)
(not
(with-current-buffer (current-buffer)
(search-forward "warning" nil t))))
(run-with-timer 1.5 nil
(lambda ()
(and (eq (point) 1)
(string-match "compilation" (buffer-name (current-buffer)))
(popper-close-latest))))))
(add-hook 'compilation-finish-functions 'joe/close-compilation-if-no-warn-err)
#+end_src
*** Godot
#+begin_src emacs-lisp
(require 'gdscript-mode)
#+end_src
*** Indentation
#+begin_src emacs-lisp
(setq indent-bars-color '(highlight :face-bg t :blend 0.1))
(setq indent-bars-color-by-depth nil)
(global-set-key (kbd "C-x x i") #'indent-bars-mode)
#+end_src
** Game Dev
#+begin_src emacs-lisp
(add-to-list 'auto-mode-alist '("\\.vert\\'" . shader-mode))
(add-to-list 'auto-mode-alist '("\\.frag\\'" . shader-mode))
(add-to-list 'auto-mode-alist '("\\.glsl\\'" . shader-mode))
#+end_src
** AI
#+begin_src emacs-lisp
(setq gptel-default-mode #'org-mode)
(setq
gptel-model 'claude-3-sonnet-20240229
gptel-backend (gptel-make-anthropic "Claude"
:stream t :key (with-temp-buffer
(insert-file-contents (expand-file-name "gptel-key" user-emacs-directory))
(buffer-string))))
(add-hook 'gptel-post-response-functions #'font-lock-ensure)
#+end_src
This function was suggested by Karthink in order to fix an issue where gptel
org-mode was jumping back up to the top anytime the buffer was saved. Keeping it
around just in case.
https://github.com/karthink/gptel/issues/199
#+begin_src emacs-lisp :tangle no
(defun gptel--save-state ()
"Write the gptel state to the buffer.
This saves chat metadata when writing the buffer to disk. To
restore a chat session, turn on `gptel-mode' after opening the
file."
(pcase major-mode
('org-mode
(org-with-wide-buffer
(goto-char (point-min))
(when (org-at-heading-p)
(org-open-line 1))
(org-entry-put (point-min) "GPTEL_MODEL" gptel-model)
(org-entry-put (point-min) "GPTEL_BACKEND" (gptel-backend-name gptel-backend))
(unless (equal (default-value 'gptel-temperature) gptel-temperature)
(org-entry-put (point-min) "GPTEL_TEMPERATURE"
(number-to-string gptel-temperature)))
(unless (string= (default-value 'gptel--system-message)
gptel--system-message)
(org-entry-put (point-min) "GPTEL_SYSTEM"
gptel--system-message))
(when gptel-max-tokens
(org-entry-put
(point-min) "GPTEL_MAX_TOKENS" gptel-max-tokens))
;; Save response boundaries
(letrec ((write-bounds
(lambda (attempts)
(let* ((bounds (gptel--get-bounds))
(offset (caar bounds))
(offset-marker (set-marker (make-marker) offset)))
(org-entry-put (point-min) "GPTEL_BOUNDS"
(prin1-to-string (gptel--get-bounds)))
(when (and (not (= (marker-position offset-marker) offset))
(> attempts 0))
(funcall write-bounds (1- attempts)))))))
(funcall write-bounds 6))))
(_ (save-excursion
(save-restriction
(add-file-local-variable 'gptel-model gptel-model)
(add-file-local-variable 'gptel--backend-name
(gptel-backend-name gptel-backend))
(unless (equal (default-value 'gptel-temperature) gptel-temperature)
(add-file-local-variable 'gptel-temperature gptel-temperature))
(unless (string= (default-value 'gptel--system-message)
gptel--system-message)
(add-file-local-variable 'gptel--system-message gptel--system-message))
(when gptel-max-tokens
(add-file-local-variable 'gptel-max-tokens gptel-max-tokens))
(add-file-local-variable 'gptel--bounds (gptel--get-bounds)))))))
#+end_src
** Programming Languages
*** COMMENT treesitter
#+begin_src emacs-lisp
(setq major-mode-remap-alist
'((csharp-mode . csharp-ts-mode)))
#+end_src
To run the default hooks, I came up with this, which is either really stupid or really cool.
This checks if itβs a ts mode then runs their counterparts.
β MahmoudAdam
[[https://www.emacswiki.org/emacs/Tree-sitter][Link]]
#+begin_src emacs-lisp
(defun run-non-ts-hooks ()
(let ((major-name (symbol-name major-mode)))
(when (string-match-p ".*-ts-mode" major-name)
(run-hooks (intern (concat (replace-regexp-in-string "-ts" "" major-name) "-hook"))))))
(add-hook 'prog-mode-hook 'run-non-ts-hooks)
#+end_src
*** CSharp
Omnisharp is very slow to start, so I want to start it manually, but not if the
buffer already belongs to the current lsp workspace. This function checks if a
newly opened buffer belongs to an LSP session and if it does, start lsp
#+begin_src emacs-lisp
(defun lsp-mode-server-exists-p+ (mode &optional all-workspaces)
(when (buffer-file-name) ; Needed for lsp--matching-clients?
(let ((major-mode mode))
(seq-intersection
(lsp--filter-clients (-andfn #'lsp--supports-buffer?
#'lsp--server-binary-present?))
(when-let ((workspaces
(if all-workspaces
(-flatten (hash-table-values (lsp-session-folder->servers (lsp-session))))
(gethash (lsp-workspace-root) (lsp-session-folder->servers (lsp-session))))))
(mapcar #'lsp--workspace-client workspaces))
#'equal))))
;; (setq treesit-load-name-override-list '((csharp "libtree-sitter-c-sharp.so")))
#+end_src
And we do the rest here, including a macro
#+begin_src emacs-lisp
(defun joe/csharp-mode-hook ()
(yas-minor-mode t)
(defalias 'joe/serialized-private-public-getter
(kmacro "^ W W Y o C-y ^ i p u b l i c SPC l W y i w P a SPC = > SPC B B ~"))
(evil-set-register ?g (lambda nil "Unity/C#"
(call-interactively 'joe/serialized-private-public-getter)))
;; (schmo/reapply-csharp-ts-mode-font-lock-settings)
(electric-pair-local-mode t)
(when (lsp-mode-server-exists-p+ 'csharp-mode)
(lsp)))
(add-hook 'csharp-mode-hook #'joe/csharp-mode-hook)
#+end_src
*** Odin
#+begin_src emacs-lisp
(with-eval-after-load 'lsp-mode
(setq-default lsp-auto-guess-root t) ;; Helps find the ols.json file with Projectile or project.el
(setq lsp-language-id-configuration (cons '(odin-mode . "odin") lsp-language-id-configuration))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection "~/.local/bin/ols") ;; Adjust the path here
:major-modes '(odin-mode)
:server-id 'ols
:multi-root t))) ;; Ensures lsp-mode sends "workspaceFolders" to the server
(add-hook 'odin-mode-hook #'lsp)
(defun joe/odin-mode-hook ()
(electric-pair-local-mode))
(add-hook 'odin-mode-hook #'joe/odin-mode-hook)
#+end_src
*** C
Design some basic functions for compiling. There's also a hook to close the popper window if there
are no warnings or errors. It will check if we are still in the compilation buffer as well as
whether the point hasn't been moved. This way, if I switched to another popper buffer, like vterm,
it doesn't close it.
#+begin_src emacs-lisp
(require 'disaster)
(setq c-default-style "bsd")
(defun joe/c-mode-hook ()
(local-set-key (kbd "C-x c r") (defun joe/make-run () (interactive) (compile "make run")))
(local-set-key (kbd "C-x c c") (defun joe/make () (interactive) (compile "make")))
(electric-pair-local-mode t)
(c-toggle-comment-style -1))
(add-hook 'c-mode-hook #'joe/c-mode-hook)
#+end_src
*** Python
#+begin_src emacs-lisp
(require 'python)
(setq python-shell-interpreter "python")
(setq python-shell-interpreter-args "--pylab") ;; What is this for?
(setq python-shell-interpreter-args "") ;; What is this for?
(define-key inferior-python-mode-map (kbd "C-n") #'comint-next-input)
(define-key inferior-python-mode-map (kbd "C-p") #'comint-previous-input)
#+end_src
*** Rust
#+begin_src emacs-lisp
(setq rustic-lsp-setup-p nil)
(require 'rustic)
(require 'ob-rust)
;; Org-Babel
;; Disabling until we figure out how to get it working
;; (elpaca 'parsec) ;; Required by evcxr-mode
;; (elpaca
;; '(evcxr
;; :type git
;; :host github
;; :repo "serialdev/evcxr-mode"))
(add-hook 'rust-mode-hook
(lambda ()
;; (evcxr-minor-mode)
(electric-pair-local-mode)))
;; (with-eval-after-load 'rustic
;; ;; Don't autostart
;; ;; (define-key rustic-mode-map (kbd "") #'joe/save-then-recompile)
;; (setq lsp-rust-analyzer-server-display-inlay-hints t)
;; (setq lsp-rust-analyzer-display-lifetime-elision-hints-enable "always")
;; (setq lsp-rust-analyzer-display-chaining-hints t)
;; (setq lsp-rust-analyzer-display-lifetime-elision-hints-use-parameter-names t)
;; (setq lsp-rust-analyzer-display-closure-return-type-hints t)
;; (setq lsp-rust-analyzer-display-parameter-hints t)
;; (setq lsp-rust-analyzer-display-reborrow-hints t)
;; (setq lsp-rust-analyzer-cargo-watch-command "clippy"))
#+end_src
*** OCaml
#+begin_src emacs-lisp
(require 'tuareg)
(require 'dune)
(require 'utop)
;; (require 'merlin)
;; (require 'merlin-eldoc)
;; Might be worth checking out, depeding on whether we stick with flycheck or not
;; (elpaca 'flycheck-ocaml)
;; Also check this out, see if it adds anything
;; (require 'ocp-indent)
(defun opam-env ()
"Load the opam env to get the PATH variables so everything works"
(interactive nil)
(dolist (var
(car (read-from-string
(shell-command-to-string "opam config env --sexp"))))
(setenv (car var) (cadr var))))
(setq opam-share
(substring (shell-command-to-string
"opam config var share 2> /dev/null") 0 -1))
(add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))
(add-to-list 'exec-path "/home/joe/.opam/default/bin/")
#+end_src
We won't use the LSP server but rather directly talk to Merlin, since I guess LSP just wraps Merlin
and there's no need for a middle-man when it's already been implemented.
#+begin_src emacs-lisp
;; (require 'utop)
;; Use the opam installed utop
(setq utop-command "opam exec -- utop -emacs")
(let ((opam-share (ignore-errors (car (process-lines "opam" "var" "share")))))
(when (and opam-share (file-directory-p opam-share))
;; Register Merlin
(add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))
(autoload 'merlin-mode "merlin" nil t nil)
;; Automatically start it in OCaml buffers
(add-hook 'tuareg-mode-hook 'merlin-mode t)
(add-hook 'caml-mode-hook 'merlin-mode t)
;; Use opam switch to lookup ocamlmerlin binary
(setq merlin-command 'opam)))
#+end_src
*** Elisp
#+begin_src emacs-lisp
(global-set-key (kbd "C-x C-r") 'eval-region)
(evil-define-key 'insert emacs-lisp-mode-map (kbd "C-j") 'eval-print-last-sexp)
#+end_src
*** Web
#+begin_src emacs-lisp
(require 'typescript-mode)
(setq typescript-indent-level 2)
#+end_src
*** SQL
#+begin_src emacs-lisp
(defun joe/mark-sql-defun ()
"Mark the current SQL function definition."
(interactive)
(let ((beg (save-excursion
(re-search-backward "^create or replace function [^(]+" nil t)
(match-end 0)))
(end (save-excursion
(re-search-forward "\\(language\\)" nil t)
(line-end-position))))
(when (and beg end)
(goto-char beg)
(set-mark end)))
(evil-visual-line)
(evil-visual-line))
(defun sql-send-pg-function ()
"Send the current PGSQL function to sql interactive."
(interactive)
(let ((beg (save-excursion
(re-search-backward "^create or replace function [^(]+" nil t)
(line-beginning-position)))
(end (save-excursion
(re-search-forward "\\(language\\)" nil t)
(line-end-position))))
(save-excursion
(when (and beg end)
(goto-char beg)
(set-mark end))
(sql-send-region beg end)
(deactivate-mark))))
;; This is required so that .dir-locals that read env files for credentials works
(require 'dotenv)
(defun joe/sql-mode-hook ()
(define-key sql-mode-map (kbd "C-M-h") #'joe/mark-sql-defun)
(define-key sql-mode-map (kbd "C-M-x") #'sql-send-pg-function)
(global-set-key (kbd "") #'sql-connect))
(add-hook 'sql-mode-hook #'joe/sql-mode-hook)
(defun joe/sql-save-history-hook ()
(let ((lval 'sql-input-ring-file-name)
(rval 'sql-product))
(if (symbol-value rval)
(let ((filename
(concat user-emacs-directory "sql/"
(symbol-name (symbol-value rval))
"-history.sql")))
(set (make-local-variable lval) filename))
(error
(format "SQL history will not be saved because %s is nil"
(symbol-name rval))))))
(add-hook 'sql-interactive-mode-hook 'joe/sql-save-history-hook)
(defun joe/sql-login-hook ()
"Custom SQL log-in behaviours. See `sql-login-hook'."
;; n.b. If you are looking for a response and need to parse the
;; response, use `sql-redirect-value' instead of `comint-send-string'.
(when (eq sql-product 'postgres)
(let ((proc (get-buffer-process (current-buffer))))
;; Output each query before executing it. (n.b. this also avoids
;; the psql prompt breaking the alignment of query results.)
(comint-send-string proc "\\set ECHO queries\n"))))
(add-hook 'sql-login-hook 'joe/sql-login-hook)
#+end_src
*** COMMENT Haskell
#+begin_src emacs-lisp
(require 'haskell-mode)
(setq haskell-interactive-popup-errors nil)
(evil-define-key 'insert haskell-interactive-mode-map (kbd "C-n") #'haskell-interactive-mode-history-next)
(evil-define-key 'insert haskell-interactive-mode-map (kbd "C-p") #'haskell-interactive-mode-history-previous)
#+end_src
*** COMMENT Clojure
#+begin_src emacs-lisp
(require 'clojure-mode)
(require 'cider)
(setq cider-show-error-buffer 'only-in-repl)
#+end_src
*** COMMENT FSharp
#+begin_src emacs-lisp
(require 'fsharp-mode)
;; (elpaca 'eglot-fsharp)
#+end_src
*** COMMENT Go
#+begin_src emacs-lisp
(require 'go-mode)
;; (elpaca 'go-imports)
#+end_src
*** Other
#+begin_src emacs-lisp
(require 'json-mode)
(require 'markdown-mode)
#+end_src
** Debugging
*** COMMENT DAP
#+begin_src emacs-lisp
(require 'dap-mode)
;; (setq dap-auto-configure-features '(locals breakpoints expressions tooltip))
(require 'dap-cpptools)
(dap-cpptools-setup)
(add-hook 'dap-stopped-hook
(lambda (arg) (call-interactively #'dap-hydra)))
(setq dap-cpptools-extension-version "1.12.1")
(setq dap-default-terminal-kind "integrated")
(dap-auto-configure-mode +1)
(dap-register-debug-template
"Rust::CppTools Run Configuration"
(list :type "cppdbg"
:request "launch"
:name "Rust::Run"
:MIMode "gdb"
:miDebuggerPath "rust-gdb"
:environment []
:program "${workspaceFolder}/target/debug/kanban-tui"
:cwd "${workspaceFolder}"
:console "external"
:dap-compilation "cargo build"
:dap-compilation-dir "${workspaceFolder}"))
#+end_src
*** COMMENT GDB/GUD
#+begin_src emacs-lisp
(setq gdb-many-windows t)
(setq gud-tooltip-dereference t)
(defun joe/gdb ()
(interactive)
(gdb (format "%s -i=mi" (file-name-sans-extension buffer-file-name))))
(defun hook-gud-mode ()
(define-key gud-global-map (kbd "C-c") #'gud-cont)
(define-key gud-global-map (kbd "C-r") #'gud-run))
(add-hook 'gud-mode-hook #'hook-gud-mode)
#+end_src
*** dape
#+begin_src emacs-lisp
(setq dape-buffer-window-arrangement 'right)
(defun joe/dape-toggle-breakpoint-and-rerun ()
(interactive)
(unless (dape--breakpoints-at-point)
(dape-breakpoint-toggle))
(dape-restart))
(define-key global-map (kbd "") #'joe/dape-toggle-breakpoint-and-rerun)
#+end_src
Apparently this helps with the performance of dape
#+begin_src emacs-lisp
(setq read-process-output-max (* 1024 1024)) ;; 1mb
#+end_src
** org-mode
*** General
#+begin_src emacs-lisp
(setq org-todo-keywords '((sequence "TODO" "IN-PROGRESS" "|" "DONE" "BACKLOG")))
(setq org-directory "~/Notes/")
(evil-define-key 'normal calendar-mode-map (kbd "RET") #'org-calendar-select)
;; This is for org-clock-report
(setq org-duration-format 'h:mm)
#+end_src
There's an issue when I invoke ~org-clock-in~ where it seems to search all my
~org-agenda-files~ for any open clocks. This is a bit annoying as it opens all
these files whenever I clock in, making clocking in slow. This fixes it but then
existing clocks don't get resolved.
Here's a github [[https://github.com/doomemacs/doomemacs/issues/5317][issue]] describing a little bit about the problem;
#+begin_src emacs-lisp
(with-eval-after-load 'org-clock
(defun joe/org-files-list ()
"A version of `org-files-list' that doesn't open all `org-agenda-files'"
(let ((files '()))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (and (derived-mode-p 'org-mode) (buffer-file-name))
(cl-pushnew (expand-file-name (buffer-file-name)) files
:test #'equal))))
files))
(defun org-resolve-clocks (&optional only-dangling-p prompt-fn last-valid)
"Resolve all currently open Org clocks.
If `only-dangling-p' is non-nil, only ask to resolve dangling
\(i.e., not currently open and valid) clocks."
(interactive "P")
(unless org-clock-resolving-clocks
(let ((org-clock-resolving-clocks t))
(dolist (file (joe/org-files-list))
(let ((clocks (org-find-open-clocks file)))
(dolist (clock clocks)
(let ((dangling (or (not (org-clock-is-active))
(/= (car clock) org-clock-marker))))
(if (or (not only-dangling-p) dangling)
(org-clock-resolve
clock
(or prompt-fn
(lambda (clock)
(format
"Dangling clock started %d mins ago"
(floor (org-time-convert-to-integer
(time-since (cdr clock)))
60))))
(or last-valid
(cdr clock))))))))))))
#+end_src
In this modified function, =dolist= processes the list returned by =buffer-list=
instead of =org-files-list=. For each buffer in the list, we switch to it with
=with-current-buffer= and then test if its major mode is =org-mode=. If it is, we
process the clocks in that buffer as before. We find open clocks in the current
buffer's associated file using =(buffer-file-name buffer)=. It's assumed that all
Org mode buffers have associated files.
#+end_src
*** Visuals
#+begin_src emacs-lisp
(setq org-blank-before-new-entry
'((heading . nil)
(plain-list-item . nil)))
(setq org-cycle-separator-lines 1)
(setq org-hide-emphasis-markers t)
(require 'org-bullets)
(require 'org-fancy-priorities)
;; (setq org-fancy-priorities-list '("π
°" "π
±" "π
²" "π
³" "π
΄"))
;; (setq org-fancy-priorities-list '("β‘" "β¬" "β¬"))
;; (setq org-fancy-priorities-list '("β‘" "β¬" "β¬"))
(setq org-fancy-priorities-list '((?D . "π") (?C . "π") (?B . "βοΈ") (?A . "π")))
(defun joe/org-hook ()
(org-fancy-priorities-mode)
(visual-line-mode)
(org-bullets-mode)
(org-indent-mode)
(olivetti-mode))
(add-hook 'org-mode-hook 'joe/org-hook)
#+end_src
*** org-agenda
#+begin_src emacs-lisp
(setq org-agenda-files '("~/Notes/Schedule.org"))
(setq org-agenda-time-grid '((daily today require-timed)
(700 900 1200 1700 1900 2030 2300)
" βββββ "
" ββββββββββββββββββββ "))
(define-key global-map (kbd "C-c a") #'org-agenda)
(setq org-agenda-custom-commands
'(("n" "Agenda and all TODOs"
((agenda "")
(alltodo "")))
("d" "Daily view"
((agenda ""
((org-agenda-overriding-header "Daily Agenda")
(org-agenda-span 1)
(org-agenda-time-grid nil)
;; (org-agenda-overriding-columns-format "%20ITEM %DEADLINE")
(org-agenda-view-columns-initially nil)
(org-agenda-block-separator ?β)))
(tags-todo "work"
((org-agenda-overriding-header "π₯οΈπΈ Work\n")
(org-agenda-block-separator ?β)))
(tags-todo "gamingpads"
((org-agenda-overriding-header "π₯οΈποΈπΉοΈ Gaming Pads\n")
(org-agenda-block-separator ?β―)))
(tags-todo "admin"
((org-agenda-overriding-header "βπ» Admin\n")
(org-agenda-block-separator ?β―)))
(tags-todo "sideprojects"
((org-agenda-overriding-header "π₯οΈπ§π» Side Projects\n")
(org-agenda-block-separator ?β―)))
))))
#+end_src
*** org-capture
#+begin_src emacs-lisp
(defun joe/capture-leetcode-newfile ()
(let* ((title (read-string "Title: "))
(title (string-replace " " "-" title))
(title (string-replace "." "" title)))
(expand-file-name (format "%s.org" title) "~/Development/coding-challenges/leetcode/")))
(defun joe/capture-leetcode-template ()
(mapconcat
'identity
'(":PROPERTIES:"
":DIFFICULTY: medium"
":END:"
""
"#+TITLE: Leetcode %?"
"#+AUTHOR: Joseph Ferano"
"#+STARTUP: show2levels"
"#+TAGS: "
""
"* Python Solution"
":PROPERTIES:"
":CompletionTime: "
":TimeComplexity: O()"
":MemoryComplexity: O()"
":END:"
":LOGBOOK:"
":END:"
""
"#+begin_src python"
"#+end_src"
"** Review"
"** Techniques & Patterns")
"\n"))
(defun my-org-capture-today ()
(concat "<" (format-time-string "%Y-%m-%d %a") ">"))
(defun joe/capture-daily ()
(concat
"** %^t\n"
"*** Schedule\n"
"*** Resources\n"
"*** Notes"))
(setq org-capture-templates
`(
("e" "Emacs Improvement" entry
(file+headline "Emacs.org" "Quick Wins")
"** TODO ")
("l" "Leetcode Solution" plain
(function ,(lambda () (find-file (joe/capture-leetcode-newfile))))
#'joe/capture-leetcode-template)
("d" "Daily Entry" entry
(file "Daily.org")
#'joe/capture-daily)
("b" "New Book To Read" entry
(file+headline "Books.org" "To Read")
"** %^{Book Title}%^{AUTHOR}p" :prepend t)))
(define-key global-map (kbd "C-c c") #'org-capture)
#+end_src
*** org-babel
#+begin_src emacs-lisp
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(makefile . t)
(ocaml . t)
(python . t)
(C . t)
(haskell . t)
;; (rust . t)
(shell . t)))
(add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("ml" . "src ocaml"))
(add-to-list 'org-structure-template-alist '("rs" . "src rust"))
(add-to-list 'org-structure-template-alist '("py" . "src python"))
(add-to-list 'org-structure-template-alist '("hs" . "src haskell"))
(add-to-list 'org-structure-template-alist '("sh" . "src shell"))
(add-to-list 'org-structure-template-alist '("cs" . "src csharp"))
(add-to-list 'org-structure-template-alist '("cc" . "src C :includes stdio.h stdlib.h"))
(setq org-edit-src-content-indentation 0)
(setq org-src-window-setup 'current-window)
#+end_src
org-tempo provides the ability to insert block snippets in the form of =