diff options
Diffstat (limited to 'users/wpcarro/emacs')
180 files changed, 7274 insertions, 0 deletions
diff --git a/users/wpcarro/emacs/.emacs.d/init.el b/users/wpcarro/emacs/.emacs.d/init.el new file mode 100644 index 000000000000..5db74d36c70b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/init.el @@ -0,0 +1,15 @@ +;; load order is intentional +(setq-default debug-on-error t) +(require 'wpc-package) +(require 'wpc-misc) +(require 'ssh) +(require 'keyboard) +(require 'email) +(require 'keybindings) +(require 'window-manager) +(require 'wpc-ui) +(require 'wpc-dired) +(require 'wpc-org) +(require 'wpc-company) +(require 'wpc-shell) +(require 'wpc-language-support) diff --git a/users/wpcarro/emacs/.emacs.d/opam-user-setup.el b/users/wpcarro/emacs/.emacs.d/opam-user-setup.el new file mode 100644 index 000000000000..a23addefafe4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/opam-user-setup.el @@ -0,0 +1,145 @@ +;; ## added by OPAM user-setup for emacs / base ## cfd3c9b7837c85cffd0c59de521990f0 ## you can edit, but keep this line +(provide 'opam-user-setup) + +;; Base configuration for OPAM + +(defun opam-shell-command-to-string (command) + "Similar to shell-command-to-string, but returns nil unless the process + returned 0, and ignores stderr (shell-command-to-string ignores return value)" + (let* ((return-value 0) + (return-string + (with-output-to-string + (setq return-value + (with-current-buffer standard-output + (process-file shell-file-name nil '(t nil) nil + shell-command-switch command)))))) + (if (= return-value 0) return-string nil))) + +(defun opam-update-env (switch) + "Update the environment to follow current OPAM switch configuration" + (interactive + (list + (let ((default + (car (split-string (opam-shell-command-to-string "opam switch show --safe"))))) + (completing-read + (concat "opam switch (" default "): ") + (split-string (opam-shell-command-to-string "opam switch list -s --safe") "\n") + nil t nil nil default)))) + (let* ((switch-arg (if (= 0 (length switch)) "" (concat "--switch " switch))) + (command (concat "opam config env --safe --sexp " switch-arg)) + (env (opam-shell-command-to-string command))) + (when (and env (not (string= env ""))) + (dolist (var (car (read-from-string env))) + (setenv (car var) (cadr var)) + (when (string= (car var) "PATH") + (setq exec-path (split-string (cadr var) path-separator))))))) + +(opam-update-env nil) + +(defvar opam-share + (let ((reply (opam-shell-command-to-string "opam config var share --safe"))) + (when reply (substring reply 0 -1)))) + +(add-to-list 'load-path (concat opam-share "/emacs/site-lisp")) +;; OPAM-installed tools automated detection and initialisation + +(defun opam-setup-tuareg () + (add-to-list 'load-path (concat opam-share "/tuareg") t) + (load "tuareg-site-file")) + +(defun opam-setup-add-ocaml-hook (h) + (add-hook 'tuareg-mode-hook h t) + (add-hook 'caml-mode-hook h t)) + +(defun opam-setup-complete () + (if (require 'company nil t) + (opam-setup-add-ocaml-hook + (lambda () + (company-mode) + (defalias 'auto-complete 'company-complete))) + (require 'auto-complete nil t))) + +(defun opam-setup-ocp-indent () + (opam-setup-complete) + (autoload 'ocp-setup-indent "ocp-indent" "Improved indentation for Tuareg mode") + (autoload 'ocp-indent-caml-mode-setup "ocp-indent" "Improved indentation for Caml mode") + (add-hook 'tuareg-mode-hook 'ocp-setup-indent t) + (add-hook 'caml-mode-hook 'ocp-indent-caml-mode-setup t)) + +(defun opam-setup-ocp-index () + (autoload 'ocp-index-mode "ocp-index" "OCaml code browsing, documentation and completion based on build artefacts") + (opam-setup-add-ocaml-hook 'ocp-index-mode)) + +(defun opam-setup-merlin () + (opam-setup-complete) + (require 'merlin) + (opam-setup-add-ocaml-hook 'merlin-mode) + + (defcustom ocp-index-use-auto-complete nil + "Use auto-complete with ocp-index (disabled by default by opam-user-setup because merlin is in use)" + :group 'ocp_index) + (defcustom merlin-ac-setup 'easy + "Use auto-complete with merlin (enabled by default by opam-user-setup)" + :group 'merlin-ac) + + ;; So you can do it on a mac, where `C-<up>` and `C-<down>` are used + ;; by spaces. + (define-key merlin-mode-map + (kbd "C-c <up>") 'merlin-type-enclosing-go-up) + (define-key merlin-mode-map + (kbd "C-c <down>") 'merlin-type-enclosing-go-down) + (set-face-background 'merlin-type-face "skyblue")) + +(defun opam-setup-utop () + (autoload 'utop "utop" "Toplevel for OCaml" t) + (autoload 'utop-minor-mode "utop" "Minor mode for utop" t) + (add-hook 'tuareg-mode-hook 'utop-minor-mode)) + +(defvar opam-tools + '(("tuareg" . opam-setup-tuareg) + ("ocp-indent" . opam-setup-ocp-indent) + ("ocp-index" . opam-setup-ocp-index) + ("merlin" . opam-setup-merlin) + ("utop" . opam-setup-utop))) + +(defun opam-detect-installed-tools () + (let* + ((command "opam list --installed --short --safe --color=never") + (names (mapcar 'car opam-tools)) + (command-string (mapconcat 'identity (cons command names) " ")) + (reply (opam-shell-command-to-string command-string))) + (when reply (split-string reply)))) + +(defvar opam-tools-installed (opam-detect-installed-tools)) + +(defun opam-auto-tools-setup () + (interactive) + (dolist (tool opam-tools) + (when (member (car tool) opam-tools-installed) + (funcall (symbol-function (cdr tool)))))) + +(opam-auto-tools-setup) +;; ## end of OPAM user-setup addition for emacs / base ## keep this line +;; ## added by OPAM user-setup for emacs / tuareg ## b10f42abebd2259b784b70d1a7f7e426 ## you can edit, but keep this line +;; Set to autoload tuareg from its original switch when not found in current +;; switch (don't load tuareg-site-file as it adds unwanted load-paths) +(defun opam-tuareg-autoload (fct file doc args) + (let ((load-path (cons "/home/wpcarro/.opam/default/share/emacs/site-lisp" load-path))) + (load file)) + (apply fct args)) +(when (not (member "tuareg" opam-tools-installed)) + (defun tuareg-mode (&rest args) + (opam-tuareg-autoload 'tuareg-mode "tuareg" "Major mode for editing OCaml code" args)) + (defun tuareg-run-ocaml (&rest args) + (opam-tuareg-autoload 'tuareg-run-ocaml "tuareg" "Run an OCaml toplevel process" args)) + (defun ocamldebug (&rest args) + (opam-tuareg-autoload 'ocamldebug "ocamldebug" "Run the OCaml debugger" args)) + (defalias 'run-ocaml 'tuareg-run-ocaml) + (defalias 'camldebug 'ocamldebug) + (add-to-list 'auto-mode-alist '("\\.ml[iylp]?\\'" . tuareg-mode)) + (add-to-list 'auto-mode-alist '("\\.eliomi?\\'" . tuareg-mode)) + (add-to-list 'interpreter-mode-alist '("ocamlrun" . tuareg-mode)) + (add-to-list 'interpreter-mode-alist '("ocaml" . tuareg-mode)) + (dolist (ext '(".cmo" ".cmx" ".cma" ".cmxa" ".cmxs" ".cmt" ".cmti" ".cmi" ".annot")) + (add-to-list 'completion-ignored-extensions ext))) +;; ## end of OPAM user-setup addition for emacs / tuareg ## keep this line diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio new file mode 100644 index 000000000000..52bc717e470e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdio @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: <stdio.h> +# key: sio +# -- +#include <stdio.h> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib new file mode 100644 index 000000000000..5d44e8ed7989 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/stdlib @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: <stdlib.h> +# key: slb +# -- +#include <stdlib.h> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct new file mode 100644 index 000000000000..6e9282f83c79 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/c-mode/struct @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: struct +# key: struct +# -- +typedef struct $1 { + $2 +} $1_t; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs new file mode 100644 index 000000000000..8ea7b8f07724 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/elisp-module-docs @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: Elisp module docs +# key: emd +# -- +;;; `(-> (buffer-file-name) f-filename)` --- $2 -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; $3 + +;;; Code: \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function new file mode 100644 index 000000000000..bfa888d5265d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/function @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# expand-env: ((yas-indent-line 'fixed)) +# -- +(defun $1 ($2) + "$3" + $4) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header new file mode 100644 index 000000000000..bf6e525f8c65 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/generic-header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; $1 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header new file mode 100644 index 000000000000..0f0ad5c4fc4e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/library-header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Library header +# key: lib +# -- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer new file mode 100644 index 000000000000..2a0bcc33f7bb --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/emacs-lisp-mode/provide-footer @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Provide footer +# key: elf +# -- +(provide '`(-> (buffer-file-name) f-filename f-no-ext)`) +;;; `(-> (buffer-file-name) f-filename)` ends here \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy new file mode 100644 index 000000000000..95f7d9deecd0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/derive-safe-copy @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Derive Safe Copy +# key: dsc +# -- +deriveSafeCopy 0 'base ''$1 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified new file mode 100644 index 000000000000..4c4db62a8a47 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/import-qualified @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import qualified +# key: iq +# -- +import qualified $1 as $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn new file mode 100644 index 000000000000..10d194ce41f0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/instance-defn @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Instance +# key: inst +# -- +instance $1 where + $2 = $3 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension new file mode 100644 index 000000000000..9d6084acb40d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/language-extension @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: language extension +# key: lang +# -- +{-# LANGUAGE $1 #-} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator new file mode 100644 index 000000000000..1ab0d762b611 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/separator @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Separator +# key: - +# -- +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined new file mode 100644 index 000000000000..7609f801f278 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/haskell-mode/undefined @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Undefiend +# key: nd +# -- +undefined \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate new file mode 100644 index 000000000000..3cea6ce003ba --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/html-mode/index-boilerplate @@ -0,0 +1,18 @@ +# -*- mode: snippet -*- +# name: HTML index.html starter +# key: html +# -- +<!doctype html> + +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>$1</title> + <meta name="description" content="$2"> + <meta name="author" content="William Carroll"> + <link rel="stylesheet" href="index.css"> +</head> +<body> + <script src="index.js"></script> +</body> +</html> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main new file mode 100644 index 000000000000..1839a27eb5c0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/java-mode/public-static-void-main @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: public static void main +# key: psvm +# -- +public static void main(String[] args) { + $1 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage new file mode 100644 index 000000000000..7f110a9718e4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/defpackage @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# name: Define package +# key: defp +# -- +(in-package #:cl-user) +(defpackage #:$1 + (:documentation "$2") + (:use #:cl)) +(in-package #:$1) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function new file mode 100644 index 000000000000..b1769cd3d102 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +(defun $1 ($2) + "$3" + $4) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function new file mode 100644 index 000000000000..a3c236821e06 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/lisp-mode/typed-function @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# name: Typed function +# key: tfn +# -- +(type $1 ($3) $4) +(defun $1 ($2) + "$5" + $6) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix new file mode 100644 index 000000000000..b5eb5a244721 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/nix-mode/shell-nix @@ -0,0 +1,12 @@ +# -*- mode: snippet -*- +# name: shell.nix boilerplate +# key: import +# -- +{ pkgs, ... }: + +pkgs.stdenv.mkDerivation { + name = "$1"; + buildInputs = [ + $2 + ]; +} diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet new file mode 100644 index 000000000000..4215b15992b6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/code-snippet @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Code Snippet +# key: src +# -- +#+BEGIN_SRC $1 +$2 +#+END_SRC \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href new file mode 100644 index 000000000000..ac65ea2e49be --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/org-mode/href @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Org mode URL +# key: href +# -- +[[$1][$2]] \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main new file mode 100644 index 000000000000..4dd22dc0b2da --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/dunder-main @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Dunder main (__main__) +# key: mn +# -- +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function new file mode 100644 index 000000000000..379ceda1a3a6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/function @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +def $1($2): + $3 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header new file mode 100644 index 000000000000..db48adfec737 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +################################################################################ +# $1 +################################################################################ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init new file mode 100644 index 000000000000..5c407495f53a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/init @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: dunder init +# key: ctor +# -- +def __init__(self$1): + $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang new file mode 100644 index 000000000000..0f45ae782d32 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/shebang @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: shebang +# key: shb +# -- +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 new file mode 100644 index 000000000000..3babc730305a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/python-mode/utf-8 @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: utf-8 +# key: utf +# -- +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function new file mode 100644 index 000000000000..882c48ded39d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/function @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +(define ($1) $2) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda new file mode 100644 index 000000000000..b9a684588bc4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Lambda function +# key: ld +# -- +(ฮป ($1) $2) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol new file mode 100644 index 000000000000..254b9fd96b18 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/racket-mode/lambda-symbol @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Lambda symbol +# key: l +# -- +ฮป \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function new file mode 100644 index 000000000000..6b4b6a5db2a7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Function +# key: fn +# -- +let $1 = (~$2:$3) => { + $4 +}; \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch new file mode 100644 index 000000000000..40f34ff8d1f1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/reason-mode/switch @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Switch statement +# key: sw +# -- +switch ($1) { +| $2 => +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor new file mode 100644 index 000000000000..62834a29ab04 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/action-extractor @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: exactness +# key: $x +# -- +$Exact<$Call<typeof $1>> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log new file mode 100644 index 000000000000..82ec3fd8e379 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/console-log @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Console.log helper +# key: clg +# -- +console.log($1) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn new file mode 100644 index 000000000000..8e35e61fc2c4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-defn @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: const definition +# key: cn +# -- +const $1 = '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function new file mode 100644 index 000000000000..13f2018f2269 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/const-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: const function +# key: cfn +# -- +const $1 = ($2) => { + $3 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const new file mode 100644 index 000000000000..2a52c57c75cd --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/destructure-const @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Destructuring a const +# key: cds +# -- +const { $1 } = $2 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow new file mode 100644 index 000000000000..187a2efc5a7c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: fa +# -- +=> \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function new file mode 100644 index 000000000000..694914a83c95 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/fat-arrow-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: faf +# -- +() => { + $1 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured new file mode 100644 index 000000000000..ded3ce163a93 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-destructured @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import destructured +# key: ids +# -- +import { $1 } from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react new file mode 100644 index 000000000000..0463f5cd5593 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-react @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import React dependency (ES6) +# key: ir +# -- +import React from 'react' diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type new file mode 100644 index 000000000000..fcd51f687b61 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-type @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import type +# key: ixt +# -- +import type { $1 } from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y new file mode 100644 index 000000000000..09fa6df50506 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-x-from-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import x from y +# key: ix +# -- +import $1 from '$2' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y new file mode 100644 index 000000000000..9f550e300d12 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/import-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import y +# key: iy +# -- +import '$1' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test new file mode 100644 index 000000000000..ed382d4f74c4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-describe-test @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# name: Jest describe/test block +# key: dsc +# -- +describe('$1', () => { + test('$2', () => { + + expect($3).toEqual($4) + }) +}) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test new file mode 100644 index 000000000000..12ca2e786ded --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/jest-test @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Jest / Jasmine test +# key: tst +# -- +test('$1', () => { + expect($2).toBe($3) +}) \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component new file mode 100644 index 000000000000..f2a93a31d96d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/react-class-component @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: React class extends +# key: clz +# -- +class $1 extends React.Component { + render() { + $2 + } +} + +export default $1 \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action new file mode 100644 index 000000000000..681c5d0dfdf4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/redux-action @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: redux-action +# key: rax +# -- +export const ${1:$$(string-lower->caps yas-text)} = '`(downcase (functions-buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action new file mode 100644 index 000000000000..53c6e5fc5ac2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rjsx-mode/typed-redux-action @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: typed-redux-action +# key: trax +# -- +export const ${1:$$(string-lower->caps yas-text)}: '`(downcase (functions-buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' = '`(downcase (buffer-dirname))`/${1:$(string-caps->kebab yas-text)}' \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop new file mode 100644 index 000000000000..4d8e0e3bbd24 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/for-loop @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: for-loop +# key: for +# -- +for $1 in $2 { + $3 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match new file mode 100644 index 000000000000..bf0e876e2b98 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/rust-mode/match @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: match +# key: match +# -- +match $1 { + $2 => $3, +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function new file mode 100644 index 000000000000..efa946bb272f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/sh-mode/function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Create function +# key: fn +# -- +$1() { + $2 +} \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark new file mode 100644 index 000000000000..797781968881 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/check-mark @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Unicode checkmark +# key: uck +# -- +โ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark new file mode 100644 index 000000000000..bc3c356a6157 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/text-mode/x-mark @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Unicode ex-mark +# key: ux +# -- +โ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents new file mode 100644 index 000000000000..d58dacb7a0b1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/.yas-parents @@ -0,0 +1 @@ +text-mode \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header new file mode 100644 index 000000000000..ae59c7a50f9c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/header @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Header +# key: hdr +# -- +/******************************************************************************* + * $1 + ******************************************************************************/ \ No newline at end of file diff --git a/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate new file mode 100644 index 000000000000..b791cdf86fe5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/snippets/web-mode/index-boilerplate @@ -0,0 +1,18 @@ +# -*- mode: snippet -*- +# name: HTML index.html starter +# key: html +# -- +<!doctype html> + +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>$1</title> + <meta name="description" content="$2"> + <meta name="author" content="William Carroll"> + <link rel="stylesheet" href="index.css"> +</head> +<body> + <script src="index.js"></script> +</body> +</html> diff --git a/users/wpcarro/emacs/.emacs.d/wpc/>.el b/users/wpcarro/emacs/.emacs.d/wpc/>.el new file mode 100644 index 000000000000..6d5f86f8b4e6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/>.el @@ -0,0 +1,28 @@ +;;; >.el --- Small utility functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Originally I stored the `>>` macro in macros.el, but after setting up linting +;; for my Elisp in CI, `>>` failed because it didn't have the `macros-` +;; namespace. I created this module to establish a `>-` namespace under which I +;; can store some utilities that would be best kept without a cumbersome +;; namespace. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro >-> (&rest forms) + "Compose a new, point-free function by composing FORMS together." + (let ((sym (gensym))) + `(lambda (,sym) + (->> ,sym ,@forms)))) + + +(provide '>) +;;; >.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/buffer.el b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el new file mode 100644 index 000000000000..0f86f7f811e6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el @@ -0,0 +1,174 @@ +;;; buffer.el --- Working with buffers -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Utilities for CRUDing buffers in Emacs. +;; +;; Many of these functions may seem unnecessary especially when you consider +;; there implementations. In general I believe that Elisp suffers from a +;; library disorganization problem. Providing simple wrapper functions that +;; rename functions or reorder parameters is worth the effort in my opinion if +;; it improves discoverability (via intuition) and improve composability. +;; +;; I support three ways for switching between what I'm calling "source code +;; buffers": +;; 1. Toggling previous: <SPC><SPC> +;; 2. Using `ivy-read': <SPC>b +;; TODO: These obscure evil KBDs. Maybe a hydra definition would be best? +;; 3. Cycling (forwards/backwards): C-f, C-b + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'maybe) +(require 'set) +(require 'cycle) +(require 'struct) +(require 'ts) +(require 'general) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst buffer-source-code-blacklist + (set-new 'dired-mode + 'erc-mode + 'vterm-mode + 'magit-status-mode + 'magit-process-mode + 'magit-log-mode + 'magit-diff-mode + 'org-mode + 'fundamental-mode) + "A blacklist of major-modes to ignore for listing source code buffers.") + +(defconst buffer-ivy-source-code-whitelist '("*scratch*" "*Messages*") + "A whitelist of buffers to include when listing source code buffers.") + +(defconst buffer-source-code-timeout 2 + "Number of seconds to wait before invalidating the cycle.") + +(cl-defstruct source-code-cycle cycle last-called) + +(defun buffer-emacs-generated? (name) + "Return t if buffer, NAME, is an Emacs-generated buffer. +Some buffers are Emacs-generated but are surrounded by whitespace." + (let ((trimmed (s-trim name))) + (and (s-starts-with? "*" trimmed)))) + +(defun buffer-find (buffer-or-name) + "Find a buffer by its BUFFER-OR-NAME." + (get-buffer buffer-or-name)) + +(defun buffer-major-mode (name) + "Return the active `major-mode' in buffer, NAME." + (with-current-buffer (buffer-find name) + major-mode)) + +(defun buffer-source-code-buffers () + "Return a list of source code buffers. +This will ignore Emacs-generated buffers, like *Messages*. It will also ignore + any buffer whose major mode is defined in `buffer-source-code-blacklist'." + (->> (buffer-list) + (list-map #'buffer-name) + (list-reject #'buffer-emacs-generated?) + (list-reject (lambda (name) + (set-contains? (buffer-major-mode name) + buffer-source-code-blacklist))))) + +(defvar buffer-source-code-cycle-state + (make-source-code-cycle + :cycle (cycle-from-list (buffer-source-code-buffers)) + :last-called (ts-now)) + "State used to manage cycling between source code buffers.") + +(defun buffer-exists? (name) + "Return t if buffer, NAME, exists." + (maybe-some? (buffer-find name))) + +(defun buffer-new (name) + "Return a newly created buffer NAME." + (generate-new-buffer name)) + +(defun buffer-find-or-create (name) + "Find or create buffer, NAME. +Return a reference to that buffer." + (let ((x (buffer-find name))) + (if (maybe-some? x) + x + (buffer-new name)))) + +;; TODO: Should this consume: `display-buffer' or `switch-to-buffer'? +(defun buffer-show (buffer-or-name) + "Display the BUFFER-OR-NAME, which is either a buffer reference or its name." + (display-buffer buffer-or-name)) + +;; TODO: Move this and `buffer-cycle-prev' into a separate module that +;; encapsulates all of this behavior. + +(defun buffer-cycle (cycle-fn) + "Using CYCLE-FN, move through `buffer-source-code-buffers'." + (let ((last-called (source-code-cycle-last-called + buffer-source-code-cycle-state)) + (cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (if (> (ts-diff (ts-now) last-called) + buffer-source-code-timeout) + (progn + (struct-set! source-code-cycle + cycle + (cycle-from-list (buffer-source-code-buffers)) + buffer-source-code-cycle-state) + (let ((cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle))) + (struct-set! source-code-cycle + last-called + (ts-now) + buffer-source-code-cycle-state)) + (progn + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle)))))) + +(defun buffer-cycle-next () + "Cycle forward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-next!)) + +(defun buffer-cycle-prev () + "Cycle backward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-prev!)) + +(defun buffer-ivy-source-code () + "Use `ivy-read' to choose among all open source code buffers." + (interactive) + (ivy-read "Source code buffer: " + (-concat buffer-ivy-source-code-whitelist + (-drop 1 (buffer-source-code-buffers))) + :sort nil + :action #'switch-to-buffer)) + +(defun buffer-show-previous () + "Call `switch-to-buffer' on the previously visited buffer. +This function ignores Emacs-generated buffers, i.e. the ones that look like + this: *Buffer*. It also ignores buffers that are `dired-mode' or `erc-mode'. + This blacklist can easily be changed." + (interactive) + (let* ((xs (buffer-source-code-buffers)) + (candidate (list-get 1 xs))) + (prelude-assert (maybe-some? candidate)) + (switch-to-buffer candidate))) + +(provide 'buffer) +;;; buffer.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el b/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el new file mode 100644 index 000000000000..ec2a46f5404f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el @@ -0,0 +1,40 @@ +;;; clipboard.el --- Working with X11's pasteboard -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Simple functions for copying and pasting. +;; +;; Integrate with bburns/clipmon so that System Clipboard can integrate with +;; Emacs's kill-ring. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun clipboard-copy (x &key (message "[clipboard.el] Copied!")) + "Copy string, X, to X11's clipboard and `message' MESSAGE." + (kill-new x) + (message message)) + +(cl-defun clipboard-paste (&key (message "[clipboard.el] Pasted!")) + "Paste contents of X11 clipboard and `message' MESSAGE." + (yank) + (message message)) + +(defun clipboard-contents () + "Return the contents of the clipboard as a string." + (substring-no-properties (current-kill 0))) + +(provide 'clipboard) +;;; clipboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/constants.el b/users/wpcarro/emacs/.emacs.d/wpc/constants.el new file mode 100644 index 000000000000..48bcd9042f78 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/constants.el @@ -0,0 +1,29 @@ +;;; constants.el --- Constants for organizing my Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file contains constants that are shared across my configuration. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst constants-ci? (maybe-some? (getenv "CI")) + "Defined as t when Emacs is running in CI.") + +(defconst constants-osx? (eq system-type 'darwin) + "Defined as t when OSX is running.") + +(provide 'constants) +;;; constants.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/display.el b/users/wpcarro/emacs/.emacs.d/wpc/display.el new file mode 100644 index 000000000000..69dae6939e40 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/display.el @@ -0,0 +1,103 @@ +;;; display.el --- Working with single or multiple displays -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Mostly wrappers around xrandr. +;; +;; Troubleshooting: +;; The following commands help me when I (infrequently) interact with xrandr. +;; - xrandr --listmonitors +;; - xrandr --query + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defmacro display-register (name &key + output + primary + coords + size + rate + dpi + rotate) + "Macro to define constants and two functions for {en,dis}abling a display. + +NAME - the human-readable identifier for the display +OUTPUT - the xrandr identifier for the display +PRIMARY - if true, send --primary flag to xrandr +COORDS - X and Y offsets +SIZE - the pixel resolution of the display (width height) +RATE - the refresh rate +DPI - the pixel density in dots per square inch +rotate - one of {normal,left,right,inverted} + +See the man-page for xrandr for more details." + `(progn + (defconst ,(intern (format "display-%s" name)) ,output + ,(format "The xrandr identifier for %s" name)) + (defconst ,(intern (format "display-%s-args" name)) + ,(replace-regexp-in-string + "\s+" " " + (s-format "--output ${output} ${primary-flag} --auto \ + --size ${size-x}x${size-y} --rate ${rate} --dpi ${dpi} \ + --rotate ${rotate} ${pos-flag}" + #'aget + `(("output" . ,output) + ("primary-flag" . ,(if primary "--primary" "--noprimary")) + ("pos-flag" . ,(if coords + (format "--pos %dx%d" + (car coords) + (cadr coords)) + "")) + ("size-x" . ,(car size)) + ("size-y" . ,(cadr size)) + ("rate" . ,rate) + ("dpi" . ,dpi) + ("rotate" . ,rotate)))) + ,(format "The arguments we pass to xrandr for display-%s." name)) + (defconst ,(intern (format "display-%s-command" name)) + (format "xrandr %s" ,(intern (format "display-%s-args" name))) + ,(format "The command we run to configure %s" name)) + (defun ,(intern (format "display-enable-%s" name)) () + ,(format "Attempt to enable my %s monitor" name) + (interactive) + (prelude-start-process + :name ,(format "display-enable-%s" name) + :command ,(intern (format "display-%s-command" name)))) + (defun ,(intern (format "display-disable-%s" name)) () + ,(format "Attempt to disable my %s monitor." name) + (interactive) + (prelude-start-process + :name ,(format "display-disable-%s" name) + :command ,(format + "xrandr --output %s --off" + output))))) + +(defmacro display-arrangement (name &key displays) + "Create a function, display-arrange-<NAME>, to enable all your DISPLAYS." + `(defun ,(intern (format "display-arrange-%s" name)) () + (interactive) + (prelude-start-process + :name ,(format "display-configure-%s" name) + :command ,(format "xrandr %s" + (->> displays + (-map (lambda (x) + (eval (intern (format "display-%s-args" x))))) + (s-join " ")))))) + +(provide 'display) +;;; display.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/email.el b/users/wpcarro/emacs/.emacs.d/wpc/email.el new file mode 100644 index 000000000000..a83ca25e6c17 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/email.el @@ -0,0 +1,76 @@ +;;; email.el --- My email settings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Attempting to configure to `notmuch' for my personal use. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'notmuch) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq notmuch-saved-searches + '((:name "inbox" :query "tag:inbox" :key "i") + (:name "direct" + :query "tag:direct and tag:unread and not tag:sent" + :key "d") + (:name "action" :query "tag:action" :key "a") + (:name "review" :query "tag:review" :key "r") + (:name "waiting" :query "tag:waiting" :key "w") + (:name "broadcast" :query "tag:/broadcast\/.+/ and tag:unread" :key "b") + (:name "systems" :query "tag:/systems\/.+/ and tag:unread" :key "s") + (:name "sent" :query "tag:sent" :key "t") + (:name "drafts" :query "tag:draft" :key "D"))) + +;; Sort results from newest-to-oldest. +(setq notmuch-search-oldest-first nil) + +;; Discard noisy email signatures. +(setq notmuch-mua-cite-function #'message-cite-original-without-signature) + +;; By default, this is just '("-inbox") +(setq notmuch-archive-tags '("-inbox" "-unread" "+archive")) + +;; Show saved searches even when they're empty. +(setq notmuch-show-empty-saved-searches t) + +;; Currently the sendmail executable on my system is symlinked to msmtp. +(setq send-mail-function #'sendmail-send-it) + +;; I'm not sure if I need this or not. Copying it from tazjin@'s monorepo. +(setq notmuch-always-prompt-for-sender nil) + +;; Add the "User-Agent" header to my emails and ensure that it includes Emacs +;; and notmuch information. +(setq notmuch-mua-user-agent-function + (lambda () + (format "Emacs %s; notmuch.el %s" emacs-version notmuch-emacs-version))) + +;; I was informed that Gmail does this server-side +(setq notmuch-fcc-dirs nil) + +;; Ensure buffers are closed after sending mail. +(setq message-kill-buffer-on-exit t) + +;; Ensure sender is correctly passed to msmtp. +(setq mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from 'header) + +;; Assert that no two saved searches share share a KBD +(prelude-assert + (list-xs-distinct-by? (lambda (x) (plist-get x :key)) notmuch-saved-searches)) + +(provide 'email) +;;; email.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/fonts.el b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el new file mode 100644 index 000000000000..0f70f69c2b8d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el @@ -0,0 +1,99 @@ +;;; fonts.el --- Font preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Control my font preferences with ELisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup fonts nil + "Customize group for fonts configuration.") + +(defcustom fonts-size "10" + "My preferred default font-size." + :group 'fonts) + +(defcustom fonts-size-step 10 + "The amount (%) by which to increase or decrease a font." + :group 'fonts) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun fonts-set (font &optional size) + "Change the font to `FONT' with option integer, SIZE, in pixels." + (if (maybe-some? size) + (set-frame-font (string-format "%s %s" font size) nil t) + (set-frame-font font nil t))) + +(defun fonts-current () + "Return the currently enabled font." + (symbol-name (font-get (face-attribute 'default :font) :family))) + +(defun fonts-increase-size () + "Increase font size." + (interactive) + (->> (face-attribute 'default :height) + (+ fonts-size-step) + (set-face-attribute 'default (selected-frame) :height))) + +(defun fonts-decrease-size () + "Decrease font size." + (interactive) + (->> (face-attribute 'default :height) + (+ (- fonts-size-step)) + (set-face-attribute 'default (selected-frame) :height))) + +(defun fonts-reset-size () + "Restore font size to its default value." + (interactive) + (fonts-set (fonts-current) fonts-size)) + +(defun fonts-enable-ligatures () + "Call this function to enable ligatures." + (interactive) + (let ((alist '((33 . ".\\(?:\\(?:==\\|!!\\)\\|[!=]\\)") + (35 . ".\\(?:###\\|##\\|_(\\|[#(?[_{]\\)") ;; + (36 . ".\\(?:>\\)") + (37 . ".\\(?:\\(?:%%\\)\\|%\\)") + (38 . ".\\(?:\\(?:&&\\)\\|&\\)") + (42 . ".\\(?:\\(?:\\*\\*/\\)\\|\\(?:\\*[*/]\\)\\|[*/>]\\)") ;; + (43 . ".\\(?:\\(?:\\+\\+\\)\\|[+>]\\)") + (45 . ".\\(?:\\(?:-[>-]\\|<<\\|>>\\)\\|[<>}~-]\\)") + (46 . ".\\(?:\\(?:\\.[.<]\\)\\|[.=-]\\)") ;; + (47 . ".\\(?:\\(?:\\*\\*\\|//\\|==\\)\\|[*/=>]\\)") + (48 . ".\\(?:x[a-zA-Z]\\)") + (58 . ".\\(?:::\\|[:=]\\)") + (59 . ".\\(?:;;\\|;\\)") + (60 . ".\\(?:\\(?:!--\\)\\|\\(?:~~\\|->\\|\\$>\\|\\*>\\|\\+>\\|--\\|<[<=-]\\|=[<=>]\\||>\\)\\|[*$+~/<=>|-]\\)") + (61 . ".\\(?:\\(?:/=\\|:=\\|<<\\|=[=>]\\|>>\\)\\|[<=>~]\\)") + (62 . ".\\(?:\\(?:=>\\|>[=>-]\\)\\|[=>-]\\)") + (63 . ".\\(?:\\(\\?\\?\\)\\|[:=?]\\)") + (91 . ".\\(?:]\\)") + (92 . ".\\(?:\\(?:\\\\\\\\\\)\\|\\\\\\)") + (94 . ".\\(?:=\\)") + (119 . ".\\(?:ww\\)") + (123 . ".\\(?:-\\)") + (124 . ".\\(?:\\(?:|[=|]\\)\\|[=>|]\\)") + (126 . ".\\(?:~>\\|~~\\|[>=@~-]\\)")))) + (dolist (char-regexp alist) + (set-char-table-range composition-function-table (car char-regexp) + `([,(cdr char-regexp) 0 font-shape-gstring]))))) + +(provide 'fonts) +;;; fonts.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el new file mode 100644 index 000000000000..3303237d52af --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el @@ -0,0 +1,67 @@ +;;; ivy-helpers.el --- More interfaces to ivy -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Hopefully to improve my workflows. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tuple) +(require 'string) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun ivy-helpers-kv (prompt kv f) + "PROMPT users with the keys in KV and return its corresponding value. + +Apply key and value from KV to F." + (ivy-read + prompt + kv + :require-match t + :action (lambda (entry) + (funcall f (car entry) (cdr entry))))) + +(defun ivy-helpers-do-run-external-command (cmd) + "Execute the specified CMD and notify the user when it finishes." + (message "Starting %s..." cmd) + (set-process-sentinel + (start-process-shell-command cmd nil cmd) + (lambda (process event) + (when (string= event "finished\n") + (message "%s process finished." process))))) + +(defun ivy-helpers-list-external-commands () + "Create a list of all external commands available on $PATH." + (cl-loop + for dir in (split-string (getenv "PATH") path-separator) + when (and (file-exists-p dir) (file-accessible-directory-p dir)) + for lsdir = (cl-loop for i in (directory-files dir t) + for bn = (file-name-nondirectory i) + when (and (not (s-contains? "-wrapped" i)) + (not (member bn completions)) + (not (file-directory-p i)) + (file-executable-p i)) + collect bn) + append lsdir into completions + finally return (sort completions 'string-lessp))) + +(defun ivy-helpers-run-external-command () + "Prompts the user with a list of all installed applications to launch." + (interactive) + (let ((external-commands-list (ivy-helpers-list-external-commands))) + (ivy-read "Command:" external-commands-list + :require-match t + :action #'ivy-helpers-do-run-external-command))) + +;;; Code: +(provide 'ivy-helpers) +;;; ivy-helpers.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/kbd.el b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el new file mode 100644 index 000000000000..7defc3d08f3b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el @@ -0,0 +1,85 @@ +;;; kbd.el --- Elisp keybinding -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; In order to stay organized, I'm attempting to dedicate KBD prefixes to +;; specific functions. I'm hoping I can be more deliberate with my keybinding +;; choices this way. +;; +;; Terminology: +;; For a more thorough overview of the terminology refer to `keybindings.md' +;; file. Here's a brief overview: +;; - workspace: Anything concerning EXWM workspaces. +;; - x11: Anything concerning X11 applications. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'al) +(require 'set) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst kbd-prefixes + '((workspace . "s") + (x11 . "C-s")) + "Mapping of functions to designated keybinding prefixes to stay organized.") + +;; Assert that no keybindings are colliding. +(prelude-assert + (= (al-count kbd-prefixes) + (->> kbd-prefixes + al-values + set-from-list + set-count))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun kbd-raw (f x) + "Return the string keybinding for function F and appendage X. +Values for F include: +- workspace +- x11" + (prelude-assert (al-has-key? f kbd-prefixes)) + (string-format + "%s-%s" + (al-get f kbd-prefixes) + x)) + +(defun kbd-for (f x) + "Return the `kbd' for function F and appendage X. +Values for F include: +- workspace +- x11" + (kbd (kbd-raw f x))) + +;; TODO: Prefer copying human-readable versions to the clipboard. Right now +;; this isn't too useful. +(defun kbd-copy-keycode () + "Copy the pressed key to the system clipboard." + (interactive) + (message "[kbd] Awaiting keypress...") + (let ((key (read-key))) + (clipboard-copy (string-format "%s" key)) + (message (string-format "[kbd] \"%s\" copied!" key)))) + +(defun kbd-print-keycode () + "Prints the pressed keybinding." + (interactive) + (message "[kbd] Awaiting keypress...") + (message (string-format "[kbd] keycode: %s" (read-key)))) + +(provide 'kbd) +;;; kbd.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el new file mode 100644 index 000000000000..a55bf2733011 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el @@ -0,0 +1,495 @@ +;;; keybindings.el --- Centralizing my keybindings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Attempting to centralize my keybindings to simplify my configuration. +;; +;; I have some expectations about my keybindings. Here are some of those +;; defined: +;; - In insert mode: +;; - C-a: beginning-of-line +;; - C-e: end-of-line +;; - C-b: backwards-char +;; - C-f: forwards-char + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'screen-brightness) +(require 'pulse-audio) +(require 'scrot) +(require 'ivy) +(require 'ivy-clipmenu) +(require 'ivy-helpers) +(require 'general) +(require 'exwm) +(require 'vterm-mgt) +(require 'buffer) +(require 'fonts) +(require 'bookmark) +(require 'tvl) +(require 'window-manager) + +;; Note: The following lines must be sorted this way. +(setq evil-want-integration t) +(setq evil-want-keybinding nil) +(general-evil-setup) +(require 'evil) +(require 'evil-collection) +(require 'evil-commentary) +(require 'evil-surround) +(require 'key-chord) +(require 'edebug) +(require 'avy) +(require 'passage) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helper Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun keybindings--window-vsplit-right () + "Split the window vertically and focus the right half." + (interactive) + (evil-window-vsplit) + (windmove-right)) + +(defun keybindings--window-split-down () + "Split the window horizontal and focus the bottom half." + (interactive) + (evil-window-split) + (windmove-down)) + +(defun keybindings--create-snippet () + "Create a window split and then opens the Yasnippet editor." + (interactive) + (evil-window-vsplit) + (call-interactively #'yas-new-snippet)) + +(defun keybindings--replace-under-point () + "Faster than typing %s//thing/g." + (interactive) + (let ((term (s-replace "/" "\\/" (symbol-to-string (symbol-at-point))))) + (save-excursion + (evil-ex (concat "%s/\\b" term "\\b/"))))) + +(defun keybindings--evil-ex-define-cmd-local (cmd f) + "Define CMD to F locally to a buffer." + (unless (local-variable-p 'evil-ex-commands) + (setq-local evil-ex-commands (copy-alist evil-ex-commands))) + (evil-ex-define-cmd cmd f)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; General Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Ensure that evil's command mode behaves with readline bindings. +(general-define-key + :keymaps 'evil-ex-completion-map + "C-a" #'move-beginning-of-line + "C-e" #'move-end-of-line + "C-k" #'kill-line + "C-u" #'evil-delete-whole-line + "C-v" #'evil-paste-after + "C-d" #'delete-char + "C-f" #'forward-char + "M-b" #'backward-word + "M-f" #'forward-word + "M-d" #'kill-word + "M-DEL" #'backward-kill-word + "C-b" #'backward-char) + +(general-mmap + :keymaps 'override + "RET" #'evil-goto-line + "H" #'evil-first-non-blank + "L" #'evil-end-of-line + "_" #'ranger + "-" #'dired-jump + "sl" #'keybindings--window-vsplit-right + "sh" #'evil-window-vsplit + "sk" #'evil-window-split + "sj" #'keybindings--window-split-down) + +(general-nmap + :keymaps 'override + "gu" #'browse-url-at-point + "gd" #'xref-find-definitions + ;; Wrapping `xref-find-references' in the `let' binding to prevent xref from + ;; prompting. There are other ways to handle this variable, such as setting + ;; it globally with `setq' or buffer-locally with `setq-local'. For now, I + ;; prefer setting it with `let', which should bind it in the dynamic scope + ;; for the duration of the `xref-find-references' function call. + "gx" (lambda () + (interactive) + (let ((xref-prompt-for-identifier nil)) + (call-interactively #'xref-find-references)))) + +(general-unbind 'motion "M-." "C-p" "<SPC>") +(general-unbind 'normal "s" "M-." "C-p" "C-n") +(general-unbind 'insert "C-v" "C-d" "C-a" "C-e" "C-n" "C-p" "C-k") + +(customize-set-variable 'evil-symbol-word-search t) +(evil-mode 1) +(evil-collection-init) +(evil-commentary-mode) +(global-evil-surround-mode 1) + +;; Ensure the Evil search results get centered vertically. +;; When Emacs is run from a terminal, this forces Emacs to redraw itself, which +;; is visually disruptive. +(when window-system + (progn + (defadvice isearch-update + (before advice-for-isearch-update activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-next + (after advice-for-evil-search-next activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-previous + (after advice-for-evil-search-previous activate) + (evil-scroll-line-to-center (line-number-at-pos))))) + +(general-define-key + :keymaps '(isearch-mode-map) + "C-p" #'isearch-ring-retreat + "C-n" #'isearch-ring-advance + "<up>" #'isearch-ring-retreat + "<down>" #'isearch-ring-advance) + +(general-define-key + :keymaps '(minibuffer-local-isearch-map) + "C-p" #'previous-line-or-history-element + "C-n" #'next-line-or-history-element + "<up>" #'previous-line-or-history-element + "<down>" #'next-line-or-history-element) + +(key-chord-mode 1) +(key-chord-define evil-insert-state-map "jk" 'evil-normal-state) + +;; This may be contraversial, but I never use the prefix key, and I'd prefer to +;; have to bound to the readline function that deletes the entire line. +(general-unbind "C-u") + +(defmacro keybindings-exwm (c fn) + "Bind C to FN using `exwm-input-set-key' with `kbd' applied to C." + `(exwm-input-set-key (kbd ,c) ,fn)) + +(keybindings-exwm "C-M-v" #'ivy-clipmenu-copy) +(keybindings-exwm "<XF86MonBrightnessUp>" #'screen-brightness-increase) +(keybindings-exwm "<XF86MonBrightnessDown>" #'screen-brightness-decrease) +(keybindings-exwm "<XF86AudioMute>" #'pulse-audio-toggle-mute) +(keybindings-exwm "<XF86AudioLowerVolume>" #'pulse-audio-decrease-volume) +(keybindings-exwm "<XF86AudioRaiseVolume>" #'pulse-audio-increase-volume) +(keybindings-exwm "<XF86AudioMicMute>" #'pulse-audio-toggle-microphone) +(keybindings-exwm (kbd-raw 'x11 "s") #'scrot-select) +(keybindings-exwm "<C-M-tab>" #'window-manager-switch-to-exwm-buffer) +(keybindings-exwm (kbd-raw 'workspace "k") #'fonts-increase-size) +(keybindings-exwm (kbd-raw 'workspace "j") #'fonts-decrease-size) +(keybindings-exwm (kbd-raw 'workspace "0") #'fonts-reset-size) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window sizing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "C-M-=" #'balance-windows) +(keybindings-exwm "C-M-j" #'shrink-window) +(keybindings-exwm "C-M-k" #'enlarge-window) +(keybindings-exwm "C-M-h" #'shrink-window-horizontally) +(keybindings-exwm "C-M-l" #'enlarge-window-horizontally) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window Management +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-h" #'windmove-left) +(keybindings-exwm "M-j" #'windmove-down) +(keybindings-exwm "M-k" #'windmove-up) +(keybindings-exwm "M-l" #'windmove-right) +(keybindings-exwm "M-\\" #'evil-window-vsplit) +(keybindings-exwm "M--" #'evil-window-split) +(keybindings-exwm "M-q" #'delete-window) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-:" #'eval-expression) +(keybindings-exwm "M-SPC" #'ivy-helpers-run-external-command) +(keybindings-exwm "M-x" #'counsel-M-x) +(keybindings-exwm "<M-tab>" #'window-manager-next-workspace) +(keybindings-exwm "<M-S-iso-lefttab>" #'window-manager-prev-workspace) +(keybindings-exwm "C-S-f" #'window-manager-toggle-previous) +(keybindings-exwm "C-M-\\" #'passage-select) + +(defun keybindings-copy-emoji () + "Select an emoji from the completing-read menu." + (interactive) + (clipboard-copy (emojify-completing-read "Copy: "))) + +(keybindings-exwm "s-e" #'keybindings-copy-emoji) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Workspaces +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm (kbd-raw 'workspace "l") + (lambda () + (interactive) + (shell-command window-manager-screenlocker))) + +(general-define-key + :keymaps 'override + "M-q" #'delete-window + "<s-return>" #'toggle-frame-fullscreen + "M-h" #'windmove-left + "M-l" #'windmove-right + "M-k" #'windmove-up + "M-j" #'windmove-down + "M-q" #'delete-window) + +;; Support pasting in M-:. +(general-define-key + :keymaps 'read-expression-map + "C-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +(general-define-key + :prefix "<SPC>" + :states '(normal) + "." #'ffap + "gn" #'notmuch + "i" #'counsel-semantic-or-imenu + "I" #'ibuffer + "hk" #'helpful-callable + "hf" #'helpful-function + "hm" #'helpful-macro + "hc" #'helpful-command + "hk" #'helpful-key + "hv" #'helpful-variable + "hp" #'helpful-at-point + "hi" #'info-apropos + "s" #'flyspell-mode + "S" #'sort-lines + "=" #'align + "p" #'flycheck-previous-error + "f" #'project-find-file + "n" #'flycheck-next-error + "N" #'smerge-next + "W" #'balance-windows + "gss" #'magit-status + "gsd" #'tvl-depot-status + "E" #'refine + "es" #'keybindings--create-snippet + "l" #'linum-mode + "B" #'magit-blame + "w" #'save-buffer + "r" #'keybindings--replace-under-point + "R" #'deadgrep) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Vterm +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Show or hide a vterm buffer. I'm intentionally not defining this in +;; vterm-mgt.el because it consumes `buffer-show-previous', and I'd like to +;; avoid bloating vterm-mgt.el with dependencies that others may not want. +(general-define-key (kbd-raw 'x11 "t") + (lambda () + (interactive) + (if (vterm-mgt--instance? (current-buffer)) + (switch-to-buffer (first (buffer-source-code-buffers))) + (call-interactively #'vterm-mgt-find-or-create)))) + +(general-define-key + :keymaps '(vterm-mode-map) + ;; For some reason vterm captures this KBD instead of EXWM + "C-S-f" nil + "s-x" #'vterm-mgt-select + "C-S-n" #'vterm-mgt-instantiate + "C-S-w" #'vterm-mgt-kill + "<C-tab>" #'vterm-mgt-next + "<C-S-iso-lefttab>" #'vterm-mgt-prev + "<s-backspace>" #'vterm-mgt-rename-buffer + ;; Without this, typing "+" is effectively no-op. Try for yourself: + ;; (vterm-send-key "<kp-add>") + "<kp-add>" "+" + "M--" #'evil-window-split + "M-\\" #'evil-window-vsplit) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; notmuch +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; evil-collection adds many KBDs to notmuch modes. Some of these I find +;; disruptive. +(general-define-key + :states '(normal) + :keymaps '(notmuch-show-mode-map) + "M-j" nil + "M-k" nil + "<C-S-iso-lefttab>" #'notmuch-show-previous-thread-show + "<C-tab>" #'notmuch-show-next-thread-show + "e" #'notmuch-show-archive-message-then-next-or-next-thread) + +(add-hook 'notmuch-message-mode-hook + (lambda () + (keybindings--evil-ex-define-cmd-local "x" #'notmuch-mua-send-and-exit))) + +;; For now, I'm mimmicking Gmail KBDs that I have memorized and enjoy +(general-define-key + :states '(normal visual) + :keymaps '(notmuch-search-mode-map) + "M" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+muted"))) + "mi" (lambda () + (interactive) + (notmuch-search-tag '("+inbox" "-action" "-review" "-waiting" "-muted"))) + "ma" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+action" "-review" "-waiting"))) + "mr" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "+review" "-waiting"))) + "mw" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "-review" "+waiting"))) + "e" #'notmuch-search-archive-thread) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; magit +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(magit-status-mode-map + magit-log-mode-map + magit-revision-mode-map) + "l" #'evil-forward-char + "L" #'magit-log) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Info-mode +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; NOTE: I find some of the following, existing KBDs useful: +;; M-x info-apropos +;; u Info-up +;; M-n clone-buffer +(general-define-key + :states '(normal) + :keymaps '(Info-mode-map) + "SPC" nil + "g SPC" #'Info-scroll-up + "RET" #'Info-follow-nearest-node + "<C-tab>" #'Info-next + "<C-S-iso-lefttab>" #'Info-prev + "g l" #'Info-history-back + "g t" #'Info-toc) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ibuffer +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(ibuffer-mode-map) + "M-j" nil + "K" #'ibuffer-do-delete) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; buffers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + "C-f" #'buffer-cycle-next + "C-b" #'buffer-cycle-prev) + +(general-define-key + :prefix "<SPC>" + :states '(normal) + "b" #'buffer-ivy-source-code + "<SPC>" #'buffer-show-previous + "k" #'kill-buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; edebug +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(edebug-mode-map) + ;; this restores my ability to move-left while debugging + "h" nil) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; deadgrep +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(deadgrep-mode-map) + "<tab>" #'deadgrep-forward + "<backtab>" #'deadgrep-backward) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; bookmarks +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(bookmark-install-kbd + (make-bookmark :label "wpcarro" + :path (f-join tvl-depot-path "users/wpcarro") + :kbd "w")) + +(bookmark-install-kbd + (make-bookmark :label "depot" + :path tvl-depot-path + :kbd "d")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; refine +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :keymaps '(refine-mode-map) + :states '(normal) + "K" #'refine-delete + "q" #'kill-this-buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; avy +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(global-set-key (kbd "C-;") #'avy-goto-char) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ivy +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; restore the ability to paste in ivy +(general-define-key + :keymaps '(ivy-minibuffer-map) + "C-k" #'kill-line + "C-u" (lambda () (interactive) (kill-line 0)) + "C-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rust +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :keymaps '(rust-mode-map) + :states '(normal) + "gd" #'lsp-find-definition + "gr" #'lsp-find-references) + +(general-define-key + :keymaps '(rust-mode-map) + "TAB" #'company-indent-or-complete-common) + +(provide 'keybindings) +;;; keybindings.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el new file mode 100644 index 000000000000..0ee00e1b84e7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el @@ -0,0 +1,138 @@ +;;; keyboard.el --- Managing keyboard preferences with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Setting key repeat and other values. +;; +;; Be wary of suspiciously round numbers. Especially those divisible by ten! + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cl-lib) +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support clamping functions for repeat-{rate,delay} to ensure only valid +;; values are sent to xset. +(defcustom keyboard-repeat-rate 80 + "The number of key repeat signals sent per second.") + +(defcustom keyboard-repeat-delay 170 + "The number of milliseconds before autorepeat starts.") + +(defconst keyboard-repeat-rate-copy keyboard-repeat-rate + "Copy of `keyboard-repeat-rate' to support `keyboard-reset-key-repeat'.") + +(defconst keyboard-repeat-delay-copy keyboard-repeat-delay + "Copy of `keyboard-repeat-delay' to support `keyboard-reset-key-repeat'.") + +(defcustom keyboard-install-preferences? t + "When t, install keyboard preferences.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun keyboard-message (x) + "Message X in a structured way." + (message (format "[keyboard.el] %s" x))) + +(cl-defun keyboard-set-key-repeat (&key + (rate keyboard-repeat-rate) + (delay keyboard-repeat-delay)) + "Use xset to set the key-repeat RATE and DELAY." + (prelude-start-process + :name "keyboard-set-key-repeat" + :command (format "xset r rate %s %s" delay rate))) + +;; NOTE: Settings like this are machine-dependent. For instance I only need to +;; do this on my laptop and other devices where I don't have access to my split +;; keyboard. +;; NOTE: Running keysym Caps_Lock is not idempotent. If this is called more +;; than once, xmodmap will start to error about non-existent Caps_Lock symbol. +;; For more information see here: +;; https://unix.stackexchange.com/questions/108207/how-to-map-caps-lock-as-the-compose-key-using-xmodmap-portably-and-idempotently +(defun keyboard-swap-caps-lock-and-escape () + "Swaps the caps lock and escape keys using xmodmap." + (interactive) + ;; TODO: Ensure these work once the tokenizing in prelude-start-process works + ;; as expected. + (start-process "keyboard-swap-caps-lock-and-escape" + nil "/usr/bin/xmodmap" "-e" "remove Lock = Caps_Lock") + (start-process "keyboard-swap-caps-lock-and-escape" + nil "/usr/bin/xmodmap" "-e" "keysym Caps_Lock = Escape")) + +(defun keyboard-inc-repeat-rate () + "Increment `keyboard-repeat-rate'." + (interactive) + (setq keyboard-repeat-rate (1+ keyboard-repeat-rate)) + (keyboard-set-key-repeat :rate keyboard-repeat-rate) + (keyboard-message + (format "Rate: %s" keyboard-repeat-rate))) + +(defun keyboard-dec-repeat-rate () + "Decrement `keyboard-repeat-rate'." + (interactive) + (setq keyboard-repeat-rate (1- keyboard-repeat-rate)) + (keyboard-set-key-repeat :rate keyboard-repeat-rate) + (keyboard-message + (format "Rate: %s" keyboard-repeat-rate))) + +(defun keyboard-inc-repeat-delay () + "Increment `keyboard-repeat-delay'." + (interactive) + (setq keyboard-repeat-delay (1+ keyboard-repeat-delay)) + (keyboard-set-key-repeat :delay keyboard-repeat-delay) + (keyboard-message + (format "Delay: %s" keyboard-repeat-delay))) + +(defun keyboard-dec-repeat-delay () + "Decrement `keyboard-repeat-delay'." + (interactive) + (setq keyboard-repeat-delay (1- keyboard-repeat-delay)) + (keyboard-set-key-repeat :delay keyboard-repeat-delay) + (keyboard-message + (format "Delay: %s" keyboard-repeat-delay))) + +(defun keyboard-print-key-repeat () + "Print the currently set values for key repeat." + (interactive) + (keyboard-message + (format "Rate: %s. Delay: %s" + keyboard-repeat-rate + keyboard-repeat-delay))) + +(defun keyboard-set-preferences () + "Reset the keyboard preferences to their default values. +NOTE: This function exists because occasionally I unplug and re-plug in a + keyboard and all of the preferences that I set using xset disappear." + (interactive) + (keyboard-swap-caps-lock-and-escape) + (keyboard-set-key-repeat :rate keyboard-repeat-rate + :delay keyboard-repeat-delay) + ;; TODO: Implement this message function as a macro that pulls the current + ;; file name. + (keyboard-message "Keyboard preferences set!")) + +(defun keyboard-reset-key-repeat () + "Set key repeat rate and delay to original values." + (interactive) + (keyboard-set-key-repeat :rate keyboard-repeat-rate-copy + :delay keyboard-repeat-delay-copy) + (keyboard-message "Key repeat preferences reset.")) + +(when keyboard-install-preferences? + (keyboard-set-preferences)) + +(provide 'keyboard) +;;; keyboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/modeline.el b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el new file mode 100644 index 000000000000..df1cddec9d92 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el @@ -0,0 +1,68 @@ +;;; modeline.el --- Customize my mode-line -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Because I use EXWM, I treat my Emacs mode-line like my system bar: I need to +;; quickly check the system time, and I expect it to be at the bottom-right of +;; my Emacs frame. I used doom-modeline for awhile, which is an impressive +;; package, but it conditionally colorizes on the modeline for the active +;; buffer. So if my bottom-right window is inactive, I cannot see the time. +;; +;; My friend, @tazjin, has a modeline setup that I think is more compatible with +;; EXWM, so I'm going to base my setup off of his. + +;;; Code: + +(use-package telephone-line) + +(defun modeline-bottom-right-window? () + "Determines whether the last (i.e. +bottom-right) window of the +active frame is showing the buffer in which this function is + executed." + (let* ((frame (selected-frame)) + (right-windows (window-at-side-list frame 'right)) + (bottom-windows (window-at-side-list frame 'bottom)) + (last-window (car (seq-intersection right-windows bottom-windows)))) + (eq (current-buffer) (window-buffer last-window)))) + +(defun modeline-maybe-render-time () + "Conditionally renders the `mode-line-misc-info' string. + + The idea is to not display information like the current time, + load, battery levels on all buffers." + (when (modeline-bottom-right-window?) + (telephone-line-raw mode-line-misc-info t))) + +(defun modeline-setup () + "Render my custom modeline." + (telephone-line-defsegment telephone-line-last-window-segment () + (modeline-maybe-render-time)) + ;; Display the current EXWM workspace index in the mode-line + (telephone-line-defsegment telephone-line-exwm-workspace-index () + (when (modeline-bottom-right-window?) + (format "[%s]" exwm-workspace-current-index))) + ;; Define a highlight font for ~ important ~ information in the last + ;; window. + (defface special-highlight + '((t (:foreground "white" :background "#5f627f"))) "") + (add-to-list 'telephone-line-faces + '(highlight . (special-highlight . special-highlight))) + (setq telephone-line-lhs + '((nil . (telephone-line-position-segment)) + (accent . (telephone-line-buffer-segment)))) + (setq telephone-line-rhs + '((accent . (telephone-line-major-mode-segment)) + (nil . (telephone-line-last-window-segment + telephone-line-exwm-workspace-index)))) + (setq telephone-line-primary-left-separator 'telephone-line-tan-left + telephone-line-primary-right-separator 'telephone-line-tan-right + telephone-line-secondary-left-separator 'telephone-line-tan-hollow-left + telephone-line-secondary-right-separator 'telephone-line-tan-hollow-right) + (telephone-line-mode 1)) + +(provide 'modeline) +;;; modeline.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/prelude.el b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el new file mode 100644 index 000000000000..4a332cb8ca0e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el @@ -0,0 +1,144 @@ +;;; prelude.el --- My attempt at augmenting Elisp stdlib -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Some of these ideas are scattered across other modules like `fs', +;; `string-functions', etc. I'd like to keep everything modular. I still don't +;; have an answer for which items belond in `misc'; I don't want that to become +;; a dumping grounds. Ideally this file will `require' all other modules and +;; define just a handful of functions. + +;; TODO: Consider removing all dependencies from prelude.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 's) +(require 'f) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Utilities +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-to-string (x) + "Convert X to a string." + (format "%s" x)) + +(defun prelude-inspect (&rest args) + "Message ARGS where ARGS are any type." + (->> args + (-map #'prelude-to-string) + (apply #'s-concat) + message)) + +(defmacro prelude-call-process-to-string (cmd &rest args) + "Return the string output of CMD called with ARGS." + `(with-temp-buffer + (call-process ,cmd nil (current-buffer) nil ,@args) + (buffer-string))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Assertions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Should I `throw' instead of `error' here? +(defmacro prelude-assert (x) + "Errors unless X is t. +These are strict assertions and purposely do not rely on truthiness." + (let ((as-string (prelude-to-string x))) + `(unless (equal t ,x) + (error (s-concat "Assertion failed: " ,as-string))))) + +(defmacro prelude-refute (x) + "Errors unless X is nil." + (let ((as-string (prelude-to-string x))) + `(unless (equal nil ,x) + (error (s-concat "Refutation failed: " ,as-string))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Adapter functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-identity (x) + "Return X unchanged." + x) + +(defun prelude-const (x) + "Return a variadic lambda that will return X." + (lambda (&rest _) x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider packaging these into a linum-color.el package. +;; TODO: Generate the color used here from the theme. +(defvar prelude--linum-safe? nil + "Flag indicating whether it is safe to work with function `linum-mode'.") + +(defvar prelude--linum-mru-color nil + "Stores the color most recently attempted to be applied.") + +(add-hook 'linum-mode-hook + (lambda () + (setq prelude--linum-safe? t) + (when (maybe-some? prelude--linum-mru-color) + (set-face-foreground 'linum prelude--linum-mru-color)))) + +(defun prelude-set-line-number-color (color) + "Safely set linum color to `COLOR'. + +If this is called before Emacs initializes, the color will be stored in +`prelude--linum-mru-color' and applied once initialization completes. + +Why is this safe? +If `(set-face-foreground 'linum)' is called before initialization completes, +Emacs will silently fail. Without this function, it is easy to introduce +difficult to troubleshoot bugs in your init files." + (if prelude--linum-safe? + (set-face-foreground 'linum color) + (setq prelude--linum-mru-color color))) + +(defun prelude-prompt (prompt) + "Read input from user with PROMPT." + (read-string prompt)) + +(cl-defun prelude-start-process (&key name command) + "Pass command string, COMMAND, and the function name, NAME. +This is a wrapper around `start-process' that has an API that resembles +`shell-command'." + ;; TODO: Fix the bug with tokenizing here, since it will split any whitespace + ;; character, even though it shouldn't in the case of quoted string in shell. + ;; e.g. - "xmodmap -e 'one two three'" => '("xmodmap" "-e" "'one two three'") + (prelude-refute (s-contains? "'" command)) + (let* ((tokens (s-split " " command)) + (program-name (nth 0 tokens)) + (program-args (cdr tokens))) + (apply #'start-process + `(,(format "*%s<%s>*" program-name name) + ,nil + ,program-name + ,@program-args)))) + +(defun prelude-executable-exists? (name) + "Return t if CLI tool NAME exists according to the variable `exec-path'." + (let ((file (locate-file name exec-path))) + (require 'maybe) + (if (maybe-some? file) + (f-exists? file) + nil))) + +(defmacro prelude-time (x) + "Print the time it takes to evaluate X." + `(benchmark 1 ',x)) + +(provide 'prelude) +;;; prelude.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el new file mode 100644 index 000000000000..eaa610659073 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el @@ -0,0 +1,69 @@ +;;; pulse-audio.el --- Control audio with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Because everything in my configuration is turning into Elisp these days. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst pulse-audio--step-size 5 + "The size by which to increase or decrease the volume.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun pulse-audio--message (x) + "Output X to *Messages*." + (message (string-format "[pulse-audio.el] %s" x))) + +(defun pulse-audio-toggle-mute () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-mute" + :command "pactl set-sink-mute @DEFAULT_SINK@ toggle") + (pulse-audio--message "Mute toggled.")) + +(defun pulse-audio-toggle-microphone () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-microphone" + :command "pactl set-source-mute @DEFAULT_SOURCE@ toggle") + (pulse-audio--message "Microphone toggled.")) + +(defun pulse-audio-decrease-volume () + "Low the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-decrease-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ -%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume decreased.")) + +(defun pulse-audio-increase-volume () + "Raise the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-increase-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ +%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume increased.")) + +(provide 'pulse-audio) +;;; pulse-audio.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/region.el b/users/wpcarro/emacs/.emacs.d/wpc/region.el new file mode 100644 index 000000000000..0b692981f899 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/region.el @@ -0,0 +1,23 @@ +;;; region.el --- Functions for working with regions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Sometimes Emacs's function names and argument ordering is great; other times, +;; it isn't. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun region-to-string () + "Return the string in the active region." + (buffer-substring-no-properties (region-beginning) + (region-end))) + +(provide 'region) +;;; region.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el new file mode 100644 index 000000000000..851be9f99f80 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el @@ -0,0 +1,57 @@ +;;; screen-brightness.el --- Control laptop screen brightness -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Control your laptop's screen brightness. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup screen-brightness nil "Configuration for screen-brightness.") + +(defcustom screen-brightness-increase-cmd + "light -A 3" + "The shell command to run to increase screen brightness." + :group 'screen-brightness + :type 'string) + +(defcustom screen-brightness-decrease-cmd + "light -U 3" + "The shell command to run to decrease screen brightness." + :group 'screen-brightness + :type 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun screen-brightness-increase () + "Increase the screen brightness." + (interactive) + (prelude-start-process + :name "screen-brightness-increase" + :command screen-brightness-increase-cmd) + (message "[screen-brightness.el] Increased screen brightness.")) + +(defun screen-brightness-decrease () + "Decrease the screen brightness." + (interactive) + (prelude-start-process + :name "screen-brightness-decrease" + :command screen-brightness-decrease-cmd) + (message "[screen-brightness.el] Decreased screen brightness.")) + +(provide 'screen-brightness) +;;; screen-brightness.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/scrot.el b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el new file mode 100644 index 000000000000..08994fea5fda --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el @@ -0,0 +1,54 @@ +;;; scrot.el --- Screenshot functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; scrot is a Linux utility for taking screenshots. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'ts) +(require 'clipboard) +(require 'kbd) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst scrot-screenshot-directory "~/Downloads" + "The default directory for screenshot outputs.") + +(defconst scrot-output-format "screenshot_%H:%M:%S_%Y-%m-%d.png" + "The format string for the output screenshot file. +See scrot's man page for more information.") + +(defun scrot--copy-image (path) + "Use xclip to copy the image at PATH to the clipboard. +This currently only works for PNG files because that's what I'm outputting" + (call-process "xclip" nil nil nil + "-selection" "clipboard" "-t" "image/png" path) + (message (string-format "[scrot.el] Image copied to clipboard!"))) + +(defun scrot-select () + "Click-and-drag to screenshot a region. +The output path is copied to the user's clipboard." + (interactive) + (let ((screenshot-path (f-join scrot-screenshot-directory + (ts-format scrot-output-format (ts-now))))) + (make-process + :name "scrot-select" + :command `("scrot" "--select" ,screenshot-path) + :sentinel (lambda (proc _err) + (when (= 0 (process-exit-status proc)) + (scrot--copy-image screenshot-path)))))) + +(provide 'scrot) +;;; scrot.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ssh.el b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el new file mode 100644 index 000000000000..1179e9036334 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el @@ -0,0 +1,67 @@ +;;; ssh.el --- When working remotely -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Configuration to make remote work easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tramp) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Is "ssh" preferable to "scp"? +(setq tramp-default-method "ssh") + +;; Taken from: https://superuser.com/questions/179313/tramp-waiting-for-prompts-from-remote-shell +(setq tramp-shell-prompt-pattern "^[^$>\n]*[#$%>] *\\(\[[0-9;]*[a-zA-Z] *\\)*") + +;; Sets the value of the TERM variable to "dumb" when logging into the remote +;; host. This allows me to check for the value of "dumb" in my shell's init file +;; and control the startup accordingly. You can see in the (shamefully large) +;; commit, 0b4ef0e, that I added a check like this to my ~/.zshrc. I've since +;; switched from z-shell to fish. I don't currently have this check in +;; config.fish, but I may need to add it one day soon. +(setq tramp-terminal-type "dumb") + +;; Maximizes the tramp debugging noisiness while I'm still learning about tramp. +(setq tramp-verbose 10) + +;; As confusing as this may seem, this forces Tramp to use *my* .ssh/config +;; options, which enable ControlMaster. In other words, disabling this actually +;; enables ControlMaster. +(setq tramp-use-ssh-controlmaster-options nil) + +(defcustom ssh-hosts '("wpcarro@wpcarro.dev" + "foundation" + "edge") + "List of hosts to which I commonly connect.") + +(defun ssh-sudo-buffer () + "Open the current buffer with sudo rights." + (interactive) + (with-current-buffer (current-buffer) + (if (s-starts-with? "/ssh:" buffer-file-name) + (pcase (s-split ":" buffer-file-name) + (`(,one ,two ,three) (find-file (format "/ssh:%s|sudo:%s:%s" two two three)))) + (find-file + (s-join ":" (-insert-at 2 "|sudo" (s-split ":" buffer-file-name)))) + (find-file (format "/sudo::%s" buffer-file-name))))) + +(defun ssh-cd-home () + "Prompt for an SSH host and open a dired buffer for wpcarro on that machine." + (interactive) + (let ((machine (completing-read "Machine: " ssh-hosts))) + (find-file (format "/ssh:%s:~" machine)))) + +(provide 'ssh) +;;; ssh.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el new file mode 100644 index 000000000000..94fb99d4271b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el @@ -0,0 +1,228 @@ +;;; window-manager.el --- Functions augmenting my usage of EXWM -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; I switched to EXWM from i3, and I haven't looked back. One day I may write a +;; poem declaring my love for Emacs and EXWM. For now, I haven't the time. + +;; Wist List: +;; - TODO: Consider supporting MRU cache of worksapces. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'alert) +(require 'cycle) +(require 'dash) +(require 'kbd) +(require 's) +(require 'exwm) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup window-manager nil + "Customization options for `window-manager'.") + +(cl-defstruct window-manager-named-workspace + label kbd display) + +(defcustom window-manager-named-workspaces nil + "List of `window-manager-named-workspace' structs." + :group 'window-manager + :type (list 'window-manager-named-workspace)) + +(defcustom window-manager-screenlocker "xsecurelock" + "Reference to a screen-locking executable." + :group 'window-manager + :type 'string) + +(defvar window-manager--workspaces nil + "Cycle of the my EXWM workspaces.") + +(defconst window-manager--modes + (cycle-from-list (list #'window-manager--char-mode + #'window-manager--line-mode)) + "Functions to switch exwm modes.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun window-manager--alert (x) + "Message X with a structured format." + (alert (s-concat "[exwm] " x))) + +(cl-defun window-manager-init (&key init-hook) + "Call `exwm-enable' alongside other bootstrapping functions." + (require 'exwm-config) + (require 'exwm-randr) + (setq exwm-randr-workspace-monitor-plist + (->> window-manager-named-workspaces + (-map-indexed (lambda (i x) + (list i (window-manager-named-workspace-display x)))) + -flatten)) + (setq exwm-workspace-number (length window-manager-named-workspaces)) + (setq exwm-input-simulation-keys + '(([?\C-b] . [left]) + ([?\M-b] . [C-left]) + ([?\C-f] . [right]) + ([?\M-f] . [C-right]) + ([?\C-p] . [up]) + ([?\C-n] . [down]) + ([?\C-a] . [home]) + ([?\C-e] . [end]) + ([?\C-d] . [delete]) + ([?\C-c] . [C-c]))) + ;; Install workspace KBDs + (progn + (->> window-manager-named-workspaces + (list-map #'window-manager--register-kbd)) + (window-manager--alert "Registered workspace KBDs!")) + ;; Ensure exwm apps open in char-mode. + (add-hook 'exwm-manage-finish-hook #'window-manager--char-mode) + (add-hook 'exwm-init-hook init-hook) + (setq window-manager--workspaces + (cycle-from-list window-manager-named-workspaces)) + (exwm-randr-enable) + (exwm-enable)) + +(defun window-manager-next-workspace () + "Cycle forwards to the next workspace." + (interactive) + (window-manager--change-workspace (cycle-next! window-manager--workspaces))) + +(defun window-manager-prev-workspace () + "Cycle backwards to the previous workspace." + (interactive) + (window-manager--change-workspace (cycle-prev! window-manager--workspaces))) + +;; Here is the code required to toggle EXWM's modes. +(defun window-manager--line-mode () + "Switch exwm to line-mode." + (call-interactively #'exwm-input-grab-keyboard) + (window-manager--alert "Switched to line-mode")) + +(defun window-manager--char-mode () + "Switch exwm to char-mode." + (call-interactively #'exwm-input-release-keyboard) + (window-manager--alert "Switched to char-mode")) + +(defun window-manager-toggle-mode () + "Switch between line- and char- mode." + (interactive) + (with-current-buffer (window-buffer) + (when (eq major-mode 'exwm-mode) + (funcall (cycle-next! window-manager--modes))))) + +(defun window-manager--label->index (label workspaces) + "Return the index of the workspace in WORKSPACES named LABEL." + (let ((index (-elem-index label (-map #'window-manager-named-workspace-label + workspaces)))) + (if index index (error (format "No workspace found for label: %s" label))))) + +(defun window-manager--register-kbd (workspace) + "Registers a keybinding for WORKSPACE struct. +Currently using super- as the prefix for switching workspaces." + (let ((handler (lambda () + (interactive) + (window-manager--switch + (window-manager-named-workspace-label workspace)))) + (key (window-manager-named-workspace-kbd workspace))) + (exwm-input-set-key + (kbd-for 'workspace key) + handler))) + +(defun window-manager--change-workspace (workspace) + "Switch EXWM workspaces to the WORKSPACE struct." + (exwm-workspace-switch + (window-manager--label->index + (window-manager-named-workspace-label workspace) + window-manager-named-workspaces)) + (window-manager--alert + (format "Switched to: %s" + (window-manager-named-workspace-label workspace)))) + +(defun window-manager--switch (label) + "Switch to a named workspaces using LABEL." + (cycle-focus! (lambda (x) + (equal label + (window-manager-named-workspace-label x))) + window-manager--workspaces) + (window-manager--change-workspace (cycle-current window-manager--workspaces))) + +(defun window-manager-toggle-previous () + "Focus the previously active EXWM workspace." + (interactive) + (window-manager--change-workspace + (cycle-focus-previous! window-manager--workspaces))) + +(defun window-manager--exwm-buffer? (x) + "Return t if buffer X is an EXWM buffer." + (equal 'exwm-mode (buffer-local-value 'major-mode x))) + +(defun window-manager--application-name (buffer) + "Return the name of the application running in the EXWM BUFFER. +This function asssumes that BUFFER passes the `window-manager--exwm-buffer?' +predicate." + (with-current-buffer buffer exwm-class-name)) + +;; TODO: Support disambiguating between two or more instances of the same +;; application. For instance if two `exwm-class-name' values are +;; "Google-chrome", find a encode this information in the `buffer-alist'. +(defun window-manager-switch-to-exwm-buffer () + "Use `completing-read' to focus an EXWM buffer." + (interactive) + (let* ((buffer-alist (->> (buffer-list) + (-filter #'window-manager--exwm-buffer?) + (-map + (lambda (buffer) + (cons (window-manager--application-name buffer) + buffer))))) + (label (completing-read "Switch to EXWM buffer: " buffer-alist))) + (exwm-workspace-switch-to-buffer + (al-get label buffer-alist)))) + +(defun window-manager-current-workspace () + "Output the label of the currently active workspace." + (->> window-manager--workspaces + cycle-current + window-manager-named-workspace-label)) + +(defun window-manager-workspace-move () + "Prompt the user to move the current workspace to another." + (interactive) + (exwm-workspace-move + exwm-workspace--current + (window-manager--label->index + (completing-read "Move current workspace to: " + (->> window-manager-named-workspaces + (-map #'window-manager-named-workspace-label)) + nil + t) + window-manager-named-workspaces))) + +(defun window-manager-move-window () + "Prompt the user to move the current window to another workspace." + (interactive) + (let ((window (get-buffer-window)) + (dest (completing-read "Move current window to: " + (->> window-manager-named-workspaces + (-map #'window-manager-named-workspace-label)) + nil + t))) + (exwm-workspace-move-window + (exwm-workspace--workspace-from-frame-or-index + (window-manager--label->index dest window-manager-named-workspaces)) + (exwm--buffer->id window)) + (window-manager--switch dest))) + +(provide 'window-manager) +;;; window-manager.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el new file mode 100644 index 000000000000..5582641b3f35 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el @@ -0,0 +1,71 @@ +;;; wpc-clojure.el --- My Clojure preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosting my Clojure tooling preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package clojure-mode + :config + ;; from Ryan Schmukler: + (setq cljr-magic-require-namespaces + '(("io" . "clojure.java.io") + ("sh" . "clojure.java.shell") + ("jdbc" . "clojure.java.jdbc") + ("set" . "clojure.set") + ("time" . "java-time") + ("str" . "cuerdas.core") + ("path" . "pathetic.core") + ("walk" . "clojure.walk") + ("zip" . "clojure.zip") + ("async" . "clojure.core.async") + ("component" . "com.stuartsierra.component") + ("http" . "clj-http.client") + ("url" . "cemerick.url") + ("sql" . "honeysql.core") + ("csv" . "clojure.data.csv") + ("json" . "cheshire.core") + ("s" . "clojure.spec.alpha") + ("fs" . "me.raynes.fs") + ("ig" . "integrant.core") + ("cp" . "com.climate.claypoole") + ("re-frame" . "re-frame.core") + ("rf" . "re-frame.core") + ("re" . "reagent.core") + ("reagent" . "reagent.core") + ("u.core" . "utopia.core") + ("gen" . "clojure.spec.gen.alpha")))) + +(use-package cider + :config + (general-define-key + :keymaps 'cider-repl-mode-map + "C-l" #'cider-repl-clear-buffer + "C-u" #'kill-whole-line + "<up>" #'cider-repl-previous-input + "<down>" #'cider-repl-next-input) + (general-define-key + :keymaps 'clojure-mode-map + :states '(normal) + :prefix "<SPC>" + "x" #'cider-eval-defun-at-point + "X" #'cider-eval-buffer + "d" #'cider-symbol-at-point) + (setq cider-prompt-for-symbol nil)) + +(provide 'wpc-clojure) +;;; wpc-clojure.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el new file mode 100644 index 000000000000..89fde4b6a177 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el @@ -0,0 +1,41 @@ +;;; wpc-company.el --- Autocompletion package, company, preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosts my company mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; autocompletion client +(use-package company + :config + (general-define-key + :keymaps 'company-active-map + "C-j" #'company-select-next + "C-n" #'company-select-next + "C-k" #'company-select-previous + "C-p" #'company-select-previous + "C-d" #'company-show-doc-buffer) + (setq company-tooltip-align-annotations t) + (setq company-idle-delay 0) + (setq company-show-numbers t) + (setq company-minimum-prefix-length 2) + (setq company-dabbrev-downcase nil + company-dabbrev-ignore-case t) + (global-company-mode)) + +(provide 'wpc-company) +;;; wpc-company.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el new file mode 100644 index 000000000000..7c22a4657fc0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el @@ -0,0 +1,51 @@ +;;; wpc-dired.el --- My dired preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; File management in Emacs, if learned and configured properly, should be +;; capable to reduce my dependency on the terminal. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (require 'dired) + (setq dired-recursive-copies 'always + dired-recursive-deletes 'top) + (setq dired-listing-switches "-la --group-directories-first") + (general-define-key + :keymaps 'dired-mode-map + :states '(normal) + ;; Overriding some KBDs defined in the evil-collection module. + "o" #'dired-find-file-other-window + "<SPC>" nil ;; This unblocks some of my leader-prefixed KBDs. + "s" nil ;; This unblocks my window-splitting KBDs. + "c" #'find-file + "f" #'project-find-file + "-" (lambda () (interactive) (find-alternate-file ".."))) + (general-add-hook 'dired-mode-hook + (list (macros-enable dired-hide-details-mode) + #'auto-revert-mode))) + +(progn + (require 'locate) + (general-define-key + :keymaps 'locate-mode-map + :states 'normal + "o" #'dired-find-file-other-window)) + +(provide 'wpc-dired) +;;; wpc-dired.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-dotnet.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dotnet.el new file mode 100644 index 000000000000..03fc430e4846 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dotnet.el @@ -0,0 +1,16 @@ +;;; wpc-dotnet.el --- C# and company -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Windows things v0v. + +;;; Code: + +(require 'macros) + +(use-package csharp-mode) +(macros-support-file-extension "csproj" xml-mode) + +(provide 'wpc-dotnet) +;;; wpc-dotnet.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el new file mode 100644 index 000000000000..69259274c86d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el @@ -0,0 +1,27 @@ +;;; wpc-elixir.el --- Elixir / Erland configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; My preferences for working with Elixir / Erlang projects + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package elixir-mode + :config + (macros-add-hook-before-save 'elixir-mode-hook #'elixir-format)) + +(provide 'wpc-elixir) +;;; wpc-elixir.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el new file mode 100644 index 000000000000..c32c5daeff13 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el @@ -0,0 +1,17 @@ +;;; wpc-flycheck.el --- My flycheck configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Flycheck preferences + +;;; Code: + +(use-package flycheck + :config + (global-flycheck-mode)) + +(provide 'wpc-flycheck) +;;; wpc-flycheck.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el new file mode 100644 index 000000000000..47198c8e02a1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el @@ -0,0 +1,42 @@ +;;; wpc-golang.el --- Tooling preferences for Go -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Tooling support for golang development. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support jumping to go source code for fmt.Println, etc. + +(use-package go-mode + :config + (setq gofmt-command "goimports") + ;; TODO: Consider configuring `xref-find-definitions' to use `godef-jump' + ;; instead of shadowing the KBD here. + (general-define-key + :states '(normal) + :keymaps '(go-mode-map) + "M-." #'godef-jump) + ;; Support calling M-x `compile'. + (add-hook 'go-mode-hook (lambda () + (setq-local tab-width 2) + (setq-local compile-command "go build -v"))) + (macros-add-hook-before-save 'go-mode-hook #'gofmt-before-save)) + +(provide 'wpc-golang) +;;; wpc-golang.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el new file mode 100644 index 000000000000..536790e36d61 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el @@ -0,0 +1,53 @@ +;;; wpc-haskell.el --- My Haskell preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Haskell development preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; font-locking, glyph support, etc +(use-package haskell-mode + :config + (macros-add-hook-before-save 'haskell-mode #'haskell-align-imports)) + +;; Test toggling +(defun wpc-haskell-module->test () + "Jump from a module to a test." + (let ((filename (->> buffer-file-name + (s-replace "/src/" "/test/") + (s-replace ".hs" "Test.hs") + find-file))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test->module () + "Jump from a test to a module." + (let ((filename (->> buffer-file-name + (s-replace "/test/" "/src/") + (s-replace "Test.hs" ".hs")))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test<->module () + "Toggle between test and module in Haskell." + (interactive) + (if (s-contains? "/src/" buffer-file-name) + (wpc-haskell-module->test) + (wpc-haskell-test->module))) + +(provide 'wpc-haskell) +;;; wpc-haskell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el new file mode 100644 index 000000000000..9e137ad8803e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el @@ -0,0 +1,93 @@ +;;; wpc-javascript.el --- My Javascript preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This module hosts my Javascript tooling preferences. This also includes +;; tooling for TypeScript and other frontend tooling. Perhaps this module will +;; change names to more accurately reflect that. +;; +;; Depends +;; - yarn global add prettier + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Constants +(defconst wpc-javascript--js-hooks + '(js-mode-hook + web-mode-hook + typescript-mode-hook + js2-mode-hook + rjsx-mode-hook) + "All of the commonly used hooks for Javascript buffers.") + +(defconst wpc-javascript--frontend-hooks + (-insert-at 0 'css-mode-hook wpc-javascript--js-hooks) + "All of the commonly user hooks for frontend development.") + +;; frontend indentation settings +(setq typescript-indent-level 2 + js-indent-level 2 + css-indent-offset 2) + +(use-package web-mode + :mode "\\.html\\'" + :config + (setq web-mode-css-indent-offset 2) + (setq web-mode-code-indent-offset 2) + (setq web-mode-markup-indent-offset 2)) + +;; JSX highlighting +(use-package rjsx-mode + :config + (general-unbind rjsx-mode-map "<" ">" "C-d") + (general-nmap + :keymaps 'rjsx-mode-map + "K" #'flow-minor-type-at-pos) + (setq js2-mode-show-parse-errors nil + js2-mode-show-strict-warnings nil)) + +(progn + (defun wpc-javascript-tide-setup () + (interactive) + (tide-setup) + (flycheck-mode 1) + (setq flycheck-check-syntax-automatically '(save mode-enabled)) + (eldoc-mode 1) + (tide-hl-identifier-mode 1) + (company-mode 1)) + (use-package tide + :config + (add-hook 'typescript-mode-hook #'wpc-javascript-tide-setup)) + (require 'web-mode) + (add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode)) + (add-hook 'web-mode-hook + (lambda () + (when (string-equal "tsx" (f-ext buffer-file-name)) + (wpc-javascript-tide-setup)))) + (flycheck-add-mode 'typescript-tslint 'web-mode)) + +;; JS autoformatting +(use-package prettier-js + :config + (general-add-hook wpc-javascript--frontend-hooks #'prettier-js-mode)) + +;; Support Elm +(use-package elm-mode + :config + (add-hook 'elm-mode-hook #'elm-format-on-save-mode)) + +(provide 'wpc-javascript) +;;; wpc-javascript.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-language-support.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-language-support.el new file mode 100644 index 000000000000..8363e3c08ea0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-language-support.el @@ -0,0 +1,36 @@ +;;; wpc-language-support.el --- Support for miscellaneous programming languages -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; I defined this module to declutter my init.el. +;; +;; When a particular programming-language's configuration gets too complicated, +;; I break it out into a dedicated module. Everything else gets dumped in +;; "Miscellaneous Configuration". + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dedicated Modules +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'wpc-lisp) +(require 'wpc-haskell) +(require 'wpc-elixir) +(require 'wpc-nix) +(require 'wpc-rust) +(require 'wpc-clojure) +(require 'wpc-prolog) +(require 'wpc-dotnet) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package terraform-mode) + +(provide 'wpc-language-support) +;;; wpc-language-support.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el new file mode 100644 index 000000000000..599d42620419 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el @@ -0,0 +1,123 @@ +;;; wpc-lisp.el --- Generic LISP preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; parent (up) +;; child (down) +;; prev-sibling (left) +;; next-sibling (right) + +;;; Code: + +;; TODO: Consider having a separate module for each LISP dialect. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst wpc-lisp--hooks + '(lisp-mode-hook + emacs-lisp-mode-hook + clojure-mode-hook + clojurescript-mode-hook + racket-mode-hook) + "List of LISP modes.") + +(use-package sly + :config + (setq inferior-lisp-program "sbcl") + (general-define-key + :keymaps 'sly-mode-map + :states '(normal) + :prefix "<SPC>" + "x" #'sly-eval-defun + "X" #'sly-eval-buffer + "d" #'sly-describe-symbol)) + +(use-package rainbow-delimiters + :config + (general-add-hook wpc-lisp--hooks #'rainbow-delimiters-mode)) + +(use-package racket-mode + :config + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "<SPC>" + "x" #'racket-send-definition + "X" #'racket-run + "d" #'racket-describe) + (setq racket-program "~/.nix-profile/bin/racket")) + +(use-package lispyville + :init + (defconst wpc-lisp--lispyville-key-themes + '(c-w + operators + text-objects + prettify + commentary + slurp/barf-cp + wrap + additional + additional-insert + additional-wrap + escape) + "All available key-themes in Lispyville.") + :config + (general-add-hook wpc-lisp--hooks #'lispyville-mode) + (lispyville-set-key-theme wpc-lisp--lispyville-key-themes) + (progn + (general-define-key + :keymaps 'lispyville-mode-map + :states 'motion + ;; first unbind + "M-h" nil + "M-l" nil) + (general-define-key + :keymaps 'lispyville-mode-map + :states 'normal + ;; first unbind + "M-j" nil + "M-k" nil + ;; second rebind + "C-s-h" #'lispyville-drag-backward + "C-s-l" #'lispyville-drag-forward + "C-s-e" #'lispyville-end-of-defun + "C-s-a" #'lispyville-beginning-of-defun))) + +;; Elisp +(use-package elisp-slime-nav + :config + (general-add-hook 'emacs-lisp-mode #'ielm-mode)) + +(defun wpc-lisp-copy-elisp-eval-output () + "Copy the output of the elisp evaluation" + (interactive) + (call-interactively 'eval-last-sexp) + (clipboard-copy (current-message) + :message (format "%s - copied!" (current-message)))) + +(general-define-key + :keymaps 'emacs-lisp-mode-map + :prefix "<SPC>" + :states 'normal + "c" #'wpc-lisp-copy-elisp-eval-output + "x" #'eval-defun + "X" #'eval-buffer + "d" (lambda () + (interactive) + (with-current-buffer (current-buffer) + (helpful-function (symbol-at-point))))) + +(provide 'wpc-lisp) +;;; wpc-lisp.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el new file mode 100644 index 000000000000..36fbf8b12ce6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el @@ -0,0 +1,330 @@ +;;; wpc-misc.el --- Hosting miscellaneous configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; This is the home of any configuration that couldn't find a better home. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'project) +(require 'f) +(require 'dash) +(require 'tvl) +(require 'region) +(require 'general) +(require 'constants) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq display-time-string-forms + '((format-time-string "%H:%M %a %b %d"))) +(display-time-mode 1) + +;; Remove the boilerplate in the *scratch* buffer +(setq initial-scratch-message "") + +;; disable custom variable entries from being written to ~/.emacs.d/init.el +(setq custom-file (f-join user-emacs-directory "custom.el")) +(load custom-file 'noerror) + +;; integrate Emacs with X11 clipboard +(customize-set-variable 'select-enable-primary t) +(customize-set-variable 'select-enable-clipboard t) +(customize-set-variable 'evil-visual-update-x-selection-p nil) +(general-def 'insert + "s-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +;; transparently edit compressed files +(auto-compression-mode t) + +;; autowrap when over the fill-column +(setq-default auto-fill-function #'do-auto-fill) + +;; link to Emacs source code +;; TODO: Update this link. +(setq find-function-C-source-directory + "~/Dropbox/programming/emacs/src") + +;; change emacs prompts from "yes or no" -> "y or n" +(fset 'yes-or-no-p 'y-or-n-p) + +;; open photos in Emacs +(auto-image-file-mode 1) + +;; disable line-wrapping +(setq-default truncate-lines 1) + +;; shell file indentation +(setq sh-basic-offset 2) +(setq sh-indentation 2) + +(use-package vterm + :config + (general-define-key + :keymaps '(vterm-mode-map) + :states '(insert) + "C-S-v" #'vterm-yank) + (general-define-key + :keymaps '(vterm-mode-map) + :states '(normal) + "K" #'evil-scroll-line-up + "J" #'evil-scroll-line-down + "C-b" #'evil-scroll-page-up + "C-f" #'evil-scroll-page-down)) + +;; Use en Emacs buffer as a REST client. +;; For more information: http://emacsrocks.com/e15.html +(use-package restclient) + +;; Run `package-lint' before publishing to MELPA. +(use-package package-lint) + +;; Parser combinators in Elisp. +(use-package parsec) + +;; disable company mode when editing markdown +;; TODO: move this out of wpc-misc.el and into a later file to call +;; `(disable company-mode)' +(use-package markdown-mode + :config + ;; TODO: Add assertion that pandoc is installed and it is accessible from + ;; Emacs. + (setq markdown-command "pandoc") + (setq markdown-split-window-direction 'right) + ;; (add-hook 'markdown-mode-hook #'markdown-live-preview-mode) + ;; Use mode-specific syntax highlighting for code blocks. + (setq markdown-fontify-code-blocks-natively t) + ;; Prevent Emacs from adding a space after the leading 3x-backticks. + (setq markdown-spaces-after-code-fence 0)) + +(use-package alert) + +(use-package refine) + +;; Required by some google-emacs package commands. +(use-package deferred) + +;; git integration +(use-package magit + :config + (add-hook 'git-commit-setup-hook + (lambda () + (company-mode -1) + (flyspell-mode 1))) + (setq magit-display-buffer-function + #'magit-display-buffer-same-window-except-diff-v1)) + +(use-package magit-popup) + +;; http +(use-package request) + +;; TVL depot stuff +(use-package tvl) + +;; perl-compatible regular expressions +(use-package pcre2el) + +;; alternative to help +(use-package helpful) + +;; If called from an existing helpful-mode buffer, reuse that buffer; otherwise, +;; call `pop-to-buffer'. +(setq helpful-switch-buffer-function + (lambda (buffer-or-name) + (if (eq major-mode 'helpful-mode) + (switch-to-buffer buffer-or-name) + (pop-to-buffer buffer-or-name)))) + +;; Emacs integration with direnv +(use-package direnv + :config + (direnv-mode)) + +;; Superior Elisp library for working with dates and times. +;; TODO: Put this where my other installations for dash.el, s.el, a.el, and +;; other utility Elisp libraries are located. +(use-package ts) + +;; persist history etc b/w Emacs sessions +(setq desktop-save 'if-exists) +(desktop-save-mode 1) +(setq desktop-globals-to-save + (append '((extended-command-history . 30) + (file-name-history . 100) + (grep-history . 30) + (compile-history . 30) + (minibuffer-history . 50) + (query-replace-history . 60) + (read-expression-history . 60) + (regexp-history . 60) + (regexp-search-ring . 20) + (search-ring . 20) + (shell-command-history . 50) + tags-file-name + register-alist))) + +;; configure ibuffer +(setq ibuffer-default-sorting-mode 'major-mode) + +;; Emacs autosave, backup, interlocking files +(setq auto-save-default nil + make-backup-files nil + create-lockfiles nil) + +;; ensure code wraps at 80 characters by default +(setq-default fill-column 80) + +;; render tabs 2x-chars wide +(setq tab-width 2) + +(put 'narrow-to-region 'disabled nil) + +;; trim whitespace on save +(add-hook 'before-save-hook #'delete-trailing-whitespace) + +;; call `git secret hide` after saving secrets.json +(add-hook 'after-save-hook + (lambda () + (when (f-equal? (buffer-file-name) + (f-join tvl-depot-path + "users" + "wpcarro" + "secrets.json")) + (shell-command "git secret hide")))) + +;; use tabs instead of spaces +(setq-default indent-tabs-mode nil) + +;; prefer shorter tab-widths (e.g. writing Go code) +(setq-default tab-width 2) + +;; automatically follow symlinks +(setq vc-follow-symlinks t) + +;; fullscreen settings +(setq ns-use-native-fullscreen nil) + +(use-package yasnippet + :config + (unless constants-ci? + (setq yas-snippet-dirs (list (f-join user-emacs-directory "snippets"))) + (yas-global-mode 1))) + +(use-package projectile + :config + (projectile-mode t)) + +;; TODO(wpcarro): Consider replacing this with a TVL version if it exists. +(defun wpc-misc--depot-find (dir) + "Find the default.nix nearest to DIR." + ;; I use 'vc only at the root of my monorepo because 'transient doesn't use my + ;; .gitignore, which slows things down. Ideally, I could write a version that + ;; behaves like 'transient but also respects my monorepo's .gitignore and any + ;; ancestor .gitignore files. + (if (f-equal? tvl-depot-path dir) + (cons 'vc dir) + (when (f-ancestor-of? tvl-depot-path dir) + (if (f-exists? (f-join dir "default.nix")) + (cons 'transient dir) + (wpc-misc--depot-find (f-parent dir)))))) + +(add-to-list 'project-find-functions #'wpc-misc--depot-find) + +(defun wpc-misc-pkill (name) + "Call the pkill executable using NAME as its argument." + (interactive "sProcess name: ") + (call-process "pkill" nil nil nil name)) + +(use-package deadgrep + :config + (general-define-key + :keymaps 'deadgrep-mode-map + :states 'normal + "o" #'deadgrep-visit-result-other-window) + (setq-default deadgrep--context '(0 . 3)) + (defun wpc-misc-deadgrep-region () + "Run a ripgrep search on the active region." + (interactive) + (deadgrep (region-to-string))) + (defun wpc-misc-deadgrep-dwim () + "If a region is active, use that as the search, otherwise don't." + (interactive) + (with-current-buffer (current-buffer) + (if (region-active-p) + (setq deadgrep--additional-flags '("--multiline")) + (wpc-misc-deadgrep-region) + (call-interactively #'deadgrep)))) + (advice-add 'deadgrep--arguments + :filter-return + (lambda (args) + (push "--hidden" args) + (push "--follow" args)))) + +;; TODO: Do I need this when I have swiper? +(use-package counsel) + +(use-package counsel-projectile) + +;; search Google, Stackoverflow from within Emacs +(use-package engine-mode + :config + (defengine google + "http://www.google.com/search?ie=utf-8&oe=utf-8&q=%s" + :keybinding "g") + (defengine stack-overflow + "https://stackoverflow.com/search?q=%s" + :keybinding "s")) + +;; EGlot (another LSP client) +(use-package eglot) + +;; Microsoft's Debug Adapter Protocol (DAP) +(use-package dap-mode + :after lsp-mode + :config + (dap-mode 1) + (dap-ui-mode 1)) + +;; Microsoft's Language Server Protocol (LSP) +(use-package lsp-ui + :config + (add-hook 'lsp-mode-hook #'lsp-ui-mode)) + +;; Wilfred/suggest.el - Tool for discovering functions basesd on declaring your +;; desired inputs and outputs. +(use-package suggest) + +;; Malabarba/paradox - Enhances the `list-packages' view. +(use-package paradox + :config + (paradox-enable)) + +;; render emojis in Emacs ๐บ +(use-package emojify + :config + (add-hook 'after-init-hook #'global-emojify-mode) + ;; Disable the default styles of: + ;; - ascii :P (When this is enabled, the vim command, :x, renders as ๐ถ) + ;; - github :smile: + (setq emojify-emoji-styles '(unicode))) + +;; Always auto-close parantheses and other pairs +(electric-pair-mode) + +;; Start the Emacs server +(when (not (server-running-p)) + (server-start)) + +(provide 'wpc-misc) +;;; wpc-misc.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el new file mode 100644 index 000000000000..e9dc203691c2 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el @@ -0,0 +1,37 @@ +;;; wpc-nix.el --- Nix support -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Configuration to support working with Nix. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tvl) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package nix-mode + :mode "\\.nix\\'") + +(defun wpc-nix-rebuild-emacs () + "Use nix-env to rebuild wpcarros-emacs." + (interactive) + (let* ((pname (format "nix-env -iA users.wpcarro.emacs.nixos")) + (bname (format "*%s*" pname))) + (start-process pname bname + "nix-env" + "-f" tvl-depot-path + "-iA" "users.wpcarro.emacs.nixos") + (display-buffer bname))) + +(provide 'wpc-nix) +;;; wpc-nix.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el new file mode 100644 index 000000000000..229177220b1e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el @@ -0,0 +1,39 @@ +;;; wpc-org.el --- My org preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) + +;;; Commentary: +;; Hosts my org mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package org + :config + (evil-set-initial-state 'org-mode 'normal) + (general-add-hook 'org-mode-hook + (list (macros-disable linum-mode) + (macros-disable company-mode))) + (setq org-startup-folded nil) + (setq org-todo-keywords '((sequence "TODO" "BLOCKED" "DONE"))) + (general-unbind 'normal org-mode-map "M-h" "M-j" "M-k" "M-l")) + +(use-package org-bullets + :config + (general-add-hook 'org-mode-hook (macros-enable org-bullets-mode))) + +(provide 'wpc-org) +;;; wpc-org.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el new file mode 100644 index 000000000000..9c57bb427076 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el @@ -0,0 +1,32 @@ +;;; wpc-package.el --- My package configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) + +;;; Commentary: +;; This module hosts all of the settings required to work with ELPA, +;; MELPA, QUELPA, and co. + +;;; Code: + +(require 'package) + +;; Even though we're packaging our Emacs with Nix, having MELPA registered is +;; helpful to ad-hoc test out packages before declaratively adding them to +;; emacs/default.nix. +(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) +(package-initialize) + +(unless (package-installed-p 'use-package) + ;; TODO: Consider removing this to improve initialization speed. + (package-refresh-contents) + (package-install 'use-package)) +(eval-when-compile + (require 'use-package)) +;; TODO: Consider removing this, since I'm requiring general.el in individual +;; modules. +(use-package general) + +(provide 'wpc-package) +;;; wpc-package.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el new file mode 100644 index 000000000000..6779431c1215 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el @@ -0,0 +1,19 @@ +;;; wpc-prolog.el --- For Prologging things -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Code configuring my Prolog work. + +;;; Code: + +(require 'macros) + +;; TODO: Notice that the .pl extension conflicts with Perl files. This may +;; become a problem should I start working with Perl. +(macros-support-file-extension "pl" prolog-mode) + +(provide 'wpc-prolog) +;;; wpc-prolog.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el new file mode 100644 index 000000000000..9ffb7c4c8a2b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el @@ -0,0 +1,24 @@ +;;; wpc-python.el --- Python configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; My Python configuration settings +;; +;; Depends +;; - `apti yapf` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package py-yapf + :config + (add-hook 'python-mode-hook #'py-yapf-enable-on-save)) + +(provide 'wpc-python) +;;; wpc-python.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el new file mode 100644 index 000000000000..b609efb431fd --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el @@ -0,0 +1,30 @@ +;;; wpc-rust.el --- Support Rust language -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Supports my Rust work. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'lsp) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package rust-mode + :config + (setq lsp-rust-server #'rust-analyzer) + (setq rust-format-show-buffer nil) + (setq rust-format-on-save t) + (add-hook 'rust-mode-hook #'lsp)) + +(provide 'wpc-rust) +;;; wpc-rust.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el new file mode 100644 index 000000000000..f4229ed328e7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el @@ -0,0 +1,31 @@ +;;; wpc-shell.el --- POSIX Shell scripting support -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Helpers for my shell scripting. Includes bash, zsh, etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'zle) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Code +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package flymake-shellcheck + :commands flymake-shellcheck-load + :init + (add-hook 'sh-mode-hook #'flymake-shellcheck-load) + (add-hook 'sh-mode-hook #'zle-minor-mode)) + +(use-package fish-mode) + +(provide 'wpc-shell) +;;; wpc-shell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el new file mode 100644 index 000000000000..a2f533cec095 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el @@ -0,0 +1,186 @@ +;;; wpc-ui.el --- Any related to the UI/UX goes here -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts font settings, scrolling, color schemes. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require '>) +(require 'al) +(require 'constants) +(require 'dash) +(require 'fonts) +(require 'general) +(require 'modeline) +(require 'prelude) +(require 'theme) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; line height +(setq-default line-spacing 0) + +(when window-system + (setq frame-title-format '(buffer-file-name "%f" ("%b")))) + +;; Ensure that buffers update when their contents change on disk. +(global-auto-revert-mode t) + +;; smooth scrolling settings +(setq scroll-step 1 + scroll-conservatively 10000) + +;; clean up modeline +(use-package diminish + :config + (diminish 'emacs-lisp-mode "elisp") + (diminish 'evil-commentary-mode) + (diminish 'flycheck-mode) + (diminish 'auto-revert-mode) + (diminish 'which-key-mode) + (diminish 'yas-minor-mode) + (diminish 'lispyville-mode) + (diminish 'undo-tree-mode) + (diminish 'company-mode) + (diminish 'projectile-mode) + (diminish 'eldoc-mode) + ;; This is how to diminish `auto-fill-mode'. + (diminish 'auto-fill-function) + (diminish 'counsel-mode) + (diminish 'ivy-mode)) + +;; TODO: Further customize `mode-line-format' variable. +(delete 'mode-line-modes mode-line-format) +(delete '(vc-mode vc-mode) mode-line-format) + +;; disable startup screen +(setq inhibit-startup-screen t) + +;; disable toolbar +(tool-bar-mode -1) + +;; premium Emacs themes +(use-package doom-themes + :config + (setq doom-themes-enable-bold t + doom-themes-enable-italic t) + (doom-themes-visual-bell-config) + (doom-themes-org-config)) + +;; kbd discovery +(use-package which-key + :config + (setq which-key-idle-delay 0.25) + (which-key-mode)) + +;; completion framework +(use-package ivy + :config + (counsel-mode t) + (ivy-mode t) + ;; Remove preceding "^" from ivy prompts + (setq ivy-initial-inputs-alist nil) + ;; prefer using `helpful' variants + (progn + (setq counsel-describe-function-function #'helpful-callable) + (setq counsel-describe-variable-function #'helpful-variable)) + (general-define-key + :keymaps '(ivy-minibuffer-map ivy-switch-buffer-map) + ;; prev + "C-k" #'ivy-previous-line + "<backtab>" #'ivy-previous-line + ;; next + "C-j" #'ivy-next-line + "<tab>" #'ivy-next-line)) + +(use-package ivy-prescient + :config + (ivy-prescient-mode 1) + (unless constants-ci? + (prescient-persist-mode 1))) + +;; all-the-icons +(use-package all-the-icons + :config + (unless (or constants-ci? + (f-exists? "~/.local/share/fonts/all-the-icons.ttf") + (f-exists? "~/Library/Fonts/all-the-icons.ttf")) + (all-the-icons-install-fonts t))) + +;; icons for Ivy +(use-package all-the-icons-ivy + :after (ivy all-the-icons) + :config + (all-the-icons-ivy-setup)) + +;; disable menubar +(menu-bar-mode -1) + +;; reduce noisiness of auto-revert-mode +(setq auto-revert-verbose nil) + +;; highlight lines that are over 80 characters long +(use-package whitespace + :config + ;; TODO: This should change depending on the language and project. For + ;; example, Google Java projects prefer 100 character width instead of 80 + ;; character width. + (setq whitespace-line-column 80) + (setq whitespace-style '(face lines-tail tabs)) + (global-whitespace-mode t)) + +;; dirname/filename instead of filename<dirname> +(setq uniquify-buffer-name-style 'forward) + +;; highlight matching parens, brackets, etc +(show-paren-mode 1) + +;; hide the scroll-bars in the GUI +(scroll-bar-mode -1) + +;; TODO: Learn how to properly integrate this with dunst or another system-level +;; notification program. +;; GUI alerts in emacs +(use-package alert + :commands (alert) + :config + (setq alert-default-style 'notifier)) + +(display-battery-mode 1) + +(setq theme-whitelist + (->> (custom-available-themes) + (list-map #'symbol-name) + (list-filter (>-> (s-starts-with? "doom-"))) + (list-map #'intern) + cycle-from-list)) +(setq theme-linum-color-override "da5478") +(add-hook 'theme-after-change + (lambda () (prelude-set-line-number-color "#da5478"))) +(theme-whitelist-set 'doom-flatwhite) + +(when window-system + ;; On OSX, JetBrainsMono is installed as "JetBrains Mono", and I'm + ;; not sure how to change that. + (let ((font (if constants-osx? "JetBrains Mono" "JetBrainsMono"))) + (fonts-set font) + ;; Some themes (e.g. doom-acario-*) change the font for comments. This + ;; should prevent that. + (set-face-attribute font-lock-comment-face nil + :family font + :slant 'normal))) + +(modeline-setup) + +(provide 'wpc-ui) +;;; wpc-ui.el ends here diff --git a/users/wpcarro/emacs/AppIcon.icns b/users/wpcarro/emacs/AppIcon.icns new file mode 100644 index 000000000000..b3be251ccf8e --- /dev/null +++ b/users/wpcarro/emacs/AppIcon.icns Binary files differdiff --git a/users/wpcarro/emacs/README.md b/users/wpcarro/emacs/README.md new file mode 100644 index 000000000000..16f4fc31f989 --- /dev/null +++ b/users/wpcarro/emacs/README.md @@ -0,0 +1,15 @@ +# Emacs + +Emacs is one of a handful software projects that I highly value. I consider it +as central to my workflow as `git` and `nix`. + +## Installing + +If you already have `depot` on your local file system, run the following from +the top-level `depot` directory: + +```shell +$ nix-env -iA users.wpcarro.emacs.nixos +``` + +Test edit (from depot). diff --git a/users/wpcarro/emacs/ci.el b/users/wpcarro/emacs/ci.el new file mode 100644 index 000000000000..9dfaf3056fdc --- /dev/null +++ b/users/wpcarro/emacs/ci.el @@ -0,0 +1,44 @@ +;; This script initializes Emacs and exits with either a zero or non-zero status +;; depending on whether or not Emacs initialized without logging warnings or +;; encountering errors. +;; +;; This script reads the location of init.el as the last argument in `argv'. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) +(require 'dash) +(require 'buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Script +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar init-el-path (-last-item argv) + "Path to the init.el file that this script attempts to load.") + +(prelude-assert (f-exists? init-el-path)) + +(condition-case err + (load init-el-path) + (error + (message "Encountered an error while attempting to load init.el: %s" err) + (kill-emacs 1))) + +(when (buffer-exists? "*Errors*") + (progn + (with-current-buffer "*Errors*" + (message "Encountered errors in *Errors* buffer: %s" (buffer-string))) + (kill-emacs 1))) + +(when (buffer-exists? "*Warnings*") + (progn + (with-current-buffer "*Warnings*" + (message "Encountered warnings in *Warnings* buffer: %s" (buffer-string))) + (kill-emacs 1))) + +(message "Successfully initialized Emacs without errors or warnings!") +(kill-emacs 0) diff --git a/users/wpcarro/emacs/default.nix b/users/wpcarro/emacs/default.nix new file mode 100644 index 000000000000..78c66c693d09 --- /dev/null +++ b/users/wpcarro/emacs/default.nix @@ -0,0 +1,265 @@ +# My Emacs distribution, which is supporting the following platforms: +# - Linux +# - Darwin +# +# USAGE: +# $ nix-build -A users.wpcarro.emacs.osx -o /Applications/BillsEmacs.app +{ depot, pkgs, lib, ... }: + +# TODO(wpcarro): See if it's possible to expose emacsclient on PATH, so that I +# don't need to depend on wpcarros-emacs and emacs in my NixOS configurations. +let + inherit (depot.third_party.nixpkgs) emacsPackagesFor emacs28; + inherit (depot.users) wpcarro; + inherit (lib) mapAttrsToList; + inherit (lib.strings) concatStringsSep makeBinPath; + inherit (pkgs) runCommand writeShellScriptBin; + + emacsBinPath = makeBinPath ( + wpcarro.common.shell-utils ++ + # Rust dependencies + (with pkgs; [ + cargo + rust-analyzer + rustc + rustfmt + ]) ++ + # Misc dependencies + (with pkgs; [ + ispell + nix + rust-analyzer + rustc + rustfmt + xorg.xset + ] ++ + (if pkgs.stdenv.isLinux then [ + scrot + ] else [ ])) + ); + + emacsWithPackages = (emacsPackagesFor emacs28).emacsWithPackages; + + wpcarrosEmacs = emacsWithPackages (epkgs: + (with wpcarro.emacs.pkgs; [ + al + bookmark + cycle + list + macros + maybe + passage + set + string + struct + symbol + theme + tuple + vterm-mgt + zle + ]) ++ + + (with epkgs.tvlPackages; [ + tvl + ]) ++ + + (with epkgs.elpaPackages; [ + exwm + ]) ++ + + (with epkgs.melpaPackages; [ + alert + all-the-icons + all-the-icons-ivy + avy + base16-theme + cider + clojure-mode + company + counsel + counsel-projectile + csharp-mode + dap-mode + dash + deadgrep + deferred + diminish + direnv + dockerfile-mode + doom-themes + elisp-slime-nav + elixir-mode + elm-mode + emojify + engine-mode + evil + evil-collection + evil-commentary + evil-surround + f + fish-mode + flycheck + flymake-shellcheck + general + go-mode + haskell-mode + helpful + ivy + ivy-clipmenu + ivy-prescient + key-chord + lispyville + lsp-ui + magit + magit-popup + markdown-mode + nix-mode + notmuch + org-bullets + package-lint + paradox + parsec + pcre2el + prettier-js + projectile + py-yapf + racket-mode + rainbow-delimiters + reason-mode + refine + request + restclient + rjsx-mode + rust-mode + sly + suggest + telephone-line + terraform-mode + tide + ts + tuareg + use-package + vterm + web-mode + which-key + yaml-mode + yasnippet + ]) ++ + + [ + epkgs.eglot # from elpa devel + ]); + + loadPath = concatStringsSep ":" [ + ./.emacs.d/wpc + # TODO(wpcarro): Explain why the trailing ":" is needed. + "${wpcarrosEmacs.deps}/share/emacs/site-lisp:" + ]; + + # Transform an attrset into "export k=v" statements. + makeEnvVars = env: concatStringsSep "\n" + (mapAttrsToList (k: v: "export ${k}=\"${v}\"") env); + + withEmacsPath = { emacsBin, env ? { }, load ? [ ] }: + writeShellScriptBin "wpcarros-emacs" '' + export XMODIFIERS=emacs + export PATH="${emacsBinPath}:$PATH" + export EMACSLOADPATH="${loadPath}" + ${makeEnvVars env} + exec ${emacsBin} \ + --debug-init \ + --no-init-file \ + --no-site-file \ + --no-site-lisp \ + --load ${./.emacs.d/init.el} \ + ${concatStringsSep "\n " (map (el: "--load ${el} \\") load)} + "$@" + ''; + + # I can't figure out how to augment LSEnvironment.PATH such that it inherits + # the default $PATH and adds the things that I need as well, so let's + # hardcode the desired outcome in the meantime. + osxDefaultPath = builtins.concatStringsSep ":" [ + "/Users/bill/.nix-profile/bin" + "/nix/var/nix/profiles/default/bin" + "/opt/homebrew/bin" + "/opt/homebrew/sbin" + "/usr/local/bin" + "/usr/bin" + "/bin" + "/usr/sbin" + "/sbin" + "/opt/X11/bin" + ]; + + infoPlist = pkgs.writeText "Info.plist" (pkgs.lib.generators.toPlist { } { + LSEnvironment = { + PATH = "${emacsBinPath}:${osxDefaultPath}"; + }; + CFBundleExecutable = "BillsEmacs"; + CFBundleDisplayName = "BillsEmacs"; + CFBundleIconFile = "AppIcon"; + CFBundleIconName = "AppIcon"; + }); + + versionPlist = pkgs.writeText "version.plist" (pkgs.lib.generators.toPlist { } { + ProjectName = "OSXPlatformSupport"; + }); +in +{ + # TODO(wpcarro): Support this with base.overrideAttrs or something similar. + nixos = { load ? [ ] }: withEmacsPath { + inherit load; + emacsBin = "${wpcarrosEmacs}/bin/emacs"; + }; + + # To install GUI: + # $ nix-build -A users.wpcarro.emacs.osx -o /Applications/BillsEmacs.app + osx = pkgs.stdenv.mkDerivation { + pname = "bills-emacs"; + version = "0.0.1"; + src = ./.; + dontFixup = true; + installPhase = '' + runHook preInstall + APP="$out" + mkdir -p "$APP/Contents/MacOS" + mkdir -p "$APP/Contents/Resources" + cp ${infoPlist} "$APP/Contents/Info.plist" + cp ${versionPlist} "$APP/Contents/version.plist" + cp ${./AppIcon.icns} "$APP/Contents/Resources/AppIcon.icns" + echo "APPL????" > "$APP/Contents/PkgInfo" + cat << EOF > "$APP/Contents/MacOS/BillsEmacs" + #!${pkgs.stdenvNoCC.shell} + export EMACSLOADPATH="${loadPath}" + exec ${wpcarrosEmacs}/bin/emacs \ + --debug-init \ + --no-init-file \ + --no-site-file \ + --no-site-lisp \ + --load ${./.emacs.d/init.el} + EOF + chmod +x "$APP/Contents/MacOS/BillsEmacs" + runHook postInstall + ''; + meta.platforms = [ "aarch64-darwin" ]; + }; + + # Script that asserts my Emacs can initialize without warnings or errors. + check = runCommand "check-emacs" { } '' + # Even though Buildkite defines this, I'd still like still be able to test + # this locally without depending on my ability to remember to set CI=true. + export CI=true + export PATH="${emacsBinPath}:$PATH" + export EMACSLOADPATH="${loadPath}" + ${wpcarrosEmacs}/bin/emacs \ + --no-site-file \ + --no-site-lisp \ + --no-init-file \ + --script ${./ci.el} \ + ${./.emacs.d/init.el} && \ + touch $out + ''; + + meta.ci.targets = [ "check" ]; +} diff --git a/users/wpcarro/emacs/elisp-conventions.md b/users/wpcarro/emacs/elisp-conventions.md new file mode 100644 index 000000000000..0e39c3069d8b --- /dev/null +++ b/users/wpcarro/emacs/elisp-conventions.md @@ -0,0 +1,20 @@ +# Elisp Conventions + +Some of this aligns with existing style guides. Some of it does not. + +In general, prefer functions with fixed arities instead of variadic +alternatives. + +- Namespace functions with `namespace/function-name` +- Use `ensure`, `assert`, `refute` whenever possible. +- When talking about encoding and decoding, let's use the words "encoding" and + "decoding" rather than the myriad of other variants that appear like: + - `marshalling` and `unmarshalling` + - `parse` and `deparse`, `serialize`, `stringify` + - `unpickle` and `pickle` (Python) + - `from-string` and `to-string` + - TODO: Add more examples of these; there should be close to a dozen. +- Annotate assertions with `!` endings. +- Prefer the Scheme style of `predicate?` +- Variadic functions *should* encode this by appending * onto their + name. E.g. `maybe/nil?*` diff --git a/users/wpcarro/emacs/keybindings.md b/users/wpcarro/emacs/keybindings.md new file mode 100644 index 000000000000..96ba7c96459b --- /dev/null +++ b/users/wpcarro/emacs/keybindings.md @@ -0,0 +1,47 @@ +# Keybindings + +Since I'm using Emacs to manage most of my workflow, all of the keybindings +should be defined herein and -- in order to scale -- order must be imposed. This +can help avoid KBD collisions and improve my ability to remember each KBD. + +See `kbd.el` for the programmatic encoding of these principles. + +## Troubleshooting + +When in doubt, use Emacs's `read-key` and `read-event` to learn what signal +you're sending Emacs. + +### Super- + +- EXWM X11 windows are not processing `s-`. +- EXWM X11 windows are not processing `<M-ESC>`. + +### Super-Ctrl- + +I'm reserving `C-s-` for opening X11 applications. + +- `terminator`: `t` +- `google-chrome`: `c` + +## Emacs nouns + +Most of my keybindings should be organized according to their function, which in +turn should be related to the following Emacs nouns. + +- `workspace`: As defined by EXWM. +- `frame`: What non-Emacs users would call a "window". Currently my workflow + doesn't use or rely on Emacs frames. +- `window`: A vertical or horizontal split within an Emacs frame. +- `buffer`: Anything storing text in memory. + +## Prefixes and their meanings + +TODO: Have a system for leader-prefixed KBDs, chords, and prefixed chords. + +- `s-`: Switching between named workspaces. Right now, super is too overloaded + and would benefit from having more deliberate keybindings. +- `C-M-`: Window sizing +- `M-{h,j,k,l}`: Window traversing +- `M-{\,-}`: Window splitting +- `M-q`: Window deletion +- `<leader>-q`: Window deletion diff --git a/users/wpcarro/emacs/pkgs/al/al.el b/users/wpcarro/emacs/pkgs/al/al.el new file mode 100644 index 000000000000..4c37526c644a --- /dev/null +++ b/users/wpcarro/emacs/pkgs/al/al.el @@ -0,0 +1,227 @@ +;;; al.el --- Interface for working with associative lists -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Firstly, a rant: +;; In most cases, I find Elisp's APIs to be confusing. There's a mixture of +;; overloaded functions that leak the implementation details (TODO: provide an +;; example of this.) of the abstract data type, which I find privileges those +;; "insiders" who spend disproportionately large amounts of time in Elisp land, +;; and other functions with little-to-no pattern about the order in which +;; arguments should be applied. In theory, however, most of these APIs could +;; and should be much simpler. This module represents a step in that direction. +;; +;; I'm modelling these APIs after Elixir's APIs. +;; +;; On my wishlist is to create protocols that will allow generic interfaces like +;; Enum protocols, etc. Would be nice to abstract over... +;; - associative lists (i.e. alists) +;; - property lists (i.e. plists) +;; - hash tables +;; ...with some dictionary or map-like interface. This will probably end up +;; being quite similar to the kv.el project but with differences at the API +;; layer. +;; +;; Similar libraries: +;; - map.el: Comes bundled with recent versions of Emacs. +;; - asoc.el: Helpers for working with alists. asoc.el is similar to alist.el +;; because it uses the "!" convention for signalling that a function mutates +;; the underlying data structure. +;; - ht.el: Hash table library. +;; - kv.el: Library for dealing with key-value collections. Note that map.el +;; has a similar typeclass because it works with lists, hash-tables, or +;; arrays. +;; - a.el: Clojure-inspired way of working with key-value data structures in +;; Elisp. Works with alists, hash-tables, and sometimes vectors. +;; +;; Some API design principles: +;; - The "noun" (i.e. alist) of the "verb" (i.e. function) comes last to improve +;; composability with the threading macro (i.e. `->>') and to improve consumers' +;; intuition with the APIs. Learn this once, know it always. +;; +;; - Every function avoids mutating the alist unless it ends with !. +;; +;; - CRUD operations will be named according to the following table: +;; - "create" *and* "set" +;; - "read" *and* "get" +;; - "update" +;; - "delete" *and* "remove" +;; +;; For better or worse, all of this code expects alists in the form of: +;; ((first-name . "William") (last-name . "Carroll")) +;; +;; Special thanks to github.com/alphapapa/emacs-package-dev-handbook for some of +;; the idiomatic ways to update alists. +;; +;; TODO: Include a section that compares alist.el to a.el from +;; github.com/plexus/a.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies: +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'list) +(require 'map) + +;; TODO: Support function aliases for: +;; - create/set +;; - read/get +;; - update +;; - delete/remove + +;; Support mutative variants of functions with an ! appendage to their name. + +;; Ensure that the same message about only updating the first occurrence of a +;; key is consistent throughout documentation using string interpolation or some +;; other mechanism. + +;; TODO: Consider wrapping all of this with `(cl-defstruct alist xs)'. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support a variadic version of this to easily construct alists. +(defun al-new () + "Return a new, empty alist." + '()) + +;; Create +;; TODO: See if this mutates. +(defun al-set (k v xs) + "Set K to V in XS." + (if (al-has-key? k xs) + (progn + ;; Note: this is intentional `alist-get' and not `al-get'. + (setf (alist-get k xs) v) + xs) + (list-cons `(,k . ,v) xs))) + +(defun al-set! (k v xs) + "Set K to V in XS mutatively. +Note that this doesn't append to the alist in the way that most alists handle + writing. If the k already exists in XS, it is overwritten." + (map-delete xs k) + (map-put! xs k v)) + +;; Read +(defun al-get (k xs &optional default) + "Return the value at K in XS; otherwise, return nil or DEFAULT (if set). +Returns the first occurrence of K in XS since alists support multiple entries." + (if (not (al-has-key? k xs)) + default + (cdr (assoc k xs)))) + +(defun al-get-entry (k xs) + "Return the first key-value pair at K in XS." + (assoc k xs)) + +;; Update +;; TODO: Add warning about only the first occurrence being updated in the +;; documentation. +(defun al-update (k f xs) + "Apply F to the value stored at K in XS. +If `K' is not in `XS', this function errors. Use `al-upsert' if you're +interested in inserting a value when a key doesn't already exist." + (if (not (al-has-key? k xs)) + (error "Refusing to update: key does not exist in alist") + (al-set k (funcall f (al-get k xs)) xs))) + +(defun al-update! (k f xs) + "Call F on the entry at K in XS. +Mutative variant of `al-update'." + (al-set! k (funcall f (al-get k xs))xs)) + +;; TODO: Support this. +(defun al-upsert (k v f xs) + "If K exists in `XS' call `F' on the value otherwise insert `V'." + (if (al-has-key? k xs) + (al-update k f xs) + (al-set k v xs))) + +;; Delete +;; TODO: Make sure `delete' and `remove' behave as advertised in the Elisp docs. +(defun al-delete (k xs) + "Deletes the entry of K from XS. +This only removes the first occurrence of K, since alists support multiple + key-value entries. See `al-delete-all' and `al-dedupe'." + (remove (assoc k xs) xs)) + +(defun al-delete! (k xs) + "Delete the entry of K from XS. +Mutative variant of `al-delete'." + (delete (assoc k xs) xs)) + +;; Additions to the CRUD API +;; TODO: Implement this function. +(defun al-dedupe-keys (xs) + "Remove the entries in XS where the keys are `equal'.") + +(defun al-dedupe-entries (xs) + "Remove the entries in XS where the key-value pair are `equal'." + (delete-dups xs)) + +(defun al-keys (xs) + "Return a list of the keys in XS." + (mapcar 'car xs)) + +(defun al-values (xs) + "Return a list of the values in XS." + (mapcar 'cdr xs)) + +(defun al-has-key? (k xs) + "Return t if XS has a key `equal' to K." + (not (eq nil (assoc k xs)))) + +(defun al-has-value? (v xs) + "Return t if XS has a value of V." + (not (eq nil (rassoc v xs)))) + +(defun al-count (xs) + "Return the number of entries in XS." + (length xs)) + +;; TODO: Should I support `al-find-key' and `al-find-value' variants? +(defun al-find (p xs) + "Find an element in XS. + +Apply a predicate fn, P, to each key and value in XS and return the key of the +first element that returns t." + (let ((result (list-find (lambda (x) (funcall p (car x) (cdr x))) xs))) + (if result + (car result) + nil))) + +(defun al-map-keys (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(funcall f (car x)) . ,(cdr x))) + xs)) + +(defun al-map-values (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(car x) . ,(funcall f (cdr x)))) + xs)) + +(defun al-reduce (acc f xs) + "Return a new alist by calling F on k v and ACC from XS. +F should return a tuple. See tuple.el for more information." + (->> (al-keys xs) + (list-reduce acc + (lambda (k acc) + (funcall f k (al-get k xs) acc))))) + +(defun al-merge (a b) + "Return a new alist with a merge of alists, A and B. +In this case, the last writer wins, which is B." + (al-reduce a #'al-set b)) + +(provide 'al) +;;; al.el ends here diff --git a/users/wpcarro/emacs/pkgs/al/default.nix b/users/wpcarro/emacs/pkgs/al/default.nix new file mode 100644 index 000000000000..d88e0757a875 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/al/default.nix @@ -0,0 +1,28 @@ +{ pkgs, depot, ... }: + +let + al = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "al"; + version = "1.0.0"; + src = ./al.el; + packageRequires = + (with emacsPackages; [ + dash + ]) ++ + (with depot.users.wpcarro.emacs.pkgs; [ + list + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ al ]); +in +al.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/al/tests.el b/users/wpcarro/emacs/pkgs/al/tests.el new file mode 100644 index 000000000000..04fe4dcbb5a6 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/al/tests.el @@ -0,0 +1,53 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'al) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest al-has-key? () + (should (al-has-key? 'fname '((fname . "William")))) + (should (not (al-has-key? 'lname '((fname . "William")))))) + +(ert-deftest al-get () + (let ((xs (->> (al-new) + (al-set 'fname "John") + (al-set 'employed? nil)))) + (should (string= "John" (al-get 'fname xs))) + (should (string= "Cleese" (al-get 'lname xs "Cleese"))) + ;; Test that the value of nil is returned even when a default is defined, + ;; which could be a subtle bug in the typical Elisp pattern of supporting + ;; defaults with: (or foo default). + (should (eq nil (al-get 'employed? xs))) + (should (eq nil (al-get 'employed? xs "default"))))) + +(ert-deftest al-has-value? () + (should (al-has-value? "William" '((fname . "William")))) + (should (not (al-has-key? "John" '((fname . "William")))))) + +(ert-deftest al-map-keys () + (should + (equal '((2 . one) + (3 . two)) + (al-map-keys #'1+ + '((1 . one) + (2 . two)))))) + +(ert-deftest al-map-values () + (should (equal '((one . 2) + (two . 3)) + (al-map-values #'1+ + '((one . 1) + (two . 2)))))) + +(ert-deftest al-delete () + (let ((person (->> (al-new) + (al-set "fname" "John") + (al-set "lname" "Cleese") + (al-set "age" 82)))) + (should (al-has-key? "age" person)) + (should (not (al-has-key? "age" (al-delete "age" person)))))) diff --git a/users/wpcarro/emacs/pkgs/bag/bag.el b/users/wpcarro/emacs/pkgs/bag/bag.el new file mode 100644 index 000000000000..502f5672536e --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bag/bag.el @@ -0,0 +1,78 @@ +;;; bag.el --- Working with bags (aka multi-sets) -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; What is a bag? A bag should be thought of as a frequency table. It's a way +;; to convert a list of something into a set that allows duplicates. Isn't +;; allowing duplicates the whole thing with Sets? Kind of. But the interface +;; of Sets is something that bags resemble, so multi-set isn't as bad of a name +;; as it may first seem. +;; +;; If you've used Python's collections.Counter, the concept of a bag should be +;; familiar already. +;; +;; Interface: +;; - add :: x -> Bag(x) -> Bag(x) +;; - remove :: x -> Bag(x) -> Bag(x) +;; - union :: Bag(x) -> Bag(x) -> Bag(x) +;; - difference :: Bag(x) -> Bag(x) -> Bag(x) + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'al) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bag xs) + +(defun bag-new () + "Create an empty bag." + (make-bag :xs (al-new))) + +(defun bag-from-list (xs) + "Map a list of `XS' into a bag." + (->> xs + (list-reduce (bag-new) #'bag-add))) + +(defun bag-add (x xs) + "Add X to XS." + (if (bag-contains? x xs) + (struct-update + bag xs (lambda (xs) (al-update x (lambda (x) (+ 1 x)) xs)) xs) + (struct-update bag xs (lambda (xs) (al-set x 1 xs)) xs))) + +(defun bag-remove (x xs) + "Remove X from XS. +This is a no-op is X doesn't exist in XS." + (when (bag-contains? x xs) + (struct-update bag xs (lambda (xs) (al-delete x xs)) xs))) + +(defun bag-count (x xs) + "Return the number of occurrences of X in XS." + (al-get x (bag-xs xs) 0)) + +(defun bag-total (xs) + "Return the total number of elements in XS." + (->> (bag-xs xs) + (al-reduce 0 (lambda (_key v acc) (+ acc v))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bag-contains? (x xs) + "Return t if XS has X." + (al-has-key? x (bag-xs xs))) + +(provide 'bag) +;;; bag.el ends here diff --git a/users/wpcarro/emacs/pkgs/bag/default.nix b/users/wpcarro/emacs/pkgs/bag/default.nix new file mode 100644 index 000000000000..3dedc27286d1 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bag/default.nix @@ -0,0 +1,26 @@ +{ pkgs, depot, ... }: + +let + bag = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "bag"; + version = "1.0.0"; + src = ./bag.el; + packageRequires = + (with depot.users.wpcarro.emacs.pkgs; [ + al + list + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ bag ]); +in +bag.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/bag/tests.el b/users/wpcarro/emacs/pkgs/bag/tests.el new file mode 100644 index 000000000000..4970f70815c9 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bag/tests.el @@ -0,0 +1,32 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'bag) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq fixture (bag-from-list '(1 1 1 2 2 3))) + +(ert-deftest bag-add () + (should (not (bag-contains? 4 fixture))) + (should (bag-contains? 4 (bag-add 4 fixture)))) + +(ert-deftest bag-remove () + (should (bag-contains? 1 fixture)) + (should (not (bag-contains? 3 (bag-remove 3 fixture))))) + +(ert-deftest bag-count () + (should (= 3 (bag-count 1 fixture))) + (should (= 2 (bag-count 2 fixture))) + (should (= 1 (bag-count 3 fixture)))) + +(ert-deftest bag-total () + (should (= 6 (bag-total fixture)))) + +(ert-deftest bag-contains? () + (should (bag-contains? 1 fixture)) + (should (not (bag-contains? 4 fixture)))) diff --git a/users/wpcarro/emacs/pkgs/bookmark/bookmark.el b/users/wpcarro/emacs/pkgs/bookmark/bookmark.el new file mode 100644 index 000000000000..ab9169a078d4 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bookmark/bookmark.el @@ -0,0 +1,50 @@ +;;; bookmark.el --- Saved files and directories on my filesystem -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; A more opinionated version of Emacs's builtin `jump-to-register'. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'project) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bookmark label path kbd) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bookmark-open (b) + "Open bookmark, B, as either a project directory or a regular directory." + (with-temp-buffer + (cd (bookmark-path b)) + (call-interactively #'project-find-file))) + +(defun bookmark-install-kbd (b) + "Define two functions to explore B and assign them to keybindings." + (eval `(defun ,(intern (format "bookmark-visit-%s" (bookmark-label b))) () + (interactive) + (find-file ,(bookmark-path b)))) + (eval `(defun ,(intern (format "bookmark-browse-%s" (bookmark-label b))) () + (interactive) + (bookmark-open ,b))) + (general-define-key + :prefix "<SPC>" + :states '(motion) + (format "J%s" (bookmark-kbd b)) `,(intern (format "bookmark-visit-%s" (bookmark-label b))) + (format "j%s" (bookmark-kbd b)) `,(intern (format "bookmark-browse-%s" (bookmark-label b))))) + +(provide 'bookmark) +;;; bookmark.el ends here diff --git a/users/wpcarro/emacs/pkgs/bookmark/default.nix b/users/wpcarro/emacs/pkgs/bookmark/default.nix new file mode 100644 index 000000000000..882481701fa2 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bookmark/default.nix @@ -0,0 +1,13 @@ +{ pkgs, depot, ... }: + +pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "bookmark"; + version = "1.0.0"; + src = ./bookmark.el; + packageRequires = (with pkgs.emacsPackages; [ + general + ]); + }) +{ } diff --git a/users/wpcarro/emacs/pkgs/bytes/bytes.el b/users/wpcarro/emacs/pkgs/bytes/bytes.el new file mode 100644 index 000000000000..b0d64795a074 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bytes/bytes.el @@ -0,0 +1,94 @@ +;;; bytes.el --- Working with byte values -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Functions to help with human-readable representations of byte values. +;; +;; Usage: +;; See the test cases for example usage. Or better yet, I should use a type of +;; structured documentation that would allow me to expose a view into the test +;; suite here. Is this currently possible in Elisp? +;; +;; API: +;; - serialize :: Integer -> String +;; +;; Wish list: +;; - Rounding: e.g. (bytes (* 1024 1.7)) => "2KB" + +;;; Code: + +;; TODO: Support -ibabyte variants like Gibibyte (GiB). + +;; Ranges: +;; B: [ 0, 1e3) +;; KB: [ 1e3, 1e6) +;; MB: [ 1e6, 1e6) +;; GB: [ 1e9, 1e12) +;; TB: [1e12, 1e15) +;; PB: [1e15, 1e18) +;; +;; Note: I'm currently not support exabytes because that causes the integer to +;; overflow. I imagine a larger integer type may exist, but for now, I'll +;; treat this as a YAGNI. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tuple) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst bytes-kb (expt 2 10) + "Number of bytes in a kilobyte.") + +(defconst bytes-mb (expt 2 20) + "Number of bytes in a megabytes.") + +(defconst bytes-gb (expt 2 30) + "Number of bytes in a gigabyte.") + +(defconst bytes-tb (expt 2 40) + "Number of bytes in a terabyte.") + +(defconst bytes-pb (expt 2 50) + "Number of bytes in a petabyte.") + +(defconst bytes-eb (expt 2 60) + "Number of bytes in an exabyte.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bytes-classify (x) + "Return unit that closest fits byte count, X." + (cond + ((and (>= x 0) (< x bytes-kb)) 'byte) + ((and (>= x bytes-kb) (< x bytes-mb)) 'kilobyte) + ((and (>= x bytes-mb) (< x bytes-gb)) 'megabyte) + ((and (>= x bytes-gb) (< x bytes-tb)) 'gigabyte) + ((and (>= x bytes-tb) (< x bytes-pb)) 'terabyte) + ((and (>= x bytes-pb) (< x bytes-eb)) 'petabyte))) + +(defun bytes-to-string (x) + "Convert integer X into a human-readable string." + (let ((base-and-unit + (pcase (bytes-classify x) + ('byte (tuple-from 1 "B")) + ('kilobyte (tuple-from bytes-kb "KB")) + ('megabyte (tuple-from bytes-mb "MB")) + ('gigabyte (tuple-from bytes-gb "GB")) + ('terabyte (tuple-from bytes-tb "TB")) + ('petabyte (tuple-from bytes-pb "PB"))))) + (format "%d%s" + (round x (tuple-first base-and-unit)) + (tuple-second base-and-unit)))) + +(provide 'bytes) +;;; bytes.el ends here diff --git a/users/wpcarro/emacs/pkgs/bytes/default.nix b/users/wpcarro/emacs/pkgs/bytes/default.nix new file mode 100644 index 000000000000..4e9f52d9b927 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bytes/default.nix @@ -0,0 +1,25 @@ +{ pkgs, depot, ... }: + +let + bytes = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "bytes"; + version = "1.0.0"; + src = ./bytes.el; + packageRequires = + (with depot.users.wpcarro.emacs.pkgs; [ + tuple + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ bytes ]); +in +bytes.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/bytes/tests.el b/users/wpcarro/emacs/pkgs/bytes/tests.el new file mode 100644 index 000000000000..9b71a466c736 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/bytes/tests.el @@ -0,0 +1,18 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'bytes) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest bytes-to-string () + (should (equal "1000B" (bytes-to-string 1000))) + (should (equal "2KB" (bytes-to-string (* 2 bytes-kb)))) + (should (equal "17MB" (bytes-to-string (* 17 bytes-mb)))) + (should (equal "419GB" (bytes-to-string (* 419 bytes-gb)))) + (should (equal "999TB" (bytes-to-string (* 999 bytes-tb)))) + (should (equal "2PB" (bytes-to-string (* 2 bytes-pb))))) diff --git a/users/wpcarro/emacs/pkgs/cycle/README.md b/users/wpcarro/emacs/pkgs/cycle/README.md new file mode 100644 index 000000000000..416900d2532b --- /dev/null +++ b/users/wpcarro/emacs/pkgs/cycle/README.md @@ -0,0 +1,7 @@ +# cycle.el + +[![Build status](https://badge.buildkite.com/016bff4b8ae2704a3bbbb0a250784e6692007c582983b6dea7.svg?branch=refs/heads/canon)](https://buildkite.com/tvl/depot) + +Cycle data structure exposing mutable and (coming soon!) immutable APIs. + +[![asciicast](https://asciinema.org/a/FvFujpRpYjV9qCSGvk3uobOpV.svg)](https://asciinema.org/a/FvFujpRpYjV9qCSGvk3uobOpV) diff --git a/users/wpcarro/emacs/pkgs/cycle/cycle.el b/users/wpcarro/emacs/pkgs/cycle/cycle.el new file mode 100644 index 000000000000..2f5b252a0d6a --- /dev/null +++ b/users/wpcarro/emacs/pkgs/cycle/cycle.el @@ -0,0 +1,194 @@ +;;; cycle.el --- Simple module for working with cycles -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Something like this may already exist, but I'm having trouble finding it, and +;; I think writing my own is a nice exercise for learning more Elisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'struct) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish list +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Provide immutable variant. +;; - TODO: Replace mutable consumption with immutable variant. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `current-index' tracks the current index +;; `xs' is the original list +(cl-defstruct cycle current-index previous-index xs) + +(defun cycle-from-list (xs) + "Create a cycle from a list of `XS'." + (if (= 0 (length xs)) + (make-cycle :current-index nil + :previous-index nil + :xs xs) + (make-cycle :current-index 0 + :previous-index nil + :xs xs))) + +(defun cycle-new (&rest xs) + "Create a cycle with XS as the values." + (cycle-from-list xs)) + +(defun cycle-to-list (xs) + "Return the list representation of a cycle, XS." + (cycle-xs xs)) + +(defun cycle-previous-focus (cycle) + "Return the previously focused entry in CYCLE." + (let ((i (cycle-previous-index cycle))) + (when i (nth i (cycle-xs cycle))))) + +(defun cycle-focus-previous! (xs) + "Jump to the item in XS that was most recently focused; return the cycle. +This will error when previous-index is nil. This function mutates the +underlying struct." + (let ((i (cycle-previous-index xs))) + (if i + (progn (cycle-jump! i xs) (cycle-current xs)) + (error "Cannot focus the previous element since cycle-previous-index is nil")))) + +(defun cycle-next! (xs) + "Return the next value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (cycle--next-index-> 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-prev! (xs) + "Return the previous value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (cycle--next-index<- 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-current (cycle) + "Return the current value in `CYCLE'." + (nth (cycle-current-index cycle) (cycle-xs cycle))) + +(defun cycle-count (cycle) + "Return the length of `xs' in `CYCLE'." + (length (cycle-xs cycle))) + +(defun cycle-jump! (i xs) + "Jump to the I index of XS." + (let ((current-index (cycle-current-index xs)) + (next-index (mod i (cycle-count xs)))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs)) + xs) + +(defun cycle-focus! (p cycle) + "Focus the element in CYCLE for which predicate, P, is t." + (let ((i (->> cycle + cycle-xs + (-find-index p)))) + (if i + (cycle-jump! i cycle) + (error "No element in cycle matches predicate")))) + +(defun cycle-focus-item! (x xs) + "Focus item, X, in cycle XS. +ITEM is the first item in XS that t for `equal'." + (cycle-focus! (lambda (y) (equal x y)) xs)) + +(defun cycle-append! (x xs) + "Add X to the left of the focused element in XS. +If there is no currently focused item, add X to the beginning of XS." + (if (cycle-empty? xs) + (progn + (struct-set! cycle xs (list x) xs) + (struct-set! cycle current-index 0 xs) + (struct-set! cycle previous-index nil xs)) + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs))) + (if curr-i + (progn + (struct-set! cycle xs (-insert-at curr-i x (cycle-xs xs)) xs) + (when (and prev-i (>= prev-i curr-i)) + (struct-set! cycle previous-index (1+ prev-i) xs)) + (when curr-i (struct-set! cycle current-index (1+ curr-i) xs))) + (progn + (struct-set! cycle xs (cons x (cycle-xs xs)) xs) + (when prev-i (struct-set! cycle previous-index (1+ prev-i) xs)))) + xs))) + +(defun cycle-remove! (x xs) + "Attempt to remove X from XS. + +X is found using `equal'. + +If X is the currently focused value, after it's deleted, current-index will be + nil. If X is the previously value, after it's deleted, previous-index will be + nil." + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs)) + (rm-i (-elem-index x (cycle-xs xs)))) + (struct-set! cycle xs (-remove-at rm-i (cycle-xs xs)) xs) + (when prev-i + (when (> prev-i rm-i) (struct-set! cycle previous-index (1- prev-i) xs)) + (when (= prev-i rm-i) (struct-set! cycle previous-index nil xs))) + (when curr-i + (when (> curr-i rm-i) (struct-set! cycle current-index (1- curr-i) xs)) + (when (= curr-i rm-i) (struct-set! cycle current-index nil xs))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun cycle-contains? (x xs) + "Return t if cycle, XS, has member X." + (not (null (-contains? (cycle-xs xs) x)))) + +(defun cycle-empty? (xs) + "Return t if cycle XS has no elements." + (= 0 (length (cycle-xs xs)))) + +(defun cycle-focused? (xs) + "Return t if cycle XS has a non-nil value for current-index." + (not (null (cycle-current-index xs)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helper Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun cycle--next-index<- (lo hi x) + "Return the next index in a cycle when moving downwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (< (- x 1) lo) + (- hi 1) + (- x 1))) + +(defun cycle--next-index-> (lo hi x) + "Return the next index in a cycle when moving upwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (>= (+ 1 x) hi) + lo + (+ 1 x))) + +(provide 'cycle) +;;; cycle.el ends here diff --git a/users/wpcarro/emacs/pkgs/cycle/default.nix b/users/wpcarro/emacs/pkgs/cycle/default.nix new file mode 100644 index 000000000000..7ef3b431ada6 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/cycle/default.nix @@ -0,0 +1,36 @@ +{ pkgs, depot, ... }: + +let + cycle = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "cycle"; + version = "1.0.0"; + src = ./cycle.el; + packageRequires = + (with emacsPackages; [ + dash + ]) ++ + (with depot.users.wpcarro.emacs.pkgs; [ + struct + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + epkgs.dash + cycle + ]); +in +cycle.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; + passthru.meta.ci.extraSteps.github = depot.tools.releases.filteredGitPush { + filter = ":/users/wpcarro/emacs/pkgs/cycle"; + remote = "git@github.com:wpcarro/cycle.el.git"; + ref = "refs/heads/canon"; + }; +}) diff --git a/users/wpcarro/emacs/pkgs/cycle/tests.el b/users/wpcarro/emacs/pkgs/cycle/tests.el new file mode 100644 index 000000000000..29c0e2a0d582 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/cycle/tests.el @@ -0,0 +1,79 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'cycle) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq xs (cycle-new 1 2 3)) + +(ert-deftest cycle-initializes-properly () + (should (= 3 (cycle-count xs))) + (should (null (cycle-previous-focus xs))) + (should (cycle-contains? 1 xs)) + (should (cycle-contains? 2 xs)) + (should (cycle-contains? 3 xs))) + +(ert-deftest cycle-contains? () + ;; Returns t or nil + (should (eq t (cycle-contains? 1 xs))) + (should (eq t (cycle-contains? 2 xs))) + (should (eq t (cycle-contains? 3 xs))) + (should (eq nil (cycle-contains? 4 xs)))) + +(ert-deftest cycle-empty? () + (should (eq t (cycle-empty? (cycle-new)))) + (should (eq nil (cycle-empty? xs)))) + +(ert-deftest cycle-current () + (should (= 1 (cycle-current xs)))) + +(ert-deftest cycle-next! () + (let ((xs (cycle-from-list '(1 2 3)))) + (should (= 2 (cycle-next! xs))))) + +(ert-deftest cycle-prev! () + (let ((xs (cycle-from-list '(1 2 3)))) + (cycle-next! xs) + (should (= 1 (cycle-prev! xs))))) + +(ert-deftest cycle-previous-focus () + (let ((xs (cycle-from-list '(1 2 3)))) + (cycle-focus-item! 2 xs) + (cycle-next! xs) + (should (= 2 (cycle-previous-focus xs))))) + +(ert-deftest cycle-jump! () + (let ((xs (cycle-from-list '(1 2 3)))) + (should (= 1 (->> xs (cycle-jump! 0) cycle-current))) + (should (= 2 (->> xs (cycle-jump! 1) cycle-current))) + (should (= 3 (->> xs (cycle-jump! 2) cycle-current))))) + +(ert-deftest cycle-focus-previous! () + (let ((xs (cycle-from-list '(1 2 3)))) + (cycle-focus-item! 2 xs) + (cycle-next! xs) + (should (= 2 (cycle-previous-focus xs))) + (should (= 2 (cycle-focus-previous! xs))))) + +(ert-deftest cycle-append! () + (let ((xs (cycle-from-list '(1 2 3)))) + (cycle-focus-item! 2 xs) + (cycle-append! 4 xs) + (should (equal '(1 4 2 3) (cycle-xs xs))))) + +(ert-deftest cycle-remove! () + (let ((xs (cycle-from-list '(1 2 3)))) + (should (equal '(1 2) (cycle-xs (cycle-remove! 3 xs)))))) + +(ert-deftest cycle-misc () + (cycle-focus-item! 3 xs) + (cycle-focus-item! 2 xs) + (cycle-remove! 1 xs) + (should (= 2 (cycle-current xs))) + (should (= 3 (cycle-previous-focus xs)))) diff --git a/users/wpcarro/emacs/pkgs/fs/default.nix b/users/wpcarro/emacs/pkgs/fs/default.nix new file mode 100644 index 000000000000..e6afd107e96b --- /dev/null +++ b/users/wpcarro/emacs/pkgs/fs/default.nix @@ -0,0 +1,29 @@ +{ pkgs, depot, ... }: + +let + fs = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "fs"; + version = "1.0.0"; + src = ./fs.el; + packageRequires = + (with emacsPackages; [ + dash + f + s + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + fs + ]); +in +fs.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/fs/fs.el b/users/wpcarro/emacs/pkgs/fs/fs.el new file mode 100644 index 000000000000..125c1f1007bd --- /dev/null +++ b/users/wpcarro/emacs/pkgs/fs/fs.el @@ -0,0 +1,47 @@ +;;; fs.el --- Make working with the filesystem easier -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) + +;;; Commentary: +;; Ergonomic alternatives for working with the filesystem. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'f) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun fs-ensure-file (path) + "Ensure that a file and its directories in `PATH' exist. +Will error for inputs with a trailing slash." + (when (s-ends-with? "/" path) + (error (format "Input path has trailing slash: %s" path))) + (->> path + f-dirname + fs-ensure-dir) + (f-touch path)) + +(defun fs-ensure-dir (path) + "Ensure that a directory and its ancestor directories in `PATH' exist." + (->> path + f-split + (apply #'f-mkdir))) + +(defun fs-ls (dir &optional full-path?) + "List the files in `DIR' one-level deep. +Should behave similarly in spirit to the Unix command, ls. +If `FULL-PATH?' is set, return the full-path of the files." + (-drop 2 (directory-files dir full-path?))) + +(provide 'fs) +;;; fs.el ends here diff --git a/users/wpcarro/emacs/pkgs/fs/tests.el b/users/wpcarro/emacs/pkgs/fs/tests.el new file mode 100644 index 000000000000..adef11a607ae --- /dev/null +++ b/users/wpcarro/emacs/pkgs/fs/tests.el @@ -0,0 +1,26 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'fs) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest fs-test-ensure-file () + (let ((file "/tmp/file/a/b/c/file.txt")) + ;; Ensure this file doesn't exist first to prevent false-positives. + (f-delete file t) + (fs-ensure-file file) + (should (and (f-exists? file) + (f-file? file))))) + +(ert-deftest fs-test-ensure-dir () + (let ((dir "/tmp/dir/a/b/c")) + ;; Ensure the directory doesn't exist. + (f-delete dir t) + (fs-ensure-dir dir) + (should (and (f-exists? dir) + (f-dir? dir))))) diff --git a/users/wpcarro/emacs/pkgs/list/README.md b/users/wpcarro/emacs/pkgs/list/README.md new file mode 100644 index 000000000000..7afa8494fb7a --- /dev/null +++ b/users/wpcarro/emacs/pkgs/list/README.md @@ -0,0 +1,19 @@ +# list.el + +Functions for working with lists in Elisp. + +## Wish List + +Here are some additional functions that I'd like to support. + +- **TODO**: delete_at/2 +- **TODO**: flatten/1 +- **TODO**: flatten/2 +- **TODO**: foldl/3 +- **TODO**: foldr/3 +- **TODO**: insert_at/3 +- **TODO**: pop_at/3 +- **TODO**: replace_at/3 +- **TODO**: starts_with?/2 +- **TODO**: update_at/3 +- **TODO**: zip/1 diff --git a/users/wpcarro/emacs/pkgs/list/default.nix b/users/wpcarro/emacs/pkgs/list/default.nix new file mode 100644 index 000000000000..1be0b901eb3e --- /dev/null +++ b/users/wpcarro/emacs/pkgs/list/default.nix @@ -0,0 +1,26 @@ +{ pkgs, depot, ... }: + +let + list = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "list"; + version = "1.0.0"; + src = ./list.el; + packageRequires = + (with depot.users.wpcarro.emacs.pkgs; [ + maybe + set + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ list ]); +in +list.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/list/list.el b/users/wpcarro/emacs/pkgs/list/list.el new file mode 100644 index 000000000000..18be5f0a716c --- /dev/null +++ b/users/wpcarro/emacs/pkgs/list/list.el @@ -0,0 +1,219 @@ +;;; list.el --- Functions for working with lists -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Since I prefer having the `list-' namespace, I wrote this module to wrap many +;; of the functions that are defined in the the global namespace in Elisp. I +;; sometimes forget the names of these functions, so it's nice for them to be +;; organized like this. +;; +;; Motivation: +;; Here are some examples of function names where I prefer more modern +;; alternatives: +;; - `car': Return the first element (i.e. "head") of a linked list +;; - `cdr': Return the tail of a linked list + +;; As are most APIs for standard libraries that I write, this is influenced by +;; Elixir's standard library. +;; +;; Similar libraries: +;; - dash.el: Excellent and widely adopted library for working with lists. +;; - list-utils.el: Utility library that covers things that dash.el may not +;; cover. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) +(require 'set) +(require 'set) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list-new () + "Return a new, empty list." + '()) + +(defun list-concat (&rest lists) + "Joins `LISTS' into on list." + (apply #'append lists)) + +(defun list-duplicate (n x) + "Duplicates the given element, X, N times in a list." + (list-map (lambda (_) x) (number-sequence 1 n))) + +(defun list-join (joint xs) + "Join a list of strings, XS, with JOINT." + (if (list-empty? xs) + "" + (list-reduce (list-first xs) + (lambda (x acc) + (format "%s%s%s" acc joint x)) + (list-tail xs)))) + +(defun list-length (xs) + "Return the number of elements in `XS'." + (length xs)) + +(defun list-get (i xs) + "Return the value in `XS' at `I', or nil." + (nth i xs)) + +(defun list-first (xs &optional default) + "Alias for `list-head' for `XS'." + (if (list-empty? xs) + default + (car xs))) + +(defun list-last (xs &optional default) + "Returns the last element in XS or DEFAULT if empty." + (if (list-empty? xs) + default + (nth (- (length xs) 1) xs))) + +(defun list-tail (xs) + "Return the tail of `XS'." + (cdr xs)) + +(defun list-reverse (xs) + "Reverses `XS'." + (reverse xs)) + +(defun list-cons (x xs) + "Add `X' to the head of `XS'." + (cons x xs)) + +(defun list-delete (x xs) + "Deletes the given element, X, from XS. +Returns a new list without X. If X occurs more than once, only the first + occurrence is removed." + (let ((deleted? nil)) + (list-reject (lambda (y) + (if deleted? nil + (when (equal x y) + (setq deleted? t) t))) + xs))) + +(defun list-filter (p xs) + "Return a subset of XS where predicate P returned t." + (list--assert-instance xs) + (seq-filter p xs)) + +(defun list-map (f xs) + "Call `F' on each element of `XS'." + (list--assert-instance xs) + (seq-map f xs)) + +(defun list-reduce (acc f xs) + "Return over `XS' calling `F' on an element in `XS'and `ACC'." + (list--assert-instance xs) + (seq-reduce (lambda (acc x) (funcall f x acc)) xs acc)) + +(defun list-map-indexed (f xs) + "Call `F' on each element of `XS' along with its index." + (list-reverse + (cdr + (list-reduce '(0 . nil) + (lambda (x acc) + (let ((i (car acc)) + (result (cdr acc))) + `(,(+ 1 i) . ,(cons (funcall f x i) result)))) + xs)))) + +(defun list-reject (p xs) + "Return a subset of XS where predicate of P return nil." + (list-filter (lambda (x) (not (funcall p x))) xs)) + +(defun list-find (p xs) + "Return the first x in XS that passes P or nil." + (list--assert-instance xs) + (seq-find p xs)) + +(defun list-dedupe-adjacent (xs) + "Return XS without adjacent duplicates." + (list-reverse + (list-reduce (list (list-first xs)) + (lambda (x acc) + (if (equal x (list-first acc)) + acc + (list-cons x acc))) + xs))) + +(defun list-chunk (n xs) + "Chunk XS into lists of size N." + (if (> n (length xs)) + (list xs) + (let* ((xs (list-reduce '(:curr () :result ()) + (lambda (x acc) + (let ((curr (plist-get acc :curr)) + (result (plist-get acc :result))) + (if (= (- n 1) (length curr)) + `(:curr () :result ,(list-cons (list-reverse (list-cons x curr)) result)) + `(:curr ,(list-cons x curr) :result + ,result)))) xs)) + (curr (plist-get xs :curr)) + (result (plist-get xs :result))) + (list-reverse (if curr (list-cons curr result) result))))) + +(defun list-wrap (xs) + "Wraps XS in a list if it is not a list already." + (if (list-instance? xs) + xs + (list xs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list-instance? (xs) + "Return t if `XS' is a list. +Be leery of using this with things like alists. Many data structures in Elisp + are implemented using linked lists." + (listp xs)) + +(defun list-empty? (xs) + "Return t if XS are empty." + (= 0 (list-length xs))) + +(defun list-all? (p xs) + "Return t if all `XS' pass the predicate, `P'." + (if (list-empty? xs) + t + (and (maybe-some? (funcall p (car xs))) + (list-all? p (cdr xs))))) + +(defun list-any? (p xs) + "Return t if any `XS' pass the predicate, `P'." + (if (list-empty? xs) + nil + (or (maybe-some? (funcall p (car xs))) + (list-any? p (cdr xs))))) + +(defun list-contains? (x xs) + "Return t if X is in XS using `equal'." + (list--assert-instance xs) + (maybe-some? (seq-contains-p xs x))) + +(defun list-xs-distinct-by? (f xs) + "Return t if all elements in XS are distinct after applying F to each." + (= (length xs) + (set-count (set-from-list (list-map f xs))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list--assert-instance (xs) + (unless (list-instance? xs) + (error (format "Assertion failed: argument is not a list: %s" xs)))) + +(provide 'list) +;;; list.el ends here diff --git a/users/wpcarro/emacs/pkgs/list/tests.el b/users/wpcarro/emacs/pkgs/list/tests.el new file mode 100644 index 000000000000..4b4579688331 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/list/tests.el @@ -0,0 +1,107 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq xs '(1 2 3 4 5)) + +(ert-deftest list-length () + (should (= 0 (list-length '()))) + (should (= 5 (list-length xs)))) + +(ert-deftest list-reduce () + (should (= 16 (list-reduce 1 (lambda (x acc) (+ x acc)) xs)))) + +(ert-deftest list-map () + (should + (equal '(2 4 6 8 10) + (list-map (lambda (x) (* x 2)) xs)))) + +(ert-deftest list-xs-distinct-by? () + (should + (equal t (list-xs-distinct-by? + (lambda (x) (plist-get x :kbd)) + '((:kbd "C-a" :name "foo") + (:kbd "C-b" :name "foo")))))) + +(ert-deftest list-dedupe-adjacent () + (should (equal '(1 2 3 4 3 5) + (list-dedupe-adjacent '(1 1 1 2 2 3 4 4 3 5 5))))) + +(ert-deftest list-contains? () + ;; Assert returns t or nil + (should (equal t (list-contains? 1 xs))) + (should (equal nil (list-contains? 100 xs)))) + +(ert-deftest list-join () + (should (equal "foo-bar-baz" + (list-join "-" '("foo" "bar" "baz"))))) + +(ert-deftest list-chunk () + (should (equal '((1 2 3 4 5 6)) + (list-chunk 7 '(1 2 3 4 5 6)))) + (should (equal '((1) (2) (3) (4) (5) (6)) + (list-chunk 1 '(1 2 3 4 5 6)))) + (should (equal '((1 2 3) (4 5 6)) + (list-chunk 3 '(1 2 3 4 5 6)))) + (should (equal '((1 2) (3 4) (5 6)) + (list-chunk 2 '(1 2 3 4 5 6))))) + +(ert-deftest list-find () + (should (equal 2 (list-find (lambda (x) (= 2 x)) '(1 2 3 4))))) + +(ert-deftest list-all? () + (should (equal t (list-all? (lambda (x) (= 2 x)) nil))) + (should (null (list-all? (lambda (x) (= 2 x)) '(1 2 3)))) + (should (equal t (list-all? (lambda (x) (= 2 x)) '(2 2 2 2))))) + +(ert-deftest list-any? () + (should (null (list-any? (lambda (x) (= 2 x)) nil))) + (should (equal t (list-any? (lambda (x) (= 2 x)) '(1 2 3)))) + (should (null (list-any? (lambda (x) (= 4 x)) '(1 2 3))))) + +(ert-deftest list-duplicate () + (should (equal '() (list-duplicate 0 "hello"))) + (should (equal '("hi") (list-duplicate 1 "hi"))) + (should (equal '("bye" "bye") (list-duplicate 2 "bye"))) + (should (equal '((1 2) (1 2) (1 2)) (list-duplicate 3 '(1 2))))) + +(ert-deftest list-first () + (should (null (list-first '()))) + (should (equal 1 (list-first '() 1))) + (should (equal 1 (list-first '(1)))) + (should (equal 1 (list-first '(1) 2))) + (should (equal 1 (list-first '(1 2 3))))) + +(ert-deftest list-last () + (should (null (list-last '()))) + (should (equal 1 (list-last '() 1))) + (should (equal 1 (list-last '(1)))) + (should (equal 1 (list-last '(1) 2))) + (should (equal 3 (list-last '(1 2 3))))) + +(ert-deftest list-wrap () + (should (equal '("hello") (list-wrap "hello"))) + (should (equal '(1 2 3) (list-wrap '(1 2 3)))) + (should (equal '() (list-wrap nil)))) + +(ert-deftest list-delete () + (should (equal '(b c) (list-delete 'a '(a b c)))) + (should (equal '(a b c) (list-delete 'd '(a b c)))) + (should (equal '(a b c) (list-delete 'b '(a b b c)))) + (should (equal '() (list-delete 'b '())))) + +(ert-deftest list-concat () + (should (equal '(1 2 3 4 5) (list-concat '(1) '(2 3) '(4 5)))) + (should (equal '(1 2 3) (list-concat '() '(1 2 3))))) + +;; TODO(wpcarro): Supoprt this. +;; (ert-deftest list-zip () +;; (should (equal '((1 3 5) (2 4 6)) (list-zip '(1 2) '(3 4) '(5 6)))) +;; (should (equal '((1 3 5)) (list-zip '(1 2) '(3) '(5 6))))) diff --git a/users/wpcarro/emacs/pkgs/macros/default.nix b/users/wpcarro/emacs/pkgs/macros/default.nix new file mode 100644 index 000000000000..d2811ed39fc6 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/macros/default.nix @@ -0,0 +1,10 @@ +{ pkgs, depot, ... }: + +pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "macros"; + version = "1.0.0"; + src = ./macros.el; + }) +{ } diff --git a/users/wpcarro/emacs/pkgs/macros/macros.el b/users/wpcarro/emacs/pkgs/macros/macros.el new file mode 100644 index 000000000000..3642686eeb4b --- /dev/null +++ b/users/wpcarro/emacs/pkgs/macros/macros.el @@ -0,0 +1,45 @@ +;;; macros.el --- Helpful variables for making my ELisp life more enjoyable -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file contains helpful variables that I use in my ELisp development. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro macros-enable (mode) + "Helper for enabling `MODE'. +Useful in `add-hook' calls. Some modes, like `linum-mode' need to be called as +`(linum-mode 1)', so `(add-hook mode #'linum-mode)' won't work." + `#'(lambda nil (,mode 1))) + +(defmacro macros-disable (mode) + "Helper for disabling `MODE'. +Useful in `add-hook' calls." + `#'(lambda nil (,mode -1))) + +(defmacro macros-add-hook-before-save (mode f) + "Register a hook, `F', for a mode, `MODE' more conveniently. +Usage: (macros-add-hook-before-save 'reason-mode-hook #'refmt-before-save)" + `(add-hook ,mode + (lambda () + (add-hook 'before-save-hook ,f)))) + +(defmacro macros-comment (&rest _) + "Empty comment s-expresion where `BODY' is ignored." + `nil) + +(defmacro macros-support-file-extension (ext mode) + "Register MODE to automatically load with files ending with EXT extension. +Usage: (macros-support-file-extension \"pb\" protobuf-mode)" + (let ((extension (format "\\.%s\\'" ext))) + `(add-to-list 'auto-mode-alist '(,extension . ,mode)))) + +(provide 'macros) +;;; macros.el ends here diff --git a/users/wpcarro/emacs/pkgs/math/default.nix b/users/wpcarro/emacs/pkgs/math/default.nix new file mode 100644 index 000000000000..9167d61d4edc --- /dev/null +++ b/users/wpcarro/emacs/pkgs/math/default.nix @@ -0,0 +1,30 @@ +{ pkgs, depot, ... }: + +let + math = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "math"; + version = "1.0.0"; + src = ./math.el; + packageRequires = + (with emacsPackages; [ + dash + ]) ++ + (with depot.users.wpcarro.emacs.pkgs; [ + maybe + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + math + ]); +in +math.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/math/math.el b/users/wpcarro/emacs/pkgs/math/math.el new file mode 100644 index 000000000000..dbc527928a30 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/math/math.el @@ -0,0 +1,63 @@ +;;; math.el --- Math stuffs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Containing some useful mathematical functions. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst math-pi pi + "The number pi.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support all three arguments. +;; Int -> Int -> Int -> Boolean +(cl-defun math-triangle-of-power (&key base power result) + (cond + ((-all? #'maybe-some? (list base power result)) + (error "All three arguments should not be set")) + ((-all? #'maybe-some? (list power result)) + (message "power and result")) + ((-all? #'maybe-some? (list base result)) + (log result base)) + ((-all? #'maybe-some? (list base power)) + (expt base power)) + (t + (error "Two of the three arguments must be set")))) + +(defun math-mod (x y) + "Return X mod Y." + (mod x y)) + +(defun math-exp (x y) + "Return X raised to the Y." + (expt x y)) + +(defun math-round (x) + "Round X to nearest ones digit." + (round x)) + +(defun math-floor (x) + "Floor value X." + (floor x)) + +(provide 'math) +;;; math.el ends here diff --git a/users/wpcarro/emacs/pkgs/math/tests.el b/users/wpcarro/emacs/pkgs/math/tests.el new file mode 100644 index 000000000000..ef3430c9131b --- /dev/null +++ b/users/wpcarro/emacs/pkgs/math/tests.el @@ -0,0 +1,25 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'math) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest math-mod () + (should (= 0 (math-mod 9 3))) + (should (= 4 (math-mod 9 5)))) + +(ert-deftest math-exp () + (should (= 9 (math-exp 3 2))) + (should (= 8 (math-exp 2 3)))) + +(ert-deftest math-round () + (should (= 10 (math-round 9.5))) + (should (= 9 (math-round 9.45)))) + +(ert-deftest math-floor () + (should (= 9 (math-floor 9.5)))) diff --git a/users/wpcarro/emacs/pkgs/maybe/default.nix b/users/wpcarro/emacs/pkgs/maybe/default.nix new file mode 100644 index 000000000000..68e058b42b19 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/maybe/default.nix @@ -0,0 +1,24 @@ +{ pkgs, depot, ... }: + +let + maybe = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "maybe"; + version = "1.0.0"; + src = ./maybe.el; + packageRequires = [ ]; + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + maybe + ]); +in +maybe.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/maybe/maybe.el b/users/wpcarro/emacs/pkgs/maybe/maybe.el new file mode 100644 index 000000000000..581568d8ccba --- /dev/null +++ b/users/wpcarro/emacs/pkgs/maybe/maybe.el @@ -0,0 +1,54 @@ +;;; maybe.el --- Library for dealing with nil values -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Inspired by Elm's Maybe library. +;; +;; For now, a Nothing value will be defined exclusively as a nil value. I'm +;; uninterested in supported falsiness in this module even at risk of going +;; against the LISP grain. +;; +;; I'm avoiding introducing a struct to handle the creation of Just and Nothing +;; variants of Maybe. Perhaps this is a mistake in which case this file would +;; be more aptly named nil.el. I may change that. Because of this limitation, +;; functions in Elm's Maybe library like andThen, which is the monadic bind for +;; the Maybe type, doesn't have a home here since we cannot compose multiple +;; Nothing or Just values without a struct or some other construct. +;; +;; Possible names for the variants of a Maybe. +;; None | Some +;; Nothing | Something +;; None | Just +;; Nil | Set +;; +;; NOTE: In Elisp, values like '() (i.e. the empty list) are aliases for nil. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun maybe-nil? (x) + "Return t if X is nil." + (null x)) + +(defun maybe-some? (x) + "Return t when X is non-nil." + (not (maybe-nil? x))) + +(defun maybe-default (default x) + "Return DEFAULT when X is nil." + (if (maybe-nil? x) default x)) + +(defun maybe-map (f x) + "Apply F to X if X is not nil." + (if (maybe-some? x) + (funcall f x) + x)) + +(provide 'maybe) +;;; maybe.el ends here diff --git a/users/wpcarro/emacs/pkgs/maybe/tests.el b/users/wpcarro/emacs/pkgs/maybe/tests.el new file mode 100644 index 000000000000..c0463cc65a53 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/maybe/tests.el @@ -0,0 +1,25 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest maybe-nil? () + (should (maybe-nil? nil)) + (should (not (maybe-nil? t)))) + +(ert-deftest maybe-some? () + (should (maybe-some? '(1 2 3))) + (should (not (maybe-some? nil)))) + +(ert-deftest maybe-default () + (should (string= "some" (maybe-default "some" nil))) + (should (= 10 (maybe-default 1 10)))) + +(ert-deftest maybe-map () + (should (eq nil (maybe-map (lambda (x) (* x 2)) nil))) + (should (= 4 (maybe-map (lambda (x) (* x 2)) 2)))) diff --git a/users/wpcarro/emacs/pkgs/passage/README.md b/users/wpcarro/emacs/pkgs/passage/README.md new file mode 100644 index 000000000000..51f7bd6efda4 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/passage/README.md @@ -0,0 +1,12 @@ +# passage.el + +Emacs support for `passage`. + +## Alternative Packages + +If you're looking for more feature-complete, configurable alternatives, +check-out the following packages: + +- `ivy-pass.el` +- `password-store.el` +- `pass.el` diff --git a/users/wpcarro/emacs/pkgs/passage/default.nix b/users/wpcarro/emacs/pkgs/passage/default.nix new file mode 100644 index 000000000000..ac87f193b4e2 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/passage/default.nix @@ -0,0 +1,12 @@ +{ pkgs, depot, ... }: + +pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "passage"; + version = "1.0.0"; + src = ./passage.el; + packageRequires = (with emacsPackages; [ dash f s ]); + } + ) +{ } diff --git a/users/wpcarro/emacs/pkgs/passage/passage.el b/users/wpcarro/emacs/pkgs/passage/passage.el new file mode 100644 index 000000000000..4a43920e0bed --- /dev/null +++ b/users/wpcarro/emacs/pkgs/passage/passage.el @@ -0,0 +1,65 @@ +;;; passage.el --- Emacs passage support -*- lexical-binding: t; -*- + +;; Copyright (C) 2022-2023 William Carroll <wpcarro@gmail.com> + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 1.0.0 + +;; This file is not part of GNU Emacs. + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This package provides functions for working with passage. + +;;; Code: + +(require 'dash) +(require 'f) +(require 's) + +(defgroup passage nil + "Customization options for `passage'." + :prefix "passage-" + :group 'vterm) + +(defcustom passage-store + "~/.passage/store" + "Path to the passage store directory." + :type 'string + :group 'passage) + +(defcustom passage-executable + (or (executable-find "passage") + "/nix/store/jgffkfdiiwiqa4zqpxn3691mx9xc6axa-passage-unstable-2022-05-01/bin/passage") + "Path to passage executable." + :type 'string + :group 'passage) + +(defun passage-select () + "Select an entry and copy its password to the kill ring." + (interactive) + (let ((key (completing-read "Copy password of entry: " + (-map (lambda (x) + (f-no-ext (f-relative x passage-store))) + (f-files passage-store nil t))))) + (kill-new + (s-trim-right + (shell-command-to-string + (format "%s show %s | head -1" passage-executable key)))) + (message "[passage.el] Copied \"%s\"!" key))) + +(provide 'passage) +;;; passage.el ends here diff --git a/users/wpcarro/emacs/pkgs/set/default.nix b/users/wpcarro/emacs/pkgs/set/default.nix new file mode 100644 index 000000000000..319ba9274423 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/set/default.nix @@ -0,0 +1,32 @@ +{ pkgs, depot, ... }: + +let + set = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "set"; + version = "1.0.0"; + src = ./set.el; + packageRequires = + (with emacsPackages; [ + dash + ht + ]) ++ + (with depot.users.wpcarro.emacs.pkgs; [ + struct + ]); + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + epkgs.dash + set + ]); +in +set.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/set/set.el b/users/wpcarro/emacs/pkgs/set/set.el new file mode 100644 index 000000000000..2d6e14917a45 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/set/set.el @@ -0,0 +1,116 @@ +;;; set.el --- Working with mathematical sets -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; The set data structure is a collection that deduplicates its elements. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cl-lib) +(require 'dash) +(require 'ht) ;; friendlier API for hash-tables +(require 'struct) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish List +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Support enum protocol for set. +;; - TODO: Prefer a different hash-table library that doesn't rely on mutative +;; code. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct set xs) + +(defun set-from-list (xs) + "Create a new set from the list XS." + (make-set :xs (->> xs + (-map (lambda (x) (cons x nil))) + ht-from-alist))) + +(defun set-new (&rest args) + "Create a new set from ARGS." + (set-from-list args)) + +(defun set-to-list (xs) + "Map set XS into a list." + (->> xs + set-xs + ht-keys)) + +(defun set-add (x xs) + "Add X to set XS." + (struct-update set + xs + (lambda (table) + (let ((table-copy (ht-copy table))) + (ht-set table-copy x nil) + table-copy)) + xs)) + +;; TODO: Ensure all `*/reduce' functions share the same API. +(defun set-reduce (acc f xs) + "Return a new set by calling F on each element of XS and ACC." + (->> xs + set-to-list + (-reduce-from (lambda (acc x) (funcall f x acc)) acc))) + +(defun set-intersection (a b) + "Return the set intersection between A and B." + (set-reduce (set-new) + (lambda (x acc) + (if (set-contains? x b) + (set-add x acc) + acc)) + a)) + +(defun set-count (xs) + "Return the number of elements in XS." + (->> xs + set-xs + ht-size)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun set-empty? (xs) + "Return t if XS has no elements in it." + (= 0 (set-count xs))) + +(defun set-contains? (x xs) + "Return t if set XS has X." + (ht-contains? (set-xs xs) x)) + +;; TODO: Prefer using `ht.el' functions for this. +(defun set-equal? (a b) + "Return t if A and B share the name members." + (ht-equal? (set-xs a) + (set-xs b))) + +(defun set-distinct? (a b) + "Return t if A and B have no shared members." + (set-empty? (set-intersection a b))) + +(defun set-superset? (a b) + "Return t if A has all of the members of B." + (->> b + set-to-list + (-all? (lambda (x) (set-contains? x a))))) + +(defun set-subset? (a b) + "Return t if each member of set A is present in set B." + (set-superset? b a)) + +(provide 'set) +;;; set.el ends here diff --git a/users/wpcarro/emacs/pkgs/set/tests.el b/users/wpcarro/emacs/pkgs/set/tests.el new file mode 100644 index 000000000000..7f5c2ae3ffd9 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/set/tests.el @@ -0,0 +1,69 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'dash) +(require 'set) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest set-from-list () + (should (equal '(1 2 3) + (->> '(1 2 3 1 2 3) + set-from-list + set-to-list)))) + +(ert-deftest set-distinct? () + (should (set-distinct? (set-new 'one 'two 'three) + (set-new 'a 'b 'c))) + (should (not + (set-distinct? (set-new 1 2 3) + (set-new 3 4 5)))) + (should (not + (set-distinct? (set-new 1 2 3) + (set-new 1 2 3))))) + +(ert-deftest set-equal? () + (should (not (set-equal? (set-new 'a 'b 'c) + (set-new 'x 'y 'z)))) + (should (not (set-equal? (set-new 'a 'b 'c) + (set-new 'a 'b)))) + (should (set-equal? (set-new 'a 'b 'c) + (set-new 'a 'b 'c)))) + +(ert-deftest set-intersection () + (should (set-equal? (set-new 2 3) + (set-intersection (set-new 1 2 3) + (set-new 2 3 4))))) + +(ert-deftest set-to/from-list () + (should (equal '(1 2 3) + (->> '(1 1 2 2 3 3) + set-from-list + set-to-list)))) + +(ert-deftest set-subset? () + (should (not (set-subset? (set-new "black" "grey") + (set-new "red" "green" "blue")))) + (should (set-subset? (set-new "red") + (set-new "red" "green" "blue")))) + +(ert-deftest set-superset? () + (let ((primary-colors (set-new "red" "green" "blue"))) + (should (not (set-superset? primary-colors + (set-new "black" "grey")))) + (should (set-superset? primary-colors + (set-new "red" "green" "blue"))) + (should (set-superset? primary-colors + (set-new "red" "blue"))))) + +(ert-deftest set-empty? () + (should (set-empty? (set-new))) + (should (not (set-empty? (set-new 1 2 3))))) + +(ert-deftest set-count () + (should (= 0 (set-count (set-new)))) + (should (= 2 (set-count (set-new 1 1 2 2))))) diff --git a/users/wpcarro/emacs/pkgs/string/default.nix b/users/wpcarro/emacs/pkgs/string/default.nix new file mode 100644 index 000000000000..406cccdfcb58 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/string/default.nix @@ -0,0 +1,27 @@ +{ pkgs, depot, ... }: + +let + string = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "string"; + version = "1.0.0"; + src = ./string.el; + packageRequires = [ + emacsPackages.dash + emacsPackages.s + ]; + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + string + ]); +in +string.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/string/string.el b/users/wpcarro/emacs/pkgs/string/string.el new file mode 100644 index 000000000000..30da1805e83f --- /dev/null +++ b/users/wpcarro/emacs/pkgs/string/string.el @@ -0,0 +1,98 @@ +;;; string.el --- Library for working with strings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with strings. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 's) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-split (y x) + "Map string X into a list of strings that were separated by Y." + (s-split y x)) + +(defun string-format (x &rest args) + "Format template string X with ARGS." + (apply #'format (cons x args))) + +(defun string-concat (&rest strings) + "Joins `STRINGS' into onto string." + (apply #'s-concat strings)) + +(defun string-to-symbol (string) + "Maps `STRING' to a symbol." + (intern string)) + +(defun string-from-symbol (symbol) + "Maps `SYMBOL' into a string." + (symbol-name symbol)) + +(defun string-prepend (prefix x) + "Prepend `PREFIX' onto `X'." + (s-concat prefix x)) + +(defun string-append (postfix x) + "Appen `POSTFIX' onto `X'." + (s-concat x postfix)) + +(defun string-surround (s x) + "Surrounds `X' one each side with `S'." + (->> x + (string-prepend s) + (string-append s))) + +;; TODO: Define a macro for defining a function and a test. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Casing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-caps->kebab (x) + "Change the casing of `X' from CAP_CASE to kebab-case." + (->> x + s-downcase + (s-replace "_" "-"))) + +(defun string-kebab->caps (x) + "Change the casing of X from CAP_CASE to kebab-case." + (->> x + s-upcase + (s-replace "-" "_"))) + +(defun string-lower->caps (x) + "Change the casing of X from lowercase to CAPS_CASE." + (->> x + s-upcase + (s-replace " " "_"))) + +(defun string-lower->kebab (x) + "Change the casing of `X' from lowercase to kebab-case." + (s-replace " " "-" x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun string-instance? (x) + "Return t if X is a string." + (stringp x)) + +(defun string-contains? (c x) + "Return t if X is in C." + (s-contains? c x)) + +(provide 'string) +;;; string.el ends here diff --git a/users/wpcarro/emacs/pkgs/string/tests.el b/users/wpcarro/emacs/pkgs/string/tests.el new file mode 100644 index 000000000000..351e305466d3 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/string/tests.el @@ -0,0 +1,22 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest string-caps->kebab () + (should (string= "foo-bar-baz" (string-caps->kebab "FOO_BAR_BAZ")))) + +(ert-deftest string-kebab->caps () + (should (string= "FOO_BAR_BAZ" (string-kebab->caps "foo-bar-baz")))) + +(ert-deftest string-lower->caps () + (should (string= "FOO_BAR_BAZ" (string-lower->caps "foo bar baz")))) + +(ert-deftest string-lower->kebab () + (should (string= "foo-bar-baz" (string-lower->kebab "foo bar baz")))) diff --git a/users/wpcarro/emacs/pkgs/struct/README.md b/users/wpcarro/emacs/pkgs/struct/README.md new file mode 100644 index 000000000000..34dac6614cd1 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/struct/README.md @@ -0,0 +1,6 @@ +# struct.el + +[![Build status](https://badge.buildkite.com/016bff4b8ae2704a3bbbb0a250784e6692007c582983b6dea7.svg?branch=refs/heads/canon)](https://buildkite.com/tvl/depot) + +Provides new macros exposing immutable and mutable interfaces for working with +structs in Elisp. diff --git a/users/wpcarro/emacs/pkgs/struct/default.nix b/users/wpcarro/emacs/pkgs/struct/default.nix new file mode 100644 index 000000000000..558ebd0a3d20 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/struct/default.nix @@ -0,0 +1,29 @@ +{ pkgs, depot, ... }: + +let + struct = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "struct"; + version = "1.0.0"; + src = ./struct.el; + packageRequires = [ ]; + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + struct + ]); +in +struct.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; + passthru.meta.ci.extraSteps.github = depot.tools.releases.filteredGitPush { + filter = ":/users/wpcarro/emacs/pkgs/struct"; + remote = "git@github.com:wpcarro/struct.el.git"; + ref = "refs/heads/canon"; + }; +}) diff --git a/users/wpcarro/emacs/pkgs/struct/struct.el b/users/wpcarro/emacs/pkgs/struct/struct.el new file mode 100644 index 000000000000..5d6572bf6dde --- /dev/null +++ b/users/wpcarro/emacs/pkgs/struct/struct.el @@ -0,0 +1,65 @@ +;;; struct.el --- Helpers for working with structs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 1.0.0 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Provides new macros for working with structs. Also provides adapter +;; interfaces to existing struct macros, that should have more intuitive +;; interfaces. +;; +;; Sometimes `setf' just isn't enough. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro struct-update (type field f xs) + "Apply F to FIELD in XS, which is a struct of TYPE. +This is immutable." + (let ((copier (struct--copier-for type)) + (accessor (struct--accessor-for type field))) + `(let ((copy (,copier ,xs))) + (setf (,accessor copy) (funcall ,f (,accessor copy))) + copy))) + +(defmacro struct-update! (type field f xs) + "Mutably apply F to FIELD in XS." + (let ((accessor (struct--accessor-for type field))) + `(progn + (setf (,accessor ,xs) (funcall ,f (,accessor ,xs))) + ,xs))) + +(defmacro struct-set (type field x xs) + "Immutably set FIELD in XS (struct TYPE) to X." + (let ((copier (struct--copier-for type)) + (accessor (struct--accessor-for type field))) + `(let ((copy (,copier ,xs))) + (setf (,accessor copy) ,x) + copy))) + +(defmacro struct-set! (type field x xs) + "Set FIELD in XS (struct TYPE) to X mutably. +This is an adapter interface to `setf'." + (let ((accessor (struct--accessor-for type field))) + `(progn + (setf (,accessor ,xs) ,x) + ,xs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helper Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun struct--copier-for (type) + (intern (format "copy-%s" (symbol-name type)))) + +(defun struct--accessor-for (type field) + (intern (format "%s-%s" + (symbol-name type) + (symbol-name field)))) + +(provide 'struct) +;;; struct.el ends here diff --git a/users/wpcarro/emacs/pkgs/struct/tests.el b/users/wpcarro/emacs/pkgs/struct/tests.el new file mode 100644 index 000000000000..a7ddb52c46d6 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/struct/tests.el @@ -0,0 +1,44 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'struct) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct dummy name age) + +(ert-deftest struct-update () + (let* ((test (make-dummy :name "Roofus" :age 19)) + (result (struct-update dummy name #'upcase test))) + ;; test + (should (string= "Roofus" (dummy-name test))) + (should (= 19 (dummy-age test))) + ;; result + (should (string= "ROOFUS" (dummy-name result))) + (should (= 19 (dummy-age result))))) + +(ert-deftest struct-update! () + (let ((test (make-dummy :name "Roofus" :age 19))) + (struct-update! dummy name #'upcase test) + (should (string= "ROOFUS" (dummy-name test))) + (should (= 19 (dummy-age test))))) + +(ert-deftest struct-set () + (let* ((test (make-dummy :name "Roofus" :age 19)) + (result (struct-set dummy name "Shoofus" test))) + ;; test + (should (string= "Roofus" (dummy-name test))) + (should (= 19 (dummy-age test))) + ;; result + (should (string= "Shoofus" (dummy-name result))) + (should (= 19 (dummy-age result))))) + +(ert-deftest struct-set! () + (let ((test (make-dummy :name "Roofus" :age 19))) + (struct-set! dummy name "Doofus" test) + (should (string= "Doofus" (dummy-name test))) + (should (= 19 (dummy-age test))))) diff --git a/users/wpcarro/emacs/pkgs/symbol/default.nix b/users/wpcarro/emacs/pkgs/symbol/default.nix new file mode 100644 index 000000000000..9334697e3203 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/symbol/default.nix @@ -0,0 +1,24 @@ +{ pkgs, depot, ... }: + +let + symbol = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "symbol"; + version = "1.0.0"; + src = ./symbol.el; + packageRequires = [ ]; + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ + symbol + ]); +in +symbol.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/symbol/symbol.el b/users/wpcarro/emacs/pkgs/symbol/symbol.el new file mode 100644 index 000000000000..4b16351831c9 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/symbol/symbol.el @@ -0,0 +1,38 @@ +;;; symbol.el --- Library for working with symbols -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with symbols. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun symbol-to-string (symbol) + "Map `SYMBOL' into a string." + (symbol-name symbol)) + +(defun symbol-from-string (string) + "Map `STRING' into a symbol." + (intern string)) + +(defun symbol-as-string (f x) + "Treat the symbol, X, as a string while applying F to it. +Coerce back to a symbol on the way out." + (symbol-from-string (funcall f (symbol-to-string x)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun symbol-instance? (x) + "Return t if X is a symbol." + (symbolp x)) + +(provide 'symbol) +;;; symbol.el ends here diff --git a/users/wpcarro/emacs/pkgs/symbol/tests.el b/users/wpcarro/emacs/pkgs/symbol/tests.el new file mode 100644 index 000000000000..b10362b162c7 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/symbol/tests.el @@ -0,0 +1,22 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'symbol) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest symbol-to-string () + (should (string= "foo" (symbol-to-string 'foo)))) + +(ert-deftest symbol-from-string () + (should (eq 'foo (symbol-from-string "foo")))) + +(ert-deftest symbol-as-string () + (should (eq 'foo-hook + (symbol-as-string + (lambda (x) (format "%s-hook" x)) + 'foo)))) diff --git a/users/wpcarro/emacs/pkgs/theme/default.nix b/users/wpcarro/emacs/pkgs/theme/default.nix new file mode 100644 index 000000000000..aea639436961 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/theme/default.nix @@ -0,0 +1,14 @@ +{ pkgs, depot, ... }: + +pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "theme"; + version = "1.0.0"; + src = ./theme.el; + packageRequires = + (with depot.users.wpcarro.emacs.pkgs; [ + cycle + ]); + }) +{ } diff --git a/users/wpcarro/emacs/pkgs/theme/theme.el b/users/wpcarro/emacs/pkgs/theme/theme.el new file mode 100644 index 000000000000..32f2c89a4d0b --- /dev/null +++ b/users/wpcarro/emacs/pkgs/theme/theme.el @@ -0,0 +1,78 @@ +;;; theme.el --- Colors and stuff -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; +;; Cycle through a whitelist of themes. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cycle) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup theme nil + "Customization options for `theme'." + :group 'theme) + +(defcustom theme-whitelist + (cycle-from-list (custom-available-themes)) + "The whitelist of themes through which to cycle." + :type '(cycle symbol) + :group 'theme) + +(defcustom theme-after-change + nil + "Hook invoked after a new theme is loaded" + :type 'hook + :group 'theme) + +(defun theme-whitelist-set (theme) + "Focus the THEME in the `theme-whitelist' cycle." + (cycle-focus! (lambda (x) (equal x theme)) theme-whitelist) + (theme--set (cycle-current theme-whitelist))) + +(defun theme-select () + "Load a theme using `completing-read'." + (interactive) + (let ((theme (completing-read "Theme: " (cycle-to-list theme-whitelist)))) + (theme--disable-all) + (theme--set (intern theme)))) + +(defun theme-next () + "Disable the currently active theme and load the next theme." + (interactive) + (disable-theme (cycle-current theme-whitelist)) + (theme--set (cycle-next! theme-whitelist)) + (message (format "Active theme: %s" (cycle-current theme-whitelist)))) + +(defun theme-prev () + "Disable the currently active theme and load the previous theme." + (interactive) + (disable-theme (cycle-current theme-whitelist)) + (theme--set (cycle-prev! theme-whitelist)) + (message (format "Active theme: %s" (cycle-current theme-whitelist)))) + +(defun theme--disable-all () + "Disable all currently enabled themes." + (interactive) + (dolist (x custom-enabled-themes) + (disable-theme x))) + +(defun theme--set (theme) + "Call `load-theme' with `THEME', ensuring that the line numbers are bright. +There is no hook that I'm aware of to handle this more elegantly." + (load-theme theme t) + (run-hooks 'theme-after-change)) + +(provide 'theme) +;;; theme.el ends here diff --git a/users/wpcarro/emacs/pkgs/tuple/default.nix b/users/wpcarro/emacs/pkgs/tuple/default.nix new file mode 100644 index 000000000000..0626370e4795 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/tuple/default.nix @@ -0,0 +1,10 @@ +{ pkgs, depot, ... }: + +pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "tuple"; + version = "1.0.0"; + src = ./tuple.el; + }) +{ } diff --git a/users/wpcarro/emacs/pkgs/tuple/tuple.el b/users/wpcarro/emacs/pkgs/tuple/tuple.el new file mode 100644 index 000000000000..848c6fa48b15 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/tuple/tuple.el @@ -0,0 +1,93 @@ +;;; tuple.el --- Tuple API for Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Work with cons cells with two elements with a familiar API for those who have +;; worked with tuples before. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct tuple first second) + +;; Create +(defun tuple-new () + "Return an empty tuple." + (make-tuple :first nil + :second nil)) + +(defun tuple-from (a b) + "Return a new tuple from A and B." + (make-tuple :first a + :second b)) + +(defun tuple-from-dotted (dp) + "Convert dotted pair, DP, into a tuple." + (tuple-from (car dp) (cdr dp))) + +;; Read +(defun tuple-first (pair) + "Return the first element of PAIR." + (tuple-first pair)) + +(defun tuple-second (pair) + "Return the second element of PAIR." + (tuple-second pair)) + +;; Update +(defun tuple-map-each (f g pair) + "Apply F to first, G to second in PAIR." + (->> pair + (tuple-map-first f) + (tuple-map-second g))) + +(defun tuple-map (f pair) + "Apply F to PAIR." + (let ((pair-copy (copy-tuple pair))) + (funcall f pair-copy))) + +(defun tuple-map-first (f pair) + "Apply function F to the first element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-first pair-copy) (funcall f (tuple-first pair-copy))) + pair-copy)) + +(defun tuple-map-second (f pair) + "Apply function F to the second element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-second pair-copy) (funcall f (tuple-second pair-copy))) + pair-copy)) + +(defun tuple-set-first (a pair) + "Return a new tuple with the first element set as A in PAIR." + (tuple-map-first (lambda (_) a) pair)) + +(defun tuple-set-second (b pair) + "Return a new tuple with the second element set as B in PAIR." + (tuple-map-second (lambda (_) b) pair)) + +;; Delete +(defun tuple-delete-first (pair) + "Return PAIR with the first element set to nil." + (tuple-set-first nil pair)) + +(defun tuple-delete-second (pair) + "Return PAIR with the second element set to nil." + (tuple-set-second nil pair)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tuple-instance? (x) + "Return t if X is a tuple." + (tuple-p x)) + +(provide 'tuple) +;;; tuple.el ends here diff --git a/users/wpcarro/emacs/pkgs/vector/default.nix b/users/wpcarro/emacs/pkgs/vector/default.nix new file mode 100644 index 000000000000..c0a475aaaa82 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/vector/default.nix @@ -0,0 +1,21 @@ +{ pkgs, depot, ... }: + +let + vector = pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "vector"; + version = "1.0.0"; + src = ./vector.el; + }) + { }; + + emacs = (pkgs.emacsPackagesFor pkgs.emacs28).emacsWithPackages (epkgs: [ vector ]); +in +vector.overrideAttrs (_old: { + doCheck = true; + checkPhase = '' + ${emacs}/bin/emacs -batch \ + -l ert -l ${./tests.el} -f ert-run-tests-batch-and-exit + ''; +}) diff --git a/users/wpcarro/emacs/pkgs/vector/tests.el b/users/wpcarro/emacs/pkgs/vector/tests.el new file mode 100644 index 000000000000..ffa983188229 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/vector/tests.el @@ -0,0 +1,20 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ert) +(require 'vector) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(ert-deftest vector-misc-tests () + (let ((xs [1 2 3]) + (ys [1 2 3])) + (should (= 1 (vector-get 0 ys))) + (vector-set 0 4 ys) + (should (= 1 (vector-get 0 ys))) + (should (= 1 (vector-get 0 xs))) + (vector-set! 0 4 xs) + (should (= 4 (vector-get 0 xs))))) diff --git a/users/wpcarro/emacs/pkgs/vector/vector.el b/users/wpcarro/emacs/pkgs/vector/vector.el new file mode 100644 index 000000000000..87f38d7d93e2 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/vector/vector.el @@ -0,0 +1,58 @@ +;;; vector.el --- Working with Elisp's Vector data type -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; It might be best to think of Elisp vectors as tuples in languages like +;; Haskell or Erlang. +;; +;; Not surprisingly, this API is modelled after Elixir's Tuple API. +;; +;; Some Elisp trivia: +;; - "Array": Usually means vector or string. +;; - "Sequence": Usually means list or "array" (see above). +;; +;; It might be a good idea to think of Array and Sequence as typeclasses in +;; Elisp. This is perhaps more similar to Elixir's notion of the Enum protocol. +;; +;; Intentionally not supporting a to-list function, because tuples can contain +;; heterogenous types whereas lists should contain homogenous types. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun vector-concat (&rest args) + "Return a new vector composed of all vectors in `ARGS'." + (apply #'vconcat args)) + +(defun vector-prepend (x xs) + "Add `X' to the beginning of `XS'." + (vector-concat `[,x] xs)) + +(defun vector-append (x xs) + "Add `X' to the end of `XS'." + (vector-concat xs `[,x])) + +(defun vector-get (i xs) + "Return the value in `XS' at index, `I'." + (aref xs i)) + +(defun vector-set (i v xs) + "Set index `I' to value `V' in `XS'. +Returns a copy of `XS' with the updates." + (let ((copy (vconcat [] xs))) + (aset copy i v) + copy)) + +(defun vector-set! (i v xs) + "Set index `I' to value `V' in `XS'. +This function mutates XS." + (aset xs i v)) + +(provide 'vector) +;;; vector.el ends here diff --git a/users/wpcarro/emacs/pkgs/vterm-mgt/README.md b/users/wpcarro/emacs/pkgs/vterm-mgt/README.md new file mode 100644 index 000000000000..b855826929ca --- /dev/null +++ b/users/wpcarro/emacs/pkgs/vterm-mgt/README.md @@ -0,0 +1,17 @@ +# vterm-mgt.el + +[![Build status](https://badge.buildkite.com/016bff4b8ae2704a3bbbb0a250784e6692007c582983b6dea7.svg?branch=refs/heads/canon)](https://buildkite.com/tvl/depot) + +[emacs-libvterm](https://github.com/akermu/emacs-libvterm) is a feature-complete +terminal emulator inside Emacs based on libvterm. + +`vterm-mgt.el`, adds functionality on top of `vterm` to allow you to: + +* find-or-create `vterm` instances +* fuzzily switch between existing `vterm` buffers +* cycle through existing `vterm` instances +* easily rename `vterm` buffers + +## Alternatives to vterm-mgt.el + +* [multi-vterm](https://github.com/suonlight/multi-vterm) diff --git a/users/wpcarro/emacs/pkgs/vterm-mgt/default.nix b/users/wpcarro/emacs/pkgs/vterm-mgt/default.nix new file mode 100644 index 000000000000..88eb5502042a --- /dev/null +++ b/users/wpcarro/emacs/pkgs/vterm-mgt/default.nix @@ -0,0 +1,19 @@ +{ pkgs, depot, ... }: + +pkgs.emacsPackages.trivialBuild { + pname = "vterm-mgt"; + version = "1.0.0"; + src = ./vterm-mgt.el; + packageRequires = + (with pkgs.emacsPackages; [ + vterm + ]) ++ + (with depot.users.wpcarro.emacs.pkgs; [ + cycle + ]); + passthru.meta.ci.extraSteps.github = depot.tools.releases.filteredGitPush { + filter = ":/users/wpcarro/emacs/pkgs/vterm-mgt"; + remote = "git@github.com:wpcarro/vterm-mgt.el.git"; + ref = "refs/heads/canon"; + }; +} diff --git a/users/wpcarro/emacs/pkgs/vterm-mgt/vterm-mgt.el b/users/wpcarro/emacs/pkgs/vterm-mgt/vterm-mgt.el new file mode 100644 index 000000000000..c082e54a5976 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/vterm-mgt/vterm-mgt.el @@ -0,0 +1,140 @@ +;;; vterm-mgt.el --- Help me manage my vterm instances -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Supporting functions to instantiate vterm buffers, kill existing vterm +;; buffers, rename vterm buffers, cycle forwards and backwards through vterm +;; buffers. +;; +;; Many of the functions defined herein are intended to be bound to +;; `vterm-mode-map'. Some assertions are made to guard against calling +;; functions that are intended to be called from outside of a vterm buffer. +;; These assertions shouldn't error when the functions are bound to +;; `vterm-mode-map'. If for some reason, you'd like to bind these functions to +;; a separate keymap, caveat emptor. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cycle) +(require 'vterm) +(require 'seq) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup vterm-mgt nil + "Customization options for `vterm-mgt'." + :group 'vterm) + +(defcustom vterm-mgt-scroll-on-focus nil + "When t, call `end-of-buffer' after focusing a vterm instance." + :type '(boolean) + :group 'vterm-mgt) + +(defconst vterm-mgt--instances (cycle-new) + "A cycle tracking all of my vterm instances.") + +(defun vterm-mgt--instance? (b) + "Return t if the buffer B is a vterm instance." + (equal 'vterm-mode (buffer-local-value 'major-mode b))) + +(defun vterm-mgt--assert-vterm-buffer () + "Error when the `current-buffer' is not a vterm buffer." + (unless (vterm-mgt--instance? (current-buffer)) + (error "Current buffer is not a vterm buffer"))) + +(defun vterm-mgt-next () + "Replace the current buffer with the next item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (vterm-mgt-reconcile-state) + (cycle-focus-item! (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-next! vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-prev () + "Replace the current buffer with the previous item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (vterm-mgt-reconcile-state) + (cycle-focus-item! (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-prev! vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-instantiate () + "Create a new vterm instance. + +Prefer calling this function instead of `vterm'. This function ensures that the + newly created instance is added to `vterm-mgt--instances'. + +If however you must call `vterm', if you'd like to cycle through vterm + instances, make sure you call `vterm-mgt-reconcile-state' to allow vterm-mgt + to collect any untracked vterm instances." + (interactive) + (vterm-mgt-reconcile-state) + (let ((buffer (vterm t))) + (cycle-append! buffer vterm-mgt--instances) + (cycle-focus-item! buffer vterm-mgt--instances))) + +(defun vterm-mgt-kill () + "Kill the current buffer and remove it from `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (let* ((buffer (current-buffer))) + (when (kill-buffer buffer) + (vterm-mgt-reconcile-state)))) + +(defun vterm-mgt-find-or-create () + "Call `switch-to-buffer' on a focused vterm instance if there is one. + +When `cycle-focused?' returns nil, focus the first item in the cycle. When +there are no items in the cycle, call `vterm-mgt-instantiate' to create a vterm +instance." + (interactive) + (vterm-mgt-reconcile-state) + (if (cycle-empty? vterm-mgt--instances) + (vterm-mgt-instantiate) + (if (cycle-focused? vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)) + (progn + (cycle-jump! 0 vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)))))) + +(defun vterm-mgt-rename-buffer (name) + "Rename the current buffer ensuring that its NAME is wrapped in *vterm<...>*. +This function should be called from a buffer running vterm." + (interactive "SRename vterm buffer: ") + (vterm-mgt--assert-vterm-buffer) + (rename-buffer (format "*vterm<%s>*" name))) + +(defun vterm-mgt-reconcile-state () + "Fill `vterm-mgt--instances' with the existing vterm buffers. + +If for whatever reason, the state of `vterm-mgt--instances' is corrupted and + misaligns with the state of vterm buffers in Emacs, use this function to + restore the state." + (interactive) + (setq vterm-mgt--instances + (cycle-from-list (seq-filter #'vterm-mgt--instance? (buffer-list))))) + +(defun vterm-mgt-select () + "Select a vterm instance by name from the list in `vterm-mgt--instances'." + (interactive) + (vterm-mgt-reconcile-state) + (switch-to-buffer + (completing-read "Switch to vterm: " + (seq-map #'buffer-name (cycle-to-list vterm-mgt--instances))))) + +(provide 'vterm-mgt) +;;; vterm-mgt.el ends here diff --git a/users/wpcarro/emacs/pkgs/zle/default.nix b/users/wpcarro/emacs/pkgs/zle/default.nix new file mode 100644 index 000000000000..9d4820a9445e --- /dev/null +++ b/users/wpcarro/emacs/pkgs/zle/default.nix @@ -0,0 +1,10 @@ +{ pkgs, ... }: + +pkgs.callPackage + ({ emacsPackages }: + emacsPackages.trivialBuild { + pname = "zle"; + version = "1.0.0"; + src = ./zle.el; + }) +{ } diff --git a/users/wpcarro/emacs/pkgs/zle/zle.el b/users/wpcarro/emacs/pkgs/zle/zle.el new file mode 100644 index 000000000000..21a6e35f13d3 --- /dev/null +++ b/users/wpcarro/emacs/pkgs/zle/zle.el @@ -0,0 +1,90 @@ +;;; zle.el --- Functions to mimmick my ZLE KBDs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This is primarily for personal use. The keybindings that I choose are those +;; that feel slightly mnemonic while also not shadowing important bindings. +;; It's quite possible that our tastes will differ here. +;; +;; All of these keybindings are intended to shave off milliseconds off your +;; typing. I don't expect these numbers to sum up to a meaningful amount. The +;; primary reason that I wrote this, is that it introduces a small amount of +;; structural editing to my workflow. I've been using these exact keybindings +;; on the command line, and I find them subtely delightful to use. So much so +;; that I decided to bring them to my Emacs configuration. +;; +;; ZLE is the Z-shell line editor. I have some KBDs and functions that I often +;; want in Emacs. +;; +;; Usage: +;; Consider running `(zle-minor-mode)' to run this globally. Depending on your +;; configuration, it could be non-disruptive, disruptive, or extremely +;; disruptive. + +;;; Code: + +;; subshell (C-j) +(defun zle-subshell () + "Insert the characters necessary to create a subshell." + (interactive) + (insert-char ?$) + (insert-char ?\() + (save-excursion + (insert-char ?\)))) + +;; variable (C-v) +(defun zle-variable () + "Insert the characters to reference a variable." + (interactive) + (insert-char ?$) + (insert-char ?{) + (save-excursion + (insert-char ?}))) + +;; 2x dash (C-M--) +(defun zle-dash-dash () + "Insert the characters for flags with 2x dashes." + (interactive) + (insert-char ? ) + (insert-char ?-) + (insert-char ?-)) + +;; 1x quotes (M-') +(defun zle-single-quote () + "Insert the characters to quickly create single quotes." + (interactive) + (insert-char ? ) + (insert-char ?') + (save-excursion + (insert-char ?'))) + +;; 2x quotes (M-") +(defun zle-double-quote () + "Insert the characters to quickly create double quotes." + (interactive) + (insert-char ? ) + (insert-char ?\") + (save-excursion + (insert-char ?\"))) + +(defvar zle-kbds + (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-j") #'zle-subshell) + (define-key map (kbd "C-v") #'zle-variable) + (define-key map (kbd "C-M--") #'zle-dash-dash) + (define-key map (kbd "M-'") #'zle-single-quote) + (define-key map (kbd "M-\"") #'zle-double-quote) + map) + "Keybindings shaving milliseconds off of typing.") + +(define-minor-mode zle-minor-mode + "A minor mode mirroring my ZLE keybindings." + :init-value nil + :lighter " zle" + :keymap zle-kbds) + +(provide 'zle) +;;; zle.el ends here diff --git a/users/wpcarro/emacs/snippets.md b/users/wpcarro/emacs/snippets.md new file mode 100644 index 000000000000..2081b5617184 --- /dev/null +++ b/users/wpcarro/emacs/snippets.md @@ -0,0 +1,22 @@ +# Snippets + +Specifying snippets that I plan on defining for most of the programming +languages with which I work. I hope this will serve as a checklist of language +constructs I should support when adopting a new language. + +## Shared language features + +These are language features that should be available across most of the +languages that I'm hoping to support. + +- `ld`: anonymous functions (i.e. lambdas) +- `fn`: named function definition +- `var`: variable definition + +## Miscellaneous other language KBDs + +Some of this is related to language tool must-haves, which may need to be a +separate document. + +- `<leader>d`: Show documentation +- `<leader>x`: Evaluate expression (works mostly for LISPs) diff --git a/users/wpcarro/emacs/workspace.josh b/users/wpcarro/emacs/workspace.josh new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/wpcarro/emacs/workspace.josh |