diff options
author | William Carroll <wpcarro@gmail.com> | 2020-01-30T16·00+0000 |
---|---|---|
committer | William Carroll <wpcarro@gmail.com> | 2020-01-30T16·00+0000 |
commit | 578ed1ba98510058cf48f897a9bf4e3391684120 (patch) | |
tree | 5eadabd3d232151c75ac2424eaa8771ab25e927e /emacs/.emacs.d/wpc | |
parent | 3684adf23f847114e1beeaab162398628ba571aa (diff) |
Move move .emacs.d out of configs/shared
Moving all of my Emacs-related files into their own directory at the root of this repository.
Diffstat (limited to 'emacs/.emacs.d/wpc')
102 files changed, 11033 insertions, 0 deletions
diff --git a/emacs/.emacs.d/wpc/alist.el b/emacs/.emacs.d/wpc/alist.el new file mode 100644 index 000000000000..f23109ce6a38 --- /dev/null +++ b/emacs/.emacs.d/wpc/alist.el @@ -0,0 +1,277 @@ +;;; 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)'. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst alist/enable-tests? t + "When t, run the test suite.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support a variadic version of this to easily construct alists. +(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)) + +;; TODO: Should I support `alist/find-key' and `alist/find-value' variants? +(defun alist/find (p xs) + "Apply a predicate fn, P, to each key and value in XS and return the key of + the first element that returns t." + (let ((result (list/find (lambda (x) (funcall p (car x) (cdr x))) xs))) + (if result + (car result) + nil))) + +(defun alist/map-keys (f xs) + "Call F on the values in XS, returning a new alist." + (list/map (lambda (x) + `(,(funcall f (car x)) . ,(cdr x))) + xs)) + +(defun alist/map-values (f xs) + "Call F on the values in XS, returning a new alist." + (list/map (lambda (x) + `(,(car x) . ,(funcall f (cdr x)))) + xs)) + +(defun 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 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when alist/enable-tests? + (prelude/assert + (equal '((2 . one) + (3 . two)) + (alist/map-keys #'1+ + '((1 . one) + (2 . two))))) + (prelude/assert + (equal '((one . 2) + (two . 3)) + (alist/map-values #'1+ + '((one . 1) + (two . 2)))))) + + +;; TODO: Support test cases for the entire API. + +(provide 'alist) +;;; alist.el ends here diff --git a/emacs/.emacs.d/wpc/bag.el b/emacs/.emacs.d/wpc/bag.el new file mode 100644 index 000000000000..c9511b18e737 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/bills.el b/emacs/.emacs.d/wpc/bills.el new file mode 100644 index 000000000000..fbdeb9d0f820 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/bookmark.el b/emacs/.emacs.d/wpc/bookmark.el new file mode 100644 index 000000000000..734ddaa13a27 --- /dev/null +++ b/emacs/.emacs.d/wpc/bookmark.el @@ -0,0 +1,145 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'buffer) +(require 'list) +(require 'string) +(require 'set) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bookmark label path kbd) + +(defconst bookmark/install-kbds? t + "When t, install keybindings.") + +;; 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 + (make-bookmark :label "depot" + :path "~/depot" + :kbd "t") + (make-bookmark :label "org" + :path "~/Dropbox/org" + :kbd "o") + (make-bookmark :label "universe" + :path "~/universe" + :kbd "m") + (make-bookmark :label "dotfiles" + :path "~/dotfiles" + :kbd "d") + (make-bookmark :label "current project" + :path constants/current-project + :kbd "p")) + "List of registered bookmarks.") + +(defun bookmark/from-label (label) + "Return the bookmark with LABEL or nil." + (->> bookmark/whitelist + (list/find (lambda (b) (equal label (bookmark-label b)))))) + +(defun bookmark/magit-status () + "Use ivy to select a bookmark and jump to its `magit-status' buffer." + (interactive) + (let ((labels (set/new "dotfiles" "universe" "depot")) + (all-labels (->> bookmark/whitelist + (list/map (>> bookmark-label)) + set/from-list))) + (prelude/assert (set/subset? labels all-labels)) + (ivy-read "Repository: " + (set/to-list labels) + :require-match t + :action (lambda (label) + (->> label + bookmark/from-label + bookmark-path + magit-status))))) + +;; 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/open (bookmark/from-label label))))) + +(when bookmark/install-kbds? + (general-define-key + :prefix "<SPC>" + :states '(normal) + "jj" #'bookmark/ivy-open) + (->> bookmark/whitelist + (list/map + (lambda (b) + (general-define-key + :prefix "<SPC>" + :states '(normal) + (string/concat "j" (bookmark-kbd b)) + ;; TODO: Consider `cl-labels' so `which-key' minibuffer is more + ;; helpful. + (lambda () (interactive) (bookmark/open b)))))) + (general-define-key + :states '(normal) + :prefix "<SPC>" + "gS" #'bookmark/magit-status)) + +(provide 'bookmark) +;;; bookmark.el ends here diff --git a/emacs/.emacs.d/wpc/buffer.el b/emacs/.emacs.d/wpc/buffer.el new file mode 100644 index 000000000000..d388818e58a4 --- /dev/null +++ b/emacs/.emacs.d/wpc/buffer.el @@ -0,0 +1,198 @@ +;;; 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. +;; +;; I support three ways for switching between what I'm calling "source code +;; buffers": +;; 1. Toggling previous: <SPC><SPC> +;; 2. Using `ivy-read': <SPC>b +;; TODO: These obscure evil KBDs. Maybe a hydra definition would be best? +;; 3. Cycling (forwards/backwards): C-f, C-b + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'maybe) +(require 'set) +(require 'cycle) +(require 'struct) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst buffer/enable-tests? t + "When t, run the test suite.") + +(defconst buffer/install-kbds? t + "When t, install the keybindings defined herein.") + +(defconst buffer/source-code-blacklist + (set/new 'dired-mode + 'erc-mode + 'magit-status-mode + 'magit-process-mode + 'magit-log-mode + 'org-mode + 'fundamental-mode) + "A blacklist of major-modes to ignore for listing source code buffers.") + +(defconst buffer/source-code-timeout 2 + "Number of seconds to wait before invalidating the cycle.") + +(cl-defstruct source-code-cycle cycle last-called) + +(defun buffer/emacs-generated? (name) + "Return t if buffer, NAME, is an Emacs-generated buffer. +Some buffers are Emacs-generated but are surrounded by whitespace." + (let ((trimmed (s-trim name))) + (and (s-starts-with? "*" trimmed)))) + +(defun buffer/find (buffer-or-name) + "Find a buffer by its BUFFER-OR-NAME." + (get-buffer buffer-or-name)) + +(defun buffer/major-mode (name) + "Return the active `major-mode' in buffer, NAME." + (with-current-buffer (buffer/find name) + major-mode)) + +(defun buffer/source-code-buffers () + "Return a list of source code buffers. +This will ignore Emacs-generated buffers, like *Messages*. It will also ignore + any buffer whose major mode is defined in `buffer/source-code-blacklist'." + (->> (buffer-list) + (list/map #'buffer-name) + (list/reject #'buffer/emacs-generated?) + (list/reject (lambda (name) + (set/contains? (buffer/major-mode name) + buffer/source-code-blacklist))))) + +(defvar buffer/source-code-cycle-state + (make-source-code-cycle + :cycle (cycle/from-list (buffer/source-code-buffers)) + :last-called (ts-now)) + "State used to manage cycling between source code buffers.") + +(defun buffer/exists? (name) + "Return t if buffer, NAME, exists." + (maybe/some? (buffer/find name))) + +(defun buffer/new (name) + "Return a newly created buffer NAME." + (generate-new-buffer name)) + +(defun buffer/find-or-create (name) + "Find or create buffer, NAME. +Return a reference to that buffer." + (let ((x (buffer/find name))) + (if (maybe/some? x) + x + (buffer/new name)))) + +;; TODO: Should this consume: `display-buffer' or `switch-to-buffer'? +(defun buffer/show (buffer-or-name) + "Display the BUFFER-OR-NAME, which is either a buffer reference or its name." + (display-buffer buffer-or-name)) + +;; TODO: Move this and `buffer/cycle-prev' into a separate module that +;; encapsulates all of this behavior. + +(defun buffer/cycle (cycle-fn) + "Cycle forwards or backwards through `buffer/source-code-buffers'." + (let ((last-called (source-code-cycle-last-called + buffer/source-code-cycle-state)) + (cycle (source-code-cycle-cycle + buffer/source-code-cycle-state))) + (if (> (ts-diff (ts-now) last-called) + buffer/source-code-timeout) + (progn + (struct/set! source-code-cycle + cycle + (cycle/from-list (buffer/source-code-buffers)) + buffer/source-code-cycle-state) + (let ((cycle (source-code-cycle-cycle + buffer/source-code-cycle-state))) + (funcall cycle-fn cycle) + (switch-to-buffer (cycle/current cycle))) + (struct/set! source-code-cycle + last-called + (ts-now) + buffer/source-code-cycle-state)) + (progn + (funcall cycle-fn cycle) + (switch-to-buffer (cycle/current cycle)))))) + +(defun buffer/cycle-next () + "Cycle forward through the `buffer/source-code-buffers'." + (interactive) + (buffer/cycle #'cycle/next)) + +(defun buffer/cycle-prev () + "Cycle backward through the `buffer/source-code-buffers'." + (interactive) + (buffer/cycle #'cycle/prev)) + +(defun buffer/ivy-source-code () + "Use `ivy-read' to choose among all open source code buffers." + (interactive) + (ivy-read "Source code buffer: " + (-drop 1 (buffer/source-code-buffers)) + :sort nil + :action #'switch-to-buffer)) + +(defun buffer/show-previous () + "Call `switch-to-buffer' on the previously visited buffer. +This function ignores Emacs-generated buffers, i.e. the ones that look like + this: *Buffer*. It also ignores buffers that are `dired-mode' or `erc-mode'. + This blacklist can easily be changed." + (interactive) + (let* ((xs (buffer/source-code-buffers)) + (candidate (list/get 1 xs))) + (prelude/assert (maybe/some? candidate)) + (switch-to-buffer candidate))) + +(when buffer/install-kbds? + (general-define-key + :states '(normal) + "C-f" #'buffer/cycle-next + "C-b" #'buffer/cycle-prev) + (general-define-key + :prefix "<SPC>" + :states '(normal) + "b" #'buffer/ivy-source-code + "<SPC>" #'buffer/show-previous + "k" #'kill-buffer)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when buffer/enable-tests? + (prelude/assert + (list/all? #'buffer/emacs-generated? + '("*scratch*" + "*Messages*" + "*shell*" + "*Shell Command Output*" + "*Occur*" + "*Warnings*" + "*Help*" + "*Completions*" + "*Apropos*" + "*info*")))) + +(provide 'buffer) +;;; buffer.el ends here diff --git a/emacs/.emacs.d/wpc/bytes.el b/emacs/.emacs.d/wpc/bytes.el new file mode 100644 index 000000000000..d8bd2e288614 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/cache.el b/emacs/.emacs.d/wpc/cache.el new file mode 100644 index 000000000000..7b7e1aa2a37f --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/chrome.el b/emacs/.emacs.d/wpc/chrome.el new file mode 100644 index 000000000000..133c7af355fa --- /dev/null +++ b/emacs/.emacs.d/wpc/chrome.el @@ -0,0 +1,82 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'alist) +(require 'list) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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 &key 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))))) + +(provide 'chrome) +;;; chrome.el ends here diff --git a/emacs/.emacs.d/wpc/clipboard.el b/emacs/.emacs.d/wpc/clipboard.el new file mode 100644 index 000000000000..0688c9d87fe0 --- /dev/null +++ b/emacs/.emacs.d/wpc/clipboard.el @@ -0,0 +1,44 @@ +;;; 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 'prelude) +(require 'ivy-clipmenu) + +(prelude/assert (prelude/executable-exists? "clipmenu")) +(prelude/assert (prelude/executable-exists? "clipmenud")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun clipboard/copy (x &key (message "[clipboard.el] Copied!")) + "Copy string, X, to X11's clipboard." + (kill-new x) + (message message)) + +(cl-defun clipboard/paste (&key (message "[clipboard.el] Pasted!")) + "Paste contents of X11 clipboard." + (yank) + (message message)) + +(defun clipboard/contents () + "Return the contents of the clipboard as a string." + (substring-no-properties (current-kill 0))) + +(provide 'clipboard) +;;; clipboard.el ends here diff --git a/emacs/.emacs.d/wpc/colorscheme.el b/emacs/.emacs.d/wpc/colorscheme.el new file mode 100644 index 000000000000..830fc5ac3e28 --- /dev/null +++ b/emacs/.emacs.d/wpc/colorscheme.el @@ -0,0 +1,96 @@ +;;; 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) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom colorscheme/install-kbds? t + "If non-nil, enable the keybindings.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom colorscheme/whitelist + (cycle/from-list + (->> (custom-available-themes) + (list/map #'symbol-name) + (list/filter (>> (s-starts-with? "doom-"))) + (list/map #'intern))) + "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 t) + (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/set 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 +(when colorscheme/install-kbds? + (general-define-key + :prefix "<SPC>" + :states '(normal) + "Ft" #'colorscheme/next + "Pt" #'colorscheme/prev)) + +(provide 'colorscheme) +;;; colorscheme.el ends here diff --git a/emacs/.emacs.d/wpc/constants.el b/emacs/.emacs.d/wpc/constants.el new file mode 100644 index 000000000000..5bfedf5553c6 --- /dev/null +++ b/emacs/.emacs.d/wpc/constants.el @@ -0,0 +1,41 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; 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 "~/universe" + "Variable holding the directory for my currently active project.") + +(prelude/assert (f-directory? constants/current-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/emacs/.emacs.d/wpc/cycle.el b/emacs/.emacs.d/wpc/cycle.el new file mode 100644 index 000000000000..9475ddd99659 --- /dev/null +++ b/emacs/.emacs.d/wpc/cycle.el @@ -0,0 +1,155 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'math) +(require 'maybe) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 previous-index xs) + +(defconst cycle/enable-tests? t + "When t, run the tests defined herein.") + +(defun cycle/new (&rest xs) + "Create an empty cycle." + (make-cycle :current-index 0 + :previous-index nil + :xs xs)) + +(defun cycle/from-list (xs) + "Create a cycle from a list of `XS'." + (make-cycle :current-index 0 + :previous-index nil + :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/previous-focus (cycle) + "Return the previously focused entry in CYCLE." + (let ((i (cycle-previous-index cycle))) + (if (maybe/some? i) + (nth i (cycle-xs cycle)) + nil))) + +;; TODO: Consider adding "!" to the function name herein since many of them +;; mutate the collection, and the APIs are beginning to confuse me. +(defun cycle/focus-previous! (xs) + "Jump to the item in XS that was most recently focused; return the cycle. +This will error when previous-index is nil. This function mutates the +underlying struct." + (let ((i (cycle-previous-index xs))) + (if (maybe/some? i) + (progn + (cycle/jump i xs) + (cycle/current xs)) + (error "Cannot focus the previous element since cycle-previous-index is nil")))) + +(defun cycle/next (xs) + "Return the next value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (next-index-> 0 (cycle/count xs) current-index))) + (struct/set! cycle previous-index current-index xs) + (struct/set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle/prev (xs) + "Return the previous value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (next-index<- 0 (cycle/count xs) current-index))) + (struct/set! cycle previous-index current-index xs) + (struct/set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle/current (cycle) + "Return the current value in `CYCLE'." + (nth (cycle-current-index cycle) (cycle-xs cycle))) + +(defun cycle/count (cycle) + "Return the length of `xs' in `CYCLE'." + (length (cycle-xs cycle))) + +(defun cycle/jump (i xs) + "Jump to the I index of XS." + (let ((current-index (cycle-current-index xs)) + (next-index (math/mod i (cycle/count xs)))) + (struct/set! cycle previous-index current-index xs) + (struct/set! cycle current-index next-index xs)) + xs) + +(defun cycle/focus (p cycle) + "Focus the element in CYCLE for which predicate, P, is t." + (let ((i (->> cycle + cycle-xs + (-find-index p)))) + (if i + (cycle/jump i cycle) + (error "No element in cycle matches predicate")))) + +(defun cycle/contains? (x xs) + "Return t if cycle, XS, has member X." + (->> xs + cycle-xs + (list/contains? x))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when cycle/enable-tests? + (let ((xs (cycle/new 1 2 3))) + (prelude/assert (maybe/nil? (cycle/previous-focus xs))) + (prelude/assert (= 1 (cycle/current xs))) + (prelude/assert (= 2 (cycle/next xs))) + (prelude/assert (= 1 (cycle/previous-focus xs))) + (prelude/assert (= 1 (->> xs (cycle/jump 0) cycle/current))) + (prelude/assert (= 2 (->> xs (cycle/jump 1) cycle/current))) + (prelude/assert (= 3 (->> xs (cycle/jump 2) cycle/current))) + (prelude/assert (= 2 (cycle/previous-focus xs))) + (prelude/assert (= 2 (cycle/focus-previous! xs))))) + +(provide 'cycle) +;;; cycle.el ends here diff --git a/emacs/.emacs.d/wpc/device.el b/emacs/.emacs.d/wpc/device.el new file mode 100644 index 000000000000..03eb55beb7f4 --- /dev/null +++ b/emacs/.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 + '(("zeno.lon.corp.google.com" . work-desktop) + ("seneca" . work-laptop)) + "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/emacs/.emacs.d/wpc/display.el b/emacs/.emacs.d/wpc/display.el new file mode 100644 index 000000000000..8e5b89030325 --- /dev/null +++ b/emacs/.emacs.d/wpc/display.el @@ -0,0 +1,98 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst display/install-kbds? t + "When t, install the keybindings defined in this module.") + +;; TODO: Consider if this logic should be conditioned by `device/work-laptop?'. +(defconst display/laptop-monitor "eDP1" + "The xrandr identifier for my primary screen (on work laptop).") + +;; TODO: Why is HDMI-1, eDP-1 sometimes and HDMI1, eDP1 other times. +(defconst display/4k-monitor "HDMI1" + "The xrandr identifer for my 4K monitor.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Debug why something this scales to 4k appropriately and other times it +;; doesn't. +(defun display/enable-4k () + "Attempt to connect to my 4K monitor." + (interactive) + (prelude/start-process + :name "display/enable-4k" + :command (string/format + "xrandr --output %s --above %s --primary --auto --dpi 144" + display/4k-monitor + display/laptop-monitor))) + +(defun display/disable-4k () + "Disconnect from the 4K monitor." + (interactive) + (prelude/start-process + :name "display/disable-4k" + :command (string/format "xrandr --output %s --off" + display/4k-monitor))) + +(defun display/enable-laptop () + "Turn the laptop monitor off. +Sometimes this is useful when I'm sharing my screen in a Google Hangout and I + only want to present one of my monitors." + (interactive) + (prelude/start-process + :name "display/disable-laptop" + :command (string/format "xrandr --output %s --auto" + display/laptop-monitor))) + +(defun display/disable-laptop () + "Turn the laptop monitor off. +Sometimes this is useful when I'm sharing my screen in a Google Hangout and I + only want to present one of my monitors." + (interactive) + (prelude/start-process + :name "display/disable-laptop" + :command (string/format "xrandr --output %s --off" + display/laptop-monitor))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when display/install-kbds? + (general-define-key + :prefix "<SPC>" + :states '(normal) + "d0" #'display/disable-laptop + "d1" #'display/enable-laptop) + (general-define-key + :prefix "<SPC>" + :states '(normal) + "D0" #'display/disable-4k + "D1" #'display/enable-4k)) + +(provide 'display) +;;; display.el ends here diff --git a/emacs/.emacs.d/wpc/do.el b/emacs/.emacs.d/wpc/do.el new file mode 100644 index 000000000000..7dc2b260fdcd --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/dotfiles.el b/emacs/.emacs.d/wpc/dotfiles.el new file mode 100644 index 000000000000..2e78cf213733 --- /dev/null +++ b/emacs/.emacs.d/wpc/dotfiles.el @@ -0,0 +1,53 @@ +;;; dotfiles.el --- Elisp to make dotfile management -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Quickly edit commonly used files. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'f) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst dotfiles/install-kbds? t + "When t, install the keybindings.") + +(defconst dotfiles/whitelist + '(("compton" . "~/.config/compton.conf") + ("dotfiles" . "~/dotfiles/") + ("functions" . "~/functions.zsh") + ("aliases" . "~/aliases.zsh") + ("variables" . "~/variables.zsh") + ("Xresources" . "~/.Xresources.shared") + ("xsession" . "~/.xsessionrc.shared") + ("tmux" . "~/.tmux.conf") + ("zshrc" . "~/.zshrc") + ("config.fish" . "~/.config/fish/config.fish") + ("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))) + +(defun dotfiles/find-emacs-file (name) + "Call `find-file' on NAME located in dotfiles's emacs.d directory." + (find-file + (f-join "~/dotfiles/configs/shared/.emacs.d" name))) + +(provide 'dotfiles) +;;; dotfiles.el ends here diff --git a/emacs/.emacs.d/wpc/dotted.el b/emacs/.emacs.d/wpc/dotted.el new file mode 100644 index 000000000000..90ef39f92e7e --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/email.el b/emacs/.emacs.d/wpc/email.el new file mode 100644 index 000000000000..6a266a717cd2 --- /dev/null +++ b/emacs/.emacs.d/wpc/email.el @@ -0,0 +1,11 @@ +;;; email.el --- My Emacs email settings -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Attempting to configure to `notmuch' for my personal use. + +;;; Code: +(message "Not implemented.") + +(provide 'email) +;;; email.el ends here diff --git a/emacs/.emacs.d/wpc/entr.el b/emacs/.emacs.d/wpc/entr.el new file mode 100644 index 000000000000..ac2a5812c328 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/enum.el b/emacs/.emacs.d/wpc/enum.el new file mode 100644 index 000000000000..078e7972099c --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/finance.el b/emacs/.emacs.d/wpc/finance.el new file mode 100644 index 000000000000..b124061ccba3 --- /dev/null +++ b/emacs/.emacs.d/wpc/finance.el @@ -0,0 +1,119 @@ +;;; finance.el --- Functions to help me organize my finances -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Using functions to organize my financial thinking. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'math) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar finance/enable-tests? t + "When t, run the tests defined herein.") + +;; TODO: Support printing an org-table of these amount in a similar format to: +;; https://keisan.casio.com/exec/system/1234231998 +(cl-defun finance/future-value (amt + &key + num-years + (frequency 'monthly) + (interest-rate 0.06) + (payment-due-at 'beg) + (present-value 0)) + "Compute the Future Value of AMT. + +This function assumes that the interest rate is applied annually and not +monthly. + +This function will attempt to provide the following defaults: +- frequency: 'monthly +- interest-rate: 6% +- payment-due-at: 'beg +- present-value: 0.00" + (prelude/assert (set/contains? payment-due-at (set/new 'beg 'end))) + (prelude/assert (set/contains? frequency (set/new 'annually + 'semiannually + 'quarterly + 'monthly))) + (let ((pmt amt) + (k (alist/get frequency '((annually . 1) + (semiannually . 2) + (quarterly . 4) + (monthly . 12)))) + (r interest-rate) + (n num-years) + (pv present-value)) + (if (= 0 r) + (+ pv (* pmt n k)) + (if (equal 'beg payment-due-at) + (+ (* pv (math/exp (+ 1 (/ r k)) (* n k))) + (* pmt + (/ (- (math/exp (+ 1 (/ r k)) (* n k)) 1) + (/ r k)) + (+ 1 (/ r k)))) + (+ (* pv (math/exp (+ 1 (/ r k)) (* n k))) + (* pmt + (/ (- (math/exp (+ 1 (/ r k)) (* n k)) 1) + (/ r k)))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when finance/enable-tests? + (prelude/assert + (equal "1551.27" + (string/format "%0.2f" + (finance/future-value + 9.99 + :interest-rate 0.05 + :num-years 10 + :frequency 'monthly + :payment-due-at 'end + :present-value 0)))) + (prelude/assert + (equal "14318.34" + (string/format "%0.2f" + (finance/future-value 10.0 :num-years 35)))) + (prelude/assert + (equal "4200.00" + (string/format "%0.2f" + (finance/future-value + 10.0 + :interest-rate 0.0 + :num-years 35 + :frequency 'monthly + :payment-due-at 'beg + :present-value 0)))) + (prelude/assert + (equal "14318.34" + (string/format "%0.2f" + (finance/future-value + 10.0 + :interest-rate 0.06 + :num-years 35 + :frequency 'monthly + :payment-due-at 'beg + :present-value 0)))) + (prelude/assert + (equal "38282.77" + (string/format "%0.2f" + (finance/future-value + 10.0 + :interest-rate 0.1 + :num-years 35 + :frequency 'monthly + :payment-due-at 'beg + :present-value 0))))) + +(provide 'finance) +;;; finance.el ends here diff --git a/emacs/.emacs.d/wpc/fonts.el b/emacs/.emacs.d/wpc/fonts.el new file mode 100644 index 000000000000..3c6fe6bfebfb --- /dev/null +++ b/emacs/.emacs.d/wpc/fonts.el @@ -0,0 +1,153 @@ +;;; 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) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 "9") + ('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 + '("JetBrainsMono" + "Mononoki Medium" + "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 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 + (general-define-key + :prefix "<SPC>" + :states '(normal) + "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/emacs/.emacs.d/wpc/fs.el b/emacs/.emacs.d/wpc/fs.el new file mode 100644 index 000000000000..b1a79e280a57 --- /dev/null +++ b/emacs/.emacs.d/wpc/fs.el @@ -0,0 +1,65 @@ +;;; 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. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun fs/ensure-file (path) + "Ensure that a file and its directories in `PATH' exist. +Will error for inputs with a trailing slash." + (when (s-ends-with? "/" path) + (error (format "Input path has trailing slash: %s" path))) + (->> path + f-dirname + fs/ensure-dir) + (f-touch path)) + +(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/emacs/.emacs.d/wpc/functions.el b/emacs/.emacs.d/wpc/functions.el new file mode 100644 index 000000000000..2ef82d54bbe1 --- /dev/null +++ b/emacs/.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-file-split (filename) + "Creates a window split and then edits `filename'." + (interactive) + (evil-window-vsplit) + (find-file filename)) + +(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/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/emacs/.emacs.d/wpc/graph.el b/emacs/.emacs.d/wpc/graph.el new file mode 100644 index 000000000000..c68c308590f4 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/imdb.el b/emacs/.emacs.d/wpc/imdb.el new file mode 100644 index 000000000000..2969da140935 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/irc.el b/emacs/.emacs.d/wpc/irc.el new file mode 100644 index 000000000000..b9a1e3131769 --- /dev/null +++ b/emacs/.emacs.d/wpc/irc.el @@ -0,0 +1,177 @@ +;;; 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) +(require 'prelude) +(require 'alist) +(require 'set) +(require 'maybe) +(require 'macros) +(require 'password-store) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst irc/enable-tests? t + "When t, run the tests defined herein.") + +(setq erc-rename-buffers t) + +;; TODO: Find a way to avoid putting "freenode" and "#freenode" as channels +;; here. I'm doing it because when erc first connects, it's `(buffer-name)' is +;; "freenode", so when `irc/next-channel' is called, it 404s on the +;; `cycle/contains?' call in `irc/channel->cycle" unless "freenode" is there. To +;; make matters even uglier, when `erc-join-channel' is called with "freenode" +;; as the value, it connects to the "#freenode" channel, so unless "#freenode" +;; exists in this cycle also, `irc/next-channel' breaks again. This doesn't +;; pass my smell test. +(defconst irc/server->channels + `(("irc.freenode.net" . ,(cycle/new "freenode" "#freenode" "#nixos" "#emacs" "#pass")) + ("irc.corp.google.com" . ,(cycle/new "#omg" "#london" "#panic" "#prod-team"))) + "Mapping of IRC servers to a cycle of my preferred channels.") + +;; TODO: Assert that no two servers have a channel with the same name. We need +;; this because that's the assumption that underpins the `irc/channel->server' +;; function. This will probably be an O(n^2) operation. +(prelude/assert + (set/distinct? (set/from-list + (cycle/to-list + (alist/get "irc.freenode.net" + irc/server->channels))) + (set/from-list + (cycle/to-list + (alist/get "irc.corp.google.com" + irc/server->channels))))) + +(defun irc/channel->server (server->channels channel) + "Resolve an IRC server from a given CHANNEL." + (let ((result (alist/find (lambda (k v) (cycle/contains? channel v)) + server->channels))) + (prelude/assert (maybe/some? result)) + result)) + +(defun irc/channel->cycle (server->channels channel) + "Resolve an IRC's channels cycle from a given CHANNEL." + (alist/get (irc/channel->server server->channels channel) + server->channels)) + +;; 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) + +;; TODO: Here is another horrible hack that should be revisted. +(setq erc-autojoin-channels-alist + (->> irc/server->channels + (alist/map-values #'cycle/to-list) + (alist/map-keys (>> (s-chop-prefix "irc.") + (s-chop-suffix ".net"))))) + +(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))) + +;; TODO: Integrate Google setup with Freenode setup. + +;; TODO: Support function or KBD for switching to an ERC buffer. + +(defun irc/kill-all-erc-processes () + "Kills all ERC buffers and processes." + (interactive) + (->> (erc-buffer-list) + (-map #'kill-buffer))) + +(defun irc/switch-to-erc-buffer () + "Switch to an ERC buffer." + (interactive) + (let ((buffers (erc-buffer-list))) + (if (list/empty? buffers) + (error "[irc.el] No ERC buffers available") + (switch-to-buffer (list/head (erc-buffer-list)))))) + +(defun irc/connect-to-freenode () + "Connect to Freenode IRC." + (interactive) + (erc-ssl :server "irc.freenode.net" + :port 6697 + :nick "wpcarro" + :password (password-store-get "programming/irc/freenode") + :full-name "William Carroll")) + +;; TODO: Handle failed connections. +(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")) + +;; TODO: Prefer defining these with a less homespun solution. There is a +;; function call `erc-buffer-filter' that would be more appropriate for the +;; implementation of `irc/next-channel' and `irc/prev-channel'. +(defun irc/next-channel () + "Join the next channel for the active server." + (interactive) + (with-current-buffer (current-buffer) + (let ((cycle (irc/channel->cycle irc/server->channels (buffer-name)))) + (erc-join-channel + (cycle/next cycle)) + (irc/message + (string/format "Current IRC channel: %s" (cycle/current cycle)))))) + +(defun irc/prev-channel () + "Join the previous channel for the active server." + (interactive) + (with-current-buffer (current-buffer) + (let ((cycle (irc/channel->cycle irc/server->channels (buffer-name)))) + (erc-join-channel + (cycle/prev cycle)) + (irc/message + (string/format "Current IRC channel: %s" (cycle/current cycle)))))) + +(add-hook 'erc-mode-hook (disable auto-fill-mode)) +(add-hook 'erc-mode-hook (disable company-mode)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when irc/install-kbds? + (general-define-key + :keymaps 'erc-mode-map + "<C-tab>" #'irc/next-channel + "<C-S-iso-lefttab>" #'irc/prev-channel)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when irc/enable-tests? + (prelude/assert + (equal + (irc/channel->server `(("irc.dairy.com" . ,(cycle/new "#cheese" "#milk")) + ("irc.color.com" . ,(cycle/new "#red" "#blue"))) + "#cheese") + "irc.dairy.com"))) + +(provide 'irc) +;;; irc.el ends here diff --git a/emacs/.emacs.d/wpc/iso.el b/emacs/.emacs.d/wpc/iso.el new file mode 100644 index 000000000000..c9ce4a48fc71 --- /dev/null +++ b/emacs/.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 &key 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/emacs/.emacs.d/wpc/ivy-clipmenu.el b/emacs/.emacs.d/wpc/ivy-clipmenu.el new file mode 100644 index 000000000000..f3896137bd9f --- /dev/null +++ b/emacs/.emacs.d/wpc/ivy-clipmenu.el @@ -0,0 +1,134 @@ +;;; ivy-clipmenu.el --- Emacs client for clipmenu -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Ivy integration with the clipboard manager, clipmenu. Essentially, clipmenu +;; turns your system clipboard into a list. +;; +;; To use this module, you must first install clipmenu and ensure that the +;; clipmenud daemon is running. Refer to the installation instructions at +;; github.com/cdown/clipmenu for those details. +;; +;; This module intentionally does not define any keybindings since I'd prefer +;; not to presume my users' preferences. Personally, I use EXWM as my window +;; manager, so I call `exwm-input-set-key' and map it to `ivy-clipmenu/copy'. +;; +;; Usually clipmenu integrates with rofi or dmenu. This Emacs module integrates +;; with ivy. Launch this when you want to select a clip. +;; +;; Clipmenu itself supports a variety of environment variables that allow you to +;; customize its behavior. These variables are respected herein. If you'd +;; prefer to customize clipmenu's behavior from within Emacs, refer to the +;; variables defined in this module. +;; +;; For more information: +;; - See `clipmenu --help`. +;; - Visit github.com/cdown/clipmenu. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 's) +(require 'dash) +(require 'ivy) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup ivy-clipmenu nil + "Ivy integration for clipmenu." + :group 'ivy) + +(defcustom ivy-clipmenu/directory + (or (getenv "XDG_RUNTIME_DIR") + (getenv "TMPDIR") + "/tmp") + "Base directory for clipmenu's data." + :type 'string + :group 'ivy-clipmenu) + +(defconst ivy-clipmenu/executable-version 5 + "The major version number for the clipmenu executable.") + +(defconst ivy-clipmenu/cache-directory + (f-join ivy-clipmenu/directory + (format "clipmenu.%s.%s" + ivy-clipmenu/executable-version + (getenv "USER"))) + "Directory where the clips are stored.") + +(defconst ivy-clipmenu/cache-file-pattern + (f-join ivy-clipmenu/cache-directory "line_cache_*") + "Glob pattern matching the locations on disk for clipmenu's labels.") + +(defcustom ivy-clipmenu/history-length + (or (getenv "CM_HISTLENGTH") 25) + "Limit the number of clips in the history. +This value defaults to 25.") + +(defvar ivy-clipmenu/history nil + "History for `ivy-clipmenu/copy'.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun ivy-clipmenu/parse-content (x) + "Parse the label from the entry in clipmenu's line-cache." + (->> (s-split " " x) + (-drop 1) + (s-join " "))) + +(defun ivy-clipmenu/list-clips () + "Return a list of the content of all of the clips." + (->> ivy-clipmenu/cache-file-pattern + f-glob + (-map (lambda (path) + (s-split "\n" (f-read path) t))) + -flatten + (-reject #'s-blank?) + (-sort #'string>) + (-map #'ivy-clipmenu/parse-content) + delete-dups + (-take ivy-clipmenu/history-length))) + +(defun ivy-clipmenu/checksum (content) + "Return the CRC checksum of CONTENT." + (s-trim-right + (with-temp-buffer + (call-process "/bin/bash" nil (current-buffer) nil "-c" + (format "cksum <<<'%s'" content)) + (buffer-string)))) + +(defun ivy-clipmenu/line-to-content (line) + "Map the chosen LINE from the line cache its content from disk." + (->> line + ivy-clipmenu/checksum + (f-join ivy-clipmenu/cache-directory) + f-read)) + +(defun ivy-clipmenu/do-copy (x) + "Copy string, X, to the system clipboard." + (kill-new x) + (message "[ivy-clipmenu.el] Copied!")) + +(defun ivy-clipmenu/copy () + "Use `ivy-read' to select and copy a clip. +It's recommended to bind this function to a globally available keymap." + (interactive) + (let ((ivy-sort-functions-alist nil)) + (ivy-read "Clipmenu: " + (ivy-clipmenu/list-clips) + :history 'ivy-clipmenu/history + :action (lambda (line) + (->> line + ivy-clipmenu/line-to-content + ivy-clipmenu/do-copy))))) + +(provide 'ivy-clipmenu) +;;; ivy-clipmenu.el ends here diff --git a/emacs/.emacs.d/wpc/ivy-helpers.el b/emacs/.emacs.d/wpc/ivy-helpers.el new file mode 100644 index 000000000000..c71a907a20c1 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/kaomoji.el b/emacs/.emacs.d/wpc/kaomoji.el new file mode 100644 index 000000000000..d6d509c14667 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/kbd.el b/emacs/.emacs.d/wpc/kbd.el new file mode 100644 index 000000000000..49b346bc6ea8 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/keybindings.el b/emacs/.emacs.d/wpc/keybindings.el new file mode 100644 index 000000000000..755311483dd1 --- /dev/null +++ b/emacs/.emacs.d/wpc/keybindings.el @@ -0,0 +1,46 @@ +;;; keybindings.el --- Centralizing my keybindings -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Attempting to centralize my keybindings to simplify my configuration. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'clipboard) +(require 'screen-brightness) +(require 'chrome) +(require 'scrot) +(require 'ivy-clipmenu) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro keybinding/exwm (c fn) + "Bind C to FN using `exwm-input-set-key' with `kbd' applied to C." + `(exwm-input-set-key (kbd ,c) ,fn)) + +(keybinding/exwm "C-M-v" #'ivy-clipmenu/copy) + +(keybinding/exwm "<XF86MonBrightnessUp>" #'screen-brightness/increase) +(keybinding/exwm "<XF86MonBrightnessDown>" #'screen-brightness/decrease) + +(keybinding/exwm "<XF86AudioMute>" #'pulse-audio/toggle-mute) +(keybinding/exwm "<XF86AudioLowerVolume>" #'pulse-audio/decrease-volume) +(keybinding/exwm "<XF86AudioRaiseVolume>" #'pulse-audio/increase-volume) +(keybinding/exwm "<XF86AudioMicMute>" #'pulse-audio/toggle-microphone) + +(keybinding/exwm "C-M-c" #'chrome/browse) + +(keybinding/exwm (kbd/raw 'x11 "s") #'scrot/select) + +;; TODO: I need this because my Ergodox EZ sends super+shift instead of just +;; super. Remove this once I fix my Ergodox. +(keybinding/exwm "C-S-s-s" #'scrot/select) + +(provide 'keybindings) +;;; keybindings.el ends here diff --git a/emacs/.emacs.d/wpc/keyboard.el b/emacs/.emacs.d/wpc/keyboard.el new file mode 100644 index 000000000000..ec50cabd2719 --- /dev/null +++ b/emacs/.emacs.d/wpc/keyboard.el @@ -0,0 +1,152 @@ +;;; 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." + (prelude/start-process + :name "keyboard/set-key-repeat" + :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) + ;; TODO: Ensure these work once the tokenizing in prelude/start-process works + ;; as expected. + (start-process "keyboard/swap-caps-lock-and-escape" nil "/usr/bin/xmodmap" "-e" + "remove Lock = Caps_Lock") + (start-process "keyboard/swap-caps-lock-and-escape" nil "/usr/bin/xmodmap" "-e" + "keysym Caps_Lock = Escape")) + +(defun keyboard/inc-repeat-rate () + "Increment `keyboard/repeat-rate'." + (interactive) + (setq keyboard/repeat-rate (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/emacs/.emacs.d/wpc/keymap.el b/emacs/.emacs.d/wpc/keymap.el new file mode 100644 index 000000000000..87d340fcdbf1 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/laptop-battery.el b/emacs/.emacs.d/wpc/laptop-battery.el new file mode 100644 index 000000000000..3ec03553d2ca --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/list.el b/emacs/.emacs.d/wpc/list.el new file mode 100644 index 000000000000..5a63c8bd94e0 --- /dev/null +++ b/emacs/.emacs.d/wpc/list.el @@ -0,0 +1,235 @@ +;;; 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 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Move `prelude/assert' elsewhere so that I can require it without +;; introducing the circular dependency of list.el -> prelude.el -> list.el. +;;(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)) + +;; TODO: Support this. It seems like `alist/set' is not working as I expected it +;; to. Perhaps we should add some tests to confirm the expected behavior. +;; (cl-defun list/index (f xs &key (transform (lambda (x) x))) +;; "Return a mapping of F applied to each x in XS to TRANSFORM applied to x. +;; The TRANSFORM function defaults to the identity function." +;; (->> xs +;; (list/reduce (alist/new) +;; (lambda (x acc) +;; (let ((k (funcall f x)) +;; (v (funcall transform x))) +;; (if (alist/has-key? k acc) +;; (setf (alist-get k acc) (list v)) +;; (setf (alist-get k acc) (list v)))))))) +;; (prelude/assert +;; (equal '(("John" . ("Cleese" "Malkovich")) +;; ("Thomas" . ("Aquinas"))) +;; (list/index (lambda (x) (plist-get x :first-name)) +;; '((:first-name "John" :last-name "Cleese") +;; (:first-name "John" :last-name "Malkovich") +;; (:first-name "Thomas" :last-name "Aquinas")) +;; :transform (lambda (x) (plist-get x :last-name))))) + +(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)) + +;; TODO: Support dedupe. +;; TODO: Should we call this unique? Or distinct? + +;; TODO: Add tests. +(defun list/dedupe-adjacent (xs) + "Return XS without adjacent duplicates." + (prelude/assert (not (list/empty? xs))) + (list/reduce (list (list/first xs)) + (lambda (x acc) + (if (equal x (list/first acc)) + acc + (list/cons x acc))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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/emacs/.emacs.d/wpc/list.nix b/emacs/.emacs.d/wpc/list.nix new file mode 100644 index 000000000000..e664ba6fd4a1 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/macros.el b/emacs/.emacs.d/wpc/macros.el new file mode 100644 index 000000000000..5f7c93902e3e --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/math.el b/emacs/.emacs.d/wpc/math.el new file mode 100644 index 000000000000..3176d906b466 --- /dev/null +++ b/emacs/.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/somes? base power result) + (error "All three arguments should not be set")) + ((maybe/somes? power result) + (message "power and result")) + ((maybe/somes? base result) + (log result base)) + ((maybe/somes? 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/emacs/.emacs.d/wpc/maybe.el b/emacs/.emacs.d/wpc/maybe.el new file mode 100644 index 000000000000..0973b1ed65f7 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/me-seconds.el b/emacs/.emacs.d/wpc/me-seconds.el new file mode 100644 index 000000000000..f03e5d07d790 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/monoid.el b/emacs/.emacs.d/wpc/monoid.el new file mode 100644 index 000000000000..401d63c41728 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/number.el b/emacs/.emacs.d/wpc/number.el new file mode 100644 index 000000000000..f496349050d9 --- /dev/null +++ b/emacs/.emacs.d/wpc/number.el @@ -0,0 +1,153 @@ +;;; 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? +;; TODO: Resolve the circular dependency that this introduces with series.el, +;; and then re-enable this function and its tests below. +;; (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/emacs/.emacs.d/wpc/org-helpers.el b/emacs/.emacs.d/wpc/org-helpers.el new file mode 100644 index 000000000000..ef99b18ee053 --- /dev/null +++ b/emacs/.emacs.d/wpc/org-helpers.el @@ -0,0 +1,29 @@ +;;; org-helpers.el --- Utility functions for working with my Org setup -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Some small utility functions intended to make me more likely to use Org mode +;; more often. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst org-helpers/directory "~/Dropbox/org" + "The directory where I store most of my Org files.") + +(defun org-helpers/find-file (name) + "Call `find-file' on NAME in my org directory" + (find-file + (f-join org-helpers/directory name))) + +(provide 'org-helpers) +;;; org-helpers.el ends here diff --git a/emacs/.emacs.d/wpc/playback.el b/emacs/.emacs.d/wpc/playback.el new file mode 100644 index 000000000000..e7ad4b2481a4 --- /dev/null +++ b/emacs/.emacs.d/wpc/playback.el @@ -0,0 +1,41 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun playback/prev () + "Move to the previous song." + (interactive) + (prelude/start-process + :name "playback/prev" + :command "playerctl previous")) + +(defun playback/next () + "Move to the next song." + (interactive) + (prelude/start-process + :name "playback/next" + :command "playerctl next")) + +(defun playback/play-pause () + "Play or pause the current song." + (interactive) + (prelude/start-process + :name "playback/play-pause" + :command "playerctl play-pause")) + +(provide 'playback) +;;; playback.el ends here diff --git a/emacs/.emacs.d/wpc/polymorphism.el b/emacs/.emacs.d/wpc/polymorphism.el new file mode 100644 index 000000000000..09045f7fb258 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/prelude.el b/emacs/.emacs.d/wpc/prelude.el new file mode 100644 index 000000000000..6ef9e3ba7afb --- /dev/null +++ b/emacs/.emacs.d/wpc/prelude.el @@ -0,0 +1,149 @@ +;;; 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)) + +(defmacro prelude/call-process-to-string (cmd &rest args) + "Return the string output of CMD called with ARGS." + `(with-temp-buffer + (call-process ,cmd nil (current-buffer) nil ,@args) + (buffer-string))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Assertions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Should I `throw' instead of `error' here? +(defmacro prelude/assert (x) + "Errors unless X is t. +These are strict assertions and purposely do not rely on truthiness." + (let ((as-string (prelude/to-string x))) + `(unless (equal t ,x) + (error (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)) + +(cl-defun prelude/start-process (&key name command) + "Pass command string, COMMAND, and the function name, NAME. +This is a wrapper around `start-process' that has an API that resembles +`shell-command'." + ;; TODO: Fix the bug with tokenizing here, since it will split any whitespace + ;; character, even though it shouldn't in the case of quoted string in shell. + ;; e.g. - "xmodmap -e 'one two three'" => '("xmodmap" "-e" "'one two three'") + (prelude/refute (string/contains? "'" command)) + (let* ((tokens (string/split " " command)) + (program-name (list/head tokens)) + (program-args (list/tail tokens))) + (apply #'start-process + `(,(string/format "*%s<%s>*" program-name name) + ,nil + ,program-name + ,@program-args)))) + +(defun prelude/executable-exists? (name) + "Return t if CLI tool NAME exists according to `exec-path'." + (let ((file (locate-file name exec-path))) + (require 'maybe) + (if (maybe/some? file) + (f-exists? file) + nil))) + +(defmacro prelude/time (x) + "Print the time it takes to evaluate X." + `(benchmark 1 ',x)) + +(provide 'prelude) +;;; prelude.el ends here diff --git a/emacs/.emacs.d/wpc/prelude.nix b/emacs/.emacs.d/wpc/prelude.nix new file mode 100644 index 000000000000..626d4526a25d --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/pulse-audio.el b/emacs/.emacs.d/wpc/pulse-audio.el new file mode 100644 index 000000000000..dba4151a9e3d --- /dev/null +++ b/emacs/.emacs.d/wpc/pulse-audio.el @@ -0,0 +1,66 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst pulse-audio/step-size 5 + "The size by which to increase or decrease the volume.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun pulse-audio/message (x) + "Output X to *Messages*." + (message (string/format "[pulse-audio.el] %s" x))) + +(defun pulse-audio/toggle-mute () + "Mute the default sink." + (interactive) + (prelude/start-process + :name "pulse-audio/toggle-mute" + :command "pactl set-sink-mute @DEFAULT_SINK@ toggle") + (pulse-audio/message "Mute toggled.")) + +(defun pulse-audio/toggle-microphone () + "Mute the default sink." + (interactive) + (prelude/start-process + :name "pulse-audio/toggle-microphone" + :command "pactl set-source-mute @DEFAULT_SOURCE@ toggle") + (pulse-audio/message "Microphone toggled.")) + +(defun pulse-audio/decrease-volume () + "Low the volume output of the default sink." + (interactive) + (prelude/start-process + :name "pulse-audio/decrease-volume" + :command (string/format "pactl set-sink-volume @DEFAULT_SINK@ -%s%%" + pulse-audio/step-size)) + (pulse-audio/message "Volume decreased.")) + +(defun pulse-audio/increase-volume () + "Raise the volume output of the default sink." + (interactive) + (prelude/start-process + :name "pulse-audio/increase-volume" + :command (string/format "pactl set-sink-volume @DEFAULT_SINK@ +%s%%" + pulse-audio/step-size)) + (pulse-audio/message "Volume increased.")) + +(provide 'pulse-audio) +;;; pulse-audio.el ends here diff --git a/emacs/.emacs.d/wpc/pushover.el b/emacs/.emacs.d/wpc/pushover.el new file mode 100644 index 000000000000..fb06656cf467 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/random.el b/emacs/.emacs.d/wpc/random.el new file mode 100644 index 000000000000..148506c04d4e --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/region.el b/emacs/.emacs.d/wpc/region.el new file mode 100644 index 000000000000..a2119b4c96ce --- /dev/null +++ b/emacs/.emacs.d/wpc/region.el @@ -0,0 +1,20 @@ +;;; region.el --- Functions for working with Emacs's regions -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Sometimes Emacs's function names and argument ordering is great; other times, +;; it isn't. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun region/to-string () + "Returns the string in the active region." + (buffer-substring-no-properties (region-beginning) + (region-end))) + +(provide 'region) +;;; region.el ends here diff --git a/emacs/.emacs.d/wpc/scheduler.el b/emacs/.emacs.d/wpc/scheduler.el new file mode 100644 index 000000000000..bae953228925 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/scope.el b/emacs/.emacs.d/wpc/scope.el new file mode 100644 index 000000000000..48aa85ad0e5d --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/screen-brightness.el b/emacs/.emacs.d/wpc/screen-brightness.el new file mode 100644 index 000000000000..ad51e7578cca --- /dev/null +++ b/emacs/.emacs.d/wpc/screen-brightness.el @@ -0,0 +1,45 @@ +;;; 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 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst screen-brightness/step-size 15 + "The size of the increment or decrement step for the screen's brightness.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun screen-brightness/increase () + "Increase the screen brightness." + (interactive) + (prelude/start-process + :name "screen-brightness/increase" + :command (string/format "xbacklight -inc %s" screen-brightness/step-size)) + (message "[screen-brightness.el] Increased screen brightness.")) + +(defun screen-brightness/decrease () + "Decrease the screen brightness." + (interactive) + (prelude/start-process + :name "screen-brightness/decrease" + :command (string/format "xbacklight -dec %s" screen-brightness/step-size)) + (message "[screen-brightness.el] Decreased screen brightness.")) + +(provide 'screen-brightness) +;;; screen-brightness.el ends here diff --git a/emacs/.emacs.d/wpc/scrot.el b/emacs/.emacs.d/wpc/scrot.el new file mode 100644 index 000000000000..eeb12b3731e9 --- /dev/null +++ b/emacs/.emacs.d/wpc/scrot.el @@ -0,0 +1,64 @@ +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; scrot is a Linux utility for taking screenshots. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'ts) +(require 'clipboard) +(require 'kbd) + +(prelude/assert + (prelude/executable-exists? "scrot")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst scrot/screenshot-directory "~/Downloads" + "The default directory for screenshot outputs.") + +(defconst scrot/path-to-executable "/usr/bin/scrot" + "Path to the scrot executable.") + +(defconst scrot/output-format "screenshot_%H:%M:%S_%Y-%m-%d.png" + "The format string for the output screenshot file. +See scrot's man page for more information.") + +(defun scrot/copy-image (path) + "Use xclip to copy the image at PATH to the clipboard. +This currently only works for PNG files because that's what I'm outputting" + (call-process "xclip" nil nil nil + "-selection" "clipboard" "-t" "image/png" path) + (message (string/format "[scrot.el] Image copied to clipboard!"))) + +(defmacro scrot/call (&rest args) + "Call scrot with ARGS." + `(call-process ,scrot/path-to-executable nil nil nil ,@args)) + +(defun scrot/fullscreen () + "Screenshot the entire screen." + (interactive) + (let ((screenshot-path (f-join scrot/screenshot-directory + (ts-format scrot/output-format (ts-now))))) + (scrot/call screenshot-path) + (scrot/copy-image screenshot-path))) + +(defun scrot/select () + "Click-and-drag to screenshot a region. +The output path is copied to the user's clipboard." + (interactive) + (let ((screenshot-path (f-join scrot/screenshot-directory + (ts-format scrot/output-format (ts-now))))) + (scrot/call "--select" screenshot-path) + (scrot/copy-image screenshot-path))) + +(provide 'scrot) +;;; scrot.el ends here diff --git a/emacs/.emacs.d/wpc/sequence.el b/emacs/.emacs.d/wpc/sequence.el new file mode 100644 index 000000000000..a5428ef04448 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/series.el b/emacs/.emacs.d/wpc/series.el new file mode 100644 index 000000000000..55e97f278984 --- /dev/null +++ b/emacs/.emacs.d/wpc/series.el @@ -0,0 +1,89 @@ +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'number) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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/emacs/.emacs.d/wpc/set.el b/emacs/.emacs.d/wpc/set.el new file mode 100644 index 000000000000..ff2db75d94ab --- /dev/null +++ b/emacs/.emacs.d/wpc/set.el @@ -0,0 +1,171 @@ +;;; 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) +(require 'struct) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Wish List +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; - TODO: Support enum protocol for set. +;; - TODO: Prefer a different hash-table library that doesn't rely on mutative +;; code. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct set xs) + +(defconst set/enable-testing? t + "Run tests when t.") + +(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 nil) + table-copy)) + xs)) + +;; TODO: Ensure all `*/reduce' functions share the same API. +(defun set/reduce (acc f xs) + "Return a new set by calling F on each element of XS and ACC." + (->> xs + set/to-list + (list/reduce acc f))) + +(defun set/intersection (a b) + "Return the set intersection between sets A and B." + (set/reduce (set/new) + (lambda (x acc) + (if (set/contains? x b) + (set/add x acc) + acc)) + a)) + +(defun set/count (xs) + "Return the number of elements in XS." + (->> xs + set-xs + ht-size)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun set/empty? (xs) + "Return t if XS has no elements in it." + (= 0 (set/count xs))) + +(defun set/contains? (x xs) + "Return t if set XS has X." + (ht-contains? (set-xs xs) x)) + +;; TODO: Prefer using `ht.el' functions for this. +(defun set/equal? (a b) + "Return t if A and B share the name members." + (ht-equal? (set-xs a) + (set-xs b))) + +(defun set/distinct? (a b) + "Return t if sets A and B have no shared members." + (set/empty? (set/intersection a b))) + +(defun set/superset? (a b) + "Return t if set A contains all of the members of set B." + (->> b + set/to-list + (list/all? (lambda (x) (set/contains? x a))))) + +(defun set/subset? (a b) + "Return t if each member of set A is present in set B." + (set/superset? b a)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when set/enable-testing? + ;; set/distinct? + (prelude/assert + (set/distinct? (set/new 'one 'two 'three) + (set/new 'a 'b 'c))) + (prelude/refute + (set/distinct? (set/new 1 2 3) + (set/new 3 4 5))) + (prelude/refute + (set/distinct? (set/new 1 2 3) + (set/new 1 2 3))) + ;; set/equal? + (prelude/refute + (set/equal? (set/new 'a 'b 'c) + (set/new 'x 'y 'z))) + (prelude/refute + (set/equal? (set/new 'a 'b 'c) + (set/new 'a 'b))) + (prelude/assert + (set/equal? (set/new 'a 'b 'c) + (set/new 'a 'b 'c))) + ;; set/intersection + (prelude/assert + (set/equal? (set/new 2 3) + (set/intersection (set/new 1 2 3) + (set/new 2 3 4)))) + ;; set/{from,to}-list + (prelude/assert (equal '(1 2 3) + (->> '(1 1 2 2 3 3) + set/from-list + set/to-list))) + (let ((primary-colors (set/new "red" "green" "blue"))) + ;; set/subset? + (prelude/refute + (set/subset? (set/new "black" "grey") + primary-colors)) + (prelude/assert + (set/subset? (set/new "red") + primary-colors)) + ;; set/superset? + (prelude/refute + (set/superset? primary-colors + (set/new "black" "grey"))) + (prelude/assert + (set/superset? primary-colors + (set/new "red" "green" "blue"))) + (prelude/assert + (set/superset? primary-colors + (set/new "red" "blue")))) + ;; set/empty? + (prelude/assert (set/empty? (set/new))) + (prelude/refute (set/empty? (set/new 1 2 3))) + ;; set/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/emacs/.emacs.d/wpc/ssh.el b/emacs/.emacs.d/wpc/ssh.el new file mode 100644 index 000000000000..d7039375731e --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/stack.el b/emacs/.emacs.d/wpc/stack.el new file mode 100644 index 000000000000..052ed881d20f --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/string.el b/emacs/.emacs.d/wpc/string.el new file mode 100644 index 000000000000..f8694d5f18a1 --- /dev/null +++ b/emacs/.emacs.d/wpc/string.el @@ -0,0 +1,128 @@ +;; 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) +;; TODO: Resolve the circular dependency that this introduces. +;; (require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst string/test? t + "When t, run the tests.") + +(defun string/contains? (c x) + "Return t if X is in C." + (s-contains? c x)) + +(defun string/hookify (x) + "Append \"-hook\" to X." + (s-append "-hook" x)) + +(defun string/split (y x) + "Map string X into a list of strings that were separated by Y." + (s-split y 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 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; (when string/test? +;; (prelude/assert +;; (string= +;; (string/surround "-*-" "surround") +;; "-*-surround-*-")) +;; (prelude/assert +;; (string= +;; (string/caps->kebab "CAPS_CASE_STRING") +;; "caps-case-string")) +;; (prelude/assert +;; (string= +;; (string/kebab->caps "kebab-case-string") +;; "KEBAB_CASE_STRING"))) + +(provide 'string) +;;; string.el ends here diff --git a/emacs/.emacs.d/wpc/string.nix b/emacs/.emacs.d/wpc/string.nix new file mode 100644 index 000000000000..1f815b26bb37 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/struct.el b/emacs/.emacs.d/wpc/struct.el new file mode 100644 index 000000000000..7d237d3259ff --- /dev/null +++ b/emacs/.emacs.d/wpc/struct.el @@ -0,0 +1,88 @@ +;;; 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) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar struct/enable-tests? t + "When t, run the test suite defined herein.") + +(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))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when struct/enable-tests? + (cl-defstruct dummy name age) + (defvar test-dummy (make-dummy :name "Roofus" :age 19)) + (struct/set! dummy name "Doofus" test-dummy) + (prelude/assert (string= "Doofus" (dummy-name test-dummy))) + (let ((result (struct/set dummy name "Shoofus" test-dummy))) + ;; Test the immutability of `struct/set' + (prelude/assert (string= "Doofus" (dummy-name test-dummy))) + (prelude/assert (string= "Shoofus" (dummy-name result))))) + +(provide 'struct) +;;; struct.el ends here diff --git a/emacs/.emacs.d/wpc/symbol.el b/emacs/.emacs.d/wpc/symbol.el new file mode 100644 index 000000000000..9119b29470fd --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/terminator-themes.json b/emacs/.emacs.d/wpc/terminator-themes.json new file mode 100644 index 000000000000..e021ef12932f --- /dev/null +++ b/emacs/.emacs.d/wpc/terminator-themes.json @@ -0,0 +1,1794 @@ +{ + "themes": [ + { + "name": "3024 Day", + "palette": "#090300:#db2d20:#01a252:#fded02:#01a0e4:#a16a94:#b5e4f4:#a5a2a2:#5c5855:#e8bbd0:#3a3432:#4a4543:#807d7c:#d6d5d4:#cdab53:#f7f7f7", + "background_color": "#f7f7f7", + "cursor_color": "#4a4543", + "foreground_color": "#4a4543", + "background_image": "None", + "type": "light" + }, + { + "name": "3024 Night", + "palette": "#090300:#db2d20:#01a252:#fded02:#01a0e4:#a16a94:#b5e4f4:#a5a2a2:#5c5855:#e8bbd0:#3a3432:#4a4543:#807d7c:#d6d5d4:#cdab53:#f7f7f7", + "background_color": "#090300", + "cursor_color": "#a5a2a2", + "foreground_color": "#a5a2a2", + "background_image": "None", + "type": "dark" + }, + { + "name": "Aci", + "background_color": "#0d1926", + "background_image": "None", + "cursor_color": "#c4e9ff", + "foreground_color": "#b4e1fd", + "palette": "#363636:#ff0883:#83ff08:#ff8308:#0883ff:#8308ff:#08ff83:#b6b6b6:#363636:#ff0883:#83ff08:#ff8308:#0883ff:#8308ff:#08ff83:#b6b6b6", + "type": "dark" + }, + { + "name": "Aco", + "background_color": "#1f1305", + "background_image": "None", + "cursor_color": "#bae2fb", + "foreground_color": "#b4e1fd", + "palette": "#3f3f3f:#ff0883:#83ff08:#ff8308:#0883ff:#8308ff:#08ff83:#bebebe:#474747:#ff1e8e:#8eff1e:#ff8e1e:#0883ff:#8e1eff:#1eff8e:#c4c4c4", + "type": "dark" + }, + { + "name": "AdventureTime", + "palette": "#050404:#bd0013:#4ab118:#e7741e:#0f4ac6:#665993:#70a598:#f8dcc0:#4e7cbf:#fc5f5a:#9eff6e:#efc11a:#1997c6:#9b5953:#c8faf4:#f6f5fb", + "background_color": "#1f1d45", + "cursor_color": "#efbf38", + "foreground_color": "#f8dcc0", + "background_image": "None", + "type": "dark" + }, + { + "name": "After Dark", + "background_color": "#10111b", + "cursor_color": "#aaaaaa", + "palette": "#2e3436:#ef4a9e:#00d2bc:#e7ca7a:#9399fa:#ca5bcc:#86d079:#d3d7cf:#555753:#ef4a9e:#00d2bc:#e7ca7a:#9399fa:#ca5bcc:#86d079:#eeeeec", + "type": "dark" + }, + { + "name": "Afterglow", + "palette": "#151515:#ac4142:#7e8e50:#e5b567:#6c99bb:#9f4e85:#7dd6cf:#d0d0d0:#505050:#ac4142:#7e8e50:#e5b567:#6c99bb:#9f4e85:#7dd6cf:#f5f5f5", + "background_color": "#212121", + "cursor_color": "#d0d0d0", + "foreground_color": "#d0d0d0", + "background_image": "None", + "type": "dark" + }, + { + "name": "AlienBlood", + "palette": "#112616:#7f2b27:#2f7e25:#717f24:#2f6a7f:#47587f:#327f77:#647d75:#3c4812:#e08009:#18e000:#bde000:#00aae0:#0058e0:#00e0c4:#73fa91", + "background_color": "#0f1610", + "cursor_color": "#73fa91", + "foreground_color": "#637d75", + "background_image": "None", + "type": "dark" + }, + { + "name": "Argonaut", + "palette": "#232323:#ff000f:#8ce10b:#ffb900:#008df8:#6d43a6:#00d8eb:#ffffff:#444444:#ff2740:#abe15b:#ffd242:#0092ff:#9a5feb:#67fff0:#ffffff", + "background_color": "#0e1019", + "cursor_color": "#ff0018", + "foreground_color": "#fffaf4", + "background_image": "None", + "type": "dark" + }, + { + "name": "Arthur", + "palette": "#3d352a:#cd5c5c:#86af80:#e8ae5b:#6495ed:#deb887:#b0c4de:#bbaa99:#554444:#cc5533:#88aa22:#ffa75d:#87ceeb:#996600:#b0c4de:#ddccbb", + "background_color": "#1c1c1c", + "cursor_color": "#e2bbef", + "foreground_color": "#ddeedd", + "background_image": "None", + "type": "dark" + }, + { + "name": "AtelierSulphurpool", + "palette": "#202746:#c94922:#ac9739:#c08b30:#3d8fd1:#6679cc:#22a2c9:#979db4:#6b7394:#c76b29:#293256:#5e6687:#898ea4:#dfe2f1:#9c637a:#f5f7ff", + "background_color": "#202746", + "cursor_color": "#979db4", + "foreground_color": "#979db4", + "background_image": "None", + "type": "dark" + }, + { + "name": "Atom", + "palette": "#000000:#fd5ff1:#87c38a:#ffd7b1:#85befd:#b9b6fc:#85befd:#e0e0e0:#000000:#fd5ff1:#94fa36:#f5ffa8:#96cbfe:#b9b6fc:#85befd:#e0e0e0", + "background_color": "#161719", + "cursor_color": "#d0d0d0", + "foreground_color": "#c5c8c6", + "background_image": "None", + "type": "dark" + }, + { + "name": "AtomOneLight", + "palette": "#000000:#de3e35:#3f953a:#d2b67c:#2f5af3:#950095:#3f953a:#bbbbbb:#000000:#de3e35:#3f953a:#d2b67c:#2f5af3:#a00095:#3f953a:#ffffff", + "background_color": "#f9f9f9", + "cursor_color": "#bbbbbb", + "foreground_color": "#2a2c33", + "background_image": "None", + "type": "light" + }, + { + "name": "ayu", + "palette": "#000000:#ff3333:#b8cc52:#e7c547:#36a3d9:#f07178:#95e6cb:#ffffff:#323232:#ff6565:#eafe84:#fff779:#68d5ff:#ffa3aa:#c7fffd:#ffffff", + "background_color": "#0f1419", + "cursor_color": "#f29718", + "foreground_color": "#e6e1cf", + "background_image": "None", + "type": "dark" + }, + { + "name": "Ayu mirage", + "background_color": "#212733", + "background_image": "None", + "cursor_color": "#FFD580", + "foreground_color": "#d9d7ce", + "palette": "#212733:#ff3333:#bae67e:#ffd580:#5ccfe6:#d4bfff:#5ccfe6:#3d4752:#3e4b59:#ff3333:#bae67e:#ffd580:#5ccfe6:#d4bfff:#5ccfe6:#eeeeec", + "type": "dark" + }, + { + "name": "ayu_light", + "palette": "#000000:#ff3333:#86b300:#f29718:#41a6d9:#f07178:#4dbf99:#ffffff:#323232:#ff6565:#b8e532:#ffc94a:#73d8ff:#ffa3aa:#7ff1cb:#ffffff", + "background_color": "#fafafa", + "cursor_color": "#ff6a00", + "foreground_color": "#5c6773", + "background_image": "None", + "type": "light" + }, + { + "name": "Azu", + "background_color": "#09111a", + "background_image": "None", + "cursor_color": "#d2e8fc", + "foreground_color": "#d9e6f2", + "palette": "#000000:#ac6d74:#74ac6d:#aca46d:#6d74ac:#a46dac:#6daca4:#e6e6e6:#262626:#d6b8bc:#bcd6b8:#d6d3b8:#b8bcd6:#d3b8d6:#b8d6d3:#ffffff", + "type": "dark" + }, + { + "name": "Batman", + "palette": "#1b1d1e:#e6dc44:#c8be46:#f4fd22:#737174:#747271:#62605f:#c6c5bf:#505354:#fff78e:#fff27d:#feed6c:#919495:#9a9a9d:#a3a3a6:#dadbd6", + "background_color": "#1b1d1e", + "cursor_color": "#fcef0c", + "foreground_color": "#6f6f6f", + "background_image": "None", + "type": "dark" + }, + { + "name": "Belafonte Day", + "palette": "#20111b:#be100e:#858162:#eaa549:#426a79:#97522c:#989a9c:#968c83:#5e5252:#be100e:#858162:#eaa549:#426a79:#97522c:#989a9c:#d5ccba", + "background_color": "#d5ccba", + "cursor_color": "#45373c", + "foreground_color": "#45373c", + "background_image": "None", + "type": "light" + }, + { + "name": "Belafonte Night", + "palette": "#20111b:#be100e:#858162:#eaa549:#426a79:#97522c:#989a9c:#968c83:#5e5252:#be100e:#858162:#eaa549:#426a79:#97522c:#989a9c:#d5ccba", + "background_color": "#20111b", + "cursor_color": "#968c83", + "foreground_color": "#968c83", + "background_image": "None", + "type": "dark" + }, + { + "name": "Bim", + "background_color": "#012849", + "background_image": "None", + "cursor_color": "#c4d0de", + "foreground_color": "#a9bed8", + "palette": "#2c2423:#f557a0:#a9ee55:#f5a255:#5ea2ec:#a957ec:#5eeea0:#918988:#918988:#f579b2:#bbee78:#f5b378:#81b3ec:#bb79ec:#81eeb2:#f5eeec", + "type": "dark" + }, + { + "name": "BirdsOfParadise", + "palette": "#573d26:#be2d26:#6ba18a:#e99d2a:#5a86ad:#ac80a6:#74a6ad:#e0dbb7:#9b6c4a:#e84627:#95d8ba:#d0d150:#b8d3ed:#d19ecb:#93cfd7:#fff9d5", + "background_color": "#2a1f1d", + "cursor_color": "#573d26", + "foreground_color": "#e0dbb7", + "background_image": "None", + "type": "dark" + }, + { + "name": "Blazer", + "palette": "#000000:#b87a7a:#7ab87a:#b8b87a:#7a7ab8:#b87ab8:#7ab8b8:#d9d9d9:#262626:#dbbdbd:#bddbbd:#dbdbbd:#bdbddb:#dbbddb:#bddbdb:#ffffff", + "background_color": "#0d1926", + "cursor_color": "#d9e6f2", + "foreground_color": "#d9e6f2", + "background_image": "None", + "type": "dark" + }, + { + "name": "Blitz", + "background_color": "#16141e", + "cursor_color": "#00ecc8", + "foreground_color": "#00ecc8", + "palette": "#2e3436:#f70047:#00ff7d:#fcdd42:#26b3d2:#b055f4:#ff8db4:#d3d7cf:#555753:#ff5555:#55ff55:#ffff55:#729fcf:#ff55ff:#34e2e2:#eeeeec", + "type": "dark" + }, + { + "name": "Bloody", + "background_color": "#1e1f29", + "background_image": "None", + "cursor_color": "#f9dc5c", + "foreground_color": "#aaaaaa", + "palette": "#2e3436:#ff512f:#b2ffa9:#fffd82:#3185fc:#dd2476:#66d7d1:#f2efea:#555753:#ff512f:#b2ffa9:#fffd82:#3185fc:#dd2476:#66d7d1:#f2efea", + "type": "dark" + }, + { + "name": "Borland", + "palette": "#4f4f4f:#ff6c60:#a8ff60:#ffffb6:#96cbfe:#ff73fd:#c6c5fe:#eeeeee:#7c7c7c:#ffb6b0:#ceffac:#ffffcc:#b5dcff:#ff9cfe:#dfdffe:#ffffff", + "background_color": "#0000a4", + "cursor_color": "#ffa560", + "foreground_color": "#ffff4e", + "background_image": "None", + "type": "dark" + }, + { + "name": "Bright Lights", + "palette": "#191919:#ff355b:#b7e876:#ffc251:#76d4ff:#ba76e7:#6cbfb5:#c2c8d7:#191919:#ff355b:#b7e876:#ffc251:#76d5ff:#ba76e7:#6cbfb5:#c2c8d7", + "background_color": "#191919", + "cursor_color": "#f34b00", + "foreground_color": "#b3c9d7", + "background_image": "None", + "type": "dark" + }, + { + "name": "Broadcast", + "palette": "#000000:#da4939:#519f50:#ffd24a:#6d9cbe:#d0d0ff:#6e9cbe:#ffffff:#323232:#ff7b6b:#83d182:#ffff7c:#9fcef0:#ffffff:#a0cef0:#ffffff", + "background_color": "#2b2b2b", + "cursor_color": "#ffffff", + "foreground_color": "#e6e1dc", + "background_image": "None", + "type": "dark" + }, + { + "name": "Brogrammer", + "palette": "#1f1f1f:#f81118:#2dc55e:#ecba0f:#2a84d2:#4e5ab7:#1081d6:#d6dbe5:#d6dbe5:#de352e:#1dd361:#f3bd09:#1081d6:#5350b9:#0f7ddb:#ffffff", + "background_color": "#131313", + "cursor_color": "#b9b9b9", + "foreground_color": "#d6dbe5", + "background_image": "None", + "type": "dark" + }, + { + "name": "C64", + "palette": "#090300:#883932:#55a049:#bfce72:#40318d:#8b3f96:#67b6bd:#ffffff:#000000:#883932:#55a049:#bfce72:#40318d:#8b3f96:#67b6bd:#f7f7f7", + "background_color": "#40318d", + "cursor_color": "#7869c4", + "foreground_color": "#7869c4", + "background_image": "None", + "type": "dark" + }, + { + "name": "Cai", + "background_color": "#09111a", + "background_image": "None", + "cursor_color": "#e3eef9", + "foreground_color": "#d9e6f2", + "palette": "#000000:#ca274d:#4dca27:#caa427:#274dca:#a427ca:#27caa4:#808080:#808080:#e98da3:#a3e98d:#e9d48d:#8da3e9:#d48de9:#8de9d4:#ffffff", + "type": "dark" + }, + { + "name": "Candy", + "background_color": "#000000", + "foreground_color": "#AAAAAA", + "cursor_color": "#aaaaaa", + "palette": "#2e3436:#fa2573:#a6e32d:#fc951e:#c48dff:#fa2573:#67d9f0:#f2f2f2:#555753:#fa2573:#8ae234:#fce94f:#729fcf:#fa2573:#34e2e2:#eeeeec", + "type": "dark" + }, + { + "name": "Chalk", + "palette": "#7d8b8f:#b23a52:#789b6a:#b9ac4a:#2a7fac:#bd4f5a:#44a799:#d2d8d9:#888888:#f24840:#80c470:#ffeb62:#4196ff:#fc5275:#53cdbd:#d2d8d9", + "background_color": "#2b2d2e", + "cursor_color": "#708284", + "foreground_color": "#d2d8d9", + "background_image": "None", + "type": "dark" + }, + { + "name": "Chalkboard", + "palette": "#000000:#c37372:#72c373:#c2c372:#7372c3:#c372c2:#72c2c3:#d9d9d9:#323232:#dbaaaa:#aadbaa:#dadbaa:#aaaadb:#dbaada:#aadadb:#ffffff", + "background_color": "#29262f", + "cursor_color": "#d9e6f2", + "foreground_color": "#d9e6f2", + "background_image": "None", + "type": "dark" + }, + { + "name": "Chalkby", + "background_color": "#1f2d2d", + "cursor_color": "#ffffff", + "cursor_color_fg": "False", + "foreground_color": "#ffffff", + "palette": "#2e3436:#ffb0b0:#c8ff9b:#fffca4:#6f9ceb:#9395d3:#bdeaff:#d3d7cf:#555753:#ffb0b0:#c8ff9b:#fffca4:#6f9ceb:#9395d3:#bdeaff:#eeeeec", + "type": "dark" + }, + { + "name": "Chesterish", + "background_color": "#293340", + "background_image": "None", + "cursor_color": "#2c85f7", + "foreground_color": "#cdd2e9", + "palette": "#293340:#e17e85:#61ba86:#ffec8e:#4cb2ff:#be86e3:#2dced0:#cdd2e9:#546386:#e17e85:#61ba86:#ffec8e:#4cb2ff:#be86e3:#2dced0:#cdd2e9", + "type": "dark" + }, + { + "name": "Ciapre", + "palette": "#181818:#810009:#48513b:#cc8b3f:#576d8c:#724d7c:#5c4f4b:#aea47f:#555555:#ac3835:#a6a75d:#dcdf7c:#3097c6:#d33061:#f3dbb2:#f4f4f4", + "background_color": "#191c27", + "cursor_color": "#92805b", + "foreground_color": "#aea47a", + "background_image": "None", + "type": "dark" + }, + { + "name": "CLRS", + "palette": "#000000:#f8282a:#328a5d:#fa701d:#135cd0:#9f00bd:#33c3c1:#b3b3b3:#555753:#fb0416:#2cc631:#fdd727:#1670ff:#e900b0:#3ad5ce:#eeeeec", + "background_color": "#ffffff", + "cursor_color": "#6fd3fc", + "foreground_color": "#262626", + "background_image": "None", + "type": "light" + }, + { + "name": "Cobalt Neon", + "palette": "#142631:#ff2320:#3ba5ff:#e9e75c:#8ff586:#781aa0:#8ff586:#ba46b2:#fff688:#d4312e:#8ff586:#e9f06d:#3c7dd2:#8230a7:#6cbc67:#8ff586", + "background_color": "#142838", + "cursor_color": "#c4206f", + "foreground_color": "#8ff586", + "background_image": "None", + "type": "dark" + }, + { + "name": "Cobalt2", + "palette": "#000000:#ff0000:#38de21:#ffe50a:#1460d2:#ff005d:#00bbbb:#bbbbbb:#555555:#f40e17:#3bd01d:#edc809:#5555ff:#ff55ff:#6ae3fa:#ffffff", + "background_color": "#132738", + "cursor_color": "#f0cc09", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "CrayonPonyFish", + "palette": "#2b1b1d:#91002b:#579524:#ab311b:#8c87b0:#692f50:#e8a866:#68525a:#3d2b2e:#c5255d:#8dff57:#c8381d:#cfc9ff:#fc6cba:#ffceaf:#b0949d", + "background_color": "#150707", + "cursor_color": "#68525a", + "foreground_color": "#68525a", + "background_image": "None", + "type": "dark" + }, + { + "name": "Dark Pastel", + "palette": "#000000:#ff5555:#55ff55:#ffff55:#5555ff:#ff55ff:#55ffff:#bbbbbb:#555555:#ff5555:#55ff55:#ffff55:#5555ff:#ff55ff:#55ffff:#ffffff", + "background_color": "#000000", + "cursor_color": "#bbbbbb", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Darkside", + "palette": "#000000:#e8341c:#68c256:#f2d42c:#1c98e8:#8e69c9:#1c98e8:#bababa:#000000:#e05a4f:#77b869:#efd64b:#387cd3:#957bbe:#3d97e2:#bababa", + "background_color": "#222324", + "cursor_color": "#bbbbbb", + "foreground_color": "#bababa", + "background_image": "None", + "type": "dark" + }, + { + "name": "deep", + "palette": "#000000:#d70005:#1cd915:#d9bd26:#5665ff:#b052da:#50d2da:#e0e0e0:#535353:#fb0007:#22ff18:#fedc2b:#9fa9ff:#e09aff:#8df9ff:#ffffff", + "background_color": "#090909", + "cursor_color": "#d0d0d0", + "foreground_color": "#cdcdcd", + "background_image": "None", + "type": "dark" + }, + { + "name": "Desert", + "palette": "#4d4d4d:#ff2b2b:#98fb98:#f0e68c:#cd853f:#ffdead:#ffa0a0:#f5deb3:#555555:#ff5555:#55ff55:#ffff55:#87ceff:#ff55ff:#ffd700:#ffffff", + "background_color": "#333333", + "cursor_color": "#00ff00", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "DimmedMonokai", + "palette": "#3a3d43:#be3f48:#879a3b:#c5a635:#4f76a1:#855c8d:#578fa4:#b9bcba:#888987:#fb001f:#0f722f:#c47033:#186de3:#fb0067:#2e706d:#fdffb9", + "background_color": "#1f1f1f", + "cursor_color": "#f83e19", + "foreground_color": "#b9bcba", + "background_image": "None", + "type": "dark" + }, + { + "name": "DotGov", + "palette": "#191919:#bf091d:#3d9751:#f6bb34:#17b2e0:#7830b0:#8bd2ed:#ffffff:#191919:#bf091d:#3d9751:#f6bb34:#17b2e0:#7830b0:#8bd2ed:#ffffff", + "background_color": "#262c35", + "cursor_color": "#d9002f", + "foreground_color": "#ebebeb", + "background_image": "None", + "type": "dark" + }, + { + "name": "Dracula", + "background_color": "#1e1f29", + "background_image": "None", + "cursor_color": "#aaaaaa", + "foreground_color": "#f8f8f2", + "palette": "#44475a:#ff5555:#50fa7b:#f1fa8c:#8be9fd:#bd93f9:#ff79c6:#94a3a5:#000000:#ff5555:#50fa7b:#f1fa8c:#8be9fd:#bd93f9:#ff79c6:#ffffff", + "type": "dark" + }, + { + "name": "Duotone Dark", + "palette": "#1f1d27:#d9393e:#2dcd73:#d9b76e:#ffc284:#de8d40:#2488ff:#b7a1ff:#353147:#d9393e:#2dcd73:#d9b76e:#ffc284:#de8d40:#2488ff:#eae5ff", + "background_color": "#1f1d27", + "cursor_color": "#ff9839", + "foreground_color": "#b7a1ff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Earthsong", + "palette": "#121418:#c94234:#85c54c:#f5ae2e:#1398b9:#d0633d:#509552:#e5c6aa:#675f54:#ff645a:#98e036:#e0d561:#5fdaff:#ff9269:#84f088:#f6f7ec", + "background_color": "#292520", + "cursor_color": "#f6f7ec", + "foreground_color": "#e5c7a9", + "background_image": "None", + "type": "dark" + }, + { + "name": "Elemental", + "palette": "#3c3c30:#98290f:#479a43:#7f7111:#497f7d:#7f4e2f:#387f58:#807974:#555445:#e0502a:#61e070:#d69927:#79d9d9:#cd7c54:#59d599:#fff1e9", + "background_color": "#22211d", + "cursor_color": "#facb80", + "foreground_color": "#807a74", + "background_image": "None", + "type": "dark" + }, + { + "name": "Elementary", + "palette": "#242424:#d71c15:#5aa513:#fdb40c:#063b8c:#e40038:#2595e1:#efefef:#4b4b4b:#fc1c18:#6bc219:#fec80e:#0955ff:#fb0050:#3ea8fc:#8c00ec", + "background_color": "#181818", + "cursor_color": "#bbbbbb", + "foreground_color": "#efefef", + "background_image": "None", + "type": "dark" + }, + { + "name": "Elio", + "background_color": "#041a3b", + "background_image": "None", + "cursor_color": "#fbfbfb", + "foreground_color": "#f2f2f2", + "palette": "#303030:#e1321a:#6ab017:#ffc005:#729FCF:#ec0048:#2aa7e7:#f2f2f2:#5d5d5d:#ff361e:#7bc91f:#ffd00a:#0071ff:#ff1d62:#4bb8fd:#a020f0", + "type": "dark" + }, + { + "name": "ENCOM", + "palette": "#000000:#9f0000:#008b00:#ffd000:#0081ff:#bc00ca:#008b8b:#bbbbbb:#555555:#ff0000:#00ee00:#ffff00:#0000ff:#ff00ff:#00cdcd:#ffffff", + "background_color": "#000000", + "cursor_color": "#bbbbbb", + "foreground_color": "#00a595", + "background_image": "None", + "type": "dark" + }, + { + "name": "Espresso", + "palette": "#353535:#d25252:#a5c261:#ffc66d:#6c99bb:#d197d9:#bed6ff:#eeeeec:#535353:#f00c0c:#c2e075:#e1e48b:#8ab7d9:#efb5f7:#dcf4ff:#ffffff", + "background_color": "#323232", + "cursor_color": "#d6d6d6", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Espresso Libre", + "palette": "#000000:#cc0000:#1a921c:#f0e53a:#0066ff:#c5656b:#06989a:#d3d7cf:#555753:#ef2929:#9aff87:#fffb5c:#43a8ed:#ff818a:#34e2e2:#eeeeec", + "background_color": "#2a211c", + "cursor_color": "#ffffff", + "foreground_color": "#b8a898", + "background_image": "None", + "type": "dark" + }, + { + "name": "Fideloper", + "palette": "#292f33:#cb1e2d:#edb8ac:#b7ab9b:#2e78c2:#c0236f:#309186:#eae3ce:#092028:#d4605a:#d4605a:#a86671:#7c85c4:#5c5db2:#819090:#fcf4df", + "background_color": "#292f33", + "cursor_color": "#d4605a", + "foreground_color": "#dbdae0", + "background_image": "None", + "type": "dark" + }, + { + "name": "FirefoxDev", + "palette": "#002831:#e63853:#5eb83c:#a57706:#359ddf:#d75cff:#4b73a2:#dcdcdc:#001e27:#e1003f:#1d9000:#cd9409:#006fc0:#a200da:#005794:#e2e2e2", + "background_color": "#0e1011", + "cursor_color": "#708284", + "foreground_color": "#7c8fa4", + "background_image": "None", + "type": "dark" + }, + { + "name": "Firewatch", + "palette": "#585f6d:#d95360:#5ab977:#dfb563:#4d89c4:#d55119:#44a8b6:#e6e5ff:#585f6d:#d95360:#5ab977:#dfb563:#4c89c5:#d55119:#44a8b6:#e6e5ff", + "background_color": "#1e2027", + "cursor_color": "#f6f7ec", + "foreground_color": "#9ba2b2", + "background_image": "None", + "type": "dark" + }, + { + "name": "FishTank", + "palette": "#03073c:#c6004a:#acf157:#fecd5e:#525fb8:#986f82:#968763:#ecf0fc:#6c5b30:#da4b8a:#dbffa9:#fee6a9:#b2befa:#fda5cd:#a5bd86:#f6ffec", + "background_color": "#232537", + "cursor_color": "#fecd5e", + "foreground_color": "#ecf0fe", + "background_image": "None", + "type": "dark" + }, + { + "name": "Flat", + "palette": "#222d3f:#a82320:#32a548:#e58d11:#3167ac:#781aa0:#2c9370:#b0b6ba:#212c3c:#d4312e:#2d9440:#e5be0c:#3c7dd2:#8230a7:#35b387:#e7eced", + "background_color": "#002240", + "cursor_color": "#e5be0c", + "foreground_color": "#2cc55d", + "background_image": "None", + "type": "dark" + }, + { + "name": "Flatland", + "palette": "#1d1d19:#f18339:#9fd364:#f4ef6d:#5096be:#695abc:#d63865:#ffffff:#1d1d19:#d22a24:#a7d42c:#ff8949:#61b9d0:#695abc:#d63865:#ffffff", + "background_color": "#1d1f21", + "cursor_color": "#708284", + "foreground_color": "#b8dbef", + "background_image": "None", + "type": "dark" + }, + { + "name": "Floraverse", + "palette": "#08002e:#64002c:#5d731a:#cd751c:#1d6da1:#b7077e:#42a38c:#f3e0b8:#331e4d:#d02063:#b4ce59:#fac357:#40a4cf:#f12aae:#62caa8:#fff5db", + "background_color": "#0e0d15", + "cursor_color": "#bbbbbb", + "foreground_color": "#dbd1b9", + "background_image": "None", + "type": "dark" + }, + { + "name": "ForestBlue", + "palette": "#333333:#f8818e:#92d3a2:#1a8e63:#8ed0ce:#5e468c:#31658c:#e2d8cd:#3d3d3d:#fb3d66:#6bb48d:#30c85a:#39a7a2:#7e62b3:#6096bf:#e2d8cd", + "background_color": "#051519", + "cursor_color": "#9e9ecb", + "foreground_color": "#e2d8cd", + "background_image": "None", + "type": "dark" + }, + { + "name": "Freya", + "background_color": "#252e32", + "background_image": "None", + "cursor_color": "#839496", + "foreground_color": "#94a3a5", + "palette": "#073642:#dc322f:#859900:#b58900:#268bd2:#ec0048:#2aa198:#94a3a5:#586e75:#cb4b16:#859900:#b58900:#268bd2:#d33682:#2aa198:#6c71c4", + "type": "dark" + }, + { + "name": "FrontEndDelight", + "palette": "#242526:#f8511b:#565747:#fa771d:#2c70b7:#f02e4f:#3ca1a6:#adadad:#5fac6d:#f74319:#74ec4c:#fdc325:#3393ca:#e75e4f:#4fbce6:#8c735b", + "background_color": "#1b1c1d", + "cursor_color": "#cdcdcd", + "foreground_color": "#adadad", + "background_image": "None", + "type": "dark" + }, + { + "name": "FunForrest", + "palette": "#000000:#d6262b:#919c00:#be8a13:#4699a3:#8d4331:#da8213:#ddc265:#7f6a55:#e55a1c:#bfc65a:#ffcb1b:#7cc9cf:#d26349:#e6a96b:#ffeaa3", + "background_color": "#251200", + "cursor_color": "#e5591c", + "foreground_color": "#dec165", + "background_image": "None", + "type": "dark" + }, + { + "name": "Galaxy", + "palette": "#000000:#f9555f:#21b089:#fef02a:#589df6:#944d95:#1f9ee7:#bbbbbb:#555555:#fa8c8f:#35bb9a:#ffff55:#589df6:#e75699:#3979bc:#ffffff", + "background_color": "#1d2837", + "cursor_color": "#bbbbbb", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Github", + "palette": "#3e3e3e:#970b16:#07962a:#f8eec7:#003e8a:#e94691:#89d1ec:#ffffff:#666666:#de0000:#87d5a2:#f1d007:#2e6cba:#ffa29f:#1cfafe:#ffffff", + "background_color": "#f4f4f4", + "cursor_color": "#3f3f3f", + "foreground_color": "#3e3e3e", + "background_image": "None", + "type": "light" + }, + { + "name": "Glacier", + "palette": "#2e343c:#bd0f2f:#35a770:#fb9435:#1f5872:#bd2523:#778397:#ffffff:#404a55:#bd0f2f:#49e998:#fddf6e:#2a8bc1:#ea4727:#a0b6d3:#ffffff", + "background_color": "#0c1115", + "cursor_color": "#6c6c6c", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Grape", + "palette": "#2d283f:#ed2261:#1fa91b:#8ddc20:#487df4:#8d35c9:#3bdeed:#9e9ea0:#59516a:#f0729a:#53aa5e:#b2dc87:#a9bcec:#ad81c2:#9de3eb:#a288f7", + "background_color": "#171423", + "cursor_color": "#a288f7", + "foreground_color": "#9f9fa1", + "background_image": "None", + "type": "dark" + }, + { + "name": "Grass", + "palette": "#000000:#bb0000:#00bb00:#e7b000:#0000a3:#950062:#00bbbb:#bbbbbb:#555555:#bb0000:#00bb00:#e7b000:#0000bb:#ff55ff:#55ffff:#ffffff", + "background_color": "#13773d", + "cursor_color": "#8c2800", + "foreground_color": "#fff0a5", + "background_image": "None", + "type": "dark" + }, + { + "name": "Gruvbox Dark", + "palette": "#161819:#f73028:#aab01e:#f7b125:#719586:#c77089:#7db669:#faefbb:#7f7061:#be0f17:#868715:#cc881a:#377375:#a04b73:#578e57:#e6d4a3", + "background_color": "#1e1e1e", + "cursor_color": "#bbbbbb", + "foreground_color": "#e6d4a3", + "background_image": "None", + "type": "dark" + }, + { + "name": "Hardcore", + "palette": "#1b1d1e:#f92672:#a6e22e:#fd971f:#66d9ef:#9e6ffe:#5e7175:#ccccc6:#505354:#ff669d:#beed5f:#e6db74:#66d9ef:#9e6ffe:#a3babf:#f8f8f2", + "background_color": "#121212", + "cursor_color": "#bbbbbb", + "foreground_color": "#a0a0a0", + "background_image": "None", + "type": "dark" + }, + { + "name": "Harper", + "palette": "#010101:#f8b63f:#7fb5e1:#d6da25:#489e48:#b296c6:#f5bfd7:#a8a49d:#726e6a:#f8b63f:#7fb5e1:#d6da25:#489e48:#b296c6:#f5bfd7:#fefbea", + "background_color": "#010101", + "cursor_color": "#a8a49d", + "foreground_color": "#a8a49d", + "background_image": "None", + "type": "dark" + }, + { + "name": "Hemisu dark", + "background_image": "None", + "cursor_color": "#BAFFAA", + "foreground_color": "#FFFFFF", + "palette": "#444444:#FF0054:#B1D630:#9D895E:#67BEE3:#B576BC:#569A9F:#EDEDED:#777777:#D65E75:#BAFFAA:#ECE1C8:#9FD3E5:#DEB3DF:#B6E0E5:#FFFFFF", + "type": "dark" + }, + { + "name": "Hemisu light", + "background_color": "#EFEFEF", + "background_image": "None", + "cursor_color": "#FF0054", + "foreground_color": "#444444", + "palette": "#777777:#FF0055:#739100:#503D15:#538091:#5B345E:#538091:#999999:#999999:#D65E76:#9CC700:#947555:#9DB3CD:#A184A4:#85B2AA:#BABABA", + "type": "light" + }, + { + "name": "Highway", + "palette": "#000000:#d00e18:#138034:#ffcb3e:#006bb3:#6b2775:#384564:#ededed:#5d504a:#f07e18:#b1d130:#fff120:#4fc2fd:#de0071:#5d504a:#ffffff", + "background_color": "#222225", + "cursor_color": "#e0d9b9", + "foreground_color": "#ededed", + "background_image": "None", + "type": "dark" + }, + { + "name": "Hipster Green", + "palette": "#000000:#b6214a:#00a600:#bfbf00:#246eb2:#b200b2:#00a6b2:#bfbfbf:#666666:#e50000:#86a93e:#e5e500:#0000ff:#e500e5:#00e5e5:#e5e5e5", + "background_color": "#100b05", + "cursor_color": "#23ff18", + "foreground_color": "#84c138", + "background_image": "None", + "type": "dark" + }, + { + "name": "Homebrew", + "palette": "#000000:#990000:#00a600:#999900:#0000b2:#b200b2:#00a6b2:#bfbfbf:#666666:#e50000:#00d900:#e5e500:#0000ff:#e500e5:#00e5e5:#e5e5e5", + "background_color": "#000000", + "cursor_color": "#23ff18", + "foreground_color": "#00ff00", + "background_image": "None", + "type": "dark" + }, + { + "name": "Hurtado", + "palette": "#575757:#ff1b00:#a5e055:#fbe74a:#496487:#fd5ff1:#86e9fe:#cbcccb:#262626:#d51d00:#a5df55:#fbe84a:#89beff:#c001c1:#86eafe:#dbdbdb", + "background_color": "#000000", + "cursor_color": "#bbbbbb", + "foreground_color": "#dbdbdb", + "background_image": "None", + "type": "dark" + }, + { + "name": "Hybrid", + "palette": "#2a2e33:#b84d51:#b3bf5a:#e4b55e:#6e90b0:#a17eac:#7fbfb4:#b5b9b6:#1d1f22:#8d2e32:#798431:#e58a50:#4b6b88:#6e5079:#4d7b74:#5a626a", + "background_color": "#161719", + "cursor_color": "#b7bcba", + "foreground_color": "#b7bcba", + "background_image": "None", + "type": "dark" + }, + { + "name": "IC_Green_PPL", + "palette": "#1f1f1f:#fb002a:#339c24:#659b25:#149b45:#53b82c:#2cb868:#e0ffef:#032710:#a7ff3f:#9fff6d:#d2ff6d:#72ffb5:#50ff3e:#22ff71:#daefd0", + "background_color": "#3a3d3f", + "cursor_color": "#42ff58", + "foreground_color": "#d9efd3", + "background_image": "None", + "type": "dark" + }, + { + "name": "IC_Orange_PPL", + "palette": "#000000:#c13900:#a4a900:#caaf00:#bd6d00:#fc5e00:#f79500:#ffc88a:#6a4f2a:#ff8c68:#f6ff40:#ffe36e:#ffbe55:#fc874f:#c69752:#fafaff", + "background_color": "#262626", + "cursor_color": "#fc531d", + "foreground_color": "#ffcb83", + "background_image": "None", + "type": "dark" + }, + { + "name": "idleToes", + "palette": "#323232:#d25252:#7fe173:#ffc66d:#4099ff:#f680ff:#bed6ff:#eeeeec:#535353:#f07070:#9dff91:#ffe48b:#5eb7f7:#ff9dff:#dcf4ff:#ffffff", + "background_color": "#323232", + "cursor_color": "#d6d6d6", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "IR_Black", + "palette": "#4f4f4f:#fa6c60:#a8ff60:#fffeb7:#96cafe:#fa73fd:#c6c5fe:#efedef:#7b7b7b:#fcb6b0:#cfffab:#ffffcc:#b5dcff:#fb9cfe:#e0e0fe:#ffffff", + "background_color": "#000000", + "cursor_color": "#808080", + "foreground_color": "#f1f1f1", + "background_image": "None", + "type": "dark" + }, + { + "name": "Jackie Brown", + "palette": "#2c1d16:#ef5734:#2baf2b:#bebf00:#246eb2:#d05ec1:#00acee:#bfbfbf:#666666:#e50000:#86a93e:#e5e500:#0000ff:#e500e5:#00e5e5:#e5e5e5", + "background_color": "#2c1d16", + "cursor_color": "#23ff18", + "foreground_color": "#ffcc2f", + "background_image": "None", + "type": "dark" + }, + { + "name": "Japanesque", + "palette": "#343935:#cf3f61:#7bb75b:#e9b32a:#4c9ad4:#a57fc4:#389aad:#fafaf6:#595b59:#d18fa6:#767f2c:#78592f:#135979:#604291:#76bbca:#b2b5ae", + "background_color": "#1e1e1e", + "cursor_color": "#edcf4f", + "foreground_color": "#f7f6ec", + "background_image": "None", + "type": "dark" + }, + { + "name": "Jellybeans", + "palette": "#929292:#e27373:#94b979:#ffba7b:#97bedc:#e1c0fa:#00988e:#dedede:#bdbdbd:#ffa1a1:#bddeab:#ffdca0:#b1d8f6:#fbdaff:#1ab2a8:#ffffff", + "background_color": "#121212", + "cursor_color": "#ffa560", + "foreground_color": "#dedede", + "background_image": "None", + "type": "dark" + }, + { + "name": "JetBrains Darcula", + "palette": "#000000:#fa5355:#126e00:#c2c300:#4581eb:#fa54ff:#33c2c1:#adadad:#555555:#fb7172:#67ff4f:#ffff00:#6d9df1:#fb82ff:#60d3d1:#eeeeee", + "background_color": "#202020", + "cursor_color": "#ffffff", + "foreground_color": "#adadad", + "background_image": "None", + "type": "dark" + }, + { + "name": "Juicy", + "background_color": "#212121", + "cursor_color": "#fcfcfc", + "foreground_color": "#fcfcfc", + "palette": "#2e3436:#ff0945:#1aff81:#fff64a:#2bf1ff:#7b68ee:#98f4ff:#d3d7cf:#555753:#ff0945:#1aff81:#fff64a:#2bf1ff:#7b68ee:#98f4ff:#eeeeec", + "background_image": "None", + "type": "dark" + }, + { + "name": "Kibble", + "palette": "#4d4d4d:#c70031:#29cf13:#d8e30e:#3449d1:#8400ff:#0798ab:#e2d1e3:#5a5a5a:#f01578:#6ce05c:#f3f79e:#97a4f7:#c495f0:#68f2e0:#ffffff", + "background_color": "#0e100a", + "cursor_color": "#9fda9c", + "foreground_color": "#f7f7f7", + "background_image": "None", + "type": "dark" + }, + { + "name": "Later This Evening", + "palette": "#2b2b2b:#d45a60:#afba67:#e5d289:#a0bad6:#c092d6:#91bfb7:#3c3d3d:#454747:#d3232f:#aabb39:#e5be39:#6699d6:#ab53d6:#5fc0ae:#c1c2c2", + "background_color": "#222222", + "cursor_color": "#424242", + "foreground_color": "#959595", + "background_image": "None", + "type": "dark" + }, + { + "name": "Lavandula", + "palette": "#230046:#7d1625:#337e6f:#7f6f49:#4f4a7f:#5a3f7f:#58777f:#736e7d:#372d46:#e05167:#52e0c4:#e0c386:#8e87e0:#a776e0:#9ad4e0:#8c91fa", + "background_color": "#050014", + "cursor_color": "#8c91fa", + "foreground_color": "#736e7d", + "background_image": "None", + "type": "dark" + }, + { + "name": "LiquidCarbon", + "palette": "#000000:#ff3030:#559a70:#ccac00:#0099cc:#cc69c8:#7ac4cc:#bccccc:#000000:#ff3030:#559a70:#ccac00:#0099cc:#cc69c8:#7ac4cc:#bccccc", + "background_color": "#303030", + "cursor_color": "#ffffff", + "foreground_color": "#afc2c2", + "background_image": "None", + "type": "dark" + }, + { + "name": "LiquidCarbonTransparent", + "palette": "#000000:#ff3030:#559a70:#ccac00:#0099cc:#cc69c8:#7ac4cc:#bccccc:#000000:#ff3030:#559a70:#ccac00:#0099cc:#cc69c8:#7ac4cc:#bccccc", + "background_color": "#000000", + "cursor_color": "#ffffff", + "foreground_color": "#afc2c2", + "background_image": "None", + "type": "dark" + }, + { + "name": "LiquidCarbonTransparentInverse", + "palette": "#bccccd:#ff3030:#559a70:#ccac00:#0099cc:#cc69c8:#7ac4cc:#000000:#ffffff:#ff3030:#559a70:#ccac00:#0099cc:#cc69c8:#7ac4cc:#000000", + "background_color": "#000000", + "cursor_color": "#ffffff", + "foreground_color": "#afc2c2", + "background_image": "None", + "type": "dark" + }, + { + "name": "Lucy", + "background_color": "#1a1b23", + "cursor_color": "#af98e6", + "foreground_color": "#96979b", + "palette": "#2e3436:#fb7da7:#76c5a4:#e8d56d:#3465a4:#af98e6:#56c9db:#d3d7cf:#555753:#fb7da7:#76c5a4:#e8d56d:#729fcf:#af98e6:#56c9db:#eeeeec", + "type": "dark" + }, + { + "name": "Man Page", + "palette": "#000000:#cc0000:#00a600:#999900:#0000b2:#b200b2:#00a6b2:#cccccc:#666666:#e50000:#00d900:#e5e500:#0000ff:#e500e5:#00e5e5:#e5e5e5", + "background_color": "#fef49c", + "cursor_color": "#7f7f7f", + "foreground_color": "#000000", + "background_image": "None", + "type": "light" + }, + { + "name": "Mar", + "background_color": "#ffffff", + "background_image": "None", + "cursor_color": "#23476a", + "foreground_color": "#23476a", + "palette": "#000000:#b5407b:#7bb540:#b57b40:#407bb5:#7b40b5:#40b57b:#f8f8f8:#737373:#cd73a0:#a0cd73:#cda073:#73a0cd:#a073cd:#73cda0:#ffffff", + "type": "light" + }, + { + "name": "Material", + "palette": "#212121:#b7141f:#457b24:#f6981e:#134eb2:#560088:#0e717c:#efefef:#424242:#e83b3f:#7aba3a:#ffea2e:#54a4f3:#aa4dbc:#26bbd1:#d9d9d9", + "background_color": "#eaeaea", + "cursor_color": "#16afca", + "foreground_color": "#232322", + "background_image": "None", + "type": "light" + }, + { + "name": "Material colors", + "background_color": "#1E282C", + "background_image": "None", + "cursor_color": "#657B83", + "foreground_color": "#C3C7D1", + "palette": "#073641:#EB606B:#C3E88D:#F7EB95:#80CBC3:#FF2490:#AEDDFF:#FFFFFF:#002B36:#EB606B:#C3E88D:#F7EB95:#7DC6BF:#6C71C3:#34434D:#FFFFFF", + "type": "dark" + }, + { + "name": "Material-Ocean", + "background_color": "#0f111a", + "cursor_color": "#ffcc00", + "cursor_color_fg": "False", + "foreground_color": "#8f93a2", + "palette": "#2e3436:#ff5370:#c3e88d:#ffcb6b:#82aaff:#c792ea:#89ddff:#d3d7cf:#555753:#f07178:#c3e88d:#f78c6c:#729fcf:#bb80b3:#89ddff:#eeeeec", + "type": "dark" + }, + { + "name": "Material-Palenight", + "background_color": "#292d3e", + "cursor_color": "#ffcc00", + "cursor_color_fg": "False", + "foreground_color": "#a6accd", + "palette": "#2e3436:#ff5370:#c3e88d:#ffcb6b:#82aaff:#c792ea:#89ddff:#d3d7cf:#555753:#f07178:#c3e88d:#f78c6c:#729fcf:#bb80b3:#89ddff:#eeeeec", + "type": "dark" + }, + { + "name": "MaterialDark", + "palette": "#212121:#b7141f:#457b24:#f6981e:#134eb2:#560088:#0e717c:#efefef:#424242:#e83b3f:#7aba3a:#ffea2e:#54a4f3:#aa4dbc:#26bbd1:#d9d9d9", + "background_color": "#232322", + "cursor_color": "#16afca", + "foreground_color": "#e5e5e5", + "background_image": "None", + "type": "dark" + }, + { + "name": "Mathias", + "palette": "#000000:#e52222:#a6e32d:#fc951e:#c48dff:#fa2573:#67d9f0:#f2f2f2:#555555:#ff5555:#55ff55:#ffff55:#5555ff:#ff55ff:#55ffff:#ffffff", + "background_color": "#000000", + "cursor_color": "#bbbbbb", + "foreground_color": "#bbbbbb", + "background_image": "None", + "type": "dark" + }, + { + "name": "Medallion", + "palette": "#000000:#b64c00:#7c8b16:#d3bd26:#616bb0:#8c5a90:#916c25:#cac29a:#5e5219:#ff9149:#b2ca3b:#ffe54a:#acb8ff:#ffa0ff:#ffbc51:#fed698", + "background_color": "#1d1908", + "cursor_color": "#d3ba30", + "foreground_color": "#cac296", + "background_image": "None", + "type": "dark" + }, + { + "name": "Misterioso", + "palette": "#000000:#ff4242:#74af68:#ffad29:#338f86:#9414e6:#23d7d7:#e1e1e0:#555555:#ff3242:#74cd68:#ffb929:#23d7d7:#ff37ff:#00ede1:#ffffff", + "background_color": "#2d3743", + "cursor_color": "#000000", + "foreground_color": "#e1e1e0", + "background_image": "None", + "type": "dark" + }, + { + "name": "Miu", + "background_color": "#0d1926", + "background_image": "None", + "cursor_color": "#d7dee4", + "foreground_color": "#d9e6f2", + "palette": "#000000:#b87a7a:#7ab87a:#b8b87a:#7a7ab8:#b87ab8:#7ab8b8:#d9d9d9:#262626:#dbbdbd:#bddbbd:#dbdbbd:#bdbddb:#dbbddb:#bddbdb:#ffffff", + "type": "dark" + }, + { + "name": "Molokai", + "palette": "#121212:#fa2573:#98e123:#dfd460:#1080d0:#8700ff:#43a8d0:#bbbbbb:#555555:#f6669d:#b1e05f:#fff26d:#00afff:#af87ff:#51ceff:#ffffff", + "background_color": "#121212", + "cursor_color": "#bbbbbb", + "foreground_color": "#bbbbbb", + "background_image": "None", + "type": "dark" + }, + { + "name": "MonaLisa", + "palette": "#351b0e:#9b291c:#636232:#c36e28:#515c5d:#9b1d29:#588056:#f7d75c:#874228:#ff4331:#b4b264:#ff9566:#9eb2b4:#ff5b6a:#8acd8f:#ffe598", + "background_color": "#120b0d", + "cursor_color": "#c46c32", + "foreground_color": "#f7d66a", + "background_image": "None", + "type": "dark" + }, + { + "name": "Monokai dark", + "background_color": "#272822", + "background_image": "None", + "cursor_color": "#ffffff", + "foreground_color": "#f8f8f2", + "palette": "#75715e:#f92672:#a6e22e:#f4bf75:#66d9ef:#ae81ff:#2aa198:#f9f8f5:#272822:#f92672:#a6e22e:#f4bf75:#66d9ef:#ae81ff:#2aa198:#f9f8f5", + "type": "dark" + }, + { + "name": "Monokai Soda", + "palette": "#1a1a1a:#f4005f:#98e024:#fa8419:#9d65ff:#f4005f:#58d1eb:#c4c5b5:#625e4c:#f4005f:#98e024:#e0d561:#9d65ff:#f4005f:#58d1eb:#f6f6ef", + "background_color": "#1a1a1a", + "cursor_color": "#f6f7ec", + "foreground_color": "#c4c5b5", + "background_image": "None", + "type": "dark" + }, + { + "name": "Monokai Vivid", + "palette": "#121212:#fa2934:#98e123:#fff30a:#0443ff:#f800f8:#01b6ed:#ffffff:#838383:#f6669d:#b1e05f:#fff26d:#0443ff:#f200f6:#51ceff:#ffffff", + "background_color": "#121212", + "cursor_color": "#fb0007", + "foreground_color": "#f9f9f9", + "background_image": "None", + "type": "dark" + }, + { + "name": "N0tch2k", + "palette": "#383838:#a95551:#666666:#a98051:#657d3e:#767676:#c9c9c9:#d0b8a3:#474747:#a97775:#8c8c8c:#a99175:#98bd5e:#a3a3a3:#dcdcdc:#d8c8bb", + "background_color": "#222222", + "cursor_color": "#aa9175", + "foreground_color": "#a0a0a0", + "background_image": "None", + "type": "dark" + }, + { + "name": "Nebula", + "background_color": "#23262e", + "cursor_color": "#00e8c6", + "foreground_color": "#ffffff", + "palette": "#2e3436:#ff007a:#84ff39:#f3d56e:#7cb7ff:#c74ded:#00e8c6:#d3d7cf:#555753:#ff007a:#84ff39:#f3d56e:#7cb7ff:#c74ded:#00e8c6:#eeeeec", + "type": "dark" + }, + { + "name": "Neopolitan", + "palette": "#000000:#800000:#61ce3c:#fbde2d:#253b76:#ff0080:#8da6ce:#f8f8f8:#000000:#800000:#61ce3c:#fbde2d:#253b76:#ff0080:#8da6ce:#f8f8f8", + "background_color": "#271f19", + "cursor_color": "#ffffff", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Neutron", + "palette": "#23252b:#b54036:#5ab977:#deb566:#6a7c93:#a4799d:#3f94a8:#e6e8ef:#23252b:#b54036:#5ab977:#deb566:#6a7c93:#a4799d:#3f94a8:#ebedf2", + "background_color": "#1c1e22", + "cursor_color": "#f6f7ec", + "foreground_color": "#e6e8ef", + "background_image": "None", + "type": "dark" + }, + { + "name": "Night Owl", + "background_color": "#011627", + "cursor_color": "#80a4c2", + "cursor_color_fg": "False", + "foreground_color": "#d6deeb", + "palette": "#2e3436:#ef5350:#80cbc4:#ffeb95:#82aaff:#c792ea:#addb67:#d3d7cf:#555753:#ef5350:#80cbc4:#ffeb95:#82aaff:#c792ea:#addb67:#eeeeec", + "type": "dark" + }, + { + "name": "NightLion v1", + "palette": "#4c4c4c:#bb0000:#5fde8f:#f3f167:#276bd8:#bb00bb:#00dadf:#bbbbbb:#555555:#ff5555:#55ff55:#ffff55:#5555ff:#ff55ff:#55ffff:#ffffff", + "background_color": "#000000", + "cursor_color": "#bbbbbb", + "foreground_color": "#bbbbbb", + "background_image": "None", + "type": "dark" + }, + { + "name": "NightLion v2", + "palette": "#4c4c4c:#bb0000:#04f623:#f3f167:#64d0f0:#ce6fdb:#00dadf:#bbbbbb:#555555:#ff5555:#7df71d:#ffff55:#62cbe8:#ff9bf5:#00ccd8:#ffffff", + "background_color": "#171717", + "cursor_color": "#bbbbbb", + "foreground_color": "#bbbbbb", + "background_image": "None", + "type": "dark" + }, + { + "name": "Nord", + "background_color": "#2E3440", + "cursor_color": "#D8DEE9", + "foreground_color": "#D8DEE9", + "palette": "#3B4252:#BF616A:#A3BE8C:#EBCB8B:#81A1C1:#B48EAD:#88C0D0:#E5E9F0:#4C566A:#BF616A:#A3BE8C:#EBCB8B:#81A1C1:#B48EAD:#8FBCBB:#ECEFF4", + "type": "dark" + }, + { + "name": "Novel", + "palette": "#000000:#cc0000:#009600:#d06b00:#0000cc:#cc00cc:#0087cc:#cccccc:#808080:#cc0000:#009600:#d06b00:#0000cc:#cc00cc:#0087cc:#ffffff", + "background_color": "#dfdbc3", + "cursor_color": "#73635a", + "foreground_color": "#3b2322", + "background_image": "None", + "type": "light" + }, + { + "name": "Obsidian", + "palette": "#000000:#a60001:#00bb00:#fecd22:#3a9bdb:#bb00bb:#00bbbb:#bbbbbb:#555555:#ff0003:#93c863:#fef874:#a1d7ff:#ff55ff:#55ffff:#ffffff", + "background_color": "#283033", + "cursor_color": "#c0cad0", + "foreground_color": "#cdcdcd", + "background_image": "None", + "type": "dark" + }, + { + "name": "Ocean", + "palette": "#000000:#990000:#00a600:#999900:#0000b2:#b200b2:#00a6b2:#bfbfbf:#666666:#e50000:#00d900:#e5e500:#0000ff:#e500e5:#00e5e5:#e5e5e5", + "background_color": "#224fbc", + "cursor_color": "#7f7f7f", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Ocean dark", + "background_color": "#1c1f27", + "background_image": "None", + "cursor_color": "#a0a4b2", + "foreground_color": "#979cac", + "palette": "#4F4F4F:#AF4B57:#AFD383:#E5C079:#7D90A4:#A4799D:#85A6A5:#EEEDEE:#7B7B7B:#AF4B57:#CEFFAB:#FFFECC:#B5DCFE:#FB9BFE:#DFDFFD:#FEFFFE", + "type": "dark" + }, + { + "name": "OceanicMaterial", + "palette": "#000000:#ee2b2a:#40a33f:#ffea2e:#1e80f0:#8800a0:#16afca:#a4a4a4:#777777:#dc5c60:#70be71:#fff163:#54a4f3:#aa4dbc:#42c7da:#ffffff", + "background_color": "#1c262b", + "cursor_color": "#b3b8c3", + "foreground_color": "#c2c8d7", + "background_image": "None", + "type": "dark" + }, + { + "name": "Ollie", + "palette": "#000000:#ac2e31:#31ac61:#ac4300:#2d57ac:#b08528:#1fa6ac:#8a8eac:#5b3725:#ff3d48:#3bff99:#ff5e1e:#4488ff:#ffc21d:#1ffaff:#5b6ea7", + "background_color": "#222125", + "cursor_color": "#5b6ea7", + "foreground_color": "#8a8dae", + "background_image": "None", + "type": "dark" + }, + { + "name": "One dark", + "background_color": "#1e2127", + "background_image": "None", + "cursor_color": "#676c76", + "foreground_color": "#5c6370", + "palette": "#000000:#e06c75:#98c379:#d19a66:#61afef:#c678dd:#56b6c2:#abb2bf:#5c6370:#e06c75:#98c379:#d19a66:#61afef:#c678dd:#56b6c2:#fffefe", + "type": "dark" + }, + { + "name": "OneHalfDark", + "palette": "#282c34:#e06c75:#98c379:#e5c07b:#61afef:#c678dd:#56b6c2:#dcdfe4:#282c34:#e06c75:#98c379:#e5c07b:#61afef:#c678dd:#56b6c2:#dcdfe4", + "background_color": "#282c34", + "cursor_color": "#a3b3cc", + "foreground_color": "#dcdfe4", + "background_image": "None", + "type": "dark" + }, + { + "name": "OneHalfLight", + "palette": "#383a42:#e45649:#50a14f:#c18401:#0184bc:#a626a4:#0997b3:#fafafa:#4f525e:#e06c75:#98c379:#e5c07b:#61afef:#c678dd:#56b6c2:#ffffff", + "background_color": "#fafafa", + "cursor_color": "#bfceff", + "foreground_color": "#383a42", + "background_image": "None", + "type": "light" + }, + { + "name": "Pali", + "background_color": "#232e37", + "background_image": "None", + "cursor_color": "#e3ecf5", + "foreground_color": "#d9e6f2", + "palette": "#0a0a0a:#ab8f74:#74ab8f:#8fab74:#8f74ab:#ab748f:#748fab:#f2f2f2:#5d5d5d:#ff1d62:#9cc3af:#ffd00a:#af9cc3:#ff1d62:#4bb8fd:#a020f0", + "type": "dark" + }, + { + "name": "Panda", + "background_color": "#292a2b", + "cursor_color": "#f0eeee", + "foreground_color": "#e6e6e6", + "palette": "#676b79:#ff2c6d:#19f9d8:#ffb86c:#45a9f9:#b084eb:#6fc1ff:#d3d7cf:#676b79:#ff9ac1:#19f9d8:#ffcc95:#45a9f9:#b084eb:#6fc1ff:#eeeeec", + "background_image": "None", + "type": "dark" + }, + { + "name": "Pandora", + "palette": "#000000:#ff4242:#74af68:#ffad29:#338f86:#9414e6:#23d7d7:#e2e2e2:#3f5648:#ff3242:#74cd68:#ffb929:#23d7d7:#ff37ff:#00ede1:#ffffff", + "background_color": "#141e43", + "cursor_color": "#43d58e", + "foreground_color": "#e1e1e1", + "background_image": "None", + "type": "dark" + }, + { + "name": "Paraiso Dark", + "palette": "#2f1e2e:#ef6155:#48b685:#fec418:#06b6ef:#815ba4:#5bc4bf:#a39e9b:#776e71:#ef6155:#48b685:#fec418:#06b6ef:#815ba4:#5bc4bf:#e7e9db", + "background_color": "#2f1e2e", + "cursor_color": "#a39e9b", + "foreground_color": "#a39e9b", + "background_image": "None", + "type": "dark" + }, + { + "name": "Parasio Dark", + "palette": "#2f1e2e:#ef6155:#48b685:#fec418:#06b6ef:#815ba4:#5bc4bf:#a39e9b:#776e71:#ef6155:#48b685:#fec418:#06b6ef:#815ba4:#5bc4bf:#e7e9db", + "background_color": "#2f1e2e", + "cursor_color": "#a39e9b", + "foreground_color": "#a39e9b", + "background_image": "None", + "type": "dark" + }, + { + "name": "PaulMillr", + "palette": "#2a2a2a:#ff0000:#79ff0f:#e7bf00:#396bd7:#b449be:#66ccff:#bbbbbb:#666666:#ff0080:#66ff66:#f3d64e:#709aed:#db67e6:#7adff2:#ffffff", + "background_color": "#000000", + "cursor_color": "#4d4d4d", + "foreground_color": "#f2f2f2", + "background_image": "None", + "type": "dark" + }, + { + "name": "PencilDark", + "palette": "#212121:#c30771:#10a778:#a89c14:#008ec4:#523c79:#20a5ba:#d9d9d9:#424242:#fb007a:#5fd7af:#f3e430:#20bbfc:#6855de:#4fb8cc:#f1f1f1", + "background_color": "#212121", + "cursor_color": "#20bbfc", + "foreground_color": "#f1f1f1", + "background_image": "None", + "type": "dark" + }, + { + "name": "PencilLight", + "palette": "#212121:#c30771:#10a778:#a89c14:#008ec4:#523c79:#20a5ba:#d9d9d9:#424242:#fb007a:#5fd7af:#f3e430:#20bbfc:#6855de:#4fb8cc:#f1f1f1", + "background_color": "#f1f1f1", + "cursor_color": "#20bbfc", + "foreground_color": "#424242", + "background_image": "None", + "type": "light" + }, + { + "name": "Peppermint", + "background_image": "None", + "cursor_color": "#BBBBBB", + "foreground_color": "#c7c7c7", + "palette": "#353535:#E64569:#89D287:#DAB752:#439ECF:#D961DC:#64AAAF:#B3B3B3:#535353:#E4859A:#A2CCA1:#E1E387:#6FBBE2:#E586E7:#96DCDA:#DEDEDE", + "type": "dark" + }, + { + "name": "Piatto Light", + "palette": "#414141:#b23771:#66781e:#cd6f34:#3c5ea8:#a454b2:#66781e:#ffffff:#3f3f3f:#db3365:#829429:#cd6f34:#3c5ea8:#a454b2:#829429:#f2f2f2", + "background_color": "#ffffff", + "cursor_color": "#5e77c8", + "foreground_color": "#414141", + "background_image": "None", + "type": "light" + }, + { + "name": "Pnevma", + "palette": "#2f2e2d:#a36666:#90a57d:#d7af87:#7fa5bd:#c79ec4:#8adbb4:#d0d0d0:#4a4845:#d78787:#afbea2:#e4c9af:#a1bdce:#d7beda:#b1e7dd:#efefef", + "background_color": "#1c1c1c", + "cursor_color": "#e4c9af", + "foreground_color": "#d0d0d0", + "background_image": "None", + "type": "dark" + }, + { + "name": "Pro", + "palette": "#000000:#990000:#00a600:#999900:#2009db:#b200b2:#00a6b2:#bfbfbf:#666666:#e50000:#00d900:#e5e500:#0000ff:#e500e5:#00e5e5:#e5e5e5", + "background_color": "#000000", + "cursor_color": "#4d4d4d", + "foreground_color": "#f2f2f2", + "background_image": "None", + "type": "dark" + }, + { + "name": "Red Alert", + "palette": "#000000:#d62e4e:#71be6b:#beb86b:#489bee:#e979d7:#6bbeb8:#d6d6d6:#262626:#e02553:#aff08c:#dfddb7:#65aaf1:#ddb7df:#b7dfdd:#ffffff", + "background_color": "#762423", + "cursor_color": "#ffffff", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Red Planet", + "palette": "#202020:#8c3432:#728271:#e8bf6a:#69819e:#896492:#5b8390:#b9aa99:#676767:#b55242:#869985:#ebeb91:#60827e:#de4974:#38add8:#d6bfb8", + "background_color": "#222222", + "cursor_color": "#c2b790", + "foreground_color": "#c2b790", + "background_image": "None", + "type": "dark" + }, + { + "name": "Red Sands", + "palette": "#000000:#ff3f00:#00bb00:#e7b000:#0072ff:#bb00bb:#00bbbb:#bbbbbb:#555555:#bb0000:#00bb00:#e7b000:#0072ae:#ff55ff:#55ffff:#ffffff", + "background_color": "#7a251e", + "cursor_color": "#ffffff", + "foreground_color": "#d7c9a7", + "background_image": "None", + "type": "dark" + }, + { + "name": "Relaxed", + "palette": "#151515:#bc5653:#909d63:#ebc17a:#6a8799:#b06698:#c9dfff:#d9d9d9:#636363:#bc5653:#a0ac77:#ebc17a:#7eaac7:#b06698:#acbbd0:#f7f7f7", + "background_color": "#353a44", + "cursor_color": "#d9d9d9", + "foreground_color": "#d9d9d9", + "background_image": "None", + "type": "dark" + }, + { + "name": "Rippedcasts", + "palette": "#000000:#cdaf95:#a8ff60:#bfbb1f:#75a5b0:#ff73fd:#5a647e:#bfbfbf:#666666:#eecbad:#bcee68:#e5e500:#86bdc9:#e500e5:#8c9bc4:#e5e5e5", + "background_color": "#2b2b2b", + "cursor_color": "#7f7f7f", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Royal", + "palette": "#241f2b:#91284c:#23801c:#b49d27:#6580b0:#674d96:#8aaabe:#524966:#312d3d:#d5356c:#2cd946:#fde83b:#90baf9:#a479e3:#acd4eb:#9e8cbd", + "background_color": "#100815", + "cursor_color": "#524966", + "foreground_color": "#514968", + "background_image": "None", + "type": "dark" + }, + { + "name": "Ryuuko", + "palette": "#2c3941:#865f5b:#66907d:#b1a990:#6a8e95:#b18a73:#88b2ac:#ececec:#5d7079:#865f5b:#66907d:#b1a990:#6a8e95:#b18a73:#88b2ac:#ececec", + "background_color": "#2c3941", + "cursor_color": "#ececec", + "foreground_color": "#ececec", + "background_image": "None", + "type": "dark" + }, + { + "name": "Seafoam Pastel", + "palette": "#757575:#825d4d:#728c62:#ada16d:#4d7b82:#8a7267:#729494:#e0e0e0:#8a8a8a:#cf937a:#98d9aa:#fae79d:#7ac3cf:#d6b2a1:#ade0e0:#e0e0e0", + "background_color": "#243435", + "cursor_color": "#57647a", + "foreground_color": "#d4e7d4", + "background_image": "None", + "type": "dark" + }, + { + "name": "SeaShells", + "palette": "#17384c:#d15123:#027c9b:#fca02f:#1e4950:#68d4f1:#50a3b5:#deb88d:#434b53:#d48678:#628d98:#fdd39f:#1bbcdd:#bbe3ee:#87acb4:#fee4ce", + "background_color": "#09141b", + "cursor_color": "#fca02f", + "foreground_color": "#deb88d", + "background_image": "None", + "type": "dark" + }, + { + "name": "Seti", + "palette": "#323232:#c22832:#8ec43d:#e0c64f:#43a5d5:#8b57b5:#8ec43d:#eeeeee:#323232:#c22832:#8ec43d:#e0c64f:#43a5d5:#8b57b5:#8ec43d:#ffffff", + "background_color": "#111213", + "cursor_color": "#e3bf21", + "foreground_color": "#cacecd", + "background_image": "None", + "type": "dark" + }, + { + "name": "Shaman", + "palette": "#012026:#b2302d:#00a941:#5e8baa:#449a86:#00599d:#5d7e19:#405555:#384451:#ff4242:#2aea5e:#8ed4fd:#61d5ba:#1298ff:#98d028:#58fbd6", + "background_color": "#001015", + "cursor_color": "#4afcd6", + "foreground_color": "#405555", + "background_image": "None", + "type": "dark" + }, + { + "name": "Shel", + "background_color": "#2a201f", + "background_image": "None", + "cursor_color": "#6192d2", + "foreground_color": "#4882cd", + "palette": "#2c2423:#ab2463:#6ca323:#ab6423:#2c64a2:#6c24a2:#2ca363:#918988:#918988:#f588b9:#c2ee86:#f5ba86:#8fbaec:#c288ec:#8feeb9:#f5eeec", + "type": "dark" + }, + { + "name": "Slate", + "palette": "#222222:#e2a8bf:#81d778:#c4c9c0:#264b49:#a481d3:#15ab9c:#02c5e0:#ffffff:#ffcdd9:#beffa8:#d0ccca:#7ab0d2:#c5a7d9:#8cdfe0:#e0e0e0", + "background_color": "#222222", + "cursor_color": "#87d3c4", + "foreground_color": "#35b1d2", + "background_image": "None", + "type": "dark" + }, + { + "name": "Smyck", + "palette": "#000000:#b84131:#7da900:#c4a500:#62a3c4:#ba8acc:#207383:#a1a1a1:#7a7a7a:#d6837c:#c4f137:#fee14d:#8dcff0:#f79aff:#6ad9cf:#f7f7f7", + "background_color": "#1b1b1b", + "cursor_color": "#bbbbbb", + "foreground_color": "#f7f7f7", + "background_image": "None", + "type": "dark" + }, + { + "name": "Snazzy", + "background_color": "#242424", + "background_image": "None", + "cursor_color": "#97979b", + "foreground_color": "#eff0eb", + "palette": "#282a36:#ff5c57:#5af78e:#f3f99d:#57c7ff:#ff6ac1:#9aedfe:#f1f1f0:#686868:#ff5c57:#5af78e:#f3f99d:#57c7ff:#ff6ac1:#9aedfe:#eff0eb", + "type": "dark" + }, + { + "name": "SoftServer", + "palette": "#000000:#a2686a:#9aa56a:#a3906a:#6b8fa3:#6a71a3:#6ba58f:#99a3a2:#666c6c:#dd5c60:#bfdf55:#deb360:#62b1df:#606edf:#64e39c:#d2e0de", + "background_color": "#242626", + "cursor_color": "#d2e0de", + "foreground_color": "#99a3a2", + "background_image": "None", + "type": "dark" + }, + { + "name": "Solarized Darcula", + "palette": "#25292a:#f24840:#629655:#b68800:#2075c7:#797fd4:#15968d:#d2d8d9:#25292a:#f24840:#629655:#b68800:#2075c7:#797fd4:#15968d:#d2d8d9", + "background_color": "#3d3f41", + "cursor_color": "#708284", + "foreground_color": "#d2d8d9", + "background_image": "None", + "type": "dark" + }, + { + "name": "Solarized Dark", + "palette": "#002831:#d11c24:#738a05:#a57706:#2176c7:#c61c6f:#259286:#eae3cb:#001e27:#bd3613:#475b62:#536870:#708284:#5956ba:#819090:#fcf4dc", + "background_color": "#001e27", + "cursor_color": "#708284", + "foreground_color": "#708284", + "background_image": "None", + "type": "dark" + }, + { + "name": "Solarized Dark - Patched", + "palette": "#002831:#d11c24:#738a05:#a57706:#2176c7:#c61c6f:#259286:#eae3cb:#475b62:#bd3613:#475b62:#536870:#708284:#5956ba:#819090:#fcf4dc", + "background_color": "#001e27", + "cursor_color": "#708284", + "foreground_color": "#708284", + "background_image": "None", + "type": "dark" + }, + { + "name": "Solarized Dark Higher Contrast", + "palette": "#002831:#d11c24:#6cbe6c:#a57706:#2176c7:#c61c6f:#259286:#eae3cb:#006488:#f5163b:#51ef84:#b27e28:#178ec8:#e24d8e:#00b39e:#fcf4dc", + "background_color": "#001e27", + "cursor_color": "#f34b00", + "foreground_color": "#9cc2c3", + "background_image": "None", + "type": "dark" + }, + { + "name": "Solarized Light", + "palette": "#002831:#d11c24:#738a05:#a57706:#2176c7:#c61c6f:#259286:#eae3cb:#001e27:#bd3613:#475b62:#536870:#708284:#5956ba:#819090:#fcf4dc", + "background_color": "#fcf4dc", + "cursor_color": "#536870", + "foreground_color": "#536870", + "background_image": "None", + "type": "light" + }, + { + "name": "Spacedust", + "palette": "#6e5346:#e35b00:#5cab96:#e3cd7b:#0f548b:#e35b00:#06afc7:#f0f1ce:#684c31:#ff8a3a:#aecab8:#ffc878:#67a0ce:#ff8a3a:#83a7b4:#fefff1", + "background_color": "#0a1e24", + "cursor_color": "#708284", + "foreground_color": "#ecf0c1", + "background_image": "None", + "type": "dark" + }, + { + "name": "SpaceGray", + "palette": "#000000:#b04b57:#87b379:#e5c179:#7d8fa4:#a47996:#85a7a5:#b3b8c3:#000000:#b04b57:#87b379:#e5c179:#7d8fa4:#a47996:#85a7a5:#ffffff", + "background_color": "#20242d", + "cursor_color": "#b3b8c3", + "foreground_color": "#b3b8c3", + "background_image": "None", + "type": "dark" + }, + { + "name": "SpaceGray Eighties", + "palette": "#15171c:#ec5f67:#81a764:#fec254:#5486c0:#bf83c1:#57c2c1:#efece7:#555555:#ff6973:#93d493:#ffd256:#4d84d1:#ff55ff:#83e9e4:#ffffff", + "background_color": "#222222", + "cursor_color": "#bbbbbb", + "foreground_color": "#bdbaae", + "background_image": "None", + "type": "dark" + }, + { + "name": "SpaceGray Eighties Dull", + "palette": "#15171c:#b24a56:#92b477:#c6735a:#7c8fa5:#a5789e:#80cdcb:#b3b8c3:#555555:#ec5f67:#89e986:#fec254:#5486c0:#bf83c1:#58c2c1:#ffffff", + "background_color": "#222222", + "cursor_color": "#bbbbbb", + "foreground_color": "#c9c6bc", + "background_image": "None", + "type": "dark" + }, + { + "name": "Spiderman", + "palette": "#1b1d1e:#e60813:#e22928:#e24756:#2c3fff:#2435db:#3256ff:#fffef6:#505354:#ff0325:#ff3338:#fe3a35:#1d50ff:#747cff:#6184ff:#fffff9", + "background_color": "#1b1d1e", + "cursor_color": "#2c3fff", + "foreground_color": "#e3e3e3", + "background_image": "None", + "type": "dark" + }, + { + "name": "Spring", + "palette": "#000000:#ff4d83:#1f8c3b:#1fc95b:#1dd3ee:#8959a8:#3e999f:#ffffff:#000000:#ff0021:#1fc231:#d5b807:#15a9fd:#8959a8:#3e999f:#ffffff", + "background_color": "#ffffff", + "cursor_color": "#4d4d4c", + "foreground_color": "#4d4d4c", + "background_image": "None", + "type": "light" + }, + { + "name": "Square", + "palette": "#050505:#e9897c:#b6377d:#ecebbe:#a9cdeb:#75507b:#c9caec:#f2f2f2:#141414:#f99286:#c3f786:#fcfbcc:#b6defb:#ad7fa8:#d7d9fc:#e2e2e2", + "background_color": "#1a1a1a", + "cursor_color": "#fcfbcc", + "foreground_color": "#acacab", + "background_image": "None", + "type": "dark" + }, + { + "name": "Sundried", + "palette": "#302b2a:#a7463d:#587744:#9d602a:#485b98:#864651:#9c814f:#c9c9c9:#4d4e48:#aa000c:#128c21:#fc6a21:#7999f7:#fd8aa1:#fad484:#ffffff", + "background_color": "#1a1818", + "cursor_color": "#ffffff", + "foreground_color": "#c9c9c9", + "background_image": "None", + "type": "dark" + }, + { + "name": "Symfonic", + "palette": "#000000:#dc322f:#56db3a:#ff8400:#0084d4:#b729d9:#ccccff:#ffffff:#1b1d21:#dc322f:#56db3a:#ff8400:#0084d4:#b729d9:#ccccff:#ffffff", + "background_color": "#000000", + "cursor_color": "#dc322f", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Teerb", + "palette": "#1c1c1c:#d68686:#aed686:#d7af87:#86aed6:#d6aed6:#8adbb4:#d0d0d0:#1c1c1c:#d68686:#aed686:#e4c9af:#86aed6:#d6aed6:#b1e7dd:#efefef", + "background_color": "#262626", + "cursor_color": "#e4c9af", + "foreground_color": "#d0d0d0", + "background_image": "None", + "type": "dark" + }, + { + "name": "Terminal Basic", + "palette": "#000000:#990000:#00a600:#999900:#0000b2:#b200b2:#00a6b2:#bfbfbf:#666666:#e50000:#00d900:#e5e500:#0000ff:#e500e5:#00e5e5:#e5e5e5", + "background_color": "#ffffff", + "cursor_color": "#7f7f7f", + "foreground_color": "#000000", + "background_image": "None", + "type": "light" + }, + { + "name": "Thayer Bright", + "palette": "#1b1d1e:#f92672:#4df840:#f4fd22:#2757d6:#8c54fe:#38c8b5:#ccccc6:#505354:#ff5995:#b6e354:#feed6c:#3f78ff:#9e6ffe:#23cfd5:#f8f8f2", + "background_color": "#1b1d1e", + "cursor_color": "#fc971f", + "foreground_color": "#f8f8f8", + "background_image": "None", + "type": "dark" + }, + { + "name": "The Hulk", + "palette": "#1b1d1e:#269d1b:#13ce30:#63e457:#2525f5:#641f74:#378ca9:#d9d8d1:#505354:#8dff2a:#48ff77:#3afe16:#506b95:#72589d:#4085a6:#e5e6e1", + "background_color": "#1b1d1e", + "cursor_color": "#16b61b", + "foreground_color": "#b5b5b5", + "background_image": "None", + "type": "dark" + }, + { + "name": "Tomorrow", + "palette": "#000000:#c82829:#718c00:#eab700:#4271ae:#8959a8:#3e999f:#ffffff:#000000:#c82829:#718c00:#eab700:#4271ae:#8959a8:#3e999f:#ffffff", + "background_color": "#ffffff", + "cursor_color": "#4d4d4c", + "foreground_color": "#4d4d4c", + "background_image": "None", + "type": "light" + }, + { + "name": "Tomorrow Night", + "palette": "#000000:#cc6666:#b5bd68:#f0c674:#81a2be:#b294bb:#8abeb7:#ffffff:#000000:#cc6666:#b5bd68:#f0c674:#81a2be:#b294bb:#8abeb7:#ffffff", + "background_color": "#1d1f21", + "cursor_color": "#c5c8c6", + "foreground_color": "#c5c8c6", + "background_image": "None", + "type": "dark" + }, + { + "name": "Tomorrow Night Blue", + "palette": "#000000:#ff9da4:#d1f1a9:#ffeead:#bbdaff:#ebbbff:#99ffff:#ffffff:#000000:#ff9da4:#d1f1a9:#ffeead:#bbdaff:#ebbbff:#99ffff:#ffffff", + "background_color": "#002451", + "cursor_color": "#ffffff", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Tomorrow Night Bright", + "palette": "#000000:#d54e53:#b9ca4a:#e7c547:#7aa6da:#c397d8:#70c0b1:#ffffff:#000000:#d54e53:#b9ca4a:#e7c547:#7aa6da:#c397d8:#70c0b1:#ffffff", + "background_color": "#000000", + "cursor_color": "#eaeaea", + "foreground_color": "#eaeaea", + "background_image": "None", + "type": "dark" + }, + { + "name": "Tomorrow Night Eighties", + "palette": "#000000:#f2777a:#99cc99:#ffcc66:#6699cc:#cc99cc:#66cccc:#ffffff:#000000:#f2777a:#99cc99:#ffcc66:#6699cc:#cc99cc:#66cccc:#ffffff", + "background_color": "#2d2d2d", + "cursor_color": "#cccccc", + "foreground_color": "#cccccc", + "background_image": "None", + "type": "dark" + }, + { + "name": "ToyChest", + "palette": "#2c3f58:#be2d26:#1a9172:#db8e27:#325d96:#8a5edc:#35a08f:#23d183:#336889:#dd5944:#31d07b:#e7d84b:#34a6da:#ae6bdc:#42c3ae:#d5d5d5", + "background_color": "#24364b", + "cursor_color": "#d5d5d5", + "foreground_color": "#31d07b", + "background_image": "None", + "type": "dark" + }, + { + "name": "Treehouse", + "palette": "#321300:#b2270e:#44a900:#aa820c:#58859a:#97363d:#b25a1e:#786b53:#433626:#ed5d20:#55f238:#f2b732:#85cfed:#e14c5a:#f07d14:#ffc800", + "background_color": "#191919", + "cursor_color": "#fac814", + "foreground_color": "#786b53", + "background_image": "None", + "type": "dark" + }, + { + "name": "Twilight", + "palette": "#141414:#c06d44:#afb97a:#c2a86c:#44474a:#b4be7c:#778385:#ffffd4:#262626:#de7c4c:#ccd88c:#e2c47e:#5a5e62:#d0dc8e:#8a989b:#ffffd4", + "background_color": "#141414", + "cursor_color": "#ffffff", + "foreground_color": "#ffffd4", + "background_image": "None", + "type": "dark" + }, + { + "name": "Ubuntu", + "palette": "#2e3436:#cc0000:#4e9a06:#c4a000:#3465a4:#75507b:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:#34e2e2:#eeeeec", + "background_color": "#300a24", + "cursor_color": "#bbbbbb", + "foreground_color": "#eeeeec", + "background_image": "None", + "type": "dark" + }, + { + "name": "UnderTheSea", + "palette": "#022026:#b2302d:#00a941:#59819c:#459a86:#00599d:#5d7e19:#405555:#384451:#ff4242:#2aea5e:#8ed4fd:#61d5ba:#1298ff:#98d028:#58fbd6", + "background_color": "#011116", + "cursor_color": "#4afcd6", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Urple", + "palette": "#000000:#b0425b:#37a415:#ad5c42:#564d9b:#6c3ca1:#808080:#87799c:#5d3225:#ff6388:#29e620:#f08161:#867aed:#a05eee:#eaeaea:#bfa3ff", + "background_color": "#1b1b23", + "cursor_color": "#a063eb", + "foreground_color": "#877a9b", + "background_image": "None", + "type": "dark" + }, + { + "name": "Vag", + "background_color": "#191f1d", + "background_image": "None", + "cursor_color": "#e5f0fa", + "foreground_color": "#d9e6f2", + "palette": "#303030:#a87139:#39a871:#71a839:#7139a8:#a83971:#3971a8:#8a8a8a:#494949:#b0763b:#3bb076:#76b03b:#763bb0:#b03b76:#3b76b0:#cfcfcf", + "type": "dark" + }, + { + "name": "Vaughn", + "palette": "#25234f:#705050:#60b48a:#dfaf8f:#5555ff:#f08cc3:#8cd0d3:#709080:#709080:#dca3a3:#60b48a:#f0dfaf:#5555ff:#ec93d3:#93e0e3:#ffffff", + "background_color": "#25234f", + "cursor_color": "#ff5555", + "foreground_color": "#dcdccc", + "background_image": "None", + "type": "dark" + }, + { + "name": "Venom", + "background_color": "#060d14", + "cursor_color": "#9ecfa2", + "foreground_color": "#668198", + "palette": "#2e3436:#e94759:#9ecfa2:#f3efa9:#00898d:#9c21b0:#06989a:#d3d7cf:#555753:#ef2929:#8ae234:#fce94f:#729fcf:#ad7fa8:#34e2e2:#eeeeec", + "type": "dark" + }, + { + "name": "VibrantInk", + "palette": "#878787:#ff6600:#ccff04:#ffcc00:#44b4cc:#9933cc:#44b4cc:#f5f5f5:#555555:#ff0000:#00ff00:#ffff00:#0000ff:#ff00ff:#00ffff:#e5e5e5", + "background_color": "#000000", + "cursor_color": "#ffffff", + "foreground_color": "#ffffff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Violet Dark", + "palette": "#56595c:#c94c22:#85981c:#b4881d:#2e8bce:#d13a82:#32a198:#c9c6bd:#45484b:#bd3613:#738a04:#a57705:#2176c7:#c61c6f:#259286:#c9c6bd", + "background_color": "#1c1d1f", + "cursor_color": "#708284", + "foreground_color": "#708284", + "background_image": "None", + "type": "dark" + }, + { + "name": "Violet Light", + "palette": "#56595c:#c94c22:#85981c:#b4881d:#2e8bce:#d13a82:#32a198:#d3d0c9:#45484b:#bd3613:#738a04:#a57705:#2176c7:#c61c6f:#259286:#c9c6bd", + "background_color": "#fcf4dc", + "cursor_color": "#536870", + "foreground_color": "#536870", + "background_image": "None", + "type": "light" + }, + { + "name": "WarmNeon", + "palette": "#000000:#e24346:#39b13a:#dae145:#4261c5:#f920fb:#2abbd4:#d0b8a3:#fefcfc:#e97071:#9cc090:#ddda7a:#7b91d6:#f674ba:#5ed1e5:#d8c8bb", + "background_color": "#404040", + "cursor_color": "#30ff24", + "foreground_color": "#afdab6", + "background_image": "None", + "type": "dark" + }, + { + "name": "Wez", + "palette": "#000000:#cc5555:#55cc55:#cdcd55:#5555cc:#cc55cc:#7acaca:#cccccc:#555555:#ff5555:#55ff55:#ffff55:#5555ff:#ff55ff:#55ffff:#ffffff", + "background_color": "#000000", + "cursor_color": "#53ae71", + "foreground_color": "#b3b3b3", + "background_image": "None", + "type": "dark" + }, + { + "name": "WildCherry", + "palette": "#000507:#d94085:#2ab250:#ffd16f:#883cdc:#ececec:#c1b8b7:#fff8de:#009cc9:#da6bac:#f4dca5:#eac066:#308cba:#ae636b:#ff919d:#e4838d", + "background_color": "#1f1726", + "cursor_color": "#dd00ff", + "foreground_color": "#dafaff", + "background_image": "None", + "type": "dark" + }, + { + "name": "Wombat", + "palette": "#000000:#ff615a:#b1e969:#ebd99c:#5da9f6:#e86aff:#82fff7:#dedacf:#313131:#f58c80:#ddf88f:#eee5b2:#a5c7ff:#ddaaff:#b7fff9:#ffffff", + "background_color": "#171717", + "cursor_color": "#bbbbbb", + "foreground_color": "#dedacf", + "background_image": "None", + "type": "dark" + }, + { + "name": "Wryan", + "palette": "#333333:#8c4665:#287373:#7c7c99:#395573:#5e468c:#31658c:#899ca1:#3d3d3d:#bf4d80:#53a6a6:#9e9ecb:#477ab3:#7e62b3:#6096bf:#c0c0c0", + "background_color": "#101010", + "cursor_color": "#9e9ecb", + "foreground_color": "#999993", + "background_image": "None", + "type": "dark" + }, + { + "name": "Zenburn", + "palette": "#4d4d4d:#705050:#60b48a:#f0dfaf:#506070:#dc8cc3:#8cd0d3:#dcdccc:#709080:#dca3a3:#c3bf9f:#e0cf9f:#94bff3:#ec93d3:#93e0e3:#ffffff", + "background_color": "#3f3f3f", + "cursor_color": "#73635a", + "foreground_color": "#dcdccc", + "background_image": "None", + "type": "dark" + } + ] +} \ No newline at end of file diff --git a/emacs/.emacs.d/wpc/terminator.el b/emacs/.emacs.d/wpc/terminator.el new file mode 100644 index 000000000000..4794ce2d90a3 --- /dev/null +++ b/emacs/.emacs.d/wpc/terminator.el @@ -0,0 +1,94 @@ +;;; terminator.el --- Experimenting with theming Terminator -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; I think most of this module is me getting carried away with the idea of +;; theming Terminator. Terminator themes are defined in a themes.json file. As +;; far as I know, Terminator does not support specifying these themes by name on +;; the command line, which would greatly simplify things. Terminator does +;; support passing a --profile flag, however, which can be used to specify the +;; themes. The idea, albeit quite awkward and over-engineered, was to create +;; these profile files on the fly and pass them to terminator. After around 45 +;; minutes of tinkering with this, the idea is starting to disenchant me. +;; +;; Alternative solutions include: +;; 1. Further investigating what other options Terminator supports. +;; 2. Using a different terminal emulator. +;; 3. Just right clicking Terminator and changing the themes manually. + +;;; Code: +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'alist) +(require 'string) +(require 'json) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct terminator/theme + foreground-color + background-color + cursor-color + palette) + +(defvar terminator/palettes + '((solarized-light . "#002831:#d11c24:#738a05:#a57706:#2176c7:#c61c6f:#259286:#eae3cb:#001e27:#bd3613:#475b62:#536870:#708284:#5956ba:#819090:#fcf4dc")) + "Mapping of theme names to the color palette that terminator expects.") + +(defconst terminator/profile-template "[global_config] + enabled_plugins = LaunchpadBugURLHandler, LaunchpadCodeURLHandler, APTURLHandler, TerminatorThemes +[keybindings] +[profiles] + [[default]] + background_color = \"%s\" + cursor_shape = ibeam + cursor_color = \"%s\" + font = Input Mono Medium 12 + foreground_color = \"%s\" + show_titlebar = False + scrollbar_position = hidden + palette = \"%s\" + use_system_font = False +[layouts] + [[default]] + [[[child1]]] + parent = window0 + type = Terminal + profile = Molokai + [[[window0]]] + parent = \"\" + type = Window +[plugins]" + "Template string of a terminator profile file.") + +(cl-defun terminator/render-profile (&key foreground-color + background-color + cursor-color + palette) + "Create a terminator profile with THEME as the palette." + (string/format terminator/profile-template + background-color + cursor-color + foreground-color + palette)) + +(defun terminator/as-heredoc (x) + "Return an EOF-terminator heredoc of X." + (string/format "<<EOF\n%s\nEOF" x)) + +(prelude/start-process + :name "termination" + :command (string/format "zsh -c terminator --profile=%s" + (->> 'solarized-light + terminator/render-profile + terminator/as-heredoc))) +(string/format terminator/profile-template + (alist/get 'solarized-light terminator/palettes)) + +(provide 'terminator) +;;; terminator.el ends here diff --git a/emacs/.emacs.d/wpc/themes.el b/emacs/.emacs.d/wpc/themes.el new file mode 100644 index 000000000000..ee81d3beed72 --- /dev/null +++ b/emacs/.emacs.d/wpc/themes.el @@ -0,0 +1,204 @@ +;;; 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 + "Shapely Patterns" + (make-theme + :font "Operator Mono Light" + :wallpaper "geometric_dark_4k.jpg" + :colorscheme 'doom-vibrant)) + ;; TODO: Support setting backgrounds as solid colors. + (dotted/new + "Gruvbox" + (make-theme + :font "JetBrainsMono" + :wallpaper "geometric_dark_4k.jpg" + :colorscheme 'doom-gruvbox)) + (dotted/new + "Solarized Light" + (make-theme + :font "JetBrainsMono" + :wallpaper "solarized_light_thinkpad.jpg" + :colorscheme 'doom-solarized-light)) + (dotted/new + "Lightness" + (make-theme + :font "Input Mono Medium" + :wallpaper "construction_paper_iceberg_4k.jpg" + :colorscheme 'doom-one-light)) + (dotted/new + "Edison Lightbulb" + (make-theme + :font "Mononoki Medium" + :wallpaper "lightbulb_4k.jpg" + :colorscheme 'base16-atelier-cave)) + (dotted/new + "Wall-E" + (make-theme + :font "Input Mono Medium" + :wallpaper "walle_4k.jpg" + :colorscheme 'doom-material)) + (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/emacs/.emacs.d/wpc/todo.el b/emacs/.emacs.d/wpc/todo.el new file mode 100644 index 000000000000..236912c086fd --- /dev/null +++ b/emacs/.emacs.d/wpc/todo.el @@ -0,0 +1,293 @@ +;;; 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))) + +(provide 'todo) +;;; todo.el ends here diff --git a/emacs/.emacs.d/wpc/tree.el b/emacs/.emacs.d/wpc/tree.el new file mode 100644 index 000000000000..43df4dc500e7 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/tuple.el b/emacs/.emacs.d/wpc/tuple.el new file mode 100644 index 000000000000..ccebf7299abd --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/vector.el b/emacs/.emacs.d/wpc/vector.el new file mode 100644 index 000000000000..6d2fe20d1209 --- /dev/null +++ b/emacs/.emacs.d/wpc/vector.el @@ -0,0 +1,81 @@ +;;; 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. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst vector/enable-tests? t + "When t, run the tests defined herein.") + +;; 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)) + +(defun vector/set! (i v xs) + "Set index `I' to value `V' in `XS'. +This function mutates XS." + (aset xs i v)) + +(when vector/enable-tests? + (let ((xs [1 2 3]) + (ys [1 2 3])) + (prelude/assert (= 1 (vector/get 0 ys))) + (vector/set 0 4 ys) + (prelude/assert (= 1 (vector/get 0 ys))) + (prelude/assert (= 1 (vector/get 0 xs))) + (vector/set! 0 4 xs) + (prelude/assert (= 4 (vector/get 0 xs))))) + +;; 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/emacs/.emacs.d/wpc/wallpaper.el b/emacs/.emacs.d/wpc/wallpaper.el new file mode 100644 index 000000000000..9aa41cd364a4 --- /dev/null +++ b/emacs/.emacs.d/wpc/wallpaper.el @@ -0,0 +1,92 @@ +;;; wallpaper.el --- Control Linux desktop wallpaper -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Functions for setting desktop wallpaper. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'fs) +(require 'cycle) +(require 'string) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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. +`PATH' can be absolute or relative since `f-expand' is called in the function + body to ensure feh can resolve the path." + (prelude/start-process + :name "wallpaper/set" + :command (string/format "feh --bg-scale --no-feh-bg %s" (f-expand 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? + (general-define-key + :prefix "<SPC>" + :states '(normal) + "Fw" #'wallpaper/next + "Pw" #'wallpaper/prev)) + +(provide 'wallpaper) +;;; wallpaper.el ends here diff --git a/emacs/.emacs.d/wpc/window-manager.el b/emacs/.emacs.d/wpc/window-manager.el new file mode 100644 index 000000000000..cf7f1efeb799 --- /dev/null +++ b/emacs/.emacs.d/wpc/window-manager.el @@ -0,0 +1,647 @@ +;;; window-manager.el --- Functions augmenting my usage of EXWM. -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; I switched to EXWM from i3, and I haven't looked back. One day I may write a +;; poem declaring my love for Emacs and EXWM. For now, I haven't the time. + +;; Wish list: +;; - TODO: Support different startup commands and layouts depending on laptop or +;; desktop. +;; - TODO: Support a Music named-workspace. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'alert) +(require 'prelude) +(require 'string) +(require 'cycle) +(require 'set) +(require 'kbd) +(require 'ivy-helpers) +(require 'display) +(require 'dotfiles) +(require 'org-helpers) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Move this function to another module. +(defun pkill (name) + "Call the pkill executable using NAME as its argument." + (interactive "sProcess name: ") + (call-process "pkill" nil nil nil name)) + +;; 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 "IRC" + :index 7 + :kbd "i") + (make-exwm/named-workspace + :label "Work" + :index 8 + :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-monitor + 1 display/laptop-monitor)) + + (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/change-workspace (cycle/next exwm/workspaces))) + +(defun exwm/prev-workspace () + "Cycle backwards to the previous workspace." + (interactive) + (exwm/change-workspace (cycle/prev 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 +;; TODO: Consider writing a better client for this. +(use-package ivy-pass) + +;; TODO: Prefer a more idiomatic Emacs way like `with-output-to-temp-buffer'. + +;; TODO: Create a mode similar to `help-mode' that also kills the buffer when +;; "q" is pressed since this is sensitive information that we probably don't +;; want persisting. + +;; TODO: Have this interactively show all of the listings in ~/.password-store +;; in an ivy list. +(defun password-store/show (key) + "Show the contents of KEY from the password-store in a buffer." + (interactive) + (let ((b (buffer/find-or-create (string/format "*password-store<%s>*" key)))) + (with-current-buffer b + (insert (password-store-get key)) + (help-mode)) + (buffer/show b))) + +;; TODO: I'm having difficulties with the Nix-built terminator. The one at +;; /usr/bin/terminator (i.e. built w/o Nix) works just fine. Using this, +;; however, cheapens my Nix setup. +(defconst exwm/preferred-terminal "terminator" + "My preferred terminal.") + +;; TODO: How do I handle this dependency? +(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/open exwm/preferred-terminal)) + (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" + "telegram-desktop" + "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. + +;; 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)) + +(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/change-workspace (workspace) + "Switch EXWM workspaces to the WORKSPACE struct." + (exwm-workspace-switch (exwm/named-workspace-index workspace)) + (window-manager/alert + (string/format "Switched to: %s" (exwm/named-workspace-label workspace)))) + +(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/change-workspace (cycle/current exwm/workspaces))) + +;; TODO: Assign an easy-to-remember keybinding to this. +(exwm-input-set-key (kbd "C-S-f") #'exwm/toggle-previous) +(defun exwm/toggle-previous () + "Focus the previously active EXWM workspace." + (interactive) + (exwm/change-workspace (cycle/focus-previous! exwm/workspaces))) + +(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") + (org-helpers/find-file "today-expected.org") + (wpc/evil-window-vsplit-right) + (org-helpers/find-file "today-actual.org")) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Dotfiles + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (progn + (exwm/switch "Dotfiles") + (dotfiles/find-emacs-file "init.el") + (wpc/evil-window-vsplit-right) + (dotfiles/find-emacs-file "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/emacs/.emacs.d/wpc/window.el b/emacs/.emacs.d/wpc/window.el new file mode 100644 index 000000000000..132156bc4465 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/wpc-clojure.el b/emacs/.emacs.d/wpc/wpc-clojure.el new file mode 100644 index 000000000000..d9262cdda8eb --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-company.el b/emacs/.emacs.d/wpc/wpc-company.el new file mode 100644 index 000000000000..1152f496c2b7 --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-dired.el b/emacs/.emacs.d/wpc/wpc-dired.el new file mode 100644 index 000000000000..bc3915914bf1 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-dired.el @@ -0,0 +1,41 @@ +;;; 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) + ;; Overriding some KBDs defined in the evil-collection module. + "o" #'dired-find-file-other-window + "<SPC>" nil ;; This unblocks some of my leader-prefixed KBDs. + "s" nil ;; This unblocks my window-splitting KBDs. + "c" #'find-file + "f" #'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-find-file-other-window)) + +(provide 'wpc-dired) +;;; wpc-dired.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-docker.el b/emacs/.emacs.d/wpc/wpc-docker.el new file mode 100644 index 000000000000..270eaec6fe4c --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-elixir.el b/emacs/.emacs.d/wpc/wpc-elixir.el new file mode 100644 index 000000000000..e64abe70fc36 --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-flycheck.el b/emacs/.emacs.d/wpc/wpc-flycheck.el new file mode 100644 index 000000000000..d7bb834a6257 --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-haskell.el b/emacs/.emacs.d/wpc/wpc-haskell.el new file mode 100644 index 000000000000..e8ab16e585b7 --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-java.el b/emacs/.emacs.d/wpc/wpc-java.el new file mode 100644 index 000000000000..4f33ba962e5d --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-javascript.el b/emacs/.emacs.d/wpc/wpc-javascript.el new file mode 100644 index 000000000000..3de9fff3aaa5 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-javascript.el @@ -0,0 +1,83 @@ +;; wpc-javascript.el --- My Javascript preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This module hosts my Javascript tooling preferences. This also includes +;; tooling for TypeScript and other frontend tooling. Perhaps this module will +;; change names to more accurately reflect that. +;; +;; Depends +;; - yarn global add prettier + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Constants +(defconst wpc/js-hooks + '(js-mode-hook web-mode-hook typescript-mode-hook js2-mode-hook rjsx-mode-hook) + "All of the commonly used hooks for Javascript buffers.") + +(defconst wpc/frontend-hooks + (-insert-at 0 'css-mode-hook wpc/js-hooks) + "All of the commonly user hooks for frontend development.") + + +;; frontend indentation settings +(setq typescript-indent-level 2 + js-indent-level 2 + css-indent-offset 2) + +;; 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)) + +(progn + (defun tide/setup () + (interactive) + (tide-setup) + (flycheck-mode 1) + (setq flycheck-check-syntax-automatically '(save mode-enabled)) + (eldoc-mode 1) + (tide-hl-identifier-mode 1) + (company-mode 1)) + (use-package tide + :config + (add-hook 'typescript-mode-hook #'tide/setup)) + (require 'web-mode) + (add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode)) + (add-hook 'web-mode-hook + (lambda () + (when (string-equal "tsx" (f-ext buffer-file-name)) + (tide/setup)))) + (flycheck-add-mode 'typescript-tslint 'web-mode)) + +;; 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/emacs/.emacs.d/wpc/wpc-keybindings.el b/emacs/.emacs.d/wpc/wpc-keybindings.el new file mode 100644 index 000000000000..2ff4fe375829 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-keybindings.el @@ -0,0 +1,229 @@ +;;; keybindings.el --- My Evil preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; This module hosts my Evil preferences +;; +;; Wish List: +;; - restore support for concise (n <kbd> <function>) instead of `general-mmap' +;; - restore support for `general-unbind' + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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)) + +;; TODO: Write `evil-collection' KBDs for `refine'. +;; evil keybindings +(use-package evil-collection + :after (evil) + :config + (evil-collection-init)) + +;; `evil-collection' does not support `magit', and the preferred way to get evil +;; kbds for magit is with `evil-magit'. +(use-package evil-magit) + +;; TODO: Consider moving this to another module. +(general-define-key + :prefix "<SPC>" + :states '(normal) + "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 + "W" #'balance-windows + "gs" #'magit-status + "E" #'refine + "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")) + "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.") + +;; Support pasting in M-:. +(general-define-key + :keymaps 'read-expression-map + "C-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +(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)) + +;; Ensure the Evil search results get centered vertically. +;; TODO: Consider packaging this up for others. +(progn + (defadvice isearch-update + (before advice-for-isearch-update activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-next + (after advice-for-evil-search-next activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-previous + (after advice-for-evil-search-previous activate) + (evil-scroll-line-to-center (line-number-at-pos)))) + +(provide 'wpc-keybindings) +;;; wpc-keybindings.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-lisp.el b/emacs/.emacs.d/wpc/wpc-lisp.el new file mode 100644 index 000000000000..1eeb8550a205 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-lisp.el @@ -0,0 +1,111 @@ +;;; 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: + +;; TODO: Consider having a separate module for each LISP dialect. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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 sly + :config + (setq inferior-lisp-program "sbcl") + (general-define-key + :keymaps 'sly-mode-map + :states '(normal) + :prefix "<SPC>" + "x" #'sly-eval-defun + "X" #'sly-eval-buffer + "d" #'sly-describe-symbol)) + +(use-package rainbow-delimiters + :config + (general-add-hook wpc/lisp-mode-hooks #'rainbow-delimiters-mode)) + +(use-package racket-mode + :config + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "<SPC>" + "x" #'racket-send-definition + "X" #'racket-run + "d" #'racket-describe) + (setq racket-program "~/.nix-profile/bin/racket")) + +(use-package lispyville + :init + (defconst lispyville-key-themes + '(c-w + operators + text-objects + prettify + commentary + slurp/barf-cp + wrap + additional + additional-insert + additional-wrap + escape) + "All available key-themes in Lispyville.") + :config + (general-add-hook wpc/lisp-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 + "C-s-h" #'lispyville-drag-backward + "C-s-l" #'lispyville-drag-forward + "C-s-e" #'lispyville-end-of-defun + "C-s-a" #'lispyville-beginning-of-defun))) + +;; Elisp +(use-package elisp-slime-nav + :config + (general-add-hook 'emacs-lisp-mode #'ielm-mode)) + +(general-define-key + :keymaps 'emacs-lisp-mode-map + :prefix "<SPC>" + :states 'normal + "x" #'eval-defun + "X" #'eval-buffer + "d" (lambda () + (interactive) + (with-current-buffer (current-buffer) + (helpful-function (symbol-at-point))))) + +(provide 'wpc-lisp) +;;; wpc-lisp.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-misc.el b/emacs/.emacs.d/wpc/wpc-misc.el new file mode 100644 index 000000000000..167c4b88ab9c --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-misc.el @@ -0,0 +1,248 @@ +;;; 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) + +;; Emacs library that interfaces with my Linux password manager. +(use-package password-store) + +;; Use en Emacs buffer as a REST client. +;; For more information: http://emacsrocks.com/e15.html +(use-package restclient) + +;; Run `package-lint' before publishing to MELPA. +(use-package package-lint) + +;; Parser combinators in Elisp. +(use-package parsec) + +;; disable company mode when editing markdown +;; TODO: move this out of wpc-misc.el and into a later file to call +;; `(disable company-mode)' +(use-package markdown-mode + :config + ;; TODO: Add assertion that pandoc is installed and it is accessible from + ;; Emacs. + (setq markdown-command "pandoc") + (setq markdown-split-window-direction 'right) + ;; (add-hook 'markdown-mode-hook #'markdown-live-preview-mode) + ) + +(use-package alert) + +(use-package refine) + +;; Required by some google-emacs package commands. +(use-package deferred) + +;; git integration +(use-package magit + :config + (setq magit-display-buffer-function + #'magit-display-buffer-fullframe-status-v1)) + +(use-package magit-popup) + +;; http +(use-package request) + +;; perl-compatible regular expressions +(use-package pcre2el) + +;; alternative to help +(use-package helpful) + +;; Emacs integration with direnv +(use-package direnv + :config + (direnv-mode)) + +;; Superior Elisp library for working with dates and times. +;; TODO: Put this where my other installations for dash.el, s.el, a.el, and +;; other utility Elisp libraries are located. +(use-package ts) + +;; persist history etc b/w Emacs sessions +(setq desktop-save 'if-exists) +(desktop-save-mode 1) +(setq desktop-globals-to-save + (append '((extended-command-history . 30) + (file-name-history . 100) + (grep-history . 30) + (compile-history . 30) + (minibuffer-history . 50) + (query-replace-history . 60) + (read-expression-history . 60) + (regexp-history . 60) + (regexp-search-ring . 20) + (search-ring . 20) + (shell-command-history . 50) + tags-file-name + register-alist))) + +;; 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 + (setq yas-snippet-dirs '("~/.emacs.d/snippets/")) + (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) + (setq-default deadgrep--context '(0 . 3)) + (defun deadgrep/region () + "Run a ripgrep search on the active region." + (interactive) + (deadgrep (region/to-string))) + (defun deadgrep/dwim () + "If a region is active, use that as the search, otherwise don't." + (interactive) + (with-current-buffer (current-buffer) + (if (region-active-p) + (setq deadgrep--additional-flags '("--multiline")) + (deadgrep/region) + (call-interactively #'deadgrep)))) + (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)) + +(provide 'wpc-misc) +;;; wpc-misc.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-nix.el b/emacs/.emacs.d/wpc/wpc-nix.el new file mode 100644 index 000000000000..68d542e01176 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-nix.el @@ -0,0 +1,56 @@ +;;; wpc-nix.el --- Nix support -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Configuration to support working with Nix. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(prelude/assert (f-exists? "~/universe")) +(prelude/assert (f-exists? "~/depot")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; Code: +(use-package nix-mode + :mode "\\.nix\\'") + +(defun nix/sly-from-universe (attribute) + "Start a Sly REPL configured with a Lisp matching a derivation + from my monorepo. + +This function was taken from @tazjin's depot and adapted for my monorepo. + + The derivation invokes nix.buildLisp.sbclWith and is built + asynchronously. The build output is included in the error + thrown on build failures." + (interactive "sAttribute: ") + (lexical-let* ((outbuf (get-buffer-create (format "*universe-out/%s*" attribute))) + (errbuf (get-buffer-create (format "*universe-errors/%s*" attribute))) + (expression (format "let depot = import <depot> {}; universe = import <universe> {}; in depot.nix.buildLisp.sbclWith [ universe.%s ]" attribute)) + (command (list "nix-build" "-E" expression))) + (message "Acquiring Lisp for <depot>.%s" attribute) + (make-process :name (format "depot-nix-build/%s" attribute) + :buffer outbuf + :stderr errbuf + :command command + :sentinel + (lambda (process event) + (unwind-protect + (pcase event + ("finished\n" + (let* ((outpath (s-trim (with-current-buffer outbuf (buffer-string)))) + (lisp-path (s-concat outpath "/bin/sbcl"))) + (message "Acquired Lisp for <depot>.%s at %s" attribute lisp-path) + (sly lisp-path))) + (_ (with-current-buffer errbuf + (error "Failed to build '%s':\n%s" attribute (buffer-string))))) + (kill-buffer outbuf) + (kill-buffer errbuf)))))) + +(provide 'wpc-nix) +;;; wpc-nix.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-ocaml.el b/emacs/.emacs.d/wpc/wpc-ocaml.el new file mode 100644 index 000000000000..26add2d6f957 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-ocaml.el @@ -0,0 +1,43 @@ +;;; 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 user-setup install` +;; - `opam install ocamlformat` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) + +(prelude/assert + (prelude/executable-exists? "opam")) + +(defvar opam-user-setup "~/.emacs.d/opam-user-setup.el" + "File for the OPAM Emacs integration.") + +(prelude/assert (f-file? opam-user-setup)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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) + +(provide 'wpc-ocaml) +;;; wpc-ocaml.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-org.el b/emacs/.emacs.d/wpc/wpc-org.el new file mode 100644 index 000000000000..3263fb50380c --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-org.el @@ -0,0 +1,70 @@ +;;; org.el --- My org preferences -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Hosts my org mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq org-directory "~/Dropbox/org") + +;; TODO: figure out how to nest this in (use-package org ...) +(setq org-capture-templates + `(("w" "work" entry (file+headline + ,(f-join org-directory "work.org") + "Tasks") + "* TODO %?") + ("p" "personal" entry (file+headline + ,(f-join org-directory "personal.org") + "Tasks") + "* TODO %? ") + ("i" "ideas" entry (file+headline + ,(f-join org-directory "ideas.org") + "Tasks") + "* %? ") + ("s" "shopping list" entry (file+headline + ,(f-join 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 org-directory "notes.org")) + (setq org-agenda-files (list (f-join org-directory "work.org") + (f-join 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))) + +(provide 'wpc-org) +;;; wpc-org.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-package.el b/emacs/.emacs.d/wpc/wpc-package.el new file mode 100644 index 000000000000..5fd7a89982fb --- /dev/null +++ b/emacs/.emacs.d/wpc/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) + +;; Even though we're packaging our Emacs with Nix, having MELPA registered is +;; helpful to ad-hoc test out packages before declaratively adding them to +;; emacs/default.nix. +(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) +(package-initialize) + +(unless (package-installed-p 'use-package) + ;; TODO: Consider removing this to improve initialization speed. + (package-refresh-contents) + (package-install 'use-package)) +(eval-when-compile + (require 'use-package)) +(use-package general) + +(provide 'wpc-package) +;;; wpc-package.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-prolog.el b/emacs/.emacs.d/wpc/wpc-prolog.el new file mode 100644 index 000000000000..94e705b1b114 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-prolog.el @@ -0,0 +1,16 @@ +;;; wpc-prolog.el --- For Prologging things -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; Code configuring my Prolog work. + +;;; Code: + +(require 'macros) + +;; TODO: Notice that the .pl extension conflicts with Perl files. This may +;; become a problem should I start working with Perl. +(macros/support-file-extension "pl" prolog-mode) + +(provide 'wpc-prolog) +;;; wpc-prolog.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-python.el b/emacs/.emacs.d/wpc/wpc-python.el new file mode 100644 index 000000000000..25f1a4816a67 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-python.el @@ -0,0 +1,21 @@ +;;; wpc-python.el --- Python configuration -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; My Python configuration settings +;; +;; Depends +;; - `apti yapf` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package py-yapf + :config + (add-hook 'python-mode-hook #'py-yapf-enable-on-save)) + +(provide 'wpc-python) +;;; wpc-python.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-reasonml.el b/emacs/.emacs.d/wpc/wpc-reasonml.el new file mode 100644 index 000000000000..909c33d121f7 --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-rust.el b/emacs/.emacs.d/wpc/wpc-rust.el new file mode 100644 index 000000000000..fafa27d18c77 --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-shell.el b/emacs/.emacs.d/wpc/wpc-shell.el new file mode 100644 index 000000000000..803a3232ef5b --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-shell.el @@ -0,0 +1,17 @@ +;;; 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)) + +(use-package fish-mode) + +(provide 'wpc-shell) +;;; wpc-shell.el ends here diff --git a/emacs/.emacs.d/wpc/wpc-terminal.el b/emacs/.emacs.d/wpc/wpc-terminal.el new file mode 100644 index 000000000000..c232bb85a7b7 --- /dev/null +++ b/emacs/.emacs.d/wpc/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/emacs/.emacs.d/wpc/wpc-ui.el b/emacs/.emacs.d/wpc/wpc-ui.el new file mode 100644 index 000000000000..6ac587c46567 --- /dev/null +++ b/emacs/.emacs.d/wpc/wpc-ui.el @@ -0,0 +1,179 @@ +;;; 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 'themes) +(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)) + +;; TODO: Further customize `mode-line-format' variable. +(delete 'mode-line-modes mode-line-format) +(delete '(vc-mode vc-mode) mode-line-format) + +;; disable startup screen +(setq inhibit-startup-screen t) + +;; disable toolbar +(tool-bar-mode -1) + +;; 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) + +;; TODO: Re-enable this when base16-wpgtk are looking better. +;; integration with wpgtk (in vendor directory) +;; (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 + :config + (counsel-mode t) + (ivy-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 + (unless (f-exists? "~/.local/share/fonts/all-the-icons.ttf") + (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) + +;; reduce noisiness of auto-revert-mode +(setq auto-revert-verbose nil) + +;; highlight lines that are over `constants/fill-column' characters long +(use-package whitespace + :config + ;; TODO: This should change depending on the language and project. For + ;; example, Google Java projects prefer 100 character width instead of 80 + ;; character width. + (setq whitespace-line-column constants/fill-column) + (setq whitespace-style '(face lines-tail)) + (add-hook 'prog-mode-hook #'whitespace-mode)) + +;; dirname/filename instead of filename<dirname> +(setq uniquify-buffer-name-style 'forward) + +;; highlight matching parens, brackets, etc +(show-paren-mode 1) + +;; hide the scroll-bars in the GUI +(scroll-bar-mode -1) + +;; TODO: Learn how to properly integrate this with dunst or another system-level +;; notification program. +;; GUI alerts in emacs +(use-package alert + :commands (alert) + :config + (setq alert-default-style 'notifier)) + +;; 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/set "Solarized Light") + +(provide 'wpc-ui) +;;; wpc-ui.el ends here diff --git a/emacs/.emacs.d/wpc/wpgtk.el b/emacs/.emacs.d/wpc/wpgtk.el new file mode 100644 index 000000000000..432d82884399 --- /dev/null +++ b/emacs/.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/emacs/.emacs.d/wpc/ynab.el b/emacs/.emacs.d/wpc/ynab.el new file mode 100644 index 000000000000..7e132e20c244 --- /dev/null +++ b/emacs/.emacs.d/wpc/ynab.el @@ -0,0 +1,56 @@ +;;; ynab.el --- Functions for YNAB's API -*- lexical-binding: t -*- +;; Author: William Carroll <wpcarro@gmail.com> + +;;; Commentary: +;; I'm not sure what the outcome of this project is. I'm just writing some +;; Elisp at the moment to document some of my cursory interactions with YNAB's +;; API. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'json) +(require 'a) +(require 'request) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar ynab/api-url "https://api.youneedabudget.com/v1/" + "The URL of the YNAB API.") + +(defun ynab/get-secret (name) + "Fetch and decrypt the secret for YNAB at NAME in the password store." + (password-store-get (format "%s/%s" "finance/youneedabudget.com" name))) + +(defvar ynab/personal-access-token + (ynab/get-secret "personal-access-token") + "My personal access token to YNAB's API.") + +(defvar ynab/budget-id + (ynab/get-secret "budget-id") + "The ID of my current budget on YNAB.") + +(defvar ynab/account-id + (ynab/get-secret "account-id") + "The ID of my current budget on YNAB.") + +(defun ynab/url-for-endpoint (endpoint) + "Return the URL for the YNAB ENDPOINT. +This will resolve any variables in the form of {variable_name} using a prefined +scope object." + (format "%s%s" ynab/api-url endpoint)) + +(macros/comment + ;; TODO: Use these this map to resolve variables in an endpoint URL like + ;; '/budgets/{budget_id}/'. + '((budget_id . (ynab/get-secret "budget-id")) + (account_id . (ynab/get-secret "account-id"))) + (request (ynab/url-for-endpoint "/budgets/{budget_id}/transactions"))) + +(provide 'ynab) +;;; ynab.el ends here diff --git a/emacs/.emacs.d/wpc/zle.el b/emacs/.emacs.d/wpc/zle.el new file mode 100644 index 000000000000..1b01da938456 --- /dev/null +++ b/emacs/.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 |