Table of Contents
- 1. Variables and PATH
- 2. Package management
- 3. Various utility functions
- 4. Various configurations
- 5. term & eshell
- 6. UI
- 7. Theme
- 8. Fonts
- 9. VCS
- 10. keybindings
- 11. evil-mode
- 12. Spell checking
- 13. Buffer & window management
- 14. eww
- 15. dired
- 16. regex replace
- 17. direnv
- 18. lsp
- 19. LANGUAGES
- 20. WRITING
- 21. elfeed
- 22. pdf tools
- 23. searching
- 24. imenu-list
- 25. company
- 26. flycheck
- 27. projectile
- 28. ivy/counsel/swiper
- 29. yasnippet
- 30. org-mode
- 31. modeline
- 32. Setup
1. Variables and PATH
(add-to-list 'load-path (expand-file-name (expand-file-name "lisp" user-emacs-directory))) (add-to-list 'custom-theme-load-path (expand-file-name (expand-file-name "custom-themes/" user-emacs-directory))) (setq user-full-name "Alex Peitsinis" user-mail-address "alexpeitsinis@gmail.com") (dolist (pth '( "/usr/local/bin" "~/bin" "~/.local/bin" "~/.ghcup/bin" )) (add-to-list 'exec-path (expand-file-name pth)) (setenv "PATH" (concat (expand-file-name pth) path-separator (getenv "PATH")))) (defvar is-mac (eq system-type 'darwin) "Whether emacs is running in mac or not") (defvar is-windows (eq window-system 'w32) "Whether emacs is running in windows or not") (defvar is-linux (not (or is-mac is-windows)) "Whether emacs is running in linux or not") (defvar is-gui (display-graphic-p) "Whether emacs is running in gui mode or not") (defvar is-term (not is-gui) "Whether emacs is running in a terminal or not") (defvar my/dropbox-dir (if is-windows "c:/Users/alexp/Dropbox" (expand-file-name "~/Dropbox")) "Private directory synced with dropbox") (defvar my/dropbox-emacs-dir (expand-file-name "emacs/" my/dropbox-dir) "Private directory synced with dropbox") (defvar my/org-directory (expand-file-name "org/" my/dropbox-emacs-dir) "Org directory") (defvar my/xmonad-emacs-sp-name "emacs-sp")
2. Package management
package.el
(require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") ;; '("MELPA Stable" . "https://stable.melpa.org/packages/") ) (unless (bound-and-true-p package--initialized) (package-initialize) (setq package-enable-at-startup nil))
use-package
(unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) ;; Can be used to debug slow packages ;; (setq use-package-minimum-reported-time 0.05 use-package-verbose t) (eval-when-compile (require 'use-package))
load required packages
(use-package diminish :ensure t) ;; commonly used packages (use-package dash :ensure t) (use-package s :ensure t) ;; normally the PATH in nixos contains everything we'd like (e.g. rg, direnv), ;; but on mac that's not true, so load the PATH that the shell would have and ;; rely on bash/zsh/fishrc (use-package exec-path-from-shell :ensure t :if is-mac :init (exec-path-from-shell-initialize))
3. Various utility functions
(defmacro my/add-hooks (hooks &rest body) `(dolist (hook ,hooks) (add-hook hook (lambda () ,@body)))) (defmacro my/execute-f-with-hook (f winf) `(lambda (&rest args) (interactive) (,winf) (apply (quote ,f) args))) (defmacro my/control-function-window-split (f height width) `(lambda (&rest args) (interactive) (let ((split-height-threshold ,height) (split-width-threshold ,width)) (apply (quote ,f) args)))) ;; what it says (defun my/revert-all-buffers (also-git) "Refresh all open file buffers without confirmation. Buffers in modified \(not yet saved) state in EMACS will not be reverted. They will be reverted though if they were modified outside EMACS. Buffers visiting files which do not exist any more or are no longer readable will be killed. With prefix argument ALSO-GIT, refresh the git state as well \(branch status on modeline)." (interactive "P") (dolist (buf (buffer-list)) (let ((filename (buffer-file-name buf))) ;; Revert only buffers containing files, which are not modified; ;; do not try to revert non-file buffers like *Messages*. (when (and filename (not (buffer-modified-p buf))) (if (file-readable-p filename) ;; If the file exists and is readable, revert the buffer. (with-current-buffer buf (revert-buffer :ignore-auto :noconfirm :preserve-modes) (when also-git (vc-refresh-state))) ;; Otherwise, kill the buffer. (let (kill-buffer-query-functions) ; No query done when killing buffer (kill-buffer buf) (message "Killed non-existing/unreadable file buffer: %s" filename)))))) (let ((msg-end (if also-git ", and their git state." "."))) (message (format "Finished reverting buffers containing unmodified files%s" msg-end)))) (defalias 'rb 'revert-buffer) (defalias 'rab 'my/revert-all-buffers) (defun my/indent-region-or-buffer () "Indent a region if selected, otherwise the whole buffer." (interactive) (save-excursion (if (region-active-p) (progn (indent-region (region-beginning) (region-end)) (message "Indented selected region.")) (progn (indent-region (point-min) (point-max)) (message "Indented buffer."))))) (global-set-key (kbd "C-M-\\") #'my/indent-region-or-buffer) (defun my/line-length (&optional line) "Length of the Nth line." (let ((ln (if line line (line-number-at-pos)))) (save-excursion (goto-char (point-min)) (if (zerop (forward-line (1- ln))) (- (line-end-position) (line-beginning-position)) 0)))) (defun my/format-region-or-buffer (cmd &rest args) (interactive) (let ((buf (current-buffer)) (cur-point (point)) (cur-line (line-number-at-pos)) (cur-col (current-column)) (cur-rel-line (- (line-number-at-pos) (line-number-at-pos (window-start))))) (with-current-buffer (get-buffer-create "*codefmt*") (erase-buffer) (insert-buffer-substring buf) (if (zerop (apply 'call-process-region `(,(point-min) ,(point-max) ,cmd t (t nil) nil ,@args))) (progn (if (not (string= (buffer-string) (with-current-buffer buf (buffer-string)))) (copy-to-buffer buf (point-min) (point-max))) (kill-buffer)) (error (format "%s failed, see *codefmt* for details" cmd)))) (goto-line cur-line) (when (< cur-col (my/line-length cur-line)) (forward-char cur-col)) (recenter cur-rel-line) (message (format "Formatted with %s" cmd)))) (defun my/format-and-save (cmd &rest args) (interactive) (apply 'my/format-region-or-buffer `(,cmd ,@args)) (save-buffer)) (defvar my/select-a-major-mode-last-selected nil) (defun my/select-a-major-mode (&optional default) "Interactively select a major mode and return it as a string." (let* ((def (or default my/select-a-major-mode-last-selected (symbol-name initial-major-mode))) (choice (completing-read "major mode: " (apropos-internal "-mode$") nil nil nil nil def))) (setq my/select-a-major-mode-last-selected choice))) (defun my/create-scratch-buffer-with-mode (other-window) "Create a new scratch buffer and select major mode to use. With a prefix argument, open the buffer using `switch-to-buffer-other-window'." (interactive "P") (let* ((mmode (my/select-a-major-mode "markdown-mode")) (buf (generate-new-buffer (concat "*scratch" "-" mmode "*"))) (switch-func (if other-window 'switch-to-buffer-other-window 'switch-to-buffer))) (funcall switch-func buf) (funcall (intern mmode)) (setq buffer-offer-save nil))) ;; https://www.reddit.com/r/emacs/comments/ac9gsf/question_emacs_way_of_using_windows/ (defun my/window-dedicated (&optional window) "Toggle the dedicated flag on a window." (interactive) (let* ((window (or window (selected-window))) (dedicated (not (window-dedicated-p window)))) (when (called-interactively-p) (message (format "%s %sdedicated" (buffer-name (window-buffer window)) (if dedicated "" "un")))) (set-window-dedicated-p window dedicated) dedicated)) (defun my/window-fixed (&optional window) "Make a window non-resizable." (interactive) (let* ((window (or window (selected-window))) (new-status (with-selected-window window (not window-size-fixed)))) (when (called-interactively-p) (message (format "%s %sfixed" (buffer-name (window-buffer window)) (if new-status "" "un")))) (with-selected-window window (setq window-size-fixed new-status)) new-status)) (defun my/copy-file-path (include-line-number) (interactive "P") (let* ((full-fp (buffer-file-name)) (prefix (read-directory-name "prefix to strip: " (projectile-project-root))) (suffix (if include-line-number (format ":%s" (number-to-string (line-number-at-pos))) "")) (fp (concat (string-remove-prefix prefix full-fp) suffix))) (kill-new fp) (message fp) t)) (defvar my/useful-files '( ;; nix "default.nix" "shell.nix" ;; haskell "package.yaml" "stack.yaml" ".hlint.yaml" ;; python "requirements.txt" "pyproject.toml" "setup.cfg" ;; ruby "Gemfile" ;; js "package.json" ;; docker "docker-compose.yml" "Dockerfile" ;; bazel "BUILD.bazel" ;; drone ".drone.star" ".drone.yml" ;; make "Makefile" ;; git repo "README.md" ".pre-commit-config.yaml" ;; writing ".markdownlint.yml" ".vale.ini" ;; emacs ".dir-locals.el" )) (defun my/try-open-dominating-file (other-window) (interactive "P") (let* ((cur-file (or (buffer-file-name) (user-error "Not a file"))) (paths (seq-filter #'(lambda (pair) (not (null (cdr pair)))) (mapcar #'(lambda (fn) (cons fn (locate-dominating-file cur-file fn))) my/useful-files))) (file (completing-read "File name: " paths nil nil nil nil nil)) (dir (cdr (assoc file paths))) (find-file-func (if other-window 'find-file-other-window 'find-file))) (funcall find-file-func (expand-file-name file (file-name-as-directory dir))))) (with-eval-after-load 'ivy (defun my/try-open-dominating-file-display-transformer (fn) (let ((dir (locate-dominating-file (buffer-file-name) fn)) (max-length (apply 'max (mapcar 'length my/useful-files)))) (format (format "%%-%ds (in %%s)" max-length) fn (propertize dir 'face 'font-lock-type-face)))) (ivy-configure 'my/try-open-dominating-file :display-transformer-fn #'my/try-open-dominating-file-display-transformer)) (defun my/line-numbers (relative) (interactive "P") (if display-line-numbers (setq display-line-numbers nil) (if relative (setq display-line-numbers 'relative) (setq display-line-numbers t)))) (defun my/shell-command-on-buffer-or-region (cmd) (save-excursion (unless (region-active-p) (mark-whole-buffer)) (shell-command-on-region (region-beginning) (region-end) cmd nil t)))
4. Various configurations
disable custom file
(use-package cus-edit :defer t :init (setq custom-file (expand-file-name "custom.el" user-emacs-directory)))
basic editing
;; remember last position (use-package saveplace :hook (after-init . save-place-mode)) ;; undo tree (use-package undo-tree :ensure t :bind ("C-x u" . undo-tree-visualize) :diminish undo-tree-mode :hook (after-init . global-undo-tree-mode) :init (setq undo-tree-visualizer-relative-timestamps t undo-tree-visualizer-diff t undo-tree-history-directory-alist `(("." . ,(expand-file-name "undo" user-emacs-directory))))) ;; use column width 80 to fill (e.g. with `M-q'/`gq') (setq-default fill-column 80) (setq fill-indent-according-to-mode t) (use-package autorevert :hook (after-init . global-auto-revert-mode) :diminish auto-revert-mode :init (setq auto-revert-verbose nil)) (use-package eldoc :diminish eldoc-mode) (use-package files :init ;; add trailing newline if missing (setq require-final-newline t) ;; store all backup and autosave files in ;; one dir (setq backup-directory-alist `((".*" . ,temporary-file-directory))) (setq auto-save-file-name-transforms `((".*" ,temporary-file-directory t)))) (use-package simple :diminish visual-line-mode :init (defalias 'dw #'delete-trailing-whitespace)) ;; only with this set to nil can org-mode export & open too ;; ... but it also breaks some stuff so it's disabled ;; (setq process-connection-type nil) ;; yesss (defalias 'yes-or-no-p #'y-or-n-p) ;; Always confirm before closing because I'm stupid (add-hook 'kill-emacs-query-functions (lambda () (y-or-n-p "Do you really want to exit Emacs? ")) 'append) ;; use spaces (setq-default indent-tabs-mode nil) ;; always scroll to the end of compilation buffers ;; (setq compilation-scroll-output t) ;; vim-like scrolling (emacs=0) (setq scroll-conservatively 101) ;; Supress "ad-handle-definition: x got redefined" warnings (setq ad-redefinition-action 'accept) ;; smooth mouse scrolling (setq mouse-wheel-scroll-amount '(1 ((shift) . 1)) ;; one line at a time mouse-wheel-progressive-speed t ;; don't accelerate scrolling mouse-wheel-follow-mouse 't) ;; scroll window under mouse ;; turn off because it causes delays in some modes (e.g. coq-mode) ;; TODO: not sure if this makes a difference (setq smie-blink-matching-inners nil) ;; (setq blink-matching-paren nil) ;; who in their right mind ends sentences with 2 spaces? (setq sentence-end-double-space nil) ;; Don't autofill when pressing RET (aset auto-fill-chars ?\n nil) ;; always trim whitespace before saving ;; (add-hook 'before-save-hook 'delete-trailing-whitespace) ;; some keymaps (global-set-key (kbd "M-o") 'other-window) (global-set-key (kbd "C-c j") 'previous-buffer) (global-set-key (kbd "C-c k") 'next-buffer) ;; I use that to switch to Greek layout (global-set-key (kbd "M-SPC") nil) ;; Bind M-\ to just-one-space instead of delete-horizontal-space (global-set-key (kbd "M-\\") 'just-one-space) ;; proper count-words keybinding (global-set-key (kbd "M-=") 'count-words) (use-package newcomment :commands (comment-indent comment-kill) :bind (("C-;" . my/comment-end-of-line) ("C-:" . comment-kill)) :init (setq-default comment-indent-function nil) (defvar-local my/comment-offset 2) (defun my/comment-end-of-line () "Add an inline comment, 2 spaces after EOL." (interactive) (let* ((len (- (line-end-position) (line-beginning-position))) (comment-column (+ my/comment-offset len))) (funcall-interactively 'comment-indent)))) ;; DocView (setq doc-view-continuous t) ;; shr (html rendering) (make-variable-buffer-local 'shr-width) (use-package expand-region :ensure t :bind (("C-=" . er/expand-region) ("C-M-=" . er/contract-region))) ;; M-x is zap-to-char (use-package misc :bind ("M-Z" . zap-up-to-char)) (use-package subword :diminish subword-mode :commands (subword-mode) :init (advice-add 'subword-mode :after #'(lambda (&optional arg) (setq evil-symbol-word-search subword-mode)))) (use-package outline :defer t :bind (:map outline-minor-mode-map ("<tab>" . my/outline-toggle-heading)) :diminish outline-minor-mode :init (defun my/outline-toggle-heading () (interactive) (when (outline-on-heading-p) (funcall-interactively 'outline-toggle-children)))) ;; elisp: ;; -*- eval: (outshine-mode) -*- (use-package outshine :ensure t :after outline :bind (:map outline-minor-mode-map ("<S-iso-lefttab>" . outshine-cycle-buffer)) :commands (outshine-mode)) (use-package rainbow-mode :ensure t :commands (rainbow-mode) :init (setq rainbow-ansi-colors nil rainbow-html-colors nil rainbow-latex-colors nil rainbow-r-colors nil rainbow-x-colors nil)) (use-package rainbow-delimiters :ensure t :hook ((lisp-mode emacs-lisp-mode clojure-mode) . rainbow-delimiters-mode) :commands (rainbow-delimiters-mode) :diminish)
advise raise-frame with wmctrl (linux only)
(defun my/wmctrl-raise-frame (&optional frame) (when (executable-find "wmctrl") (let* ((fr (or frame (selected-frame))) (name (frame-parameter fr 'name)) (flag (if (string-equal name my/xmonad-emacs-sp-name) "-R" "-a"))) ;; catch any exception, otherwise might interfere with terminal emacsclients (condition-case ex (call-process "wmctrl" nil nil nil "-i" flag (frame-parameter fr 'outer-window-id)) ('error nil))))) (when is-linux (advice-add 'raise-frame :after 'my/wmctrl-raise-frame))
compilation
(defvar my/fast-recompile-mode-map (make-sparse-keymap)) (define-minor-mode my/fast-recompile-mode "Minor mode for fast recompilation using C-c C-c" :lighter " rc" :global t :keymap my/fast-recompile-mode-map (if my/fast-recompile-mode (progn (put 'my/-old-compilation-ask-about-save 'state compilation-ask-about-save) (setq compilation-ask-about-save nil)) (setq compilation-ask-about-save (get 'my/-old-compilation-ask-about-save 'state)))) (define-key my/fast-recompile-mode-map (kbd "C-c C-c") #'recompile) (use-package ansi-color :commands (ansi-color-apply-on-region) :init ;; http://endlessparentheses.com/ansi-colors-in-the-compilation-buffer-output.html (defun my/compilation-mode-colorize () "Colorize from `compilation-filter-start' to `point'." (let ((inhibit-read-only t)) (ansi-color-apply-on-region compilation-filter-start (point))))) (use-package compile :commands (compile recompile) :init (defun my/compile-in-dir () (interactive) (let ((default-directory (read-directory-name "Run command in: "))) (call-interactively 'compile))) (setq compilation-scroll-output 'first-error) (add-hook 'compilation-filter-hook #'my/compilation-mode-colorize))
Smartparens
Paredit keys:
key | opposite | description | example |
---|---|---|---|
C-M-f |
C-M-b |
forward/backward sexp | _(...)(...) <-> (...)_(...) |
C-M-d |
C-M-u |
down-up sexp | _(...) <-> (_...) |
C-M-n |
C-M-p |
up-down sexp (end) | (..._) <-> (...)_ |
(use-package smartparens-config :after smartparens :config ;; don't create a pair with single quote in minibuffer (sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil) ;; because DataKinds ;;(with-eval-after-load 'haskell-mode ;; (sp-local-pair 'haskell-mode "'" nil :actions nil)) ;; indent after inserting any kinds of parens (defun my/smartparens-pair-newline-and-indent (id action context) (save-excursion (newline) (indent-according-to-mode)) (indent-according-to-mode)) (sp-pair "(" nil :post-handlers '(:add (my/smartparens-pair-newline-and-indent "RET"))) (sp-pair "{" nil :post-handlers '(:add (my/smartparens-pair-newline-and-indent "RET"))) (sp-pair "[" nil :post-handlers '(:add (my/smartparens-pair-newline-and-indent "RET"))) ) (use-package smartparens :ensure t :hook (after-init . show-smartparens-global-mode) :bind (:map smartparens-mode-map ;; paredit bindings ("C-M-f" . sp-forward-sexp) ("C-M-b" . sp-backward-sexp) ("C-M-d" . sp-down-sexp) ("C-M-u" . sp-backward-up-sexp) ("C-M-n" . sp-up-sexp) ("C-M-p" . sp-backward-down-sexp) ("M-s" . sp-splice-sexp) ("M-<up>" . sp-splice-sexp-killing-backward) ("M-<down>" . sp-splice-sexp-killing-forward) ("M-r" . sp-splice-sexp-killing-around) ("M-(" . sp-wrap-round) ("M-{" . sp-wrap-curly) ("C-)" . sp-forward-slurp-sexp) ("C-<right>" . sp-forward-slurp-sexp) ("C-}" . sp-forward-barf-sexp) ("C-<left>" . sp-forward-barf-sexp) ("C-(" . sp-backward-slurp-sexp) ("C-M-<left>" . sp-backward-slurp-sexp) ("C-{" . sp-backward-barf-sexp) ("C-M-<right>" . sp-backward-barf-sexp) ("M-S" . sp-split-sexp) ;; mine ("C-M-k" . sp-kill-sexp) ("C-M-w" . sp-copy-sexp) ("M-@" . sp-mark-sexp) ) :diminish smartparens-mode :init (setq sp-show-pair-delay 0.2 ;; avoid slowness when editing inside a comment for modes with ;; parenthesized comments (e.g. coq) sp-show-pair-from-inside nil sp-cancel-autoskip-on-backward-movement nil sp-highlight-pair-overlay nil sp-highlight-wrap-overlay nil sp-highlight-wrap-tag-overlay nil sp-python-insert-colon-in-function-definitions nil) (my/add-hooks '(emacs-lisp-mode-hook clojure-mode-hook) (smartparens-strict-mode) (evil-smartparens-mode)) (my/add-hooks '(prog-mode-hook coq-mode-hook comint-mode-hook css-mode-hook) (smartparens-mode)) :config (when is-gui ;; interferes in terminal (define-key smartparens-mode-map (kbd "M-[") 'sp-wrap-square))) (use-package evil-smartparens :ensure t :after smartparens :diminish evil-smartparens-mode)
Documentation & help
(use-package which-key :ensure t :hook (after-init . which-key-mode) :diminish which-key-mode)
mark
(defun my/goto-line-show () "Show line numbers temporarily, while prompting for the line number input." (interactive) (let ((cur display-line-numbers)) (unwind-protect (progn (setq display-line-numbers t) (call-interactively #'goto-line)) (setq display-line-numbers cur)))) (global-set-key (kbd "M-g M-g") 'my/goto-line-show) (define-key prog-mode-map (kbd "M-a") 'beginning-of-defun) (define-key prog-mode-map (kbd "M-e") 'end-of-defun) (defun my/push-mark-no-activate () "Pushes `point' to `mark-ring' and does not activate the region Equivalent to \\[set-mark-command] when \\[transient-mark-mode] is disabled" (interactive) (push-mark (point) t nil) (message "Pushed mark to ring")) (global-set-key (kbd "C-`") 'my/push-mark-no-activate) (defun my/jump-to-mark () "Jumps to the local mark, respecting the `mark-ring' order. This is the same as using \\[set-mark-command] with the prefix argument." (interactive) (set-mark-command 1)) (global-set-key (kbd "M-`") 'my/jump-to-mark)
abbrev etc
(use-package dabbrev :commands (dabbrev-expand) :init ;; Don't consider punctuation part of word for completion, helps complete ;; qualified symbols (my/add-hooks '(prog-mode-hook) (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_\\|\\sw\\s."))) (use-package abbrev :commands (abbrev-mode abbrev-prefix-mark) :diminish) ;; Testing it out (use-package hippie-exp :bind (("M-/" . hippie-expand)) :init (setq hippie-expand-verbose nil) (setq hippie-expand-try-functions-list '(try-expand-dabbrev try-expand-dabbrev-all-buffers try-expand-dabbrev-from-kill try-complete-file-name-partially try-complete-file-name try-expand-all-abbrevs try-expand-list try-expand-line try-complete-lisp-symbol-partially try-complete-lisp-symbol)))
engine-mode
(use-package engine-mode :ensure t :hook (after-init . engine-mode) :bind-keymap ("C-x /" . engine-mode-map) :config (defengine google "http://www.google.com/search?ie=utf-8&oe=utf-8&q=%s" :keybinding "g") (defengine google-images "http://www.google.com/images?hl=en&source=hp&biw=1440&bih=795&gbv=2&aq=f&aqi=&aql=&oq=&q=%s" :keybinding "i") (defengine google-maps "http://maps.google.com/maps?q=%s") (defengine wikipedia "http://www.wikipedia.org/search-redirect.php?language=en&go=Go&search=%s" :keybinding "w") (defengine wiktionary "https://www.wikipedia.org/search-redirect.php?family=wiktionary&language=en&go=Go&search=%s" :keybinding "d") (defengine wolfram-alpha "http://www.wolframalpha.com/input/?i=%s" :keybinding "m") (defengine youtube "http://www.youtube.com/results?aq=f&oq=&search_query=%s" :keybinding "v") (defengine hoogle "https://hoogle.haskell.org/?hoogle=%s" :keybinding "h") (defengine stackage "https://www.stackage.org/lts/hoogle?q=%s" :keybinding "s") (defengine haskell-language-extensions "https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extension-%s" :keybinding "#") )
browser
(use-package browse-url :init (setq browse-url-browser-function (cond ((or (executable-find "google-chrome-stable") (executable-find "google-chrome")) 'browse-url-chrome) ((executable-find "firefox") 'browse-url-firefox) (t 'browse-url-default-browser))))
prettify symbols
;; show original symbol when cursor is on it, or right next to it (setq prettify-symbols-unprettify-at-point 'right-edge)
recentf
(use-package recentf :hook (after-init . recentf-mode) :init (setq recentf-max-saved-items 100))
hi-lock & symbol overlay
(use-package hi-lock :hook (after-init . global-hi-lock-mode) :init (setq hi-lock-face-defaults '( "hi-black-b" "hi-red-b" "hi-green-b" "hi-blue-b" "hi-green" "hi-blue" "hi-pink" "hi-yellow" )) (setq hi-lock-auto-select-face t) :config (define-key hi-lock-map (kbd "M-H") (lookup-key hi-lock-map (kbd "C-x w"))) ;; TODO: find out why I can't just `define-key' (substitute-key-definition 'highlight-regexp 'my/highlight-regexp hi-lock-map) (defun my/highlight-regexp (regexp &optional face) (interactive (list (hi-lock-regexp-okay (read-regexp "Regexp to highlight" 'regexp-history-last)) (hi-lock-read-face-name))) (or (facep face) (setq face 'hi-yellow)) (unless hi-lock-mode (hi-lock-mode 1)) (hi-lock-set-pattern regexp face nil))) (use-package symbol-overlay :ensure t :commands (symbol-overlay-mode) :diminish)
highlight keywords in some modes
(defface my/special-keyword-face '((t (:inherit font-lock-keyword-face))) "Face for highlighting special keywords" :group 'my/faces) (defface my/special-comment-keyword-face '((t (:inherit font-lock-preprocessor-face))) "Face for highlighting special keywords in comments" :group 'my/faces) (defun my/highlight-keyword-in-mode (mode kw &optional in-comment face) (let ((fc (or face (if in-comment 'my/special-comment-keyword-face 'my/special-keyword-face))) (str (format "\\<\\(%s\\)\\>" kw))) (font-lock-add-keywords mode (if in-comment `((,str 1 ,`(quote ,fc) prepend)) `((,str . ,`(quote ,fc))))))) (defvar my/comment-keywords '("TODO" "NOTE" "FIXME" "WARNING" "HACK" "XXX" "DONE")) (defun my/highlight-comment-keywords (mode &optional face) (dolist (kw my/comment-keywords) (my/highlight-keyword-in-mode mode kw t face))) (dolist (mode '(haskell-mode literate-haskell-mode purescript-mode js2-mode html-mode python-mode idris-mode agda-mode rust-mode c-mode emacs-lisp-mode coq-mode enh-ruby-mode )) (my/highlight-comment-keywords mode))
alignment
(use-package align :bind ("C-c \\" . align-regexp))
temp project roots
(defvar my/temp-project-root nil) (defun my/get-or-set-temp-root (reset) (let* ((reset-root (if reset my/temp-project-root nil)) (root (if (or reset (null my/temp-project-root) (not (file-directory-p my/temp-project-root))) (read-directory-name "Temp root dir: " reset-root) my/temp-project-root))) (setq my/temp-project-root root)))
edit-indirect
(use-package edit-indirect :ensure t :commands (edit-indirect-region) :bind ("C-c C-'" . my/edit-indirect-region) :config (add-hook 'edit-indirect-after-creation-hook 'my/edit-indirect-dedent) (add-hook 'edit-indirect-before-commit-hook 'my/edit-indirect-indent)) (defun my/edit-indirect-region () (interactive) (unless (region-active-p) (user-error "No region selected")) (save-excursion (let* ((begin (region-beginning)) (end (region-end)) (mode (my/select-a-major-mode)) (edit-indirect-guess-mode-function (lambda (_parent _beg _end) (funcall (intern mode))))) (edit-indirect-region begin end 'display-buffer)))) (defun my/get-buffer-min-leading-spaces (&optional buffer) (let* ((buf (or buffer (current-buffer))) (ind nil)) (save-excursion (goto-char (point-min)) (setq ind (org-get-indentation)) (while (not (or (evil-eobp) (eobp))) (unless (string-match-p "\\`\\s-*$" (thing-at-point 'line)) (setq ind (min ind (org-get-indentation)))) (ignore-errors (next-line)) )) ind)) (defun my/edit-indirect-dedent () (let ((amount (my/get-buffer-min-leading-spaces))) (setq-local my/edit-indirect-dedented-amount amount) (save-excursion (indent-rigidly (point-min) (point-max) (- amount))))) (defun my/edit-indirect-indent () (when (boundp 'my/edit-indirect-dedented-amount) (save-excursion (indent-rigidly (point-min) (point-max) my/edit-indirect-dedented-amount))))
5. term & eshell
terms
(use-package term :defer t :config (my/add-hooks '(term-mode-hook) (define-key term-raw-map (kbd "M-o") nil) (define-key term-raw-map (kbd "M-+") nil)) ;; automatically close term buffers on EOF (defun my/term-exec-hook () (let* ((buff (current-buffer)) (proc (get-buffer-process buff))) (set-process-sentinel proc `(lambda (process event) (if (string= event "finished\n") (kill-buffer ,buff)))))) (add-hook 'term-exec-hook 'my/term-exec-hook)) (use-package comint :defer t :init (setq comint-prompt-read-only t) :config (defun my/comint-clear-buffer () (interactive) (let ((comint-buffer-maximum-size 0)) (comint-truncate-buffer))) (add-hook 'comint-mode-hook (lambda () (define-key comint-mode-map (kbd "C-l") 'my/comint-clear-buffer))))
eshell
(use-package em-hist :after eshell) (use-package eshell :commands (eshell) :bind (("C-!" . my/eshell) ("<f2>" . my/eshell)) :init ;; eshell/clear doesn't work anymore because eshell has its own clear function (defun my/eshell-clear () (interactive) "Clear the eshell buffer." (let ((eshell-buffer-maximum-lines 0)) (eshell-truncate-buffer))) ;; eshell bug prevents using eshell-mode-map so this is run in the mode hook (defun my/eshell-define-keys () (let ((map eshell-mode-map)) (define-key map (kbd "C-l") #'my/eshell-clear))) (defalias 'eshell/x 'eshell/exit) (defalias 'eshell/e 'find-file) (defalias 'eshell/ff 'find-file) (defalias 'eshell/gc 'magit-commit-create) (setq eshell-destroy-buffer-when-process-dies t eshell-history-size 1024 eshell-prompt-regexp "^[^#$]* [#$] ") (setq eshell-prompt-function (lambda () (concat (propertize ((lambda (p-lst) (if (> (length p-lst) 3) (concat (mapconcat (lambda (elm) (if (zerop (length elm)) "" (substring elm 0 1))) (butlast p-lst 3) "/") "/" (mapconcat (lambda (elm) elm) (last p-lst 3) "/")) (mapconcat (lambda (elm) elm) p-lst "/"))) (split-string (my/eshell-prompt-dir (eshell/pwd)) "/")) 'face 'font-lock-type-face) (or (my/eshell-prompt-git (eshell/pwd))) " " (propertize "$" 'face 'font-lock-function-name-face) (propertize " " 'face 'default)))) :config (add-hook 'eshell-mode-hook #'my/eshell-define-keys) (add-hook 'eshell-exit-hook 'delete-window) ;; Don't ask, just save (if (boundp 'eshell-save-history-on-exit) (setq eshell-save-history-on-exit t)) ;; For older(?) version (if (boundp 'eshell-ask-to-save-history) (setq eshell-ask-to-save-history 'always))) (use-package em-smart :after eshell :init (setq eshell-where-to-jump 'begin eshell-review-quick-commands nil eshell-smart-space-goes-to-end t)) (defun my/eshell (&optional dir prompt) "Open up a new shell in the directory associated with the current buffer. The shell is renamed to match that directory to make multiple eshell windows easier. If DIR is provided, open the shell there. If PROMPT is non-nil, prompt for the directory instead. With a prefix argument, prompt for directory." (interactive (list nil current-prefix-arg)) (let* ((parent (if prompt (read-directory-name "Open eshell in: ") (if dir dir (if (buffer-file-name) (file-name-directory (buffer-file-name)) default-directory)))) (height (/ (window-total-height) 3)) (name (car (last (split-string parent "/" t)))) (bufname (format "*eshell:%s*" name)) (default-directory parent)) (split-window-vertically (- height)) (other-window 1) (let ((eshell-banner-message (format "eshell in %s\n\n" (propertize (abbreviate-file-name parent) 'face 'font-lock-keyword-face)))) (eshell :new)) (rename-buffer (generate-new-buffer-name bufname)))) (defun my/eshell-prompt-dir (pwd) (interactive) (let* ((home (expand-file-name (getenv "HOME"))) (home-len (length home))) (if (and (>= (length pwd) home-len) (equal home (substring pwd 0 home-len))) (concat "~" (substring pwd home-len)) pwd))) (defun my/eshell-prompt-git (cwd) "Returns current git branch as a string, or the empty string if CWD is not in a git repo (or the git command is not found)." (interactive) (when (and (eshell-search-path "git") (locate-dominating-file cwd ".git")) (let ((git-output (shell-command-to-string (format "git -C %s branch | grep '\\*' | sed -e 's/^\\* //'" cwd)))) (concat (propertize (concat "[" (if (> (length git-output) 0) (substring git-output 0 -1) "(no branch)") ) 'face 'font-lock-string-face) (my/git-collect-status cwd) (propertize "]" 'face 'font-lock-string-face) ) ))) ;; TODO ;; https://github.com/xuchunyang/eshell-git-prompt/blob/master/eshell-git-prompt.el (defun my/git-collect-status (cwd) (when (and (eshell-search-path "git") (locate-dominating-file cwd ".git")) (let ((git-output (split-string (shell-command-to-string (format "git -C %S status --porcelain" cwd)) "\n" t)) (untracked 0) (modified 0) (modified-updated 0) (new-added 0) (deleted 0) (deleted-updated 0) (renamed-updated 0) (commits-ahead 0) ;; TODO (commits-behind 0) ;; TODO ) (dolist (x git-output) (pcase (substring x 0 2) ("??" (cl-incf untracked)) ("MM" (progn (cl-incf modified) (cl-incf modified-updated))) (" M" (cl-incf modified)) ("M " (cl-incf modified-updated)) ("A " (cl-incf new-added)) (" D" (cl-incf deleted)) ("D " (cl-incf deleted-updated)) ("R " (cl-incf renamed-updated)) )) (concat (propertize (if (> (+ untracked deleted) 0) "•" "") 'face '(:foreground "salmon3")) (propertize (if (> modified 0) "•" "") 'face '(:foreground "goldenrod3")) (propertize (if (> modified-updated 0) "•" "") 'face '(:foreground "SeaGreen4"))))))
vterm
;; NOTE: on NixOS this is managed by the OS, not melpa (use-package vterm :ensure t :commands (vterm) :bind (("C-@" . my/vterm) ("<S-f2>" . my/vterm)) :init (defun my/vterm () (interactive) (let* ((height (/ (window-total-height) 3)) (parent (if (buffer-file-name) (file-name-directory (buffer-file-name)) default-directory)) (name (car (last (split-string parent "/" t)))) (bufname (format "*vterm:%s*" name))) (split-window-vertically (- height)) (other-window 1) (vterm (generate-new-buffer-name bufname)))) ;; kill vterm buffers when exiting with C-d (defun my/vterm-exit-kill-buffer (buffer event) (kill-buffer buffer)) (setq vterm-exit-functions '(my/vterm-exit-kill-buffer)) :config (add-to-list 'vterm-eval-cmds '("magit-commit-create" magit-commit-create)))
6. UI
various
;; highlight numbers (use-package highlight-numbers :ensure t :hook ((prog-mode haskell-cabal-mode css-mode) . highlight-numbers-mode)) ;; show column in modeline (setq column-number-mode t) ;; disable annoying stuff (setq ring-bell-function 'ignore inhibit-startup-message t inhibit-splash-screen t initial-scratch-message nil) (menu-bar-mode -1) (scroll-bar-mode -1) (set-window-scroll-bars (minibuffer-window) nil nil) (tool-bar-mode -1) (use-package hl-line :hook (prog-mode . hl-line-mode) :commands (hl-line-mode global-hl-line-mode) :init (setq hl-line-sticky-flag nil)) (use-package display-fill-column-indicator :commands (display-fill-column-indicator-mode) :hook ((python-mode markdown-mode) . display-fill-column-indicator-mode)) (use-package visual-fill-column :ensure t :commands (visual-fill-column-mode) :init (defun my/visual-fill-column-mode-hook () (if visual-fill-column-mode (visual-line-mode) (visual-line-mode -1))) (add-hook 'visual-fill-column-mode-hook #'my/visual-fill-column-mode-hook))
highlight trailing whitespace
(use-package whitespace :diminish whitespace-mode :diminish global-whitespace-mode :hook ((prog-mode . whitespace-mode)) :init (setq whitespace-line-column 80 whitespace-style '(face trailing)))
7. Theme
theme loading
(setq custom--inhibit-theme-enable nil) (defvar my/avail-themes '(deeper-blue adwaita)) (defvar my/current-theme 0) (defvar my/after-set-theme-hook nil "Hook called after setting a theme") (defun my/set-theme (&optional theme) (let ((theme (or theme (elt my/avail-themes my/current-theme)))) (mapc 'disable-theme custom-enabled-themes) (if (functionp theme) (funcall theme) (load-theme theme t)) (run-hooks 'my/after-set-theme-hook))) (defun my/toggle-theme () (interactive) (let* ((next-theme (mod (1+ my/current-theme) (length my/avail-themes))) (theme (elt my/avail-themes next-theme))) (setq my/current-theme next-theme) (my/set-theme))) (defun my/refresh-theme () (interactive) (my/set-theme)) (use-package color :commands (color-darken-name color-lighten-name))
modus themes
(use-package modus-themes :ensure t :defer t :init (setq modus-themes-org-blocks 'greyscale modus-themes-headings '((t . (background overline))) modus-themes-scale-headings t modus-themes-intense-hl-line t modus-themes-scale-5 1.3 modus-themes-scale-4 1.2 modus-themes-scale-3 1.0 modus-themes-scale-2 1.0 modus-themes-scale-1 1.0 modus-themes-subtle-line-numbers nil modus-themes-mode-line '(accented)) (setq modus-themes-common-palette-overrides '((bg-mode-line-active bg-cyan-subtle) (fg-mode-line-active fg-main) (border-mode-line-active slate) (fg-region nil) (bg-region bg-cyan-subtle) )) (setq modus-vivendi-palette-overrides '((bg-main "#0d0d0d") (fg-main "#e7e7e7") (bg-hl-line "#272727") (type "#8ae4f2"))) (setq modus-operandi-palette-overrides nil) )
zenburn theme (low contrast)
(use-package zenburn-theme :ensure t :defer t :init (setq zenburn-use-variable-pitch nil zenburn-scale-org-headlines t zenburn-height-minus-1 1.0 zenburn-height-plus-4 1.2 zenburn-height-plus-3 1.0 zenburn-height-plus-2 1.0 zenburn-height-plus-1 1.0) (defun my/zenburn-theme () (load-theme 'zenburn t) (zenburn-with-color-variables (custom-theme-set-faces 'zenburn `(region ((t (:background ,zenburn-bg+2)))) `(vertical-border ((t (:foreground "#a5a5a5")))) `(fringe ((t (:background "#484848")))) `(link ((t (:foreground ,zenburn-yellow :underline t)))) `(hl-line ((t (:background ,zenburn-bg+05)))) `(fill-column-indicator ((t (:foreground ,zenburn-bg+2)))) `(compilation-info ((t (:foreground ,zenburn-green+3 :weight bold)))) `(isearch ((t (:foreground ,zenburn-blue+2 :background ,zenburn-blue-5 :weight bold)))) `(lazy-highlight ((t (:foreground ,zenburn-green+2 :background ,zenburn-bg+2 :weight bold)))) `(mode-line ((t (:box (:line-width -1 :color nil :style released-button) :foreground ,zenburn-green+3 :background ,zenburn-bg+05)))) `(mode-line-inactive ((t (:box (:line-width -1 :color nil :style released-button) :foreground ,zenburn-green-2 :background ,zenburn-bg-05)))) `(mode-line-buffer-id ((t (:weight bold)))) `(persp-selected-face ((t (:foreground ,zenburn-yellow-2 :weight bold)))) `(projectile-tab-bar-modeline-active-face ((t (:foreground ,zenburn-yellow-2 :weight bold)))) `(font-lock-comment-delimiter-face ((t (:inherit font-lock-comment-face)))) `(font-lock-keyword-face ((t (:foreground ,zenburn-yellow-1 :weight bold)))) `(diff-hl-insert ((t (:foreground "#789c78" :background "#3c543c")))) `(diff-hl-change ((t (:foreground "#79b3b5" :background "#425f61")))) `(diff-hl-delete ((t (:foreground "#ab8080" :background "#694848")))) `(diredfl-dir-name ((t (:foreground ,zenburn-blue+1 :weight bold)))) `(diredfl-dir-heading ((t (:foreground ,zenburn-blue-1)))) `(org-block ((t (:background "#444444" :extend t)))) `(org-block-begin-line ((t (:background "#4b4b4b" :foreground ,zenburn-fg-05 :slant italic :extend t)))) `(org-block-end-line ((t (:inherit org-block-begin-line)))) `(org-roam-link ((t (:foreground ,zenburn-green+3 :background ,zenburn-bg+1 :underline t)))) `(coq-cheat-face ((t (:background ,zenburn-red-6 :foreground ,zenburn-red+2 :weight bold)))) `(coq-button-face ((t (:foreground ,zenburn-green+2 :background ,zenburn-bg+05)))) `(coq-button-face-pressed ((t (:foreground ,zenburn-green+4 :background ,zenburn-bg+2)))) `(enh-ruby-op-face ((t nil))) `(enh-ruby-string-delimiter-face ((t (:inherit font-lock-string-face)))) `(proof-locked-face ((t (:background ,(color-darken-name zenburn-blue-5 4))))) `(proof-warning-face ((t (:background ,(color-darken-name zenburn-yellow-2 35))))) `(proof-error-face ((t (:background ,zenburn-red-6 :foreground ,zenburn-red+2)))) `(proof-tactics-name-face ((t (:inherit font-lock-constant-face)))) `(rst-level-1 ((t (:inherit rst-adornment)))) `(rst-level-2 ((t (:inherit rst-level-1)))) `(rst-level-3 ((t (:inherit rst-level-1)))) `(rst-level-4 ((t (:inherit rst-level-1)))) `(rst-level-5 ((t (:inherit rst-level-1)))) `(rst-level-6 ((t (:inherit rst-level-1)))) `(my/elfeed-blue ((t (:foreground ,zenburn-blue+1)))) `(my/elfeed-cyan ((t (:foreground ,zenburn-blue-1)))) `(my/elfeed-green ((t (:foreground ,zenburn-green)))) `(my/elfeed-yellow ((t (:foreground ,zenburn-yellow)))) `(my/elfeed-magenta ((t (:foreground ,zenburn-magenta)))) `(my/elfeed-red ((t (:foreground ,zenburn-red)))) `(elfeed-search-date-face ((t (:foreground ,zenburn-orange)))) ) (custom-theme-set-variables 'zenburn `(coq-highlighted-hyps-bg ,zenburn-bg+2)))))
solarized-theme
(setq solarized-use-variable-pitch nil solarized-height-minus-1 1.0 solarized-height-plus-4 1.2 solarized-height-plus-3 1.0 solarized-height-plus-2 1.0 solarized-height-plus-1 1.0)
8. Fonts
(defvar my/font-variant "default") (defvar my/fonts '(("default" . (:fixed ("Monospace" . 12) :variable ("sans-serif" . 12))))) (defvar my/after-set-font-hook nil "Hook called after updating fonts") (defun my/all-font-variants () (mapcar 'car my/fonts)) (defun my/set-font (&optional variant) (let* ((variant (or variant my/font-variant)) (spec (cdr (assoc variant my/fonts))) (fixed (plist-get spec :fixed)) (variable (plist-get spec :variable)) (spacing (or (plist-get spec :spacing) 0))) (dolist (face '(default fixed-pitch)) (set-face-attribute face nil :font (format "%s-%s" (car fixed) (cdr fixed)))) (set-face-attribute 'variable-pitch nil :font (format "%s-%s" (car variable) (cdr variable))) (setq line-spacing spacing) (setq-default line-spacing spacing) (run-hooks 'my/after-set-font-hook))) (defun my/select-font-variant (&optional new-variant) (interactive) (let* ((variants (my/all-font-variants)) (new-variant (or new-variant (completing-read "Font variant: " variants nil nil nil nil my/font-variant)))) (setq my/font-variant new-variant) (my/set-font))) (defun my/toggle-font () (interactive) (let* ((variants (my/all-font-variants)) (cur-idx (cl-position my/font-variant variants :test 'string-equal)) (next-idx (mod (1+ cur-idx) (length variants))) (new-variant (elt variants next-idx))) (my/select-font-variant new-variant))) (defun my/refresh-font () (interactive) (my/set-font)) ;; size & scaling (setq text-scale-mode-step 1.05) (define-key global-map (kbd "C-+") 'text-scale-increase) (define-key global-map (kbd "C--") 'text-scale-decrease)
9. VCS
vc
Common prefix is C-x v
Some useful commands:
key | name | description |
---|---|---|
C-x v C-h | - | show help for vc-related actions |
C-x v p | my/vc-project |
run vc-dir in repo root |
C-x v v | vc-next-action |
next logical action in a repo (init, add, commit) |
C-x v d or = | vc-diff |
show diff for current file |
C-x v D | vc-root-diff |
show diff for whole repo |
C-x v a | vc-annotate |
show history, color-coded |
C-x v h | vc-region-history |
show history (buffer or region) |
C-x v l | vc-print-log |
show log for current file |
C-x v + | vc-update |
pull |
C-x v P | vc-push |
push |
In vc-git-log-edit-mode
:
key | name | description |
---|---|---|
C-c C-c | log-edit-done |
save commit |
C-c C-k | log-edit-kill-buffer |
cancel commit |
(use-package vc :bind (("C-x v p" . my/vc-project) ("C-x v d" . vc-diff) :map log-view-mode-map ("<tab>" . log-view-toggle-entry-display) ("j" . next-line) ("k" . previous-line) ("l" . forward-char) ("h" . backward-char)) :init ;; prot (defun my/vc-project () (interactive) (vc-dir (vc-root-dir))) (defun my/log-edit-toggle-amend () (interactive) (log-edit-toggle-header "Amend" "yes")) :config (use-package log-view) (add-hook 'vc-git-log-edit-mode-hook 'auto-fill-mode) (define-key diff-mode-map (kbd "M-o") nil)) (use-package log-edit :defer t :bind (:map log-edit-mode-map ("C-c C-a" . my/log-edit-toggle-amend))) (use-package vc-git :init (setq vc-git-print-log-follow t vc-git-diff-switches '("--patch-with-stat" "--histogram"))) (use-package vc-annotate :bind (("C-x v a" . vc-annotate) :map vc-annotate-mode-map ("t" . vc-annotate-toggle-annotation-visibility)) :init (setq vc-annotate-display-mode 'scale))
magit
(use-package magit :ensure t :commands (magit-status magit-dispatch-popup magit-blame-addition magit-log-buffer-file) :bind (("C-x g" . magit-status) ("C-x M-g" . magit-dispatch-popup)) :init (defalias 'magb 'magit-blame-addition) (defalias 'gl 'magit-log-buffer-file) (defalias 'magl 'magit-log-buffer-file) :config (add-hook 'magit-blame-mode-hook (lambda () (if (or (not (boundp 'magit-blame-mode)) magit-blame-mode) (evil-emacs-state) (evil-exit-emacs-state))))) ;; most stuff copied from prot (use-package magit-diff :after magit :init (setq magit-diff-refine-hunk t)) (use-package git-commit :after magit :init (setq git-commit-summary-max-length 50) (setq git-commit-style-convention-checks '(non-empty-second-line overlong-summary-line))) (use-package magit-repos :after magit :commands (magit-list-repositories) :bind (:map magit-repolist-mode-map ("d" . my/magit-repolist-dired)) :config (defun my/magit-repolist-dired () (interactive) (--if-let (tabulated-list-get-id) (dired (expand-file-name it)) (user-error "There is no repository at point")))) (use-package magit-todos :ensure t :after magit :config (magit-todos-mode))
git modes
(add-to-list 'auto-mode-alist '("/\\.gitignore\\'" . conf-unix-mode)) (add-to-list 'auto-mode-alist '("CODEOWNERS\\'" . conf-unix-mode))
ediff
(use-package ediff :commands (ediff-files ediff-files3 ediff-buffers ediff-buffers3 smerge-ediff) :init (setq ediff-keep-variants nil ediff-make-buffers-readonly-at-startup nil ediff-show-clashes-only t ediff-split-window-function 'split-window-horizontally ediff-window-setup-function 'ediff-setup-windows-plain))
git-timemachine
(use-package git-timemachine :ensure t :commands (git-timemachine) :config (add-hook 'git-timemachine-mode-hook '(lambda () (evil-local-mode -1))))
diff-hl & git-gutter+
(use-package diff-hl :ensure t :if is-gui :hook ((after-init . global-diff-hl-mode) (dired-mode . diff-hl-dired-mode)) :config ;; https://github.com/dgutov/diff-hl#magit (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh) (defun my/toggle-git-gutters () (interactive) (call-interactively 'global-diff-hl-mode))) (use-package git-gutter+ :ensure t :unless is-gui :diminish :hook (after-init . global-git-gutter+-mode) :config (defun my/toggle-git-gutters () (interactive) (call-interactively 'global-git-gutter+-mode)))
10. keybindings
keybind to command mapping
(setq my/leader-keys '( ("SPC" display-fill-column-indicator-mode) ("a" align-regexp) ("b" my/eww-browse-dwim) ;; dired ("dn" find-name-dired) ("dg" find-grep-dired) ("dv" my/git-grep-dired) ;; errors ("el" my/toggle-flycheck-error-list) ;; browsing/files ("fc" my/copy-file-path) ("fd" pwd) ("fp" my/try-open-dominating-file) ("fs" my/create-scratch-buffer-with-mode) ;; git/vc ("h" help) ;; insert ("iu" counsel-unicode-char) ;; project ("pa" counsel-projectile-ag) ("pr" counsel-projectile-rg) ("ps" my/rg-project-or-ask) ("pt" my/counsel-ag-todos-global) ;; show/display ("sd" pwd) ;; find/search ("sa" ag) ("sr" rg) ("sca" counsel-ag) ("scr" counsel-rg) ("sr" rgrep) ;; toggle ("t8" display-fill-column-indicator-mode) ("tc" global-company-mode) ("tf" my/toggle-font) ("tF" my/select-font-variant) ("tg" my/toggle-git-gutters) ("tl" my/line-numbers) ("to" symbol-overlay-mode) ("th" hl-line-mode) ("ts" flycheck-mode) ("tt" my/toggle-theme) ("tw" toggle-truncate-lines) ;; ui ("uh" rainbow-mode) ("um" (lambda () (interactive) (call-interactively 'tool-bar-mode) (call-interactively 'menu-bar-mode))) ("up" rainbow-delimiters-mode) ;; windows ("wf" my/window-fixed) ("wd" my/window-dedicated) ;; theme ("Ts" counsel-load-theme) ("Q" evil-local-mode) ))
setup keybindings
(define-prefix-command 'my/leader-map) ;; (define-key ctl-x-map "m" 'my/leader-map) (define-prefix-command 'my/leader-map) (global-set-key (kbd "C-c m") 'my/leader-map) (dolist (i my/leader-keys) (let ((k (car i)) (f (cadr i))) (define-key my/leader-map (kbd k) f))) (define-prefix-command 'my/major-mode-map) (if is-gui (progn ;; distinguish `C-m` from `RET` (define-key input-decode-map [?\C-m] [C-m]) ;; distinguish `C-i` from `TAB` ;; (define-key input-decode-map [?\C-i] [C-i]) (global-set-key (kbd "C-c <C-m>") 'my/leader-map) (setq my/major-mode-map-key "<C-m>")) (setq my/major-mode-map-key "C-c m m")) ;; on hold ;; (defun my/define-major-mode-keys (hook &rest combinations) ;; "Bind all pairs of (key . function) under `my/major-mode-map-key' ;; ;; The keys are bound after `hook'." ;; (add-hook ;; hook ;; `(lambda () ;; (let ((map (make-sparse-keymap))) ;; (local-set-key (kbd ,my/major-mode-map-key) map) ;; (dolist (comb (quote ,combinations)) ;; (define-key map (kbd (car comb)) (cdr comb))))))) (defun my/define-major-mode-key (mode key func) (let* ((map-symbol (intern (format "my/%s-map" mode))) (hook (intern (format "%s-hook" mode))) (map (if (boundp map-symbol) (symbol-value map-symbol) (progn (let ((map- (make-sparse-keymap))) (add-hook hook `(lambda () (local-set-key (kbd ,my/major-mode-map-key) (quote ,map-)))) (set (intern (format "my/%s-map" mode)) map-)))))) (define-key map (kbd key) func) (evil-leader/set-key-for-mode mode (kbd (format "m %s" key)) func))) (if is-gui (global-set-key (kbd "<C-m>") 'my/major-mode-map) (global-set-key (kbd "C-c m m") 'my/major-mode-map))
11. evil-mode
evil-mode setup
(use-package evil-leader :hook (evil-local-mode . evil-leader-mode) :ensure t :config (evil-leader/set-leader "<SPC>") (dolist (i my/leader-keys) (let ((k (car i)) (f (cadr i))) (evil-leader/set-key k f)))) (use-package evil-visualstar :hook (evil-local-mode . evil-visualstar-mode) :ensure t) (use-package evil :ensure t :hook ((prog-mode text-mode haskell-cabal-mode bibtex-mode coq-mode easycrypt-mode phox-mode mermaid-mode feature-mode conf-unix-mode conf-colon-mode conf-space-mode conf-windows-mode conf-toml-mode) . evil-local-mode) :init (setq evil-disable-insert-state-bindings t evil-want-C-i-jump nil evil-undo-system 'undo-tree evil-intercept-esc t evil-respect-visual-line-mode t evil-mode-line-format '(before . mode-line-front-space)) ;; (setq evil-move-cursor-back nil) ;; works better with lisp navigation :config (defun my/make-emacs-mode (mode) "Make `mode' use emacs keybindings." (delete mode evil-insert-state-modes) (add-to-list 'evil-emacs-state-modes mode)) (global-set-key (kbd "<f5>") 'evil-local-mode) ;; don't need C-n, C-p (define-key evil-insert-state-map (kbd "C-n") nil) (define-key evil-insert-state-map (kbd "C-p") nil) ;; magit (evil-define-key 'normal magit-blame-mode-map (kbd "q") 'magit-blame-quit) ;; intercept ESC when running in terminal (evil-esc-mode) ;; move search result to center of the screen (defadvice evil-search-next (after advice-for-evil-search-next activate) (evil-scroll-line-to-center (line-number-at-pos))) (defadvice evil-search-previous (after advice-for-evil-search-previous activate) (evil-scroll-line-to-center (line-number-at-pos))) ;; this is needed to be able to use C-h (global-set-key (kbd "C-h") 'help) (define-key evil-normal-state-map (kbd "C-h") 'undefined) (define-key evil-insert-state-map (kbd "C-h") 'undefined) (define-key evil-visual-state-map (kbd "C-h") 'undefined) (define-key evil-emacs-state-map (kbd "C-h") 'help) (define-key evil-insert-state-map (kbd "C-k") nil) (define-key evil-normal-state-map (kbd "M-.") nil) (define-key evil-normal-state-map (kbd "C-h") 'evil-window-left) (define-key evil-normal-state-map (kbd "C-j") 'evil-window-down) (define-key evil-normal-state-map (kbd "C-k") 'evil-window-up) (define-key evil-normal-state-map (kbd "C-l") 'evil-window-right) (define-key evil-normal-state-map (kbd ";") 'evil-ex) (define-key evil-visual-state-map (kbd ";") 'evil-ex) (evil-ex-define-cmd "sv" 'evil-window-split) (define-key evil-normal-state-map (kbd "C-p") 'counsel-projectile-find-file) (define-key evil-insert-state-map (kbd "C-M-i") 'company-complete) (define-key evil-visual-state-map (kbd "<") #'(lambda () (interactive) (progn (call-interactively 'evil-shift-left) (execute-kbd-macro "gv")))) (define-key evil-visual-state-map (kbd ">") #'(lambda () (interactive) (progn (call-interactively 'evil-shift-right) (execute-kbd-macro "gv")))) ;; redefine so that $ doesn't include the EOL char (setq my/evil-$-include-eol nil) (evil-define-motion evil-end-of-line (count) "Move the cursor to the end of the current line. If COUNT is given, move COUNT - 1 lines downward first." :type inclusive (move-end-of-line count) (when evil-track-eol (setq temporary-goal-column most-positive-fixnum this-command 'next-line)) (unless (and (evil-visual-state-p) my/evil-$-include-eol) (evil-adjust-cursor) (when (eolp) ;; prevent "c$" and "d$" from deleting blank lines (setq evil-this-type 'exclusive)))) ;; https://github.com/emacs-evil/evil-surround/issues/141 (defmacro my/evil-define-text-object (name key start-regex end-regex) (let ((inner-name (make-symbol (concat "evil-inner-" name))) (outer-name (make-symbol (concat "evil-a-" name)))) `(progn (evil-define-text-object ,inner-name (count &optional beg end type) (evil-select-paren ,start-regex ,end-regex beg end type count nil)) (evil-define-text-object ,outer-name (count &optional beg end type) (evil-select-paren ,start-regex ,end-regex beg end type count t)) (define-key evil-inner-text-objects-map ,key #',inner-name) (define-key evil-outer-text-objects-map ,key #',outer-name)))) )
evil packages that can be used without evil-mode
(use-package evil-nerd-commenter :ensure t :bind ("M-;" . evilnc-comment-or-uncomment-lines) :init ;; evilnc toggles instead of commenting/uncommenting (setq evilnc-invert-comment-line-by-line t)) (use-package evil-surround :ensure t :hook (after-init . global-evil-surround-mode) :config (evil-define-key 'visual evil-surround-mode-map "s" 'evil-surround-region) (defconst my/mark-active-alist `((mark-active ,@(let ((m (make-sparse-keymap))) (define-key m (kbd "C-c s") 'evil-surround-region) m)))) (add-to-list 'emulation-mode-map-alists 'my/mark-active-alist))
terminal cursor
;; in <user-emacs-directory>/lisp (use-package term-cursor :if is-term :hook (after-init . global-term-cursor-mode))
12. Spell checking
(use-package flyspell :commands (flyspell-mode flyspell-prog-mode) :config (add-hook 'flyspell-mode-hook (lambda () (add-hook 'hack-local-variables-hook 'flyspell-buffer))))
13. Buffer & window management
ibuffer
(use-package ibuffer :init ;; `/ R` to toggle showing these groups ;; `/ \` to disable (setq-default ibuffer-saved-filter-groups `(("Default" ("rg" (name . "\*rg.*\*")) ("Dired" (mode . dired-mode)) ("Scratch" (name . "\*scratch.*")) ("Temporary" (name . "\*.*\*")) ))) (setq ibuffer-show-empty-filter-groups nil) :config (define-key ibuffer-mode-map (kbd "M-o") nil) (global-set-key (kbd "C-x C-b") 'ibuffer) (add-hook 'ibuffer-mode-hook #'(lambda () (ibuffer-auto-mode 1))))
avy
(use-package avy :ensure t :bind (("C-c i" . avy-goto-char-timer)))
ace-window
(use-package ace-window :ensure t :bind ("C-c o" . ace-window) :init (setq aw-dispatch-always nil aw-keys (string-to-list "asdfghjkl;")) (my/add-hooks '(term-mode-hook) (define-key term-raw-map (kbd "C-c o") #'ace-window)))
buffer-move
(use-package buffer-move :ensure t :bind (("<C-S-up>" . buf-move-up) ("<C-S-down>" . buf-move-down) ("<C-S-left>" . buf-move-left) ("<C-S-right>" . buf-move-right)))
zoom
(use-package zoom :ensure t :bind ("M-+" . zoom) :init (defun my/zoom-size () (let* ((total-w (frame-width)) (total-h (frame-height)) (focus-w (max 100 (/ total-w 4))) (focus-h (max 65 (/ total-h 3))) (rest-w 20) (rest-h 10) (remain-w (abs (- total-w rest-w))) (remain-h (abs (- total-h rest-h))) (final-w (min focus-w remain-w)) (final-h (min focus-h remain-h)) ) (cons final-w final-h) )) (setq zoom-size 'my/zoom-size))
14. eww
(use-package eww :commands (eww) :bind (:map eww-mode-map ("q" . my/eww-quit)) :config (my/add-hooks '(eww-mode-hook) (setq shr-width 100) (setq-local shr-max-image-proportion 0.35)) (defun my/eww-quit () (interactive) (quit-window :kill) (unless (one-window-p) (delete-window))))
15. dired
(use-package dired :bind (:map dired-mode-map ("j" . dired-next-line) ("J" . dired-next-dirline) ("k" . dired-previous-line) ("K" . dired-prev-dirline) ("h" . backward-char) ("l" . forward-char) ("C-c C-n" . my/dired-find-file-ace) ("C-c C-l" . my/dired-limit-regexp) ("M-j" . my/dired-file-jump-from-here) ("M-u" . dired-up-directory) ("C-c C-q" . my/dired-kill-all-buffers)) :init ;; hide files being edited & flycheck files from dired (setq dired-omit-files "\\`[.]?#\\|\\`.flycheck_" dired-omit-verbose nil) (setq dired-hide-details-hide-symlink-targets nil) :config (add-hook 'dired-mode-hook #'auto-revert-mode) (add-hook 'dired-mode-hook #'dired-omit-mode) ;; prot (defvar my/dired-limit-hist '() "Minibuffer history for `my/dired-limit-regexp'") (defun my/dired-limit-regexp (regexp omit) "Limit Dired to keep files matching REGEXP. With optional OMIT argument as a prefix (\\[universal-argument]), exclude files matching REGEXP. Restore the buffer with \\<dired-mode-map>`\\[revert-buffer]'." (interactive (list (read-regexp (concat "Files " (when current-prefix-arg (propertize "NOT " 'face 'warning)) "matching PATTERN: ") nil 'prot-dired--limit-hist) current-prefix-arg)) (dired-mark-files-regexp regexp) (unless omit (dired-toggle-marks)) (dired-do-kill-lines) (add-to-history 'my/dired-limit-hist regexp)) (define-key dired-mode-map (kbd "C-c v") (my/control-function-window-split dired-find-file-other-window nil 0)) (define-key dired-mode-map (kbd "C-c s") (my/control-function-window-split dired-find-file-other-window 0 nil))) (use-package dired-sidebar :ensure t :commands (dired-sidebar-hide-sidebar dired-sidebar-showing-sidebar-p dired-sidebar-jump-to-sidebar dired-sidebar-toggle-sidebar dired-sidebar-toggle-with-current-directory) :bind (("C-\"" . my/dired-sidebar-smart-toggle) :map dired-sidebar-mode-map ("M-u" . dired-sidebar-up-directory)) :init (setq dired-sidebar-theme 'none dired-sidebar-should-follow-file t)) (defun my/dired-sidebar-smart-toggle (curdir) (interactive "P") (if (eq major-mode 'dired-sidebar-mode) (dired-sidebar-hide-sidebar) (if (dired-sidebar-showing-sidebar-p) (dired-sidebar-jump-to-sidebar) (if curdir (dired-sidebar-toggle-with-current-directory) (dired-sidebar-toggle-sidebar))))) (use-package dired-subtree :ensure t :after dired :bind (:map dired-mode-map ("<tab>" . dired-subtree-toggle) ("TAB" . dired-subtree-toggle))) (use-package dired-filter :ensure t :after dired) (use-package dired-git-info :ensure t :after dired :bind (:map dired-mode-map (")" . dired-git-info-mode)) :commands (dired-git-info-mode)) ;; more detailed colors (use-package diredfl :ensure t :hook (dired-mode . diredfl-mode)) (defun my/dired-find-file-ace () (interactive) (let ((find-file-run-dired t) (fname (dired-get-file-for-visit))) (if (ace-select-window) (find-file fname)))) (defun my/dired-file-jump-from-here () (interactive) (let ((find-file-run-dired t) (fname (dired-get-file-for-visit))) (my/counsel-file-jump-from-here fname))) (defun my/dired-kill-all-buffers () (interactive) (mapc (lambda (buf) (when (eq 'dired-mode (buffer-local-value 'major-mode buf)) (kill-buffer buf))) (buffer-list))) (use-package dired-x :after dired :init (if is-mac (setq dired-use-ls-dired nil)))
16. regex replace
re-builder (useful for debugging)
(use-package re-builder :commands (re-builder) :init (setq reb-re-syntax 'string))
visual-regexp-steroids
(use-package visual-regexp-steroids :ensure t :bind (("M-%" . vr/replace) ("C-M-%" . vr/query-replace)) :init (setq vr/engine 'python vr/match-separator-use-custom-face t))
17. direnv
(use-package direnv :ensure t :if (executable-find "direnv") :hook (after-init . direnv-mode) :init (setq direnv-show-paths-in-summary nil direnv-always-show-summary nil) (unless (fboundp 'file-attribute-size) (defun file-attribute-size (attrs) (elt attrs 7))))
18. lsp
(use-package lsp-mode :ensure t :commands lsp :hook (lsp-mode . lsp-lens-mode) :init (setq lsp-prefer-flymake nil)) (use-package lsp-ui :ensure t :commands lsp-ui-mode :init (setq lsp-ui-doc-delay 1))
19. LANGUAGES
nix
(use-package nix-mode :ensure t :mode (("\\.nix\\'" . nix-mode) ("\\.drv\\'" . nix-drv-mode)) :init (setq nix-nixfmt-bin "nixpkgs-fmt") :config (my/add-hooks '(nix-mode-hook) (subword-mode 1)) (my/define-major-mode-key 'nix-mode "s" 'my/nix-format-and-save) (my/define-major-mode-key 'nix-mode "m" 'my/nix-mark-multiline-string) (define-key nix-mode-map (kbd "C-c '") 'my/nix-edit-indirect-multiline-string)) (defun my/nix-format-and-save () (interactive) (nix-format-buffer) (save-buffer)) (defun my/nix-mark-multiline-string () (interactive) (deactivate-mark) (re-search-backward "''$" nil t) (next-line) (beginning-of-line 1) (call-interactively 'set-mark-command) (re-search-forward "^\s*''" nil t) (previous-line) (end-of-line 1)) (defun my/nix-edit-indirect-multiline-string () (interactive) (my/nix-mark-multiline-string) (my/edit-indirect-region))
haskell
(use-package haskell-mode :ensure t :mode (("\\.hs\\'" . haskell-mode) ("\\.lhs\\'" . literate-haskell-mode) ("\\.cabal\\'" . haskell-cabal-mode) ("\\.c2hs\\'" . haskell-c2hs-mode) ("\\.hcr\\'" . ghc-core-mode) ("\\.dump-simpl\\'" . ghc-core-mode)) :init (setq haskell-align-imports-pad-after-name t haskell-hoogle-command "hoogle --count=100" haskell-interactive-popup-errors nil ;; choices: auto, ghci, cabal-repl, stack-ghci ;; cabal-repl is the one to use with nix-shell & direnv ;; NOTE: cabal-new-repl is deprecated and equivalent to cabal-repl haskell-process-type 'cabal-repl ) (with-eval-after-load 'evil (my/evil-define-text-object "haskell-inline-comment" "#" "{- " " -}")) ;; TODO: sort out this shit (with-eval-after-load 'smartparens (with-eval-after-load 'haskell-mode (sp-local-pair 'haskell-mode "'" nil :actions nil))) ;; fontify special ghcid comments in haskell-mode ;; the comments look like this '-- $> ' ;; and are evaluated if ghcid is started with the '-a/--allow-eval' flag (defface my/haskell-ghcid-eval-face '((t (:inherit font-lock-warning-face))) "Face for highlighting ghcid eval directives in haskell-mode" :group 'my/faces) (font-lock-add-keywords 'haskell-mode '(("^[ \t]*-- $> .*" 0 'my/haskell-ghcid-eval-face prepend))) :config (my/highlight-keyword-in-mode 'haskell-mode "error" nil 'font-lock-preprocessor-face) (my/highlight-keyword-in-mode 'haskell-mode "undefined" nil 'font-lock-preprocessor-face) (my/define-major-mode-key 'haskell-mode "aa" 'my/haskell-align-and-sort-everything) (my/define-major-mode-key 'haskell-mode "ai" 'my/haskell-align-and-sort-imports) (my/define-major-mode-key 'haskell-mode "al" 'my/haskell-align-and-sort-language-extensions) (my/define-major-mode-key 'haskell-mode "c" 'projectile-compile-project) (my/define-major-mode-key 'haskell-mode "d" 'my/haskell-open-haddock-documentation) (my/define-major-mode-key 'haskell-mode "h" 'hoogle) (my/define-major-mode-key 'haskell-mode "i" 'my/haskell-insert-import) (my/define-major-mode-key 'haskell-mode "l" 'my/haskell-insert-language-extension) (my/define-major-mode-key 'haskell-mode "o" 'my/haskell-insert-ghc-option) (my/define-major-mode-key 'haskell-mode "r" 'my/haskell-insert-ghcid-repl-statement) (my/define-major-mode-key 'haskell-mode "s" 'my/haskell-format-and-save) (my/define-major-mode-key 'haskell-mode "/" 'engine/search-hoogle) (my/define-major-mode-key 'haskell-mode "?" 'engine/search-stackage) (my/define-major-mode-key 'haskell-mode "#" 'engine/search-haskell-language-extensions) (my/define-major-mode-key 'haskell-cabal-mode "s" 'my/haskell-cabal-format-and-save) (my/add-hooks '(haskell-mode-hook) (setq evil-shift-width 2) (push '(?# . ("{- " . " -}")) evil-surround-pairs-alist) (haskell-decl-scan-mode) (subword-mode 1)) ) (use-package ormolu :ensure t :commands (ormolu-format ormolu-format-buffer ormolu-format-region ormolu-format-on-save-mode) :init (setq ormolu-extra-args '("-o" "-XTypeApplications" "-o" "-XInstanceSigs" "-o" "-XBangPatterns" "-o" "-XPatternSynonyms" "-o" "-XUnicodeSyntax" ))) (defvar my/haskell-align-stuff t) (defvar my/haskell-use-ormolu t) (defun my/haskell-cabal-format-and-save () (interactive) (save-buffer) (shell-command (format "cabal format %s" (buffer-file-name))) (revert-buffer nil t)) (defun my/haskell-format-and-save (use-ormolu) "Format the import statements and save the current file." (interactive "P") (save-buffer) (if (or use-ormolu my/haskell-use-ormolu) (ormolu-format-buffer) (progn (my/haskell-align-and-sort-imports) (my/haskell-align-and-sort-language-extensions))) (save-buffer)) (defun my/haskell-align-and-sort-imports () (interactive) (save-excursion (goto-char 0) (let ((n-runs 0) (max-runs 10)) (while (and (< n-runs max-runs) (haskell-navigate-imports)) (progn (setq n-runs (1+ n-runs)) (when my/haskell-align-stuff (call-interactively 'haskell-align-imports)) (call-interactively 'haskell-sort-imports))) (if (>= n-runs max-runs) (message "Sorting/aligning imports probably timed out"))))) (defun my/-haskell-mark-language-extensions () (interactive) (deactivate-mark) (goto-char 0) (re-search-forward "^{-# LANGUAGE" nil t) (beginning-of-line 1) (call-interactively 'set-mark-command) (while (re-search-forward "^{-# LANGUAGE" nil t) nil) (end-of-line 1)) (defun my/haskell-align-and-sort-language-extensions () (interactive) (save-excursion (when my/haskell-align-stuff (my/-haskell-mark-language-extensions) (align-regexp (region-beginning) (region-end) "\\(\\s-*\\)#-")) (my/-haskell-mark-language-extensions) (sort-lines nil (region-beginning) (region-end)))) (defun my/haskell-insert-language-extension () (interactive) (let* ((all-exts (split-string (shell-command-to-string "ghc --supported-languages"))) (ext (completing-read "extension: " all-exts nil nil nil nil nil))) (save-excursion (goto-char 0) (re-search-forward "^{-#" nil t) (beginning-of-line 1) (open-line 1) (insert (format "{-# LANGUAGE %s #-}" ext))))) (defun my/haskell-insert-ghc-option () (interactive) (let* ((all-opts (split-string (shell-command-to-string "ghc --show-options"))) (ext (completing-read "option: " all-opts nil nil nil nil nil))) (save-excursion (goto-char 0) (re-search-forward "^module" nil t) (beginning-of-line 1) (open-line 1) (insert (format "{-# OPTIONS_GHC %s #-}" ext))))) (defun my/haskell-align-and-sort-everything () (interactive) (my/haskell-align-and-sort-imports) (my/haskell-align-and-sort-language-extensions)) (defun my/haskell-insert-ghcid-repl-statement (new-line) (interactive "P") (setq current-prefix-arg nil) (when new-line (end-of-line 1) (call-interactively 'newline)) (beginning-of-line 1) (call-interactively 'delete-horizontal-space) (insert "-- $> ")) (defun my/haskell-open-haddock-documentation (use-eww) (interactive "P") (let ((url "https://haskell-haddock.readthedocs.io/en/latest/markup.html")) (if use-eww (eww url) (browse-url url)))) (defvar my/ghc-source-path (expand-file-name "~/sources/ghc/")) (defun my/visit-ghc-tags-table () (interactive) (let ((tags (expand-file-name "TAGS" my/ghc-source-path))) (if (file-exists-p tags) (visit-tags-table tags) (error "No TAGS file found in ghc source directory"))))
(use-package dante :ensure t :after haskell-mode :commands (dante-mode) :hook (haskell-mode . dante-mode) :init (setq dante-methods '(new-impure-nix)) (add-hook 'dante-mode-hook '(lambda () (flycheck-add-next-checker 'haskell-dante '(warning . haskell-hlint)))))
coq (proof-general)
(use-package proof-general :ensure t :init (setq proof-splash-enable nil proof-script-fly-past-comments t)) (use-package holes :ensure proof-general :after proof-general coq-mode :commands (holes-mode) :diminish) (use-package coq-mode :ensure proof-general :mode (("\\.v\\'" . coq-mode)) :bind (:map coq-mode-map ("C-c ." . proof-electric-terminator-toggle) ("M-e" . forward-paragraph) ("M-a" . backward-paragraph) ("M-RET" . proof-goto-point) ("M-n" . proof-assert-next-command-interactive) ("M-p" . proof-undo-last-successful-command)) :init (setq coq-one-command-per-line nil coq-compile-before-require t) :config (my/add-hooks '(my/after-set-theme-hook) (when (fboundp 'coq-highlight-selected-hyps) (coq-highlight-selected-hyps))) (my/add-hooks '(coq-mode-hook) (setq evil-shift-width 2) (push '(?# . ("(* " . " *)")) evil-surround-pairs-alist) (undo-tree-mode 1) (whitespace-mode 1)) (defun my/coq-browse-stdlib () (interactive) (browse-url "https://coq.inria.fr/library/")) (my/define-major-mode-key 'coq-mode "t" 'engine/search-coq-tactics) (my/define-major-mode-key 'coq-mode "i" 'my/coq-browse-stdlib) ;; use yas-expand instead (define-key coq-mode-map (kbd "<C-return>") nil))
python
(use-package python :mode ("\\.py\\'" . python-mode) :init (setq python-shell-prompt-detect-failure-warning nil) :config (my/add-hooks '(python-mode-hook) (setq-default flycheck-disabled-checkers (append flycheck-disabled-checkers '(python-pycompile python-mypy))) (setq fill-column 100)) (defun my/python-format-and-save () (interactive) (blacken-buffer) (py-isort-before-save) (save-buffer)) (my/define-major-mode-key 'python-mode "s" 'my/python-format-and-save)) (use-package blacken :ensure t :if (executable-find "black") :after python :commands (blacken-mode blacken-buffer) :diminish) (use-package py-isort :ensure t :if (executable-find "isort") :after python :commands (py-isort-buffer py-isort-before-save)) (define-minor-mode my/python-format-on-save-mode "Minor mode for autoformatting python buffers on save." :lighter " pyf" :global nil (if my/python-format-on-save-mode (if (eq major-mode 'python-mode) (progn (blacken-mode +1) (add-hook 'before-save-hook #'py-isort-before-save nil :local)) (progn (setq my/python-format-on-save-mode nil) (user-error "Not a python-mode buffer"))) (progn (blacken-mode -1) (remove-hook 'before-save-hook #'py-isort-before-save :local)))) (use-package elpy :ensure t :hook ((python-mode . elpy-enable)) :diminish :init (setq elpy-modules '(elpy-module-sane-defaults elpy-module-company ;; elpy-module-eldoc elpy-module-pyvenv)) (setq eldoc-idle-delay 1) (setq python-shell-interpreter "ipython" python-shell-interpreter-args "-i --simple-prompt")) (use-package pyvenv :ensure t :after python :commands (pyvenv-workon) :config (defun my/mode-line-extra-python-mode () (let ((venv pyvenv-virtual-env-name)) (format "(%s) " (or venv "-"))))) (use-package pyenv-mode :ensure t :after python :commands (pyenv-mode pyenv-mode-set pyenv-mode-unset)) (use-package poetry :ensure t :after python :commands (poetry poetry-venv-workon) :config (my/define-major-mode-key 'python-mode "v" 'poetry-venv-workon)) (defvar my/python-poetry-env-path (if is-mac (expand-file-name "~/Library/Caches/pypoetry/virtualenvs"))) (defun my/python-poetry-venv-activate () (interactive) (let* ((all-envs (directory-files my/python-poetry-env-path nil directory-files-no-dot-files-regexp)) (env (completing-read "env: " all-envs)) (env-path (expand-file-name env my/python-poetry-env-path))) (message (format "Activating venv in %s" env-path)) (pyvenv-activate env-path))) (defun eshell/workon (arg) (pyvenv-workon arg)) (defun eshell/deactivate () (pyvenv-deactivate)) (add-to-list 'auto-mode-alist '("\\.flake8\\'" . conf-toml-mode))
rust
(use-package rust-mode :ensure t :mode (("\\.rs\\'" . rust-mode)) ; :hook (rust-mode . lsp) ) (use-package cargo :ensure t :after rust-mode :hook (rust-mode . cargo-minor-mode) :diminish cargo-minor-mode) (use-package flycheck-rust :ensure t :after (rust-mode flycheck) :init (with-eval-after-load 'rust-mode (add-hook 'flycheck-mode-hook #'flycheck-rust-setup))) (use-package racer :ensure t :after rust-mode :hook (rust-mode . racer-mode) :diminish racer-mode :init :config (let* ((root (string-trim (shell-command-to-string "rustc --print sysroot"))) (rust-src (expand-file-name "lib/rustlib/src/rust/library/" root))) (setq racer-rust-src-path rust-src)))
ruby
;; https://github.com/howardabrams/dot-files/blob/master/emacs-ruby.org (use-package enh-ruby-mode :ensure t :mode (("_spec\\.rb\\'" . ruby-rspec-mode) ("\\.rb\\'" . enh-ruby-mode) ("Gemfile\\'" . enh-ruby-mode) ("\\.rake\\'" . ruby-rake-mode) ("Rakefile\\'" . ruby-rake-mode)) :init (setq enh-ruby-indent-level 2 enh-ruby-indent-tabs-mode nil enh-ruby-check-syntax nil) (define-derived-mode ruby-rake-mode enh-ruby-mode "ruby-rake") (define-derived-mode ruby-rspec-mode enh-ruby-mode "ruby-rspec") (defface my/rake-keyword-face '((t (:inherit font-lock-function-name-face))) "Face for highlighting rake task keywords." :group 'my/faces) (defface my/rspec-keyword-face '((t (:inherit font-lock-function-name-face))) "Face for highlighting rspec test keywords." :group 'my/faces) (defvar my/rake-keywords '("namespace" "desc" "task")) (defvar my/rspec-keywords '( "describe" "context" "it" "before" "let" "expect" "expect_any_instance_of" "allow" "allow_any_instance_of" "shared_examples" "it_behaves_like" "include_context" )) :config (my/add-hooks '(enh-ruby-mode-hook) (setq evil-shift-width 2) (setq fill-column 120) (when (buffer-file-name) (add-hook 'before-save-hook #'my/ruby-insert-frozen-string-literal nil :local))) (my/define-major-mode-key 'enh-ruby-mode "s" 'my/ruby-format-and-save) (dolist (kw my/rake-keywords) (my/highlight-keyword-in-mode 'ruby-rake-mode kw nil 'my/rake-keyword-face)) (dolist (kw my/rspec-keywords) (my/highlight-keyword-in-mode 'ruby-rspec-mode kw nil 'my/rspec-keyword-face)) (custom-set-faces '(enh-ruby-op-face ((t nil)))) (custom-set-faces '(enh-ruby-string-delimiter-face ((t (:inherit font-lock-string-face))))) ) (use-package robe :ensure t :after enh-ruby-mode :hook (enh-ruby-mode . robe-mode) :diminish) (use-package rbenv :ensure t :after enh-ruby-mode :commands (rbenv--locate-file rbenv-use-corresponding) ;; :hook (enh-ruby-mode . rbenv-use-corresponding) :init ;; testing this, runs on every buffer switch so might add overhead (defvar my/rbenv-current-version-file nil) (defun my/rbenv-use-corresponding () (interactive) (when (eq major-mode 'enh-ruby-mode) (let ((version-file-path (or (rbenv--locate-file ".ruby-version") (rbenv--locate-file ".rbenv-version")))) (when (and version-file-path (not (string-equal version-file-path my/rbenv-current-version-file))) (setq my/rbenv-current-version-file version-file-path) (rbenv-use-corresponding))))) (add-hook 'buffer-list-update-hook 'my/rbenv-use-corresponding) :config (defun my/mode-line-extra-enh-ruby-mode () (let ((version (rbenv--active-ruby-version))) (format "(%s) " (or version "-"))))) (use-package rubocopfmt :ensure t :after enh-ruby-mode :commands (rubocopfmt rubocopfmt-mode) :init (defun my/ruby-format-and-save () (interactive) (call-interactively 'rubocopfmt) (save-buffer))) (defvar my/ruby-frozen-string-literal "# frozen_string_literal: true") (defvar my/ruby-do-insert-frozen-string-literal t) (defun my/ruby-insert-frozen-string-literal () (interactive) (when my/ruby-do-insert-frozen-string-literal (save-excursion (goto-char (point-min)) (unless (re-search-forward my/ruby-frozen-string-literal nil t) (goto-char (point-min)) (newline 2) (previous-line 2) (insert my/ruby-frozen-string-literal)))))
clojure
(use-package cider :ensure t :commands (cider-jack-in) :diminish) (use-package clojure-mode :ensure t :mode (("\\.clj\\'" . clojure-mode) ("\\.edn\\'" . clojure-mode)))
javascript, typescript, html, css
(use-package rjsx-mode :ensure t :mode (("\\.jsx?\\'" . rjsx-mode)) :init (setq js2-mode-show-strict-warnings nil) :config (my/define-major-mode-key 'rjsx-mode "s" 'my/prettier-and-save) (my/define-major-mode-key 'rjsx-mode "d" 'js-doc-insert-function-doc) (my/define-major-mode-key 'rjsx-mode "D" 'js-doc-insert-file-doc) (my/add-hooks '(rjsx-mode-hook) (setq evil-shift-width 2) (define-key js2-mode-map (kbd "C-c C-f") nil))) (use-package flow-js2-mode :ensure t :after rjsx-mode :diminish) (use-package typescript-mode :ensure t :mode (("\\.ts\\'" . typescript-mode)) :init (setq typescript-indent-level 2) :config (my/define-major-mode-key 'typescript-mode "s" 'my/prettier-and-save) (my/add-hooks '(typescript-mode-hook) (subword-mode 1) (setq evil-shift-width 2))) (use-package js-doc :ensure t :commands (js-doc-insert-function-doc js-doc-insert-file-doc)) (use-package nvm :ensure t :commands (nvm-use nvm-use-for nvm-use-for-buffer) :init (defun my/nvm-auto-use () (when (locate-dominating-file (buffer-file-name) ".nvmrc") (nvm-use-for-buffer))) (defun my/nvm () (interactive) (nvm-use-for-buffer)) (my/add-hooks '(rjsx-mode-hook typescript-mode-hook web-tsx-mode-hook web-jsx-mode-hook) (my/nvm-auto-use))) (use-package js :commands (js-mode) :init (setq js-indent-level 2)) (use-package prettier-js :ensure t :commands (prettier-js prettier-js-mode) :init (defun my/prettier-and-save () (interactive) (prettier-js) (save-buffer))) (use-package add-node-modules-path :ensure t :commands (add-node-modules-path) :hook ((js-mode rjsx-mode typescript-mode web-tsx-mode) . add-node-modules-path)) (use-package web-mode :ensure t :mode (("\\.html\\'" . web-html-mode) ("\\.tsx\\'" . web-tsx-mode)) :init (setq web-mode-markup-indent-offset 2 web-mode-css-indent-offset 2 web-mode-code-indent-offset 2 web-mode-attr-indent-offset 2 web-mode-enable-auto-quoting nil) (define-derived-mode web-tsx-mode web-mode "web-tsx") (define-derived-mode web-html-mode web-mode "web-html") :config ;; web-tsx-mode (my/define-major-mode-key 'web-tsx-mode "s" 'my/prettier-and-save) (custom-set-faces '(web-mode-keyword-face ((t (:inherit font-lock-keyword-face))))) (my/add-hooks '(web-tsx-mode) (subword-mode 1))) (with-eval-after-load 'mhtml-mode (define-key mhtml-mode-map (kbd "M-o") nil)) (use-package css-mode :mode (("\\.css\\'" . css-mode)) :init (setq css-indent-offset 2 css-fontify-colors nil)) (use-package emmet-mode :ensure t :commands (emmet-expand-line) :bind (:map web-html-mode-map ("<C-return>" . emmet-expand-line) :map html-mode-map ("<C-return>" . emmet-expand-line) :map css-mode-map ("<C-return>" . emmet-expand-line)) :hook ((web-html-mode html-mode css-mode) . emmet-mode))
purescript
(use-package purescript-mode :ensure t :mode ("\\.purs\\'" . purescript-mode) :init (setq purescript-indent-offset 2 purescript-align-imports-pad-after-name t) :config (my/define-major-mode-key 'purescript-mode "a" 'my/purescript-sort-and-align-imports) (my/define-major-mode-key 'purescript-mode "i" 'purescript-navigate-imports) (my/define-major-mode-key 'purescript-mode "s" 'my/purescript-format-and-save) (my/define-major-mode-key 'purescript-mode "/" 'engine/search-pursuit) (add-hook 'purescript-mode-hook (lambda () (setq evil-shift-width 2) (turn-on-purescript-indentation) (turn-on-purescript-decl-scan) ;; (turn-on-purescript-font-lock) (push '(?# . ("{- " . " -}")) evil-surround-pairs-alist) (subword-mode 1) (make-variable-buffer-local 'find-tag-default-function) (setq find-tag-default-function (lambda () (current-word t t))) )) ;; xref for purescript works a bit weird with qualified identifiers ;; (define-key purescript-mode-map (kbd "M-.") ;; #'(lambda () (interactive) (xref-find-definitions (current-word t t)))) ) (defvar my/purescript-align-stuff t) (defun my/purescript-sort-and-align-imports () (interactive) (save-excursion (goto-line 1) (while (purescript-navigate-imports) (progn (purescript-sort-imports) (when my/purescript-align-stuff (purescript-align-imports)))) (purescript-navigate-imports-return))) (defun my/purescript-format-and-save () "Formats the import statements using haskell-stylish and saves the current file." (interactive) (my/purescript-sort-and-align-imports) (save-buffer))
all lisps
;; expand macros in another window (define-key lisp-mode-map (kbd "C-c C-m") #'(lambda () (interactive) (macrostep-expand t))) (my/add-hooks '(lisp-mode-hook emacs-lisp-mode-hook lisp-interaction-mode-hook) (eldoc-mode))
emacs lisp
(use-package elisp-mode :mode (("\\.el\\'" . emacs-lisp-mode) ("\\.elc\\'" . elisp-byte-code-mode)) :config (defun my/emacs-lisp-format-and-save () (interactive) (my/indent-region-or-buffer) (save-buffer)) (my/define-major-mode-key 'emacs-lisp-mode "s" #'my/emacs-lisp-format-and-save)) (use-package eros :ensure t :after elisp-mode :config (eros-mode))
scala
(use-package scala-mode :ensure t :mode (("\\.scala\\'" . scala-mode)) :interpreter ("scala" . scala-mode) :hook (scala-mode . lsp)) (use-package sbt-mode :ensure t :after scala-mode :commands (sbt-start sbt-command) :config ;; WORKAROUND: https://github.com/ensime/emacs-sbt-mode/issues/31 ;; allows using SPACE when in the minibuffer (substitute-key-definition 'minibuffer-complete-word 'self-insert-command minibuffer-local-completion-map) ;; sbt-supershell kills sbt-mode: https://github.com/hvesalai/emacs-sbt-mode/issues/152 (setq sbt:program-options '("-Dsbt.supershell=false"))) (use-package lsp-metals :ensure t :after scala-mode :init (setq lsp-metals-treeview-show-when-views-received t))
ocaml
(use-package tuareg :ensure t :mode ("\\.ml[ip]?\\'" . tuareg-mode))
agda
;; currently managed by nixos (emacsPackages.agda2-mode) (use-package agda2-mode :mode ("\\.l?agda\\'" . agda2-mode) :config (my/add-hooks '(agda2-mode-hook) (activate-input-method "Agda")))
idris
(use-package idris-mode :ensure t :mode ("\\.idr\\'" . idris-mode))
c, c++
(setq c-default-style "linux" c-basic-offset 4)
dhall
(use-package dhall-mode :ensure t :mode ("\\.dhall\\'" . dhall-mode) :init (setq dhall-format-at-save nil dhall-format-arguments '("--ascii")) (with-eval-after-load 'smartparens (with-eval-after-load 'dhall-mode (sp-local-pair 'dhall-mode "\\(" ")"))) :config (defun my/dhall-format-and-save () (interactive) (dhall-format-buffer) (save-buffer)) (my/add-hooks '(dhall-mode-hook) (setq indent-tabs-mode nil evil-shift-width 2)) (my/define-major-mode-key 'dhall-mode "s" #'my/dhall-format-and-save))
bazel
(use-package bazel :ensure t :mode (("\\.bazel\\'" . bazel-mode) ("\\.bzl\\'" . bazel-mode) ("\\.star\\'" . bazel-starlark-mode)) :config (defun my/bazel-format-and-save () (interactive) (let* ((fn (file-name-nondirectory buffer-file-name)) (ext (file-name-extension fn)) (tp (cond ((string= fn "BUILD.bazel") "build") ((string= ext "bzl") "bzl") (t (user-error (format "Not a bazel file extension: %s" ext)))))) (my/format-and-save "buildifier" "--type" tp)) (save-buffer)) (my/define-major-mode-key 'bazel-mode "s" #'my/bazel-format-and-save))
nginx
(use-package nginx-mode :ensure t :mode (("nginx\\.conf\\'" . nginx-mode) ("nginx\\.conf\\.template\\'" . nginx-mode)))
terraform
(use-package terraform-mode :ensure t :mode ("\\.tf\\'" . terraform-mode) :config (defun my/terraform-format-and-save () (interactive) (terraform-format-buffer) (save-buffer)) (my/define-major-mode-key 'terraform-mode "s" #'my/terraform-format-and-save) (my/add-hooks '(terraform-mode-hook) (when (executable-find "terraform") (terraform-format-on-save-mode +1))))
docker
(use-package dockerfile-mode :ensure t :mode ("Dockerfile.*" . dockerfile-mode))
elasticsearch
(add-to-list 'auto-mode-alist '(".es\\'" . js-mode))
json
(use-package json-mode :ensure t :mode (("\\.json\\'" . json-mode) ("\\.json.tmpl\\'" . json-mode) ("\\.json.template\\'" . json-mode)) :config (defun my/json-format-and-save () (interactive) (json-mode-beautify) (save-buffer)) (my/define-major-mode-key 'json-mode "s" #'my/json-format-and-save))
yaml
(use-package yaml-mode :ensure t :mode (("\\.ya?ml\\'" . yaml-mode) ("\\.ya?ml.tmpl\\'" . yaml-mode) ("\\.ya?ml.template\\'" . yaml-mode) ("\\.ya?ml.sample\\'" . yaml-mode)) :config (my/add-hooks '(yaml-mode-hook) (setq evil-shift-width 2)) ) (use-package flycheck-yamllint :ensure t :after (flycheck yaml-mode) :commands (flycheck-yamllint-setup) :hook (yaml-mode . flycheck-yamllint-setup))
conf
;; add env files to conf-mode alist (add-to-list 'auto-mode-alist '("\\.env\\'" . conf-mode)) (add-to-list 'auto-mode-alist '("\\.env.*\\'" . conf-mode)) (add-to-list 'auto-mode-alist '("env\\.example\\'" . conf-mode)) (add-to-list 'auto-mode-alist '("\\.env\\..*\\.sample\\'" . conf-mode)) (add-to-list 'auto-mode-alist '("\\.env.sample\\'" . conf-mode))
cucumber
(use-package feature-mode :ensure t :mode (("\\.feature\\'" . feature-mode)) :init ;; add keywords (dolist (kw '("Example" "Rule" "Scenario Template" )) (font-lock-add-keywords 'feature-mode `((,(format "^[ ]*\\(%s\\):?" kw) . ((1 '(face font-lock-keyword-face))))))))
graphviz
(use-package graphviz-dot-mode :ensure t :mode (("\\.dot\\'" . graphviz-dot-mode)) :init (setq graphviz-dot-indent-width 4))
mermaid
(use-package mermaid-mode :ensure t :mode (("\\.mmd\\'" . mermaid-mode) ("\\.mermaid\\'" . mermaid-mode)))
mustache
(use-package mustache-mode :ensure t :mode (("\\.mustache\\'" . mustache-mode)) :init (setq mustache-basic-offset 2) :config (defconst my/mustache-mode-unescape (concat "\\({{&\s*" mustache-mode-mustache-token "\s*}}\\)")) (font-lock-add-keywords 'mustache-mode `((,my/mustache-mode-unescape (1 font-lock-variable-name-face)))) (add-hook 'mustache-mode-hook #'evil-local-mode))
editorconfig
(use-package editorconfig :ensure t :mode (("\\.editorconfig\\'" . editorconfig-conf-mode)))
20. WRITING
markdown
(defvar my/markdown-css `(,(expand-file-name "static/github.css" user-emacs-directory) ,(expand-file-name "static/pygments.css" user-emacs-directory))) (use-package markdown-mode :ensure t :commands (markdown-mode gfm-mode) :bind (:map markdown-mode-map ("M-a" . beginning-of-defun) ("M-e" . end-of-defun) :map markdown-mode-command-map ("r" . ivy-bibtex)) :mode (("README\\.md\\'" . gfm-mode) ("\\.md\\'" . markdown-mode) ("\\.markdown\\'" . markdown-mode) ("\\.mdx\\'" . markdown-mode) ("\\.page\\'" . gfm-mode)) :init (setq markdown-asymmetric-header t markdown-enable-wiki-links t markdown-wiki-link-fontify-missing t markdown-enable-math t markdown-gfm-use-electric-backquote nil markdown-list-indent-width 2 markdown-command "pandoc --highlight-style=pygments --mathjax --to html" markdown-css-paths my/markdown-css) (defface my/markdown-pandoc-native-div-face '((t (:inherit font-lock-preprocessor-face))) "Face for highlighting pandoc native div blocks (starting with ':::')" :group 'my/faces) ;; (font-lock-add-keywords ;; 'markdown-mode ;; '(("^::::*.*" 0 'my/markdown-pandoc-native-div-face))) (defface my/markdown-shortcut-reference-link-face '((t (:inherit markdown-link-face))) "Face for highlighting shortcut reference links ([link]) in pandoc markdown." :group 'my/faces) (font-lock-add-keywords 'markdown-mode '(("\\(\\[\\)\\([^]@][^]]*?\\)\\(\\]\\)[^[]" . ((1 markdown-markup-properties) (2 '(face my/markdown-shortcut-reference-link-face)) (3 markdown-markup-properties))))) (defface my/markdown-citation-face '((t (:inherit font-lock-function-name-face))) "Face for highlighting citations ([@citation]) in pandoc markdown." :group 'my/faces) (font-lock-add-keywords 'markdown-mode '(("\\(\\[@\\)\\(.+?\\)\\(\\]\\)[^[]" . ((1 markdown-markup-properties) (2 '(face my/markdown-citation-face)) (3 markdown-markup-properties))))) (defface my/markdown-mustache-curly-face '((t (:inherit font-lock-preprocessor-face))) "Face for highlighting template boundaries in pandoc markdown." :group 'my/faces) (defface my/markdown-mustache-modifier-face '((t (:inherit font-lock-preprocessor-face))) "Face for highlighting template modifiers (#, / or ^) in pandoc markdown." :group 'my/faces) (defface my/markdown-mustache-variable-face '((t (:inherit font-lock-warning-face))) "Face for highlighting template variables ({{ var }}) in pandoc markdown." :group 'my/faces) (font-lock-add-keywords 'markdown-mode '(("\\({{\\)\\([#/^&]?\\)\\(.+?\\)\\(}}\\)" . ((1 '(face my/markdown-mustache-curly-face)) (2 '(face my/markdown-mustache-modifier-face)) (3 '(face my/markdown-mustache-variable-face)) (4 '(face my/markdown-mustache-curly-face)))))) :config (if (executable-find "marked") (setq markdown-command "marked")) (my/define-major-mode-key 'markdown-mode "c" 'check-parens) (my/define-major-mode-key 'markdown-mode "o" 'my/writeroom) (my/add-hooks '(markdown-mode-hook) (setq evil-shift-width 2) (auto-fill-mode 1) (whitespace-mode +1) (push '(?# . ("<!-- " . " -->")) evil-surround-pairs-alist)) ) (use-package markdown-toc :ensure t :after markdown-mode :commands (markdown-toc-refresh-toc markdown-toc-generate-toc markdown-toc-generate-or-refresh-toc) :init (defalias 'mtoc 'markdown-toc-generate-or-refresh-toc))
writeroom
(use-package writeroom-mode :ensure t :commands (writeroom-mode) :init (setq writeroom-global-effects nil writeroom-fringes-outside-margins nil writeroom-maximize-window nil writeroom-width 100 writeroom-header-line nil my/writeroom-fill-column-prev nil) (setq writeroom-mode-line '((:eval (my/split-mode-line-render ;; left (quote ("%e" " " mode-line-modified " " mode-line-buffer-identification " " (:eval (my/mode-line-major-mode-extra)) )) ;; right (quote ((:eval (my/mode-line-input-method)) (:eval (my/mode-line-region-info)) mode-line-position " " )) )))) (defun my/writeroom (variable-pitch) (interactive "P") (if (and (boundp 'writeroom-mode) writeroom-mode) (progn (writeroom-mode -1) (variable-pitch-mode -1) (when my/writeroom-fill-column-prev (display-fill-column-indicator-mode))) (setq-local writeroom-width fill-column) (writeroom-mode) (when variable-pitch (variable-pitch-mode)) (setq-local my/writeroom-fill-column-prev (and (boundp 'display-fill-column-indicator-mode) display-fill-column-indicator-mode)) (display-fill-column-indicator-mode -1))))
reStructuredText
(use-package rst :mode ("\\.rst\\'" . rst-mode) :bind (:map rst-mode-map ("M-a" . rst-backward-section) ("M-e" . rst-forward-section)) :init (setq rst-indent-width 2) :config (my/add-hooks '(rst-mode-hook) (setq evil-shift-width rst-indent-width)))
asciidoc
(use-package adoc-mode :ensure t :mode ("\\.adoc\\'" . adoc-mode))
LaTeX
(use-package tex-mode :ensure auctex :mode (("\\.tex\\'" . latex-mode)) :init (defvar-local my/texcount nil) (defun my/texcount-update () (let ((fname (buffer-file-name))) (when (and (eq major-mode 'latex-mode) (not (null fname))) (setq-local my/texcount (string-trim (shell-command-to-string (format "texcount -sum -brief -total %s" fname))))))) (setq TeX-brace-indent-level 0 LaTeX-item-indent 0 LaTeX-indent-level 2) :config (my/add-hooks '(LaTeX-mode-hook) (smartparens-mode) (whitespace-mode) (when (executable-find "texcount") (my/texcount-update) (add-hook 'after-save-hook #'my/texcount-update nil t))) (defun my/mode-line-extra-latex-mode () (if (null my/texcount) "(-)" (let ((face (if (buffer-modified-p) 'compilation-warning 'compilation-info))) (format "(%s)" (propertize my/texcount 'face face)))))) (use-package latex-preview-pane :ensure t :after tex-mode :commands (latex-preview-pane-mode latex-preview-pane-enable)) (use-package ivy-bibtex :ensure t :commands (ivy-bibtex) :config (defalias 'ib 'ivy-bibtex) (setq bibtex-completion-cite-prompt-for-optional-arguments nil ivy-bibtex-default-action 'ivy-bibtex-insert-citation))
21. elfeed
(use-package elfeed-web :ensure t :commands (elfeed-web-start) :after elfeed :config (defun my/elfeed-web-browse () (interactive) (browse-url "http://localhost:8080/elfeed/"))) (use-package elfeed :ensure t :commands (elfeed) :bind (:map elfeed-search-mode-map ("U" . elfeed-update) ("o" . my/elfeed-search-other-window) ("t" . my/elfeed-filter-in-tag) ("T" . my/elfeed-filter-out-tag) ("f" . my/elfeed-filter-in-feed) ("q" . my/elfeed-kill-buffer-close-window-dwim) ("e" . my/elfeed-show-eww) ("x" . my/elfeed-search-org-capture) ("h" . backward-char) ("j" . next-line) ("k" . previous-line) ("l" . forward-char) :map elfeed-show-mode-map ("i" . my/elfeed-hide-images) ("e" . my/elfeed-show-eww) ("q" . my/elfeed-kill-buffer-close-window-dwim) ("b" . my/elfeed-show-browse-url) ("x" . my/elfeed-show-org-capture) ("h" . backward-char) ("j" . next-line) ("k" . previous-line) ("l" . forward-char)) :init (setq elfeed-db-directory (expand-file-name "~/.elfeed") elfeed-use-curl t elfeed-curl-max-connections 10 elfeed-search-clipboard-type 'CLIPBOARD elfeed-search-filter "@5-days-ago +unread " elfeed-search-title-max-width 100 elfeed-search-title-min-width 30 elfeed-search-trailing-width 30) (setq my/elfeed-org-capture-default-filename (expand-file-name "saved-elfeed-posts.org" my/org-directory)) :config (defun my/elfeed-hide-images (tog) (interactive "P") (let ((shr-inhibit-images (not tog))) (elfeed-show-refresh))) (defun my/elfeed-all-visible-feeds () "Return an alist (name -> feed struct) of all currently shown feeds" ;; `-compare-fn' is used internally by `-distinct', to remove duplicate ;; feeds by name (`car') (let ((-compare-fn (-on 'eq 'car))) (-distinct (-annotate 'elfeed-feed-title (mapcar 'elfeed-entry-feed elfeed-search-entries))))) (defun my/elfeed-filter-in-feed () (interactive) (call-interactively 'elfeed-search-clear-filter) (let* ((feeds (my/elfeed-all-visible-feeds)) (old-filter elfeed-search-filter) (selection (completing-read "Feed: " feeds nil :require-match)) (feed (cdr (assoc selection feeds))) (feed-url (elfeed-feed-url feed)) (new-filter (format "%s =%s" old-filter feed-url))) (elfeed-search-set-filter new-filter))) (defun my/elfeed-all-visible-tags () "Return a list of all currently shown tags" (-distinct (apply #'append (mapcar #'elfeed-entry-tags elfeed-search-entries)))) (defun my/elfeed-filter-tag (clear inverse) (when clear (call-interactively 'elfeed-search-clear-filter)) (let* ((tags (my/elfeed-all-visible-tags)) (old-filter elfeed-search-filter) (selection (completing-read "Tag: " tags nil :require-match)) (op (if inverse "-" "+")) (new-filter (format "%s %s%s" old-filter op selection))) (elfeed-search-set-filter new-filter))) (defun my/elfeed-filter-in-tag (clear) (interactive "P") (my/elfeed-filter-tag clear nil)) (defun my/elfeed-filter-out-tag (clear) (interactive "P") (my/elfeed-filter-tag clear t)) ;; prot (defun my/elfeed-search-other-window (&optional horz) (interactive "P") (let* ((entry (if (eq major-mode 'elfeed-show-mode) elfeed-show-entry (elfeed-search-selected :ignore-region))) (link (elfeed-entry-link entry)) (win (selected-window))) (with-current-buffer (get-buffer "*elfeed-search*") (when (null (get-buffer-window "*elfeed-entry*")) (if horz (split-window win (/ (frame-width) 3) 'right) (split-window win (/ (frame-height) 3) 'below))) (other-window 1) (elfeed-search-show-entry entry)))) (defun my/elfeed-show-eww (&optional link) (interactive) (let* ((entry (if (eq major-mode 'elfeed-show-mode) elfeed-show-entry (elfeed-search-selected :ignore-region))) (link (if link link (elfeed-entry-link entry)))) (eww link) (add-hook 'eww-after-render-hook 'eww-readable nil t))) (defun my/elfeed-show-browse-url () (interactive) (browse-url (elfeed-entry-link elfeed-show-entry))) (defun my/elfeed-kill-buffer-close-window-dwim () (interactive) (let ((win (selected-window))) (cond ((eq major-mode 'elfeed-show-mode) (elfeed-kill-buffer) (unless (one-window-p) (delete-window win)) (switch-to-buffer "*elfeed-search*")) ((eq major-mode 'elfeed-search-mode) (if (one-window-p) (elfeed-search-quit-window) (delete-other-windows win)))))) (defun my/elfeed-org-capture-get-tags (entry) (let* ((all-tags (elfeed-entry-tags entry)) (tags (seq-filter #'(lambda (tag) (not (eq tag 'unread))) all-tags)) (tags-str (mapcar #'(lambda (tag) (format "%s" tag)) tags))) (if (null tags) "" (string-join tags-str ", ")))) (defvar my/elfeed-org-capture-entry nil) (defun my/elfeed-org-capture (entry immediate) (let ((my/elfeed-org-capture-entry entry) (template (if immediate "E" "e"))) (org-capture nil template))) (defun my/elfeed-search-org-capture (immediate) (interactive "P") (my/elfeed-org-capture (elfeed-search-selected :ignore-region) immediate)) (defun my/elfeed-show-org-capture (immediate) (interactive "P") (my/elfeed-org-capture elfeed-show-entry immediate)) (add-hook 'elfeed-show-mode-hook #'(lambda () (setq-local shr-width 100))) (add-hook 'elfeed-new-entry-hook (elfeed-make-tagger :before "2 weeks ago" :remove 'unread)) ;; (add-hook 'elfeed-search-update-hook #'elfeed-db-save) (add-hook 'elfeed-update-init-hooks #'elfeed-db-save) (defface my/elfeed-important '((t (:foreground "salmon"))) "Elfeed unread" :group 'my/faces) (push '(important my/elfeed-important) elfeed-search-face-alist) (defun my/elfeed-export-opml-feedly (file) "Export all feeds to FILE, categorized for import in feedly." (interactive "FOutput OPML file: ") (with-temp-file file (let ((standard-output (current-buffer)) (categorized (-group-by 'cadr elfeed-feeds))) (princ "<?xml version=\"1.0\"?>\n") (xml-print `((opml ((version . "1.0")) (head () (title () "Elfeed Export")) (body () ,@(cl-loop for group in (-group-by 'cadr elfeed-feeds) for category = (car group) for feeds = (cdr group) collect `(outline ((text . ,(format "%s" category)) (title . ,(format "%s" category))) ,@(cl-loop for feed in feeds for url = (car feed) for elfeed-feed = (elfeed-db-get-feed url) for maybe-title = (elfeed-feed-title elfeed-feed) for title = (if (or (null maybe-title) (string-empty-p maybe-title)) (url-host (url-generic-parse-url url)) maybe-title) collect `(outline ((xmlUrl . ,url) (title . ,title))))))))))))) ;; get feeds from personal dir (load-file (expand-file-name "lisp/elfeed-feeds.el" my/dropbox-emacs-dir)))
22. pdf tools
(use-package pdf-tools :ensure t :defer t :magic ("%PDF" . pdf-view-mode) :bind (:map pdf-view-mode-map ("j" . pdf-view-next-line-or-next-page) ("k" . pdf-view-previous-line-or-previous-page) ("h" . image-backward-hscroll) ("l" . image-forward-hscroll) ("C-s" . isearch-forward) ("ss" . my/pdf-view-remove-margins-mode) ("cc" . pdf-cache-clear-data) ) :init (setq pdf-view-display-size 'fit-page pdf-view-midnight-colors '("#cfe8e7" . "#0a3749") ;; cache stuff, testing pdf-cache-image-limit 15 pdf-cache-prefetch-delay 2 ) ;; remove cached images after x seconds (setq image-cache-eviction-delay 15) :config (pdf-tools-install :no-query) (defun my/mode-line-extra-pdf-view-mode () (let ((cur (number-to-string (pdf-view-current-page))) (tot (or (ignore-errors (number-to-string (pdf-cache-number-of-pages))) "???"))) (format "(%s/%s)" cur tot))) (define-minor-mode my/pdf-view-remove-margins-mode "Minor mode for removing margins from every pdf page." :lighter " pdf-margins" (if (not (eq major-mode 'pdf-view-mode)) (user-error "Not in a pdf-view-mode buffer") (if my/pdf-view-remove-margins-mode (progn (pdf-view-set-slice-from-bounding-box) (add-hook 'pdf-view-after-change-page-hook #'pdf-view-set-slice-from-bounding-box)) (progn (pdf-view-reset-slice) (remove-hook 'pdf-view-after-change-page-hook #'pdf-view-set-slice-from-bounding-box))))) ) (use-package pdf-outline :defer t :bind (:map pdf-outline-buffer-mode-map ("<backtab>" . outline-hide-sublevels)))
23. searching
isearch
C-h k C-s
to get a help menu for isearch
Some useful isearch keys (before starting the search):
key | description |
---|---|
M-s . | isearch for thing at point |
And while inside a search:
key | description |
---|---|
C-w | add next word to search |
C-e | add until EOL to search |
M-e | edit search in minibuffer (commit with RET ) |
M-s o | run occur |
M-s r | toggle regexp search |
M-s . | mark whole thing for search |
M-s h r | highlight current search (hi-lock ) |
M-% | run query-replace on search term |
C-M-% | run query-replace-regexp on search term |
C-l | recenter |
And some custom additions (mostly stolen from Protesilaos):
where | key | description |
---|---|---|
in search | C-SPC | mark search and exit |
in search | C-RET | exit search, but move point to the other side |
in search | <backspace> | delete failing part, one char or exit |
Other useful stuff:
- When exiting a search (with
RET
),C-x C-x
will mark from where the search started to where it finished.
(use-package isearch :diminish :bind (:map isearch-mode-map ("C-l" . recenter) ("C-SPC" . my/isearch-mark-and-exit) ("<C-return>" . my/isearch-other-end) ("<M-backspace>" . my/isearch-abort-dwim) ("<C-backspace>" . my/isearch-abort-dwim)) :init (setq search-whitespace-regexp ".*?" ;; spaces match anything isearch-lax-whitespace t ;; the default isearch-regex-lax-whitespace nil isearch-yank-on-move 'shift isearch-allow-scroll 'unlimited isearch-lazy-count t ;; show match count and current match index lazy-count-prefix-format nil lazy-count-suffix-format " (%s/%s)") :config ;; prot (defun my/isearch-mark-and-exit () (interactive) (push-mark isearch-other-end t 'activate) (setq deactivate-mark nil) (isearch-done)) (defun my/isearch-other-end () (interactive) (isearch-done) (when isearch-other-end (goto-char isearch-other-end))) (defun my/isearch-abort-dwim () "Delete failed `isearch' input, single char, or cancel search. This is a modified variant of `isearch-abort' that allows us to perform the following, based on the specifics of the case: (i) delete the entirety of a non-matching part, when present; (ii) delete a single character, when possible; (iii) exit current search if no character is present and go back to point where the search started." (interactive) (if (eq (length isearch-string) 0) (isearch-cancel) (isearch-del-char) (while (or (not isearch-success) isearch-error) (isearch-pop-state))) (isearch-update)) ;; https://www.reddit.com/r/emacs/comments/b7yjje/isearch_region_search/ (defun my/isearch-region (&optional not-regexp no-recursive-edit) "If a region is active, make this the isearch default search pattern." (interactive "P\np") (when (use-region-p) (let ((search (buffer-substring-no-properties (region-beginning) (region-end)))) (deactivate-mark) (isearch-yank-string search)))) (advice-add 'isearch-forward :after 'my/isearch-region) (advice-add 'isearch-forward-regexp :after 'my/isearch-region) (advice-add 'isearch-backward :after 'my/isearch-region) (advice-add 'isearch-backward-regexp :after 'my/isearch-region) (with-eval-after-load 'evil (dolist (st '(normal visual)) (evil-global-set-key st (kbd "gs") 'isearch-forward) (evil-global-set-key st (kbd "gr") 'isearch-backward))) )
rg
(use-package rg :ensure t :commands (rg my/rg-project-or-ask) :bind (("C-c g" . my/rg-project-or-ask) :map rg-mode-map ("m" . rg-menu) ("l" . rg-list-searches) ("s" . my/rg-save-search-as-name) ("N" . my/rg-open-ace-window) ("C-n" . next-line) ("C-p" . previous-line) ("j" . next-line) ("k" . previous-line) ("M-n" . rg-next-file) ("M-p" . rg-prev-file)) :init (setq rg-group-result t rg-ignore-case 'smart) (setq rg-custom-type-aliases '(("coq" . "*.v") ("rake" . "*.rake"))) (defalias 'rgp 'my/rg-project-or-ask) :config (rg-define-toggle "--multiline --multiline-dotall" "u") (rg-define-toggle "--word-regexp" "w") (rg-define-toggle "--files-with-matches" "L") (rg-define-search my/rg-org-directory :query ask :format regexp :files "org" :dir my/org-directory :confirm prefix) ;; prot ;; https://protesilaos.com/dotemacs/#h:31622bf2-526b-4426-9fda-c0fc59ac8f4b (rg-define-search my/rg-project-or-ask :query ask :format regexp :files "all" :dir (or (projectile-project-root) (read-directory-name "rg in: ")) :confirm prefix) (defun my/rg-save-search-as-name () "Save `rg' buffer, naming it after the current search query." (interactive) (let ((pattern (rg-search-pattern rg-cur-search))) (rg-save-search-as-name (concat "«" pattern "»")))) )
anzu
(use-package anzu :ensure t :hook (after-init . global-anzu-mode) :diminish :init (setq anzu-mode-lighter "")) (use-package evil-anzu :ensure t :after (evil anzu))
interactively search files/folders in project by regex with fd
;; stolen from prot :) (use-package dired-aux :bind (("C-c C-s" . my/dired-fd-files-and-dirs)) :init (setq dired-isearch-filenames 'dwim dired-create-destination-dirs 'ask dired-vc-rename-file t) :config (defmacro my/dired-fd (name doc prompt &rest flags) "Make commands for selecting 'fd' results with completion. NAME is how the function should be named. DOC is the function's documentation string. PROMPT describes the scope of the query. FLAGS are the command-line arguments passed to the 'fd' executable, each of which is a string." `(defun ,name (&optional arg) ,doc (interactive "P") (let* ((vc (vc-root-dir)) (dir (expand-file-name (if vc vc default-directory))) (regexp (read-regexp (format "%s matching REGEXP in %s: " ,prompt (propertize dir 'face 'bold)))) (names (process-lines "fd" ,@flags regexp dir)) (buf "*FD Dired*")) (if names (if arg (dired (cons (generate-new-buffer-name buf) names)) (find-file (completing-read (format "Items matching %s (%s): " (propertize regexp 'face 'success) (length names)) names nil t)))) (user-error (format "No matches for « %s » in %s" regexp dir))))) (my/dired-fd my/dired-fd-dirs "Search for directories in VC root or PWD. With \\[universal-argument] put the results in a `dired' buffer. This relies on the external 'fd' executable." "Subdirectories" "-i" "-H" "-a" "-t" "d" "-c" "never") (my/dired-fd my/dired-fd-files-and-dirs "Search for files and directories in VC root or PWD. With \\[universal-argument] put the results in a `dired' buffer. This relies on the external 'fd' executable." "Files and dirs" "-i" "-H" "-a" "-t" "d" "-t" "f" "-c" "never") )
24. imenu-list
(use-package imenu-list :ensure t :bind ("C-|" . my/imenu-list-smart-toggle) :config (defun my/imenu-list-jump-to-window () "Jump to imenu-list window if visible, otherwise create it and jump." (interactive) (if (get-buffer-window imenu-list-buffer-name) (select-window (get-buffer-window imenu-list-buffer-name)) (progn (imenu-list-minor-mode) (select-window (get-buffer-window imenu-list-buffer-name))))) (defun my/imenu-list-smart-toggle () "If imenu-list window doesn't exist, create it and jump. If if does but it is not the current buffer, jump there. If it exists and it's the current buffer, close it." (interactive) (if (eq (current-buffer) (get-buffer imenu-list-buffer-name)) (imenu-list-quit-window) (my/imenu-list-jump-to-window))) (setq imenu-list-size 40))
25. company
(use-package company :ensure t :diminish company-mode :bind (("C-M-i" . company-complete) :map company-active-map ("C-p" . company-select-previous) ("C-n" . company-select-next) ("C-f" . company-show-location) ("TAB" . company-complete-common-or-cycle) ("<tab>" . company-complete-common-or-cycle) ("<escape>" . company-abort)) :hook ((after-init . global-company-mode) (global-company-mode . company-quickhelp-mode)) :init (setq company-dabbrev-downcase nil company-minimum-prefix-length 3 company-idle-delay 0.4) :config (setq company-backends (delete 'company-dabbrev company-backends)) ;; (setq company-backends (delete 'company-capf company-backends)) (add-to-list 'company-backends 'company-capf) (add-to-list 'company-backends 'company-elisp) (add-to-list 'company-backends 'company-files)) (use-package company-quickhelp :ensure t :after company)
26. flycheck
(defun my/mode-line-flycheck ()) (use-package flycheck :ensure t :diminish flycheck-mode :bind (("C-c ! t" . flycheck-mode)) :hook (after-init . global-flycheck-mode) :init (setq flycheck-temp-prefix ".flycheck" flycheck-emacs-lisp-load-path 'inherit flycheck-check-syntax-automatically '(save mode-enabled) flycheck-markdown-markdownlint-cli-config ".markdownlint.yml" ;; to check while typing: ;; flycheck-check-syntax-automatically '(save idle-change new-line mode-enabled) ) :config (defun my/toggle-flycheck-error-list () (interactive) (-if-let (window (flycheck-get-error-list-window)) (quit-window nil window) (flycheck-list-errors))) (add-to-list 'display-buffer-alist `(,(rx bos "*Flycheck errors*" eos) (display-buffer-reuse-window display-buffer-in-side-window) (side . bottom) (reusable-frames . visible) (window-height . 0.33))) (setq-default flycheck-disabled-checkers (append flycheck-disabled-checkers '(javascript-jshint haskell-ghc haskell-stack-ghc yaml-ruby proselint))) (flycheck-add-mode 'javascript-eslint 'web-mode) (flycheck-add-mode 'javascript-eslint 'js2-mode) ;; modeline stuff (defface modeline-flycheck-error '((t (:foreground "#e05e5e" :distant-foreground "#e05e5e"))) "Face for flycheck error feedback in the modeline." :group 'modeline-flycheck) (defface modeline-flycheck-warning '((t (:foreground "#bfb03d" :distant-foreground "#bfb03d"))) "Face for flycheck warning feedback in the modeline." :group 'modeline-flycheck) (defface modeline-flycheck-info '((t (:foreground "DeepSkyBlue3" :distant-foreground "DeepSkyBlue3"))) "Face for flycheck info feedback in the modeline." :group 'modeline-flycheck) (defface modeline-flycheck-ok '((t (:foreground "SeaGreen3" :distant-foreground "SeaGreen3"))) "Face for flycheck ok feedback in the modeline." :group 'modeline-flycheck) (defvar modeline-flycheck-bullet "•%s") (defun my/mode-line-flycheck-state (state) (let* ((counts (flycheck-count-errors flycheck-current-errors)) (errorp (flycheck-has-current-errors-p state)) (err (or (cdr (assq state counts)) "?")) (running (eq 'running flycheck-last-status-change)) (face (intern (format "modeline-flycheck-%S" state)))) (if (or errorp running) (propertize (format modeline-flycheck-bullet err) 'face face)))) (defun my/mode-line-flycheck () (let* ((ml-error (my/mode-line-flycheck-state 'error)) (ml-warning (my/mode-line-flycheck-state 'warning)) (ml-info (my/mode-line-flycheck-state 'info)) (ml-status (concat ml-error ml-warning ml-info))) (if (null ml-status) "" (concat " " ml-status " ")))))
27. projectile
configuration
(use-package projectile :ensure t :hook (after-init . projectile-mode) :bind-keymap ("C-c p" . projectile-command-map) :diminish projectile-mode :init (setq projectile-completion-system 'ivy projectile-mode-line-function '(lambda () (format " P[%s]" (or (projectile-project-name) "-"))))) (use-package perspective :ensure t :hook (after-init . persp-mode) :bind (("M-N" . persp-next) ("M-P" . persp-prev) ("M-J" . persp-switch)) :init (setq persp-mode-prefix-key (kbd "C-x x")) :config ;; accidentally changing perspectives while in the minibuffer messes things up ;; using `ignore' (rather than nil) makes these keys do nothing (add-hook 'minibuffer-setup-hook '(lambda () (dolist (k '("M-N" "M-P" "M-J")) (local-set-key (kbd k) 'ignore)))) (advice-add 'persp-switch :after #'(lambda (n &optional r) (message (persp-name (persp-curr))))) ;; emacs window title (setq frame-title-format '("" invocation-name (:eval (when persp-mode (format "[%s]" (persp-name (persp-curr)))))))) (use-package persp-projectile :ensure t :after (perspective projectile))
project name override (for use with persp)
;; override chosen project names (defvar my/projectile-project-name-overrides '()) (defun my/projectile-add-to-project-name-overrides (proj name) (add-to-list 'my/projectile-project-name-overrides `(,(file-name-as-directory (expand-file-name proj)) . ,name))) (defun my/projectile-override-project-name (orig &rest args) (let* ((dir (file-name-as-directory (expand-file-name (car args)))) (match (assoc dir my/projectile-project-name-overrides)) (name (if (null match) nil (cdr match)))) (if (null name) (apply orig args) name))) (advice-add 'projectile-default-project-name :around #'my/projectile-override-project-name) ;; usage: ;; (dolist (override '( ;; ("/path/to/my/project" . "some-name") ;; ("/other/project" . "some-other-name") ;; )) ;; (let ((proj (car override)) ;; (name (cdr override))) ;; (my/projectile-add-to-project-name-overrides proj name)))
28. ivy/counsel/swiper
counsel-projectile
(use-package counsel-projectile :ensure t :after projectile :bind (:map projectile-command-map ("f" . counsel-projectile-find-file) ("s" . counsel-projectile-rg) ("b" . counsel-projectile-switch-to-buffer)) :init (setq projectile-switch-project-action 'counsel-projectile-find-file))
swiper
(defun my/swiper (fuzzy) (interactive "P") (if fuzzy (let* ((temp-builders (copy-alist ivy-re-builders-alist)) (ivy-re-builders-alist (add-to-list 'temp-builders '(swiper . ivy--regex-fuzzy)))) (swiper)) (swiper))) (defun my/swiper-fuzzy-or-all (all) (interactive "P") (if all (swiper-all) (my/swiper :fuzzy))) (defun my/swiper-isearch (fuzzy) (interactive "P") (if fuzzy (let* ((temp-builders (copy-alist ivy-re-builders-alist)) (ivy-re-builders-alist (add-to-list 'temp-builders '(swiper-isearch . ivy--regex-fuzzy)))) (swiper-isearch)) (swiper-isearch))) (use-package swiper :ensure t :bind (("C-c f" . my/swiper-fuzzy-or-all)) :commands (swiper swiper-isearch swiper-all swiper-multi))
flx, amx
;; better fuzzy matching (use-package flx :ensure t :after ivy) ;; mostly to bring recently used M-x targets at the top ;; (trying out instead of `smex`) (use-package amx :ensure t :after ivy :init (setq amx-backend 'auto amx-save-file (expand-file-name "amx-items" user-emacs-directory) amx-history-length 50 amx-show-key-bindings nil) :config (amx-mode +1))
ivy, counsel
(defun my/ivy-reset-builders () (setq ivy-re-builders-alist '((swiper . ivy--regex-plus) (swiper-isearch . ivy--regex-plus) (ivy-bibtex . ivy--regex-ignore-order) (counsel-unicode-char . ivy--regex-ignore-order) (insert-char . ivy--regex-ignore-order) (ucs-insert . ivy--regex-ignore-order) (counsel-unicode-char . ivy--regex-ignore-order) (counsel-ag . ivy--regex-ignore-order) ;; NOTE: testing (counsel-rg . ivy--regex-ignore-order) (t . ivy--regex-fuzzy)))) (defun my/counsel-rg-in () (interactive) (counsel-rg nil (read-directory-name "rg in: ") "")) (defun my/counsel-file-jump-temp-root (reset) (interactive "P") (my/get-or-set-temp-root reset) (let ((current-prefix-arg nil)) (counsel-file-jump nil my/temp-project-root))) (defun my/counsel-rg-temp-root (reset) (interactive "P") (my/get-or-set-temp-root reset) (let ((current-prefix-arg nil)) (counsel-rg "" my/temp-project-root))) (defun my/set-temp-root-and-jump (dir) (setq my/temp-project-root dir) (my/counsel-file-jump-temp-root nil)) (defun my/counsel-file-jump-from-here (path) (interactive) (let ((dir (if (file-directory-p path) path (file-name-directory path)))) (counsel-file-jump "" dir))) (defun my/ivy-insert-relative (fn) (let* ((trimmed-fn (ivy--trim-grep-line-number fn)) (curdir (file-name-directory (buffer-file-name))) (rel-fn (file-relative-name trimmed-fn curdir))) (insert rel-fn))) (use-package counsel :ensure t :after ivy :bind (("M-x" . counsel-M-x) ("M-i" . counsel-imenu) ("C-x C-f" . counsel-find-file) ("C-x f" . counsel-file-jump) ("C-x r b" . counsel-bookmark) ("C-x C-a" . counsel-recentf) ("C-c s" . my/counsel-rg-in) ("C-S-p" . my/counsel-file-jump-temp-root) ("C-S-s" . my/counsel-rg-temp-root) :map org-mode-map ("M-i" . counsel-outline) :map help-map ("f" . counsel-describe-function) ("o" . counsel-describe-symbol) ("u" . counsel-describe-face) ("v" . counsel-describe-variable)) :diminish :init (setq counsel-rg-base-command "rg --with-filename --no-heading --line-number --color never -S %s" counsel-ag-base-command "ag --vimgrep --nocolor --nogroup %s") :config (my/ivy-reset-builders) (setq ivy-initial-inputs-alist nil) ;; no ^ initially ;; counsel-ag ;; S-SPC doesn't work properly in counsel-ag anyway ;; NOTE: this also applies to rg (define-key counsel-ag-map (kbd "S-SPC") nil) (defvar my/ripgrep-config (expand-file-name "~/.config/ripgrep/ripgreprc")) (when (file-exists-p my/ripgrep-config) (setenv "RIPGREP_CONFIG_PATH" my/ripgrep-config)) (dolist (action '(counsel-find-file counsel-file-jump counsel-recentf)) (ivy-set-actions action `( ("I" my/ivy-insert-relative "insert relative") ("s" ,(my/control-function-window-split find-file-other-window 0 nil) "split horizontally") ("v" ,(my/control-function-window-split find-file-other-window nil 0) "split vertically") ("n" ,(my/execute-f-with-hook find-file ace-select-window) "select window") ("e" my/eshell "eshell") ("j" my/counsel-file-jump-from-here "jump") ("J" my/set-temp-root-and-jump "set temp root and jump") ("r" (lambda (dir) (counsel-rg nil dir)) "counsel-rg") ))) (dolist (action '(counsel-projectile-find-file projectile-recentf)) (ivy-set-actions action `(("s" ,(my/control-function-window-split counsel-projectile-find-file-action-other-window 0 nil) "split horizontally") ("v" ,(my/control-function-window-split counsel-projectile-find-file-action-other-window nil 0) "split vertically") ("n" ,(my/execute-f-with-hook counsel-projectile-find-file-action ace-select-window) "select window") ("R" (lambda (f) (projectile-recentf)) "recent files") ("e" (lambda (f) (my/eshell (projectile-expand-root f))) "eshell") ))) ;; also applies to counsel-projectile-ag (dolist (action '(counsel-ag counsel-rg)) (ivy-set-actions action '(("v" (lambda (x) (split-window-right) (windmove-right) (counsel-git-grep-action x)) "split vertically") ("s" (lambda (x) (split-window-below) (windmove-down) (counsel-git-grep-action x)) "split horizontally") ("n" (lambda (x) (ace-select-window) (counsel-git-grep-action x)) "select window") )))) (defun my/ivy-yank-current-region-or-word (&optional qual) "Insert current region, if it's active, otherwise the current word,into the minibuffer." (interactive "P") (let (text) (with-ivy-window (unwind-protect (setq text (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) (current-word t (not qual)))))) (when text (insert text)))) (use-package ivy :ensure t :diminish ivy-mode :hook (after-init . ivy-mode) :bind (("C-c r" . ivy-resume) ("C-x b" . ivy-switch-buffer) :map ivy-minibuffer-map ("M-j" . my/ivy-yank-current-region-or-word) ("M-r" . ivy-rotate-preferred-builders) ("C-l" . ivy-call-and-recenter) ("C-o" . ivy-minibuffer-grow) ("C-S-o" . ivy-minibuffer-shrink)) :init (setq ivy-use-virtual-buffers nil ivy-count-format "(%d/%d) " ivy-magic-tilde nil ivy-initial-inputs-alist nil) :config (my/ivy-reset-builders) ;; minibuffer actions for specific commands (ivy-set-actions 'ivy-switch-buffer `(("s" ,(my/control-function-window-split ivy--switch-buffer-other-window-action 0 nil) "split horizontally") ("v" ,(my/control-function-window-split ivy--switch-buffer-other-window-action nil 0) "split vertically") ("n" ,(my/execute-f-with-hook (lambda (b) (switch-to-buffer b nil 'force-same-window)) ace-select-window) "select window") ("k" kill-buffer "kill buffer") )) (ivy-set-actions 'projectile-switch-project '(("d" dired "Open Dired in project's directory") ("v" projectile-vc "Open project root in vc-dir or magit") ("r" projectile-remove-known-project "Remove project(s)")))) (use-package ivy-xref ;; currently in lisp/ because of patches :commands (ivy-xref-show-xrefs) :init (setq xref-show-xrefs-function 'ivy-xref-show-xrefs) :after ivy)
29. yasnippet
(use-package yasnippet-snippets :ensure t :after yasnippet :config (yas-reload-all)) (use-package yasnippet :ensure t :bind (("C-c y" . yas-expand) ("<C-return>" . yas-expand)) :diminish (yas-global-mode yas-minor-mode) :config ;; NOTE: the reason for not putting it in `after-init-hook' is to defer ;; loading until it `yas-expand' has been run the first time (yas-global-mode +1))
30. org-mode
org-babel
;; this is the same as doing org-babel-load-languages (use-package ob-python :commands (org-babel-execute:python)) (use-package ob-haskell :commands (org-babel-execute:haskell)) (use-package ob-sql :commands (org-babel-execute:sql)) (use-package ob-lilypond :commands (org-babel-execute:lilypond) :custom (org-babel-lilypond-commands '("lilypond -daux-files=#f" "xdg-open" "xdg-open")) :config (defun my/org-lilypond-preprocess-block (args) (let* ((defaults (string-join '("\\layout{" "#(layout-set-staff-size 25)" "}" "\\paper{" "indent=0\\mm" "line-width=200\\mm" "oddFooterMarkup=##f" "oddHeaderMarkup=##f" "bookTitleMarkup=##f" "scoreTitleMarkup=##f" "}" ) "\n")) (body (car args)) (newbody (format "%s\n%s" defaults body)) (params (cadr args))) (list newbody params))) (advice-add 'org-babel-execute:lilypond :filter-args 'my/org-lilypond-preprocess-block))
org-protocol
;; use this bookmark: ;; javascript:location.href='org-protocol://capture?template=r'+ ;; '&url='+encodeURIComponent(location.href)+ ;; '&title='+encodeURIComponent(document.title)+ ;; '&body='+encodeURIComponent(window.getSelection()) (require 'org-protocol) ;; Without this, quitting an org-protocol capture results in re-opening ;; the link with another mimeapp (firefox), and might result in an ;; infinite loop. This still deletes the client, but it does so cleanly. ;; TODO: think of a better way (advice-add 'server-return-error :override '(lambda (proc err) (message "exiting client") (server-delete-client proc)))
org configuration
(use-package org-indent :after org :hook (org-mode . org-indent-mode) :commands (org-indent-mode) :diminish) (use-package org-src :after org :bind (:map org-src-mode-map ("C-c C-c" . org-edit-src-exit)) :init (setq org-src-fontify-natively t org-src-tab-acts-natively t org-src-window-setup 'other-window org-src-preserve-indentation t)) (use-package ox :after org :init (setq org-export-with-toc nil org-export-with-section-numbers 1)) (use-package org :mode ("\\.org\\'" . org-mode) :bind (("C-c l" . org-store-link) ("C-c &" . org-mark-ring-goto)) :init (setq org-directory my/org-directory org-default-notes-file (expand-file-name "notes.org" org-directory) org-log-done 'time org-confirm-babel-evaluate nil org-clock-into-drawer t org-log-into-drawer t org-keep-stored-link-after-insertion t org-edit-src-content-indentation 0 org-src-window-setup 'other-window org-adapt-indentation nil org-ellipsis "…" org-tags-column -80 org-image-actual-width (list 500) org-startup-with-inline-images t org-blank-before-new-entry '((heading . nil) (plain-list-item . nil)) org-todo-keywords '((sequence "TODO(!)" "IN PROGRESS(!)" "|" "DONE(!)" "CANCELLED(!)")) org-todo-keyword-faces '(("IN PROGRESS" . (:foreground "DodgerBlue" :weight bold)) ("CANCELLED" . (:foreground "red3"))) org-fontify-done-headline nil org-treat-insert-todo-heading-as-state-change t org-link-frame-setup '((file . find-file)) org-habit-graph-column 50 org-habit-show-habits-only-for-today nil) ;; templates (setq org-structure-template-alist '(("s" . "src") ("e" . "example") ("q" . "quote") ("v" . "verse") ("ht" . "export html") ("la" . "export latex") ("b" . "src bash") ("sh" . "src shell") ("sr" . "src ruby") ("t" . "src terraform") ("y" . "src yaml") ("el" . "src emacs-lisp") ("h" . "src haskell") ("p" . "src python") ("py" . "src python") )) (setq org-tempo-keywords-alist '(("I" . "INDEX") ("L" . "LATEX") ("T" . "TITLE") ("H" . "HTML"))) ;; refile (setq org-refile-targets '((nil . (:maxlevel . 1))) org-refile-use-outline-path 'file org-outline-path-complete-in-steps nil) ;; format string used when creating CLOCKSUM lines and when generating a ;; time duration (avoid showing days) (setq org-time-clocksum-format '(:hours "%d" :require-hours t :minutes ":%02d" :require-minutes t)) :config (require 'org-tempo nil :noerror) (require 'org-ref nil :noerror) (my/define-major-mode-key 'org-mode "c" 'org-cliplink) (my/define-major-mode-key 'org-mode "o" 'my/writeroom) (my/define-major-mode-key 'org-mode "it" 'my/org-insert-date-today) (my/define-major-mode-key 'org-mode "ti" 'org-toggle-inline-images) (my/define-major-mode-key 'org-mode "tl" 'org-toggle-link-display) (my/define-major-mode-key 'org-mode "tm" 'my/org-toggle-markup) (add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append) (my/add-hooks '(org-mode-hook) (define-key org-mode-map (kbd "TAB") 'org-cycle) (define-key org-mode-map (kbd "<tab>") 'org-cycle) (evil-define-key 'normal org-mode-map (kbd "TAB") 'org-cycle) (evil-define-key 'normal org-mode-map (kbd "<tab>") 'org-cycle) (whitespace-mode +1) (smartparens-mode) (setq fill-column 100) ) (with-eval-after-load 'smartparens (sp-local-pair 'org-mode "=" "=") (sp-local-pair 'org-mode "'" "'" :actions '(rem)) (sp-local-pair 'org-mode "\"" "\"" :actions '(rem))) ;; kill any "unsaved" fontification buffer that might cause a save prompt when quitting emacs (advice-add 'save-buffers-kill-terminal :before #'my/org-kill-fontification-buffers)) (defun my/org-insert-date-today (active) (interactive "P") (org-insert-time-stamp (current-time) nil (not active))) (defun my/org-toggle-markup () (interactive) (setq org-hide-emphasis-markers (not org-hide-emphasis-markers)) (font-lock-fontify-buffer :interactively)) (defun my/org-export-conf () (interactive) (let ((org-export-with-toc t) (org-export-with-section-numbers 1) (org-html-htmlize-output-type 'css) (org-html-head-extra (string-join '("<link href=\"/htmlize.css\" rel=\"stylesheet\">" "<link href=\"/readtheorg.css\" rel=\"stylesheet\">" ) "\n"))) (with-temp-buffer (insert-file (expand-file-name "configuration.org" user-emacs-directory)) (org-export-to-file 'html (expand-file-name "docs/index.html" user-emacs-directory))))) (defun my/org-kill-fontification-buffers (&optional silent) (mapc #'kill-buffer (seq-filter (lambda (buf) (string-match "^\\ \\*org-src-fontification:.*\\*$" (buffer-name buf))) (buffer-list))))
various extensions
(use-package org-bullets :ensure t :if is-gui :after org :hook (org-mode . org-bullets-mode) :init (setq org-bullets-bullet-list (append '("◉") (make-list 7 "○")) org-hide-leading-stars t)) (use-package htmlize :ensure t :defer t :init (setq org-html-htmlize-output-type 'inline-css))
31. modeline
doom-modeline
(use-package doom-modeline :ensure t :if is-gui :hook (after-init . doom-modeline-mode) :init (setq doom-modeline-height 21 doom-modeline-hud t doom-modeline-bar-width 8) (defun my/doom-modeline--font-height () "Calculate the actual char height of the mode-line." (+ (frame-char-height) 2)) (advice-add #'doom-modeline--font-height :override #'my/doom-modeline--font-height) :config (add-hook 'my/after-set-font-hook 'doom-modeline-refresh-font-width-cache))
32. Setup
macos specific
(when is-mac (setq mac-command-modifier 'meta) (global-set-key [?\A-\C-i] nil))
Per-workstation setup
(defvar my/after-init-hook nil "Hook called after initialization") ;; add extra keys (defvar my/extra-key-mappings nil "Extra key mappings") (defun my/add-key (key func) (define-key my/leader-map (kbd key) func) (evil-leader/set-key key func))
;; these have to be paths to projects that projectile recognizes (e.g. git) (defvar my/start-up-projects '()) (defun my/open-start-up-projects () (unless (null my/start-up-projects) (let ((projectile-switch-project-action 'projectile-dired)) (dolist (proj my/start-up-projects) (projectile-persp-switch-project proj))) (persp-switch "main"))) (add-hook 'my/after-init-hook 'my/open-start-up-projects) ;; usage: ;; (add-to-list 'my/start-up-projects "/path/to/some/project")
;; https://nicolas.petton.fr/blog/per-computer-emacs-settings.html (defvar my/hosts-dir (expand-file-name (expand-file-name "hosts/" user-emacs-directory))) (defvar my/hostname (substring (shell-command-to-string "hostname") 0 -1)) (let* ((host-file (concat my/hosts-dir "init-" my/hostname ".el"))) (load-file host-file))
(dolist (mapping my/extra-key-mappings) (let ((key (car mapping)) (func (cdr mapping))) (my/add-key key func)))
Performance
;; stolen from doom (defun my/defer-garbage-collection-h () (setq gc-cons-threshold 100000000)) (defun my/restore-garbage-collection-h () ;; Defer it so that commands launched immediately after will enjoy the ;; benefits. (run-at-time 1 nil (lambda () (setq gc-cons-threshold 800000)))) (add-hook 'minibuffer-setup-hook #'my/defer-garbage-collection-h) (add-hook 'minibuffer-exit-hook #'my/restore-garbage-collection-h)
Global setup
;; make underlines show under mode-line (cleaner) (setq x-underline-at-descent-line t) (my/set-theme) (my/set-font) (setq linum-format 'dynamic) (setq default-input-method "greek") (winner-mode) (run-hooks 'my/after-init-hook) (use-package server :commands (server-mode server-running-p) :hook (after-init . my/maybe-server-mode) :init (defun my/maybe-server-mode () (unless (server-running-p) (server-mode +1))))
Custom
(dolist (val '((eval . (setq flycheck-disabled-checkers (append flycheck-disabled-checkers (quote (intero))))) (haskell-hoogle-command . "stack hoogle -- --count=100") (projectile-tags-command . "npm run etags") (projectile-tags-command . "fast-tags -e -R .") (projectile-tags-command . "fast-tags -e -R -o %s --exclude=\"%s\" \"%s\"") (psc-ide-output-directory . "build/") (my/use-intero . t) (my/haskell-align-stuff . nil) (my/haskell-use-ormolu . t) (my/purescript-align-stuff . nil) (org-download-image-dir . "~/Dropbox/emacs/org/static/images/") (my/ruby-do-insert-frozen-string-literal . nil) (flycheck-markdown-mdl-rules . nil) (flycheck-markdown-mdl-style . nil) (eval progn (visual-line-mode 1) (visual-fill-column-mode 1)) )) (add-to-list 'safe-local-variable-values val)) (dolist (val '(bibtex-completion-bibliography)) (put val 'safe-local-variable (lambda (_) t)))