Skip to content

jwinder/emacs-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jwinder emacs config

introduction

I prefer to clone this repository as $HOME/emacs-config and symlink it to $HOME/.emacs.d:

$ git clone git@github.com:jwinder/emacs-config.git $HOME/emacs-config
$ ln -s $HOME/emacs-config $HOME/.emacs.d

Optionally, custom settings can be placed inside of a private directory of elisp files, or a private.org file of elisp code blocks.

$ ln -s /custom/path/private $HOME/emacs-config/private # directory containing .el files
$ ln -s /custom/path/private.org $HOME/emacs-config/private.org # file containing elisp blocks

Now, start emacs.

packages

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t)
(package-initialize)
(unless (file-exists-p package-user-dir) (package-refresh-contents))

(require 'use-package)
(setq use-package-always-ensure t)

(setq package-check-signature nil)
(use-package gnu-elpa-keyring-update)
(setq package-check-signature 'allow-unsigned)

(use-package xterm-color)

(use-package beacon)
(beacon-mode 1)
(setq beacon-blink-when-window-scrolls nil
      beacon-blink-when-window-changes t
      beacon-blink-when-buffer-changes t)

(use-package dash)
(use-package pcache)
(use-package f)
(use-package s)
(defalias 's-empty? 's-blank?)
(defalias 's-nonempty? 's-present?)
(defalias 's-nonempty-or-nil 's-presence)

(use-package yaml)

(use-package helpful)
(fset 'describe-key 'helpful-key)
(fset 'describe-function 'helpful-callable)
(fset 'describe-variable 'helpful-variable)
(fset 'describe-symbol 'helpful-symbol)

(use-package golden-ratio)
(golden-ratio-mode 1)

(use-package magit) ;; brings in transient
(use-package transient)

(when (and (executable-find "cmake") (executable-find "libtool") (executable-find "vterm-ctrl"))
  (use-package vterm))

(use-package verb)
(use-package curl-to-elisp)

(use-package browse-at-remote)

(use-package expand-region)

(use-package multiple-cursors)
(defun mc/prompt-for-inclusion-in-whitelist (original-command)
  "Rewrite of `mc/prompt-for-inclusion-in-whitelist' to not ask yes/no for every newly seen command."
  (add-to-list 'mc/cmds-to-run-for-all original-command)
  (mc/save-lists)
  t)

(use-package smartparens)
(require 'smartparens-config)
(smartparens-global-mode t)

(use-package rainbow-delimiters)
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)

(use-package rainbow-mode)

(use-package edit-indirect)

(use-package yasnippet)
(use-package yasnippet-snippets)

(use-package flycheck)
(use-package flycheck-eglot)
(add-hook 'js-json-mode-hook 'flycheck-mode)

(use-package counsel) ;; brings in ivy & swiper
(use-package counsel-projectile)

(use-package company)
(setq company-idle-delay nil)
(setq company-format-margin-function nil)
(global-company-mode 1)

(use-package dape)

(use-package wgrep)
(setq wgrep-auto-save-buffer t)

(use-package undo-tree)
(global-undo-tree-mode)

(use-package dumb-jump)
(setq dumb-jump-prefer-searcher 'rg)
(setq dumb-jump-force-searcher 'rg) ;; for some reason, dumb-jump xref broke recently so this is needed too
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
(setq xref-show-definitions-function #'xref-show-definitions-completing-read)

(use-package editorconfig)
(editorconfig-mode 1)

(use-package nodejs-repl)

(use-package json-navigator)

(use-package yaml-mode)

(use-package markdown-mode)

(use-package cmake-mode)

(use-package dockerfile-mode)

(use-package scala-mode)
(use-package sbt-mode)

(use-package typescript-mode)

(use-package go-mode)
(use-package ob-go)
(add-hook 'before-save-hook 'gofmt-before-save)

(use-package ob-mongo)

(use-package coffee-mode)
(setq coffee-tab-width 2)

(use-package terraform-mode)
(add-to-list 'auto-mode-alist '("\\.tfstate$" . js-mode))

(use-package protobuf-mode)

(use-package groovy-mode)
(add-to-list 'auto-mode-alist '("Jenkinsfile$" . groovy-mode))

(use-package glsl-mode)
(add-to-list 'auto-mode-alist '("\\.vert$" . glsl-mode))
(add-to-list 'auto-mode-alist '("\\.frag$" . glsl-mode))
(add-to-list 'auto-mode-alist '("\\.vs$" . glsl-mode))
(add-to-list 'auto-mode-alist '("\\.fs$" . glsl-mode))

(use-package jsonnet-mode)
(add-to-list 'auto-mode-alist '("\\.libsonnet\\'" . jsonnet-mode))

;; eventually replace with built-in treesit support, maybe https://github.com/renzmann/treesit-auto
;; or wait for emacs 30+ and see how treesit evolves: https://github.com/renzmann/treesit-auto?tab=readme-ov-file#caveats
(use-package tree-sitter)
(use-package tree-sitter-langs)
(add-to-list 'tree-sitter-major-mode-language-alist '(js-json-mode . json))
(add-hook 'tree-sitter-after-on-hook '(lambda () (tree-sitter-hl-mode 1)))
(global-tree-sitter-mode 1)

(when (executable-find "docker")
  (use-package docker)
  (global-set-key (kbd "C-c d") 'docker))

(when (executable-find "kubectl")
  (use-package kubel))

helper functions

These are non-interactive library functions useful in other parts of this config

(require 'dash)
(require 'pcache)
(require 's)
(require 'f)
(require 'vc)

(defconst jw--init-org-file (expand-file-name "emacs.org" user-emacs-directory))
(defconst jw--init-file (expand-file-name "emacs.el" user-emacs-directory))
(defconst jw--init-private-org-file (expand-file-name "private.org" user-emacs-directory))
(defconst jw--init-private-file (expand-file-name "private.el" user-emacs-directory))
(defconst jw--init-private-dir (expand-file-name "private" user-emacs-directory))

(defconst jw--emacs-visual-areas '(nil right-fringe left-fringe right-margin left-margin header-line tab-line tab-bar vertical-line vertical-scroll-bar mode-line menu-bar))

(defun jw--font-name (&optional size)
  (if size (format "Monaco %s" size) "Monaco"))

(defconst jw--default-font-size 14)
(setq jw--current-font-size jw--default-font-size)
(defun jw--get-font-size () jw--current-font-size)

(defun jw--set-font-size (&optional size)
  (let ((s (or size jw--default-font-size)))
    (when (and (numberp s) (>= s 8) (<= s 80))
      (setq jw--current-font-size s)
      (set-face-attribute 'default nil :font (jw--font-name s)))))

(defconst jw--default-transparency-alpha 75)
(setq jw--current-transparency-alpha jw--default-transparency-alpha)
(defun jw--get-transparency-alpha () jw--current-transparency-alpha)

(defun jw--set-transparency-alpha (&optional value)
  (let ((v (or value jw--default-transparency-alpha)))
    (when (and (numberp v) (>= v 0) (<= v 100))
      (setq jw--current-transparency-alpha v)
      (set-frame-parameter (selected-frame) 'alpha v))))

(defun jw--login-shell ()
  (file-name-nondirectory (getenv "SHELL")))

(defun jw--all-minor-modes ()
  (-sort 's-less-p minor-mode-list))

(defun jw--all-major-modes ()
  (-sort 's-less-p (-distinct (mapcar 'cdr (-filter '(lambda (entry) (and (cdr entry) (atom (cdr entry)))) auto-mode-alist)))))

(defmacro jw--save-current-message (&rest body)
  "Saves `current-message', executes the body, then `message' the saved message to the echo area. Any `message' calls within the body will likely not be seen."
  (declare (indent defun))
  `(let ((msg (current-message)))
     (progn ,@body)
     (message msg)))

(defun jw--file-write (string file &optional append encoding)
  (let ((enc (or encoding 'utf-8)))
    (let ((file-dir (file-name-directory file)))
      (unless (f-exists? file-dir) (make-directory file-dir 'mk-parents)))
    (if append (f-append string enc file) (f-write string enc file))))

(defun jw--make-uuid ()
  (downcase (shell-command-to-string "uuidgen | tr -d '\n'")))

(defun jw--pwd ()
  (file-truename default-directory))

(defun jw--abs-filename (f)
  (if f (file-truename f) nil))

(defun jw--vc-root-dir ()
  (let ((vc-root-dir (ignore-errors (vc-call-backend (vc-responsible-backend (jw--pwd)) 'root (jw--pwd)))))
    (jw--abs-filename vc-root-dir)))

(defun jw--git-root-dir ()
  (let ((git-root-dir (ignore-errors (vc-find-root (jw--pwd) ".git"))))
    (jw--abs-filename git-root-dir)))

(defun jw--git-config-get (key)
  (s-presence (s-trim (shell-command-to-string (format "git config --get %s 2>/dev/null" key)))))

(defun jw--iso-current-time-string (&optional utc)
  (if utc
      (format-time-string "%Y-%m-%dT%TZ" nil t)
    (concat (format-time-string "%Y-%m-%dT%T")
            ((lambda (x) (concat (substring x 0 3) ":" (substring x 3 5))) (format-time-string "%z")))))

(defun jw--current-date-string (&optional utc)
  (if utc
      (format-time-string "%Y-%m-%d" nil t)
    (format-time-string "%Y-%m-%d")))

(defun jw--symbol-name (symbol-or-string)
  (if (symbolp symbol-or-string) (symbol-name symbol-or-string) symbol-or-string))

(defun jw--as-function (func)
  (if (symbolp func) (symbol-function func)
    (if (stringp func) (symbol-function (intern func))
      (if (functionp func) func
        nil))))

(defun jw--http-get-request-to-string (url)
  (with-current-buffer (url-retrieve-synchronously url)
    (goto-char url-http-end-of-headers)
    (delete-region (point-min) (point))
    (s-trim (buffer-string))))

(defun jw--global-set-visual-area-key (key command &optional areas)
  (global-set-key (kbd key) command)
  (-each (or areas jw--emacs-visual-areas)
    (lambda (area) (global-set-key (kbd (format "<%s> %s" (jw--symbol-name area) key)) command))))

(defun jw--do-when-process-finishes (process fn)
  "Invoke function `fn' after process `process' finishes or exits. `fn' is a one-arg function providing the finished process."
  (when process
    (set-process-sentinel process
                          `(lambda (proc change)
                             (when (string-match "\\(?:finished\\|exited\\)" change)
                               (funcall ,fn proc))))))

(defun jw--kill-buffer (&rest buffers)
  "Kill each buffer in `buffers'. If no list is provided, then kill the current buffer."
  (if (not buffers)
      (kill-buffer (current-buffer))
    (dolist (buffer buffers)
      (when (get-buffer buffer) (kill-buffer buffer)))))

(defun jw--kill-process-buffer (&rest processes)
  "Kill the buffers associated with each process in `processes'."
  (dolist (process processes)
    (jw--kill-buffer (process-buffer process))))

(defun jw--buffers-list-using-major-mode (mode)
  (-sort (lambda (b1 b2) (s-less-p (buffer-name b1) (buffer-name b2)))
         (-filter (lambda (buffer) (equal (buffer-local-value 'major-mode buffer) mode)) (buffer-list))))

(defun jw--buffer-names-list-using-major-mode (mode)
  (-map (lambda (b) (buffer-name b)) (jw--buffers-list-using-major-mode mode)))

(defun jw--buffers-cycle-to-next-using-major-mode (mode &optional cycle-backward create-new args-list)
  "This function switches to the next buffer from all buffers using the provided major mode. The ordering of the buffers is determined by name, alphabetically sorted. If there are no buffers using the major mode, then a new one is created using the `create-new' argument. Using non-nil `cycle-backward' will reverse the order of cycling."
  (let ((buffers (jw--buffer-names-list-using-major-mode mode)))
    (if (or (not (eq major-mode mode)) (not buffers))
        (when create-new (apply create-new args-list))
      (let* ((num-buffers (length buffers))
             (idx (or (-elem-index (buffer-name) buffers) num-buffers))
             (next-idx (mod (if cycle-backward (- idx 1) (+ idx 1)) num-buffers))
             (next-buffer (nth next-idx buffers)))
        (switch-to-buffer next-buffer)))))

(defun jw--kill-buffers-using-major-mode (mode)
  (when-let (buffers (jw--buffers-list-using-major-mode mode))
    (-each buffers (lambda (b) (kill-buffer b)))))

(setq jw--run-cmd-shell "bash")
(setq jw--run-cmd-script-dir "/tmp/emacs-jw-run-cmd/")

(cl-defun jw--run-cmd (command &key process-name before-process-creation after-process-creation after-process-finish tail-output kill-process-buffer delete-tmp-script)
  "An opinionated wrapper around `make-comint-in-buffer'.

For commands that have already finished, this will clean up the process buffer and re-run the command.
For long running processes, this will always toggle back and forth between the process buffer and the other buffer as long as the process is alive.

`:process-name' can be used to override the automatic naming of the process & buffer (from the `command').
`:before-process-creation' is an optional zero-arg function that is run before the process is started.
`:after-process-creation' is an optional one-arg function (providing the process an arg) that is run after the process is started.
`:after-process-finish' is an optional one-arg function (providing the process as an arg) that is run after the process finishes.
`:tail-output' t will direct the cursor to tail the output in the emacs buffer, instead of leaving it at the top of the buffer.
`:kill-process-buffer' t will kill the buffer when the process finishes or exits.
`:delete-tmp-script' t will remove the underlying shell script, instead of leaving it in the tmp directory.
"
  (require 'comint)
  (let* ((prepared-cmd (string-trim command))
         (prepared-cmd-readable (s-collapse-whitespace (s-left 100 prepared-cmd)))
         (cmd-process-name (or process-name (format "*%s*" prepared-cmd-readable)))
         (cmd-buffer-name cmd-process-name)) ;; keep the process & buffer name the same
    (if (s-empty? prepared-cmd)
        (message "Empty command! Doing nothing.")
      (if (process-live-p (get-process cmd-process-name))
          (if (string= (buffer-name) cmd-buffer-name) (switch-to-buffer (other-buffer)) (switch-to-buffer cmd-buffer-name))
        (let* ((tmp-script-rel-filename (replace-regexp-in-string "[^a-zA-Z0-9]+" "-" cmd-process-name))
               (tmp-script-abs-filename (concat jw--run-cmd-script-dir tmp-script-rel-filename))
               (tmp-script-sh-executable (or (ignore-errors (executable-find jw--run-cmd-shell))
                                             (executable-find (jw--login-shell))))
               (tmp-script-contents (format "#!%s\n\ncd %s\n\n%s\n\necho" tmp-script-sh-executable (shell-quote-argument (jw--pwd)) prepared-cmd)))
          (when (get-buffer cmd-buffer-name) (kill-buffer cmd-buffer-name))
          (jw--file-write tmp-script-contents tmp-script-abs-filename)
          (unless (file-executable-p tmp-script-abs-filename) (chmod tmp-script-abs-filename #o744))
          (letrec ((process-buffer (get-buffer-create cmd-buffer-name))
                   (before-process-creation-func (jw--as-function before-process-creation))
                   (after-process-creation-func (jw--as-function after-process-creation))
                   (after-process-finish-func (jw--as-function after-process-finish))
                   (run-cmd (lambda ()
                              (when before-process-creation-func (funcall before-process-creation-func))
                              (insert "$ " prepared-cmd "\n\n")
                              (message cmd-process-name)
                              (apply 'make-comint-in-buffer cmd-process-name process-buffer tmp-script-abs-filename nil nil)
                              (let ((proc (get-buffer-process process-buffer)))
                                (when after-process-creation-func (funcall after-process-creation-func proc))
                                (jw--do-when-process-finishes (get-buffer-process process-buffer)
                                                              `(lambda (proc)
                                                                 (message "%s finished" ,cmd-process-name)
                                                                 (when ,after-process-finish-func (funcall ,after-process-finish-func proc))
                                                                 (when ,kill-process-buffer (jw--kill-buffer ,process-buffer))
                                                                 (when ,delete-tmp-script (f-delete ,tmp-script-abs-filename 'force))))))))
            (switch-to-buffer process-buffer)
            (if tail-output (funcall run-cmd)
              (save-excursion (funcall run-cmd))
              (next-line) ;; put cursor on the next line to help prevent accidentally running the command again
              )))))))

(defun jw--run-cmd-tmux (command tmux-session)
  "Create session `tmux-session' if needed, and send `command' to it."
  (call-process "tmux" nil nil nil "new-session" "-d" "-s" tmux-session) ;; this does nothing if the session already exists
  (call-process "tmux" nil nil nil "send-keys" "-t" tmux-session command "C-m"))

(defun jw--sql-pretty-print (begin end)
  "Formats SQL on region between `begin' and `end' using underlying sql-formatter-cli."
  (if (executable-find "sql-formatter")
      (shell-command-on-region begin end "sql-formatter" nil 'replace)
    (message "Required: https://www.npmjs.com/package/sql-formatter -- npm install -g sql-formatter")))

(defun jw--xml-pretty-print (begin end)
  (if (executable-find "xmllint")
      (shell-command-on-region begin end "xmllint --format -" nil 'replace)
    (message "Required: xmllint")))

(setq jw--cache-repo (pcache-repository "jw-cache"))
(defun jw--cache-delete (sym) (pcache-invalidate jw--cache-repo sym))
(defun jw--cache-set (sym &optional value) (if value (pcache-put jw--cache-repo sym value) (jw--cache-delete sym)))
(defun jw--cache-get (sym) (pcache-get jw--cache-repo sym))

env

(require 'eshell)
(require 'esh-mode)

(defun jw-env-set ()
  (interactive)
  (let* ((cmd (format "%s -l -i -c env" (jw--login-shell)))
         (env-big-str (shell-command-to-string cmd))
         (lines (split-string env-big-str "\n")))
    (dolist (line lines)
      (unless (= 0 (length line))
        (let* ((tokens (split-string line "="))
               (name (car tokens))
               (value (mapconcat 'identity (cdr tokens) "=")))
          (setenv name value)
          (when (string= name "PATH")
            (setq exec-path (split-string value ":"))
            (setq eshell-path-env value))))))
  (setenv "EDITOR" "emacsclient")
  (setenv "TERM" "xterm-256color"))

(jw-env-set)
(add-to-list 'eshell-mode-hook 'jw-env-set)

style

(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)

(setq frame-title-format nil
      inhibit-startup-message t
      initial-scratch-message ""
      initial-major-mode 'org-mode)

(when (eq system-type 'darwin)
  (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
  (add-to-list 'default-frame-alist '(ns-appearance . dark))
  (setq ns-use-proxy-icon nil)
  (setq ns-auto-hide-menu-bar t))

;; these can cause issues with toggling full screen causing TAB to not work
;; TAB only seems to work in fullscreen when the titlebar is on
(defun titlebar-on () (interactive) (set-frame-parameter nil 'undecorated nil))
(defun titlebar-off () (interactive) (set-frame-parameter nil 'undecorated t))

(defvar jw-mode-line-config-show-pwd nil)
(defun mode-line-pwd-on () (interactive) (setq jw-mode-line-config-show-pwd t))
(defun mode-line-pwd-off () (interactive) (setq jw-mode-line-config-show-pwd nil))
(defun mode-line-toggle-pwd () (interactive) (setq jw-mode-line-config-show-pwd (not jw-mode-line-config-show-pwd)))

(defvar jw--mode-line-buffer-id-keymap
  (let ((map (make-sparse-keymap)))
    (define-key map [mode-line mouse-1] 'dired-jump)
    (define-key map [mode-line mouse-3] 'dired-jump)
    map))

(defun jw--mode-line-make-buffer-id ()
  (let* ((b (current-buffer))
         (buffer-name (buffer-name b))
         (file-name (buffer-file-name b))
         (pwd (abbreviate-file-name (jw--pwd)))
         (process-name (get-buffer-process b))
         (process-id (when process-name (process-id process-name)))
         (projectile-project-raw (projectile-project-name))
         (projectile-project (if (string= projectile-project-raw "-") nil projectile-project-raw))
         (display-name (if jw-mode-line-config-show-pwd (format "%s · %s" (or projectile-project pwd) buffer-name) buffer-name)))
    (list (propertize display-name 'face 'mode-line-buffer-id
                      'help-echo (format "Buffer name: %s\nFile name: %s\nProcess name: %s\npid: %s\npwd: %s\nProjectile project: %s"
                                         buffer-name (or file-name "N/A") (or process-name "N/A") (or process-id "N/A") pwd (or projectile-project "N/A"))
                      'mouse-face 'mode-line-highlight
                      'local-map jw--mode-line-buffer-id-keymap))))

(defvar jw--mode-line-buffer-id `(:eval (jw--mode-line-make-buffer-id)))
(put 'jw--mode-line-buffer-id 'risky-local-variable t)
(make-variable-buffer-local 'jw--mode-line-buffer-id)

(setq jw-mode-line-format '(" " jw--mode-line-buffer-id " "))
(setq-default mode-line-format jw-mode-line-format)

(defun mode-line-on (&optional local)
  (interactive "P")
  (if local
      (setq-local mode-line-format jw-mode-line-format)
    (setq-default mode-line-format jw-mode-line-format)))

(defun mode-line-off (&optional local)
  (interactive "P")
  (if local
      (setq-local mode-line-format nil)
    (setq-default mode-line-format nil)))

(defun mode-line-toggle-visibility (&optional local)
  (interactive "P")
  (if mode-line-format
      (mode-line-off local)
    (mode-line-on local)))

theme

(deftheme jw)

(let* ((class t) ;; don't worry about different classes yet
       (font-name (jw--font-name jw--default-font-size))
       (background "black")
       (foreground "#eaeaea")
       (cursor "dark gray")
       (selection "#103050")
       (secondary-selection "#305010")
       (comment "#969896")
       (red "#d54e53")
       (orange "goldenrod")
       (yellow "#e7c547")
       (green "DarkOliveGreen3")
       (cyan "#70c0b1")
       (blue "DeepSkyBlue1")
       (magenta "#c397d8")
       )
  (custom-theme-set-faces
   'jw

   `(default ((,class (:foreground ,foreground :background ,background))))
   `(cursor ((,class (:background ,cursor))))
   `(fringe ((,class (:background nil))))

   `(link ((,class (:foreground ,foreground :underline t))))
   `(link-visited ((,class (:foreground ,foreground :underline t))))
   `(linum ((,class (:background nil :foreground ,green))))
   `(border ((,class (:background ,selection))))
   `(highlight ((,class (:inverse-video nil :background ,background)))) ;; match bg. i never rely on this face.

   `(minibuffer-prompt ((,class (:foreground ,blue))))
   `(region ((,class (:background ,selection))))
   `(secondary-selection ((,class (:background ,secondary-selection))))

   `(trailing-whitespace ((,class (:foreground ,red :inverse-video t :underline nil))))

   `(bold ((,class (:weight bold))))
   `(bold-italic ((,class (:slant italic :weight bold))))
   `(underline ((,class (:underline t))))
   `(italic ((,class (:slant italic))))

   `(mode-line ((,class (:font ,font-name :foreground "#7db5d6" :background "#22083397778B" :box (:style released-button)))))
   `(mode-line-inactive ((,class (:foreground "gray" :background "#263238" :box (:style released-button)))))
   `(mode-line-buffer-id ((,class (:foreground ,foreground))))
   `(mode-line-highlight ((,class (:foreground "#7db5d6"))))
   `(mode-line-emphasis ((,class (:foreground "#7db5d6" :slant italic))))
   `(header-line ((,class (:foreground ,foreground :background "#005858" :box (:style released-button)))))

   `(font-lock-builtin-face ((,class (:foreground "LightCoral"))))
   `(font-lock-comment-delimiter-face ((,class (:foreground ,comment))))
   `(font-lock-comment-face ((,class (:foreground ,comment))))
   `(font-lock-constant-face ((,class (:foreground ,green))))
   `(font-lock-doc-face ((,class (:foreground "moccasin"))))
   `(font-lock-doc-string-face ((,class (:foreground ,yellow))))
   `(font-lock-function-name-face ((,class (:foreground ,orange))))
   `(font-lock-keyword-face ((,class (:foreground ,blue))))
   `(font-lock-negation-char-face ((,class (:foreground ,blue))))
   `(font-lock-preprocessor-face ((,class (:foreground "gold"))))
   `(font-lock-regexp-grouping-backslash ((,class (:foreground ,yellow))))
   `(font-lock-regexp-grouping-construct ((,class (:foreground ,magenta))))
   `(font-lock-string-face ((,class (:foreground "burlywood"))))
   `(font-lock-type-face ((,class (:foreground "CadetBlue1"))))
   `(font-lock-variable-name-face ((,class (:foreground ,yellow))))
   `(font-lock-warning-face ((,class (:foreground ,red))))
   `(shadow ((,class (:foreground ,comment))))
   `(success ((,class (:foreground "SeaGreen2"))))
   `(error ((,class (:foreground ,red))))
   `(warning ((,class (:foreground ,orange))))

   `(match ((,class (:foreground ,blue :background ,background :inverse-video t))))
   `(isearch ((,class (:foreground ,yellow :background ,background :inverse-video t))))
   `(isearch-lazy-highlight-face ((,class (:foreground ,cyan :background ,background :inverse-video t))))
   `(isearch-fail ((,class (:background ,background :inherit font-lock-warning-face :inverse-video t))))

   `(flycheck-error ((,class (:underline (:style wave :color ,red)))))
   `(flycheck-warning ((,class (:underline (:style wave :color ,orange)))))

   `(flymake-warnline ((,class (:underline (:style wave :color ,orange) :background ,background))))
   `(flymake-errline ((,class (:underline (:style wave :color ,red) :background ,background))))

   `(outline-1 ((,class (:inherit nil :foreground "SkyBlue1"))))
   `(outline-2 ((,class (:inherit nil :foreground ,yellow))))
   `(outline-3 ((,class (:inherit nil :foreground ,magenta))))
   `(outline-4 ((,class (:inherit nil :foreground ,cyan))))
   `(outline-5 ((,class (:inherit nil :foreground ,orange))))
   `(outline-6 ((,class (:inherit nil :foreground "CadetBlue1"))))
   `(outline-7 ((,class (:inherit nil :foreground "DarkSeaGreen"))))
   `(outline-8 ((,class (:inherit nil :foreground "turquoise2"))))
   `(outline-9 ((,class (:inherit nil :foreground "LightSteelBlue1"))))

   `(org-link ((,class (:foreground ,blue :underline nil))))
   `(org-date ((,class (:foreground ,blue :underline nil))))
   `(org-hide ((,class (:foreground ,background :background ,background))))
   `(org-headline-done ((,class (:inherit shadow))))
   `(org-agenda-structure ((,class (:foreground ,magenta))))
   `(org-agenda-date ((,class (:foreground ,blue :underline nil))))
   `(org-agenda-done ((,class (:foreground ,green))))
   `(org-agenda-dimmed-todo-face ((,class (:foreground ,comment))))
   `(org-block ((,class (:foreground ,foreground))))
   `(org-code ((,class (:foreground ,yellow))))
   `(org-column ((,class (:background nil))))
   `(org-column-title ((,class (:inherit org-column :underline t))))
   `(org-document-info ((,class (:foreground ,cyan))))
   `(org-document-info-keyword ((,class (:foreground ,green))))
   `(org-document-title ((,class (:foreground ,orange))))
   `(org-done ((,class (:foreground ,green))))
   `(org-ellipsis ((,class (:foreground ,comment))))
   `(org-footnote ((,class (:foreground ,cyan))))
   `(org-formula ((,class (:foreground ,red))))
   `(org-scheduled ((,class (:foreground ,green))))
   `(org-scheduled-previously ((,class (:foreground ,orange))))
   `(org-scheduled-today ((,class (:foreground ,green))))
   `(org-special-keyword ((,class (:foreground ,orange))))
   `(org-table ((,class (:foreground ,magenta))))
   `(org-todo ((,class (:foreground ,red))))
   `(org-upcoming-deadline ((,class (:foreground ,orange))))
   `(org-warning ((,class (:foreground ,red))))

   `(magit-log-author ((,class (:foreground ,cyan))))

   `(transient-red ((,class (:foreground ,red))))
   `(transient-amaranth ((,class (:foreground ,yellow))))
   `(transient-blue ((,class (:foreground ,blue))))
   `(transient-purple ((,class (:foreground ,magenta))))
   `(transient-pink ((,class (:foreground ,orange))))

   `(rcirc-my-nick ((,class (:foreground "#00ffff"))))
   `(rcirc-other-nick ((,class (:foreground "#90ee90"))))
   `(rcirc-server ((,class (:foreground "#a2b5cd"))))
   `(rcirc-server-prefix ((,class (:foreground "#00bfff"))))
   `(rcirc-timestamp ((,class (:foreground "#7d7d7d"))))
   `(rcirc-nick-in-message ((,class (:foreground "#00ffff"))))
   `(rcirc-prompt ((,class (:foreground "#00bfff"))))
   `(rcirc-keyword ((,class :foreground "#00ffff")))
   `(rcirc-nick-in-message-full-line ((,class ())))
   `(rcirc-track-nick ((,class (:foreground "#00ffff"))))
   `(rcirc-track-keyword ((,class (:foreground "#00ffff"))))

   `(erc-direct-msg-face ((,class (:foreground ,orange))))
   `(erc-error-face ((,class (:foreground ,red))))
   `(erc-header-face ((,class (:foreground ,foreground :background ,selection))))
   `(erc-input-face ((,class (:foreground ,green))))
   `(erc-keyword-face ((,class (:foreground ,yellow))))
   `(erc-current-nick-face ((,class (:foreground ,green))))
   `(erc-my-nick-face ((,class (:foreground ,green))))
   `(erc-nick-default-face ((,class (:weight normal :foreground ,cyan))))
   `(erc-nick-msg-face ((,class (:weight normal :foreground ,yellow))))
   `(erc-notice-face ((,class (:foreground ,comment))))
   `(erc-pal-face ((,class (:foreground ,orange))))
   `(erc-prompt-face ((,class (:foreground ,blue))))
   `(erc-timestamp-face ((,class (:foreground ,magenta))))
   `(erc-keyword-face ((,class (:foreground ,green))))

   `(rainbow-delimiters-depth-1-face ((,class (:foreground ,foreground))))
   `(rainbow-delimiters-depth-2-face ((,class (:foreground ,cyan))))
   `(rainbow-delimiters-depth-3-face ((,class (:foreground ,yellow))))
   `(rainbow-delimiters-depth-4-face ((,class (:foreground ,green))))
   `(rainbow-delimiters-depth-5-face ((,class (:foreground ,blue))))
   `(rainbow-delimiters-depth-6-face ((,class (:foreground ,foreground))))
   `(rainbow-delimiters-depth-7-face ((,class (:foreground ,cyan))))
   `(rainbow-delimiters-depth-8-face ((,class (:foreground ,yellow))))
   `(rainbow-delimiters-depth-9-face ((,class (:foreground ,green))))
   `(rainbow-delimiters-unmatched-face ((,class (:foreground ,red))))

   `(term ((,class (:foreground nil :background nil :inherit default))))
   `(term-color-black   ((,class (:foreground ,foreground :background ,foreground))))
   `(term-color-red     ((,class (:foreground ,red :background ,red))))
   `(term-color-green   ((,class (:foreground ,green :background ,green))))
   `(term-color-yellow  ((,class (:foreground ,yellow :background ,yellow))))
   `(term-color-blue    ((,class (:foreground ,blue :background ,blue))))
   `(term-color-magenta ((,class (:foreground ,magenta :background ,magenta))))
   `(term-color-cyan    ((,class (:foreground ,cyan :background ,cyan))))
   `(term-color-white   ((,class (:foreground ,background :background ,background))))

   `(ansi-color-black   ((,class (:foreground ,foreground :background ,foreground))))
   `(ansi-color-red   ((,class (:foreground ,red :background ,red))))
   `(ansi-color-green   ((,class (:foreground ,green :background ,green))))
   `(ansi-color-yellow   ((,class (:foreground ,yellow :background ,yellow))))
   `(ansi-color-blue   ((,class (:foreground ,blue :background ,blue))))
   `(ansi-color-magenta   ((,class (:foreground ,magenta :background ,magenta))))
   `(ansi-color-cyan   ((,class (:foreground ,cyan :background ,cyan))))
   `(ansi-color-white   ((,class (:foreground ,foreground :background ,foreground))))
   )

  (custom-theme-set-variables
   'jw
   `(fci-rule-color ,selection)
   `(vc-annotate-color-map
     '((20  . ,red)
       (40  . ,orange)
       (60  . ,yellow)
       (80  . ,green)
       (100 . ,cyan)
       (120 . ,blue)
       (140 . ,magenta)
       (160 . ,red)
       (180 . ,orange)
       (200 . ,yellow)
       (220 . ,green)
       (240 . ,cyan)
       (260 . ,blue)
       (280 . ,magenta)
       (300 . ,red)
       (320 . ,orange)
       (340 . ,yellow)
       (360 . ,green)))
   `(vc-annotate-very-old-color nil)
   `(vc-annotate-background nil))
  )

(provide-theme 'jw)
(load-theme 'jw t)

settings

(setq warning-minimum-level :error)

(setq gc-cons-threshold 100000000)
(add-function :after after-focus-change-function (lambda () (unless (frame-focus-state) (garbage-collect))))

(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file 'noerror)

(setq save-interprogram-paste-before-kill t
      yank-pop-change-selection t
      select-enable-clipboard t)

(setq jw-scratch-file (f-expand "scratch.org" user-emacs-directory))

(setq help-window-select t)

(pixel-scroll-precision-mode 1)

(setq enable-local-variables :all)

(setq vc-follow-symlinks t)

;; colors for comint
(ansi-color-for-comint-mode-off)
(setq comint-output-filter-functions (remove 'ansi-color-process-output comint-output-filter-functions))
(add-hook 'comint-preoutput-filter-functions 'xterm-color-filter)

;; colors for compilation-mode
(setq compilation-environment '("TERM=xterm-256color"))
(advice-add 'compilation-filter :around '(lambda (f proc str) (funcall f proc (xterm-color-filter str))))

;; colors for eshell
(add-hook 'eshell-before-prompt-hook '(lambda () (setq xterm-color-preserve-properties t)))
(add-to-list 'eshell-preoutput-filter-functions 'xterm-color-filter)
(setq eshell-output-filter-functions (remove 'eshell-handle-ansi-color eshell-output-filter-functions))

(setq compilation-scroll-output t)

(show-paren-mode 1)

(setq use-short-answers t)

(setq use-dialog-box nil)

(fset 'linum-mode 'display-line-numbers-mode)
(fset 'global-linum-mode 'global-display-line-numbers-mode)

(setq history-delete-duplicates t)

(setq create-lockfiles nil)

(setq save-silently t)

(setq suggest-key-bindings nil)

(setq kill-whole-line t)

(setq dabbrev-case-replace nil
      dabbrev-case-distinction nil)

(global-auto-revert-mode 1)

(setq global-auto-revert-non-file-buffers t
      auto-revert-verbose nil)

(setq-default indent-tabs-mode nil)

(setq tab-width 2)
(setq js-indent-level 2)
(setq typescript-indent-level 2)

(delete-selection-mode 1)

(defun cc-mode-electric-parens-fix-delete-selection ()
  "Fixes a weird behavior in cc-mode where typing a paren or brace causes some nasty hard-deletion of more than whitespace from the file.
  It also messes with undo/undo-tree history, making it hard to revert."
  (put 'c-electric-paren 'delete-selection nil)
  (put 'c-electric-brace 'delete-selection nil)
  (put 'c-electric-lt-gt 'delete-selection nil))
(add-hook 'c++-mode-hook 'cc-mode-electric-parens-fix-delete-selection)
(add-hook 'c-mode-hook 'cc-mode-electric-parens-fix-delete-selection)

(c-set-offset 'case-label '+) ;; https://www.gnu.org/software/emacs/manual/html_node/efaq/Indenting-switch-statements.html

(when (executable-find "rg")
  (setq grep-command "rg --with-filename --no-heading --line-number --ignore-case "))

(defun grep--advice--focus-grep-buffer (&rest args)
  (when-let ((b (get-buffer "*grep*"))) (pop-to-buffer b)))
(advice-add 'grep :after 'grep--advice--focus-grep-buffer)

(winner-mode 1)

(recentf-mode 1)

(global-subword-mode 1)

(save-place-mode 1)

(require 'dired-x)

(put 'dired-find-alternate-file 'disabled nil)

(setq wdired-allow-to-change-permissions 'advanced)

(setq dired-listing-switches "-alh")

(setq dired-dwim-target t)

(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)

(add-hook 'before-save-hook 'delete-trailing-whitespace)

(add-hook 'next-error-hook 'delete-other-windows)

(setq uniquify-buffer-name-style 'forward)

(setq ring-bell-function 'ignore)

(setq enable-recursive-minibuffers t)

(setq bookmark-fringe-mark nil) ;; don't show red icon in left fringe for bookmarks

(add-to-list 'auto-mode-alist '("\\.jsonc$" . js-mode))
(add-to-list 'auto-mode-alist '("\\.scss$" . css-mode))
(add-to-list 'auto-mode-alist '("Gemfile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Rakefile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Vagrantfile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Berksfile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("\\.irbrc$" . ruby-mode))

(setq ruby-insert-encoding-magic-comment nil)

;; (add-hook 'text-mode-hook 'flyspell-mode) ;; i don't use this

(setq ediff-window-setup-function 'ediff-setup-windows-plain)

(put 'narrow-to-region 'disabled nil)
(put 'narrow-to-page 'disabled nil)
(put 'set-goal-column 'disabled nil)

;; (setq calc-angle-mode 'rad)
(setq calc-angle-mode 'deg)

(setq tramp-default-method "ssh")

(setq zoneinfo-style-world-list
      '(("UTC" "UTC")
        ("US/Central" "US Central") ("US/Eastern" "US Eastern")
        ("US/Mountain" "US Mountain") ("US/Pacific" "US Pacific")
        ("US/Hawaii" "US Hawaii")
        ("Europe/London" "Europe London") ("Europe/Paris" "Europe Paris")
        ("Asia/Tokyo" "Asia Tokyo")))

(defun save-buffers-kill-terminal--advice--ask-yes-or-no (original-function &rest args)
  (if (yes-or-no-p "Is life too much? ") (apply original-function args) (message "Keep up the good fight!")))
(advice-add 'save-buffers-kill-terminal :around 'save-buffers-kill-terminal--advice--ask-yes-or-no)

(defun shell-command--advice--ignore-message-with-no-output (&rest args)
  (when (and (current-message) (string-match "Shell command succeeded with no output" (current-message))) (message nil)))
(advice-add 'shell-command-on-region :after 'shell-command--advice--ignore-message-with-no-output)

(defun term--kill-buffer-on-exit ()
  (let ((process (get-buffer-process (current-buffer))))
    (jw--do-when-process-finishes process
                                  (lambda (proc)
                                    (kill-buffer (process-buffer proc))))))
(add-hook 'term-mode-hook 'term--kill-buffer-on-exit)

(defun kmacro-end-and-call-macro--advice--ask-for-repeat-number-instead-of-using-prefix-numeric-arg (original-function &rest args)
  (if (car args)
      (let* ((repeat-times-string (read-string "How many times to repeat kmacro? "))
             (repeat-times (string-to-number repeat-times-string)))
        (apply original-function (cons repeat-times (cdr args))))
    (apply original-function args)))
(advice-add 'kmacro-end-and-call-macro :around 'kmacro-end-and-call-macro--advice--ask-for-repeat-number-instead-of-using-prefix-numeric-arg)

;; todo -- not sure I like this. it doesn't take into account other modes, and recompile doesn't toggle to renamed buffers from outside the compilation buffer
;; (defun compile--advice--rename-buffer-and-toggle-to-process-buffer-if-active (original-function &rest args)
;;   (let* ((command (car args))
;;          (buffer-name (format "*compilation: %s*" command))
;;          (buffer (get-buffer buffer-name)))
;;     (when buffer
;;       (switch-to-buffer buffer)
;;       (unless (get-buffer-process buffer-name) (recompile)))

;;     (when (not buffer)
;;       (save-window-excursion
;;         (apply original-function args))
;;       (switch-to-buffer "*compilation*")
;;       (rename-buffer buffer-name)
;;       )
;;     ))
;; (advice-add 'compile :around 'compile--advice--rename-buffer-and-toggle-to-process-buffer-if-active)

(setq user-auto-save-directory (expand-file-name "auto-saves/" user-emacs-directory ))
(unless (file-exists-p user-auto-save-directory) (make-directory user-auto-save-directory)) ;; auto-save won't create directories
(setq auto-save-file-name-transforms `((".*" ,user-auto-save-directory t)))

(setq user-backup-directory (expand-file-name "backups/" user-emacs-directory))
(unless (file-exists-p user-backup-directory) (make-directory user-backup-directory))

(setq version-control t
      vc-make-backup-files t
      kept-new-versions 500
      kept-old-versions 10
      backup-by-copying t ;; deep copy of symlinks
      delete-old-versions t)

(setq backup-directory-alist `(("." . ,user-backup-directory)))

(setq savehist-file (expand-file-name "savehist" user-emacs-directory))
(savehist-mode 1)
(setq history-length 5000)
(add-to-list 'savehist-additional-variables 'kill-ring)
(add-to-list 'savehist-additional-variables 'search-ring)
(add-to-list 'savehist-additional-variables 'regexp-search-ring)

(setq undo-tree-auto-save-history nil) ;; saving the undo-tree acts weird on startup, don't use it.

(setq kill-ring-max 10000)

(when (eq system-type 'gnu/linux)
  (setq interprogram-paste-function 'x-cut-buffer-or-selection-value
        browse-url-browser-function 'browse-url-generic
        browse-url-generic-program "google-chrome"))

(when (eq system-type 'darwin)
  (setq ns-command-modifier 'meta
        ns-option-modifier 'super
        browse-url-browser-function 'browse-url-default-macosx-browser
        browse-url-generic-program "open"))

functions

(require 'url-util)

(defalias 'life-is-too-much 'save-buffers-kill-terminal)
(defalias 'filter-lines 'keep-lines)
(defalias 'filter-out-lines 'flush-lines)
(defalias 'elisp-shell 'ielm)

(defalias 'times-timezones 'world-clock)

(defun jw-nothing (&rest args)
  (interactive "P"))

(defun sudo-su ()
  (interactive)
  (let ((goto (or (buffer-file-name) (jw--pwd))))
    (find-file (format "/sudo:root@localhost:%s" goto))))

(defun font-size-set ()
  (interactive)
  (let* ((current (jw--get-font-size))
         (updated (read-string (format "Font size 8 to 80, default %s, current %s: " jw--default-font-size current) (number-to-string current))))
    (jw--set-font-size (string-to-number updated))))

(defun font-size-default () (interactive) (jw--set-font-size))
(defun font-size-increase () (interactive) (jw--set-font-size (1+ (jw--get-font-size))))
(defun font-size-decrease () (interactive) (jw--set-font-size (1- (jw--get-font-size))))

(defun transparency-alpha-set ()
  (interactive)
  (let* ((current (jw--get-transparency-alpha))
         (updated (read-string (format "Transparency alpha 0 to 100, default %s, current %s: " jw--default-transparency-alpha current) (number-to-string current))))
    (jw--set-transparency-alpha (string-to-number updated))))

(defun transparency-alpha-default () (interactive) (jw--set-transparency-alpha))
(defun transparency-alpha-increase () (interactive) (jw--set-transparency-alpha (1+ (jw--get-transparency-alpha))))
(defun transparency-alpha-decrease () (interactive) (jw--set-transparency-alpha (1- (jw--get-transparency-alpha))))

(defun font-size-transparency-alpha-default ()
  (interactive)
  (font-size-default)
  (transparency-alpha-default))

(defun kill-ring-cleanup-last-kill (&optional in-major-mode)
  "Cleans whitespace and reindents the text in the head of the kill ring as if in the major mode."
  (interactive)
  (with-temp-buffer
    (jw--save-current-message
      (let ((mode (or in-major-mode (completing-read "Major mode to mimic: " (jw--all-major-modes) nil t))))
        (yank)
        (funcall (intern-soft mode))
        (indent-region (point-min) (point-max))
        (whitespace-cleanup)
        (kill-new (buffer-substring (point-min) (point-max)) t)))))

(defun kill-ring-save-region-or-line (arg)
  (interactive "P")
  (let ((cleanup-kill arg))
    (if (region-active-p)
        (kill-ring-save (mark) (point))
      (kill-ring-save (line-beginning-position) (line-end-position)))
    (when cleanup-kill (kill-ring-cleanup-last-kill major-mode))))

(defun kill-region-or-line (arg)
  (interactive "P")
  (let ((cleanup-kill arg))
    (if (region-active-p)
        (kill-region (mark) (point))
      (progn (beginning-of-line) (kill-line)))
    (when cleanup-kill (kill-ring-cleanup-last-kill major-mode))))

(defun kill-save-file-or-buffer-name (arg)
  "Kill ring save the current file name. With prefix arg, save the fully qualified path + file name. If the buffer is not visiting a file, use the buffer name."
  (interactive "P")
  (if buffer-file-name
      (if arg
          (kill-new buffer-file-name)
        (kill-new (f-filename buffer-file-name)))
    (kill-new (buffer-name))))

(defun unique-lines ()
  (interactive)
  (if (region-active-p)
      (delete-duplicate-lines (region-beginning) (region-end))
    (delete-duplicate-lines (point-min) (point-max))))

(defun date (&optional arg)
  "Display current date time.
With single prefix arg (C-u M-x date), display calendar around current date.
With extra prefix arg (C-u C-u M-x date), prompt for year & month for calendar."
  (interactive "P")
  (when arg
    (pcase (prefix-numeric-value arg)
      (16 (calendar arg))
      (_ (calendar))))
  (message (current-time-string)))

(defun iso-datetime (utc)
  (interactive "P")
  (message (jw--iso-current-time-string utc)))

(defun insert-iso-datetime (utc)
  (interactive "P")
  (insert (jw--iso-current-time-string utc)))

(defun insert-date (utc)
  (interactive "P")
  (insert (jw--current-date-string utc)))

(defun weather (&optional arg)
  (interactive "P")
  (let* ((raw-query
          (pcase (prefix-numeric-value arg)
            (16 ":help")
            (4 (read-string "Weather for city/state/country/zip/latlong/:help/etc - curl wttr.in/"))
            (_ "")))
         (query (url-encode-url (s-replace "\s" "+" (s-trim raw-query)))))
    (jw--run-cmd (format "curl http://wttr.in/%s?F" query))))

(setq cheat-sh-candidates nil)
(defun cheat-sh (&optional arg)
  (interactive "P")
  (let* ((raw-query
          (pcase (prefix-numeric-value arg)
            (16 ":help")
            (4 ":list")
            (_ (let ((candidates (progn (when (not cheat-sh-candidates)
                                          (message "Caching cheat.sh candidates list…")
                                          (setq cheat-sh-candidates (process-lines "curl" "--silent" "http://cheat.sh/:list")))
                                        cheat-sh-candidates))
                     (initial (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) nil)))
                 (completing-read "curl http://cheat.sh/" candidates nil nil initial)))
            ))
         (query (url-encode-url (s-replace "\s" "+" (s-trim raw-query)))))
    (jw--run-cmd (format "curl http://cheat.sh/%s" query))))

(defun jw-web-search ()
  "Open URL or search query in a default external web browser, controlled by `browse-url-browser-function'."
  (interactive)
  (let* ((raw-text (if (use-region-p)
                       (buffer-substring-no-properties (region-beginning) (region-end))
                     (read-string "Enter URL or keywords for external web browser search: " (or (thing-at-point 'url t) (thing-at-point 'word t)))))
         (parsed-url (url-generic-parse-url raw-text))
         (is-url (and parsed-url (url-type parsed-url) (url-host parsed-url)))
         (url (if is-url (url-encode-url raw-text) (format "https://duckduckgo.com?q=%s" (url-hexify-string raw-text)))))
    (browse-url url)))

(defun web-search-dwim (arg)
  "Open URL or search query in a web browser. By default, this delegates to `jw-web-search' and an external web browser. With a prefix arg, this delegates to `eww-search-words'  and the eww browser."
  (interactive "P")
  (call-interactively (if arg 'eww-search-words 'jw-web-search)))

(defun scratch-buffer ()
  "Save the scratch buffer in a file. Use any mode you'd like by customizing `jw-scratch-file` to a separate (fully-qualified) filename & extension."
  (interactive)
  (find-file jw-scratch-file)
  (cd (getenv "HOME")))

(defun toggle-scratch-buffer ()
  (interactive)
  (if (s-equals? (buffer-name) (f-filename jw-scratch-file))
      (progn
        (save-buffer)
        (switch-to-buffer (other-buffer)))
    (scratch-buffer)))

(defun scratch-buffer-remember (arg)
  "Opinionated alternative to remember-mode that automates my usage patterns. Remembers either the current region or line."
  (interactive "P")
  (let ((text (string-trim (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) (thing-at-point 'line)))))
    (if (s-empty? text)
        (message "text to remember is empty, doing nothing")
      (save-window-excursion
        (find-file jw-scratch-file)
        (save-excursion
          (kill-new text)
          (if arg (progn (beginning-of-buffer) (insert text) (newline 2)) (progn (end-of-buffer) (newline 2) (insert text)) )
          (save-buffer)))
      (message "remembered in scratch & added to kill ring"))))

(defun uuid ()
  (interactive)
  (insert (jw--make-uuid)))

(defun json-prettify ()
  (interactive)
  (if (region-active-p)
      (json-pretty-print (region-beginning) (region-end))
    (json-pretty-print-buffer)))

(defun sql-prettify ()
  (interactive)
  (if (region-active-p)
      (jw--sql-pretty-print (region-beginning) (region-end))
    (jw--sql-pretty-print (point-min) (point-max))))

(defun xml-prettify ()
  (interactive)
  (if (region-active-p)
      (jw--xml-pretty-print (region-beginning) (region-end))
    (jw--xml-pretty-print (point-min) (point-max))))

(defun cmd (command)
  (interactive "sCommand: ")
  (jw--run-cmd command))

(defun cmd-tmux (command &optional tmux-session)
  (interactive "sCommand: ")
  (let ((ts (or tmux-session "emacs")))
    (jw--run-cmd-tmux command ts)
    (message "Sent to tmux session: %s" ts)))

(defun cmd-dwim (arg &optional command)
  "Shell command dwim.

M-x `cmd-dwim' will run an async shell command in a new buffer.
C-u M-x `cmd-dwim' will run a shell command and print the response in the echo area.
C-u C-u M-x `cmd-dwim' will run a shell command and insert the response in the buffer on the next line.
C-u C-u C-u M-x `cmd-dwim' will send a shell command to the default tmux session using `cmd-tmux'.
C-- M-x `cmd-dwim' will run an async shell command in a new buffer and kill the calling buffer.

Interactively:
 - If a region is selected, the region will be used as the shell command.
 - If the point is on a line beginning with a dollar sign (e.g. \"$ whoami\"), the entire line will be used as the shell command.
 - Otherwise, the shell command is read from prompt."
  (interactive "P")
  (let ((prepared-cmd (or command (if (region-active-p)
                                      (buffer-substring-no-properties (region-beginning) (region-end))
                                    (if (s-starts-with? "$" (s-trim (or (thing-at-point 'line t) "")))
                                        (s-trim-left (s-chop-prefix "$" (s-trim (thing-at-point 'line t))))
                                      (read-shell-command "Command: "))))))
    (deactivate-mark) ;; don't leave an region active on the previous buffer, to prevent accidentally running the command twice.
    (pcase (prefix-numeric-value arg)
      (16 (save-excursion (open-line-next) (insert (s-trim (shell-command-to-string prepared-cmd)))))
      (64 (cmd-tmux prepared-cmd))
      (4 (message (string-trim (shell-command-to-string prepared-cmd))))
      (-1 (cmd prepared-cmd) (kill-buffer (other-buffer)))
      (_ (cmd prepared-cmd)))))

(transient-define-prefix cmd-menu ()
  ["Run command"
   ("!" "cmd-dwim          M-!   C-u to echo area / C-u C-u on next line" cmd-dwim)
   ("*" "calculator        M-*" calculator)
   (":" "eval-expression   M-:" eval-expression)
   ("e" "eval-last-sexp    C-x C-e" eval-last-sexp)
   ("x" "eval-defun        C-M-x" eval-defun)
   ("r" "eval-region" eval-region)
   ("b" "eval-buffer" eval-buffer)
   ])

(defun jw-curl-to-elisp-dwim ()
  (interactive)
  (jw--save-current-message
    (save-excursion
      (let* ((curl (if (region-active-p)
                       (buffer-substring-no-properties (region-beginning) (region-end))
                     (read-string "curl command: ")))
             (elisp (curl-to-elisp curl nil)))
        (when (region-active-p) (delete-region (region-beginning) (region-end)))
        (insert (pp elisp))))))

(defun jw-curl-to-verb-dwim ()
  (interactive)
  (save-excursion
    (let* ((curl (if (region-active-p)
                     (buffer-substring-no-properties (region-beginning) (region-end))
                   (read-string "curl command: ")))
           (verb (curl-to-elisp-verb curl nil)))
      (when (region-active-p) (delete-region (region-beginning) (region-end)))
      (insert verb))))

(defface jw-pulse-text-face '((t :background "#75C1FA")) "Face used to pulse text.")
(defun jw-pulse-text (arg)
  (interactive "P")
  (if arg (pulse-momentary-highlight-region (point-min) (point-max) 'jw-pulse-text-face)
    (if (region-active-p) (pulse-momentary-highlight-region (region-beginning) (region-end) 'jw-pulse-text-face)
      (pulse-momentary-highlight-region (line-beginning-position) (line-end-position) 'jw-pulse-text-face))))

(defun beginning-of-line-or-indentation ()
  (interactive)
  (let ((previous-point (point)))
    (back-to-indentation)
    (if (equal previous-point (point))
        (beginning-of-line))))

(defun indent-region-or-buffer--org-mode (arg)
  "Do not indent the entire buffer, only indent active regions.
   My org files can get pretty big, and I tend to indent certain parts of them manually as I see fit."
  (save-excursion
    (when (region-active-p)
      (indent-region (region-beginning) (region-end)))))

(defun indent-region-or-buffer--default (arg)
  (save-excursion
    (if (region-active-p)
        (indent-region (region-beginning) (region-end))
      (indent-region (point-min) (point-max))))
  (when arg (whitespace-cleanup)))

(defun indent-region-or-buffer (arg)
  (interactive "P")
  (jw--save-current-message
    (if (equal major-mode 'org-mode)
        (indent-region-or-buffer--org-mode arg)
      (indent-region-or-buffer--default arg))))

(defun comment-dwim-dwim (&optional arg)
  "When the region is active, then toggle comments over it.
Otherwise, toggle commenting the current line.
With C-u, then append a comment to the end of the line instead.
With C-u C-u, then kill the comment on the current line."
  (interactive "*P")
  (if (region-active-p)
      (comment-dwim arg)
    (pcase (prefix-numeric-value arg)
      (16 (save-excursion (comment-dwim arg)))
      (4 (comment-dwim nil))
      (_ (comment-or-uncomment-region (line-beginning-position) (line-end-position))))))

(defun open-line-next ()
  (interactive)
  (end-of-line)
  (open-line 1)
  (next-line 1)
  (indent-according-to-mode))

(defun open-line-previous ()
  (interactive)
  (beginning-of-line)
  (open-line 1)
  (indent-according-to-mode))

(defun newline-and-open-line-previous ()
  (interactive)
  (let ((was-at-end-of-line (equal (point) (line-end-position))))
    (newline-and-indent)
    (unless was-at-end-of-line (open-line-previous))))

(defun current-prefix-arg-raw (arg)
  (interactive "P")
  (message "%s" arg))

(defun current-prefix-arg-numeric (arg)
  (interactive "P")
  (message "%s" (prefix-numeric-value arg)))

(defun toggle-window-split ()
  (interactive)
  (if (= (count-windows) 2)
      (let* ((this-win-buffer (window-buffer))
             (next-win-buffer (window-buffer (next-window)))
             (this-win-edges (window-edges (selected-window)))
             (next-win-edges (window-edges (next-window)))
             (this-win-2nd (not (and (<= (car this-win-edges)
                                         (car next-win-edges))
                                     (<= (cadr this-win-edges)
                                         (cadr next-win-edges)))))
             (splitter
              (if (= (car this-win-edges)
                     (car (window-edges (next-window))))
                  'split-window-horizontally
                'split-window-vertically)))
        (delete-other-windows)
        (let ((first-win (selected-window)))
          (funcall splitter)
          (if this-win-2nd (other-window 1))
          (set-window-buffer (selected-window) this-win-buffer)
          (set-window-buffer (next-window) next-win-buffer)
          (select-window first-win)
          (if this-win-2nd (other-window 1))))))

(defun rotate-windows (count)
  "Rotate your windows.
Dedicated windows are left untouched. Giving a negative prefix
argument makes the windows rotate backwards."
  (interactive "p")
  (let* ((non-dedicated-windows (seq-remove 'window-dedicated-p (window-list)))
         (num-windows (length non-dedicated-windows))
         (i 0)
         (step (+ num-windows count)))
    (cond ((not (> num-windows 1))
           (message "You can't rotate a single window!"))
          (t
           (dotimes (counter (- num-windows 1))
             (let* ((next-i (% (+ step i) num-windows))

                    (w1 (elt non-dedicated-windows i))
                    (w2 (elt non-dedicated-windows next-i))

                    (b1 (window-buffer w1))
                    (b2 (window-buffer w2))

                    (s1 (window-start w1))
                    (s2 (window-start w2)))
               (set-window-buffer w1 b2)
               (set-window-buffer w2 b1)
               (set-window-start w1 s2)
               (set-window-start w2 s1)
               (setq i next-i)))))))

(defalias 'find-file-external 'counsel-find-file-extern) ;; no need to re-invent the wheel here

(defun dired-hide-subdir-dwim (arg)
  (interactive "P")
  (save-excursion (if arg (dired-hide-all) (dired-hide-subdir 1))))

(defun dired-do-kill-line-dwim (arg)
  (interactive "P")
  (when arg (dired-tree-up 0))
  (dired-do-kill-lines 1 ""))

(defun dired-do-kill-subdir-dwim ()
  (interactive)
  (dired-do-kill-line-dwim 1))

(defun dired-find-file-external ()
  (interactive)
  (dolist (f (dired-get-marked-files))
    (find-file-external f)))

(defun jw-narrow-dwim (arg)
  (interactive "P")
  (cond (arg (widen))
        ((buffer-narrowed-p) (widen))
        ((region-active-p) (narrow-to-region (region-beginning) (region-end)))
        ((org-at-heading-p) (org-narrow-to-subtree))
        ((org-at-block-p) (org-narrow-to-block))
        (t (narrow-to-defun))))

(defun edit-indirect-region-css ()
  (interactive)
  (call-interactively 'edit-indirect-region)
  (css-mode)
  (rename-buffer (format "%s-css" (buffer-name))))

(defun edit-indirect-region-html ()
  (interactive)
  (call-interactively 'edit-indirect-region)
  (html-mode)
  (rename-buffer (format "%s-html" (buffer-name))))

(defun jw-gist (arg)
  "Simple function to create single-file gists, from a marked region or entire buffer."
  (interactive "P")
  (if (not (executable-find "hub"))
      (message "Executable hub required to create gists: https://hub.github.com/")
    (let* ((content (if (region-active-p)
                        (buffer-substring-no-properties (region-beginning) (region-end))
                      (buffer-substring-no-properties (point-min) (point-max))))
           (gist-file-name (if (buffer-file-name)
                               (file-name-nondirectory (buffer-file-name))
                             (read-string "Gist filename, including extension: ")))
           (public-flag (if arg "--public=true" "--public=false"))
           (tmp-gist-dir "/tmp/emacs-tmp-gists")
           (tmp-gist-file (format "%s/%s" tmp-gist-dir gist-file-name))
           (default-directory tmp-gist-dir))
      (jw--file-write content tmp-gist-file)
      (jw--run-cmd (format "hub gist create %s %s" public-flag (shell-quote-argument gist-file-name))))))

(defun jw-gist-dired-files (arg)
  "Create a single gist from either the marked dired files, or the current file if none are marked."
  (interactive "P")
  (if (not (executable-find "hub"))
      (message "Executable hub required to create gists: https://hub.github.com/")
    (let* ((files-list (dired-get-marked-files))
           (files-str (mapconcat 'shell-quote-argument files-list " "))
           (public-flag (if arg "--public=true" "--public=false")))
      (if files-list
          (jw--run-cmd (format "hub gist create %s %s" public-flag files-str))
        (message "Cannot find files to gist. Either mark files or hover a file with the cursor.")))))

(defun jw-pair-programming-on ()
  (interactive)
  (mode-line-on)
  (mode-line-pwd-on))

(defun jw-pair-programming-off ()
  (interactive)
  (mode-line-off)
  (mode-line-pwd-off))

emacs functions

(defun emacs-config ()
  (interactive)
  (find-file jw--init-org-file))

(defun emacs-private-config ()
  (interactive)
  (find-file jw--init-private-org-file))

(defun emacs-configs-toggle (arg)
  (interactive "P")
  (if arg
      (if (string= (buffer-name) (file-name-nondirectory jw--init-private-org-file))
          (switch-to-buffer (other-buffer))
        (emacs-private-config))
    (if (string= (buffer-name) (file-name-nondirectory jw--init-org-file))
        (switch-to-buffer (other-buffer))
      (emacs-config))))

(defun emacs-reload-config ()
  (interactive)
  (load-file user-init-file))

(defun emacs-archive-packages ()
  (when (f-exists? package-user-dir)
    (let ((archive-dir (format "/tmp/emacs-elpa--%s" (jw--iso-current-time-string))))
      (f-move package-user-dir archive-dir))))

(defun emacs-archive-packages-and-die ()
  (interactive)
  (emacs-archive-packages)
  (life-is-too-much))

key bindings

;; remove bindings for functions I don't regularly use
(global-unset-key (kbd "C-z")) ;; suspend-frame
(global-unset-key (kbd "C-x C-z")) ;; suspend-frame
(global-unset-key (kbd "C-x .")) ;; set-fill-prefix
(global-unset-key (kbd "C-x f")) ;; set-fill-column
(global-unset-key (kbd "C-x C-n")) ;; set-goal-column, too easy to confuse with narrow

(define-prefix-command 'jw-keymap)
(global-set-key (kbd "C-x m") 'jw-keymap)
(global-set-key (kbd "C-c m") 'jw-keymap)

(global-set-key (kbd "M-!") 'cmd-dwim)
(global-set-key (kbd "M-&") 'cmd-dwim)
(define-key jw-keymap (kbd "!") 'cmd-menu)
(define-key jw-keymap (kbd "&") 'cmd-menu)
(define-key jw-keymap (kbd "q") 'emacs-configs-toggle)
(define-key jw-keymap (kbd "d") 'date)
(define-key jw-keymap (kbd "D") 'world-clock)
(define-key jw-keymap (kbd "w") 'weather)
(define-key jw-keymap (kbd "i") 'toggle-scratch-buffer)
(define-key jw-keymap (kbd "I") 'scratch-buffer-remember)

(global-set-key (kbd "M-*") 'calculator)
(global-set-key (kbd "C-s") 'isearch-forward-regexp)
(global-set-key (kbd "C-r") 'isearch-backward-regexp)
(global-set-key (kbd "M-s i") 'imenu)
(global-set-key (kbd "C-M-/") 'counsel-company)
(global-set-key (kbd "C-M-g") 'goto-line)
(global-set-key (kbd "C-M-9") 'winner-undo)
(global-set-key (kbd "C-M-0") 'winner-redo)
(global-set-key (kbd "C-w") 'kill-region-or-line)
(global-set-key (kbd "M-w") 'kill-ring-save-region-or-line)
(global-set-key (kbd "C-a") 'beginning-of-line-or-indentation)
(global-set-key (kbd "C-o") 'open-line-previous)
(global-set-key (kbd "C-<return>") 'open-line-next)
(global-set-key (kbd "C-j") 'newline-and-open-line-previous)
(global-set-key (kbd "M-c") 'capitalize-dwim)
(global-set-key (kbd "M-u") 'upcase-dwim)
(global-set-key (kbd "C-x C-u") 'upcase-dwim)
(global-set-key (kbd "M-l") 'downcase-dwim)
(global-set-key (kbd "C-x C-l") 'downcase-dwim)
(global-set-key (kbd "C-<tab>") 'indent-region-or-buffer)
(global-set-key (kbd "C-M-<tab>") 'jw-pulse-text)
(global-set-key (kbd "C-M-;") 'cycle-spacing) ;; just-one-space is at S-M-<space>
(global-set-key (kbd "M-;") 'comment-dwim-dwim)
(global-set-key (kbd "C-=") 'er/expand-region)
(global-set-key (kbd "C-*") 'mc/mark-all-like-this)
(global-set-key (kbd "C-<") 'mc/mark-previous-like-this)
(global-set-key (kbd "C->") 'mc/mark-next-like-this)
(global-set-key (kbd "C-x r t") 'mc/edit-lines)
(define-key sp-keymap (kbd "M-<backspace>") nil)
(define-key sp-keymap (kbd "C-M-p") nil)
(define-key sp-keymap (kbd "C-M-n") nil)
(global-set-key (kbd "C-x C-d") 'dired)
(global-set-key (kbd "C-x d") 'cd)
(define-key ctl-x-4-map (kbd "C-d") 'dired-other-window)
(define-key grep-mode-map (kbd "w") 'wgrep-change-to-wgrep-mode)
(define-key dired-mode-map (kbd "C-x C-q") 'wdired-change-to-wdired-mode)
(define-key dired-mode-map (kbd "w") 'wdired-change-to-wdired-mode)
(define-key dired-mode-map (kbd "<tab>") 'dired-hide-subdir-dwim)
(define-key dired-mode-map (kbd "$") 'dired-hide-subdir-dwim)
(define-key dired-mode-map (kbd "k") 'dired-do-kill-line-dwim)
(define-key dired-mode-map (kbd "K") 'dired-do-kill-subdir-dwim)
(define-key dired-mode-map (kbd "e") 'dired-find-file-external)
(define-key narrow-map (kbd "r") 'narrow-to-region)
(define-key narrow-map (kbd "n") 'jw-narrow-dwim)
(define-key narrow-map (kbd "' '") 'edit-indirect-region)
(define-key narrow-map (kbd "' c") 'edit-indirect-region-css)
(define-key narrow-map (kbd "' h") 'edit-indirect-region-html)
(define-key help-map (kbd "M-n") 'helpful-at-point)
(define-key help-map (kbd "H") 'info-apropos)
(define-key help-map (kbd "h") 'cheat-sh)
(define-key search-map (kbd "M-w") 'web-search-dwim)

;; bind C-<backspace> and s-<backspace> both to the same command, to match some other apps
(global-set-key (kbd "s-<backspace>") 'backward-kill-word)

(define-key verb-command-map (kbd "C-r") 'verb-send-request-on-point)
(define-key verb-command-map (kbd "C-s") 'verb-send-request-on-point-other-window-stay)
(define-key verb-command-map (kbd "C-f") 'verb-send-request-on-point-other-window)
(define-key verb-command-map (kbd "C-i") 'jw-curl-to-verb-dwim)
(define-key verb-response-body-mode-map (kbd "C-c C-r C-r") 'verb-re-send-request)
(define-key verb-response-body-mode-map (kbd "C-c C-r C-h") 'verb-toggle-show-headers)

(define-key ctl-x-5-map (kbd "<return>") 'toggle-frame-maximized)
(define-key ctl-x-5-map (kbd "S-<return>") 'toggle-frame-fullscreen)

(define-key ctl-x-4-map (kbd "2") 'toggle-window-split)
(define-key ctl-x-4-map (kbd "3") 'toggle-window-split)
(define-key ctl-x-4-map (kbd "1") 'rotate-windows)
(define-key ctl-x-4-map (kbd "g") 'font-size-transparency-alpha-default)
(define-key ctl-x-4-map (kbd "SPC") 'mode-line-toggle-visibility)

;; text-scale-adjust is still accessible via C-x C-=/-/0
(global-set-key (kbd "s-=") 'font-size-increase)
(global-set-key (kbd "s--") 'font-size-decrease)
(global-set-key (kbd "s-0") 'font-size-default)

(global-set-key (kbd "s-+") 'transparency-alpha-decrease)
(global-set-key (kbd "s-_") 'transparency-alpha-increase)
(global-set-key (kbd "s-)") 'transparency-alpha-default)

;; doubling these up between super & control for now, to align with above bindings. might re-purpose one set later.
(jw--global-set-visual-area-key "<s-wheel-up>" 'font-size-increase)
(jw--global-set-visual-area-key "<s-wheel-down>" 'font-size-decrease)
(jw--global-set-visual-area-key "<s-S-wheel-up>" 'transparency-alpha-decrease)
(jw--global-set-visual-area-key "<s-S-wheel-down>" 'transparency-alpha-increase)

(jw--global-set-visual-area-key "<C-wheel-up>" 'font-size-increase)
(jw--global-set-visual-area-key "<C-wheel-down>" 'font-size-decrease)
(jw--global-set-visual-area-key "<C-S-wheel-up>" 'transparency-alpha-decrease)
(jw--global-set-visual-area-key "<C-S-wheel-down>" 'transparency-alpha-increase)

;; suppress undefined binding messages for the following wheel gestures.
;; this happens too easily with pixel-scroll-precision-mode.
(jw--global-set-visual-area-key "<s-wheel-right>" 'jw-nothing)
(jw--global-set-visual-area-key "<s-wheel-left>" 'jw-nothing)
(jw--global-set-visual-area-key "<s-S-wheel-right>" 'jw-nothing)
(jw--global-set-visual-area-key "<s-S-wheel-left>" 'jw-nothing)

(jw--global-set-visual-area-key "<C-wheel-right>" 'jw-nothing)
(jw--global-set-visual-area-key "<C-wheel-left>" 'jw-nothing)
(jw--global-set-visual-area-key "<C-S-wheel-right>" 'jw-nothing)
(jw--global-set-visual-area-key "<C-S-wheel-left>" 'jw-nothing)

eshell and vterm

(defun eshell-dwim (arg)
  (interactive "P")
  (jw--shells-dwim-gen 'eshell-mode arg 'eshell))

(defun eshell-kill-all ()
  (interactive)
  (jw--kill-buffers-using-major-mode 'eshell-mode))

(define-key jw-keymap (kbd "e") 'eshell-dwim)
(define-key jw-keymap (kbd "E") 'eshell-kill-all)

(when (package-installed-p 'vterm)
  (defun vterm-dwim (arg)
    (interactive "P")
    (jw--shells-dwim-gen 'vterm-mode arg 'vterm))

  ;; not the most optimal way to exit vterm since it has functions it runs on exit
  (defun vterm-kill-all ()
    (interactive)
    (jw--kill-buffers-using-major-mode 'vterm-mode))

  (define-key jw-keymap (kbd "v") 'vterm-dwim)
  (define-key jw-keymap (kbd "V") 'vterm-kill-all)
  )

(defun jw--shells-dwim-gen (mode arg create-new)
  "A wrapper for `eshell' and `vterm', except that this function provides ordered cycling through all shells creating using prefix arguments.
No prefix argument: Create a new shell or switch to an existing shell. If multiple shell buffers exist, then cycle through them in their buffer number order.
Single prefix arg C-u: Create an additional shell: *eshell*, *eshell*<2>, *eshell*<3>, etc.
Negative prefix arg C--: Similar behavior to no prefix argument, except the cycling behavior is in reverse."
  (pcase (prefix-numeric-value arg)
    (4 (funcall create-new t))
    (-1 (jw--buffers-cycle-to-next-using-major-mode mode t create-new nil))
    (_ (jw--buffers-cycle-to-next-using-major-mode mode nil create-new nil))))

(require 'em-alias)
(eshell/alias "l" "ls -alh")
(eshell/alias "d" "dired $1")
(eshell/alias "e" "find-file $1")
(eshell/alias "emacs" "find-file $1")
(eshell/alias "vi" "find-file $1")
(eshell/alias "vim" "find-file $1")
(eshell/alias "less" "find-file $1")
(eshell/alias "cat" "find-file $1")
(eshell/alias ":q" "exit")
(eshell/alias ":Q" "exit")

(setq eshell-banner-message "")

(add-to-list 'eshell-mode-hook (lambda ()
                                 (add-to-list 'eshell-visual-subcommands '("git" "log" "diff" "show"))
                                 (add-to-list 'eshell-visual-subcommands '("g" "log" "diff" "show"))))

(defun eshell/which--advice--add-login-shell-which-output (eshell/which-function &rest names)
  (eshell-printn "\neshell/which:")
  (apply eshell/which-function names)
  (let* ((login-shell-program (jw--login-shell))
         (raw-result (shell-command-to-string (format "%s -c \"which %s\"" login-shell-program (s-join " " names))))
         (login-shell-which-result (format "\n%s's which:\n%s" login-shell-program raw-result)))
    (eshell-printn login-shell-which-result)))

(advice-add 'eshell/which :around 'eshell/which--advice--add-login-shell-which-output)

eshell prompt

(defun eshell--last-command-status-prompt-string ()
  (if (= 0 eshell-last-command-status)
      ""
    (propertize (format "-%s-\n" eshell-last-command-status) 'face '(:foreground "red3"))))

(defun eshell--git-prompt-string ()
  (require 'vc)
  (if (jw--git-root-dir)
      ;; vc-git-branches returns (list nil) instead of nil when there is no branch name instead of just nil (i.e. after a git-init)
      (let* ((git-branch-name (or (car (vc-git-branches)) "(in the beginning there was darkness)"))
             (git-is-clean (s-blank? (shell-command-to-string "git status --porcelain")))
             (git-is-clean-marker (if git-is-clean "" ""))
             (git-is-clean-color (if git-is-clean "green" "red1"))
             (git-branch-name-string (propertize git-branch-name 'face '(:foreground "yellow3")))
             (git-is-clean-string (propertize git-is-clean-marker 'face `(:foreground ,git-is-clean-color))))
        (format "%s %s" git-branch-name-string git-is-clean-string))
    ""))

(defun eshell--prompt-function ()
  (let* ((last-status-string (eshell--last-command-status-prompt-string))
         (dir-string (propertize (abbreviate-file-name (eshell/pwd)) 'face '(:foreground "CornflowerBlue")))
         (git-string (eshell--git-prompt-string))
         (prompt-string (propertize "»" 'face '(:foreground "red3")))
         (right-pad-string (propertize " " 'face '(:foreground nil)))
         (prompt-string (s-collapse-whitespace (format "%s %s %s %s" dir-string git-string prompt-string right-pad-string))))
    (concat last-status-string prompt-string)))

(setq eshell-prompt-function 'eshell--prompt-function)
(setq eshell-prompt-regexp "^[^#$»\n]* [#$»] ")

eglot and dape

(require 'eglot)
(require 'dape)

(global-flycheck-eglot-mode 1)

(setq eglot-confirm-server-initiated-edits nil)

(global-set-key (kbd "C-c l l") 'eglot)
(define-key eglot-mode-map (kbd "C-c l L") 'eglot-shutdown)
(define-key eglot-mode-map (kbd "C-c l a") 'eglot-code-actions) ;; should other specific code actions have their own bindings?
(define-key eglot-mode-map (kbd "C-c l R") 'eglot-rename)
(define-key eglot-mode-map (kbd "C-c l <TAB>") 'eglot-format)

(setq dape-buffer-window-arrangement 'right)
(setq dape-cwd-fn 'projectile-project-root)

(defalias 'dape-shutdown 'dape-quit)

yasnippet

(defun yas-dwim (arg)
  (interactive "P")
  (when arg (end-of-buffer) (newline 2))
  (yas-insert-snippet))

(yas-global-mode 1)

(global-set-key (kbd "M-?") 'yas-dwim)

(setq yas-indent-line nil)

(setq yas-dynamic-snippets-dir (f-expand "snippets-dynamic" user-emacs-directory))
(add-to-list 'yas-snippet-dirs yas-dynamic-snippets-dir)

(defun yas-write-dynamic-snippet (mode shortcut contents)
  (let* ((mode-string (jw--symbol-name mode))
         (shortcut-string (jw--symbol-name shortcut))
         (file-location (f-expand (format "%s/%s" mode-string shortcut-string) yas-dynamic-snippets-dir))
         (file-contents-format-string "# -*- mode: snippet -*-\n# name: %s\n# --\n%s")
         (file-contents (format file-contents-format-string shortcut-string contents)))
    (jw--file-write file-contents file-location)))

magit

(defalias 'git-browse-at-remote 'browse-at-remote)
(defalias 'github-browse-file 'browse-at-remote)

(setq browse-at-remote-add-line-number-if-no-region-selected nil)

(setq transient-enable-popup-navigation t)
(setq transient-display-buffer-action '(display-buffer-below-selected))

;; https://github.com/magit/transient/commit/bb056e7156b3d88f42770ec55e1a7447a95aca96
;; https://github.com/magit/transient/commit/98d502023817aa06f3046ba89c7c5a856ed88c35
;; todo revisit this after more transient changes hapen & the feature is more settled
(define-key transient-popup-navigation-map (kbd "C-p") #'transient-backward-button)
(define-key transient-popup-navigation-map (kbd "C-n") #'transient-forward-button)
(define-key transient-popup-navigation-map (kbd "RET") #'transient-push-button)

;; magit works faster with the full path to git instead of just "git". https://magit.vc/manual/magit/MacOS-Performance.html
;; also, use-package magit tries to set this before jw-env-set is called, pointing it to a different git.
(setq magit-git-executable (executable-find "git"))
(setq magit-define-global-key-bindings nil)

(define-prefix-command 'jw-magit-map)
(global-set-key (kbd "M-g") 'jw-magit-map)
(global-set-key (kbd "M-G") goto-map) ;; move old M-g (goto-map) to capital G since I'm hijacking it for magit

(define-key jw-magit-map (kbd "g") 'magit-status)
(define-key jw-magit-map (kbd "d") 'magit-dispatch)
(define-key jw-magit-map (kbd "f") 'magit-file-dispatch)
(define-key jw-magit-map (kbd "C-x C-f") 'magit-find-file)
(define-key jw-magit-map (kbd "M-w") 'browse-at-remote)

irc

(require 'rcirc)
(require 'erc)

(defun jw-irc (&optional server port nick user-name full-name startup-channels password encryption)
  "Wrapper for `rcirc' and `rcirc-connect'.
If no server is provided, then prompt for server & connection details.
If the server is not connected, then connect to it.
If the server is connected, then toggle to it's process buffer.
If the server is connected and a prefix arg is provided, then invoke a quick /msg on the server and toggle back to the other-buffer."
  (interactive)
  (if server
      (let ((existing-sp (get-process server)))
        (if (process-live-p existing-sp)
            (if current-prefix-arg
                (save-window-excursion
                  (switch-to-buffer (process-buffer existing-sp))
                  (call-interactively 'rcirc-cmd-msg))
              (switch-to-buffer (process-buffer existing-sp)))
          (let* ((proc (rcirc-connect server port nick user-name full-name startup-channels password encryption))
                 (buffer (process-buffer proc)))
            (switch-to-buffer buffer))))
    (rcirc t)))

(defalias 'irc 'jw-irc)

(cl-defun jw-irc-connect (&key server port nick user-name full-name startup-channels password encryption)
  (jw-irc server port nick user-name full-name startup-channels password encryption))

(setq rcirc-buffer-maximum-lines 5000)
(add-to-list 'rcirc-omit-responses "MODE")

(defun rcirc-hook--initial-config ()
  (jw--save-current-message
    (set (make-local-variable 'scroll-conservatively) 8192)
    (rcirc-track-minor-mode t)
    (rcirc-omit-mode)
    (cd (getenv "HOME"))))

(add-hook 'rcirc-mode-hook 'rcirc-hook--initial-config)

(defun irc-hook--span-window-width ()
  (setq rcirc-fill-column (- (window-width) 2))
  (setq erc-fill-column (- (window-width) 2)))

(add-hook 'window-configuration-change-hook 'irc-hook--span-window-width)

(defun rcirc-handler-NOTICE--advice--ignore-KEEPALIVE (original-function &rest args)
  (let* ((function-args (nth 2 args))
         (msg (cadr function-args)))
    (unless (string-match "keepalive" msg)
      (apply original-function args))))

(advice-add 'rcirc-handler-NOTICE :around 'rcirc-handler-NOTICE--advice--ignore-KEEPALIVE)

(setq erc-header-line-format nil
      erc-hide-list '("JOIN" "PART" "QUIT")
      erc-prompt (lambda () (concat (buffer-name) " >")))

(add-to-list 'erc-mode-hook (lambda () (set (make-local-variable 'scroll-conservatively) 8192)))

org

(require 'org)
(require 'verb)

(unless (boundp 'jw-org-todo-file)
  (setq jw-org-todo-file (f-expand "todo.org" user-emacs-directory)))

(defun jw-todo ()
  (interactive)
  (if (s-equals? (buffer-name) (f-filename jw-org-todo-file))
      (switch-to-buffer (other-buffer))
    (find-file jw-org-todo-file)
    (cd (getenv "HOME"))))

(defun jw-org-capture ()
  (interactive)
  (if (fboundp 'counsel-org-capture) (counsel-org-capture) (org-capture)))

(defun jw-todo-or-catpure (arg)
  (interactive "P")
  (if arg (jw-org-capture) (jw-todo)))

(defun jw-org-feed-update-all-or-one (arg)
  "When called with a prefix argument, interactively call `org-feed-update'. Otherwise call `org-feed-update-all'."
  (interactive "P")
  (if arg
      (call-interactively 'org-feed-update)
    (org-feed-update-all)))

(defun jw-org-id (arg)
  "Ensure an org-id exists and copy to kill ring. With prefix arg, force creation of a new org-id."
  (interactive "P")
  (org-id-get-create arg)
  (org-id-copy))

(setq org-special-ctrl-a/e t
      org-special-ctrl-k t
      org-special-ctrl-o t
      org-startup-folded t
      org-startup-with-inline-images t
      org-hide-block-startup t
      org-enforce-todo-dependencies t
      org-enforce-todo-checkbox-dependencies t
      org-return-follows-link t
      org-tags-column -100
      org-adapt-indentation t
      org-src-preserve-indentation t
      org-cycle-open-archived-trees t
      org-todo-keywords '((sequence "" "" "|" "" ""))
      org-hide-leading-stars t
      org-ellipsis ""
      org-pretty-entities nil ;; do not use this, it causes _ subscripts, which interfere with pg table names and such
      org-hide-emphasis-markers nil ;; do not use this, interferes with things like example URLs being made italic
      org-fontify-done-headline t
      org-log-repeat nil ;; don't add a state-change timestamp everytime a recurring task repeats
      org-confirm-babel-evaluate nil
      org-link-shell-confirm-function nil
      org-link-elisp-confirm-function nil
      org-src-window-setup 'current-window
      org-src-tab-acts-natively nil ;; do not use this, it interferes with newlines
      org-agenda-todo-list-sublevels nil
      org-agenda-window-setup 'only-window
      org-refile-targets '((org-agenda-files :maxlevel . 10))
      org-refile-use-outline-path t
      org-refile-allow-creating-parent-nodes 'confirm
      org-id-link-to-org-use-id 'create-if-interactive
      org-capture-bookmark nil ;; org variant of bookmark-fringe-mark
      )

(defun jw-toggle-org-src-window-setup ()
  (interactive)
  (pcase org-src-window-setup
    (`current-window
     (setq org-src-window-setup 'split-window-below)
     (message "Set org-src-window-setup to split-window-below"))
    (_
     (setq org-src-window-setup 'current-window)
     (message "Set org-src-window-setup to current-window"))
    ))

(add-hook 'org-babel-after-execute-hook 'org-display-inline-images)

(define-key jw-keymap (kbd "o") 'jw-todo-or-catpure)
(define-key jw-keymap (kbd "a") 'org-agenda)
(define-key ctl-x-4-map (kbd "'") 'jw-toggle-org-src-window-setup)
(add-hook 'org-mode-hook (lambda ()
                           (local-set-key (kbd "C-c r") 'org-reveal)
                           (local-set-key (kbd "C-c C-r") verb-command-map)
                           (local-set-key (kbd "C-c C-x g") 'jw-org-feed-update-all-or-one)))

(defun org--color-red-box-state (s) `(,s :background "DarkRed" :foreground "LightGrey" :box (:style released-button)))
(defun org--color-red-state (s) `(,s :foreground "Coral"))
(defun org--color-blue-box-state (s) `(,s :background "DeepSkyBlue4" :foreground "LightGrey" :box (:style released-button)))
(defun org--color-blue-state (s) `(,s :foreground "DeepSkyBlue1"))
(defun org--color-green-box-state (s) `(,s :background "DarkGreen" :foreground "LightGrey" :box (:style released-button)))
(defun org--color-green-state (s) `(,s :foreground "LimeGreen"))

(setq org--todo-todo-boxed-states '("todo" "maybe" "someday" "started" "incoming" "captured" "unread" "question" "problem" "issue" "shitshow" "alert" "warning")
      org--todo-todo-states '("")
      org--blocked-todo-boxed-states '("blocked" "halted" "stalled" "paused")
      org--doing-todo-boxed-states '("doing" "going")
      org--doing-todo-states '("")
      org--delegated-todo-boxed-states '("thinking" "investigating" "delegated" "assigned" "pr" "waiting" "merged" "deploying" "note" "idea")
      org--done-todo-boxed-states '("done" "cancelled" "canceled" "finished" "boom" "read" "answered" "noted" "fixed" "solved" "warned")
      org--done-todo-states '("" ""))

(setq org-todo-keyword-faces
      (append
       (mapcar 'org--color-red-box-state org--todo-todo-boxed-states)
       (mapcar 'org--color-red-box-state (mapcar 'upcase org--todo-todo-boxed-states))

       (mapcar 'org--color-red-box-state org--blocked-todo-boxed-states)
       (mapcar 'org--color-red-box-state (mapcar 'upcase org--blocked-todo-boxed-states))

       (mapcar 'org--color-red-state org--todo-todo-states)

       (mapcar 'org--color-blue-box-state org--doing-todo-boxed-states)
       (mapcar 'org--color-blue-box-state (mapcar 'upcase org--doing-todo-boxed-states))

       (mapcar 'org--color-blue-box-state org--delegated-todo-boxed-states)
       (mapcar 'org--color-blue-box-state (mapcar 'upcase org--delegated-todo-boxed-states))

       (mapcar 'org--color-blue-state org--doing-todo-states)

       (mapcar 'org--color-green-box-state org--done-todo-boxed-states)
       (mapcar 'org--color-green-box-state (mapcar 'upcase org--done-todo-boxed-states))

       (mapcar 'org--color-green-state org--done-todo-states)
       ))

(delete '("+" (:strike-through t)) org-emphasis-alist)
(add-to-list 'org-emphasis-alist '("+" (:strike-through t :inherit shadow)))

org-babel languages & automatic yasnippet creation

(defun org-babel-src-yasnippet (ob-lang &optional ob-src-header-override ob-src-header-additional-args)
  (let* ((yas-src-shortcut (concat "src-" (jw--symbol-name ob-lang)))
         (ob-src-header-lang-str (jw--symbol-name (or ob-src-header-override ob-lang)))
         (ob-src-header-additional-args-str (if ob-src-header-additional-args (concat " " ob-src-header-additional-args) ""))
         (ob-src-string (format "#+BEGIN_SRC %s%s\n$0\n#+END_SRC" ob-src-header-lang-str ob-src-header-additional-args-str)))
    (yas-write-dynamic-snippet 'org-mode yas-src-shortcut ob-src-string)))

(defun org-babel-support-langs (langs)
  (org-babel-do-load-languages 'org-babel-load-languages (-map (lambda (lang) `(,lang . t)) langs))
  (-each langs (lambda (lang) (org-babel-src-yasnippet lang))))

(org-babel-support-langs
 (list 'awk 'emacs-lisp 'lisp 'eshell 'shell 'calc
       'C 'java 'js 'latex 'makefile 'org 'perl 'python 'R 'ruby 'scheme 'sql 'go 'mongo
       'verb))

(add-to-list 'org-src-lang-modes '("elisp" . emacs-lisp))
(org-babel-src-yasnippet 'elisp 'emacs-lisp)

;; org-babel-execute:* already exists or there is no explicit ob-*.el file, only need the yasnippet
(org-babel-src-yasnippet 'bash)
(org-babel-src-yasnippet 'cpp)
(org-babel-src-yasnippet 'C++)

;; alias yasnippets to other ob-* blocks
(org-babel-src-yasnippet 'javascript 'js)
(org-babel-src-yasnippet 'nodejs 'js)
(org-babel-src-yasnippet 'http 'verb)

;; ob/yasnippets for modes that don't need an org-babel-execute function, just give them a dummy execute function that returns the body
(defun org-babel-format-lang (ob-lang &optional ob-src-header-override)
  (org-babel-src-yasnippet ob-lang ob-src-header-override)
  (fset (intern (format "org-babel-execute:%s" (or ob-src-header-override ob-lang)))
        `(lambda (body params) (interactive) body)))

(org-babel-format-lang 'fundamental)
(org-babel-format-lang 'markdown)
(org-babel-format-lang 'gfm)
(org-babel-format-lang 'conf)
(org-babel-format-lang 'text)
(org-babel-format-lang 'yaml)
(org-babel-format-lang 'html)
(org-babel-format-lang 'xml)
(org-babel-format-lang 'css)

(org-babel-format-lang 'json)
(add-to-list 'org-src-lang-modes '("json" . js-json))

(setq org-babel-default-header-args:sh '((:results . "output"))
      org-babel-default-header-args:shell '((:results . "output"))
      org-babel-default-header-args:bash '((:results . "output")))

(let ((c-compiler (executable-find "clang"))
      (cpp-compiler (executable-find "clang++")))
  (when c-compiler (setq org-babel-C-compiler c-compiler))
  (when cpp-compiler (setq org-babel-C++-compiler cpp-compiler)))

support cmd function in org-babel and cmd org link

(defconst org-babel-header-args:cmd '((bg . :any) (tmux . :any)))

;; warning: cmd does not work with the :async header since ob-cmd is never provided (which org-babel-do-load-languages requires)
(defun org-babel-execute:cmd (body params)
  (let* ((bg-option (assoc :bg params))
         (in-bg (and bg-option (not (string= (cdr bg-option) "no"))))
         (tmux-option (assoc :tmux params))
         (tmux-session (or (cdr tmux-option) "emacs")))
    (if tmux-option
        (progn (cmd-tmux body tmux-session) (format "Sent to tmux session: %s" tmux-session))
      (progn
        (cmd body)
        (when in-bg (switch-to-buffer (other-buffer)))
        "Running command"))))

(add-to-list 'org-src-lang-modes '("cmd" . sh))

(define-derived-mode cmd-mode sh-mode "cmd")

(setq org-babel-default-header-args:cmd '((:results . "silent")))

(org-babel-src-yasnippet 'cmd)
(org-babel-src-yasnippet 'tmux "cmd :tmux")

(org-link-set-parameters "cmd" :follow #'(lambda (path _) (cmd path)))
(org-link-set-parameters "cmd+tmux" :follow #'(lambda (path _) (cmd-tmux path)))

support gist & gist+raw org links

(defun org-gist-link-follow (ref &optional raw)
  (let ((url-segment (if (s-contains? "/" ref) ref
                       (concat (or (jw--git-config-get "github.user") (jw--git-config-get "user.name")) "/" ref)))
        (raw-segment (if raw "raw" "")))
    (browse-url (format "https://gist.github.com/%s/%s" url-segment raw-segment))))

(org-link-set-parameters "gist" :follow #'(lambda (ref _) (org-gist-link-follow ref)))
(org-link-set-parameters "gist+raw" :follow #'(lambda (ref _) (org-gist-link-follow ref 'raw)))

ivy counsel

(ivy-mode 1)

(setq ivy-use-virtual-buffers t
      ivy-count-format "%d/%d "
      ivy-initial-inputs-alist nil
      ivy-use-selectable-prompt t
      ivy-magic-tilde nil
      ivy-re-builders-alist '((t . ivy--regex-ignore-order)))

(setq max-mini-window-height 0.90) ;; fix for https://github.com/abo-abo/swiper/issues/2397

(setq counsel-switch-buffer-preview-virtual-buffers nil) ;; performance increase to main switch-buffer function

(defun counsel-find-file-dwim (arg)
  (interactive "P")
  (let ((initial-input (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) nil)))
    (pcase (prefix-numeric-value arg)
      (16 (counsel-file-jump initial-input))
      (4 (counsel-recentf))
      (_ (counsel-find-file initial-input)))))

(defun ivy-dispatching-done-ivy ()
  (interactive)
  (let ((ivy-read-action-function #'ivy-read-action-ivy))
    (ivy-dispatching-done)))

(defun ivy-resume-or-else-counsel-M-x ()
  (interactive)
  (when (not (ignore-errors (call-interactively 'ivy-resume)))
    (call-interactively 'counsel-M-x)))

(defun counsel-emacs-processes-dwim (arg)
  (interactive "P")
  (if arg (list-processes) (counsel-list-processes)))

(defun counsel-grep-switch-to-grep ()
  "Switch interactive counsel-grep/rg minibuffer to opening a good old grep buffer."
  (interactive)
  (let ((d (or ivy--directory (jw--pwd)))
        (cmd (concat grep-command (shell-quote-argument ivy-text))))
    (ivy-exit-with-action `(lambda (_) (let ((default-directory ,d)) (grep ,cmd))))))

(defun counsel-find-file--cmd-dwim-action (file) (let ((default-directory ivy--directory)) (cmd-dwim ivy-current-prefix-arg)))
(defun counsel-find-file--magit-status-action (file) (let ((default-directory ivy--directory)) (magit-status)))
(defun counsel-find-file--eshell-action (file) (let ((default-directory ivy--directory)) (eshell)))
(defun counsel-find-file--dired-action (file) (dired ivy--directory))
(defun counsel-find-file--jw-sbt-action (file) (let ((default-directory ivy--directory)) (jw-sbt)))
(defun counsel-find-file--sbt-compile-action (file) (let ((default-directory ivy--directory)) (sbt-compile ivy-current-prefix-arg)))

(ivy-add-actions
 'counsel-find-file
 '(("!" counsel-find-file--cmd-dwim-action "cmd-dwim in pwd")
   ("g" counsel-find-file--magit-status-action "magit-status in pwd")
   ("C-xC-d" counsel-find-file--dired-action "dired in pwd")
   ("C-cme" counsel-find-file--eshell-action "eshell in pwd")
   ("C-css" counsel-find-file--jw-sbt-action "sbt in pwd")
   ("C-csc" counsel-find-file--sbt-compile-action "sbt compile in pwd")))

(global-set-key (kbd "M-x") 'counsel-M-x)
(global-set-key (kbd "M-z") 'ivy-resume-or-else-counsel-M-x)
(define-key ivy-minibuffer-map (kbd "C-o") 'ivy-dispatching-done)
(define-key ivy-minibuffer-map (kbd "M-o") 'ivy-dispatching-done-ivy)
(global-set-key (kbd "C-x C-f") 'counsel-find-file-dwim)
(global-set-key (kbd "C-x f") 'pop-to-buffer)
(global-set-key (kbd "C-x F") 'find-file-external)
(global-set-key (kbd "C-x b") 'counsel-switch-buffer)
(global-set-key (kbd "C-x C-b") 'ibuffer)
(global-set-key (kbd "C-x E") 'counsel-kmacro)
(global-set-key (kbd "C-x p") 'counsel-emacs-processes-dwim)
(global-set-key (kbd "C-x P") 'proced)
(global-set-key (kbd "M-s o") 'swiper-thing-at-point)
(global-set-key (kbd "M-s O") 'swiper-all-thing-at-point)
(global-set-key (kbd "C-s") 'swiper)
(global-set-key (kbd "C-r") 'swiper-backward)
(global-set-key (kbd "M-s i") 'counsel-imenu)
(define-key swiper-map (kbd "C-s") 'ivy-next-line)
(define-key swiper-map (kbd "C-r") 'ivy-previous-line)
(global-set-key (kbd "M-s s") 'counsel-rg)
(global-set-key (kbd "M-s g") 'counsel-grep)
(global-set-key (kbd "M-s S") 'grep)
(global-set-key (kbd "M-s G") 'grep)
(global-set-key (kbd "C-M-y") 'counsel-yank-pop)
(define-key counsel-find-file-map (kbd "C-l") 'counsel-up-directory)
(define-key minibuffer-local-map (kbd "C-r") 'counsel-minibuffer-history)
(define-key org-mode-map (kbd "C-c C-j") 'counsel-outline) ;; counsel-org-goto
(add-hook 'eshell-mode-hook (lambda () (define-key eshell-mode-map (kbd "M-p") 'counsel-esh-history)))

(define-key counsel-grep-map (kbd "M-s S") 'counsel-grep-switch-to-grep)
(define-key counsel-grep-map (kbd "M-s G") 'counsel-grep-switch-to-grep)
(define-key counsel-ag-map (kbd "M-s S") 'counsel-grep-switch-to-grep)
(define-key counsel-ag-map (kbd "M-s G") 'counsel-grep-switch-to-grep)

(define-key help-map (kbd "b") 'counsel-descbinds)
(define-key help-map (kbd "f") 'counsel-describe-function)
(define-key help-map (kbd "v") 'counsel-describe-variable)
(define-key help-map (kbd "S") 'counsel-info-lookup-symbol)
(define-key help-map (kbd "a") 'counsel-apropos)
(define-key help-map (kbd "c") 'counsel-describe-face)

projectile

(setq projectile-completion-system 'ivy)
(counsel-projectile-mode)

(global-set-key (kbd "C-c p") 'projectile-command-map)

(setq projectile-sort-order 'recently-active)
(setq projectile-enable-caching t)
(setq projectile-ignored-projects '("~/"))

;; default node projects to yarn instead of npm
(projectile-register-project-type 'npm '("package.json")
                                  :project-file "package.json"
                                  :configure "yarn install"
                                  :compile "yarn dev"
                                  :run "yarn dev"
                                  :test "yarn test")

(defun counsel-projectile-switch-project-action-test (project)
  (let ((projectile-switch-project-action (lambda () (projectile-test-project ivy-current-prefix-arg))))
    (counsel-projectile-switch-project-by-name project)))

(defun counsel-projectile-switch-project--cmd-dwim-action (project)
  (let ((projectile-switch-project-action (lambda () (cmd-dwim ivy-current-prefix-arg))))
    (counsel-projectile-switch-project-by-name project)))

(defun counsel-projectile-switch-project--basic-grep-action (project)
  (projectile-with-default-dir project
    (call-interactively 'grep)))

(defun counsel-projectile-switch-project--jw-sbt-action (project)
  (let ((projectile-switch-project-action 'jw-sbt))
    (counsel-projectile-switch-project-by-name project)))

(defun counsel-projectile-switch-project--jw-sbt-compile-action (project)
  (let ((projectile-switch-project-action (lambda () (jw-sbt-compile ivy-current-prefix-arg))))
    (counsel-projectile-switch-project-by-name project)))

(defun counsel-projectile-switch-project--jw-sbt-test-action (project)
  (let ((projectile-switch-project-action (lambda () (jw-sbt-test ivy-current-prefix-arg))))
    (counsel-projectile-switch-project-by-name project)))

(defun counsel-projectile-switch-project--sbt-run-previous-command-action (project)
  (let ((projectile-switch-project-action (lambda () (sbt-run-previous-command))))
    (counsel-projectile-switch-project-by-name project)))

(defun counsel-projectile--filter-out-action-keys (actions keys-to-filter)
  (-filter (lambda (action) (not (-contains? keys-to-filter (car action)))) actions))

(let* ((head (car counsel-projectile-switch-project-action))
       (original-actions (cdr counsel-projectile-switch-project-action))
       (fixed-actions (counsel-projectile--filter-out-action-keys original-actions (list "si" "ss" "sr" "sg"))))
  (setq counsel-projectile-switch-project-action (cons head fixed-actions)))

(ivy-add-actions
 'counsel-projectile-switch-project
 '(("ss" counsel-projectile-switch-project-action-rg "run rg in project root")
   ("sg" counsel-projectile-switch-project-action-grep "run grep in project root")
   ("sS" counsel-projectile-switch-project--basic-grep-action "run basic grep in project root")
   ("sG" counsel-projectile-switch-project--basic-grep-action "run basic grep in project root")
   ("P" counsel-projectile-switch-project-action-test "run project test command")
   ("!" counsel-projectile-switch-project--cmd-dwim-action "cmd-dwim in project root")
   ("g" counsel-projectile-switch-project-action-vc "open project in vc-dir / magit / monky")
   ("C-xC-d" counsel-projectile-switch-project-action-dired "open project in dired")
   ("C-cme" counsel-projectile-switch-project-action-run-eshell "invoke eshell from project root")
   ("C-css" counsel-projectile-switch-project--jw-sbt-action "sbt")
   ("C-csc" counsel-projectile-switch-project--jw-sbt-compile-action "sbt compile")
   ("C-cst" counsel-projectile-switch-project--jw-sbt-test-action "sbt test")
   ("C-csg" counsel-projectile-switch-project--sbt-run-previous-command-action "sbt run previous command")
   ))

(defun projectile-basic-grep ()
  (interactive)
  (projectile-with-default-dir (projectile-acquire-root)
    (call-interactively 'grep)))

(defun projectile-cmd-dwim ()
  (interactive)
  (projectile-with-default-dir (projectile-acquire-root)
    (call-interactively 'cmd-dwim)))

(define-key projectile-command-map (kbd "s r") nil) ;; remove original rg keybinding. only use s & g for grepping.
(define-key projectile-command-map (kbd "s s") 'counsel-projectile-rg)
(define-key projectile-command-map (kbd "s S") 'projectile-basic-grep)
(define-key projectile-command-map (kbd "s G") 'projectile-basic-grep)
(define-key projectile-command-map (kbd "!") 'projectile-cmd-dwim)
(define-key projectile-command-map (kbd "&") 'projectile-cmd-dwim)

(defalias 'projectile-empty-garbage 'projectile-cleanup-known-projects)
(defalias 'projectile-purge-everything 'projectile-clear-known-projects)

(defun projectile-clear-known-projects--advice--ask-y-or-n (original-function)
  (if (yes-or-no-p "This will REMOVE ALL projects from projectile. Are you sure?")
      (apply original-function)
    (message "Did NOT clear the projectile projects.")))

(advice-add 'projectile-clear-known-projects :around 'projectile-clear-known-projects--advice--ask-y-or-n)

kubel

(when (package-installed-p 'kubel)
  (require 'kubel)

  (defalias 'k8s 'kubel)

  (kubel-vterm-setup)

  (setq kubel-list-wide t)

  (advice-add 'kubel-kill-buffer :after 'delete-window) ;; kubel-kill-buffer leaves the frame split into two windows from pop-to-buffer, when it was originally just one window.

  (defun jw-kubel--run-via-cmd (command context namespace k8s-resource selected-resource)
    (let ((command-str (format "%s --context %s -n %s %s %s %s" kubel-kubectl context namespace command k8s-resource selected-resource)))
      (kubel--log-command "jw-kubel-command" command-str)
      (jw--run-cmd command-str)
      ))

  (defun jw-kubel-edit-resource-at-point ()
    (interactive)
    (jw-kubel--run-via-cmd "edit" kubel-context kubel-namespace kubel-resource (kubel--get-resource-under-cursor))
    )

  (defun jw-kubel-read-and-edit-resource ()
    (interactive)
    (let* ((k8s-resource (completing-read "Select resource: " (kubel--kubernetes-resources-list)))
           (selected-resource (kubel--select-resource k8s-resource)))
      (jw-kubel--run-via-cmd "edit" kubel-context kubel-namespace k8s-resource selected-resource)))

  ;; update kubel edit functions bound to RET and E to use kubectl-edit via jw--run-cmd when C-u is used. any other prefix arg goes back to the original function.
  ;; easier via advice since the functions are not only bound both by a mode map and in the magit/transient help menu macro.

  (advice-add 'kubel-get-resource-details :around (lambda (f &rest args)
                                                    (if (equal (prefix-numeric-value (car args)) 4) (jw-kubel-edit-resource-at-point) (apply f args))
                                                    ))



  (advice-add 'kubel-quick-edit :around (lambda (f &rest args)
                                          (if (equal (prefix-numeric-value current-prefix-arg) 4) (jw-kubel-read-and-edit-resource) (apply f args))
                                          ))
  )

scala

(defun jw-sbt ()
  (interactive)
  (if (eq major-mode 'sbt-mode)
      (switch-to-buffer (other-buffer))
    (with-temp-buffer
      (if (sbt:find-root)
          (sbt-start)
        (call-interactively 'jw-sbt-run-or-create-new)))))

(defun jw-sbt-run-or-create-new (dir)
  (interactive "DSBT run or create new project in: ")
  (when (not (f-exists? dir)) (make-directory dir 'make-parents))
  (let ((default-directory dir))
    (with-temp-buffer
      (if (sbt:find-root)
          (sbt-start)
        (cmd "sbt-new")))))

(defun sbt-current-tests-in-buffer ()
  (save-excursion
    (let* ((pkg-name-components)
           (test-names))
      (goto-char (point-min))
      (while (re-search-forward "package " nil t)
        (push (buffer-substring-no-properties (point) (point-at-eol)) pkg-name-components))
      (goto-char (point-min))
      (while (re-search-forward "\\(object\\|class\\) " nil t)
        (push (buffer-substring-no-properties (point) (progn (re-search-forward " ") (forward-char -1) (point)))
              test-names))
      (let* ((full-pkg-name (string-join (reverse pkg-name-components) "."))
             (full-test-names (mapcar #'(lambda (test-name) (string-join (list full-pkg-name "." test-name))) test-names))
             (full-test-names-str (string-join full-test-names " ")))
        full-test-names-str))))

(defun jw-sbt-compile (test-compile)
  (interactive "P")
  (if test-compile
      (sbt-command "test:compile")
    (sbt-command "compile")))

(defun jw-sbt-test (arg)
  (interactive "P")
  (pcase (prefix-numeric-value arg)
    (4 (sbt-command (concat "testOnly " (sbt-current-tests-in-buffer) " -- ex zzz")))
    (16 (sbt-command "test"))
    (_ (sbt-command (concat "testOnly " (sbt-current-tests-in-buffer))))))

(defun sbt:command--advice--message-command (original-function &rest args)
  (message "sbt %s" (car args))
  (apply original-function args))
(advice-add 'sbt:command :around 'sbt:command--advice--message-command) ;; sbt-command delegates to sbt:command

(add-to-list 'auto-mode-alist '("\\.scala$" . scala-mode))
(add-to-list 'auto-mode-alist '("\\.sbt$" . scala-mode))

(setq scala-indent:align-forms t
      scala-indent:align-parameters t)

(define-prefix-command 'sbt-keymap)
(global-set-key (kbd "C-c s") 'sbt-keymap)

(define-key sbt-keymap (kbd "s") 'jw-sbt)
(define-key sbt-keymap (kbd "c") 'jw-sbt-compile)
(define-key sbt-keymap (kbd "t") 'jw-sbt-test)
(define-key sbt-keymap (kbd "g") 'sbt-run-previous-command)

support ammonite repl in org babel, requires `amm` command, or ammonite-repl

(require 'org)
(require 'ob)

(defun org-babel-execute:ammonite (body params)
  (jw--file-write body "/tmp/ob-ammonite-input.scala")
  (shell-command-to-string "amm --silent /tmp/ob-ammonite-input.scala"))

(add-to-list 'org-src-lang-modes '("ammonite" . scala))

(org-babel-src-yasnippet 'ammonite)
(org-babel-src-yasnippet 'scala 'ammonite) ;; default ob-scala requires brew scala & ensime, which I don't use.

javascript

(require 'js)

(defun nodejs-repl-send-region-or-buffer ()
  (interactive)
  (if (region-active-p)
      (nodejs-repl-send-region (region-beginning) (region-end))
    (nodejs-repl-send-buffer)))

(define-key js-mode-map (kbd "C-c C-j") nil) ;; remove js-set-js-context
(define-key js-mode-map (kbd "M-.") nil) ;; remove js-find-symbol -- the built-in xref-find-definitions works better for me

(define-key js-mode-map (kbd "C-x C-e") 'nodejs-repl-send-last-expression)
(define-key js-mode-map (kbd "C-c C-j") 'nodejs-repl-send-line)
(define-key js-mode-map (kbd "C-c C-r") 'nodejs-repl-send-region)
(define-key js-mode-map (kbd "C-c C-c") 'nodejs-repl-send-region-or-buffer)
(define-key js-mode-map (kbd "C-c C-l") 'nodejs-repl-load-file)
(define-key js-mode-map (kbd "C-c C-z") 'nodejs-repl-switch-to-repl)

(require 'json)
(require 's)
(require 'f)
(require 'projectile)

(defun jw-js--package-json-scripts-alist ()
  (alist-get 'scripts (json-parse-string (f-read-text "package.json") :object-type 'alist)))

(defun jw-js-package-json-scripts ()
  "Function that autogenerates interactive functions for each package.json script in either the current projectile project or else the pwd."
  (interactive)
  (let* ((default-directory (or (projectile-project-root) (jw--pwd)))
         (dir-name (car (last (f-split default-directory)))))
    (dolist (scripts-alist (jw-js--package-json-scripts-alist))
      (let ((command-name (format "yarn %s" (jw--symbol-name (car scripts-alist))))
            (command-impl (cdr scripts-alist)))
        (fset (intern (format "package-json--%s--%s" dir-name (s-replace " " "-" command-name)))
              `(lambda (arg)
                 ,(format "Autogenerated function from `jw-js-package-json-scripts' function.\n\nRuns:\n %s\n %s\n\nIn directory:\n %s"
                          command-name command-impl default-directory
                          )
                 (interactive "P")
                 (let ((default-directory ,default-directory))
                   (message "Compile: %s [%s]" ,command-name ,command-impl)
                   (pcase (prefix-numeric-value arg)
                     (16 (cmd ,command-name))
                     (_ (compile ,command-name (not (null arg))))
                     ))))))
    (counsel-M-x (format "package-json--%s" dir-name))))

support typescript in org babel

(require 'org)
(require 'ob)

(defun org-babel-execute:typescript (body params)
  (let* ((run-js-option (assoc :run-js params))
         (run-js (and run-js-option (string= (cdr run-js-option) "true")))
         (input-ts-file "/tmp/ob-typescript-tsc-input.ts")
         (output-js-file "/tmp/ob-typescript-tsc-output.js"))
    (delete-file output-js-file)
    (jw--file-write body input-ts-file)
    (message "Running tsc on %s" input-ts-file)
    (shell-command (format "tsc %s --outFile %s" input-ts-file output-js-file))
    (if (not (file-exists-p output-js-file)) "Error running tsc, check messages buffer"
      (message "Output of tsc in %s" output-js-file)
      (if (not run-js)
          (f-read output-js-file)
        (message "Executing js %s" output-js-file)
        (org-babel-execute:js (f-read output-js-file) params)))))

(org-babel-src-yasnippet 'typescript nil ":run-js true")
(org-babel-src-yasnippet 'ts 'typescript ":run-js true")

(setq org-babel-default-header-args:typescript '((:results . "output")))

private config

load the private directory and the private.org file if they exist

(when (file-exists-p jw--init-private-dir)
  (add-to-list 'load-path jw--init-private-dir)
  (mapcar 'load-file (directory-files jw--init-private-dir t "\.el$")))

(when (file-exists-p jw--init-private-org-file)
  (org-babel-load-file jw--init-private-org-file))

final settings

(when (get-buffer "*scratch*") (kill-buffer "*scratch*"))
(scratch-buffer)

(yas-reload-all)

;; (titlebar-off)

(font-size-transparency-alpha-default)

(unless (eq (frame-parameter nil 'fullscreen) 'maximized) (toggle-frame-maximized))

(unless (server-running-p) (server-start))

(cd (getenv "HOME"))

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published