diff options
author | Vincent Ambo <mail@tazj.in> | 2021-12-13T22·51+0300 |
---|---|---|
committer | Vincent Ambo <mail@tazj.in> | 2021-12-13T23·15+0300 |
commit | 019f8fd2113df4c5247c3969c60fd4f0e08f91f7 (patch) | |
tree | 76a857f61aa88f62a30e854651e8439db77fd0ea /users/wpcarro/emacs/.emacs.d/wpc | |
parent | 464bbcb15c09813172c79820bcf526bb10cf4208 (diff) | |
parent | 6123e976928ca3d8d93f0b2006b10b5f659eb74d (diff) |
subtree(users/wpcarro): docking briefcase at '24f5a642' r/3226
git-subtree-dir: users/wpcarro git-subtree-mainline: 464bbcb15c09813172c79820bcf526bb10cf4208 git-subtree-split: 24f5a642af3aa1627bbff977f0a101907a02c69f Change-Id: I6105b3762b79126b3488359c95978cadb3efa789
Diffstat (limited to 'users/wpcarro/emacs/.emacs.d/wpc')
73 files changed, 7353 insertions, 0 deletions
diff --git a/users/wpcarro/emacs/.emacs.d/wpc/>.el b/users/wpcarro/emacs/.emacs.d/wpc/>.el new file mode 100644 index 000000000000..68d8576b8079 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/>.el @@ -0,0 +1,29 @@ +;;; >.el --- Small utility functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Originally I stored the `>>` macro in macros.el, but after setting up linting +;; for my Elisp in CI, `>>` failed because it didn't have the `macros-` +;; namespace. I created this module to establish a `>-` namespace under which I +;; can store some utilities that would be best kept without a cumbersome +;; namespace. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro >-> (&rest forms) + "Compose a new, point-free function by composing FORMS together." + (let ((sym (gensym))) + `(lambda (,sym) + (->> ,sym ,@forms)))) + + +(provide '>) +;;; >.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/al.el b/users/wpcarro/emacs/.emacs.d/wpc/al.el new file mode 100644 index 000000000000..e29f853f8ea5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/al.el @@ -0,0 +1,255 @@ +;;; al.el --- Interface for working with associative lists -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Firstly, a rant: +;; In most cases, I find Elisp's APIs to be confusing. There's a mixture of +;; overloaded functions that leak the implementation details (TODO: provide an +;; example of this.) of the abstract data type, which I find privileges those +;; "insiders" who spend disproportionately large amounts of time in Elisp land, +;; and other functions with little-to-no pattern about the order in which +;; arguments should be applied. In theory, however, most of these APIs could +;; and should be much simpler. This module represents a step in that direction. +;; +;; I'm modelling these APIs after Elixir's APIs. +;; +;; On my wishlist is to create protocols that will allow generic interfaces like +;; Enum protocols, etc. Would be nice to abstract over... +;; - associative lists (i.e. alists) +;; - property lists (i.e. plists) +;; - hash tables +;; ...with some dictionary or map-like interface. This will probably end up +;; being quite similar to the kv.el project but with differences at the API +;; layer. +;; +;; Similar libraries: +;; - map.el: Comes bundled with recent versions of Emacs. +;; - asoc.el: Helpers for working with alists. asoc.el is similar to alist.el +;; because it uses the "!" convention for signalling that a function mutates +;; the underlying data structure. +;; - ht.el: Hash table library. +;; - kv.el: Library for dealing with key-value collections. Note that map.el +;; has a similar typeclass because it works with lists, hash-tables, or +;; arrays. +;; - a.el: Clojure-inspired way of working with key-value data structures in +;; Elisp. Works with alists, hash-tables, and sometimes vectors. +;; +;; Some API design principles: +;; - The "noun" (i.e. alist) of the "verb" (i.e. function) comes last to improve +;; composability with the threading macro (i.e. `->>') and to improve consumers' +;; intuition with the APIs. Learn this once, know it always. +;; +;; - Every function avoids mutating the alist unless it ends with !. +;; +;; - CRUD operations will be named according to the following table: +;; - "create" *and* "set" +;; - "read" *and* "get" +;; - "update" +;; - "delete" *and* "remove" +;; +;; For better or worse, all of this code expects alists in the form of: +;; ((first-name . "William") (last-name . "Carroll")) +;; +;; Special thanks to github.com/alphapapa/emacs-package-dev-handbook for some of +;; the idiomatic ways to update alists. +;; +;; TODO: Include a section that compares alist.el to a.el from +;; github.com/plexus/a.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies: +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'dash) +(require 'tuple) +(require 'maybe) + +;; 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 al-enable-tests? t + "When t, run the test suite.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support a variadic version of this to easily construct alists. +(defun al-new () + "Return a new, empty alist." + '()) + +;; Create +;; TODO: See if this mutates. +(defun al-set (k v xs) + "Set K to V in XS." + (if (al-has-key? k xs) + (progn + ;; Note: this is intentional `alist-get' and not `al-get'. + (setf (alist-get k xs) v) + xs) + (list-cons `(,k . ,v) xs))) + +(defun al-set! (k v xs) + "Set K to V in XS mutatively. +Note that this doesn't append to the alist in the way that most alists handle + writing. If the k already exists in XS, it is overwritten." + (map-delete xs k) + (map-put! xs k v)) + +;; Read +(defun al-get (k xs) + "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 al-get-entry (k xs) + "Return the first key-value pair at K in XS." + (assoc k xs)) + +;; Update +;; TODO: Add warning about only the first occurrence being updated in the +;; documentation. +(defun al-update (k f xs) + "Apply F to the value stored at K in XS. +If `K' is not in `XS', this function errors. Use `al-upsert' if you're +interested in inserting a value when a key doesn't already exist." + (if (not (al-has-key? k xs)) + (error "Refusing to update: key does not exist in alist") + (al-set k (funcall f (al-get k xs)) xs))) + +(defun al-update! (k f xs) + "Call F on the entry at K in XS. +Mutative variant of `al-update'." + (al-set! k (funcall f (al-get k xs))xs)) + +;; TODO: Support this. +(defun al-upsert (k v f xs) + "If K exists in `XS' call `F' on the value otherwise insert `V'." + (if (al-has-key? k xs) + (al-update k f xs) + (al-set k v xs))) + +;; Delete +;; TODO: Make sure `delete' and `remove' behave as advertised in the Elisp docs. +(defun al-delete (k xs) + "Deletes the entry of K from XS. +This only removes the first occurrence of K, since alists support multiple + key-value entries. See `al-delete-all' and `al-dedupe'." + (remove (assoc k xs) xs)) + +(defun al-delete! (k xs) + "Delete the entry of K from XS. +Mutative variant of `al-delete'." + (delete (assoc k xs) xs)) + +;; Additions to the CRUD API +;; TODO: Implement this function. +(defun al-dedupe-keys (xs) + "Remove the entries in XS where the keys are `equal'.") + +(defun al-dedupe-entries (xs) + "Remove the entries in XS where the key-value pair are `equal'." + (delete-dups xs)) + +(defun al-keys (xs) + "Return a list of the keys in XS." + (mapcar 'car xs)) + +(defun al-values (xs) + "Return a list of the values in XS." + (mapcar 'cdr xs)) + +(defun al-has-key? (k xs) + "Return t if XS has a key `equal' to K." + (maybe-some? (assoc k xs))) + +(defun al-has-value? (v xs) + "Return t if XS has a value of V." + (maybe-some? (rassoc v xs))) + +(defun al-count (xs) + "Return the number of entries in XS." + (length xs)) + +;; TODO: Should I support `al-find-key' and `al-find-value' variants? +(defun al-find (p xs) + "Find an element in XS. + +Apply a predicate fn, P, to each key and value in XS and return the key of the +first element that returns t." + (let ((result (list-find (lambda (x) (funcall p (car x) (cdr x))) xs))) + (if result + (car result) + nil))) + +(defun al-map-keys (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(funcall f (car x)) . ,(cdr x))) + xs)) + +(defun al-map-values (f xs) + "Call F on the values in XS, returning a new alist." + (list-map (lambda (x) + `(,(car x) . ,(funcall f (cdr x)))) + xs)) + +(defun al-reduce (acc f xs) + "Return a new alist by calling F on k v and ACC from XS. +F should return a tuple. See tuple.el for more information." + (->> (al-keys xs) + (list-reduce acc + (lambda (k acc) + (funcall f k (al-get k xs) acc))))) + +(defun al-merge (a b) + "Return a new alist with a merge of alists, A and B. +In this case, the last writer wins, which is B." + (al-reduce a #'al-set b)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when al-enable-tests? + (prelude-assert + (equal '((2 . one) + (3 . two)) + (al-map-keys #'1+ + '((1 . one) + (2 . two))))) + (prelude-assert + (equal '((one . 2) + (two . 3)) + (al-map-values #'1+ + '((one . 1) + (two . 2)))))) + + +;; TODO: Support test cases for the entire API. + +(provide 'al) +;;; al.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/bag.el b/users/wpcarro/emacs/.emacs.d/wpc/bag.el new file mode 100644 index 000000000000..38a09d94f900 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bag.el @@ -0,0 +1,71 @@ +;;; bag.el --- Working with bags (aka multi-sets) -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; What is a bag? A bag should be thought of as a frequency table. It's a way +;; to convert a list of something into a set that allows duplicates. Isn't +;; allowing duplicates the whole thing with Sets? Kind of. But the interface +;; of Sets is something that bags resemble, so multi-set isn't as 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 'al) +(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 (al-new))) + +(defun bag-contains? (x xs) + "Return t if XS has X." + (al-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/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el b/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el new file mode 100644 index 000000000000..76fc6fe4d75b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bookmark.el @@ -0,0 +1,100 @@ +;;; bookmark.el --- Saved files and directories on my filesystem -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; 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) +(require 'constants) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct bookmark label path kbd) + +;; TODO: Consider hosting this function somewhere other than here, since it +;; feels useful above of the context of bookmarks. +;; TODO: Assess whether it'd be better to use the existing function: +;; `counsel-projectile-switch-project-action'. See the noise I made on GH for +;; more context: https://github.com/ericdanan/counsel-projectile/issues/137 + +(defun bookmark-handle-directory-dwim (path) + "Open PATH as either a project directory or a regular directory. +If PATH is `projectile-project-p', open with `counsel-projectile-find-file'. +Otherwise, open with `counsel-find-file'." + (if (projectile-project-p path) + (with-temp-buffer + (cd (projectile-project-p path)) + (call-interactively #'counsel-projectile-find-file)) + (let ((ivy-extra-directories nil)) + (counsel-find-file path)))) + +(defconst bookmark-handle-directory #'bookmark-handle-directory-dwim + "Function to call when a bookmark points to a directory.") + +(defconst bookmark-handle-file #'counsel-find-file-action + "Function to call when a bookmark points to a file.") + +(defconst bookmark-whitelist + (list + (make-bookmark :label "briefcase" + :path constants-briefcase + :kbd "b") + (make-bookmark :label "current project" + :path constants-current-project + :kbd "p")) + "List of registered bookmarks.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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-install-kbds () + "Install the keybindings defined herein." + (->> bookmark-whitelist + (list-map + (lambda (b) + (general-define-key + :prefix "<SPC>" + :states '(normal) + (format "J%s" (bookmark-kbd b)) + (lambda () (interactive) (find-file (bookmark-path b))) + (format "j%s" (bookmark-kbd b)) + ;; TODO: Consider `cl-labels' so `which-key' minibuffer is more + ;; helpful. + (lambda () (interactive) (bookmark-open b))))))) + +(provide 'bookmark) +;;; bookmark.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/buffer.el b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el new file mode 100644 index 000000000000..3c78601b79f3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/buffer.el @@ -0,0 +1,206 @@ +;;; buffer.el --- Working with buffers -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Utilities for CRUDing buffers in Emacs. +;; +;; Many of these functions may seem unnecessary especially when you consider +;; there implementations. In general I believe that Elisp suffers from a +;; library disorganization problem. Providing simple wrapper functions that +;; rename functions or reorder parameters is worth the effort in my opinion if +;; it improves discoverability (via intuition) and improve composability. +;; +;; I support three ways for switching between what I'm calling "source code +;; buffers": +;; 1. Toggling previous: <SPC><SPC> +;; 2. Using `ivy-read': <SPC>b +;; TODO: These obscure evil KBDs. Maybe a hydra definition would be best? +;; 3. Cycling (forwards/backwards): C-f, C-b + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'maybe) +(require 'set) +(require 'cycle) +(require 'struct) +(require 'ts) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 + 'vterm-mode + 'magit-status-mode + 'magit-process-mode + 'magit-log-mode + 'magit-diff-mode + 'org-mode + 'fundamental-mode) + "A blacklist of major-modes to ignore for listing source code buffers.") + +(defconst buffer-source-code-timeout 2 + "Number of seconds to wait before invalidating the cycle.") + +(cl-defstruct source-code-cycle cycle last-called) + +(defun buffer-emacs-generated? (name) + "Return t if buffer, NAME, is an Emacs-generated buffer. +Some buffers are Emacs-generated but are surrounded by whitespace." + (let ((trimmed (s-trim name))) + (and (s-starts-with? "*" trimmed)))) + +(defun buffer-find (buffer-or-name) + "Find a buffer by its BUFFER-OR-NAME." + (get-buffer buffer-or-name)) + +(defun buffer-major-mode (name) + "Return the active `major-mode' in buffer, NAME." + (with-current-buffer (buffer-find name) + major-mode)) + +(defun buffer-source-code-buffers () + "Return a list of source code buffers. +This will ignore Emacs-generated buffers, like *Messages*. It will also ignore + any buffer whose major mode is defined in `buffer-source-code-blacklist'." + (->> (buffer-list) + (list-map #'buffer-name) + (list-reject #'buffer-emacs-generated?) + (list-reject (lambda (name) + (set-contains? (buffer-major-mode name) + buffer-source-code-blacklist))))) + +(defvar buffer-source-code-cycle-state + (make-source-code-cycle + :cycle (cycle-from-list (buffer-source-code-buffers)) + :last-called (ts-now)) + "State used to manage cycling between source code buffers.") + +(defun buffer-exists? (name) + "Return t if buffer, NAME, exists." + (maybe-some? (buffer-find name))) + +(defun buffer-new (name) + "Return a newly created buffer NAME." + (generate-new-buffer name)) + +(defun buffer-find-or-create (name) + "Find or create buffer, NAME. +Return a reference to that buffer." + (let ((x (buffer-find name))) + (if (maybe-some? x) + x + (buffer-new name)))) + +;; TODO: Should this consume: `display-buffer' or `switch-to-buffer'? +(defun buffer-show (buffer-or-name) + "Display the BUFFER-OR-NAME, which is either a buffer reference or its name." + (display-buffer buffer-or-name)) + +;; TODO: Move this and `buffer-cycle-prev' into a separate module that +;; encapsulates all of this behavior. + +(defun buffer-cycle (cycle-fn) + "Using CYCLE-FN, move through `buffer-source-code-buffers'." + (let ((last-called (source-code-cycle-last-called + buffer-source-code-cycle-state)) + (cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (if (> (ts-diff (ts-now) last-called) + buffer-source-code-timeout) + (progn + (struct-set! source-code-cycle + cycle + (cycle-from-list (buffer-source-code-buffers)) + buffer-source-code-cycle-state) + (let ((cycle (source-code-cycle-cycle + buffer-source-code-cycle-state))) + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle))) + (struct-set! source-code-cycle + last-called + (ts-now) + buffer-source-code-cycle-state)) + (progn + (funcall cycle-fn cycle) + (switch-to-buffer (cycle-current cycle)))))) + +(defun buffer-cycle-next () + "Cycle forward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-next)) + +(defun buffer-cycle-prev () + "Cycle backward through the `buffer-source-code-buffers'." + (interactive) + (buffer-cycle #'cycle-prev)) + +(defun buffer-ivy-source-code () + "Use `ivy-read' to choose among all open source code buffers." + (interactive) + (ivy-read "Source code buffer: " + (-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/users/wpcarro/emacs/.emacs.d/wpc/bytes.el b/users/wpcarro/emacs/.emacs.d/wpc/bytes.el new file mode 100644 index 000000000000..48d3932f1cf8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/bytes.el @@ -0,0 +1,113 @@ +;;; bytes.el --- Working with byte values -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Functions to help with human-readable representations of byte values. +;; +;; Usage: +;; See the test cases for example usage. Or better yet, I should use a type of +;; structured documentation that would allow me to expose a view into the test +;; suite here. Is this currently possible in Elisp? +;; +;; API: +;; - serialize :: Integer -> String +;; +;; Wish list: +;; - Rounding: e.g. (bytes (* 1024 1.7)) => "2KB" + +;;; Code: + +;; TODO: Support -ibabyte variants like Gibibyte (GiB). + +;; Ranges: +;; B: [ 0, 1e3) +;; KB: [ 1e3, 1e6) +;; MB: [ 1e6, 1e6) +;; GB: [ 1e9, 1e12) +;; TB: [1e12, 1e15) +;; PB: [1e15, 1e18) +;; +;; Note: I'm currently not support exabytes because that causes the integer to +;; overflow. I imagine a larger integer type may exist, but for now, I'll +;; treat this as a YAGNI. + +(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/users/wpcarro/emacs/.emacs.d/wpc/cache.el b/users/wpcarro/emacs/.emacs.d/wpc/cache.el new file mode 100644 index 000000000000..be2049091c6d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/cache.el @@ -0,0 +1,89 @@ +;;; cache.el --- Caching things -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'struct) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el b/users/wpcarro/emacs/.emacs.d/wpc/clipboard.el new file mode 100644 index 000000000000..47cc459061e3 --- /dev/null +++ b/users/wpcarro/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> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Simple functions for copying and pasting. +;; +;; Integrate with bburns/clipmon so that System Clipboard can integrate with +;; Emacs's kill-ring. +;; +;; Wish list: +;; - Create an Emacs integration with github.com/cdown/clipmenud. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun clipboard-copy (x &key (message "[clipboard.el] Copied!")) + "Copy string, X, to X11's clipboard and `message' MESSAGE." + (kill-new x) + (message message)) + +(cl-defun clipboard-paste (&key (message "[clipboard.el] Pasted!")) + "Paste contents of X11 clipboard and `message' MESSAGE." + (yank) + (message message)) + +(defun clipboard-contents () + "Return the contents of the clipboard as a string." + (substring-no-properties (current-kill 0))) + +(provide 'clipboard) +;;; clipboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el b/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el new file mode 100644 index 000000000000..a02dc67c56a9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/colorscheme.el @@ -0,0 +1,86 @@ +;;; colorscheme.el --- Syntax highlight and friends -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; 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 '>) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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)) + +(provide 'colorscheme) +;;; colorscheme.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/constants.el b/users/wpcarro/emacs/.emacs.d/wpc/constants.el new file mode 100644 index 000000000000..ae21a089cc05 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/constants.el @@ -0,0 +1,55 @@ +;;; constants.el --- Constants for organizing my Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file contains constants that are shared across my configuration. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'f) +(require 'maybe) + +(prelude-assert (f-exists? (getenv "BRIEFCASE"))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst constants-ci? + (maybe-some? (getenv "CI")) + "Encoded as t when Emacs is running in CI.") + +(defconst constants-briefcase + (getenv "BRIEFCASE") + "Path to my monorepo, which various parts of my configuration rely on.") + +;; 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 + constants-briefcase + "Variable holding the directory for my currently active project.") + +(defconst constants-mouse-kbds + '([mouse-1] [down-mouse-1] [drag-mouse-1] [double-mouse-1] [triple-mouse-1] + [mouse-2] [down-mouse-2] [drag-mouse-2] [double-mouse-2] [triple-mouse-2] + [mouse-3] [down-mouse-3] [drag-mouse-3] [double-mouse-3] [triple-mouse-3] + [mouse-4] [down-mouse-4] [drag-mouse-4] [double-mouse-4] [triple-mouse-4] + [mouse-5] [down-mouse-5] [drag-mouse-5] [double-mouse-5] [triple-mouse-5]) + "All of the mouse-related keybindings that Emacs recognizes.") + +(defconst constants-fill-column 80 + "Variable used to set the defaults for wrapping, highlighting, etc.") + +(provide 'constants) +;;; constants.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/cycle.el b/users/wpcarro/emacs/.emacs.d/wpc/cycle.el new file mode 100644 index 000000000000..5ea015930017 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/cycle.el @@ -0,0 +1,224 @@ +;;; cycle.el --- Simple module for working with cycles -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Something like this may already exist, but I'm having trouble finding it, and +;; I think writing my own is a nice exercise for learning more Elisp. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'math) +(require 'maybe) +(require 'struct) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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-from-list (xs) + "Create a cycle from a list of `XS'." + (if (= 0 (length xs)) + (make-cycle :current-index nil + :previous-index nil + :xs xs) + (make-cycle :current-index 0 + :previous-index nil + :xs xs))) + +(defun cycle-new (&rest xs) + "Create a cycle with XS as the values." + (cycle-from-list xs)) + +(defun cycle-to-list (xs) + "Return the list representation of a cycle, XS." + (cycle-xs xs)) + +(defun cycle--next-index<- (lo hi x) + "Return the next index in a cycle when moving downwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (< (- x 1) lo) + (- hi 1) + (- x 1))) + +(defun cycle--next-index-> (lo hi x) + "Return the next index in a cycle when moving upwards. +- `LO' is the lower bound. +- `HI' is the upper bound. +- `X' is the current index." + (if (>= (+ 1 x) hi) + lo + (+ 1 x))) + +(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 (cycle--next-index-> 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-prev (xs) + "Return the previous value in `XS' and update `current-index'." + (let* ((current-index (cycle-current-index xs)) + (next-index (cycle--next-index<- 0 (cycle-count xs) current-index))) + (struct-set! cycle previous-index current-index xs) + (struct-set! cycle current-index next-index xs) + (nth next-index (cycle-xs xs)))) + +(defun cycle-current (cycle) + "Return the current value in `CYCLE'." + (nth (cycle-current-index cycle) (cycle-xs cycle))) + +(defun cycle-count (cycle) + "Return the length of `xs' in `CYCLE'." + (length (cycle-xs cycle))) + +(defun cycle-jump (i xs) + "Jump to the I index of XS." + (let ((current-index (cycle-current-index xs)) + (next-index (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-focus-item (x xs) + "Focus item, X, in cycle XS. +ITEM is the first item in XS that t for `equal'." + (cycle-focus (lambda (y) (equal x y)) xs)) + +(defun cycle-contains? (x xs) + "Return t if cycle, XS, has member X." + (->> xs + cycle-xs + (list-contains? x))) + +(defun cycle-empty? (xs) + "Return t if cycle XS has no elements." + (= 0 (length (cycle-xs xs)))) + +(defun cycle-focused? (xs) + "Return t if cycle XS has a non-nil value for current-index." + (maybe-some? (cycle-current-index xs))) + +(defun cycle-append (x xs) + "Add X to the left of the focused element in XS. +If there is no currently focused item, add X to the beginning of XS." + (if (cycle-empty? xs) + (progn + (struct-set! cycle xs (list x) xs) + (struct-set! cycle current-index 0 xs) + (struct-set! cycle previous-index nil xs)) + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs))) + (if curr-i + (progn + (struct-set! cycle xs (-insert-at curr-i x (cycle-xs xs)) xs) + (when (>= prev-i curr-i) (struct-set! cycle previous-index (1+ prev-i) xs)) + (when curr-i (struct-set! cycle current-index (1+ curr-i) xs))) + (progn + (struct-set! cycle xs (cons x (cycle-xs xs)) xs) + (when prev-i (struct-set! cycle previous-index (1+ prev-i) xs)))) + xs))) + +(defun cycle-remove (x xs) + "Attempt to remove X from XS. + +X is found using `equal'. + +If X is the currently focused value, after it's deleted, current-index will be + nil. If X is the previously value, after it's deleted, previous-index will be + nil." + (let ((curr-i (cycle-current-index xs)) + (prev-i (cycle-previous-index xs)) + (rm-i (-elem-index x (cycle-xs xs)))) + (struct-set! cycle xs (-remove-at rm-i (cycle-xs xs)) xs) + (when prev-i + (when (> prev-i rm-i) (struct-set! cycle previous-index (1- prev-i) xs)) + (when (= prev-i rm-i) (struct-set! cycle previous-index nil xs))) + (when curr-i + (when (> curr-i rm-i) (struct-set! cycle current-index (1- curr-i) xs)) + (when (= curr-i rm-i) (struct-set! cycle current-index nil xs))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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))) + (prelude-assert (equal '(1 4 2 3) (cycle-xs (cycle-append 4 xs)))) + (prelude-assert (equal '(1 2 3) (cycle-xs (cycle-remove 4 xs)))) + (progn + (cycle-focus-item 3 xs) + (cycle-focus-item 2 xs) + (cycle-remove 1 xs) + (prelude-assert (= 2 (cycle-current xs))) + (prelude-assert (= 3 (cycle-previous-focus xs)))))) + +(provide 'cycle) +;;; cycle.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/device.el b/users/wpcarro/emacs/.emacs.d/wpc/device.el new file mode 100644 index 000000000000..0e7992fd79e7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/device.el @@ -0,0 +1,50 @@ +;;; device.el --- Physical device information -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Functions for querying device information. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'al) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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." + (al-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))) + +(defun device-corporate? () + "Return t if the current device is owned by my company." + (or (device-work-laptop?) (device-work-desktop?))) + +(provide 'device) +;;; device.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/display.el b/users/wpcarro/emacs/.emacs.d/wpc/display.el new file mode 100644 index 000000000000..24c00e3f73ea --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/display.el @@ -0,0 +1,138 @@ +;;; display.el --- Working with single or multiple displays -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Mostly wrappers around xrandr. +;; +;; Troubleshooting: +;; The following commands help me when I (infrequently) interact with xrandr. +;; - xrandr --listmonitors +;; - xrandr --query + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'dash) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defmacro display-register (name &key + output + primary + coords + size + rate + dpi + rotate) + "Macro to define constants and two functions for {en,dis}abling a display. + +NAME - the human-readable identifier for the display +OUTPUT - the xrandr identifier for the display +PRIMARY - if true, send --primary flag to xrandr +COORDS - X and Y offsets +SIZE - the pixel resolution of the display (width height) +RATE - the refresh rate +DPI - the pixel density in dots per square inch +rotate - one of {normal,left,right,inverted} + +See the man-page for xrandr for more details." + `(progn + (defconst ,(intern (format "display-%s" name)) ,output + ,(format "The xrandr identifier for %s" name)) + (defconst ,(intern (format "display-%s-args" name)) + ,(replace-regexp-in-string + "\s+" " " + (s-format "--output ${output} ${primary-flag} --auto \ + --size ${size-x}x${size-y} --rate ${rate} --dpi ${dpi} \ + --rotate ${rotate} ${pos-flag}" + #'aget + `(("output" . ,output) + ("primary-flag" . ,(if primary "--primary" "--noprimary")) + ("pos-flag" . ,(if coords + (format "--pos %dx%d" + (car coords) + (cadr coords)) + "")) + ("size-x" . ,(car size)) + ("size-y" . ,(cadr size)) + ("rate" . ,rate) + ("dpi" . ,dpi) + ("rotate" . ,rotate)))) + ,(format "The arguments we pass to xrandr for display-%s." name)) + (defconst ,(intern (format "display-%s-command" name)) + (format "xrandr %s" ,(intern (format "display-%s-args" name))) + ,(format "The command we run to configure %s" name)) + (defun ,(intern (format "display-enable-%s" name)) () + ,(format "Attempt to enable my %s monitor" name) + (interactive) + (prelude-start-process + :name ,(format "display-enable-%s" name) + :command ,(intern (format "display-%s-command" name)))) + (defun ,(intern (format "display-disable-%s" name)) () + ,(format "Attempt to disable my %s monitor." name) + (interactive) + (prelude-start-process + :name ,(format "display-disable-%s" name) + :command ,(format + "xrandr --output %s --off" + output))))) + +(defmacro display-arrangement (name &key displays) + "Create a function, display-arrange-<NAME>, to enable all your DISPLAYS." + `(defun ,(intern (format "display-arrange-%s" name)) () + (interactive) + (prelude-start-process + :name ,(format "display-configure-%s" name) + :command ,(format "xrandr %s" + (->> displays + (-map (lambda (x) + (eval (intern (format "display-%s-args" x))))) + (s-join " ")))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(display-register laptop + :output "eDP1" + :primary nil + :coords (2560 1440) + :size (1920 1080) + :rate 30.0 + :dpi 144 + :rotate normal) + +(display-register 4k-horizontal + :output "DP2" + :primary t + :coords (0 0) + :size (2560 1440) + :rate 30.0 + :dpi 144 + :rotate normal) + +(display-register 4k-vertical + :output "HDMI1" + :primary nil + :coords (-1440 -560) + :size (2560 1440) + :rate 30.0 + :dpi 144 + :rotate left) + +(display-arrangement primary + :displays (laptop 4k-horizontal 4k-vertical)) + +(provide 'display) +;;; display.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/dotted.el b/users/wpcarro/emacs/.emacs.d/wpc/dotted.el new file mode 100644 index 000000000000..f400affd6ec6 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/dotted.el @@ -0,0 +1,58 @@ +;;; dotted.el --- Working with dotted pairs in Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun dotted-new (&optional a b) + "Create a new dotted pair of A and B." + (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/users/wpcarro/emacs/.emacs.d/wpc/email.el b/users/wpcarro/emacs/.emacs.d/wpc/email.el new file mode 100644 index 000000000000..ef5d7c9534ed --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/email.el @@ -0,0 +1,77 @@ +;;; email.el --- My email settings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Attempting to configure to `notmuch' for my personal use. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'notmuch) +(require 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq notmuch-saved-searches + '((:name "inbox" :query "tag:inbox" :key "i") + (:name "direct" + :query "tag:direct and tag:unread and not tag:sent" + :key "d") + (:name "action" :query "tag:action" :key "a") + (:name "review" :query "tag:review" :key "r") + (:name "waiting" :query "tag:waiting" :key "w") + (:name "broadcast" :query "tag:/broadcast\/.+/ and tag:unread" :key "b") + (:name "systems" :query "tag:/systems\/.+/ and tag:unread" :key "s") + (:name "sent" :query "tag:sent" :key "t") + (:name "drafts" :query "tag:draft" :key "D"))) + +;; Sort results from newest-to-oldest. +(setq notmuch-search-oldest-first nil) + +;; Discard noisy email signatures. +(setq notmuch-mua-cite-function #'message-cite-original-without-signature) + +;; By default, this is just '("-inbox") +(setq notmuch-archive-tags '("-inbox" "-unread" "+archive")) + +;; Show saved searches even when they're empty. +(setq notmuch-show-empty-saved-searches t) + +;; Currently the sendmail executable on my system is symlinked to msmtp. +(setq send-mail-function #'sendmail-send-it) + +;; I'm not sure if I need this or not. Copying it from tazjin@'s monorepo. +(setq notmuch-always-prompt-for-sender nil) + +;; Add the "User-Agent" header to my emails and ensure that it includes Emacs +;; and notmuch information. +(setq notmuch-mua-user-agent-function + (lambda () + (format "Emacs %s; notmuch.el %s" emacs-version notmuch-emacs-version))) + +;; I was informed that Gmail does this server-side +(setq notmuch-fcc-dirs nil) + +;; Ensure buffers are closed after sending mail. +(setq message-kill-buffer-on-exit t) + +;; Ensure sender is correctly passed to msmtp. +(setq mail-specify-envelope-from t + message-sendmail-envelope-from 'header + mail-envelope-from 'header) + +;; Assert that no two saved searches share share a KBD +(prelude-assert + (list-xs-distinct-by? (lambda (x) (plist-get x :key)) notmuch-saved-searches)) + +(provide 'email) +;;; email.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/fonts.el b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el new file mode 100644 index 000000000000..ad77b512d78e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/fonts.el @@ -0,0 +1,172 @@ +;;; fonts.el --- Font preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Control my font preferences with ELisp. + +;;; Code: + +;; TODO: `defcustom' font-size. +;; TODO: `defcustom' fonts. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'cycle) +(require 'device) +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 "10") + ('work-desktop "10")) + "My preferred default font-size, which is device specific.") + +(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))) + +(defun fonts-enable-ligatures () + "Call this function to enable ligatures." + (interactive) + (let ((alist '((33 . ".\\(?:\\(?:==\\|!!\\)\\|[!=]\\)") + (35 . ".\\(?:###\\|##\\|_(\\|[#(?[_{]\\)") ;; + (36 . ".\\(?:>\\)") + (37 . ".\\(?:\\(?:%%\\)\\|%\\)") + (38 . ".\\(?:\\(?:&&\\)\\|&\\)") + (42 . ".\\(?:\\(?:\\*\\*/\\)\\|\\(?:\\*[*/]\\)\\|[*/>]\\)") ;; + (43 . ".\\(?:\\(?:\\+\\+\\)\\|[+>]\\)") + (45 . ".\\(?:\\(?:-[>-]\\|<<\\|>>\\)\\|[<>}~-]\\)") + (46 . ".\\(?:\\(?:\\.[.<]\\)\\|[.=-]\\)") ;; + (47 . ".\\(?:\\(?:\\*\\*\\|//\\|==\\)\\|[*/=>]\\)") + (48 . ".\\(?:x[a-zA-Z]\\)") + (58 . ".\\(?:::\\|[:=]\\)") + (59 . ".\\(?:;;\\|;\\)") + (60 . ".\\(?:\\(?:!--\\)\\|\\(?:~~\\|->\\|\\$>\\|\\*>\\|\\+>\\|--\\|<[<=-]\\|=[<=>]\\||>\\)\\|[*$+~/<=>|-]\\)") + (61 . ".\\(?:\\(?:/=\\|:=\\|<<\\|=[=>]\\|>>\\)\\|[<=>~]\\)") + (62 . ".\\(?:\\(?:=>\\|>[=>-]\\)\\|[=>-]\\)") + (63 . ".\\(?:\\(\\?\\?\\)\\|[:=?]\\)") + (91 . ".\\(?:]\\)") + (92 . ".\\(?:\\(?:\\\\\\\\\\)\\|\\\\\\)") + (94 . ".\\(?:=\\)") + (119 . ".\\(?:ww\\)") + (123 . ".\\(?:-\\)") + (124 . ".\\(?:\\(?:|[=|]\\)\\|[=>|]\\)") + (126 . ".\\(?:~>\\|~~\\|[>=@~-]\\)")))) + (dolist (char-regexp alist) + (set-char-table-range composition-function-table (car char-regexp) + `([,(cdr char-regexp) 0 font-shape-gstring]))))) + +(provide 'fonts) +;;; fonts.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/fs.el b/users/wpcarro/emacs/.emacs.d/wpc/fs.el new file mode 100644 index 000000000000..2f83019fd4c9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/fs.el @@ -0,0 +1,70 @@ +;;; fs.el --- Make working with the filesystem easier -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.1")) + +;;; Commentary: +;; Ergonomic alternatives for working with the filesystem. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'f) +(require 's) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun fs-ensure-file (path) + "Ensure that a file and its directories in `PATH' exist. +Will error for inputs with a trailing slash." + (when (s-ends-with? "/" path) + (error (format "Input path has trailing slash: %s" path))) + (->> path + f-dirname + fs-ensure-dir) + (f-touch path)) + +(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 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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/users/wpcarro/emacs/.emacs.d/wpc/functions.el b/users/wpcarro/emacs/.emacs.d/wpc/functions.el new file mode 100644 index 000000000000..c0b873aaff14 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/functions.el @@ -0,0 +1,47 @@ +;;; functions.el --- Helper functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This file hopefully contains friendly APIs that making ELisp development more +;; enjoyable. + +;; TODO: Break these out into separate modules. + +;;; Code: +(defun functions-evil-window-vsplit-right () + "Split the window vertically and focus the right half." + (interactive) + (evil-window-vsplit) + (windmove-right)) + +(defun functions-evil-window-split-down () + "Split the window horizontal and focus the bottom half." + (interactive) + (evil-window-split) + (windmove-down)) + +(defun functions-create-snippet () + "Create a window split and then opens the Yasnippet editor." + (interactive) + (evil-window-vsplit) + (call-interactively #'yas-new-snippet)) + +(defun functions-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 functions-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/users/wpcarro/emacs/.emacs.d/wpc/graph.el b/users/wpcarro/emacs/.emacs.d/wpc/graph.el new file mode 100644 index 000000000000..9965622bb6d7 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/graph.el @@ -0,0 +1,95 @@ +;;; graph.el --- Working with in-memory graphs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; 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/users/wpcarro/emacs/.emacs.d/wpc/irc.el b/users/wpcarro/emacs/.emacs.d/wpc/irc.el new file mode 100644 index 000000000000..f221db9eb9e0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/irc.el @@ -0,0 +1,171 @@ +;;; irc.el --- Configuration for IRC chat -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Need to decide which client I will use for IRC. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'erc) +(require 'cycle) +(require 'string) +(require 'prelude) +(require 'al) +(require 'set) +(require 'maybe) +(require 'macros) +(require '>) +(require 'password-store) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defcustom irc-install-kbds? t + "When t, install the keybindings defined herein.") + +(setq erc-rename-buffers t) + +;; 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: 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. +(defconst irc-server->channels + `(("irc.freenode.net" . ,(cycle-new "freenode" "#freenode" "#nixos" "#emacs" "#pass")) + ("irc.corp.google.com" . ,(cycle-new "#drive-prod"))) + "Mapping of IRC servers to a cycle of my preferred channels.") + +;; TODO: Here is another horrible hack that should be revisted. +(setq erc-autojoin-channels-alist + (->> irc-server->channels + (al-map-values #'cycle-to-list) + (al-map-keys (>-> (s-chop-prefix "irc.") + (s-chop-suffix ".net"))))) + +;; 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 + (al-get "irc.freenode.net" + irc-server->channels))) + (set-from-list + (cycle-to-list + (al-get "irc.corp.google.com" + irc-server->channels))))) + +(defun irc-channel->server (server->channels channel) + "Using SERVER->CHANNELS, resolve an IRC server from a given CHANNEL." + (let ((result (al-find (lambda (k v) (cycle-contains? channel v)) + server->channels))) + (prelude-assert (maybe-some? result)) + result)) + +(defun irc-channel->cycle (server->channels channel) + "Using SERVER->CHANNELS, resolve an IRC's channels cycle from CHANNEL." + (al-get (irc-channel->server server->channels channel) + server->channels)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 () + "Kill 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 (macros-disable auto-fill-mode)) +(add-hook 'erc-mode-hook (macros-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 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(provide 'irc) +;;; irc.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el b/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el new file mode 100644 index 000000000000..9f5b7ca8a0f1 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ivy-clipmenu.el @@ -0,0 +1,138 @@ +;;; ivy-clipmenu.el --- Emacs client for clipmenu -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; 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, X, 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/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el new file mode 100644 index 000000000000..d13b99353451 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ivy-helpers.el @@ -0,0 +1,68 @@ +;;; ivy-helpers.el --- More interfaces to ivy -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Hopefully to improve my workflows. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tuple) +(require 'string) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defun ivy-helpers-kv (prompt kv f) + "PROMPT users with the keys in KV and return its corresponding value. + +Apply key and value from KV to F." + (ivy-read + prompt + kv + :require-match t + :action (lambda (entry) + (funcall f (car entry) (cdr entry))))) + +(defun ivy-helpers-do-run-external-command (cmd) + "Execute the specified CMD and notify the user when it finishes." + (message "Starting %s..." cmd) + (set-process-sentinel + (start-process-shell-command cmd nil cmd) + (lambda (process event) + (when (string= event "finished\n") + (message "%s process finished." process))))) + +(defun ivy-helpers-list-external-commands () + "Create a list of all external commands available on $PATH." + (cl-loop + for dir in (split-string (getenv "PATH") path-separator) + when (and (file-exists-p dir) (file-accessible-directory-p dir)) + for lsdir = (cl-loop for i in (directory-files dir t) + for bn = (file-name-nondirectory i) + when (and (not (s-contains? "-wrapped" i)) + (not (member bn completions)) + (not (file-directory-p i)) + (file-executable-p i)) + collect bn) + append lsdir into completions + finally return (sort completions 'string-lessp))) + +(defun ivy-helpers-run-external-command () + "Prompts the user with a list of all installed applications to launch." + (interactive) + (let ((external-commands-list (ivy-helpers-list-external-commands))) + (ivy-read "Command:" external-commands-list + :require-match t + :action #'ivy-helpers-do-run-external-command))) + +;;; Code: +(provide 'ivy-helpers) +;;; ivy-helpers.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/kbd.el b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el new file mode 100644 index 000000000000..b456f30cba89 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/kbd.el @@ -0,0 +1,86 @@ +;;; kbd.el --- Elisp keybinding -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; In order to stay organized, I'm attempting to dedicate KBD prefixes to +;; specific functions. I'm hoping I can be more deliberate with my keybinding +;; choices this way. +;; +;; Terminology: +;; For a more thorough overview of the terminology refer to `keybindings.md' +;; file. Here's a brief overview: +;; - workspace: Anything concerning EXWM workspaces. +;; - x11: Anything concerning X11 applications. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'al) +(require 'set) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst kbd-prefixes + '((workspace . "s") + (x11 . "C-s")) + "Mapping of functions to designated keybinding prefixes to stay organized.") + +;; Assert that no keybindings are colliding. +(prelude-assert + (= (al-count kbd-prefixes) + (->> kbd-prefixes + al-values + set-from-list + set-count))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun kbd-raw (f x) + "Return the string keybinding for function F and appendage X. +Values for F include: +- workspace +- x11" + (prelude-assert (al-has-key? f kbd-prefixes)) + (string-format + "%s-%s" + (al-get f kbd-prefixes) + x)) + +(defun kbd-for (f x) + "Return the `kbd' for function F and appendage X. +Values for F include: +- workspace +- x11" + (kbd (kbd-raw f x))) + +;; TODO: Prefer copying human-readable versions to the clipboard. Right now +;; this isn't too useful. +(defun kbd-copy-keycode () + "Copy the pressed key to the system clipboard." + (interactive) + (message "[kbd] Awaiting keypress...") + (let ((key (read-key))) + (clipboard-copy (string-format "%s" key)) + (message (string-format "[kbd] \"%s\" copied!" key)))) + +(defun kbd-print-keycode () + "Prints the pressed keybinding." + (interactive) + (message "[kbd] Awaiting keypress...") + (message (string-format "[kbd] keycode: %s" (read-key)))) + +(provide 'kbd) +;;; kbd.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el new file mode 100644 index 000000000000..bbc7aaa47724 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keybindings.el @@ -0,0 +1,367 @@ +;;; keybindings.el --- Centralizing my keybindings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Attempting to centralize my keybindings to simplify my configuration. +;; +;; I have some expectations about my keybindings. Here are some of those +;; defined: +;; - In insert mode: +;; - C-a: beginning-of-line +;; - C-e: end-of-line +;; - C-b: backwards-char +;; - C-f: forwards-char + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'functions) +(require 'clipboard) +(require 'screen-brightness) +(require 'pulse-audio) +(require 'scrot) +(require 'ivy-clipmenu) +(require 'general) +(require 'exwm) +(require 'vterm-mgt) +(require 'buffer) +(require 'display) +(require 'device) +(require 'fonts) +(require 'bookmark) +(require 'constants) +(require 'window-manager) + +;; Note: The following lines must be sorted this way. +(setq evil-want-integration t) +(setq evil-want-keybinding nil) +(general-evil-setup) +(require 'evil) +(require 'evil-collection) +(require 'evil-magit) +(require 'evil-commentary) +(require 'evil-surround) +(require 'key-chord) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; General Keybindings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Install KBDs like <SPC>jb to search through my monorepo. +(bookmark-install-kbds) + +;; Ensure that evil's command mode behaves with readline bindings. +(general-define-key + :keymaps 'evil-ex-completion-map + "C-a" #'move-beginning-of-line + "C-e" #'move-end-of-line + "C-k" #'kill-line + "C-u" #'evil-delete-whole-line + "C-v" #'evil-paste-after + "C-d" #'delete-char + "C-f" #'forward-char + "M-b" #'backward-word + "M-f" #'forward-word + "M-d" #'kill-word + "M-DEL" #'backward-kill-word + "C-b" #'backward-char) + +(general-mmap + :keymaps 'override + "RET" #'evil-goto-line + "H" #'evil-first-non-blank + "L" #'evil-end-of-line + "_" #'ranger + "-" #'dired-jump + "sl" #'functions-evil-window-vsplit-right + "sh" #'evil-window-vsplit + "sk" #'evil-window-split + "sj" #'functions-evil-window-split-down) + +(general-nmap + :keymaps 'override + "gu" #'browse-url-at-point + "gd" #'xref-find-definitions + ;; Wrapping `xref-find-references' in the `let' binding to prevent xref from + ;; prompting. There are other ways to handle this variable, such as setting + ;; it globally with `setq' or buffer-locally with `setq-local'. For now, I + ;; prefer setting it with `let', which should bind it in the dynamic scope + ;; for the duration of the `xref-find-references' function call. + "gx" (lambda () + (interactive) + (let ((xref-prompt-for-identifier nil)) + (call-interactively #'xref-find-references)))) + +(general-unbind 'motion "M-." "C-p" "<SPC>") +(general-unbind 'normal "s" "M-." "C-p" "C-n") +(general-unbind 'insert "C-v" "C-d" "C-a" "C-e" "C-n" "C-p" "C-k") + +(setq evil-symbol-word-search t) +(evil-mode 1) +(evil-collection-init) +(evil-commentary-mode) +(global-evil-surround-mode 1) + +;; Ensure the Evil search results get centered vertically. +;; When Emacs is run from a terminal, this forces Emacs to redraw itself, which +;; is visually disruptive. +(when window-system + (progn + (defadvice isearch-update + (before advice-for-isearch-update activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-next + (after advice-for-evil-search-next activate) + (evil-scroll-line-to-center (line-number-at-pos))) + (defadvice evil-search-previous + (after advice-for-evil-search-previous activate) + (evil-scroll-line-to-center (line-number-at-pos))))) + +(key-chord-mode 1) +(key-chord-define evil-insert-state-map "jk" 'evil-normal-state) + +;; This may be contraversial, but I never use the prefix key, and I'd prefer to +;; have to bound to the readline function that deletes the entire line. +(general-unbind "C-u") + +(defmacro keybindings-exwm (c fn) + "Bind C to FN using `exwm-input-set-key' with `kbd' applied to C." + `(exwm-input-set-key (kbd ,c) ,fn)) + +(keybindings-exwm "C-M-v" #'ivy-clipmenu-copy) +(keybindings-exwm "<XF86MonBrightnessUp>" #'screen-brightness-increase) +(keybindings-exwm "<XF86MonBrightnessDown>" #'screen-brightness-decrease) +(keybindings-exwm "<XF86AudioMute>" #'pulse-audio-toggle-mute) +(keybindings-exwm "<XF86AudioLowerVolume>" #'pulse-audio-decrease-volume) +(keybindings-exwm "<XF86AudioRaiseVolume>" #'pulse-audio-increase-volume) +(keybindings-exwm "<XF86AudioMicMute>" #'pulse-audio-toggle-microphone) +(keybindings-exwm (kbd-raw 'x11 "s") #'scrot-select) +(keybindings-exwm "<C-M-tab>" #'window-manager-switch-to-exwm-buffer) +(keybindings-exwm (kbd-raw 'workspace "k") #'fonts-increase-size) +(keybindings-exwm (kbd-raw 'workspace "j") #'fonts-decrease-size) +(keybindings-exwm (kbd-raw 'workspace "0") #'fonts-reset-size) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window sizing +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "C-M-=" #'balance-windows) +(keybindings-exwm "C-M-j" #'shrink-window) +(keybindings-exwm "C-M-k" #'enlarge-window) +(keybindings-exwm "C-M-h" #'shrink-window-horizontally) +(keybindings-exwm "C-M-l" #'enlarge-window-horizontally) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Window Management +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-h" #'windmove-left) +(keybindings-exwm "M-j" #'windmove-down) +(keybindings-exwm "M-k" #'windmove-up) +(keybindings-exwm "M-l" #'windmove-right) +(keybindings-exwm "M-\\" #'evil-window-vsplit) +(keybindings-exwm "M--" #'evil-window-split) +(keybindings-exwm "M-q" #'delete-window) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm "M-:" #'eval-expression) +(keybindings-exwm "M-SPC" #'ivy-helpers-run-external-command) +(keybindings-exwm "M-x" #'counsel-M-x) +(keybindings-exwm "<M-tab>" #'window-manager-next-workspace) +(keybindings-exwm "<M-S-iso-lefttab>" #'window-manager-prev-workspace) +(keybindings-exwm "C-M-\\" #'ivy-pass) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Workspaces +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(keybindings-exwm (kbd-raw 'workspace "l") #'window-manager-logout) + +(general-define-key + :keymaps 'override + "M-q" #'delete-window + "<s-return>" #'toggle-frame-fullscreen + "M-h" #'windmove-left + "M-l" #'windmove-right + "M-k" #'windmove-up + "M-j" #'windmove-down + "M-q" #'delete-window) + +;; Support pasting in M-:. +(general-define-key + :keymaps 'read-expression-map + "C-v" #'clipboard-yank + "C-S-v" #'clipboard-yank) + +(general-define-key + :prefix "<SPC>" + :states '(normal) + "." #'ffap + "gn" #'notmuch + "i" #'counsel-semantic-or-imenu + "I" #'ibuffer + "hk" #'helpful-callable + "hf" #'helpful-function + "hm" #'helpful-macro + "hc" #'helpful-command + "hk" #'helpful-key + "hv" #'helpful-variable + "hp" #'helpful-at-point + "hi" #'info-apropos + "s" #'flyspell-mode + "S" #'sort-lines + "=" #'align + "p" #'flycheck-previous-error + "f" #'project-find-file + "n" #'flycheck-next-error + "N" #'smerge-next + "W" #'balance-windows + "gss" #'magit-status + "gsb" (lambda () + (interactive) + (magit-status constants-briefcase)) + "E" #'refine + "es" #'functions-create-snippet + "l" #'linum-mode + "B" #'magit-blame + "w" #'save-buffer + "r" #'functions-evil-replace-under-point + "R" #'deadgrep) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Vterm +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Show or hide a vterm buffer. I'm intentionally not defining this in +;; vterm-mgt.el because it consumes `buffer-show-previous', and I'd like to +;; avoid bloating vterm-mgt.el with dependencies that others may not want. +(general-define-key (kbd-raw 'x11 "t") + (lambda () + (interactive) + (if (vterm-mgt--instance? (current-buffer)) + (switch-to-buffer (first (buffer-source-code-buffers))) + (call-interactively #'vterm-mgt-find-or-create)))) + +(general-define-key + :keymaps '(vterm-mode-map) + "C-S-n" #'vterm-mgt-instantiate + "C-S-w" #'vterm-mgt-kill + "<C-tab>" #'vterm-mgt-next + "<C-S-iso-lefttab>" #'vterm-mgt-prev + "<s-backspace>" #'vterm-mgt-rename-buffer) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Displays +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(when (device-work-laptop?) + (general-define-key + :prefix "<SPC>" + :states '(normal) + "d0" #'display-enable-laptop + "D0" #'display-disable-laptop + "d1" #'display-enable-4k-horizontal + "D1" #'display-disable-4k-horizontal)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; notmuch +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; evil-collection adds many KBDs to notmuch modes. Some of these I find +;; disruptive. +(general-define-key + :states '(normal) + :keymaps '(notmuch-show-mode-map) + "M-j" nil + "M-k" nil + "<C-S-iso-lefttab>" #'notmuch-show-previous-thread-show + "<C-tab>" #'notmuch-show-next-thread-show + "e" #'notmuch-show-archive-message-then-next-or-next-thread) + +;; TODO(wpcarro): Consider moving this to a separate module +(defun keybindings--evil-ex-define-cmd-local (cmd f) + "Define CMD to F locally to a buffer." + (unless (local-variable-p 'evil-ex-commands) + (setq-local evil-ex-commands (copy-alist evil-ex-commands))) + (evil-ex-define-cmd cmd f)) + +;; TODO(wpcarro): Support a macro that can easily define evil-ex commands for a +;; particular mode. +;; Consumption: +;; (evil-ex-for-mode 'notmuch-message-mode +;; "x" #'notmuch-mua-send-and-exit) + +(add-hook 'notmuch-message-mode-hook + (lambda () + (keybindings--evil-ex-define-cmd-local "x" #'notmuch-mua-send-and-exit))) + +;; For now, I'm mimmicking Gmail KBDs that I have memorized and enjoy +(general-define-key + :states '(normal visual) + :keymaps '(notmuch-search-mode-map) + "M" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+muted"))) + "mi" (lambda () + (interactive) + (notmuch-search-tag '("+inbox" "-action" "-review" "-waiting" "-muted"))) + "ma" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "+action" "-review" "-waiting"))) + "mr" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "+review" "-waiting"))) + "mw" (lambda () + (interactive) + (notmuch-search-tag '("-inbox" "-action" "-review" "+waiting"))) + "e" #'notmuch-search-archive-thread) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; magit +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(magit-status-mode-map + magit-log-mode-map + magit-revision-mode-map) + "l" #'evil-forward-char + "L" #'magit-log) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Info-mode +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; NOTE: I find some of the following, existing KBDs useful: +;; M-x info-apropos +;; u Info-up +;; M-n clone-buffer +(general-define-key + :states '(normal) + :keymaps '(Info-mode-map) + "SPC" nil + "g SPC" #'Info-scroll-up + "RET" #'Info-follow-nearest-node + "<C-tab>" #'Info-next + "<C-S-iso-lefttab>" #'Info-prev + "g l" #'Info-history-back + "g t" #'Info-toc) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ibuffer +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(general-define-key + :states '(normal) + :keymaps '(ibuffer-mode-map) + "M-j" nil + "K" #'ibuffer-do-delete) + +(provide 'keybindings) +;;; keybindings.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el new file mode 100644 index 000000000000..d5a3f92f122b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/keyboard.el @@ -0,0 +1,140 @@ +;;; keyboard.el --- Managing keyboard preferences with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Setting key repeat and other values. +;; +;; Be wary of suspiciously round numbers. Especially those divisible by ten! + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) +(require 'number) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support clamping functions for repeat-{rate,delay} to ensure only valid +;; values are sent to xset. +(defcustom keyboard-repeat-rate 80 + "The number of key repeat signals sent per second.") + +(defcustom keyboard-repeat-delay 170 + "The number of milliseconds before autorepeat starts.") + +(defconst keyboard-repeat-rate-copy keyboard-repeat-rate + "Copy of `keyboard-repeat-rate' to support `keyboard-reset-key-repeat'.") + +(defconst keyboard-repeat-delay-copy keyboard-repeat-delay + "Copy of `keyboard-repeat-delay' to support `keyboard-reset-key-repeat'.") + +(defcustom keyboard-install-preferences? t + "When t, install keyboard preferences.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun keyboard-message (x) + "Message X in a structured way." + (message (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)) + +(provide 'keyboard) +;;; keyboard.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el b/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el new file mode 100644 index 000000000000..91b2e3125001 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/laptop-battery.el @@ -0,0 +1,64 @@ +;;; laptop-battery.el --- Display laptop battery information -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; 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 'al) +(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 + (al-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/users/wpcarro/emacs/.emacs.d/wpc/list.el b/users/wpcarro/emacs/.emacs.d/wpc/list.el new file mode 100644 index 000000000000..cc91ac1eaf60 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/list.el @@ -0,0 +1,222 @@ +;;; list.el --- Functions for working with lists -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Since I prefer having the `list-' namespace, I wrote this module to wrap many +;; of the functions that are defined in the the global namespace in ELisp. I +;; sometimes forget the names of these functions, so it's nice for them to be +;; organized like this. +;; +;; Motivation: +;; Here are some examples of function names 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)) + +(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)) + +(defun list-xs-distinct-by? (f xs) + "Return t if all elements in XS are distinct after applying F to each." + (= (length xs) + (->> xs (-map f) set-from-list set-count))) + +;; 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/users/wpcarro/emacs/.emacs.d/wpc/macros.el b/users/wpcarro/emacs/.emacs.d/wpc/macros.el new file mode 100644 index 000000000000..07b88d8f5860 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/macros.el @@ -0,0 +1,64 @@ +;;; macros.el --- Helpful variables for making my ELisp life more enjoyable -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'symbol) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro macros-enable (mode) + "Helper for enabling `MODE'. +Useful in `add-hook' calls. Some modes, like `linum-mode' need to be called as +`(linum-mode 1)', so `(add-hook mode #'linum-mode)' won't work." + `#'(lambda nil (,mode 1))) + +(defmacro macros-disable (mode) + "Helper for disabling `MODE'. +Useful in `add-hook' calls." + `#'(lambda nil (,mode -1))) + +(defmacro macros-add-hook-before-save (mode f) + "Register a hook, `F', for a mode, `MODE' more conveniently. +Usage: (macros-add-hook-before-save 'reason-mode-hook #'refmt-before-save)" + `(add-hook ,mode + (lambda () + (add-hook 'before-save-hook ,f)))) + +;; TODO: Privatize? +(defun macros--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) + +(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/users/wpcarro/emacs/.emacs.d/wpc/math.el b/users/wpcarro/emacs/.emacs.d/wpc/math.el new file mode 100644 index 000000000000..9f6218fa4930 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/math.el @@ -0,0 +1,63 @@ +;;; math.el --- Math stuffs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Containing some useful mathematical functions. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'maybe) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst math-pi pi + "The number pi.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support all three arguments. +;; Int -> Int -> Int -> Boolean +(cl-defun math-triangle-of-power (&key base power result) + (cond + ((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/users/wpcarro/emacs/.emacs.d/wpc/maybe.el b/users/wpcarro/emacs/.emacs.d/wpc/maybe.el new file mode 100644 index 000000000000..270ee909a8e8 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/maybe.el @@ -0,0 +1,79 @@ +;;; maybe.el --- Library for dealing with nil values -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; 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 'list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar maybe--run-tests? 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)) + +(provide 'maybe) +;;; maybe.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/modeline.el b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el new file mode 100644 index 000000000000..6852bb284bc5 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/modeline.el @@ -0,0 +1,69 @@ +;;; modeline.el --- Customize my mode-line -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Because I use EXWM, I treat my Emacs mode-line like my system bar: I need to +;; quickly check the system time, and I expect it to be at the bottom-right of +;; my Emacs frame. I used doom-modeline for awhile, which is an impressive +;; package, but it conditionally colorizes on the modeline for the active +;; buffer. So if my bottom-right window is inactive, I cannot see the time. +;; +;; My friend, @tazjin, has a modeline setup that I think is more compatible with +;; EXWM, so I'm going to base my setup off of his. + +;;; Code: + +(use-package telephone-line) + +(defun modeline-bottom-right-window? () + "Determines whether the last (i.e. +bottom-right) window of the +active frame is showing the buffer in which this function is + executed." + (let* ((frame (selected-frame)) + (right-windows (window-at-side-list frame 'right)) + (bottom-windows (window-at-side-list frame 'bottom)) + (last-window (car (seq-intersection right-windows bottom-windows)))) + (eq (current-buffer) (window-buffer last-window)))) + +(defun modeline-maybe-render-time () + "Conditionally renders the `mode-line-misc-info' string. + + The idea is to not display information like the current time, + load, battery levels on all buffers." + (when (modeline-bottom-right-window?) + (telephone-line-raw mode-line-misc-info t))) + +(defun modeline-setup () + "Render my custom modeline." + (telephone-line-defsegment telephone-line-last-window-segment () + (modeline-maybe-render-time)) + ;; Display the current EXWM workspace index in the mode-line + (telephone-line-defsegment telephone-line-exwm-workspace-index () + (when (modeline-bottom-right-window?) + (format "[%s]" exwm-workspace-current-index))) + ;; Define a highlight font for ~ important ~ information in the last + ;; window. + (defface special-highlight + '((t (:foreground "white" :background "#5f627f"))) "") + (add-to-list 'telephone-line-faces + '(highlight . (special-highlight . special-highlight))) + (setq telephone-line-lhs + '((nil . (telephone-line-position-segment)) + (accent . (telephone-line-buffer-segment)))) + (setq telephone-line-rhs + '((accent . (telephone-line-major-mode-segment)) + (nil . (telephone-line-last-window-segment + telephone-line-exwm-workspace-index)))) + (setq telephone-line-primary-left-separator 'telephone-line-tan-left + telephone-line-primary-right-separator 'telephone-line-tan-right + telephone-line-secondary-left-separator 'telephone-line-tan-hollow-left + telephone-line-secondary-right-separator 'telephone-line-tan-hollow-right) + (telephone-line-mode 1)) + +(provide 'modeline) +;;; modeline.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/number.el b/users/wpcarro/emacs/.emacs.d/wpc/number.el new file mode 100644 index 000000000000..c8ed665b3098 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/number.el @@ -0,0 +1,142 @@ +;;; number.el --- Functions for working with numbers -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; 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)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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/users/wpcarro/emacs/.emacs.d/wpc/prelude.el b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el new file mode 100644 index 000000000000..824a80f8047c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/prelude.el @@ -0,0 +1,145 @@ +;;; prelude.el --- My attempt at augmenting Elisp stdlib -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.3")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Some of these ideas are scattered across other modules like `fs', +;; `string-functions', etc. I'd like to keep everything modular. I still don't +;; have an answer for which items belond in `misc'; I don't want that to become +;; a dumping grounds. Ideally this file will `require' all other modules and +;; define just a handful of functions. + +;; TODO: Consider removing all dependencies from prelude.el. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 's) +(require 'f) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Utilities +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-to-string (x) + "Convert X to a string." + (format "%s" x)) + +(defun prelude-inspect (&rest args) + "Message ARGS where ARGS are any type." + (->> args + (-map #'prelude-to-string) + (apply #'s-concat) + message)) + +(defmacro prelude-call-process-to-string (cmd &rest args) + "Return the string output of CMD called with ARGS." + `(with-temp-buffer + (call-process ,cmd nil (current-buffer) nil ,@args) + (buffer-string))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Assertions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Should I `throw' instead of `error' here? +(defmacro prelude-assert (x) + "Errors unless X is t. +These are strict assertions and purposely do not rely on truthiness." + (let ((as-string (prelude-to-string x))) + `(unless (equal t ,x) + (error (s-concat "Assertion failed: " ,as-string))))) + +(defmacro prelude-refute (x) + "Errors unless X is nil." + (let ((as-string (prelude-to-string x))) + `(unless (equal nil ,x) + (error (s-concat "Refutation failed: " ,as-string))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Adapter functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun prelude-identity (x) + "Return X unchanged." + x) + +(defun prelude-const (x) + "Return a variadic lambda that will return X." + (lambda (&rest _) x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Consider packaging these into a linum-color.el package. +;; TODO: Generate the color used here from the theme. +(defvar prelude--linum-safe? nil + "Flag indicating whether it is safe to work with function `linum-mode'.") + +(defvar prelude--linum-mru-color nil + "Stores the color most recently attempted to be applied.") + +(add-hook 'linum-mode-hook + (lambda () + (setq prelude--linum-safe? t) + (when (maybe-some? prelude--linum-mru-color) + (set-face-foreground 'linum prelude--linum-mru-color)))) + +(defun prelude-set-line-number-color (color) + "Safely set linum color to `COLOR'. + +If this is called before Emacs initializes, the color will be stored in +`prelude--linum-mru-color' and applied once initialization completes. + +Why is this safe? +If `(set-face-foreground 'linum)' is called before initialization completes, +Emacs will silently fail. Without this function, it is easy to introduce +difficult to troubleshoot bugs in your init files." + (if prelude--linum-safe? + (set-face-foreground 'linum color) + (setq prelude--linum-mru-color color))) + +(defun prelude-prompt (prompt) + "Read input from user with PROMPT." + (read-string prompt)) + +(cl-defun prelude-start-process (&key name command) + "Pass command string, COMMAND, and the function name, NAME. +This is a wrapper around `start-process' that has an API that resembles +`shell-command'." + ;; TODO: Fix the bug with tokenizing here, since it will split any whitespace + ;; character, even though it shouldn't in the case of quoted string in shell. + ;; e.g. - "xmodmap -e 'one two three'" => '("xmodmap" "-e" "'one two three'") + (prelude-refute (s-contains? "'" command)) + (let* ((tokens (s-split " " command)) + (program-name (nth 0 tokens)) + (program-args (cdr tokens))) + (apply #'start-process + `(,(format "*%s<%s>*" program-name name) + ,nil + ,program-name + ,@program-args)))) + +(defun prelude-executable-exists? (name) + "Return t if CLI tool NAME exists according to the variable `exec-path'." + (let ((file (locate-file name exec-path))) + (require 'maybe) + (if (maybe-some? file) + (f-exists? file) + nil))) + +(defmacro prelude-time (x) + "Print the time it takes to evaluate X." + `(benchmark 1 ',x)) + +(provide 'prelude) +;;; prelude.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el new file mode 100644 index 000000000000..d22cace46de9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/pulse-audio.el @@ -0,0 +1,70 @@ +;;; pulse-audio.el --- Control audio with Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Because everything in my configuration is turning into Elisp these days. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst pulse-audio--step-size 5 + "The size by which to increase or decrease the volume.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun pulse-audio--message (x) + "Output X to *Messages*." + (message (string-format "[pulse-audio.el] %s" x))) + +(defun pulse-audio-toggle-mute () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-mute" + :command "pactl set-sink-mute @DEFAULT_SINK@ toggle") + (pulse-audio--message "Mute toggled.")) + +(defun pulse-audio-toggle-microphone () + "Mute the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-toggle-microphone" + :command "pactl set-source-mute @DEFAULT_SOURCE@ toggle") + (pulse-audio--message "Microphone toggled.")) + +(defun pulse-audio-decrease-volume () + "Low the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-decrease-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ -%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume decreased.")) + +(defun pulse-audio-increase-volume () + "Raise the volume output of the default sink." + (interactive) + (prelude-start-process + :name "pulse-audio-increase-volume" + :command (string-format "pactl set-sink-volume @DEFAULT_SINK@ +%s%%" + pulse-audio--step-size)) + (pulse-audio--message "Volume increased.")) + +(provide 'pulse-audio) +;;; pulse-audio.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/random.el b/users/wpcarro/emacs/.emacs.d/wpc/random.el new file mode 100644 index 000000000000..aa3c3071bb8f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/random.el @@ -0,0 +1,81 @@ +;;; random.el --- Functions for working with randomness -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Functions for working with randomness. Some of this code is not as +;; functional as I'd like from. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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/users/wpcarro/emacs/.emacs.d/wpc/region.el b/users/wpcarro/emacs/.emacs.d/wpc/region.el new file mode 100644 index 000000000000..f915f24ec851 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/region.el @@ -0,0 +1,24 @@ +;;; region.el --- Functions for working with regions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Sometimes Emacs's function names and argument ordering is great; other times, +;; it isn't. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun region-to-string () + "Return the string in the active region." + (buffer-substring-no-properties (region-beginning) + (region-end))) + +(provide 'region) +;;; region.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/scope.el b/users/wpcarro/emacs/.emacs.d/wpc/scope.el new file mode 100644 index 000000000000..267baac9fb14 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/scope.el @@ -0,0 +1,107 @@ +;;; scope.el --- Work with a scope data structure -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'al) +(require 'stack) +(require 'struct) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct scope scopes) + +(defun scope-new () + "Return an empty scope." + (make-scope :scopes (->> (stack-new) + (stack-push (al-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 (al-new) + (lambda (scope acc) + (al-merge acc scope))))) + +(defun scope-push-new (xs) + "Push a new, empty scope onto XS." + (struct-update scope + scopes + (>-> (stack-push (al-new))) + xs)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun scope-get (k xs) + "Return K from XS if it's in scope." + (->> xs + scope-flatten + (al-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 (>-> (al-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 + (al-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/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el new file mode 100644 index 000000000000..30973607bb03 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/screen-brightness.el @@ -0,0 +1,49 @@ +;;; screen-brightness.el --- Control laptop screen brightness -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; 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/users/wpcarro/emacs/.emacs.d/wpc/scrot.el b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el new file mode 100644 index 000000000000..e7231b44fd95 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/scrot.el @@ -0,0 +1,58 @@ +;;; scrot.el --- Screenshot functions -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; scrot is a Linux utility for taking screenshots. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'string) +(require 'ts) +(require 'clipboard) +(require 'kbd) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst scrot-screenshot-directory "~/Downloads" + "The default directory for screenshot outputs.") + +(defconst scrot-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!"))) + +(defun scrot-select () + "Click-and-drag to screenshot a region. +The output path is copied to the user's clipboard." + (interactive) + (let ((screenshot-path (f-join scrot-screenshot-directory + (ts-format scrot-output-format (ts-now))))) + (make-process + :name "scrot-select" + :command `(,scrot-path-to-executable "--select" ,screenshot-path) + :sentinel (lambda (proc _err) + (when (= 0 (process-exit-status proc)) + (scrot--copy-image screenshot-path)))))) + +(provide 'scrot) +;;; scrot.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/sequence.el b/users/wpcarro/emacs/.emacs.d/wpc/sequence.el new file mode 100644 index 000000000000..9da3ba4575f9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/sequence.el @@ -0,0 +1,109 @@ +;;; sequence.el --- Working with the "sequence" types -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; 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/users/wpcarro/emacs/.emacs.d/wpc/series.el b/users/wpcarro/emacs/.emacs.d/wpc/series.el new file mode 100644 index 000000000000..00be03a976f0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/series.el @@ -0,0 +1,93 @@ +;;; series.el --- Hosting common series of numbers -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; 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/users/wpcarro/emacs/.emacs.d/wpc/set.el b/users/wpcarro/emacs/.emacs.d/wpc/set.el new file mode 100644 index 000000000000..51c0c434f54e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/set.el @@ -0,0 +1,175 @@ +;;; set.el --- Working with mathematical sets -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; 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 A and B." + (set-reduce (set-new) + (lambda (x acc) + (if (set-contains? x b) + (set-add x acc) + acc)) + a)) + +(defun set-count (xs) + "Return the number of elements in XS." + (->> xs + set-xs + ht-size)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun set-empty? (xs) + "Return t if XS has no elements in it." + (= 0 (set-count xs))) + +(defun set-contains? (x xs) + "Return t if set XS has X." + (ht-contains? (set-xs xs) x)) + +;; TODO: Prefer using `ht.el' functions for this. +(defun set-equal? (a b) + "Return t if A and B share the name members." + (ht-equal? (set-xs a) + (set-xs b))) + +(defun set-distinct? (a b) + "Return t if A and B have no shared members." + (set-empty? (set-intersection a b))) + +(defun set-superset? (a b) + "Return t if A has all of the members of B." + (->> b + set-to-list + (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/users/wpcarro/emacs/.emacs.d/wpc/ssh.el b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el new file mode 100644 index 000000000000..2e5839c04698 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/ssh.el @@ -0,0 +1,65 @@ +;;; ssh.el --- When working remotely -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Configuration to make remote work easier. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'tramp) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Is "ssh" preferable to "scp"? +(setq tramp-default-method "ssh") + +;; Taken from: https://superuser.com/questions/179313/tramp-waiting-for-prompts-from-remote-shell +(setq tramp-shell-prompt-pattern "^[^$>\n]*[#$%>] *\\(\[[0-9;]*[a-zA-Z] *\\)*") + +;; Sets the value of the TERM variable to "dumb" when logging into the remote +;; host. This allows me to check for the value of "dumb" in my shell's init file +;; and control the startup accordingly. You can see in the (shamefully large) +;; commit, 0b4ef0e, that I added a check like this to my ~/.zshrc. I've since +;; switched from z-shell to fish. I don't currently have this check in +;; config.fish, but I may need to add it one day soon. +(setq tramp-terminal-type "dumb") + +;; Maximizes the tramp debugging noisiness while I'm still learning about tramp. +(setq tramp-verbose 10) + +;; As confusing as this may seem, this forces Tramp to use *my* .ssh/config +;; options, which enable ControlMaster. In other words, disabling this actually +;; enables ControlMaster. +(setq tramp-use-ssh-controlmaster-options nil) + +(defcustom ssh-hosts '("desktop" "socrates") + "List of hosts to which I commonly connect. +Note: It could be interesting to read these values from ~/.ssh-config, but + that's more than I need at the moment.") + +(defun ssh-sudo-buffer () + "Open the current buffer with sudo rights." + (interactive) + (with-current-buffer (current-buffer) + (if (s-starts-with? "/ssh:" buffer-file-name) + (message "[ssh.el] calling ssh-sudo-buffer for remote files isn't currently supported") + (find-file (format "/sudo::%s" buffer-file-name))))) + +(defun ssh-cd-home () + "Prompt for an SSH host and open a dired buffer for wpcarro on that machine." + (interactive) + (let ((machine (completing-read "Machine: " ssh-hosts))) + (find-file (format "/ssh:wpcarro@%s:~" machine)))) + +(provide 'ssh) +;;; ssh.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/stack.el b/users/wpcarro/emacs/.emacs.d/wpc/stack.el new file mode 100644 index 000000000000..c90f41e7602d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/stack.el @@ -0,0 +1,102 @@ +;;; stack.el --- Working with stacks in Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; 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: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'list) +(require '>) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Create +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct stack xs) + +(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/users/wpcarro/emacs/.emacs.d/wpc/string.el b/users/wpcarro/emacs/.emacs.d/wpc/string.el new file mode 100644 index 000000000000..9a43f1664501 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/string.el @@ -0,0 +1,111 @@ +;;; string.el --- Library for working with strings -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with string. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 's) +(require 'dash) +;; TODO: Resolve the circular dependency that this introduces. +;; (require 'prelude) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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)) + +(provide 'string) +;;; string.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/struct.el b/users/wpcarro/emacs/.emacs.d/wpc/struct.el new file mode 100644 index 000000000000..35957e834449 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/struct.el @@ -0,0 +1,86 @@ +;;; struct.el --- Helpers for working with structs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24.3")) + +;;; Commentary: +;; Provides new macros for working with structs. Also provides adapter +;; interfaces to existing struct macros, that should have more intuitive +;; interfaces. +;; +;; Sometimes `setf' just isn't enough. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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 struct--test-dummy (make-dummy :name "Roofus" :age 19)) + (struct-set! dummy name "Doofus" struct--test-dummy) + (prelude-assert (string= "Doofus" (dummy-name struct--test-dummy))) + (let ((result (struct-set dummy name "Shoofus" struct--test-dummy))) + ;; Test the immutability of `struct-set' + (prelude-assert (string= "Doofus" (dummy-name struct--test-dummy))) + (prelude-assert (string= "Shoofus" (dummy-name result))))) + +(provide 'struct) +;;; struct.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/symbol.el b/users/wpcarro/emacs/.emacs.d/wpc/symbol.el new file mode 100644 index 000000000000..39450caff59e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/symbol.el @@ -0,0 +1,49 @@ +;;; symbol.el --- Library for working with symbols -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Library for working with symbols. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'string) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; 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)) + +(defun symbol-instance? (x) + "Return t if X is a symbol." + (symbolp x)) + +(provide 'symbol) +;;; symbol.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/timestring.el b/users/wpcarro/emacs/.emacs.d/wpc/timestring.el new file mode 100644 index 000000000000..a9bf64e9a459 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/timestring.el @@ -0,0 +1,78 @@ +;;; timestring.el --- Quickly access timestamps in different formats -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: + +;; I was making some API calls where a URL needed a `since` parameter that of an +;; RFC 3339 encoded string. +;; +;; Because I didn't know what a RFC 3339 encoded +;; string was at the time, and because I didn't know what its format was +;; according to strftime, and because I'm most likely to forget both of these +;; things by the next time that I need something similar, I decided to write +;; this package so that I can accumulate a list of common time encodings. +;; +;; Thank you, Emacs. +;; +;; p.s. - I may turn this into a proper module and publish it. But not today. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'ts) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup timestring nil + "Customize group for timestring configuration.") + +(defcustom timestring-supported-encodings + '(("RFC 3339" . "%Y-%m-%dT%H:%M:%SZ") + ;; Does anyone recognize this format? + ("IDK" . "%Y-%m-%d %H:%M:%S %z")) + "Mapping of encoding names to their format strings." + :group 'timestring) + +(defcustom timestring-supported-times + '(("yesterday" . timestring--yesterday) + ("now" . ts-now) + ("tomorrow" . timestring--tomorrow)) + "Mapping of a labels to the functions that create those time objects." + :group 'timestring) + +(defun timestring--yesterday () + "Return a time object for yesterday." + (ts-adjust 'day -1 (ts-now))) + +(defun timestring--tomorrow () + "Return a time object for yesterday." + (ts-adjust 'day +1 (ts-now))) + +(defun timestring--completing-read (label xs) + "Call `completing-read' with LABEL over the collection XS." + (alist-get (completing-read label xs) xs nil nil #'equal)) + +(defun timestring-copy-encoded-time () + "Select a common time and an encoding. + +The selected time will be encoded using the selected encoding and copied onto +your clipboard." + (interactive) + (let ((time (funcall (timestring--completing-read + "Time: " timestring-supported-times))) + (fmt (timestring--completing-read + "Encoding: " timestring-supported-encodings))) + (kill-new (ts-format fmt time)) + (message "Copied!"))) + +(provide 'timestring) +;;; timestring.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/tree.el b/users/wpcarro/emacs/.emacs.d/wpc/tree.el new file mode 100644 index 000000000000..ae5fba7950c9 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/tree.el @@ -0,0 +1,200 @@ +;;; tree.el --- Working with Trees -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; 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) +(require 'cl-lib) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; 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. + +This uses 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/users/wpcarro/emacs/.emacs.d/wpc/tuple.el b/users/wpcarro/emacs/.emacs.d/wpc/tuple.el new file mode 100644 index 000000000000..dd8e88f5c34d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/tuple.el @@ -0,0 +1,94 @@ +;;; tuple.el --- Tuple API for Elisp -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Work with cons cells with two elements with a familiar API for those who have +;; worked with tuples before. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(cl-defstruct tuple first second) + +;; Create +(defun tuple-new () + "Return an empty tuple." + (make-tuple :first nil + :second nil)) + +(defun tuple-from (a b) + "Return a new tuple from A and B." + (make-tuple :first a + :second b)) + +(defun tuple-from-dotted (dp) + "Convert dotted pair, DP, into a tuple." + (tuple-from (car dp) (cdr dp))) + +;; Read +(defun tuple-first (pair) + "Return the first element of PAIR." + (tuple-first pair)) + +(defun tuple-second (pair) + "Return the second element of PAIR." + (tuple-second pair)) + +;; Update +(defun tuple-map-each (f g pair) + "Apply F to first, G to second in PAIR." + (->> pair + (tuple-map-first f) + (tuple-map-second g))) + +(defun tuple-map (f pair) + "Apply F to PAIR." + (let ((pair-copy (copy-tuple pair))) + (funcall f pair-copy))) + +(defun tuple-map-first (f pair) + "Apply function F to the first element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-first pair-copy) (funcall f (tuple-first pair-copy))) + pair-copy)) + +(defun tuple-map-second (f pair) + "Apply function F to the second element of PAIR." + (let ((pair-copy (copy-tuple pair))) + (setf (tuple-second pair-copy) (funcall f (tuple-second pair-copy))) + pair-copy)) + +(defun tuple-set-first (a pair) + "Return a new tuple with the first element set as A in PAIR." + (tuple-map-first (lambda (_) a) pair)) + +(defun tuple-set-second (b pair) + "Return a new tuple with the second element set as B in PAIR." + (tuple-map-second (lambda (_) b) pair)) + +;; Delete +(defun tuple-delete-first (pair) + "Return PAIR with the first element set to nil." + (tuple-set-first nil pair)) + +(defun tuple-delete-second (pair) + "Return PAIR with the second element set to nil." + (tuple-set-second nil pair)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Predicates +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun tuple-instance? (x) + "Return t if X is a tuple." + (tuple-p x)) + +(provide 'tuple) +;;; tuple.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/vector.el b/users/wpcarro/emacs/.emacs.d/wpc/vector.el new file mode 100644 index 000000000000..033f8de8347d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/vector.el @@ -0,0 +1,85 @@ +;;; vector.el --- Working with Elisp's Vector data type -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; It might be best to think of Elisp vectors as tuples in languages like +;; Haskell or Erlang. +;; +;; Not surprisingly, this API is modelled after Elixir's Tuple API. +;; +;; Some Elisp trivia: +;; - "Array": Usually means vector or string. +;; - "Sequence": Usually means list or "array" (see above). +;; +;; It might be a good idea to think of Array and Sequence as typeclasses in +;; Elisp. This is perhaps more similar to Elixir's notion of the Enum protocol. +;; +;; Intentionally not supporting a to-list function, because tuples can contain +;; heterogenous types whereas lists should contain homogenous types. + +;;; Code: + +;; 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/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el b/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el new file mode 100644 index 000000000000..ce60bab149c0 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/vterm-mgt.el @@ -0,0 +1,129 @@ +;;; vterm-mgt.el --- Help me manage my vterm instances -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Supporting functions to instantiate vterm buffers, kill existing vterm +;; buffers, rename vterm buffers, cycle forwards and backwards through vterm +;; buffers. +;; +;; Many of the functions defined herein are intended to be bound to +;; `vterm-mode-map'. Some assertions are made to guard against calling +;; functions that are intended to be called from outside of a vterm buffer. +;; These assertions shouldn't error when the functions are bound to +;; `vterm-mode-map'. If for some reason, you'd like to bind these functions to +;; a separate keymap, caveat emptor. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'dash) +(require 'cycle) +(require 'vterm) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defgroup vterm-mgt nil + "Customization options for `vterm-mgt'.") + +(defcustom vterm-mgt-scroll-on-focus nil + "When t, call `end-of-buffer' after focusing a vterm instance." + :type '(boolean) + :group 'vterm-mgt) + +(defconst vterm-mgt--instances (cycle-new) + "A cycle tracking all of my vterm instances.") + +(defun vterm-mgt--instance? (b) + "Return t if the buffer B is a vterm instance." + (equal 'vterm-mode (buffer-local-value 'major-mode b))) + +(defmacro vterm-mgt--assert-vterm-buffer () + "Error when the `current-buffer' is not a vterm buffer." + '(prelude-assert (vterm-mgt--instance? (current-buffer)))) + +(defun vterm-mgt-next () + "Replace the current buffer with the next item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (cycle-focus-item (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-next vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-prev () + "Replace the current buffer with the previous item in `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (cycle-focus-item (current-buffer) vterm-mgt--instances) + (switch-to-buffer (cycle-prev vterm-mgt--instances)) + (when vterm-mgt-scroll-on-focus (end-of-buffer))) + +(defun vterm-mgt-instantiate () + "Create a new vterm instance. + +Prefer calling this function instead of `vterm'. This function ensures that the + newly created instance is added to `vterm-mgt--instances'. + +If however you must call `vterm', if you'd like to cycle through vterm + instances, make sure you call `vterm-mgt-populate-cycle' to allow vterm-mgt to + collect any untracked vterm instances." + (interactive) + (let ((buffer (vterm))) + (cycle-append buffer vterm-mgt--instances) + (cycle-focus-item buffer vterm-mgt--instances))) + +(defun vterm-mgt-kill () + "Kill the current buffer and remove it from `vterm-mgt--instances'. +This function should be called from a buffer running vterm." + (interactive) + (vterm-mgt--assert-vterm-buffer) + (let ((buffer (current-buffer))) + (cycle-remove buffer vterm-mgt--instances) + (kill-buffer buffer))) + +(defun vterm-mgt-find-or-create () + "Call `switch-to-buffer' on a focused vterm instance if there is one. + +When `cycle-focused?' returns nil, focus the first item in the cycle. When +there are no items in the cycle, call `vterm-mgt-instantiate' to create a vterm +instance." + (interactive) + (if (cycle-empty? vterm-mgt--instances) + (vterm-mgt-instantiate) + (if (cycle-focused? vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)) + (progn + (cycle-jump 0 vterm-mgt--instances) + (switch-to-buffer (cycle-current vterm-mgt--instances)))))) + +(defun vterm-mgt-rename-buffer (name) + "Rename the current buffer ensuring that its NAME is wrapped in *vterm*<...>. +This function should be called from a buffer running vterm." + (interactive "SRename vterm buffer: ") + (vterm-mgt--assert-vterm-buffer) + (rename-buffer (format "vterm<%s>" name))) + +(defun vterm-mgt-repopulate-cycle () + "Fill `vterm-mgt--instances' with the existing vterm buffers. + +If for whatever reason, the state of `vterm-mgt--instances' is corrupted and + misaligns with the state of vterm buffers in Emacs, use this function to + restore the state." + (interactive) + (setq vterm-mgt--instances + (->> (buffer-list) + (-filter #'vterm-mgt--instance?) + cycle-from-list))) + +(provide 'vterm-mgt) +;;; vterm-mgt.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el new file mode 100644 index 000000000000..6030461da61b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/window-manager.el @@ -0,0 +1,354 @@ +;;; window-manager.el --- Functions augmenting my usage of EXWM -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; I switched to EXWM from i3, and I haven't looked back. One day I may write a +;; poem declaring my love for Emacs and EXWM. For now, I haven't the time. + +;; 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 'al) +(require 'prelude) +(require 'string) +(require 'cycle) +(require 'set) +(require 'kbd) +(require 'ivy-helpers) +(require 'display) +(require 'vterm-mgt) +(require 'dash) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Associate `window-purpose' window-layouts with each of these named +;; workspaces. + +;; TODO: Associate KBDs for each of these named-layouts. + +;; TODO: Decide between window-manager, exwm, or some other namespace. + +;; TODO: Support (cycle-from-list '(current previous)) to toggle back and forth +;; between most recent workspace. + +;; TODO: Support ad hoc cycle for loading a few workspaces that can be cycled +;; between. (cycle-from-list '("Project" "Workspace")) + +;; TODO: Consider supporting a workspace for Racket, Clojure, Common Lisp, +;; Haskell, Elixir, and a few other languages. These could behave very similarly +;; to repl.it, which I've wanted to have locally for awhile now. + +;; TODO: Support MRU cache of workspaces for easily switching back-and-forth +;; between workspaces. + +(cl-defstruct window-manager--named-workspace label kbd display) + +(defconst window-manager--install-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 window-manager--named-workspaces + (list (make-window-manager--named-workspace + :label "Web Browsing" + :kbd "c" + :display display-4k-horizontal) + (make-window-manager--named-workspace + :label "Coding" + :kbd "d" + :display display-4k-horizontal) + (make-window-manager--named-workspace + :label "Vertical" + :kbd "h" + :display display-4k-vertical) + (make-window-manager--named-workspace + :label "Laptop" + :kbd "p" + :display display-laptop)) + "List of `window-manager--named-workspace' structs.") + +;; Assert that no two workspaces share KBDs. +(prelude-assert (= (list-length window-manager--named-workspaces) + (->> window-manager--named-workspaces + (list-map #'window-manager--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) + (setq exwm-randr-workspace-monitor-plist + (->> window-manager--named-workspaces + (-map-indexed (lambda (i x) + (list i (window-manager--named-workspace-display x)))) + -flatten)) + (setq exwm-workspace-number (list-length window-manager--named-workspaces)) + (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)) + +;; Here is the code required to allow EXWM to cycle workspaces. +(defconst window-manager--workspaces + (->> window-manager--named-workspaces + cycle-from-list) + "Cycle of the my EXWM workspaces.") + +(prelude-assert + (= exwm-workspace-number + (list-length window-manager--named-workspaces))) + +(defun window-manager-next-workspace () + "Cycle forwards to the next workspace." + (interactive) + (window-manager--change-workspace (cycle-next window-manager--workspaces))) + +(defun window-manager-prev-workspace () + "Cycle backwards to the previous workspace." + (interactive) + (window-manager--change-workspace (cycle-prev window-manager--workspaces))) + +;; TODO: Create friendlier API for working with EXWM. + +;; Here is the code required to toggle EXWM's modes. +(defun window-manager--line-mode () + "Switch exwm to line-mode." + (call-interactively #'exwm-input-grab-keyboard) + (window-manager--alert "Switched to line-mode")) + +(defun window-manager--char-mode () + "Switch exwm to char-mode." + (call-interactively #'exwm-input-release-keyboard) + (window-manager--alert "Switched to char-mode")) + +(defconst window-manager--modes + (cycle-from-list (list #'window-manager--char-mode + #'window-manager--line-mode)) + "Functions to switch exwm modes.") + +(defun window-manager-toggle-mode () + "Switch between line- and char- mode." + (interactive) + (with-current-buffer (window-buffer) + (when (eq major-mode 'exwm-mode) + (funcall (cycle-next window-manager--modes))))) + +;; Ensure exwm apps open in char-mode. +(add-hook 'exwm-manage-finish-hook #'window-manager--char-mode) + +;; Interface to the Linux password manager +;; TODO: Consider writing a better client for this. +(use-package ivy-pass) + +;; TODO: How do I handle this dependency? +(defconst window-manager--preferred-browser "google-chrome" + "My preferred web browser.") + +;; 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" . + (lambda () + (shell-command window-manager--xsecurelock))) + ("Logout" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "systemctl stop lightdm")))) + ("Suspend" . + (lambda () + (shell-command "systemctl suspend"))) + ("Hibernate" . + (lambda () + (shell-command "systemctl hibernate"))) + ("Reboot" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "reboot")))) + ("Shutdown" . + (lambda () + (let ((default-directory "/sudo::")) + (shell-command "shutdown now"))))))) + (funcall + (lambda () + (funcall (al-get (ivy-read "System: " (al-keys name->cmd)) + name->cmd)))))) + +(defun window-manager--label->index (label workspaces) + "Return the index of the workspace in WORKSPACES named LABEL." + (let ((index (-elem-index label (-map #'window-manager--named-workspace-label + workspaces)))) + (if index index (error (format "No workspace found for label: %s" label))))) + +(defun window-manager--register-kbd (workspace) + "Registers a keybinding for WORKSPACE struct. +Currently using super- as the prefix for switching workspaces." + (let ((handler (lambda () + (interactive) + (window-manager--switch + (window-manager--named-workspace-label workspace)))) + (key (window-manager--named-workspace-kbd workspace))) + (exwm-input-set-key + (kbd-for 'workspace key) + handler))) + +(defun window-manager--change-workspace (workspace) + "Switch EXWM workspaces to the WORKSPACE struct." + (exwm-workspace-switch + (window-manager--label->index + (window-manager--named-workspace-label workspace) + window-manager--named-workspaces)) + (window-manager--alert + (string-format "Switched to: %s" + (window-manager--named-workspace-label workspace)))) + +(defun window-manager--switch (label) + "Switch to a named workspaces using LABEL." + (cycle-focus (lambda (x) + (equal label + (window-manager--named-workspace-label x))) + window-manager--workspaces) + (window-manager--change-workspace (cycle-current window-manager--workspaces))) + +(exwm-input-set-key (kbd "C-S-f") #'window-manager-toggle-previous) + +(defun window-manager-toggle-previous () + "Focus the previously active EXWM workspace." + (interactive) + (window-manager--change-workspace + (cycle-focus-previous! window-manager--workspaces))) + +(defun window-manager--exwm-buffer? (x) + "Return t if buffer X is an EXWM buffer." + (equal 'exwm-mode (buffer-local-value 'major-mode x))) + +(defun window-manager--application-name (buffer) + "Return the name of the application running in the EXWM BUFFER. +This function asssumes that BUFFER passes the `window-manager--exwm-buffer?' +predicate." + (with-current-buffer buffer exwm-class-name)) + +;; TODO: Support disambiguating between two or more instances of the same +;; application. For instance if two `exwm-class-name' values are +;; "Google-chrome", find a encode this information in the `buffer-alist'. +(defun window-manager-switch-to-exwm-buffer () + "Use `completing-read' to focus an EXWM buffer." + (interactive) + (let* ((buffer-alist (->> (buffer-list) + (-filter #'window-manager--exwm-buffer?) + (-map + (lambda (buffer) + (cons (window-manager--application-name buffer) + buffer))))) + (label (completing-read "Switch to EXWM buffer: " buffer-alist))) + (exwm-workspace-switch-to-buffer + (al-get label buffer-alist)))) + +(when window-manager--install-kbds? + (progn + (->> window-manager--named-workspaces + (list-map #'window-manager--register-kbd)) + (window-manager--alert "Registered workspace KBDs!"))) + +(defun window-manager-current-workspace () + "Output the label of the currently active workspace." + (->> window-manager--workspaces + cycle-current + window-manager--named-workspace-label)) + +(defun window-manager-swap-workspaces () + "Prompt the user to switch the current workspace with another." + (interactive) + (let* ((selection (->> window-manager--named-workspaces + (-map #'window-manager--named-workspace-label) + (-reject + (lambda (x) + (s-equals? x (window-manager-current-workspace)))) + (completing-read + (format "Swap current workspace (i.e. \"%s\") with: " + (window-manager-current-workspace))))) + (i (-find-index (lambda (x) + (s-equals? selection (window-manager--named-workspace-label x))) + window-manager--named-workspaces))) + (exwm-workspace-swap exwm-workspace--current (elt exwm-workspace--list i)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Startup Applications in `window-manager--named-workspaces' +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(add-hook 'exwm-init-hook + (lambda () + (display-arrange-primary) + (window-manager--switch "Coding"))) + +(provide 'window-manager) +;;; window-manager.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/window.el b/users/wpcarro/emacs/.emacs.d/wpc/window.el new file mode 100644 index 000000000000..ee3c109b79b4 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/window.el @@ -0,0 +1,41 @@ +;;; window.el --- Working with windows -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; 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/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el new file mode 100644 index 000000000000..025ef9aab988 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-clojure.el @@ -0,0 +1,72 @@ +;;; wpc-clojure.el --- My Clojure preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosting my Clojure tooling preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package clojure-mode + :config + ;; from Ryan Schmukler: + (setq cljr-magic-require-namespaces + '(("io" . "clojure.java.io") + ("sh" . "clojure.java.shell") + ("jdbc" . "clojure.java.jdbc") + ("set" . "clojure.set") + ("time" . "java-time") + ("str" . "cuerdas.core") + ("path" . "pathetic.core") + ("walk" . "clojure.walk") + ("zip" . "clojure.zip") + ("async" . "clojure.core.async") + ("component" . "com.stuartsierra.component") + ("http" . "clj-http.client") + ("url" . "cemerick.url") + ("sql" . "honeysql.core") + ("csv" . "clojure.data.csv") + ("json" . "cheshire.core") + ("s" . "clojure.spec.alpha") + ("fs" . "me.raynes.fs") + ("ig" . "integrant.core") + ("cp" . "com.climate.claypoole") + ("re-frame" . "re-frame.core") + ("rf" . "re-frame.core") + ("re" . "reagent.core") + ("reagent" . "reagent.core") + ("u.core" . "utopia.core") + ("gen" . "clojure.spec.gen.alpha")))) + +(use-package cider + :config + (general-define-key + :keymaps 'cider-repl-mode-map + "C-l" #'cider-repl-clear-buffer + "C-u" #'kill-whole-line + "<up>" #'cider-repl-previous-input + "<down>" #'cider-repl-next-input) + (general-define-key + :keymaps 'clojure-mode-map + :states '(normal) + :prefix "<SPC>" + "x" #'cider-eval-defun-at-point + "X" #'cider-eval-buffer + "d" #'cider-symbol-at-point) + (setq cider-prompt-for-symbol nil)) + +(provide 'wpc-clojure) +;;; wpc-clojure.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el new file mode 100644 index 000000000000..03535621cf2a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-company.el @@ -0,0 +1,42 @@ +;;; wpc-company.el --- Autocompletion package, company, preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; Hosts my company mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; autocompletion client +(use-package company + :config + (general-define-key + :keymaps 'company-active-map + "C-j" #'company-select-next + "C-n" #'company-select-next + "C-k" #'company-select-previous + "C-p" #'company-select-previous + "C-d" #'company-show-doc-buffer) + (setq company-tooltip-align-annotations t) + (setq company-idle-delay 0) + (setq company-show-numbers t) + (setq company-minimum-prefix-length 2) + (setq company-dabbrev-downcase nil + company-dabbrev-ignore-case t) + (global-company-mode)) + +(provide 'wpc-company) +;;; wpc-company.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el new file mode 100644 index 000000000000..bd2805c7369c --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-dired.el @@ -0,0 +1,53 @@ +;;; wpc-dired.el --- My dired preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "25.1")) + +;;; Commentary: +;; File management in Emacs, if learned and configured properly, should be +;; capable to reduce my dependency on the terminal. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(progn + (require 'dired) + (setq dired-recursive-copies 'always + dired-recursive-deletes 'top + dired-dwim-target t) + (setq dired-listing-switches "-la --group-directories-first") + (general-define-key + :keymaps 'dired-mode-map + :states '(normal) + ;; Overriding some KBDs defined in the evil-collection module. + "o" #'dired-find-file-other-window + "<SPC>" nil ;; This unblocks some of my leader-prefixed KBDs. + "s" nil ;; This unblocks my window-splitting KBDs. + "c" #'find-file + "f" #'project-find-file + "-" (lambda () (interactive) (find-alternate-file ".."))) + (general-add-hook 'dired-mode-hook + (list (macros-enable dired-hide-details-mode) + #'auto-revert-mode))) + +(progn + (require 'locate) + (general-define-key + :keymaps 'locate-mode-map + :states 'normal + "o" #'dired-find-file-other-window)) + +(provide 'wpc-dired) +;;; wpc-dired.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el new file mode 100644 index 000000000000..0b5e3917139e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-elixir.el @@ -0,0 +1,28 @@ +;;; wpc-elixir.el --- Elixir / Erland configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; My preferences for working with Elixir / Erlang projects + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package elixir-mode + :config + (macros-add-hook-before-save 'elixir-mode-hook #'elixir-format)) + +(provide 'wpc-elixir) +;;; wpc-elixir.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el new file mode 100644 index 000000000000..1f32ce51361e --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-flycheck.el @@ -0,0 +1,18 @@ +;;; wpc-flycheck.el --- My flycheck configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Flycheck preferences + +;;; Code: + +(use-package flycheck + :config + (global-flycheck-mode)) + +(provide 'wpc-flycheck) +;;; wpc-flycheck.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el new file mode 100644 index 000000000000..d73249334f89 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-golang.el @@ -0,0 +1,48 @@ +;;; wpc-golang.el --- Tooling preferences for Go -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Tooling support for golang development. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'prelude) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: Support jumping to go source code for fmt.Println, etc. + +;; I'm unsure if this belongs in wpc-golang.el because it's a generic setting, +;; but because go is the first languages I've encountered that enforces tab +;; usage (I think) I'm configuring it. +(setq-default tab-width 4) + +(use-package go-mode + :config + (setq gofmt-command "goimports") + ;; TODO: Consider configuring `xref-find-definitions' to use `godef-jump' + ;; instead of shadowing the KBD here. + (general-define-key + :states '(normal) + :keymaps '(go-mode-map) + "M-." #'godef-jump) + ;; Support calling M-x `compile'. + (add-hook 'go-mode-hook (lambda () + (set (make-local-variable 'compile-command) + "go build -v"))) + (macros-add-hook-before-save 'go-mode-hook #'gofmt-before-save)) + +(provide 'wpc-golang) +;;; wpc-golang.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el new file mode 100644 index 000000000000..f9ed8552e0be --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-haskell.el @@ -0,0 +1,62 @@ +;;; wpc-haskell.el --- My Haskell preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts my Haskell development preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; font-locking, glyph support, etc +(use-package haskell-mode + :config + (macros-add-hook-before-save 'haskell-mode #'haskell-align-imports)) + +;; 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 wpc-haskell-module->test () + "Jump from a module to a test." + (let ((filename (->> buffer-file-name + (s-replace "/src/" "/test/") + (s-replace ".hs" "Test.hs") + find-file))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test->module () + "Jump from a test to a module." + (let ((filename (->> buffer-file-name + (s-replace "/test/" "/src/") + (s-replace "Test.hs" ".hs")))) + (make-directory (f-dirname filename) t) + (find-file filename))) + +(defun wpc-haskell-test<->module () + "Toggle between test and module in Haskell." + (interactive) + (if (s-contains? "/src/" buffer-file-name) + (wpc-haskell-module->test) + (wpc-haskell-test->module))) + +(provide 'wpc-haskell) +;;; wpc-haskell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el new file mode 100644 index 000000000000..da84df66d409 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-javascript.el @@ -0,0 +1,99 @@ +;;; wpc-javascript.el --- My Javascript preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This module hosts my Javascript tooling preferences. This also includes +;; tooling for TypeScript and other frontend tooling. Perhaps this module will +;; change names to more accurately reflect that. +;; +;; Depends +;; - yarn global add prettier + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Constants +(defconst wpc-javascript--js-hooks + '(js-mode-hook + web-mode-hook + typescript-mode-hook + js2-mode-hook + rjsx-mode-hook) + "All of the commonly used hooks for Javascript buffers.") + +(defconst wpc-javascript--frontend-hooks + (-insert-at 0 'css-mode-hook wpc-javascript--js-hooks) + "All of the commonly user hooks for frontend development.") + +;; frontend indentation settings +(setq typescript-indent-level 2 + js-indent-level 2 + css-indent-offset 2) + +;; Flow for Javascript +(use-package add-node-modules-path + :config + (general-add-hook wpc-javascript--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 + :config + (general-unbind rjsx-mode-map "<" ">" "C-d") + (general-nmap + :keymaps 'rjsx-mode-map + "K" #'flow-minor-type-at-pos) + (setq js2-mode-show-parse-errors nil + js2-mode-show-strict-warnings nil)) + +(progn + (defun wpc-javascript-tide-setup () + (interactive) + (tide-setup) + (flycheck-mode 1) + (setq flycheck-check-syntax-automatically '(save mode-enabled)) + (eldoc-mode 1) + (tide-hl-identifier-mode 1) + (company-mode 1)) + (use-package tide + :config + (add-hook 'typescript-mode-hook #'wpc-javascript-tide-setup)) + (require 'web-mode) + (add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode)) + (add-hook 'web-mode-hook + (lambda () + (when (string-equal "tsx" (f-ext buffer-file-name)) + (wpc-javascript-tide-setup)))) + (flycheck-add-mode 'typescript-tslint 'web-mode)) + +;; JS autoformatting +(use-package prettier-js + :config + (general-add-hook wpc-javascript--frontend-hooks #'prettier-js-mode)) + +;; Support Elm +(use-package elm-mode + :config + (add-hook 'elm-mode-hook #'elm-format-on-save-mode)) + +(provide 'wpc-javascript) +;;; wpc-javascript.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el new file mode 100644 index 000000000000..f4f8c6931582 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-lisp.el @@ -0,0 +1,116 @@ +;;; wpc-lisp.el --- Generic LISP preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; parent (up) +;; child (down) +;; prev-sibling (left) +;; next-sibling (right) + +;;; Code: + +;; TODO: Consider having a separate module for each LISP dialect. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defconst wpc-lisp--hooks + '(lisp-mode-hook + emacs-lisp-mode-hook + clojure-mode-hook + clojurescript-mode-hook + racket-mode-hook) + "List of LISP modes.") + +(use-package sly + :config + (setq inferior-lisp-program "sbcl") + (general-define-key + :keymaps 'sly-mode-map + :states '(normal) + :prefix "<SPC>" + "x" #'sly-eval-defun + "X" #'sly-eval-buffer + "d" #'sly-describe-symbol)) + +(use-package rainbow-delimiters + :config + (general-add-hook wpc-lisp--hooks #'rainbow-delimiters-mode)) + +(use-package racket-mode + :config + (general-define-key + :keymaps 'racket-mode-map + :states 'normal + :prefix "<SPC>" + "x" #'racket-send-definition + "X" #'racket-run + "d" #'racket-describe) + (setq racket-program "~/.nix-profile/bin/racket")) + +(use-package lispyville + :init + (defconst wpc-lisp--lispyville-key-themes + '(c-w + operators + text-objects + prettify + commentary + slurp/barf-cp + wrap + additional + additional-insert + additional-wrap + escape) + "All available key-themes in Lispyville.") + :config + (general-add-hook wpc-lisp--hooks #'lispyville-mode) + (lispyville-set-key-theme wpc-lisp--lispyville-key-themes) + (progn + (general-define-key + :keymaps 'lispyville-mode-map + :states 'motion + ;; first unbind + "M-h" nil + "M-l" nil) + (general-define-key + :keymaps 'lispyville-mode-map + :states 'normal + ;; first unbind + "M-j" nil + "M-k" nil + ;; second rebind + "C-s-h" #'lispyville-drag-backward + "C-s-l" #'lispyville-drag-forward + "C-s-e" #'lispyville-end-of-defun + "C-s-a" #'lispyville-beginning-of-defun))) + +;; Elisp +(use-package elisp-slime-nav + :config + (general-add-hook 'emacs-lisp-mode #'ielm-mode)) + +(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/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el new file mode 100644 index 000000000000..574162fbfd82 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-misc.el @@ -0,0 +1,315 @@ +;;; wpc-misc.el --- Hosting miscellaneous configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; URL: https://git.wpcarro.dev/wpcarro/briefcase + +;;; Commentary: +;; This is the home of any configuration that couldn't find a better home. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'project) +(require 'f) +(require 'dash) +(require 'constants) +(require 'region) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(setq display-time-string-forms + '((format-time-string "%H:%M %a %b %d"))) +(display-time-mode 1) + +;; Remove the boilerplate in the *scratch* buffer +(setq initial-scratch-message "") + +;; disable custom variable entries from being written to ~/.emacs.d/init.el +(setq custom-file (f-join user-emacs-directory "custom.el")) +(load custom-file 'noerror) + +;; integrate Emacs with X11 clipboard +(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-package vterm + :config + (general-define-key + :keymaps '(vterm-mode-map) + :states '(insert) + "C-S-v" #'vterm-yank) + (general-define-key + :keymaps '(vterm-mode-map) + :states '(normal) + "K" #'evil-scroll-line-up + "J" #'evil-scroll-line-down + "C-b" #'evil-scroll-page-up + "C-f" #'evil-scroll-page-down)) + +;; Use en Emacs buffer as a REST client. +;; For more information: http://emacsrocks.com/e15.html +(use-package restclient) + +;; Run `package-lint' before publishing to MELPA. +(use-package package-lint) + +;; Parser combinators in Elisp. +(use-package parsec) + +;; disable company mode when editing markdown +;; TODO: move this out of wpc-misc.el and into a later file to call +;; `(disable company-mode)' +(use-package markdown-mode + :config + ;; TODO: Add assertion that pandoc is installed and it is accessible from + ;; Emacs. + (setq markdown-command "pandoc") + (setq markdown-split-window-direction 'right) + ;; (add-hook 'markdown-mode-hook #'markdown-live-preview-mode) + ) + +(use-package alert) + +(use-package refine) + +;; Required by some google-emacs package commands. +(use-package deferred) + +;; git integration +(use-package magit + :config + (add-hook 'git-commit-setup-hook + (lambda () + (company-mode -1) + (flyspell-mode 1))) + (setq magit-display-buffer-function + #'magit-display-buffer-fullframe-status-v1)) + +(use-package magit-popup) + +;; http +(use-package request) + +;; perl-compatible regular expressions +(use-package pcre2el) + +;; alternative to help +(use-package helpful) + +;; If called from an existing helpful-mode buffer, reuse that buffer; otherwise, +;; call `pop-to-buffer'. +(setq helpful-switch-buffer-function + (lambda (buffer-or-name) + (if (eq major-mode 'helpful-mode) + (switch-to-buffer buffer-or-name) + (pop-to-buffer buffer-or-name)))) + +;; Emacs integration with direnv +(use-package direnv + :config + (direnv-mode)) + +;; Superior Elisp library for working with dates and times. +;; TODO: Put this where my other installations for dash.el, s.el, a.el, and +;; other utility Elisp libraries are located. +(use-package ts) + +;; persist history etc b/w Emacs sessions +(setq desktop-save 'if-exists) +(desktop-save-mode 1) +(setq desktop-globals-to-save + (append '((extended-command-history . 30) + (file-name-history . 100) + (grep-history . 30) + (compile-history . 30) + (minibuffer-history . 50) + (query-replace-history . 60) + (read-expression-history . 60) + (regexp-history . 60) + (regexp-search-ring . 20) + (search-ring . 20) + (shell-command-history . 50) + tags-file-name + register-alist))) + +;; configure ibuffer +(setq ibuffer-default-sorting-mode 'major-mode) + +;; 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) + +;; call `git secret hide` after saving secrets.json +(add-hook 'after-save-hook + (lambda () + (when (f-equal? (buffer-file-name) + (f-join constants-briefcase "secrets.json")) + (shell-command "git secret hide")))) + +;; use tabs instead of spaces +(setq-default indent-tabs-mode nil) + +;; automatically follow symlinks +(setq vc-follow-symlinks t) + +;; fullscreen settings +(setq ns-use-native-fullscreen nil) + +(use-package yasnippet + :config + (setq yas-snippet-dirs (list (f-join user-emacs-directory "snippets"))) + (yas-global-mode 1)) + +(use-package projectile + :config + (projectile-mode t)) + +;; TODO: Consider moving this into a briefcase.el module. +(defun wpc-misc--briefcase-find (dir) + "Find the default.nix nearest to DIR." + ;; I use 'vc only at the root of my monorepo because 'transient doesn't use my + ;; .gitignore, which slows things down. Ideally, I could write a version that + ;; behaves like 'transient but also respects my monorepo's .gitignore and any + ;; ancestor .gitignore files. + (if (f-equal? constants-briefcase dir) + (cons 'vc dir) + (when (f-ancestor-of? constants-briefcase dir) + (if (f-exists? (f-join dir "default.nix")) + (cons 'transient dir) + (wpc-misc--briefcase-find (f-parent dir)))))) + +(add-to-list 'project-find-functions #'wpc-misc--briefcase-find) + +(defun wpc-misc-pkill (name) + "Call the pkill executable using NAME as its argument." + (interactive "sProcess name: ") + (call-process "pkill" nil nil nil name)) + +(use-package deadgrep + :config + (general-define-key + :keymaps 'deadgrep-mode-map + :states 'normal + "o" #'deadgrep-visit-result-other-window) + (setq-default deadgrep--context '(0 . 3)) + (defun wpc-misc-deadgrep-region () + "Run a ripgrep search on the active region." + (interactive) + (deadgrep (region-to-string))) + (defun wpc-misc-deadgrep-dwim () + "If a region is active, use that as the search, otherwise don't." + (interactive) + (with-current-buffer (current-buffer) + (if (region-active-p) + (setq deadgrep--additional-flags '("--multiline")) + (wpc-misc-deadgrep-region) + (call-interactively #'deadgrep)))) + (advice-add + 'deadgrep--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)) + +;; Start the Emacs server +(when (not (server-running-p)) + (server-start)) + +(provide 'wpc-misc) +;;; wpc-misc.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el new file mode 100644 index 000000000000..a555e4621a1f --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-nix.el @@ -0,0 +1,72 @@ +;;; wpc-nix.el --- Nix support -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "25.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Configuration to support working with Nix. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'device) +(require 'constants) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package nix-mode + :mode "\\.nix\\'") + +;; TODO(wpcarro): Ensure the sub-process can resolve <briefcase>. +(defun wpc-nix-rebuild-emacs () + "Use nix-env to rebuild wpcarros-emacs." + (interactive) + (let* ((pname (format "nix-build <briefcase/emacs.nixos>")) + (bname (format "*%s*" pname))) + (start-process pname bname + "nix-env" + "-I" (format "briefcase=%s" constants-briefcase) + "-f" "<briefcase>" "-iA" "emacs.nixos") + (display-buffer bname))) + +(defun wpc-nix-sly-from-briefcase (attr) + "Start a Sly REPL configured using the derivation pointed at by ATTR. + + 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 "*briefcase-out/%s*" attr))) + (errbuf (get-buffer-create (format "*briefcase-errors/%s*" attr))) + (expression (format "let briefcase = import <briefcase> {}; in briefcase.third_party.depot.nix.buildLisp.sbclWith [ briefcase.%s ]" attr)) + (command (list "nix-build" "-E" expression))) + (message "Acquiring Lisp for <briefcase>.%s" attr) + (make-process :name (format "nix-build/%s" attr) + :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 <briefcase>.%s at %s" + attr lisp-path) + (sly lisp-path))) + (_ (with-current-buffer errbuf + (error "Failed to build '%s':\n%s" attr + (buffer-string))))) + (kill-buffer outbuf) + (kill-buffer errbuf)))))) + +(provide 'wpc-nix) +;;; wpc-nix.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el new file mode 100644 index 000000000000..bed4dd067615 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-org.el @@ -0,0 +1,40 @@ +;;; wpc-org.el --- My org preferences -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Hosts my org mode preferences + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'f) +(require 'macros) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package org + :config + (evil-set-initial-state 'org-mode 'normal) + (general-add-hook 'org-mode-hook + (list (macros-disable linum-mode) + (macros-disable company-mode))) + (setq org-startup-folded nil) + (setq org-todo-keywords '((sequence "TODO" "BLOCKED" "DONE"))) + (general-unbind 'normal org-mode-map "M-h" "M-j" "M-k" "M-l")) + +(use-package org-bullets + :config + (general-add-hook 'org-mode-hook (macros-enable org-bullets-mode))) + +(provide 'wpc-org) +;;; wpc-org.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el new file mode 100644 index 000000000000..3a363df8ec9b --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-package.el @@ -0,0 +1,33 @@ +;;; wpc-package.el --- My package configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24.1")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; This module hosts all of the settings required to work with ELPA, +;; MELPA, QUELPA, and co. + +;;; Code: + +(require 'package) + +;; Even though we're packaging our Emacs with Nix, having MELPA registered is +;; helpful to ad-hoc test out packages before declaratively adding them to +;; emacs/default.nix. +(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/")) +(package-initialize) + +(unless (package-installed-p 'use-package) + ;; TODO: Consider removing this to improve initialization speed. + (package-refresh-contents) + (package-install 'use-package)) +(eval-when-compile + (require 'use-package)) +;; TODO: Consider removing this, since I'm requiring general.el in individual +;; modules. +(use-package general) + +(provide 'wpc-package) +;;; wpc-package.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el new file mode 100644 index 000000000000..b891a7df6912 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-prolog.el @@ -0,0 +1,20 @@ +;;; wpc-prolog.el --- For Prologging things -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Code configuring my Prolog work. + +;;; Code: + +(require 'macros) + +;; TODO: Notice that the .pl extension conflicts with Perl files. This may +;; become a problem should I start working with Perl. +(macros-support-file-extension "pl" prolog-mode) + +(provide 'wpc-prolog) +;;; wpc-prolog.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el new file mode 100644 index 000000000000..cd30f3ea3739 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-python.el @@ -0,0 +1,25 @@ +;;; wpc-python.el --- Python configuration -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; My Python configuration settings +;; +;; Depends +;; - `apti yapf` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package py-yapf + :config + (add-hook 'python-mode-hook #'py-yapf-enable-on-save)) + +(provide 'wpc-python) +;;; wpc-python.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el new file mode 100644 index 000000000000..396d6349ae6a --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-rust.el @@ -0,0 +1,48 @@ +;;; wpc-rust.el --- Support Rust language -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Supports my Rust work. +;; +;; Dependencies: +;; - `rustup` +;; - `rustup component add rust-src` +;; - `rustup toolchain add nightly && cargo +nightly install racer` + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'macros) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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) + (macros-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/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el new file mode 100644 index 000000000000..855f234b281d --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-shell.el @@ -0,0 +1,32 @@ +;;; wpc-shell.el --- POSIX Shell scripting support -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; Package-Requires: ((emacs "24")) +;; Homepage: https://user.git.corp.google.com/wpcarro/briefcase + +;;; Commentary: +;; Helpers for my shell scripting. Includes bash, zsh, etc. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'zle) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Code +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(use-package flymake-shellcheck + :commands flymake-shellcheck-load + :init + (add-hook 'sh-mode-hook #'flymake-shellcheck-load) + (add-hook 'sh-mode-hook #'zle-minor-mode)) + +(use-package fish-mode) + +(provide 'wpc-shell) +;;; wpc-shell.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el new file mode 100644 index 000000000000..fd025ddd8fb3 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/wpc-ui.el @@ -0,0 +1,181 @@ +;;; wpc-ui.el --- Any related to the UI/UX goes here -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; Hosts font settings, scrolling, color schemes. + +;;; Code: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(require 'constants) +(require 'prelude) +(require 'al) +(require 'fonts) +(require 'colorscheme) +(require 'device) +(require 'laptop-battery) +(require 'modeline) +(require 'general) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Configuration +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; line height +(setq-default line-spacing 0) + +(when window-system + (setq frame-title-format '(buffer-file-name "%f" ("%b")))) + +;; Ensure that buffers update when their contents change on disk. +(global-auto-revert-mode t) + +;; smooth scrolling settings +(setq scroll-step 1 + scroll-conservatively 10000) + +;; clean up modeline +(use-package diminish + :config + (diminish 'emacs-lisp-mode "elisp") + (diminish 'evil-commentary-mode) + (diminish 'flycheck-mode) + (diminish 'auto-revert-mode) + (diminish 'which-key-mode) + (diminish 'yas-minor-mode) + (diminish 'lispyville-mode) + (diminish 'undo-tree-mode) + (diminish 'company-mode) + (diminish 'projectile-mode) + (diminish 'eldoc-mode) + ;; This is how to diminish `auto-fill-mode'. + (diminish 'auto-fill-function) + (diminish 'counsel-mode) + (diminish 'ivy-mode)) + +;; TODO: Further customize `mode-line-format' variable. +(delete 'mode-line-modes mode-line-format) +(delete '(vc-mode vc-mode) mode-line-format) + +;; disable startup screen +(setq inhibit-startup-screen t) + +;; disable toolbar +(tool-bar-mode -1) + +;; set default buffer for Emacs +(setq initial-buffer-choice constants-current-project) + +;; premium Emacs themes +(use-package doom-themes + :config + (setq doom-themes-enable-bold t + doom-themes-enable-italic t) + (doom-themes-visual-bell-config) + (doom-themes-org-config)) + +;; kbd discovery +(use-package which-key + :config + (setq which-key-idle-delay 0.25) + (which-key-mode)) + +;; completion framework +(use-package ivy + :config + (counsel-mode t) + (ivy-mode t) + ;; Remove preceding "^" from ivy prompts + (setq ivy-initial-inputs-alist nil) + ;; prefer using `helpful' variants + (progn + (setq counsel-describe-function-function #'helpful-callable) + (setq counsel-describe-variable-function #'helpful-variable)) + (general-define-key + :keymaps '(ivy-minibuffer-map ivy-switch-buffer-map) + ;; prev + "C-k" #'ivy-previous-line + "<backtab>" #'ivy-previous-line + ;; next + "C-j" #'ivy-next-line + "<tab>" #'ivy-next-line)) + +(use-package ivy-prescient + :config + (ivy-prescient-mode 1) + (prescient-persist-mode 1)) + +;; all-the-icons +(use-package all-the-icons + :config + (when (not constants-ci?) + (unless (f-exists? "~/.local/share/fonts/all-the-icons.ttf") + (all-the-icons-install-fonts t)))) + +;; icons for Ivy +(use-package all-the-icons-ivy + :after (ivy all-the-icons) + :config + (all-the-icons-ivy-setup)) + +;; disable menubar +(menu-bar-mode -1) + +;; reduce noisiness of auto-revert-mode +(setq auto-revert-verbose nil) + +;; highlight lines that are over `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)) + +(if window-system + (progn + (fonts-whitelist-set "JetBrainsMono") + (fonts-enable-ligatures) + (colorscheme-whitelist-set 'doom-solarized-light) + ;; the doom-acario-dark theme uses "Monospace Serif" as the font for + ;; comments, and I'd prefer JetBrainsMono (no italics). + (set-face-attribute font-lock-comment-face nil + :family "JetBrainsMono" + :slant 'normal)) + (load-theme 'wombat)) + +(modeline-setup) + +(provide 'wpc-ui) +;;; wpc-ui.el ends here diff --git a/users/wpcarro/emacs/.emacs.d/wpc/zle.el b/users/wpcarro/emacs/.emacs.d/wpc/zle.el new file mode 100644 index 000000000000..0e0baad2d974 --- /dev/null +++ b/users/wpcarro/emacs/.emacs.d/wpc/zle.el @@ -0,0 +1,92 @@ +;;; zle.el --- Functions to mimmick my ZLE KBDs -*- lexical-binding: t -*- + +;; Author: William Carroll <wpcarro@gmail.com> +;; Version: 0.0.1 +;; URL: https://git.wpcarro.dev/wpcarro/briefcase +;; Package-Requires: ((emacs "24")) + +;;; Commentary: +;; This is primarily for personal use. The keybindings that I choose are those +;; that feel slightly mnemonic while also not shadowing important bindings. +;; It's quite possible that our tastes will differ here. +;; +;; All of these keybindings are intended to shave off milliseconds off your +;; typing. I don't expect these numbers to sum up to a meaningful amount. The +;; primary reason that I wrote this, is that it introduces a small amount of +;; structural editing to my workflow. I've been using these exact keybindings +;; on the command line, and I find them subtely delightful to use. So much so +;; that I decided to bring them to my Emacs configuration. +;; +;; ZLE is the Z-shell line editor. I have some KBDs and functions that I often +;; want in Emacs. +;; +;; Usage: +;; Consider running `(zle-minor-mode)' to run this globally. Depending on your +;; configuration, it could be non-disruptive, disruptive, or extremely +;; disruptive. + +;;; Code: + +;; subshell (C-j) +(defun zle-subshell () + "Insert the characters necessary to create a subshell." + (interactive) + (insert-char ?$) + (insert-char ?\() + (save-excursion + (insert-char ?\)))) + +;; variable (C-v) +(defun zle-variable () + "Insert the characters to reference a variable." + (interactive) + (insert-char ?$) + (insert-char ?{) + (save-excursion + (insert-char ?}))) + +;; 2x dash (C-M--) +(defun zle-dash-dash () + "Insert the characters for flags with 2x dashes." + (interactive) + (insert-char ? ) + (insert-char ?-) + (insert-char ?-)) + +;; 1x quotes (M-') +(defun zle-single-quote () + "Insert the characters to quickly create single quotes." + (interactive) + (insert-char ? ) + (insert-char ?') + (save-excursion + (insert-char ?'))) + +;; 2x quotes (M-") +(defun zle-double-quote () + "Insert the characters to quickly create double quotes." + (interactive) + (insert-char ? ) + (insert-char ?\") + (save-excursion + (insert-char ?\"))) + +(defvar zle-kbds + (let ((map (make-sparse-keymap))) + (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 |