From 180cab37b6034248c12a50d0a77360b52ef1fbfe Mon Sep 17 00:00:00 2001 From: Amin Bandali Date: Sat, 28 Apr 2018 15:58:15 -0400 Subject: [PATCH] [emacs] add Borg's layer/essentials init, with some of my stuff --- .gitignore | 2 + .gitmodules | 4 + init.org | 659 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 665 insertions(+) create mode 100644 init.org diff --git a/.gitignore b/.gitignore index 5a1c0bf..86b24fa 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ ncmpcpp/*/error.log # getmail oldmail file oldmail-* +/early-init.el +/init.el /var \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 452a4ed..6a5b74d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,6 +14,10 @@ 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 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 + +;; 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 . +#+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 -- 2.20.1