.emacs.d
文件大小: unknow
源码售价: 5 个金币 积分规则     积分充值
资源说明:Vanilla, Evil, literate Emacs configuration
#+TITLE: mpereira's Emacs configuration
#+AUTHOR: Murilo Pereira 

:PROPERTIES:
:TOC:      ignore
:END:

This is my vanilla, [[https://github.com/emacs-evil/evil][Evil]], literate Emacs configuration. It enables most of my
computing needs, since most of the time I'm on a computer I'm on Emacs.

It can be found at https://github.com/mpereira/.emacs.d.

I wouldn't recommend others to use this configuration as-is. I'm sure there are
sections, snippets, or settings that might be interesting, though.

If you'd like to know more about my relationship with Emacs, check out this
thing I wrote: /[[https://www.murilopereira.com/how-to-open-a-file-in-emacs/][How to open a file in Emacs]]: a short story about Lisp,
technology, and human progress/.

One day I'll include some screenshots here.

* Installing Emacs

I mostly use GUI Emacs on macOS. For work I use TUI Emacs on Ubuntu via SSH. On
macOS I install the excellent [[https://github.com/daviderestivo/homebrew-emacs-head][homebrew-emacs-head]] package created by [[https://github.com/daviderestivo][Davide
Restivo]]:

#+begin_src bash
brew install emacs-head@28 \
     --with-cocoa \
     --with-imagemagick \
     --with-modern-icon-pen \
     --with-native-comp \
     --with-no-frame-refocus \
     --with-pdumper \
     --with-xwidgets
#+end_src

On Ubuntu I install [[https://github.com/alexmurray][Alex Murray]]'s [[https://github.com/alexmurray/emacs-snap][GNU Emacs Snap package]] based on the
"latest/edge" channel, which comes with =native-comp= and works great.

First make sure libgccjit is installed:

#+begin_src bash
sudo apt install libgccjit-10-dev
#+end_src

Then install the Emacs snap:

#+begin_src bash
sudo snap install emacs --edge --classic
#+end_src

* Table of Contents
:PROPERTIES:
:TOC:      :include all :depth 3
:END:
:CONTENTS:
- [[#installing-emacs][Installing Emacs]]
- [[#table-of-contents][Table of Contents]]
- [[#dependencies][Dependencies]]
- [[#silent-exports-for-emacs-lisp-org-babel-code-blocks][Silent exports for emacs lisp org babel code blocks]]
- [[#quelpa-and-quelpa-use-package][quelpa and quelpa-use-package]]
- [[#utility-libraries][Utility libraries]]
  - [[#async][async]]
  - [[#aio][aio]]
  - [[#cl-lib][cl-lib]]
  - [[#s][s]]
  - [[#dash][dash]]
  - [[#thingatpt][thingatpt+]]
  - [[#help-fns][help-fns+]]
  - [[#ts][ts]]
  - [[#elx][elx]]
- [[#foundational][Foundational]]
  - [[#general][general]]
  - [[#paradox][paradox]]
  - [[#exec-path-from-shell][exec-path-from-shell]]
- [[#variables][Variables]]
- [[#redefinitions-advices][Redefinitions, advices]]
  - [[#make-org-src-buffer-name-shorter-and-nicer][Make org src buffer name shorter and nicer]]
  - [[#improve-lisp-code-indentation][Improve Lisp code indentation]]
  - [[#archive-org-subtrees-under-the-same-hierarchy-as-the-original-in-the-archive-files][Archive org subtrees under the same hierarchy as the original in the archive files]]
  - [[#provide-counsel-symbol-at-point][Provide counsel-symbol-at-point]]
- [[#helper-functions][Helper functions]]
  - [[#standard-library-type-of-things][Standard library type of things]]
  - [[#miscellaneous][Miscellaneous]]
- [[#toggle-buffer-maximize][Toggle buffer maximize]]
- [[#native-compilation][Native compilation]]
- [[#reload-directory-local-variables-when-saving-dir-localsel-files][Reload directory local variables when saving .dir-locals.el files]]
- [[#tramp][Tramp]]
  - [[#counsel-tramp][counsel-tramp]]
- [[#server][Server]]
- [[#options][Options]]
  - [[#file-backups][File backups]]
- [[#performance][Performance]]
  - [[#increase-the-amount-of-data-read-from-processes][Increase the amount of data read from processes]]
  - [[#asynchronous-undo-tree-history-save][Asynchronous undo-tree history save]]
  - [[#asynchronous-org-babel-tangling-and-byte-recompilation][Asynchronous Org Babel tangling and byte recompilation]]
  - [[#dont-save-minibuffer-winner-mode-state][Don't save minibuffer winner-mode state]]
  - [[#dont-show-buffer-remote-path-in-minibuffer][Don't show buffer remote path in minibuffer]]
  - [[#show-garbage-collections-in-minibuffer][Show garbage collections in minibuffer]]
  - [[#prevent-garbage-collecting-when-opening-the-minibuffer][Prevent garbage collecting when opening the minibuffer]]
  - [[#garbage-collection-magic-hack][Garbage collection magic hack]]
  - [[#dont-delete-trailing-whitespace-on-save][*Don't* delete trailing whitespace on save]]
  - [[#make-cursor-movement-an-order-of-magnitude-faster][Make cursor movement an order of magnitude faster]]
  - [[#start-up-profiler-esup][Start-up profiler: esup]]
  - [[#explain-pause-mode][explain-pause-mode]]
- [[#color-themes][Color themes]]
  - [[#create-hook-for-theme-change][Create hook for theme change]]
  - [[#change-themes-when-changing-macos-light-or-dark-appearance][Change themes when changing macOS light or dark appearance]]
- [[#configure-mode-line][Configure Mode Line]]
- [[#configure-header-line][Configure Header Line]]
- [[#vi-emulation][Vi emulation]]
  - [[#evil][evil]]
  - [[#evil-org][evil-org]]
  - [[#evil-exchange][evil-exchange]]
  - [[#evil-nerd-commenter][evil-nerd-commenter]]
  - [[#evil-surround][evil-surround]]
  - [[#evil-matchit][evil-matchit]]
  - [[#evil-lion][evil-lion]]
  - [[#evil-string-inflection][evil-string-inflection]]
  - [[#evil-goggles][evil-goggles]]
  - [[#evil-multiedit][evil-multiedit]]
  - [[#evil-owl][evil-owl]]
  - [[#evil-collection][evil-collection]]
- [[#org][Org]]
  - [[#org-mode][org-mode]]
  - [[#org-babel][Org Babel]]
  - [[#align-all-tags-in-the-buffer-on-tag-changes][Align all tags in the buffer on tag changes]]
  - [[#paste-images-in-the-clipboard-directly-into-org-buffers][Paste images in the clipboard directly into org buffers]]
  - [[#sort-org-entries-by-multiple-properties][Sort org entries by multiple properties]]
  - [[#org-clock][Org clock]]
  - [[#org-gcal][org-gcal]]
  - [[#org-agenda][Org agenda]]
  - [[#my-custom-persistent-cached-org-agendas][My custom persistent (cached) org agendas]]
  - [[#shrface][shrface]]
  - [[#outshine][outshine]]
  - [[#counsel-org-clock][counsel-org-clock]]
  - [[#org-download][org-download]]
  - [[#org-web-tools][org-web-tools]]
  - [[#org-insert-link-dwim][org-insert-link-dwim]]
  - [[#org-expiry][org-expiry]]
  - [[#org-bullets][org-bullets]]
  - [[#org-make-toc][org-make-toc]]
  - [[#org-tree-slide][org-tree-slide]]
  - [[#org-sidebar][org-sidebar]]
  - [[#org-pomodoro][org-pomodoro]]
  - [[#org-archive-hierarchically][org-archive-hierarchically]]
  - [[#org-autonum][org-autonum]]
  - [[#ob-async][ob-async]]
  - [[#ox-jira][ox-jira]]
  - [[#ox-twbs][ox-twbs]]
  - [[#ox-gfm][ox-gfm]]
  - [[#ox-hugo][ox-hugo]]
  - [[#ox-pandoc][ox-pandoc]]
- [[#file-management][File management]]
  - [[#dired][dired]]
  - [[#dired-ranger][dired-ranger]]
  - [[#dired-plus][dired-plus]]
  - [[#dired-show-readme][dired-show-readme]]
  - [[#dired-subtree][dired-subtree]]
  - [[#reveal-in-osx-finder][reveal-in-osx-finder]]
- [[#shell-terminal][Shell, terminal]]
  - [[#with-editor][with-editor]]
  - [[#xterm-color][xterm-color]]
  - [[#shell][shell]]
  - [[#eshell][eshell]]
  - [[#vterm][vterm]]
  - [[#term][term]]
  - [[#eterm-256color][eterm-256color]]
  - [[#configure-xterm-color][Configure xterm-color]]
  - [[#bash-completion][bash-completion]]
  - [[#fish-completion][fish-completion]]
  - [[#load-bash-alias][load-bash-alias]]
- [[#ui][UI]]
  - [[#settings][Settings]]
  - [[#default-text-scale][default-text-scale]]
  - [[#font-sizes][Font sizes]]
  - [[#posframe][posframe]]
  - [[#ivy-posframe][ivy-posframe]]
  - [[#flycheck-posframe][flycheck-posframe]]
  - [[#so-long][so-long]]
  - [[#too-long-lines-mode][too-long-lines-mode]]
  - [[#company-box][company-box]]
  - [[#minibuffer-line][minibuffer-line]]
  - [[#highlight-indent-guides][highlight-indent-guides]]
  - [[#hideshow][hideshow]]
  - [[#beacon][beacon]]
  - [[#rainbow-delimiters][rainbow-delimiters]]
  - [[#diff-hl][diff-hl]]
  - [[#dimmer][dimmer]]
  - [[#all-the-icons][all-the-icons]]
  - [[#dired-sidebar][dired-sidebar]]
  - [[#all-the-icons-dired][all-the-icons-dired]]
  - [[#emojify][emojify]]
  - [[#ivy-rich][ivy-rich]]
  - [[#all-the-icons-ivy-rich][all-the-icons-ivy-rich]]
- [[#movement][Movement]]
  - [[#bm][bm]]
  - [[#avy][avy]]
  - [[#goto-address-mode][goto-address-mode]]
  - [[#dumb-jump][dumb-jump]]
  - [[#frog-jump-buffer][frog-jump-buffer]]
  - [[#link-hint][link-hint]]
- [[#text-search-and-manipulation][Text search and manipulation]]
  - [[#swiper][swiper]]
  - [[#ripgrep][ripgrep]]
  - [[#wgrep][wgrep]]
  - [[#string-edit][string-edit]]
  - [[#double-saber][double-saber]]
  - [[#symbol-overlay][symbol-overlay]]
  - [[#expand-region][expand-region]]
  - [[#ialign][ialign]]
  - [[#yasnippet][yasnippet]]
  - [[#yasnippet-snippets][yasnippet-snippets]]
  - [[#add-yasnippet-support-for-all-company-backends][Add yasnippet support for all company backends]]
  - [[#electric-pair-mode][electric-pair-mode]]
  - [[#undo-tree][undo-tree]]
  - [[#move-text][move-text]]
  - [[#unfill][unfill]]
  - [[#string-inflection][string-inflection]]
  - [[#string-edit][string-edit]]
  - [[#format-all][format-all]]
  - [[#blacken][blacken]]
  - [[#prettier][prettier]]
- [[#git][git]]
  - [[#magit][magit]]
  - [[#libegit2-and-magit-libjit][libegit2 and magit-libjit]]
  - [[#forge][forge]]
  - [[#magit-todos][magit-todos]]
  - [[#a-vscode-git-lens-type-of-thing][A VSCode git lens type of thing]]
  - [[#gist][gist]]
  - [[#gitignore-mode][gitignore-mode]]
  - [[#magit-delta][magit-delta]]
  - [[#git-timemachine][git-timemachine]]
  - [[#browse-at-remote][browse-at-remote]]
- [[#software-development][Software development]]
  - [[#flycheck][flycheck]]
  - [[#lsp][LSP]]
  - [[#company][company]]
  - [[#aggressive-indent][aggressive-indent]]
  - [[#lisp][LISP]]
  - [[#emacs-lisp][Emacs Lisp]]
  - [[#java][Java]]
  - [[#clojure][Clojure]]
  - [[#go][Go]]
  - [[#rust][Rust]]
  - [[#kotlin][Kotlin]]
  - [[#c][C#]]
  - [[#javascript][JavaScript]]
  - [[#typescript][TypeScript]]
  - [[#graphql][GraphQL]]
  - [[#shell-script][Shell script]]
  - [[#python][Python]]
  - [[#json][JSON]]
  - [[#scala][Scala]]
  - [[#sql][SQL]]
  - [[#terraform-mode][terraform-mode]]
  - [[#docker][docker]]
  - [[#bazel-mode][bazel-mode]]
  - [[#groovy-mode][groovy-mode]]
  - [[#dockerfile-mode][dockerfile-mode]]
  - [[#literate-calc-mode][literate-calc-mode]]
- [[#web-development][Web development]]
  - [[#web-mode][web-mode]]
  - [[#auto-rename-tag][auto-rename-tag]]
  - [[#css][css]]
  - [[#js2-refactor][js2-refactor]]
  - [[#rjsx-mode][rjsx-mode]]
- [[#infrastructure][Infrastructure]]
  - [[#kubel][kubel]]
  - [[#nginx-mode][nginx-mode]]
- [[#multimedia][Multimedia]]
  - [[#pdf-tools][pdf-tools]]
  - [[#screenshot][screenshot]]
- [[#writing-prose][Writing prose]]
  - [[#grammarly][Grammarly]]
  - [[#flyspell][flyspell]]
  - [[#flyspell-correct-ivy][flyspell-correct-ivy]]
  - [[#mw-thesaurus][mw-thesaurus]]
  - [[#atomic-chrome][atomic-chrome]]
- [[#distraction-free-editing][Distraction-free editing]]
  - [[#hide-mode-line][hide-mode-line]]
  - [[#olivetti][olivetti]]
- [[#buffer-management][Buffer management]]
  - [[#transpose-frame][transpose-frame]]
  - [[#buffer-expose][buffer-expose]]
  - [[#buffer-move][buffer-move]]
  - [[#rotate][rotate]]
  - [[#persistent-scratch][persistent-scratch]]
  - [[#prevent-scratch-buffers-from-being-killed][Prevent scratch buffers from being killed]]
  - [[#display-buffer-alist-configuration][display-buffer-alist configuration]]
  - [[#display-compilation-result-buffers-to-a-single-window-to-the-right][Display compilation result buffers to a single window to the right]]
- [[#project-management][Project management]]
  - [[#a-fast-non-projectile-based-project-file-finder][A fast non-Projectile-based project file finder]]
  - [[#projectile][projectile]]
  - [[#term-projectile][term-projectile]]
  - [[#ibuffer][ibuffer]]
  - [[#ibuffer-projectile][ibuffer-projectile]]
  - [[#perspective][perspective]]
  - [[#counsel][counsel]]
  - [[#persp-projectile][persp-projectile]]
  - [[#counsel-projectile][counsel-projectile]]
  - [[#find-file-in-project][find-file-in-project]]
- [[#commands][Commands]]
  - [[#amx][amx]]
  - [[#ivy][ivy]]
  - [[#prescient][prescient]]
  - [[#ivy-prescient][ivy-prescient]]
  - [[#company-prescient][company-prescient]]
- [[#help][Help]]
  - [[#helpful][helpful]]
  - [[#discover-my-major][discover-my-major]]
  - [[#which-key][which-key]]
  - [[#dash-at-point][dash-at-point]]
  - [[#command-log-mode][command-log-mode]]
- [[#markup][Markup]]
  - [[#markdown-mode][markdown-mode]]
  - [[#toml-mode][toml-mode]]
  - [[#yaml-mode][yaml-mode]]
  - [[#htmlize][htmlize]]
  - [[#grip-mode][grip-mode]]
- [[#interactions-with-websites][Interactions with websites]]
  - [[#counsel-web][counsel-web]]
  - [[#emacs-webkit][emacs-webkit]]
  - [[#xwwp-follow-link][xwwp-follow-link]]
  - [[#elfeed][elfeed]]
  - [[#stackoverflow][stackoverflow]]
  - [[#google-this][google-this]]
  - [[#wolfram-alpha][Wolfram Alpha]]
  - [[#hackernews][hackernews]]
- [[#miscellaneous][Miscellaneous]]
  - [[#suggest][suggest]]
  - [[#open-junk-file][open-junk-file]]
  - [[#gif-screencast][gif-screencast]]
  - [[#disk-usage][disk-usage]]
  - [[#circe][circe]]
  - [[#mingus][mingus]]
  - [[#osascripts][osascripts]]
  - [[#emacs-audit][emacs-audit]]
- [[#mappings][Mappings]]
- [[#fun][Fun]]
  - [[#fireplace][fireplace]]
  - [[#let-it-snow][let-it-snow]]
- [[#tips-and-tricks][Tips and tricks]]
  - [[#org-mode-file-links-to-search-patterns-cant-start-with-open-parens][org mode file links to search patterns can't start with open parens]]
  - [[#expression-can-be-used-only-once-per-org-agenda-prefix-format][EXPRESSION can be used only once per org-agenda-prefix-format]]
  - [[#emulate-c-u-universal-argument][Emulate C-u (universal-argument)]]
  - [[#after-modifying-path][After modifying PATH]]
  - [[#terminate-initel-loading-early][Terminate init.el loading early]]
  - [[#change-font-m-x-x-select-font][Change font: M-x x-select-font]]
  - [[#when-melpaorg-is-down][When melpa.org is down]]
  - [[#overriding-a-function][Overriding a function]]
- [[#license][License]]
- [[#file-local-variables][File-local variables]]
:END:

* Dependencies

Some dependencies are installed with the =setup.sh= script, which is tangled
from this file.

Getting the file name:
#+name: configuration-org-file
#+begin_src emacs-lisp :results silent :exports none
(let ((inhibit-message t)
      (message-log-max nil))
  (prin1 (buffer-name)))
#+end_src

=setup.sh= preamble:
#+begin_src bash :tangle setup.sh :results verbatim :noweb yes :shebang #!/usr/bin/env bash
# This file is auto-generated by Emacs via `(org-babel-tangle-file "<>")'.

set -euxo pipefail
#+end_src

Other dependencies have to be manually set up:
- [[https://github.com/settings/tokens][GitHub personal token]] (for magit, gist, etc.)
- [[http://developer.wolframalpha.com/portal/myapps/][Wolfram Alpha AppID]] (for wolfram)
- TODO: Google Apps Calendar (for org-gcal)
- =~/.emacs.d/circe-secrets.el=
  - =mpereira/secret-circe-nickserv-password=
- =~/.emacs.d/org-gcal-secrets.el=
  - =mpereira/secret-org-gcal-client-id=
  - =mpereira/secret-org-gcal-client-secret=
  - =mpereira/secret-org-gcal-file-alist=
- =~/.emacs.d/wolfram-secrets.el=
  - =mpereira/secret-wolfram-alpha-app-id=

* Silent exports for emacs lisp org babel code blocks
Having this as an org file property doesn't seem to work for some reason.

#+begin_src emacs-lisp
:PROPERTIES:
:header-args: :results output silent :exports both
:END:
#+end_src

Set it with emacs lisp.

#+begin_src emacs-lisp :tangle yes
(setq org-babel-default-header-args:emacs-lisp '((:results . "output silent")))
#+end_src

* quelpa and quelpa-use-package
#+begin_src emacs-lisp :tangle yes
(unless (package-installed-p 'quelpa)
  (with-temp-buffer
    (url-insert-file-contents "https://raw.githubusercontent.com/quelpa/quelpa/master/quelpa.el")
    (eval-buffer)
    (quelpa-self-upgrade)))

(quelpa '(quelpa-use-package
          :fetcher github
          :repo "quelpa/quelpa-use-package"))

(require 'quelpa-use-package)

;; This needs to be set after requiring `quelpa-use-package'.
;; See https://github.com/quelpa/quelpa/pull/187#issuecomment-644709715.
(setq quelpa--override-version-check t)
#+end_src

* Utility libraries
** async
#+begin_src emacs-lisp :tangle yes
(use-package async)
#+end_src

** aio
#+begin_src emacs-lisp :tangle yes
(use-package aio)
#+end_src

** cl-lib
#+begin_src emacs-lisp :tangle yes
(use-package cl-lib)
#+end_src

** s
#+begin_src emacs-lisp :tangle yes
(use-package s)
#+end_src

** dash
#+begin_src emacs-lisp :tangle yes
(use-package dash)
#+end_src

** thingatpt+
#+begin_src emacs-lisp :tangle yes
(use-package thingatpt+
  :ensure nil
  :quelpa (thingatpt+
           :url "https://raw.githubusercontent.com/emacsmirror/emacswiki.org/master/thingatpt+.el"
           :fetcher url))
#+end_src

** help-fns+
#+begin_src emacs-lisp :tangle yes
(use-package help-fns+
  :ensure nil
  :quelpa (help-fns+
           :fetcher github
           :repo "emacsmirror/help-fns-plus"))
#+end_src

** ts
#+begin_src emacs-lisp :tangle yes
(use-package ts
  :ensure nil
  :quelpa (ts
           :fetcher github
           :repo "alphapapa/ts.el"))
#+end_src

** elx
#+begin_src emacs-lisp :tangle yes
(use-package elx
  :ensure nil
  :quelpa (elx
           :fetcher github
           :branch "dont-break-if-no-licensee"
           :repo "mpereira/elx"))
#+end_src

* Foundational
** general
#+begin_src emacs-lisp :tangle yes
(use-package general
  :custom
  (use-package-hook-name-suffix . nil))
#+end_src

** paradox
#+begin_src emacs-lisp :tangle yes
(use-package paradox
  :config
  (paradox-enable)

  ;; Disable annoying "do you want to set up GitHub integration" prompt.
  ;; https://github.com/Malabarba/paradox/issues/23
  (setq paradox-github-token t))
#+end_src

** exec-path-from-shell
This needs to be loaded before code that depends on PATH modifications, e.g.
~executable-find~.

#+begin_src emacs-lisp :tangle yes
(use-package exec-path-from-shell
  :config
  (dolist (shell-variable '("SSH_AUTH_SOCK"
                            "SSH_AGENT_PID"))
    (add-to-list 'exec-path-from-shell-variables shell-variable))
  (exec-path-from-shell-initialize))
#+end_src

* Variables
#+begin_src emacs-lisp :tangle yes
(setq mpereira/custom-file (expand-file-name "custom.el" user-emacs-directory))

(setq mpereira/leader ",")

(setq mpereira/light-theme 'doom-acario-light)
(setq mpereira/dark-theme 'doom-monokai-classic)
(setq mpereira/initial-theme mpereira/dark-theme)

(setq mpereira/dropbox-directory (file-name-as-directory
                                  (expand-file-name "~/Dropbox")))
(setq mpereira/org-directory (expand-file-name "org" mpereira/dropbox-directory))

(setq mpereira/org-calendar-file (expand-file-name "gcal/calendar.org"
                                                   mpereira/org-directory))
(setq mpereira/org-calendar-buffer-name (file-name-nondirectory
                                         mpereira/org-calendar-file))
;; Empirically, 2 seconds seems to be good enough.
(setq mpereira/org-gcal-request-timeout 2)

(setq mpereira/magit-status-width 120)

(setq mpereira/org-agenda-width 120)

(setq mpereira/fill-column 80)
(setq mpereira/fill-column-wide 120)

(setq mpereira/eshell-prompt-max-directory-length 50)
(setq mpereira/mode-line-max-directory-length 15)
#+end_src

* Redefinitions, advices
** Make org src buffer name shorter and nicer
Before
#+begin_src text
*Org Src configuration.org[ emacs-lisp ]*
*Org Src configuration.org[ emacs-lisp ]<2>*
#+end_src

After
#+begin_src text
configuration.org (org src)
configuration.org (org src)<2>
#+end_src

#+begin_src emacs-lisp :tangle yes
(defun org-src--construct-edit-buffer-name (org-buffer-name lang)
  "Construct the buffer name for a source editing buffer."
  (concat org-buffer-name " (org src)"))
#+end_src

** Improve Lisp code indentation
Before
#+begin_src emacs-lisp :tangle no
(:foo bar
      :baz qux)
#+end_src

After
#+begin_src emacs-lisp :tangle no
(:foo bar
 :baz qux)
#+end_src

I got this from [[https://github.com/Fuco1/.emacs.d/blob/a8230343bb7e2f07f5eac8e63e5506fa164344f6/site-lisp/my-redef.el#L25][Fuco1/.emacs.d/site-lisp/my-redef.el]].

#+begin_src emacs-lisp :tangle yes
(eval-after-load "lisp-mode"
  '(defun lisp-indent-function (indent-point state)
     "This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine if the arguments of
a Lisp function call should be indented specially. INDENT-POINT is the position
at which the line being indented begins. Point is located at the point to indent
under (for default indentation); STATE is the `parse-partial-sexp' state for
that position. If the current line is in a call to a Lisp function that has a
non-nil property `lisp-indent-function' (or the deprecated `lisp-indent-hook'),
it specifies how to indent. The property value can be: * `defun', meaning indent
`defun'-style \(this is also the case if there is no property and the function
has a name that begins with \"def\", and three or more arguments); * an integer
N, meaning indent the first N arguments specially
  (like ordinary function arguments), and then indent any further
  arguments like a body;
,* a function to call that returns the indentation (or nil).
  `lisp-indent-function' calls this function with the same two arguments
  that it itself received.
This function returns either the indentation to use, or nil if the
Lisp function does not specify a special indentation."
     (let ((normal-indent (current-column))
           (orig-point (point)))
       (goto-char (1+ (elt state 1)))
       (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
       (cond
        ;; car of form doesn't seem to be a symbol, or is a keyword
        ((and (elt state 2)
              (or (not (looking-at "\\sw\\|\\s_"))
                  (looking-at ":")))
         (if (not (> (save-excursion (forward-line 1) (point))
                     calculate-lisp-indent-last-sexp))
             (progn (goto-char calculate-lisp-indent-last-sexp)
                    (beginning-of-line)
                    (parse-partial-sexp (point)
                                        calculate-lisp-indent-last-sexp 0 t)))
         ;; Indent under the list or under the first sexp on the same
         ;; line as calculate-lisp-indent-last-sexp.  Note that first
         ;; thing on that line has to be complete sexp since we are
         ;; inside the innermost containing sexp.
         (backward-prefix-chars)
         (current-column))
        ((and (save-excursion
                (goto-char indent-point)
                (skip-syntax-forward " ")
                (not (looking-at ":")))
              (save-excursion
                (goto-char orig-point)
                (looking-at ":")))
         (save-excursion
           (goto-char (+ 2 (elt state 1)))
           (current-column)))
        (t
         (let ((function (buffer-substring (point)
                                           (progn (forward-sexp 1) (point))))
               method)
           (setq method (or (function-get (intern-soft function)
                                          'lisp-indent-function)
                            (get (intern-soft function) 'lisp-indent-hook)))
           (cond ((or (eq method 'defun)
                      (and (null method)
                           (> (length function) 3)
                           (string-match "\\`def" function)))
                  (lisp-indent-defform state indent-point))
                 ((integerp method)
                  (lisp-indent-specform method state
                                        indent-point normal-indent))
                 (method
                  (funcall method indent-point state)))))))))
#+end_src

** Archive org subtrees under the same hierarchy as the original in the archive files
I got this from [[https://github.com/Fuco1/.emacs.d/blob/b55c7e85d87186f16c395bd35f289da0b5bb84b1/files/org-defs.el#L1582-L1619][Fuco1/.emacs.d/files/org-defs.el]].

FIXME: I've been having issues with archiving lately because this defadvice
became incompatible with newer versions of org. Fuco1 is [[https://github.com/Fuco1/.emacs.d/issues/60][thinking of turning it
into a package]]. For now I'm making this source block not be tangled and using
[[https://gitlab.com/andersjohansson/org-archive-hierarchically][andersjohansson/org-archive-hierarchically]] instead.

Not tangled!
#+begin_src emacs-lisp :tangle no
(defadvice org-archive-subtree (around fix-hierarchy activate)
  (let* ((fix-archive-p (and (not current-prefix-arg)
                             (not (use-region-p))))
         (afile (org-extract-archive-file (org-get-local-archive-location)))
         (buffer (or (find-buffer-visiting afile) (find-file-noselect afile))))
    ad-do-it
    (when fix-archive-p
      (with-current-buffer buffer
        (goto-char (point-max))
        (while (org-up-heading-safe))
        (let* ((olpath (org-entry-get (point) "ARCHIVE_OLPATH"))
               (path (and olpath (split-string olpath "/")))
               (level 1)
               tree-text)
          (when olpath
            (org-mark-subtree)
            (setq tree-text (buffer-substring (region-beginning) (region-end)))
            (let (this-command) (org-cut-subtree))
            (goto-char (point-min))
            (save-restriction
              (widen)
              (-each path
                (lambda (heading)
                  (if (re-search-forward
                       (rx-to-string
                        `(: bol (repeat ,level "*") (1+ " ") ,heading)) nil t)
                      (org-narrow-to-subtree)
                    (goto-char (point-max))
                    (unless (looking-at "^")
                      (insert "\n"))
                    (insert (make-string level ?*)
                            " "
                            heading
                            "\n"))
                  (cl-incf level)))
              (widen)
              (org-end-of-subtree t t)
              (org-paste-subtree level tree-text))))))))
#+end_src

** Provide ~counsel-symbol-at-point~
~counsel-symbol-at-point~ was removed from counsel so I'm adding a version I found
on the internet here.

#+begin_src emacs-lisp :tangle yes
(defun counsel-symbol-at-point ()
  "Return current symbol at point as a string."
  (let ((s (thing-at-point 'symbol)))
    (and (stringp s)
         (if (string-match "\\`[`']?\\(.*?\\)'?\\'" s)
             (match-string 1 s)
           s))))
#+end_src

* Helper functions
** Standard library type of things
#+begin_src emacs-lisp :tangle yes
(defmacro comment (&rest body)
  "Comment out one or more s-expressions."
  nil)

(defun eshell-p (buffer)
  "Return t if BUFFER is an Eshell buffer."
  (with-current-buffer buffer
    (eq major-mode 'eshell-mode)))

(defun plist-each (function plist)
  "Iterate FUNCTION (a two-argument function) over PLIST."
  (when plist
    (funcall function (car plist) (cadr plist))
    (plist-each function (cddr plist))))

(defun queue-push (queue-sym element &optional bounded-limit)
  "TODO: docstring."
  (when (or (not bounded-limit)
            (< (length (symbol-value queue-sym))
               bounded-limit))
    (add-to-list queue-sym element t (lambda (a b) nil))))

(defun queue-pop (queue-sym)
  "TODO: docstring."
  (let* ((queue (symbol-value queue-sym))
         (popped-element (car queue)))
    (when popped-element
      (set queue-sym (cdr queue)))
    popped-element))

(defun unadvice (sym)
  "Remove all advices from symbol SYM."
  (interactive "aFunction symbol: ")
  (advice-mapc (lambda (advice _props) (advice-remove sym advice)) sym))
#+end_src

** Miscellaneous
#+begin_src emacs-lisp :tangle yes
(defmacro print-and-return (&rest body)
  "TODO: docstring."
  (let ((result-symbol (make-symbol "result")))
    `(let ((,result-symbol ,@body))
       (message "************************************************************")
       (pp ',@body)
       (message "||")
       (message "\\/")
       (print ,result-symbol)
       (message "************************************************************")
       ,result-symbol)))

(defun mpereira/hl-line-mode-disable ()
  "TODO: docstring."
  (interactive)
  (setq-local global-hl-line-mode nil))

(defun mpereira/hide-trailing-whitespace ()
  (interactive)
  (setq-local show-trailing-whitespace nil))

(defun mpereira/delete-file-and-buffer ()
  "Kill the current buffer and deletes the file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (when filename
      (if (vc-backend filename)
          (vc-delete-file filename)
        (progn
          (delete-file filename)
          (message "Deleted file %s" filename)
          (kill-buffer))))))

(defun mpereira/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)))))))

(defun mpereira/pp-macroexpand-all ()
  "TODO: docstring."
  (interactive)
  (let ((form (macroexpand-all (sexp-at-point))))
    (with-current-buffer-window " *mpereira/pp-macroexpand-all*" nil nil
      (pp form)
      (emacs-lisp-mode))))

(require 'thingatpt)
(require 'thingatpt+)
(defun mpereira/eval-thing-at-or-around-point ()
  "Evaluate thing at or surrounding the point."
  (interactive)
  (save-excursion
    (let* ((string-thing (tap-string-at-point))
           (symbol-thing (tap-symbol-at-point))
           (sexp-thing (sexp-at-point)))
      (cond
       (string-thing
        (let* ((_ (message "string"))
               (bounds (tap-bounds-of-string-at-point))
               (string-form (substring-no-properties string-thing))
               (string-value (substring-no-properties
                              (tap-string-contents-at-point))))
          (message "%s → %s" string-form string-form)
          (eros--eval-overlay string-value (cdr bounds))))
       (symbol-thing
        (let* ((_ (message "symbol"))
               (bounds (tap-bounds-of-symbol-at-point))
               (symbol-name (substring-no-properties
                             (tap-symbol-name-at-point)))
               (symbol-value (eval symbol-thing)))
          (message "%s" symbol-name)
          (message "↓")
          (message "%s" symbol-value)
          (eros--eval-overlay symbol-value (cdr bounds))))
       (sexp-thing
        (let* ((_ (message "sexp"))
               (bounds (tap-bounds-of-sexp-at-point))
               (value (eval sexp-thing)))
          (message "%s" sexp-thing)
          (message "↓")
          (message "%s" value)
          (eros--eval-overlay value (cdr bounds))))))))

(defun mpereira/split-window-below-and-switch ()
  "Split the window horizontally then switch to the new window."
  (interactive)
  (split-window-below)
  (balance-windows)
  (other-window 1))

(defun mpereira/split-window-right-and-switch ()
  "Split the window vertically then switch to the new window."
  (interactive)
  (split-window-right)
  (balance-windows)
  (other-window 1))

(defun mpereira/toggle-window-split ()
  (interactive)
  (if (= (count-windows) 2)
      (let* ((this-win-buffer (window-buffer))
             (next-win-buffer (window-buffer (next-window)))
             (this-win-edges (window-edges (selected-window)))
             (next-win-edges (window-edges (next-window)))
             (this-win-2nd (not (and (<= (car this-win-edges)
                                         (car next-win-edges))
                                     (<= (cadr this-win-edges)
                                         (cadr next-win-edges)))))
             (splitter
              (if (= (car this-win-edges)
                     (car (window-edges (next-window))))
                  'split-window-horizontally
                'split-window-vertically)))
        (delete-other-windows)
        (let ((first-win (selected-window)))
          (funcall splitter)
          (if this-win-2nd (other-window 1))
          (set-window-buffer (selected-window) this-win-buffer)
          (set-window-buffer (next-window) next-win-buffer)
          (select-window first-win)
          (if this-win-2nd (other-window 1))))
    (message "Can only toggle window split for 2 windows")))

(defun mpereira/indent-buffer ()
  "Indents the current buffer."
  (interactive)
  (indent-region (point-min) (point-max)))

(with-eval-after-load "lispy"
  (defun mpereira/inside-bounds-dwim ()
    ;; (when-let (lispy--bounds-dwim)
    ;;   (when (<)))
    )

  (defun mpereira/backward-sexp-begin (arg)
    "Moves to the beginning of the previous ARG nth sexp."
    (interactive "p")
    (if-let (bounds (lispyville--in-string-p))
        ;; Go to beginning of string.
        (goto-char (car bounds))
      ;; `backward-sexp' will enter list-like sexps when point is on the closing
      ;; character. So we move one character to the right.
      (when (looking-at lispy-right)
        (forward-char 1))
      (backward-sexp arg)))

  (defun mpereira/forward-sexp-begin (arg)
    "Moves to the beginning of the next ARG nth sexp. The fact that this doesn't
exist in any structured movement package is mind-boggling to me."
    (interactive "p")
    (when-let (bounds (lispyville--in-string-p))
      (goto-char (car bounds)))
    (dotimes (_ arg)
      (forward-sexp 1)
      (if (looking-at lispy-right)
          ;; Prevent moving forward from last element in current level.
          (backward-sexp 1)
        (progn
          (forward-sexp 1)
          (backward-sexp 1)))))

  ;; Idea: move up to the parent sexp, count the number of sexps inside it with
  ;; `scan-lists' or `scan-sexps' or `paredit-scan-sexps-hack' to know whether
  ;; or not we're at the last sexp.
  (defun mpereira/forward-sexp-end (arg)
    "Moves to the end of the next ARG nth sexp. The fact that this doesn't exist
in any structured movement package is mind-boggling to me."
    (interactive "p")
    (let ((region-was-active (region-active-p)))
      ;; If a region is selected, pretend it's not so that `lispy--bounds-dwim'
      ;; doesn't return the bounds of the region. We want the bounds of the
      ;; actual thing under the point.
      (cl-letf (((symbol-function 'region-active-p) #'(lambda () nil)))
        (when-let (bounds (lispy--bounds-dwim))
          (let ((end (- (cdr bounds) 1)))
            (if (< (point) end)
                ;; Move to the end of the current sexp if not already there.
                (progn
                  (goto-char end)
                  ;; When a region is active we need to move right an extra
                  ;; character.
                  (when (and region-was-active)
                    (forward-char 1)))
              (progn
                ;; Move one character to the right in case point is on a list-like
                ;; closing character so that the subsequent `lispy--bounds-dwim'
                ;; start is right.
                (when (looking-at lispy-right)
                  (forward-char 1))
                ;; Go to the beginning of the current sexp so that
                ;; `mpereira/forward-sexp-begin' works.
                (when-let (bounds (lispy--bounds-dwim))
                  (goto-char (car bounds)))
                ;; Move to the beginning of the next sexp.
                (mpereira/forward-sexp-begin arg)
                ;; Go to the end of the sexp.
                (when-let (bounds (lispy--bounds-dwim))
                  (goto-char (- (cdr bounds) 1))
                  ;; When a region is active and we're not at the last sexp we
                  ;; need to move right an extra character.
                  (when (and region-was-active
                             ;; TODO
                             ;; (not last-sexp)
                             )
                    (forward-char 1)))))))))))

(with-eval-after-load "evil"
  (with-eval-after-load "lispyville"
    (defun mpereira/insert-to-beginning-of-list (arg)
      (interactive "p")
      (lispyville-backward-up-list)
      (evil-forward-char)
      (evil-insert arg))

    (defun mpereira/append-to-end-of-list (arg)
      (interactive "p")
      (lispyville-up-list)
      (evil-insert arg))))

(defun mpereira/org-sort-parent-entries (&rest args)
  ;; `org-sort-entries' doesn't respect `save-excursion'.
  (let ((origin (point)))
    (org-up-heading-safe)
    (apply #'org-sort-entries args)
    (goto-char origin)))

(defun mpereira/org-cycle-cycle ()
  (org-cycle)
  ;; https://www.mail-archive.com/emacs-orgmode@gnu.org/msg86779.html
  (ignore-errors
    (org-cycle)))

(defun mpereira/call-interactively-with-prefix-arg (prefix-arg func)
  (let ((current-prefix-arg prefix-arg))
    (call-interactively func)))

(defun mpereira/perspective-switch (perspective-name
                                    &optional after-perspective-creation-function)
  "TODO: docstring."
  (let ((perspective (gethash perspective-name (perspectives-hash))))
    (if perspective
        ;; Perspective already exists and is not the current.
        (when (not (equal perspective (persp-curr)))
          (persp-switch perspective-name))
      ;; Perspective doesn't exist.
      (progn
        (persp-switch perspective-name)
        (and after-perspective-creation-function
             (funcall after-perspective-creation-function perspective-name))))))

(defun mpereira/projectile-default-project-name (project-root)
  "TODO: PROJECT-ROOT docstring."
  (let* ((default-directory project-root)
         (suffix (if (file-remote-p project-root)
                     (format " @ %s" (mpereira/remote-host))
                   "")))
    (concat (file-name-nondirectory (directory-file-name default-directory))
            suffix)))

(defun mpereira/projectile-switch-project-action (project-root)
  "TODO: PROJECT-ROOT docstring."
  (let ((perspective-name (funcall
                           projectile-project-name-function
                           project-root)))
    (mpereira/perspective-switch perspective-name
                                 (lambda (perspective-name)
                                   (if (file-remote-p project-root)
                                       (let ((default-directory project-root))
                                         (mpereira/maybe-projectile-dired))
                                     (counsel-projectile-switch-project-action-dired
                                      project-root))))))

(defun mpereira/counsel-projectile-perspective-switch-project (&optional default-action)
  "TODO: docstring."
  (interactive)
  (ivy-read (projectile-prepend-project-name "Switch to project: ")
            (projectile-relevant-known-projects)
            :preselect (and (projectile-project-p)
                            (projectile-project-root))
            :action (or default-action
                        'mpereira/projectile-switch-project-action)
            :require-match t
            :sort 'ivy-prescient-sort-function
            :caller 'mpereira/counsel-projectile-perspective-switch-project))

(with-eval-after-load "ivy"
  (ivy-configure 'mpereira/counsel-projectile-perspective-switch-project
    :display-transformer-fn 'mpereira/projectile-default-project-name))

(with-eval-after-load "find-file-in-project"
  (defun mpereira/find-directory ()
    (interactive)
    (ffip-find-files "" nil t)))

(with-eval-after-load "projectile"
  (defun mpereira/maybe-projectile-dired ()
    (interactive)
    (if (projectile-project-p)
        (projectile-dired)
      (dired ".")))

  (defun mpereira/maybe-projectile-ibuffer ()
    (interactive)
    (if (projectile-project-p)
        (projectile-ibuffer nil)
      (ibuffer ".")))

  (with-eval-after-load "eshell"
    (defun mpereira/maybe-projectile-eshell ()
      (interactive)
      (if (projectile-project-p)
          (projectile-run-eshell t)
        (eshell t))))

  (with-eval-after-load "find-file-in-project"
    (with-eval-after-load "counsel-projectile"
      (defun mpereira/maybe-projectile-switch-buffer ()
        (interactive)
        (if (projectile-project-p)
            (counsel-projectile-switch-to-buffer)
          (ivy-switch-buffer)))

      (defun mpereira/maybe-projectile-find-file ()
        (interactive)
        (if (projectile-project-p)
            (counsel-projectile-find-file)
          (fast-project-find-file)))

      (defun mpereira/maybe-projectile-find-directory ()
        (interactive)
        (if (projectile-project-p)
            (counsel-projectile-find-dir)
          (mpereira/find-directory))))))

(defun mpereira/enable-line-numbers ()
  (setq display-line-numbers t))

(defun mpereira/disable-line-numbers ()
  (setq display-line-numbers nil))

(defun mpereira/maybe-enable-aggressive-indent-mode ()
  "TODO: docstring."
  (when (not (or (cl-member-if #'derived-mode-p aggressive-indent-excluded-modes)
                 (-contains? aggressive-indent-excluded-buffers (buffer-name))
                 buffer-read-only))
    (aggressive-indent-mode)))

(defun mpereira/lock-screen ()
  "TODO: docstring."
  (interactive)
  ;; TODO: make file path joining portable.
  (let ((command (concat "/System"
                         "/Library"
                         "/CoreServices"
                         "/Menu\\ Extras"
                         "/User.menu"
                         "/Contents"
                         "/Resources"
                         "/CGSession"
                         " "
                         "-suspend")))
    (shell-command command)))

(defun mpereira/epoch-at-point-to-timestamp ()
  "TODO: docstring"
  (interactive)
  (if-let (thing (counsel-symbol-at-point))
      (let* ((seconds (string-to-number thing))
             (time (seconds-to-time seconds))
             (timestamp (format-time-string "%Y-%m-%d %a %H:%M:%S" time)))
        (kill-new timestamp)
        (message timestamp)
        timestamp)))

(defun mpereira/pwd ()
  "TODO: docstring"
  (interactive)
  (let ((pwd (if (eshell-p (current-buffer))
                 (eshell/pwd)
               (buffer-file-name))))
    (kill-new pwd)
    (message pwd)
    pwd))

(defun mpereira/make-hs-hide-level (n)
  "TODO: docstring"
  (lexical-let ((n n))
    #'(lambda ()
        (interactive)
        (save-excursion
          (goto-char (point-min))
          (hs-hide-level n)))))

(defun mpereira/bm-counsel-get-list (bookmark-overlays)
  "TODO: docstring.
Arguments: BOOKMARK-OVERLAYS."
  (-map (lambda (bm)
          (with-current-buffer (overlay-buffer bm)
            (let* ((line (replace-regexp-in-string
                          "\n$"
                          ""
                          (buffer-substring (overlay-start bm)
                                            (overlay-end bm))))
                   ;; line numbers start on 1
                   (line-num (+ 1 (count-lines (point-min) (overlay-start bm))))
                   (name (format "%s:%d - %s" (buffer-name) line-num line)))
              `(,name . ,bm))))
        bookmark-overlays))

(defun mpereira/bm-counsel-find-bookmark ()
  "TODO: docstring.
Arguments: none."
  (interactive)
  (let* ((bm-list (mpereira/bm-counsel-get-list (bm-overlays-lifo-order t)))
         (bm-hash-table (make-hash-table :test 'equal))
         (search-list (-map (lambda (bm) (car bm)) bm-list)))
    (-each bm-list (lambda (bm)
                     (puthash (car bm) (cdr bm) bm-hash-table)))
    (ivy-read "Find bookmark: "
              search-list
              :require-match t
              :keymap counsel-describe-map
              :action (lambda (chosen)
                        (let ((bookmark (gethash chosen bm-hash-table)))
                          (switch-to-buffer (overlay-buffer bookmark))
                          (bm-goto bookmark)))
              :sort t)))

(defun mpereira/narrow-or-widen-dwim (p)
  "Widen if buffer is narrowed, narrow-dwim otherwise.
Dwim means: region, org-src-block, org-subtree, or defun, whichever applies
first. Narrowing to org-src-block actually calls `org-edit-src-code'.

With prefix P, don't widen, just narrow even if buffer is already narrowed."
  (interactive "P")
  (declare (interactive-only))
  (cond ((and (buffer-narrowed-p) (not p)) (widen))
        ((region-active-p)
         (narrow-to-region (region-beginning)
                           (region-end)))
        ((derived-mode-p 'org-mode)
         ;; `org-edit-src-code' is not a real narrowing command. Remove this
         ;; first conditional if you don't want it.
         (cond ((ignore-errors (org-edit-src-code) t)
                (delete-other-windows))
               ((ignore-errors (org-narrow-to-block) t))
               (t (org-narrow-to-subtree))))
        ((derived-mode-p 'latex-mode)
         (LaTeX-narrow-to-environment))
        (t (narrow-to-defun))))

(defun mpereira/uuid ()
  "Return a UUID and make it the latest kill in the kill ring."
  (interactive)
  (kill-new (format "%04x%04x-%04x-%04x-%04x-%06x%06x"
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 6))
                    (random (expt 16 6)))))

;; TODO: make this better.
(defun mpereira/kill-last-kbd-macro ()
  "Save last executed macro definition in the kill ring."
  (let ((name (gensym "kill-last-kbd-macro-")))
    (name-last-kbd-macro name)
    (with-temp-buffer
      (insert-kbd-macro name)
      (kill-new (buffer-substring-no-properties (point-min) (point-max))))))

(defun mpereira/load-light-theme ()
  "TODO: docstring."
  (interactive)
  (counsel-load-theme-action (symbol-name mpereira/light-theme)))

(defun mpereira/load-dark-theme ()
  "TODO: docstring."
  (interactive)
  (counsel-load-theme-action (symbol-name mpereira/dark-theme)))

(defun mpereira/process-using-port ()
  "Show list of processes listening on ports via TCP.
  Copies the selected process's PID to the clipboard."
  (interactive)
  (let ((sort-fn (lambda (name candidates)
                   candidates))
        (ivy-sort-functions-alist '((t . sort-fn)))
        (candidates (split-string (shell-command-to-string
                                   "lsof -nP -iTCP | grep LISTEN")
                                  "\n"
                                  t)))
    (ivy-read "Port: "
              candidates
              :action (lambda (project-path)
                        (kill-new (cadr (split-string project-path " " t)))))))

(defun mpereira/ps ()
  "Show list of system processes.
Copies the selected process's PID to the clipboard."
  (interactive)
  (let ((ps-sort (lambda (name candidates)
                   candidates))
        (ivy-sort-functions-alist '((t . ps-sort)))
        (ps (split-string (shell-command-to-string
                           "ps axco user,pid,%cpu,%mem,start,time,command -r")
                          "\n"
                          t)))
    (ivy-read "Process: "
              ps
              :action (lambda (project-path)
                        (kill-new (cadr (split-string project-path " " t)))))))

(defun mpereira/kill-buffer-and-maybe-window ()
  "TODO."
  (interactive)
  (if (window-prev-buffers)
      (let ((previous-buffer (car (window-prev-buffers))) ; not using this.
            (current-buffer* (current-buffer)))
        (kill-buffer current-buffer*))
    (kill-buffer-and-window)))

(with-eval-after-load "counsel"
  (with-eval-after-load "lispy"
    ;; `lispy-goto-local' doesn't work in org babel indirect src block buffers.
    (defun mpereira/lispy-goto-local (&optional args)
      "lispy-goto-local with fallback to counsel-imenu."
      (interactive)
      (if (lispy--file-list)
          (funcall 'lispy-goto-local args)
        (funcall 'counsel-imenu)))))

;; TODO: make it be able to get indirect buffer file names.
(defun mpereira/file-metadata ()
  "TODO."
  (interactive)
  (let* ((fname (buffer-file-name))
         (data (file-attributes fname))
         (access (current-time-string (nth 4 data)))
         (mod (current-time-string (nth 5 data)))
         (change (current-time-string (nth 6 data)))
         (size (nth 7 data))
         (mode (nth 8 data))
         (output (format
                  "%s:

Accessed: %s
Modified: %s
Changed:  %s
Size:     %s bytes
Mode:     %s"
                  fname access mod change size mode)))
    (kill-new output)
    (message output)
    output))

(defun mpereira/buffer-project-directory (project-root-directory
                                          buffer-directory
                                          &optional max-length)
  "Returns a possibly left-truncated relative directory for a project buffer."
  (let* ((truncation-string (if (char-displayable-p ?…) "…/" ".../"))
         (relative-directory (s-chop-prefix project-root-directory buffer-directory))
         (abbreviated-directory (abbreviate-file-name relative-directory))
         (max-length (or max-length 1.0e+INF)))
    ;; If it fits, return the string.
    (if (and max-length
             (<= (string-width abbreviated-directory) max-length))
        abbreviated-directory
      ;; If it doesn't, shorten it.
      (let ((path (reverse (split-string abbreviated-directory "/")))
            (output ""))
        (when (and path (equal "" (car path)))
          (setq path (cdr path)))
        (let ((max (- max-length (string-width truncation-string))))
          ;; Concat as many levels as possible, leaving 4 chars for safety.
          (while (and path (<= (string-width (concat (car path) "/" output))
                               max))
            (setq output (concat (car path) "/" output))
            (setq path (cdr path))))
        ;; If we had to shorten, prepend …/.
        (when path
          (setq output (concat truncation-string output)))
        output))))

(defun mpereira/short-directory-path (directory &optional max-length)
  "Returns a potentially trimmed-down version of the directory DIRECTORY,
replacing parent directories with their initial characters to try to get the
character length of directory (sans directory slashes) down to MAX-LENGTH."
  (let* ((components (split-string (abbreviate-file-name directory) "/"))
         (max-length (or max-length 1.0e+INF))
         (len (+ (1- (length components))
                 (cl-reduce '+ components :key 'length)))
         (str ""))
    (while (and (> len max-length)
                (cdr components))
      (setq str (concat str
                        (cond ((= 0 (length (car components))) "/")
                              ((= 1 (length (car components)))
                               (concat (car components) "/"))
                              (t
                               (if (string= "."
                                            (string (elt (car components) 0)))
                                   (concat (substring (car components) 0 2)
                                           "/")
                                 (string (elt (car components) 0) ?/)))))
            len (- len (1- (length (car components))))
            components (cdr components)))
    (concat str (cl-reduce (lambda (a b) (concat a "/" b)) components))))

(defun mpereira/elpy-shell-clear-shell ()
  "Clear the current shell buffer."
  (interactive)
  (with-current-buffer (process-buffer (elpy-shell-get-or-create-process))
    (comint-clear-buffer)))

(defun mpereira/prevent-buffer-kill ()
  "Prevents the current buffer from being killed."
  (interactive)
  (emacs-lock-mode 'kill))

(defun mpereira/exec-path-from-shell-initialize ()
  "Clears PATH before running `exec-path-from-shell-initialize' so that there's
no duplicate or conflicting entries."
  (interactive)
  (setenv "PATH" "")
  (exec-path-from-shell-initialize))

(defun mpereira/org-todo-with-date (&optional arg)
  (interactive "P")
  (cl-letf* ((org-read-date-prefer-future nil)
             (my-current-time (org-read-date t t nil "when:" nil nil nil))
             ((symbol-function #'org-current-effective-time)
              #'(lambda () my-current-time)))
    (org-todo arg)))

(defun iso8601-date-string ()
  "TODO: docstring."
  (interactive)
  (let* ((time-zone-part (format-time-string "%z"))
         (iso8601-date-string (concat
                               (format-time-string "%Y-%m-%dT%T")
                               (substring time-zone-part 0 3)
                               ":"
                               (substring time-zone-part 3 5))))
    (message iso8601-date-string)
    (kill-new iso8601-date-string)))
#+end_src

* Toggle buffer maximize
#+begin_src emacs-lisp :tangle yes
(defvar mpereira/toggle-buffer-maximize-window-configuration nil
  "A window configuration to return to when unmaximizing the buffer.")

(defvar mpereira/toggle-buffer-maximize-point nil
  "A point to return to when unmaximizing the buffer.")

(defvar mpereira/toggle-buffer-maximize-centered-p nil
  "Whether or not the buffer was maximixed in centered mode.")

(defun mpereira/toggle-buffer-maximize (&optional centered-p)
  "Saves the current window configuration and makes the current buffer occupy
the whole window. Calling it a second time will restore the saved window
configuration."
  (interactive)
  (if (bound-and-true-p mpereira/toggle-buffer-maximize-window-configuration)
      (progn
        (set-window-configuration mpereira/toggle-buffer-maximize-window-configuration)
        (setq mpereira/toggle-buffer-maximize-window-configuration nil)
        (goto-char mpereira/toggle-buffer-maximize-point)
        (setq mpereira/toggle-buffer-maximize-point nil)
        (when mpereira/toggle-buffer-maximize-centered-p
          (call-interactively 'olivetti-mode)
          (setq mpereira/toggle-buffer-maximize-centered-p nil)))
    (progn
      (setq mpereira/toggle-buffer-maximize-window-configuration
            (current-window-configuration))
      (setq mpereira/toggle-buffer-maximize-point (point))
      (setq mpereira/toggle-buffer-maximize-centered-p centered-p)
      (delete-other-windows)
      (when centered-p
        (call-interactively 'olivetti-mode)))))
#+end_src

* Native compilation
#+begin_src emacs-lisp :tangle yes
(use-package emacs
  :custom
  (native-comp-async-report-warnings-errors nil))
#+end_src

* Reload directory local variables when saving .dir-locals.el files
Taken from [[https://emacs.stackexchange.com/a/13096][Stack Overflow]].

#+begin_src emacs-lisp :tangle yes
(defun mpereira/reload-dir-locals-for-current-buffer ()
  "Reload directory local variables on the current buffer."
  (interactive)
  (let ((enable-local-variables :all))
    (hack-dir-local-variables-non-file-buffer)))

(defun mpereira/reload-dir-locals-for-all-buffer-in-this-directory ()
  "Reload directory local variables on every buffer with the same
`default-directory' as the current buffer."
  (interactive)
  (let ((dir default-directory))
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when (equal default-directory dir))
        (mpereira/reload-dir-locals-for-current-buffer)))))

(defun mpereira/enable-autoreload-for-dir-locals ()
  (when (and (buffer-file-name)
             (equal dir-locals-file
                    (file-name-nondirectory (buffer-file-name))))
    (add-hook (make-variable-buffer-local 'after-save-hook)
              'mpereira/reload-dir-locals-for-all-buffer-in-this-directory)))

(add-hook 'emacs-lisp-mode-hook #'mpereira/enable-autoreload-for-dir-locals)
#+end_src

* Tramp
#+begin_src emacs-lisp :tangle yes
(require 'tramp)
#+end_src

Disable version control on tramp buffers to avoid freezes.
#+begin_src emacs-lisp :tangle yes
(setq vc-ignore-dir-regexp
      (format "\\(%s\\)\\|\\(%s\\)"
              vc-ignore-dir-regexp
              tramp-file-name-regexp))
#+end_src

Don't clean up recentf tramp buffers.
#+begin_src emacs-lisp :tangle yes
(setq recentf-auto-cleanup 'never)
#+end_src

[[https://github.com/syl20bnr/spacemacs/issues/11381#issuecomment-481239700][Make Emacs not crazy slow under TRAMP]].

Yes, this is [[https://github.com/bbatsov/projectile/issues/1232#issuecomment-683449873][still needed]].
#+begin_src emacs-lisp :tangle yes
(defadvice projectile-project-root (around ignore-remote first activate)
  (unless (file-remote-p default-directory 'no-identification) ad-do-it))
#+end_src

This is supposedly [[https://www.emacswiki.org/emacs/TrampMode][faster than the default]], =scp=.
#+begin_src emacs-lisp :tangle yes
(setq tramp-default-method "ssh")
#+end_src

SSH controlmaster settings are set in =~/.ssh/config=.
#+begin_src emacs-lisp :tangle yes
(setq tramp-use-ssh-controlmaster-options nil)
#+end_src

This will put in effect =PATH= changes in the remote =~/.profile=.
#+begin_src emacs-lisp :tangle yes
(add-to-list 'tramp-remote-path 'tramp-own-remote-path)
#+end_src

Store TRAMP auto-save files locally.
#+begin_src emacs-lisp :tangle yes
(setq tramp-auto-save-directory
      (expand-file-name "tramp-auto-save" user-emacs-directory))
#+end_src

A more representative name for this file.
#+begin_src emacs-lisp :tangle yes
(setq tramp-persistency-file-name
      (expand-file-name "tramp-connection-history" user-emacs-directory))
#+end_src

Cache SSH passwords during the whole Emacs session.
#+begin_src emacs-lisp :tangle yes
(setq password-cache-expiry nil)
#+end_src

Reuse SSH connections. Taken from the [[https://www.gnu.org/software/emacs/manual/html_node/tramp/Frequently-Asked-Questions.html][TRAMP FAQ]].

Not tangled for now because it seems to affect remote LSP buffers under
=rust-analyzer=.

[2020-08-17 Mon] Tangling this again to see if it helps with TRAMP slowness and
freezes.
#+begin_src emacs-lisp :tangle no
(customize-set-variable 'tramp-ssh-controlmaster-options
                        (concat
                         "-o ControlPath=/tmp/ssh-tramp-%%r@%%h:%%p "
                         "-o ControlMaster=auto -o ControlPersist=yes"))
#+end_src

** counsel-tramp
#+begin_src emacs-lisp :tangle yes
(use-package counsel-tramp)
#+end_src

* Server
#+begin_src emacs-lisp :tangle yes
(require 'server)

(unless (server-running-p)
  (server-start))
#+end_src

* Options
#+begin_src emacs-lisp :tangle yes
;; Don't append customizations to init.el.
(setq custom-file mpereira/custom-file)
(load custom-file 'noerror)

;; Don't ask whether custom themes are safe.
(setq custom-safe-themes t)

;; Avoid loading old bytecode instead of newer source.
(setq load-prefer-newer t)

;; Automatically scroll compilation buffers to the bottom.
(setq compilation-scroll-output t)

;; Show CRLF characters.
;; http://pragmaticemacs.com/emacs/dealing-with-dos-line-endings/
(setq inhibit-eol-conversion t)

;; Enable narrowing commands.
(put 'narrow-to-region 'disabled nil)

;; Don't complain when calling `list-timers'.
(put 'list-timers 'disabled nil)

;; Show matching parens.
(setq show-paren-delay 0)
(show-paren-mode 1)

;; Disable eldoc.
(global-eldoc-mode -1)

;; Break lines automatically in "text" buffers.
(add-hook 'text-mode-hook 'auto-fill-mode)

;; Highlight current line.
(global-hl-line-mode t)

;; Provide undo/redo commands for window changes.
(winner-mode t)

;; Don't lock files.
(setq create-lockfiles nil)

;; Make Finder's "Open with Emacs" create a buffer in the existing Emacs frame.
(setq ns-pop-up-frames nil)

;; macOS modifiers.
(when (display-graphic-p)
  (setq mac-command-modifier 'meta)
  ;; Setting "Option" to nil allows me to type umlauts with "Option+u".
  (setq mac-option-modifier nil)
  (setq mac-control-modifier 'control)
  (setq ns-function-modifier 'hyper))

;; By default Emacs thinks a sentence is a full-stop followed by 2 spaces. Make
;; it a full-stop and 1 space.
(setq sentence-end-double-space nil)

;; Switch to help buffer when it's opened.
(setq help-window-select t)

;; Don't recenter buffer point when point goes outside window. This prevents
;; centering the buffer when scrolling down its last line.
(setq scroll-conservatively 100)

;; Keep cursor position when scrolling.
(setq scroll-preserve-screen-position 1)

(dolist (hook '(prog-mode-hook text-mode-hook))
  (add-hook hook #'mpereira/enable-line-numbers))

;; Better unique buffer names for files with the same base name.
(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)

;; Remember point position between sessions.
(require 'saveplace)
(save-place-mode t)

;; Save a bunch of session state stuff.
(require 'savehist)
(setq savehist-additional-variables '(regexp-search-ring)
      savehist-autosave-interval 60
      savehist-file (expand-file-name "savehist" user-emacs-directory))
(savehist-mode t)

;; `setq', `setq-default' and `setq-local' don't seem to work with symbol
;; variables, hence the absence of a `dolist' here.
(setq-default whitespace-line-column mpereira/fill-column
              fill-column mpereira/fill-column
              comment-column mpereira/fill-column)

(setq emacs-lisp-docstring-fill-column 'fill-column)

;; UTF8 stuff.
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)

;; Tab first tries to indent the current line, and if the line was already
;; indented, then try to complete the thing at point.
(setq tab-always-indent 'complete)

;; Make it impossible to insert tabs.
(setq-default indent-tabs-mode nil)

;; Make TABs be displayed with a width of 2.
(setq-default tab-width 2)

;; Week start on monday.
(setq calendar-week-start-day 1)

(setq select-enable-clipboard t
      select-enable-primary t
      save-interprogram-paste-before-kill t
      apropos-do-all t
      mouse-yank-at-point t
      require-final-newline t
      save-place-file (concat user-emacs-directory "places"))

(setq display-time-world-list '(("Europe/Berlin" "Hamburg")
                                ("America/Sao_Paulo" "São Paulo")
                                ("America/Los_Angeles" "San Francisco")))
#+end_src
** File backups
=make-backup-files= and =auto-save-default= are set to =t= by default.

#+begin_src emacs-lisp :tangle yes
(setq backup-directory-alist `(("." . ,(concat user-emacs-directory "file-backups"))))
(setq tramp-backup-directory-alist `(("." . ,(concat user-emacs-directory "remote-file-backups"))))
(setq auto-save-file-name-transforms `((".*" ,(concat user-emacs-directory "auto-saves") t)))
#+end_src

* Performance
** Increase the amount of data read from processes
https://emacs-lsp.github.io/lsp-mode/page/performance/
#+begin_src emacs-lisp :tangle yes
(setq read-process-output-max (* 1024 1024)) ; 1mb.
#+end_src

** Asynchronous =undo-tree= history save
I found that ~undo-tree-save-history-from-hook~, which =undo-tree= calls via the
~write-file-functions~ hook (called on every file save), took 1-2 seconds on any
non-trivial org mode buffers. This was a special nuisance when making small
changes in small indirect buffers.

The following replaces ~undo-tree-save-history-from-hook~ with an asynchronous
version.

#+begin_src emacs-lisp :tangle yes
(use-package undo-tree)

(defvar async-undo-tree-save-history-cached-load-path
  (when-let ((undo-tree-library (locate-library "undo-tree")))
    (file-name-directory undo-tree-library)))

(defun async-undo-tree-save-history ()
  "TODO: docstring."
  (interactive)
  (when async-undo-tree-save-history-cached-load-path
    (let ((file-name (buffer-file-name)))
      (async-start
       `(lambda ()
          (if (stringp ,file-name)
              (list 'ok
                    (list :output (with-output-to-string
                                    (add-to-list
                                     'load-path
                                     ,async-undo-tree-save-history-cached-load-path)
                                    (require 'undo-tree)
                                    (find-file ,file-name)
                                    (undo-tree-save-history-from-hook))
                          :messages (with-current-buffer "*Messages*"
                                      (buffer-string))))
            (list 'err
                  (list :output "File name must be string"
                        :messages (with-current-buffer "*Messages*"
                                    (buffer-string))))))
       `(lambda (result)
          (let ((outcome (car result))
                (messages (plist-get (cadr result) :messages))
                (output (plist-get (cadr result) :output))
                (inhibit-message t))
            (message
             (cond
              ((eq 'ok outcome)
               "undo-tree history saved asynchronously for %s%s%s")
              ((eq 'err outcome)
               "error saving undo-tree history asynchronously for %s%s%s")
              (:else
               "unexpected result from asynchronous undo-tree history save %s%s%s"))
             ,file-name
             (if (string= "" output)
                 ""
               (format "\noutput:\n%s" output))
             (if (string

本源码包内暂不包含可直接显示的源代码文件,请下载源码包。