Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up| ;; -*- lexical-binding: t -*- | |
| ;; To see the outline of this file, run M-x outline-minor-mode and | |
| ;; then press C-c @ C-t. To also show the top-level functions and | |
| ;; variable declarations in each section, run M-x occur with the | |
| ;; following query: ^;;;;* \|^( | |
| ;;; Load built-in utility libraries | |
| (require 'cl-lib) | |
| (require 'map) | |
| (require 'subr-x) | |
| ;;; Define Radian customization group | |
| (defgroup radian nil | |
| "Customize your Radian Emacs experience." | |
| :prefix "radian-" | |
| :group 'emacs | |
| :link '(url-link :tag "GitHub" "https://github.com/raxod502/radian")) | |
| ;;; Define utility functions and variables | |
| (defvar radian-directory (file-name-directory | |
| (directory-file-name | |
| (file-name-directory | |
| radian-lib-file))) | |
| "Path to the Radian Git repository.") | |
| (defmacro radian-protect-macros (&rest body) | |
| "Eval BODY, protecting macros from incorrect expansion. | |
| Any code in a `with-eval-after-load' form that uses macros | |
| defined in the form being `with-eval-after-load'ed should be | |
| wrapped in this macro; otherwise, its correct evaluation is not | |
| guaranteed by Elisp." | |
| (declare (indent 0)) | |
| `(eval '(progn ,@body))) | |
| (defmacro radian-defadvice (name arglist where place docstring &rest body) | |
| "Define an advice called NAME and add it to a function. | |
| ARGLIST is as in `defun'. WHERE is a keyword as passed to | |
| `advice-add', and PLACE is the function to which to add the | |
| advice, like in `advice-add'. DOCSTRING and BODY are as in | |
| `defun'." | |
| (declare (indent 2) | |
| (doc-string 5)) | |
| (unless (stringp docstring) | |
| (error "radian-defadvice: no docstring provided")) | |
| `(progn | |
| (defun ,name ,arglist | |
| ,(let ((article (if (string-match-p "^:[aeiou]" (symbol-name where)) | |
| "an" | |
| "a"))) | |
| (format "%s\n\nThis is %s `%S' advice for `%S'." | |
| docstring article where | |
| (if (and (listp place) | |
| (memq (car place) ''function)) | |
| (cadr place) | |
| place))) | |
| ,@body) | |
| (advice-add ',place ',where #',name) | |
| ',name)) | |
| (defmacro radian-defhook (name arglist hook docstring &rest body) | |
| "Define a function called NAME and add it to a hook. | |
| ARGLIST is as in `defun'. HOOK is the hook to which to add the | |
| function. DOCSTRING and BODY are as in `defun'." | |
| (declare (indent 2) | |
| (doc-string 4)) | |
| (unless (string-match-p "-hook$" (symbol-name hook)) | |
| (error "Symbol `%S' is not a hook" hook)) | |
| (unless (stringp docstring) | |
| (error "radian-defhook: no docstring provided")) | |
| `(progn | |
| (defun ,name ,arglist | |
| ,(format "%s\n\nThis function is for use in `%S'." | |
| docstring hook) | |
| ,@body) | |
| (add-hook ',hook ',name))) | |
| (defmacro radian-with-operating-system (os &rest body) | |
| "If OS matches the system type, eval and return BODY. Else return nil. | |
| Allowable values for OS (not quoted): | |
| - macOS | |
| - windows | |
| - unix | |
| - linux" | |
| (declare (indent 1)) | |
| (let ((check | |
| (pcase os | |
| (`unix (not (memq system-type '(ms-dos windows-nt cygwin)))) | |
| (`macOS (eq system-type 'darwin)) | |
| (`linux (not (memq system-type | |
| '(darwin ms-dos windows-nt cygwin)))) | |
| (`windows (memq system-type '(ms-dos windows-nt cygwin)))))) | |
| (when check | |
| `(progn | |
| ,@body)))) | |
| (defun radian-managed-p (filename) | |
| "Return non-nil if FILENAME is managed by Radian. | |
| This means that FILENAME is a symlink whose target is inside | |
| `radian-directory'." | |
| (let ((truename (file-truename filename))) | |
| (string-prefix-p radian-directory truename | |
| (when (file-name-case-insensitive-p truename) | |
| 'ignore-case)))) | |
| (defmacro radian--with-silent-load (&rest body) | |
| "Execute BODY, silencing any calls to `load' within." | |
| (declare (indent 0)) | |
| `(cl-letf* ((load-orig (symbol-function #'load)) | |
| ((symbol-function #'load) | |
| (lambda (file &optional noerror _nomessage &rest args) | |
| (apply load-orig file noerror 'nomessage args)))) | |
| ,@body)) | |
| (defmacro radian--with-silent-write (&rest body) | |
| "Execute BODY, silencing any calls to `write-region' within." | |
| (declare (indent 0)) | |
| `(cl-letf* ((write-region-orig (symbol-function #'write-region)) | |
| ((symbol-function #'write-region) | |
| (lambda (start end filename &optional append visit lockname | |
| mustbenew) | |
| (funcall write-region-orig start end filename append 0 | |
| lockname mustbenew) | |
| (set-buffer-modified-p nil) | |
| (set-visited-file-modtime))) | |
| ((symbol-function #'message) #'ignore)) | |
| ,@body)) | |
| (defun radian--random-string () | |
| "Return a random string designed to be globally unique." | |
| (md5 (format "%s%s%s%s" | |
| (system-name) (emacs-pid) (current-time) (random)))) | |
| (defun radian--list-of-strings-p (obj) | |
| "Return non-nil if OBJ is a list of strings." | |
| (and (listp obj) | |
| (cl-every #'stringp obj))) | |
| ;;; Define hooks and load local configuration | |
| ;; Reset the value of this variable so that stale functions don't | |
| ;; stick around. | |
| (setq radian--finalize-init-hook nil) | |
| (defcustom radian-before-straight-hook nil | |
| "Hook run just before Radian bootstraps straight.el." | |
| :type 'hook) | |
| ;; Allow binding this variable dynamically before straight.el has been | |
| ;; loaded. | |
| (defvar straight-current-profile) | |
| (defun radian--run-hook (hook) | |
| "Run the given local init HOOK, a symbol. | |
| This delegates to `run-hooks', binding `straight-current-profile' | |
| appropriately." | |
| (let ((straight-current-profile 'radian-local)) | |
| (run-hooks hook))) | |
| ;; Allow to disable local customizations with a | |
| ;; command-line argument. | |
| (if (member "--no-local" command-line-args) | |
| ;; Make sure to delete --no-local from the list, because | |
| ;; otherwise Emacs will issue a warning about the unknown | |
| ;; argument. | |
| (setq command-line-args | |
| (delete "--no-local" command-line-args)) | |
| ;; Load local customizations. | |
| (load radian-local-init-file 'noerror 'nomessage)) | |
| ;;; Disable GC during startup | |
| ;; Disabling GC (by setting `gc-cons-threshold' to a very large value, | |
| ;; in this case 500MB) during startup is said to improve startup time | |
| ;; by reducing the number of GC runs. | |
| (defvar radian--orig-gc-cons-threshold gc-cons-threshold | |
| "Original value of `gc-cons-threshold'.") | |
| (radian-defhook radian--reenable-gc () | |
| radian--finalize-init-hook | |
| "Reset `gc-cons-threshold' to its original value. | |
| Otherwise, Emacs will just get slower and slower over time." | |
| (setq gc-cons-threshold radian--orig-gc-cons-threshold)) | |
| (setq gc-cons-threshold (* 5 1000 1000)) | |
| ;;; Networking | |
| ;; Use `with-eval-after-load' instead of `use-feature' because we have | |
| ;; not yet set up package management. | |
| ;; Feature `gnutls' provides support for SSL/TLS connections, using | |
| ;; the GnuTLS library. | |
| (with-eval-after-load 'gnutls | |
| ;; Do not allow insecure TLS connections. | |
| (setq gnutls-verify-error t) | |
| ;; Bump the required security level for TLS to an acceptably modern | |
| ;; value. | |
| (setq gnutls-min-prime-bits 3072)) | |
| ;; Feature `url-http' is a library for making HTTP requests. | |
| (with-eval-after-load 'url-http | |
| (radian-defadvice radian--no-query-on-http-kill | |
| (buffer) | |
| :filter-return url-http | |
| "Disable query-on-exit for all network connections. | |
| This prevents Emacs shutdown from being interrupted just because | |
| there is a pending network request. (It also works around a bug | |
| in `anaconda-mode' which causes unanswered network connections to | |
| pile up, and then interrupt Emacs shutdown.)" | |
| (prog1 buffer | |
| (set-process-query-on-exit-flag | |
| (get-buffer-process buffer) nil)))) | |
| ;;; Set up package management | |
| ;;;; straight.el | |
| ;; Tell straight.el about the profiles we are going to be using. | |
| (setq straight-profiles | |
| '(;; Packages registered in this file. | |
| (radian . "radian.el") | |
| ;; Packages registered in the local init-file during hooks. | |
| (radian-local . "radian-local.el") | |
| ;; Packages registered interactively. | |
| (nil . "default.el"))) | |
| ;; Pretend to dynamically bind `straight-current-profile' to `radian' | |
| ;; over the init-file. We do this to avoid having straight.el | |
| ;; configuration mentioned in the top-level init-file. | |
| (radian-defhook radian--reset-straight-current-profile () | |
| radian--finalize-init-hook | |
| "Reset `straight-current-profile' to nil. | |
| This function is used on `radian--finalize-init-hook' to emulate | |
| binding the variable dynamically over the entire init-file." | |
| (setq straight-current-profile nil)) | |
| (setq straight-current-profile 'radian) | |
| ;; Treat loading the init-file as a transaction. See the straight.el | |
| ;; documentation for more information about this. | |
| (radian-defhook radian--finalize-straight-transaction () | |
| radian--finalize-init-hook | |
| "Finalize the init-file's straight.el transaction." | |
| (setq straight-treat-as-init nil) | |
| (straight-finalize-transaction)) | |
| (setq straight-treat-as-init t) | |
| ;; Use the develop branch of straight.el on Radian's develop branch. | |
| ;; (On Radian's master branch, we use the master branch of | |
| ;; straight.el.) | |
| (setq straight-repository-branch "develop") | |
| ;; If watchexec and Python are installed, use file watchers to detect | |
| ;; package modifications. This saves time at startup. Otherwise, use | |
| ;; the less robust but equally fast `before-save-hook' modification | |
| ;; checker. In both cases, allow using find(1) when explicitly | |
| ;; requested. | |
| (if (and (executable-find "watchexec") | |
| (executable-find "python3")) | |
| (setq straight-check-for-modifications '(watch-files find-when-checking)) | |
| (setq straight-check-for-modifications '(check-on-save find-when-checking))) | |
| ;; Use GNU ELPA mirror so that packages like AUCTeX will work. | |
| (setq straight-recipes-gnu-elpa-use-mirror t) | |
| (radian--run-hook 'radian-before-straight-hook) | |
| ;; Bootstrap the package manager, straight.el. | |
| (defvar bootstrap-version) | |
| (let ((bootstrap-file | |
| (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) | |
| (bootstrap-version 5)) | |
| (unless (file-exists-p bootstrap-file) | |
| (with-current-buffer | |
| (url-retrieve-synchronously | |
| "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" | |
| 'silent 'inhibit-cookies) | |
| (goto-char (point-max)) | |
| (eval-print-last-sexp))) | |
| (load bootstrap-file nil 'nomessage)) | |
| ;;;; use-package | |
| ;; Package `use-package' provides a handy macro by the same name which | |
| ;; is essentially a wrapper around `with-eval-after-load' with a lot | |
| ;; of handy syntactic sugar and useful features. | |
| (straight-use-package 'use-package) | |
| ;; When configuring a feature with `use-package', also tell | |
| ;; straight.el to install a package of the same name, unless otherwise | |
| ;; specified using the `:straight' keyword. | |
| (setq straight-use-package-by-default t) | |
| ;; Tell `use-package' to always load features lazily unless told | |
| ;; otherwise. It's nicer to have this kind of thing be deterministic: | |
| ;; if `:demand' is present, the loading is eager; otherwise, the | |
| ;; loading is lazy. See | |
| ;; https://github.com/jwiegley/use-package#notes-about-lazy-loading. | |
| (setq use-package-always-defer t) | |
| (defmacro use-feature (name &rest args) | |
| "Like `use-package', but with `straight-use-package-by-default' disabled." | |
| (declare (indent defun)) | |
| `(use-package ,name | |
| :straight nil | |
| ,@args)) | |
| ;; Package `blackout' provides a convenient function for customizing | |
| ;; mode lighters. It supports both major and minor modes with the same | |
| ;; interface, and includes `use-package' integration. The features are | |
| ;; a strict superset of those provided by similar packages `diminish', | |
| ;; `delight', and `dim'. | |
| (use-package blackout | |
| :straight (:host github :repo "raxod502/blackout") | |
| :demand t) | |
| ;;;; straight.el configuration | |
| ;; Feature `straight-x' from package `straight' provides | |
| ;; experimental/unstable extensions to straight.el which are not yet | |
| ;; ready for official inclusion. | |
| (use-feature straight-x | |
| ;; Add an autoload for this extremely useful command. | |
| :commands (straight-x-fetch-all)) | |
| ;;; Configure ~/.emacs.d paths | |
| ;; Package `no-littering' changes the default paths for lots of | |
| ;; different packages, with the net result that the ~/.emacs.d folder | |
| ;; is much more clean and organized. | |
| (use-package no-littering | |
| :demand t) | |
| ;;; Prevent Emacs-provided Org from being loaded | |
| ;; The following is a temporary hack until straight.el supports | |
| ;; building Org, see: | |
| ;; | |
| ;; * https://github.com/raxod502/straight.el/issues/211 | |
| ;; * https://github.com/raxod502/radian/issues/410 | |
| ;; | |
| ;; There are three things missing from our version of Org: the | |
| ;; functions `org-git-version' and `org-release', and the feature | |
| ;; `org-version'. We provide all three of those ourself, therefore. | |
| ;; Package `git' is a library providing convenience functions for | |
| ;; running Git. | |
| (use-package git) | |
| (defun org-git-version () | |
| "The Git version of org-mode. | |
| Inserted by installing org-mode or when a release is made." | |
| (require 'git) | |
| (let ((git-repo (expand-file-name | |
| "straight/repos/org/" user-emacs-directory))) | |
| (string-trim | |
| (git-run "describe" | |
| "--match=release\*" | |
| "--abbrev=6" | |
| "HEAD")))) | |
| (defun org-release () | |
| "The release version of org-mode. | |
| Inserted by installing org-mode or when a release is made." | |
| (require 'git) | |
| (let ((git-repo (expand-file-name | |
| "straight/repos/org/" user-emacs-directory))) | |
| (string-trim | |
| (string-remove-prefix | |
| "release_" | |
| (git-run "describe" | |
| "--match=release\*" | |
| "--abbrev=0" | |
| "HEAD"))))) | |
| (provide 'org-version) | |
| ;; Our real configuration for Org comes much later. Doing this now | |
| ;; means that if any packages that are installed in the meantime | |
| ;; depend on Org, they will not accidentally cause the Emacs-provided | |
| ;; (outdated and duplicated) version of Org to be loaded before the | |
| ;; real one is registered. | |
| (straight-use-package 'org) | |
| ;;; el-patch | |
| ;; Package `el-patch' provides a way to override the definition of an | |
| ;; internal function from another package by providing an s-expression | |
| ;; based diff which can later be validated to ensure that the upstream | |
| ;; definition has not changed. | |
| (use-package el-patch | |
| :straight (:host github | |
| :repo "raxod502/el-patch" | |
| :branch "develop") | |
| :demand t) | |
| ;;; Keybindings | |
| ;; Package `bind-key' provides a macro by the same name (along with | |
| ;; `bind-key*', `bind-keys', `bind-keys*', and `unbind-key') which | |
| ;; provides a much prettier API for manipulating keymaps than | |
| ;; `define-key' and `global-set-key' do. It's also the same API that | |
| ;; `:bind' and similar keywords in `use-package' use. | |
| (use-package bind-key) | |
| (defvar radian-keymap (make-sparse-keymap) | |
| "Keymap for Radian commands that should be put under a prefix. | |
| This keymap is bound under M-P.") | |
| (bind-key "M-P" radian-keymap) | |
| (defmacro radian-bind-key (key-name command &optional predicate) | |
| "Bind a key in `radian-keymap'." | |
| `(bind-key ,key-name ,command radian-keymap ,predicate)) | |
| (defun radian-join-keys (&rest keys) | |
| "Join key sequences. Empty strings and nils are discarded. | |
| \(radian--join-keys \"M-P e\" \"e i\") => \"M-P e e i\" | |
| \(radian--join-keys \"M-P\" \"\" \"e i\") => \"M-P e i\"" | |
| (string-join (remove "" (mapcar #'string-trim (remove nil keys))) " ")) | |
| ;;; Environment | |
| ;;;; Environment variables | |
| (defun radian-source-profile () | |
| "Load ~/.profile and set environment variables exported therein." | |
| (interactive) | |
| (let ((profile-file "~/.profile") | |
| (buf-name " *radian-env-output*")) | |
| (when (and profile-file | |
| (file-exists-p profile-file) | |
| (executable-find "python")) | |
| (ignore-errors (kill-buffer buf-name)) | |
| (with-current-buffer (get-buffer-create buf-name) | |
| (let* ((python-script | |
| (expand-file-name "scripts/print_env.py" radian-directory)) | |
| (delimiter (radian--random-string)) | |
| (sh-script (format ". %s && %s %s" | |
| (shell-quote-argument | |
| (expand-file-name profile-file)) | |
| (shell-quote-argument python-script) | |
| (shell-quote-argument delimiter))) | |
| (return (call-process "sh" nil t nil "-c" sh-script)) | |
| (found-delimiter | |
| (progn | |
| (goto-char (point-min)) | |
| (search-forward delimiter nil 'noerror)))) | |
| (if (and (= 0 return) found-delimiter) | |
| (let* ((results (split-string | |
| (buffer-string) (regexp-quote delimiter))) | |
| (results (cl-subseq results 1 (1- (length results))))) | |
| (if (cl-evenp (length results)) | |
| (cl-loop for (var value) on results by #'cddr do | |
| (setenv var value) | |
| (when (string= var "PATH") | |
| (setq exec-path (parse-colon-path value)))) | |
| (message | |
| "Loading %s produced malformed result; see buffer %S" | |
| profile-file | |
| buf-name))) | |
| (message "Failed to load %s; see buffer %S" | |
| profile-file | |
| buf-name))))))) | |
| (defvar radian--source-profile-timer | |
| (run-at-time 1 nil #'radian-source-profile) | |
| "Timer used to run `radian-source-profile'. | |
| We shouldn't need environment variables to be set correctly | |
| during init, so deferring their processing saves some time at | |
| startup.") | |
| ;;;; Clipboard integration | |
| ;; On macOS, clipboard integration works out of the box in windowed | |
| ;; mode but not terminal mode. The following code to fix it was | |
| ;; originally based on [1], and then modified based on [2]. | |
| ;; | |
| ;; [1]: https://gist.github.com/the-kenny/267162 | |
| ;; [2]: https://emacs.stackexchange.com/q/26471/12534 | |
| (radian-with-operating-system macOS | |
| (unless (display-graphic-p) | |
| (defvar radian--clipboard-last-copy nil | |
| "The last text that was copied to the system clipboard. | |
| This is used to prevent duplicate entries in the kill ring.") | |
| (defun radian--clipboard-paste () | |
| "Return the contents of the macOS clipboard, as a string." | |
| (let* (;; Setting `default-directory' to a directory that is | |
| ;; sure to exist means that this code won't error out | |
| ;; when the directory for the current buffer does not | |
| ;; exist. | |
| (default-directory "/") | |
| ;; Command pbpaste returns the clipboard contents as a | |
| ;; string. | |
| (text (shell-command-to-string "pbpaste"))) | |
| ;; If this function returns nil then the system clipboard is | |
| ;; ignored and the first element in the kill ring (which, if | |
| ;; the system clipboard has not been modified since the last | |
| ;; kill, will be the same) is used instead. Including this | |
| ;; `unless' clause prevents you from getting the same text | |
| ;; yanked the first time you run `yank-pop'. (Of course, this | |
| ;; is less relevant due to `counsel-yank-pop', but still is | |
| ;; definitely the correct behavior.) | |
| (unless (string= text radian--clipboard-last-copy) | |
| text))) | |
| (defun radian--clipboard-copy (text) | |
| "Set the contents of the macOS clipboard to given TEXT string." | |
| (let* (;; Setting `default-directory' to a directory that is | |
| ;; sure to exist means that this code won't error out | |
| ;; when the directory for the current buffer does not | |
| ;; exist. | |
| (default-directory "/") | |
| ;; Setting `process-connection-type' makes Emacs use a pipe to | |
| ;; communicate with pbcopy, rather than a pty (which is | |
| ;; overkill). | |
| (process-connection-type nil) | |
| ;; The nil argument tells Emacs to discard stdout and | |
| ;; stderr. Note, we aren't using `call-process' here | |
| ;; because we want this command to be asynchronous. | |
| ;; | |
| ;; Command pbcopy writes stdin to the clipboard until it | |
| ;; receives EOF. | |
| (proc (start-process "pbcopy" nil "pbcopy"))) | |
| (process-send-string proc text) | |
| (process-send-eof proc)) | |
| (setq radian--clipboard-last-copy text)) | |
| (setq interprogram-paste-function #'radian--clipboard-paste) | |
| (setq interprogram-cut-function #'radian--clipboard-copy))) | |
| ;; If you have something on the system clipboard, and then kill | |
| ;; something in Emacs, then by default whatever you had on the system | |
| ;; clipboard is gone and there is no way to get it back. Setting the | |
| ;; following option makes it so that when you kill something in Emacs, | |
| ;; whatever was previously on the system clipboard is pushed into the | |
| ;; kill ring. This way, you can paste it with `yank-pop'. | |
| (setq save-interprogram-paste-before-kill t) | |
| ;;;; Mouse integration | |
| ;; On macOS, mouse integration works out of the box in windowed mode | |
| ;; but not terminal mode. The following code to fix it was based on | |
| ;; https://stackoverflow.com/a/8859057/3538165. | |
| (radian-with-operating-system macOS | |
| (unless (display-graphic-p) | |
| ;; Enable basic mouse support (click and drag). | |
| (xterm-mouse-mode t) | |
| ;; Note that the reason for the next two functions is that | |
| ;; `scroll-down' and `scroll-up' scroll by a "near full screen" by | |
| ;; default, whereas we want a single line. | |
| (defun radian-scroll-down () | |
| "Scroll down one line." | |
| (interactive) | |
| (scroll-down 1)) | |
| (defun radian-scroll-up () | |
| "Scroll up one line." | |
| (interactive) | |
| (scroll-up 1)) | |
| ;; Enable scrolling with the mouse wheel. | |
| (bind-keys ("<mouse-4>" . radian-scroll-down) | |
| ("<mouse-5>" . radian-scroll-up)))) | |
| ;;; Candidate selection | |
| ;; Package `ivy' provides a user interface for choosing from a list of | |
| ;; options by typing a query to narrow the list, and then selecting | |
| ;; one of the remaining candidates. This offers a significant | |
| ;; improvement over the default Emacs interface for candidate | |
| ;; selection. | |
| (use-package ivy | |
| ;; Use my fork until https://github.com/abo-abo/swiper/issues/1632 | |
| ;; is fixed. | |
| :straight (:host github :repo "abo-abo/swiper" | |
| :files (:defaults | |
| (:exclude "swiper.el" "counsel.el" "ivy-hydra.el") | |
| "doc/ivy-help.org") | |
| :fork (:repo "raxod502/swiper" :branch "fork/1")) | |
| :init/el-patch | |
| (defvar ivy-mode-map | |
| (let ((map (make-sparse-keymap))) | |
| (define-key map [remap switch-to-buffer] | |
| 'ivy-switch-buffer) | |
| (define-key map [remap switch-to-buffer-other-window] | |
| 'ivy-switch-buffer-other-window) | |
| map) | |
| "Keymap for `ivy-mode'.") | |
| (define-minor-mode ivy-mode | |
| (el-patch-concat | |
| "Toggle Ivy mode on or off. | |
| Turn Ivy mode on if ARG is positive, off otherwise. | |
| Turning on Ivy mode sets `completing-read-function' to | |
| `ivy-completing-read'. | |
| Global bindings: | |
| \\{ivy-mode-map} | |
| Minibuffer bindings: | |
| \\{ivy-minibuffer-map}" | |
| (el-patch-add | |
| "\n\nTo make it easier to lazy-load `ivy', this function | |
| sets `completion-in-region-function' regardless of the value of | |
| `ivy-do-completion-in-region'.")) | |
| :group 'ivy | |
| :global t | |
| :keymap ivy-mode-map | |
| (el-patch-remove | |
| :lighter " ivy") | |
| (if ivy-mode | |
| (progn | |
| (setq completing-read-function 'ivy-completing-read) | |
| (el-patch-splice 2 | |
| (when ivy-do-completion-in-region | |
| (setq completion-in-region-function 'ivy-completion-in-region)))) | |
| (setq completing-read-function 'completing-read-default) | |
| (setq completion-in-region-function 'completion--in-region))) | |
| (ivy-mode +1) | |
| :config | |
| ;; With enough packages loaded, it is easy to get commands like | |
| ;; `describe-symbol' to offer more than 30,000 candidates. Allow | |
| ;; sorting in these cases. | |
| (setq ivy-sort-max-size 50000) | |
| :blackout t) | |
| ;; Package `counsel' provides purpose-built replacements for many | |
| ;; built-in Emacs commands that use enhanced configurations of `ivy' | |
| ;; to provide extra features. | |
| (use-package counsel | |
| :init/el-patch | |
| (defvar counsel-mode-map | |
| (let ((map (make-sparse-keymap))) | |
| (dolist (binding | |
| '((execute-extended-command . counsel-M-x) | |
| (describe-bindings . counsel-descbinds) | |
| (el-patch-remove | |
| (describe-function . counsel-describe-function) | |
| (describe-variable . counsel-describe-variable)) | |
| (apropos-command . counsel-apropos) | |
| (describe-face . counsel-describe-face) | |
| (list-faces-display . counsel-faces) | |
| (find-file . counsel-find-file) | |
| (find-library . counsel-find-library) | |
| (imenu . counsel-imenu) | |
| (load-library . counsel-load-library) | |
| (load-theme . counsel-load-theme) | |
| (yank-pop . counsel-yank-pop) | |
| (info-lookup-symbol . counsel-info-lookup-symbol) | |
| (pop-to-mark-command . counsel-mark-ring) | |
| (bookmark-jump . counsel-bookmark))) | |
| (define-key map (vector 'remap (car binding)) (cdr binding))) | |
| map) | |
| (el-patch-concat | |
| "Map for `counsel-mode'. | |
| Remaps built-in functions to counsel replacements." | |
| (el-patch-add | |
| "\n\nBindings that are remapped by `helpful' have been removed."))) | |
| (defcustom counsel-mode-override-describe-bindings nil | |
| "Whether to override `describe-bindings' when `counsel-mode' is active." | |
| :group 'ivy | |
| :type 'boolean) | |
| (define-minor-mode counsel-mode | |
| "Toggle Counsel mode on or off. | |
| Turn Counsel mode on if ARG is positive, off otherwise. Counsel | |
| mode remaps built-in emacs functions that have counsel | |
| replacements. | |
| Local bindings (`counsel-mode-map'): | |
| \\{counsel-mode-map}" | |
| :group 'ivy | |
| :global t | |
| :keymap counsel-mode-map | |
| (el-patch-remove | |
| :lighter " counsel") | |
| (if counsel-mode | |
| (progn | |
| (when (and (fboundp 'advice-add) | |
| counsel-mode-override-describe-bindings) | |
| (advice-add #'describe-bindings :override #'counsel-descbinds)) | |
| (define-key minibuffer-local-map (kbd "C-r") | |
| 'counsel-minibuffer-history)) | |
| (when (fboundp 'advice-remove) | |
| (advice-remove #'describe-bindings #'counsel-descbinds)))) | |
| :init | |
| (counsel-mode +1) | |
| :bind* (;; Keybinding suggested by the documentation of Counsel, see | |
| ;; https://github.com/abo-abo/swiper. | |
| ("C-c k" . counsel-rg)) | |
| :config/el-patch | |
| (defcustom counsel-rg-base-command | |
| (el-patch-concat | |
| "rg -S --no-heading --line-number --color never " | |
| (el-patch-add | |
| "-z ") | |
| "%s .") | |
| (el-patch-concat | |
| "Alternative to `counsel-ag-base-command' using ripgrep. | |
| Note: don't use single quotes for the regex." | |
| (el-patch-add | |
| "\n\nSupport for searching compressed files has been added | |
| by `el-patch'.")) | |
| :type 'string | |
| :group 'ivy) | |
| :blackout t) | |
| ;; Package `prescient' is a library for intelligent sorting and | |
| ;; filtering in various contexts. | |
| (use-package prescient | |
| :config | |
| ;; Remember usage statistics across Emacs sessions. | |
| (prescient-persist-mode +1)) | |
| ;; Package `ivy-prescient' provides intelligent sorting and filtering | |
| ;; for candidates in Ivy menus. | |
| (use-package ivy-prescient | |
| :demand t | |
| :after ivy | |
| :config | |
| ;; Use `prescient' for Ivy menus. | |
| (ivy-prescient-mode +1)) | |
| ;;; Window management | |
| (radian-defadvice radian--advice-keyboard-quit-minibuffer-first | |
| (keyboard-quit) | |
| :around keyboard-quit | |
| "Cause \\[keyboard-quit] to exit the minibuffer, if it is active. | |
| Normally, \\[keyboard-quit] will just act in the current buffer. | |
| This advice modifies the behavior so that it will instead exit an | |
| active minibuffer, even if the minibuffer is not selected." | |
| (if-let ((minibuffer (active-minibuffer-window))) | |
| (with-current-buffer (window-buffer minibuffer) | |
| (minibuffer-keyboard-quit)) | |
| (funcall keyboard-quit))) | |
| ;; Split windows horizontally (into tall subwindows) rather than | |
| ;; vertically (into wide subwindows) by default. | |
| (el-patch-defun split-window-sensibly (&optional window) | |
| "Split WINDOW in a way suitable for `display-buffer'. | |
| WINDOW defaults to the currently selected window. | |
| If `split-height-threshold' specifies an integer, WINDOW is at | |
| least `split-height-threshold' lines tall and can be split | |
| vertically, split WINDOW into two windows one above the other and | |
| return the lower window. Otherwise, if `split-width-threshold' | |
| specifies an integer, WINDOW is at least `split-width-threshold' | |
| columns wide and can be split horizontally, split WINDOW into two | |
| windows side by side and return the window on the right. If this | |
| can't be done either and WINDOW is the only window on its frame, | |
| try to split WINDOW vertically disregarding any value specified | |
| by `split-height-threshold'. If that succeeds, return the lower | |
| window. Return nil otherwise. | |
| By default `display-buffer' routines call this function to split | |
| the largest or least recently used window. To change the default | |
| customize the option `split-window-preferred-function'. | |
| You can enforce this function to not split WINDOW horizontally, | |
| by setting (or binding) the variable `split-width-threshold' to | |
| nil. If, in addition, you set `split-height-threshold' to zero, | |
| chances increase that this function does split WINDOW vertically. | |
| In order to not split WINDOW vertically, set (or bind) the | |
| variable `split-height-threshold' to nil. Additionally, you can | |
| set `split-width-threshold' to zero to make a horizontal split | |
| more likely to occur. | |
| Have a look at the function `window-splittable-p' if you want to | |
| know how `split-window-sensibly' determines whether WINDOW can be | |
| split." | |
| (let ((window (or window (selected-window)))) | |
| (or (el-patch-let | |
| (($fst (and (window-splittable-p window) | |
| ;; Split window vertically. | |
| (with-selected-window window | |
| (split-window-below)))) | |
| ($snd (and (window-splittable-p window t) | |
| ;; Split window horizontally. | |
| (with-selected-window window | |
| (split-window-right))))) | |
| (el-patch-swap $fst $snd) | |
| (el-patch-swap $snd $fst)) | |
| (and | |
| ;; If WINDOW is the only usable window on its frame (it | |
| ;; is the only one or, not being the only one, all the | |
| ;; other ones are dedicated) and is not the minibuffer | |
| ;; window, try to split it s/vertically/horizontally | |
| ;; disregarding the value of `split-height-threshold'. | |
| (let ((frame (window-frame window))) | |
| (or | |
| (eq window (frame-root-window frame)) | |
| (catch 'done | |
| (walk-window-tree (lambda (w) | |
| (unless (or (eq w window) | |
| (window-dedicated-p w)) | |
| (throw 'done nil))) | |
| frame) | |
| t))) | |
| (not (window-minibuffer-p window)) | |
| (let ((split-height-threshold 0)) | |
| (when (window-splittable-p window) | |
| (with-selected-window window | |
| (split-window-below)))))))) | |
| ;; Feature `windmove' provides keybindings S-left, S-right, S-up, and | |
| ;; S-down to move between windows. This is much more convenient and | |
| ;; efficient than using the default binding, C-x o, to cycle through | |
| ;; all of them in an essentially unpredictable order. | |
| (use-feature windmove | |
| :demand t | |
| :config | |
| (windmove-default-keybindings)) | |
| ;; Feature `winner' provides an undo/redo stack for window | |
| ;; configurations, with undo and redo being C-c left and C-c right, | |
| ;; respectively. (Actually "redo" doesn't revert a single undo, but | |
| ;; rather a whole sequence of them.) For instance, you can use C-x 1 | |
| ;; to focus on a particular window, then return to your previous | |
| ;; layout with C-c left. | |
| (use-feature winner | |
| :demand t | |
| :config | |
| (winner-mode +1)) | |
| ;; Package `transpose-frame' provides simple commands to mirror, | |
| ;; rotate, and transpose Emacs windows: `flip-frame', `flop-frame', | |
| ;; `transpose-frame', `rotate-frame-clockwise', | |
| ;; `rotate-frame-anticlockwise', `rotate-frame'. | |
| (use-package transpose-frame) | |
| ;; Package `buffer-move' provides simple commands to swap Emacs | |
| ;; windows: `buf-move-up', `buf-move-down', `buf-move-left', | |
| ;; `buf-move-right'. | |
| (use-package buffer-move) | |
| ;; Feature `ibuffer' provides a more modern replacement for the | |
| ;; `list-buffers' command. | |
| (use-feature ibuffer | |
| :bind (([remap list-buffers] . ibuffer))) | |
| ;;; Finding files | |
| ;; Follow symlinks when opening files. This has the concrete impact, | |
| ;; for instance, that when you edit init.el with M-P e e i and then | |
| ;; later do C-x C-f, you will be in the Radian repository instead of | |
| ;; your home directory. | |
| (setq find-file-visit-truename t) | |
| ;; Disable the warning "X and Y are the same file" which normally | |
| ;; appears when you visit a symlinked file by the same name. (Doing | |
| ;; this isn't dangerous, as it will just redirect you to the existing | |
| ;; buffer.) | |
| (setq find-file-suppress-same-file-warnings t) | |
| ;; Feature `saveplace' provides a minor mode for remembering the | |
| ;; location of point in each file you visit, and returning it there | |
| ;; when you find the file again. | |
| (use-feature saveplace | |
| :demand t | |
| :config | |
| (save-place-mode +1) | |
| :config/el-patch | |
| (defun save-place-alist-to-file () | |
| ;; No docstring on this function originally. To clarify, the reason | |
| ;; for this patch is to silence the save. | |
| (el-patch-add | |
| "Write `save-place-alist' to a file, but do so silently.") | |
| (let ((file (expand-file-name save-place-file)) | |
| (coding-system-for-write 'utf-8)) | |
| (with-current-buffer (get-buffer-create " *Saved Places*") | |
| (delete-region (point-min) (point-max)) | |
| (when save-place-forget-unreadable-files | |
| (save-place-forget-unreadable-files)) | |
| (insert (format ";;; -*- coding: %s -*-\n" | |
| (symbol-name coding-system-for-write))) | |
| (let ((print-length nil) | |
| (print-level nil)) | |
| (pp save-place-alist (current-buffer))) | |
| (let ((version-control | |
| (cond | |
| ((null save-place-version-control) nil) | |
| ((eq 'never save-place-version-control) 'never) | |
| ((eq 'nospecial save-place-version-control) version-control) | |
| (t | |
| t)))) | |
| (condition-case nil | |
| ;; Don't use write-file; we don't want this buffer to visit it. | |
| (write-region (point-min) (point-max) file | |
| (el-patch-add nil 'nomsg)) | |
| (file-error (message "Saving places: can't write %s" file))) | |
| (kill-buffer (current-buffer))))))) | |
| ;; Package `projectile' keeps track of a "project" list, which is | |
| ;; automatically added to as you visit Git repositories, Node.js | |
| ;; projects, etc. It then provides commands for quickly navigating | |
| ;; between and within these projects. | |
| (use-package projectile | |
| :init/el-patch | |
| (defcustom projectile-keymap-prefix nil | |
| "Projectile keymap prefix." | |
| :group 'projectile | |
| :type 'string) | |
| (defvar projectile-command-map | |
| (let ((map (make-sparse-keymap))) | |
| (define-key map (kbd "4 a") #'projectile-find-other-file-other-window) | |
| (define-key map (kbd "4 b") #'projectile-switch-to-buffer-other-window) | |
| (define-key map (kbd "4 C-o") #'projectile-display-buffer) | |
| (define-key map (kbd "4 d") #'projectile-find-dir-other-window) | |
| (define-key map (kbd "4 D") #'projectile-dired-other-window) | |
| (define-key map (kbd "4 f") #'projectile-find-file-other-window) | |
| (define-key map (kbd "4 g") #'projectile-find-file-dwim-other-window) | |
| (define-key map (kbd "4 t") #'projectile-find-implementation-or-test-other-window) | |
| (define-key map (kbd "5 a") #'projectile-find-other-file-other-frame) | |
| (define-key map (kbd "5 b") #'projectile-switch-to-buffer-other-frame) | |
| (define-key map (kbd "5 d") #'projectile-find-dir-other-frame) | |
| (define-key map (kbd "5 D") #'projectile-dired-other-frame) | |
| (define-key map (kbd "5 f") #'projectile-find-file-other-frame) | |
| (define-key map (kbd "5 g") #'projectile-find-file-dwim-other-frame) | |
| (define-key map (kbd "5 t") #'projectile-find-implementation-or-test-other-frame) | |
| (define-key map (kbd "!") #'projectile-run-shell-command-in-root) | |
| (define-key map (kbd "&") #'projectile-run-async-shell-command-in-root) | |
| (define-key map (kbd "a") #'projectile-find-other-file) | |
| (define-key map (kbd "b") #'projectile-switch-to-buffer) | |
| (define-key map (kbd "C") #'projectile-configure-project) | |
| (define-key map (kbd "c") #'projectile-compile-project) | |
| (define-key map (kbd "d") #'projectile-find-dir) | |
| (define-key map (kbd "D") #'projectile-dired) | |
| (define-key map (kbd "e") #'projectile-recentf) | |
| (define-key map (kbd "E") #'projectile-edit-dir-locals) | |
| (define-key map (kbd "f") #'projectile-find-file) | |
| (define-key map (kbd "g") #'projectile-find-file-dwim) | |
| (define-key map (kbd "F") #'projectile-find-file-in-known-projects) | |
| (define-key map (kbd "i") #'projectile-invalidate-cache) | |
| (define-key map (kbd "I") #'projectile-ibuffer) | |
| (define-key map (kbd "j") #'projectile-find-tag) | |
| (define-key map (kbd "k") #'projectile-kill-buffers) | |
| (define-key map (kbd "l") #'projectile-find-file-in-directory) | |
| (define-key map (kbd "m") #'projectile-commander) | |
| (define-key map (kbd "o") #'projectile-multi-occur) | |
| (define-key map (kbd "p") #'projectile-switch-project) | |
| (define-key map (kbd "q") #'projectile-switch-open-project) | |
| (define-key map (kbd "P") #'projectile-test-project) | |
| (define-key map (kbd "r") #'projectile-replace) | |
| (define-key map (kbd "R") #'projectile-regenerate-tags) | |
| (define-key map (kbd "s g") #'projectile-grep) | |
| (define-key map (kbd "s r") #'projectile-ripgrep) | |
| (define-key map (kbd "s s") #'projectile-ag) | |
| (define-key map (kbd "S") #'projectile-save-project-buffers) | |
| (define-key map (kbd "t") #'projectile-toggle-between-implementation-and-test) | |
| (define-key map (kbd "T") #'projectile-find-test-file) | |
| (define-key map (kbd "u") #'projectile-run-project) | |
| (define-key map (kbd "v") #'projectile-vc) | |
| (define-key map (kbd "V") #'projectile-browse-dirty-projects) | |
| (define-key map (kbd "x e") #'projectile-run-eshell) | |
| (define-key map (kbd "x i") #'projectile-run-ielm) | |
| (define-key map (kbd "x t") #'projectile-run-term) | |
| (define-key map (kbd "x s") #'projectile-run-shell) | |
| (define-key map (kbd "z") #'projectile-cache-current-file) | |
| (define-key map (kbd "ESC") #'projectile-project-buffers-other-buffer) | |
| map) | |
| "Keymap for Projectile commands after `projectile-keymap-prefix'.") | |
| (fset 'projectile-command-map projectile-command-map) | |
| (defvar projectile-mode-map | |
| (let ((map (make-sparse-keymap))) | |
| (when projectile-keymap-prefix | |
| (define-key map projectile-keymap-prefix 'projectile-command-map)) | |
| (easy-menu-define projectile-mode-menu map | |
| "Menu for Projectile" | |
| '("Projectile" | |
| ["Find file" projectile-find-file] | |
| ["Find file in known projects" projectile-find-file-in-known-projects] | |
| ["Find test file" projectile-find-test-file] | |
| ["Find directory" projectile-find-dir] | |
| ["Find file in directory" projectile-find-file-in-directory] | |
| ["Find other file" projectile-find-other-file] | |
| ["Switch to buffer" projectile-switch-to-buffer] | |
| ["Jump between implementation file and test file" projectile-toggle-between-implementation-and-test] | |
| ["Kill project buffers" projectile-kill-buffers] | |
| ["Recent files" projectile-recentf] | |
| "--" | |
| ["Toggle project wide read-only" projectile-toggle-project-read-only] | |
| ["Edit .dir-locals.el" projectile-edit-dir-locals] | |
| "--" | |
| ["Switch to project" projectile-switch-project] | |
| ["Switch to open project" projectile-switch-open-project] | |
| ["Discover projects in directory" projectile-discover-projects-in-directory] | |
| ["Browse dirty projects" projectile-browse-dirty-projects] | |
| ["Open project in dired" projectile-dired] | |
| "--" | |
| ["Search in project (grep)" projectile-grep] | |
| ["Search in project (ag)" projectile-ag] | |
| ["Replace in project" projectile-replace] | |
| ["Multi-occur in project" projectile-multi-occur] | |
| "--" | |
| ["Run shell" projectile-run-shell] | |
| ["Run eshell" projectile-run-eshell] | |
| ["Run ielm" projectile-run-ielm] | |
| ["Run term" projectile-run-term] | |
| "--" | |
| ["Cache current file" projectile-cache-current-file] | |
| ["Invalidate cache" projectile-invalidate-cache] | |
| ["Regenerate [e|g]tags" projectile-regenerate-tags] | |
| "--" | |
| ["Configure project" projectile-configure-project] | |
| ["Compile project" projectile-compile-project] | |
| ["Test project" projectile-test-project] | |
| ["Run project" projectile-run-project] | |
| "--" | |
| ["Project info" projectile-project-info] | |
| ["About" projectile-version])) | |
| map) | |
| "Keymap for Projectile mode.") | |
| (define-minor-mode projectile-mode | |
| "Minor mode to assist project management and navigation. | |
| When called interactively, toggle `projectile-mode'. With prefix | |
| ARG, enable `projectile-mode' if ARG is positive, otherwise disable | |
| it. | |
| When called from Lisp, enable `projectile-mode' if ARG is omitted, | |
| nil or positive. If ARG is `toggle', toggle `projectile-mode'. | |
| Otherwise behave as if called interactively. | |
| \\{projectile-mode-map}" | |
| (el-patch-remove | |
| :lighter projectile--mode-line) | |
| :keymap projectile-mode-map | |
| :group 'projectile | |
| :require 'projectile | |
| :global t | |
| (cond | |
| (projectile-mode | |
| (el-patch-remove | |
| ;; setup the commander bindings | |
| (projectile-commander-bindings) | |
| ;; initialize the projects cache if needed | |
| (unless projectile-projects-cache | |
| (setq projectile-projects-cache | |
| (or (projectile-unserialize projectile-cache-file) | |
| (make-hash-table :test 'equal)))) | |
| (unless projectile-projects-cache-time | |
| (setq projectile-projects-cache-time | |
| (make-hash-table :test 'equal))) | |
| ;; load the known projects | |
| (projectile-load-known-projects) | |
| ;; update the list of known projects | |
| (projectile--cleanup-known-projects) | |
| (projectile-discover-projects-in-search-path) | |
| (add-hook 'find-file-hook 'projectile-find-file-hook-function) | |
| (add-hook 'projectile-find-dir-hook #'projectile-track-known-projects-find-file-hook t) | |
| (add-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook t t) | |
| (ad-activate 'compilation-find-file) | |
| (ad-activate 'delete-file))) | |
| (el-patch-remove | |
| (t | |
| (remove-hook 'find-file-hook #'projectile-find-file-hook-function) | |
| (remove-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook t) | |
| (ad-deactivate 'compilation-find-file) | |
| (ad-deactivate 'delete-file))))) | |
| :init | |
| ;; Defining the prefix key must be done manually as per | |
| ;; documentation for recent versions of Projectile, see | |
| ;; https://projectile.readthedocs.io/en/latest/installation/. | |
| (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map) | |
| (projectile-mode +1) | |
| :defer 1 | |
| :config | |
| ;; Enable the mode again now that we have all the supporting hooks | |
| ;; and stuff defined. | |
| (projectile-mode +1) | |
| (defun radian--projectile-indexing-method-p (method) | |
| "Non-nil if METHOD is a safe value for `projectile-indexing-method'." | |
| (memq method '(native alien))) | |
| (put 'projectile-indexing-method 'safe-local-variable | |
| #'radian--projectile-indexing-method-p) | |
| :blackout t) | |
| ;; Package `counsel-projectile' provides alternate versions of | |
| ;; Projectile commands which use Counsel. | |
| (use-package counsel-projectile | |
| :init/el-patch | |
| (defcustom counsel-projectile-key-bindings | |
| '((projectile-find-file . counsel-projectile-find-file) | |
| (projectile-find-file-dwim . counsel-projectile-find-file-dwim) | |
| (projectile-find-dir . counsel-projectile-find-dir) | |
| (projectile-switch-to-buffer . counsel-projectile-switch-to-buffer) | |
| (projectile-grep . counsel-projectile-grep) | |
| (projectile-ag . counsel-projectile-ag) | |
| (projectile-ripgrep . counsel-projectile-rg) | |
| (projectile-switch-project . counsel-projectile-switch-project) | |
| (" " . counsel-projectile) | |
| ("si" . counsel-projectile-git-grep) | |
| ("Oc" . counsel-projectile-org-capture) | |
| ("Oa" . counsel-projectile-org-agenda)) | |
| "Alist of counsel-projectile key bindings. | |
| Each element is of the form \(KEY . DEF\) where KEY is either a | |
| key sequence to bind in `projectile-command-map' or a projectile | |
| command to remap in `projectile-mode-map', and DEF is the | |
| counsel-projectile command to which KEY is remapped or bound." | |
| :type '(alist :key-type (choice (function :tag "Projectile command") | |
| key-sequence) | |
| :value-type (function :tag "Counsel-projectile command")) | |
| :group 'counsel-projectile) | |
| (define-minor-mode counsel-projectile-mode | |
| "Toggle Counsel-Projectile mode on or off. | |
| With a prefix argument ARG, enable the mode if ARG is positive, | |
| and disable it otherwise. If called from Lisp, enable the mode | |
| if ARG is omitted or nil, and toggle it if ARG is `toggle'. | |
| Counsel-Projectile mode turns on Projectile mode, thus enabling | |
| all projectile key bindings, and adds the counsel-projectile key | |
| bindings on top of them. | |
| The counsel-projectile key bindings either remap existing | |
| projectile commands to their counsel-projectile replacements or | |
| bind keys to counsel-projectile commands that have no projectile | |
| counterparts." | |
| :group 'counsel-projectile | |
| :require 'counsel-projectile | |
| :global t | |
| (cond | |
| (counsel-projectile-mode | |
| (projectile-mode) | |
| (dolist (binding counsel-projectile-key-bindings) | |
| (if (functionp (car binding)) | |
| (define-key projectile-mode-map `[remap ,(car binding)] (cdr binding)) | |
| (define-key projectile-command-map (car binding) (cdr binding))))) | |
| (t | |
| (dolist (binding counsel-projectile-key-bindings) | |
| (if (functionp (car binding)) | |
| (define-key projectile-mode-map `[remap ,(car binding)] nil) | |
| (define-key projectile-command-map (car binding) nil))) | |
| (projectile-mode -1)))) | |
| :init | |
| (counsel-projectile-mode +1)) | |
| (defun radian--advice-find-file-create-directories | |
| (find-file filename &rest args) | |
| "Automatically create and delete parent directories of new files. | |
| This advice automatically creates the parent directory (or directories) of | |
| the file being visited, if necessary. It also sets a buffer-local | |
| variable so that the user will be prompted to delete the newly | |
| created directories if they kill the buffer without saving it. | |
| This advice has no effect for remote files. | |
| This is an `:around' advice for `find-file' and similar | |
| functions." | |
| (if (file-remote-p filename) | |
| (apply find-file filename args) | |
| (let ((orig-filename filename) | |
| ;; For relative paths where none of the named parent | |
| ;; directories exist, we might get a nil from | |
| ;; `file-name-directory' below, which would be bad. Thus we | |
| ;; expand the path fully. | |
| (filename (expand-file-name filename)) | |
| ;; The variable `dirs-to-delete' is a list of the | |
| ;; directories that will be automatically created by | |
| ;; `make-directory'. We will want to offer to delete these | |
| ;; directories if the user kills the buffer without saving | |
| ;; it. | |
| (dirs-to-delete ())) | |
| ;; If the file already exists, we don't need to worry about | |
| ;; creating any directories. | |
| (unless (file-exists-p filename) | |
| ;; It's easy to figure out how to invoke `make-directory', | |
| ;; because it will automatically create all parent | |
| ;; directories. We just need to ask for the directory | |
| ;; immediately containing the file to be created. | |
| (let* ((dir-to-create (file-name-directory filename)) | |
| ;; However, to find the exact set of directories that | |
| ;; might need to be deleted afterward, we need to | |
| ;; iterate upward through the directory tree until we | |
| ;; find a directory that already exists, starting at | |
| ;; the directory containing the new file. | |
| (current-dir dir-to-create)) | |
| ;; If the directory containing the new file already exists, | |
| ;; nothing needs to be created, and therefore nothing needs | |
| ;; to be destroyed, either. | |
| (while (not (file-exists-p current-dir)) | |
| ;; Otherwise, we'll add that directory onto the list of | |
| ;; directories that are going to be created. | |
| (push current-dir dirs-to-delete) | |
| ;; Now we iterate upwards one directory. The | |
| ;; `directory-file-name' function removes the trailing | |
| ;; slash of the current directory, so that it is viewed as | |
| ;; a file, and then the `file-name-directory' function | |
| ;; returns the directory component in that path (which | |
| ;; means the parent directory). | |
| (setq current-dir (file-name-directory | |
| (directory-file-name current-dir)))) | |
| ;; Only bother trying to create a directory if one does not | |
| ;; already exist. | |
| (unless (file-exists-p dir-to-create) | |
| ;; Make the necessary directory and its parents. | |
| (make-directory dir-to-create 'parents)))) | |
| ;; Call the original `find-file', now that the directory | |
| ;; containing the file to found exists. We make sure to preserve | |
| ;; the return value, so as not to mess up any commands relying | |
| ;; on it. | |
| (prog1 (apply find-file orig-filename args) | |
| ;; If there are directories we want to offer to delete later, | |
| ;; we have more to do. | |
| (when dirs-to-delete | |
| ;; Since we already called `find-file', we're now in the | |
| ;; buffer for the new file. That means we can transfer the | |
| ;; list of directories to possibly delete later into a | |
| ;; buffer-local variable. But we pushed new entries onto the | |
| ;; beginning of `dirs-to-delete', so now we have to reverse | |
| ;; it (in order to later offer to delete directories from | |
| ;; innermost to outermost). | |
| (setq-local radian--dirs-to-delete (reverse dirs-to-delete)) | |
| ;; Now we add a buffer-local hook to offer to delete those | |
| ;; directories when the buffer is killed, but only if it's | |
| ;; appropriate to do so (for instance, only if the | |
| ;; directories still exist and the file still doesn't | |
| ;; exist). | |
| (add-hook 'kill-buffer-hook | |
| #'radian--kill-buffer-delete-directory-if-appropriate | |
| 'append 'local) | |
| ;; The above hook removes itself when it is run, but that | |
| ;; will only happen when the buffer is killed (which might | |
| ;; never happen). Just for cleanliness, we automatically | |
| ;; remove it when the buffer is saved. This hook also | |
| ;; removes itself when run, in addition to removing the | |
| ;; above hook. | |
| (add-hook 'after-save-hook | |
| #'radian--remove-kill-buffer-delete-directory-hook | |
| 'append 'local)))))) | |
| (defun radian--kill-buffer-delete-directory-if-appropriate () | |
| "Delete parent directories if appropriate. | |
| This is a function for `kill-buffer-hook'. If | |
| `radian--advice-find-file-create-directories' created the | |
| directory containing the file for the current buffer | |
| automatically, then offer to delete it. Otherwise, do nothing. | |
| Also clean up related hooks." | |
| (when (and | |
| ;; Stop if the local variables have been killed. | |
| (boundp 'radian--dirs-to-delete) | |
| ;; Stop if there aren't any directories to delete (shouldn't | |
| ;; happen). | |
| radian--dirs-to-delete | |
| ;; Stop if `radian--dirs-to-delete' somehow got set to | |
| ;; something other than a list (shouldn't happen). | |
| (listp radian--dirs-to-delete) | |
| ;; Stop if the current buffer doesn't represent a | |
| ;; file (shouldn't happen). | |
| buffer-file-name | |
| ;; Stop if the buffer has been saved, so that the file | |
| ;; actually exists now. This might happen if the buffer were | |
| ;; saved without `after-save-hook' running, or if the | |
| ;; `find-file'-like function called was `write-file'. | |
| (not (file-exists-p buffer-file-name))) | |
| (cl-dolist (dir-to-delete radian--dirs-to-delete) | |
| ;; Ignore any directories that no longer exist or are malformed. | |
| ;; We don't return immediately if there's a nonexistent | |
| ;; directory, because it might still be useful to offer to | |
| ;; delete other (parent) directories that should be deleted. But | |
| ;; this is an edge case. | |
| (when (and (stringp dir-to-delete) | |
| (file-exists-p dir-to-delete)) | |
| ;; Only delete a directory if the user is OK with it. | |
| (if (y-or-n-p (format "Also delete directory `%s'? " | |
| ;; The `directory-file-name' function | |
| ;; removes the trailing slash. | |
| (directory-file-name dir-to-delete))) | |
| (delete-directory dir-to-delete) | |
| ;; If the user doesn't want to delete a directory, then they | |
| ;; obviously don't want to delete any of its parent | |
| ;; directories, either. | |
| (cl-return))))) | |
| ;; It shouldn't be necessary to remove this hook, since the buffer | |
| ;; is getting killed anyway, but just in case... | |
| (radian--remove-kill-buffer-delete-directory-hook)) | |
| (defun radian--remove-kill-buffer-delete-directory-hook () | |
| "Clean up directory-deletion hooks, if necessary. | |
| This is a function for `after-save-hook'. Remove | |
| `radian--kill-buffer-delete-directory-if-appropriate' from | |
| `kill-buffer-hook', and also remove this function from | |
| `after-save-hook'." | |
| (remove-hook 'kill-buffer-hook | |
| #'radian--kill-buffer-delete-directory-if-appropriate | |
| 'local) | |
| (remove-hook 'after-save-hook | |
| #'radian--remove-kill-buffer-delete-directory-hook | |
| 'local)) | |
| (dolist (fun '(find-file ; C-x C-f | |
| find-alternate-file ; C-x C-v | |
| write-file ; C-x C-w | |
| )) | |
| (advice-add fun :around #'radian--advice-find-file-create-directories)) | |
| (defmacro radian-register-dotfile | |
| (filename &optional keybinding pretty-filename) | |
| "Establish functions and keybindings to open a dotfile. | |
| The FILENAME should be a path relative to the user's home | |
| directory. Two interactive functions are created: one to find the | |
| file in the current window, and one to find it in another window. | |
| If KEYBINDING is non-nil, the first function is bound to that key | |
| sequence after it is prefixed by \"M-P e\", and the second | |
| function is bound to the same key sequence, but prefixed instead | |
| by \"M-P o\". | |
| This is best demonstrated by example. Suppose FILENAME is | |
| \".emacs.d/init.el\" and KEYBINDING is \"e i\". Then | |
| `radian-register-dotfile' will create the interactive functions | |
| `radian-find-init-el' and `radian-find-init-el-other-window', and | |
| it will bind them to the key sequences \"M-P e e i\" and \"M-P o | |
| e i\" respectively. | |
| If PRETTY-FILENAME, a string, is non-nil, then it will be used in | |
| place of \"init-el\" in this example. Otherwise, that string will | |
| be generated automatically from the basename of FILENAME. | |
| To pass something other than a literal string as FILENAME, | |
| unquote it using a comma." | |
| (when (and (listp filename) (eq (car filename) '\,)) | |
| (setq filename (eval (cadr filename)))) | |
| (let* ((bare-filename (replace-regexp-in-string ".*/" "" filename)) | |
| (full-filename (expand-file-name filename "~")) | |
| (defun-name (intern | |
| (replace-regexp-in-string | |
| "-+" | |
| "-" | |
| (concat | |
| "radian-find-" | |
| (or pretty-filename | |
| (replace-regexp-in-string | |
| "[^a-z0-9]" "-" | |
| bare-filename)))))) | |
| (defun-other-window-name | |
| (intern | |
| (concat (symbol-name defun-name) | |
| "-other-window"))) | |
| (docstring (format "Edit file %s." | |
| full-filename)) | |
| (docstring-other-window | |
| (format "Edit file %s, in another window." | |
| full-filename)) | |
| (defun-form `(defun ,defun-name () | |
| ,docstring | |
| (interactive) | |
| (find-file ,full-filename))) | |
| (defun-other-window-form | |
| `(defun ,defun-other-window-name () | |
| ,docstring-other-window | |
| (interactive) | |
| (find-file-other-window ,full-filename))) | |
| (full-keybinding | |
| (when keybinding | |
| (radian-join-keys "e" keybinding))) | |
| (full-other-window-keybinding | |
| (radian-join-keys "o" keybinding))) | |
| `(progn | |
| ,defun-form | |
| ,defun-other-window-form | |
| ,@(when full-keybinding | |
| `((radian-bind-key ,full-keybinding #',defun-name))) | |
| ,@(when full-other-window-keybinding | |
| `((radian-bind-key | |
| ,full-other-window-keybinding | |
| #',defun-other-window-name))) | |
| ;; Return the symbols for the two functions defined. | |
| (list ',defun-name ',defun-other-window-name)))) | |
| ;; Now we register shortcuts to files relevant to Radian. | |
| (radian-register-dotfile ,radian-directory "r a" "radian-repo") | |
| ;; Emacs | |
| (radian-register-dotfile ".emacs.d/init.el" "e i") | |
| (radian-register-dotfile | |
| ,(expand-file-name | |
| "emacs/radian.el" | |
| radian-directory) | |
| "e r") | |
| (radian-register-dotfile | |
| ".emacs.d/straight/versions/radian.el" | |
| "e v" "radian-versions-el") | |
| (radian-register-dotfile ".emacs.d/init.local.el" "e l") | |
| (radian-register-dotfile | |
| ".emacs.d/straight/versions/radian-local.el" | |
| "e V" "radian-local-versions-el") | |
| ;; Git | |
| (radian-register-dotfile ".gitconfig" "g c") | |
| (radian-register-dotfile ".gitexclude" "g e") | |
| (radian-register-dotfile ".gitconfig.local" "g l") | |
| ;; Leiningen | |
| (radian-register-dotfile ".lein/profiles.clj" "l p") | |
| ;; Shell | |
| (radian-register-dotfile ".profile" "p r") | |
| (radian-register-dotfile ".profile.local" "p l") | |
| ;; Tmux | |
| (radian-register-dotfile ".tmux.conf" "t c") | |
| (radian-register-dotfile ".tmux.local.conf" "t l") | |
| ;; Zsh | |
| (radian-register-dotfile ".zshrc" "z r") | |
| (radian-register-dotfile ".zshrc.local" "z l") | |
| ;;; Saving files | |
| ;; Don't make backup files. | |
| (setq make-backup-files nil) | |
| ;; Don't make autosave files. | |
| (setq auto-save-default nil) | |
| ;; Don't make lockfiles. | |
| (setq create-lockfiles nil) | |
| ;;; Editing | |
| ;;;; Text formatting | |
| (defun radian-reverse-region-characters (beg end) | |
| "Reverse the characters in the region from BEG to END. | |
| Interactively, reverse the characters in the current region." | |
| (interactive "*r") | |
| (insert | |
| (reverse | |
| (delete-and-extract-region | |
| beg end)))) | |
| ;; When filling paragraphs, assume that sentences end with one space | |
| ;; rather than two. | |
| (setq sentence-end-double-space nil) | |
| ;; Trigger auto-fill after punctutation characters, not just | |
| ;; whitespace. | |
| (mapcar | |
| (lambda (c) | |
| (set-char-table-range auto-fill-chars c t)) | |
| "!-=+]};:'\",.?") | |
| (defun radian--auto-fill-disable () | |
| "Disable `auto-fill-mode' in the current buffer." | |
| (auto-fill-mode -1)) | |
| (define-minor-mode radian-fix-whitespace-mode | |
| "Minor mode to automatically fix whitespace on save. | |
| If enabled, then saving the buffer deletes all trailing | |
| whitespace and ensures that the file ends with exactly one | |
| newline." | |
| nil nil nil | |
| (if radian-fix-whitespace-mode | |
| (progn | |
| (setq require-final-newline t) | |
| (add-hook 'before-save-hook #'delete-trailing-whitespace nil 'local)) | |
| (setq require-final-newline nil) | |
| (remove-hook 'before-save-hook #'delete-trailing-whitespace 'local))) | |
| (define-globalized-minor-mode radian-fix-whitespace-global-mode | |
| radian-fix-whitespace-mode radian-fix-whitespace-mode) | |
| (radian-fix-whitespace-global-mode +1) | |
| (put 'radian-fix-whitespace-mode 'safe-local-variable #'booleanp) | |
| ;; Feature `whitespace' provides a minor mode for highlighting | |
| ;; whitespace in various special ways. | |
| (use-feature whitespace | |
| :init | |
| (define-minor-mode radian-highlight-long-lines-mode | |
| "Minor mode for highlighting long lines." | |
| nil nil nil | |
| (if radian-highlight-long-lines-mode | |
| (progn | |
| (setq-local whitespace-style '(face lines-tail)) | |
| (setq-local whitespace-line-column 79) | |
| (whitespace-mode +1)) | |
| (whitespace-mode -1) | |
| (kill-local-variable 'whitespace-style) | |
| (kill-local-variable 'whitespace-line-column))) | |
| :blackout t) | |
| ;; Feature `outline' provides major and minor modes for collapsing | |
| ;; sections of a buffer into an outline-like format. | |
| (use-feature outline | |
| :demand t | |
| :config | |
| (define-globalized-minor-mode global-outline-minor-mode | |
| outline-minor-mode outline-minor-mode) | |
| (global-outline-minor-mode +1) | |
| :blackout outline-minor-mode) | |
| ;;;; Kill and yank | |
| (radian-defadvice radian--advice-stop-kill-at-whitespace | |
| (kill-line &rest args) | |
| :around kill-line | |
| "Prevent `kill-line' from killing through whitespace to a newline. | |
| This affects the case where you press \\[kill-line] when point is | |
| followed by some whitespace and then a newline. Without this | |
| advice, \\[kill-line] will kill both the whitespace and the | |
| newline, which is inconsistent with its behavior when the | |
| whitespace is replaced with non-whitespace. With this advice, | |
| \\[kill-line] will kill just the whitespace, and another | |
| invocation will kill the newline. | |
| This is an `:around' advice for `kill-line'. KILL-LINE is the | |
| original definition of `kill-line' and ARGS are the arguments | |
| that were passed to it." | |
| (let ((show-trailing-whitespace t)) | |
| (apply kill-line args))) | |
| ;; Eliminate duplicates in the kill ring. That is, if you kill the | |
| ;; same thing twice, you won't have to use M-y twice to get past it to | |
| ;; older entries in the kill ring. | |
| (setq kill-do-not-save-duplicates t) | |
| (radian-defadvice radian--advice-disallow-password-copying (func &rest args) | |
| :around read-passwd | |
| "Don't allow copying a password to the kill ring." | |
| (cl-letf (((symbol-function #'kill-new) #'ignore) | |
| ((symbol-function #'kill-append) #'ignore)) | |
| (apply func args))) | |
| ;; Feature `delsel' provides an alternative behavior for certain | |
| ;; actions when you have a selection active. Namely: if you start | |
| ;; typing when you have something selected, then the selection will be | |
| ;; deleted; and if you press DEL while you have something selected, it | |
| ;; will be deleted rather than killed. (Otherwise, in both cases the | |
| ;; selection is deselected and the normal function of the key is | |
| ;; performed.) | |
| (use-feature delsel | |
| :demand t | |
| :config | |
| (delete-selection-mode +1)) | |
| ;;;; Undo/redo | |
| ;; Package `undo-tree' replaces the default Emacs undo system, which | |
| ;; is poorly designed and hard to use, with a much more powerful | |
| ;; tree-based system. In basic usage, you don't even have to think | |
| ;; about the tree, because it acts like a conventional undo/redo | |
| ;; system. Bindings are C-/, M-/, and C-x u. | |
| (use-package undo-tree | |
| :demand t | |
| :bind (;; By default, `undo' (and by extension `undo-tree-undo') is | |
| ;; bound to C-_ and C-/, and `undo-tree-redo' is bound to | |
| ;; M-_. It's logical to also bind M-/ to `undo-tree-redo'. | |
| ;; This overrides the default binding of M-/, which is to | |
| ;; `dabbrev-expand'. | |
| :map undo-tree-map | |
| ("M-/" . undo-tree-redo)) | |
| :config | |
| (global-undo-tree-mode +1) | |
| (radian-defadvice radian--advice-suppress-undo-tree-buffer-modified-message | |
| (undo-tree-load-history &rest args) | |
| :around undo-tree-load-history | |
| "Suppress the annoying message saying undo history could not be loaded. | |
| Normally, this message is printed when undo history could not be | |
| loaded since the file was changed outside of Emacs." | |
| (let ((inhibit-message t)) | |
| (apply undo-tree-load-history args))) | |
| ;; Disable undo-in-region. It sounds like a cool feature, but | |
| ;; unfortunately the implementation is very buggy and usually causes | |
| ;; you to lose your undo history if you use it by accident. | |
| (setq undo-tree-enable-undo-in-region nil) | |
| :blackout t) | |
| ;;;; Navigation | |
| ;; Feature `subword' provides a minor mode which causes the | |
| ;; `forward-word' and `backward-word' commands to stop at | |
| ;; capitalization changes within a word, so that you can step through | |
| ;; the components of CamelCase symbols one at a time. | |
| (use-feature subword | |
| :demand t | |
| :config | |
| (global-subword-mode +1) | |
| :blackout t) | |
| (radian-defadvice radian--advice-allow-unpopping-mark | |
| (set-mark-command &optional arg) | |
| :around set-mark-command | |
| "Allow \\[set-mark-command] to step in reverse. | |
| If a negative prefix argument is given (like | |
| \\[negative-argument] \\[set-mark-command]), then it will step in | |
| the reverse direction from \\[universal-argument] | |
| \\[set-mark-command]." | |
| ;; Based on https://stackoverflow.com/a/14539202/3538165. | |
| (interactive "P") | |
| (if (< (prefix-numeric-value arg) 0) | |
| ;; If we don't have any marks set, no-op. | |
| (when mark-ring | |
| ;; I can't remember how this code works. Sorry. | |
| (setq mark-ring (cons (copy-marker (mark-marker)) mark-ring)) | |
| (set-marker (mark-marker) (car (last mark-ring)) (current-buffer)) | |
| (when (null (mark t)) (ding)) | |
| (setq mark-ring (nbutlast mark-ring)) | |
| (goto-char (marker-position (car (last mark-ring))))) | |
| ;; If no prefix argument, or prefix argument is nonnegative, defer | |
| ;; to the original behavior. | |
| (funcall set-mark-command arg))) | |
| (radian-defadvice radian--advice-allow-unpopping-global-mark | |
| (pop-global-mark &optional arg) | |
| :around pop-global-mark | |
| "Allow \\[pop-global-mark] to step in reverse. | |
| If a negative prefix argument is given (like | |
| \\[negative-argument] \\[pop-global-mark]), then it will step in | |
| the reverse direction from \\[pop-global-mark]. | |
| This is an `:around' advice for `pop-global-mark'. | |
| POP-GLOBAL-MARK is the original function and ARG is its | |
| argument." | |
| (interactive "P") | |
| (if arg | |
| ;; Tweaked from the implementation of `pop-global-mark'. | |
| (progn | |
| (or global-mark-ring | |
| (error "No global mark set")) | |
| ;; We need to do this earlier than `pop-global-mark' does the | |
| ;; corresponding action in order to properly undo its | |
| ;; behavior. | |
| (setq global-mark-ring (nconc (list (car (last global-mark-ring))) | |
| (butlast global-mark-ring))) | |
| (while (and global-mark-ring (not (marker-buffer (car (last global-mark-ring))))) | |
| (setq global-mark-ring (butlast global-mark-ring))) | |
| (let* ((marker (car (last global-mark-ring))) | |
| (buffer (marker-buffer marker)) | |
| (position (marker-position marker))) | |
| (set-buffer buffer) | |
| (or (and (>= position (point-min)) | |
| (<= position (point-max))) | |
| (if widen-automatically | |
| (widen) | |
| (error "Global mark position is outside accessible part of buffer"))) | |
| (goto-char position) | |
| (switch-to-buffer buffer))) | |
| (funcall pop-global-mark))) | |
| ;; Feature `bookmark' provides a way to mark places in a buffer. I | |
| ;; don't use it, but some other packages do. | |
| (use-feature bookmark | |
| :config | |
| (radian-defadvice radian--advice-bookmark-silence (&rest _) | |
| :override bookmark-maybe-message | |
| "Silence useless messages from bookmark.el.")) | |
| ;;;; Find and replace | |
| ;; Package `visual-regexp' provides an alternate version of | |
| ;; `query-replace' which highlights matches and replacements as you | |
| ;; type. | |
| (use-package visual-regexp | |
| :bind (("M-%" . vr/query-replace))) | |
| ;; Package `visual-regexp-steroids' allows `visual-regexp' to use | |
| ;; regexp engines other than Emacs'; for example, Python or Perl | |
| ;; regexps. | |
| (use-package visual-regexp-steroids | |
| :demand t | |
| :after visual-regexp | |
| :config | |
| ;; Use Emacs-style regular expressions by default, instead of | |
| ;; Python-style. | |
| (setq vr/engine 'emacs)) | |
| ;; Feature `isearch' provides a basic and fast mechanism for jumping | |
| ;; forward or backward to occurrences of a given search string. | |
| (use-feature isearch | |
| :config | |
| ;; Eliminate the 0.25s idle delay for isearch highlighting, as in my | |
| ;; opinion it usually produces a rather disjointed and distracting | |
| ;; UX. | |
| (setq isearch-lazy-highlight-initial-delay 0)) | |
| ;; Package `swiper' provides an alternative to `isearch' which instead | |
| ;; uses `ivy' to display and select from the results. | |
| (use-package swiper | |
| :init | |
| (radian-bind-key "g" #'swiper) | |
| :config | |
| ;; Use only one color for subgroups in Swiper highlighting. | |
| (setq swiper-faces '(swiper-match-face-1 | |
| swiper-match-face-2 | |
| swiper-match-face-2 | |
| swiper-match-face-2))) | |
| ;;; Electricity: automatic things | |
| ;;;; Autorevert | |
| ;; Feature `autorevert' allows the use of file-watchers or polling in | |
| ;; order to detect when the file visited by a buffer has changed, and | |
| ;; optionally reverting the buffer to match the file (unless it has | |
| ;; unsaved changes). | |
| (use-feature autorevert | |
| :defer 2 | |
| :init | |
| (defun radian--autorevert-silence () | |
| "Silence messages from `auto-revert-mode' in the current buffer." | |
| (setq-local auto-revert-verbose nil)) | |
| :config/el-patch | |
| (defun auto-revert-buffers () | |
| (el-patch-concat | |
| "Revert buffers as specified by Auto-Revert and Global Auto-Revert Mode. | |
| Should `global-auto-revert-mode' be active all file buffers are checked. | |
| Should `auto-revert-mode' be active in some buffers, those buffers | |
| are checked. | |
| Non-file buffers that have a custom `revert-buffer-function' and | |
| `buffer-stale-function' are reverted either when Auto-Revert | |
| Mode is active in that buffer, or when the variable | |
| `global-auto-revert-non-file-buffers' is non-nil and Global | |
| Auto-Revert Mode is active. | |
| This function stops whenever there is user input. The buffers not | |
| checked are stored in the variable `auto-revert-remaining-buffers'. | |
| To avoid starvation, the buffers in `auto-revert-remaining-buffers' | |
| are checked first the next time this function is called. | |
| This function is also responsible for removing buffers no longer in | |
| Auto-Revert Mode from `auto-revert-buffer-list', and for canceling | |
| the timer when no buffers need to be checked." | |
| (el-patch-add | |
| "\n\nOnly currently displayed buffers are reverted.")) | |
| (setq auto-revert-buffers-counter | |
| (1+ auto-revert-buffers-counter)) | |
| (save-match-data | |
| (let ((bufs (el-patch-wrap 2 | |
| (cl-remove-if-not | |
| #'get-buffer-window | |
| (if global-auto-revert-mode | |
| (buffer-list) | |
| auto-revert-buffer-list)))) | |
| remaining new) | |
| ;; Partition `bufs' into two halves depending on whether or not | |
| ;; the buffers are in `auto-revert-remaining-buffers'. The two | |
| ;; halves are then re-joined with the "remaining" buffers at the | |
| ;; head of the list. | |
| (dolist (buf auto-revert-remaining-buffers) | |
| (if (memq buf bufs) | |
| (push buf remaining))) | |
| (dolist (buf bufs) | |
| (if (not (memq buf remaining)) | |
| (push buf new))) | |
| (setq bufs (nreverse (nconc new remaining))) | |
| (while (and bufs | |
| (not (and auto-revert-stop-on-user-input | |
| (input-pending-p)))) | |
| (let ((buf (car bufs))) | |
| (with-current-buffer buf | |
| (if (buffer-live-p buf) | |
| (progn | |
| ;; Test if someone has turned off Auto-Revert Mode | |
| ;; in a non-standard way, for example by changing | |
| ;; major mode. | |
| (if (and (not auto-revert-mode) | |
| (not auto-revert-tail-mode) | |
| (memq buf auto-revert-buffer-list)) | |
| (auto-revert-remove-current-buffer)) | |
| (when (auto-revert-active-p) | |
| ;; Enable file notification. | |
| (when (and auto-revert-use-notify | |
| (not auto-revert-notify-watch-descriptor)) | |
| (auto-revert-notify-add-watch)) | |
| (auto-revert-handler))) | |
| ;; Remove dead buffer from `auto-revert-buffer-list'. | |
| (auto-revert-remove-current-buffer)))) | |
| (setq bufs (cdr bufs))) | |
| (setq auto-revert-remaining-buffers bufs) | |
| ;; Check if we should cancel the timer. | |
| (when (and (not global-auto-revert-mode) | |
| (null auto-revert-buffer-list)) | |
| (cancel-timer auto-revert-timer) | |
| (setq auto-revert-timer nil))))) | |
| :config | |
| ;; Turn the delay on auto-reloading from 5 seconds down to 1 second. | |
| ;; We have to do this before turning on `auto-revert-mode' for the | |
| ;; change to take effect. (Note that if we set this variable using | |
| ;; `customize-set-variable', all it does is toggle the mode off and | |
| ;; on again to make the change take effect, so that way is dumb.) | |
| (setq auto-revert-interval 1) | |
| (global-auto-revert-mode +1) | |
| ;; Auto-revert all buffers, not only file-visiting buffers. The | |
| ;; docstring warns about potential performance problems but this | |
| ;; should not be an issue since we only revert visible buffers. | |
| (setq global-auto-revert-non-file-buffers t) | |
| ;; Since we automatically revert all visible buffers after one | |
| ;; second, there's no point in asking the user whether or not they | |
| ;; want to do it when they find a file. This disables that prompt. | |
| (setq revert-without-query '(".*")) | |
| :blackout auto-revert-mode) | |
| ;;;; Automatic delimiter pairing | |
| ;; Package `smartparens' provides an API for manipulating paired | |
| ;; delimiters of many different types, as well as interactive commands | |
| ;; and keybindings for operating on paired delimiters at the | |
| ;; s-expression level. It provides a Paredit compatibility layer. | |
| (use-package smartparens | |
| :demand t | |
| :config | |
| ;; Load the default pair definitions for Smartparens. | |
| (require 'smartparens-config) | |
| ;; Enable Smartparens functionality in all buffers. | |
| (smartparens-global-mode +1) | |
| ;; When in Paredit emulation mode, Smartparens binds M-( to wrap the | |
| ;; following s-expression in round parentheses. By analogy, we | |
| ;; should bind M-[ to wrap the following s-expression in square | |
| ;; brackets. However, this breaks escape sequences in the terminal, | |
| ;; so it may be controversial upstream. We only enable the | |
| ;; keybinding in windowed mode. | |
| (when (display-graphic-p) | |
| (map-put sp-paredit-bindings "M-[" #'sp-wrap-square)) | |
| ;; Set up keybindings for s-expression navigation and manipulation | |
| ;; in the style of Paredit. | |
| (sp-use-paredit-bindings) | |
| ;; Highlight matching delimiters. | |
| (show-smartparens-global-mode +1) | |
| ;; Prevent all transient highlighting of inserted pairs. | |
| (setq sp-highlight-pair-overlay nil) | |
| (setq sp-highlight-wrap-overlay nil) | |
| (setq sp-highlight-wrap-tag-overlay nil) | |
| ;; Don't disable autoskip when point moves backwards. (This lets you | |
| ;; open a sexp, type some things, delete some things, etc., and then | |
| ;; type over the closing delimiter as long as you didn't leave the | |
| ;; sexp entirely.) | |
| (setq sp-cancel-autoskip-on-backward-movement nil) | |
| ;; Disable Smartparens in Org-related modes, since the keybindings | |
| ;; conflict. | |
| (add-to-list 'sp-ignore-modes-list #'org-mode) | |
| (add-to-list 'sp-ignore-modes-list #'org-agenda-mode) | |
| ;; Make C-k kill the sexp following point in Lisp modes, instead of | |
| ;; just the current line. | |
| (bind-key [remap kill-line] #'sp-kill-hybrid-sexp smartparens-mode-map | |
| (apply #'derived-mode-p sp-lisp-modes)) | |
| ;; When pressing RET after a newly entered pair, add an extra | |
| ;; newline and indent. See | |
| ;; https://github.com/Fuco1/smartparens/issues/80#issuecomment-18910312. | |
| ;; | |
| ;; What is currently here is to be considered a hack. | |
| (defun radian--smartparens-indent-new-pair (&rest _) | |
| "Insert an extra newline after point, and reindent." | |
| (newline) | |
| (indent-according-to-mode) | |
| (forward-line -1) | |
| (indent-according-to-mode)) | |
| (dolist (mode '(c-mode c++-mode css-mode objc-mode java-mode | |
| js2-mode json-mode | |
| python-mode sh-mode web-mode)) | |
| (sp-local-pair mode "{" nil :post-handlers | |
| '((radian--smartparens-indent-new-pair "RET") | |
| (radian--smartparens-indent-new-pair "<return>")))) | |
| (dolist (mode '(js2-mode json-mode python-mode web-mode)) | |
| (sp-local-pair mode "[" nil :post-handlers | |
| '((radian--smartparens-indent-new-pair "RET") | |
| (radian--smartparens-indent-new-pair "<return>")))) | |
| (dolist (mode '(python-mode)) | |
| (sp-local-pair mode "(" nil :post-handlers | |
| '((radian--smartparens-indent-new-pair "RET") | |
| (radian--smartparens-indent-new-pair "<return>"))) | |
| (sp-local-pair mode "\"\"\"" "\"\"\"" :post-handlers | |
| '((radian--smartparens-indent-new-pair "RET") | |
| (radian--smartparens-indent-new-pair "<return>")))) | |
| :blackout t) | |
| ;;;; Autocompletion | |
| ;; Package `company' provides an in-buffer autocompletion framework. | |
| ;; It allows for packages to define backends that supply completion | |
| ;; candidates, as well as optional documentation and source code. Then | |
| ;; Company allows for multiple frontends to display the candidates, | |
| ;; such as a tooltip menu. Company stands for "Complete Anything". | |
| (use-package company | |
| :defer 3 | |
| :init | |
| (defvar radian--company-backends-global | |
| '(company-capf | |
| company-files | |
| (company-dabbrev-code company-keywords) | |
| company-dabbrev) | |
| "Values for `company-backends' used everywhere. | |
| If `company-backends' is overridden by Radian, then these | |
| backends will still be included.") | |
| :bind (;; Remap the standard Emacs keybindings for invoking | |
| ;; completion to instead use Company. You might think this | |
| ;; could be put in the `:bind*' declaration below, but it | |
| ;; seems that `bind-key*' does not work with remappings. | |
| ([remap completion-at-point] . company-manual-begin) | |
| ([remap complete-symbol] . company-manual-begin) | |
| ;; The following are keybindings that take effect whenever | |
| ;; the completions menu is visible, even if the user has not | |
| ;; explicitly interacted with Company. | |
| :map company-active-map | |
| ;; Make TAB always complete the current selection, instead of | |
| ;; only completing a common prefix. | |
| ("<tab>" . company-complete-selection) | |
| ("TAB" . company-complete-selection) | |
| ;; The following are keybindings that only take effect if the | |
| ;; user has explicitly interacted with Company. Note that | |
| ;; `:map' from above is "sticky", and applies also below: see | |
| ;; https://github.com/jwiegley/use-package/issues/334#issuecomment-349473819. | |
| :filter (company-explicit-action-p) | |
| ;; Make RET trigger a completion if and only if the user has | |
| ;; explicitly interacted with Company, instead of always | |
| ;; doing so. | |
| ("<return>" . company-complete-selection) | |
| ("RET" . company-complete-selection) | |
| ;; We then make <up> and <down> abort the completions menu | |
| ;; unless the user has interacted explicitly. Note that we | |
| ;; use `company-select-previous' instead of | |
| ;; `company-select-previous-or-abort'. I think the former | |
| ;; makes more sense since the general idea of this `company' | |
| ;; configuration is to decide whether or not to steal | |
| ;; keypresses based on whether the user has explicitly | |
| ;; interacted with `company', not based on the number of | |
| ;; candidates. | |
| ;; | |
| ;; Note that M-p and M-n work regardless of whether explicit | |
| ;; interaction has happened yet, and note also that M-TAB | |
| ;; when the completions menu is open counts as an | |
| ;; interaction. | |
| ("<up>" . company-select-previous) | |
| ("<down>" . company-select-next)) | |
| :bind* (;; The default keybinding for `completion-at-point' and | |
| ;; `complete-symbol' is M-TAB or equivalently C-M-i. We | |
| ;; already remapped those bindings to `company-manual-begin' | |
| ;; above. Here we make sure that they definitely invoke | |
| ;; `company-manual-begin' even if a minor mode binds M-TAB | |
| ;; directly. | |
| ("M-TAB" . company-manual-begin)) | |
| :config | |
| ;; Always display the entire suggestion list onscreen, placing it | |
| ;; above the cursor if necessary. | |
| (setq company-tooltip-minimum company-tooltip-limit) | |
| ;; Always display suggestions in the tooltip, even if there is only | |
| ;; one. Also, don't display metadata in the echo area. (This | |
| ;; conflicts with ElDoc.) | |
| (setq company-frontends '(company-pseudo-tooltip-frontend)) | |
| ;; Show quick-reference numbers in the tooltip. (Select a completion | |
| ;; with M-1 through M-0.) | |
| (setq company-show-numbers t) | |
| ;; Prevent non-matching input (which will dismiss the completions | |
| ;; menu), but only if the user interacts explicitly with Company. | |
| (setq company-require-match #'company-explicit-action-p) | |
| ;; Company appears to override our settings in `company-active-map' | |
| ;; based on `company-auto-complete-chars'. Turning it off ensures we | |
| ;; have full control. | |
| (setq company-auto-complete-chars nil) | |
| ;; Only search the current buffer to get suggestions for | |
| ;; `company-dabbrev' (a backend that creates suggestions from text | |
| ;; found in your buffers). This prevents Company from causing lag | |
| ;; once you have a lot of buffers open. | |
| (setq company-dabbrev-other-buffers nil) | |
| ;; Make the `company-dabbrev' backend fully case-sensitive, to | |
| ;; improve the UX when working with domain-specific words that have | |
| ;; particular casing. | |
| (setq company-dabbrev-ignore-case nil) | |
| (setq company-dabbrev-downcase nil) | |
| ;; When candidates in the autocompletion tooltip have additional | |
| ;; metadata, like a type signature, align that information to the | |
| ;; right-hand side. This usually makes it look neater. | |
| (setq company-tooltip-align-annotations t) | |
| (global-company-mode +1) | |
| :blackout t) | |
| ;; Package `company-prescient' provides intelligent sorting and | |
| ;; filtering for candidates in Company completions. | |
| (use-package company-prescient | |
| :demand t | |
| :after company | |
| :config | |
| ;; Use `prescient' for Company menus. | |
| (company-prescient-mode +1)) | |
| ;;;; Automatic display of documentation in the minibuffer | |
| ;; Feature `eldoc' provides a minor mode (enabled by default in Emacs | |
| ;; 25) which allows function signatures or other metadata to be | |
| ;; displayed in the echo area. | |
| (use-feature eldoc | |
| :demand t | |
| :config | |
| ;; Always truncate ElDoc messages to one line. This prevents the | |
| ;; echo area from resizing itself unexpectedly when point is on a | |
| ;; variable with a multiline docstring. | |
| (setq eldoc-echo-area-use-multiline-p nil) | |
| ;; Original code from | |
| ;; https://github.com/PythonNut/emacs-config/blob/1a92a1ff1d563fa6a9d7281bbcaf85059c0c40d4/modules/config-intel.el#L130-L137, | |
| ;; thanks! | |
| (radian-defadvice radian--advice-disable-eldoc-on-flycheck | |
| (&rest _) | |
| :after-while eldoc-display-message-no-interference-p | |
| "Disable ElDoc when point is on a Flycheck overlay. | |
| This prevents ElDoc and Flycheck from fighting over the echo | |
| area." | |
| (not (and (bound-and-true-p flycheck-mode) | |
| (flycheck-overlay-errors-at (point))))) | |
| :blackout t) | |
| ;;;; Automatic syntax checking | |
| ;; Package `flycheck' provides a framework for in-buffer error and | |
| ;; warning highlighting, or more generally syntax checking. It comes | |
| ;; with a large number of checkers pre-defined, and other packages | |
| ;; define more. | |
| (use-package flycheck | |
| ;; Use my fork which has support for running a syntax check on | |
| ;; switching buffers until [1] is merged. | |
| ;; | |
| ;; [1]: https://github.com/flycheck/flycheck/pull/1308 | |
| :straight (:host github :repo "flycheck/flycheck" | |
| :fork (:repo "raxod502/flycheck" :branch "fork/4")) | |
| :defer 4 | |
| :init | |
| (defun radian--flycheck-disable-checkers (&rest checkers) | |
| "Disable the given Flycheck syntax CHECKERS, symbols. | |
| This function affects only the current buffer, and neither causes | |
| nor requires Flycheck to be loaded." | |
| (unless (boundp 'flycheck-disabled-checkers) | |
| (setq flycheck-disabled-checkers nil)) | |
| (make-local-variable 'flycheck-disabled-checkers) | |
| (dolist (checker checkers) | |
| (cl-pushnew checker flycheck-disabled-checkers))) | |
| :bind-keymap (("C-c !" . flycheck-mode-map)) | |
| :config | |
| (global-flycheck-mode +1) | |
| (dolist (name '("python" "python2" "python3")) | |
| (add-to-list 'safe-local-variable-values | |
| `(flycheck-python-pycompile-executable . ,name))) | |
| ;; Run a syntax check when changing buffers, just in case you | |
| ;; modified some other files that impact the current one. See | |
| ;; https://github.com/flycheck/flycheck/pull/1308. | |
| (add-to-list 'flycheck-check-syntax-automatically 'idle-buffer-switch) | |
| ;; For the above functionality, check syntax in a buffer that you | |
| ;; switched to only briefly. This allows "refreshing" the syntax | |
| ;; check state for several buffers quickly after e.g. changing a | |
| ;; config file. | |
| (setq flycheck-buffer-switch-check-intermediate-buffers t) | |
| ;; Display errors in the echo area after only 0.2 seconds, not 0.9. | |
| (setq flycheck-display-errors-delay 0.2) | |
| :config | |
| (radian-bind-key "p" #'flycheck-previous-error) | |
| (radian-bind-key "n" #'flycheck-next-error) | |
| :blackout t) | |
| ;;;; Indentation | |
| ;; Don't use tabs for indentation. Use only spaces. Frankly, the fact | |
| ;; that `indent-tabs-mode' is even *available* as an *option* disgusts | |
| ;; me, much less the fact that it's *enabled* by default (meaning that | |
| ;; *both* tabs and spaces are used at the same time). | |
| (setq-default indent-tabs-mode nil) | |
| (defun radian-indent-defun () | |
| "Indent the surrounding defun." | |
| (interactive) | |
| (save-excursion | |
| (when (beginning-of-defun) | |
| (let ((beginning (point))) | |
| (end-of-defun) | |
| (let ((end (point))) | |
| (let ((inhibit-message t) | |
| (message-log-max nil)) | |
| (indent-region beginning end))))))) | |
| (bind-key* "C-M-q" #'radian-indent-defun) | |
| ;;;; Snippet expansion | |
| ;; Feature `abbrev' provides functionality for expanding user-defined | |
| ;; abbreviations. We prefer to use `yasnippet' instead, though. | |
| (use-feature abbrev | |
| :blackout t) | |
| ;; Package `yasnippet' allows the expansion of user-defined | |
| ;; abbreviations into fillable templates. It is also used by | |
| ;; `clj-refactor' for some of its refactorings. | |
| (use-package yasnippet | |
| :bind (:map yas-minor-mode-map | |
| ;; Disable TAB from expanding snippets, as I don't use it and | |
| ;; it's annoying. | |
| ("TAB" . nil) | |
| ("<tab>" . nil)) | |
| :config | |
| ;; Reduce verbosity. The default value is 3. Bumping it down to 2 | |
| ;; eliminates a message about successful snippet lazy-loading setup | |
| ;; on every(!) Emacs init. Errors should still be shown. | |
| (setq yas-verbosity 2) | |
| ;; Make it so that Company's keymap overrides Yasnippet's keymap | |
| ;; when a snippet is active. This way, you can TAB to complete a | |
| ;; suggestion for the current field in a snippet, and then TAB to | |
| ;; move to the next field. Plus, C-g will dismiss the Company | |
| ;; completions menu rather than cancelling the snippet and moving | |
| ;; the cursor while leaving the completions menu on-screen in the | |
| ;; same location. | |
| (use-feature company | |
| :config | |
| ;; This function translates the "event types" I get from | |
| ;; `map-keymap' into things that I can pass to `lookup-key' and | |
| ;; `define-key'. It's a hack, and I'd like to find a built-in | |
| ;; function that accomplishes the same thing while taking care of | |
| ;; any edge cases I might have missed in this ad-hoc solution. | |
| (defun radian--yasnippet-normalize-event (event) | |
| "This function is a complete hack, do not use. | |
| But in principle, it translates what we get from `map-keymap' | |
| into what `lookup-key' and `define-key' want." | |
| (if (vectorp event) | |
| event | |
| (vector event))) | |
| ;; Here we define a hybrid keymap that delegates first to | |
| ;; `company-active-map' and then to `yas-keymap'. | |
| (defvar radian--yasnippet-then-company-keymap | |
| ;; It starts out as a copy of `yas-keymap', and then we | |
| ;; merge in all of the bindings from `company-active-map'. | |
| (let ((keymap (copy-keymap yas-keymap))) | |
| (map-keymap | |
| (lambda (event company-cmd) | |
| (let* ((event (radian--yasnippet-normalize-event event)) | |
| (yas-cmd (lookup-key yas-keymap event))) | |
| ;; Here we use an extended menu item with the | |
| ;; `:filter' option, which allows us to dynamically | |
| ;; decide which command we want to run when a key is | |
| ;; pressed. | |
| (define-key keymap event | |
| `(menu-item | |
| nil ,company-cmd :filter | |
| (lambda (cmd) | |
| ;; There doesn't seem to be any obvious | |
| ;; function from Company to tell whether or not | |
| ;; a completion is in progress (à la | |
| ;; `company-explicit-action-p'), so I just | |
| ;; check whether or not `company-my-keymap' is | |
| ;; defined, which seems to be good enough. | |
| (if company-my-keymap | |
| ',company-cmd | |
| ',yas-cmd)))))) | |
| company-active-map) | |
| keymap) | |
| "Keymap which delegates to both `company-active-map' and `yas-keymap'. | |
| The bindings in `company-active-map' only apply if Company is | |
| currently active.") | |
| (radian-defadvice radian--advice-company-overrides-yasnippet | |
| (yas--make-control-overlay &rest args) | |
| :around yas--make-control-overlay | |
| "Allow `company' keybindings to override those of `yasnippet'." | |
| ;; The function `yas--make-control-overlay' uses the current | |
| ;; value of `yas-keymap' to build the Yasnippet overlay, so to | |
| ;; override the Yasnippet keymap we only need to dynamically | |
| ;; rebind `yas-keymap' for the duration of that function. | |
| (let ((yas-keymap radian--yasnippet-then-company-keymap)) | |
| (apply yas--make-control-overlay args)))) | |
| :blackout yas-minor-mode) | |
| ;;; Language support | |
| ;;;; Text-based languages | |
| ;; Feature `text-mode' provides a major mode for editing plain text. | |
| (use-feature text-mode | |
| :config | |
| (add-hook 'text-mode-hook #'auto-fill-mode) | |
| (radian-defhook radian--flycheck-text-setup () | |
| text-mode-hook | |
| "Disable some Flycheck checkers for plain text." | |
| (radian--flycheck-disable-checkers 'proselint))) | |
| ;;;; Lisp languages | |
| ;; Feature `lisp-mode' provides a base major mode for Lisp languages, | |
| ;; and supporting functions for dealing with Lisp code. | |
| (use-feature lisp-mode | |
| :init | |
| (add-to-list 'safe-local-variable-values | |
| '(lisp-indent-function . common-lisp-indent-function))) | |
| ;;;; AppleScript | |
| ;; https://developer.apple.com/library/content/documentation/AppleScript/Conceptual/AppleScriptLangGuide/introduction/ASLR_intro.html | |
| ;; Package `apples-mode' provides a major mode for AppleScript. | |
| (use-package apples-mode) | |
| ;;;; C, C++, Objective-C, Java | |
| ;; https://en.wikipedia.org/wiki/C_(programming_language) | |
| ;; https://en.wikipedia.org/wiki/C%2B%2B | |
| ;; https://en.wikipedia.org/wiki/Objective-C | |
| ;; https://en.wikipedia.org/wiki/Java_(programming_language) | |
| ;; Feature `cc-mode' provides major modes for C, C++, Objective-C, and | |
| ;; Java. | |
| (use-feature cc-mode | |
| :config | |
| (radian-defadvice radian--advice-inhibit-c-submode-indicators (&rest _) | |
| :override c-update-modeline | |
| "Unconditionally inhibit CC submode indicators in the mode lighter.") | |
| ;; Switch to a better indentation-and-braces style. This turns the | |
| ;; following code: | |
| ;; | |
| ;; if (condition) | |
| ;; { | |
| ;; statement; | |
| ;; } | |
| ;; | |
| ;; Into this: | |
| ;; | |
| ;; if (condition) | |
| ;; { | |
| ;; statement; | |
| ;; } | |
| ;; | |
| ;; We do this by defining a custom style that is based on BSD, and | |
| ;; then overriding the indentation (which is set to 8 spaces by | |
| ;; default). This style is only used for languages which do not have | |
| ;; a more specific style set in `c-default-style'. | |
| (c-add-style "radian-bsd" | |
| '("bsd" | |
| (c-basic-offset . 2))) | |
| (map-put c-default-style 'other "radian-bsd") | |
| (put 'c-default-style 'safe-local-variable #'stringp) | |
| (defun radian--flycheck-c/c++-setup () | |
| "Disable some Flycheck checkers for CC modes. | |
| This function is for use in `c-mode-hook' and `c++-mode-hook'." | |
| ;; These checkers are usually not accurate enough to find proper | |
| ;; headers and such. Disable them by default. | |
| (radian--flycheck-disable-checkers 'c/c++-clang 'c/c++-gcc)) | |
| (add-hook 'c-mode #'radian--flycheck-c/c++-setup) | |
| (add-hook 'c++-mode #'radian--flycheck-c/c++-setup)) | |
| ;; Package `irony-mode' provides a framework to use libclang to get | |
| ;; semantic information about C, C++, and Objective-C code. Such | |
| ;; information can be used by Company, ElDoc, or other packages. | |
| (use-package irony | |
| :init | |
| (add-hook 'c-mode-hook #'irony-mode) | |
| (add-hook 'c++-mode-hook #'irony-mode) | |
| (add-hook 'objc-mode-hook #'irony-mode) | |
| :config | |
| ;; This tells `irony-mode' to discover compile options in a | |
| ;; .clang_complete file or another similar format automatically. See | |
| ;; [1] for further discussion. | |
| ;; | |
| ;; [1]: https://github.com/Sarcasm/irony-mode#configuration | |
| (add-hook 'irony-mode-hook #'irony-cdb-autosetup-compile-options) | |
| :blackout t) | |
| ;; Package `company-irony' provides a Company backend that uses Irony | |
| ;; to complete symbols. See also `company-irony-c-headers'. | |
| (use-package company-irony | |
| :demand t | |
| :after (:all company irony)) | |
| ;; Package `company-irony-c-headers' provides a Company backend that | |
| ;; uses Irony to complete header file #includes. See also | |
| ;; `company-irony'. | |
| (use-package company-irony-c-headers | |
| :demand t | |
| :after company-irony | |
| :config | |
| (radian-defhook radian--company-irony-setup () | |
| irony-mode-hook | |
| "Configure Company to use Irony as a backend." | |
| (setq-local company-backends | |
| (cons (list #'company-irony #'company-irony-c-headers) | |
| radian--company-backends-global)))) | |
| ;; Package `irony-eldoc' provides an ElDoc backend that uses Irony to | |
| ;; display function signatures. | |
| (use-package irony-eldoc | |
| :demand t | |
| :after irony | |
| :config | |
| (add-hook 'irony-mode-hook #'irony-eldoc)) | |
| ;; Package `flycheck-irony' provides a Flycheck syntax checker that | |
| ;; uses Irony to display compilation errors and warnings. | |
| (use-package flycheck-irony | |
| :demand t | |
| :after (:all flycheck irony) | |
| :config | |
| ;; This setup is global, so no need to run it on a mode hook. | |
| (flycheck-irony-setup) | |
| ;; Also use cppcheck. See [1] for discussion. | |
| ;; | |
| ;; [1]: https://github.com/Sarcasm/flycheck-irony/issues/9 | |
| (flycheck-add-next-checker 'irony '(warning . c/c++-cppcheck))) | |
| ;;;; Clojure | |
| ;; https://clojure.org/ | |
| ;; Package `clojure-mode' provides a major mode for Clojure. | |
| (use-package clojure-mode | |
| :config/el-patch | |
| ;; `clojure-mode' does not correctly identify the docstrings of | |
| ;; protocol methods as docstrings, and as such electric indentation | |
| ;; does not work for them. Additionally, when you hack a | |
| ;; clojure.core function, such as defonce or defrecord, to provide | |
| ;; docstring functionality, those docstrings are (perhaps rightly, | |
| ;; but annoyingly) not recognized as docstrings either. However, | |
| ;; there is an easy way to get electric indentation working for all | |
| ;; potential docstrings: simply tell `clojure-mode' that *all* | |
| ;; strings are docstrings. This will not change the font locking, | |
| ;; because for some weird reason `clojure-mode' determines whether | |
| ;; you're in a docstring by the font color instead of the other way | |
| ;; around. Note that this will cause electric indentation by two | |
| ;; spaces in *all* multiline strings, but since there are not very | |
| ;; many non-docstring multiline strings in Clojure this is not too | |
| ;; inconvenient. | |
| ;; | |
| ;; Unfortunately, `clojure-in-docstring-p' is defined as an inline | |
| ;; function, so after changing it, we also have to replace | |
| ;; `clojure-indent-line'. That is done in an advice in the | |
| ;; `radian-clojure-strings-as-docstrings-mode' minor mode. | |
| (defsubst (el-patch-swap clojure-in-docstring-p | |
| radian--clojure-in-string-p) | |
| () | |
| (el-patch-concat | |
| "Check whether point is in " | |
| (el-patch-swap "a docstring" "any type of string") | |
| ".") | |
| (let ((ppss (syntax-ppss))) | |
| ;; are we in a string? | |
| (when (nth 3 ppss) | |
| ;; check font lock at the start of the string | |
| ((el-patch-swap eq memq) | |
| (get-text-property (nth 8 ppss) 'face) | |
| (el-patch-wrap 1 | |
| ('font-lock-string-face 'font-lock-doc-face)))))) | |
| (defun (el-patch-swap clojure-indent-line | |
| radian--advice-clojure-strings-as-docstrings) | |
| () | |
| (el-patch-concat | |
| "Indent current line as Clojure code." | |
| (el-patch-add | |
| "\n\nThis is an `:override' advice for `clojure-indent-line'.")) | |
| (if (el-patch-swap | |
| (clojure-in-docstring-p) | |
| (radian--clojure-in-string-p)) | |
| (save-excursion | |
| (beginning-of-line) | |
| (when (and (looking-at "^\\s-*") | |
| (<= (string-width (match-string-no-properties 0)) | |
| (string-width (clojure-docstring-fill-prefix)))) | |
| (replace-match (clojure-docstring-fill-prefix)))) | |
| (lisp-indent-line))) | |
| :config | |
| ;; Customize indentation like this: | |
| ;; | |
| ;; (some-function | |
| ;; argument | |
| ;; argument) | |
| ;; | |
| ;; (some-function argument | |
| ;; argument) | |
| ;; | |
| ;; (-> foo | |
| ;; thread | |
| ;; thread) | |
| ;; | |
| ;; (->> foo | |
| ;; thread | |
| ;; thread) | |
| ;; | |
| ;; (:keyword | |
| ;; map) | |
| (setq clojure-indent-style :align-arguments) | |
| ;; Ideally, we would be able to set the identation rules for *all* | |
| ;; keywords at the same time. But until we figure out how to do | |
| ;; that, we just have to deal with every keyword individually. See | |
| ;; https://github.com/raxod502/radian/issues/26. | |
| (radian-protect-macros | |
| (define-clojure-indent | |
| (-> 1) | |
| (->> 1) | |
| (:import 0) | |
| (:require 0) | |
| (:use 0))) | |
| (define-minor-mode radian-clojure-strings-as-docstrings-mode | |
| "Treat all Clojure strings as docstrings. | |
| You want to turn this on if you want to treat strings like | |
| docstrings even though they technically are not, and you want to | |
| turn it off if you have multiline strings that are not | |
| docstrings." | |
| nil nil nil | |
| (if radian-clojure-strings-as-docstrings-mode | |
| (advice-add #'clojure-indent-line :override | |
| #'radian--advice-clojure-strings-as-docstrings) | |
| (advice-remove #'clojure-indent-line | |
| #'radian--advice-clojure-strings-as-docstrings)))) | |
| ;; Package `cider' provides integrated Clojure and ClojureScript REPLs | |
| ;; directly in Emacs, a Company backend that uses a live REPL | |
| ;; connection to retrieve completion candidates, and documentation and | |
| ;; source lookups for Clojure code. | |
| (use-package cider | |
| :config | |
| ;; By default, any error messages that occur when CIDER is starting | |
| ;; up are placed in the *nrepl-server* buffer and not in the | |
| ;; *cider-repl* buffer. This is silly, since no-one wants to check | |
| ;; *nrepl-server* every time they start a REPL, and if you don't | |
| ;; then startup errors (including errors in anything loaded by the | |
| ;; :main namespace) are effectively silenced. So we copy everything | |
| ;; from the *nrepl-server* buffer to the *cider-repl* buffer, as | |
| ;; soon as the latter is available. | |
| ;; | |
| ;; Note that this does *not* help in the case of things going so | |
| ;; horribly wrong that the REPL can't even start. In this case you | |
| ;; will have to check the *nrepl-server* buffer manually. Perhaps an | |
| ;; error message that is visible from any buffer could be added in | |
| ;; future. | |
| ;; | |
| ;; Thanks to malabarba on Clojurians Slack for providing the | |
| ;; following code: | |
| (radian-defhook radian--cider-dump-nrepl-server-log () | |
| cider-connected-hook | |
| "Copy contents of *nrepl-server* to beginning of *cider-repl*." | |
| (save-excursion | |
| (goto-char (point-min)) | |
| (insert | |
| (with-current-buffer nrepl-server-buffer | |
| (buffer-string))))) | |
| ;; Use the :emacs profile defined in profiles.clj. This enables lots | |
| ;; of cool extra features in the REPL. | |
| (when (radian-managed-p "~/.lein/profiles.clj") | |
| (setq cider-lein-parameters "with-profile +emacs repl :headless")) | |
| ;; The CIDER welcome message often obscures any error messages that | |
| ;; the above code is supposed to be making visible. So, we need to | |
| ;; turn off the welcome message. | |
| (setq cider-repl-display-help-banner nil) | |
| ;; Sometimes in the CIDER REPL, when Emacs is running slowly, you | |
| ;; can manage to press TAB before the Company completions menu pops | |
| ;; up. This triggers a `completing-read', which is disorienting. So | |
| ;; we reset TAB to its default functionality (i.e. indent only) in | |
| ;; the CIDER REPL. | |
| (setq cider-repl-tab-command 'indent-for-tab-command) | |
| ;; Don't focus the cursor in the CIDER REPL once it starts. Since | |
| ;; the REPL takes so long to start up, especially for large | |
| ;; projects, you either have to wait for a minute without doing | |
| ;; anything or be prepared for your cursor to suddenly shift buffers | |
| ;; without warning sometime in the near future. This is annoying, so | |
| ;; turn off the behavior. For a historical perspective see [1]. | |
| ;; | |
| ;; [1]: https://github.com/clojure-emacs/cider/issues/1872 | |
| (setq cider-repl-pop-to-buffer-on-connect 'display-only) | |
| ;; Use figwheel-sidecar for launching ClojureScript REPLs. This | |
| ;; supports a fully integrated ClojureScript development experience | |
| ;; in Emacs, for use with e.g. [1]. The last three forms are from | |
| ;; the definition of `cider--cljs-repl-types'; the first two work | |
| ;; around [2]. | |
| ;; | |
| ;; [1]: https://github.com/reagent-project/reagent-template | |
| ;; [2]: https://github.com/reagent-project/reagent-template/issues/132 | |
| (setq cider-cljs-lein-repl | |
| "(do | |
| (require 'clojure.java.shell) | |
| (clojure.java.shell/sh \"lein\" \"clean\") | |
| (require 'figwheel-sidecar.repl-api) | |
| (figwheel-sidecar.repl-api/start-figwheel!) | |
| (figwheel-sidecar.repl-api/cljs-repl))") | |
| :blackout t) | |
| ;; Package `clj-refactor' provides automated refactoring commands for | |
| ;; Clojure code. | |
| (use-package clj-refactor | |
| ;; Use my fork which has support for automatically sorting project | |
| ;; dependencies after adding them to the project.clj. | |
| :straight (:host github :repo "clojure-emacs/clj-refactor.el" | |
| :fork (:repo "raxod502/clj-refactor.el" :branch "fork/3") | |
| :files (:defaults "CHANGELOG.md")) | |
| :init | |
| (radian-defhook radian--clj-refactor-enable () | |
| clojure-mode-hook | |
| "Enable `clj-refactor' mode properly. | |
| This means that `yas-minor-mode' also needs to be enabled, and | |
| the `clj-refactor' keybindings need to be installed." | |
| (clj-refactor-mode +1) | |
| (yas-minor-mode +1) | |
| (cljr-add-keybindings-with-prefix "C-c RET")) | |
| :config | |
| ;; Make clj-refactor show its messages right away, instead of | |
| ;; waiting for you to do another command. | |
| (radian-defadvice radian--advice-clj-refactor-message-eagerly (&rest args) | |
| :override cljr--post-command-message | |
| "Make `clj-refactor' show messages right away. | |
| Otherwise, it waits for you to do another command, and then | |
| overwrites the message from *that* command." | |
| (apply #'message args)) | |
| ;; Automatically sort project dependencies after changing them. | |
| (setq cljr-auto-sort-project-dependencies t) | |
| ;; Don't print a warning when starting a REPL outside of project | |
| ;; context. | |
| (setq cljr-suppress-no-project-warning t) | |
| :blackout t) | |
| ;;;; Go | |
| ;; https://golang.org/ | |
| ;; Package `go-mode' provides a major mode for Go. | |
| (use-package go-mode) | |
| ;;;; Haskell | |
| ;; https://www.haskell.org/ | |
| ;; Package `haskell-mode' provides a major mode and REPL integration | |
| ;; for Haskell. | |
| (use-package haskell-mode | |
| :config | |
| ;; Disable in-buffer underlining of errors and warnings, since we | |
| ;; already have them from Flycheck. | |
| (setq haskell-process-show-overlays nil) | |
| ;; Enable REPL integration. | |
| (add-hook 'haskell-mode-hook #'interactive-haskell-mode) | |
| ;; Work around upstream bug, see | |
| ;; https://github.com/haskell/haskell-mode/issues/1553. | |
| (setq haskell-process-args-ghci | |
| '("-ferror-spans" "-fshow-loaded-modules")) | |
| (setq haskell-process-args-cabal-repl | |
| '("--ghc-options=-ferror-spans -fshow-loaded-modules")) | |
| (setq haskell-process-args-stack-ghci | |
| '("--ghci-options=-ferror-spans -fshow-loaded-modules" | |
| "--no-build" "--no-load")) | |
| (setq haskell-process-args-cabal-new-repl | |
| '("--ghc-options=-ferror-spans -fshow-loaded-modules")) | |
| ;; Allow `haskell-mode' to use Stack with the global project instead | |
| ;; of trying to invoke GHC directly, if not inside any sort of | |
| ;; project. | |
| (setq haskell-process-type 'stack-ghci) | |
| (radian-defadvice radian--advice-haskell-fix-back-to-indentation | |
| (back-to-indentation) | |
| :around back-to-indentation | |
| "Fix `back-to-indentation' in `literate-haskell-mode'. | |
| Otherwise, it just moves point to column 0, which is wrong. | |
| This works around an upstream bug; see | |
| https://github.com/haskell/haskell-mode/issues/1594." | |
| (if (derived-mode-p 'literate-haskell-mode) | |
| (progn | |
| (beginning-of-line 1) | |
| (when-let ((c (char-after))) | |
| (when (= c ? ) | |
| (forward-char))) | |
| (skip-syntax-forward " " (line-end-position)) | |
| (backward-prefix-chars)) | |
| (funcall back-to-indentation))) | |
| :blackout interactive-haskell-mode) | |
| ;; Package `hindent' provides a way to invoke the Haskell code | |
| ;; formatter of the same name as a `fill-paragraph' replacement. You | |
| ;; need to install the hindent(1) utility for this to work. | |
| (use-package hindent | |
| :demand t | |
| :after haskell-mode | |
| :config | |
| (radian-defhook radian--hindent-enable-maybe (&optional arg) | |
| haskell-mode-hook | |
| "Enable `hindent-mode' if not in `literate-haskell-mode'. | |
| ARG is passed to `hindent-mode' toggle function." | |
| ;; Don't enable `hindent-mode' in `literate-haskell-mode'. See | |
| ;; https://github.com/commercialhaskell/hindent/issues/496. | |
| (unless (derived-mode-p 'literate-haskell-mode) | |
| (hindent-mode arg))) | |
| :blackout t) | |
| ;;;; HTML | |
| ;; https://www.w3.org/TR/html5/ | |
| ;; Package `web-mode' provides a major mode for HTML and related | |
| ;; templating languages (PHP, ASP, Handlebars, etc.). It replaces the | |
| ;; major mode `html-mode' which comes with Emacs. | |
| (use-package web-mode | |
| ;; Unfortunately `web-mode' does not come with `auto-mode-alist' | |
| ;; autoloads. We have to establish them manually. This list comes | |
| ;; from the official website at http://web-mode.org/ as of | |
| ;; 2018-07-09. | |
| :mode (("\\.phtml\\'" . web-mode) | |
| ("\\.tpl\\.php\\'" . web-mode) | |
| ("\\.[agj]sp\\'" . web-mode) | |
| ("\\.as[cp]x\\'" . web-mode) | |
| ("\\.erb\\'" . web-mode) | |
| ("\\.mustache\\'" . web-mode) | |
| ("\\.djhtml\\'" . web-mode) | |
| ("\\.html?\\'" . web-mode)) | |
| :config | |
| ;; Indent by two spaces by default. | |
| (setq web-mode-markup-indent-offset 2) | |
| (setq web-mode-code-indent-offset 2) | |
| ;; Autocomplete </ instantly. | |
| (setq web-mode-enable-auto-closing t)) | |
| ;;;; JavaScript | |
| ;; https://developer.mozilla.org/en-US/docs/Web/JavaScript | |
| ;; Feature `js' provides a major mode `js-mode' for JavaScript. | |
| (use-feature js | |
| :config | |
| ;; Indent by two spaces by default. | |
| (setq js-indent-level 2)) | |
| ;; Package `js2-mode' provides a better major mode for JavaScript. It | |
| ;; builds on the major mode `js-mode' which comes with Emacs. | |
| (use-package js2-mode | |
| ;; The `js2-mode' package does not come with `auto-mode-alist' | |
| ;; autoloads. | |
| :mode (("\\.js\\'" . js2-mode) | |
| ("\\.jsx\\'" . js2-jsx-mode)) | |
| :interpreter ("node" . js2-mode) | |
| :config | |
| ;; Treat shebang lines (e.g. for node) correctly. | |
| (setq js2-skip-preprocessor-directives t) | |
| ;; Replace the mode lighters. By default they are Javascript-IDE and | |
| ;; JSX-IDE, which are not only improperly capitalized but also | |
| ;; excessively wordy. | |
| :blackout ((js2-mode . "JavaScript") | |
| (js2-jsx-mode . "JSX"))) | |
| ;; Package `tern' provides a static code analyzer for JavaScript. This | |
| ;; includes ElDoc and jump-to-definition out of the box. | |
| (use-package tern | |
| :demand t | |
| :after js2-mode | |
| :config | |
| (add-hook 'js2-mode-hook #'tern-mode) | |
| :blackout t) | |
| ;; Package `company-tern' provides a Company backend which uses Tern. | |
| (use-package company-tern | |
| :demand t | |
| :after (:all js2-mode company tern) | |
| :config | |
| (radian-defhook radian--company-tern-enable () | |
| js2-mode-hook | |
| "Enable `company-tern' in the current buffer." | |
| (setq-local company-backends | |
| (cons 'company-tern radian--company-backends-global)))) | |
| ;;;; Markdown | |
| ;; https://daringfireball.net/projects/markdown/ | |
| ;; Package `markdown-mode' provides a major mode for Markdown. | |
| (use-package markdown-mode | |
| :mode (;; Extension used by Hugo. | |
| ("\\.mmark\\'" . markdown-mode)) | |
| :bind (;; C-c C-s p is a really dumb binding, we prefer C-c C-s C-p. | |
| :map markdown-mode-style-map | |
| ("C-p" . markdown-insert-pre)) | |
| :config | |
| (radian-defhook radian--flycheck-markdown-setup () | |
| markdown-mode-hook | |
| "Disable some Flycheck checkers for Markdown." | |
| (radian--flycheck-disable-checkers | |
| 'markdown-markdownlint-cli | |
| 'markdown-mdl | |
| 'proselint)) | |
| (radian-defadvice radian--disable-markdown-metadata-fontification (&rest _) | |
| :override markdown-match-generic-metadata | |
| "Prevent fontification of YAML metadata blocks in `markdown-mode'. | |
| This prevents a mis-feature wherein if the first line of a | |
| Markdown document has a colon in it, then it's distractingly and | |
| usually wrongly fontified as a metadata block. See | |
| https://github.com/jrblevin/markdown-mode/issues/328." | |
| (prog1 nil (goto-char (point-max))))) | |
| ;;;; Python | |
| ;; https://www.python.org/ | |
| ;; Feature `python' provides a major mode for Python. | |
| (use-feature python | |
| :config | |
| ;; The only consistent style. | |
| (setq python-fill-docstring-style 'django) | |
| (radian-defhook radian--python-fix-outline-mode-config () | |
| python-mode-hook | |
| "Prevent `python-mode' from overriding `outline-minor-mode' config. | |
| If this hook is not used, then `python-mode' will override even a | |
| file-local setting of e.g. `outline-regexp' with its own setting." | |
| (kill-local-variable 'outline-regexp) | |
| (kill-local-variable 'outline-level) | |
| (kill-local-variable 'outline-heading-end-regexp)) | |
| (radian-defhook radian--python-no-reindent-on-colon () | |
| python-mode-hook | |
| "Don't reindent on typing a colon. | |
| See https://emacs.stackexchange.com/a/3338/12534." | |
| (setq electric-indent-chars (delq ?: electric-indent-chars))) | |
| ;; Default to Python 3. Prefer the versioned Python binaries since | |
| ;; some systems stupidly make the unversioned one point at Python 2. | |
| (cond | |
| ((executable-find "python3") | |
| (setq python-shell-interpreter "python3")) | |
| ((executable-find "python2") | |
| (setq python-shell-interpreter "python2")) | |
| (t | |
| (setq python-shell-interpreter "python"))) | |
| (radian-defhook radian--python-use-correct-flycheck-executable () | |
| python-mode-hook | |
| "Use the correct Python executable for Flycheck." | |
| (let ((executable python-shell-interpreter)) | |
| (save-excursion | |
| (save-match-data | |
| (when (or (looking-at "#!/usr/bin/env \\(python[^ \n]+\\)") | |
| (looking-at "#!\\([^ \n]+/python[^ \n]+\\)")) | |
| (setq executable (substring-no-properties (match-string 1)))))) | |
| ;; We actually want to run Python here. | |
| (dolist (var '(flycheck-python-pycompile-executable)) | |
| (make-local-variable var) | |
| (set var executable)) | |
| ;; In this case we want to run the modules, which can only be | |
| ;; done using Python 3. | |
| (dolist (var '(flycheck-python-pylint-executable | |
| flycheck-python-flake8-executable)) | |
| (make-local-variable var) | |
| (set var python-shell-interpreter))))) | |
| ;; Package `elpy' provides a language server for Python, including | |
| ;; integration with most other packages that need to draw information | |
| ;; from it (e.g. Company). | |
| (use-package elpy | |
| :demand t | |
| :after python | |
| :config | |
| ;; Don't highlight indentation levels, as it looks rather weird. | |
| (setq elpy-modules (remq 'elpy-module-highlight-indentation elpy-modules)) | |
| ;; Don't use Flymake, since we use Flycheck instead. | |
| (setq elpy-modules (remq 'elpy-module-flymake elpy-modules)) | |
| ;; Use the correct version of Python. | |
| (setq elpy-rpc-python-command python-shell-interpreter) | |
| (elpy-enable) | |
| :blackout t) | |
| ;;;; ReST | |
| ;; http://docutils.sourceforge.net/rst.html | |
| ;; Feature `rst-mode' provides a major mode for ReST. | |
| (use-feature rst-mode | |
| :config | |
| (radian-defhook radian--flycheck-rst-setup () | |
| rst-mode-hook | |
| "If inside Sphinx project, disable the `rst' Flycheck checker. | |
| This prevents it from signalling spurious errors. See also | |
| https://github.com/flycheck/flycheck/issues/953." | |
| (when (locate-dominating-file default-directory "conf.py") | |
| (radian--flycheck-disable-checkers 'rst)))) | |
| ;;;; Ruby | |
| ;; https://www.ruby-lang.org/ | |
| ;; Package `robe' provides a language server for Ruby which draws | |
| ;; information for autocompletions and source code navigation from a | |
| ;; live REPL in the project context. Start it with `robe-start'. | |
| (use-package robe | |
| :init | |
| (add-hook 'ruby-mode-hook #'robe-mode) | |
| :blackout t) | |
| ;; Package `ruby-electric' allows you to have Emacs insert a paired | |
| ;; "end" when you type "do", and analogously for other paired | |
| ;; keywords. | |
| (use-package ruby-electric | |
| :init/el-patch | |
| ;; We already have paired delimiter support from Smartparens. | |
| ;; However, `ruby-electric' provides its own copy of this | |
| ;; functionality, in a less optimal way. (In particular, typing a | |
| ;; closing paren when your cursor is right before a closing paren | |
| ;; will insert another paren rather than moving through the existing | |
| ;; one.) Unfortunately, `ruby-electric-delimiters-alist' is defined | |
| ;; as a constant, so we can't customize it by setting it to nil | |
| ;; (actually, we can, but byte-compilation inserts the value | |
| ;; literally at its use sites, so this does not take effect). | |
| ;; Instead, we override the definition of `ruby-electric-mode-map' | |
| ;; to make it ignore `ruby-electric-delimiters-alist'. Also note | |
| ;; that we are actually doing this before `ruby-electric' is loaded. | |
| ;; This is so that the modification will actually affect the | |
| ;; definition of `ruby-electric-mode', which gets whatever value | |
| ;; `ruby-electric-mode-map' happens to have at definition time. (The | |
| ;; alternative is to also patch `ruby-electric-mode'.) | |
| (defvar ruby-electric-mode-map | |
| (let ((map (make-sparse-keymap))) | |
| (define-key map " " 'ruby-electric-space/return) | |
| (define-key map [remap delete-backward-char] 'ruby-electric-delete-backward-char) | |
| (define-key map [remap newline] 'ruby-electric-space/return) | |
| (define-key map [remap newline-and-indent] 'ruby-electric-space/return) | |
| (define-key map [remap electric-newline-and-maybe-indent] 'ruby-electric-space/return) | |
| (define-key map [remap reindent-then-newline-and-indent] 'ruby-electric-space/return) | |
| (el-patch-remove | |
| (dolist (x ruby-electric-delimiters-alist) | |
| (let* ((delim (car x)) | |
| (plist (cdr x)) | |
| (name (plist-get plist :name)) | |
| (func (plist-get plist :handler)) | |
| (closing (plist-get plist :closing))) | |
| (define-key map (char-to-string delim) func) | |
| (if closing | |
| (define-key map (char-to-string closing) 'ruby-electric-closing-char))))) | |
| map) | |
| (el-patch-concat | |
| "Keymap used in ruby-electric-mode" | |
| (el-patch-add ".\n\nThe single-character bindings have been removed."))) | |
| :init | |
| (add-hook 'ruby-mode #'ruby-electric-mode) | |
| :blackout t) | |
| ;;;; Rust | |
| ;; https://www.rust-lang.org/ | |
| ;; Package `rust-mode' provides a major mode for Rust. | |
| (use-package rust-mode) | |
| ;; Package `racer' provides a language server for Rust, and a Company | |
| ;; backend which uses this server to display autocompletions. Racer | |
| ;; also provides source code navigation support. | |
| (use-package racer | |
| :init | |
| (add-hook 'rust-mode #'racer-mode) | |
| :blackout t) | |
| ;;;; Scheme | |
| ;; http://www.schemers.org/ | |
| ;; Package `geiser' provides REPL integration for several | |
| ;; implementations of Scheme. | |
| (use-package geiser) | |
| ;;;; Shell | |
| ;; http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html | |
| ;; https://www.gnu.org/software/bash/ | |
| ;; http://www.zsh.org/ | |
| (use-feature sh-script | |
| :config/el-patch | |
| ;; Inhibit the "Indentation setup for shell type *sh" message. | |
| (defun sh-set-shell (shell &optional no-query-flag insert-flag) | |
| (el-patch-concat | |
| "Set this buffer's shell to SHELL (a string). | |
| When used interactively, insert the proper starting #!-line, | |
| and make the visited file executable via `executable-set-magic', | |
| perhaps querying depending on the value of `executable-query'. | |
| When this function is called noninteractively, INSERT-FLAG (the third | |
| argument) controls whether to insert a #!-line and think about making | |
| the visited file executable, and NO-QUERY-FLAG (the second argument) | |
| controls whether to query about making the visited file executable. | |
| Calls the value of `sh-set-shell-hook' if set. | |
| Shell script files can cause this function be called automatically | |
| when the file is visited by having a `sh-shell' file-local variable | |
| whose value is the shell name (don't quote it)." | |
| (el-patch-add | |
| "\n\nThis function does not print superfluous messages.")) | |
| (interactive (list (completing-read | |
| (format "Shell (default %s): " | |
| sh-shell-file) | |
| ;; This used to use interpreter-mode-alist, but that is | |
| ;; no longer appropriate now that uses regexps. | |
| ;; Maybe there could be a separate variable that lists | |
| ;; the shells, used here and to construct i-mode-alist. | |
| ;; But the following is probably good enough: | |
| (append (mapcar (lambda (e) (symbol-name (car e))) | |
| sh-ancestor-alist) | |
| '("csh" "rc" "sh")) | |
| nil nil nil nil sh-shell-file) | |
| (eq executable-query 'function) | |
| t)) | |
| (if (string-match "\\.exe\\'" shell) | |
| (setq shell (substring shell 0 (match-beginning 0)))) | |
| (setq sh-shell (sh-canonicalize-shell shell)) | |
| (if insert-flag | |
| (setq sh-shell-file | |
| (executable-set-magic shell (sh-feature sh-shell-arg) | |
| no-query-flag insert-flag))) | |
| (setq mode-line-process (format "[%s]" sh-shell)) | |
| (setq-local sh-shell-variables nil) | |
| (setq-local sh-shell-variables-initialized nil) | |
| (setq-local imenu-generic-expression | |
| (sh-feature sh-imenu-generic-expression)) | |
| (let ((tem (sh-feature sh-mode-syntax-table-input))) | |
| (when tem | |
| (setq-local sh-mode-syntax-table | |
| (apply 'sh-mode-syntax-table tem)) | |
| (set-syntax-table sh-mode-syntax-table))) | |
| (dolist (var (sh-feature sh-variables)) | |
| (sh-remember-variable var)) | |
| (if (setq-local sh-indent-supported-here | |
| (sh-feature sh-indent-supported)) | |
| (progn | |
| (el-patch-remove | |
| (message "Setting up indent for shell type %s" sh-shell)) | |
| (let ((mksym (lambda (name) | |
| (intern (format "sh-smie-%s-%s" | |
| sh-indent-supported-here name))))) | |
| (add-function :around (local 'smie--hanging-eolp-function) | |
| (lambda (orig) | |
| (if (looking-at "[ \t]*\\\\\n") | |
| (goto-char (match-end 0)) | |
| (funcall orig)))) | |
| (add-hook 'smie-indent-functions #'sh-smie--indent-continuation nil t) | |
| (smie-setup (symbol-value (funcall mksym "grammar")) | |
| (funcall mksym "rules") | |
| :forward-token (funcall mksym "forward-token") | |
| :backward-token (funcall mksym "backward-token"))) | |
| (setq-local parse-sexp-lookup-properties t) | |
| (unless sh-use-smie | |
| (setq-local sh-kw-alist (sh-feature sh-kw)) | |
| (let ((regexp (sh-feature sh-kws-for-done))) | |
| (if regexp | |
| (setq-local sh-regexp-for-done | |
| (sh-mkword-regexpr (regexp-opt regexp t))))) | |
| (el-patch-remove | |
| (message "setting up indent stuff")) | |
| ;; sh-mode has already made indent-line-function local | |
| ;; but do it in case this is called before that. | |
| (setq-local indent-line-function 'sh-indent-line)) | |
| (if sh-make-vars-local | |
| (sh-make-vars-local)) | |
| (el-patch-remove | |
| (message "Indentation setup for shell type %s" sh-shell))) | |
| (el-patch-remove | |
| (message "No indentation for this shell type.")) | |
| (setq-local indent-line-function 'sh-basic-indent-line)) | |
| (when font-lock-mode | |
| (setq font-lock-set-defaults nil) | |
| (font-lock-set-defaults) | |
| (font-lock-flush)) | |
| (setq sh-shell-process nil) | |
| (run-hooks 'sh-set-shell-hook)) | |
| ;; Inhibit the "Indentation variables are now local" message. | |
| (defun sh-make-vars-local () | |
| (el-patch-concat | |
| "Make the indentation variables local to this buffer. | |
| Normally they already are local. This command is provided in case | |
| variable `sh-make-vars-local' has been set to nil. | |
| To revert all these variables to the global values, use | |
| command `sh-reset-indent-vars-to-global-values'." | |
| (el-patch-add | |
| "\n\nThis function does not print superfluous messages.")) | |
| (interactive) | |
| (mapc 'make-local-variable sh-var-list) | |
| (el-patch-remove | |
| (message "Indentation variables are now local.")))) | |
| ;;;; Swift | |
| ;; https://developer.apple.com/swift/ | |
| ;; Package `swift-mode' provides a major mode for Swift code. | |
| (use-package swift-mode) | |
| ;;;; TeX | |
| ;; https://www.tug.org/begin.html | |
| ;; Package `auctex' provides major modes for TeX code, including | |
| ;; compiler and viewer integration. | |
| (straight-use-package 'auctex) | |
| ;; Feature `tex' from package `auctex' provides the base major mode | |
| ;; for TeX. | |
| (use-feature tex | |
| :config/el-patch | |
| ;; Remove annoying messages when opening *.tex files. | |
| (defun TeX-update-style (&optional force) | |
| (el-patch-concat | |
| "Run style specific hooks" | |
| (el-patch-add | |
| ", silently,") | |
| " for the current document. | |
| Only do this if it has not been done before, or if optional argument | |
| FORCE is not nil.") | |
| (unless (or (and (boundp 'TeX-auto-update) | |
| (eq TeX-auto-update 'BibTeX)) ; Not a real TeX buffer | |
| (and (not force) | |
| TeX-style-hook-applied-p)) | |
| (setq TeX-style-hook-applied-p t) | |
| (el-patch-remove | |
| (message "Applying style hooks...")) | |
| (TeX-run-style-hooks (TeX-strip-extension nil nil t)) | |
| ;; Run parent style hooks if it has a single parent that isn't itself. | |
| (if (or (not (memq TeX-master '(nil t))) | |
| (and (buffer-file-name) | |
| (string-match TeX-one-master | |
| (file-name-nondirectory (buffer-file-name))))) | |
| (TeX-run-style-hooks (TeX-master-file))) | |
| (if (and TeX-parse-self | |
| (null (cdr-safe (assoc (TeX-strip-extension nil nil t) | |
| TeX-style-hook-list)))) | |
| (TeX-auto-apply)) | |
| (run-hooks 'TeX-update-style-hook) | |
| (el-patch-remove | |
| (message "Applying style hooks...done")))) | |
| :config | |
| ;; The following configuration is recommended in the manual at | |
| ;; https://www.gnu.org/software/auctex/manual/auctex/Quick-Start.html. | |
| (setq TeX-auto-save t) | |
| (setq TeX-parse-self t) | |
| (radian-with-operating-system macOS | |
| (when (or (file-directory-p "/Applications/TeXShop.app") | |
| (file-directory-p "/Applications/TeX/TeXShop.app")) | |
| ;; Use TeXShop for previewing LaTeX, rather than Preview. This | |
| ;; means we have to define the command to run TeXShop as a "viewer | |
| ;; program", and then tell AUCTeX to use the TeXShop viewer when | |
| ;; opening PDFs. | |
| (add-to-list 'TeX-view-program-list | |
| '("TeXShop" "/usr/bin/open -a TeXShop.app %s.pdf")) | |
| (map-put TeX-view-program-selection 'output-pdf '("TeXShop")))) | |
| (radian-defadvice radian--advice-inhibit-tex-style-loading-message | |
| (TeX-load-style-file file) | |
| :around TeX-load-style-file | |
| "Inhibit the \"Loading **/auto/*.el (source)...\" messages. | |
| This is an `:around' advice for `TeX-load-style-file'." | |
| (cl-letf* (((symbol-function #'raw-load) (symbol-function #'load)) | |
| ((symbol-function #'load) | |
| (lambda (file &optional | |
| noerror _nomessage | |
| nosuffix must-suffix) | |
| (raw-load file noerror 'nomessage nosuffix must-suffix)))) | |
| (funcall TeX-load-style-file file))) | |
| (radian-defhook radian--flycheck-tex-setup () | |
| TeX-mode-hook | |
| "Disable some Flycheck checkers in TeX buffers. | |
| This function is for use on `TeX-mode-hook'." | |
| (radian--flycheck-disable-checkers 'tex-chktex 'tex-lacheck))) | |
| ;; Feature `tex-buf' from package `auctex' provides support for | |
| ;; running TeX commands and displaying their output. | |
| (use-feature tex-buf | |
| :config | |
| ;; Save buffers automatically when compiling, instead of prompting. | |
| (setq TeX-save-query nil) | |
| (radian-defadvice radian--advice-hide-tex-compilation-buffers (name) | |
| :filter-return TeX-process-buffer-name | |
| "Hide AUCTeX compilation buffers by prepending a space to their names. | |
| This prevents them from getting in the way of buffer selection." | |
| (concat " " name))) | |
| ;; Feature `latex' from package `auctex' provides the major mode for | |
| ;; LaTeX. | |
| (use-feature latex | |
| :config | |
| ;; Don't be afraid to break inline math between lines. | |
| (setq LaTeX-fill-break-at-separators nil) | |
| ;; When inserting a left brace, delete the current selection first, | |
| ;; as per `delete-selection-mode'. | |
| (put 'LaTeX-insert-left-brace 'delete-selection t) | |
| (put 'LaTeX-using-Biber 'safe-local-variable #'booleanp)) | |
| ;; Feature `font-latex' from package `auctex' provides the syntax | |
| ;; highlighting for the LaTeX major mode. | |
| (use-feature font-latex | |
| :init | |
| ;; Do the following customizations before `font-latex' is loaded, | |
| ;; since otherwise we would have to call | |
| ;; `font-latex-update-sectioning-faces'. | |
| ;; Prevent superscripts and subscripts from being displayed in a | |
| ;; different font size. | |
| (setq font-latex-fontify-script nil) | |
| ;; Prevent section headers from being displayed in different font | |
| ;; sizes. | |
| (setq font-latex-fontify-sectioning 1)) | |
| ;; Package `company-auctex' provides a Company backend that uses | |
| ;; information from AUCTeX for autocompletion. | |
| (use-package company-auctex | |
| :demand t | |
| :after (:all company tex) | |
| :config | |
| (company-auctex-init)) | |
| ;;;; TypeScript | |
| ;; https://www.typescriptlang.org/ | |
| ;; Package `typescript-mode' provides a major mode for TypeScript. | |
| (use-package typescript-mode | |
| :config | |
| ;; The standard TypeScript indent width is two spaces, not four. | |
| (setq typescript-indent-level 2) | |
| (radian-defhook radian--flycheck-typescript-setup () | |
| typescript-mode-hook | |
| "If inside node_modules, disable the `typescript-tslint' Flycheck checker. | |
| If we don't disable it, then it will generally just generate | |
| several thousand errors, disable itself, and print a warning." | |
| (when (locate-dominating-file "node_modules") | |
| (radian--flycheck-disable-checkers 'typescript-tslint))) | |
| ;; Fix capitalization. It's TypeScript, not typescript. | |
| :blackout "TypeScript") | |
| ;; Package `tide' provides integration with the tsserver TypeScript | |
| ;; language server in order to provide source navigation, a Company | |
| ;; backend, and code formatting. | |
| (use-package tide | |
| :demand t | |
| :after typescript-mode | |
| :config | |
| (add-hook 'typescript-mode-hook #'tide-setup) | |
| (define-minor-mode radian-tide-format-mode | |
| "Minor mode to reformat buffer using tsserver on save." | |
| nil nil nil | |
| (if radian-tide-format-mode | |
| (add-hook 'before-save-hook #'tide-format-before-save nil 'local) | |
| (remove-hook 'before-save-hook #'tide-format-before-save))) | |
| (add-hook 'tide-mode-hook #'radian-tide-format-mode) | |
| ;; Maintain standard TypeScript indent width. | |
| (setq tide-format-options '(:indentSize 2 :tabSize 2)) | |
| :blackout t) | |
| ;;;; VimScript | |
| ;; http://vimdoc.sourceforge.net/htmldoc/usr_41.html | |
| ;; Package `vimrc-mode' provides a major mode for VimScript. | |
| ;; Provides syntax highlighting for VimScript files. | |
| (use-package vimrc-mode | |
| :config | |
| (radian-defhook radian--fix-vimrc-indentation () | |
| vimrc-mode-hook | |
| "Indent by two spaces in `vimrc-mode' rather than eight." | |
| ;; Based on https://stackoverflow.com/a/1819405/3538165. | |
| (setq-local tab-width 2) | |
| (setq-local indent-line-function 'insert-tab))) | |
| ;;; Configuration file formats | |
| ;; Package `apache-mode' provides a major mode for .htaccess and | |
| ;; similar files. | |
| (use-package apache-mode) | |
| ;; Package `crontab-mode' provides a major mode for crontab files. | |
| (use-package crontab-mode) | |
| ;; Package `dockerfile-mode' provides a major mode for Dockerfiles. | |
| (use-package dockerfile-mode) | |
| ;; Package `gitconfig-mode' provides a major mode for .gitconfig and | |
| ;; .gitmodules files. | |
| (use-package gitconfig-mode) | |
| ;; Package `gitignore-mode' provides a major mode for .gitignore | |
| ;; files. | |
| (use-package gitignore-mode) | |
| ;; Package `json-mode' provides a major mode for JSON. | |
| (use-package json-mode | |
| :init/el-patch | |
| (defconst json-mode-standard-file-ext '(".json" ".jsonld") | |
| "List of JSON file extensions.") | |
| (defsubst json-mode--update-auto-mode (filenames) | |
| "Update the `json-mode' entry of `auto-mode-alist'. | |
| FILENAMES should be a list of file as string. | |
| Return the new `auto-mode-alist' entry" | |
| (let* ((new-regexp | |
| (rx-to-string | |
| `(seq (eval | |
| (cons 'or | |
| (append json-mode-standard-file-ext | |
| ',filenames))) | |
| eot))) | |
| (new-entry (cons new-regexp 'json-mode)) | |
| (old-entry (when (boundp 'json-mode--auto-mode-entry) | |
| json-mode--auto-mode-entry))) | |
| (setq auto-mode-alist (delete old-entry auto-mode-alist)) | |
| (add-to-list 'auto-mode-alist new-entry) | |
| new-entry)) | |
| (defcustom json-mode-auto-mode-list '(".babelrc" ".bowerrc" "composer.lock") | |
| "List of filename as string to pass for the JSON entry of | |
| `auto-mode-alist'. | |
| Note however that custom `json-mode' entries in `auto-mode-alist' | |
| won’t be affected." | |
| :group 'json-mode | |
| :type '(repeat string) | |
| :set (lambda (symbol value) | |
| "Update SYMBOL with a new regexp made from VALUE. | |
| This function calls `json-mode--update-auto-mode' to change the | |
| `json-mode--auto-mode-entry' entry in `auto-mode-alist'." | |
| (set-default symbol value) | |
| (setq json-mode--auto-mode-entry | |
| (json-mode--update-auto-mode value)))) | |
| (defvar json-mode--auto-mode-entry | |
| (json-mode--update-auto-mode json-mode-auto-mode-list) | |
| "Regexp generated from the `json-mode-auto-mode-list'.")) | |
| ;; Package `pip-requirements' provides a major mode for | |
| ;; requirements.txt files used by Pip. | |
| (use-package pip-requirements | |
| ;; The default mode lighter is "pip-require". Ew. | |
| :blackout "Requirements") | |
| ;; Package `ssh-config-mode' provides major modes for files in ~/.ssh. | |
| (use-package ssh-config-mode) | |
| ;; Package `terraform-mode' provides major modes for Terraform | |
| ;; configuration files. | |
| (use-package terraform-mode) | |
| ;; Package `toml-mode' provides a major mode for TOML. | |
| (use-package toml-mode | |
| ;; Correct the capitalization from "Toml" to "TOML". | |
| :blackout "TOML") | |
| ;; Package `yaml-mode' provides a major mode for YAML. | |
| (use-package yaml-mode | |
| :config | |
| (add-hook 'yaml-mode-hook #'radian--auto-fill-disable)) | |
| ;;; Introspection | |
| ;;;; Help | |
| ;; Feature `help' powers the *Help* buffer and related functionality. | |
| (use-feature help | |
| :bind (:map help-map | |
| ("M-k" . radian-describe-keymap)) | |
| :config | |
| (radian-defadvice radian--advice-help-inhibit-hints (&rest _) | |
| :override help-window-display-message | |
| "Inhibit the \"Type q in help window to delete it\" hints. | |
| Normally these are printed in the echo area whenever you open a | |
| help buffer.") | |
| (radian-defadvice radian--advice-help-disable-revert-prompt | |
| (help-mode-revert-buffer ignore-auto _noconfirm) | |
| :around help-mode-revert-buffer | |
| "Don't ask for confirmation before reverting help buffers. | |
| \(Reverting is done by pressing \\<help-mode-map>\\[revert-buffer].)" | |
| (funcall help-mode-revert-buffer ignore-auto 'noconfirm)) | |
| (defun radian-describe-keymap (keymap) | |
| "Display the bindings defined by KEYMAP, a symbol or keymap. | |
| Interactively, select a keymap from the list of all defined | |
| keymaps." | |
| (interactive | |
| (list | |
| (intern | |
| (completing-read | |
| "Keymap: " obarray | |
| (lambda (m) | |
| (and (boundp m) | |
| (keymapp (symbol-value m)))) | |
| 'require-match)))) | |
| (with-help-window (help-buffer) | |
| (with-current-buffer (help-buffer) | |
| (insert (format "Keymap `%S' defines the following bindings:" keymap) | |
| "\n\n" | |
| (substitute-command-keys (format "\\{%S}" keymap)))))) | |
| (radian-defhook radian--xref-help-setup () | |
| help-mode-hook | |
| "Make xref look up Elisp symbols in help buffers. | |
| Otherwise, it will try to find a TAGS file using etags, which is | |
| unhelpful." | |
| (add-hook 'xref-backend-functions #'elisp--xref-backend nil 'local))) | |
| ;; Package `helpful' provides a complete replacement for the built-in | |
| ;; Emacs help facility which provides much more contextual information | |
| ;; in a better format. | |
| (use-package helpful | |
| :bind (;; Remap standard commands. | |
| ([remap describe-function] . helpful-callable) | |
| ([remap describe-variable] . helpful-variable) | |
| ([remap describe-symbol] . helpful-symbol) | |
| ([remap describe-key] . helpful-key) | |
| ;; Suggested bindings from the documentation at | |
| ;; https://github.com/Wilfred/helpful. | |
| :map help-map | |
| ("F" . helpful-function) | |
| ("M-f" . helpful-macro) | |
| ("C" . helpful-command) | |
| :map global-map | |
| ("C-c C-d" . helpful-at-point))) | |
| ;;;; Custom | |
| ;; Feature `cus-edit' powers Customize buffers and related | |
| ;; functionality. | |
| (use-feature cus-edit | |
| :config | |
| ;; Don't show the search box in Custom. | |
| (setq custom-search-field nil)) | |
| ;;;; Emacs Lisp development | |
| ;; Feature `elisp-mode' provides the major mode for Emacs Lisp. Very | |
| ;; important! It also provides the major mode for the *scratch* | |
| ;; buffer, which is very similar but slightly different. Not as | |
| ;; important. | |
| (use-feature elisp-mode | |
| :config | |
| (radian-defhook radian--flycheck-elisp-setup () | |
| emacs-lisp-mode-hook | |
| "Disable some Flycheck checkers for Emacs Lisp." | |
| ;; These checkers suck at reporting error locations, so they're | |
| ;; actually quite distracting to work with. | |
| (radian--flycheck-disable-checkers 'emacs-lisp 'emacs-lisp-checkdoc)) | |
| ;; The default mode lighter has a space instead of a hyphen. | |
| ;; Disgusting! | |
| :blackout (lisp-interaction-mode . "Lisp-Interaction")) | |
| (defun radian-reload-init () | |
| (interactive) | |
| (message "Reloading init-file...") | |
| (load user-init-file nil 'nomessage) | |
| (message "Reloading init-file...done")) | |
| (radian-bind-key "r" #'radian-reload-init) | |
| (defun radian-eval-buffer-or-region (&optional start end) | |
| "Evaluate the current region, or the whole buffer if no region is active. | |
| In Lisp code, START and END denote the region to be evaluated; | |
| they default to `point-min' and `point-max' respectively. | |
| If evaluating a buffer visiting this file, then delegate instead | |
| to `radian-reload-init'." | |
| (interactive) | |
| (if (and (string= buffer-file-name radian-lib-file) | |
| (not (region-active-p))) | |
| (radian-reload-init) | |
| (let ((name nil)) | |
| (if (region-active-p) | |
| (progn | |
| (setq start (region-beginning)) | |
| (setq end (region-end)) | |
| (setq name "region")) | |
| (setq start (point-min)) | |
| (setq end (point-max)) | |
| (setq name (buffer-name))) | |
| (let ((load-file-name (buffer-file-name))) | |
| (message "Evaluating %s..." name) | |
| (straight-transaction | |
| (eval-region start end)) | |
| (message "Evaluating %s...done" name))))) | |
| ;; This keybinding is used for evaluating a buffer of Clojure code in | |
| ;; CIDER, and for evaluating a buffer of Scheme code in Geiser. | |
| (bind-key "C-c C-k" #'radian-eval-buffer-or-region) | |
| (defun radian-find-symbol (&optional symbol) | |
| "Same as `xref-find-definitions' but only for Elisp symbols." | |
| (interactive) | |
| (let ((xref-backend-functions '(elisp--xref-backend)) | |
| ;; Make this command behave the same as `find-function' and | |
| ;; `find-variable', i.e. always prompt for an identifier, | |
| ;; defaulting to the one at point. | |
| (xref-prompt-for-identifier t)) | |
| (if symbol | |
| (xref-find-definitions symbol) | |
| (call-interactively 'xref-find-definitions)))) | |
| ;; By default, C-h f, C-h v, and C-h o are bound to | |
| ;; `describe-function', `describe-variable', and `describe-symbol' | |
| ;; respectively. By analogy, C-h C-f, C-h C-v, and C-h C-o should be | |
| ;; bound as follows. (There's no `find-symbol' function by default for | |
| ;; some reason; note that `xref-find-definitions' is not a replacement | |
| ;; because it is major-mode dependent.) By further analogy, we should | |
| ;; bind `find-library'. | |
| (bind-keys* | |
| ("C-h C-f" . find-function) | |
| ("C-h C-v" . find-variable) | |
| ("C-h C-o" . radian-find-symbol) | |
| ("C-h C-l" . find-library)) | |
| ;;;;; Emacs Lisp linting | |
| ;; Feature `checkdoc' provides some tools for validating Elisp | |
| ;; docstrings against common conventions. | |
| (use-feature checkdoc | |
| :init | |
| ;; Not sure why this isn't included by default. | |
| (put 'checkdoc-package-keywords-flag 'safe-local-variable #'booleanp)) | |
| ;; Package `elisp-lint', not installed, provides a linting framework | |
| ;; for Elisp code. | |
| (use-feature elisp-lint | |
| :init | |
| ;; From the package. We need this because some packages set this as | |
| ;; a file-local variable, but we don't install the package so Emacs | |
| ;; doesn't know the variable is safe. | |
| (put 'elisp-lint-indent-specs 'safe-local-variable #'listp)) | |
| ;; Package `package-lint' provides a command that lets you check for | |
| ;; common package.el packaging problems in your packages. | |
| (use-package package-lint) | |
| ;;; Applications | |
| ;;;; Organization | |
| ;; Use `use-feature' here because we already installed Org earlier. | |
| ;; Package `org' provides too many features to describe in any | |
| ;; reasonable amount of space. It is built fundamentally on | |
| ;; `outline-mode', and adds TODO states, deadlines, properties, | |
| ;; priorities, etc. to headings. Then it provides tools for | |
| ;; interacting with this data, including an agenda view, a time | |
| ;; clocker, etc. There are *many* extensions. | |
| (use-feature org | |
| :bind (:map org-mode-map | |
| ;; Prevent Org from overriding the bindings for | |
| ;; windmove. By default, these keys are mapped to | |
| ;; `org-shiftleft', etc. | |
| ("S-<left>" . nil) | |
| ("S-<right>" . nil) | |
| ("S-<up>" . nil) | |
| ("S-<down>" . nil) | |
| ;; Add replacements for the keybindings we just removed. | |
| ;; C-<left> and C-<right> are unused by Org. C-<up> and | |
| ;; C-<down> are bound to `org-backward-paragraph', etc. | |
| ;; (but see below). | |
| ("C-<left>" . org-shiftleft) | |
| ("C-<right>" . org-shiftright) | |
| ("C-<up>" . org-shiftup) | |
| ("C-<down>" . org-shiftdown) | |
| ;; By default, Org maps C-<up> to | |
| ;; `org-backward-paragraph' instead of | |
| ;; `backward-paragraph' (and analogously for C-<down>). | |
| ;; However, it doesn't do the same remapping for the | |
| ;; other bindings of `backward-paragraph' (e.g. M-{). | |
| ;; Here we establish that remapping. (This is important | |
| ;; since we remap C-<up> and C-<down> to other things, | |
| ;; above. So otherwise there would be no easy way to | |
| ;; invoke `org-backward-paragraph' and | |
| ;; `org-forward-paragraph'.) | |
| ([remap backward-paragraph] . org-backward-paragraph) | |
| ([remap forward-paragraph] . org-forward-paragraph) | |
| ;; See discussion of this function below. | |
| ("C-M-RET" . radian-org-insert-heading-at-point) | |
| ("C-M-<return>" . radian-org-insert-heading-at-point)) | |
| :bind* (;; Add the global keybindings for accessing Org Agenda and | |
| ;; Org Capture that are recommended in the Org manual. | |
| ("C-c a" . org-agenda) | |
| ("C-c c" . org-capture)) | |
| :config | |
| ;; If you try to insert a heading in the middle of an entry, don't | |
| ;; split it in half, but instead insert the new heading after the | |
| ;; end of the current entry. | |
| (setq org-insert-heading-respect-content t) | |
| ;; But add a new function for recovering the old behavior (see | |
| ;; `:bind' above). | |
| (defun radian-org-insert-heading-at-point () | |
| "Insert heading without respecting content. | |
| This runs `org-insert-heading' with | |
| `org-insert-heading-respect-content' bound to nil." | |
| (interactive) | |
| (let ((org-insert-heading-respect-content nil)) | |
| (org-insert-heading))) | |
| ;; Show headlines but not content by default. | |
| (setq org-startup-folded 'content) | |
| ;; Make it possible to dim or hide blocked tasks in the agenda view. | |
| (setq org-enforce-todo-dependencies t) | |
| ;; Make C-a, C-e, and C-k smarter with regard to headline tags. | |
| (setq org-special-ctrl-a/e t) | |
| (setq org-special-ctrl-k t) | |
| (put 'org-tags-exclude-from-inheritance 'safe-local-variable | |
| #'radian--list-of-strings-p) | |
| ;; When you create a sparse tree and `org-indent-mode' is enabled, | |
| ;; the highlighting destroys the invisibility added by | |
| ;; `org-indent-mode'. Therefore, don't highlight when creating a | |
| ;; sparse tree. | |
| (setq org-highlight-sparse-tree-matches nil)) | |
| ;; Feature `org-indent' provides an alternative view for Org files in | |
| ;; which sub-headings are indented. | |
| (use-feature org-indent | |
| :init | |
| (add-hook 'org-mode-hook #'org-indent-mode)) | |
| ;; Feature `org-agenda' from package `org' provides the agenda view | |
| ;; functionality, which allows for collating TODO items from your Org | |
| ;; files into a single buffer. | |
| (use-feature org-agenda | |
| :bind (:map org-agenda-mode-map | |
| ;; Prevent Org Agenda from overriding the bindings for | |
| ;; windmove. | |
| ("S-<up>" . nil) | |
| ("S-<down>" . nil) | |
| ("S-<left>" . nil) | |
| ("S-<right>" . nil) | |
| ;; Same routine as above. Now for Org Agenda, we could use | |
| ;; C-up and C-down because M-{ and M-} are bound to the same | |
| ;; commands. But I think it's best to take the same approach | |
| ;; as before, for consistency. | |
| ("C-<left>" . org-agenda-do-date-earlier) | |
| ("C-<right>" . org-agenda-do-date-later)) | |
| :config | |
| (radian-defadvice radian--advice-org-agenda-default-directory | |
| (org-agenda &rest args) | |
| :around org-agenda | |
| "If `org-directory' exists, set `default-directory' to it in the agenda. | |
| This makes the behavior of `find-file' more reasonable." | |
| (let ((default-directory (if (file-exists-p org-directory) | |
| org-directory | |
| default-directory))) | |
| (apply org-agenda args))) | |
| (radian-defadvice radian--advice-blackout-org-agenda | |
| (&rest _) | |
| :override org-agenda-set-mode-name | |
| "Override the `org-agenda' mode lighter to just \"Org-Agenda\"." | |
| "Org-Agenda") | |
| (radian-defhook radian--org-agenda-setup () | |
| org-agenda-mode-hook | |
| "Disable `visual-line-mode' locally." | |
| ;; See https://superuser.com/a/531670/326239. | |
| (visual-line-mode -1) | |
| (let ((inhibit-message t) | |
| (message-log-max nil)) | |
| ;; I'm not exactly sure why this is necessary. More research is | |
| ;; needed. | |
| (toggle-truncate-lines +1))) | |
| ;; Hide blocked tasks in the agenda view. | |
| (setq org-agenda-dim-blocked-tasks 'invisible)) | |
| ;; Feature `org-clock' from package `org' provides the task clocking | |
| ;; functionality. | |
| (use-feature org-clock | |
| ;; We have to autoload these functions in order for the below code | |
| ;; that enables clock persistence without slowing down startup to | |
| ;; work. | |
| :commands (org-clock-load org-clock-save) | |
| :init | |
| ;; Allow clock data to be saved persistently. | |
| (setq org-clock-persist t) | |
| ;; Actually enable clock persistence. This is taken from | |
| ;; `org-clock-persistence-insinuate', but we can't use that function | |
| ;; since it causes both `org' and `org-clock' to be loaded for no | |
| ;; good reason. | |
| (add-hook 'org-mode-hook 'org-clock-load) | |
| (radian-defhook radian--org-clock-save () | |
| kill-emacs-hook | |
| "Run `org-clock-save', but only if Org has been loaded. | |
| Using this on `kill-emacs-hook' instead of `org-clock-save' | |
| prevents a delay on killing Emacs when Org was not yet loaded." | |
| (when (featurep 'org) | |
| (org-clock-save))) | |
| :bind* (;; Make some `org-mode-map' bindings global instead. | |
| ("C-c C-x C-i" . org-clock-in) | |
| ("C-c C-x C-o" . org-clock-out) | |
| ("C-c C-x C-x" . org-clock-in-last) | |
| ("C-c C-x C-j" . org-clock-goto) | |
| ("C-c C-x C-q" . org-clock-cancel)) | |
| :config/el-patch | |
| ;; Silence the messages that are usually printed when the clock data | |
| ;; is loaded from disk. | |
| (defun org-clock-load () | |
| (el-patch-concat | |
| "Load clock-related data from disk, maybe resuming a stored clock." | |
| (el-patch-add "\n\nDo so without emitting any superfluous messages.")) | |
| (when (and org-clock-persist (not org-clock-loaded)) | |
| (if (not (file-readable-p org-clock-persist-file)) | |
| (el-patch-swap | |
| (message "Not restoring clock data; %S not found" org-clock-persist-file) | |
| nil) | |
| (el-patch-remove | |
| (message "Restoring clock data")) | |
| ;; Load history. | |
| (el-patch-wrap 1 | |
| (radian--with-silent-load | |
| (load-file org-clock-persist-file))) | |
| (setq org-clock-loaded t) | |
| (pcase-dolist (`(,(and file (pred file-exists-p)) . ,position) | |
| org-clock-stored-history) | |
| (org-clock-history-push position (find-file-noselect file))) | |
| ;; Resume clock. | |
| (pcase org-clock-stored-resume-clock | |
| (`(,(and file (pred file-exists-p)) . ,position) | |
| (with-current-buffer (find-file-noselect file) | |
| (when (or (not org-clock-persist-query-resume) | |
| (y-or-n-p (format "Resume clock (%s) " | |
| (save-excursion | |
| (goto-char position) | |
| (org-get-heading t t))))) | |
| (goto-char position) | |
| (let ((org-clock-in-resume 'auto-restart) | |
| (org-clock-auto-clock-resolution nil)) | |
| (org-clock-in) | |
| (when (org-invisible-p) (org-show-context)))))) | |
| (_ nil))))) | |
| :config | |
| (defun radian--advice-org-clock-load-automatically (&rest _) | |
| "Run `org-clock-load'. | |
| This is a `:before' advice for various Org functions which might | |
| be invoked before `org-mode-hook' is run." | |
| (org-clock-load)) | |
| (dolist (fun '(org-clock-in | |
| org-clock-out | |
| org-clock-in-last | |
| org-clock-goto | |
| org-clock-cancel)) | |
| (advice-add fun :before #'radian--advice-org-clock-load-automatically))) | |
| ;;;; Filesystem management | |
| ;; When deleting a file interactively, move it to the trash instead. | |
| (setq delete-by-moving-to-trash t) | |
| ;; Package `osx-trash' provides functionality that allows Emacs to | |
| ;; place files in the trash on macOS. | |
| (use-package osx-trash | |
| :commands (osx-trash-move-file-to-trash) | |
| :init/el-patch | |
| (defun osx-trash-setup () | |
| "Provide trash support for OS X. | |
| Provide `system-move-file-to-trash' as an alias for | |
| `osx-trash-move-file-to-trash'. | |
| Note that you still need to set `delete-by-moving-to-trash' to a | |
| non-nil value to enable trashing for file operations." | |
| (when (and (eq system-type 'darwin) | |
| (not (fboundp 'system-move-file-to-trash))) | |
| (defalias 'system-move-file-to-trash | |
| 'osx-trash-move-file-to-trash))) | |
| (osx-trash-setup)) | |
| ;;;;; Dired | |
| ;; For some reason, the autoloads from `dired-aux' and `dired-x' are | |
| ;; not loaded automatically. Do it. | |
| (require 'dired-loaddefs) | |
| ;; Dired has some trouble parsing out filenames that have e.g. leading | |
| ;; spaces, unless the ls program used has support for Dired. GNU ls | |
| ;; has this support, so if it is available we tell Dired (and the | |
| ;; `list-directory' command, not that it sees much use) to use it. | |
| ;; | |
| ;; This is in an advice so that we can defer the PATH search until | |
| ;; necessary. | |
| (radian-defadvice radian--use-gls-for-list-directory (&rest _) | |
| :before list-directory | |
| "Make Dired use GNU ls, if it is available." | |
| (when (executable-find "gls") | |
| (setq insert-directory-program "gls")) | |
| ;; Only do the check once, for efficiency. | |
| (advice-remove #'list-directory #'radian--use-gls-for-list-directory)) | |
| ;; Feature `dired' provides a simplistic filesystem manager in Emacs. | |
| (use-feature dired | |
| :bind (:map dired-mode-map | |
| ;; This binding is way nicer than ^. It's inspired by | |
| ;; Sunrise Commander. | |
| ("J" . dired-up-directory)) | |
| :config | |
| (radian-defadvice radian--advice-dired-check-for-ls-dired (&rest _) | |
| :before dired-insert-directory | |
| "Check if ls --dired is supported ahead of time, and silently. | |
| This advice prevents Dired from printing a message if your ls | |
| does not support the --dired option. (We do this by performing | |
| the check ourselves, and refraining from printing a message in | |
| the problematic case.)" | |
| (when (eq dired-use-ls-dired 'unspecified) | |
| (setq dired-use-ls-dired | |
| (eq 0 (call-process insert-directory-program | |
| nil nil nil "--dired"))))) | |
| (add-hook 'dired-mode-hook #'radian--autorevert-silence) | |
| ;; Disable the prompt about whether I want to kill the Dired buffer | |
| ;; for a deleted directory. Of course I do! It's just a Dired | |
| ;; buffer, after all. Note that this variable, for reasons unknown | |
| ;; to me, is defined in `dired-x', but only affects the behavior of | |
| ;; functions defined in `dired'. | |
| (setq dired-clean-confirm-killing-deleted-buffers nil) | |
| ;; Instantly revert Dired buffers on re-visiting them, with no | |
| ;; message. (A message is shown if insta-revert is either disabled | |
| ;; or determined dynamically by setting this variable to a | |
| ;; function.) | |
| (setq dired-auto-revert-buffer t)) | |
| (use-feature dired-x | |
| :bind (;; Bindings for jumping to the current directory in Dired. | |
| ("C-x C-j" . dired-jump) | |
| ("C-x 4 C-j" . dired-jump-other-window)) | |
| :config | |
| ;; Prevent annoying "Omitted N lines" messages when auto-reverting. | |
| (setq dired-omit-verbose nil) | |
| (radian-with-operating-system macOS | |
| (radian-defadvice radian--advice-dired-guess-open-on-macos | |
| (orig-fun &rest args) | |
| :override dired-guess-default | |
| "Cause Dired's '!' command to use open(1). | |
| This advice is only activated on macOS, where it is helpful since | |
| most of the Linux utilities in `dired-guess-shell-alist-default' | |
| are probably not going to be installed." | |
| "open"))) | |
| ;;;;; Sunrise Commander | |
| ;; Package `sunrise-commander' offers an improved two-pane version of | |
| ;; Dired for efficient filesystem management. Unfortunately, the UX is | |
| ;; sometimes a bit clunky, so some things will have to be fixed in | |
| ;; future (or the package will need to be replaced). | |
| (use-package sunrise-commander | |
| :preface | |
| ;; Prevent extensions from being loaded. We have to do this in | |
| ;; `:preface' because for some stupid reason the extensions are | |
| ;; loaded in the autoloads(!!!). | |
| (setq sr-autoload-extensions nil) | |
| ;; Use my fork until | |
| ;; https://github.com/escherdragon/sunrise-commander/pull/58 and | |
| ;; https://github.com/escherdragon/sunrise-commander/pull/59 are | |
| ;; merged. | |
| :straight (:host github :repo "escherdragon/sunrise-commander" | |
| :fork (:repo "raxod502/sunrise-commander" :branch "fork/1")) | |
| :init | |
| ;; Add integration with wdx, see https://github.com/raxod502/wdx. | |
| (defun radian--wdx-location () | |
| "Return the path to the wdx binary. | |
| Signal an error if it doesn't exist." | |
| (let ((path "~/.zplugin/plugins/raxod502---wdx/bin/wdx")) | |
| (if (file-executable-p path) | |
| path | |
| (user-error "wdx is not installed")))) | |
| (defun radian-sunrise-wdx () | |
| "Prompt for wdx warp point and go there." | |
| (interactive) | |
| (let ((wdx (radian--wdx-location))) | |
| (with-temp-buffer | |
| (unless (= 0 (call-process wdx nil t nil "ls")) | |
| (error "Unexpected error from wdx")) | |
| (let ((point (completing-read | |
| "Go to warp point: " (split-string (buffer-string) "\n") | |
| nil 'require-match))) | |
| (erase-buffer) | |
| (unless (= 0 (call-process wdx nil t nil "show" "--" point)) | |
| (error "Unexpected error from wdx")) | |
| (let ((path (string-remove-suffix "\n" (buffer-string)))) | |
| (sr-goto-dir path)))))) | |
| (defun radian-sunrise-wdx-set-or-remove (&optional remove) | |
| "Prompt for wdx warp point name and set it at the current directory. | |
| With prefix argument, prompt for warp point to remove." | |
| (interactive "P") | |
| (let ((wdx (radian--wdx-location))) | |
| (if remove | |
| (with-temp-buffer | |
| (unless (= 0 (call-process wdx nil t nil "ls")) | |
| (error "Unexpected error from wdx")) | |
| (let ((point (completing-read | |
| "Delete warp point: " | |
| (split-string (buffer-string) "\n")))) | |
| (unless (= 0 (call-process wdx nil t nil "rm" "--" point)) | |
| (error "Unexpected error from wdx")) | |
| (message "Deleted warp point %S" point))) | |
| (let ((point (read-from-minibuffer "Set warp point: "))) | |
| (unless (= 0 (call-process wdx nil t nil "set" "--" point)) | |
| (error "Unexpected error from wdx")) | |
| (message "Set warp point %S" point))))) | |
| (cl-defun radian-sunrise-cd () | |
| "Like `sunrise-cd' but after you've already run `sunrise'." | |
| (interactive) | |
| (dolist (buf (buffer-list)) | |
| (let ((path nil)) | |
| (with-current-buffer buf | |
| (unless (derived-mode-p 'sr-mode) | |
| (setq path (or buffer-file-name default-directory)))) | |
| (when path | |
| (sr-dired path) | |
| (cl-return-from radian-sunrise-cd)))) | |
| (user-error "No buffers to show in Sunrise Commander")) | |
| :bind (:map sr-mode-map | |
| ("j" . radian-sunrise-wdx) | |
| ("k" . radian-sunrise-wdx-set-or-remove) | |
| ("o" . radian-sunrise-cd) | |
| ;; See | |
| ;; https://github.com/escherdragon/sunrise-commander/issues/61. | |
| ("C-e" . move-end-of-line)) | |
| :bind* (("C-c s" . sunrise)) | |
| :config | |
| ;; Prevent Sunrise Commander from doing anything when you move the | |
| ;; mouse. Otherwise it becomes rather annoying when you have to move | |
| ;; the mouse across the Emacs frame. | |
| (setq sr-cursor-follows-mouse nil)) | |
| ;;;; Terminal emulator | |
| ;; Feature `term' provides a workable, though slow, terminal emulator | |
| ;; within Emacs. | |
| (use-feature term | |
| :bind (;; Allow usage of more commands from within the terminal. | |
| :map term-raw-map | |
| ("M-x" . execute-extended-command) | |
| ("C-h" . help-command))) | |
| ;;;; Version control | |
| ;; Feature `vc-hooks' provides hooks for the Emacs VC package. We | |
| ;; don't use VC, because Magit is superior in pretty much every way. | |
| (use-feature vc-hooks | |
| :config | |
| ;; Disable VC. This improves performance and disables some annoying | |
| ;; warning messages and prompts, especially regarding symlinks. See | |
| ;; https://stackoverflow.com/a/6190338/3538165. | |
| (setq vc-handled-backends nil)) | |
| ;; Feature `smerge-mode' provides an interactive mode for visualizing | |
| ;; and resolving Git merge conflicts. | |
| (use-feature smerge-mode | |
| :blackout t) | |
| ;; Package `with-editor' provides infrastructure for using Emacs as an | |
| ;; external editor for programs like Git. It is used by Magit. | |
| (use-package with-editor | |
| :config/el-patch | |
| ;; Make sure that `with-editor' always starts a server with a | |
| ;; nonstandard name, instead of using the default one, so that | |
| ;; emacsclient from a tty never picks it up (which messes up the | |
| ;; color theme). | |
| (defun with-editor--setup () | |
| (if (or (not with-editor-emacsclient-executable) | |
| (file-remote-p default-directory)) | |
| (push (concat with-editor--envvar "=" with-editor-sleeping-editor) | |
| process-environment) | |
| ;; Make sure server-use-tcp's value is valid. | |
| (unless (featurep 'make-network-process '(:family local)) | |
| (setq server-use-tcp t)) | |
| ;; Make sure the server is running. | |
| (unless (process-live-p server-process) | |
| (el-patch-splice 2 | |
| (when (server-running-p server-name) | |
| (setq server-name (format "server%s" (emacs-pid))) | |
| (when (server-running-p server-name) | |
| (server-force-delete server-name)))) | |
| (server-start)) | |
| ;; Tell $EDITOR to use the Emacsclient. | |
| (push (concat with-editor--envvar "=" | |
| (shell-quote-argument with-editor-emacsclient-executable) | |
| ;; Tell the process where the server file is. | |
| (and (not server-use-tcp) | |
| (concat " --socket-name=" | |
| (shell-quote-argument | |
| (expand-file-name server-name | |
| server-socket-dir))))) | |
| process-environment) | |
| (when server-use-tcp | |
| (push (concat "EMACS_SERVER_FILE=" | |
| (expand-file-name server-name server-auth-dir)) | |
| process-environment)) | |
| ;; As last resort fallback to the sleeping editor. | |
| (push (concat "ALTERNATE_EDITOR=" with-editor-sleeping-editor) | |
| process-environment)))) | |
| ;; Package `magit' provides a full graphical interface for Git within | |
| ;; Emacs. | |
| (use-package magit | |
| :bind (;; This is the primary entry point for Magit. Binding to C-x | |
| ;; g is recommended in the manual: | |
| ;; https://magit.vc/manual/magit.html#Getting-Started | |
| ("C-x g" . magit-status)) | |
| :init | |
| ;; Suppress the message we get about "Turning on | |
| ;; magit-auto-revert-mode" when loading Magit. | |
| (setq magit-no-message '("Turning on magit-auto-revert-mode...")) | |
| :config/el-patch | |
| ;; Prevent Emacs asking if we're sure we want to exit, if a | |
| ;; Magit-spawned git-credential-cache process is running. | |
| (defun magit-maybe-start-credential-cache-daemon () | |
| "Maybe start a `git-credential-cache--daemon' process. | |
| If such a process is already running or if the value of option | |
| `magit-credential-cache-daemon-socket' is nil, then do nothing. | |
| Otherwise start the process passing the value of that options | |
| as argument." | |
| (unless (or (not magit-credential-cache-daemon-socket) | |
| (process-live-p magit-credential-cache-daemon-process) | |
| (memq magit-credential-cache-daemon-process | |
| (list-system-processes))) | |
| (setq magit-credential-cache-daemon-process | |
| (or (--first (let* ((attr (process-attributes it)) | |
| (comm (cdr (assq 'comm attr))) | |
| (user (cdr (assq 'user attr)))) | |
| (and (string= comm "git-credential-cache--daemon") | |
| (string= user user-login-name))) | |
| (list-system-processes)) | |
| (condition-case nil | |
| (el-patch-wrap 2 | |
| (with-current-buffer | |
| (get-buffer-create " *git-credential-cache--daemon*") | |
| (start-process "git-credential-cache--daemon" | |
| (el-patch-swap | |
| " *git-credential-cache--daemon*" | |
| (current-buffer)) | |
| magit-git-executable | |
| "credential-cache--daemon" | |
| magit-credential-cache-daemon-socket) | |
| (el-patch-add | |
| (set-process-query-on-exit-flag | |
| (get-buffer-process (current-buffer)) nil)))) | |
| ;; Some Git implementations (e.g. Windows) won't have | |
| ;; this program; if we fail the first time, stop trying. | |
| ((debug error) | |
| (remove-hook 'magit-credential-hook | |
| #'magit-maybe-start-credential-cache-daemon))))))) | |
| :config | |
| ;; Enable C-c M-g as a shortcut to go to a popup of Magit commands | |
| ;; relevant to the current file. | |
| (global-magit-file-mode +1) | |
| ;; The default location for git-credential-cache is in | |
| ;; ~/.config/git/credential. However, if ~/.git-credential-cache/ | |
| ;; exists, then it is used instead. Magit seems to be hardcoded to | |
| ;; use the latter, so here we override it to have more correct | |
| ;; behavior. | |
| (unless (file-exists-p "~/.git-credential-cache/") | |
| (let* ((xdg-config-home (or (getenv "XDG_CONFIG_HOME") | |
| (expand-file-name "~/.config/"))) | |
| (socket (expand-file-name "git/credential/socket" xdg-config-home))) | |
| (setq magit-credential-cache-daemon-socket socket))) | |
| ;; Allow pulling with --rebase just once, without needing to | |
| ;; configure pull.rebase permanently. See | |
| ;; https://github.com/magit/magit/issues/2597#issuecomment-201392835. | |
| (magit-define-popup-switch 'magit-pull-popup ?r "Rebase" "--rebase") | |
| ;; Allow merging unrelated histories. | |
| (magit-define-popup-switch 'magit-merge-popup ?u | |
| "Allow unrelated" "--allow-unrelated-histories")) | |
| ;; Package `gh' provides an Elisp interface to the GitHub API. | |
| (use-package gh | |
| ;; Disable autoloads because this package autoloads *way* too much | |
| ;; code. See https://github.com/sigma/gh.el/issues/95. | |
| :straight (:host github :repo "sigma/gh.el" | |
| :no-autoloads t)) | |
| ;; Package `magit-gh-pulls' adds a section to Magit which displays | |
| ;; open pull requests on a corresponding GitHub repository, if any, | |
| ;; and allows you to check them out locally. | |
| (use-package magit-gh-pulls | |
| :demand t | |
| :after magit | |
| :config | |
| (add-hook 'magit-mode-hook 'turn-on-magit-gh-pulls) | |
| (radian-defadvice radian--advice-hide-magit-gh-pulls-when-not-cached () | |
| :before-while magit-gh-pulls-insert-gh-pulls | |
| "Hide the \"Pull Requests\" section when the list is not cached." | |
| (when-let ((repo (magit-gh-pulls-guess-repo))) | |
| (magit-gh-pulls-requests-cached-p | |
| (magit-gh-pulls-get-api) (car repo) (cdr repo)))) | |
| :blackout t) | |
| ;; Package `git-commit' allows you to use Emacsclient as a Git commit | |
| ;; message editor, providing syntax highlighting and using | |
| ;; `with-editor' to allow you to conveniently accept or abort the | |
| ;; commit. | |
| (use-package git-commit | |
| :init/el-patch | |
| (defun git-commit-setup-check-buffer () | |
| (and buffer-file-name | |
| (string-match-p git-commit-filename-regexp buffer-file-name) | |
| (git-commit-setup))) | |
| (define-minor-mode global-git-commit-mode | |
| "Edit Git commit messages. | |
| This global mode arranges for `git-commit-setup' to be called | |
| when a Git commit message file is opened. That usually happens | |
| when Git uses the Emacsclient as $GIT_EDITOR to have the user | |
| provide such a commit message." | |
| :group 'git-commit | |
| :type 'boolean | |
| :global t | |
| :init-value t | |
| :initialize (lambda (symbol exp) | |
| (custom-initialize-default symbol exp) | |
| (when global-git-commit-mode | |
| (add-hook 'find-file-hook 'git-commit-setup-check-buffer))) | |
| (if global-git-commit-mode | |
| (add-hook 'find-file-hook 'git-commit-setup-check-buffer) | |
| (remove-hook 'find-file-hook 'git-commit-setup-check-buffer))) | |
| :init | |
| (global-git-commit-mode +1) | |
| :config | |
| ;; Max length for commit message summary is 50 characters as per | |
| ;; https://chris.beams.io/posts/git-commit/. | |
| (setq git-commit-summary-max-length 50)) | |
| ;;;; External commands | |
| ;; Feature `compile' provides a way to run a shell command from Emacs | |
| ;; and view the output in real time, with errors and warnings | |
| ;; highlighted and hyperlinked. | |
| (use-feature compile | |
| :init | |
| (radian-bind-key "m" #'compile) | |
| :config | |
| ;; Automatically scroll the Compilation buffer as output appears, | |
| ;; but stop at the first error. | |
| (setq compilation-scroll-output 'first-error)) | |
| ;; Package `rg' just provides an interactive command `rg' to run the | |
| ;; search tool of the same name. | |
| (use-package rg) | |
| ;;;; Internet applications | |
| ;; Feature `browse-url' provides commands for opening URLs in | |
| ;; browsers. | |
| (use-feature browse-url | |
| :init | |
| (defun radian--browse-url-predicate () | |
| "Return non-nil if \\[browse-url-at-point] should be rebound." | |
| ;; All of these major modes provide more featureful bindings for | |
| ;; C-c C-o than `browse-url-at-point'. | |
| (not (derived-mode-p 'markdown-mode 'org-mode 'org-agenda-mode))) | |
| :bind* (:filter (radian--browse-url-predicate) | |
| ("C-c C-o" . browse-url-at-point))) | |
| ;; Package `atomic-chrome' provides a way for you to edit textareas in | |
| ;; Chrome or Firefox using Emacs. See | |
| ;; https://chrome.google.com/webstore/detail/atomic-chrome/lhaoghhllmiaaagaffababmkdllgfcmc | |
| ;; for the Chrome extension. | |
| (use-package atomic-chrome | |
| ;; Use my fork until | |
| ;; https://github.com/alpha22jp/atomic-chrome/issues/42 is fixed. | |
| :straight (:host github :repo "alpha22jp/atomic-chrome" | |
| :fork (:repo "raxod502/atomic-chrome" :branch "fork/1")) | |
| :defer 5 | |
| :bind (:map atomic-chrome-edit-mode-map | |
| :filter (not radian-atomic-chrome-allow-filling) | |
| ("M-q" . ignore)) | |
| :config | |
| (defvar-local radian-atomic-chrome-url nil | |
| "The URL of the text area being edited.") | |
| (defvar-local radian-atomic-chrome-allow-filling nil | |
| "Non-nil means that hard line wrapping may be used.") | |
| (defcustom radian-atomic-chrome-setup-hook nil | |
| "Hook run while setting up an `atomic-chrome' buffer. | |
| You can set `radian-atomic-chrome-allow-filling' based on the | |
| value of `radian-atomic-chrome-url' in this hook." | |
| :type 'hook) | |
| (radian-defadvice radian--advice-atomic-chrome-setup (url) | |
| :after atomic-chrome-set-major-mode | |
| "Save the URL in `radian-atomic-chrome-url'. | |
| Also set up filling correctly based on | |
| `radian-atomic-chrome-allow-filling', after running | |
| `radian-atomic-chrome-setup-hook'." | |
| (setq radian-atomic-chrome-url url) | |
| (run-hooks 'radian-atomic-chrome-setup-hook) | |
| (unless radian-atomic-chrome-allow-filling | |
| (auto-fill-mode -1) | |
| (visual-line-mode +1))) | |
| ;; Edit in Markdown by default, because many sites support it and | |
| ;; it's not a big deal if the text area doesn't actually support | |
| ;; Markdown. | |
| (setq atomic-chrome-default-major-mode 'markdown-mode) | |
| (radian-defhook radian--atomic-chrome-switch-back () | |
| atomic-chrome-edit-done-hook | |
| "Switch back to Google Chrome after finishing with `atomic-chrome'. | |
| This only works on macOS currently." | |
| (radian-with-operating-system macOS | |
| (when (file-directory-p "/Applications/Google Chrome.app") | |
| (call-process "open" nil nil nil "-a" "Google Chrome")))) | |
| ;; Listen for requests from the Chrome/Firefox extension. | |
| (atomic-chrome-start-server)) | |
| ;; Package `webpaste' provides Emacs support for many different | |
| ;; command-line pastebins. | |
| (use-package webpaste) | |
| ;; Package `sx' allows you to browse Stack Overflow from within Emacs. | |
| ;; First, run `sx-authenticate' in order to provide your username and | |
| ;; password. After that, you can use any of the autoloaded entry | |
| ;; points. Navigation is keyboard-centric. | |
| (use-package sx) | |
| ;;;; Emacs profiling | |
| ;; Package `esup' allows you to run a child Emacs process with special | |
| ;; profiling functionality, and to collect timing results for each | |
| ;; form in your init-file. | |
| (use-package esup | |
| :config | |
| ;; Work around a bug where esup tries to step into the byte-compiled | |
| ;; version of `cl-lib', and fails horribly. | |
| (setq esup-depth 0) | |
| (radian-defadvice radian--advice-esup-unwrap-init-file | |
| (esup &optional init-file) | |
| :around esup | |
| "Help `esup' to work with the Radian init-file." | |
| (if init-file | |
| (funcall esup init-file) | |
| (let ((fname (expand-file-name "esup-init.el" temporary-file-directory))) | |
| (with-temp-file fname | |
| `(progn | |
| ;; We need this for `string-trim', but it's not | |
| ;; `require'd until the beginning of radian.el. | |
| (require 'subr-x) | |
| ;; Prevent indentation from being lost in the profiling | |
| ;; results. | |
| (advice-add #'esup-child-chomp :override #'string-trim) | |
| ;; esup does not set `user-init-file'. | |
| (setq user-init-file ,radian-lib-file) | |
| ;; If there's an error, let me see where it is. | |
| (setq debug-on-error t) | |
| ;; Make it possible to detect whether the init-file is | |
| ;; being profiled. | |
| (defvar radian--currently-profiling-p t) | |
| ;; Abbreviated (and flattened) version of init.el. | |
| (defvar radian-minimum-emacs-version "26.1") | |
| (defvar radian-local-init-file "~/.emacs.d/init.local.el") | |
| (setq package-enable-at-startup nil) | |
| (setq custom-file (expand-file-name | |
| (format "custom-%d-%d.el" (emacs-pid) (random)) | |
| temporary-file-directory)) | |
| (defvar radian-lib-file ,radian-lib-file) | |
| (defvar radian--finalize-init-hook nil)) | |
| (current-buffer)) | |
| (insert-file-contents-literally radian-lib-file) | |
| (goto-char (point-max)) | |
| '(run-hooks 'radian--finalize-init-hook) | |
| (current-buffer))) | |
| (funcall esup fname))))) | |
| ;;; Startup | |
| (radian-defadvice radian--advice-inhibit-startup-message (&rest _) | |
| :override display-startup-echo-area-message | |
| "Unconditionally inhibit the startup message in the echo area. | |
| This is the message that reads \"For more information about GNU | |
| Emacs...\". Emacs suggests setting | |
| `inhibit-startup-echo-area-message' to your username so that | |
| other people using your configuration will still get to see this | |
| spam. This advice, however, inhibits the message for everyone.") | |
| ;; Disable the *About GNU Emacs* buffer at startup, and go straight | |
| ;; for the scratch buffer. | |
| (setq inhibit-startup-screen t) | |
| ;; Remove the initial *scratch* message. Start with a blank screen, we | |
| ;; know what we're doing. | |
| (setq initial-scratch-message nil) | |
| ;;; Shutdown | |
| (defun radian-really-kill-emacs () | |
| "Kill Emacs immediately, bypassing `kill-emacs-hook'." | |
| (interactive) | |
| (let ((kill-emacs-hook nil)) | |
| (kill-emacs))) | |
| ;; Package `restart-emacs' provides an easy way to restart Emacs from | |
| ;; inside of Emacs, both in the terminal and in windowed mode. | |
| (use-package restart-emacs | |
| :commands (radian-new-emacs) | |
| :init | |
| (defvar radian--restart-in-progress nil | |
| "Used to prevent infinite recursion. | |
| This is non-nil if `radian--advice-kill-emacs-dispatch' has called | |
| `restart-emacs'.") | |
| (radian-defadvice radian--advice-kill-emacs-dispatch | |
| (save-buffers-kill-emacs &optional arg) | |
| :around save-buffers-kill-emacs | |
| "Allow restarting Emacs or starting a new session on shutdown." | |
| (if radian--restart-in-progress | |
| (funcall save-buffers-kill-emacs arg) | |
| (let ((prompt "Really exit (or restart, or start new) Emacs? (y/n/r/e) ") | |
| (key nil)) | |
| (while (null key) | |
| (let ((cursor-in-echo-area t)) | |
| (when minibuffer-auto-raise | |
| (raise-frame (window-frame (minibuffer-window)))) | |
| (pcase (setq key | |
| (read-key (propertize prompt | |
| 'face 'minibuffer-prompt))) | |
| ((or ?y ?Y) (funcall save-buffers-kill-emacs arg)) | |
| ((or ?n ?N)) | |
| ((or ?r ?R) (let ((radian--restart-in-progress t)) | |
| (restart-emacs arg))) | |
| ((or ?e ?E) (radian-new-emacs arg)) | |
| (?\C-g (signal 'quit nil)) | |
| (_ (setq key nil))))) | |
| (message "%s%c" prompt key)))) | |
| :config/el-patch | |
| (defun (el-patch-swap restart-emacs radian-new-emacs) | |
| (&optional args) | |
| (el-patch-concat | |
| (el-patch-swap | |
| "Restart Emacs." | |
| "Start a new Emacs session without killing the current one.") | |
| " | |
| When called interactively ARGS is interpreted as follows | |
| - with a single `universal-argument' (`C-u') Emacs is " | |
| (el-patch-swap "restarted" "started") | |
| " | |
| with `--debug-init' flag | |
| - with two `universal-argument' (`C-u') Emacs is " | |
| (el-patch-swap "restarted" "started") | |
| " with | |
| `-Q' flag | |
| - with three `universal-argument' (`C-u') the user prompted for | |
| the arguments | |
| When called non-interactively ARGS should be a list of arguments | |
| with which Emacs should be " | |
| (el-patch-swap "restarted" "started") | |
| ".") | |
| (interactive "P") | |
| ;; Do not trigger a restart unless we are sure, we can restart emacs | |
| (restart-emacs--ensure-can-restart) | |
| ;; We need the new emacs to be spawned after all kill-emacs-hooks | |
| ;; have been processed and there is nothing interesting left | |
| (let* ((default-directory (restart-emacs--guess-startup-directory)) | |
| (translated-args (if (called-interactively-p 'any) | |
| (restart-emacs--translate-prefix-to-args args) | |
| args)) | |
| (restart-args (append translated-args | |
| ;; When Emacs is started with a -Q | |
| ;; restart-emacs's autoloads would not be present | |
| ;; causing the the --restart-emacs-desktop | |
| ;; argument to be unhandled | |
| (unless (member "-Q" translated-args) | |
| (restart-emacs--frame-restore-args)))) | |
| (el-patch-remove | |
| (kill-emacs-hook (append kill-emacs-hook | |
| (list (apply-partially #'restart-emacs--launch-other-emacs | |
| restart-args)))))) | |
| (el-patch-swap | |
| (save-buffers-kill-emacs) | |
| (restart-emacs--launch-other-emacs restart-args))))) | |
| ;;; Miscellaneous | |
| ;; Enable all disabled commands. | |
| (setq disabled-command-function nil) | |
| ;; Disable warnings from obsolete advice system. They don't provide | |
| ;; useful diagnostic information and often they can't be fixed except | |
| ;; by changing packages upstream. | |
| (setq ad-redefinition-action 'accept) | |
| ;;; Appearance | |
| (defcustom radian-font nil | |
| "Default font, as a string. Nil means use the default. | |
| This is passed to `set-frame-font'." | |
| :type '(choice string (const :tag "Default" nil))) | |
| (defcustom radian-font-size nil | |
| "Default font size, in pixels. Nil means use the default." | |
| :type '(choice integer (const :tag "Default" nil))) | |
| ;; Turn off the alarm bell. | |
| (setq ring-bell-function #'ignore) | |
| ;; Display keystrokes in the echo area immediately, not after one | |
| ;; second. We can't set the delay to zero because somebody thought it | |
| ;; would be a good idea to have that value suppress keystroke display | |
| ;; entirely. | |
| (setq echo-keystrokes 1e-6) | |
| ;; Don't blink the cursor on the opening paren when you insert a | |
| ;; closing paren, as we already have superior handling of that from | |
| ;; Smartparens. | |
| (setq blink-matching-paren nil) | |
| ;; Disable the contextual menu that pops up when you right-click. | |
| (unbind-key "<C-down-mouse-1>") | |
| (when (display-graphic-p) | |
| ;; Disable the scroll bars. | |
| (scroll-bar-mode -1) | |
| ;; Disable the tool bar. | |
| (tool-bar-mode -1) | |
| ;; Prevent the cursor from blinking. | |
| (blink-cursor-mode -1) | |
| ;; Set the default font size. | |
| (when radian-font-size | |
| (set-face-attribute 'default nil :height radian-font-size)) | |
| ;; Set the default font. | |
| (when radian-font | |
| (set-frame-font radian-font 'keep-size t)) | |
| ;; Use the same font for fixed-pitch text as the rest of Emacs (you | |
| ;; *are* using a monospace font, right?). | |
| (set-face-attribute 'fixed-pitch nil :family 'unspecified) | |
| ;; On macOS, set the title bar to match the frame background. | |
| (when (eq window-system 'ns) | |
| (add-to-list 'default-frame-alist '(ns-appearance . dark)) | |
| (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)))) | |
| ;; Feature `menu-bar' provides the annoying menu bar which we will | |
| ;; disable immediately. | |
| (use-feature menu-bar | |
| :config | |
| (menu-bar-mode -1)) | |
| ;;;; Mode line | |
| ;; The following code customizes the mode line to something like: | |
| ;; [*] radian.el 18% (18,0) [radian:develop*] (Emacs-Lisp) | |
| (defun radian--mode-line-buffer-modified-status () | |
| "Return a mode line construct indicating buffer modification status. | |
| This is [*] if the buffer has been modified and whitespace | |
| otherwise. (Non-file-visiting buffers are never considered to be | |
| modified.) It is shown in the same color as the buffer name, i.e. | |
| `mode-line-buffer-id'." | |
| (propertize | |
| (if (and (buffer-modified-p) | |
| (buffer-file-name)) | |
| "[*]" | |
| " ") | |
| 'face 'mode-line-buffer-id)) | |
| ;; Normally the buffer name is right-padded with whitespace until it | |
| ;; is at least 12 characters. This is a waste of space, so we | |
| ;; eliminate the padding here. Check the docstrings for more | |
| ;; information. | |
| (setq-default mode-line-buffer-identification | |
| (propertized-buffer-identification "%b")) | |
| (defvar-local radian--mode-line-project-and-branch nil | |
| "Mode line construct showing Projectile project and Git status. | |
| The format is [project:branch*], where the * is shown if the | |
| working directory is dirty. Either component can be missing; this | |
| might happen if Projectile is not available or if the project is | |
| not version-controlled with Git. If nothing should be displayed, | |
| this variable is set to nil. | |
| This variable is actually only a cached value; it is set by | |
| `radian-mode-line-compute-project-and-branch' for performance | |
| reasons. | |
| See also `radian-show-git-mode'.") | |
| ;; Don't clear the cache when switching major modes (or using M-x | |
| ;; normal-mode). | |
| (put 'radian--mode-line-project-and-branch 'permanent-local t) | |
| (defun radian--mode-line-recompute-project-and-branch () | |
| "Recalculate and set `radian--mode-line-project-and-branch'. | |
| Force a redisplay of the mode line if necessary. This is | |
| buffer-local." | |
| (condition-case-unless-debug err | |
| (let ((old radian--mode-line-project-and-branch) | |
| (new | |
| (let* (;; Don't insist on having Projectile loaded. | |
| (project-name (when (featurep 'projectile) | |
| (projectile-project-name))) | |
| ;; Projectile returns "-" to mean "no project". | |
| ;; I'm still wondering what happens if someone | |
| ;; makes a project named "-". | |
| (project-name (unless (equal project-name "-") | |
| project-name)) | |
| ;; Check if we are actually in a Git repo, and Git | |
| ;; is available, and we want to show the Git | |
| ;; status. | |
| (git (and | |
| radian-show-git-mode | |
| (executable-find "git") | |
| (locate-dominating-file default-directory ".git"))) | |
| (branch-name | |
| (when git | |
| ;; Determine a reasonable string to show for | |
| ;; the current branch. This is actually more or | |
| ;; less the same logic as we use for the Radian | |
| ;; Zsh prompt. | |
| (with-temp-buffer | |
| ;; First attempt uses symbolic-ref, which | |
| ;; returns the branch name if it exists. | |
| (call-process "git" nil '(t nil) nil | |
| "symbolic-ref" "HEAD") | |
| (if (> (buffer-size) 0) | |
| ;; It actually returns something like | |
| ;; refs/heads/master, though, so let's | |
| ;; try to trim it if possible. | |
| (let ((regex "^\\(refs/heads/\\)?\\(.+\\)$") | |
| (str (string-trim (buffer-string)))) | |
| (if (string-match regex str) | |
| (match-string 2 str) | |
| ;; If it's something weird then just | |
| ;; show it literally. | |
| str)) | |
| ;; If symbolic-ref didn't return anything | |
| ;; on stdout (we discarded stderr), we | |
| ;; probably have a detached head and we | |
| ;; should show the abbreviated commit hash | |
| ;; (e.g. b007692). | |
| (erase-buffer) | |
| (call-process "git" nil '(t nil) nil | |
| "rev-parse" "--short" "HEAD") | |
| (if (> (buffer-size) 0) | |
| (string-trim (buffer-string)) | |
| ;; We shouldn't get here. Unfortunately, | |
| ;; it turns out that we do every once in | |
| ;; a while. (I have no idea why.) | |
| "???"))))) | |
| (dirty (when git | |
| (with-temp-buffer | |
| (call-process "git" nil t nil | |
| "status" "--porcelain") | |
| (if (> (buffer-size) 0) | |
| "*" ""))))) | |
| (cond | |
| ((and project-name git) | |
| (format " [%s:%s%s]" project-name branch-name dirty)) | |
| (project-name | |
| (format " [%s]" project-name)) | |
| ;; This should never happen unless you do something | |
| ;; perverse like create a version-controlled | |
| ;; Projectile project whose name is a hyphen, but we | |
| ;; want to handle it anyway. | |
| (git | |
| (format " [%s%s]" branch-name dirty)))))) | |
| (unless (equal old new) | |
| (setq radian--mode-line-project-and-branch new) | |
| (force-mode-line-update))) | |
| (error | |
| ;; We should not usually get an error here. In the case that we | |
| ;; do, however, let's try to avoid displaying garbage data, and | |
| ;; instead delete the construct entirely from the mode line. | |
| (unless (null radian--mode-line-project-and-branch) | |
| (setq radian--mode-line-project-and-branch nil) | |
| (force-mode-line-update))))) | |
| ;; We will make sure this information is updated after some time of | |
| ;; inactivity, for the current buffer. | |
| (defvar radian--mode-line-update-delay 1 | |
| "Seconds of inactivity before updating the mode line. | |
| Specifically, this entails updating the Projectile project, Git | |
| branch, and dirty status, which are the most computationally | |
| taxing elements.") | |
| ;; We only need one global timer pair for all the buffers, since we | |
| ;; will only be updating the cached mode line value for the current | |
| ;; buffer. | |
| ;; | |
| ;; The way this is set up, the main idle timer runs each time that | |
| ;; Emacs is idle for exactly one second. That triggers a recomputation | |
| ;; of the mode line, and also schedules the repeat timer, which | |
| ;; reschedules itself repeatedly. Why do we need two timers? If we | |
| ;; tried to use just the idle timer, then the recomputation would only | |
| ;; get scheduled once per idle session, one second in, instead of | |
| ;; going once per second after one second of initial idleness. If we | |
| ;; tried to use just the repeat timer, then we would get | |
| ;; ever-increasing delays before it would fire, in each new idle | |
| ;; session. Why? Because the pattern for scheduling an idle timer | |
| ;; repeatedly is to increase the idle delay, since the idle time is | |
| ;; not re-set just because a timer fired. And if the idle session ends | |
| ;; between timer fires, then the repeat timer will be stuck with a | |
| ;; really long idle delay, and won't fire again. | |
| (defun radian--mode-line-recompute-and-reschedule () | |
| "Compute mode line data and re-set timers. | |
| The delay is `radian--mode-line-update-delay'. The timers are | |
| `radian--mode-line-idle-timer' and | |
| `radian--mode-line-repeat-timer'." | |
| ;; Cancel any existing timer (we wouldn't want to introduce | |
| ;; duplicate timers!), and do it early in a half-hearted attempt to | |
| ;; avoid race conditions. | |
| (when radian--mode-line-repeat-timer | |
| (cancel-timer radian--mode-line-repeat-timer)) | |
| ;; Do the computation. | |
| (radian--mode-line-recompute-project-and-branch) | |
| ;; If Emacs is already idle (meaning that the main idle timer has | |
| ;; already been triggered, and won't go again), then we need to | |
| ;; schedule the repeat timer. Otherwise, the main idle timer will be | |
| ;; triggered when Emacs does become idle, and we don't need to | |
| ;; schedule anything. There's no need to clear an old repeat timer, | |
| ;; since the idle timer will always get called before the repeat | |
| ;; timer and that will cause the repeat timer to be re-set as below. | |
| (when (current-idle-time) | |
| (setq radian--mode-line-repeat-timer | |
| (run-with-idle-timer | |
| (time-add (current-idle-time) radian--mode-line-update-delay) | |
| nil #'radian--mode-line-recompute-and-reschedule)))) | |
| (defvar radian--mode-line-idle-timer | |
| (run-with-idle-timer | |
| radian--mode-line-update-delay 'repeat | |
| #'radian--mode-line-recompute-and-reschedule) | |
| "Timer that recomputes information for the mode line, or nil. | |
| This runs once each time Emacs is idle. | |
| Future recomputations are scheduled under | |
| `radian--mode-line-repeat-timer'. See also | |
| `radian--mode-line-recompute-and-reschedule' and | |
| `radian--mode-line-recompute-project-and-branch'.") | |
| (defvar radian--mode-line-repeat-timer nil | |
| "Timer that recomputes information for the mode line, or nil. | |
| This is scheduled repeatedly at intervals after | |
| `radian--mode-line-idle-timer' runs once. See also | |
| `radian--mode-line-recompute-and-reschedule' and | |
| `radian--mode-line-recompute-project-and-branch'.") | |
| ;; Make `mode-line-position' show the column, not just the row. | |
| (column-number-mode +1) | |
| (define-minor-mode radian-show-git-mode | |
| "Minor mode for showing Git status in mode line. | |
| If enabled, then both the current Projectile project and the | |
| current Git branch are shown in the mode line. Otherwise, only | |
| the former is shown.") | |
| (define-globalized-minor-mode radian-show-git-global-mode | |
| radian-show-git-mode radian-show-git-mode) | |
| (radian-show-git-global-mode +1) | |
| ;; https://emacs.stackexchange.com/a/7542/12534 | |
| (defun radian--mode-line-align (left right) | |
| "Render a left/right aligned string for the mode line. | |
| LEFT and RIGHT are strings, and the return value is a string that | |
| displays them left- and right-aligned respectively, separated by | |
| spaces." | |
| (let ((width (- (window-width) (length left)))) | |
| (format (format "%%s%%%ds" width) left right))) | |
| (defvar radian--mode-line-left | |
| '(;; Show [*] if the buffer is modified. | |
| (:eval (radian--mode-line-buffer-modified-status)) | |
| " " | |
| ;; Show the name of the current buffer. | |
| mode-line-buffer-identification | |
| " " | |
| ;; Show the row and column of point. | |
| mode-line-position | |
| ;; Show the current Projectile project and Git branch. | |
| radian--mode-line-project-and-branch | |
| ;; Show the active major and minor modes. | |
| " " | |
| mode-line-modes) | |
| "Composite mode line construct to be shown left-aligned.") | |
| (defvar radian--mode-line-right nil | |
| "Composite mode line construct to be shown right-aligned.") | |
| ;; Actually reset the mode line format to show all the things we just | |
| ;; defined. | |
| (setq-default mode-line-format | |
| '(:eval (replace-regexp-in-string | |
| "%" "%%" | |
| (radian--mode-line-align | |
| (format-mode-line radian--mode-line-left) | |
| (format-mode-line radian--mode-line-right)) | |
| 'fixedcase 'literal))) | |
| ;;;; Color theme | |
| (defcustom radian-color-theme-enable t | |
| "Non-nil means to load the default Radian color theme. | |
| Set this to nil if you wish to load a different color theme in | |
| your local configuration." | |
| :type 'boolean) | |
| ;; Package `zerodark-theme' provides a good-looking color theme that | |
| ;; works in both windowed and tty Emacs. | |
| ;; | |
| ;; Use my fork until | |
| ;; https://github.com/NicolasPetton/zerodark-theme/pull/54 is merged. | |
| (straight-register-package | |
| '(zerodark-theme :host github :repo "NicolasPetton/zerodark-theme" | |
| :fork (:repo "raxod502/zerodark-theme" :branch "fork/2"))) | |
| (when radian-color-theme-enable | |
| (use-package zerodark-theme)) | |
| ;;; Closing | |
| (radian--run-hook 'radian-after-init-hook) | |
| ;; Prune the build cache for straight.el; this will prevent it from | |
| ;; growing too large. Do this after the final hook to prevent packages | |
| ;; installed there from being pruned. | |
| (straight-prune-build-cache) | |
| ;; Occasionally prune the build directory as well. For similar reasons | |
| ;; as above, we need to do this after local configuration. | |
| (unless (bound-and-true-p radian--currently-profiling-p) | |
| (when (= 0 (random 100)) | |
| (straight-prune-build-directory))) | |
| ;; Enable color theme as late as is humanly possible. This reduces | |
| ;; frame flashing and other artifacts during startup. | |
| (when radian-color-theme-enable | |
| (use-feature zerodark-theme | |
| :demand t | |
| :config | |
| (load-theme 'zerodark 'no-confirm))) | |
| ;; Local Variables: | |
| ;; indent-tabs-mode: nil | |
| ;; outline-regexp: ";;;;* " | |
| ;; End: |