1478 lines
48 KiB
Org Mode
1478 lines
48 KiB
Org Mode
#+TITLE: Emacs
|
|
#+STARTUP: content
|
|
|
|
* Configuration :emacs:
|
|
** About my Emacs
|
|
This is my detailed Emacs configuration. It's an ~org~ file that is transpiled to ~emacs-lisp~ as part of the Nix build process.
|
|
|
|
Why an org file? My Emacs config is large, and this enables me to greatly improve readability of its documentation. I edit this file the same way you're reading it (nicely formatted) as I use Emacs and Emacs speaks org. Sounds complicated, but it's really not; [[https://github.com/dustinlyons/nixos-config/blob/main/nixos/default.nix#L215][just a few lines]] in my Nix config. [[https://github.com/dustinlyons/nixos-config/blob/main/darwin/default.nix#L28][MacOS too.]]
|
|
|
|
This is the main configuration, but there also exists one more init file, ~init.el~ , that bootstraps ~org-mode~ before this file is interpreted. That's defined [[https://github.com/dustinlyons/nixos-config/blob/main/shared/files.nix#L5][here]].
|
|
|
|
/Each block of code below is the actual Emacs configuration./ Formally, this style of configuration is named [[https://en.wikipedia.org/wiki/Literate_programming]["literate programming"]].
|
|
|
|
** Personal Information
|
|
Just me!
|
|
|
|
#+NAME: personal-info
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq user-full-name "Jip J. Dekker"
|
|
user-mail-address "jip@dekker.one")
|
|
#+END_SRC
|
|
|
|
** Initialization
|
|
*** Booting up
|
|
Mainly splash screen settings. In the future we may look to optimize performance here.
|
|
|
|
#+NAME: startup
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Turn off the splash screen
|
|
(setq inhibit-startup-screen t)
|
|
;; Turn off the splash screen
|
|
(setq initial-scratch-message nil)
|
|
;; Confirm before exiting Emacs
|
|
(setq confirm-kill-emacs #'yes-or-no-p)
|
|
;; Set default frame size and position
|
|
|
|
(defun adjust-frame-size-and-position (&optional frame)
|
|
"Adjust size and position of FRAME based on its type."
|
|
(if (display-graphic-p frame)
|
|
(let* ((w 150) ; Set to desired width in characters
|
|
(h 50) ; Set to desired height in lines
|
|
(width (* w (frame-char-width frame)))
|
|
(height (* h (frame-char-height frame)))
|
|
(left (max 0 (floor (/ (- (x-display-pixel-width) width) 2))))
|
|
(top (max 0 (floor (/ (- (x-display-pixel-height) height) 2)))))
|
|
|
|
(set-frame-size frame w h)
|
|
(set-frame-position frame left top))
|
|
;; Ensure the menu bar is off in terminal mode
|
|
(when (and (not (display-graphic-p frame))
|
|
(menu-bar-mode 1))
|
|
(menu-bar-mode -1))))
|
|
|
|
(if (daemonp)
|
|
(add-hook 'after-make-frame-functions
|
|
(lambda (frame)
|
|
(select-frame frame)
|
|
(when (system-is-mac) (adjust-frame-size-and-position frame)))
|
|
(adjust-frame-size-and-position)))
|
|
#+END_SRC
|
|
|
|
*** Add package sources
|
|
This associates our package manager with the right source (MELPA).
|
|
|
|
#+NAME: package-sources
|
|
#+BEGIN_SRC emacs-lisp
|
|
(unless (assoc-default "melpa" package-archives)
|
|
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t))
|
|
(unless (assoc-default "nongnu" package-archives)
|
|
(add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t))
|
|
#+END_SRC
|
|
|
|
*** System Definitions
|
|
**** Conditionals
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun system-is-mac ()
|
|
"Return true if system is darwin-based (Mac OS X)"
|
|
(string-equal system-type "darwin"))
|
|
|
|
(defun system-is-linux ()
|
|
"Return true if system is GNU/Linux-based"
|
|
(string-equal system-type "gnu/linux"))
|
|
|
|
;; Set path for darwin
|
|
(when (system-is-mac)
|
|
(setenv "PATH" (concat (getenv "PATH") ":/Users/dekker1/.nix-profile/bin:/usr/bin"))
|
|
(setq exec-path (append '("/Users/dekker1/bin" "/profile/bin" "/Users/dekker1/.npm-packages/bin" "/Users/dekker1/.nix-profile/bin" "/nix/var/nix/profiles/default/bin" "/usr/local/bin" "/usr/bin") exec-path)))
|
|
#+END_SRC
|
|
|
|
*** Counsel/Ivy framework
|
|
Ivy and associated helpers that uses the minibuffer. Ivy describes itself as "a generic completion mechanism for Emacs." Basically, it's a prettier popup window to input Emacs commands. I've defined this to show at the bottom of the screen, which is conveniently also the default.
|
|
|
|
#+NAME: ivy-framework
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package counsel
|
|
:demand t
|
|
:bind (("M-x" . counsel-M-x)
|
|
("C-x b" . counsel-ibuffer)
|
|
("C-x C-f" . counsel-find-file)
|
|
("C-M-j" . counsel-switch-buffer)
|
|
:map minibuffer-local-map
|
|
("C-r" . 'counsel-minibuffer-history))
|
|
:custom
|
|
(counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only)
|
|
:config
|
|
(setq ivy-initial-inputs-alist nil)) ;; Don't start searches with ^
|
|
|
|
(use-package prescient
|
|
:config
|
|
(prescient-persist-mode 1))
|
|
|
|
(use-package ivy
|
|
:bind (("C-s" . swiper-all)
|
|
:map ivy-minibuffer-map
|
|
("TAB" . ivy-partial-or-done)
|
|
("C-f" . ivy-alt-done)
|
|
("C-l" . ivy-alt-done)
|
|
("C-j" . ivy-next-line)
|
|
("C-k" . ivy-previous-line)
|
|
:map ivy-switch-buffer-map
|
|
("C-k" . ivy-previous-line)
|
|
("C-l" . ivy-done)
|
|
("C-d" . ivy-switch-buffer-kill)
|
|
:map ivy-reverse-i-search-map
|
|
("C-k" . ivy-previous-line)
|
|
("C-d" . ivy-reverse-i-search-kill))
|
|
:init
|
|
(ivy-mode 1)
|
|
:config
|
|
(setq ivy-use-virtual-buffers t)
|
|
(setq ivy-wrap t)
|
|
(setq ivy-count-format "(%d/%d) ")
|
|
(setq enable-recursive-minibuffers t))
|
|
|
|
(use-package ivy-rich
|
|
:init (ivy-rich-mode 1))
|
|
|
|
(use-package ivy-prescient
|
|
:after ivy
|
|
:custom
|
|
(prescient-save-file "~/.emacs.d/prescient-data")
|
|
(prescient-filter-method 'fuzzy)
|
|
:config
|
|
(ivy-prescient-mode t))
|
|
|
|
(use-package all-the-icons-ivy
|
|
:init (add-hook 'after-init-hook 'all-the-icons-ivy-setup))
|
|
#+END_SRC
|
|
|
|
*** Leader keys
|
|
I use ~general.el~ to define groups of keybindings under my 'leader' definition. You will see these definitions sprinkled throughout this file; they are just quick shortcuts. For more info, [[https://medium.com/usevim/vim-101-what-is-the-leader-key-f2f5c1fa610f][here]] is a good explanation on leader keys.
|
|
|
|
#+NAME: keybindings
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; ESC will also cancel/quit/etc.
|
|
(global-set-key (kbd "<escape>") 'keyboard-escape-quit)
|
|
(use-package general
|
|
:init
|
|
(setq evil-want-keybinding nil)
|
|
:config
|
|
(general-evil-setup t)
|
|
(general-create-definer dl/leader-keys
|
|
:keymaps '(normal visual emacs)
|
|
:prefix ","))
|
|
#+END_SRC
|
|
|
|
**** Emacs cleanup
|
|
Helpful keybindings to help keep Emacs sane.
|
|
|
|
#+NAME: emacs-cleanup
|
|
#+BEGIN_SRC emacs-lisp
|
|
(dl/leader-keys
|
|
"k" '(:ignore k :which-key "cleanup")
|
|
"ko" '(kill-buffer-and-window :which-key "kill buffer and window")
|
|
"kk" '(kill-some-buffers :which-key "cleanup buffers"))
|
|
(global-set-key (kbd "C-x -") 'kill-buffer-and-window)
|
|
#+END_SRC
|
|
|
|
**** Treemacs
|
|
#+NAME: treemacs
|
|
#+BEGIN_SRC emacs-lisp
|
|
(dl/leader-keys
|
|
"t" '(:ignore t :which-key "treemacs")
|
|
"tt" '(treemacs :which-key "toggle treemacs")
|
|
"tx" '(treemacs-collapse-all-projects :which-key "collapse projects")
|
|
"to" '(treemacs-select-window :which-key "select treemacs")
|
|
"tw" '(treemacs-toggle-fixed-width :which-key "size treemacs"))
|
|
#+END_SRC
|
|
|
|
**** Toggles
|
|
Various UI related toggles.
|
|
#+NAME: toggles-ui
|
|
#+BEGIN_SRC emacs-lisp
|
|
(dl/leader-keys
|
|
"h" '(counsel-load-theme :which-key "choose theme"))
|
|
#+END_SRC
|
|
|
|
***** Rotate windows
|
|
Various helpers and packages I find useful for window management.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Rotates windows and layouts
|
|
(use-package rotate
|
|
:config)
|
|
|
|
(dl/leader-keys
|
|
"r" '(:ignore t :which-key "rotate")
|
|
"rw" '(rotate-window :which-key "rotate window")
|
|
"rl" '(rotate-layout :which-key "rotate layout"))
|
|
#+END_SRC
|
|
|
|
*** Gutter
|
|
**** Line numbers
|
|
These functions define vim-style relative line numbers. This means my line numbers look like -1, -2, 0, 1, 2...
|
|
|
|
*** Modes
|
|
**** Window minor modes
|
|
I like these window related minor modes.
|
|
|
|
#+NAME: windows-ui-settings
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Turn off UI junk
|
|
;; Note to future self: If you have problems with these later,
|
|
;; move these into custom file and set variable custom-file
|
|
(column-number-mode)
|
|
(scroll-bar-mode 0)
|
|
(menu-bar-mode -1)
|
|
(tool-bar-mode 0)
|
|
(winner-mode 1) ;; ctrl-c left, ctrl-c right for window undo/redo
|
|
#+END_SRC
|
|
|
|
**** Set mode margins
|
|
This is used primarily to center org mode text.
|
|
|
|
#+NAME: mode-margins
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun dl/org-mode-visual-fill ()
|
|
(setq visual-fill-column-width 110
|
|
visual-fill-column-center-text t))
|
|
|
|
(use-package visual-fill-column
|
|
:defer t
|
|
:hook (org-mode . dl/org-mode-visual-fill))
|
|
#+END_SRC
|
|
|
|
**** Don't blink the cursor
|
|
#+NAME: cursor-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
(blink-cursor-mode -1)
|
|
#+END_SRC
|
|
|
|
**** Colors
|
|
***** Rainbow delimiters
|
|
Makes my lisp parens pretty, and easy to spot.
|
|
|
|
#+NAME: rainbow-delmiters
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package rainbow-delimiters
|
|
:hook (prog-mode . rainbow-delimiters-mode))
|
|
#+END_SRC
|
|
|
|
***** Color definitions
|
|
Define a global set of colors to be used everywhere in Emacs.
|
|
|
|
#+NAME: color-definitions
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar dl/black-color "#1F2528")
|
|
(defvar dl/red-color "#EC5F67")
|
|
(defvar dl/yellow-color "#FAC863")
|
|
(defvar dl/blue-color "#6699CC")
|
|
(defvar dl/green-color "#99C794")
|
|
(defvar dl/purple-color "#C594C5")
|
|
(defvar dl/teal-color "#5FB3B3")
|
|
(defvar dl/light-grey-color "#C0C5CE")
|
|
(defvar dl/dark-grey-color "#65737E")
|
|
#+END_SRC
|
|
|
|
**** Addons
|
|
***** "Powerline"
|
|
Keeps info at my fingertips. Modeline is much better than Vim's Powerline (sorry Vim).
|
|
|
|
#+NAME: modeline
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Run M-x all-the-icons-install-fonts to install
|
|
(use-package all-the-icons)
|
|
(use-package doom-modeline
|
|
:ensure t
|
|
:init (doom-modeline-mode 1))
|
|
#+END_SRC
|
|
|
|
***** Treemacs
|
|
Although I'm primarily a keyboard user and use Projectile for quickly finding files, I still find the need to browse through files in a more visual way. Treemacs does the job, and beautifully might I add.
|
|
|
|
#+NAME: treemacs
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package treemacs
|
|
:config
|
|
(setq treemacs-is-never-other-window 1)
|
|
:bind
|
|
("C-c t" . treemacs-find-file)
|
|
("C-c b" . treemacs-bookmark))
|
|
|
|
(use-package treemacs-icons-dired)
|
|
(use-package treemacs-all-the-icons)
|
|
(use-package treemacs-projectile)
|
|
(use-package treemacs-magit)
|
|
(use-package treemacs-evil)
|
|
#+END_SRC
|
|
|
|
**** Easy window motions with ace-window
|
|
Predefine windows with hotkeys and jump to them.
|
|
|
|
#+NAME: easy-window-motions
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Remove binding for facemap-menu, use for ace-window instead
|
|
(global-unset-key (kbd "M-o"))
|
|
|
|
(use-package ace-window
|
|
:bind (("M-o" . ace-window))
|
|
:custom
|
|
(aw-scope 'frame)
|
|
(aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
|
|
(aw-minibuffer-flag t)
|
|
:config
|
|
(ace-window-display-mode 1))
|
|
#+END_SRC
|
|
|
|
*** Package managers
|
|
Using ~straight.el~ under the hood of ~use-package~ enables us to download packages using ~git~. This is preferred for easier hacking; I maintain my own ~org-roam~ fork, for example, and it's just another directory where I organize code. I configure ~straight.el~ with one line to use it.
|
|
|
|
*** Windows
|
|
**** Fonts
|
|
#+NAME: fonts
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Set the default pitch face
|
|
(when (system-is-linux)
|
|
(set-face-attribute 'default nil :font "JetBrainsMono" :height 100))
|
|
(when (system-is-mac)
|
|
(set-face-attribute 'default nil :font "JetBrains Mono" :height 140))
|
|
|
|
;; Set the fixed pitch face
|
|
(when (system-is-linux)
|
|
(set-face-attribute 'fixed-pitch nil :font "JetBrainsMono" :weight 'normal :height 100))
|
|
(when (system-is-mac)
|
|
(set-face-attribute 'fixed-pitch nil :font "JetBrains Mono" :weight 'normal :height 150))
|
|
|
|
;; Set the variable pitch face
|
|
(when (system-is-linux)
|
|
(set-face-attribute 'variable-pitch nil :font "Helvetica LT Std Condensed" :weight 'normal :height 140))
|
|
(when (system-is-mac)
|
|
(set-face-attribute 'variable-pitch nil :font "Helvetica" :weight 'normal :height 170))
|
|
|
|
#+END_SRC
|
|
|
|
*** Dashboard
|
|
#+NAME: dashboard-settings
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package dashboard
|
|
:ensure t
|
|
:config
|
|
(dashboard-setup-startup-hook)
|
|
(setq dashboard-startup-banner 'ascii
|
|
dashboard-center-content t
|
|
dashboard-items '((projects . 5)
|
|
(recents . 5)))
|
|
(setq dashboard-set-footer nil))
|
|
|
|
(setq dashboard-banner-logo-title "Welcome to your life")
|
|
(setq dashboard-set-file-icons t)
|
|
(setq dashboard-projects-backend 'projectile)
|
|
|
|
(setq initial-buffer-choice (lambda ()
|
|
(get-buffer-create "*dashboard*")
|
|
(dashboard-refresh-buffer)))
|
|
(setq dashboard-projects-switch-function 'counsel-projectile-switch-project-by-name)
|
|
#+END_SRC
|
|
|
|
** Keybindings
|
|
*** Spaces over tabs
|
|
We use two spaces in place of tabs. I don't even want to hear it.
|
|
|
|
#+NAME: next-buffer
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq-default indent-tabs-mode nil
|
|
js-indent-level 2
|
|
tab-width 2)
|
|
(setq-default evil-shift-width 2)
|
|
#+END_SRC
|
|
|
|
*** Buffers
|
|
#+NAME: next-buffer
|
|
#+BEGIN_SRC emacs-lisp
|
|
(global-set-key (kbd "<C-tab>") 'next-buffer)
|
|
#+END_SRC
|
|
|
|
** Display options
|
|
*** Themes
|
|
**** Doom Emacs
|
|
#+NAME: themes-autothemer
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package doom-themes
|
|
:ensure t
|
|
:config
|
|
(setq doom-themes-enable-bold t
|
|
doom-themes-enable-italic t)
|
|
(load-theme 'doom-one t)
|
|
(doom-themes-visual-bell-config)
|
|
(doom-themes-org-config))
|
|
#+END_SRC
|
|
|
|
** Global Settings
|
|
*** Global Modes
|
|
I like these modes, what can I say. They're good to me.
|
|
|
|
#+NAME: global-modes
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defalias 'yes-or-no-p 'y-or-n-p) ;; Use Y or N in prompts, instead of full Yes or No
|
|
|
|
(global-visual-line-mode t) ;; Wraps lines everywhere
|
|
(global-auto-revert-mode t) ;; Auto refresh buffers from disk
|
|
(line-number-mode t) ;; Line numbers in the gutter
|
|
(show-paren-mode t) ;; Highlights parans for me
|
|
|
|
(setq warning-minimum-level :error)
|
|
#+END_SRC
|
|
|
|
** Org mode
|
|
*** Agenda
|
|
Initialize org-agenda file and set some key bindings to create tasks.
|
|
#+NAME::org-mode-agenda
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq org-agenda-files "~/.emacs.d/agenda.txt" )
|
|
|
|
(defun my-org-insert-subheading (heading-type)
|
|
"Inserts a new org heading with unique ID and creation date.
|
|
The type of heading (TODO, PROJECT, etc.) is specified by HEADING-TYPE."
|
|
(let ((uuid (org-id-uuid))
|
|
(date (format-time-string "[%Y-%m-%d %a %H:%M]")))
|
|
(org-end-of-line) ;; Make sure we are at the end of the line
|
|
(unless (looking-at-p "\n") (insert "\n")) ;; Insert newline if next character is not a newline
|
|
(org-insert-subheading t) ;; Insert a subheading instead of a heading
|
|
(insert (concat heading-type " "))
|
|
(save-excursion
|
|
(org-set-property "ID" uuid)
|
|
(org-set-property "CREATED" date))))
|
|
|
|
(defun my-org-insert-todo ()
|
|
"Inserts a new TODO heading with unique ID and creation date."
|
|
(interactive)
|
|
(my-org-insert-subheading "TODO"))
|
|
|
|
(defun my-org-insert-project ()
|
|
"Inserts a new PROJECT heading with unique ID and creation date."
|
|
(interactive)
|
|
(my-org-insert-subheading "PROJECT"))
|
|
|
|
(defun my-org-copy-link-from-id ()
|
|
"Copies a link to the current Org mode item by its ID to clipboard"
|
|
(interactive)
|
|
(when (org-at-heading-p)
|
|
(let* ((element (org-element-at-point))
|
|
(title (org-element-property :title element))
|
|
(id (org-entry-get nil "ID"))
|
|
(link (format "[[id:%s][%s]]" id title)))
|
|
(when id
|
|
(kill-new link)
|
|
(message "Link saved to clipboard")))))
|
|
|
|
(define-prefix-command 'my-org-todo-prefix)
|
|
|
|
(global-set-key (kbd "C-c c") 'org-capture)
|
|
(global-set-key (kbd "C-c t") 'my-org-todo-prefix)
|
|
|
|
(define-key 'my-org-todo-prefix (kbd "t") 'my-org-insert-todo)
|
|
(define-key 'my-org-todo-prefix (kbd "p") 'my-org-insert-project)
|
|
|
|
(define-key org-mode-map (kbd "C-c l") 'my-org-copy-link-from-id)
|
|
#+END_SRC
|
|
|
|
**** Set org faces
|
|
Set various types and colors for ~org-mode~.
|
|
|
|
#+NAME::org-mode-faces
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Fast access to tag common contexts I use
|
|
(setq org-todo-keywords
|
|
'((sequence "TODO(t)" "STARTED(s)" "WAITING(w@/!)"
|
|
"DELEGATED(g@/!)" "DEFERRED(r)" "SOMEDAY(y)"
|
|
"|" "DONE(d@)" "CANCELED(x@)")
|
|
(sequence "PROJECT(p)" "|" "DONE(d@)" "CANCELED(x@)")
|
|
(sequence "APPT(a)" "|" "DONE(d@)" "CANCELED(x@)")))
|
|
|
|
(setq org-todo-keyword-faces
|
|
`(("TODO" . ,dl/green-color)
|
|
("STARTED" . ,dl/yellow-color)
|
|
("WAITING" . ,dl/light-grey-color)
|
|
("DELEGATED" . ,dl/teal-color)
|
|
("DEFERRED" . ,dl/dark-grey-color)
|
|
("SOMEDAY" . ,dl/purple-color)
|
|
("DONE" . ,dl/dark-grey-color)
|
|
("CANCELED" . ,dl/dark-grey-color)
|
|
("PROJECT" . ,dl/blue-color)
|
|
("APPT" . ,dl/green-color)))
|
|
|
|
(defface my-org-agenda-face-1-2
|
|
'((t (:inherit default :height 1.2)))
|
|
"Face for org-agenda mode.")
|
|
|
|
(defun my-set-org-agenda-font ()
|
|
"Set the font for `org-agenda-mode'."
|
|
(buffer-face-set 'my-org-agenda-face-1-2))
|
|
|
|
(add-hook 'org-agenda-mode-hook 'my-set-org-agenda-font)
|
|
|
|
(setq display-buffer-alist
|
|
`((".*Org Agenda.*"
|
|
(display-buffer-below-selected)
|
|
(inhibit-same-window . t)
|
|
(window-height . 0.5))))
|
|
|
|
#+END_SRC
|
|
|
|
**** Format org-agenda views
|
|
This block sets the ~org-agenda-prefix-format~ in an friendly way for ~org-roam~ (credit to [[https://d12frosted.io/posts/2020-06-24-task-management-with-roam-vol2.html][this post)]]. It truncates long filenames and removes the ~org-roam~ timestamp slug.
|
|
|
|
#+NAME::org-agenda-prefixes
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun dl/buffer-prop-get (name)
|
|
"Get a buffer property called NAME as a string."
|
|
(org-with-point-at 1
|
|
(when (re-search-forward (concat "^#\\+" name ": \\(.*\\)")
|
|
(point-max) t)
|
|
(buffer-substring-no-properties
|
|
(match-beginning 1)
|
|
(match-end 1)))))
|
|
|
|
(defun dl/agenda-category (&optional len)
|
|
"Get category of item at point for agenda."
|
|
(let* ((file-name (when buffer-file-name
|
|
(file-name-sans-extension
|
|
(file-name-nondirectory buffer-file-name))))
|
|
(title (dl/buffer-prop-get "title"))
|
|
(category (org-get-category))
|
|
(result (or (if (and title (string-equal category file-name))
|
|
title
|
|
category))))
|
|
(if (numberp len)
|
|
(s-truncate len (s-pad-right len " " result))
|
|
result)))
|
|
|
|
(evil-set-initial-state 'org-agenda-mode 'normal)
|
|
(with-eval-after-load 'org-agenda
|
|
(define-key org-agenda-mode-map (kbd "j") 'org-agenda-next-line)
|
|
(define-key org-agenda-mode-map (kbd "k") 'org-agenda-previous-line))
|
|
|
|
(setq org-agenda-todo-ignore-keywords '("PROJECT"))
|
|
#+END_SRC
|
|
|
|
**** org-super-agenda views
|
|
Setup for ~org-super-agenda~ and ~org-ql~.
|
|
|
|
#+NAME::org-super-agenda
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package org-super-agenda
|
|
:after org-agenda
|
|
:init
|
|
(setq org-agenda-dim-blocked-tasks nil))
|
|
|
|
;; Define custom faces for group highlighting
|
|
(defface org-super-agenda-header
|
|
'((t (:inherit org-agenda-structure :height 1.1 :foreground "#7cc3f3" :background "#282c34")))
|
|
"Face for highlighting org-super-agenda groups.")
|
|
|
|
(defface org-super-agenda-subheader
|
|
'((t (:inherit org-agenda-structure :height 1.0 :foreground "light slate gray" :background "black")))
|
|
"Face for highlighting org-super-agenda subgroups.")
|
|
|
|
;; Apply the custom faces to org-super-agenda
|
|
(custom-set-faces
|
|
'(org-super-agenda-header ((t (:inherit org-agenda-structure :height 1.1 :foreground "#7cc3f3" :background "#282c34"))))
|
|
'(org-super-agenda-subheader ((t (:inherit org-agenda-structure :height 1.0 :foreground "light slate gray" :background "black")))))
|
|
|
|
(setq org-super-agenda-groups
|
|
'((:name "Priority A"
|
|
:priority "A")
|
|
(:name "Priority B"
|
|
:priority "B")
|
|
(:name "Priority C"
|
|
:priority "C")
|
|
(:name "Started"
|
|
:todo "STARTED")
|
|
(:name "Waiting"
|
|
:todo "WAITING")
|
|
(:name "Tasks"
|
|
:todo "TODO")
|
|
(:name "Someday"
|
|
:todo "SOMEDAY")
|
|
(:name "Projects"
|
|
:tag "PROJECT")))
|
|
|
|
(org-super-agenda-mode)
|
|
#+END_SRC
|
|
|
|
**** org-transclusion
|
|
Let's us move text but still see it in another file. I primarily use this to move text around in my journal.
|
|
|
|
#+NAME::org-transclusion
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package org-transclusion
|
|
:after org
|
|
:hook (org-mode . org-transclusion-mode))
|
|
|
|
(defun org-global-props (&optional property buffer)
|
|
"Helper function to grab org properties"
|
|
(unless property (setq property "PROPERTY"))
|
|
(with-current-buffer (or buffer (current-buffer))
|
|
(org-element-map (org-element-parse-buffer) 'keyword
|
|
(lambda (el) (when (string-match property (org-element-property :key el)) el)))))
|
|
|
|
#+END_SRC
|
|
|
|
*** Install package
|
|
If you haven't heard of org mode, go watch [[https://www.youtube.com/watch?v=SzA2YODtgK4][this]] talk and come back when you are finished.
|
|
|
|
**** Leader key shortcuts
|
|
#+NAME::org-mode-quick-entry
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar current-time-format "%H:%M:%S"
|
|
"Format of date to insert with `insert-current-time' func.
|
|
Note the weekly scope of the command's precision.")
|
|
|
|
(defun dl/find-file (path)
|
|
"Helper function to open a file in a buffer"
|
|
(interactive)
|
|
(find-file path))
|
|
|
|
(defun dl/reload-emacs ()
|
|
"Reload the emacs configuration"
|
|
(interactive)
|
|
(load "~/.emacs.d/init.el"))
|
|
|
|
(defun dl/insert-header ()
|
|
"Insert a header indented one level from the current header, unless the current header is a timestamp."
|
|
(interactive)
|
|
(let* ((level (org-current-level))
|
|
(headline (org-get-heading t t t t))
|
|
(next-level (if (string-match "^\\([0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\)" headline)
|
|
(1+ level)
|
|
level)))
|
|
(end-of-line)
|
|
(newline)
|
|
(insert (make-string next-level ?*))
|
|
(insert " ")))
|
|
|
|
(defun dl/insert-current-time ()
|
|
"Insert the current time into the current buffer, at a level one deeper than the current heading."
|
|
(interactive)
|
|
(let* ((level (org-current-level))
|
|
(next-level (1+ level)))
|
|
(end-of-line)
|
|
(newline)
|
|
(insert (make-string next-level ?*))
|
|
(insert " " (format-time-string "%H:%M:%S" (current-time)) "\n")))
|
|
|
|
"Emacs relates shortcuts"
|
|
(dl/leader-keys
|
|
"e" '(:ignore t :which-key "emacs")
|
|
"ee" '(dl/load-buffer-with-emacs-config :which-key "open emacs config")
|
|
"er" '(dl/reload-emacs :which-key "reload emacs"))
|
|
|
|
"A few of my own personal shortcuts"
|
|
(dl/leader-keys
|
|
"," '(dl/insert-header :which-key "insert header")
|
|
"<" '(dl/insert-current-time :which-key "insert header with current time"))
|
|
#+END_SRC
|
|
|
|
***** Roam capture templates
|
|
These are templates used to create new notes.
|
|
|
|
#+NAME::roam-templates
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq org-roam-capture-templates
|
|
'(("d" "default" plain
|
|
"%?"
|
|
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n\n")
|
|
:unnarrowed t)))
|
|
#+END_SRC
|
|
|
|
**** Org Roam
|
|
***** Install package
|
|
#+NAME::org-roam-package
|
|
#+BEGIN_SRC emacs-lisp
|
|
(require 'ucs-normalize)
|
|
(use-package org-roam
|
|
:straight (:host github :repo "org-roam/org-roam"
|
|
:branch "master"
|
|
:files (:defaults "extensions/*")
|
|
:build (:not compile))
|
|
:init
|
|
(setq org-roam-v2-ack t) ;; Turn off v2 warning
|
|
(setq org-roam-mode-section-functions
|
|
(list #'org-roam-backlinks-section
|
|
#'org-roam-reflinks-section
|
|
#'org-roam-unlinked-references-section))
|
|
(add-to-list 'display-buffer-alist
|
|
'("\\*org-roam\\*"
|
|
(display-buffer-in-direction)
|
|
(direction . right)
|
|
(window-width . 0.33)
|
|
(window-height . fit-window-to-buffer)))
|
|
:custom
|
|
(org-roam-directory (file-truename "~/.local/share/org-roam"))
|
|
(org-roam-dailies-directory "daily/")
|
|
(org-roam-completion-everywhere t)
|
|
:bind
|
|
(("C-c r b" . org-roam-buffer-toggle)
|
|
("C-c r t" . org-roam-dailies-goto-today)
|
|
("C-c r y" . org-roam-dailies-goto-yesterday)
|
|
("C-M-n" . org-roam-node-insert)
|
|
:map org-mode-map
|
|
("C-M-i" . completion-at-point)
|
|
("C-M-f" . org-roam-node-find)
|
|
("C-M-c" . dl/org-roam-create-id)
|
|
("C-<left>" . org-roam-dailies-goto-previous-note)
|
|
("C-`" . org-roam-buffer-toggle)
|
|
("C-<right>" . org-roam-dailies-goto-next-note)))
|
|
(org-roam-db-autosync-mode)
|
|
#+END_SRC
|
|
|
|
***** Configure templates
|
|
#+NAME::org-roam-templates
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq org-roam-dailies-capture-templates
|
|
'(("d" "default" entry
|
|
"* %?"
|
|
:if-new (file+head "%<%Y-%m-%d>.org"
|
|
(lambda () (concat ":PROPERTIES:\n:ID: " (org-id-new) "\n:END:\n"
|
|
"#+TITLE: %<%Y-%m-%d>\n#+filetags: Daily \n" ; Added space here
|
|
"* Log\n"))))))
|
|
#+END_SRC
|
|
|
|
***** Extending Roam
|
|
Here we add additional function to ~org-roam~ to either do something specific for more workflow, or otherwise make ~org-roam~ more full featured.
|
|
|
|
****** Set CREATED and LAST_MODIFIED filetags on save
|
|
Sets timestamps in the Properties drawer of files. I intend to use this one day when rendering these notes as HTML, to quickly see files last touched.
|
|
|
|
#+NAME::org-roam-set-timestamps-on-save
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defvar dl/org-created-property-name "CREATED")
|
|
|
|
(defun dl/org-set-created-property (&optional active name)
|
|
(interactive)
|
|
(let* ((created (or name dl/org-created-property-name))
|
|
(fmt (if active "<%s>" "[%s]"))
|
|
(now (format fmt (format-time-string "%Y-%m-%d %a %H:%M"))))
|
|
(unless (org-entry-get (point) created nil)
|
|
(org-set-property created now)
|
|
now)))
|
|
|
|
(defun dl/org-find-time-file-property (property &optional anywhere)
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(let ((first-heading
|
|
(save-excursion
|
|
(re-search-forward org-outline-regexp-bol nil t))))
|
|
(when (re-search-forward (format "^#\\+%s:" property)
|
|
(if anywhere nil first-heading) t)
|
|
(point)))))
|
|
|
|
(defun dl/org-has-time-file-property-p (property &optional anywhere)
|
|
(when-let ((pos (dl/org-find-time-file-property property anywhere)))
|
|
(save-excursion
|
|
(goto-char pos)
|
|
(if (and (looking-at-p " ")
|
|
(progn (forward-char)
|
|
(org-at-timestamp-p 'lax)))
|
|
pos -1))))
|
|
|
|
(defun dl/org-set-time-file-property (property &optional anywhere pos)
|
|
(when-let ((pos (or pos
|
|
(dl/org-find-time-file-property property))))
|
|
(save-excursion
|
|
(goto-char pos)
|
|
(if (looking-at-p " ")
|
|
(forward-char)
|
|
(insert " "))
|
|
(delete-region (point) (line-end-position))
|
|
(let* ((now (format-time-string "[%Y-%m-%d %a %H:%M]")))
|
|
(insert now)))))
|
|
|
|
(defun dl/org-set-last-modified ()
|
|
"Update the LAST_MODIFIED file property in the preamble."
|
|
(when (derived-mode-p 'org-mode)
|
|
(dl/org-set-time-file-property "LAST_MODIFIED")))
|
|
#+END_SRC
|
|
|
|
****** Set CREATED on node creation
|
|
#+NAME::org-roam-set-timestamps-on-save
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun dl/org-roam-create-id ()
|
|
"Add created date to org-roam node."
|
|
(interactive)
|
|
(org-id-get-create)
|
|
(dl/org-set-created-property))
|
|
#+END_SRC
|
|
|
|
*** UI improvements
|
|
Anything related to improving org mode's appearance.
|
|
|
|
**** Change color of ivy window selection
|
|
#+NAME::ivy-window-selection
|
|
#+BEGIN_SRC emacs-lisp
|
|
(set-face-attribute 'ivy-current-match nil :foreground "#3d434d" :background "#ffcc66")
|
|
#+END_SRC
|
|
|
|
**** Change default bullets to be pretty
|
|
Replaces the standard org-mode header asterisks with dots.
|
|
#+NAME::org-mode-visuals
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package org-superstar
|
|
:after org
|
|
:hook (org-mode . org-superstar-mode)
|
|
:custom
|
|
(org-superstar-remove-leading-stars t)
|
|
(org-superstar-headline-bullets-list '("•" "•" "•" "◦" "◦" "◦" "◦")))
|
|
#+END_SRC
|
|
|
|
**** Fonts
|
|
#+NAME::org-mode-variable-width-fonts
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-hook 'org-mode-hook 'variable-pitch-mode)
|
|
(require 'org-indent)
|
|
(set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch)
|
|
(set-face-attribute 'org-table nil :inherit 'fixed-pitch)
|
|
(set-face-attribute 'org-formula nil :inherit 'fixed-pitch)
|
|
(set-face-attribute 'org-code nil :inherit '(shadow fixed-pitch))
|
|
(set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch))
|
|
(set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
|
|
(set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
|
|
(set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch))
|
|
(set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch)
|
|
(when (system-is-linux)
|
|
(set-face-attribute 'org-document-title nil :font "Helvetica LT Std Condensed" :weight 'bold :height 1.2))
|
|
(when (system-is-mac)
|
|
(set-face-attribute 'variable-pitch nil :font "Helvetica" :height 120))
|
|
(dolist (face '((org-level-1 . 1.2)
|
|
(org-level-2 . 1.15)
|
|
(org-level-3 . 1.1)
|
|
(org-level-4 . 1.05)
|
|
(org-level-5 . 1.05)
|
|
(org-level-6 . 1.0)
|
|
(org-level-7 . 1.0)
|
|
(org-level-8 . 1.0)))
|
|
(when (system-is-linux)
|
|
(set-face-attribute (car face) nil :font "Helvetica LT Std Condensed" :weight 'medium :height (cdr face)))
|
|
(when (system-is-mac)
|
|
(set-face-attribute 'variable-pitch nil :font "Helvetica" :weight 'medium :height 170)))
|
|
#+END_SRC
|
|
|
|
** Evil mode (aka Vim mode)
|
|
*** Install package
|
|
This is what makes emacs possible for me. All evil mode packages and related configuration.
|
|
|
|
#+NAME: evil-packages
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun dl/evil-hook ()
|
|
(dolist (mode '(eshell-mode
|
|
git-rebase-mode
|
|
term-mode))
|
|
(add-to-list 'evil-emacs-state-modes mode))) ;; no evil mode for these modes
|
|
|
|
(use-package evil
|
|
:init
|
|
(setq evil-want-integration t) ;; TODO: research what this does
|
|
(setq evil-want-fine-undo 'fine) ;; undo/redo each motion
|
|
(setq evil-want-Y-yank-to-eol t) ;; Y copies to end of line like vim
|
|
(setq evil-want-C-u-scroll t) ;; vim like scroll up
|
|
(evil-mode 1)
|
|
:hook (evil-mode . dl/evil-hook)
|
|
:config
|
|
;; Emacs "cancel" == vim "cancel"
|
|
(define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state)
|
|
|
|
;; Ctrl-h deletes in vim insert mode
|
|
(define-key evil-insert-state-map (kbd "C-h")
|
|
'evil-delete-backward-char-and-join)
|
|
|
|
;; When we wrap lines, jump visually, not to the "actual" next line
|
|
(evil-global-set-key 'motion "j" 'evil-next-visual-line)
|
|
(evil-global-set-key 'motion "k" 'evil-previous-visual-line)
|
|
|
|
(evil-set-initial-state 'message-buffer-mode 'normal)
|
|
(evil-set-initial-state 'dashboard-mode 'normal))
|
|
|
|
;; Gives me vim bindings elsewhere in emacs
|
|
(use-package evil-collection
|
|
:after evil
|
|
:config
|
|
(evil-collection-init))
|
|
|
|
;; Keybindings in org mode
|
|
(use-package evil-org
|
|
:after evil
|
|
:hook
|
|
(org-mode . (lambda () evil-org-mode))
|
|
:config
|
|
(require 'evil-org-agenda)
|
|
(evil-org-agenda-set-keys))
|
|
|
|
;; Branching undo system
|
|
(use-package undo-tree
|
|
:after evil
|
|
:diminish
|
|
:config
|
|
(evil-set-undo-system 'undo-tree)
|
|
(global-undo-tree-mode 1))
|
|
|
|
(use-package evil-commentary
|
|
:after evil
|
|
:config
|
|
(evil-commentary-mode))
|
|
|
|
;; Keep undo files from littering directories
|
|
(setq undo-tree-history-directory-alist '(("." . "~/.local/state/emacs/undo")))
|
|
#+END_SRC
|
|
|
|
** Terminal
|
|
#+NAME: vterm
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package vterm
|
|
:commands vterm
|
|
:config
|
|
(setq term-prompt-regexp "^[^#$%>\n]*[#$%>] *")
|
|
(setq vterm-shell "zsh")
|
|
(setq vterm-max-scrollback 10000))
|
|
#+END_SRC
|
|
|
|
** Managing files
|
|
Configuration related to filesystem, either basic IO and interaction from emacs or directly moving files around where it makes sense.
|
|
*** File browser
|
|
~dired~ provides a more visual interface to browsing files; similar to terminal programs like ~ranger~.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package all-the-icons-dired)
|
|
|
|
(use-package dired
|
|
:ensure nil
|
|
:straight nil
|
|
:defer 1
|
|
:commands (dired dired-jump)
|
|
:config
|
|
(setq dired-listing-switches "-agho --group-directories-first")
|
|
(setq dired-omit-files "^\\.[^.].*")
|
|
(setq dired-omit-verbose nil)
|
|
(setq dired-hide-details-hide-symlink-targets nil)
|
|
(put 'dired-find-alternate-file 'disabled nil)
|
|
(setq delete-by-moving-to-trash t)
|
|
(autoload 'dired-omit-mode "dired-x")
|
|
(add-hook 'dired-load-hook
|
|
(lambda ()
|
|
(interactive)
|
|
(dired-collapse)))
|
|
(add-hook 'dired-mode-hook
|
|
(lambda ()
|
|
(interactive)
|
|
(dired-omit-mode 1)
|
|
(dired-hide-details-mode 1)
|
|
(all-the-icons-dired-mode 1))
|
|
(hl-line-mode 1)))
|
|
|
|
(use-package dired-single)
|
|
(use-package dired-ranger)
|
|
(use-package dired-collapse)
|
|
|
|
(evil-collection-define-key 'normal 'dired-mode-map
|
|
"h" 'dired-single-up-directory
|
|
"c" 'find-file
|
|
"H" 'dired-omit-mode
|
|
"l" 'dired-single-buffer
|
|
"y" 'dired-ranger-copy
|
|
"X" 'dired-ranger-move
|
|
"p" 'dired-ranger-paste)
|
|
|
|
;; Darwin needs ls from coreutils for dired to work
|
|
(when (system-is-mac)
|
|
(setq insert-directory-program
|
|
(expand-file-name ".nix-profile/bin/ls" (getenv "HOME"))))
|
|
#+END_SRC
|
|
|
|
**** Quick shortcuts for common file tasks
|
|
#+NAME::buffer-and-file-movement
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun my-org-archive-done-tasks ()
|
|
"Archive all DONE tasks in the current buffer."
|
|
(interactive)
|
|
(org-map-entries
|
|
(lambda ()
|
|
(org-archive-subtree)
|
|
(setq org-map-continue-from (outline-previous-heading)))
|
|
"/DONE" 'tree))
|
|
|
|
(defun er-delete-file-and-buffer ()
|
|
"Kill the current buffer and deletes the file it is visiting."
|
|
(interactive)
|
|
(let ((filename (buffer-file-name)))
|
|
(when filename
|
|
(if (yes-or-no-p (concat "Do you really want to delete file: " filename "? ")) ; Ask for confirmation
|
|
(if (vc-backend filename)
|
|
(vc-delete-file filename)
|
|
(progn
|
|
(delete-file filename)
|
|
(message "Deleted file %s" filename)
|
|
(kill-buffer)))
|
|
(message "Aborted"))))) ; Abort message
|
|
|
|
(define-key org-mode-map (kbd "C-c D") 'my-org-archive-done-tasks)
|
|
(define-key org-mode-map (kbd "C-c d") 'org-archive-subtree)
|
|
(global-set-key (kbd "C-c x") #'er-delete-file-and-buffer)
|
|
#+END_SRC
|
|
|
|
*** Images
|
|
Quickly work with images over drag-and-drop or the clipboard. [[https://github.com/abo-abo/org-download][Link to Project README]].
|
|
#+NAME: org-download-copy
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package org-download)
|
|
;; Drag-and-drop to `dired`
|
|
(add-hook 'dired-mode-hook 'org-download-enable)
|
|
#+END_SRC
|
|
|
|
*** Backups and auto-save
|
|
These settings keep emacs from littering the filesystem with buffer backups. These files look like ~#yourfilename.txt#~ and would otherwise be dropped in your working directory.
|
|
|
|
#+NAME: backup-files
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq backup-directory-alist
|
|
`((".*" . "~/.local/state/emacs/backup"))
|
|
backup-by-copying t ; Don't delink hardlinks
|
|
version-control t ; Use version numbers on backups
|
|
delete-old-versions t) ; Automatically delete excess backups
|
|
#+END_SRC
|
|
|
|
#+NAME: local-file-transforms
|
|
#+BEGIN_SRC emacs-lisp
|
|
(setq auto-save-file-name-transforms
|
|
`((".*" "~/.local/state/emacs/" t)))
|
|
(setq lock-file-name-transforms
|
|
`((".*" "~/.local/state/emacs/lock-files/" t)))
|
|
#+END_SRC
|
|
|
|
** Managing projects
|
|
*** Projectile
|
|
Projectile enables me to organize projects with a killer grep interface.
|
|
|
|
#+NAME: projectile
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package ripgrep)
|
|
(use-package projectile
|
|
:diminish projectile-mode
|
|
:config (projectile-mode)
|
|
:custom
|
|
((projectile-completion-system 'ivy))
|
|
:bind-keymap
|
|
("C-c p" . projectile-command-map)
|
|
:init
|
|
(setq projectile-enable-caching t)
|
|
(setq projectile-sort-order 'recently-active)
|
|
(setq projectile-switch-project-action #'projectile-dired))
|
|
|
|
(setq projectile-project-root-files-bottom-up '("package.json" ".projectile" ".project" ".git"))
|
|
(setq projectile-ignored-projects '("~/.emacs.d/"))
|
|
(setq projectile-globally-ignored-directories '("dist" "node_modules" ".log" ".git"))
|
|
|
|
;; Gives me Ivy options in the Projectile menus
|
|
(use-package counsel-projectile :after projectile)
|
|
#+END_SRC
|
|
|
|
** Writing
|
|
*** Modes
|
|
Experimenting with different distraction free writing modes.
|
|
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun enter-writing-mode ()
|
|
(load-theme 'doom-one-light t)
|
|
(when (bound-and-true-p treemacs-mode) (treemacs))
|
|
(add-hook 'window-buffer-change-functions 'check-leaving-buffer nil t))
|
|
|
|
(defun exit-writing-mode ()
|
|
(load-theme 'doom-one t)
|
|
(when (bound-and-true-p treemacs-mode) (treemacs))
|
|
(remove-hook 'window-buffer-change-functions 'check-leaving-buffer t))
|
|
|
|
(add-hook 'writeroom-mode-hook
|
|
(lambda ()
|
|
(if writeroom-mode
|
|
(enter-writing-mode)
|
|
(exit-writing-mode))))
|
|
|
|
(use-package writeroom-mode
|
|
:ensure t)
|
|
|
|
(global-set-key (kbd "C-c w") 'writeroom-mode)
|
|
#+END_SRC
|
|
|
|
*** Spell Check / Flycheck Mode
|
|
Everything related to spell and grammar checking.
|
|
|
|
#+NAME: spell-check
|
|
#+BEGIN_SRC emacs-lisp
|
|
(when (system-is-mac)
|
|
(with-eval-after-load "ispell"
|
|
(setq ispell-program-name
|
|
(expand-file-name ".nix-profile/bin/hunspell" (getenv "HOME")))
|
|
(setq ispell-dictionary "en_US")))
|
|
|
|
(use-package flyspell-correct
|
|
:after flyspell
|
|
:bind (:map flyspell-mode-map ("C-;" . flyspell-correct-wrapper)))
|
|
|
|
(use-package flyspell-correct-ivy
|
|
:after flyspell-correct)
|
|
|
|
(add-hook 'git-commit-mode-hook 'turn-on-flyspell)
|
|
(add-hook 'text-mode-hook 'flyspell-mode)
|
|
;; Disable this for now, doesn't play well with long literate configuration
|
|
;; (add-hook 'org-mode-hook 'flyspell-mode)
|
|
(add-hook 'prog-mode-hook 'flyspell-prog-mode)
|
|
|
|
(defun spell() (interactive) (flyspell-mode 1))
|
|
#+END_SRC
|
|
|
|
** Coding
|
|
*** Compile buffers
|
|
Everything related to M-x compile.
|
|
|
|
#+NAME: compilation-buffer
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Auto scroll the buffer as we compile
|
|
(setq compilation-scroll-output t)
|
|
|
|
;; By default, eshell doesn't support ANSI colors. Enable them for compilation.
|
|
(require 'ansi-color)
|
|
(defun colorize-compilation-buffer ()
|
|
(let ((inhibit-read-only t))
|
|
(ansi-color-apply-on-region (point-min) (point-max))))
|
|
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)
|
|
#+END_SRC
|
|
*** Tide
|
|
#+NAME: tide-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package tide
|
|
:ensure t
|
|
:after (typescript-mode company flycheck)
|
|
:hook ((typescript-mode . tide-setup)
|
|
(typescript-mode . tide-hl-identifier-mode)
|
|
(before-save . tide-format-before-save)))
|
|
|
|
(setq tide-format-options
|
|
'(:insertSpaceAfterFunctionKeywordForAnonymousFunctions t
|
|
:placeOpenBraceOnNewLineForFunctions nil))
|
|
#+END_SRC
|
|
|
|
*** LSP
|
|
This is my IDE. It includes the same engine that powers VS Code, in addition to Github Copilot.
|
|
|
|
#+NAME: lsp-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package lsp-mode
|
|
:commands lsp lsp-deferred
|
|
:init
|
|
(setq lsp-keymap-prefix "C-c l")
|
|
;;(setq lsp-keep-workspace-alive nil)
|
|
;;(setq lsp-restart 'ignore)
|
|
(setq lsp-headerline-breadcrumb-enable nil)
|
|
(setq lsp-auto-guess-root t)
|
|
(setq lsp-enable-which-key-integration t))
|
|
|
|
(use-package lsp-ui
|
|
:hook (lsp-mode . lsp-ui-mode)
|
|
:custom
|
|
(lsp-ui-doc-position 'bottom))
|
|
|
|
(use-package lsp-treemacs
|
|
:after lsp)
|
|
|
|
(use-package company
|
|
:after lsp-mode
|
|
:hook (lsp-mode . company-mode)
|
|
:bind (:map company-active-map
|
|
("<tab>" . company-complete-selection))
|
|
(:map lsp-mode-map
|
|
("<tab>" . company-indent-or-complete-common))
|
|
:custom
|
|
(company-minimum-prefix-length 1)
|
|
(company-idle-delay 0.0))
|
|
|
|
(use-package company-box
|
|
:hook (company-mode . company-box-mode))
|
|
|
|
(add-hook 'lsp-mode-hook #'lsp-headerline-breadcrumb-mode)
|
|
#+END_SRC
|
|
|
|
**** Shortcuts
|
|
Leader keys for lsp-mode.
|
|
|
|
#+NAME: lsp-leader-keys
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun dl/lsp-find-references-other-window ()
|
|
(interactive)
|
|
(switch-to-buffer-other-window (current-buffer))
|
|
(lsp-find-references))
|
|
|
|
(defun dl/lsp-find-implementation-other-window ()
|
|
(interactive)
|
|
(switch-to-buffer-other-window (current-buffer))
|
|
(lsp-find-implementation))
|
|
|
|
(defun dl/lsp-find-definition-other-window ()
|
|
(interactive)
|
|
(switch-to-buffer-other-window (current-buffer))
|
|
(lsp-find-definition))
|
|
|
|
(dl/leader-keys
|
|
"l" '(:ignore t :which-key "lsp")
|
|
"lf" '(dl/lsp-find-references-other-window :which-key "find references")
|
|
"lc" '(dl/lsp-find-implementation-other-window :which-key "find implementation")
|
|
"ls" '(lsp-treemacs-symbols :which-key "list symbols")
|
|
"lt" '(list-flycheck-errors :which-key "list errors")
|
|
"lh" '(lsp-treemacs-call-hierarchy :which-key "call hierarchy")
|
|
"lF" '(lsp-format-buffer :which-key "format buffer")
|
|
"li" '(lsp-organize-imports :which-key "organize imports")
|
|
"ll" '(lsp :which-key "enable lsp mode")
|
|
"lr" '(lsp-rename :which-key "rename")
|
|
"ld" '(dl/lsp-find-definition-other-window :which-key "goto definition"))
|
|
#+END_SRC
|
|
|
|
*** Languages
|
|
**** Python
|
|
#+NAME: python
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package lsp-pyright
|
|
:ensure t
|
|
:hook (python-mode . (lambda ()
|
|
(require 'lsp-pyright)
|
|
(lsp-deferred)))) ; or lsp-deferred
|
|
#+END_SRC
|
|
|
|
**** Shell scripts
|
|
#+NAME: shell-scripts
|
|
#+BEGIN_SRC emacs-lisp
|
|
(add-to-list 'auto-mode-alist '("\\.env" . shell-script-mode))
|
|
#+END_SRC
|
|
|
|
**** YAML
|
|
#+NAME: yaml-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package yaml-mode
|
|
:commands (markdown-mode gfm-mode)
|
|
:mode (("\\.yml\\'" . yaml-mode)))
|
|
#+END_SRC
|
|
|
|
**** Markdown
|
|
#+NAME: markdown-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; This uses Github Flavored Markdown for README files
|
|
(use-package markdown-mode
|
|
:commands (markdown-mode gfm-mode)
|
|
:mode (("README\\.md\\'" . gfm-mode)
|
|
("\\.md\\'" . markdown-mode)
|
|
("\\.markdown\\'" . markdown-mode))
|
|
:init (setq markdown-command "pandoc"))
|
|
#+END_SRC
|
|
|
|
**** HTML
|
|
***** Web mode
|
|
Emmet mode gives autocompletion for HTML tags using short hand notations, which for I use the TAB key.
|
|
|
|
#+NAME: html-auto-completion
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package emmet-mode)
|
|
(add-hook 'sgml-mode-hook 'emmet-mode)
|
|
(add-hook 'css-mode-hook 'emmet-mode)
|
|
(define-key emmet-mode-keymap [tab] 'emmet-expand-line)
|
|
(add-to-list 'emmet-jsx-major-modes 'jsx-mode)
|
|
#+END_SRC
|
|
|
|
***** Rainbow mode
|
|
Rainbow mode is an Emacs minor mode to highlight the color shown by a RGB hex triplet (example #FFFFFF).
|
|
|
|
#+NAME: rainbow-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package rainbow-mode)
|
|
#+END_SRC
|
|
|
|
**** golang
|
|
#+NAME: golang-config
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package go-mode)
|
|
(use-package company-go)
|
|
|
|
;; Set up before-save hooks to format buffer and add/delete imports.
|
|
;; Make sure you don't have other gofmt/goimports hooks enabled.
|
|
(defun lsp-go-install-save-hooks ()
|
|
(add-hook 'before-save-hook #'lsp-format-buffer t t)
|
|
(add-hook 'before-save-hook #'lsp-organize-imports t t))
|
|
|
|
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)
|
|
(add-hook 'go-mode-hook #'lsp-deferred)
|
|
|
|
(defun dl/go-mode-hook ()
|
|
; Call Gofmt before saving
|
|
(add-hook 'before-save-hook 'gofmt-before-save)
|
|
; Customize compile command to run go build
|
|
(if (not (string-match "go" compile-command))
|
|
(set (make-local-variable 'compile-command)
|
|
"go build -v && go test -v && go vet"))
|
|
; Godef jump key binding
|
|
(local-set-key (kbd "M-.") 'godef-jump)
|
|
;; pop-tag-mark moves back before jump, to undo M-,
|
|
(local-set-key (kbd "M-*") 'pop-tag-mark))
|
|
|
|
(add-hook 'go-mode-hook 'dl/go-mode-hook)
|
|
#+END_SRC
|
|
|
|
**** Javascript / Typescript
|
|
#+NAME: javascript
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package pnpm-mode)
|
|
(use-package prisma-mode
|
|
:straight (:host github :repo "pimeys/emacs-prisma-mode"
|
|
:branch "main"))
|
|
|
|
(use-package web-mode
|
|
:hook (web-mode . lsp-deferred))
|
|
|
|
(add-to-list 'auto-mode-alist '("\\.jsx?$" . web-mode))
|
|
(add-to-list 'auto-mode-alist '("\\.tsx$" . web-mode))
|
|
(add-to-list 'auto-mode-alist '("\\.ts$" . web-mode))
|
|
(add-to-list 'auto-mode-alist '("\\.js$" . web-mode))
|
|
(add-to-list 'auto-mode-alist '("\\.html$" . web-mode))
|
|
(add-to-list 'auto-mode-alist '("\\.vue\\'" . web-mode))
|
|
|
|
(defun web-mode-init-hook ()
|
|
"Hooks for Web mode. Adjust indent."
|
|
(setq web-mode-markup-indent-offset 2))
|
|
(add-hook 'web-mode-hook 'web-mode-init-hook)
|
|
|
|
;; Vue.js / Nuxt.js Language Server
|
|
(straight-use-package
|
|
'(lsp-volar :type git :host github :repo "jadestrong/lsp-volar"))
|
|
|
|
(add-hook 'typescript-mode-hook #'lsp-deferred)
|
|
|
|
;; Keeps indentation organized across these modes
|
|
(use-package prettier-js)
|
|
(add-hook 'js2-mode-hook 'prettier-js-mode)
|
|
(add-hook 'web-mode-hook 'prettier-js-mode)
|
|
(add-hook 'css-mode-hook 'prettier-js-mode)
|
|
#+END_SRC
|
|
|
|
*** Git
|
|
#+NAME: magit-git
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package magit
|
|
:commands (magit-status magit-get-current-branch))
|
|
(define-key magit-hunk-section-map (kbd "RET") 'magit-diff-visit-file-other-window)
|
|
#+END_SRC
|
|
|
|
*** Infrastructure
|
|
**** Nix
|
|
Nix is my package manager and operating system of choice; this mode enables me to have a better time writing Nix expressions.
|
|
|
|
#+NAME: nix-mode
|
|
#+begin_src emacs-lisp
|
|
(use-package nix-mode
|
|
:mode "\\.nix\\'")
|
|
#+end_src
|
|
|
|
**** Docker mode
|
|
#+NAME: dockerfile-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; This uses dockerfile-mode for Docker files
|
|
(use-package dockerfile-mode)
|
|
(put 'dockerfile-image-name 'safe-local-variable #'stringp)
|
|
(add-to-list 'auto-mode-alist '("\\Dockerfile?$" . dockerfile-mode)) ;; auto-enable for Dockerfiles
|
|
#+END_SRC
|
|
|
|
**** Terraform
|
|
#+NAME: terraform-mode
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package terraform-mode
|
|
:hook ((terraform-mode . lsp-deferred)
|
|
(terraform-mode . terraform-format-on-save-mode)))
|
|
|
|
(add-to-list 'auto-mode-alist '("\\.tf\\'" . terraform-mode))
|
|
#+END_SRC
|
|
|
|
** AI
|
|
*** Copilot
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package copilot
|
|
:straight (:host github :repo "zerolfx/copilot.el" :files ("dist" "*.el"))
|
|
:ensure t)
|
|
|
|
(add-hook 'prog-mode-hook 'copilot-mode)
|
|
|
|
(define-key copilot-completion-map (kbd "<tab>") 'copilot-accept-completion)
|
|
(define-key copilot-completion-map (kbd "TAB") 'copilot-accept-completion)
|
|
#+END_SRC
|
|
|
|
** Learning Emacs
|
|
These packages may come and go, but ultimately aid in my understanding of emacs and emacs lisp.
|
|
|
|
*** org-babel
|
|
**** Load languages to run in org mode code blocks
|
|
#+BEGIN_SRC emacs-lisp
|
|
(with-eval-after-load 'org
|
|
(org-babel-do-load-languages
|
|
'org-babel-load-languages
|
|
'(
|
|
(emacs-lisp . t)
|
|
(python . t)
|
|
(sql . t)
|
|
(shell . t)))
|
|
)
|
|
#+END_SRC
|
|
|
|
**** ANSI color codes in org babel shell output
|
|
Found [[https://emacs.stackexchange.com/questions/44664/apply-ansi-color-escape-sequences-for-org-babel-results][here]].
|
|
#+BEGIN_SRC emacs-lisp
|
|
(defun dl/babel-ansi ()
|
|
(when-let ((beg (org-babel-where-is-src-block-result nil nil)))
|
|
(save-excursion
|
|
(goto-char beg)
|
|
(when (looking-at org-babel-result-regexp)
|
|
(let ((end (org-babel-result-end))
|
|
(ansi-color-context-region nil))
|
|
(ansi-color-apply-on-region beg end))))))
|
|
(add-hook 'org-babel-after-execute-hook 'dl/babel-ansi)
|
|
#+END_SRC
|
|
|
|
*** Show real-time key bindings in a separate buffer
|
|
#+NAME: command-log
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Gives me a fancy list of commands I run
|
|
(use-package command-log-mode)
|
|
(setq global-command-log-mode t)
|
|
#+END_SRC
|
|
|
|
*** Panel popup to show key bindings
|
|
#+NAME: which-key
|
|
#+BEGIN_SRC emacs-lisp
|
|
;; Gives me a fancy list of commands I run
|
|
(use-package which-key
|
|
:init (which-key-mode)
|
|
:diminish which-key-mode
|
|
:config
|
|
(setq which-key-idle-delay 0.3))
|
|
#+END_SRC
|
|
|
|
*** Helpful documentation strings for common functions
|
|
#+NAME: helpful
|
|
#+BEGIN_SRC emacs-lisp
|
|
(use-package helpful
|
|
:custom
|
|
;; Remap Counsel help functions
|
|
(counsel-describe-function-function #'helpful-callable)
|
|
(counsel-describe-variable-function #'helpful-variable)
|
|
:bind
|
|
;; Remap default help functions
|
|
([remap describe-function] . helpful-function)
|
|
([remap describe-symbol] . helpful-symbol)
|
|
([remap describe-variable] . helpful-variable)
|
|
([remap describe-command] . helpful-command)
|
|
([remap describe-key] . helpful-key))
|
|
#+END_SRC
|