diff options
Diffstat (limited to 'configs/shared/.emacs.d/wpc')
95 files changed, 8144 insertions, 0 deletions
diff --git a/configs/shared/.emacs.d/wpc/alist.el b/configs/shared/.emacs.d/wpc/alist.el new file mode 100644 index 000000000000..da0ce68b8f75 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/alist.el @@ -0,0 +1,227 @@ +;;; alist.el --- Interface for working with associative lists -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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. + +;; Dependencies: + +;; TODO: Consider dropping explicit dependency white-listing since all of these +;; should be available in my Emacs. The problem arises when this library needs +;; to be published, in which case, something like Nix and a build process could +;; possible insert the necessary require statements herein. Not sure how I feel +;; about this though. +(require 'maybe) +(require 'macros) +(require 'dash) +(require 'tuple) +(require 'maybe) + +;;; Code: + +;; 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)'. + +(defun alist/new () + "Return a new, empty alist." + '()) + +;; Create +;; TODO: See if this mutates. +(defun alist/set (k v xs) + "Set K to V in XS." + (if (alist/has-key? k xs) + (progn + (setf (alist-get k xs) v) + xs) + (list/cons `(,k . ,v) xs))) + +(defun alist/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 alist/get (k xs) + "Return the value at K in XS; otherwise, return nil. +Returns the first occurrence of K in XS since alists support multiple entries." + (cdr (assoc k xs))) + +(defun alist/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 alist/update (k f xs) + "Apply F to the value stored at K in XS. +If `K' is not in `XS', this function errors. Use `alist/upsert' if you're +interested in inserting a value when a key doesn't already exist." + (if (maybe/nil? (alist/get k xs)) + (error "Refusing to update: key does not exist in alist") + (alist/set k (funcall f (alist/get k xs)) xs))) + +(defun alist/update! (k f xs) + "Call F on the entry at K in XS. +Mutative variant of `alist/update'." + (alist/set! k (funcall f (alist/get k xs))xs)) + +;; TODO: Support this. +(defun alist/upsert (k v f xs) + "If K exists in `XS' call `F' on the value otherwise insert `V'." + (if (alist/get k xs) + (alist/update k f xs) + (alist/set k v xs))) + +;; Delete +;; TODO: Make sure `delete' and `remove' behave as advertised in the Elisp docs. +(defun alist/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 `alist/delete-all' and `alist/dedupe'." + (remove (assoc k xs) xs)) + +(defun alist/delete! (k xs) + "Delete the entry of K from XS. +Mutative variant of `alist/delete'." + (delete (assoc k xs) xs)) + +;; Additions to the CRUD API +;; TODO: Implement this function. +(defun alist/dedupe-keys (xs) + "Remove the entries in XS where the keys are `equal'.") + +(defun alist/dedupe-entries (xs) + "Remove the entries in XS where the key-value pair are `equal'." + (delete-dups xs)) + +(defun alist/keys (xs) + "Return a list of the keys in XS." + (mapcar 'car xs)) + +(defun alist/values (xs) + "Return a list of the values in XS." + (mapcar 'cdr xs)) + +(defun alist/has-key? (k xs) + "Return t if XS has a key `equal' to K." + (maybe/some? (assoc k xs))) + +(defun alist/has-value? (v xs) + "Return t if XS has a value of V." + (maybe/some? (rassoc v xs))) + +(defun alist/count (xs) + "Return the number of entries in XS." + (length xs)) + +(defun alist/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." + (->> (alist/keys xs) + (list/reduce acc + (lambda (k acc) + (funcall f k (alist/get k xs) acc))))) + +(defun alist/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." + (alist/reduce a #'alist/set b)) + +;; TODO: Support `-all' variants like: +;; - get-all +;; - delete-all +;; - update-all + +;; Scratch-pad +(macros/comment + (progn + (setq person '((first-name . "William") + (first-name . "William") + (last-name . "Carroll") + (last-name . "Another"))) + (alist/set 'last-name "Van Gogh" person) + (alist/get 'last-name person) + (alist/update 'last-name (lambda (x) "whoops") person) + (alist/delete 'first-name person) + (alist/keys person) + (alist/values person) + (alist/count person) + (alist/has-key? 'first-name person) + (alist/has-value? "William" person) + ;; (alist/dedupe-keys person) + (alist/dedupe-entries person) + (alist/count person))) + +;; Tests + +;; TODO: Support test cases for the entire API. + +(provide 'alist) +;;; alist.el ends here diff --git a/configs/shared/.emacs.d/wpc/bag.el b/configs/shared/.emacs.d/wpc/bag.el new file mode 100644 index 000000000000..c9511b18e737 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/bag.el @@ -0,0 +1,66 @@ +;;; bag.el --- Working with bags (aka multi-sets) -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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 bag 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 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bag xs) + +(defun bag/update (f xs) + "Call F on alist in XS." + (let ((ys (bag-xs xs))) + (setf (bag-xs xs) (funcall f ys)))) + +(defun bag/new () + "Create an empty bag." + (make-bag :xs (alist/new))) + +(defun bag/contains? (x xs) + "Return t if XS has X." + (alist/has-key? x (bag-xs xs))) + +;; TODO: Tabling this for now since working with structs seems to be +;; disappointingly difficult. Where is `struct/update'? +;; (defun bag/add (x xs) +;; "Add X to XS.") + +;; TODO: What do we name delete vs. remove? +;; (defun bag/remove (x xs) +;; "Remove X from XS. +;; This is a no-op is X doesn't exist in XS.") + +(defun bag/from-list (xs) + "Map a list of `XS' into a bag." + (->> xs + (list/reduce + (bag/new) + (lambda (x acc) + (bag/add x 1 #'number/inc acc))))) + +(provide 'bag) +;;; bag.el ends here diff --git a/configs/shared/.emacs.d/wpc/bills.el b/configs/shared/.emacs.d/wpc/bills.el new file mode 100644 index 000000000000..fbdeb9d0f820 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/bills.el @@ -0,0 +1,26 @@ +;;; bills.el --- Helping me manage my bills -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; For personal use only. + +;;; Code: + +(defconst bills/whitelist '(("Council Tax" . "rbkc.gov.uk/onlinepayments/counciltaxpayments/") + ("Internet". "plus.net/member-centre/login")) + "Maps searchable labels to URLs to pay these bills.") + +(defun bills/url () + "Copies the URL to pay a bill onto the clipboard." + (ivy-read + "Bill: " + bills/whitelist + :action (lambda (entry) + (kill-new (cdr entry)) + (alert "Copied to clipboard!")))) + +(macros/comment + (bills/url)) + +(provide 'bills) +;;; bills.el ends here diff --git a/configs/shared/.emacs.d/wpc/bookmark.el b/configs/shared/.emacs.d/wpc/bookmark.el new file mode 100644 index 000000000000..2a4156041e15 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/bookmark.el @@ -0,0 +1,108 @@ +;;; bookmark.el --- Saved files and directories on my filesystem -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; After enjoying and relying on Emacs's builtin `jump-to-register' command, I'd +;; like to recreate this functionality with a few extensions. +;; +;; Everything herein will mimmick my previous KBDs for `jump-to-register', which +;; were <leader>-j-<register-kbd>. If the `bookmark-path' is a file, Emacs will +;; open a buffer with that file. If the `bookmark-path' is a directory, Emacs +;; will open an ivy window searching that directory. + +;;; Code: + +(require 'f) +(require 'buffer) +(require 'list) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bookmark label path kbd) + +;; TODO: Consider hosting this function somewhere other than here, since it +;; feels useful above of the context of bookmarks. +;; TODO: Assess whether it'd be better to use the existing function: +;; `counsel-projectile-switch-project-action'. See the noise I made on GH for +;; more context: https://github.com/ericdanan/counsel-projectile/issues/137 +(defun bookmark/handle-directory-dwim (path) + "Open PATH as either a project directory or a regular directory. +If PATH is `projectile-project-p', open with `counsel-projectile-find-file'. +Otherwise, open with `counsel-find-file'." + (if (projectile-project-p path) + (with-temp-buffer + (cd (projectile-project-p path)) + (call-interactively #'counsel-projectile-find-file)) + (let ((ivy-extra-directories nil)) + (counsel-find-file path)))) + +(defconst bookmark/handle-directory #'bookmark/handle-directory-dwim + "Function to call when a bookmark points to a directory.") + +(defconst bookmark/handle-file #'counsel-find-file-action + "Function to call when a bookmark points to a file.") + +(defconst bookmark/whitelist + (list + ;; TODO: Consider using (getenv "ORG_DIRECTORY") + (make-bookmark :label "org" + :path "~/Dropbox/org" + :kbd "o") + (make-bookmark :label "dotfiles" + :path "~/Dropbox/dotfiles" + :kbd "d") + (make-bookmark :label "current project" + :path constants/current-project + :kbd "p")) + "List of registered bookmarks.") + +(defconst bookmark/install-kbds? t + "When t, install keybindings.") + +;; TODO: Consider `ivy-read' extension that takes a list of structs, +;; `struct-to-label' and `label-struct' functions. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bookmark/open (b) + "Open bookmark, B, in a new buffer or an ivy minibuffer." + (let ((path (bookmark-path b))) + (cond + ((f-directory? path) + (funcall bookmark/handle-directory path)) + ((f-file? path) + (funcall bookmark/handle-file path))))) + +(defun bookmark/ivy-open () + "Use ivy to filter available bookmarks." + (interactive) + (ivy-read "Bookmark: " + (->> bookmark/whitelist + (list/map #'bookmark-label)) + :require-match t + :action (lambda (label) + (->> bookmark/whitelist + (list/find + (lambda (b) + (equal label (bookmark-label b)))) + bookmark/open)))) + +(when bookmark/install-kbds? + (evil-leader/set-key + "jj" #'bookmark/ivy-open) + (->> bookmark/whitelist + (list/map + (lambda (b) + (evil-leader/set-key + (string/concat "j" (bookmark-kbd b)) + ;; TODO: Consider `cl-labels' so `which-key' minibuffer is more + ;; helpful. + (lambda () (interactive) (bookmark/open b))))))) + +(provide 'bookmark) +;;; bookmark.el ends here diff --git a/configs/shared/.emacs.d/wpc/buffer.el b/configs/shared/.emacs.d/wpc/buffer.el new file mode 100644 index 000000000000..9de14b4bd4c1 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/buffer.el @@ -0,0 +1,51 @@ +;;; buffer.el --- Working with Emacs buffers -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun buffer/exists? (name) + "Return t if buffer, NAME, exists." + (maybe/some? (buffer/find name))) + +(defun buffer/find (buffer-or-name) + "Find a buffer by its BUFFER-OR-NAME." + (get-buffer buffer-or-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)) + +(provide 'buffer) +;;; buffer.el ends here diff --git a/configs/shared/.emacs.d/wpc/bytes.el b/configs/shared/.emacs.d/wpc/bytes.el new file mode 100644 index 000000000000..d8bd2e288614 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/bytes.el @@ -0,0 +1,109 @@ +;;; bytes.el --- Working with byte values -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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. + +(require 'prelude) +(require 'tuple) +(require 'math) +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst bytes/kb (math/exp 2 10) + "Number of bytes in a kilobyte.") + +(defconst bytes/mb (math/exp 2 20) + "Number of bytes in a megabytes.") + +(defconst bytes/gb (math/exp 2 30) + "Number of bytes in a gigabyte.") + +(defconst bytes/tb (math/exp 2 40) + "Number of bytes in a terabyte.") + +(defconst bytes/pb (math/exp 2 50) + "Number of bytes in a petabyte.") + +(defconst bytes/eb (math/exp 2 60) + "Number of bytes in an exabyte.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun bytes/classify (x) + "Return unit that closest fits byte count, X." + (prelude/assert (number/whole? 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"))))) + (string/format "%d%s" + (round x (tuple/first base-and-unit)) + (tuple/second base-and-unit)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (prelude/assert + (equal "1000B" (bytes/to-string 1000))) + (prelude/assert + (equal "2KB" (bytes/to-string (* 2 bytes/kb)))) + (prelude/assert + (equal "17MB" (bytes/to-string (* 17 bytes/mb)))) + (prelude/assert + (equal "419GB" (bytes/to-string (* 419 bytes/gb)))) + (prelude/assert + (equal "999TB" (bytes/to-string (* 999 bytes/tb)))) + (prelude/assert + (equal "2PB" (bytes/to-string (* 2 bytes/pb))))) + +(provide 'bytes) +;;; bytes.el ends here diff --git a/configs/shared/.emacs.d/wpc/cache.el b/configs/shared/.emacs.d/wpc/cache.el new file mode 100644 index 000000000000..7b7e1aa2a37f --- /dev/null +++ b/configs/shared/.emacs.d/wpc/cache.el @@ -0,0 +1,80 @@ +;;; cache.el --- Caching things -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; An immutable cache data structure. +;; +;; This is like a sideways stack, that you can pull values out from and re-push +;; to the top. It'd be like a stack supporting push, pop, pull. +;; +;; This isn't a key-value data-structure like you might expect from a +;; traditional cache. The name is subject to change, but the underlying idea of +;; a cache remains the same. +;; +;; Think about prescient.el, which uses essentially an LRU cache integrated into +;; counsel to help create a "clairovoyant", self-organizing list. +;; +;; Use-cases: +;; - Keeps an cache of workspaces sorted as MRU with an LRU eviction strategy. + +;;; Code: + +(require 'prelude) +(require 'struct) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct cache xs) + +;; TODO: Prefer another KBD for yasnippet form completion than company-mode's +;; current KBD. + +(defun cache/from-list (xs) + "Turn list, XS, into a cache." + (make-cache :xs xs)) + +(defun cache/contains? (x xs) + "Return t if X in XS." + (->> xs + cache-xs + (list/contains? x))) + +(defun cache/touch (x xs) + "Ensure value X in cache, XS, is front of the list. +If X isn't in XS (using `equal'), insert it at the front." + (struct/update + cache + xs + (>> (list/reject (lambda (y) (equal x y))) + (list/cons x)) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (let ((cache (cache/from-list '("chicken" "nugget")))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; contains?/2 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (prelude/refute + (cache/contains? "turkey" cache)) + (prelude/assert + (cache/contains? "chicken" cache)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; touch/2 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (prelude/assert + (equal + (cache/touch "nugget" cache) + (cache/from-list '("nugget" "chicken")))) + (prelude/assert + (equal + (cache/touch "spicy" cache) + (cache/from-list '("spicy" "chicken" "nugget")))))) + +(provide 'cache) +;;; cache.el ends here diff --git a/configs/shared/.emacs.d/wpc/chrome.el b/configs/shared/.emacs.d/wpc/chrome.el new file mode 100644 index 000000000000..a27c9dd3967f --- /dev/null +++ b/configs/shared/.emacs.d/wpc/chrome.el @@ -0,0 +1,78 @@ +;;; chrome.el --- Helpers for Google Chrome -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Some helper functions for working with Google Chrome. + +;;; Code: + +(require 'macros) +(require 'alist) +(require 'list) + +(defvar chrome/install-kbds? t + "If t, install keybinding.") + +;; TODO: Consider modelling this as a rose-tree that can nest itself +;; arbitrarily. +;; TODO: Consider exporting existing chrome bookmarks. +(defconst chrome/label->url + '(("Google" . "www.google.com") + ("Hacker News" . "news.ycombinator.com") + ("Gmail" . "www.gmail.com") + ("WhatsApp" . "web.whatsapp.com") + ("Google Chat" . "chat/") + ("Google Calendar" . "calendar/") + ("Teknql" . "teknql.slack.com/messages") + ("Twitter" . "twitter.com")) + "Mapping labels to urls for my bookmarks.") + +(defconst chrome/splash-pages + '("Google Calendar" + "Gmail" + "Google Chat" + "WhatsApp" + "Teknql") + "The pages that should open when I open Chrome.") + +;; TODO: Add defensive check to start chrome if it isn't already open. + +;; TODO: Support option to create new session even if one already exists. + +(defun chrome/open-splash-pages () + "Opens Chrome with my preferred splash pages." + (interactive) + (->> chrome/splash-pages + (-map (lambda (x) (alist/get x chrome/label->url))) + chrome/open-urls)) + +;; TODO: Support optional kwargs. +(cl-defun chrome/open-url (url &keys new-window?) + "Opens `URL' in google-chrome. +Will open without toolbars if APP-MODE? is t." + (shell-command (s-concat + "google-chrome " + (if new-window? "--new-window " "") + url))) + +(defun chrome/open-urls (urls) + "Open multiple `URLS' in chrome." + (chrome/open-url + (list/join " " urls))) + +(defun chrome/browse () + "Display a counsel window for browsing URLs." + (interactive) + (ivy-read + "URL: " + chrome/label->url + :action (lambda (entry) + (chrome/open-url (cdr entry))))) + +(when chrome/install-kbds? + (evil-leader/set-key + "cb" #'chrome/browse + "cs" #'chrome/open-splash-pages)) + +(provide 'chrome) +;;; chrome.el ends here diff --git a/configs/shared/.emacs.d/wpc/clipboard.el b/configs/shared/.emacs.d/wpc/clipboard.el new file mode 100644 index 000000000000..975e06c5064f --- /dev/null +++ b/configs/shared/.emacs.d/wpc/clipboard.el @@ -0,0 +1,74 @@ +;;; clipboard.el --- Working with X11's pasteboard -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Simple functions for copying and pasting. +;; +;; Integrate with bburns/clipmon so that System Clipboard can integrate with +;; Emacs's kill-ring. +;; +;; Wish list: +;; - Create an Emacs integration with github.com/cdown/clipmenud. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'bytes) + +;; autoinsert feature feels unappealing at first attempt. +(use-package clipmon + :config + ;; If this is too large, it could be set machine-dependently, so use + ;; `clipboard/print-clipboard-size' to help troubleshoot this if it becomes + ;; problematic. + (setq kill-ring-max 500) + (add-to-list 'after-init-hook #'clipmon-mode-start) + (clipmon-mode 1)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar clipboard/install-kbds? t + "When t, install keybindings.") + +(defun clipboard/copy (x) + "Copy string, X, to X11's clipboard." + (kill-new x) + (message "Copied!")) + +(defun clipboard/paste () + "Paste contents of X11 clipboard." + (yank) + (message "Pasted!")) + +(defun clipboard/print-clipboard-size () + "Message the size (in Bytes) of `kill-ring'." + (interactive) + (->> (clipmon-kill-ring-total) + bytes/to-string + message)) + +(defun clipboard/ivy-select () + "Use counsel to copy the selected entry to the system clipboard. +NOTE: A function, `counsel-yank-pop', exists that does something similar. + However instead of copying the entry to the system clipboard, it inserts it + where the current point is." + (interactive) + (ivy-read "kill-ring: " (counsel--yank-pop-kills) + :require-match t + :action #'clipboard/copy)) + +;; TODO: Support ivy-actions to insert into an Emacs buffer when an Emacs buffer +;; was the last active buffer. However, if an X window is the last buffer, +;; maybe use xdotool to insert the selected entry. This would be a bit of a +;; DWIM command. +(when clipboard/install-kbds? + (exwm-input-set-key + (kbd "C-M-v") #'clipboard/ivy-select)) + +(provide 'clipboard) +;;; clipboard.el ends here diff --git a/configs/shared/.emacs.d/wpc/colorscheme.el b/configs/shared/.emacs.d/wpc/colorscheme.el new file mode 100644 index 000000000000..349d6f8e7e05 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/colorscheme.el @@ -0,0 +1,91 @@ +;;; colorscheme.el --- Syntax highlight and friends -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; +;; TODO: Clarify this. +;; Since I have my own definition of "theme", which couples wallpaper, font, +;; with Emacs's traditional notion of the word "theme", I'm choosing to use +;; "colorscheme" to refer to *just* the notion of syntax highlight etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cycle) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom colorscheme/install-kbds? t + "If non-nil, enable the keybindings.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom colorscheme/whitelist + (cycle/from-list + (custom-available-themes)) + "The whitelist of colorschemes through which to cycle.") + +(defun colorscheme/current () + "Return the currently enabled colorscheme." + (cycle/current colorscheme/whitelist)) + +(defun colorscheme/disable-all () + "Disable all currently enabled colorschemes." + (interactive) + (->> custom-enabled-themes + (list/map #'disable-theme))) + +(defun colorscheme/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) + (prelude/set-line-number-color "#da5468")) + +(defun colorscheme/whitelist-set (colorscheme) + "Focus the COLORSCHEME in the `colorscheme/whitelist' cycle." + (cycle/focus (lambda (x) (equal x colorscheme)) colorscheme/whitelist) + (colorscheme/set (colorscheme/current))) + +(defun colorscheme/ivy-select () + "Load a colorscheme using ivy." + (interactive) + (let ((theme (ivy-read "Theme: " (cycle/to-list colorscheme/whitelist)))) + (colorscheme/disable-all) + (colorscheme/set (intern theme)))) + +(cl-defun colorscheme/cycle (&key forward?) + "Cycle next if `FORWARD?' is non-nil. +Cycle prev otherwise." + (disable-theme (cycle/current colorscheme/whitelist)) + (let ((theme (if forward? + (cycle/next colorscheme/whitelist) + (cycle/prev colorscheme/whitelist)))) + (colorscheme/load theme) + (message (s-concat "Active theme: " (symbol/to-string theme))))) + +(defun colorscheme/next () + "Disable the currently active theme and load the next theme." + (interactive) + (colorscheme/cycle :forward? t)) + +(defun colorscheme/prev () + "Disable the currently active theme and load the previous theme." + (interactive) + (colorscheme/cycle :forward? nil)) + +;; Keybindings +;; TODO: Prefer minor mode definition with a custom keymap. +(when colorscheme/install-kbds? + (evil-leader/set-key + "Ft" #'colorscheme/next + "Pt" #'colorscheme/prev)) + +(provide 'colorscheme) +;;; colorscheme.el ends here diff --git a/configs/shared/.emacs.d/wpc/constants.el b/configs/shared/.emacs.d/wpc/constants.el new file mode 100644 index 000000000000..96f5a54cf4c5 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/constants.el @@ -0,0 +1,28 @@ +;;; constants.el --- Constants for organizing my Emacs -*- lexical-binding: t -*- +;; Authpr: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This file contains constants that are shared across my configuration. + +;;; Code: + +;; TODO: Consider merging `ui.el' and `misc.el' because those are the only +;; current consumers of these constants, and I'm unsure if the indirection that +;; globally defined constants introduces is worth it. + +(defconst constants/current-project "~/Dropbox/ide/src" + "Variable holding the directory for my currently active project.") + +(defconst constants/mouse-kbds + '([mouse-1] [down-mouse-1] [drag-mouse-1] [double-mouse-1] [triple-mouse-1] + [mouse-2] [down-mouse-2] [drag-mouse-2] [double-mouse-2] [triple-mouse-2] + [mouse-3] [down-mouse-3] [drag-mouse-3] [double-mouse-3] [triple-mouse-3] + [mouse-4] [down-mouse-4] [drag-mouse-4] [double-mouse-4] [triple-mouse-4] + [mouse-5] [down-mouse-5] [drag-mouse-5] [double-mouse-5] [triple-mouse-5]) + "All of the mouse-related keybindings that Emacs recognizes.") + +(defconst constants/fill-column 80 + "Variable used to set the defaults for wrapping, highlighting, etc.") + +(provide 'constants) +;;; constants.el ends here diff --git a/configs/shared/.emacs.d/wpc/cycle.el b/configs/shared/.emacs.d/wpc/cycle.el new file mode 100644 index 000000000000..762488887ddf --- /dev/null +++ b/configs/shared/.emacs.d/wpc/cycle.el @@ -0,0 +1,105 @@ +;;; cycle.el --- Simple module for working with cycles. -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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: + +(require 'struct) +(require 'math) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish list +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Provide immutable variant. +;; - TODO: Replace mutable consumption with immutable variant. +;; - TODO: Replace indexing with (math/mod current cycle). + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; `current-index' tracks the current index +;; `xs' is the original list +(cl-defstruct cycle current-index xs) + +(defun cycle/new () + "Create an empty cycle." + (make-cycle :current-index 0 + :xs '())) + +(defun cycle/from-list (xs) + "Create a cycle from a list of `XS'." + (make-cycle :current-index 0 + :xs xs)) + +(defun cycle/to-list (xs) + "Return the list representation of a cycle, XS." + (cycle-xs xs)) + +(defun 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 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))) + +(defun cycle/prev (cycle) + "Return the previous value in `CYCLE' and update `current-index'." + (let* ((current-index (cycle-current-index cycle)) + (next-index (next-index<- 0 (cycle/count cycle) current-index))) + (setf (cycle-current-index cycle) next-index) + (nth next-index (cycle-xs cycle)))) + +(defun cycle/next (cycle) + "Return the next value in `CYCLE' and update `current-index'." + (let* ((current-index (cycle-current-index cycle)) + (next-index (next-index-> 0 (cycle/count cycle) current-index))) + (setf (cycle-current-index cycle) next-index) + (nth next-index (cycle-xs cycle)))) + +(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 cycle) + "Jump to the I index of CYCLE." + (setf (cycle-current-index cycle) + (math/mod i (cycle/count cycle))) + cycle) + +(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/contains? (x xs) + "Return t if cycle, XS, has member X." + (->> xs + cycle-xs + (list/contains? x))) + +(provide 'cycle) +;;; cycle.el ends here diff --git a/configs/shared/.emacs.d/wpc/device.el b/configs/shared/.emacs.d/wpc/device.el new file mode 100644 index 000000000000..4d79214c378a --- /dev/null +++ b/configs/shared/.emacs.d/wpc/device.el @@ -0,0 +1,38 @@ +;;; device.el --- Physical device information -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Functions for querying device information. + +;;; Code: + +(require 'dash) +(require 'alist) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst device/hostname->device + '(("wpcarro2" . work-laptop) + ("wpcarro.lon.corp.google.com" . work-desktop)) + "Mapping hostname to a device symbol.") + +;; TODO: Should I generate these predicates? + +(defun device/classify () + "Return the device symbol for the current host or nil if not supported." + (alist/get system-name device/hostname->device)) + +(defun device/work-laptop? () + "Return t if current device is work laptop." + (equal 'work-laptop + (device/classify))) + +(defun device/work-desktop? () + "Return t if current device is work desktop." + (equal 'work-desktop + (device/classify))) + +(provide 'device) +;;; device.el ends here diff --git a/configs/shared/.emacs.d/wpc/display.el b/configs/shared/.emacs.d/wpc/display.el new file mode 100644 index 000000000000..716f3e5f5742 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/display.el @@ -0,0 +1,47 @@ +;;; display.el --- Working with single or multiple displays -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Mostly wrappers around xrandr. +;; +;; TODO: Look into autorandr to see if it could be useful. +;; +;; Troubleshooting: +;; The following commands help me when I (infrequently) interact with xrandr. +;; - xrandr --listmonitors +;; - xrandr --query + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider if this logic should be conditioned by `device/work-laptop?'. +(defconst display/primary "eDP-1" + "The xrandr identifier for my primary screen (on work laptop).") + +(defconst display/4k "HDMI-1" + "The xrandr identifer for my 4K monitor.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun display/enable-4k () + "Attempt to connect to my 4K monitor." + (interactive) + (shell-command + (string/format "xrandr --output %s --dpi 144 --auto --right-of %s" + display/4k + display/primary))) + +(defun display/disable-4k () + "Disconnect from the 4K monitor." + (interactive) + (shell-command + (string/format "xrandr --output %s --off" + display/4k))) + +(provide 'display) +;;; display.el ends here diff --git a/configs/shared/.emacs.d/wpc/do.el b/configs/shared/.emacs.d/wpc/do.el new file mode 100644 index 000000000000..7dc2b260fdcd --- /dev/null +++ b/configs/shared/.emacs.d/wpc/do.el @@ -0,0 +1,54 @@ +;;; do.el --- Small assertion library for Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Assertion library inspired by Elixir's core testing library. +;; +;; The goal here is to create this module without relying on other non-core +;; Elisp libraries. I will attempt to do this as long as I'm not sacrificing +;; the readability of this code nor the ease at which it can be written. +;; +;; A note on testing: +;; Another goal with this library is to blur the line between testing code and +;; runtime code. Developers should ideally be using `do/assert' and `do/refute' +;; in their library code. Because of this, I'm avoiding referring +;; to the notion of testing in the names of these functions. +;; +;; Hypothesis: +;; The lower the friction is for writing tests, the more likely people will +;; write tests. + +;; TODO: Support better error messages, which might include information about +;; line numbers in source code where the assertion failed. + +;; TODO: Consider offering the ability to have some of these functions compile +;; to nothing at runtime if developers want to use them while developing without +;; incurring the costs at runtime. + +;; TODO: Consider using this module instead of prelude.el. Right now, I'm +;; having troubling preferring one to the other. The benefit of this module is +;; that it's independent of prelude, but that might also be a downside, since +;; the messaging that asserting should be a critical part of any core library +;; like prelude. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro do/assert (x) + "Errors unless X is t. +These are strict assertions and purposely do not rely on truthiness." + (let ((as-string (format "%s" x))) + `(unless (equal t ,x) + (error (concat "Assertion failed: " ,as-string))))) + +(defmacro do/refute (x) + "Errors unless X is nil." + (let ((as-string (format "%s" x))) + `(unless (eq nil ,x) + (error (concat "Refutation failed: " ,as-string))))) + +(provide 'do) +;;; do.el ends here diff --git a/configs/shared/.emacs.d/wpc/dotfiles.el b/configs/shared/.emacs.d/wpc/dotfiles.el new file mode 100644 index 000000000000..3ee99196bde9 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/dotfiles.el @@ -0,0 +1,45 @@ +;;; dotfiles.el --- Elisp to make dotfile management -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Quickly edit commonly used files. + +;;; Code: + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst dotfiles/install-kbds? t + "When t, install the keybindings.") + +(defconst dotfiles/whitelist + '(("compton" . "~/.config/compton.conf") + ("dotfiles" . "~/Dropbox/dotfiles/") + ("functions" . "~/functions.zsh") + ("aliases" . "~/aliases.zsh") + ("variables" . "~/variables.zsh") + ("Xresources" . "~/.Xresources.shared") + ("tmux" . "~/.tmux.conf") + ("i3" . "~/.config/i3/config") ;; TODO: Remove this one day. + ("zshrc" . "~/.zshrc") + ("configuration.nix" . "~/Dropbox/programming/nixify/configuration.nix") + ("init.el" . "~/.emacs.d/init.el") + ("init.vim" . "~/.config/nvim/init.vim")) + "Dotfiles that I commonly edit.") + +(defun dotfiles/edit () + "Select a dotfile from ivy and edit it in an Emacs buffer." + (interactive) + (ivy-read + "Dotfile: " + dotfiles/whitelist + :action (>> cdr find-file))) + +(when dotfiles/install-kbds? + (evil-leader/set-key "J" #'dotfiles/edit)) + +(provide 'dotfiles) +;;; dotfiles.el ends here diff --git a/configs/shared/.emacs.d/wpc/dotted.el b/configs/shared/.emacs.d/wpc/dotted.el new file mode 100644 index 000000000000..90ef39f92e7e --- /dev/null +++ b/configs/shared/.emacs.d/wpc/dotted.el @@ -0,0 +1,49 @@ +;;; dotted.el --- Working with dotted pairs in Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Part of my primitives library extensions in Elisp. Contrast my primitives +;; with the wrapper extensions that I provide, which expose immutable variants +;; of data structures like an list, alist, tuple, as well as quasi-typeclasses +;; like sequence, etc. + +;;; Code: + +(require 'prelude) +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun dotted/new (&optional a b) + "Create a new dotted pair (i.e. cons cell)." + (cons a b)) + +(defun dotted/instance? (x) + "Return t if X is a dotted pair." + (let ((b (cdr x))) + (and b (atom b)))) + +(defun dotted/first (x) + "Return the first element of X." + (car x)) + +(defun dotted/second (x) + "Return the second element of X." + (cdr x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (prelude/assert + (equal '(fname . "Bob") (dotted/new 'fname "Bob"))) + (prelude/assert + (dotted/instance? '(one . two))) + (prelude/refute + (dotted/instance? '(1 2 3)))) + +(provide 'dotted) +;;; dotted.el ends here diff --git a/configs/shared/.emacs.d/wpc/entr.el b/configs/shared/.emacs.d/wpc/entr.el new file mode 100644 index 000000000000..ac2a5812c328 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/entr.el @@ -0,0 +1,115 @@ +;;; entr.el --- Working with terminals and entr -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Help make watch commands easier. +;; +;; This should be entirely temporary because in reality we should be able to use +;; Emacs's buffer watching abilities to run commands. +;; TODO: Explore Emacs integration that obviates `entr`. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'buffer) +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support a generic file-watcher for commonly used languages. +(defconst entr/major-mode->save-handler + '((python-mode . entr/handle-python3)) + "Mapping of language to the `after-save-hook' function it should register.") + +(defun entr/shell-command-to-buffer (cmd name) + "Run CMD in a shell and output to the buffer NAME. +The buffer is a find-or-create operation. +The buffer is erased between runs with `erase-buffer'." + (let ((b (buffer/find-or-create name))) + (with-current-buffer b (erase-buffer)) + (shell-command cmd b) + (buffer/show b))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Python +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: This should be a top-level function. +(defconst entr/handle-python3 + (lambda () + (entr/shell-command-to-buffer + (format "python3 %s" (buffer-file-name)) + "*python3*")) + "Function that is registered as the `after-save-hook' for python3.") + +(defun entr/register-python3 () + "Register a buffer-local `after-save-hook' for calling python3 with filename." + (interactive) + (add-hook 'after-save-hook entr/handle-python3 nil t)) + +(defun entr/deregister-python3 () + "Remove the buffer-local `after-save-hook' for python3." + (interactive) + (remove-hook 'after-save-hook entr/handle-python3 t)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Protobuf +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun entr/format-protobuf () + "Formats a protobuf buffer." + (call-interactively #'clang-format)) + +;; TODO: Run this automatically with .proto file extensions. Do this after +;; verifying that `clang-format' complies with Google's style guide. +(defun entr/register-protobuf () + "Register a buffer-local `before-save-hook' for formatting protobuf buffers." + (interactive) + (add-hook + 'before-save-hook + #'entr/format-protobuf + nil + t)) + +;; TODO: Is there an interactive way to remove hooks in Emacs? +(defun entr/deregister-protobuf () + "Remove the buffer-local `before-save-hook' for protobuf." + (interactive) + (remove-hook + 'before-save-hook + #'entr/format-protobuf + t)) + +;; TODO: Support this. Currently the `intern' call is the problem. +;; (defun entr/ivy-remove-hook (hook) +;; "Use Counsel to remove a handler from HOOK." +;; (interactive) +;; (ivy-read +;; "Remove hook: " +;; (intern (prelude/prompt "Hook name: ")) +;; :action (lambda (x) (message x)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun entr/command (command) + "Create a terminal instance with entr running COMMAND. +COMMAND is a function that is called with the current filename." + ;; Algorithm: + ;; - Get buffer's filename. + ;; - Open terminator running: `echo entr <filename> | entr <command>`. + (interactive) + (with-current-buffer (current-buffer) + (let ((filename (buffer-file-name))) + (prelude/inspect + (format "echo %s | entr %s" filename (funcall command filename)))))) + +(provide 'entr) +;;; entr.el ends here diff --git a/configs/shared/.emacs.d/wpc/enum.el b/configs/shared/.emacs.d/wpc/enum.el new file mode 100644 index 000000000000..078e7972099c --- /dev/null +++ b/configs/shared/.emacs.d/wpc/enum.el @@ -0,0 +1,98 @@ +;;; enum.el --- Enumerable protocol for Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Heavily influenced by Elixir. + +;; I will not be implement every function in the Enum library, since I don't +;; need every function. Some of the streaming functionality may prove difficult +;; to write in Elisp. We shall see. + +;; TODO: Implement the following functions: +;; - all?/2 +;; - any?/2 +;; - at/3 +;; - chunk_by/2 +;; - chunk_every/{2,3,4} +;; - chunk_while/4 +;; - concat/1 +;; - concat/2 +;; - count/{1,2} +;; - dedup/1 # prefer calling this function dedupe +;; - dedup_by/2 # same as above +;; - drop/2 +;; - drop_every/2 +;; - drop_while/2 +;; - each/2 +;; - empty?/1 +;; - fetch/2 +;; - fetch!/2 +;; - filter/2 +;; - find/3 +;; - find_index/2 +;; - find_value/3 +;; - flat_map/2 +;; - flat_map_reduce/3 +;; - group_by/3 +;; - intersperse/2 +;; - into/{2,3} +;; - join/2 +;; - map/2 +;; - map_every/3 +;; - map_join/3 +;; - map_reduce/3 +;; - max/2 +;; - max_by/3 +;; - member?/2 # consider calling this contains? +;; - min/2 +;; - min_by/2 +;; - min_max/2 # This is a great function because of O(n) time. +;; - min_max_by/3 +;; - random/1 # Consider just sample with num=1 +;; - reduce/{2,3} +;; - reduce_while/3 +;; - reject/2 +;; - reverse/{1,2} +;; - reverse_slice/3 +;; - scan/{2,3} +;; - shuffle/1 +;; - slice/{2,3} +;; - sort/{1,2} +;; - sort/2 +;; - sort_by/3 +;; - split/2 +;; - split_while/2 +;; - split_with/2 +;; - sum/1 +;; - take/2 +;; - take_every/2 +;; - take_random/2 # prefer calling this function sample +;; - take_while/2 +;; - to_list/1 +;; - uniq/1 # prefer calling this unique +;; - uniq_by/2 # prefer calling this unique-by +;; - unzip/1 +;; - with_index/2 +;; - zip/{1,2} + +;; TODO: Consider how to handle dispatching by type. + +;; TODO: Which types should be supported herein? +;; - linked-lists +;; - associative-lists +;; - cycles + +;; Warning: This module is a total work-in-progress, and it's quite possible +;; that I may never even finish it. + +;;; Code: + +(defun enum/count (xs) + "Return the number of elements in `XS'." + (cond + ((alist/instance? xs) (alist/count xs)) + ((list/instance? xs) (list/length xs))) + ) + +(provide 'enum) +;;; enum.el ends here diff --git a/configs/shared/.emacs.d/wpc/fonts.el b/configs/shared/.emacs.d/wpc/fonts.el new file mode 100644 index 000000000000..fca544a4c35e --- /dev/null +++ b/configs/shared/.emacs.d/wpc/fonts.el @@ -0,0 +1,148 @@ +;;; fonts.el --- Font preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Control my font preferences with ELisp. + +;;; Code: + +;; TODO: `defcustom' font-size. +;; TODO: `defcustom' fonts. +;; TODO: Remove wpc/ namespace. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'cycle) +(require 'device) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Troubleshoot why "8" appears so large on my desktop. + +;; TODO: Consider having a different font size when I'm using my 4K monitor. + +(defconst fonts/size + (pcase (device/classify) + ('work-laptop "12") + ('work-desktop "8")) + "My preferred default font-size, which is device specific.") + +(defconst fonts/keybindings? t + "Install the keybindings when non-nil.") + +(defconst fonts/size-step 10 + "The amount (%) by which to increase or decrease a font.") + +(defconst fonts/hacker-news-recommendations + '("APL385 Unicode" + "Go Mono" + "Sudo" + "Monoid" + "Input Mono Medium" ;; NOTE: Also "Input Mono Thin" is nice. + ) + "List of fonts optimized for programming I found in a HN article.") + +(defconst fonts/whitelist + (cycle/from-list + (list/concat + fonts/hacker-news-recommendations + '("Monospace" + "Operator Mono Light" + "Courier" + "Andale Mono" + "Source Code Pro" + "Terminus"))) + "This is a list of my preferred fonts.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: fonts and fonts/whitelist make it difficult to name functions like +;; fonts/set as a generic Emacs function vs choosing a font from the whitelist. + +(cl-defun fonts/cycle (&key forward?) + "Cycle forwards when `FORWARD?' non-nil." + (let ((font (if forward? + (cycle/next fonts/whitelist) + (cycle/prev fonts/whitelist)))) + (message (s-concat "Active font: " font)) + (fonts/set (fonts/fontify font)))) + +(defun fonts/next () + "Quickly cycle through preferred fonts." + (interactive) + (fonts/cycle :forward? t)) + +(defun fonts/prev () + "Quickly cycle through preferred fonts." + (interactive) + (fonts/cycle :forward? nil)) + +(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/whitelist-set (font) + "Focuses the FONT in the `fonts/whitelist' cycle. +The size of the font is determined by `fonts/size'." + (prelude/assert (cycle/contains? font fonts/whitelist)) + (cycle/focus (lambda (x) (equal x font)) fonts/whitelist) + (fonts/set (fonts/current) fonts/size)) + +(defun fonts/ivy-select () + "Select a font from an ivy prompt." + (interactive) + (fonts/whitelist-set + (ivy-read "Font: " (cycle/to-list fonts/whitelist)))) + +(defun fonts/print-current () + "Message the currently enabled font." + (interactive) + (message + (string/format "[fonts] Current font: \"%s\"" + (fonts/current)))) + +(defun fonts/current () + "Return the currently enabled font." + (cycle/current fonts/whitelist)) + +(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/whitelist-set (fonts/current))) + +(when fonts/keybindings? + (progn + (evil-leader/set-key + "Ff" #'fonts/next + "Pf" #'fonts/prev) + (general-define-key "s-9" #'fonts/ivy-select) + (general-define-key "s-0" #'fonts/reset-size) + (general-define-key "s-j" #'fonts/decrease-size) + (general-define-key "s-k" #'fonts/increase-size))) + +(provide 'fonts) +;;; fonts.el ends here diff --git a/configs/shared/.emacs.d/wpc/fs.el b/configs/shared/.emacs.d/wpc/fs.el new file mode 100644 index 000000000000..adc331d176ce --- /dev/null +++ b/configs/shared/.emacs.d/wpc/fs.el @@ -0,0 +1,59 @@ +;;; fs.el --- Make working with the filesystem easier -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Ergonomic alternatives for working with the filesystem. + +;; Dependencies +(require 'f) + +;;; Code: + +(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)) + +(f-dirname "/tmp/a/b/file.txt") + +(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?))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support `refute' function / macro. +(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))))) + +(provide 'fs) +;;; fs.el ends here diff --git a/configs/shared/.emacs.d/wpc/functions.el b/configs/shared/.emacs.d/wpc/functions.el new file mode 100644 index 000000000000..33e256be59bb --- /dev/null +++ b/configs/shared/.emacs.d/wpc/functions.el @@ -0,0 +1,133 @@ +;; functions.el --- Helper functions for my Emacs development -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This file hopefully contains friendly APIs that making ELisp development more +;; enjoyable. + +;; TODO: Break these out into separate modules. + +;;; Code: +(defun wpc/evil-window-vsplit-right () + (interactive) + (evil-window-vsplit) + (windmove-right)) + +(defun wpc/evil-window-split-down () + (interactive) + (evil-window-split) + (windmove-down)) + +(defun wpc/reindent-defun-and-align-clojure-map () + (interactive) + (call-interactively #'paredit-reindent-defun) + (call-interactively #'clojure-align)) + +(defun wpc/find-file () + "Prefer project-based file-finding if inside of project; otherwise gracefully fallback." + (interactive) + (with-current-buffer (current-buffer) + (if (projectile-project-p) + (call-interactively #'counsel-projectile-find-file) + (call-interactively #'find-file)))) + +(defun wpc/find-or-create-js-test () + (->> buffer-file-name + (s-chop-suffix ".js") + (s-append ".test.js") + (find-file))) + +(defun wpc/find-or-create-js-module () + (->> buffer-file-name + (s-chop-suffix ".test.js") + (s-append ".js") + (find-file))) + +(defun wpc/find-or-create-js-store () + (->> buffer-file-name + (s-replace "index.js" "store.js") + (find-file))) + +(defun wpc/find-or-create-js-component () + (->> buffer-file-name + (s-replace "store.js" "index.js") + (find-file))) + +(defun wpc/toggle-between-js-test-and-module () + "Toggle between a Javascript test or module." + (interactive) + (if (s-ends-with? ".test.js" buffer-file-name) + (wpc/find-or-create-js-module) + (if (s-ends-with? ".js" buffer-file-name) + (wpc/find-or-create-js-test) + (message "Not in a Javascript file. Exiting...")))) + +(defun wpc/toggle-between-js-component-and-store () + "Toggle between a React component and its Redux store." + (interactive) + (if (s-ends-with? "index.js" buffer-file-name) + (wpc/find-or-create-js-store) + (if (or (s-ends-with? "store.js" buffer-file-name) + (s-ends-with? "store.test.js" buffer-file-name)) + (wpc/find-or-create-js-component) + (message "Not in a React/Redux file. Exiting...")))) + +(defun wpc/read-file-as-string (filename) + (with-temp-buffer + (insert-file-contents filename) + (s-trim (buffer-string)))) + +(defun wpc/create-snippet () + "Creates a window split and then opens the Yasnippet editor." + (interactive) + (evil-window-vsplit) + (call-interactively #'yas-new-snippet)) + +(defun wpc/find-file-split (filename) + "Creates a window split and then edits `filename'." + (interactive) + (evil-window-vsplit) + (find-file filename)) + +(defun wpc/jump-to-parent-file () + "Jumps to a React store or component's parent file. Useful for store or index file." + (interactive) + (-> buffer-file-name + f-dirname + (f-join "..") + (f-join (f-filename buffer-file-name)) + find-file)) + +(defun wpc/add-earmuffs (x) + "Returns X surrounded by asterisks." + (format "*%s*" x)) + +(defun wpc/put-file-name-on-clipboard () + "Put the current file name on the clipboard" + (interactive) + (let ((filename (if (equal major-mode 'dired-mode) + default-directory + (buffer-file-name)))) + (when filename + (with-temp-buffer + (insert filename) + (clipboard-kill-region (point-min) (point-max))) + (message filename)))) + +(s-replace "/" "x" "a/b/c") + +(defun wpc/evil-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 buffer-dirname () + "Return the directory name of the current buffer as a string." + (->> buffer-file-name + f-dirname + f-filename)) + +(provide 'functions) +;;; functions.el ends here diff --git a/configs/shared/.emacs.d/wpc/google-stuff.el b/configs/shared/.emacs.d/wpc/google-stuff.el new file mode 100644 index 000000000000..4f4fe635a362 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/google-stuff.el @@ -0,0 +1,183 @@ +;;; google-stuff.el --- Working with Google infrastructure from Emacs -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Some of this is just encoding my learnings as notes in Elisp format. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'ivy-helpers) +(require 'evil-leader) +(require 'maybe) +(require 'device) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Ensure a consistent and deliberate usage of `defvar', `defconst', and +;; `defcustom' across all Elisp modules. +(defcustom google-stuff/install-kbds? t + "When t, install the keybindings defined herein.") + +;; Definitions as explained by the highly knowledgeable Matthew (i.e. mjo@) +(defconst google-stuff/definitions + '( + ;; command-line tools + ("gcert" . "Requests a CorpSSH certificate.") + ("glogin" . "SSO (i.e. Single Sign-On) cookie.") + ("googlenetworkaccess" . "Device certificate that gives users a certificate +to access to the Google corp network.") + ("prodaccess" . "Sets up a LOAS session on Goobuntu.") + ;; general wtfs + ("LOAS" . "Distributed authentication service used by jobs in production and +corp to authenticate each other. It's more efficient than SSL and works with +Stubby.") + )) + +;; TODO: Straighten out fig, citc, google3 and have modules for each. + +;; TODO: Move this to a google3.el module. +(defconst google-stuff/root + "/google/src/cloud/wpcarro" + "The root directory to access google3.") + +;; TODO: Find a fast way to generate this. +(defconst google-stuff/citc-clients + '("auto-consult" + "ac-skeleton") + "A list of my active CitC clients.") + + +;; TODO: Can this be sourced from ~/.g4d? +(defconst google-stuff/citc-aliases + '(("escalations" . "/google3/corp/gtech/pto/tda/beacons_extension") + ("spewall_fe" . "/google3/alkali/apps/speakeasydashboard") + ("spewall_be" . "/google3/java/com/google/alkali/applications/speakeasydashboard") + ("spewall_protos" . "/google3/google/internal/alkali/applications/speakeasydashboard") + ("spewall_tests" . "/google3/javatests/com/google/alkali/applications/speakeasydashboard") + ("gti" . "/google3/experimental/engedu/gti/projects/week20190422/mtv/Team10") + ("authwf" . "/google3/customer_support/automation/workflow") + ("redwood" . "/google3/customer_support/kms/redwood/ui") + ("wf-fe" . "/google3/customer_support/kms/redwood/ui/client/components/item/workflow_editor") + ("ac" . "/google3/google/internal/alkali/applications/casesconsultservice") + ("ac-server" . "/google3/java/com/google/alkali/applications/casesconsultservice/server/") + ("ac-server (tests)" . "/google3/javatests/com/google/alkali/applications/casesconsultservice/server/")) + "Mapping of a label to commonly visited locations in Google3.") + + +(defvar google-stuff/active-citc-client nil + "Currently active CitC client.") + +(defun google-stuff/depot-prefix () + "Return the current prefix for //depot/google3." + (string/format "/google/src/cloud/wpcarro/%s/google3/" + google-stuff/active-citc-client)) + +(defun google-stuff/cs-url () + "Return the code-search URL for the current buffer and line number." + (string/format "cs.corp.google.com/piper///depot/google3/%s?l=%s" + (s-chop-prefix + (google-stuff/depot-prefix) + (buffer-file-name)) + (line-number-at-pos))) + +(defun google-stuff/copy-cs-url () + "Copy the current file and line-position to the system clipboard." + (interactive) + (clipboard/copy (google-stuff/cs-url))) + +(defun google-stuff/open-buffer-in-cs () + "Open the current file in Google's CodeSearch." + (interactive) + (shell-command + (string/format "google-chrome '%s'" + (google-stuff/cs-url) + (line-number-at-pos)))) + +;; TODO: As a naming convention, should I prefer ivy or select? Or counsel? +(defun google-stuff/select-citc-client () + "Set `google-stuff/active-citc-client' with counsel." + (interactive) + (setq google-stuff/active-citc-client + (ivy-read "CitC Client: " google-stuff/citc-clients))) + +(defun google-stuff/remote-buffer? () + "Return t if buffer is one accessed via Tramp." + (with-current-buffer (current-buffer) + (if (file-remote-p default-directory) + t + nil))) + +(defun google-stuff/jump-to-citc-alias () + "Use `find-file' to open an alias registered in `google-stuff/citc-aliases'. +When on a corporate laptop, remote connections are made using Tramp." + (interactive) + (when (maybe/nil? google-stuff/active-citc-client) + (call-interactively #'google-stuff/select-citc-client)) + (ivy-helpers/kv + "Jump to CitC Alias: " + google-stuff/citc-aliases + (lambda (k v) + (->> v + ;; If I don't remove the leading slash, `f-join' won't return a valid + ;; path. + (s-chop-prefix "/") + (f-join google-stuff/root + google-stuff/active-citc-client) + (s-prepend (if (device/work-laptop?) "/ssh:wpcarro@desktop:" "")) + find-file)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Stuff I learned reading go/emacs +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Fig +;; TODO: Make sure there are Evil-compatible KBDs for `fig-status'. +;; (require 'google-fig) + +;; This allows `find-file' handle "//depot/google3/devtools/editors/". +(require 'p4-files) +(p4-enable-file-name-handler) + +;; Blaze Support +;; - `google3-compile-current-file' is an excellent command! + +;; google3-eglot (uses CiderLSP) +;; TODO: Make sure the functionality is supported as advertised: +;; - auto-completion +;; - eglot-help-at-point for documentation. +;; - goto-definition +;; - `eglot-code-actions' fixits +;; - `eglot-rename' refactoring +(require 'google3-eglot) +(google3-eglot-setup) + +;; CodeSearch +;; TODO: Debug why this depends on google-piper and why I don't have that on my +;; desktop. +;; (require 'ivy-cs) + +;; Auto completion +;; TODO: Is the part of or separate from google3-eglot? Because google3-eglot +;; advertises auto-completion support. +(require 'google3-build-capf) +(google3-build-capf-enable-completions) +(add-to-list 'company-backends #'company-capf) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when google-stuff/install-kbds? + (evil-leader/set-key "Gs" #'fig-status) + (evil-leader/set-key "Cs" #'google-stuff/open-buffer-in-cs) + (evil-leader/set-key "jc" #'google-stuff/jump-to-citc-alias)) + +(provide 'google-stuff) +;;; google-stuff.el ends here diff --git a/configs/shared/.emacs.d/wpc/google-tooling.el b/configs/shared/.emacs.d/wpc/google-tooling.el new file mode 100644 index 000000000000..661df41d6c63 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/google-tooling.el @@ -0,0 +1,53 @@ +;;; google-tooling.el --- Better access to Google tooling -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: + +;; First, I must opine. Feel free to skip this section. In general, it seems +;; that the average programmer's workflow suffer from what economists call +;; "inelastic demand". This means that any increase in price for something +;; sends the demand plummeting. Another way of phrasing this is that +;; programmers are "price sensitive" when it comes to adopting new workflows. +;; +;; For us, any deviation from our "established" workflow feels costly. This +;; makes sense to me because programming is already taxing, so any additional +;; taxation can feel unbearable. Until programming changes dramatically, and we +;; relieve our dependence on files and text for modeling complex applications, +;; this inelastic demand will remain the status quo. Therefore, it's critical +;; to reduce the price of experimenting with new tools such that new, superior +;; habits may form. In this vain, this module attempts to surface "luxury +;; tools" (i.e. dependency pruners, code linters, code formatters) via Emacs to +;; reduce the price of experimenting with them. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;; TODO: Figure out whether or not to integrate with google-emacs. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst google-tooling/tools + '(("Depana" . "depana") + ("Build cleaner" . "build_cleaner") + ("Java formatter" . "google-java-format") + ("Proto formatter" . "clang-format")) + "Mapping of names of tools to the names of the executables that run them.") + +(use-package protobuf-mode + :config + (macros/support-file-extension "pb" protobuf-mode)) + +;; TODO: Call blaze build, use Counsel to select an action, run that action on +;; the nearest BUILD file. + +;; TODO: Call build-cleaner on the nearest BUILD file. + +(provide 'google-tooling) +;;; google-tooling.el ends here diff --git a/configs/shared/.emacs.d/wpc/graph.el b/configs/shared/.emacs.d/wpc/graph.el new file mode 100644 index 000000000000..c68c308590f4 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/graph.el @@ -0,0 +1,91 @@ +;;; graph.el --- Working with in-memory graphs -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; +;; Remember that there are optimal three ways to model a graph: +;; 1. Edge List +;; 2. Vertex Table (a.k.a. Neighbors Table) +;; 3. Adjacency Matrix +;; +;; I may call these "Edges", "Neighbors", "Adjacencies" to avoid verbose naming. +;; For now, I'm avoiding dealing with Adjacency Matrices as I don't have an +;; immediate use-case for them. This is subject to change. +;; +;; There are also hybrid representations of graphs that combine the three +;; aforementioned models. I believe Erlang's digraph module models graphs in +;; Erlang Term Storage (i.e. ETS) this way. +;; TODO: Verify this claim. +;; +;; Graphs can be weighted or unweighted. They can also be directed or +;; undirected. +;; TODO: Create a table explaining all graph variants. +;; +;; TODO: Figure out the relationship of this module and tree.el, which should in +;; principle overlap. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; For now, I'll support storing *either* neighbors or edges in the graph struct +;; as long as both aren't set, since that introduces consistency issues. I may +;; want to handle that use-case in the future, but not now. +(cl-defstruct graph neighbors edges) + +;; TODO: How do you find the starting point for a topo sort? +(defun graph/sort (xs) + "Return a topological sort of XS.") + +(defun graph/from-edges (xs) + "Create a graph struct from the Edge List, XS. +The user must pass in a valid Edge List since asserting on the shape of XS might + be expensive." + (make-graph :edges xs)) + +(defun graph/from-neighbors (xs) + "Create a graph struct from a Neighbors Table, XS. +The user must pass in a valid Neighbors Table since asserting on the shape of + XS might be expensive." + (make-graph :neighbors xs)) + +(defun graph/instance? (xs) + "Return t if XS is a graph struct." + (graph-p xs)) + +;; TODO: Model each of the mapping functions into an isomorphism. +(defun graph/edges->neighbors (xs) + "Map Edge List, XS, into a Neighbors Table." + (prelude/assert (graph/instance? xs))) + +(defun graph/neighbors->edges (xs) + "Map Neighbors Table, XS, into an Edge List." + (prelude/assert (graph/instance? xs))) + +;; Below are three different models of the same unweighted, directed graph. + +(defvar graph/edges + '((a . b) (a . c) (a . e) + (b . c) (b . d) + (c . e) + (d . f) + (e . d) (e . f))) + +(defvar graph/neighbors + ((a b c e) + (b c d) + (c e) + (d f) + (e d g) + (f))) + +(provide 'graph) +;;; graph.el ends here diff --git a/configs/shared/.emacs.d/wpc/imdb.el b/configs/shared/.emacs.d/wpc/imdb.el new file mode 100644 index 000000000000..2969da140935 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/imdb.el @@ -0,0 +1,128 @@ +;;; imdb.el --- Internet Movie Database -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Some Elisp to help me pick movies more quickly. + +;;; Code: + +(require 'f) +(require 'macros) +(require 'pcre2el) +(require 'random) +(require 'maybe) + +;; TODO: How do you support types herein? +(cl-defstruct movie + name + year + director + watched?) + +;; TODO: Support famous directors like: +;; - Wes Anderson +;; - Woody Allen +;; - Tarantino +;; - Coen Brothers +;; - Alfonso Cauron +;; - Alejandro Gonzรกlez Iรฑรกrritu +;; - Alfred Hitchcock +;; - Stanley Kubrick + +;; TODO: Dump this into SQL. + +(defconst imdb/kubrick-films + (->> '((:watched? nil :year 1951 :name "Flying Padre") + (:watched? nil :year 1953 :name "Fear and Desire") + (:watched? nil :year 1953 :name "The Seafarers") + (:watched? nil :year 1955 :name "Killer's Kiss") + (:watched? nil :year 1956 :name "The Killing") + (:watched? nil :year 1957 :name "Paths of Glory") + (:watched? nil :year 1960 :name "Spartacus") + (:watched? nil :year 1962 :name "Lolita") + (:watched? nil :year 1964 :name "Dr. Strangelove") + (:watched? nil :year 1968 :name "2001: A Space Odyssey") + (:watched? t :year 1971 :name "A Clockwork Orange") + (:watched? nil :year 1975 :name "Barry Lyndon") + (:watched? nil :year 1980 :name "The Shining") + (:watched? t :year 1987 :name "Full Metal Jacket") + (:watched? nil :year 1999 :name "Eyes Wide Shut")) + (list/map (lambda (x) + (make-movie :name (plist-get :name x) + :year (plist-get :year x) + :director "Stanley Kubrick" + :watched? (plist-get :watched? x)))))) + +(defconst imdb/non-top-250 + (->> '("Doctor Zhivago" + ) + (list/map #'imdb/new-movie))) + +(defun imdb/new-movie (name) + "Create a new movie with NAME." + (make-movie :name name + :year nil + :director nil + :watched? nil)) + +(defun imdb/org->movie (line) + "Parse an org LINE into a movie struct." + (let ((match (s-match + (pcre-to-elisp "^\*\*\s(TODO|DONE)\s(.+)$") + line))) + (if (maybe/some? match) + (make-movie :name (list/get 2 match) + :year nil + :director nil + :watched? (equal "DONE" (list/get 1 match))) + (error (s-concat "Parsing error: " line))))) + +;; TODO: Store these in a database or define them herein. +(defun imdb/org->movies () + "Parse entire IMDB org file into movie structs." + (->> "~/Dropbox/org/imdb_top_250.org" + f-read + (s-split "\n") + (-drop 1) + (list/filter (>> (s-starts-with? "** "))) + (list/map #'imdb/org->movie))) + +(defun imdb/watched? (movie) + "Return t if MOVIE has been watched." + (movie-watched? movie)) + +(defconst imdb/movies (imdb/org->movies) + "Structs of all watched movies.") + +(defun imdb/unwatched () + "Return list of unwatched movies." + (->> imdb/movies + (list/filter (lambda (x) (not (imdb/watched? x)))))) + +(defun imdb/name (movie) + "Return name of MOVIE." + (movie-name movie)) + + +(defun imdb/suggest () + "Randomly select movie from unwatched list." + (->> (imdb/unwatched) + (random/choice) + (imdb/name))) + +(defun imdb/unwatched-list () + "Dump all unwatched movies into a list." + (f-write-text (->> (imdb/unwatched) + (list/map #'imdb/name) + (s-join "\n")) + 'utf-8 + "/tmp/unwatched.txt")) + +(macros/comment + (imdb/org->movies) + (imdb/unwatched-list) + (imdb/suggest) + ) + +(provide 'imdb) +;;; imdb.el ends here diff --git a/configs/shared/.emacs.d/wpc/irc.el b/configs/shared/.emacs.d/wpc/irc.el new file mode 100644 index 000000000000..3dfaabdb397b --- /dev/null +++ b/configs/shared/.emacs.d/wpc/irc.el @@ -0,0 +1,79 @@ +;;; irc.el --- Configuration for IRC chat -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Need to decide which client I will use for IRC. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'erc) +(require 'cycle) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq erc-rename-buffers t) + +(defvar irc/channels-cycle + (cycle/from-list + '("#omg" "#london" "#panic" "#prod-team")) + "List of channels through which I can cycle.") + +;; Setting `erc-join-buffer' to 'bury prevents erc from stealing focus of the +;; current buffer when it connects to IRC servers. +(setq erc-join-buffer 'bury) + +(setq erc-autojoin-channels-alist + `(("corp.google.com" . ,(cycle/to-list irc/channels-cycle)))) + +(defcustom irc/install-kbds? t + "When t, install the keybindings defined herein.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun irc/message (x) + "Print message X in a structured way." + (message (string/format "[irc.el] %s" x))) + +(defun irc/connect-to-google () + "Connect to Google's Corp IRC using ERC." + (interactive) + (erc-ssl :server "irc.corp.google.com" + :port 6697 + :nick "wpcarro" + :full-name "William Carroll")) + +(defun irc/next-channel () + "Join the next channel in `irc/channels-cycle'." + (interactive) + (erc-join-channel + (cycle/next irc/channels-cycle)) + (irc/message + (string/format "Current IRC channel: %s" + (cycle/current irc/channels-cycle)))) + +(defun irc/prev-channel () + "Join the previous channel in `irc/channels-cycle'." + (interactive) + (erc-join-channel + (cycle/prev irc/channels-cycle)) + (irc/message + (string/format "Current IRC channel: %s" + (cycle/current irc/channels-cycle)))) + +(when irc/install-kbds? + (general-define-key + :keymaps 'erc-mode-map + "<C-tab>" #'irc/next-channel + "<C-S-iso-lefttab>" #'irc/prev-channel)) + +(provide 'irc) +;;; irc.el ends here diff --git a/configs/shared/.emacs.d/wpc/iso.el b/configs/shared/.emacs.d/wpc/iso.el new file mode 100644 index 000000000000..1691a0daaad6 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/iso.el @@ -0,0 +1,95 @@ +;;; iso.el --- Isomorphisms in Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Providing basic isomorphisms to improve code quality. + +;;; Code: + +(require 'dotted) +(require 'tuple) +(require 'symbol) +(require 'string) +(require 'list) +(require 'alist) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct iso to from x) + +(defconst iso/whitelist + '((dotted . tuple) + (symbol . string)) + "Alist representing supported isomorphisms.") + +(defconst iso/vertices + (list/concat (alist/keys iso/whitelist) + (alist/values iso/whitelist)) + "List of all of the vertices in the iso graph.") + +(defun iso/classify (x) + "Return type of X." + (cond + ((string/instance? x) 'string) + ((symbol/instance? x) 'symbol) + ((dotted/instance? x) 'dotted) + ((tuple/instance? x) 'tuple))) + +(cl-defun iso/exists? (to from) + "Return t if an isomorphism of TO to FROM exists." + ;; TODO: All of this can be improved modelling this with a graph. + (cond + ;; to -> from + ((list/contains? to (alist/keys iso/whitelist)) + (list/contains? from (alist/values iso/whitelist))) + ;; from -> to + ((list/contains? from (alist/keys iso/whitelist)) + (list/contains? to (alist/values iso/whitelist))) + ;; doesn't exist + (t nil))) + +(progn + (prelude/assert + (iso/exists? 'symbol 'string)) + (prelude/assert + (iso/exists? 'dotted 'tuple)) + (prelude/refute + (iso/exists? 'dotted 'symbol)) + (prelude/refute + (iso/exists? 'symbol 'list))) + +;; TODO: Model this as a graph. +(defconst iso/morphisms + '((string . + '(symbol #') + )) + (list (:from 'string :to 'symbol :fn #'intern) + (:from 'symbol :to 'string :fn #'symbol-name) + ) + "") + +(defun iso/to (f x) + "Apply F to X's to." + (->> x + iso-to)) + +(->> (iso/new "william" :to 'symbol) + (iso/as-to #'symbol-name) + ) + +(cl-defun iso/new (x &keys to) + "Create a new isomorphism of X mapping to TO." + (let ((from (iso/classify x))) + (prelude/assert (iso/exists? to from)) + (make-iso :from from + :to to + :x x))) + +(macros/comment + (iso/new "william" :to 'symbol) + (iso/new '(one . two) :to 'tuple)) + +(provide 'iso) +;;; iso.el ends here diff --git a/configs/shared/.emacs.d/wpc/ivy-helpers.el b/configs/shared/.emacs.d/wpc/ivy-helpers.el new file mode 100644 index 000000000000..c71a907a20c1 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/ivy-helpers.el @@ -0,0 +1,31 @@ +;;; ivy-helpers.el --- More interfaces to ivy -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hopefully to improve my workflows. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'alist) +(require 'tuple) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun ivy-helpers/kv (prompt kv f) + "PROMPT users with the keys in KV and return its corresponding value. Calls F +with the key and value from KV." + (ivy-read + prompt + kv + :require-match t + :action (lambda (entry) + (funcall f (car entry) (cdr entry))))) + +;;; Code: +(provide 'ivy-helpers) +;;; ivy-helpers.el ends here diff --git a/configs/shared/.emacs.d/wpc/kaomoji.el b/configs/shared/.emacs.d/wpc/kaomoji.el new file mode 100644 index 000000000000..d6d509c14667 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/kaomoji.el @@ -0,0 +1,45 @@ +;;; kaomoji.el --- Supporting kaomoji usage -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Simple keyboards like this make life a bit better. + +;;; Code: + +(defvar kaomoji/install-kbds? + nil + "Set to t if you'd like the keybindings to be installed.") + +(defconst kaomoji/symbols '(("Joy" . "(โโฟโ)") + ("Love" . "(แฆหโฃหแฆ)") + ("Sympathy" . "ใฝ(~_~(ใป_ใป )ใ") + ("Dissatisfaction" . "(๏ผ๏น๏ผ)") + ("Anger" . "ใฝ(โต๏นยด)ใ") + ("Hugging" . "(ใฅ๏ฟฃ ยณ๏ฟฃ)ใฅ") + ("Hiding" . "โฌโดโฌโดโค( อกยฐ อสโโฌโดโฌโด") + ("Sleeping" . "(๏ผ_๏ผ) zzZ") + ("Embarrassed" . "(ร๏นร)") + ("Shrug" . "ใฝ(ใผ_ใผ )ใ")) + "Alist of human-readable emotions to the kaomoji.") + +;; TODO: Consider supporting a hydra for these. + +(defun kaomoji/select () + "Interactively select a kaomoji and copy it to the clipboard." + (interactive) + (ivy-read + "Select a kaomoji: " + kaomoji/symbols + :action (lambda (entry) + (kill-new (cdr entry)) + (alert "Copied to clipboard!")))) + +;; TODO: Define Hydra for all custom keyboards. +;; TODO: Define a better keybinding in a different keymap. +(when kaomoji/install-kbds? + (general-define-key + :keymaps 'global + "M-k" #'kaomoji/select)) + +(provide 'kaomoji) +;;; kaomoji.el ends here diff --git a/configs/shared/.emacs.d/wpc/kbd.el b/configs/shared/.emacs.d/wpc/kbd.el new file mode 100644 index 000000000000..49b346bc6ea8 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/kbd.el @@ -0,0 +1,90 @@ +;;; kbd.el --- Elisp keybinding -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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 'alist) +(require 'set) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst kbd/install-kbds? t + "When t, install keybindings defined herein.") + +(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 + (= (alist/count kbd/prefixes) + (->> kbd/prefixes + alist/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 (alist/has-key? f kbd/prefixes)) + (string/format + "%s-%s" + (alist/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)))) + +;; (when kbd/install-kbds? +;; (general-define-key +;; :prefix "<SPC>" +;; "hr" #'kbd/print-keycode)) + +(provide 'kbd) +;;; kbd.el ends here diff --git a/configs/shared/.emacs.d/wpc/keyboard.el b/configs/shared/.emacs.d/wpc/keyboard.el new file mode 100644 index 000000000000..f9d13a8e41eb --- /dev/null +++ b/configs/shared/.emacs.d/wpc/keyboard.el @@ -0,0 +1,147 @@ +;;; keyboard.el --- Managing keyboard preferences with Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Setting key repeat and other values. +;; +;; Be wary of suspiciously round numbers. Especially those divisible by ten! + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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.") + +(defcustom keyboard/install-kbds? nil + "When t, install keybindings.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun keyboard/message (x) + "Message X in a structured way." + (message (string/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." + (shell-command + (string/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) + (shell-command "xmodmap -e 'remove Lock = Caps_Lock'") + (shell-command "xmodmap -e 'keysym Caps_Lock = Escape")) + +(defun keyboard/inc-repeat-rate () + "Increment `keyboard/repeat-rate'." + (interactive) + (setq keyboard/repeat-rate (number/inc keyboard/repeat-rate)) + (keyboard/set-key-repeat :rate keyboard/repeat-rate) + (keyboard/message + (string/format "Rate: %s" keyboard/repeat-rate))) + +(defun keyboard/dec-repeat-rate () + "Decrement `keyboard/repeat-rate'." + (interactive) + (setq keyboard/repeat-rate (number/dec keyboard/repeat-rate)) + (keyboard/set-key-repeat :rate keyboard/repeat-rate) + (keyboard/message + (string/format "Rate: %s" keyboard/repeat-rate))) + +(defun keyboard/inc-repeat-delay () + "Increment `keyboard/repeat-delay'." + (interactive) + (setq keyboard/repeat-delay (number/inc keyboard/repeat-delay)) + (keyboard/set-key-repeat :delay keyboard/repeat-delay) + (keyboard/message + (string/format "Delay: %s" keyboard/repeat-delay))) + +(defun keyboard/dec-repeat-delay () + "Decrement `keyboard/repeat-delay'." + (interactive) + (setq keyboard/repeat-delay (number/dec keyboard/repeat-delay)) + (keyboard/set-key-repeat :delay keyboard/repeat-delay) + (keyboard/message + (string/format "Delay: %s" keyboard/repeat-delay))) + +(defun keyboard/print-key-repeat () + "Print the currently set values for key repeat." + (interactive) + (keyboard/message + (string/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)) + +;; TODO: Define minor-mode for this. +(when keyboard/install-kbds? + (general-unbind 'motion "C-i" "C-y") + (general-define-key + ;; TODO: Choose better KBDs for these that don't interfere with useful evil + ;; ones. + ;; Use C-y when you accidentally send the key-repeat too high or too low to + ;; be meaningful. + "C-y" #'keyboard/reset-key-repeat + "C-i" #'keyboard/inc-repeat-rate + "C-u" #'keyboard/dec-repeat-rate + "C-S-i" #'keyboard/inc-repeat-delay + "C-S-u" #'keyboard/dec-repeat-delay)) + +(provide 'keyboard) +;;; keyboard.el ends here diff --git a/configs/shared/.emacs.d/wpc/keymap.el b/configs/shared/.emacs.d/wpc/keymap.el new file mode 100644 index 000000000000..87d340fcdbf1 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/keymap.el @@ -0,0 +1,25 @@ +;;; keymap.el --- Working with Elisp keymaps -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Very much a work-in-progress. + +;;; Code: + +(require 'macros) +(require 'symbol) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun keymap/pretty-print (x) + "Pretty prints `X'." + ;; TODO: Work-in-progress + (s-concat "\\{" (symbol/to-string x) "}")) + +(macros/comment + (keymap/pretty-print lispyville-mode-map)) + +(provide 'keymap) +;;; keymap.el ends here diff --git a/configs/shared/.emacs.d/wpc/laptop-battery.el b/configs/shared/.emacs.d/wpc/laptop-battery.el new file mode 100644 index 000000000000..3ec03553d2ca --- /dev/null +++ b/configs/shared/.emacs.d/wpc/laptop-battery.el @@ -0,0 +1,60 @@ +;;; laptop-battery.el --- Display laptop battery information -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Some wrappers to obtain battery information. +;; +;; To troubleshoot battery consumpton look into the CLI `powertop`. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Roadmap +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support functions that work with reporting battery stats. +;; TODO: low-battery-reporting-threshold +;; TODO: charged-battery-reporting-threshold +;; TODO: Format modeline battery information. +;; TODO: Provide better time information in the modeline. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'battery) +(require 'alist) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun laptop-battery/available? () + "Return t if battery information is available." + (maybe/some? battery-status-function)) + +(defun laptop-battery/percentage () + "Return the current percentage of the battery." + (->> battery-status-function + funcall + (alist/get 112))) + +(defun laptop-battery/print-percentage () + "Return the current percentage of the battery." + (interactive) + (->> (laptop-battery/percentage) + message)) + +(defun laptop-battery/display () + "Display laptop battery percentage in the modeline." + (interactive) + (display-battery-mode 1)) + +(defun laptop-battery/hide () + "Hide laptop battery percentage in the modeline." + (interactive) + (display-battery-mode -1)) + +(provide 'laptop-battery) +;;; laptop-battery.el ends here diff --git a/configs/shared/.emacs.d/wpc/list.el b/configs/shared/.emacs.d/wpc/list.el new file mode 100644 index 000000000000..bc89c1326473 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/list.el @@ -0,0 +1,197 @@ +;;; list.el --- Functions for working with lists. -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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 that I cannot tolerate: +;; - `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 heavily +;; influenced by Elixir's standard library. +;; +;; Elixir's List library: +;; - ++/2 +;; - --/2 +;; - hd/1 +;; - tl/1 +;; - in/2 +;; - length/1 +;; +;; Similar libraries: +;; - dash.el: Functional library that mimmicks Clojure. It is consumed herein. +;; - list-utils.el: Utility library that covers things that dash.el may not +;; cover. +;; stream.el: Elisp implementation of streams, "implemented as delayed +;; evaluation of cons cells." + +;; TODO: Consider naming this file linked-list.el. + +;; TODO: Support module-like macro that auto-namespaces functions. + +;; TODO: Consider wrapping most data structures like linked-lists, +;; associative-lists, etc in a `cl-defstruct', so that the dispatching by type +;; can be nominal instead of duck-typing. I'm not sure if this is a good idea +;; or not. If I do this, I should provide isomorphisms to map between idiomatic +;; ways of working with Elisp data structures and my wrapped variants. + +;; TODO: Are function aliases/synonyms even a good idea? Or do they just +;; bloat the API unnecessarily? + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst list/tests? t + "When t, run the test suite.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun list/new () + "Return a new, empty list." + '()) + +(defun list/concat (&rest lists) + "Joins `LISTS' into on list." + (apply #'-concat lists)) + +(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) + (string/concat 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/head (xs) + "Return the head of `XS'." + (car xs)) + +;; TODO: Learn how to write proper function aliases. +(defun list/first (xs) + "Alias for `list/head' for `XS'." + (list/head 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)) + +;; map, filter, reduce + +;; TODO: Create function adapters like swap. +;; (defun adapter/swap (f) +;; "Return a new function that wraps `F' and swaps the arguments." +;; (lambda (a b) +;; (funcall f b a))) + +;; TODO: Make this function work. +(defun list/reduce (acc f xs) + "Return over `XS' calling `F' on an element in `XS'and `ACC'." + (-reduce-from (lambda (acc x) (funcall f x acc)) acc xs)) + +(defun list/map (f xs) + "Call `F' on each element of `XS'." + (-map f xs)) + +(defun list/map-indexed (f xs) + "Call `F' on each element of `XS' along with its index." + (-map-indexed (lambda (i x) (funcall f x i)) xs)) + +(defun list/filter (p xs) + "Return a subset of XS where predicate P returned t." + (list/reverse + (list/reduce + '() + (lambda (x acc) + (if (funcall p x) + (list/cons x acc) + acc)) + 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." + (-find p 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'." + (-all? p xs)) + +(defun list/any? (p xs) + "Return t if any `XS' pass the predicate, `P'." + (-any? p xs)) + +(defun list/contains? (x xs) + "Return t if X is in XS using `equal'." + (-contains? xs x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when list/tests? + (prelude/assert + (= 0 + (list/length '()))) + (prelude/assert + (= 5 + (list/length '(1 2 3 4 5)))) + (prelude/assert + (= 16 + (list/reduce 1 (lambda (x acc) (+ x acc)) '(1 2 3 4 5)))) + (prelude/assert + (equal '(2 4 6 8 10) + (list/map (lambda (x) (* x 2)) '(1 2 3 4 5))))) + +(provide 'list) +;;; list.el ends here diff --git a/configs/shared/.emacs.d/wpc/list.nix b/configs/shared/.emacs.d/wpc/list.nix new file mode 100644 index 000000000000..e664ba6fd4a1 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/list.nix @@ -0,0 +1,8 @@ +{ pkgs ? import (builtins.fetchTarball + "https://github.com/tazjin/depot/archive/master.tar.gz") {} }: + +pkgs.writeElispBin { + name = "list"; + deps = epkgs: [ epkgs.dash ./prelude.nix ]; + src = ./list.el; +} diff --git a/configs/shared/.emacs.d/wpc/macros.el b/configs/shared/.emacs.d/wpc/macros.el new file mode 100644 index 000000000000..a41cb8db9a52 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/macros.el @@ -0,0 +1,95 @@ +;;; macros.el --- Helpful variables for making my ELisp life more enjoyable -*- lexical-binding: t -*- +;; Authpr: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This file contains helpful variables that I use in my ELisp development. + +;; TODO: Consider a macro solution for mimmicking OCaml's auto resolution of +;; dependencies using `load-path' and friends. + +;;; Code: + +(require 'f) +(require 'string) +(require 'symbol) + +;; TODO: Support `xi' lambda shorthand macro. + +(defmacro 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 disable (mode) + "Helper for disabling `MODE'. +Useful in `add-hook' calls." + `#'(lambda nil (,mode -1))) + +(defmacro add-hooks (modes callback) + "Add multiple `MODES' for the `CALLBACK'. +Usage: (add-hooks '(one-mode-hook 'two-mode-hook) #'fn)" + `(dolist (mode ,modes) + (add-hook mode ,callback))) + +(defmacro add-hook-before-save (mode f) + "Register a hook, `F', for a mode, `MODE' more conveniently. +Usage: (add-hook-before-save 'reason-mode-hook #'refmt-before-save)" + `(add-hook ,mode + (lambda () + (add-hook 'before-save-hook ,f)))) + +;; TODO: Debug. +(defmacro macros/ilambda (&rest body) + "Surrounds `BODY' with an interactive lambda function." + `(lambda () + (interactive) + ,@body)) + +;; TODO: Privatize? +(defun namespace () + "Return the namespace for a function based on the filename." + (->> (buffer-file-name) + f-filename + f-base)) + +(defmacro macros/comment (&rest _) + "Empty comment s-expresion where `BODY' is ignored." + `nil) + +;; NOTE: Not prepending the "macros" to this macro, since brevity is its goal. +(defmacro >> (&rest forms) + "Compose a new, point-free function by composing FORMS together." + (let ((sym (gensym))) + `(lambda (,sym) + (->> ,sym ,@forms)))) + +;; TOOD: Support this. +(cl-defmacro macros/test + (&key function + test + args + expect + equality) + (let* ((namespace (namespace)) + (test-name (string/->symbol + (s-concat namespace + "/" + "test" + "/" + (s-chop-prefix + (s-concat namespace "/") + (symbol/to-string function)))))) + `(ert-deftest ,test-name () + ,test + (should (,equality (apply ,function ,args) + ,expect))))) + +(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 (string/format "\\.%s\\'" ext))) + `(add-to-list 'auto-mode-alist '(,extension . ,mode)))) + +(provide 'macros) +;;; macros.el ends here diff --git a/configs/shared/.emacs.d/wpc/math.el b/configs/shared/.emacs.d/wpc/math.el new file mode 100644 index 000000000000..55ddc427c70b --- /dev/null +++ b/configs/shared/.emacs.d/wpc/math.el @@ -0,0 +1,59 @@ +;;; math.el --- Math stuffs -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Containing some useful mathematical functions. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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) + ;; TODO: Assert two of three are set. + (cond + ((maybe/some? base power result) + (error "All three arguments should not be set")) + ((maybe/some? power result) + (message "power and result")) + ((maybe/some? base result) + (log result base)) + ((maybe/some? 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/configs/shared/.emacs.d/wpc/maybe.el b/configs/shared/.emacs.d/wpc/maybe.el new file mode 100644 index 000000000000..0973b1ed65f7 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/maybe.el @@ -0,0 +1,102 @@ +;;; maybe.el --- Library for dealing with nil values -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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. +;; What else in Elisp is an alias in this way? +;; Examples: +;; TODO: Provide examples of other nil types in Elisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar maybe/test? t + "When t, run the test suite defined herein.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun maybe/nil? (x) + "Return t if X is nil." + (eq nil x)) + +(defun maybe/some? (x) + "Return t when X is non-nil." + (not (maybe/nil? x))) + +(defun maybe/nils? (&rest xs) + "Return t if all XS are nil." + (list/all? #'maybe/nil? xs)) + +(defun maybe/somes? (&rest xs) + "Return t if all XS are non-nil." + (list/all? #'maybe/some? xs)) + +(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)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when maybe/test? + ;; nil? + (prelude/assert (maybe/nil? nil)) + (prelude/refute (maybe/nil? t)) + ;; some? + (prelude/assert (maybe/some? 10)) + (prelude/refute (maybe/some? nil)) + ;; nils? + (prelude/assert (maybe/nils? nil nil nil nil)) + (prelude/refute (maybe/nils? nil t nil t)) + ;; somes? + (prelude/assert (maybe/somes? t 10 '(1 2 3) "some")) + (prelude/refute (maybe/somes? t nil '(1 2 3) "some")) + ;; default + (prelude/assert + (and (= 0 (maybe/default 5 0)) + (= 5 (maybe/default 5 nil)))) + ;; map + (prelude/assert + (and (= 2 (maybe/map #'1+ 1)) + (eq nil (maybe/map #'1+ nil))))) + +(provide 'maybe) +;;; maybe.el ends here diff --git a/configs/shared/.emacs.d/wpc/me-seconds.el b/configs/shared/.emacs.d/wpc/me-seconds.el new file mode 100644 index 000000000000..f03e5d07d790 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/me-seconds.el @@ -0,0 +1,245 @@ +;;; me-seconds.el --- How valuable is my time? -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Inspired by Google's concept of SWE-seconds, I decided to try and compute how +;; value my personal time is. +;; +;; This library should integrate with another library that handles currency +;; conversions using locally cached data for historial values and network +;; requests for current values. +;; +;; Context sensitivity: +;; Many of the values herein are based on my values that are a function of the +;; year, my current salary, my current company holiday policy, and my current +;; country holiday policy. As such, many of these constants need to be updated +;; whenever changes occur in order for these functions to be useful. +;; +;; Units of time: +;; - seconds +;; - minutes +;; - hours +;; - days +;; - weeks +;; - months +;; - years +;; +;; Wish list: +;; - I should create a money.el struct to work with herein. This module would +;; expose basic algebra for working with money structs, which would be handy. +;; - I should create a time.el struct for working with hours in the day. I'd +;; like to be able to do (+ 9:15 17:45) cleanly. +;; +;; Terminology: +;; SWE hours give an order of magnitude approximation to the cost of resources +;; in dollars per hour at 2115 hours per year. +;; - SWE hour (SWEh) +;; - SWE year (SWEy) +;; - SWE nominal +;; - SWE opportunity +;; +;; Other isomorphisms include: +;; - Borg GCU +;; - Borg RAM +;; - Tape (library) +;; - Tape (vault) +;; - Spindles (low latency) +;; - Spindles (throughput) +;; - Spindles (throughput) +;; - Tape (throughput) +;; - SWE (nominal) +;; - SWE (opportunity) + + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun me-seconds/salary (amt) + "Return the yearly rate of AMT of money in GBP. +f :: Integer -> Rate" + (make-rate :money (make-money :whole amt :fractional 0 :currency 'GBP) + :unit 'year)) + +(defconst me-seconds/salary (me-seconds/salary 80000) + "My salary in GBP.") + +;; TODO: Consider changing these into units of time. +(defconst me-seconds/months-per-year 12 + "Number of months in a year.") + +(defconst me-seconds/days-per-year 365 + "Number of days in a year.") + +(defconst me-seconds/hours-per-year (* 24 me-seconds/days-per-year) + "Number of hours in a year.") + +(defconst me-seconds/minutes-per-year (* 60 me-seconds/hours-per-year) + "Number of minutes in a year.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Vacation +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst me-seconds/bank-holidays-per-year 8 + "Number of bank holidays in the UK each year.") + +(defconst me-seconds/pto-days-vacation-per-year 25 + "Number of days of paid-time-off I receive each year in the UK.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Sleeping +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst me-seconds/sleeping-hours-per-day 8 + "An approximation of the number of hours I sleep each night on average.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Waking +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst me-seconds/waking-hours-per-day + (- 24 me-seconds/sleeping-hours-per-night) + "An approximation of the number of hours I sleep each night on average.") + +;; TODO: Adjust this for vacation time. +(defconst me-seconds/waking-hours-per-year + (* me-seconds/waking-hours-per-day me-seconds/days-per-year) + "The number of hours that I work each year.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Working +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst me-seconds/working-hours-per-day + (- 17 9) + "An approximation of the number of hours I work each weekday on average. +Note that this differs from the assumed SWE hours per day calculation, which + assumes 9 working hours. See the discussion about this of go/rules-of-thumb.") + +(defconst me-seconds/working-hours-per-year 2115 + "This number is borrowed from go/rules-of-thumb.") + +;; Keep in mind that the following classifications of time: +;; - 9:00-17:00 M-F. Is this more expensive than time sleeping? +;; - Weekend +;; - Weekday +;; - Working hours +;; - Waking hours +;; - Sleeping hours +;; - Vacation hours +;; +;; TODO: Consider tax implications (i.e. after-tax amounts and pre-tax amounts). +;; +;; Should these all be treated the same since they all pull from the same pot of +;; time? Or perhaps there are multiples involved? Much to think about. How does +;; Google handle this? + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Supported currencies: +;; - GBP +;; NOTE: Amount is an integer. +(cl-defstruct money whole fractional currency) +(cl-defstruct rate money unit) + +;; TODO: Add to money.el. +(defun money/to-string (x) + "Return the string representation of X. +f :: Money -> String" + (let ((currency (money-currency x)) + (whole (int-to-string (money-whole x))) + (fract (int-to-string (money-fractional x)))) + (pcase currency + ('GBP (string/concat "ยฃ" whole "." fract)) + ('USD (string/concat "$" whole "." fract)) + (_ (error (string/concat + "Currency: \"" + (symbol-name currency) + "\" not supported")))))) + +(macros/comment + (money/to-string + (make-money :whole 100 :fractional 99 :currency 'GBP))) + +;; TODO: Add to rate.el. +(defun rate/to-string (x) + "Message X as a rate. +f :: Rate -> String" + (string/concat + (money/to-string (rate-money x)) + " / " + (pcase (rate-unit x) + ('second "sec") + ('minute "min") + ('hour "hr") + ('day "day") + ('week "week") + ('month "month") + ('year "year")))) + +(macros/comment + (rate/to-string + (make-rate + :money (make-money :whole 10 :fractional 10 :currency 'GBP) + :unit 'day))) + +;; TODO: Move this to math.el? +(defun ensure-float (x) + "Ensures X is treated as a float." + (+ 0.0 x)) + +;; TODO: Move these to basic time mapping module. +;; TODO: Consider making this an isomorphism. +(defun minutes/to-hours (x) + "Convert X minutes to n hours." + (/ x 60.0)) + +(defun hours/to-minutes (x) + "Convert X hours to n minutes." + (* x 60)) + +(defun days/to-minutes (x) + "Convert X days to n minutes." + (* x 24 60)) + +(defun weeks/to-minutes (x) + "Convert X weeks to n minutes." + (* x 7 24 60)) + +(defun months/to-minutes (x) + "Convert X months to n minutes. +This approximates the number of days in a month to 30." + (* x 30 24 60)) + +;; TODO: Support algebraic functions with money structs. +;; TODO: Support isomorphisms for rates to other units of time. That would +;; subsume most of this module's use. +(defun me-seconds/value-per-minute (salary) + "Computes my value per minute based on my current SALARY. +Signature: f :: Rate -> Rate +This is assuming that all of my time is equally valuable. See the above + discussion about the various classifications of my time.") + +;; TODO: See note above about isomorphisms between various rates. +(defun me-seconds/value (salary x) + "Compute the value of X minutes of my time at my current SALARY. +f :: Rate -> Integer -> Money") + +(macros/comment + (rate/to-string me-seconds/salary) + ) + +(provide 'me-seconds) +;;; me-seconds.el ends here diff --git a/configs/shared/.emacs.d/wpc/monoid.el b/configs/shared/.emacs.d/wpc/monoid.el new file mode 100644 index 000000000000..401d63c41728 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/monoid.el @@ -0,0 +1,30 @@ +;;; monoid.el --- Working with Monoids in Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; The day has finally arrived where I'm using Monoids in Elisp. +;; +;; The monoid typeclass is as follows: +;; - empty :: a +;; - concat :: (list a) -> a + +;;; Code: + +;; TODO: Consider a prelude version that works for all Elisp types. +(defun monoid/classify (xs) + "Return the type of `XS'." + (cond + ((listp xs) 'list) + ((vectorp xs) 'vector) + ((stringp xs) 'string))) + + +(defun monoid/empty (xs) + "Return the empty monoid for the type `XS'." + (pcase (monoid/classify xs) + ('list '()) + ('vector []) + ('string ""))) + +(provide 'monoid) +;;; monoid.el ends here diff --git a/configs/shared/.emacs.d/wpc/number.el b/configs/shared/.emacs.d/wpc/number.el new file mode 100644 index 000000000000..81d3c5d2b935 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/number.el @@ -0,0 +1,151 @@ +;;; number.el --- Functions for working with numbers -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; +;; Classifications of numbers: +;; - Natural: (a.k.a positive integers, counting numbers); {1, 2, 3, ... } +;; +;; - Whole: Natural Numbers, plus zero; {0, 1, 2, 3, ...} +;; +;; - Integers: Whole numbers plus all the negatives of the natural numbers; +;; {... , -2, -1, 0, 1, 2, ...} +;; +;; - Rational numbers: (a.k.a. fractions) where the top and bottom numbers are +;; integers; e.g., 1/2, 3/4, 7/2, โป4/3, 4/1. Note: The denominator cannot be +;; 0, but the numerator can be. +;; +;; - Real numbers: All numbers that can be written as a decimal. This includes +;; fractions written in decimal form e.g., 0.5, 0.75 2.35, โป0.073, 0.3333, or +;; 2.142857. It also includes all the irrational numbers such as ฯ, โ2 etc. +;; Every real number corresponds to a point on the number line. +;; +;; The functions defined herein attempt to capture the mathematical definitions +;; of numbers and their classifications as defined above. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst number/test? t + "When t, run the test suite defined herein.") + +;; TODO: What about int.el? + +;; TODO: How do we handle a number typeclass? + +(defun number/positive? (x) + "Return t if `X' is a positive number." + (> x 0)) + +(defun number/negative? (x) + "Return t if `X' is a positive number." + (< x 0)) + +;; TODO: Don't rely on this. Need to have 10.0 and 10 behave similarly. +(defun number/float? (x) + "Return t if `X' is a floating point number." + (floatp x)) + +(defun number/natural? (x) + "Return t if `X' is a natural number." + (and (number/positive? x) + (not (number/float? x)))) + +(defun number/whole? (x) + "Return t if `X' is a whole number." + (or (= 0 x) + (number/natural? x))) + +(defun number/integer? (x) + "Return t if `X' is an integer." + (or (number/whole? x) + (number/natural? (- x)))) + +;; TODO: How defensive should these guards be? Should we assert that the inputs +;; are integers before checking evenness or oddness? + +;; TODO: Look up Runar (from Unison) definition of handling zero as even or odd. + +;; TODO: How should rational numbers be handled? Lisp is supposedly famous for +;; its handling of rational numbers. +;; TODO: `calc-mode' supports rational numbers as "1:2" meaning "1/2" +;; (defun number/rational? (x)) + +;; TODO: Can or should I support real numbers? +;; (defun number/real? (x)) + +(defun number/even? (x) + "Return t if `X' is an even number." + (or (= 0 x) + (= 0 (mod x 2)))) + +(defun number/odd? (x) + "Return t if `X' is an odd number." + (not (number/even? x))) + +(defun number/dec (x) + "Subtract one from `X'. +While this function is undeniably trivial, I have unintentionally done (- 1 x) + when in fact I meant to do (- x 1) that I figure it's better for this function + to exist, and for me to train myself to reach for it and its inc counterpart." + (- x 1)) + +(defun number/inc (x) + "Add one to `X'." + (+ x 1)) + +;; TODO: Does this belong in a math module? Is math too vague? Or is number +;; too vague? +(defun number/factorial (x) + "Return factorial of `X'." + (cond + ((number/negative? x) (error "Will not take factorial of negative numbers")) + ((= 0 x) 1) + ;; NOTE: Using `series/range' introduces a circular dependency because: + ;; series -> number -> series. Conceptually, however, this should be + ;; perfectly acceptable. + (t (->> (series/range 1 x) + (list/reduce 1 #'*))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when number/test? + (prelude/assert + (number/positive? 10)) + (prelude/assert + (number/natural? 10)) + (prelude/assert + (number/whole? 10)) + (prelude/assert + (number/whole? 0)) + (prelude/assert + (number/integer? 10)) + (prelude/assert + (= 120 (number/factorial 5))) + (prelude/assert + (number/even? 6)) + (prelude/refute + (number/odd? 6)) + (prelude/refute + (number/positive? -10)) + (prelude/refute + (number/natural? 10.0)) + (prelude/refute + (number/natural? -10)) + (prelude/refute + (number/natural? -10.0))) + +(provide 'number) +;;; number.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-clojure.el b/configs/shared/.emacs.d/wpc/packages/wpc-clojure.el new file mode 100644 index 000000000000..d9262cdda8eb --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-clojure.el @@ -0,0 +1,85 @@ +;;; clojure.el --- My Clojure preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hosting my Clojure tooling preferences + +;;; Code: + +;; Helper functions + +;; (defun wpc/buffer-name-for-clojure-mode (mode) +;; (let* ((project-name (projectile-project-name)) +;; (cljs-name (concat "*cider-repl CLJS " project-name "*")) +;; (clj-name (concat "*cider-repl " project-name "*"))) +;; (cond ((eq mode 'clojurescript-mode) cljs-name) +;; ((eq mode 'clojure-mode) clj-name) +;; ((eq mode 'clojurec-mode) cljs-name)))) + +;; (defun wpc/repl-function-for-clojure-mode (mode) +;; (let ((project-name (projectile-project-name)) +;; (cljs-fn #'cider-jack-in-clojurescript) +;; (clj-fn #'cider-jack-in)) +;; (cond ((eq mode 'clojurescript-mode) cljs-fn) +;; ((eq mode 'clojure-mode) clj-fn) +;; ((eq mode 'clojurec-mode) cljs-fn)))) + +;; (defun wpc/find-or-create-clojure-or-clojurescript-repl () +;; (interactive) +;; (with-current-buffer (current-buffer) +;; (let ((buffer-name (wpc/buffer-name-for-clojure-mode major-mode)) +;; (repl-function (wpc/repl-function-for-clojure-mode major-mode))) +;; (if (get-buffer buffer-name) +;; (switch-to-buffer buffer-name) +;; (funcall repl-function))))) + +(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 + ;; "C-c 'j" #'wpc/find-or-create-clojure-or-clojurescript-repl + ) + ;; (setq cider-cljs-lein-repl + ;; "(do (require 'figwheel-sidecar.repl-api) + ;; (figwheel-sidecar.repl-api/start-figwheel!) + ;; (figwheel-sidecar.repl-api/cljs-repl))" + ;; cider-prompt-for-symbol nil) + ) + +(provide 'wpc-clojure) +;;; wpc-clojure.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-company.el b/configs/shared/.emacs.d/wpc/packages/wpc-company.el new file mode 100644 index 000000000000..1152f496c2b7 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-company.el @@ -0,0 +1,28 @@ +;;; company.el --- Autocompletion package, company, preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hosts my company mode preferences + +;;; Code: + +;; 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/configs/shared/.emacs.d/wpc/packages/wpc-dired.el b/configs/shared/.emacs.d/wpc/packages/wpc-dired.el new file mode 100644 index 000000000000..13aed975d2a5 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-dired.el @@ -0,0 +1,39 @@ +;;; dired.el --- My dired preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; File management in Emacs, if learned and configured properly, should be +;; capable to reduce my dependency on the terminal. + +;;; Code: + +;; TODO: Ensure sorting in dired is by type. + +;; TODO: Rename wpc-dired.el to file-management.el + +(progn + (require 'dired) + (setq dired-recursive-copies 'always + dired-recursive-deletes 'top + dired-dwim-target t) + (general-define-key + :keymaps 'dired-mode-map + :states 'normal + "s" nil + "q" (lambda () (interactive) (kill-buffer nil)) + "c" #'find-file + "f" #'wpc/find-file + "-" (lambda () (interactive) (find-alternate-file ".."))) + (general-add-hook 'dired-mode-hook + (list (enable dired-hide-details-mode) + #'auto-revert-mode))) + +(progn + (require 'locate) + (general-define-key + :keymaps 'locate-mode-map + :states 'normal + "o" #'dired-display-file)) + +(provide 'wpc-dired) +;;; wpc-dired.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-docker.el b/configs/shared/.emacs.d/wpc/packages/wpc-docker.el new file mode 100644 index 000000000000..270eaec6fe4c --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-docker.el @@ -0,0 +1,16 @@ +;;; docker.el --- Docker preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; My Docker preferences and configuration + +;;; Code: + +(use-package docker + :config + (setenv "DOCKER_TLS_VERIFY" "1") + (setenv "DOCKER_HOST" "tcp://10.11.12.13:2376") + (setenv "DOCKER_MACHINE_NAME" "name")) + +(provide 'wpc-docker) +;;; wpc-docker.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-elixir.el b/configs/shared/.emacs.d/wpc/packages/wpc-elixir.el new file mode 100644 index 000000000000..e64abe70fc36 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-elixir.el @@ -0,0 +1,13 @@ +;;; wpc-elixir.el --- Elixir / Erland configuration -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; My preferences for working with Elixir / Erlang projects + +;;; Code: +(use-package elixir-mode + :config + (add-hook-before-save 'elixir-mode-hook #'elixir-format)) + +(provide 'wpc-elixir) +;;; wpc-elixir.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-flycheck.el b/configs/shared/.emacs.d/wpc/packages/wpc-flycheck.el new file mode 100644 index 000000000000..d7bb834a6257 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-flycheck.el @@ -0,0 +1,14 @@ +;;; flycheck.el --- My flycheck configuration -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hosts my Flycheck preferences + +;;; Code: + +(use-package flycheck + :config + (global-flycheck-mode)) + +(provide 'wpc-flycheck) +;;; wpc-flycheck.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-haskell.el b/configs/shared/.emacs.d/wpc/packages/wpc-haskell.el new file mode 100644 index 000000000000..e8ab16e585b7 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-haskell.el @@ -0,0 +1,56 @@ +;;; haskell.el --- My Haskell preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hosts my Haskell development preferences + +;;; Code: + +;; Haskell support + +;; font-locking, glyph support, etc +(use-package haskell-mode + :config + (let ((m-symbols + '(("`mappend`" . "โ") + ("<>" . "โ")))) + (dolist (item m-symbols) (add-to-list 'haskell-font-lock-symbols-alist item))) + (setq haskell-font-lock-symbols t) + (add-hook-before-save 'haskell-mode #'haskell-align-imports)) + +;; LSP support +(use-package lsp-haskell + :after (haskell-mode) + :config + (setq lsp-haskell-process-path-hie "hie-wrapper") + (add-hook 'haskell-mode-hook #'lsp-haskell-enable) + (add-hook 'haskell-mode-hook #'flycheck-mode)) + +;; Test toggling +(defun 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 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 haskell/test<->module () + "Toggle between test and module in Haskell." + (interactive) + (if (s-contains? "/src/" buffer-file-name) + (haskell/module->test) + (haskell/test->module))) + +(provide 'wpc-haskell) +;;; wpc-haskell.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-java.el b/configs/shared/.emacs.d/wpc/packages/wpc-java.el new file mode 100644 index 000000000000..4f33ba962e5d --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-java.el @@ -0,0 +1,42 @@ +;;; wpc-java.el --- Java configuration -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; When life gets you down, and you find yourself writing Java, remember: at +;; least you're using Emacs. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) + +(prelude/assert + (prelude/executable-exists? "google-java-format")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Troubleshoot why this isn't running. +(add-hook-before-save + 'java-mode-hook + (lambda () + (call-interactively + #'google-java-format))) + +(add-hook 'java-mode-hook + (lambda () + (setq c-basic-offset 2 + tab-width 2))) + +;; TODO: Figure out whether I should use this or google-emacs. +;; (use-package lsp-java +;; :config +;; (add-hook 'java-mode-hook #'lsp)) + +(provide 'wpc-java) +;;; wpc-java.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-javascript.el b/configs/shared/.emacs.d/wpc/packages/wpc-javascript.el new file mode 100644 index 000000000000..b55e03e00c07 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-javascript.el @@ -0,0 +1,61 @@ +;; wpc-javascript.el --- My Javascript preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This module hosts my Javascript tooling preferences +;; +;; Depends +;; - yarn global add prettier + +;;; Code: + +;; Constants +(defconst wpc/js-hooks + '(js-mode-hook js2-mode-hook rjsx-mode-hook) + "All of the commonly used hooks for Javascript buffers.") + +(defconst wpc/frontend-hooks + (-insert-at 0 'css-mode-hook wpc/js-hooks) + "All of the commonly user hooks for frontend development.") + + +;; frontend indentation settings +(setq js-indent-level 2 + css-indent-offset 2) + +;; ;; javascript +;; (evil-leader/set-key-for-mode 'rjsx-mode "t" #'wpc/toggle-between-js-test-and-module) +;; (evil-leader/set-key-for-mode 'rjsx-mode "x" #'wpc/toggle-between-js-component-and-store) +;; (evil-leader/set-key-for-mode 'rjsx-mode "u" #'wpc/jump-to-parent-file) + +;; Flow for Javascript +(use-package add-node-modules-path + :config + (general-add-hook wpc/js-hooks #'add-node-modules-path)) + +(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 + :mode "\\.js\\'" + :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)) + +;; JS autoformatting +(use-package prettier-js + :after (rjsx-mode) + :config + (general-add-hook wpc/frontend-hooks #'prettier-js-mode)) + +(provide 'wpc-javascript) +;;; wpc-javascript.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-keybindings.el b/configs/shared/.emacs.d/wpc/packages/wpc-keybindings.el new file mode 100644 index 000000000000..fb9c78f0e765 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-keybindings.el @@ -0,0 +1,206 @@ +;;; keybindings.el --- My Evil preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This module hosts my Evil preferences +;; +;; Wish List: +;; - drop support for `evil-leader' library in favor of `general.el' +;; - restore support for concise (n <kbd> <function>) instead of `general-mmap' +;; - restore support for `general-unbind' + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Packages +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; 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") + +(use-package evil + :init + ;; Should remove the warning messages on init. + (setq evil-want-integration t) + ;; TODO: Troubleshoot why this binding causes the following warning: + ;; "Warning (evil-collection): `evil-want-keybinding' was set to nil but not + ;; before loading evil." + (setq evil-want-keybinding nil) + (general-evil-setup) + :config + ;; 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) + ;; TODO: Ensure all of my custom keybindings end up in a single map that is + ;; easy to enable or disable. + (general-mmap + :keymaps 'override + "RET" #'evil-goto-line + "H" #'evil-first-non-blank + "L" #'evil-end-of-line + "_" #'ranger + "-" #'dired-jump + "sl" #'wpc/evil-window-vsplit-right + "sh" #'evil-window-vsplit + "sk" #'evil-window-split + "sj" #'wpc/evil-window-split-down) + (general-nmap + :keymaps 'override + "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") + (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") + (setq evil-symbol-word-search t) + (evil-mode 1)) + +;; evil keybindings +(use-package evil-collection + :after (evil) + :config + (evil-collection-init)) + +;; expose a leader key +(use-package evil-leader + :after (evil) + :config + (global-evil-leader-mode 1) + (evil-leader/set-leader "<SPC>") + (evil-leader/set-key + "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 + "s" #'flyspell-mode + "S" #'sort-lines + "a" #'wpc-terminal/toggle + "=" #'align + "p" #'flycheck-previous-error + "f" #'wpc/find-file + "n" #'flycheck-next-error + "N" #'smerge-next + "b" #'ivy-switch-buffer + "W" #'balance-windows + "gs" #'magit-status + + "es" #'wpc/create-snippet + ;; TODO: Replace with `macros/ilambda' when that is working again. + "ev" (lambda () (interactive) (wpc/find-file-split "~/.config/nvim/init.vim")) + "ee" (lambda () (interactive) (wpc/find-file-split "~/.emacs.d/init.el")) + "ez" (lambda () (interactive) (wpc/find-file-split "~/.zshrc")) + "ea" (lambda () (interactive) (wpc/find-file-split "~/aliases.zsh")) + "ef" (lambda () (interactive) (wpc/find-file-split "~/functions.zsh")) + "el" (lambda () (interactive) (wpc/find-file-split "~/variables.zsh")) + "ex" (lambda () (interactive) (wpc/find-file-split "~/.Xresources")) + "ei" (lambda () (interactive) (wpc/find-file-split "~/.config/i3/config.shared")) + "em" (lambda () (interactive) (wpc/find-file-split "~/.tmux.conf")) + + "l" #'locate + "L" #'list-packages + "B" #'magit-blame + "w" #'save-buffer + "r" #'wpc/evil-replace-under-point + "R" #'deadgrep)) + +;; create comments easily +(use-package evil-commentary + :after (evil) + :config + (evil-commentary-mode)) + +;; evil surround +(use-package evil-surround + :after (evil) + :config + (global-evil-surround-mode 1)) + +;; I expect in insert mode: +;; C-a: beginning-of-line +;; C-e: end-of-line +;; C-b: backwards-char +;; C-f: forwards-char + +;; TODO: Move these KBD constants to kbd.el. + +(defconst wpc/up-kbds + '("C-p" "C-k" "<backtab>" "<up>") + "The keybindings that I expect to work for moving upwards in lists.") + +(defconst wpc/down-kbds + '("C-n" "C-j" "<tab>" "<down>") + "The keybindings that I expect to work for moving downwards in lists.") + +(defconst wpc/left-kbds + '("C-b" "<left>") + "The keybindings that I expect to move leftwards in insert-like modes.") + +(defconst wpc/right-kbds + '("C-f" "<right>") + "The keybindings that I expect to move rightwards in insert-like modes.") + +(defun wpc/ensure-kbds (_ignore) + "Try to ensure that my keybindings retain priority over other minor modes." + (unless (eq (caar minor-mode-map-alist) 'wpc/kbds-minor-mode) + (let ((mykbds (assq 'wpc/kbds-minor-mode minor-mode-map-alist))) + (assq-delete-all 'wpc/kbds-minor-mode minor-mode-map-alist) + (add-to-list 'minor-mode-map-alist mykbds)))) + +;; Custom minor mode that ensures that my kbds are available no matter which +;; major or minor modes are active. +(add-hook 'after-load-functions #'wpc/ensure-kbds) + +;; TODO: Prefer using general and 'override maps to implement this. +(defvar wpc/kbds + (let ((map (make-sparse-keymap))) + (bind-keys :map map + ("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)) + map) + "William Carroll's keybindings that should have the highest precedence.") + +(define-minor-mode wpc/kbds-minor-mode + "A minor mode so that my key settings override annoying major modes." + :init-value t + :lighter " wpc/kbds" + :keymap wpc/kbds) + +;; allow jk to escape +(use-package key-chord + :after (evil) + :config + (key-chord-mode 1) + (key-chord-define evil-insert-state-map "jk" 'evil-normal-state)) + +(provide 'wpc-keybindings) +;;; wpc-keybindings.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-lisp.el b/configs/shared/.emacs.d/wpc/packages/wpc-lisp.el new file mode 100644 index 000000000000..553dff1acbd9 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-lisp.el @@ -0,0 +1,114 @@ +;;; lisp.el --- Generic LISP preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;; parent (up) +;; child (down) +;; prev-sibling (left) +;; next-sibling (right) + +;;; Code: + +(defconst wpc/lisp-mode-hooks + '(lisp-mode-hook + emacs-lisp-mode-hook + clojure-mode-hook + clojurescript-mode-hook + racket-mode-hook) + "List of LISP modes.") + +(use-package racket-mode + :config + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "<SPC>" + "x" #'racket-send-definition) + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "<SPC>" + "X" #'racket-run) + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "<SPC>" + "d" #'racket-describe) + (setq racket-program "~/.nix-profile/bin/racket")) + +(use-package lispyville + :init + (defconst lispyville-key-themes + '(c-w + operators + text-objects + ;; Disabling this because I don't enjoy the way it moves around comments. + ;; atom-motions + prettify + commentary + slurp/barf-cp + wrap + additional + additional-insert + additional-wrap + escape) + "All available key-themes in Lispyville.") + :config + (general-add-hook wpc/lisp-mode-hooks #'lispyville-mode) + (lispyville-set-key-theme 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 + ;; TODO: Rebind to something that doesn't conflict with window resizing. + ;; "C-M-h" #'lispyville-drag-backward + ;; "C-M-l" #'lispyville-drag-forward + ))) + +;; deletes all bindings of f->kbd +;; binds kbd-> +;; (kbd/bind-function->key +;; :keymap 'lispyville-mode-map +;; :states 'motion +;; #'lispyville-drag-backward "H") + +;; Elisp +(use-package elisp-slime-nav + :config + (general-add-hook 'emacs-lisp-mode #'ielm-mode)) + +;; TODO: Should I be using `general-define-key' or `evil-leader/set-key'? My +;; gut say `general-define-key'. +(general-define-key + :keymaps 'emacs-lisp-mode-map + :states 'normal + :prefix "<SPC>" + "x" #'eval-defun) + +(general-define-key + :keymaps 'emacs-lisp-mode-map + :states 'normal + :prefix "<SPC>" + "X" #'eval-buffer) + +(general-define-key + :keymaps 'emacs-lisp-mode-map + :states 'normal + :prefix "<SPC>" + "d" (lambda () + (interactive) + (with-current-buffer (current-buffer) + (helpful-function (symbol-at-point))))) + +(provide 'wpc-lisp) +;;; wpc-lisp.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-misc.el b/configs/shared/.emacs.d/wpc/packages/wpc-misc.el new file mode 100644 index 000000000000..3ddfe0b3e49b --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-misc.el @@ -0,0 +1,209 @@ +;;; misc.el --- Hosting miscellaneous configuration -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This is the home of any configuration that couldn't find a better home. + +;;; Code: + +;; Display time in the modeline +;; TODO: Save preferred date format strings and cycle through them since I waver +;; about which is my favorite. +(setq display-time-format "%R %a %d %b [%U of 52 weeks]") +(display-time-mode 1) + +;; disable custom variable entries from being written to ~/.emacs.d/init.el +(setq custom-file "~/.emacs.d/custom.el") +(load custom-file 'noerror) + +;; integrate Emacs with X11 clipboard +(setq select-enable-primary t) +(setq select-enable-clipboard t) +(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) + +;; 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)) + +;; Required by some google-emacs package commands. +(use-package deferred) + +;; git integration +(use-package magit) + +;; http +(use-package request) + +;; perl-compatible regular expressions +(use-package pcre2el) + +;; alternative to help +(use-package helpful) + +;; 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))) + +;; config Emacs to use $PATH values +(use-package exec-path-from-shell + :if (memq window-system '(mac ns)) + :config + (exec-path-from-shell-initialize)) + +;; 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 constants/fill-column) + +(put 'narrow-to-region 'disabled nil) + +;; trim whitespace on save +(add-hook 'before-save-hook #'delete-trailing-whitespace) + +;; use tabs instead of spaces +(setq-default indent-tabs-mode nil) + +;; automatically follow symlinks +(setq vc-follow-symlinks t) + +;; fullscreen settings +(defvar ns-use-native-fullscreen nil) + +;; auto-close parens, brackets, quotes +(electric-pair-mode 1) + +(use-package yasnippet + :config + (yas-global-mode 1)) + +(use-package projectile + :config + (projectile-mode t)) + +(use-package deadgrep + :config + (general-define-key + :keymaps 'deadgrep-mode-map + :states 'normal + "o" #'deadgrep-visit-result-other-window) + (advice-add + 'deadgrep--format-command + :filter-return + (lambda (cmd) + (replace-regexp-in-string + "^rg " "rg --hidden " cmd)))) + +;; 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)) + +(use-package company-lsp + :config + (push 'company-lsp company-backends)) + +;; 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)) + +;; TODO: Consider supporting a wpc-elisp.el package for Elisp tooling. +;; The following functions are quite useful for Elisp development: +;; - `emr-el-find-unused-definitions' +(use-package emr + :config + (define-key prog-mode-map (kbd "M-RET") #'emr-show-refactor-menu)) + +(defun wpc/frame-name () + "Return the name of the current frame." + (frame-parameter nil 'name)) + +;; Even if I resolved the socket-name resolution issue, I couldn't find an +;; elegant way to reuse GUI frames. GUIs for me have the advantage of supporting +;; True Color, support additional keys for KBDs (i.e. super), and aren't limited +;; by the terminal for rendering certain things. +(require 'server) +(when (not (server-running-p)) + (server-start)) + +(provide 'wpc-misc) +;;; wpc-misc.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-nix.el b/configs/shared/.emacs.d/wpc/packages/wpc-nix.el new file mode 100644 index 000000000000..af439c442d63 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-nix.el @@ -0,0 +1,12 @@ +;;; wpc-nix.el --- Nix support -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Configuration to support working with Nix. + +;;; Code: +(use-package nix-mode + :mode "\\.nix\\'") + +(provide 'wpc-nix) +;;; wpc-nix.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-ocaml.el b/configs/shared/.emacs.d/wpc/packages/wpc-ocaml.el new file mode 100644 index 000000000000..3b898d32be54 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-ocaml.el @@ -0,0 +1,51 @@ +;;; wpc-ocaml.el --- My OCaml preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Tooling support for OCaml development. +;; +;; Dependencies: +;; - `opam install tuareg` +;; - `opam install merlin` +;; - `opam install user-setup` +;; - `opam install ocamlformat` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) + +(prelude/assert + (prelude/executable-exists? "opam")) + +(defvar opam-installs "~/.opam/4.08.0/share/emacs/site-lisp" + "Path to the Ocaml PAckage Manager installations.") + +(defvar opam-user-setup "~/.emacs.d/opam-user-setup.el" + "File for the OPAM Emacs integration.") + +(prelude/assert + (f-file? opam-user-setup)) + +(prelude/assert + (f-dir? opam-installs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package tuareg + :config + (add-hook-before-save 'tuareg-mode-hook #'ocamlformat-before-save)) + +;; ocamlformat +(require 'opam-user-setup "~/.emacs.d/opam-user-setup.el") +(require 'ocamlformat) +(add-to-list 'load-path opam-installs) + +(provide 'wpc-ocaml) +;;; wpc-ocaml.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-org.el b/configs/shared/.emacs.d/wpc/packages/wpc-org.el new file mode 100644 index 000000000000..d1d981a3ea25 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-org.el @@ -0,0 +1,78 @@ +;;; org.el --- My org preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hosts my org mode preferences + +;;; Code: + +;; TODO: figure out how to nest this in (use-package org ...) +(setq org-capture-templates + `( + + ("w" "work" entry (file+headline + ,(f-join (getenv "ORG_DIRECTORY") "work.org") + "Tasks") + "* TODO %?") + + ("p" "personal" entry (file+headline + ,(f-join (getenv "ORG_DIRECTORY") "personal.org") + "Tasks") + "* TODO %? ") + + ("i" "ideas" entry (file+headline + ,(f-join (getenv "ORG_DIRECTORY") "ideas.org") + "Tasks") + "* %? ") + + ("s" "shopping list" entry (file+headline + ,(f-join (getenv "ORG_DIRECTORY") "shopping.org") + "Items") + "* TODO %? ") + + )) +(evil-set-initial-state 'org-mode 'normal) + +(use-package org + :config + (general-add-hook 'org-mode-hook + ;; TODO: consider supporting `(disable (list linum-mode company-mode))' + (list (disable linum-mode) + (disable company-mode))) + (general-define-key :prefix "C-c" + "l" #'org-store-link + "a" #'org-agenda + "c" #'org-capture) + (setq org-startup-folded nil) + (setq org-todo-keywords + '((sequence "TODO" "BLOCKED" "DONE"))) + (setq org-default-notes-file (f-join (getenv "ORG_DIRECTORY") "notes.org")) + (setq org-agenda-files (list (f-join (getenv "ORG_DIRECTORY") "work.org") + (f-join (getenv "ORG_DIRECTORY") "personal.org"))) + ;; TODO: troubleshoot why `wpc/kbds-minor-mode', `wpc/ensure-kbds' aren't + ;; enough to override the following KBDs. See this discussion for more context + ;; on where the idea came from: + ;; https://stackoverflow.com/questions/683425/globally-override-key-binding-in-emacs + (general-unbind 'normal org-mode-map "M-h" "M-j" "M-k" "M-l")) + +(use-package org-bullets + :after (org) + :config + (general-add-hook 'org-mode-hook (enable org-bullets-mode))) + +;; i3, `org-mode' integration +;; Heavily influenced by: https://somethingsomething.us/post/i3_and_orgmode/ +;; TODO: Consider generalizing this since we're using "floating". +(defadvice org-switch-to-buffer-other-window + (after supress-window-splitting activate) + "Delete the extra window if we're in a capture frame." + (if (equal "floating" (wpc/frame-name)) + (delete-other-windows))) + +(add-hook 'org-capture-after-finalize-hook + (lambda () + (when (equal "floating" (wpc/frame-name)) + (delete-frame)))) + +(provide 'wpc-org) +;;; wpc-org.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-package.el b/configs/shared/.emacs.d/wpc/packages/wpc-package.el new file mode 100644 index 000000000000..6f43330ecb1a --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-package.el @@ -0,0 +1,27 @@ +;;; package.el --- My package configuration -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This module hosts all of the settings required to work with ELPA, +;; MELPA, QUELPA, and co. + +;;; Code: + +(require 'package) +(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) +(package-initialize) + +(unless (package-installed-p 'use-package) + (package-refresh-contents) + (package-install 'use-package)) +(eval-when-compile + (require 'use-package)) +(setq use-package-always-ensure t) +(use-package general) + +(add-to-list 'load-path "~/.emacs.d/vendor/") +(add-to-list 'load-path "~/.emacs.d/wpc/") +(add-to-list 'load-path "~/.emacs.d/wpc/packages") + +(provide 'wpc-package) +;;; wpc-package.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-python.el b/configs/shared/.emacs.d/wpc/packages/wpc-python.el new file mode 100644 index 000000000000..d0c6dd0d3e42 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-python.el @@ -0,0 +1,17 @@ +;;; wpc-python.el --- Python configuration -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; My Python configuration settings +;; +;; Depends +;; - `apti yapf` + +;;; Code: + +(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/configs/shared/.emacs.d/wpc/packages/wpc-reasonml.el b/configs/shared/.emacs.d/wpc/packages/wpc-reasonml.el new file mode 100644 index 000000000000..909c33d121f7 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-reasonml.el @@ -0,0 +1,29 @@ +;;; wpc-reasonml.el --- My ReasonML preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Tooling support for ReasonML development. +;; +;; Dependencies: +;; - `opam install tuareg` +;; - `opam install merlin` +;; - `opam install user-setup` +;; - `opam install ocamlformat` + +;;; Code: + +;; ReasonML configuration +(use-package reason-mode + :config + (add-hook-before-save 'reason-mode-hook #'refmt-before-save)) + +;; ReasonML LSP configuration +(lsp-register-client + (make-lsp-client :new-connection (lsp-stdio-connection (f-full "~/programming/dependencies/reason-language-server")) + :major-modes '(reason-mode) + :notification-handlers (ht ("client/registerCapability" 'ignore)) + :priority 1 + :server-id 'reason-ls)) + +(provide 'wpc-reasonml) +;;; wpc-reasonml.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-rust.el b/configs/shared/.emacs.d/wpc/packages/wpc-rust.el new file mode 100644 index 000000000000..fafa27d18c77 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-rust.el @@ -0,0 +1,34 @@ +;;; wpc-rust.el --- Support Rust language -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Supports my Rust work. +;; +;; Dependencies: +;; - `rustup` +;; - `rustup component add rust-src` +;; - `rustup toolchain add nightly && cargo +nightly install racer` + + +;;; Code: +(use-package racer + :config + (setq rust-sysroot (->> "~/.cargo/bin/rustc --print sysroot" + shell-command-to-string + s-trim-right)) + (setq racer-rust-src-path (f-join rust-sysroot "lib/rustlib/src/rust/src")) + (add-hook 'racer-mode-hook #'eldoc-mode)) + +(use-package rust-mode + :config + (add-hook 'rust-mode-hook #'racer-mode) + (add-hook-before-save 'rust-mode-hook #'rust-format-buffer) + (define-key rust-mode-map + (kbd "TAB") + #'company-indent-or-complete-common) + (define-key rust-mode-map + (kbd "M-d") + #'racer-describe)) + +(provide 'wpc-rust) +;;; wpc-rust.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-shell.el b/configs/shared/.emacs.d/wpc/packages/wpc-shell.el new file mode 100644 index 000000000000..14d0489030da --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-shell.el @@ -0,0 +1,15 @@ +;;; wpc-shell.el --- POSIX Shell scripting support -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Helpers for my shell scripting. Includes bash, zsh, etc. + +;;; Code: + +(use-package flymake-shellcheck + :commands flymake-shellcheck-load + :init + (add-hook 'sh-mode-hook #'flymake-shellcheck-load)) + +(provide 'wpc-shell) +;;; wpc-shell.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-terminal.el b/configs/shared/.emacs.d/wpc/packages/wpc-terminal.el new file mode 100644 index 000000000000..c232bb85a7b7 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-terminal.el @@ -0,0 +1,70 @@ +;;; terminal.el --- My cobbled together terminal -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; My attempts at creating a sane Emacs terminal. Most of this work was created +;; before I discovered and fully adopted EXWM. Prior to this, the appeal of +;; having terminals inside of Emacs was appealing. So appealing in fact that I +;; was willing to work with inferior alternatives to non-Emacs terminals +;; (e.g. `ansi-term') instead of GUI alternatives like `alacritty` because the +;; productivity gains of having a terminal inside of Emacs might outweigh the +;; shortcomings of that particular terminal. +;; +;; All of this changed, however, after discovering EXWM, since I can embed X11 +;; GUI windows inside of Emacs. Therefore, most of this module is maintained +;; for historical purposes. +;; +;; Benefits of `ansi-term': +;; - Color scheme remains consistent between Emacs and terminal. +;; - Same applies to my fonts. +;; +;; Downsides of `ansi-term': +;; - Paging feels sluggish with programs like `cat` and `less`. +;; - KBDs don't provide 100% coverage of what I expect from a terminal since +;; they were created to cooperate with Emacs. + +;;; Code: + +(require 'window) +(require 'buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Model all open terminals within a dictionary. + +(defconst wpc-terminal/name + "wpc/terminal" + "The name of my terminal buffers.") + +(defun wpc-terminal/find-window () + "Return a reference to an existing terminal window or nil." + (->> wpc-terminal/name + wpc/add-earmuffs + window/find)) + +(defun wpc-terminal/find-buffer () + "Return a reference to an existing terminal buffer." + (->> wpc-terminal/name + wpc/add-earmuffs + buffer/find)) + +(defun wpc-terminal/find-or-create () + "Find or create a terminal window." + (let ((buffer (wpc-terminal/find-buffer))) + (if buffer + (buffer/show buffer) + (ansi-term "/usr/bin/zsh" wpc-terminal/name)))) + +;; TODO: Focus terminal after toggling it. +(defun wpc-terminal/toggle () + "Toggle a custom terminal session in Emacs." + (interactive) + (let ((window (wpc-terminal/find-window))) + (if window + (window/delete window) + (wpc-terminal/find-or-create)))) + +(provide 'wpc-terminal) +;;; wpc-terminal.el ends here diff --git a/configs/shared/.emacs.d/wpc/packages/wpc-ui.el b/configs/shared/.emacs.d/wpc/packages/wpc-ui.el new file mode 100644 index 000000000000..4c850e5b4b40 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/packages/wpc-ui.el @@ -0,0 +1,185 @@ +;;; wpc-ui.el --- Any related to the UI/UX goes here -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hosts font settings, scrolling, color schemes. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'alist) +(require 'wallpaper) +(require 'fonts) +(require 'themes) +(require 'window-manager) +(require 'device) +(require 'laptop-battery) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; increase line height +(setq-default line-spacing 4) + +;; 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)) + +;; disable startup screen +(setq inhibit-startup-screen t) + +;; disable toolbar +(tool-bar-mode -1) + +;; TODO: Re-enable `linum-mode' when I figure out why the theming is so ugly. +;; enable line numbers +;; (general-add-hook '(prog-mode-hook +;; text-mode-hook +;; conf-mode-hook) +;; (enable linum-mode)) + +;; set default buffer for Emacs +(setq initial-buffer-choice constants/current-project) + +;; integration with wpgtk (in vendor directory) +;; TODO: Re-enable this when base16-wpgtk are looking better. +;; (require 'wpgtk-theme) + +;; base-16 themes to integrate with wpgtk +;; (use-package base16-theme +;; :config +;; (require 'wpgtk) +;; (colorscheme/set 'base16-wpgtk)) + +;; 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)) + +;; file browsing +(use-package neotree + :config + (global-set-key [f8] #'neotree-toggle)) + +;; kbd discovery +(use-package which-key + :config + (setq which-key-idle-delay 0.25) + (which-key-mode)) + +;; completion framework +(use-package ivy + ;; TODO: Restore behavior where `counsel' is used everywhere. + :config + (counsel-mode t) + (alist/set! #'counsel-M-x "" ivy-initial-inputs-alist) + ;; 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 + ;; 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) + (prescient-persist-mode 1)) + +;; all-the-icons +(use-package all-the-icons + :config + ;; Only run this once after installing. + ;; (all-the-icons-install-fonts) + ) + +;; 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) +(when (string-equal system-type "darwin") + (setq ns-auto-hide-menu-bar t)) + +;; reduce noisiness of auto-revert-mode +(setq auto-revert-verbose nil) + +;; highlight lines that are over 100 characters long +(use-package whitespace + :config + (setq whitespace-line-column constants/fill-column) + (setq whitespace-style '(face lines-tail)) + (add-hook 'prog-mode-hook #'whitespace-mode)) + +;; rebalance emacs windows after splits are created +;; (defadvice split-window-below (after rebalance-windows activate) +;; (balance-windows)) +;; (defadvice split-window-right (after rebalance-windows activate) +;; (balance-windows)) +;; (defadvice delete-window (after rebalance-window activate) +;; (balance-windows)) + +;; 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) + +;; GUI alerts in emacs +(use-package alert + :commands (alert) + :config + (setq alert-default-style 'notifier)) + +;; TODO: Should `device/work-laptop?' be a function or a constant that gets set +;; during initialization? +(when (device/work-laptop?) + (laptop-battery/display)) + +;; Load a theme +(->> (themes/random) + themes/set) + +(provide 'wpc-ui) +;;; wpc-ui.el ends here diff --git a/configs/shared/.emacs.d/wpc/playback.el b/configs/shared/.emacs.d/wpc/playback.el new file mode 100644 index 000000000000..9ab1e30ef0ac --- /dev/null +++ b/configs/shared/.emacs.d/wpc/playback.el @@ -0,0 +1,25 @@ +;;; playback.el --- Control playback with Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; As you know, my whole universe is turning Elisp, so this should too! + +;;; Code: + +(defun playback/prev () + "Move to the previous song." + (interactive) + (shell-command "playerctl previous")) + +(defun playback/next () + "Move to the next song." + (interactive) + (shell-command "playerctl next")) + +(defun playback/play-pause () + "Play or pause the current song." + (interactive) + (shell-command "playerctl play-pause")) + +(provide 'playback) +;;; playback.el ends here diff --git a/configs/shared/.emacs.d/wpc/polymorphism.el b/configs/shared/.emacs.d/wpc/polymorphism.el new file mode 100644 index 000000000000..09045f7fb258 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/polymorphism.el @@ -0,0 +1,37 @@ +;;; polymorphism.el --- Sketching my ideas for polymorphism in Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Once again: modelled after Elixir. + +;;; Code: + +;; More sketches of Elisp polymorphism initiative. +;; +;; Two macros: +;; - `defprotocol' +;; - `definstance' +;; +;; Is it just a coincidence that these two macros have the same number of +;;characters or is that fate? I say fate. +;; +;; (defprotocol monoid +;; :functions (empty concat)) +;; +;; (definstance monoid vector +;; :empty +;; (lambda () []) +;; :concat +;; #'vector/concat) +;; +;; More sketching... +;; (defun monoid/empty () +;; "Sketch." +;; (funcall #'(,(monoid/classify)/empty))) +;; (defun monoid/concat (xs) +;; "Sketch." +;; (apply #'(,(monoid/classify)/concat) args)) + + +(provide 'polymorphism) +;;; polymorphism.el ends here diff --git a/configs/shared/.emacs.d/wpc/prelude.el b/configs/shared/.emacs.d/wpc/prelude.el new file mode 100644 index 000000000000..fb459627899d --- /dev/null +++ b/configs/shared/.emacs.d/wpc/prelude.el @@ -0,0 +1,122 @@ +;;; prelude.el --- My attempt at augmenting Elisp stdlib -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Third-party libraries +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 's) +(require 'dash) +(require 'f) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Libraries +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Maybe don't globally import everything here. Disable these and attepmt +;; to reload Emacs to assess damage. +(require 'string) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 + (list/map #'prelude/to-string) + (apply #'string/concat) + message)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 (string/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 (string/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 linum/safe? nil + "Flag indicating whether or not it is safe to work with `linum-mode'.") + +(defvar linum/mru-color nil + "Stores the color most recently attempted to be applied.") + +(add-hook 'linum-mode-hook + (lambda () + (setq linum/safe? t) + (when (maybe/some? linum/mru-color) + (set-face-foreground 'linum 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 +`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 linum/safe? + (set-face-foreground 'linum color) + (setq linum/mru-color color))) + +(defun prelude/prompt (prompt) + "Read input from user with PROMPT." + (read-string prompt)) + +(defun prelude/executable-exists? (name) + "Return t if CLI tool NAME exists according to `exec-path'." + (let ((file (locate-file name exec-path))) + (if (maybe/some? file) + (f-exists? file) + nil))) + +(provide 'prelude) +;;; prelude.el ends here diff --git a/configs/shared/.emacs.d/wpc/prelude.nix b/configs/shared/.emacs.d/wpc/prelude.nix new file mode 100644 index 000000000000..626d4526a25d --- /dev/null +++ b/configs/shared/.emacs.d/wpc/prelude.nix @@ -0,0 +1,11 @@ +{ pkgs ? import (builtins.fetchTarball + "https://github.com/tazjin/depot/archive/master.tar.gz") {} }: + +# Ciruclar dependency warning: list.nix depends on prelude.nix, which depends on +# list.nix. +pkgs.writeElispBin { + name = "prelude"; + # If this can build with epkgs.ht, remove `(require 'ht)`. + deps = epkgs: [ epkgs.s epkgs.dash epkgs.f ./string.nix ./list.nix ]; + src = ./prelude.el; +} diff --git a/configs/shared/.emacs.d/wpc/productivity-timer.el b/configs/shared/.emacs.d/wpc/productivity-timer.el new file mode 100644 index 000000000000..6d421dd69ed1 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/productivity-timer.el @@ -0,0 +1,22 @@ +;;; productivity-timer.el --- Commonly used intervals for setting alarms while working -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Select common timer intervals with dmenu and play an alarm sound when +;; finished. +;; +;; This is heavily inspired by iOS's timer feature. + + +;;; Code: + +(defconst productivity-timer/intervals + '(1 2 3 4 5 10 15 20 30 45 60 120) + "Commonly used intervals for timer amounts.") + +;; `sleep-for' doesn't seem to work. Perhaps `sit-for' won't be any better. +;; How can I use dunst to alert? +;; `run-at-time' may be the most promising option + +(provide 'productivity-timer) +;;; productivity-timer.el ends here diff --git a/configs/shared/.emacs.d/wpc/pulse-audio.el b/configs/shared/.emacs.d/wpc/pulse-audio.el new file mode 100644 index 000000000000..7dff888e98ea --- /dev/null +++ b/configs/shared/.emacs.d/wpc/pulse-audio.el @@ -0,0 +1,48 @@ +;;; pulse-audio.el --- Control audio with Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Because everything in my configuration is turning into Elisp these days. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst pulse-audio/install-kbds? t + "When t, install keybindings defined herein.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun pulse-audio/toggle-mute () + "Mute the default sink." + (interactive) + (shell-command "pactl set-sink-mute @DEFAULT_SINK@ toggle") + (message (string/format "[pulse-audio.el] Mute toggled."))) + +(defun pulse-audio/lower-volume () + "Low the volume output of the default sink." + (interactive) + (shell-command "pactl set-sink-volume @DEFAULT_SINK@ -10%") + (message (string/format "[pulse-audio.el] Volume lowered."))) + +(defun pulse-audio/raise-volume () + "Raise the volume output of the default sink." + (interactive) + (shell-command "pactl set-sink-volume @DEFAULT_SINK@ +10%") + (message (string/format "[pulse-audio.el] Volume raised."))) + + +(when pulse-audio/install-kbds? + (exwm-input-set-key + (kbd "<XF86AudioMute>") #'pulse-audio/toggle-mute) + (exwm-input-set-key + (kbd "<XF86AudioLowerVolume>") #'pulse-audio/lower-volume) + (exwm-input-set-key + (kbd "<XF86AudioRaiseVolume>") #'pulse-audio/raise-volume)) + +(provide 'pulse-audio) +;;; pulse-audio.el ends here diff --git a/configs/shared/.emacs.d/wpc/pushover.el b/configs/shared/.emacs.d/wpc/pushover.el new file mode 100644 index 000000000000..fb06656cf467 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/pushover.el @@ -0,0 +1,75 @@ +;;; pushover.el --- Send generic messages to mobile device -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Pushover.net is a mobile app that accepts JSON. This supports loose +;; integration between things and mobile devices. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'request) +(require 'password-store) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst pushover/app-token + (password-store-get-field "api-keys/pushover.net" "emacs") + "App token for \"emacs\" application.") + +(defconst pushover/user-key + (password-store-get "api-keys/pushover.net") + "Key that identifies me to pushover.") + +(defconst pushover/url + "https://api.pushover.net/1/messages.json" + "URL to POST messages.") + +;; TODO: Rename module "pushover". + +(defun pushover/notify (message) + "Posts MESSAGE to all devices. +Here are the parameters that Pushover accepts: + +Required parameters: + - token - your application's API token + - user - the user/group key (not e-mail address) of your user (or you), + viewable when logged into our dashboard (often referred to as USER_KEY in + our documentation and code examples) + - message - your message + +Additional parameters (optional): + - attachment - an image attachment to send with the message; see attachments + for more information on how to upload files + device - your user's device name to send the message directly to that + device, rather than all of the user's devices (multiple devices may be + separated by a comma) + - title - your message's title, otherwise your app's name is used + - url - a supplementary URL to show with your message + - url_title - a title for your supplementary URL, otherwise just the URL is + shown + - priority - send as -2 to generate no notification/alert, -1 to always send + as a quiet notification, 1 to display as high-priority and bypass the user's + quiet hours, or 2 to also require confirmation from the user + - sound - the name of one of the sounds supported by device clients to + override the user's default sound choice + - timestamp - a Unix timestamp" + (request + pushover/url + :type "POST" + :params `(("token" . ,pushover/app-token) + ("user" . ,pushover/user-key) + ("message" . ,message)) + :data nil + :parser 'json-read + :success (cl-function + (lambda (&key data &allow-other-keys) + (message "Pushover.net notification sent!"))))) + +(provide 'pushover) +;;; pushover.el ends here diff --git a/configs/shared/.emacs.d/wpc/random.el b/configs/shared/.emacs.d/wpc/random.el new file mode 100644 index 000000000000..148506c04d4e --- /dev/null +++ b/configs/shared/.emacs.d/wpc/random.el @@ -0,0 +1,73 @@ +;;; random.el --- Functions for working with randomness -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Functions for working with randomness. Some of this code is not as +;; functional as I'd like from. + +;;; Code: + +(require 'prelude) +(require 'number) +(require 'math) +(require 'series) +(require 'list) +(require 'set) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun random/int (x) + "Return a random integer from 0 to `X'." + (random x)) + +;; TODO: Make this work with sequences instead of lists. +(defun random/choice (xs) + "Return a random element of `XS'." + (let ((ct (list/length xs))) + (list/get + (random/int ct) + xs))) + +(defun random/boolean? () + "Randonly return t or nil." + (random/choice (list t nil))) + +;; TODO: This may not work if any of these generate numbers like 0, 1, etc. +(defun random/uuid () + "Return a generated UUID string." + (let ((eight (number/dec (math/triangle-of-power :base 16 :power 8))) + (four (number/dec (math/triangle-of-power :base 16 :power 4))) + (twelve (number/dec (math/triangle-of-power :base 16 :power 12)))) + (format "%x-%x-%x-%x-%x" + (random/int eight) + (random/int four) + (random/int four) + (random/int four) + (random/int twelve)))) + +(defun random/token (length) + "Return a randomly generated hexadecimal string of LENGTH." + (->> (series/range 0 (number/dec length)) + (list/map (lambda (_) (format "%x" (random/int 15)))) + (list/join ""))) + +;; TODO: Support random/sample +(defun random/sample (n xs) + "Return a randomly sample of list XS of size N." + (prelude/assert (and (>= n 0) (< n (list/length xs)))) + (cl-labels ((do-sample + (n xs y ys) + (if (= n (set/count ys)) + (->> ys + set/to-list + (list/map (lambda (i) + (list/get i xs)))) + (if (set/contains? y ys) + (do-sample n xs (random/int (list/length xs)) ys) + (do-sample n xs y (set/add y ys)))))) + (do-sample n xs (random/int (list/length xs)) (set/new)))) + +(provide 'random) +;;; random.el ends here diff --git a/configs/shared/.emacs.d/wpc/scheduler.el b/configs/shared/.emacs.d/wpc/scheduler.el new file mode 100644 index 000000000000..bae953228925 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/scheduler.el @@ -0,0 +1,22 @@ +;;; scheduler.el --- Sketches of scheduling -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Attempting to create a FSM for scheduling things in various ways: +;; +;; Scheduling policies: +;; - earliest due date: minimizes total lateness of all tasks in a pool. Put +;; the task with the latest due date last in the list and work backwards to +;; solve the precedence constraint (i.e. dependency issue). +;; - shortest processing time: maximizes number of tasks completed. Prioritize +;; tasks in the order of how long they will take to complete from shortest to +;; longest. This breaks down when precedence constraints are introduced. +;; +;; Tasks should inherit prioritization. + + + +;;; Code: + +(provide 'scheduler) +;;; scheduler.el ends here diff --git a/configs/shared/.emacs.d/wpc/scope.el b/configs/shared/.emacs.d/wpc/scope.el new file mode 100644 index 000000000000..48aa85ad0e5d --- /dev/null +++ b/configs/shared/.emacs.d/wpc/scope.el @@ -0,0 +1,99 @@ +;;; scope.el --- Work with a scope data structure -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Exposing an API for working with a scope data structure in a non-mutative +;; way. +;; +;; What's a scope? Think of a scope as a stack of key-value bindings. + +;;; Code: + +(require 'alist) +(require 'stack) +(require 'struct) +(require 'macros) + +(cl-defstruct scope scopes) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope/new () + "Return an empty scope." + (make-scope :scopes (->> (stack/new) + (stack/push (alist/new))))) + +(defun scope/flatten (xs) + "Return a flattened representation of the scope, XS. +The newest bindings eclipse the oldest." + (->> xs + scope-scopes + stack/to-list + (list/reduce (alist/new) + (lambda (scope acc) + (alist/merge acc scope))))) + +(defun scope/push-new (xs) + "Push a new, empty scope onto XS." + (struct/update scope + scopes + (>> (stack/push (alist/new))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope/get (k xs) + "Return K from XS if it's in scope." + (->> xs + scope/flatten + (alist/get k))) + +(defun scope/current (xs) + "Return the newest scope from XS." + (let ((xs-copy (copy-scope xs))) + (->> xs-copy + scope-scopes + stack/peek))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope/set (k v xs) + "Set value, V, at key, K, in XS for the current scope." + (struct/update scope + scopes + (>> (stack/map-top (>> (alist/set k v)))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope/pop (xs) + "Return a new scope without the top element from XS." + (->> xs + scope-scopes + stack/pop)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope/defined? (k xs) + "Return t if K is in scope of XS." + (->> xs + scope/flatten + (alist/has-key? k))) + +;; TODO: Find a faster way to write aliases like this. +(defun scope/instance? (xs) + "Return t if XS is a scope struct." + (scope-p xs)) + +(provide 'scope) +;;; scope.el ends here diff --git a/configs/shared/.emacs.d/wpc/screen-brightness.el b/configs/shared/.emacs.d/wpc/screen-brightness.el new file mode 100644 index 000000000000..1b74ea34a953 --- /dev/null +++ b/configs/shared/.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> + +;;; Commentary: +;; Mainly just Elisp wrappers around `xbacklight`. + +;;; Code: + +;; TODO: Define some isomorphisms. E.g. int->string, string->int. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst screen-brightness/step-size 15 + "The size of the increment or decrement step for the screen's brightness.") + +(defcustom screen-brightness/install-kbds? t + "If t, install the keybindings to control screen brightness.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun screen-brightness/increase () + "Increase the screen brightness." + (interactive) + (->> screen-brightness/step-size + int-to-string + (string/concat "xbacklight -inc ") + shell-command) + (message "[screen-brightness.el] Increased screen brightness.")) + +(defun screen-brightness/decrease () + "Decrease the screen brightness." + (interactive) + (->> screen-brightness/step-size + int-to-string + (string/concat "xbacklight -dec ") + shell-command) + (message "[screen-brightness.el] Decreased screen brightness.")) + +(when screen-brightness/install-kbds? + (exwm-input-set-key + (kbd "<XF86MonBrightnessUp>") #'screen-brightness/increase) + (exwm-input-set-key + (kbd "<XF86MonBrightnessDown>") #'screen-brightness/decrease)) + +(provide 'screen-brightness) +;;; screen-brightness.el ends here diff --git a/configs/shared/.emacs.d/wpc/sequence.el b/configs/shared/.emacs.d/wpc/sequence.el new file mode 100644 index 000000000000..a5428ef04448 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/sequence.el @@ -0,0 +1,105 @@ +;;; sequence.el --- Working with the "sequence" types -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Elisp supports a typeclass none as "sequence" which covers the following +;; types: +;; - list: '(1 2 3 4 5) +;; - vector: ["John" 27 :blue] +;; - string: "To be or not to be..." + +;; TODO: Document the difference between a "reduce" and a "fold". I.e. - reduce +;; has an initial value whereas fold uses the first element in the sequence as +;; the initial value. +;; +;; Note: This should be an approximation of Elixir's Enum protocol albeit +;; without streams. +;; +;; Elisp has done a lot of this work already and these are mostly wrapper +;; functions. +;; See the following list for reference: +;; - sequencep +;; - elt +;; - copy-sequence +;; - reverse +;; - nreverse +;; - sort +;; - seq-elt +;; - seq-length +;; - seqp +;; - seq-drop +;; - seq-take +;; - seq-take-while +;; - seq-drop-while +;; - seq-do +;; - seq-map +;; - seq-mapn +;; - seq-filter +;; - seq-remove +;; - seq-reduce +;; - seq-some +;; - seq-find +;; - seq-every-p +;; - seq-empty-p +;; - seq-count +;; - seq-sort +;; - seq-contains +;; - seq-position +;; - seq-uniq +;; - seq-subseq +;; - seq-concatenate +;; - seq-mapcat +;; - seq-partition +;; - seq-intersection +;; - seq-difference +;; - seq-group-by +;; - seq-into +;; - seq-min +;; - seq-max +;; - seq-doseq +;; - seq-let + +;;; Code: + +;; Perhaps we can provide default implementations for `filter' and `map' derived +;; from the `reduce' implementation. +;; (defprotocol sequence +;; :functions (reduce)) +;; (definstance sequence list +;; :reduce #'list/reduce +;; :filter #'list/filter +;; :map #'list/map) +;; (definstance sequence vector +;; :reduce #'vector/reduce) +;; (definstance sequence string +;; :reduce #'string) + +(defun sequence/classify (xs) + "Return the type of `XS'." + (cond + ((listp xs) 'list) + ((vectorp xs) 'vector) + ((stringp xs) 'string))) + +(defun sequence/reduce (acc f xs) + "Reduce of `XS' calling `F' on x and `ACC'." + (seq-reduce + (lambda (acc x) + (funcall f x acc)) + xs + acc)) + +;; Elixir also turned everything into a list for efficiecy reasons. + +(defun sequence/filter (p xs) + "Filter `XS' with predicate, `P'. +Returns a list regardless of the type of `XS'." + (seq-filter p xs)) + +(defun sequence/map (f xs) + "Maps `XS' calling `F' on each element. +Returns a list regardless of the type of `XS'." + (seq-map f xs)) + +(provide 'sequence) +;;; sequence.el ends here diff --git a/configs/shared/.emacs.d/wpc/series.el b/configs/shared/.emacs.d/wpc/series.el new file mode 100644 index 000000000000..977ffc50261a --- /dev/null +++ b/configs/shared/.emacs.d/wpc/series.el @@ -0,0 +1,81 @@ +;;; series.el --- Hosting common series of numbers -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Encoding number series as I learn about them. +;; +;; These are the following series I'm interested in supporting: +;; - Fibonacci +;; - Catalan numbers +;; - Figurate number series +;; - Triangular +;; - Square +;; - Pentagonal +;; - Hexagonal +;; - Lazy-caterer +;; - Magic square +;; - Look-and-say + +;;; Code: + +(require 'number) + +(defun series/range (beg end) + "Create a list of numbers from `BEG' to `END'. +This is an inclusive number range." + (if (< end beg) + (list/reverse + (number-sequence end beg)) + (number-sequence beg end))) + +(defun series/fibonacci-number (i) + "Return the number in the fibonacci series at `I'." + (cond + ((= 0 i) 0) + ((= 1 i) 1) + (t (+ (series/fibonacci-number (- i 1)) + (series/fibonacci-number (- i 2)))))) + +(defun series/fibonacci (n) + "Return the first `N' numbers of the fibonaccci series starting at zero." + (if (= 0 n) + '() + (list/reverse + (list/cons (series/fibonacci-number (number/dec n)) + (list/reverse + (series/fibonacci (number/dec n))))))) + +;; TODO: Consider memoization. +(defun series/triangular-number (i) + "Return the number in the triangular series at `I'." + (if (= 0 i) + 0 + (+ i (series/triangular-number (number/dec i))))) + +;; TODO: Improve performance. +;; TODO: Consider creating a stream protocol with `stream/next' and implement +;; this using that. +(defun series/triangular (n) + "Return the first `N' numbers of a triangular series starting at 0." + (if (= 0 n) + '() + (list/reverse + (list/cons (series/triangular-number (number/dec n)) + (list/reverse + (series/triangular (number/dec n))))))) + +(defun series/catalan-number (i) + "Return the catalan number in the series at `I'." + (if (= 0 i) + 1 + (/ (number/factorial (* 2 i)) + (* (number/factorial (number/inc i)) + (number/factorial i))))) + +(defun series/catalan (n) + "Return the first `N' numbers in a catalan series." + (->> (series/range 0 (number/dec n)) + (list/map #'series/catalan-number))) + +(provide 'series) +;;; series.el ends here diff --git a/configs/shared/.emacs.d/wpc/set.el b/configs/shared/.emacs.d/wpc/set.el new file mode 100644 index 000000000000..fd86f9033cc3 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/set.el @@ -0,0 +1,92 @@ +;;; set.el --- Working with mathematical sets -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; The set data structure is a collection that deduplicates its elements. + +;;; Code: + +(require 'ht) ;; friendlier API for hash-tables +(require 'dotted) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 + (list/map #'dotted/new) + 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 10) + table-copy)) + xs)) + +(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)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst set/enable-testing? t + "Run tests when t.") + +(when set/enable-testing? + (progn + ;; {from,to}-list + (prelude/assert (equal '(1 2 3) + (->> '(1 1 2 2 3 3) + set/from-list + set/to-list))) + ;; empty? + (prelude/assert (set/empty? (set/new))) + (prelude/refute (set/empty? (set/new 1 2 3))) + ;; count + (prelude/assert (= 0 (set/count (set/new)))) + (prelude/assert (= 2 (set/count (set/new 1 1 2 2)))))) + +(provide 'set) +;;; set.el ends here diff --git a/configs/shared/.emacs.d/wpc/sre.el b/configs/shared/.emacs.d/wpc/sre.el new file mode 100644 index 000000000000..1c8f6ddd9a44 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/sre.el @@ -0,0 +1,26 @@ +;;; sre.el --- Site Reliability Engineering stuffs -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Storing some data that might be helpful in my ladder switch attempt. + +;;; Code: + +(defvar sre/introduction-email + "Hello! + +My name is William Carroll. I'm currently attempting a ladder switch. I have my +manager's approval to look for a new role because we believe I have been hired +for the wrong position. + +I'm eager to move ahead if there are any SRE openings in LON that fit my +profile. I'm happy to share more information with you about my background and +what I'm looking for. I've been attending the SRE Ops Review meetings in 6PS +weekly for awhile now, so we should be in the same office every Tuesday if +meeting in person is easier for you. + +Let me know!" + "Boilerplate email for reaching out to SRE hiring managers.") + +(provide 'sre) +;;; sre.el ends here diff --git a/configs/shared/.emacs.d/wpc/ssh.el b/configs/shared/.emacs.d/wpc/ssh.el new file mode 100644 index 000000000000..d7039375731e --- /dev/null +++ b/configs/shared/.emacs.d/wpc/ssh.el @@ -0,0 +1,31 @@ +;;; ssh.el --- When working remotely -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Configuration to make remote work easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tramp) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; 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] *\\)*") + +;; TODO: Re-enable this in case "dumb" isn't the default. +;; (setq tramp-terminal-type "dumb") + +(setq tramp-verbose 10) + +(provide 'ssh) +;;; ssh.el ends here diff --git a/configs/shared/.emacs.d/wpc/stack.el b/configs/shared/.emacs.d/wpc/stack.el new file mode 100644 index 000000000000..052ed881d20f --- /dev/null +++ b/configs/shared/.emacs.d/wpc/stack.el @@ -0,0 +1,93 @@ +;;; stack.el --- Working with stacks in Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; A stack is a LIFO queue. +;; The design goal here is to expose an intuitive API for working with stacks in +;; non-mutative way. +;; +;; TODO: Consider naming a Functor instance "Mappable." +;; TODO: Consider naming a Foldable instance "Reduceable." +;; +;; TODO: Consider implementing an instance for Mappable. +;; TODO: Consider implementing an instance for Reduceable. + +;;; Code: + +(require 'list) + +(cl-defstruct stack xs) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack/new () + "Create an empty stack." + (make-stack :xs '())) + +(defun stack/from-list (xs) + "Create a new stack from the list, `XS'." + (list/reduce (stack/new) #'stack/push xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack/peek (xs) + "Look at the top element of `XS' without popping it off." + (->> xs + stack-xs + list/head)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack/push (x xs) + "Push `X' on `XS'." + (struct/update stack + xs + (>> (list/cons x)) + xs)) + +;; TODO: How to return something like {(list/head xs), (list/tail xs)} in Elixir +;; TODO: How to handle popping from empty stacks? +(defun stack/pop (xs) + "Return the stack, `XS', without the top element. +Since I cannot figure out a nice way of return tuples in Elisp, if you want to +look at the first element, use `stack/peek' before running `stack/pop'." + (struct/update stack + xs + (>> list/tail) + xs)) + +(defun stack/map-top (f xs) + "Apply F to the top element of XS." + (->> xs + stack/pop + (stack/push (funcall f (stack/peek xs))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun stack/to-list (xs) + "Return XS as a list. +The round-trip property of `stack/from-list' and `stack/to-list' should hold." + (->> xs + stack-xs + list/reverse)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Create a macro that wraps `cl-defstruct' that automatically creates +;; things like `new', `instance?'. +(defun stack/instance? (xs) + "Return t if XS is a stack." + (stack-p xs)) + +(provide 'stack) +;;; stack.el ends here diff --git a/configs/shared/.emacs.d/wpc/string.el b/configs/shared/.emacs.d/wpc/string.el new file mode 100644 index 000000000000..56c9279dd5b9 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/string.el @@ -0,0 +1,126 @@ +;; string.el --- Library for working with strings -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Library for working with string. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 's) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Strings +(defun string/hookify (x) + "Append \"-hook\" to X." + (s-append "-hook" x)) + +(defun string/ensure-hookified (x) + "Ensure that X has \"-hook\" appended to it." + (if (s-ends-with? "-hook" x) + x + (string/hookify 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/->symbol (string) + "Maps `STRING' to a symbol." + (intern string)) + +(defun string/<-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)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support these. +;; (require 'macros) +;; (macros/test +;; :function string/surround +;; :test "works as expected" +;; :args '("-*-" "surround") +;; :expect "-*-surround-*-" +;; :equality string=) +; +;; (macros/test +;; :function string/caps->kebab +;; :test "works as expected" +;; :args '("CAPS_CASE_STRING") +;; :expect "caps-case-string" +;; :equality string=) +; +;; ;; TODO: Generate :test from docs of defun. +;; (macros/test +;; :function string/kebab->caps +;; :test "works as expected" +;; :args '("kebab-case-string") +;; :expect "KEBAB_CASE_STRING" +;; :equality =) + +(provide 'string) +;;; string.el ends here diff --git a/configs/shared/.emacs.d/wpc/string.nix b/configs/shared/.emacs.d/wpc/string.nix new file mode 100644 index 000000000000..1f815b26bb37 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/string.nix @@ -0,0 +1,8 @@ +{ pkgs ? import (builtins.fetchTarball + "https://github.com/tazjin/depot/archive/master.tar.gz") {} }: + +pkgs.writeElispBin { + name = "string"; + deps = epkgs: [ epkgs.dash epkgs.s ./prelude.nix ]; + src = ./string.el; +} diff --git a/configs/shared/.emacs.d/wpc/struct.el b/configs/shared/.emacs.d/wpc/struct.el new file mode 100644 index 000000000000..248d08e97c42 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/struct.el @@ -0,0 +1,71 @@ +;;; struct.el --- Helpers for working with structs -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish list +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Replace `symbol-name' and `intern' calls with isomorphism. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro struct/update (type field f xs) + "Apply F to FIELD in XS, which is a struct of TYPE. +This is immutable." + (let ((copier (->> type + symbol-name + (string/prepend "copy-") + intern)) + (accessor (->> field + symbol-name + (string/prepend (string/concat (symbol-name type) "-")) + intern))) + `(let ((copy (,copier ,xs))) + (setf (,accessor copy) (funcall ,f (,accessor copy))) + copy))) + +(defmacro struct/set (type field x xs) + "Immutably set FIELD in XS (struct TYPE) to X." + (let ((copier (->> type + symbol-name + (string/prepend "copy-") + intern)) + (accessor (->> field + symbol-name + (string/prepend (string/concat (symbol-name type) "-")) + intern))) + `(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 (->> field + symbol-name + (string/prepend (string/concat (symbol-name type) "-")) + intern))) + `(progn + (setf (,accessor xs) ,x) + xs))) + +(provide 'struct) +;;; struct.el ends here diff --git a/configs/shared/.emacs.d/wpc/symbol.el b/configs/shared/.emacs.d/wpc/symbol.el new file mode 100644 index 000000000000..9119b29470fd --- /dev/null +++ b/configs/shared/.emacs.d/wpc/symbol.el @@ -0,0 +1,43 @@ +;; symbol.el --- Library for working with symbols. -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Library for working with symbols. + +;;; Code: + +;; TODO: Why is ivy mode map everywhere? + +(require 'string) + +;; Symbols +(defun symbol/as-string (callback x) + "Treat the symbol, X, as a string while applying CALLBACK to it. +Coerce back to a symbol on the way out." + (->> x + #'symbol-name + callback + #'intern)) + +(defun symbol/to-string (x) + "Map `X' into a string." + (string/<-symbol x)) + +(defun symbol/hookify (x) + "Append \"-hook\" to X when X is a symbol." + (symbol/as-string #'string/hookify x)) + +(defun symbol/ensure-hookified (x) + "Ensure that X has \"-hook\" appended to it when X is a symbol." + (symbol/as-string #'string/ensure-hookified x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun symbol/instance? (x) + "Return t if X is a symbol." + (symbolp x)) + +(provide 'symbol) +;;; symbol.el ends here diff --git a/configs/shared/.emacs.d/wpc/themes.el b/configs/shared/.emacs.d/wpc/themes.el new file mode 100644 index 000000000000..e7109deaa968 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/themes.el @@ -0,0 +1,170 @@ +;;; themes.el --- Functions for working with my themes. -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: + + +;; Because I couldn't get cycle-themes to work, I'm writing my own version. +;; +;; Terminology: +;; - colorscheme: determines the colors used by syntax highlighting and other +;; Emacs UI elements. +;; - theme: Structural representation of a "theme" that includes colorscheme +;; (see above), font, wallpaper. "theme" is a superset of "colorscheme". +;; +;; Wishlist: +;; - TODO: Find a way to update the terminal (e.g. terminator) theme. +;; - TODO: Ensure terminal font is updated when Emacs font changes. +;; - TODO: Support a light theme. +;; - TODO: Support Rick & Morty theme. +;; - TODO: Support retro/arcade/80s theme. + +;;; Code: + +;; Dependencies +(require 'prelude) +(require 'alist) +(require 'symbol) +(require 'f) +(require 'wallpaper) +(require 'fonts) +(require 'cycle) +(require 'symbol) +(require 'random) +(require 'colorscheme) +(require 'dotted) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The theme struct couples a font, a wallpaper, and a colorschemes. +(cl-defstruct theme + font + wallpaper + colorscheme) + +(defvar themes/current nil + "Store the name of the currently enabled theme.") + +(defconst themes/themes + (list (dotted/new + "Forest" + (make-theme + :font "Operator Mono Light" + :wallpaper "forest_8k.jpg" + :colorscheme 'doom-peacock)) + (dotted/new + "Geometry" + (make-theme + :font "Input Mono Medium" + :wallpaper "geometric_4k.jpg" + :colorscheme 'doom-molokai)) + (dotted/new + "Ice" + (make-theme + :font "Go Mono" + :wallpaper "construction_paper_iceberg_4k.jpg" + :colorscheme 'doom-dracula)) + (dotted/new + "Lego Manhattan" + (make-theme + :font "Input Mono Medium" + :wallpaper "lego_manhattan.jpg" + :colorscheme 'base16-atelier-sulphurpool)) + (dotted/new + "Edison Lightbulb" + (make-theme + :font "Operator Mono Light" + :wallpaper "lightbulb_4k.jpg" + :colorscheme 'base16-atelier-cave)) + (dotted/new + "Galaxy" + (make-theme + :font "Source Code Pro" + :wallpaper "galaxy_4k.jpg" + :colorscheme 'doom-moonlight)) + (dotted/new + "Underwater" + (make-theme + :font "Go Mono" + ;; TODO: Change this wallpaper to an oceanic scene. + :wallpaper "galaxy_4k.jpg" + :colorscheme 'doom-solarized-dark)) + (dotted/new + "Fantasy Tree" + (make-theme + :font "Go Mono" + :wallpaper "fantasy_tree_4k.jpg" + :colorscheme 'doom-outrun-electric))) + "Predefined themes to suit my whims.") + +;; TODO: Choose between plural and singular names for Elisp modules. For +;; example, why have themes.el and colorscheme.el. I think singular is +;; preferable. +;; TODO: Decide between "message", "show", "print", "inspect" for naming +;; commands that output human-readable information to the "*Messages*" buffer. +;; TODO: Is there a idiomatic CL/Elisp way to print struct information? +(defun themes/print (name) + "Print a human-readable description of theme named NAME." + (let* ((theme (alist/get name themes/themes)) + (f (theme-font theme)) + (w (theme-wallpaper theme)) + (c (theme-colorscheme theme))) + (message (string/format + "[themes] Name: %s. Font: %s. Wallpaper: %s. Colorscheme: %s" + name f w c)))) + +;; TODO: Make this into a proper test. +(defun themes/debug () + "Print a human-readable description of theme named NAME." + (interactive) + (let ((theme (alist/get themes/current themes/themes))) + (prelude/assert (equal (theme-font theme) + (fonts/current))) + (prelude/assert (equal (theme-wallpaper theme) + (f-filename (wallpaper/current)))) + (prelude/assert (equal (theme-colorscheme theme) + (colorscheme/current))) + (message "[themes] Debug couldn't find any inconsistencies. All good!"))) + +;; TODO: Assert that all of the dependencies exist before attempting to load +;; theme. +;; TODO: Provide a friendlier way to define themes. +(defun themes/ivy-select () + "Use ivy to interactively load a theme." + (interactive) + (let* ((name (ivy-read "Theme: " (alist/keys themes/themes)))) + (message (string/format "name: %s" name)) + (themes/set name))) + +(defun themes/load (theme) + "Load the struct, THEME." + (colorscheme/disable-all) + (let* ((font (theme-font theme)) + (wallpaper (theme-wallpaper theme)) + (colorscheme (theme-colorscheme theme))) + (fonts/whitelist-set font) + (wallpaper/whitelist-set (f-join wallpaper/path-to-dir wallpaper)) + (colorscheme/whitelist-set colorscheme))) + +(defun themes/set (name) + "Set the currently enabled theme to the theme named NAME. +NAME needs to a key defined in `themes/themes'." + (prelude/assert (alist/has-key? name themes/themes)) + (themes/load (alist/get name themes/themes)) + (setq themes/current name)) + +(defun themes/print-current () + "Print the currently enabled theme." + (interactive) + (themes/print themes/current)) + +(defun themes/random () + "Return the name of a randomly selected theme in `themes/themes'." + (->> themes/themes + alist/keys + random/choice)) + +(provide 'themes) +;;; themes.el ends here diff --git a/configs/shared/.emacs.d/wpc/todo.el b/configs/shared/.emacs.d/wpc/todo.el new file mode 100644 index 000000000000..7369f01d82c1 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/todo.el @@ -0,0 +1,297 @@ +;;; todo.el --- Bespoke task management system -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Marriage of my personal task-management system, which I've been using for 18 +;; months and is a mixture of handwritten notes, iOS notes, and org-mode files, +;; with Emacs's famous `org-mode'. +;; +;; For me, I'd like a live, reactive state management system. I'd like +;; `org-mode' to be a nice way of rendering my TODOs, but I think the +;; relationship with `org-mode' ends there. +;; +;; Intended to supplement my org-mode workflow. +;; +;; Wish-list: +;; - Daily emails for standups +;; - Templates for commonly occurring tasks + +;; Dependencies +(require 'dash) +(require 'f) +(require 'macros) + +;;; Code: + +;; TODO: Classify habits as 'daily, 'weekly, 'monthly, 'yearly, 'event-driven + +;; TODO: Consider serving these values up to a React webpage in Chrome. + +;; TODO: Classify meetings as either 'recurrent or 'ad-hoc. + +;; TODO: Support sorting by `type'. + +;; TODO: Support work-queue idea for "Tomorrow's todos." + +;; TODO: Support macro to generate all possible predicates for todo types. + +;; TODO: Support export to org-mode file + +;; TODO: Support generic way to quickly render a list + +(defcustom todo/install-kbds? t + "When t, install the keybindings.") + +;; TODO: Add documentation. +(cl-defstruct todo type label) + +;; TODO: Consider keeping this in Dropbox. +;; TODO: Support whether or not the todo is done. +(defconst todo/org-file-path "~/Dropbox/org/today.org") + +;; TODO: Support remaining function for each type. +;; TODO: Support completed function for each type. + +(defun todo/completed? (x) + "Return t is `X' is marked complete." + (todo-complete x)) + +;; TODO: Prefer `new-{task,habit,meeting}'. + +(defun todo/completed (xs) + "Return the todo items in `XS' that are marked complete." + (->> xs + (-filter #'todo/completed?))) + +(defun todo/remaining (xs) + "Return the todo items in `XS' that are not marked complete." + (->> xs + (-reject #'todo/completed?))) + +(defun todo/task (label) + "Convenience function for creating a task todo with `LABEL'." + (make-todo + :type 'task + :label label)) + +(defun todo/meeting (label) + "Convenience function for creating a meeting todo with `LABEL'." + (make-todo + :type 'meeting + :label label)) + +(defun todo/habit (label) + "Convenience function for creating a habit todo with `LABEL'." + (make-todo + :type 'habit + :label label)) + +(defun todo/task? (x) + "Return t if `X' is a task." + (equal 'task (todo-type x))) + +(defun todo/habit? (x) + "Return t if `X' is a habit." + (equal 'habit (todo-type x))) + +(defun todo/meeting? (x) + "Return t if `X' is a meeting." + (equal 'meeting (todo-type x))) + +(defun todo/label (x) + "Return the label of `X'." + (todo-label x)) + +;; TODO: Support moving todos between todo/{today,tomorrow}. +;; TODO: Consider modelling todo/{today,tomorrow} as queues instead of lists so that I can +;; append cheaply. + +;; TODO: Find an Elisp date library. + +;; TODO: type-driven development of this habit tree. +;; TODO: Create this tree on a whiteboard first. +;; (defconst todo/habits +;; '(:beginning-of-month +;; '("Create habit template for current month" +;; "Post mortem of previous month") +;; :monday '("Jiu Jitsu") +;; :tuesday '("Jiu Jitsu") +;; :wednesday '("Jiu Jitsu") +;; :thursday '("Salsa class") +;; :friday '("Jiu Jitsu") +;; :saturday '("Borough market") +;; :sunday '("Shave") +;; :weekday '(:arrive-at-work +;; '("Breakfast" +;; "Coffee" +;; "Placeholder") +;; :before-lunch +;; '("Lock laptop" +;; "Placeholder") +;; :home->work +;; '("Michel Thomas Italian lessons")) +;; :daily '(:morning +;; '("Meditate" +;; "Stretch") +;; :))) + +;; overlay weekday with specific weekdays (e.g. BJJ is only on M,T,W) + +;; TODO: Extend the record type to support duration estimations for AFK, K +;; calculations. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Habits +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Should I be writing this in ReasonML and Haskell? + +(defconst todo/monthly-habit-challenge + "InterviewCake.com" + "The monthly habit challenge I do for fifteen minutes each day.") + +(defconst todo/daily-habits + (->> (list "Meditate" + todo/monthly-habit-challenge) + (-map #'todo/habit))) + +(defconst todo/first-of-the-month-stack + '("Create habit template for current month" + "Reserve two dinners in London for dates" + "Post mortem of previous month" + "Create monthly financial budget in Google Sheets") + "A stack of habits that I do at the beginning of each month.") + +(defconst todo/adhoc-habits + (->> (list/concat + todo/first-of-the-month-stack) + (-map #'todo/habit)) + "Habits that I have no classification for at the moment.") + +;; TODO: Model this as a function. +(defconst todo/habits + (list/concat todo/daily-habits + todo/adhoc-habits) + "My habits for today.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Meetings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Define "meeting". + +(defconst todo/daily-meetings + (->> '("Standup" + "Lunch") + (-map #'todo/meeting)) + "Daily, recurrent meetings.") + + +(defconst todo/day-of-week-meetings + '(:Monday '("Lunch") + :Tuesday '("Lunch") + :Wednesday '("Team Lunch") + :Thursday '("Lunch") + :Friday '("Lunch") + :Satuday '() + :Sunday '()) + "Meetings that occur depending on the current day of the week.") + +(parse-time-string "today") + +;; TODO: Support recurrent, non-daily meetings. + +(defconst todo/adhoc-meetings + (->> '("WSE Weekly Standup" + "Team Lunch" + "Karisa Explains It All") + (-map #'todo/meeting)) + "Non-recurrent meetings.") + +(defconst todo/meetings + (list/concat todo/daily-meetings + todo/adhoc-meetings) + "My meetings for today.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tasks +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst todo/tasks + (->> '("GetEmailCase" + "Async node workflow" + "Support C-c in EXWM" + "Post-its for bathroom mirror" + "Visit AtomicHabit.com/scorecard" + "Visit AtomicHabit.com/habitstacking" + "Create GraphViz for Carpe Diem cirriculum" + "Create CitC client for local browsing of CE codebase" + "Respond to SRE emails") + (-map #'todo/task))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Work queues (today, tomorrow, someday) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Generate standup documents from DONE items in the state. + +;; TODO: Learn how to create a gen-server style of live, reactive state. +;; TODO: This should probably be `defconst' and a reference to the live state. +(defconst todo/today + (list/concat + todo/habits + todo/meetings + todo/tasks)) + +(defconst todo/tomorrow + '()) + +(defconst todo/someday + '()) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; View functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun todo/to-org (xs) + "Map `XS' into a string with `org-mode' syntax." + ;; TODO: Create function to DRY this code up. + (let ((meetings (->> xs + (-filter #'todo/meeting?) + (-map (lambda (x) + (s-concat "** TODO " (todo/label x)))) + (s-join "\n"))) + (tasks (->> xs + (-filter #'todo/task?) + (-map (lambda (x) + (s-concat "** TODO " (todo/label x)))) + (s-join "\n"))) + (habits (->> xs + (-filter #'todo/habit?) + (-map (lambda (x) + (s-concat "** TODO " (todo/label x)))) + (s-join "\n")))) + (s-join "\n" (list + (s-concat "* Meetings\n" meetings) + (s-concat "* Tasks\n" tasks) + (s-concat "* Habits\n" habits))))) + +(defun todo/export-to-org (xs) + "Export `XS' to `todo/org-file-path'." + (f-write-text (->> xs + todo/to-org) + 'utf-8 + todo/org-file-path)) + +(defun todo/orgify-today () + "Exports today's todos to an org file." + (interactive) + (todo/export-to-org todo/today) + (alert (string/concat "Exported today's TODOs to: " todo/org-file-path))) + +(when todo/install-kbds? + (evil-leader/set-key + "to" #'todo/orgify-today)) + +(provide 'todo) +;;; todo.el ends here diff --git a/configs/shared/.emacs.d/wpc/tree.el b/configs/shared/.emacs.d/wpc/tree.el new file mode 100644 index 000000000000..43df4dc500e7 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/tree.el @@ -0,0 +1,193 @@ +;;; tree.el --- Working with Trees -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Some friendly functions that hopefully will make working with trees cheaper +;; and therefore more appealing! +;; +;; Tree terminology: +;; - leaf: node with zero children. +;; - root: node with zero parents. +;; - depth: measures a node's distance from the root node. This implies the +;; root node has a depth of zero. +;; - height: measures the longest traversal from a node to a leaf. This implies +;; that a leaf node has a height of zero. +;; - balanced? +;; +;; Tree variants: +;; - binary: the maximum number of children is two. +;; - binary search: the maximum number of children is two and left sub-trees are +;; lower in value than right sub-trees. +;; - rose: the number of children is variable. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'list) +(require 'set) +(require 'tuple) +(require 'series) +(require 'random) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct tree xs) + +(cl-defstruct node value children) + +(cl-defun tree/node (value &optional children) + "Create a node struct of VALUE with CHILDREN." + (make-node :value value + :children children)) + +(defun tree/reduce-breadth (acc f xs) + "Reduce over XS breadth-first applying F to each x and ACC (in that order). +Breadth-first traversals guarantee to find the shortest path in a graph. + They're typically more difficult to implement than DFTs and may also incur + higher memory costs on average than their depth-first counterparts.") + +;; TODO: Support :order as 'pre | 'in | 'post. +;; TODO: Troubleshoot why I need defensive (nil? node) check. +(defun tree/reduce-depth (acc f node) + "Reduce over NODE depth-first applying F to each NODE and ACC. +F is called with each NODE, ACC, and the current depth. +Depth-first traversals have the advantage of typically consuming less memory + than their breadth-first equivalents would have. They're also typically + easier to implement using recursion. This comes at the cost of not + guaranteeing to be able to find the shortest path in a graph." + (cl-labels ((do-reduce-depth + (acc f node depth) + (let ((acc-new (funcall f node acc depth))) + (if (or (maybe/nil? node) + (tree/leaf? node)) + acc-new + (list/reduce + acc-new + (lambda (node acc) + (tree/do-reduce-depth + acc + f + node + (number/inc depth))) + (node-children node)))))) + (do-reduce-depth acc f node 0))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tree/height (xs) + "Return the height of tree XS.") + +;; TODO: Troubleshoot why need for (nil? node). Similar misgiving +;; above. +(defun tree/leaf-depths (xs) + "Return a list of all of the depths of the leaf nodes in XS." + (list/reverse + (tree/reduce-depth + '() + (lambda (node acc depth) + (if (or (maybe/nil? node) + (tree/leaf? node)) + (list/cons depth acc) + acc)) + xs))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Generators +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider parameterizing height, forced min-max branching, random +;; distributions, etc. + +;; TODO: Bail out before stack overflowing by consider branching, current-depth. + +(cl-defun tree/random (&optional (value-fn (lambda (_) nil)) + (branching-factor 2)) + "Randomly generate a tree with BRANCHING-FACTOR using VALUE-FN to compute the +node values. VALUE-FN is called with the current-depth of the node. Useful for +generating test data. Warning this function can overflow the stack." + (cl-labels ((do-random + (d vf bf) + (make-node + :value (funcall vf d) + :children (->> (series/range 0 (number/dec bf)) + (list/map + (lambda (_) + (when (random/boolean?) + (do-random d vf bf)))))))) + (do-random 0 value-fn branching-factor))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tree/instance? (tree) + "Return t if TREE is a tree struct." + (node-p tree)) + +(defun tree/leaf? (node) + "Return t if NODE has no children." + (maybe/nil? (node-children node))) + +(defun tree/balanced? (n xs) + "Return t if the tree, XS, is balanced. +A tree is balanced if none of the differences between any two depths of two leaf + nodes in XS is greater than N." + (> n (->> xs + tree/leaf-depths + set/from-list + set/count + number/dec))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst tree/enable-testing? t + "When t, test suite runs.") + +;; TODO: Create set of macros for a proper test suite including: +;; - describe (arbitrarily nestable) +;; - it (arbitrarily nestable) +;; - line numbers for errors +;; - accumulated output for synopsis +;; - do we want describe *and* it? Why not a generic label that works for both? +(when tree/enable-testing? + (let ((tree-a (tree/node 1 + (list (tree/node 2 + (list (tree/node 5) + (tree/node 6))) + (tree/node 3 + (list (tree/node 7) + (tree/node 8))) + (tree/node 4 + (list (tree/node 9) + (tree/node 10)))))) + (tree-b (tree/node 1 + (list (tree/node 2 + (list (tree/node 5) + (tree/node 6))) + (tree/node 3) + (tree/node 4 + (list (tree/node 9) + (tree/node 10))))))) + ;; instance? + (prelude/assert (tree/instance? tree-a)) + (prelude/assert (tree/instance? tree-b)) + (prelude/refute (tree/instance? '(1 2 3))) + (prelude/refute (tree/instance? "oak")) + ;; balanced? + (prelude/assert (tree/balanced? 1 tree-a)) + (prelude/refute (tree/balanced? 1 tree-b)) + (message "Tests pass!"))) + +(provide 'tree) +;;; tree.el ends here diff --git a/configs/shared/.emacs.d/wpc/tuple.el b/configs/shared/.emacs.d/wpc/tuple.el new file mode 100644 index 000000000000..ccebf7299abd --- /dev/null +++ b/configs/shared/.emacs.d/wpc/tuple.el @@ -0,0 +1,86 @@ +;;; tuple.el --- Tuple API for Elisp -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Work with cons cells with two elements with a familiar API for those who have +;; worked with tuples before. + +;;; Code: + +(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/configs/shared/.emacs.d/wpc/vector.el b/configs/shared/.emacs.d/wpc/vector.el new file mode 100644 index 000000000000..4fc6ffe911d3 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/vector.el @@ -0,0 +1,59 @@ +;;; vector.el --- Working with Elisp's Vector data type -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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: + +;; TODO: Consider supporting an alias named tuple for vector. + +;; TODO: Consider labelling variadic functions like `vector/concat*' +;; vs. `vector/concat'. +(defun vector/concat (&rest args) + "Return a new vector composed of all vectors in `ARGS'." + (apply #'vconcat args)) + +;; TODO: Here's a sketch of a protocol macro being consumed. +;; (definstance monoid vector +;; :empty (lambda () [])) + +(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)) + +;; TODO: Decide between "remove" and "delete" as the appropriate verbs. +;; TODO: Implement this. +;; (defun vector/delete (i xs) +;; "Remove the element at `I' in `XS'.") + +(provide 'vector) +;;; vector.el ends here diff --git a/configs/shared/.emacs.d/wpc/wallpaper.el b/configs/shared/.emacs.d/wpc/wallpaper.el new file mode 100644 index 000000000000..63548964b76d --- /dev/null +++ b/configs/shared/.emacs.d/wpc/wallpaper.el @@ -0,0 +1,84 @@ +;;; wallpaper.el --- Control Linux desktop wallpaper -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Functions for setting desktop wallpaper. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'fs) +(require 'cycle) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom wallpaper/keybindings? t + "If non-nil, install the keybindings.") + +(defcustom wallpaper/path-to-dir + (f-expand "~/.local/share/wallpaper") + "Path to the images that will be used as the wallpaper.") + +(defconst wallpaper/whitelist + (cycle/from-list + (fs/ls wallpaper/path-to-dir t)) + "My preferred computer wallpapers.") + +(defun wallpaper/set (path) + "Set computer wallpaper to image at `PATH' using `feh` under-the-hood." + (shell-command (string/format "feh --bg-scale --no-fehbg %s" path))) + +(defun wallpaper/whitelist-set (wallpaper) + "Focuses the WALLPAPER in the `wallpaper/whitelist' cycle." + (cycle/focus (lambda (x) (equal x wallpaper)) wallpaper/whitelist) + (wallpaper/set (wallpaper/current))) + +(defun wallpaper/next () + "Cycles to the next wallpaper." + (interactive) + (let ((wallpaper (cycle/next wallpaper/whitelist))) + (wallpaper/set wallpaper) + (message (string/format "Active wallpaper: %s" (f-filename wallpaper))))) + +(defun wallpaper/prev () + "Cycles to the previous wallpaper." + (interactive) + (let ((wallpaper (cycle/prev wallpaper/whitelist))) + (wallpaper/set wallpaper) + (message (string/format "Active wallpaper: %s" (f-filename wallpaper))))) + +;; TODO: Define a macro that handles, next, prev, select, current for working +;; with cycles, since this is a common pattern. + +(defun wallpaper/print-current () + "Message the currently enabled wallpaper." + (interactive) + (message + (cycle/current wallpaper/whitelist))) + +(defun wallpaper/current () + "Return the currently enabled wallpaper." + (cycle/current wallpaper/whitelist)) + +(defun wallpaper/ivy-select () + "Use `counsel' to select and set a wallpaper from the `wallpaper/whitelist'." + (interactive) + (wallpaper/whitelist-set + (ivy-read "Select wallpaper: " (cycle/to-list wallpaper/whitelist)))) + +;; TODO: Create macro-based module system that will auto-namespace functions, +;; constants, etc. with the filename like `wallpaper'. + +(when wallpaper/keybindings? + (evil-leader/set-key + "Fw" #'wallpaper/next + "Pw" #'wallpaper/prev)) + +(provide 'wallpaper) +;;; wallpaper.el ends here diff --git a/configs/shared/.emacs.d/wpc/window-manager.el b/configs/shared/.emacs.d/wpc/window-manager.el new file mode 100644 index 000000000000..76586303dc68 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/window-manager.el @@ -0,0 +1,672 @@ +;;; window-manager.el --- Functions to ease my transition to EXWM. -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; It's possible that this module will be entirely temporary. Creating it after +;; switching to EXWM to help transfer my reliance from i3 to EXWM. +;; +;; Wish list: +;; - TODO: Support different startup commands and layouts depending on laptop or +;; desktop. +;; - TODO: Support a Music named-workspace. + +;;; Code: + +(require 'prelude) +(require 'string) +(require 'cycle) +(require 'set) +(require 'kbd) +(require 'ivy-helpers) +(require 'display) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Associate `window-purpose' window-layouts with each of these named +;; workspaces. + +;; TODO: Associate KBDs for each of these named-layouts. + +;; TODO: Decide between window-manager, exwm, or some other namespace. + +;; TODO: Support (cycle/from-list '(current previous)) to toggle back and forth +;; between most recent workspace. + +;; TODO: Support ad hoc cycle for loading a few workspaces that can be cycled +;; between. (cycle/from-list '("Project" "Workspace")) + +;; TODO: Consider supporting a workspace for Racket, Clojure, Common Lisp, +;; Haskell, Elixir, and a few other languages. These could behave very similarly +;; to repl.it, which I've wanted to have locally for awhile now. + +;; TODO: Support MRU cache of workspaces for easily switching back-and-forth +;; between workspaces. + +(cl-defstruct exwm/named-workspace + label + index + kbd) + +(defconst exwm/install-workspace-kbds? t + "When t, install the keybindings to switch between named-workspaces.") + +;; TODO: Consume `cache/touch' after changing workspaces. Use this to enable +;; cycling through workspaces. + +(defconst exwm/named-workspaces + (list (make-exwm/named-workspace + :label "Web surfing" + :index 0 + :kbd "c") + (make-exwm/named-workspace + :label "Project" + :index 1 + :kbd "p") + (make-exwm/named-workspace + :label "Dotfiles" + :index 2 + :kbd "d") + (make-exwm/named-workspace + :label "Scratch" + :index 3 + :kbd "s") + (make-exwm/named-workspace + :label "Terminal" + :index 4 + :kbd "t") + (make-exwm/named-workspace + :label "Todos" + :index 5 + :kbd "o") + (make-exwm/named-workspace + :label "Chatter" + :index 6 + :kbd "h") + (make-exwm/named-workspace + :label "Work" + :index 7 + :kbd "w")) + "List of `exwm/named-workspace' structs.") + +;; Assert that no two workspaces share KBDs. +(prelude/assert (= (list/length exwm/named-workspaces) + (->> exwm/named-workspaces + (list/map #'exwm/named-workspace-kbd) + set/from-list + set/count))) + +(defun window-manager/alert (x) + "Message X with a structured format." + (alert (string/concat "[exwm] " x))) + +;; Use Emacs as my primary window manager. +(use-package exwm + :config + (require 'exwm-config) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Multiple Displays + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (require 'exwm-randr) + (exwm-randr-enable) + ;; TODO: Consider generating this plist. + ;; TODO: Replace integer index values with their named workspace equivalents. + (setq exwm-randr-workspace-monitor-plist (list 0 display/4k + 1 display/primary)) + + (evil-set-initial-state 'exwm-mode 'emacs) + (ido-mode 1) + (exwm-config-ido) + (setq exwm-workspace-number + (list/length exwm/named-workspaces)) + ;; EXWM supports "line-mode" and "char-mode". + ;; + ;; Note: It appears that calling `exwm-input-set-key' works if it's called + ;; during startup. Once a session has started, it seems like this function is + ;; significantly less useful. Is this a bug? + ;; + ;; Glossary: + ;; - char-mode: All keystrokes except `exwm' global ones are passed to the + ;; application. + ;; - line-mode: + ;; + ;; `exwm-input-global-keys' = {line,char}-mode; can also call `exwm-input-set-key' + ;; `exwm-mode-map' = line-mode + ;; `???' = char-mode. Is there a mode-map for this? + ;; + ;; TODO: What is `exwm-input-prefix-keys'? + ;; TODO: Once I get `exwm-input-global-keys' functions, drop support for + ;; `wpc/kbds'. + (let ((kbds `( + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Window sizing + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (:key "C-M-=" :fn balance-windows) + ;; TODO: Make sure these don't interfere with LISP KBDs. + (:key "C-M-j" :fn shrink-window) + (:key "C-M-k" :fn enlarge-window) + (:key "C-M-h" :fn shrink-window-horizontally) + (:key "C-M-l" :fn enlarge-window-horizontally) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Window traversing + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (:key "M-h" :fn windmove-left) + (:key "M-j" :fn windmove-down) + (:key "M-k" :fn windmove-up) + (:key "M-l" :fn windmove-right) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Window splitting + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (:key "M-\\" :fn evil-window-vsplit) + (:key "M--" :fn evil-window-split) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Window deletion + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (:key "M-q" :fn delete-window) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Miscellaneous + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (:key "M-:" :fn eval-expression) + (:key "M-SPC" :fn window-manager/apps) + (:key "M-x" :fn counsel-M-x) + (:key "<M-tab>" :fn exwm/next-workspace) + (:key "<M-S-iso-lefttab>" :fn exwm/prev-workspace) + (:key "<M-iso-lefttab>" :fn exwm/prev-workspace) + ;; <M-escape> doesn't work in X11 windows. + (:key "<M-escape>" :fn exwm/ivy-switch) + (:key "C-M-\\" :fn ivy-pass) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; REPLs + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (:key ,(kbd/raw 'x11 "r") :fn exwm/ivy-find-or-create-repl) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Workspaces + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; NOTE: Here I need to generate lowercase and uppercase + ;; variants of each because my Ergodox is sending capitalized + ;; variants of the keycodes to EXWM. + (:key ,(kbd/raw 'workspace "l") :fn window-manager/logout) + (:key ,(kbd/raw 'workspace "L") :fn window-manager/logout) + (:key ,(kbd/raw 'workspace "i") :fn exwm/toggle-mode) + (:key ,(kbd/raw 'workspace "I") :fn exwm/toggle-mode)))) + (setq exwm-input-global-keys + (->> kbds + (-map (lambda (plist) + `(,(kbd (plist-get plist :key)) . ,(plist-get plist :fn))))))) + (setq exwm-input-simulation-keys + ;; TODO: Consider supporting M-d and other readline style KBDs. + '(([?\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]) + ;; TODO: Assess whether or not this is a good idea. + ;; TODO: Ensure C-c copies. + ([?\C-c] . [C-c]))) + (exwm-enable)) + +;; TODO: Package workspace management in another module. + +;; Here is the code required to allow EXWM to cycle workspaces. +(defconst exwm/workspaces + (->> exwm/named-workspaces + cycle/from-list) + "Cycle of the my EXWM workspaces.") + +(prelude/assert + (= exwm-workspace-number + (list/length exwm/named-workspaces))) + +(defun exwm/next-workspace () + "Cycle forwards to the next workspace." + (interactive) + (exwm-workspace-switch + (exwm/named-workspace-index (cycle/next exwm/workspaces))) + (window-manager/alert + (string/concat + "Current workspace: " + (exwm/named-workspace-label (cycle/current exwm/workspaces))))) + +(defun exwm/prev-workspace () + "Cycle backwards to the previous workspace." + (interactive) + (exwm-workspace-switch + (exwm/named-workspace-index (cycle/prev exwm/workspaces))) + (window-manager/alert + (string/concat + "Current workspace: " + (exwm/named-workspace-label (cycle/current exwm/workspaces))))) + +;; TODO: Create friendlier API for working with EXWM. + +;; Here is the code required to toggle EXWM's modes. +(defun exwm/line-mode () + "Switch exwm to line-mode." + (call-interactively #'exwm-input-grab-keyboard) + (window-manager/alert "Switched to line-mode")) + +(defun exwm/char-mode () + "Switch exwm to char-mode." + (call-interactively #'exwm-input-release-keyboard) + (window-manager/alert "Switched to char-mode")) + +(defconst exwm/modes + (cycle/from-list (list #'exwm/char-mode + #'exwm/line-mode)) + "Functions to switch exwm modes.") + +(defun exwm/toggle-mode () + "Switch between line- and char- mode." + (interactive) + (with-current-buffer (window-buffer) + (when (eq major-mode 'exwm-mode) + (funcall (cycle/next exwm/modes))))) + +;; Ensure exwm apps open in char-mode. +(add-hook + 'exwm-manage-finish-hook + #'exwm/char-mode) + +;; Interface to the Linux password manager +(use-package ivy-pass) + +(defconst exwm/preferred-terminal "terminator" + "My preferred terminal.") + +(defconst exwm/preferred-browser "google-chrome" + "My preferred web browser.") + +(defun exwm/browser-open (&optional url) + "Opens the URL in `exwm/preferred-browser'." + (exwm/open + (string/format "%s %s" exwm/preferred-browser url) + :buffer-name (string/format "*%s*<%s>" exwm/preferred-browser url) + :process-name url)) + +;; TODO: Consider storing local state of all processes started with this command +;; for some nice ways to cycle through existing terminals, etc. +(defun exwm/terminal-open (cmd) + "Call CMD using `exwm/preferred-terminal'." + (exwm/open (string/format + "%s --command '%s'" + exwm/preferred-terminal + cmd) + :buffer-name (string/format "*%s*<%s>" exwm/preferred-terminal cmd) + :process-name cmd)) + +;; TODO: Create a KBD that calls the `C-x b<Enter>' I call often. +;; TODO: Consider auto-generating KBDs for spawning these using the first +;; character in their name. Also assert that none of the generated keybindings +;; will clash with one another. +(defconst exwm/repls + '(("python" . (lambda () (exwm/terminal-open "python3"))) + ("zsh" . (lambda () (exwm/terminal-open "zsh"))) + ("fish" . (lambda () (exwm/terminal-open "fish"))) + ("nix" . (lambda () (exwm/terminal-open "nix repl"))) + ("racket" . racket-repl) + ;; NOTE: `ielm' as-is is a find-or-create operation. + ("elisp" . ielm)) + "Mapping of REPL labels to the commands needed to initialize those REPLs.") + +;; NOTE: Some of these commands split the window already. Some of these +;; commands find-or-create already. +;; +;; Find-or-create: +;; +---+---+ +;; | Y | N | +;; +---+---+ +;; python | | x | +;; zsh | | x | +;; racket | x | | +;; elisp | x | | +;; +---+---+ +;; +;; Split: +;; +---+---+ +;; | Y | N | +;; +---+---+ +;; python | | x | +;; zsh | | x | +;; racket | x | | +;; elisp | | x | +;; +---+---+ + +;; - Split: +;; - racket +(defun exwm/ivy-find-or-create-repl () + "Select a type of REPL using `ivy' and then find-or-create it." + (interactive) + (ivy-helpers/kv "REPLs: " + exwm/repls + (lambda (_ v) + (funcall v)))) + +;; KBDs to quickly open X11 applications. +(general-define-key + ;; TODO: Eventually switch this to a find-or-create operation. In general, I + ;; shouldn't need multiple instances of `python3` REPLs. + ;; TODO: Consider coupling these KBDs with the `exwm/ivy-find-or-create-repl' + ;; functionality defined above. + (kbd/raw 'x11 "n") (lambda () + (interactive) + (exwm/terminal-open "nix repl")) + (kbd/raw 'x11 "p") (lambda () + (interactive) + (exwm/terminal-open "python3")) + (kbd/raw 'x11 "t") (lambda () + (interactive) + (exwm/terminal-open "zsh")) + (kbd/raw 'x11 "c") (lambda () + (interactive) + (exwm/open exwm/preferred-browser))) + +;; TODO: Support searching all "launchable" applications like OSX's Spotlight. +;; TODO: Model this as key-value pairs. +(defconst window-manager/applications + (list "google-chrome --new-window --app=https://chat.google.com" + "google-chrome --new-window --app=https://calendar.google.com" + "google-chrome --new-window --app=https://gmail.com" + "google-chrome --new-window --app=https://web.telegram.org" + "google-chrome --new-window --app=https://teknql.slack.com" + "google-chrome --new-window --app=https://web.whatsapp.com" + "google-chrome --new-window --app=https://irccloud.com" + exwm/preferred-browser + exwm/preferred-terminal) + "Applications that I commonly use. +These are the types of items that would usually appear in dmenu.") + +;; TODO: Consider replacing the `ivy-read' call with something like `hydra' that +;; can provide a small mode for accepting user-input. +;; TODO: Put this somewhere more diliberate. + +(defun window-manager/screenshot () + "Choose between \"Local\" and \"Google\" screenshots." + (interactive) + (pcase (ivy-read "Type of screenshot: " '("Google" "Local")) + ;; TODO: Drop `zsh -i -c' dependency and reimplement in Elisp. + ("Google" (shell-command "zsh -i -c snipit")) + ("Local" (shell-command "zsh -i -c screenshot")))) + +;; TODO: Configure the environment variables for xsecurelock so that the font is +;; smaller, different, and the glinux wallpaper doesn't show. +;; - XSECURELOCK_FONT="InputMono-Black 10" +;; - XSECURE_SAVER="" +;; - XSECURE_LOGO_IMAGE="" +;; Maybe just create a ~/.xsecurelockrc +;; TODO: Is there a shell-command API that accepts an alist and serializes it +;; into variables to pass to the shell command? +(defconst window-manager/xsecurelock + "/usr/share/goobuntu-desktop-files/xsecurelock.sh" + "Path to the proper xsecurelock executable. +The other path to xsecurelock is /usr/bin/xsecurelock, which works fine, but it +is not optimized for Goobuntu devices. Goobuntu attempts to check a user's +password using the network. When there is no network connection available, the +login attempts fail with an \"unknown error\", which isn't very helpful. To +avoid this, prefer the goobuntu wrapper around xsecurelock when on a goobuntu +device. This all relates to PAM (i.e. pluggable authentication modules).") + +(defun window-manager/logout () + "Prompt the user for options for logging out, shutting down, etc. + +The following options are supported: +- Lock +- Logout +- Suspend +- Hibernate +- Reboot +- Shutdown + +Ivy is used to capture the user's input." + (interactive) + (let* ((name->cmd `(("Lock" . ,window-manager/xsecurelock) + ("Logout" . "sudo systemctl stop lightdm") + ("Suspend" . ,(string/concat + window-manager/xsecurelock + " && systemctl suspend")) + ("Hibernate" . ,(string/concat + window-manager/xsecurelock + " && systemctl hibernate")) + ("Reboot" . "systemctl reboot") + ("Shutdown" . "systemctl poweroff")))) + (funcall + (lambda () + (shell-command + (alist/get (ivy-read "System: " (alist/keys name->cmd)) + name->cmd)))))) + +(cl-defun exwm/open (command &key + (process-name command) + (buffer-name command)) + "Open COMMAND, which should be an X11 window." + (start-process-shell-command process-name buffer-name command)) + +(cl-defun window-manager/execute-from-counsel (&key prompt list) + "Display a counsel menu of `LIST' with `PROMPT' and pipe the output through +`start-process-shell-command'." + (let ((x (ivy-read prompt list))) + (exwm/open + x + :buffer-name (string/format "*exwm/open*<%s>" x) + :process-name x))) + +(defun window-manager/apps () + "Open commonly used applications from counsel." + (interactive) + (window-manager/execute-from-counsel + :prompt "Application: " + :list window-manager/applications)) + +;; TODO: KBDs for {enlarge,shrink}-window + +(use-package window-purpose + :config + (purpose-mode 1) + + ;; call `purpose-compile-user-configuration' after making changes. + + ;; Example configuration: + ;; (setq purpose-user-mode-purposes + ;; '((term-mode . terminal) + ;; (shell-mode . terminal) + ;; (ansi-term-mode . terminal) + ;; (tuareg-mode . coding) + ;; (compilation-mode . messages))) + + (setq purpose-user-mode-purposes + '( + ;; Overview: + ;; - coding: buffers for editing code + ;; - repl: interactive REPLs -- including terminals + ;; - documentation: help buffers and language documentation + ;; + ;; Ideas: + ;; - racket and racket-project and two different variants where the + ;; project version includes a file browser. + ;; - google3 workspace + + ;; Racket + (racket-mode . coding) + (racket-repl-mode . repl) + (racket-describe-mode . documentation) + ;; Elisp + (emacs-lisp-mode . coding) + (ielm-mode . repl) + ;; Python + ;; (python-mode . coding) + ;; (emacs-lisp-mode . coding) + )) + + (macros/comment + (purpose-compile-user-configuration)) + + ;; (setq purpose-user-name-purposes + ;; `(("name" . "purpose") + ;; ("name" . "purpose"))) + + ;; (setq purpose-user-regexp-purposes + ;; `(("regexp" . "purpose") + ;; ("regexp" . "purpose"))) + + ) + +(defun exwm/label->index (label workspaces) + "Return the index of the workspace in WORKSPACES named LABEL." + (let ((workspace (->> workspaces + (list/find + (lambda (x) + (equal label + (exwm/named-workspace-label x))))))) + (if (prelude/set? workspace) + (exwm/named-workspace-index workspace) + (error (string/concat "No workspace found for label: " label))))) + +(defun exwm/register-kbd (workspace) + "Registers a keybinding for WORKSPACE struct. +Currently using super- as the prefix for switching workspaces." + (let ((handler (lambda () + (interactive) + (exwm/switch (exwm/named-workspace-label workspace)))) + (key (exwm/named-workspace-kbd workspace))) + (exwm-input-set-key + (kbd/for 'workspace key) + handler) + ;; Note: We need to capitalize the KBD here because of the signals that my + ;; Ergodox is sending Emacs on my desktop. + (exwm-input-set-key + (kbd/for 'workspace (s-capitalize key)) + handler))) + +(defun exwm/switch (label) + "Switch to a named workspaces using LABEL." + (cycle/focus + (lambda (x) + (equal label + (exwm/named-workspace-label x))) + exwm/workspaces) + (exwm-workspace-switch + (exwm/named-workspace-index (cycle/current exwm/workspaces))) + (window-manager/alert + (string/concat "Switched to: " label))) + +(defun exwm/ivy-switch () + "Use ivy to switched between named workspaces." + (interactive) + (ivy-read + "Workspace: " + (->> exwm/named-workspaces + (list/map #'exwm/named-workspace-label)) + :action #'exwm/switch)) + +(when exwm/install-workspace-kbds? + (progn + (->> exwm/named-workspaces + (list/map #'exwm/register-kbd)) + (window-manager/alert "Registered workspace KBDs!"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Startup Applications in `exwm/named-workspaces' +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(add-hook + 'exwm-init-hook + (lambda () + ;; TODO: Refactor this into a bigger solution where the named-workspaces are + ;; coupled to their startup commands. Expedience wins this time. + (progn + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Chrome + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Web surfing") + ;; make sure this blocks. + ;; TODO: Support shell-cmd.el that has `shell-cmd/{sync,async}'. + ;; (call-process-shell-command "google-chrome") + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Project + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Project") + (find-file constants/current-project)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Scratch + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Scratch") + (switch-to-buffer "*scratch*")) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Terminal + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Terminal") + ;; TODO: Why does "gnome-terminal" work but not "terminator"? + ;; (call-process-shell-command "gnome-terminal") + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Todos + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Todos") + (find-file "~/Dropbox/org/today.org") + (wpc/evil-window-vsplit-right) + (find-file "~/Dropbox/org/emacs.org")) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Dotfiles + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Dotfiles") + ;; TODO: Support (dotfiles/find-file "window-manager.el")? + (find-file "~/Dropbox/dotfiles/configs/shared/.emacs.d/init.el") + (wpc/evil-window-vsplit-right) + (find-file + "~/Dropbox/dotfiles/configs/shared/.emacs.d/wpc/window-manager.el")) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Chatter + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Chatter") + ;; TODO: Support the following chat applications: + ;; - Slack teknql + ;; - irccloud.net + ;; - web.whatsapp.com + ;; - Telegram + ;; NOTE: Perhaps all of these should be borderless. + ;; (call-process-shell-command "terminator") + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Work + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Work") + ;; TODO: Support opening the following in chrome: + ;; - calendar + ;; - gmail + ;; - chat (in a horizontal split) + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Reset to default + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (exwm/switch "Dotfiles") + ))) + +(provide 'window-manager) +;;; window-manager.el ends here diff --git a/configs/shared/.emacs.d/wpc/window.el b/configs/shared/.emacs.d/wpc/window.el new file mode 100644 index 000000000000..132156bc4465 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/window.el @@ -0,0 +1,37 @@ +;;; window.el --- Working with Emacs windows -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Utilities to make CRUDing windows in Emacs easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun window/find (name) + "Find a window by the NAME of the buffer it's hosting." + (let ((buffer (get-buffer name))) + (if (maybe/some? buffer) + (get-buffer-window buffer) + nil))) + +;; TODO: Find a way to incorporate these into function documentation. +(macros/comment + (window/find "*scratch*")) + +(defun window/delete (window) + "Delete the WINDOW reference." + (delete-window window)) + +(provide 'window) +;;; window.el ends here diff --git a/configs/shared/.emacs.d/wpc/wpgtk.el b/configs/shared/.emacs.d/wpc/wpgtk.el new file mode 100644 index 000000000000..432d82884399 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/wpgtk.el @@ -0,0 +1,45 @@ +;; wpgtk.el -- A base16 colorscheme template for wpgtk. + +;;; Commentary: + +;;; Authors: +;; Template: William Carroll <wpcarro@gmail.com> + +;;; Code: + +(require 'base16-theme) +(require 'colorscheme) + +(defvar base16-wpgtk-colors + '(:base00 "#31213f" + :base01 "#E29B61" + :base02 "#E8C35F" + :base03 "#565B87" + :base04 "#A56785" + :base05 "#20A89E" + :base06 "#3CC2B5" + :base07 "#8de0e1" + :base08 "#629c9d" + :base09 "#E29B61" + :base0A "#E8C35F" + :base0B "#565B87" + :base0C "#A56785" + :base0D "#20A89E" + :base0E "#3CC2B5" + :base0F "#8de0e1") + "All colors for Base16 wpgtk are defined here.") + +;; Define the theme +(deftheme base16-wpgtk) + +;; Add all the faces to the theme +(base16-theme-define 'base16-wpgtk base16-wpgtk-colors) + +;; Mark the theme as provided +(provide-theme 'base16-wpgtk) + +(macros/comment + (colorscheme/set 'base16-wpgtk)) + +(provide 'wpgtk) +;;; wpgtk.el ends here diff --git a/configs/shared/.emacs.d/wpc/zle.el b/configs/shared/.emacs.d/wpc/zle.el new file mode 100644 index 000000000000..1b01da938456 --- /dev/null +++ b/configs/shared/.emacs.d/wpc/zle.el @@ -0,0 +1,90 @@ +;;; zle.el --- Functions to mimmick my ZLE KBDs -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; 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. +;; +;; TODO: Consider adding (general-unbind 'insert "C-v") herein. + +;;; 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))) + (bind-keys :map map + ("C-j" . zle/subshell) + ("C-v" . zle/variable) + ("C-M--" . zle/dash-dash) + ("M-'" . zle/single-quote) + ("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 |