subtree(users/wpcarro): docking briefcase at '24f5a642'

git-subtree-dir: users/wpcarro
git-subtree-mainline: 464bbcb15c
git-subtree-split: 24f5a642af
Change-Id: I6105b3762b79126b3488359c95978cadb3efa789
This commit is contained in:
Vincent Ambo 2021-12-14 01:51:19 +03:00
commit 019f8fd211
766 changed files with 175420 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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