3141 lines
106 KiB
Org Mode
3141 lines
106 KiB
Org Mode
#+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: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>
|
||
(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
|
||
sly
|
||
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 "<f2>")
|
||
"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)
|
||
'("<escape>" . 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)
|
||
'("<escape>" . 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 <return>") '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-<f10>") #'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 "<backtab>")
|
||
#'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 "<backtab>")
|
||
;; #'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 "<delete>") #'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 "<f9>") #'joe/save-then-recompile)
|
||
(define-key global-map (kbd "<f10>") #'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 <escape> ^ i p u b l i c SPC <escape> l W y i w P a SPC = > SPC <escape> 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
|
||
*** Common Lisp
|
||
#+begin_src emacs-lisp
|
||
(setq sly-lisp-implementations '((sbcl ("/usr/local/bin/sbcl" "--dynamic-space-size" "4096"))
|
||
(ecl ("/usr/bin/ecl"))))
|
||
#+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 "<f9>") #'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 "<f8>") #'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 "<f5>") #'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 =<el= and
|
||
with the cursor you hit TAB and you complete the following;
|
||
|
||
=#+begin_src emacs-lisp=
|
||
=#+end_src=
|
||
|
||
#+begin_src emacs-lisp
|
||
(require 'org-tempo)
|
||
#+end_src
|
||
*** org-download
|
||
#+begin_src emacs-lisp
|
||
(require 'org-download)
|
||
(setq org-download-image-dir "./Images/")
|
||
(setq org-download-heading-lvl nil)
|
||
#+end_src
|
||
*** org-transclusion
|
||
#+begin_src emacs-lisp
|
||
(define-key global-map (kbd "C-c t t") #'org-transclusion-mode)
|
||
(define-key global-map (kbd "C-c t a") #'org-transclusion-add)
|
||
(define-key global-map (kbd "C-c t r") #'org-transclusion-remove)
|
||
#+end_src
|
||
*** org-roam
|
||
#+begin_src emacs-lisp
|
||
(require 'org-roam)
|
||
(setq org-roam-directory "/home/joe/Notes/Roam/")
|
||
(setq org-roam-node-display-template (concat "${title:*} "
|
||
(propertize "${tags:10}" 'face 'org-tag)))
|
||
|
||
(org-roam-db-autosync-mode)
|
||
(define-key global-map (kbd "C-c n f") #'org-roam-node-find)
|
||
(define-key global-map (kbd "C-c n l") #'org-roam-buffer-toggle)
|
||
(define-key global-map (kbd "C-c n g") #'org-roam-graph)
|
||
(define-key global-map (kbd "C-c n i") #'org-roam-node-insert)
|
||
(define-key global-map (kbd "C-c n c") #'org-roam-capture)
|
||
(org-roam-setup)
|
||
#+end_src
|
||
*** COMMENT org-kanban
|
||
#+begin_src emacs-lisp
|
||
(require 'org-kanban)
|
||
#+end_src
|
||
** Calendar
|
||
#+begin_src emacs-lisp
|
||
(require 'cl)
|
||
(require 'calendar)
|
||
|
||
(defun twelve-month-calendar ()
|
||
(interactive)
|
||
(let ((calendar-buffer (get-buffer-create "12-month calendar"))
|
||
(month 12)
|
||
(year 2012))
|
||
(set-buffer calendar-buffer)
|
||
(setq calendar-frame (make-frame))
|
||
(make-variable-buffer-local 'font-lock-face)
|
||
(set-face-attribute 'default calendar-frame :height 70)
|
||
(set-frame-width calendar-frame 300)
|
||
(erase-buffer)
|
||
(dotimes (i 12)
|
||
(calendar-generate-month month year 0)
|
||
(calendar-increment-month month year -1))
|
||
(calendar-mode)))
|
||
#+end_src
|
||
** Magit
|
||
|
||
The best git porcelain/client I've ever used. Also kill stray magit buffers left
|
||
over as explained [[https://manueluberti.eu/2018/02/17/magit-bury-buffer.html][here]]
|
||
|
||
#+begin_src emacs-lisp
|
||
(require 'magit)
|
||
(setq magit-status-margin '(nil age magit-log-margin-width t 18))
|
||
(defun joe/magit-kill-buffers (param)
|
||
"Restore window configuration and kill all Magit buffers."
|
||
(let ((buffers (magit-mode-get-buffers)))
|
||
(magit-restore-window-configuration)
|
||
(mapc #'kill-buffer buffers)))
|
||
|
||
(when (boundp 'evil-mode)
|
||
(add-hook 'with-editor-mode-hook 'evil-insert-state))
|
||
|
||
(setq magit-bury-buffer-function #'joe/magit-kill-buffers)
|
||
(setq magit-clone-set-remote.pushDefault t)
|
||
(setq magit-clone-default-directory "~/Development/")
|
||
|
||
(defun joe/magit-status-here ()
|
||
(interactive)
|
||
(let ((magit-display-buffer-function
|
||
(lambda (buffer)
|
||
(display-buffer buffer '(display-buffer-same-window)))))
|
||
(magit-status)))
|
||
|
||
(evil-define-key 'normal joe/evil-space-mode-map (kbd "SPC g h") #'joe/magit-status-here)
|
||
;; (define-key magit-mode-map "h" 'backward-char)
|
||
;; (define-key magit-mode-map "l" 'backward-char)
|
||
|
||
#+end_src
|
||
** Restclient
|
||
|
||
#+begin_src emacs-lisp
|
||
(require 'restclient)
|
||
|
||
(add-to-list 'auto-mode-alist '("\\.restclient\\'" . restclient-mode))
|
||
|
||
(with-eval-after-load 'restclient
|
||
(define-key restclient-mode-map (kbd "C-c C-c") #'restclient-http-send-current-stay-in-window)
|
||
(define-key restclient-mode-map (kbd "C-c C-v") #'restclient-http-send-current))
|
||
|
||
#+end_src
|
||
** Initial Buffer
|
||
|
||
#+begin_src emacs-lisp
|
||
(load-file "/home/joe/.config/emacs/elisp/welcome.el")
|
||
#+end_src
|
||
|
||
* COMMENT Local variables
|
||
;; Local Variables:
|
||
;; eval: (add-hook 'after-save-hook '(lambda () (org-babel-tangle)) nil t)
|
||
;; End:
|