Skip to content

Latest commit

 

History

History
1384 lines (1247 loc) · 45.2 KB

config.org

File metadata and controls

1384 lines (1247 loc) · 45.2 KB

Packages

Configure archives

  1. Milkypostman’s Emacs Lisp Package Archive
  2. Official GNU archives
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("gnu" . "http://elpa.gnu.org/packages/")))

Ensure ‘use-package’ is available

(when (not (package-installed-p 'use-package))
  (package-refresh-contents)
  (package-install 'use-package))

(require 'use-package)

System

Give Emacs access to the system $PATH on MacOS.

(use-package exec-path-from-shell
  :ensure t)
(when (eq system-type 'darwin)
  (exec-path-from-shell-initialize))

Reveal in MacOS Finder

(use-package reveal-in-osx-finder
  :ensure t)

Look

Fonts

A ‘set-face’ function

This function and the following font configuration is shamelessly stolen from the excellent Nano configuration, see https://github.com/rougier/nano-emacs.

(defun set-face (face style)
  "Reset a face and make it inherit style."
  (set-face-attribute face nil
   :foreground 'unspecified :background 'unspecified
   :family     'unspecified :slant      'unspecified
   :weight     'unspecified :height     'unspecified
   :underline  'unspecified :overline   'unspecified
   :box        'unspecified :inherit    style))

Font presets

Some notes:

  1. When changing these fonts it seems like you need to restart Emacs for them to take effect.
  2. Salient used to be #000055.
(defface face-critical      '((t :foreground "#ffffff"
                                 :background "#ff6347"))   "Critical")
(defface face-popout        '((t :foreground "#00bba7"))     "Popout")
(defface face-strong        '((t :weight regular))           "Strong")
(defface face-actually-bold '((t :weight bold))     "Actually strong")
(defface face-salient       '((t :foreground "#221199"))    "Salient")
(defface face-faded         '((t :foreground "#999999"))      "Faded")
(defface face-subtle        '((t :background "#f0f0f0"))     "Subtle")

Main font

(set-face-font 'default "Fira Code 16")
(setq-default line-spacing 5)

(if (eq system-type 'windows-nt)
  (set-face-font 'default "Fira Code 11"))

(if (eq system-type 'gnu/linux)
    (set-face-font 'default "Fira Code 12"))

Programming

(set-face 'font-lock-comment-face                         'face-faded)
(set-face 'font-lock-doc-face                             'face-faded)
(set-face 'font-lock-string-face                         'face-popout)
(set-face 'font-lock-constant-face                      'face-salient)
(set-face 'font-lock-warning-face                        'face-popout)
(set-face 'font-lock-function-name-face                  'face-strong)
(set-face 'font-lock-variable-name-face                  'face-strong)
(set-face 'font-lock-builtin-face                       'face-salient)
(set-face 'font-lock-type-face                          'face-salient)
(set-face 'font-lock-keyword-face                       'face-salient)

Non-programming

(set-face 'header-line-highlight                          'face-faded)
(set-face 'region                                        'face-subtle)
(set-face 'highlight                                     'face-subtle)
(set-face 'org-link                                      'face-popout)
(set-face 'org-verbatim                                 'face-salient)
(set-face 'org-headline-done                              'face-faded)
(set-face 'bold                                   'face-actually-bold)
(set-face 'italic                                         'face-faded)
(set-face 'cursor                                        'face-strong)
(set-face-attribute 'cursor nil
                           :background (face-foreground 'face-strong))
(set-face 'minibuffer-prompt                             'face-strong)
(set-face 'link                                         'face-salient)
(set-face 'fringe                                         'face-faded)
(set-face 'isearch                                       'face-strong)
(set-face 'lazy-highlight                                'face-subtle)
(set-face 'show-paren-match                              'face-popout)
(set-face 'show-paren-mismatch                           'face-normal)
(set-face 'shadow                                         'face-faded) ;; Used for line numbers
(set-face 'warning                                       'face-popout)
(set-face 'error                                       'face-critical)
(set-face 'outline-1                                     'face-strong)
(set-face 'outline-2                                     'face-strong)
(set-face 'outline-3                                     'face-strong)
(set-face 'outline-4                                     'face-strong)
(set-face 'outline-5                                     'face-strong)
(set-face 'outline-6                                     'face-strong)

Documentation

(set-face 'info-menu-header                              'face-strong)
(set-face 'info-header-node                              'face-normal)
(set-face 'Info-quoted                                    'face-faded)
(set-face 'info-title-1                                  'face-strong)
(set-face 'info-title-2                                  'face-strong)
(set-face 'info-title-3                                  'face-strong)
(set-face 'info-title-4                                  'face-strong)

Ligatures

It should be possible to remove this, since Emacs is supporting ligatures natively now.

(defun enable-ligatures ()
  (interactive)
  (let ((alist '((33 . ".\\(?:\\(?:==\\|!!\\)\\|[!=]\\)")
                 (35 . ".\\(?:###\\|##\\|_(\\|[#(?[_{]\\)")
                 (36 . ".\\(?:>\\)")
                 (37 . ".\\(?:\\(?:%%\\)\\|%\\)")
                 (38 . ".\\(?:\\(?:&&\\)\\|&\\)")
                 ;;(42 . ".\\(?:\\(?:\\*\\*/\\)\\|\\(?:\\*[*/]\\)\\|[*/>]\\)") ;; This messes up triple stars in Org mode (***)
                 (43 . ".\\(?:\\(?:\\+\\+\\)\\|[+>]\\)")
                 (45 . ".\\(?:\\(?:-[>-]\\|<<\\|>>\\)\\|[<>}~-]\\)")
                 (46 . ".\\(?:\\(?:\\.[.<]\\)\\|[.=-]\\)")
                 (47 . ".\\(?:\\(?:\\*\\*\\|//\\|==\\)\\|[*/=>]\\)")
                 (48 . ".\\(?:x[a-zA-Z]\\)")
                 (58 . ".\\(?:::\\|[:=]\\)")
                 (59 . ".\\(?:;;\\|;\\)")
                 (60 . ".\\(?:\\(?:!--\\)\\|\\(?:~~\\|->\\|\\$>\\|\\*>\\|\\+>\\|--\\|<[<=-]\\|=[<=>]\\||>\\)\\|[*$+~/<=>|-]\\)")
                 (61 . ".\\(?:\\(?:/=\\|:=\\|<<\\|=[=>]\\|>>\\)\\|[<=>~]\\)")
                 (62 . ".\\(?:\\(?:=>\\|>[=>-]\\)\\|[=>-]\\)")
                 (63 . ".\\(?:\\(\\?\\?\\)\\|[:=?]\\)")
                 (91 . ".\\(?:]\\)")
                 (92 . ".\\(?:\\(?:\\\\\\\\\\)\\|\\\\\\)")
                 (94 . ".\\(?:=\\)")
                 (119 . ".\\(?:ww\\)")
                 (123 . ".\\(?:-\\)")
                 (124 . ".\\(?:\\(?:|[=|]\\)\\|[=>|]\\)")
                 (126 . ".\\(?:~>\\|~~\\|[>=@~-]\\)"))))
    (dolist (char-regexp alist)
      (set-char-table-range composition-function-table (car char-regexp)
                            `([,(cdr char-regexp) 0 font-shape-gstring])))))

(defun disable-ligatures ()
  (interactive)
  (let ((alist '((33 . "")
                 (35 . "")
                 (36 . "")
                 (37 . "")
                 (38 . "")
                 (43 . "")
                 (45 . "")
                 (46 . "")
                 (47 . "")
                 (48 . "")
                 (58 . "")
                 (59 . "")
                 (60 . "")
                 (61 . "")
                 (62 . "")
                 (63 . "")
                 (91 . "")
                 (92 . "")
                 (94 . "")
                 (119 . "")
                 (123 . "")
                 (124 . "")
                 (126 . ""))))
    (dolist (char-regexp alist)
      (set-char-table-range composition-function-table (car char-regexp)
                            `([,(cdr char-regexp) 0 font-shape-gstring])))))

(enable-ligatures)

Frame

(setq frame-resize-pixelwise t)
(set-frame-parameter (selected-frame) 'internal-border-width 24)
(fringe-mode '(0 . 0))
;;(add-to-list 'default-frame-alist '(fullscreen . maximized))
(if (eq system-type 'darwin)
    (add-to-list 'default-frame-alist '(undecorated . t)))
(setq frame-background-mode 'light)
(set-background-color "#ffffff")
(set-foreground-color "#000000")

Mode Line

See https://github.com/rougier/nano-emacs.

(defun mode-line-render (left right)
  "Return a string of `window-width' length containing left, and
   right aligned respectively."
  (let* ((available-width (- (window-total-width) (length left) )))
    (format (format "%%s %%%ds" available-width) left right)))

(setq-default header-line-format
  '(:eval (mode-line-render

   (format-mode-line
    (list
     (propertize "" 'face `(:weight regular))
     (propertize "%b " 'face `(:weight regular))
     '(:eval (if (and buffer-file-name (buffer-modified-p))
         (propertize "(modified)"
             'face `(:weight light
                 :foreground "#aaaaaa"))))))

   (format-mode-line
    (propertize "%3l:%2c "
    'face `(:weight light :foreground "#aaaaaa"))))))

(setq-default mode-line-format "") ;; The "normal" mode line (at the bottom)

(set-face-attribute 'mode-line nil
                    :height 10
                    :underline "black"
                    :background "white"
                    :foreground "white"
                    :box nil)

(set-face-attribute 'mode-line-inactive nil
                    :box nil
                    :inherit 'mode-line)

(set-face-attribute 'mode-line-buffer-id nil
                    :weight 'light)

(set-face-attribute 'header-line nil
                    :height (if (or (eq system-type 'windows-nt)
                                    (eq system-type 'gnu/linux))
                                160
                              180)
                    :underline t
                    :underline "black"
                    :foreground "black"
                    :background "white"
                    :box `(:line-width 12 :color "white" :style nil))

(set-face-attribute 'mode-line nil
                    :height 10
                    :underline "black"
                    :background "white"
                    :foreground "white"
                    :box nil)

(set-face 'mode-line-inactive  'mode-line)
(set-face 'mode-line-buffer-id  'default)

(defun mode-line-render (left right)
  "Return a string of `window-width' length containing left, and
   right aligned respectively."
  (let* ((available-width (- (window-total-width) (length left) )))
    (format (format "%%s %%%ds" available-width) left right)))
(define-key mode-line-major-mode-keymap [header-line]
  (lookup-key mode-line-major-mode-keymap [mode-line]))

(setq-default mode-line-format '(""))

(defun vc-branch ()
  (if vc-mode
      (let ((backend (vc-backend buffer-file-name)))
        (concat "#" (substring-no-properties vc-mode
                                 (+ (if (eq backend 'Hg) 2 3) 2))))
      ""))

(setq-default header-line-format
  '(:eval (mode-line-render
   (format-mode-line
    (list
     (propertize ""
                 'face `(:weight regular)
                 'mouse-face 'header-line-highlight
                 'help-echo  "Major mode menu"
                 'local-map   mode-line-major-mode-keymap)
     " %b "
     '(:eval (propertize (vc-branch) 'face `(:foreground ,(face-foreground 'face-popout))))
     " "
     '(:eval (if (and buffer-file-name (buffer-modified-p))
                 (propertize "(modified)"
              'face `(:foreground ,(face-foreground 'face-faded)))))
     ))
   (format-mode-line
    (propertize "%3l:%2c              "
    'face `(:foreground ,(face-foreground 'face-faded)))))))

Cursor

(setq cursor-type 'bar)
(set-default 'cursor-type 'bar)

Line numbers

;;(global-display-line-numbers-mode)
(setq linum-format (quote "%4d  "))

Parenthesis

(show-paren-mode 1)

Tabs

Always use four spaces.

(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)

Secondary selection

This is used by org-mode when editing inline code blocks (C-c ‘).

(set-face-foreground 'secondary-selection "#999")
(set-face-background 'secondary-selection "#f0f0f0")

What face?

A function for finding out info about font at cursor.

(defun what-face (pos)
  (interactive "d")
  (let ((face (or (get-char-property (point) 'read-face-name)
                  (get-char-property (point) 'face))))
    (if face (message "Face: %s" face) (message "No face at %d" pos))))

Rainbow mode

Render hex colors with actual color in the buffer.

(use-package rainbow-mode
  :ensure t)

Disable GUI

(menu-bar-mode 0)
(tool-bar-mode 0)
(tooltip-mode  0)
(scroll-bar-mode 0)

Inhibit startup cruft

(setq inhibit-splash-screen t)
(setq inhibit-startup-screen t)
(setq inhibit-startup-echo-area-message t)
(setq inhibit-startup-message t)
(setq initial-scratch-message nil)

Feel

Basic text editing conveniences

(global-set-key (kbd "RET") 'newline-and-indent)
(delete-selection-mode 1)

Activate CUA-mode on Windows

(if (eq system-type 'windows-nt)
    (cua-mode 1))

Use UTF-8 everywhere

(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(prefer-coding-system 'utf-8)

Single character ‘yes or no’ prompt

(defalias 'yes-or-no-p 'y-or-n-p)

Move focus when splitting windows

Make new windows get focus to make it easy to interact with them.

(defadvice split-window (after move-point-to-new-window activate)
  "Moves the point to the newly created window after splitting."
  (other-window 1))

;; Switch to new window when using help
(defadvice describe-key (after move-point-to-new-window activate)
  (other-window 1))

(defadvice describe-function (after move-point-to-new-window activate)
  (other-window 1))

(defadvice describe-variable (after move-point-to-new-window activate)
  (other-window 1))

(defadvice apropos-command (after move-point-to-new-window activate)
  (other-window 1))

(defadvice describe-bindings (after move-point-to-new-window activate)
  (other-window 1))

(defadvice describe-mode (after move-point-to-new-window activate)
  (other-window 1))

(defadvice find-commands-by-name (after move-point-to-new-window activate)
  (other-window 1))

(defadvice completion-list-mode (after move-point-to-new-window activate)
  (other-window 1))

Keyboard shortcuts for changing window size

(global-set-key (kbd "s-+") 'enlarge-window)
(global-set-key (kbd "s--") 'shrink-window)
(global-set-key (kbd "M-+") 'enlarge-window-horizontally)
(global-set-key (kbd "M--") 'shrink-window-horizontally)

Kill the buffer without a prompt

(global-set-key (kbd "C-x k") 'kill-current-buffer)

Navigation of error buffers

(global-set-key (kbd "M-n") 'next-error)
(global-set-key (kbd "M-p") 'previous-error)

Create new files more easily

Taken from http://xahlee.info/emacs/emacs/emacs_new_empty_buffer.html

(defun new-empty-buffer ()
  "Create a new empty buffer.
New buffer will be named “untitled” or “untitled<2>”, “untitled<3>”, etc.

It returns the buffer (for elisp programing)."
  (interactive)
  (let (($buf (generate-new-buffer "untitled")))
    (switch-to-buffer $buf)
    (funcall initial-major-mode)
    (setq buffer-offer-save t)
    $buf))

Go to symbol

(use-package imenu
  :ensure t)

Go to visible word

(use-package avy
  :ensure t)

(require 'avy)

Kill whitespace

(to the left and right of the cursor)

(defun kill-whitespace ()
  "Kill the whitespace between two non-whitespace characters"
  (interactive "*")
  (save-excursion
    (save-restriction
      (save-match-data
        (progn
          (re-search-backward "[^ \t\r\n]" nil t)
          (re-search-forward "[ \t\r\n]+" nil t)
          (replace-match "" nil nil))))))

(global-set-key [s-backspace] 'kill-whitespace)

Remove whitespace on save

(add-hook 'before-save-hook 'whitespace-cleanup)

Save without removing whitespace

(defun save-buffer-no-whitespace-cleanup ()
  (interactive)
  (let ((normally-should-whitespace-cleanup (memq 'whitespace-cleanup before-save-hook)))
    (when normally-should-whitespace-cleanup
      (remove-hook 'before-save-hook 'whitespace-cleanup))
    (save-buffer)
    (when normally-should-whitespace-cleanup
      (add-hook 'before-save-hook 'whitespace-cleanup))))

Insert line above

(defun smart-open-line-above ()
  "Insert an empty line above the current line."
  (interactive)
  (move-beginning-of-line nil)
  (newline-and-indent)
  (forward-line -1)
  (indent-according-to-mode))

(global-set-key (kbd "<C-return>") 'smart-open-line-above)

Expand region

Grow a text selection in a smart way based on common programming language delimiters.

(use-package expand-region
  :ensure t)

(global-set-key (kbd "s-e") 'er/expand-region)

Insert single characters

These characters can’t be written using the normal MacOS shortcuts (on my keyboard) without this fix.

(global-set-key (kbd "M-2") "@")
(global-set-key (kbd "M-4") "$")
(global-set-key (kbd "M-8") "[")
(global-set-key (kbd "M-9") "]")
(global-set-key (kbd "M-(") "{")
(global-set-key (kbd "M-)") "}")
(global-set-key (kbd "M-7") "|")
(global-set-key (kbd "M-/") "\\")

More special characters.

(global-set-key (kbd "C-x M-a") "") ; and
(global-set-key (kbd "C-x M-b") "") ; bottom
(global-set-key (kbd "C-x M-c") "") ; composition
(global-set-key (kbd "C-x M-d") "") ; not subset
(global-set-key (kbd "C-x M-e") "") ; element
(global-set-key (kbd "C-x M-f") "") ; for all
(global-set-key (kbd "C-x M-g") "") ; there doesn't exist
;; h
(global-set-key (kbd "C-x M-i") "") ; infinity
(global-set-key (kbd "C-x M-j") "") ; implication
(global-set-key (kbd "C-x M-k") "") ; double arrow
(global-set-key (kbd "C-x M-l") "λ") ; lambda
;; m
(global-set-key (kbd "C-x M-n") "¬") ; negation
(global-set-key (kbd "C-x M-o") "") ; or
(global-set-key (kbd "C-x M-p") "π") ; pi
(global-set-key (kbd "C-x M-P") "Π") ; capital pi
(global-set-key (kbd "C-x M-q") "") ; empty set
(global-set-key (kbd "C-x M-r") "") ; provable
(global-set-key (kbd "C-x M-s") "") ; subset
(global-set-key (kbd "C-x M-S") "Σ") ; sigma
(global-set-key (kbd "C-x M-t") "") ; true
(global-set-key (kbd "C-x M-u") "") ; union
(global-set-key (kbd "C-x M-v") "") ; intersection
(global-set-key (kbd "C-x M-w") "") ; not element
(global-set-key (kbd "C-x M-x") "") ; there exists
;; y
(global-set-key (kbd "C-x M-z") "") ; implies

Smartparens

(use-package smartparens
  :ensure t

  :config
  ;; Disable automatic pairing for these characters:
  (sp-pair "'" nil :actions :rem)
  (sp-pair "\"" nil :actions :rem)
  (sp-pair "\\\"" nil :actions :rem)

  :bind
  (("C-)" . sp-forward-slurp-sexp)
   ("C-(" . sp-backward-slurp-sexp)

   ("C-M-)" . sp-forward-barf-sexp)
   ("C-M-(" . sp-backward-barf-sexp)

   ("C-M-k" . sp-kill-sexp)
   ("C-M-w" . sp-copy-sexp)
   ("C-M-<backspace>" . sp-unwrap-sexp)

   ("C-M-t" . sp-transpose-sexp)
   ("C-M-j" . sp-join-sexp)
   ("C-M-s" . sp-split-sexp)

   ;; Move out and to the right: ( | ) => ( ) |
   ("C-M-i" . sp-up-sexp)

   ;; Move out and to the left: ( | ) => | ( )
   ("C-M-u" . sp-backward-up-sexp)

   ;; Move down right: | ( ) => ( | )
   ("C-M-d" . sp-down-sexp)

   ;; Move down left: ( ) | => ( | )
   ("C-M-c" . sp-backward-down-sexp)

   ;; Move right: ( a | b c ) => ( a b | c )
   ("C-M-f" . sp-forward-sexp)

   ;; Move left: ( a b | c ) => ( a | b c )
   ("C-M-b" . sp-backward-sexp)

   ;; Move left to outmost paren ( ( | ) ) => | ( ( ) )
   ("C-M-a" . beginning-of-defun)

   ;; Move right to outmost paren ( ( | ) ) => ( ( ) ) |
   ("C-M-e" . end-of-defun))
  )

Minibuffer completion

Of course we want to be able to start minibuffers from within minibuffers, right?

(setq enable-recursive-minibuffers t)

Oh the pains of choosing a minibuffer completion framework. I’m putting things into functions so that it’s easy to try different ones.

Ivy User Manual

(defun setup-ivy ()
  (interactive)

  (use-package ivy
    :ensure t
    :init (ivy-mode)
    :config
    (setq ivy-display-style 'fancy)
    (setq ivy-count-format "(%d/%d) ")
    ;; Virtual buffers make recent buffers appear on top of the list
    (setq ivy-use-virtual-buffers t))

  (use-package counsel
    :ensure t
    :after ivy
    :config
    (counsel-mode))

  ;; This one is for buffer and command (M-x) history
  (use-package ivy-prescient
    :ensure t
    :after counsel
    :init
    (ivy-prescient-mode)
    (prescient-persist-mode) ;; save history between sessions
    )

  (global-set-key (kbd "M-x") 'counsel-M-x))

(setup-ivy)

The ibuffer

(global-set-key (kbd "C-x C-b") 'ibuffer)

(setq ibuffer-formats
      '((mark modified read-only " "
              (name 30 30 :left :elide) ; change: 30s were originally 18s
              " "
              (size 9 -1 :right)
              " "
              (mode 16 16 :left :elide)
              " " filename-and-process)
        (mark " "
              (name 16 -1)
              " " filename)))

(setq ibuffer-saved-filter-groups
      '(("home"
         ("Magit" (or (name . "magit:")
                      (name . "magit-diff:")
                      (name . "magit-process:")
                      (name . "magit-revision:")
                      (name . "magit-log:")))
         ("Dired" (mode . dired-mode))
         ("Emacs" (or (mode . help-mode)
                      (name . "\*"))))))

(add-hook 'ibuffer-mode-hook
      '(lambda ()
         (ibuffer-switch-to-saved-filter-groups "home")))

(setq ibuffer-show-empty-filter-groups nil)

;; Refresh automatically
(add-hook 'ibuffer-mode-hook (lambda () (ibuffer-auto-mode 1)))

Projects

(use-package find-file-in-project
  :ensure t
  :config
  (setq ffip-patterns
        '("*.html" "*.org" "*.txt" "*.md" "*.el" "*.idr"
          "*.clj" "*.cljs" "*.py" "*.rb" "*.js" "*.pl" "*.go"
          "*.sh" "*.erl" "*.hs" "*.ml" "*.css" "*.elm" "*.carp"
          "*.h" "*.c" "*.cpp" "*.cs" "*.m" "*.rs" "*.glsl"))
  (setq ffip-prune-patterns
        '("*/Packages/*"
          "*/Temp/*"
          "*/Library/*"
          "*/PackageCache/*")))

(require 'find-file-in-project)
(global-set-key (kbd "s-p") 'find-file-in-project)
(global-set-key (kbd "C-x p") 'find-file-in-project) ;; overrides set-fill-column

Undo

(use-package undo-tree
  :config
  (setq undo-tree-auto-save-history nil) ;; Don't litter my folders
  :ensure t)

(global-undo-tree-mode 1)

Auto completion

(use-package company
  :ensure t
  :bind
  (("M-ESC" . company-complete))
  :config
  (setq company-tooltip-align-annotations t)
  (setq company-minimum-prefix-length 1)
  (setq company-idle-delay 0.2)
  (setq company-dabbrev-downcase nil) ;; Don't lowercase things!
  )

(use-package company-fuzzy
  :ensure t)

(add-hook 'after-init-hook 'global-company-mode)

iEdit

Simultaneously edit all matching symbols in the buffer. Be careful with this one!

(use-package iedit
  :ensure t)

;;(global-set-key (kbd "C-;") 'iedit-mode)

Commenting

(global-set-key (kbd "s-/") 'comment-or-uncomment-region)
(global-set-key (kbd "C-'") 'comment-or-uncomment-region)

Scrolling

Nudging the buffer up or down

(defun my-scroll-down ()
  (interactive)
  (scroll-up 1))

(defun my-scroll-up ()
  (interactive)
  (scroll-down 1))

(global-set-key (kbd "M-s-p") 'my-scroll-down)
(global-set-key (kbd "M-s-n") 'my-scroll-up)

(global-set-key [M-s-up] 'my-scroll-down)
(global-set-key [M-s-down] 'my-scroll-up)

Buffer switching

(use-package ace-window
  :ensure t)

(global-set-key (kbd "M-o") 'ace-window)

Navigation to beginning and end of line

(defun smart-beginning-of-line ()
  "Move point to first non-whitespace character or beginning-of-line.
   Move point to the first non-whitespace character on this line.
   If point was already at that position, move point to beginning of line."
  (interactive "^") ; Use (interactive "^") in Emacs 23 to make shift-select work
  (let ((oldpos (point)))
    (back-to-indentation)
    (and (= oldpos (point))
         (beginning-of-line))))

(global-set-key [s-left] 'smart-beginning-of-line)
(global-set-key [home] 'smart-beginning-of-line)
(global-set-key (kbd "C-a") 'smart-beginning-of-line)

(global-set-key [s-right] 'end-of-line)
(define-key global-map [end] 'end-of-line)
(global-set-key (kbd "C-e") 'end-of-line)

Navigation to beginning and end of buffer

(global-set-key [s-up] 'beginning-of-buffer)
(global-set-key [s-down] 'end-of-buffer)

Move lines

(defun move-lines (n)
  (let ((beg) (end) (keep))
    (if mark-active
        (save-excursion
          (setq keep t)
          (setq beg (region-beginning)
                end (region-end))
          (goto-char beg)
          (setq beg (line-beginning-position))
          (goto-char end)
          (setq end (line-beginning-position 2)))
      (setq beg (line-beginning-position)
            end (line-beginning-position 2)))
    (let ((offset (if (and (mark t)
                           (and (>= (mark t) beg)
                                (< (mark t) end)))
                      (- (point) (mark t))))
          (rewind (- end (point))))
      (goto-char (if (< n 0) beg end))
      (forward-line n)
      (insert (delete-and-extract-region beg end))
      (backward-char rewind)
      (if offset (set-mark (- (point) offset))))
    (if keep
        (setq mark-active t
              deactivate-mark nil))))

(defun move-lines-up (n)
  "move the line(s) spanned by the active region up by N lines."
  (interactive "*p")
  (move-lines (- (or n 1))))

(defun move-lines-down (n)
  "move the line(s) spanned by the active region down by N lines."
  (interactive "*p")
  (move-lines (or n 1)))

(global-set-key (kbd "C-s-<down>") 'move-lines-down)
(global-set-key (kbd "C-s-<up>") 'move-lines-up)

;; Alternative, since the shortcuts above clash with Rectangle.app
(global-set-key (kbd "C-s-n") 'move-lines-down)
(global-set-key (kbd "C-s-p") 'move-lines-up)

;; Gnome-compatible alternative
(global-set-key (kbd "C-M-s-n") 'move-lines-down)
(global-set-key (kbd "C-M-s-p") 'move-lines-up)

Duplicate line

(defun duplicate-line ()
  (interactive)
  (beginning-of-line)
  (kill-line)
  (yank)
  (newline)
  (yank))

(global-set-key (kbd "C-c d") 'duplicate-line)

Multiple cursors

If you want to insert a newline in multiple-cursors-mode, use C-j.

(use-package multiple-cursors
  :ensure t)

(global-set-key (kbd "<s-mouse-1>") 'mc/add-cursor-on-click)
(global-set-key (kbd "s-d") 'mc/mark-next-like-this)
;;(global-set-key (kbd "???") 'mc/mark-next-like-this)
(global-set-key (kbd "s-l") 'mc/edit-lines)
;;(global-set-key (kbd "???") 'mc/edit-lines)

Merge conflict resolution

(global-set-key (kbd "C-c n") 'smerge-next)
(global-set-key (kbd "C-c p") 'smerge-prev)
(global-set-key (kbd "C-c u") 'smerge-keep-upper)
(global-set-key (kbd "C-c l") 'smerge-keep-lower)

Rename file and buffer

(defun rename-file-and-buffer ()
  "Rename the current buffer and file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (if (not (and filename (file-exists-p filename)))
        (message "Buffer is not visiting a file!")
      (let ((new-name (read-file-name "New name: " filename)))
        (cond
         ((vc-backend filename) (vc-rename-file filename new-name))
         (t
          (rename-file filename new-name t)
          (set-visited-file-name new-name t t)))))))

Flycheck

(use-package flycheck
  :ensure t)

(use-package flymake-easy
  :ensure t)

(use-package flymake-cursor
  :ensure t)

(use-package flymake-hlint
  :ensure t)

(use-package flycheck-rust
  :ensure t)

Free Keys

(use-package free-keys
  :ensure t)

Close frame if not the last, otherwise kill Emacs

(defun delete-frame-or-kill-emacs ()
  "Delete frame or kill Emacs if there is only one frame."
  (interactive)
  (if (> (length (frame-list)) 1)
      (delete-frame)
    (save-buffers-kill-terminal)))

(global-set-key (kbd "C-x C-c") 'delete-frame-or-kill-emacs)

MacOS-style ‘super’ (⌘) key shortcuts

⌘-Q, “quit”

(global-set-key (kbd "s-q") 'delete-frame-or-kill-emacs)

⌘-W, “close tab”

(global-set-key (kbd "s-w") 'kill-current-buffer)

⌘-F, “find”

(global-set-key (kbd "s-f") 'rgrep)

⌘-G, “go to line”

(global-set-key (kbd "s-g") 'goto-line)

⌘-I, “imenu”

(global-set-key (kbd "s-i") 'imenu)
(global-set-key (kbd "C-x i") 'imenu)

⌘-O, “open”

(global-set-key (kbd "s-o") 'ido-find-file)

⌘-N, “new file”

(global-set-key (kbd "s-n") 'new-empty-buffer)

⌘-M, “minimize”

(global-set-key (kbd "s-m") 'suspend-frame)

⌘-B, “switch buffer”

This one is obviously not standard on macOS, but buffer switching needs to be as smooth as possible.

(global-set-key (kbd "s-b") 'ivy-switch-buffer)

⌘-K, “erase the whole line”

This one is usually not present on MacOS, but maybe it should be! The original Emacs keybinding for this is C-S-backspace, which actually seems fine too?

(global-set-key (kbd "s-k") 'kill-whole-line)

⌘-J, “jump to word”

(define-key global-map (kbd "s-j") 'avy-goto-word-or-subword-1)
(define-key global-map (kbd "M-j") 'avy-goto-word-or-subword-1)
(define-key global-map (kbd "C-x j") 'avy-goto-word-or-subword-1)

Fix various small idiosyncrasies

(global-auto-revert-mode 1)
(auto-save-mode 0)
(setq ring-bell-function 'ignore)
(setq undo-limit 9999999)
(setq make-backup-files nil)
(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)

Major modes

Dired

(add-hook 'dired-mode-hook
  (lambda ()
    (local-set-key (kbd "b") 'dired-up-directory)
    (auto-revert-mode t)
    (set-face 'dired-directory 'face-popout)))

Org

  (use-package org
    :bind (:map org-mode-map
                ("C-M-u" . org-up-element)
                ("C-M-f" . org-next-visible-heading)
                ("C-M-b" . org-previous-visible-heading))
    :config
    (setq org-hide-emphasis-markers t) ;; Makes bold/underlined text work properly.
    (setq org-src-fontify-natively t)
    (setq org-adapt-indentation nil) ;; Don't indent text after heading
    (setq org-src-window-setup 'current-window) ;; Make the source editor go fullscreen
    (setq org-startup-folded t)
    (setq org-structure-template-alist ;; The list shown by (C-c C-,) which inserts a block structure
          '(("a" . "export ascii")
            ("c" . "center")
            ("C" . "comment")
            ("e" . "example")
            ("E" . "export")
            ("h" . "export html")
            ("l" . "export latex")
            ("q" . "quote")
            ("s" . "src")
            ("x" . "src emacs-lisp")
            ("v" . "verse")))
    (setq org-capture-templates
          '(("l" "Link" entry (file+headline "~/Documents/Organized/links.org" "Unsorted") "* %?\n")
            ("i" "Idea" entry (file "~/Documents/Organized/ideas.org") "* %?\n")
            ("t" "Todo" entry (file "~/Documents/Organized/todo.org") "* TODO %?\n"))))

  (use-package org-bullets
    :ensure t
    :config (setq org-bullets-bullet-list '("" "" "" "")))

;; The old bullets in case I change my mind ("✸" "◇" "•" "○" "✤" "✩")

  (setq-default prettify-symbols-alist '(("#+BEGIN_SRC" . "")
                                         ("#+END_SRC" . "")
                                         ("#+begin_src" . "")
                                         ("#+end_src" . "")
                                         ("#+RESULTS:" . "")
                                         ("emacs-lisp" . "ξ")
                                         (":defun:" . "𝑓")))
  (setq prettify-symbols-unprettify-at-point 'right-edge)

  (add-hook 'org-mode-hook
            (lambda ()
              (org-bullets-mode 1)
              (prettify-symbols-mode 1)
              (local-unset-key (kbd "<S-up>"))
              (local-unset-key (kbd "<S-down>"))
              (local-unset-key (kbd "<S-left>"))
              (local-unset-key (kbd "<S-right>"))))

Magit

(use-package magit
  :ensure t
  :config (bind-key "C-x g" 'magit-status))

(add-hook 'after-save-hook 'magit-after-save-refresh-status t)

(defadvice magit-status (around magit-fullscreen activate)
  (window-configuration-to-register :magit-fullscreen)
  ad-do-it
  (delete-other-windows))

Emacs Lisp

(add-hook 'emacs-lisp-mode-hook 'smartparens-mode)
(define-key emacs-lisp-mode-map (kbd "<s-return>") 'eval-defun)
(define-key emacs-lisp-mode-map (kbd "C-c C-l") 'eval-buffer)

LSP

(use-package lsp-mode
  :ensure t
  :config
  (setq lsp-headerline-breadcrumb-enable nil)
  (setq lsp-file-watch-threshold 5000)
  ;;(setq lsp-ui-sideline-enable nil)
  ;;(setq lsp-ui-sideline-show-code-actions nil)
  ;;(setq lsp-ui-doc-enable nil)
  )
(setq lsp-enable-snippet nil)

Haskell

(use-package haskell-mode
  :ensure t
  ;;:config (setq-default prettify-symbols-alist '(("\\" . "λ") ("." . "ᐤ")))
  )

(use-package dante
  :ensure t
  :after haskell-mode
  :commands 'dante-mode)

(setq dante-methods '(stack new-build bare-cabal bare-ghci))

(add-hook 'dante-mode-hook (lambda () (local-set-key (kbd "<C-c C-t>") 'dante-type-at)))

(add-hook 'haskell-mode-hook
          (lambda ()
            (interactive-haskell-mode 1)
            (smartparens-mode 1)
            (electric-pair-local-mode 0)
            (flycheck-mode 1)
            (company-mode 1)
            (prettify-symbols-mode 1)
            (define-key haskell-mode-map (kbd "s-r") (lambda ()
                                                       (interactive)
                                                       (shell-command "stack run")))))

(setq haskell-process-type 'stack-ghci)

(use-package ormolu
  ;;:hook (haskell-mode . ormolu-format-on-save-mode) ;; Handle by dir-locals instead!
  :ensure t
  :bind
  (:map haskell-mode-map
        ("C-c r" . ormolu-format-buffer)))

Racket

(setq racket-program "/Applications/Racket/bin/racket")
(add-hook 'racket-mode-hook 'smartparens-mode)

Pie

(add-to-list 'auto-mode-alist '("\\.pie\\'" . racket-mode))
;; (font-lock-add-keywords 'racket-mode '(("Π" . font-lock-keyword-face)))
;; (font-lock-add-keywords 'racket-mode '(("->" . font-lock-keyword-face)))
(font-lock-add-keywords 'racket-mode '(("claim" . font-lock-keyword-face)))
(put 'claim 'racket-indent-function 1)

Clojure

(use-package clojure-mode
  :ensure t)

(add-hook 'cider-mode-hook 'eldoc-mode)
(add-hook 'cider-mode-hook 'smartparens-mode)
(add-hook 'clojure-mode-hook 'smartparens-mode)

(add-hook 'clojure-mode-hook
      '(lambda ()
         (put-clojure-indent 'match 1)))

(add-hook 'cider-mode-hook
      '(lambda ()
         (electric-pair-local-mode 0)
         (define-key cider-mode-map (kbd "<s-return>") 'cider-eval-defun-at-point)))

(add-hook 'cider-repl-mode-hook
      '(lambda ()
         (electric-pair-local-mode 0)
         (local-set-key (kbd "<M-up>") 'cider-repl-previous-input)
         (local-set-key (kbd "<M-down>") 'cider-repl-next-input)))

(setq cider-repl-use-clojure-font-lock t)
(setq cider-prompt-save-file-on-load 'always-save)
(setq cider-repl-display-help-banner nil)

Carp

(add-to-list 'load-path "/Users/erik/Projects/carp-emacs")
(add-to-list 'load-path "/home/erik/Projects/carp-emacs")
(add-to-list 'load-path "/Users/eriksvedang/Code/carp-emacs")
(add-to-list 'load-path "C:/Users/erik/Documents/Projects/carp-emacs")

(require 'carp-mode)
(require 'carp-flycheck)

(add-hook 'carp-mode-hook
          (lambda ()
            (electric-pair-local-mode 0)
            (smartparens-mode 1)
            ;;(flycheck-mode 1)
            ))

C

(defun compile-c ()
  (interactive)
  (save-buffer)
  (let ((project-dir (locate-dominating-file (buffer-file-name) "makefile")))
    (if project-dir
    (progn (setq default-directory project-dir)
           (compile (format "make")))
      (compile (format "clang %s -O0 -g -o %s" (buffer-name) (file-name-sans-extension (buffer-name)))))))

(defun run-c ()
  (interactive)
  (save-buffer)
  (let ((project-dir (locate-dominating-file (buffer-file-name) "makefile")))
    (if project-dir
    (progn (setq default-directory project-dir)
           (compile (format "make run")))
    (compile (format "./%s" (file-name-sans-extension (buffer-name)))))))

;; Focus on the compiler output window so it's easier to close with 'q'
;; Not a good idea unfortunately since you can't run the code with C-c C-r when not focused on source.
;; (defadvice compile-c (after move-point-to-new-window activate)
;;   (other-window 0))

(add-hook 'c-mode-hook
      (lambda ()
        (electric-pair-local-mode 1)
        (rainbow-mode 0) ;; treats #def as a color
        (disable-ligatures)
        (setq-default c-basic-offset 4)
        (c-set-style "cc-mode")
        (define-key c-mode-map (kbd "C-c C-c") 'compile-c)
        (define-key c-mode-map (kbd "C-c C-r") 'run-c)
        (define-key c-mode-map (kbd "s-r") 'run-c)
        (define-key c-mode-map (kbd "C-c C-f") 'ff-find-other-file)))

C#

(use-package csharp-mode
  :ensure t
  :bind (:map csharp-mode-map)
  :config
  (add-hook 'csharp-mode-hook #'lsp)
  (add-hook 'csharp-mode-hook #'electric-pair-mode)
  (add-hook 'csharp-mode-hook #'company-mode)
  (add-hook 'csharp-mode-hook #'flycheck-mode)
)

(defun csharp-disable-clear-string-fences (orig-fun &rest args)
  "This turns off `c-clear-string-fences' for `csharp-mode'. When
on for `csharp-mode' font lock breaks after an interpolated string
or terminating simple string."
  (unless (equal major-mode 'csharp-mode)
    (apply orig-fun args)))

(advice-add 'c-clear-string-fences :around 'csharp-disable-clear-string-fences)

Rust

(use-package rust-mode
  :ensure t
  :config
  (add-hook 'rust-mode-hook #'lsp)
  (add-hook 'rust-mode-hook #'electric-pair-mode)
  (add-hook 'rust-mode-hook #'company-mode)
  (add-hook 'rust-mode-hook #'flycheck-mode)
  (define-key rust-mode-map (kbd "C-c C-c C-a") 'lsp-execute-code-action)
  (define-key rust-mode-map (kbd "C-c r r") 'lsp-rename)
  (setq rust-format-on-save t))

(use-package ron-mode
  :ensure t)

SGML

HTML mode.

(add-hook 'sgml-mode-hook
      (lambda ()
        (local-set-key (kbd "M-s-.") 'sgml-close-tag)
        (local-set-key (kbd "M-s-…") 'sgml-close-tag)))
(add-hook 'sgml-mode-hook 'smartparens-mode)
(add-hook 'html-mode-hook 'smartparens-mode)

Pico8

(add-to-list 'auto-mode-alist '("\\.p8\\'" . lua-mode))

Go

I probably should start using LSP for Go.

(defun run-go ()
  (interactive)
  (save-buffer)
  (let ((project-dir (locate-dominating-file (buffer-file-name) ".git")))
    (if project-dir
    (progn (setq default-directory project-dir)
           (compile (format "go run")))
    (compile (format "go run %s" (buffer-file-name))))))

(defun compile-go ()
  (interactive)
  (save-buffer)
  (let ((project-dir (locate-dominating-file (buffer-file-name) "go.mod")))
    (if project-dir
    (progn (setq default-directory project-dir)
           (compile (format "go build ./...")))
      (compile (format "go build %s -o %s" (buffer-name) (file-name-sans-extension (buffer-name)))))))

(use-package go-mode
  :ensure t
  :bind (:map go-mode-map
         ("C-c C-t" . godef-describe)
         ("C-c C-d" . godef-describe)
         ("C-c C-r" . run-go)
         ("C-c C-c" . compile-go)
         ("M-."     . godef-jump))
  :config
  (add-hook 'before-save-hook #'gofmt-before-save)
  (add-hook 'go-mode-hook #'go-imenu-setup)
  (add-hook 'go-mode-hook (electric-pair-mode 1)))

;; Jump to symbol
(use-package go-imenu
  :ensure t)

;; A debugger
(use-package go-dlv
  :ensure t)

;; Stub generator
(use-package go-impl
  :ensure t)

Regexp Builder

(require 're-builder)
(setq reb-re-syntax 'string) ;; less escaping

Markdown

(use-package markdown-mode
  :ensure t)

(add-hook 'markdown-mode-hook
          (lambda ()
            (auto-fill-mode t)))

TOML

(use-package toml-mode
  :ensure t)

Godot Script

(use-package gdscript-mode
  ;;:hook (gdscript-mode . eglot-ensure)
  :ensure t
  :custom (gdscript-eglot-version 3))

Misc programming languages

Apparently ‘use-package’ is a bit too slow to use for just installing a bunch of packages (this is a bummer) so let’s use a simpler method for making sure that these programming modes are available.

(defun install-misc-programming-modes ()
  (interactive)
  (package-refresh-contents)
  (setq misc-programming-modes
        '(sgml-mode
          web-mode
          php-mode
          lua-mode
          swift-mode
          cc-mode
          glsl-mode
          cmake-mode
          yaml-mode
          elm-mode
          idris-mode
          purescript-mode
          tuareg
          fsharp-mode
          js2-mode
          restclient
          dyalog-mode
          nix-mode
          nix-buffer
          gdscript-mode))
  (dolist (m misc-programming-modes)
    (when (not (package-installed-p m))
      (package-install m))))

;; Commented out to not have to run on each Emacs startup
;; (install-misc-programming-modes)