[emacs] add Borg's layer/essentials init, with some of my stuff
authorAmin Bandali <amin@aminb.org>
Sat, 28 Apr 2018 19:58:15 +0000 (15:58 -0400)
committerAmin Bandali <amin@aminb.org>
Sat, 28 Apr 2018 19:58:15 +0000 (15:58 -0400)
.gitignore
.gitmodules
init.org [new file with mode: 0644]

index 5a1c0bf..86b24fa 100644 (file)
@@ -16,4 +16,6 @@ ncmpcpp/*/error.log
 # getmail oldmail file
 oldmail-*
 
+/early-init.el
+/init.el
 /var
\ No newline at end of file
index 452a4ed..6a5b74d 100644 (file)
        path = lib/dash
        url = git@github.com:magnars/dash.el.git
        no-makeinfo = dash-template.texi
+       # dash creates a `dir' dash info file, which makes git think
+       # that the submodule is dirty. so, let's ignore the untracked
+       # files of dash's submodule
+       ignore = untracked
 [submodule "diff-hl"]
        path = lib/diff-hl
        url = git@github.com:dgutov/diff-hl.git
diff --git a/init.org b/init.org
new file mode 100644 (file)
index 0000000..371ec1e
--- /dev/null
+++ b/init.org
@@ -0,0 +1,659 @@
+#+title: =aminb='s Literate Emacs Configuration
+#+author: Amin Bandali
+#+babel: :cache yes
+#+property: header-args :tangle yes
+
+* About
+:PROPERTIES:
+:CUSTOM_ID: about
+:END:
+
+This org file is my literate configuration for GNU Emacs, and is
+tangled to [[./init.el][init.el]]. Packages are installed and managed using [[https://github.com/emacscollective/borg][Borg]].
+
+** Installation
+
+I'd like to have a fully reproducible Emacs setup (part of the reason
+why I store my configuration in this repository) but unfortunately out
+of the box, that's not achievable with =package.el=, not currently
+anyway. So, I've opted to use Borg. For what it's worth, I briefly
+experimented with [[https://github.com/raxod502/straight.el][straight.el]], but found that it added about 2 seconds
+to my init time; which is unacceptable for me: I use Emacs as my
+window manager (via EXWM) and coming from bspwm, I'm too used to
+having fast startup times.
+
+* Contents                                                   :toc_1:noexport:
+
+- [[#about][About]]
+- [[#header][Header]]
+- [[#initial-setup][Initial setup]]
+- [[#core][Core]]
+- [[#post-init][Post initialization]]
+- [[#footer][Footer]]
+
+* Header
+:PROPERTIES:
+:CUSTOM_ID: header
+:END:
+
+** First line
+
+#+begin_src emacs-lisp :comments none
+;;; init.el --- Amin Bandali's Emacs config -*- lexical-binding: t ; eval: (view-mode 1)-*-
+#+end_src
+
+Enable =view-mode=, which both makes the file read-only (as a reminder
+that =init.el= is an auto-generated file, not supposed to be edited),
+and provides some convenient key bindings for browsing through the
+file.
+
+** License
+
+#+begin_src emacs-lisp :comments none
+;; Copyright (C) 2018  Amin Bandali <amin@aminb.org>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+#+end_src
+
+** Commentary
+
+#+begin_src emacs-lisp :comments none
+;;; Commentary:
+
+;; Emacs configuration of Amin Bandali, computer scientist and functional
+;; programmer.
+
+;; THIS FILE IS AUTO-GENERATED FROM `init.org'.
+#+end_src
+
+** Naming conventions
+
+The conventions below were inspired by [[https://github.com/hlissner/doom-emacs][Doom]]'s conventions, found
+[[https://github.com/hlissner/doom-emacs/blob/5dacbb7cb1c6ac246a9ccd15e6c4290def67757c/core/core.el#L3-L17][here]]. Naturally, I use my initials, =ab=, instead of =doom=.
+
+#+begin_src emacs-lisp :comments none
+;; Naming conventions:
+;;
+;;   ab-...   public variables or non-interactive functions
+;;   ab--...  private anything (non-interactive), not safe for direct use
+;;   ab/...   an interactive function; safe for M-x or keybinding
+;;   ab:...   an evil operator, motion, or command
+;;   ab|...   a hook function
+;;   ab*...   an advising function
+;;   ab@...   a hydra command
+;;   ...!     a macro
+#+end_src
+
+* Initial setup
+:PROPERTIES:
+:CUSTOM_ID: initial-setup
+:END:
+
+#+begin_src emacs-lisp :comments none
+;;; Code:
+#+end_src
+
+** Emacs initialization
+
+I'd like to do a couple of measurements of Emacs' startup time. First,
+let's see how long Emacs takes to start up, before even loading
+=init.el=, i.e. =user-init-file=:
+
+#+begin_src emacs-lisp
+(defvar ab--before-user-init-time (current-time)
+  "Value of `current-time' when Emacs begins loading `user-init-file'.")
+(message "Loading Emacs...done (%.3fs)"
+         (float-time (time-subtract ab--before-user-init-time
+                                    before-init-time)))
+#+end_src
+
+Also, temporarily increase ~gc-cons-threshhold~ and
+~gc-cons-percentage~ during startup to reduce garbage collection
+frequency. Clearing the ~file-name-handler-alist~ seems to help reduce
+startup time as well.
+
+#+begin_src emacs-lisp
+(defvar ab--gc-cons-threshold gc-cons-threshold)
+(defvar ab--gc-cons-percentage gc-cons-percentage)
+(defvar ab--file-name-handler-alist file-name-handler-alist)
+(setq gc-cons-threshold (* 400 1024 1024)  ; 400 MiB
+      gc-cons-percentage 0.6
+      file-name-handler-alist nil
+      ;; sidesteps a bug when profiling with esup
+      esup-child-profile-require-level 0)
+#+end_src
+
+Of course, we'd like to set them back to their defaults once we're
+done initializing.
+
+#+begin_src emacs-lisp
+(add-hook
+ 'after-init-hook
+ (lambda ()
+   (let ((elapsed (float-time (time-subtract (current-time)
+                                             ab--before-user-init-time))))
+     (message "Loading %s...done (%.3fs) [after-init]"
+              user-init-file elapsed))
+   (setq gc-cons-threshold ab--gc-cons-threshold
+         gc-cons-percentage ab--gc-cons-percentage
+         file-name-handler-alist ab--file-name-handler-alist)))
+#+end_src
+
+Increase the number of lines kept in message logs (the =*Messages*=
+buffer).
+
+#+begin_src emacs-lisp
+(setq message-log-max 20000)
+#+end_src
+
+Optionally, we could suppress some byte compiler warnings like below,
+but for now I've decided to keep them enabled. See documentation for
+~byte-compile-warnings~ for more details.
+
+#+begin_src emacs-lisp
+;; (setq byte-compile-warnings
+;;       '(not free-vars unresolved noruntime lexical make-local))
+#+end_src
+
+** Package management
+
+*** No =package.el=
+
+I can do all my package management things with Borg, and don't need
+Emacs' built-in =package.el=. Emacs 27 lets us disable =package.el= in
+the =early-init-file= (see [[https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=24acb31c04b4048b85311d794e600ecd7ce60d3b][here]]).
+
+#+begin_src emacs-lisp :tangle early-init.el
+(setq package-enable-at-startup nil)
+#+end_src
+
+But since Emacs 27 isn't out yet (Emacs 26 is just around the corner
+right now), and even when released it'll be long before most distros
+ship in their repos, I'll still put the old workaround with the
+commented call to ~package-initialize~ here anyway.
+
+#+begin_src emacs-lisp
+(setq package-enable-at-startup nil)
+;; (package-initialize)
+#+end_src
+
+*** Borg
+
+#+begin_quote
+Assimilate Emacs packages as Git submodules
+#+end_quote
+
+[[https://github.com/emacscollective/borg][Borg]] is at the heart of package management of my Emacs setup. In
+short, it creates a git submodule in =lib/= for each package, which
+can then be managed with the help of Magit or other tools.
+
+#+begin_src emacs-lisp
+(setq user-init-file (or load-file-name buffer-file-name)
+      user-emacs-directory (file-name-directory user-init-file))
+(add-to-list 'load-path
+             (expand-file-name "lib/borg" user-emacs-directory))
+(require 'borg)
+(borg-initialize)
+#+end_src
+
+*** =use-package=
+
+#+begin_quote
+A use-package declaration for simplifying your .emacs
+#+end_quote
+
+[[https://github.com/jwiegley/use-package][use-package]] is an awesome utility for managing and configuring
+packages (in our case especially the latter) in a neatly organized way
+and without compromising on performance.
+
+#+begin_src emacs-lisp
+(require 'use-package)
+(if nil                                    ; set to t when need to debug init
+    (setq use-package-verbose t
+          use-package-expand-minimally nil
+          use-package-compute-statistics t
+          debug-on-error t)
+  (setq use-package-verbose nil
+        use-package-expand-minimally t))
+#+end_src
+
+*** Epkg
+
+#+begin_quote
+Browse the Emacsmirror package database
+#+end_quote
+
+Epkg provides access to a local copy of the [[https://emacsmirror.net][Emacsmirror]] package
+database, low-level functions for querying the database, and a
+=package.el=-like user interface for browsing the available packages.
+
+#+begin_src emacs-lisp
+(use-package epkg
+  :defer t)
+#+end_src
+
+** No littering in =~/.emacs.d=
+
+#+begin_quote
+Help keeping ~/.emacs.d clean
+#+end_quote
+
+By default, even for Emacs' built-in packages, the configuration files
+and persistent data are all over the place. Use =no-littering= to help
+contain the mess.
+
+#+begin_src emacs-lisp
+(use-package no-littering
+  :demand t
+  :config
+  (savehist-mode 1)
+  (add-to-list 'savehist-additional-variables 'kill-ring)
+  (save-place-mode 1)
+  (setq auto-save-file-name-transforms
+        `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))))
+#+end_src
+
+
+** Custom file (=custom.el=)
+
+I'm not planning on using the custom file much, but even so, I
+definitely don't want it mixing with =init.el=. So, here; let's give
+it it's own file. While at it, treat themes as safe.
+
+#+begin_src emacs-lisp
+(use-package custom
+  :no-require t
+  :config
+  (setq custom-file (no-littering-expand-etc-file-name "custom.el"))
+  (when (file-exists-p custom-file)
+    (load custom-file))
+  (setf custom-safe-themes t))
+#+end_src
+
+** Better =$PATH= handling
+
+Let's use [[https://github.com/purcell/exec-path-from-shell][exec-path-from-shell]] to make Emacs use the =$PATH= as set up
+in my shell.
+
+#+begin_src emacs-lisp
+;; (use-package exec-path-from-shell
+;;   :defer 1
+;;   :init
+;;   (setq exec-path-from-shell-check-startup-files nil)
+;;   :config
+;;   (exec-path-from-shell-initialize)
+;;   ;; while we're at it, let's fix access to our running ssh-agent
+;;   (exec-path-from-shell-copy-env "SSH_AGENT_PID")
+;;   (exec-path-from-shell-copy-env "SSH_AUTH_SOCK"))
+#+end_src
+
+** Server
+
+Start server if not already running. Alternatively, can be done by
+issuing =emacs --daemon= in the terminal, which can be automated with
+a systemd service or using =brew services start emacs= on macOS. I use
+Emacs as my window manager (via EXWM), so I always start Emacs on
+login; so starting the server from inside Emacs is good enough for me.
+
+See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html#Emacs-Server][Using Emacs as a Server]].
+
+#+begin_src emacs-lisp
+(use-package server
+  :config (or (server-running-p) (server-mode)))
+#+end_src
+
+* Core
+:PROPERTIES:
+:CUSTOM_ID: core
+:END:
+
+** Defaults
+
+*** Smaller fringe
+
+Set fringe to a small value so we don't have big borders in EXWM, but
+can still see the =diff-hl= colors in the fringe.
+
+#+begin_src emacs-lisp
+(fringe-mode '(3 . 1))
+#+end_src
+
+*** Disable disabled commands
+
+Emacs disables some commands by default that could persumably be
+confusing for novice users. Let's disable that.
+
+#+begin_src emacs-lisp
+(setq disabled-command-function nil)
+#+end_src
+
+*** Kill-ring
+
+Save what I copy into clipboard from other applications into Emacs'
+kill-ring, which would allow me to still be able to easily access it
+in case I kill (cut or copy) something else inside Emacs before
+yanking (pasting) what I'd originally intended to.
+
+#+begin_src emacs-lisp
+(setq save-interprogram-paste-before-kill t)
+#+end_src
+
+*** Minibuffer
+
+#+begin_src emacs-lisp
+(setq enable-recursive-minibuffers t
+      resize-mini-windows t)
+#+end_src
+
+*** Lazy-person-friendly yes/no prompts
+
+Lazy people would prefer to type fewer keystrokes, especially for yes
+or no questions. I'm lazy.
+
+#+begin_src emacs-lisp
+(defalias 'yes-or-no-p #'y-or-n-p)
+#+end_src
+
+*** Startup screen and =*scratch*=
+
+Firstly, let Emacs know that I'd like to have =*scratch*= as my
+startup buffer.
+
+#+begin_src emacs-lisp
+(setq initial-buffer-choice t)
+#+end_src
+
+Now let's customize the =*scratch*= buffer a bit. First off, I don't
+need the default hint.
+
+#+begin_src emacs-lisp
+(setq initial-scratch-message nil)
+#+end_src
+
+Also, let's use Text mode as the major mode, in case I want to
+customize it (=*scratch*='s default major mode, Fundamental mode,
+can't really be customized).
+
+#+begin_src emacs-lisp
+(setq initial-major-mode 'text-mode)
+#+end_src
+
+Inhibit the buffer list when more than 2 files are loaded.
+
+#+begin_src emacs-lisp
+(setq inhibit-startup-buffer-menu t)
+#+end_src
+
+I don't really need to see the startup screen or echo area message
+either.
+
+#+begin_src emacs-lisp
+(advice-add #'display-startup-echo-area-message :override #'ignore)
+(setq inhibit-startup-screen t
+      inhibit-startup-echo-area-message user-login-name)
+#+end_src
+
+*** More useful frame titles
+
+Show either the file name or the buffer name (in case the buffer isn't
+visiting a file). Borrowed from Emacs Prelude.
+
+#+begin_src emacs-lisp
+(setq frame-title-format
+      '("" invocation-name " - "
+        (:eval (if (buffer-file-name)
+                   (abbreviate-file-name (buffer-file-name))
+                 "%b"))))
+#+end_src
+
+*** Backups
+
+Emacs' default backup settings aren't that great. Let's use more
+sensible options. See documentation for the ~make-backup-file~
+variable.
+
+#+begin_src emacs-lisp
+(setq backup-by-copying t
+      version-control t)
+#+end_src
+
+** Packages
+
+The packages in this section are absolutely essential to my everyday
+workflow, and they play key roles in how I do my computing. They
+immensely enhance the Emacs experience for me; both using Emacs, and
+customizing it.
+
+*** [[https://github.com/emacscollective/auto-compile][auto-compile]]
+
+#+begin_src emacs-lisp
+(use-package auto-compile
+  :demand t
+  :config
+  (auto-compile-on-load-mode)
+  (auto-compile-on-save-mode)
+  (setq auto-compile-display-buffer               nil
+       auto-compile-mode-line-counter            t
+       auto-compile-source-recreate-deletes-dest t
+       auto-compile-toggle-deletes-nonlib-dest   t
+       auto-compile-update-autoloads             t)
+  (add-hook 'auto-compile-inhibit-compile-hook
+            'auto-compile-inhibit-compile-detached-git-head))
+#+end_src
+
+*** TODO [[https://github.com/Kungsgeten/ryo-modal][ryo-modal]]
+
+#+begin_quote
+Roll your own modal mode
+#+end_quote
+
+*** [[https://github.com/ch11ng/exwm][EXWM]] (window manager)
+
+#+begin_src emacs-lisp
+;; (use-package exwm
+;;   :config
+;;   (require 'exwm-config)
+;;   (exwm-config-default)
+;;   (require 'exwm-systemtray)
+;;   (exwm-systemtray-enable)
+;;   (require 'exwm-randr)
+;;   (exwm-randr-enable))
+#+end_src
+
+*** [[https://orgmode.org/][Org mode]]
+
+#+begin_quote
+Org mode is for keeping notes, maintaining TODO lists, planning
+projects, and authoring documents with a fast and effective plain-text
+system.
+#+end_quote
+
+In short, my favourite way of life.
+
+#+begin_src emacs-lisp
+(setq org-src-tab-acts-natively t
+      org-src-preserve-indentation nil
+      org-edit-src-content-indentation 0)
+#+end_src
+
+*** [[https://magit.vc/][Magit]]
+
+#+begin_quote
+It's Magit! A Git porcelain inside Emacs.
+#+end_quote
+
+Not just how I do git, but /the/ way to do git.
+
+#+begin_src emacs-lisp
+(use-package magit
+  :defer t
+  :bind (("s-g"     . magit-status)
+        ("C-x g"   . magit-status)
+         ("C-x M-g" . magit-dispatch-popup))
+  :config
+  (magit-add-section-hook 'magit-status-sections-hook
+                          'magit-insert-modules
+                          'magit-insert-stashes
+                          'append))
+#+end_src
+
+*** [[https://github.com/abo-abo/swiper][Ivy]] (and friends)
+
+#+begin_quote
+Ivy - a generic completion frontend for Emacs, Swiper - isearch with
+an overview, and more. Oh, man!
+#+end_quote
+
+There's no way I could top that, so I won't attempt to.
+
+**** Ivy
+
+#+begin_src emacs-lisp
+;; (use-package ivy
+;;   :bind
+;;   (:map ivy-minibuffer-map
+;;         ([escape] . keyboard-escape-quit)
+;;         ("C-j"    . ivy-next-line)
+;;         ("C-k"    . ivy-previous-line)
+;;         ([S-up]   . ivy-previous-history-element)
+;;         ([S-down] . ivy-next-history-element)
+;;         ("DEL"    . ivy-backward-delete-char))
+;;   :config
+;;   (ivy-mode 1))
+#+end_src
+
+**** Swiper
+
+#+begin_src emacs-lisp
+;; (use-package swiper
+;;   :bind (([remap isearch-forward]  . swiper)
+;;      ([remap isearch-backward] . swiper)))
+#+end_src
+
+**** Counsel
+
+#+begin_src emacs-lisp
+;; (use-package counsel
+;;   :bind (([remap execute-extended-command] . counsel-M-x)
+;;      ([remap find-file] . counsel-find-file)
+;;      ("s-r"     . counsel-recentf)
+;;      :map minibuffer-local-map
+;;      ("C-r" . counsel-minibuffer-history))
+;;   :config
+;;   (counsel-mode 1)
+;;   (defalias 'locate #'counsel-locate))
+#+end_src
+
+* Borg's =layer/essentials=
+
+TODO: break this giant source block down into individual org sections.
+
+#+begin_src emacs-lisp
+(use-package dash
+  :config (dash-enable-font-lock))
+
+(use-package diff-hl
+  :config
+  (setq diff-hl-draw-borders nil)
+  (global-diff-hl-mode)
+  (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh t))
+
+(use-package dired
+  :defer t
+  :config (setq dired-listing-switches "-alh"))
+
+(use-package eldoc
+  :when (version< "25" emacs-version)
+  :config (global-eldoc-mode))
+
+(use-package help
+  :defer t
+  :config (temp-buffer-resize-mode))
+
+(progn ;    `isearch'
+  (setq isearch-allow-scroll t))
+
+(use-package lisp-mode
+  :config
+  (add-hook 'emacs-lisp-mode-hook 'outline-minor-mode)
+  (add-hook 'emacs-lisp-mode-hook 'reveal-mode)
+  (defun indent-spaces-mode ()
+    (setq indent-tabs-mode nil))
+  (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
+
+(use-package man
+  :defer t
+  :config (setq Man-width 80))
+
+(use-package paren
+  :config (show-paren-mode))
+
+(use-package prog-mode
+  :config (global-prettify-symbols-mode)
+  (defun indicate-buffer-boundaries-left ()
+    (setq indicate-buffer-boundaries 'left))
+  (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left))
+
+(use-package recentf
+  :demand t
+  :config (add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:"))
+
+(use-package savehist
+  :config (savehist-mode))
+
+(use-package saveplace
+  :when (version< "25" emacs-version)
+  :config (save-place-mode))
+
+(use-package simple
+  :config (column-number-mode))
+
+(progn ;    `text-mode'
+  (add-hook 'text-mode-hook #'indicate-buffer-boundaries-left))
+
+(use-package tramp
+  :defer t
+  :config
+  (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
+  (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
+  (add-to-list 'tramp-default-proxies-alist
+               (list (regexp-quote (system-name)) nil nil)))
+
+(use-package undo-tree
+  :config
+  (global-undo-tree-mode)
+  (setq undo-tree-mode-lighter ""))
+#+end_src
+
+* Post initialization
+:PROPERTIES:
+:CUSTOM_ID: post-init
+:END:
+
+Display how long it took to load the init file.
+
+#+begin_src emacs-lisp
+(message "Loading %s...done (%.3fs)" user-init-file
+         (float-time (time-subtract (current-time)
+                                    ab--before-user-init-time)))
+#+end_src
+
+* Footer
+:PROPERTIES:
+:CUSTOM_ID: footer
+:END:
+
+#+begin_src emacs-lisp :comments none
+;;; init.el ends here
+#+end_src