资源说明: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
本源码包内暂不包含可直接显示的源代码文件,请下载源码包。