diff --git a/emacs.d/bookmarks b/emacs.d/bookmarks new file mode 100644 index 000000000..a222a18de --- /dev/null +++ b/emacs.d/bookmarks @@ -0,0 +1,15 @@ +;;;; Emacs Bookmark Format Version 1 ;;;; +;;; This format is meant to be slightly human-readable; +;;; nevertheless, you probably don't want to edit it. +;;; -*- End Of Bookmark File Format Version Stamp -*- +(("org-capture-last-stored" + (filename . "~/org/notes.org") + (front-context-string . "** TODO testing ") + (rear-context-string) + (position . 9)) +("org-refile-last-stored" + (filename . "~/Dropbox/cryptocurrency/todo.org") + (front-context-string . "** TODO Maintain") + (rear-context-string . "h email & text)\n") + (position . 1465)) +) \ No newline at end of file diff --git a/emacs.d/custom.el b/emacs.d/custom.el new file mode 100644 index 000000000..88219f70c --- /dev/null +++ b/emacs.d/custom.el @@ -0,0 +1,52 @@ +(custom-set-variables + ;; custom-set-variables was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(ansi-color-names-vector + ["#10151C" "#D95468" "#8BD49C" "#EBBF83" "#5EC4FF" "#E27E8D" "#70E1E8" "#9CAABB"]) + '(custom-safe-themes + (quote + ("bd23e5e571f9b951eb79941ba3927fb493c26463654add2a53f4fb0de72ef08b" "013c62a1fcee7c8988c831027b1c38ae215f99722911b69e570f21fc19cb662e" "0b1ded82ebea8b76e3c17c628fe0d3c7aa46746c3efcf657f633d71989110585" "8ff5073d6c694a442c85505d6f885a752061b3738e2de7c2b9042ffd2c1579e5" "4f5fb2b25a9c71d584472abc5b6f850d616ac280a69e43df6e78ddf2b4aa68fa" "0a3a41085c19d8121ed0ad3eb658a475ccb948a70a83604641ee7d4c3575a4d5" "73e35ffa5ca98b57a9923954f296c3854ce6d8736b31fdbdda3d27502d4b4d69" "a7e7804313dbf827a441c86a8109ef5b64b03011383322cbdbf646eb02692f76" "77bddca0879cb3b0ecdf071d9635c818827c57d69164291cb27268ae324efa84" "3481e594ae6866d72c40ad77d86a1ffa338d01daa9eb0977e324f365cef4f47c" "6be42070d23e832a7493166f90e9bb08af348a818ec18389c1f21d33542771af" default))) + '(fci-rule-color "#56697A") + '(flycheck-javascript-flow-args nil) + '(jdee-db-active-breakpoint-face-colors (cons "#10151C" "#5EC4FF")) + '(jdee-db-requested-breakpoint-face-colors (cons "#10151C" "#8BD49C")) + '(jdee-db-spec-breakpoint-face-colors (cons "#10151C" "#384551")) + '(org-fontify-done-headline t t) + '(org-fontify-quote-and-verse-blocks t t) + '(org-fontify-whole-heading-line t t) + '(package-selected-packages + (quote + (writeroom-mode general rainbow-delimiters zen-mode flx-ido xterm-color evil-collection evil-text-objects-javascript evil-text-objects-haskell dired+ org-bullets slack emojify circe oauth2 engine-mode uniquify diminish elisp-slime-nav pcre2el magit-gh-pulls org-mode intero f cycle-themes ansi-term request dash-functional company-flow flycheck-flow flow-minor-mode elixir-mode oceanic-theme git-timemachine dockerfile-mode docker yaml-mode s key-chord yasnippet prettier-js rjsx-mode indium reason-mode flycheck markdown-mode smex magit all-the-icons-ivy which-key doom-themes cider hydra ace-window counsel-projectile counsel paredit projectile company evil exec-path-from-shell use-package))) + '(safe-local-variable-values + (quote + ((intero-targets "grid:lib" "grid:exe:grid-exe" "grid:test:doctests" "grid:test:grid-test")))) + '(vc-annotate-background "#10151C") + '(vc-annotate-color-map + (list + (cons 20 "#8BD49C") + (cons 40 "#abcd93") + (cons 60 "#cbc68b") + (cons 80 "#EBBF83") + (cons 100 "#e5ae6f") + (cons 120 "#df9e5b") + (cons 140 "#D98E48") + (cons 160 "#dc885f") + (cons 180 "#df8376") + (cons 200 "#E27E8D") + (cons 220 "#df7080") + (cons 240 "#dc6274") + (cons 260 "#D95468") + (cons 280 "#b05062") + (cons 300 "#884c5c") + (cons 320 "#604856") + (cons 340 "#56697A") + (cons 360 "#56697A"))) + '(vc-annotate-very-old-color nil)) +(custom-set-faces + ;; custom-set-faces was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + ) diff --git a/emacs.d/init.el b/emacs.d/init.el new file mode 120000 index 000000000..f5f1a3a10 --- /dev/null +++ b/emacs.d/init.el @@ -0,0 +1 @@ +/Users/wpcarro/dotfiles/init.el \ No newline at end of file diff --git a/emacs.d/network-security.data b/emacs.d/network-security.data new file mode 100644 index 000000000..2b0c195b2 --- /dev/null +++ b/emacs.d/network-security.data @@ -0,0 +1,4 @@ +( + (:id "sha1:88d4df3ce5800820f90edda8f27d13572234b2b9" :fingerprint "sha1:13:0e:b7:b0:c1:b7:75:40:89:0f:0b:64:20:6b:53:a5:d2:2a:a8:07" :host "api.github.com:443" :conditions (:insecure :unknown-ca :invalid)) + (:id "sha1:85b31c268009209a8d3c5387033b219264f7e62b" :fingerprint "sha1:f8:e1:fc:e4:34:8e:19:e8:48:c1:61:fe:3d:09:dd:d4:d2:7a:cb:db" :host "melpa.org:443" :conditions (:insecure :unknown-ca :invalid)) +) diff --git a/emacs.d/projectile-bookmarks.eld b/emacs.d/projectile-bookmarks.eld new file mode 100644 index 000000000..d42f6e551 --- /dev/null +++ b/emacs.d/projectile-bookmarks.eld @@ -0,0 +1 @@ +("~/urbint/meta/" "~/urbint/grid-front-end/" "~/dotfiles/" "~/urbint/evil-text-objects-javascript/" "~/pc_settings/" "~/programming/doom-emacs/" "~/urbint/evil-text-objects-haskell/" "~/urbint/grid/" "~/urbint/machina/" "~/urbint/grid-front-end-reasonml/" "~/urbint/docker-images/" "~/programming/cryptocurrency/" "~/programming/a-reason-react-tutorial/") \ No newline at end of file diff --git a/emacs.d/smex-items b/emacs.d/smex-items new file mode 100644 index 000000000..c3613fa5b --- /dev/null +++ b/emacs.d/smex-items @@ -0,0 +1,160 @@ + +;; ----- smex-history ----- +( + load-theme + disable-theme + cycle-themes-mode + cycle-themes + rjsx-mode + enable-theme + package-list-packages +) + +;; ----- smex-data ----- +( + (global-company-mode . 12) + (global-flycheck-mode . 6) + (reason-mode . 1) + (imenu . 2) + (package-refresh-contents . 24) + (package-install . 30) + (indium-connect-to-chrome . 1) + (indium-interaction-mode . 1) + (rjsx-mode . 9) + (flycheck-info . 3) + (flycheck-mode . 12) + (flycheck-next-error . 1) + (flycheck-compile . 1) + (flycheck-select-checker . 10) + (yas-new-snippet . 2) + (yas-visit-snippet-file . 1) + (yas-expand . 2) + (yas-minor-mode . 1) + (describe-mode . 4) + (mark-defun . 5) + (wpc/find-or-create-js-test . 5) + (wpc/find-or-create-js-module . 2) + (wpc/toggle-between-js-test-and-module . 2) + (yaml-mode . 1) + (docker-images . 5) + (docker-mode . 1) + (dockerfile-mode . 2) + (snippet-mode . 2) + (prettier-js-mode . 23) + (describe-variable . 1) + (elixir-mode . 1) + (git-timemachine . 10) + (disable-theme . 15) + (load-theme . 15) + (xref-goto-xref . 2) + (flycheck-verify-setup . 7) + (company-flow . 15) + (company-mode . 8) + (js2-mode . 2) + (company-diag . 7) + (flow/set-flow-executable . 1) + (org-clubhouse-create-story . 9) + (org-clubhouse-mode . 1) + (slack-send-code-snippet . 13) + (indent-pp-sexp . 1) + (describe-personal-keybindings . 2) + (wpc/insert-flow-annotation . 3) + (ibuffer . 2) + (flow-minor-mode . 4) + (paredit-mode . 1) + (evil-mode . 6) + (global-evil-leader-mode . 9) + (help . 5) + (menu-bar-mode . 3) + (enable-theme . 4) + (ansi-term . 9) + (wpcarro/terminal . 17) + (toggle-frame-maximized . 2) + (previous-buffer . 1) + (paredit-forward-slurp-sexp . 1) + (paredit-raise-sexp . 1) + (sh-mode . 2) + (haskell-mode . 1) + (counsel-semantic-or-imenu . 1) + (evil-leader-mode . 9) + (intero-targets . 5) + (find-file . 1) + (electric-pair-mode . 8) + (eval-print-last-sexp . 3) + (evil-inner-haskell-comment-block . 5) + (evil-outer-haskell-comment-block . 1) + (elp-instrument-function . 1) + (elp-instrument-list . 1) + (magit-checkout . 2) + (magit-blame . 1) + (org-insert-link . 1) + (magit-mode . 2) + (emacs-lisp-mode . 1) + (magit-status . 3) + (magit-gh-pulls-popup . 1) + (magit-log . 1) + (org-capture . 1) + (flycheck-list-errors . 2) + (elisp-slime-nav-mode . 2) + (describe-font . 1) + (load-library . 1) + (diminish . 4) + (diminished-modes . 1) + (diminish-undo . 3) + (undo-tree-mode . 2) + (undo-tree-visualize . 21) + (undo-tree-save-state-to-register . 3) + (undo-tree-restore-state-from-register . 1) + (transpose-words . 9) + (downcase-dwim . 1) + (ffap . 2) + (william-carroll-kbds-minor-mode . 4) + (ffap-other-window . 2) + (eval-expression . 2) + (engine/search-google . 2) + (counsel-spotify-next . 2) + (counsel-spotify-search-track . 3) + (counsel-spotify-search-artist . 2) + (slack-im-select . 39) + (slack-start . 10) + (slack-group-select . 2) + (slack-channel-join . 1) + (slack-channel-select . 18) + (slack-select-unread-rooms . 31) + (slack-edit-message-mode . 1) + (slack-file-upload . 2) + (smerge-next . 3) + (smerge-mode . 2) + (smerge-keep-mine . 5) + (smerge-keep-other . 31) + (emacs-lisp-macroexpand . 43) + (woman . 1) + (package-delete . 4) + (ert . 12) + (caps->kebab . 1) + (text-mode . 1) + (org-mode . 1) + (dired-do-copy . 1) + (wpc/toggle-terminal . 1) + (eshell . 1) + (term . 1) + (counsel-git-grep . 12) + (fzf . 1) + (fzf-projectile . 1) + (fzf-git . 1) + (execute-extended-command . 1) + (package-menu-mark-upgrades . 1) + (package-list-packages . 7) + (repeat-complex-command . 1) + (apropos-value . 1) + (apropos-variable . 6) + (ido-find-file . 2) + (counsel-find-file . 1) + (counsel-projectile-find-file . 1) + (projectile-find-file . 1) + (zen-mode . 6) + (zen-mode-increase-margin-width . 1) + (writeroom-mode . 7) + (cycle-themes . 2) + (cycle-themes-mode . 2) +) diff --git a/emacs.d/snippets/emacs-lisp-mode/elisp-module-docs b/emacs.d/snippets/emacs-lisp-mode/elisp-module-docs new file mode 100644 index 000000000..8ea7b8f07 --- /dev/null +++ b/emacs.d/snippets/emacs-lisp-mode/elisp-module-docs @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: Elisp module docs +# key: emd +# -- +;;; `(-> (buffer-file-name) f-filename)` --- $2 -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; $3 + +;;; Code: \ No newline at end of file diff --git a/emacs.d/snippets/emacs-lisp-mode/provide-footer b/emacs.d/snippets/emacs-lisp-mode/provide-footer new file mode 100644 index 000000000..2a0bcc33f --- /dev/null +++ b/emacs.d/snippets/emacs-lisp-mode/provide-footer @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Provide footer +# key: elf +# -- +(provide '`(-> (buffer-file-name) f-filename f-no-ext)`) +;;; `(-> (buffer-file-name) f-filename)` ends here \ No newline at end of file diff --git a/emacs.d/snippets/org-mode/code-snippet b/emacs.d/snippets/org-mode/code-snippet new file mode 100644 index 000000000..4215b1599 --- /dev/null +++ b/emacs.d/snippets/org-mode/code-snippet @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Code Snippet +# key: src +# -- +#+BEGIN_SRC $1 +$2 +#+END_SRC \ No newline at end of file diff --git a/emacs.d/snippets/org-mode/href b/emacs.d/snippets/org-mode/href new file mode 100644 index 000000000..ac65ea2e4 --- /dev/null +++ b/emacs.d/snippets/org-mode/href @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Org mode URL +# key: href +# -- +[[$1][$2]] \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/console-log b/emacs.d/snippets/rjsx-mode/console-log new file mode 100644 index 000000000..bc51f8313 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/console-log @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Console.log helper +# key: clo +# -- +console.log($1) \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/const-defn b/emacs.d/snippets/rjsx-mode/const-defn new file mode 100644 index 000000000..8e35e61fc --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/const-defn @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: const definition +# key: cn +# -- +const $1 = '$2' \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/const-function b/emacs.d/snippets/rjsx-mode/const-function new file mode 100644 index 000000000..13f2018f2 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/const-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: const function +# key: cfn +# -- +const $1 = ($2) => { + $3 +} \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/destructure-const b/emacs.d/snippets/rjsx-mode/destructure-const new file mode 100644 index 000000000..2a52c57c7 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/destructure-const @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Destructuring a const +# key: cds +# -- +const { $1 } = $2 \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/fat-arrow b/emacs.d/snippets/rjsx-mode/fat-arrow new file mode 100644 index 000000000..187a2efc5 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/fat-arrow @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: fa +# -- +=> \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/fat-arrow-function b/emacs.d/snippets/rjsx-mode/fat-arrow-function new file mode 100644 index 000000000..694914a83 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/fat-arrow-function @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Fat arrow function +# key: faf +# -- +() => { + $1 +} \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/import-destructured b/emacs.d/snippets/rjsx-mode/import-destructured new file mode 100644 index 000000000..ded3ce163 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/import-destructured @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import destructured +# key: ids +# -- +import { $1 } from '$2' \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/import-react b/emacs.d/snippets/rjsx-mode/import-react new file mode 100644 index 000000000..0463f5cd5 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/import-react @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Import React dependency (ES6) +# key: ir +# -- +import React from 'react' diff --git a/emacs.d/snippets/rjsx-mode/import-type b/emacs.d/snippets/rjsx-mode/import-type new file mode 100644 index 000000000..fcd51f687 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/import-type @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import type +# key: ixt +# -- +import type { $1 } from '$2' \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/import-x-from-y b/emacs.d/snippets/rjsx-mode/import-x-from-y new file mode 100644 index 000000000..09fa6df50 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/import-x-from-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import x from y +# key: ix +# -- +import $1 from '$2' \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/import-y b/emacs.d/snippets/rjsx-mode/import-y new file mode 100644 index 000000000..9f550e300 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/import-y @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: import y +# key: iy +# -- +import '$1' \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/jest-describe-test b/emacs.d/snippets/rjsx-mode/jest-describe-test new file mode 100644 index 000000000..ed382d4f7 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/jest-describe-test @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# name: Jest describe/test block +# key: dsc +# -- +describe('$1', () => { + test('$2', () => { + + expect($3).toEqual($4) + }) +}) \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/jest-test b/emacs.d/snippets/rjsx-mode/jest-test new file mode 100644 index 000000000..12ca2e786 --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/jest-test @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: Jest / Jasmine test +# key: tst +# -- +test('$1', () => { + expect($2).toBe($3) +}) \ No newline at end of file diff --git a/emacs.d/snippets/rjsx-mode/react-class-component b/emacs.d/snippets/rjsx-mode/react-class-component new file mode 100644 index 000000000..f2a93a31d --- /dev/null +++ b/emacs.d/snippets/rjsx-mode/react-class-component @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: React class extends +# key: clz +# -- +class $1 extends React.Component { + render() { + $2 + } +} + +export default $1 \ No newline at end of file diff --git a/emacs.d/vendor/org-clubhouse.el b/emacs.d/vendor/org-clubhouse.el new file mode 100644 index 000000000..ba1f004a2 --- /dev/null +++ b/emacs.d/vendor/org-clubhouse.el @@ -0,0 +1,365 @@ +;;; private/grfn/org-clubhouse.el + +(require 'dash) +(require 'dash-functional) +(require 's) +(require 'org) +(require 'org-element) +(require 'cl) + +;;; +;;; Configuration +;;; + +(defvar org-clubhouse-auth-token nil + "Authorization token for the Clubhouse API") + +(defvar org-clubhouse-team-name nil + "Team name to use in links to Clubhouse +ie https://app.clubhouse.io//stories") + +(defvar org-clubhouse-project-ids nil + "Specific list of project IDs to synchronize with clubhouse. +If unset all projects will be synchronized") + +(defvar org-clubhouse-workflow-name "Default") + +(defvar org-clubhouse-state-alist + '(("LATER" . "Unscheduled") + ("[ ]" . "Ready for Development") + ("TODO" . "Ready for Development") + ("OPEN" . "Ready for Development") + ("ACTIVE" . "In Development") + ("PR" . "Review") + ("DONE" . "Merged") + ("[X]" . "Merged") + ("CLOSED" . "Merged"))) + +;;; +;;; Utilities +;;; + +(defun ->list (vec) (append vec nil)) + +(defun reject-archived (item-list) + (-filter (lambda (item) (equal :json-false (alist-get 'archived item))) item-list)) + +(defun alist->plist (key-map alist) + (->> key-map + (-map (lambda (key-pair) + (let ((alist-key (car key-pair)) + (plist-key (cdr key-pair))) + (list plist-key (alist-get alist-key alist))))) + (-flatten-n 1))) + +(defun alist-get-equal (key alist) + "Like `alist-get', but uses `equal' instead of `eq' for comparing keys" + (->> alist + (-find (lambda (pair) (equal key (car pair)))) + (cdr))) + +;;; +;;; Org-element interaction +;;; + +;; (defun org-element-find-headline () +;; (let ((current-elt (org-element-at-point))) +;; (if (equal 'headline (car current-elt)) +;; current-elt +;; (let* ((elt-attrs (cadr current-elt)) +;; (parent (plist-get elt-attrs :post-affiliated))) +;; (goto-char parent) +;; (org-element-find-headline))))) + +(defun org-element-find-headline () + (let ((current-elt (org-element-at-point))) + (when (equal 'headline (car current-elt)) + (cadr current-elt)))) + +(defun org-element-extract-clubhouse-id (elt) + (when-let ((clubhouse-id-link (plist-get elt :CLUBHOUSE-ID))) + (string-match + (rx "[[" (one-or-more anything) "]" + "[" (group (one-or-more digit)) "]]") + clubhouse-id-link) + (string-to-int (match-string 1 clubhouse-id-link)))) + + + +(defun org-element-clubhouse-id () + (org-element-extract-clubhouse-id + (org-element-find-headline))) + +;;; +;;; API integration +;;; + +(defvar org-clubhouse-base-url* "https://api.clubhouse.io/api/v2") + +(defun org-clubhouse-auth-url (url) + (concat url + "?" + (url-build-query-string + `(("token" ,org-clubhouse-auth-token))))) + +(defun org-clubhouse-baseify-url (url) + (if (s-starts-with? org-clubhouse-base-url* url) url + (concat org-clubhouse-base-url* + (if (s-starts-with? "/" url) url + (concat "/" url))))) + +(defun org-clubhouse-request (method url &optional data) + (message "%s %s %s" method url (prin1-to-string data)) + (let* ((url-request-method method) + (url-request-extra-headers + '(("Content-Type" . "application/json"))) + (url-request-data data) + (buf)) + + (setq url (-> url + org-clubhouse-baseify-url + org-clubhouse-auth-url)) + + (setq buf (url-retrieve-synchronously url)) + + (with-current-buffer buf + (goto-char url-http-end-of-headers) + (prog1 (json-read) (kill-buffer))))) + +(cl-defun to-id-name-pairs + (seq &optional (id-attr 'id) (name-attr 'name)) + (->> seq + ->list + (-map (lambda (resource) + (cons (alist-get id-attr resource) + (alist-get name-attr resource)))))) + +(cl-defun org-clubhouse-fetch-as-id-name-pairs + (resource &optional + (id-attr 'id) + (name-attr 'name)) + "Returns the given resource from clubhouse as (id . name) pairs" + (let ((resp-json (org-clubhouse-request "GET" resource))) + (-> resp-json + ->list + reject-archived + (to-id-name-pairs id-attr name-attr)))) + +(defun org-clubhouse-link-to-story (story-id) + (format "https://app.clubhouse.io/%s/story/%d" + org-clubhouse-team-name + story-id)) + +(defun org-clubhouse-link-to-epic (epic-id) + (format "https://app.clubhouse.io/%s/epic/%d" + org-clubhouse-team-name + epic-id)) + +(defun org-clubhouse-link-to-project (project-id) + (format "https://app.clubhouse.io/%s/project/%d" + org-clubhouse-team-name + project-id)) + +;;; +;;; Caching +;;; + + + +(defvar org-clubhouse-cache-clear-functions ()) + +(defmacro defcache (name &optional docstring &rest body) + (let* ((doc (when docstring (list docstring))) + (cache-var-name (intern (concat (symbol-name name) + "-cache"))) + (clear-cache-function-name + (intern (concat "clear-" (symbol-name cache-var-name))))) + `(progn + (defvar ,cache-var-name :no-cache) + (defun ,name () + ,@doc + (when (equal :no-cache ,cache-var-name) + (setq ,cache-var-name (progn ,@body))) + ,cache-var-name) + (defun ,clear-cache-function-name () + (interactive) + (setq ,cache-var-name :no-cache)) + + (push (quote ,clear-cache-function-name) + org-clubhouse-cache-clear-functions)))) + +(defun org-clubhouse-clear-cache () + (interactive) + (-map #'funcall org-clubhouse-cache-clear-functions)) + +;;; +;;; API resource functions +;;; + +(defcache org-clubhouse-projects + "Returns projects as (project-id . name)" + (org-clubhouse-fetch-as-id-name-pairs "projects")) + +(defcache org-clubhouse-epics + "Returns projects as (project-id . name)" + (org-clubhouse-fetch-as-id-name-pairs "epics")) + +(defcache org-clubhouse-workflow-states + "Returns worflow states as (name . id) pairs" + (let* ((resp-json (org-clubhouse-request "GET" "workflows")) + (workflows (->list resp-json)) + ;; just assume it exists, for now + (workflow (-find (lambda (workflow) + (equal org-clubhouse-workflow-name + (alist-get 'name workflow))) + workflows)) + (states (->list (alist-get 'states workflow)))) + (to-id-name-pairs states + 'name + 'id))) + +(defun org-clubhouse-stories-in-project (project-id) + "Returns the stories in the given project as org bugs" + (let ((resp-json (org-clubhouse-request "GET" (format "/projects/%d/stories" project-id)))) + (->> resp-json ->list reject-archived + (-reject (lambda (story) (equal :json-true (alist-get 'completed story)))) + (-map (lambda (story) + (cons + (cons 'status + (cond + ((equal :json-true (alist-get 'started story)) + 'started) + ((equal :json-true (alist-get 'completed story)) + 'completed) + ('t + 'open))) + story))) + (-map (-partial #'alist->plist + '((name . :title) + (id . :id) + (status . :status))))))) + +;;; +;;; Story creation +;;; + +(cl-defun org-clubhouse-create-story-internal + (title &key project-id epic-id) + (assert (and (stringp title) + (integerp project-id) + (or (null epic-id) (integerp epic-id)))) + (org-clubhouse-request + "POST" + "stories" + (json-encode + `((name . ,title) + (project_id . ,project-id) + (epic_id . ,epic-id))))) + +(defun org-clubhouse-prompt-for-project (cb) + (ivy-read + "Select a project: " + (-map #'cdr (org-clubhouse-projects)) + :require-match t + :history 'org-clubhouse-project-history + :action (lambda (selected) + (let ((project-id + (->> (org-clubhouse-projects) + (-find (lambda (proj) + (string-equal (cdr proj) selected))) + car))) + (message "%d" project-id) + (funcall cb project-id))))) + +(defun org-clubhouse-prompt-for-epic (cb) + (ivy-read + "Select an epic: " + (-map #'cdr (org-clubhouse-epics)) + :history 'org-clubhouse-epic-history + :action (lambda (selected) + (let ((epic-id + (->> (org-clubhouse-epics) + (-find (lambda (proj) + (string-equal (cdr proj) selected))) + car))) + (message "%d" epic-id) + (funcall cb epic-id))))) + +(defun org-clubhouse-populate-created-story (story) + (let ((elt (org-element-find-headline)) + (story-id (alist-get 'id story)) + (epic-id (alist-get 'epic_id story)) + (project-id (alist-get 'project_id story))) + + (org-set-property "clubhouse-id" + (org-make-link-string + (org-clubhouse-link-to-story story-id) + (number-to-string story-id))) + + (org-set-property "clubhouse-epic" + (org-make-link-string + (org-clubhouse-link-to-epic epic-id) + (alist-get epic-id (org-clubhouse-epics)))) + + (org-set-property "clubhouse-project" + (org-make-link-string + (org-clubhouse-link-to-project project-id) + (alist-get project-id (org-clubhouse-projects)))) + + (org-todo "TODO"))) + +(defun org-clubhouse-create-story () + (interactive) + ;; (message (org-element-find-headline)) + (when-let ((elt (org-element-find-headline)) + (title (plist-get elt :title))) + (if (plist-get elt :CLUBHOUSE-ID) + (message "This headline is already a clubhouse story!") + (org-clubhouse-prompt-for-project + (lambda (project-id) + (when project-id + (org-clubhouse-prompt-for-epic + (lambda (epic-id) + (let* ((story (org-clubhouse-create-story-internal + title + :project-id project-id + :epic-id epic-id))) + (org-clubhouse-populate-created-story story)))))))))) + +;;; +;;; Story updates +;;; + +(cl-defun org-clubhouse-update-story-internal + (story-id &rest attrs) + (assert (and (integerp story-id) + (listp attrs))) + (org-clubhouse-request + "PUT" + (format "stories/%d" story-id) + (json-encode attrs))) + +(defun org-clubhouse-update-status () + (when-let (clubhouse-id (org-element-clubhouse-id)) + (let* ((elt (org-element-find-headline)) + (todo-keyword (-> elt (plist-get :todo-keyword) (substring-no-properties)))) + (message todo-keyword) + (when-let ((clubhouse-workflow-state + (alist-get-equal todo-keyword org-clubhouse-state-alist)) + (workflow-state-id + (alist-get-equal clubhouse-workflow-state (org-clubhouse-workflow-states)))) + (org-clubhouse-update-story-internal + clubhouse-id + :workflow_state_id workflow-state-id) + (message "Successfully updated clubhouse status to \"%s\"" + clubhouse-workflow-state))))) + +(define-minor-mode org-clubhouse-mode + :init-value nil + :group 'org + :lighter "Org-Clubhouse" + :keymap '() + (add-hook 'org-after-todo-state-change-hook + 'org-clubhouse-update-status + nil + t)) diff --git a/emacs.d/vendor/reason-indent.el b/emacs.d/vendor/reason-indent.el new file mode 100644 index 000000000..8fd3c9425 --- /dev/null +++ b/emacs.d/vendor/reason-indent.el @@ -0,0 +1,304 @@ +;;; reason-indent.el --- Indentation functions for ReasonML -*-lexical-binding: t-*- + +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;;; Commentary: + +;; Indentation functions for Reason. + +;;; Code: + +(defconst reason-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") + +(defcustom reason-indent-offset 2 + "Indent Reason code by this number of spaces." + :type 'integer + :group 'reason-mode + :safe #'integerp) + +(defun reason-looking-back-str (str) + "Like `looking-back' but for fixed strings rather than regexps. +Works around some regexp slowness. +Argument STR string to search for." + (let ((len (length str))) + (and (> (point) len) + (equal str (buffer-substring-no-properties (- (point) len) (point)))))) + +(defun reason-paren-level () + "Get the level of nesting inside parentheses." + (nth 0 (syntax-ppss))) + +(defun reason-in-str-or-cmnt () + "Return whether point is currently inside a string or a comment." + (nth 8 (syntax-ppss))) + +(defun reason-rewind-past-str-cmnt () + "Rewind past string or comment." + (goto-char (nth 8 (syntax-ppss)))) + +(defun reason-rewind-irrelevant () + "Rewind past irrelevant characters (whitespace of inside comments)." + (interactive) + (let ((starting (point))) + (skip-chars-backward "[:space:]\n") + (if (reason-looking-back-str "*/") (backward-char)) + (if (reason-in-str-or-cmnt) + (reason-rewind-past-str-cmnt)) + (if (/= starting (point)) + (reason-rewind-irrelevant)))) + +(defun reason-align-to-expr-after-brace () + "Align the expression at point to the expression after the previous brace." + (save-excursion + (forward-char) + ;; We don't want to indent out to the open bracket if the + ;; open bracket ends the line + (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) + (when (looking-at "[[:space:]]") + (forward-word 1) + (backward-word 1)) + (current-column)))) + +(defun reason-align-to-prev-expr () + "Align the expression at point to the previous expression." + (let ((alignment (save-excursion + (forward-char) + ;; We don't want to indent out to the open bracket if the + ;; open bracket ends the line + (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) + (if (looking-at "[[:space:]]") + (progn + (forward-word 1) + (backward-word 1)) + (backward-char)) + (current-column))))) + (if (not alignment) + (save-excursion + (forward-char) + (forward-line) + (back-to-indentation) + (current-column)) + alignment))) + +;;; Start of a reason binding +(defvar reason-binding + (regexp-opt '("let" "type" "module" "fun"))) + +(defun reason-beginning-of-defun (&optional arg) + "Move backward to the beginning of the current defun. + +With ARG, move backward multiple defuns. Negative ARG means +move forward. + +This is written mainly to be used as `beginning-of-defun-function'. +Don't move to the beginning of the line. `beginning-of-defun', +which calls this, does that afterwards." + (interactive "p") + (re-search-backward (concat "^\\(" reason-binding "\\)\\_>") + nil 'move (or arg 1))) + +(defun reason-end-of-defun () + "Move forward to the next end of defun. + +With argument, do it that many times. +Negative argument -N means move back to Nth preceding end of defun. + +Assume that this is called after ‘beginning-of-defun’. So point is +at the beginning of the defun body. + +This is written mainly to be used as `end-of-defun-function' for Reason." + (interactive) + ;; Find the opening brace + (if (re-search-forward "[{]" nil t) + (progn + (goto-char (match-beginning 0)) + ;; Go to the closing brace + (condition-case nil + (forward-sexp) + (scan-error + ;; The parentheses are unbalanced; instead of being unable to fontify, just jump to the end of the buffer + (goto-char (point-max))))) + ;; There is no opening brace, so consider the whole buffer to be one "defun" + (goto-char (point-max)))) + +(defun reason-rewind-to-beginning-of-current-level-expr () + "Rewind to the beginning of the expression on the current level of nesting." + (interactive) + (let ((current-level (reason-paren-level))) + (back-to-indentation) + (when (looking-at "=>") + (reason-rewind-irrelevant) + (back-to-indentation)) + (while (> (reason-paren-level) current-level) + (backward-up-list) + (back-to-indentation)))) + +(defun reason-mode-indent-line () + "Indent current line." + (interactive) + (let ((indent + (save-excursion + (back-to-indentation) + ;; Point is now at beginning of current line + (let* ((level (reason-paren-level)) + (baseline + ;; Our "baseline" is one level out from the indentation of the expression + ;; containing the innermost enclosing opening bracket. That + ;; way if we are within a block that has a different + ;; indentation than this mode would give it, we still indent + ;; the inside of it correctly relative to the outside. + (if (= 0 level) + 0 + (save-excursion + (reason-rewind-irrelevant) + (if (save-excursion + (reason-rewind-to-beginning-of-current-level-expr) + (looking-at "<")) + (progn + (reason-rewind-to-beginning-of-current-level-expr) + (current-column)) + (progn + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + + (cond + ((looking-at "switch") + (current-column)) + + ((looking-at "|") + (+ (current-column) (* reason-indent-offset 2))) + + (t + (let ((current-level (reason-paren-level))) + (save-excursion + (while (and (= current-level (reason-paren-level)) + (not (looking-at reason-binding))) + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr)) + (+ (current-column) reason-indent-offset))))))))))) + (cond + ;; A function return type is indented to the corresponding function arguments + ((looking-at "=>") + (+ baseline reason-indent-offset)) + + ((reason-in-str-or-cmnt) + (cond + ;; In the end of the block -- align with star + ((looking-at "*/") (+ baseline 1)) + ;; Indent to the following shape: + ;; /* abcd + ;; * asdf + ;; */ + ;; + ((looking-at "*") (+ baseline 1)) + ;; Indent to the following shape: + ;; /* abcd + ;; asdf + ;; */ + ;; + (t (+ baseline (+ reason-indent-offset 1))))) + + ((looking-at ""))) + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + (cond + ((looking-at "switch") baseline) + + (jsx? (current-column)) + + (t (- baseline reason-indent-offset)))))) + + ;; Doc comments in /** style with leading * indent to line up the *s + ((and (nth 4 (syntax-ppss)) (looking-at "*")) + (+ 1 baseline)) + + ;; If we're in any other token-tree / sexp, then: + (t + (or + ;; If we are inside a pair of braces, with something after the + ;; open brace on the same line and ending with a comma, treat + ;; it as fields and align them. + (when (> level 0) + (save-excursion + (reason-rewind-irrelevant) + (backward-up-list) + ;; Point is now at the beginning of the containing set of braces + (reason-align-to-expr-after-brace))) + + (progn + (back-to-indentation) + (cond ((looking-at (regexp-opt '("and" "type"))) + baseline) + ((save-excursion + (reason-rewind-irrelevant) + (= (point) 1)) + baseline) + ((save-excursion + (while (looking-at "|") + (reason-rewind-irrelevant) + (back-to-indentation)) + (looking-at (regexp-opt '("type")))) + (+ baseline reason-indent-offset)) + ((looking-at "|\\|/[/*]") + baseline) + ((and (> level 0) + (save-excursion + (reason-rewind-irrelevant) + (backward-up-list) + (reason-rewind-to-beginning-of-current-level-expr) + (looking-at "switch"))) + (+ baseline reason-indent-offset)) + ((save-excursion + (reason-rewind-irrelevant) + (looking-back "[{;,\\[(]" (- (point) 2))) + baseline) + ((and + (save-excursion + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr) + (and (looking-at reason-binding) + (not (progn + (forward-sexp) + (forward-sexp) + (skip-chars-forward "[:space:]\n") + (looking-at "="))))) + (not (save-excursion + (skip-chars-backward "[:space:]\n") + (reason-looking-back-str "=>")))) + (save-excursion + (reason-rewind-irrelevant) + (backward-sexp) + (reason-align-to-prev-expr))) + ((save-excursion + (reason-rewind-irrelevant) + (looking-back "<\/.*?>" (- (point) 30))) + baseline) + (t + (save-excursion + (reason-rewind-irrelevant) + (reason-rewind-to-beginning-of-current-level-expr) + + (if (looking-at "|") + baseline + (+ baseline reason-indent-offset))))) + ;; Point is now at the beginning of the current line + )))))))) + + (when indent + ;; If we're at the beginning of the line (before or at the current + ;; indentation), jump with the indentation change. Otherwise, save the + ;; excursion so that adding the indentations will leave us at the + ;; equivalent position within the line to where we were before. + (if (<= (current-column) (current-indentation)) + (indent-line-to indent) + (save-excursion (indent-line-to indent)))))) + +(provide 'reason-indent) + +;;; reason-indent.el ends here diff --git a/emacs.d/vendor/reason-interaction.el b/emacs.d/vendor/reason-interaction.el new file mode 100644 index 000000000..6ceaed1e9 --- /dev/null +++ b/emacs.d/vendor/reason-interaction.el @@ -0,0 +1,216 @@ +;;; reason-interaction.el --- Phrase navitagion for rtop -*-lexical-binding: t-*- + +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;;; Commentary: + +;; Phrase navigation for utop and maybe other REPLs. + +;; The utop compatibility layer for Reason was mainly taken from: +;; https://github.com/ocaml/tuareg/blob/master/tuareg-light.el (big thanks!) + +;;; Code: + +(defun reason-backward-char (&optional step) + "Go back one char. +Similar to `backward-char` but it does not signal errors +`beginning-of-buffer` and `end-of-buffer`. It optionally takes a +STEP parameter for jumping back more than one character." + (when step (goto-char (- (point) step)) + (goto-char (1- (point))))) + +(defun reason-forward-char (&optional step) + "Go forward one char. +Similar to `forward-char` but it does not signal errors +`beginning-of-buffer` and `end-of-buffer`. It optionally takes a +STEP parameter for jumping back more than one character." + (when step (goto-char (+ (point) step)) + (goto-char (1+ (point))))) + +(defun reason-in-literal-p () + "Return non-nil if point is inside an Reason literal." + (nth 3 (syntax-ppss))) + +(defconst reason-comment-delimiter-regexp "\\*/\\|/\\*" + "Regex for identify either open or close comment delimiters.") + +(defun reason-in-between-comment-chars-p () + "Return non-nil iff point is in between the comment delimiter chars. +It returns non-nil if point is between the chars only (*|/ or /|* +where | is point)." + (and (not (bobp)) (not (eobp)) + (or (and (char-equal ?/ (char-before)) (char-equal ?* (char-after))) + (and (char-equal ?* (char-before)) (char-equal ?/ (char-after)))))) + +(defun reason-looking-at-comment-delimiters-p () + "Return non-nil iff point in between comment delimiters." + (looking-at-p reason-comment-delimiter-regexp)) + +(defun reason-in-between-comment-delimiters-p () + "Return non-nil if inside /* and */." + (nth 4 (syntax-ppss))) + +(defun reason-in-comment-p () + "Return non-nil iff point is inside or right before a comment." + (or (reason-in-between-comment-delimiters-p) + (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p))) + +(defun reason-beginning-of-literal-or-comment () + "Skip to the beginning of the current literal or comment (or buffer)." + (interactive) + (goto-char (or (nth 8 (syntax-ppss)) (point)))) + +(defun reason-inside-block-scope-p () + "Skip to the beginning of the current literal or comment (or buffer)." + (and (> (nth 0 (syntax-ppss)) 0) + (let ((delim-start (nth 1 (syntax-ppss)))) + (save-excursion + (goto-char delim-start) + (char-equal ?{ (following-char)))))) + +(defun reason-at-phrase-break-p () + "Is the underlying `;' a phrase break?" + ;; Difference from OCaml, the phrase separator is a single semi-colon + (and (not (eobp)) + (char-equal ?\; (following-char)))) + +(defun reason-skip-to-close-delimiter (&optional limit) + "Skip to the end of a Reason block. +It basically calls `re-search-forward` in order to go to any +closing delimiter, not concerning itself with balancing of any +sort. Client code needs to check that. +LIMIT is passed to `re-search-forward` directly." + (re-search-forward "\\s)" limit 'move)) + +(defun reason-skip-back-to-open-delimiter (&optional limit) + "Skip to the beginning of a Reason block backwards. +It basically calls `re-search-backward` in order to go to any +opening delimiter, not concerning itself with balancing of any +sort. Client code needs to check that. +LIMIT is passed to `re-search-backward` directly." + (re-search-backward "\\s(" limit 'move)) + +(defun reason-find-phrase-end () + "Skip to the end of a phrase." + (while (and (not (eobp)) + (not (reason-at-phrase-break-p))) + (if (re-search-forward ";" nil 'move) + (progn (when (reason-inside-block-scope-p) + (reason-skip-to-close-delimiter)) + (goto-char (1- (point)))) + ;; avoid infinite loop at the end of the buffer + (re-search-forward "[[:space:]\\|\n]+" nil 'move))) + (min (goto-char (1+ (point))) (point-max))) + +(defun reason-skip-blank-and-comments () + "Skip blank spaces and comments." + (cond + ((eobp) (point)) + ((or (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p)) (progn + (reason-forward-char 1) + (reason-skip-blank-and-comments))) + ((reason-in-between-comment-delimiters-p) (progn + (search-forward "*/" nil t) + (reason-skip-blank-and-comments))) + ((eolp) (progn + (reason-forward-char 1) + (reason-skip-blank-and-comments))) + (t (progn (skip-syntax-forward " ") + (point))))) + +(defun reason-skip-back-blank-and-comments () + "Skip blank spaces and comments backwards." + (cond + ((bobp) (point)) + ((looking-back reason-comment-delimiter-regexp) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((reason-in-between-comment-delimiters-p) (progn + (search-backward "/*" nil t) + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((or (reason-in-between-comment-chars-p) + (reason-looking-at-comment-delimiters-p)) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + ((bolp) (progn + (reason-backward-char 1) + (reason-skip-back-blank-and-comments))) + (t (progn (skip-syntax-backward " ") + (point))))) + +(defun reason-ro (&rest words) + "Build a regex matching iff at least a word in WORDS is present." + (concat "\\<" (regexp-opt words t) "\\>")) + +(defconst reason-find-phrase-beginning-regexp + (concat (reason-ro "end" "type" "module" "sig" "struct" "class" + "exception" "open" "let") + "\\|^#[ \t]*[a-z][_a-z]*\\>\\|;")) + +(defun reason-at-phrase-start-p () + "Return t if is looking at the beginning of a phrase. +A phrase starts when a toplevel keyword is at the beginning of a line." + (or (looking-at "#") + (looking-at reason-find-phrase-beginning-regexp))) + +(defun reason-find-phrase-beginning-backward () + "Find the beginning of a phrase and return point. +It scans code backwards, therefore the caller can assume that the +beginning of the phrase (if found) is always before the starting +point. No error is signalled and (point-min) is returned when a +phrease cannot be found." + (beginning-of-line) + (while (and (not (bobp)) (not (reason-at-phrase-start-p))) + (if (reason-inside-block-scope-p) + (reason-skip-back-to-open-delimiter) + (re-search-backward reason-find-phrase-beginning-regexp nil 'move))) + (point)) + +(defun reason-discover-phrase () + "Discover a Reason phrase in the buffer." + ;; TODO reason-with-internal-syntax ;; tuareg2 modifies the syntax table (removed for now) + ;; TODO stop-at-and feature for phrase detection (do we need it?) + ;; TODO tuareg2 has some custom logic for module and class (do we need it?) + (save-excursion + (let ((case-fold-search nil)) + (reason-skip-blank-and-comments) + (list (reason-find-phrase-beginning-backward) ;; beginning + (reason-find-phrase-end) ;; end + (save-excursion ;; end-with-comment + (reason-skip-blank-and-comments) + (point)))))) + +(defun reason-discover-phrase-debug () + "Discover a Reason phrase in the buffer (debug mode)." + (let ((triple (reason-discover-phrase))) + (message (concat "Evaluating: \"" (reason-fetch-phrase triple) "\"")) + triple)) + +(defun reason-fetch-phrase (triple) + "Fetch the phrase text given a TRIPLE." + (let* ((start (nth 0 triple)) + (end (nth 1 triple))) ;; we don't need end-with-comment + (buffer-substring-no-properties start end))) + +(defun reason-next-phrase () + "Skip to the beginning of the next phrase." + (cond + ((reason-at-phrase-start-p) (point)) + ((eolp) (progn + (forward-char 1) + (reason-skip-blank-and-comments) + (reason-next-phrase))) + ((reason-inside-block-scope-p) (progn (reason-skip-to-close-delimiter) + (reason-next-phrase))) + ((looking-at ";") (progn + (forward-char 1) + (reason-next-phrase))) + (t (progn (end-of-line) + (reason-next-phrase))))) + +(provide 'reason-interaction) + +;;; reason-interaction.el ends here diff --git a/emacs.d/vendor/reason-mode.el b/emacs.d/vendor/reason-mode.el new file mode 100644 index 000000000..789735955 --- /dev/null +++ b/emacs.d/vendor/reason-mode.el @@ -0,0 +1,242 @@ +;;; reason-mode.el --- A major mode for editing ReasonML -*-lexical-binding: t-*- +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;; Version: 0.4.0 +;; Author: Mozilla +;; Url: https://github.com/reasonml-editor/reason-mode +;; Keywords: languages, ocaml +;; Package-Requires: ((emacs "24.3")) + +;; This file is NOT part of GNU Emacs. + +;; This file is distributed under the terms of both the MIT license and the +;; Apache License (version 2.0). + +;;; Commentary: +;; This project provides useful functions and helpers for developing code +;; using the Reason programming language (https://facebook.github.io/reason). +;; +;; Reason is an umbrella project that provides a curated layer for OCaml. +;; +;; It offers: +;; - A new, familiar syntax for the battle-tested language that is OCaml. +;; - A workflow for compiling to JavaScript and native code. +;; - A set of friendly documentations, libraries and utilities. +;; +;; See the README.md for more details. + +;;; Code: + +(require 'reason-indent) +(require 'refmt) +(require 'reason-interaction) + +(eval-when-compile (require 'rx) + (require 'compile) + (require 'url-vars)) + +;; Syntax definitions and helpers +(defvar reason-mode-syntax-table + (let ((table (make-syntax-table))) + + ;; Operators + (dolist (i '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@)) + (modify-syntax-entry i "." table)) + + ;; Strings + (modify-syntax-entry ?\" "\"" table) + (modify-syntax-entry ?\\ "\\" table) + (modify-syntax-entry ?\' "_" table) + + ;; Comments + (modify-syntax-entry ?/ ". 124b" table) + (modify-syntax-entry ?* ". 23n" table) + (modify-syntax-entry ?\n "> b" table) + (modify-syntax-entry ?\^m "> b" table) + + table)) + +(defgroup reason nil + "Support for Reason code." + :link '(url-link "http://facebook.github.io/reason/") + :group 'languages) + +(defcustom reason-mode-hook nil + "Hook called by `reason-mode'." + :type 'hook + :group 'reason) + +;; Font-locking definitions and helpers +(defconst reason-mode-keywords + '("and" "as" + "else" "external" + "fun" "for" + "if" "impl" "in" "include" + "let" + "module" "match" "mod" "move" "mutable" + "open" + "priv" "pub" + "rec" "ref" "return" + "self" "static" "switch" "struct" "super" + "trait" "type" + "use" + "virtual" + "where" "when" "while")) + +(defconst reason-mode-consts + '("true" "false")) + +(defconst reason-special-types + '("int" "float" "string" "char" + "bool" "unit" "list" "array" "exn" + "option" "ref")) + +(defconst reason-camel-case + (rx symbol-start + (group upper (0+ (any word nonascii digit "_"))) + symbol-end)) + +(eval-and-compile + (defconst reason--char-literal-rx + (rx (seq (group "'") + (or (seq "\\" anything) + (not (any "'\\"))) + (group "'"))))) + +(defun reason-re-word (inner) + "Build a word regexp given INNER." + (concat "\\<" inner "\\>")) + +(defun reason-re-grab (inner) + "Build a grab regexp given INNER." + (concat "\\(" inner "\\)")) + +(defun reason-regexp-opt-symbols (words) + "Like `(regexp-opt words 'symbols)`, but will work on Emacs 23. +See rust-mode PR #42. +Argument WORDS argument to pass to `regexp-opt`." + (concat "\\_<" (regexp-opt words t) "\\_>")) + +;;; Syntax highlighting for Reason +(defvar reason-font-lock-keywords + `((,(reason-regexp-opt-symbols reason-mode-keywords) . font-lock-keyword-face) + (,(reason-regexp-opt-symbols reason-special-types) . font-lock-builtin-face) + (,(reason-regexp-opt-symbols reason-mode-consts) . font-lock-constant-face) + + (,reason-camel-case 1 font-lock-type-face) + + ;; Field names like `foo:`, highlight excluding the : + (,(concat (reason-re-grab reason-re-ident) ":[^:]") 1 font-lock-variable-name-face) + ;; Module names like `foo::`, highlight including the :: + (,(reason-re-grab (concat reason-re-ident "::")) 1 font-lock-type-face) + ;; Name punned labeled args like ::foo + (,(concat "[[:space:]]+" (reason-re-grab (concat "::" reason-re-ident))) 1 font-lock-type-face) + + ;; TODO jsx attribs? + (, + (concat "<[/]?" (reason-re-grab reason-re-ident) "[^>]*" ">") + 1 font-lock-type-face))) + +(defun reason-mode-try-find-alternate-file (mod-name extension) + "Switch to the file given by MOD-NAME and EXTENSION." + (let* ((filename (concat mod-name extension)) + (buffer (get-file-buffer filename))) + (if buffer (switch-to-buffer buffer) + (find-file filename)))) + +(defun reason-mode-find-alternate-file () + "Switch to implementation/interface file." + (interactive) + (let ((name buffer-file-name)) + (when (string-match "\\`\\(.*\\)\\.re\\([il]\\)?\\'" name) + (let ((mod-name (match-string 1 name)) + (e (match-string 2 name))) + (cond + ((string= e "i") + (reason-mode-try-find-alternate-file mod-name ".re")) + (t + (reason-mode-try-find-alternate-file mod-name ".rei"))))))) + +(defun reason--syntax-propertize-multiline-string (end) + "Propertize Reason multiline string. +Argument END marks the end of the string." + (let ((ppss (syntax-ppss))) + (when (eq t (nth 3 ppss)) + (let ((key (save-excursion + (goto-char (nth 8 ppss)) + (and (looking-at "{\\([a-z]*\\)|") + (match-string 1))))) + (when (search-forward (format "|%s}" key) end 'move) + (put-text-property (1- (match-end 0)) (match-end 0) + 'syntax-table (string-to-syntax "|"))))))) + +(defun reason-syntax-propertize-function (start end) + "Propertize Reason function. +Argument START marks the beginning of the function. +Argument END marks the end of the function." + (goto-char start) + (reason--syntax-propertize-multiline-string end) + (funcall + (syntax-propertize-rules + (reason--char-literal-rx (1 "\"") (2 "\"")) + ;; multi line strings + ("\\({\\)[a-z]*|" + (1 (prog1 "|" + (goto-char (match-end 0)) + (reason--syntax-propertize-multiline-string end))))) + (point) end)) + +(defvar reason-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-a" #'reason-mode-find-alternate-file) + (define-key map "\C-c\C-r" #'refmt-region-ocaml-to-reason) + (define-key map "\C-c\C-o" #'refmt-region-reason-to-ocaml) + map)) + +;;;###autoload +(define-derived-mode reason-mode prog-mode "Reason" + "Major mode for Reason code. + +\\{reason-mode-map}" + :group 'reason + :syntax-table reason-mode-syntax-table + :keymap reason-mode-map + + ;; Syntax + (setq-local syntax-propertize-function #'reason-syntax-propertize-function) + ;; Indentation + (setq-local indent-line-function 'reason-mode-indent-line) + ;; Fonts + (setq-local font-lock-defaults '(reason-font-lock-keywords)) + ;; Misc + (setq-local comment-start "/*") + (setq-local comment-end "*/") + (setq-local indent-tabs-mode nil) + ;; Allow paragraph fills for comments + (setq-local comment-start-skip "/\\*+[ \t]*") + (setq-local paragraph-start + (concat "^[ \t]*$\\|\\*)$\\|" page-delimiter)) + (setq-local paragraph-separate paragraph-start) + (setq-local require-final-newline t) + (setq-local normal-auto-fill-function nil) + (setq-local comment-multi-line t) + + (setq-local beginning-of-defun-function 'reason-beginning-of-defun) + (setq-local end-of-defun-function 'reason-end-of-defun) + (setq-local parse-sexp-lookup-properties t)) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.rei?\\'" . reason-mode)) + +(defun reason-mode-reload () + "Reload Reason mode." + (interactive) + (unload-feature 'reason-mode) + (unload-feature 'reason-indent) + (unload-feature 'reason-interaction) + (require 'reason-mode) + (reason-mode)) + +(provide 'reason-mode) + +;;; reason-mode.el ends here diff --git a/emacs.d/vendor/refmt.el b/emacs.d/vendor/refmt.el new file mode 100644 index 000000000..b9ea2b43f --- /dev/null +++ b/emacs.d/vendor/refmt.el @@ -0,0 +1,231 @@ +;;; refmt.el --- utility functions to format reason code + +;; Copyright (c) 2014 The go-mode Authors. All rights reserved. +;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved. + +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are +;; met: + +;; * Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; * Redistributions in binary form must reproduce the above +;; copyright notice, this list of conditions and the following disclaimer +;; in the documentation and/or other materials provided with the +;; distribution. +;; * Neither the name of the copyright holder nor the names of its +;; contributors may be used to endorse or promote products derived from +;; this software without specific prior written permission. + +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.) + +;;; Commentary: +;; + +;;; Code: + +(require 'cl-lib) + +(defcustom refmt-command "refmt" + "The 'refmt' command." + :type 'string + :group 're-fmt) + +(defcustom refmt-show-errors 'buffer + "Where to display refmt error output. +It can either be displayed in its own buffer, in the echo area, or not at all. +Please note that Emacs outputs to the echo area when writing +files and will overwrite refmt's echo output if used from inside +a `before-save-hook'." + :type '(choice + (const :tag "Own buffer" buffer) + (const :tag "Echo area" echo) + (const :tag "None" nil)) + :group 're-fmt) + +(defcustom refmt-width-mode nil + "Specify width when formatting buffer contents." + :type '(choice + (const :tag "Window width" window) + (const :tag "Fill column" fill) + (const :tag "None" nil)) + :group 're-fmt) + +;;;###autoload +(defun refmt-before-save () + "Add this to .emacs to run refmt on the current buffer when saving: + (add-hook 'before-save-hook 'refmt-before-save)." + (interactive) + (when (eq major-mode 'reason-mode) (refmt))) + +(defun reason--goto-line (line) + (goto-char (point-min)) + (forward-line (1- line))) + +(defun reason--delete-whole-line (&optional arg) + "Delete the current line without putting it in the `kill-ring'. +Derived from function `kill-whole-line'. ARG is defined as for that +function." + (setq arg (or arg 1)) + (if (and (> arg 0) + (eobp) + (save-excursion (forward-visible-line 0) (eobp))) + (signal 'end-of-buffer nil)) + (if (and (< arg 0) + (bobp) + (save-excursion (end-of-visible-line) (bobp))) + (signal 'beginning-of-buffer nil)) + (cond ((zerop arg) + (delete-region (progn (forward-visible-line 0) (point)) + (progn (end-of-visible-line) (point)))) + ((< arg 0) + (delete-region (progn (end-of-visible-line) (point)) + (progn (forward-visible-line (1+ arg)) + (unless (bobp) + (backward-char)) + (point)))) + (t + (delete-region (progn (forward-visible-line 0) (point)) + (progn (forward-visible-line arg) (point)))))) + +(defun reason--apply-rcs-patch (patch-buffer &optional start-pos) + "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer." + (setq start-pos (or start-pos (point-min))) + (let ((first-line (line-number-at-pos start-pos)) + (target-buffer (current-buffer)) + ;; Relative offset between buffer line numbers and line numbers + ;; in patch. + ;; + ;; Line numbers in the patch are based on the source file, so + ;; we have to keep an offset when making changes to the + ;; buffer. + ;; + ;; Appending lines decrements the offset (possibly making it + ;; negative), deleting lines increments it. This order + ;; simplifies the forward-line invocations. + (line-offset 0)) + (save-excursion + (with-current-buffer patch-buffer + (goto-char (point-min)) + (while (not (eobp)) + (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)") + (error "invalid rcs patch or internal error in reason--apply-rcs-patch")) + (forward-line) + (let ((action (match-string 1)) + (from (string-to-number (match-string 2))) + (len (string-to-number (match-string 3)))) + (cond + ((equal action "a") + (let ((start (point))) + (forward-line len) + (let ((text (buffer-substring start (point)))) + (with-current-buffer target-buffer + (cl-decf line-offset len) + (goto-char start-pos) + (forward-line (- from len line-offset)) + (insert text))))) + ((equal action "d") + (with-current-buffer target-buffer + (reason--goto-line (- (1- (+ first-line from)) line-offset)) + (cl-incf line-offset len) + (reason--delete-whole-line len))) + (t + (error "invalid rcs patch or internal error in reason--apply-rcs-patch"))))))))) + +(defun refmt--process-errors (filename tmpfile errorfile errbuf) + (with-current-buffer errbuf + (if (eq refmt-show-errors 'echo) + (progn + (message "%s" (buffer-string)) + (refmt--kill-error-buffer errbuf)) + (insert-file-contents errorfile nil nil nil) + ;; Convert the refmt stderr to something understood by the compilation mode. + (goto-char (point-min)) + (insert "refmt errors:\n") + (while (search-forward-regexp (regexp-quote tmpfile) nil t) + (replace-match (file-name-nondirectory filename))) + (compilation-mode) + (display-buffer errbuf)))) + +(defun refmt--kill-error-buffer (errbuf) + (let ((win (get-buffer-window errbuf))) + (if win + (quit-window t win) + (with-current-buffer errbuf + (erase-buffer)) + (kill-buffer errbuf)))) + +(defun apply-refmt (&optional start end from to) + (setq start (or start (point-min)) + end (or end (point-max)) + from (or from "re") + to (or to "re")) + (let* ((ext (file-name-extension buffer-file-name t)) + (bufferfile (make-temp-file "refmt" nil ext)) + (outputfile (make-temp-file "refmt" nil ext)) + (errorfile (make-temp-file "refmt" nil ext)) + (errbuf (if refmt-show-errors (get-buffer-create "*Refmt Errors*"))) + (patchbuf (get-buffer-create "*Refmt patch*")) + (coding-system-for-read 'utf-8) + (coding-system-for-write 'utf-8) + (width-args + (cond + ((equal refmt-width-mode 'window) + (list "--print-width" (number-to-string (window-body-width)))) + ((equal refmt-width-mode 'fill) + (list "--print-width" (number-to-string fill-column))) + (t + '())))) + (unwind-protect + (save-restriction + (widen) + (write-region start end bufferfile) + (if errbuf + (with-current-buffer errbuf + (setq buffer-read-only nil) + (erase-buffer))) + (with-current-buffer patchbuf + (erase-buffer)) + (if (zerop (apply 'call-process + refmt-command nil (list (list :file outputfile) errorfile) + nil (append width-args (list "--parse" from "--print" to bufferfile)))) + (progn + (call-process-region start end "diff" nil patchbuf nil "-n" "-" + outputfile) + (reason--apply-rcs-patch patchbuf start) + (message "Applied refmt") + (if errbuf (refmt--kill-error-buffer errbuf))) + (message "Could not apply refmt") + (if errbuf + (refmt--process-errors (buffer-file-name) bufferfile errorfile errbuf))))) + (kill-buffer patchbuf) + (delete-file errorfile) + (delete-file bufferfile) + (delete-file outputfile))) + +(defun refmt () + "Format the current buffer according to the refmt tool." + (interactive) + (apply-refmt)) + +(defun refmt-region-ocaml-to-reason (start end) + (interactive "r") + (apply-refmt start end "ml")) + +(defun refmt-region-reason-to-ocaml (start end) + (interactive "r") + (apply-refmt start end "re" "ml")) + +(provide 'refmt) + +;;; refmt.el ends here diff --git a/emacs.d/vendor/slack-snippets.el b/emacs.d/vendor/slack-snippets.el new file mode 100644 index 000000000..6bf933cfb --- /dev/null +++ b/emacs.d/vendor/slack-snippets.el @@ -0,0 +1,228 @@ +;;; private/grfn/slack-snippets.el -*- lexical-binding: t; -*- + +(require 's) +(require 'json) +(require 'dash) +(require 'dash-functional) +(require 'request) +(require 'subr-x) + +;;; +;;; Configuration +;;; + +(defvar slack/token nil + "Legacy (https://api.slack.com/custom-integrations/legacy-tokens) access token") + +(defvar slack/include-public-channels 't + "Whether or not to inclue public channels in the list of conversations") + +(defvar slack/include-private-channels 't + "Whether or not to inclue public channels in the list of conversations") + +(defvar slack/include-im 't + "Whether or not to inclue IMs (private messages) in the list of conversations") + +(defvar slack/include-mpim nil + "Whether or not to inclue multi-person IMs (multi-person private messages) in + the list of conversations") + +;;; +;;; Utilities +;;; + +(defmacro comment (&rest _body) + "Comment out one or more s-expressions" + nil) + +(defun ->list (vec) (append vec nil)) + +(defun json-truthy? (x) (and x (not (equal :json-false x)))) + +;;; +;;; Generic API integration +;;; + +(defvar slack/base-url "https://slack.com/api") + +(defun slack/get (path params &optional callback) + "params is an alist of query parameters" + (let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback))) + (params (car params-callback)) (callback (cdr params-callback)) + (params (append `(("token" . ,slack/token)) params)) + (url (concat (file-name-as-directory slack/base-url) path))) + (request url + :type "GET" + :params params + :parser 'json-read + :success (cl-function + (lambda (&key data &allow-other-keys) + (funcall callback data)))))) + +(defun slack/post (path params &optional callback) + (let* ((params-callback (if (functionp params) `(() . ,params) (cons params callback))) + (params (car params-callback)) (callback (cdr params-callback)) + (url (concat (file-name-as-directory slack/base-url) path))) + (request url + :type "POST" + :data (json-encode params) + :headers `(("Content-Type" . "application/json") + ("Authorization" . ,(format "Bearer %s" slack/token))) + :success (cl-function + (lambda (&key data &allow-other-keys) + (funcall callback data)))))) + + +;;; +;;; Specific API endpoints +;;; + +;; Users + +(defun slack/users (cb) + "Returns users as (id . name) pairs" + (slack/get + "users.list" + (lambda (data) + (->> data + (assoc-default 'members) + ->list + (-map (lambda (user) + (cons (assoc-default 'id user) + (assoc-default 'real_name user)))) + (-filter #'cdr) + (funcall cb))))) + +(comment + (slack/get + "users.list" + (lambda (data) (setq response-data data))) + + (slack/users (lambda (data) (setq --users data))) + + ) + +;; Conversations + +(defun slack/conversation-types () + (->> + (list (when slack/include-public-channels "public_channel") + (when slack/include-private-channels "private_channel") + (when slack/include-im "im") + (when slack/include-mpim "mpim")) + (-filter #'identity) + (s-join ","))) + +(defun channel-label (chan users-alist) + (cond + ((json-truthy? (assoc-default 'is_channel chan)) + (format "#%s" (assoc-default 'name chan))) + ((json-truthy? (assoc-default 'is_im chan)) + (let ((user-id (assoc-default 'user chan))) + (format "Private message with %s" (assoc-default user-id users-alist)))) + ((json-truthy? (assoc-default 'is_mpim chan)) + (->> chan + (assoc-default 'purpose) + (assoc-default 'value))))) + +(defun slack/conversations (cb) + "Calls `cb' with (id . '((label . \"label\") '(topic . \"topic\") '(purpose . \"purpose\"))) pairs" + (slack/get + "conversations.list" + `(("types" . ,(slack/conversation-types)) + ("exclude-archived" . "true")) + (lambda (data) + (setq --data data) + (slack/users + (lambda (users) + (->> data + (assoc-default 'channels) + ->list + (-filter + (lambda (chan) (channel-label chan users))) + (-map + (lambda (chan) + (cons (assoc-default 'id chan) + `((label . ,(channel-label chan users)) + (topic . ,(->> chan + (assoc-default 'topic) + (assoc-default 'value))) + (purpose . ,(->> chan + (assoc-default 'purpose) + (assoc-default 'value))))))) + (funcall cb))))))) + +(comment + (slack/get + "conversations.list" + '(("types" . "public_channel,private_channel,im,mpim")) + (lambda (data) (setq response-data data))) + + (slack/get + "conversations.list" + '(("types" . "im")) + (lambda (data) (setq response-data data))) + + (slack/conversations + (lambda (convos) (setq --conversations convos))) + + ) + +;; Messages + +(cl-defun slack/post-message + (&key text channel-id (on-success #'identity)) + (slack/post "chat.postMessage" + `((text . ,text) + (channel . ,channel-id) + (as_user . t)) + on-success)) + +(comment + + (slack/post-message + :text "hi slackbot" + :channel-id slackbot-channel-id + :on-success (lambda (data) (setq resp data))) + + (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan))) + (id (car chan))) + (propertize label 'channel-id id))) + --conversations) + + ) + +;;; +;;; Posting code snippets to slack +;;; + +(defun prompt-for-channel (cb) + (slack/conversations + (lambda (conversations) + (setq testing (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan))) + (id (car chan))) + (propertize label 'channel-id id))) + conversations)) + (ivy-read + "Select channel: " + ;; TODO want to potentially use purpose / topic stuff here + (-map (lambda (chan) (let ((label (assoc-default 'label (cdr chan))) + (id (car chan))) + (propertize label 'channel-id id))) + conversations) + :history 'slack/channel-history + :action (lambda (selected) + (let ((channel-id (get-text-property 0 'channel-id selected))) + (funcall cb channel-id) + (message "Sent message to %s" selected)))))) + nil) + +(defun slack-send-code-snippet (&optional snippet-text) + (interactive) + (when-let ((snippet-text (or snippet-text + (buffer-substring-no-properties (mark) (point))))) + (prompt-for-channel + (lambda (channel-id) + (slack/post-message + :text (format "```\n%s```" snippet-text) + :channel-id channel-id))))) diff --git a/emacs.d/wpc/casing.el b/emacs.d/wpc/casing.el new file mode 100644 index 000000000..0592d9ddd --- /dev/null +++ b/emacs.d/wpc/casing.el @@ -0,0 +1,39 @@ +;; casing.el --- Helper functions for formatting text -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; These functions are intended to be bound to KBDs for daily use and +;; refactoring. + +;;; Code: + +(require 's) +(require 'dash) + +;; todo - grab the string at point and replace it with the output of +;; each fn + +(defun caps->kebab (x) + "Change the casing of X from CAP_CASE to kebab-case." + (->> x + s-downcase + (s-replace "_" "-"))) + +(defun kebab->caps (x) + "Change the casing of X from CAP_CASE to kebab-case." + (->> x + s-upcase + (s-replace "-" "_"))) + +;;; Tests: + +(ert-deftest caps->kebab-test () + (should (string= (caps->kebab "CAPS_CASE_STRING") + "caps-case-string"))) + +(ert-deftest kebab->caps-test () + (should (string= (kebab->caps "kebab-case-string") + "KEBAB_CASE_STRING"))) + +(provide 'casing) +;;; casing.el ends here diff --git a/emacs.d/wpc/functions.el b/emacs.d/wpc/functions.el new file mode 100644 index 000000000..f2514a1be --- /dev/null +++ b/emacs.d/wpc/functions.el @@ -0,0 +1,227 @@ +;; functions.el --- Helper functions for my Emacs development -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; This file hopefully contains friendly APIs that making ELisp development more enjoyable. + +;;; Code: + +;; TODO: clean up this file so this isn't necessary +(setq evil-want-integration nil) +(require 'evil) + +(require 'projectile) +(require 'paredit) +(require 'term) +(require 'f) +(require 'yasnippet) +(require 'ido) + +(defun wpc/evil-window-vsplit-right () + (interactive) + (evil-window-vsplit) + (windmove-right)) + +(defun wpc/evil-window-split-down () + (interactive) + (evil-window-split) + (windmove-down)) + +(defun wpc/reindent-defun-and-align-clojure-map () + (interactive) + (call-interactively #'paredit-reindent-defun) + (call-interactively #'clojure-align)) + +(defun wpc/find-file () + "Prefer project-based file-finding if inside of project; otherwise gracefully fallback." + (interactive) + (with-current-buffer (current-buffer) + (if (projectile-project-p) + (call-interactively #'projectile-find-file) + (call-interactively #'find-file)))) + +(defun wpc/find-or-create-js-test () + (->> buffer-file-name + (s-chop-suffix ".js") + (s-append ".test.js") + (find-file))) + +(defun wpc/find-or-create-js-module () + (->> buffer-file-name + (s-chop-suffix ".test.js") + (s-append ".js") + (find-file))) + +(defun wpc/find-or-create-js-store () + (->> buffer-file-name + (s-replace "index.js" "store.js") + (find-file))) + +(defun wpc/find-or-create-js-component () + (->> buffer-file-name + (s-replace "store.js" "index.js") + (find-file))) + +(defun wpc/bind-ido-keys () + "Adds custom KBDs for ido. This function is recommended in the ido source code." + (define-key ido-completion-map (kbd "") #'ido-next-match) + (define-key ido-completion-map (kbd "") #'ido-prev-match)) + +(defun wpc/toggle-between-js-test-and-module () + "Toggle between a Javascript test or module." + (interactive) + (if (s-ends-with? ".test.js" buffer-file-name) + (wpc/find-or-create-js-module) + (if (s-ends-with? ".js" buffer-file-name) + (wpc/find-or-create-js-test) + (message "Not in a Javascript file. Exiting...")))) + +(defun wpc/toggle-between-js-component-and-store () + "Toggle between a React component and its Redux store." + (interactive) + (if (s-ends-with? "index.js" buffer-file-name) + (wpc/find-or-create-js-store) + (if (or (s-ends-with? "store.js" buffer-file-name) + (s-ends-with? "store.test.js" buffer-file-name)) + (wpc/find-or-create-js-component) + (message "Not in a React/Redux file. Exiting...")))) + +(defun wpc/read-file-as-string (filename) + (with-temp-buffer + (insert-file-contents filename) + (s-trim (buffer-string)))) + +(defun wpc/create-snippet () + "Creates a window split and then opens the Yasnippet editor." + (interactive) + (evil-window-vsplit) + (call-interactively #'yas-new-snippet)) + +(defun wpc/edit-init-el () + "Creates a window split and then edits the init.el file." + (interactive) + (evil-window-vsplit) + (find-file "~/.emacs.d/init.el")) + +(defun wpc/set-flow-executable () + (interactive) + (let* ((root (locate-dominating-file buffer-file-name "node_modules/flow-bin")) + (executable (car (file-expand-wildcards + (concat root "node_modules/flow-bin/*osx*/flow"))))) + (setq-local company-flow-executable executable) + ;; These are not necessary for this package, but a good idea if you use + ;; these other packages + (setq-local flow-minor-default-binary executable) + (setq-local flycheck-javascript-flow-executable executable))) + +(defun wpc/jump-to-parent-file () + "Jumps to a React store or component's parent file. Useful for store or index file." + (interactive) + (-> buffer-file-name + f-dirname + (f-join "..") + (f-join (f-filename buffer-file-name)) + find-file)) + +(defun wpc/tmux-emacs-windmove (dir) + "Move windows in a Tmux-friendly way." + (let* ((dir->opts '((left . ("-L" . windmove-left)) + (right . ("-R" . windmove-right)) + (above . ("-U" . windmove-up)) + (below . ("-D" . windmove-down)))) + (opts (alist-get dir dir->opts)) + (tmux-opt (car opts)) + (emacs-fn (cdr opts))) + (if (window-in-direction dir) + (funcall emacs-fn) + (shell-command (format "tmux select-pane %s" tmux-opt))))) + +(defun wpc/tmux-emacs-windmove-left () + (interactive) + (wpc/tmux-emacs-windmove 'left)) + +(defun wpc/tmux-emacs-windmove-right () + (interactive) + (wpc/tmux-emacs-windmove 'right)) + +(defun wpc/tmux-emacs-windmove-up () + (interactive) + (wpc/tmux-emacs-windmove 'above)) + +(defun wpc/tmux-emacs-windmove-down () + (interactive) + (wpc/tmux-emacs-windmove 'below)) + +(defun wpc/get-window-by-buffername (buffername) + "Finds a window by the name of the buffer it's hosting." + (let ((buffer (get-buffer buffername))) + (when buffer + (get-buffer-window buffer)))) + +(defun wpc/add-earmuffs (x) + "Returns X surrounded by asterisks." + (format "*%s*" x)) + +(defun wpc/get-default-shell () + (or explicit-shell-file-name + (getenv "SHELL") + (getenv "ESHELL"))) + +(defun wpc/find-terminal-buffer () + (get-buffer (wpc/add-earmuffs wpc/terminal-name))) + +(defun wpc/find-terminal-window () + (wpc/get-window-by-buffername (wpc/add-earmuffs wpc/terminal-name))) + +(defun wpc/create-terminal-session () + (wpc/evil-window-vsplit-right) + (ansi-term (wpc/get-default-shell) wpc/terminal-name)) + +(defun wpc/toggle-terminal () + "Toggles a custom terminal session in Emacs." + (interactive) + (let ((window (wpc/find-terminal-window))) + (if window + (delete-window window) + (wpc/find-or-create-terminal)))) + +(defun wpc/find-or-create-terminal () + (let ((buffer (wpc/find-terminal-buffer))) + (if buffer + (display-buffer buffer) + (wpc/create-terminal-session)))) + +(defun wpc/put-file-name-on-clipboard () + "Put the current file name on the clipboard" + (interactive) + (let ((filename (if (equal major-mode 'dired-mode) + default-directory + (buffer-file-name)))) + (when filename + (with-temp-buffer + (insert filename) + (clipboard-kill-region (point-min) (point-max))) + (message filename)))) + +(defun wpc/evil-replace-under-point () + "Faster than typing %s//thing/g" + (interactive) + (save-excursion + (evil-ex (concat "%s/\\b" (symbol-name (symbol-at-point)) "\\b/")))) + +(defun wpc/disable-linum-mode () + "Convenience function defined to make adding hooks easier without a lambda." + (linum-mode -1)) + +(defun wpc/disable-company-mode () + "Convenience function defined to make adding hooks easier without a lambda." + (company-mode -1)) + +(defun wpc/toggle-term-mode () + "Toggle between term-line-mode and temr-char-mode." + (if (term-in-line-mode) + (term-char-mode) + (term-line-mode))) + +(provide 'functions) +;;; functions.el ends here diff --git a/emacs.d/wpc/macros.el b/emacs.d/wpc/macros.el new file mode 100644 index 000000000..aedd6f5b3 --- /dev/null +++ b/emacs.d/wpc/macros.el @@ -0,0 +1,33 @@ +;;; macros.el --- Helpful variables for making my ELisp life more enjoyable -*- lexical-binding: t -*- +;; Authpr: William Carroll + +;;; Commentary: +;; This file contains helpful variables that I use in my ELisp development. + +;;; Code: + +(require 'dash) +(require 's) +(require 'string-functions) + +(defmacro xi (&rest FORMS) + `(lambda ,(--filter (s-contains? (symbol-name it) + (prin1-to-string FORMS)) + '(x1 x2 x3 x4 x5)) + ,FORMS)) + +(defmacro enable (mode) + "Helper for enabling MODE. Useful in `add-hook' calls." + `#'(lambda nil (,mode 1))) + +(defmacro disable (mode) + "Helper for disabling MODE. Useful in `add-hook' calls." + `#'(lambda nil (,mode -1))) + +(defmacro add-hooks (modes) + "Add multiple MODES for the CALLBACK." + `(dolist (mode ,modes) + (add-hook (symbol/ensure-hookified mode) ,callback))) + +(provide 'macros) +;;; macros.el ends here diff --git a/emacs.d/wpc/packages/wpc-clojure.el b/emacs.d/wpc/packages/wpc-clojure.el new file mode 100644 index 000000000..3644f76a7 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-clojure.el @@ -0,0 +1,63 @@ +;;; clojure.el --- My Clojure preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Hosting my Clojure tooling preferences + +;;; Code: + +;; Helper functions +(defun wpc/buffer-name-for-clojure-mode (mode) + (let* ((project-name (projectile-project-name)) + (cljs-name (concat "*cider-repl CLJS " project-name "*")) + (clj-name (concat "*cider-repl " project-name "*"))) + (cond ((eq mode 'clojurescript-mode) cljs-name) + ((eq mode 'clojure-mode) clj-name) + ((eq mode 'clojurec-mode) cljs-name)))) + +(defun wpc/repl-function-for-clojure-mode (mode) + (let ((project-name (projectile-project-name)) + (cljs-fn #'cider-jack-in-clojurescript) + (clj-fn #'cider-jack-in)) + (cond ((eq mode 'clojurescript-mode) cljs-fn) + ((eq mode 'clojure-mode) clj-fn) + ((eq mode 'clojurec-mode) cljs-fn)))) + +(defun wpc/find-or-create-clojure-or-clojurescript-repl () + (interactive) + (with-current-buffer (current-buffer) + (let ((buffer-name (wpc/buffer-name-for-clojure-mode major-mode)) + (repl-function (wpc/repl-function-for-clojure-mode major-mode))) + (if (get-buffer buffer-name) + (switch-to-buffer buffer-name) + (funcall repl-function))))) + +;; (defun wpc/evil-leader/set-key-for-clojure-modes (kbd callback) +;; (evil-leader/set-key-for-mode 'clojure-mode kbd callback) +;; (evil-leader/set-key-for-mode 'clojurec-mode kbd callback) +;; (evil-leader/set-key-for-mode 'clojurescript-mode kbd callback)) + +;; ;; clojure +;; (wpc/evil-leader/set-key-for-clojure-modes "d" #'cider-doc) +;; (wpc/evil-leader/set-key-for-clojure-modes "e" #'cider-eval-defun-at-point) +;; (wpc/evil-leader/set-key-for-clojure-modes "r" #'wpc/find-or-create-clojure-or-clojurescript-repl) + +(use-package cider + :general + (cider-repl-mode-map + "C-l" 'cider-repl-clear-buffer + "C-u" 'kill-whole-line + "" 'cider-repl-previous-input + "" 'cider-repl-next-input + "C-c 'j" 'wpc/find-or-create-clojure-or-clojurescript-repl) + (n + "M-." 'cider-find-var) + :config + (setq cider-cljs-lein-repl + "(do (require 'figwheel-sidecar.repl-api) + (figwheel-sidecar.repl-api/start-figwheel!) + (figwheel-sidecar.repl-api/cljs-repl))" + cider-prompt-for-symbol nil)) + +(provide 'wpc-clojure) +;;; wpc-clojure.el ends here diff --git a/emacs.d/wpc/packages/wpc-company.el b/emacs.d/wpc/packages/wpc-company.el new file mode 100644 index 000000000..ec96bdf90 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-company.el @@ -0,0 +1,24 @@ +;;; company.el --- Autocompletion package, company, preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Hosts my company mode preferences + +;;; Code: + +;; autocompletion client +(use-package company + :general + (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) + :config + (setq company-idle-delay 0) + (setq company-minimum-prefix-length 2) + (global-company-mode)) + +(provide 'wpc-company) +;;; company.el ends here diff --git a/emacs.d/wpc/packages/wpc-dired.el b/emacs.d/wpc/packages/wpc-dired.el new file mode 100644 index 000000000..1c45ec35e --- /dev/null +++ b/emacs.d/wpc/packages/wpc-dired.el @@ -0,0 +1,18 @@ +;;; dired.el --- My dired preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Hosts my attempts at configuring dired + +;;; Code: + +(require 'dired) +(general-def 'dired-mode-map + "c" 'find-file + "f" 'wpc/find-file + "-" 'dired-up-directory) +(general-add-hook 'dired-mode-hook (list (enable dired-hide-details-mode) + #'auto-revert-mode)) + +(provide 'wpc-dired) +;;; dired.el ends here diff --git a/emacs.d/wpc/packages/wpc-docker.el b/emacs.d/wpc/packages/wpc-docker.el new file mode 100644 index 000000000..586878fde --- /dev/null +++ b/emacs.d/wpc/packages/wpc-docker.el @@ -0,0 +1,18 @@ +;;; docker.el --- Docker preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; My Docker preferences and configuration + +;;; Code: + +(use-package docker + :config + (setenv "DOCKER_TLS_VERIFY" "1") + (setenv "DOCKER_HOST" "tcp://10.11.12.13:2376") + (setenv "DOCKER_MACHINE_NAME" "name")) + +(use-package dockerfile-mode) + +(provide 'wpc-docker) +;;; docker.el ends here diff --git a/emacs.d/wpc/packages/wpc-flycheck.el b/emacs.d/wpc/packages/wpc-flycheck.el new file mode 100644 index 000000000..66314af98 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-flycheck.el @@ -0,0 +1,14 @@ +;;; flycheck.el --- My flycheck configuration -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Hosts my Flycheck preferences + +;;; Code: + +(use-package flycheck + :config + (global-flycheck-mode)) + +(provide 'wpc-flycheck) +;;; flycheck.el ends here diff --git a/emacs.d/wpc/packages/wpc-git.el b/emacs.d/wpc/packages/wpc-git.el new file mode 100644 index 000000000..f07040fff --- /dev/null +++ b/emacs.d/wpc/packages/wpc-git.el @@ -0,0 +1,17 @@ +;;; git.el --- My version control preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Things related to git, magit, etc belong here + +;;; Code: + +(use-package git-timemachine) + +(use-package magit) + +(use-package magit-gh-pulls + :ghook ('magit-mode-hook #'turn-on-magit-gh-pulls)) + +(provide 'wpc-git) +;;; git.el ends here diff --git a/emacs.d/wpc/packages/wpc-haskell.el b/emacs.d/wpc/packages/wpc-haskell.el new file mode 100644 index 000000000..a37a84030 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-haskell.el @@ -0,0 +1,32 @@ +;;; haskell.el --- My Haskell preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Hosts my Haskell development preferences + +;;; Code: + +;; Haskell support +(use-package intero + :config + (intero-global-mode 1)) + +;; text objects for Haskell +(quelpa '(evil-text-objects-haskell + :fetcher github + :repo "urbint/evil-text-objects-haskell")) +(require 'evil-text-objects-haskell) + +(use-package haskell-mode + :gfhook #'evil-text-objects-haskell/install + :after (intero evil-text-objects-haskell) + :config + (flycheck-add-next-checker 'intero 'haskell-hlint) + (let ((m-symbols + '(("`mappend`" . "⊕") + ("<>" . "⊕")))) + (dolist (item m-symbols) (add-to-list 'haskell-font-lock-symbols-alist item))) + (setq haskell-font-lock-symbols t)) + +(provide 'wpc-haskell) +;;; haskell.el ends here diff --git a/emacs.d/wpc/packages/wpc-javascript.el b/emacs.d/wpc/packages/wpc-javascript.el new file mode 100644 index 000000000..5dcd5937b --- /dev/null +++ b/emacs.d/wpc/packages/wpc-javascript.el @@ -0,0 +1,87 @@ +;;; javascript.el --- My Javascript preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; This module hosts my Javascript tooling preferences + +;;; Code: + +;; Helper functions +(defun wpc/indium-setup (url) + "Setup the indium environment using URL." + (indium-eval (format "window.dispatchEvent(new CustomEvent('patch', {detail: {url: %s}}" url))) + +(defun wpc/insert-flow-annotation () + "Insert a flow type annotation to the beginning of a buffer." + (interactive) + (save-excursion + (goto-char (point-min)) + (insert "// @flow\n"))) + +;; ;; javascript +;; (evil-leader/set-key-for-mode 'rjsx-mode "t" #'wpc/toggle-between-js-test-and-module) +;; (evil-leader/set-key-for-mode 'rjsx-mode "x" #'wpc/toggle-between-js-component-and-store) +;; (evil-leader/set-key-for-mode 'rjsx-mode "u" #'wpc/jump-to-parent-file) + +;; javascript setup +(use-package indium + :hook (indium-update-script-source . wpc/indium-setup)) + +;; javascript text objects +(quelpa '(evil-text-objects-javascript + :fetcher github + :repo "urbint/evil-text-objects-javascript")) +(require 'evil-text-objects-javascript) + +;; Flow for Javascript +(use-package flow-minor-mode + :hook js2-mode + :requires evil-leader + :config + (evil-leader/set-key-for-mode 'rjsx-mode "F" #'wpc/insert-flow-annotation)) + +(use-package company-flow + :after (company) + :hook (rjsx-mode . wpc/set-flow-executable) + :config + (add-to-list 'company-flow-modes 'rjsx-mode) + (add-to-list 'company-backends 'company-flow)) + +(use-package flycheck-flow + :after (flycheck) + :config + (flycheck-add-mode 'javascript-flow 'rjsx-mode) + (flycheck-add-mode 'javascript-flow 'flow-minor-mode) + (flycheck-add-mode 'javascript-eslint 'flow-minor-mode) + (flycheck-add-next-checker 'javascript-flow 'javascript-eslint)) + +;; front-end indentation +(setq js-indent-level 2 + css-indent-offset 2) + +;; eslint integration with flycheck +(setq flycheck-javascript-eslint-executable "~/urbint/grid-front-end/node_modules/.bin/eslint") + +;; JS autoformatting +(use-package prettier-js + :after (rjsx-mode) + :ghook ('(rjsx-mode-hook + js2-mode-hook + json-mode-hook + css-mode-hook))) + +;; JSX highlighting +(use-package rjsx-mode + :after (evil-text-objects-javascript) + :general + (general-unbind rjsx-mode-map "<" ">" "C-d") + (n rjsx-mode-map + "K" 'flow-minor-type-at-pos) + :gfhook #'evil-text-objects-javascript/install + :mode "\\.js\\'" + :config + (setq js2-mode-show-parse-errors nil + js2-mode-show-strict-warnings nil)) + +(provide 'wpc-javascript) +;;; wpc-javascript.el ends here diff --git a/emacs.d/wpc/packages/wpc-keybindings.el b/emacs.d/wpc/packages/wpc-keybindings.el new file mode 100644 index 000000000..0e74ce68b --- /dev/null +++ b/emacs.d/wpc/packages/wpc-keybindings.el @@ -0,0 +1,131 @@ +;;; keybindings.el --- My Evil preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; This module hosts my Evil preferences + +;;; Code: + +(quelpa + '(general + :repo "noctuid/general.el" + :fetcher github)) +(general-evil-setup t) + +;; vim... +(use-package evil + :general + (m + "RET" 'evil-goto-line + "H" 'evil-first-non-blank + "L" 'evil-end-of-line + "-" 'dired-jump + "sl" 'wpc/evil-window-vsplit-right + "sh" 'evil-window-vsplit + "sk" 'evil-window-split + "sj" 'wpc/evil-window-split-down + "sj" 'wpc/evil-window-split-down) + (general-unbind m "M-." "C-p") + (general-unbind n "s" "M-.") + (general-unbind i "C-d" "C-a" "C-e" "C-n" "C-p" "C-k") + (evil-ex-map + "M-p" 'previous-complete-history-element + "M-n" 'next-complete-history-element) + :init + (setq evil-want-integration nil) + :config + (setq evil-symbol-word-search t) + (evil-mode 1)) + +;; evil keybindings +(use-package evil-collection + :after evil + :config + (evil-collection-init)) + +;; expose a leader key +(use-package evil-leader + :after (evil counsel) + :config + (global-evil-leader-mode) + (evil-leader/set-leader "") + ;; global + (evil-leader/set-key + "i" #'counsel-semantic-or-imenu + "j" #'jump-to-register + "h" #'help + "a" #'wpc/toggle-terminal + "p" #'counsel-git-grep + "P" #'counsel-git-grep + "f" #'wpc/find-file + "N" #'smerge-next + "P" #'smerge-prev + "s" #'slack-send-code-snippet + "S" #'slack-select-unread-rooms + "b" #'ivy-switch-buffer + "gs" #'magit-status + "es" #'wpc/create-snippet + "ev" #'wpc/edit-init-el + "B" #'magit-blame + "w" #'save-buffer + "x" #'evil-save-and-close + "W" #'save-all-buffers + "r" #'wpc/evil-replace-under-point + )) + +;; create comments easily +(use-package evil-commentary + :after (evil) + :config + (evil-commentary-mode)) + +;; evil surround +(use-package evil-surround + :after (evil) + :config + (global-evil-surround-mode 1)) + +;; Custom minor mode that ensures that my kbds are available no matter which major +;; or minor modes are active. +(add-hook 'after-load-functions #'ensure-william-carroll-kbds) + +(defun ensure-william-carroll-kbds (_ignore) + "Try to ensure that my keybindings retain priority over other minor modes." + (unless (eq (caar minor-mode-map-alist) 'wpc/kbds-minor-mode) + (let ((mykbds (assq 'wpc/kbds-minor-mode minor-mode-map-alist))) + (assq-delete-all 'wpc/kbds-minor-mode minor-mode-map-alist) + (add-to-list 'minor-mode-map-alist mykbds)))) + +(defvar wpc/kbds + (let ((map (make-sparse-keymap))) + (bind-keys :map map + ("M-q" . delete-window) + ("C-x C-;" . comment-or-uncomment-region) + ("C-x h" . help) + ("" . toggle-frame-fullscreen) + ("" . ffap-other-window) + ("M-h" . wpc/tmux-emacs-windmove-left) + ("M-l" . wpc/tmux-emacs-windmove-right) + ("M-k" . wpc/tmux-emacs-windmove-up) + ("M-j" . wpc/tmux-emacs-windmove-down) + ("M--" . split-window-below) + ("M-\\" . split-window-right) + ("M-q" . delete-window)) + map) + "William Carroll's keybindings that should have the highest precedence.") + +(define-minor-mode wpc/kbds-minor-mode + "A minor mode so that my key settings override annoying major modes." + :init-value t + :lighter " wpc/kbds" + :keymap wpc/kbds) + +;; allow jk to escape +(use-package key-chord + :after (evil) + :config + (key-chord-mode 1) + (key-chord-define evil-insert-state-map "jk" 'evil-normal-state)) + +(provide 'wpc-keybindings) +;;; wpc-keybindings.el ends here diff --git a/emacs.d/wpc/packages/wpc-lisp.el b/emacs.d/wpc/packages/wpc-lisp.el new file mode 100644 index 000000000..487201d47 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-lisp.el @@ -0,0 +1,37 @@ +;;; lisp.el --- Generic LISP preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; This hosts things like Paredit settings + +;;; Code: + +(defconst wpc/lisp-mode-hooks + '(emacs-lisp-mode-hook + clojure-mode-hook + clojurescript-mode-hook)) + +;; Elisp +(use-package elisp-slime-nav + :ghook + 'emacs-lisp-mode + 'ielm-mode) + +;; paredit LISP editing +(use-package paredit + :general + (general-unbind paredit-mode-map "C-j" "M-q") + (n paredit-mode-map + ">)" 'paredit-forward-slurp-sexp + "<(" 'paredit-backward-slurp-sexp + "<)" 'paredit-forward-barf-sexp + ">(" 'paredit-backward-barf-sexp + ">e" 'paredit-move-forward + "f" 'paredit-move-backward + " + +;;; Commentary: +;; This is the home of any configuration that couldn't find a better home. + +;;; Code: + +;; disable custom variable entries from being written to ~/.emacs.d/init.el +(setq custom-file "~/.emacs.d/custom.el") +(load custom-file 'noerror) + +;; transparently edit compressed files +(auto-compression-mode t) + +;; 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) + +;; create file bookmarks +(set-register ?e '(file . "~/.emacs.d/wpc/packages")) +(set-register ?u '(file . "~/urbint")) +(set-register ?d '(file . "~/dotfiles")) +(set-register ?D '(file . "~/Dropbox")) +(set-register ?o '(file . "~/Dropbox/org/")) +(set-register ?c '(file . "~/Dropbox/org/chains.org")) +(set-register ?b '(file . "~/Dropbox/org/backlog.org")) +(set-register ?p '(file . "~/urbint/grid-front-end")) + +;; persist history etc b/w Emacs sessions +(setq desktop-save 'if-exists) +(desktop-save-mode 1) +(setq desktop-globals-to-save + (append '((extended-command-history . 30) + (file-name-history . 100) + (grep-history . 30) + (compile-history . 30) + (minibuffer-history . 50) + (query-replace-history . 60) + (read-expression-history . 60) + (regexp-history . 60) + (regexp-search-ring . 20) + (search-ring . 20) + (shell-command-history . 50) + tags-file-name + register-alist))) + +;; config Emacs to use $PATH values +(use-package exec-path-from-shell + :if (memq window-system '(mac ns)) + :config + (exec-path-from-shell-initialize)) + +;; Emacs autosave, backup, interlocking files +(setq auto-save-default nil + make-backup-files nil + create-lockfiles nil) + +;; ensure code wraps at 80 characters by default +(setq fill-column 80) + +(put 'narrow-to-region 'disabled nil) + +;; trim whitespace on save +(add-hook 'before-save-hook #'delete-trailing-whitespace) + +;; use tabs instead of spaces +(setq-default indent-tabs-mode nil) + +;; automatically follow symlinks +(setq vc-follow-symlinks t) + +;; fullscreen settings +(setq ns-use-native-fullscreen nil) + +;; auto-close parens, brackets, quotes +(electric-pair-mode 1) + +(use-package oauth2 + :init + ;; necessary to remove warnings: https://emacs.stackexchange.com/questions/37036/where-are-these-variables-defined-bytecomp-warnings + (defvar url-http-extra-headers ()) + (defvar oauth--token-data ()) + (defvar url-callback-function ()) + (defvar url-callback-arguments ())) + +(use-package smex + :general + ("M-x" 'smex) + :ghook ('ido-setup-hook #'wpc/bind-ido-keys) + :config + (smex-initialize)) + +(use-package flx-ido + :after (smex) + :config + (flx-ido-mode 1) + (setq ido-enable-flex-matching t + ido-use-faces nil)) + +(use-package swiper + :general + ("C-s" 'swiper + "C-r" 'swiper)) + +(use-package yasnippet + :config + (yas-global-mode 1)) + +(use-package ace-window + :general + ("C-x o" 'ace-window) + :config + (setq aw-keys '(?a ?s ?d ?f ?j ?k ?k ?\;))) + +(use-package projectile + :config + (projectile-mode t)) + +(use-package counsel + :config + (defun wpc/counsel-git-grep () + (interactive) + (let ((maybe-symbol (wpc/string-symbol-at-point))) + (if (string= maybe-symbol "nil") + (counsel-git-grep) + (counsel-git-grep nil maybe-symbol))))) + +;; projectile intergration with ivy +(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")) + +(use-package markdown-mode) +(use-package yaml-mode) + +(provide 'wpc-misc) +;;; misc.el ends here diff --git a/emacs.d/wpc/packages/wpc-org.el b/emacs.d/wpc/packages/wpc-org.el new file mode 100644 index 000000000..28f1f9308 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-org.el @@ -0,0 +1,45 @@ +;;; org.el --- My org preferences -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Hosts my org mode preferences + +;;; Code: + +;; Griffin's org clubhouse integration +;;(load-file "~/.emacs.d/vendor/org-clubhouse.el") +;;(setq org-clubhouse-auth-token (wpc/read-file-as-string "~/dotfiles/configs/secrets/clubhouse_token.txt") +;; org-clubhouse-team-name "urbint") +;;(add-hook 'org-mode-hook #'org-clubhouse-mode) + +(use-package org + :ghook (nil (disable linum-mode)) + :general + (:prefix "C-c" + "l" 'org-store-link + "a" 'org-agenda + "c" 'org-capture) + :preface + (defconst wpc-org-directory + "~/Dropbox/org") + (defconst ub-org-directory + "~/Dropbox/sprint-planning-staging") + (defun wpc/org-file (file) + (f-join wpc-org-directory (f-swap-ext file "org"))) + (defun ub/org-file (file) + (f-join ub-org-directory (f-swap-ext file "org"))) + :config + (setq org-default-notes-file (wpc/org-file "notes")) + (setq org-log-done 'time) + (setq org-agenda-files (list (wpc/org-file "work") + (wpc/org-file "personal"))) + (setq org-capture-templates + `(("t" "Todo" entry (file+heading ,(ub/org-file "index") "Ideas") + "* TODO %?\n %i")))) + +(use-package org-bullets + :after (org) + :ghook ('org-mode-hook (enable org-bullets-mode))) + +(provide 'wpc-org) +;;; org.el ends here diff --git a/emacs.d/wpc/packages/wpc-package.el b/emacs.d/wpc/packages/wpc-package.el new file mode 100644 index 000000000..472f9df62 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-package.el @@ -0,0 +1,32 @@ +;;; package.el --- My package configuration -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; This module hosts all of the settings required to work with ELPA, +;; MELPA, QUELPA, and co. + +;;; Code: + +(require 'package) +(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) +(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t) +(package-initialize) + +(if (require 'quelpa nil t) + (quelpa-self-upgrade) + (with-temp-buffer + (url-insert-file-contents "https://raw.github.com/quelpa/quelpa/master/bootstrap.el") + (eval-buffer))) + +(require 'use-package) +(setq use-package-always-ensure t) +;; Remove this line once general integration with use-package calls +;; with-eval-after-load 'use-package-core instead of 'use-package +(require 'general) + +(add-to-list 'load-path "~/.emacs.d/vendor/") +(add-to-list 'load-path "~/.emacs.d/wpc/") +(add-to-list 'load-path "~/.emacs.d/wpc/packages") + +(provide 'wpc-package) +;;; package.el ends here diff --git a/emacs.d/wpc/packages/wpc-slack.el b/emacs.d/wpc/packages/wpc-slack.el new file mode 100644 index 000000000..912cd1457 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-slack.el @@ -0,0 +1,65 @@ +;;; slack.el --- Slack settings -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Wrangling the Slack client in Emacs + +;;; Code: + +;; Griffin's Slack plugin +;;(defconst slack/token (wpc/read-file-as-string "~/dotfiles/configs/secrets/slack_token.txt")) +;;(defconst wpc/slack-client-secret (wpc/read-file-as-string "~/dotfiles/configs/secrets/slack-client-secret")) +(defconst wpc/slack-client-secret "uncomment above line one day") +(load-file "~/.emacs.d/vendor/slack-snippets.el") + +;; Slack client +(use-package slack + :general + (n slack-info-mode-map + :prefix "," + "u" 'slack-room-update-messages) + (n slack-mode-map + :prefix "," + "c" 'slack-buffer-kill + "ra" 'slack-message-add-reaction + "rr" 'slack-message-remove-reaction + "rs" 'slack-message-show-reaction-users + "pl" 'slack-room-pins-list + "pa" 'slack-message-pins-add + "pr" 'slack-message-pins-remove + "mm" 'slack-message-write-another-buffer + "me" 'slack-message-edit + "md" 'slack-message-delete + "u" 'slack-room-update-messages + "2" 'slack-message-embed-mention + "3" 'slack-message-embed-channel) + (n slack-mode-map + "C-n" 'slack-buffer-goto-next-message + "C-p" 'slack-buffer-goto-prev-message) + (n slack-edit-message-mode-map + :prefix "," + "k" 'slack-message-cancel-edit + "s" 'slack-message-send-from-buffer + "2" 'slack-message-embed-mention + "3" 'slack-message-embed-channel) + :commands (slack-start) + :init + (setq slack-buffer-emojify t) ;; if you want to enable emoji, default nil + (setq slack-prefer-current-team t) + :config + (add-hook 'slack-mode-hook (disable company-mode)) + (setq slack-buffer-function #'switch-to-buffer) + (slack-register-team + :name "urbint" + :default t + :client-id "william@urbint.com" + :client-secret wpc/slack-client-secret + :token slack-token + :subscribed-channels '(dev dev_questions general random recruiting) + :full-and-display-names t)) + +(use-package circe) +(use-package emojify) + +(provide 'wpc-slack) +;;; wpc-slack.el ends here diff --git a/emacs.d/wpc/packages/wpc-terminal.el b/emacs.d/wpc/packages/wpc-terminal.el new file mode 100644 index 000000000..36a24bbb3 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-terminal.el @@ -0,0 +1,19 @@ +;;; terminal.el --- My cobbled together terminal -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; My attempts at creating a sane Emacs terminal + +;;; Code: + +(setq wpc/terminal-name "wpc/terminal") + +;; 256 color support in term (instead of 8) +(use-package xterm-color) + +(use-package term + :config + (setq explicit-shell-file-name "/bin/zsh")) + +(provide 'wpc-terminal) +;;; terminal.el ends here diff --git a/emacs.d/wpc/packages/wpc-ui.el b/emacs.d/wpc/packages/wpc-ui.el new file mode 100644 index 000000000..fff2db3e1 --- /dev/null +++ b/emacs.d/wpc/packages/wpc-ui.el @@ -0,0 +1,136 @@ +;;; ui.el --- Any related to the UI/UX goes here -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; Hosts font settings, scrolling, color schemes. + +;;; Code: + +;; increase line height +(setq-default line-spacing 4) + +;; change font +(add-to-list 'default-frame-alist '(font . "Operator Mono-10")) + +;; smooth scrolling settings +(setq scroll-step 1 + scroll-conservatively 10000) + +;; theme mgt +(use-package cycle-themes + :after (doom-themes) + :config + ;; NOTE: may want to use `defconst' here + (setq wpc/doom-themes + (->> (custom-available-themes) + (-map #'symbol-name) + (-filter (-partial #'s-starts-with? "doom-")) + (-map #'intern))) + (setq cycle-themes-theme-list wpc/doom-themes)) + +;; clean up modeline +(use-package diminish + :after (yasnippet ivy which-key) + :config + (diminish 'evil-commentary-mode) + (diminish 'flycheck-mode "Flycheck") + (diminish 'company-mode "Company") + (diminish 'auto-revert-mode) + (diminish 'which-key-mode) + (diminish 'yas-minor-mode) + (diminish 'ivy-mode)) + +;; disable startup screen +(setq inhibit-startup-screen t) + +;; disable toolbar +(tool-bar-mode -1) + +;; enable line numbers +(general-add-hook '(prog-mode-hook + text-mode-hook + conf-mode-hook) + (enable linum-mode)) +;;(add-hook 'after-init-hook (lambda () (set-face-foreground 'linum "#da5468"))) + +;; set default buffer for Emacs +(setq initial-buffer-choice "~/urbint/grid-front-end") + +;; transparent Emacs +(set-frame-parameter (selected-frame) 'alpha '(100 . 100)) +(add-to-list 'default-frame-alist '(alpha . (100 . 100))) + +;; premium Emacs themes +(use-package doom-themes + :config + (setq doom-themes-enable-bold t + doom-themes-enable-italic t) + (load-theme 'doom-solarized-light 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 + (ivy-mode t)) + +;; icons for Ivy +(use-package all-the-icons-ivy + :after (ivy) + :config + (all-the-icons-ivy-setup)) + +;; disable menubar +(menu-bar-mode -1) +(when (string-equal system-type "darwin") + (setq ns-auto-hide-menu-bar t)) + +;; highlight lines that are over 100 characters long +(use-package whitespace + :config + (setq whitespace-line-column 100) + (setq whitespace-style '(face lines-tail))) + +;; disable GUI scrollbars +(when (display-graphic-p) + (scroll-bar-mode -1)) + +;; rebalance emacs windows after splits are created +(defadvice split-window-below (after rebalance-windows activate) + (balance-windows)) + +(defadvice split-window-right (after rebalance-windows activate) + (balance-windows)) + +(defadvice delete-window (after rebalance-window activate) + (balance-windows)) + +;; dirname/filename instead of filename +(setq uniquify-buffer-name-style 'forward) + +;; highlight matching parens, brackets, etc +(show-paren-mode 1) + +;; GUI alerts in emacs +(use-package alert + :commands (alert) + :config + (setq alert-default-style 'notifier)) + +;; focus mode +(quelpa '(zen-mode + :fetcher github + :repo "aki237/zen-mode")) +(require 'zen-mode) + +;; focus mode +(use-package writeroom-mode) + +(provide 'wpc-ui) +;;; ui.el ends here diff --git a/emacs.d/wpc/string-functions.el b/emacs.d/wpc/string-functions.el new file mode 100644 index 000000000..91b46b5b4 --- /dev/null +++ b/emacs.d/wpc/string-functions.el @@ -0,0 +1,41 @@ +;; functions.el --- String helper functions for my Emacs development -*- lexical-binding: t -*- +;; Author: William Carroll + +;;; Commentary: +;; String & Symbol helpers! + +;;; Code: + +(require 'dash) +(require 's) + +;; Strings +(defun string/hookify (x) + "Append \"-hook\" to X." + (s-append "-hook" x)) + +(defun string/ensure-hookified (x) + "Ensure that X has \"-hook\" appended to it." + (if (s-ends-with? "-hook" x) + x + (string/hookify x))) + +;; 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/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)) + +(provide 'string-functions) +;;; string-functions.el ends here diff --git a/emacs.d/wpc/variables.el b/emacs.d/wpc/variables.el new file mode 100644 index 000000000..af6bfde45 --- /dev/null +++ b/emacs.d/wpc/variables.el @@ -0,0 +1,18 @@ +;;; variables.el --- Helpful variables for making my ELisp life more enjoyable -*- lexical-binding: t -*- +;; Authpr: William Carroll + +;;; Commentary: +;; This file contains helpful variables that I use in my ELisp development. + +;;; Code: + +(defvar wpc/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]) + "This variable stores all of the mouse-related keybindings that Emacs recognizes.") + +(provide 'variables) +;;; variables.el ends here