Skip to content
Evil emacs configuration
Branch: master
Clone or download
Latest commit 8fa5deb Apr 8, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
test_runs
.gitignore
Makefile
README.org
configuration.org
init.el

README.org

mpereira’s emacs configuration

Evil emacs configuration.

Table of Contents

Dependencies

  • ag
  • ripgrep
  • fish
  • python3
  • GitHub personal token (for magit, gist, etc.)
  • Wolfram Alpha AppID (for wolfram)
  • TODO: Google Apps Calendar (for org-gcal)
  • ~/.emacs.d/circe-secrets.el
    • mpereira/secret-circe-nickserv-password
  • ~/.emacs.d/org-gcal-secrets.el
    • mpereira/secret-org-gcal-client-id
    • mpereira/secret-org-gcal-client-secret
    • mpereira/secret-org-gcal-file-alist
  • ~/.emacs.d/wolfram-secrets.el
    • mpereira/secret-wolfram-alpha-app-id

s

(use-package s)

dash

(use-package dash)

general

(use-package general)

Variables

(setq mpereira/custom-file (expand-file-name "custom.el" user-emacs-directory))
(setq mpereira/leader ",")
(setq mpereira/light-theme 'twilight-bright)
(setq mpereira/dark-theme 'nimbus)
(setq mpereira/dropbox-directory (file-name-as-directory
                                  (expand-file-name "~/Dropbox")))
(setq mpereira/org-directory (expand-file-name "org" mpereira/dropbox-directory))
(setq mpereira/org-calendar-file (expand-file-name "gcal/calendar.org"
                                                   mpereira/org-directory))
(setq mpereira/org-calendar-buffer-name (file-name-nondirectory
                                         mpereira/org-calendar-file))
(setq mpereira/fill-column 80)
(setq mpereira/fill-column-wide 120)

Redefinitions

;; Before:
;;   (:foo bar
;;         :baz qux)
;; After:
;;   (:foo bar
;;    :baz qux)
;; Got from:
;; https://github.com/Fuco1/.emacs.d/blob/a8230343bb7e2f07f5eac8e63e5506fa164344f6/site-lisp/my-redef.el#L25
(eval-after-load "lisp-mode"
  '(defun lisp-indent-function (indent-point state)
     "This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine if the arguments of
a Lisp function call should be indented specially. INDENT-POINT is the position
at which the line being indented begins. Point is located at the point to indent
under (for default indentation); STATE is the `parse-partial-sexp' state for
that position. If the current line is in a call to a Lisp function that has a
non-nil property `lisp-indent-function' (or the deprecated `lisp-indent-hook'),
it specifies how to indent. The property value can be: * `defun', meaning indent
`defun'-style \(this is also the case if there is no property and the function
has a name that begins with \"def\", and three or more arguments); * an integer
N, meaning indent the first N arguments specially
  (like ordinary function arguments), and then indent any further
  arguments like a body;
* a function to call that returns the indentation (or nil).
  `lisp-indent-function' calls this function with the same two arguments
  that it itself received.
This function returns either the indentation to use, or nil if the
Lisp function does not specify a special indentation."
     (let ((normal-indent (current-column))
           (orig-point (point)))
       (goto-char (1+ (elt state 1)))
       (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
       (cond
        ;; car of form doesn't seem to be a symbol, or is a keyword
        ((and (elt state 2)
              (or (not (looking-at "\\sw\\|\\s_"))
                  (looking-at ":")))
         (if (not (> (save-excursion (forward-line 1) (point))
                     calculate-lisp-indent-last-sexp))
             (progn (goto-char calculate-lisp-indent-last-sexp)
                    (beginning-of-line)
                    (parse-partial-sexp (point)
                                        calculate-lisp-indent-last-sexp 0 t)))
         ;; Indent under the list or under the first sexp on the same
         ;; line as calculate-lisp-indent-last-sexp.  Note that first
         ;; thing on that line has to be complete sexp since we are
         ;; inside the innermost containing sexp.
         (backward-prefix-chars)
         (current-column))
        ((and (save-excursion
                (goto-char indent-point)
                (skip-syntax-forward " ")
                (not (looking-at ":")))
              (save-excursion
                (goto-char orig-point)
                (looking-at ":")))
         (save-excursion
           (goto-char (+ 2 (elt state 1)))
           (current-column)))
        (t
         (let ((function (buffer-substring (point)
                                           (progn (forward-sexp 1) (point))))
               method)
           (setq method (or (function-get (intern-soft function)
                                          'lisp-indent-function)
                            (get (intern-soft function) 'lisp-indent-hook)))
           (cond ((or (eq method 'defun)
                      (and (null method)
                           (> (length function) 3)
                           (string-match "\\`def" function)))
                  (lisp-indent-defform state indent-point))
                 ((integerp method)
                  (lisp-indent-specform method state
                                        indent-point normal-indent))
                 (method
                  (funcall method indent-point state)))))))))

;; Adds support for showing completion descriptions.
(with-eval-after-load "fish-completion"
  (defun fish-completion-complete (raw-prompt)
    "Complete RAW-PROMPT (any string) using the fish shell.

If `fish-completion-fallback-on-bash-p' is non-nil and if the `bash-completion'
package is available, fall back on bash in case no completion was found with
fish."
    (while
        (pcomplete-here
         (let ((completions
                (let* (;; Keep spaces at the end with OMIT-NULLS=nil in
                       ;; `split-string'.
                       (tokens* (split-string raw-prompt
                                              split-string-default-separators
                                              nil))
                       ;; The first non-empty `car' is the command. Discard
                       ;; leading empty strings.
                       (tokens (progn (while (string= (car tokens*) "")
                                        (setq tokens* (cdr tokens*)))
                                      tokens*))
                       ;; Fish does not support subcommand completion. We make a
                       ;; special case of 'sudo' and 'env' since they are the most
                       ;; common cases involving subcommands. See
                       ;; https://github.com/fish-shell/fish-shell/issues/4093.
                       (prompt (if (not (member (car tokens) '("sudo" "env")))
                                   raw-prompt
                                 (setq tokens (cdr tokens))
                                 (while (and tokens
                                             (or (string-match "^-.*" (car tokens))
                                                 (string-match "=" (car tokens))))
                                   ;; Skip env/sudo parameters, like LC_ALL=C.
                                   (setq tokens (cdr tokens)))
                                 (mapconcat 'identity tokens " "))))
                  ;; Completion result can be a filename. pcomplete expects
                  ;; cannonical file names (i.e. without '~') while fish preserves
                  ;; non-cannonical results. If the result contains a directory,
                  ;; expand it.
                  (split-string
                   (with-output-to-string
                     (with-current-buffer standard-output
                       (call-process fish-completion-command
                                     nil
                                     t
                                     nil
                                     "-c"
                                     (format "complete -C%s"
                                             (shell-quote-argument prompt)))))
                   "\n"
                   t))))
           (if (and fish-completion-fallback-on-bash-p
                    (or (not completions)
                        (file-exists-p (car completions)))
                    (require 'bash-completion nil t))
               ;; Remove trailing spaces of bash completion entries. (Does this
               ;; only occurs when there is 1 completion item?)
               ;; TODO: Maybe this should be fixed in bash-completion instead.
               (mapcar 'string-trim-right
                       (mapcar (lambda (s)
                                 ;; bash-completion inserts "\" to escape white
                                 ;; spaces, we need to remove them since pcomplete
                                 ;; does that too.
                                 (replace-regexp-in-string (regexp-quote "\\") "" s))
                               (nth 2 (bash-completion-dynamic-complete-nocomint
                                       (save-excursion (eshell-bol) (point)) (point)))))
             (if (and completions (file-exists-p (car completions)))
                 (pcomplete-dirs-or-entries)
               (let ((formatted-completions
                      (mapcar
                       (lambda (e)
                         (multiple-value-bind (flag description) (split-string e "\t")
                           ;; Remove trailing spaces to avoid it being converted
                           ;; into "\ ".
                           (string-trim-right
                            (if description
                                (replace-regexp-in-string
                                 (regexp-quote " ")
                                 ""
                                 (format "%-50s %s" flag description))
                              flag))))
                       completions)))
                 formatted-completions))))))))

Helper functions

(defmacro comment (&rest body)
  "Comment out one or more s-expressions."
  nil)

(defmacro print-and-return (&rest body)
  "TODO: docstring."
  (let ((result-symbol (make-symbol "result")))
    `(let ((,result-symbol ,@body))
       (message "************************************************************")
       (pp ',@body)
       (message "||")
       (message "\\/")
       (print ,result-symbol)
       (message "************************************************************")
       ,result-symbol)))

(defalias 'remove-from-list 'object-remove-from-list)

(defun mpereira/shorten-directory (project-root-dir dir max-length)
  "Show up to MAX-LENGTH characters of a directory name DIR."
  (let* ((directory-truncation-string (if (char-displayable-p ?…) "…/" ".../"))
         (project-dir (s-chop-prefix project-root-dir dir))
         (dir-short (abbreviate-file-name project-dir)))
    ;; If it fits, return the string.
    (if (<= (string-width dir-short) max-length) dir-short
      ;; If it doesn't, shorten it.
      (let ((path (reverse (split-string dir-short "/")))
            (output ""))
        (when (and path (equal "" (car path)))
          (setq path (cdr path)))
        (let ((max (- max-length (string-width directory-truncation-string))))
          ;; Concat as many levels as possible, leaving 4 chars for safety.
          (while (and path (<= (string-width (concat (car path) "/" output))
                               max))
            (setq output (concat (car path) "/" output))
            (setq path (cdr path))))
        ;; If we had to shorten, prepend .../
        (when path
          (setq output (concat directory-truncation-string output)))
        output))))

(defun mpereira/hide-trailing-whitespace ()
  (interactive)
  (setq-local show-trailing-whitespace nil))

(defun mpereira/delete-file-and-buffer ()
  "Kill the current buffer and deletes the file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (when filename
      (if (vc-backend filename)
          (vc-delete-file filename)
        (progn
          (delete-file filename)
          (message "Deleted file %s" filename)
          (kill-buffer))))))

(defun mpereira/rename-file-and-buffer ()
  "Rename the current buffer and file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (if (not (and filename (file-exists-p filename)))
        (message "Buffer is not visiting a file!")
      (let ((new-name (read-file-name "New name: " filename)))
        (cond
         ((vc-backend filename) (vc-rename-file filename new-name))
         (t
          (rename-file filename new-name t)
          (set-visited-file-name new-name t t)))))))

(require 'thingatpt)

;; Depends on 'thingatpt' for `thing-at-point'.
(defun mpereira/eval-sexp-at-or-surrounding-pt ()
  "Evaluate the sexp following the point, or surrounding the point"
  (interactive)
  (save-excursion
    (forward-char 1)
    (if (search-backward "(" nil t)
        (message "%s" (eval (read-from-whole-string (thing-at-point 'sexp)))))))

(defun mpereira/split-window-below-and-switch ()
  "Split the window horizontally then switch to the new window."
  (interactive)
  (split-window-below)
  (balance-windows)
  (other-window 1))

(defun mpereira/split-window-right-and-switch ()
  "Split the window vertically then switch to the new window."
  (interactive)
  (split-window-right)
  (balance-windows)
  (other-window 1))

(defun mpereira/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))))
    (message "Can only toggle window split for 2 windows")))

(defun mpereira/indent-buffer ()
  "Indents the current buffer."
  (interactive)
  (indent-region (point-min) (point-max)))

(with-eval-after-load "lispy"
  (defun mpereira/inside-or-at-the-end-of-string ()
    (when (lispy--in-string-p)
      (let* ((p (point))
             (bounds (lispy--bounds-string)))
        (and (not (= p (car bounds)))
             (not (= p (cdr bounds)))))))

  (defun mpereira/backward-sexp (arg)
    "Moves to the beginning of the previous ARG nth sexp."
    (interactive "p")
    (if (mpereira/inside-or-at-the-end-of-string)
        (let ((bounds (lispy--bounds-string)))
          (goto-char (car bounds))
          (backward-sexp (- arg 1)))
      (backward-sexp arg)))

  (defun mpereira/forward-sexp (arg)
    "Moves to the beginning of the next ARG nth sexp. The fact that this doesn't
exist in any structured movement package is mind-boggling to me."
    (interactive "p")
    (when (mpereira/inside-or-at-the-end-of-string)
      (let ((bounds (lispy--bounds-string)))
        (goto-char (- (car bounds) 1))))
    (dotimes (i arg)
      (forward-sexp 1)
      (if (looking-at lispy-right)
          (backward-sexp 1)
        (progn
          (forward-sexp 1)
          (backward-sexp 1))))))

;; https://github.com/syl20bnr/spacemacs/blob/
;; b7e51d70aa3fb81df2da6dc16d9652a002ba5e6b/layers/%2Bspacemacs/
;; spacemacs-layouts/funcs.el#352
;;
;; plus `projectile-persp-switch-project'
(with-eval-after-load "ivy"
  (with-eval-after-load "projectile"
    (with-eval-after-load "perspective"
      (defun mpereira/ivy-persp-switch-project (arg)
        (interactive "P")
        (ivy-read "Switch to Project Perspective: "
                  (if (projectile-project-p)
                      (cons (abbreviate-file-name (projectile-project-root))
                            (projectile-relevant-known-projects))
                    projectile-known-projects)
                  :action #'projectile-persp-switch-project)))))

(with-eval-after-load "evil"
  (with-eval-after-load "lispyville"
    (defun mpereira/insert-to-beginning-of-list (arg)
      (interactive "p")
      (lispyville-backward-up-list)
      (evil-forward-char)
      (evil-insert arg))

    (defun mpereira/append-to-end-of-list (arg)
      (interactive "p")
      (lispyville-up-list)
      (evil-insert arg))))

(defun mpereira/org-current-subtree-state-p (state)
  (string= state (org-get-todo-state)))

(defun mpereira/org-up-heading-top-level ()
  "Move to the top level heading."
  (while (not (= 1 (org-outline-level)))
    (org-up-heading-safe)))

(defun mpereira/org-skip-all-but-first ()
  "Skip all but the first non-done entry."
  (let (should-skip-entry)
    (unless (mpereira/org-current-subtree-state-p "TODO")
      (setq should-skip-entry t))
    (save-excursion
      (while (and (not should-skip-entry) (org-goto-sibling t))
        (when (mpereira/org-current-subtree-state-p "TODO"))
        (setq should-skip-entry t)))
    (when should-skip-entry
      (or (outline-next-heading)
          (goto-char (point-max))))))

(defun mpereira/org-skip-subtree-if-habit ()
  "Skip an agenda entry if it has a STYLE property equal to \"habit\"."
  (let ((subtree-end (save-excursion (org-end-of-subtree t))))
    (if (string= (org-entry-get nil "STYLE") "habit")
        subtree-end
      nil)))

(defun mpereira/org-skip-subtree-unless-habit ()
  "Skip an agenda entry unless it has a STYLE property equal to \"habit\"."
  (let ((subtree-end (save-excursion (org-end-of-subtree t))))
    (if (string= (org-entry-get nil "STYLE") "habit")
        nil
      subtree-end)))

(defun mpereira/org-skip-inbox ()
  "Skip agenda entries coming from the inbox."
  (let ((subtree-end (save-excursion (org-end-of-subtree t))))
    (if (string= (org-get-category) "inbox")
        subtree-end
      nil)))

(defun mpereira/org-skip-someday-projects-subheadings ()
  "Skip agenda entries under a project with state \"SOMEDAY\"."
  (let ((subtree-end (save-excursion (org-end-of-subtree t))))
    (mpereira/org-up-heading-top-level)
    (if (mpereira/org-current-subtree-state-p "SOMEDAY")
        subtree-end
      nil)))

(defun mpereira/org-entry-at-point-get (property)
  (org-entry-get (point) property))

(defun mpereira/deadline-or-scheduled ()
  (interactive)
  (cond
   ((mpereira/org-entry-at-point-get "DEADLINE") "Deadline")
   ((mpereira/org-entry-at-point-get "SCHEDULED") "Scheduled")))

(defun mpereira/org-agenda-tags-suffix ()
  (interactive)
  (let* ((timestamp (or (mpereira/org-entry-at-point-get "DEADLINE")
                        (mpereira/org-entry-at-point-get "SCHEDULED")))
         (current (calendar-date-string (calendar-current-date)))
         (days (time-to-number-of-days (time-subtract
                                        (org-read-date nil t timestamp)
                                        (org-read-date nil t current))))
         (string (format-time-string "%d %b %Y" (org-read-date t t timestamp))))
    (format "In %dd (%s) %10s:"
            days
            string
            (mpereira/deadline-or-scheduled))))

(defun mpereira/org-agenda-project-name-prefix-format ()
  (s-truncate 20 (car (org-get-outline-path t))))

(defun mpereira/org-agenda-format-date (date)
  "Format a DATE string for display in the daily/weekly agenda.
This function makes sure that dates are aligned for easy reading."
  (let* ((dayname (calendar-day-name date))
         (day (cadr date))
         (day-of-week (calendar-day-of-week date))
         (month (car date))
         (monthname (calendar-month-name month))
         (year (nth 2 date)))
    (format "\n%-9s %2d %s"
            dayname day monthname year)))

(defun mpereira/yesterday ()
  (time-subtract (current-time) (days-to-time 1)))

(defun mpereira/time-to-calendar-date (time)
  (let* ((decoded-time (decode-time time))
         (day (nth 3 decoded-time))
         (month (nth 4 decoded-time))
         (year (nth 5 decoded-time)))
    (list month day year)))

(defun mpereira/format-calendar-date-Y-m-d (calendar-date)
  (format-time-string "%Y-%m-%d"
                      (mpereira/calendar-date-to-time calendar-date)))

(defun mpereira/format-calendar-date-d-m-Y (calendar-date)
  (format-time-string "%d %B %Y"
                      (mpereira/calendar-date-to-time calendar-date)))

(defun mpereira/calendar-date-to-time (calendar-date)
  (let* ((day (calendar-extract-day calendar-date))
         (month (calendar-extract-month calendar-date))
         (year (calendar-extract-year calendar-date)))
    (encode-time 0 0 0 day month year)))

(defun mpereira/calendar-read-date (string)
  (mpereira/time-to-calendar-date (org-read-date t t string)))

(defun mpereira/org-agenda-date-week-start (string)
  "Returns the first day of the week at DATE."
  (let* ((calendar-date (mpereira/calendar-read-date string)))
    (mpereira/format-calendar-date-Y-m-d
     (mpereira/time-to-calendar-date
      (time-subtract
       (mpereira/calendar-date-to-time calendar-date)
       (days-to-time (if (zerop (calendar-day-of-week calendar-date))
                         6 ;; magic.
                       (- (calendar-day-of-week calendar-date)
                          calendar-week-start-day))))))))

(defun mpereira/org-agenda-date-week-end (string)
  "Returns the last day of the week at DATE."
  (let* ((calendar-date (mpereira/calendar-read-date string)))
    (if (= (calendar-week-end-day) (calendar-day-of-week calendar-date))
        string
      (mpereira/format-calendar-date-Y-m-d
       (mpereira/time-to-calendar-date
        (time-add
         (mpereira/calendar-date-to-time calendar-date)
         (days-to-time (- 7 (calendar-day-of-week calendar-date)))))))))

(defun mpereira/org-agenda-review-suffix-format ()
  (let* ((timestamp (or (mpereira/org-entry-at-point-get "TIMESTAMP")
                        (mpereira/org-entry-at-point-get "TIMESTAMP_IA")
                        (mpereira/org-entry-at-point-get "DEADLINE")
                        (mpereira/org-entry-at-point-get "SCHEDULED")))
         (calendar-date (mpereira/calendar-read-date timestamp)))
    (format "%s  %-22s"
            (mpereira/format-calendar-date-Y-m-d calendar-date)
            (mpereira/org-agenda-project-name-prefix-format))))

(defun mpereira/org-agenda-review-search (start end)
  (concat "TODO=\"DONE\""
          "&"
          "TIMESTAMP_IA>=\"<" start ">\""
          "&"
          "TIMESTAMP_IA<=\"<" end ">\""
          "|"
          "TODO=\"DONE\""
          "&"
          "TIMESTAMP>=\"<" start ">\""
          "&"
          "TIMESTAMP<=\"<" end ">\""))

;; https://lists.gnu.org/archive/html/emacs-orgmode/2015-06/msg00266.html
(defun mpereira/org-agenda-delete-empty-blocks ()
  "Remove empty agenda blocks.
A block is identified as empty if there are fewer than 2 non-empty
lines in the block (excluding the line with
`org-agenda-block-separator' characters)."
  (when org-agenda-compact-blocks
    (user-error "Cannot delete empty compact blocks"))
  (setq buffer-read-only nil)
  (save-excursion
    (goto-char (point-min))
    (let* ((blank-line-re "^\\s-*$")
           (content-line-count (if (looking-at-p blank-line-re) 0 1))
           (start-pos (point))
           (block-re (format "%c\\{10,\\}" org-agenda-block-separator)))
      (while (and (not (eobp)) (forward-line))
        (cond
         ((looking-at-p block-re)
          (when (< content-line-count 2)
            (delete-region start-pos (1+ (point-at-bol))))
          (setq start-pos (point))
          (forward-line)
          (setq content-line-count (if (looking-at-p blank-line-re) 0 1)))
         ((not (looking-at-p blank-line-re))
          (setq content-line-count (1+ content-line-count)))))
      (when (< content-line-count 2)
        (delete-region start-pos (point-max)))
      (goto-char (point-min))
      ;; The above strategy can leave a separator line at the beginning of the
      ;; buffer.
      (when (looking-at-p block-re)
        (delete-region (point) (1+ (point-at-eol))))))
  (setq buffer-read-only t))


(defun mpereira/org-sort-parent-entries (&rest args)
  ;; `org-sort-entries' doesn't respect `save-excursion'.
  (let ((origin (point)))
    (org-up-heading-safe)
    (apply #'org-sort-entries args)
    (goto-char origin)))

(defun mpereira/org-cycle-cycle ()
  (org-cycle)
  ;; https://www.mail-archive.com/emacs-orgmode@gnu.org/msg86779.html
  (ignore-errors
    (org-cycle)))

(defun mpereira/call-interactively-with-prefix-arg (prefix-arg func)
  (let ((current-prefix-arg prefix-arg))
    (call-interactively func)))

(with-eval-after-load "eshell"
  (with-eval-after-load "projectile"
    (defun mpereira/projectile-eshell ()
      (interactive)
      (if (projectile-project-p)
          (let ((eshell-buffer-name (concat "*eshell "
                                            (projectile-project-name)
                                            "*")))
            (projectile-with-default-dir (projectile-project-root)
              (eshell t)))
        (eshell t)))))

(defun mpereira/enable-line-numbers ()
  (setq display-line-numbers t))

(defun mpereira/disable-line-numbers ()
  (setq display-line-numbers nil))

(defun mpereira/maybe-enable-aggressive-indent-mode ()
  (when (not
         (or (cl-member-if #'derived-mode-p aggressive-indent-excluded-modes)
             buffer-read-only))
    (aggressive-indent-mode)))

(defun mpereira/lock-screen ()
  (interactive)
  ;; TODO: make file path joining portable.
  (let ((command (concat "/System"
                         "/Library"
                         "/CoreServices"
                         "/Menu\\ Extras"
                         "/User.menu"
                         "/Contents"
                         "/Resources"
                         "/CGSession"
                         " "
                         "-suspend")))
    (shell-command command)))

;; FIXME: this is broken.
(defun mpereira/toggle-maximize-buffer (&optional centered-p)
  "Toggle maximize buffer. TODO: document CENTERED-P."
  (interactive)
  (let ((return-to-window-configuration
         (lambda ()
           (when (boundp 'window-configuration-p)
             (set-window-configuration mpereira/saved-window-configuration)
             (goto-char mpereira/saved-point)
             (setq mpereira/saved-window-configuration nil))
           (when centered-p
             (olivetti-mode -1)
             (setq mpereira/saved-point nil))
           (setq mpereira/saved-centered-p nil))))
    (if (= 1 (length (window-list)))
        (progn
          ;; Centering single buffer.
          (if olivetti-mode
              (funcall return-to-window-configuration)
            (olivetti-mode 1)))
      ;; Maximizing selected buffer.
      (progn
        (setq mpereira/saved-window-configuration (current-window-configuration))
        (setq mpereira/saved-point (point))
        (setq mpereira/saved-centered-p centered-p)
        (delete-other-windows)
        (when centered-p
          (olivetti-mode 1))))))

(defun mpereira/epoch-at-point-to-timestamp ()
  "TODO: docstring"
  (interactive)
  (if-let (thing (counsel-symbol-at-point))
      (let* ((seconds (string-to-number thing))
             (time (seconds-to-time seconds))
             (timestamp (format-time-string "%Y-%m-%d %a %H:%M:%S" time)))
        (kill-new timestamp)
        (message timestamp)
        timestamp)))

(defun mpereira/pwd ()
  "TODO: docstring"
  (interactive)
  (let ((path (buffer-file-name)))
    (kill-new path)
    (message path)
    path))

(eval-when-compile (require 'cl)) ;; for `lexical-let'.
(defun mpereira/make-hs-hide-level (n)
  "TODO: docstring"
  (lexical-let ((n n))
    #'(lambda ()
        (interactive)
        (save-excursion
          (goto-char (point-min))
          (hs-hide-level n)))))

(defun mpereira/bm-counsel-get-list (bookmark-overlays)
  "TODO: docstring.
Arguments: BOOKMARK-OVERLAYS."
  (-map (lambda (bm)
          (with-current-buffer (overlay-buffer bm)
            (let* ((line (replace-regexp-in-string
                          "\n$"
                          ""
                          (buffer-substring (overlay-start bm)
                                            (overlay-end bm))))
                   ;; line numbers start on 1
                   (line-num (+ 1 (count-lines (point-min) (overlay-start bm))))
                   (name (format "%s:%d - %s" (buffer-name) line-num line)))
              `(,name . ,bm))))
        bookmark-overlays))

(defun mpereira/bm-counsel-find-bookmark ()
  "TODO: docstring.
Arguments: none."
  (interactive)
  (let* ((bm-list (mpereira/bm-counsel-get-list (bm-overlays-lifo-order t)))
         (bm-hash-table (make-hash-table :test 'equal))
         (search-list (-map (lambda (bm) (car bm)) bm-list)))
    (-each bm-list (lambda (bm)
                     (puthash (car bm) (cdr bm) bm-hash-table)))
    (ivy-read "Find bookmark: "
              search-list
              :require-match t
              :keymap counsel-describe-map
              :action (lambda (chosen)
                        (let ((bookmark (gethash chosen bm-hash-table)))
                          (switch-to-buffer (overlay-buffer bookmark))
                          (bm-goto bookmark)))
              :sort t)))

(defun mpereira/neotree-project-dir ()
  "Open NeoTree using the git root."
  (interactive)
  (let ((project-dir (projectile-project-root))
        (file-name (buffer-file-name)))
    (if project-dir
        (progn
          (neotree-dir project-dir)
          (neotree-find file-name))
      (message "Could not find git project root."))))

(defun mpereira/narrow-or-widen-dwim (p)
  "Widen if buffer is narrowed, narrow-dwim otherwise.
Dwim means: region, org-src-block, org-subtree, or defun, whichever applies
first. Narrowing to org-src-block actually calls `org-edit-src-code'.

With prefix P, don't widen, just narrow even if buffer is already narrowed."
  (interactive "P")
  (declare (interactive-only))
  (cond ((and (buffer-narrowed-p) (not p)) (widen))
        ((region-active-p)
         (narrow-to-region (region-beginning)
                           (region-end)))
        ((derived-mode-p 'org-mode)
         ;; `org-edit-src-code' is not a real narrowing command. Remove this
         ;; first conditional if you don't want it.
         (cond ((ignore-errors (org-edit-src-code) t)
                (delete-other-windows))
               ((ignore-errors (org-narrow-to-block) t))
               (t (org-narrow-to-subtree))))
        ((derived-mode-p 'latex-mode)
         (LaTeX-narrow-to-environment))
        (t (narrow-to-defun))))

(defun mpereira/uuid ()
  "Return a UUID."
  (interactive)
  (kill-new (format "%04x%04x-%04x-%04x-%04x-%06x%06x"
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 4))
                    (random (expt 16 6))
                    (random (expt 16 6)))))

;; TODO: make this better.
(defun mpereira/kill-last-kbd-macro ()
  "Save last executed macro definition in the kill ring."
  (let ((name (gensym "kill-last-kbd-macro-")))
    (name-last-kbd-macro name)
    (with-temp-buffer
      (insert-kbd-macro name)
      (kill-new (buffer-substring-no-properties (point-min) (point-max))))))

(defun mpereira/load-theme (theme)
  "TODO: docstring. THEME."
  (interactive)
  (counsel-load-theme-action (symbol-name theme))
  (when org-bullets-mode
    (org-bullets-mode -1)
    (org-bullets-mode 1)))

(defun mpereira/load-light-theme ()
  "TODO: docstring."
  (interactive)
  (mpereira/load-theme mpereira/light-theme))

(defun mpereira/load-dark-theme ()
  "TODO: docstring."
  (interactive)
  (mpereira/load-theme mpereira/dark-theme))

(defun mpereira/ps ()
  "Show list of system processes.
Copies the selected process's PID to the clipboard."
  (interactive)
  (let ((ps (split-string
             (shell-command-to-string
              "ps axco user,pid,%cpu,%mem,start,time,command -r")
             "\n"
             t)))
    (ivy-read "Process: "
              ps
              :action (lambda (x)
                        (kill-new (cadr (split-string x " " t)))))))

Reload directory local variables when saving .dir-locals.el files

Taken from Stack Overflow.

(defun mpereira/reload-dir-locals-for-current-buffer ()
  "Reload directory local variables on the current buffer."
  (interactive)
  (let ((enable-local-variables :all))
    (hack-dir-local-variables-non-file-buffer)))

(defun mpereira/reload-dir-locals-for-all-buffer-in-this-directory ()
  "Reload directory local variables on every buffer with the same
`default-directory' as the current buffer."
  (interactive)
  (let ((dir default-directory))
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when (equal default-directory dir))
        (mpereira/reload-dir-locals-for-current-buffer)))))

(defun mpereira/enable-autoreload-for-dir-locals ()
  (when (and (buffer-file-name)
             (equal dir-locals-file
                    (file-name-nondirectory (buffer-file-name))))
    (add-hook (make-variable-buffer-local 'after-save-hook)
              'mpereira/reload-dir-locals-for-all-buffer-in-this-directory)))

(add-hook 'emacs-lisp-mode-hook #'mpereira/enable-autoreload-for-dir-locals)

quelpa and quelpa-use-package

(use-package quelpa
  :config
  (quelpa
   '(quelpa-use-package
     :fetcher github
     :repo "quelpa/quelpa-use-package"))
  (require 'quelpa-use-package))

Tramp

(require 'tramp)

;; Disable version control on tramp buffers to avoid freezes.
(setq vc-ignore-dir-regexp
      (format "\\(%s\\)\\|\\(%s\\)"
              vc-ignore-dir-regexp
              tramp-file-name-regexp))

Server

(require 'server)

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

Options

;; Don't append customizations to init.el.
(setq custom-file mpereira/custom-file)
(load custom-file 'noerror)

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

;; Show CRLF characters.
;; http://pragmaticemacs.com/emacs/dealing-with-dos-line-endings/
(setq inhibit-eol-conversion t)

;; Set default font size to 18.
(set-face-attribute 'default nil :family "Consolas" :height 180)

;; Enable narrowing commands.
(put 'narrow-to-region 'disabled nil)

;; Start in full-screen.
(add-hook 'after-init-hook #'toggle-frame-fullscreen)

;; Show matching parens.
(setq show-paren-delay 0)
(show-paren-mode 1)

;; Disable eldoc.
(global-eldoc-mode -1)

;; Break lines automatically in "text" buffers.
(add-hook 'text-mode-hook 'auto-fill-mode)

;; Highlight current line.
(global-hl-line-mode t)

;; Provide undo/redo commands for window changes.
(winner-mode t)

;; Don't create backup~ files.
(setq make-backup-files nil)

;; Don't create #autosave# files.
(setq auto-save-default nil)

;; Don't lock files.
(setq create-lockfiles nil)

;; Shh...
(setq inhibit-startup-echo-area-message t)
(setq inhibit-startup-screen t)
(setq initial-scratch-message nil)
(setq ring-bell-function 'ignore)

;; Minimal titlebar for macOS.
(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 frame-title-format nil)

;; Make Finder's "Open with Emacs" create a buffer in the existing Emacs frame.
(setq ns-pop-up-frames nil)

;; macOS modifiers.
(setq mac-command-modifier 'meta)
(setq mac-option-modifier 'super)
(setq mac-control-modifier 'control)
(setq ns-function-modifier 'hyper)

;; Start scratch buffers in org-mode.
(setq initial-major-mode 'org-mode)

;; Make cursor the width of the character it is under e.g. full width of a TAB.
(setq x-stretch-cursor t)

;; By default Emacs thinks a sentence is a full-stop followed by 2 spaces. Make
;; it a full-stop and 1 space.
(setq sentence-end-double-space nil)

(fset 'yes-or-no-p 'y-or-n-p)

;; Switch to help buffer when it's opened.
(setq help-window-select t)

;; Don't recenter buffer point when point goes outside window.
(setq scroll-conservatively 100)

(dolist (hook '(prog-mode-hook text-mode-hook))
  (add-hook hook #'mpereira/enable-line-numbers))

;; Better unique buffer names for files with the same base name.
(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)

;; Remember point position between sessions.
(require 'saveplace)
(save-place-mode t)

;; Remove `erase-buffer' from the disabled command list.
;; I had this set to `nil' for some reason. Why was it? Maybe I can just remove
;; this?
;; (put 'erase-buffer 'disabled nil)

;; Save a bunch of session state stuff.
(require 'savehist)
(setq savehist-additional-variables '(regexp-search-ring)
      savehist-autosave-interval 60
      savehist-file (expand-file-name "savehist" user-emacs-directory))
(savehist-mode t)

;; Show trailing whitespace.
(require 'whitespace)
(setq whitespace-style '(face lines-tail trailing))
(dolist (hook '(prog-mode-hook text-mode-hook))
  ;; Disabling whitespace mode for now.
  ;; (add-hook hook #'whitespace-mode)
  )

;; `setq', `setq-default' and `setq-local' don't seem to work with symbol
;; variables, hence the absence of a `dolist' here.
(setq-default whitespace-line-column mpereira/fill-column
              fill-column mpereira/fill-column
              comment-column mpereira/fill-column)

(setq emacs-lisp-docstring-fill-column 'fill-column)

;; UTF8 stuff.
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)

;; Tab first tries to indent the current line, and if the line was already
;; indented, then try to complete the thing at point.
(setq tab-always-indent 'complete)

;; Make it impossible to insert tabs.
(setq-default indent-tabs-mode nil)

;; Make TABs be displayed with a width of 2.
(setq-default tab-width 2)

;; Week start on monday.
(setq calendar-week-start-day 1)

(setq select-enable-clipboard t
      select-enable-primary t
      save-interprogram-paste-before-kill t
      apropos-do-all t
      mouse-yank-at-point t
      require-final-newline t
      load-prefer-newer t
      save-place-file (concat user-emacs-directory "places")
      backup-directory-alist `(("." . ,(concat user-emacs-directory "backups"))))

;; Keep cursor position when scrolling.
(setq scroll-preserve-screen-position 1)

;; Make cursor movement an order of magnitude faster.
;; https://emacs.stackexchange.com/questions/28736/emacs-pointcursor-movement-lag/28746
(setq auto-window-vscroll nil)

;; Delete trailing whitespace on save.
(add-hook 'before-save-hook #'delete-trailing-whitespace)

(setq display-time-world-list '(("Europe/Berlin" "Hamburg")
                                ("America/Sao_Paulo" "São Paulo")
                                ("America/Los_Angeles" "San Francisco")))

Color theme

Sources:

Dark themes I use the most:

  1. nimbus
  2. doom-Ioskvem
  3. doom-dracula
  4. srcery

Light themes I use the most:

  1. twilight-bright
  2. espresso
(use-package srcery-theme
  :defer t)

(use-package nimbus-theme
  :defer t)

(use-package doom-themes
  :defer t)

(use-package twilight-bright-theme
  :defer t)

(add-hook 'after-init-hook #'mpereira/load-light-theme t)

Configure Mode Line

(with-eval-after-load "projectile"
  (with-eval-after-load "eshell"
    (with-eval-after-load "magit"
      (setq mpereira/mode-line-max-directory-length 20)

      (defconst mpereira/mode-line-projectile
        '(:eval
          (let ((face 'bold))
            (when (projectile-project-name)
              (concat
               (propertize " " 'face face)
               (propertize (format "%s" (projectile-project-name)) 'face face)
               (propertize " " 'face face))))))

      (defconst mpereira/mode-line-vc
        '(:eval
          (when (and (stringp vc-mode) (string-match "Git[:-]" vc-mode))
            (let ((branch (replace-regexp-in-string "^ Git[:-]" "" vc-mode))
                  (face 'magit-branch-current))
              (concat
               (propertize " " 'face face)
               (propertize (format "%s" branch) 'face face)
               (propertize " " 'face face))))))

      (defconst mpereira/mode-line-buffer
        '(:eval
          (let ((modified-or-ro-symbol (cond
                                        ((and buffer-file-name
                                              (buffer-modified-p))
                                         "~")
                                        (buffer-read-only ":RO")
                                        (t "")))
                ;; Not using %b because it sometimes prepends the directory
                ;; name.
                (buffer-name* (file-name-nondirectory (buffer-name)))
                (buffer-name-face 'mode-line-buffer-id)
                (directory-face 'mode-line-inactive)
                (modified-or-ro-symbol-face 'magit-refname-wip)
                (directory (if (projectile-project-p)
                               (mpereira/shorten-directory
                                (projectile-project-root)
                                default-directory
                                mpereira/mode-line-max-directory-length)
                             "")))
            (concat
             (propertize " " 'face buffer-name-face)
             (propertize (format "%s" directory) 'face directory-face)
             (propertize (format "%s" buffer-name*) 'face buffer-name-face)
             (propertize modified-or-ro-symbol 'face modified-or-ro-symbol-face)
             (propertize " " 'face buffer-name-face)))))

      (defconst mpereira/mode-line-major-mode
        '(:eval
          (propertize " %m  " 'face 'font-lock-comment-face)))

      (defconst mpereira/mode-line-buffer-position
        '(:eval
          (unless eshell-mode
            (propertize " %p %l,%c " 'face 'org-todo))))

      (defun mpereira/flycheck-lighter (state)
        "Return flycheck information for the given error type STATE.

Source: https://git.io/vQKzv"
        (let* ((counts (flycheck-count-errors flycheck-current-errors))
               (errorp (flycheck-has-current-errors-p state))
               (err (or (cdr (assq state counts)) "?"))
               (running (eq 'running flycheck-last-status-change)))
          (if errorp (format "%s" err))))

      (defconst mpereira/flycheck
        '(:eval
          (when (and (bound-and-true-p flycheck-mode)
                     (or flycheck-current-errors
                         (eq 'running flycheck-last-status-change)))
            (concat
             (cl-loop for state in '((error . "#FB4933")
                                     (warning . "#FABD2F")
                                     (info . "#83A598"))
                      as lighter = (mpereira/flycheck-lighter (car state))
                      when lighter
                      concat (propertize
                              lighter
                              'face `(:foreground ,(cdr state))))
             " "))))
      (setq-default mode-line-format (list mpereira/mode-line-projectile
                                           mpereira/mode-line-vc
                                           mpereira/mode-line-buffer
                                           mpereira/flycheck
                                           mpereira/mode-line-major-mode
                                           mpereira/mode-line-buffer-position
                                           mode-line-misc-info
                                           mode-line-end-spaces)))))

Configure Header Line

(defun mpereira/set-header-line-format ()
  (setq header-line-format '((which-function-mode ("" which-func-format " ")))))

(add-hook 'prog-mode-hook #'mpereira/set-header-line-format)

goto-address-mode

(general-define-key
 :keymaps '(goto-address-highlight-keymap)
 "C-c C-o" #'goto-address-at-point)

(add-hook 'prog-mode-hook #'goto-address-prog-mode)

text-mode

(general-define-key
 :keymaps '(text-mode-map)
 :states '(normal visual)
 "K" #'synosaurus-lookup)

persistent-scratch

(use-package persistent-scratch
  :config
  (persistent-scratch-autosave-mode))

too-long-lines-mode

(use-package too-long-lines-mode
  :ensure nil
  :quelpa (too-long-lines-mode
           :fetcher github
           :repo "rakete/too-long-lines-mode")
  :config
  (too-long-lines-mode))

disk-usage

(use-package disk-usage)

rotate

(use-package rotate)

exec-path-from-shell

This needs to be loaded before code that depends on PATH modifications, e.g. executable-find.

(use-package exec-path-from-shell
  :config
  (dolist (shell-variable '("PYTHONPATH"
                            "SSH_AUTH_SOCK"
                            "SSH_AGENT_PID"))
    (add-to-list 'exec-path-from-shell-variables shell-variable))
  (exec-path-from-shell-initialize))

with-editor

(use-package with-editor
  :config
  (add-hook 'eshell-mode-hook 'with-editor-export-editor)
  (add-hook 'term-exec-hook 'with-editor-export-editor)
  (add-hook 'shell-mode-hook 'with-editor-export-editor))

evil

(use-package evil
  :general
  (:keymaps '(evil-motion-state-map)
   ";" 'evil-ex
   ":" 'evil-repeat-find-char)
  ;; `evil-search' as the `evil-search-module' is nice, but I still want to
  ;; navigate history with C-j and C-k.
  ;; FIXME: this isn't working for some reason?
  ;; (:keymaps '(evil-ex-search-keymap isearch-mode-map)
  ;;  "C-k" #'isearch-ring-retreat
  ;;  "C-j" #'isearch-ring-advance)
  :init
  ;; Setup for `evil-collection'.
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)

  (setq-default evil-symbol-word-search t)
  (setq-default evil-shift-width 2)
  (setq evil-jumps-cross-buffers nil)
  (setq evil-want-Y-yank-to-eol t)
  (setq evil-want-C-u-scroll t)
  (setq evil-search-module 'evil-search)

  ;; The combination of the following two configurations prevent the cursor from
  ;; moving beyond the end of line.
  (setq evil-move-cursor-back t)
  (setq evil-move-beyond-eol nil)
  :config
  (evil-mode t)

  ;; Don't create a kill entry on every visual movement.
  ;; More details: https://emacs.stackexchange.com/a/15054:
  (fset 'evil-visual-update-x-selection 'ignore))

evil-org

(use-package evil-org
  :after evil org
  :config
  (add-hook 'org-mode-hook 'evil-org-mode)

  ;; Org todo notes don't have a specific major mode, so change to insert
  ;; state based on its buffer name.
  ;; FIXME: doesn't seem to be working.
  (add-hook 'org-mode-hook
            (lambda ()
              (when (string= "*Org Note*" (buffer-name))
                (evil-insert-state))))

  (add-hook 'evil-org-mode-hook
            (lambda ()
              (evil-org-set-key-theme '(operators
                                        navigation
                                        textobjects
                                        todo)))))

evil-magit

(use-package evil-magit
  :after evil magit
  :init
  (setq evil-magit-use-z-for-folds t)
  :config
  (general-define-key
   :keymaps 'magit-mode-map
   :states '(normal visual)
   "j" 'evil-next-visual-line
   "k" 'evil-previous-visual-line
   "C-j" 'magit-section-forward
   "C-k" 'magit-section-backward)

  (general-define-key
   :states '(normal)
   :keymaps '(git-rebase-mode-map)
   "x" 'git-rebase-kill-line
   "C-S-j" 'git-rebase-move-line-down
   "C-S-k" 'git-rebase-move-line-up))

evil-extra-operator

(use-package evil-extra-operator
  :after evil
  :init
  (setq evil-extra-operator-eval-key "ge")
  :config
  (add-hook 'prog-mode-hook 'evil-extra-operator-mode))

evil-exchange

(use-package evil-exchange
  :after evil
  :config
  (evil-exchange-install))

evil-nerd-commenter

(use-package evil-nerd-commenter
  :after evil
  :config
  (general-define-key
   :keymaps '(normal)
   "gc" 'evilnc-comment-operator))

evil-surround

(use-package evil-surround
  :after evil
  :config
  (global-evil-surround-mode t))

evil-matchit

(use-package evil-matchit
  :after evil
  :config
  (global-evil-matchit-mode 1))

evil-goggles

(use-package evil-goggles
  :after evil
  :config
  (evil-goggles-mode)

  ;; Optionally use diff-mode's faces; as a result, deleted text will be
  ;; highlighed with `diff-removed` face which is typically some red color
  ;; (as defined by the color theme) other faces such as `diff-added` will
  ;; be used for other actions.
  (evil-goggles-use-diff-faces))

evil-multiedit

(use-package evil-multiedit
  :after evil
  :config
  (setq evil-multiedit-follow-matches t)

  (general-define-key
   :states '(normal)
   "C-RET" 'evil-multiedit-toggle-marker-here
   "RET" 'evil-multiedit-toggle-or-restrict-region
   "C-k" 'evil-multiedit-prev
   "C-j" 'evil-multiedit-next
   "C-n" 'evil-multiedit-match-and-next
   "C-p" 'evil-multiedit-match-and-prev
   "C-S-n" 'evil-multiedit-match-all)

  (general-define-key
   :states '(visual)
   "C-RET" 'evil-multiedit-toggle-marker-here
   "C-k" 'evil-multiedit-prev
   "C-j" 'evil-multiedit-next
   "C-n" 'evil-multiedit-match-symbol-and-next
   "C-p" 'evil-multiedit-match-symbol-and-prev
   "C-S-n" 'evil-multiedit-match-all)

  (general-define-key
   :keymaps '(evil-multiedit-state-map)
   "RET" 'evil-multiedit-toggle-or-restrict-region
   "C-k" 'evil-multiedit-prev
   "C-j" 'evil-multiedit-next))

evil-collection

(use-package evil-collection
  :after evil
  :config
  (evil-collection-init))

paradox

(use-package paradox
  :config
  (paradox-enable))

highlight-indent-guide

(use-package highlight-indent-guides
  :config
  (setq highlight-indent-guides-method 'character))

hideshow

(use-package hideshow
  :config
  (setq hs-isearch-open t)

  (defun mpereira/display-code-line-counts (ov)
    (when (eq 'code (overlay-get ov 'hs))
      (overlay-put ov
                   'display
                   (format " ... [%d]"
                           (count-lines (overlay-start ov)
                                        (overlay-end ov))))))

  (setq hs-set-up-overlay #'mpereira/display-code-line-counts)

  (defun mpereira/hs-toggle-all ()
    "If anything isn't hidden, run `hs-hide-all', else run `hs-show-all'."
    (interactive)
    (let ((starting-ov-count (length (overlays-in (point-min) (point-max)))))
      (hs-hide-all)
      (when (equal (length (overlays-in (point-min) (point-max))) starting-ov-count)
        (hs-show-all))))

  (add-hook 'prog-mode-hook #'hs-minor-mode))

string-inflection

(use-package string-inflection)

xterm-color

(use-package xterm-color
  :config
  (setq comint-output-filter-functions (remove 'ansi-color-process-output
                                               comint-output-filter-functions))

  (add-hook 'shell-mode-hook
            (lambda ()
              (add-hook 'comint-preoutput-filter-functions
                        'xterm-color-filter
                        nil
                        t))))

eshell

(require 'eshell)
(require 'em-dirs) ;; for `eshell/pwd'.
(require 'em-smart)

;; Don't display the "Welcome to the Emacs shell" banner.
(setq eshell-banner-message "")

(setenv "LANG" "en_US.UTF-8")
(setenv "LC_ALL" "en_US.UTF-8")
(setenv "LC_CTYPE" "en_US.UTF-8")

;; Don't page shell output.
(setenv "PAGER" "cat")

(setq eshell-scroll-to-bottom-on-input 'all)
(setq eshell-buffer-maximum-lines 20000)
(setq eshell-history-size 1000000)
(setq eshell-error-if-no-glob t)
(setq eshell-hist-ignoredups t)
(setq eshell-save-history-on-exit t)
;; `find` and `chmod` behave differently on eshell than unix shells. Prefer unix
;; behavior.
(setq eshell-prefer-lisp-functions nil)

;; Visual commands are commands which require a proper terminal. eshell will run
;; them in a term buffer when you invoke them.
(setq eshell-visual-commands
      '("htop" "top" "bash" "zsh" "fish" "glances" "watch"))
(setq eshell-visual-subcommands '())

(defun eshell/clear ()
  "Clears buffer while preserving input."
  (let* ((inhibit-read-only t)
         (input (eshell-get-old-input)))
    (eshell/clear-scrollback)
    (eshell-emit-prompt)
    (insert input)
    ;; This fixes the scenario where `ivy-completion-in-region-action' tries to
    ;; delete a region delimited by these two variables after they went out of
    ;; sync due to clearing an eshell buffer. The symptoms are broken completion
    ;; insertion and messages like: "Args out of range: #<buffer *eshell*>,
    ;; 237506, 237518" in the messages buffer. Should probably check with the
    ;; ivy people if this should be handled by ivy itself instead?
    (setq ivy-completion-beg nil)
    (setq ivy-completion-end nil)))

(defun mpereira/eshell-clear ()
  (interactive)
  (eshell/clear))

;; I don't use `counsel-esh-history' because it doesn't take into consideration
;; the current input.
(defun mpereira/eshell-history ()
  "Browse Eshell history."
  (interactive)
  (let ((history
         (delete-dups
          (mapcar (lambda (str)
                    (string-trim (substring-no-properties str)))
                  (ring-elements eshell-history-ring))))
        (input (let* ((beg (save-excursion (eshell-bol)))
                      (end (save-excursion (end-of-line) (point))))
                 (buffer-substring-no-properties beg end))))
    (ivy-read "Command: "
              history
              :action (lambda (x)
                        (end-of-line)
                        (eshell-kill-input)
                        (insert x))
              :initial-input input)))

;; eshell-mode-map needs to be configured in an `eshell-mode-hook'.
;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-02/msg01532.html
(defun mpereira/initialize-eshell ()
  (interactive)
  ;; Completion functions depend on pcomplete.
  ;; Don't use TAB for cycling through candidates.
  (setq pcomplete-cycle-completions nil)
  (setq pcomplete-ignore-case t)

  (eshell/alias "e" "find-file $1")

  ;; Eshell needs this variable set in addition to the PATH environment variable.
  (setq eshell-path-env (getenv "PATH"))

  (general-define-key
   :keymaps '(eshell-mode-map)
   "C-c C-c" 'eshell-interrupt-process)

  (general-define-key
   :states '(normal visual)
   :keymaps '(eshell-mode-map)
   "0" 'eshell-bol
   "C-j" 'eshell-next-prompt
   "C-k" 'eshell-previous-prompt)

  (general-define-key
   :states '(insert)
   :keymaps '(eshell-mode-map)
   ;; Uppercase TAB here doesn't work for some reason.
   ;; Overrides `eshell-pcomplete' because it doesn't work with ivy.
   "<tab>" 'completion-at-point
   ;; TODO: `eshell-{previous,next}-matching-input-from-input' only work with
   ;; prefix inputs, like "git". They don't do fuzzy matching.
   ;;
   ;; TODO: when on an empty prompt and going up and back down (or down and back
   ;; up), make it so that the prompt is empty again instead of cycling back to
   ;; the first input.
   "C-k" 'eshell-previous-matching-input-from-input
   "C-j" 'eshell-next-matching-input-from-input
   "C-/" 'mpereira/eshell-history
   ;; https://github.com/ksonney/spacemacs/commit/297945a45696e235c6983a78acdf05b5f0e015ca
   "C-l" 'mpereira/eshell-clear)

  ;; FIXME: workaround for a bug. When an eshell buffer is created the
  ;; `eshell-mode-map' mappings are not set up, even through `eshell-mode-map'
  ;; is correctly defined. Going to normal state sets them up for some reason.
  (evil-normal-state)
  (evil-insert-state)
  (forward-char))

(add-hook 'eshell-mode-hook 'mpereira/initialize-eshell)

;; Disable a few possibly-global modes.
(add-hook 'eshell-mode-hook (lambda () (company-mode -1)) t)
(add-hook 'eshell-mode-hook (lambda () (undo-tree-mode -1)) t)

(defun mpereira/remote-p ()
  (tramp-tramp-file-p default-directory))

(defun mpereira/remote-user ()
  "Return remote user name."
  (tramp-file-name-user (tramp-dissect-file-name default-directory)))

(defun mpereira/remote-host ()
  "Return remote host."
  ;; `tramp-file-name-real-host' is removed and replaced by
  ;; `tramp-file-name-host' in Emacs 26, see
  ;; https://github.com/kaihaosw/eshell-prompt-extras/issues/18
  (if (fboundp 'tramp-file-name-real-host)
      (tramp-file-name-real-host (tramp-dissect-file-name default-directory))
    (tramp-file-name-host (tramp-dissect-file-name default-directory))))

;; https://www.emacswiki.org/emacs/EshellPrompt
(defun mpereira/fish-path (path)
  "Return a potentially trimmed-down version of the directory PATH, replacing
parent directories with their initial characters to try to get the character
length of PATH (sans directory slashes) down to MAX-LEN."
  (let* ((components (split-string (abbreviate-file-name path) "/"))
         (max-len 30)
         (len (+ (1- (length components))
                 (cl-reduce '+ components :key 'length)))
         (str ""))
    (while (and (> len max-len)
                (cdr components))
      (setq str (concat str
                        (cond ((= 0 (length (car components))) "/")
                              ((= 1 (length (car components)))
                               (concat (car components) "/"))
                              (t
                               (if (string= "."
                                            (string (elt (car components) 0)))
                                   (concat (substring (car components) 0 2)
                                           "/")
                                 (string (elt (car components) 0) ?/)))))
            len (- len (1- (length (car components))))
            components (cdr components)))
    (concat str (cl-reduce (lambda (a b) (concat a "/" b)) components))))

(defun mpereira/eshell-prompt ()
  (let ((user-name (if (mpereira/remote-p)
                       (mpereira/remote-user)
                     (user-login-name)))
        (host-name (if (mpereira/remote-p)
                       (mpereira/remote-host)
                     (system-name))))
    (concat
     (propertize user-name 'face '(:foreground "green"))
     " "
     (propertize "at" 'face 'eshell-ls-unreadable)
     " "
     (propertize host-name 'face '(:foreground "cyan"))
     " "
     (propertize "in" 'face 'eshell-ls-unreadable)
     " "
     (propertize (mpereira/fish-path (eshell/pwd)) 'face 'dired-directory)
     "\n"
     (propertize (if (= (user-uid) 0)
                     "#"
                   "$")
                 'face 'eshell-prompt)
     " ")))

;; Unused (for now?)
(setq mpereira/eshell-prompt-string
      (let ((prompt (mpereira/eshell-prompt))
            (inhibit-read-only t))
        (set-text-properties 0 (length prompt) nil prompt)
        prompt))

(setq eshell-prompt-function 'mpereira/eshell-prompt)
(setq eshell-prompt-regexp "^[$#] ")

;; This causes the prompt to not be protected.
;; (setq eshell-highlight-prompt nil)

;; Make eshell append to history after each command.
;; https://emacs.stackexchange.com/questions/18564/merge-history-from-multiple-eshells
;; (setq eshell-save-history-on-exit nil)
;; (defun eshell-append-history ()
;;   "Call `eshell-write-history' with the `append' parameter set to `t'."
;;   (when eshell-history-ring
;;     (let ((newest-cmd-ring (make-ring 1)))
;;       (ring-insert newest-cmd-ring (car (ring-elements eshell-history-ring)))
;;       (let ((eshell-history-ring newest-cmd-ring))
;;         (eshell-write-history eshell-history-file-name t)))))
;; (add-hook 'eshell-pre-command-hook #'eshell-append-history)

;; Shared history.
;; https://github.com/Ambrevar/dotfiles/blob/25e2ed350b898c3fc2df3148630b5778a3db4ee7/.emacs.d/lisp/init-eshell.el#L205
;; TODO: make this per project.
(defvar mpereira/eshell-history-global-ring nil
  "The history ring shared across Eshell sessions.")

(defun mpereira/eshell-hist-use-global-history ()
  "Make Eshell history shared across different sessions."
  (unless mpereira/eshell-history-global-ring
    (when eshell-history-file-name
      (eshell-read-history nil t))
    (setq mpereira/eshell-history-global-ring
          (or eshell-history-ring (make-ring eshell-history-size))))
  (setq eshell-history-ring mpereira/eshell-history-global-ring))

(add-hook 'eshell-mode-hook #'mpereira/eshell-hist-use-global-history)

Configure xterm-color

(setenv "TERM" "xterm-256color")

(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))

fish-completion

(use-package fish-completion
  :after exec-path-from-shell
  :config
  (if (executable-find "fish")
      (global-fish-completion-mode)
    (message "fish executable not found, not enabling fish-completion-mode")))

suggest

(use-package suggest)

load-bash-alias

(use-package load-bash-alias
  :config
  (setq load-bash-alias-bashrc-file "~/.aliases"))

bm

(use-package bm)

yasnippet

(use-package yasnippet)

yasnippet-snippets

(use-package yasnippet-snippets
  :after yasnippet)

auto-yasnippet

(use-package auto-yasnippet
  :after yasnippet)
(yas-reload-all)
(add-hook 'prog-mode-hook #'yas-minor-mode)

open-junk-file

(use-package open-junk-file
  :config
  (setq open-junk-file-directory (concat user-emacs-directory
                                         "junk/%Y/%m/%d/%H%M%S.")))

reformatter

(use-package reformatter)

ialign

(use-package ialign)

rg

(use-package rg
  :config
  ;; Original mappings at:
  ;; https://github.com/dajva/rg.el/blob/77670a4bcdba138a0cef3fb12a20b1492dca902a/rg-result.el#L203-L221
  ;;
  ;; I'm overring `rg-mode-map' here, but it might make sense
  ;; overriding "parent" keymaps like `grep-mode-map' or
  ;; `compilation-mode-map'.
  ;;
  ;; TODO: figure out how to make "," resolve to the leader instead of
  ;; `evil-repeat-find-char-reverse'.
  (general-define-key
   :keymaps '(rg-mode-map)
   "C-f" 'scroll-up
   "C-b" 'scroll-down
   "C-j" 'rg-next-file
   "C-k" 'rg-prev-file
   "<" 'rg-back-history
   ">" 'rg-forward-history
   ;; "n" is `next-error-no-select' by default.
   "n" nil
   ;; digit-argument
   "0" nil
   ;; "h" is `describe-mode' by default.
   "h" 'evil-backward-char
   ;; "g" is `recompile' by default.
   "g" nil
   ;; "w" is `wgrep-change-to-wgrep-mode' by default.
   "w" nil
   ;; "l" is `rg-list-searches' by default.
   "l" nil)

  (setq rg-group-result t))

ag

(use-package ag)

web-mode

(use-package web-mode
  :config
  (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))

  (setq web-mode-engines-alist '())

  (setq web-mode-markup-indent-offset 2))

html

auto-rename-tag

(use-package auto-rename-tag
  :config
  (add-hook 'html-mode-hook #'auto-rename-tag-mode))

css

(setq css-indent-offset 2)

emacs-colorpicker

;; (use-package colorpicker
;;   :ensure nil
;;   :quelpa (emacs-colorpicker
;;            :fetcher github
;;            :repo "syohex/emacs-colorpicker"))

olivetti

(use-package olivetti
  :config
  (setq-default olivetti-body-width 0.5))

minibuffer-line

(use-package minibuffer-line
  :config
  (setq minibuffer-line-format
        '((:eval
           (let ((time-string (format-time-string "%a %b %d %R")))
             (concat
              (propertize (make-string (- (frame-text-cols)
                                          (string-width time-string))
                                       ?\s)
                          'face 'default)
              time-string)))))
  (minibuffer-line-mode t))

buffer-expose

(use-package buffer-expose
  :config
  (general-define-key
   :keymaps '(buffer-expose-grid-map)
   "h" 'buffer-expose-left-window
   "l" 'buffer-expose-right-window
   "k" 'buffer-expose-up-window
   "j" 'buffer-expose-down-window
   "0" 'buffer-expose-first-window-in-row
   "$" 'buffer-expose-last-window-in-row
   "g" 'buffer-expose-first-window
   "G" 'buffer-expose-last-window
   "SPC" 'buffer-expose-ace-window
   "]" 'buffer-expose-next-page
   "[" 'buffer-expose-prev-page
   "d" 'buffer-expose-kill-buffer))

buffer-move

(use-package buffer-move)

dumb-jump

(use-package dumb-jump
  :config
  (setq dumb-jump-selector 'ivy)

  (general-define-key
   :states '(normal visual)
   "C-]" 'dumb-jump-go
   "C-}" 'dumb-jump-quick-look))

beacon

(use-package beacon
  :config
  (add-to-list 'beacon-dont-blink-major-modes 'eshell-mode)
  (beacon-mode 1)
  (setq beacon-size 40))

undo-tree

(dolist (hook '(undo-tree-mode-hook
                undo-tree-visualizer-mode-hook))
  (add-hook hook 'mpereira/hide-trailing-whitespace))

(setq undo-limit (* 10 1024 1024)) ;; 10MB.
(setq undo-strong-limit undo-limit)
(setq undo-tree-visualizer-timestamps t)
(setq undo-tree-visualizer-diff t)

company-mode

(use-package company
  :config
  (setq company-global-modes '(not eshell-mode
                                   comint-mode
                                   message-mode
                                   help-mode))

  (add-hook 'after-init-hook 'global-company-mode)

  (setq company-tooltip-align-annotations t)
  (setq company-require-match 'never)
  (setq company-idle-delay 1)

  (general-define-key
   :states '(insert)
   "TAB" 'company-complete)

  (general-define-key
   :keymaps '(company-active-map)
   "C-b" 'company-previous-page
   "C-f" 'company-next-page
   "C-j" 'company-select-next
   "C-k" 'company-select-previous))

Flycheck

flycheck

(use-package flycheck
  :config
  (general-define-key
   :keymaps '(flycheck-mode-map)
   :states '(normal visual)
   :prefix mpereira/leader
   :infix "1"
   "c" 'flycheck-buffer
   "e" 'flycheck-explain-error-at-point
   "h" 'flycheck-display-error-at-point
   "j" 'flycheck-next-error
   "k" 'flycheck-previous-error
   "l" 'flycheck-list-errors
   "n" 'flycheck-next-error
   "p" 'flycheck-previous-error)

  (setq flycheck-display-errors-delay 0.3)

  (add-hook 'prog-mode-hook #'flycheck-mode))

flycheck-inline

(require 'cus-edit) ;; for `custom-modified' face.

(use-package flycheck-inline
  :config
  ;; Draw a nice-looking padding around the overlays.
  (dolist (face '(flycheck-inline-info flycheck-inline-warning flycheck-inline-error))
    (let ((background (face-attribute 'custom-modified :background)))
      (set-face-attribute face nil :box `(:line-width 5 :color ,background))
      (set-face-attribute face nil :background background)))
  (flycheck-inline-mode))

flycheck-posframe

Currently disabled because I’m using flycheck-inline instead. It has better support for showing full context for errors. flycheck-posframe only shows context for the thing currently being hovered.

(use-package flycheck-posframe
  :after flycheck
  :config
  (flycheck-posframe-configure-pretty-defaults)
  ;; (add-hook 'flycheck-mode-hook #'flycheck-posframe-mode)
  )

Emacs Lisp

(general-define-key
 :keymaps '(emacs-lisp-mode-map)
 :states '(normal)
 :prefix mpereira/leader
 :infix "e"
 "e" 'mpereira/eval-sexp-at-or-surrounding-pt
 "(" 'eval-defun
 "E" 'eval-buffer)

(general-define-key
 :keymaps '(emacs-lisp-mode-map)
 :states '(visual)
 :prefix mpereira/leader
 :infix "e"
 "e" 'eval-region)

(general-define-key
 :keymaps '(emacs-lisp-mode-map)
 :states '(normal)
 "C-]" 'xref-find-definitions-other-window
 "K" 'helpful-at-point)

Java

(add-hook 'java-mode-hook
          (lambda ()
            (setq-local whitespace-line-column mpereira/fill-column-wide)
            (setq-local fill-column mpereira/fill-column-wide)
            (setq-local comment-column mpereira/fill-column-wide)))

meghanada

(use-package meghanada
  :config
  ;; (add-hook 'java-mode-hook
  ;;           (lambda ()
  ;;             (meghanada-mode t)
  ;;             (setq c-basic-offset 4)
  ;;             (add-hook 'before-save-hook 'meghanada-code-beautify-before-save)))
  )

Clojure

clojure-mode

(use-package clojure-mode)

inf-clojure

(use-package inf-clojure)

cider

(use-package cider
  :config
  (setq cider-prompt-for-symbol nil)
  (setq cider-repl-display-help-banner nil)

  (general-define-key
   :keymaps 'cider-mode-map
   :states '(normal visual)
   "K" 'cider-doc
   "gf" 'cider-find-var)

  (general-define-key
   :keymaps 'cider-mode-map
   :states '(normal)
   :prefix mpereira/leader
   "ee" 'cider-eval-sexp-at-point
   "e(" 'cider-eval-defun-at-point
   "eE" 'cider-eval-buffer
   "dd" 'cider-debug-defun-at-point
   "tt" 'cider-test-run-test
   "tr" 'cider-test-rerun-test
   "tT" 'cider-test-run-ns-tests
   "tR" 'cider-test-rerun-failed-tests
   "pt" 'cider-test-run-project-tests)

  (general-define-key
   :keymaps 'cider-mode-map
   :states '(visual)
   :prefix mpereira/leader
   "ee" 'cider-eval-region))

slamhound

Currently disabled because it’s only available via Marmalade.

;; (use-package slamhound
;;   :after cider)

Rust

rust-mode

Using eglot and flycheck-inline instead of lsp-rust.

(use-package rust-mode
  :config
  (general-define-key
   :keymaps '(rust-mode-map)
   :states '(normal visual)
   "C-9" 'racer-describe-tooltip
   "C-K" 'racer-describe-tooltip
   "C-]" 'racer-find-definition
   "K" 'racer-describe)

  (with-eval-after-load "eglot"
    (add-hook 'rust-mode-hook #'eglot-ensure))

  (with-eval-after-load "lsp-mode"
    ;; (add-hook 'rust-mode-hook #'lsp-rust-enable t)
    ))

racer

(use-package racer
  :config
  (general-define-key
   :states '(normal)
   :keymaps '(racer-help-mode-map)
   "q" 'kill-current-buffer)

  (add-hook 'rust-mode-hook #'racer-mode))

flycheck-rust

(use-package flycheck-rust
  :after rust-mode
  :config
  (add-hook 'flycheck-mode-hook #'flycheck-rust-setup))

ob-rust

(use-package ob-rust)

Kotlin

flycheck-kotlin

(use-package flycheck-kotlin
  :after kotlin-mode
  :config
  (add-hook 'flycheck-mode-hook #'flycheck-kotlin-setup))

Javascript

(setq js-indent-level 2)

Shell script

(add-hook 'sh-mode-hook
          (lambda ()
            (setq-local sh-basic-offset 2)
            (setq-local sh-indentation 2)))

Python

(with-eval-after-load "elpy"
  (general-define-key
   :keymaps '(python-mode-map)
   :states '(normal visual)
   "C-j" 'python-nav-forward-block
   "C-k" 'python-nav-backward-block)

  (general-define-key
   :keymaps '(python-mode-map)
   :states '(normal visual)
   "K" 'elpy-doc)

  (general-define-key
   :keymaps '(python-mode-map)
   :states '(normal visual)
   :prefix mpereira/leader
   :infix "f"
   "o" 'imenu)

  (general-define-key
   :keymaps '(python-mode-map)
   :states '(normal visual)
   :prefix mpereira/leader
   :infix "e"
   "e" 'elpy-shell-send-statement
   "p" 'elpy-shell-send-group))

(add-hook 'python-mode-hook
          (lambda ()
            (setq evil-shift-width python-indent)))

elpy

(use-package elpy
  :init
  (setq flycheck-python-flake8-executable "python3")
  (setq flycheck-python-pylint-executable "python3")
  (setq flycheck-python-pycompile-executable "python3")
  (setq elpy-rpc-python-command "python3")
  (setq python-shell-interpreter "python3")
  (setq company-idle-delay-before-elpy-fucks-it-up company-idle-delay)
  (remove-hook 'elpy-modules 'elpy-module-eldoc)
  (remove-hook 'elpy-modules 'elpy-module-django)
  (add-hook 'elpy-mode-hook
            (lambda ()
              (setq company-idle-delay company-idle-delay-before-elpy-fucks-it-up)))
  :config
  (elpy-enable))

blacken

(use-package blacken
  :config
  (general-define-key
   :keymaps '(python-mode-map)
   :states '(normal visual)
   :prefix mpereira/leader
   ;; FIXME: why doesn't this work?
   "S-f" 'blacken-buffer))

JSON

json-mode

(use-package json-mode)

json-navigator

(use-package json-navigator)

Org mode

(setq org-directory (expand-file-name "org" mpereira/dropbox-directory))

(setq org-modules '(org-habit
                    org-info
                    org-tempo))
;; Requiring these modules because org mode only does that for `org-modules'
;; when it's defined prior to loading it.
(require 'org-habit)
(require 'org-info)
(require 'org-tempo)

(setq org-return-follows-link t)

(setq org-log-done 'time)

;; TODO: is this needed?
(setq org-catch-invisible-edits 'show)

;; Show empty line between collapsed trees if they are separated by just 1
;; line break.
(setq org-cycle-separator-lines 1)

(setq org-attach-auto-tag "attachment")

(add-hook 'org-mode-hook #'mpereira/disable-line-numbers)

(setq org-tags-column -80)

;; TODO: improve this?
(face-spec-set 'org-tag '((t :box (:color "gray30" :line-width 1))))

;; Don't indent src block content.
(setq org-edit-src-content-indentation 0)

;; Don't close all other windows when exiting the src buffer.
(setq org-src-window-setup 'current-window)

;; Open indirect buffer in the same window as the src buffer.
(setq org-indirect-buffer-display 'current-window)

;; Fontify code in code blocks.
(setq org-src-fontify-natively t)

;; Make TAB act as if it were issued in a buffer of the language’s major mode.
(setq org-src-tab-acts-natively t)

(org-babel-do-load-languages 'org-babel-load-languages
                             '((shell . t)
                               (emacs-lisp . t)))

(setq org-confirm-babel-evaluate nil)

;; By default, don't evaluate src blocks when exporting.
(setq org-export-use-babel nil)

(setq org-todo-keywords '((sequence "TODO(t!)"
                                    "DOING(d!)"
                                    "WAITING(w@/!)"
                                    "|"
                                    "SOMEDAY(s@/!)"
                                    "CANCELLED(c@/!)"
                                    "DONE(D!)")))

(setq org-capture-templates
      '(("t" "Inbox" entry
         (file "inbox.org")
         "* TODO %i%?")
        ("c" "Calendar" entry
         (file mpereira/org-calendar-file)
         "* %i%?\n  %^{When?}t")
        ("a" "Appointment" entry
         (file "appointments.org")
         "* %i%?\n  %^{When?}t")
        ("j" "Journal for today" entry
         (file+olp+datetree "journal.org" "Journal")
         "* %U %^{Title}\n  %?"
         :tree-type week
         :empty-lines-after 1)
        ("J" "Journal for some other day" entry
         (file+olp+datetree "journal.org" "Journal")
         "* %(format-time-string \"[%Y-%m-%d \\%a %H:%M]\") %^{Title}\n  %?"
         :tree-type week
         :time-prompt t)))

(add-hook 'org-capture-mode-hook #'evil-insert-state)

(setq org-refile-targets '((org-agenda-files :maxlevel . 1)))

(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)
(setq org-refile-allow-creating-parent-nodes 'confirm)

;; `org-reverse-note-order' set to true along with the two following hooks gets
;; us two things after refiling:
;; 1. Line breaks between top-level headings are maintained.
;; 2. Entries are sorted and top-level heading visibility is set to CHILDREN.
(setq org-reverse-note-order t)

(add-hook 'org-after-refile-insert-hook
          (lambda ()
            (interactive)
            (mpereira/org-sort-parent-entries nil ?o)))

(add-hook 'org-after-sorting-entries-or-items-hook #'mpereira/org-cycle-cycle)

;; Save org buffers after some operations.
(dolist (hook '(org-refile
                org-agenda-add-note
                org-agenda-deadline
                org-agenda-kill
                org-agenda-refile
                org-agenda-schedule
                org-agenda-set-property
                org-agenda-set-tags))
  ;; https://github.com/bbatsov/helm-projectile/issues/51
  (advice-add hook :after (lambda (&rest _) (org-save-all-org-buffers))))

(general-define-key
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "o"
 "a" 'mpereira/custom-agenda
 "A" (lambda ()
       (interactive)
       (org-agenda nil "r"))
 "c" 'org-capture
 "Ci" 'org-clock-in
 "Co" 'org-clock-out
 "Cg" 'org-clock-goto
 "D" 'org-check-deadlines
 "l" 'org-store-link)

(general-define-key
 :keymaps '(org-mode-map)
 :states '(normal)
 "(" 'org-up-element
 ")" 'org-down-element
 "k" 'evil-previous-visual-line
 "j" 'evil-next-visual-line
 "C-S-h" 'org-metaleft
 "C-S-j" 'org-metadown
 "C-S-k" 'org-metaup
 "C-S-l" 'org-metaright
 ;; TODO: make this call `org-babel-next-src-block' if there are no
 ;; sibling headings.
 "C-j" 'org-forward-heading-same-level
 ;; TODO: make this call `org-babel-previous-src-block' if there are
 ;; no sibling headings.
 "C-k" 'org-backward-heading-same-level
 ;; TODO: remove temporary keybinding.
 "C-n" 'org-babel-next-src-block
 ;; TODO: remove temporary keybinding.
 "C-p" 'org-babel-previous-src-block)

(general-define-key
 :keymaps '(org-mode-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "f"
 "o" 'counsel-org-goto)

(general-define-key
 :keymaps '(org-mode-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "e"
 "e" 'org-babel-execute-src-block)

(general-define-key
 :keymaps '(org-mode-map)
 :states '(normal visual)
 "gq" 'fill-paragraph)

(general-define-key
 :keymaps '(org-mode-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "o"
 "!" 'org-time-stamp-inactive
 "." 'org-time-stamp
 "|" 'org-columns
 "\\" 'org-columns
 "Cc" 'org-clock-cancel
 "Cd" 'org-clock-display
 "Ci" 'org-clock-in
 "Cl" 'org-clock-in-last
 "Co" 'org-clock-out
 "d" 'org-deadline
 "D" 'org-archive-subtree
 "b" (lambda ()
       (interactive)
       (mpereira/call-interactively-with-prefix-arg
        '(4)
        'org-tree-to-indirect-buffer))
 "B" 'outline-show-branches
 "f" 'org-attach
 "i" 'org-insert-link
 "k" 'org-cut-subtree
 "n" 'org-add-note
 "p" 'org-set-property
 "r" 'org-refile
 "X" (lambda ()
       (interactive)
       (mpereira/call-interactively-with-prefix-arg
        '(4) 'org-babel-remove-result-one-or-many))
 "Rd" (lambda ()
        (interactive)
        (mpereira/call-interactively-with-prefix-arg
         '(4) 'org-deadline))
 "Rs" (lambda ()
        (interactive)
        (mpereira/call-interactively-with-prefix-arg
         '(4) 'org-schedule))
 "s" 'org-schedule
 "S" 'org-sort-entries
 "t" 'org-set-tags-command
 "u" 'org-toggle-link-display
 "x" 'org-export-dispatch
 "y" 'org-copy-subtree)

(general-define-key
 :keymaps '(org-columns-map)
 "s" (lambda ()
       (interactive)
       (org-columns-quit)
       (org-sort-entries nil ?r)
       (org-columns)))

;; Archive subtrees under the same hierarchy as original in the archive files.
;; https://github.com/Fuco1/.emacs.d/blob/b55c7e85d87186f16c395bd35f289da0b5bb84b1/files/org-defs.el#L1582-L1619
(defadvice org-archive-subtree (around fix-hierarchy activate)
  (let* ((fix-archive-p (and (not current-prefix-arg)
                             (not (use-region-p))))
         (afile (org-extract-archive-file (org-get-local-archive-location)))
         (buffer (or (find-buffer-visiting afile) (find-file-noselect afile))))
    ad-do-it
    (when fix-archive-p
      (with-current-buffer buffer
        (goto-char (point-max))
        (while (org-up-heading-safe))
        (let* ((olpath (org-entry-get (point) "ARCHIVE_OLPATH"))
               (path (and olpath (split-string olpath "/")))
               (level 1)
               tree-text)
          (when olpath
            (org-mark-subtree)
            (setq tree-text (buffer-substring (region-beginning) (region-end)))
            (let (this-command) (org-cut-subtree))
            (goto-char (point-min))
            (save-restriction
              (widen)
              (-each path
                (lambda (heading)
                  (if (re-search-forward
                       (rx-to-string
                        `(: bol (repeat ,level "*") (1+ " ") ,heading)) nil t)
                      (org-narrow-to-subtree)
                    (goto-char (point-max))
                    (unless (looking-at "^")
                      (insert "\n"))
                    (insert (make-string level ?*)
                            " "
                            heading
                            "\n"))
                  (cl-incf level)))
              (widen)
              (org-end-of-subtree t t)
              (org-paste-subtree level tree-text))))))))

Org Clocking

;; org-clock stuff.
(setq org-clock-idle-time 15)
(setq org-clock-mode-line-total 'current)
;; Maybe automatically switching to DOING is not the best idea. Leaving it
;; commented for now.
;; (setq org-clock-in-switch-to-state "DOING")

;; Resume clocking task when emacs is restarted.
(org-clock-persistence-insinuate)
;; Save the running clock and all clock history when exiting Emacs, load it on
;; startup.
(setq org-clock-persist t)
;; Resume clocking task on clock-in if the clock is open.
(setq org-clock-in-resume t)
;; Do not prompt to resume an active clock, just resume it.
(setq org-clock-persist-query-resume nil)
;; Clock out when moving task to a done state.
(setq org-clock-out-when-done t)
;; Include current clocking task in clock reports.
(setq org-clock-report-include-clocking-task t)
;; Use pretty things for the clocktable.
(setq org-pretty-entities nil)

org-gcal

(use-package org-gcal
  :config
  (setq mpereira/org-gcal-directory (expand-file-name "gcal" org-directory))

  (load-file (expand-file-name "org-gcal-secrets.el" user-emacs-directory))

  (setq org-gcal-client-id mpereira/secret-org-gcal-client-id)
  (setq org-gcal-client-secret mpereira/secret-org-gcal-client-secret)
  (setq org-gcal-file-alist mpereira/secret-org-gcal-file-alist)
  (setq org-gcal-auto-archive nil)
  (setq org-gcal-notify-p nil))

Org Agenda

(require 'org-agenda)

(setq org-agenda-files (list org-directory
                             mpereira/org-gcal-directory))

;; Full screen org-agenda.
(setq org-agenda-window-setup 'only-window)
;; Don't destroy window splits.
(setq org-agenda-restore-windows-after-quit t)
;; Show only the current instance of a repeating timestamp.
(setq org-agenda-repeating-timestamp-show-all nil)
;; Don't look for free-form time string in headline.
(setq org-agenda-search-headline-for-time nil)

(setq org-agenda-tags-column -120)

(setq org-agenda-format-date 'mpereira/org-agenda-format-date)

(defun mpereira/custom-agenda ()
  "TODO: docstring."
  (interactive)
  (let* ((settings
          '((todo "DOING"
                  ((org-agenda-overriding-header "\nDoing\n")
                   (org-agenda-prefix-format " %i %-18c%?-12t% s")
                   (org-agenda-skip-function
                    '(org-agenda-skip-entry-if 'scheduled))))
            (todo "BLOCKED"
                  ((org-agenda-overriding-header "\Blocked\n")
                   (org-agenda-prefix-format " %i %-18c%?-12t% s")
                   (org-agenda-skip-function
                    '(org-agenda-skip-entry-if 'scheduled))))
            (todo "WAITING"
                  ((org-agenda-overriding-header "\nWaiting\n")
                   (org-agenda-prefix-format " %i %-18c%?-12t% s")))
            (agenda ""
                    ((org-agenda-overriding-header
                      (concat
                       "\nToday "
                       "(" (format-time-string "%A, %B %d" (current-time)) ")"))
                     (org-deadline-warning-days 0)
                     (org-agenda-span 'day)
                     (org-agenda-use-time-grid t)
                     (org-agenda-format-date "")
                     (org-agenda-prefix-format " %i %-18c%?-12t% s")
                     (org-habit-show-habits nil)
                     (org-agenda-skip-function
                      '(org-agenda-skip-entry-if 'todo '("WAITING" "DONE" "CANCELLED")))))
            (agenda ""
                    ((org-agenda-overriding-header "\nNext 7 Days")
                     (org-agenda-start-day "+1d")
                     (org-agenda-span 'week)
                     (org-agenda-start-on-weekday nil)
                     (org-agenda-prefix-format " %i %-18c%?-12t% s")
                     (org-agenda-skip-function
                      '(org-agenda-skip-entry-if 'todo '("WAITING" "DONE" "CANCELLED")))))
            ;; FIXME: not showing non-TODO entries (TIMESTAMP, TIMESTAMP_IA).
            (tags (concat "SCHEDULED>=\"<+8d>\"&SCHEDULED<=\"<+120d>\""
                          "|"
                          "DEADLINE>=\"<+8d>\"&DEADLINE<=\"<+120d>\""
                          "|"
                          "TIMESTAMP_IA>=\"<+8d>\"&TIMESTAMP_IA<=\"<+120d>\""
                          "|"
                          "TIMESTAMP>=\"<+8d>\"&TIMESTAMP<=\"<+120d>\""
                          "/!")
                  ((org-agenda-overriding-header
                    "\nComing up\n")
                   (org-agenda-skip-function
                    '(org-agenda-skip-entry-if 'todo '("WAITING" "DONE")))
                   (org-agenda-prefix-format
                    " %-18c %(mpereira/org-agenda-tags-suffix)  ")
                   (org-agenda-sorting-strategy '(timestamp-up))
                   (org-agenda-remove-times-when-in-prefix nil)))
            ;; Note: the section below was causing the agenda to
            ;; freeze some times. And I wasn't really using it anyway.
            ;;
            ;; (todo "TODO"
            ;;       ((org-agenda-overriding-header "\nNext Tasks\n")
            ;;        (org-agenda-skip-function
            ;;         '(or (org-agenda-skip-entry-if 'scheduled 'deadline)
            ;;              (mpereira/org-skip-inbox)
            ;;              (mpereira/org-skip-subtree-if-habit)
            ;;              (mpereira/org-skip-all-but-first)
            ;;              (mpereira/org-skip-someday-projects-subheadings)))
            ;;        (org-agenda-sorting-strategy '(deadline-up
            ;;                                       scheduled-up
            ;;                                       time-up
            ;;                                       timestamp-up
            ;;                                       todo-state-up
            ;;                                       alpha-up))
            ;;        (org-agenda-prefix-format
            ;;         " %-18c %-22(mpereira/org-agenda-project-name-prefix-format)")))
            ))
         (inbox-file (expand-file-name "inbox.org" org-directory))
         (inbox-buffer (find-file-noselect inbox-file))
         (inbox (with-current-buffer inbox-buffer
                  (org-element-contents (org-element-parse-buffer 'headline))))
         (_ (when inbox
              (add-to-list
               'settings
               `(todo "TODO"
                      ((org-agenda-overriding-header "\nInbox\n")
                       (org-agenda-files (list ,inbox-file)))))))
         (org-agenda-custom-commands (list
                                      (list
                                       "c" "Custom agenda view"
                                       settings
                                       '((org-agenda-block-separator ?\-))))))
    (org-agenda nil "c")))

;; TODO: any reason this is a custom agenda command and not just a
;; function like `mpereira/custom-agenda'?
(setq org-agenda-custom-commands
      `(("r" "Review"
         ((tags ,(mpereira/org-agenda-review-search "today" "+1d")
                ((org-agenda-overriding-header
                  (concat
                   "\nDone today "
                   "(" (format-time-string "%A, %B %d" (current-time)) ")\n"))
                 (org-agenda-prefix-format " %i %-18c%?-12t% s")))
          (tags ,(mpereira/org-agenda-review-search "-1d" "today")
                ((org-agenda-overriding-header
                  (concat
                   "\nDone yesterday "
                   "(" (format-time-string "%A, %B %d" (mpereira/yesterday)) ")\n"))
                 (org-agenda-prefix-format " %i %-18c%?-12t% s")))
          (tags ,(mpereira/org-agenda-review-search
                  (mpereira/org-agenda-date-week-start
                   (mpereira/format-calendar-date-Y-m-d
                    (mpereira/calendar-read-date "today")))
                  (mpereira/format-calendar-date-Y-m-d
                   (mpereira/calendar-read-date "today")))
                ((org-agenda-overriding-header "\nDone this week\n")
                 (org-agenda-prefix-format
                  " %-18c %(mpereira/org-agenda-review-suffix-format) ")
                 (org-agenda-show-all-dates t)
                 (org-agenda-sorting-strategy '(timestamp-down))))
          (tags (mpereira/org-agenda-review-search
                 (mpereira/org-agenda-date-week-start
                  (mpereira/format-calendar-date-Y-m-d
                   (mpereira/calendar-read-date "-1w")))
                 (mpereira/org-agenda-date-week-end
                  (mpereira/format-calendar-date-Y-m-d
                   (mpereira/calendar-read-date "-1w"))))
                ((org-agenda-overriding-header "\nDone last week\n")
                 (org-agenda-prefix-format
                  " %-18c %(mpereira/org-agenda-review-suffix-format) ")
                 (org-agenda-show-all-dates t)
                 (org-agenda-sorting-strategy '(timestamp-down)))))
         ((org-agenda-block-separator ?\-)))))

;; Redo agenda after capturing.
(add-hook 'org-capture-after-finalize-hook 'org-agenda-maybe-redo)

;; Don't show empty agenda sections.
(add-hook 'org-agenda-finalize-hook #'mpereira/org-agenda-delete-empty-blocks)

(defun mpereira/org-gcal-entry-at-point-p ()
  (when-let ((link (org-entry-get (point) "LINK")))
    (string-match "Go to gcal web page" link)))

;; Empirically, 2 seconds seems to be good enough.
(setq mpereira/org-gcal-request-timeout 2)

(general-define-key
 :keymaps '(org-agenda-mode-map)
 "/" 'org-agenda-filter-by-regexp
 "c" (lambda ()
       (interactive)
       ;; When capturing to a calendar org-gcal sends a network request that
       ;; reorders the calendar headings on completion, causing them to have a
       ;; different order than the agenda entries. Here we install a buffer
       ;; local hook that will sync the agenda entries with the calendar
       ;; headings.
       (add-hook 'org-capture-after-finalize-hook
                 (lambda ()
                   (interactive)
                   (run-at-time mpereira/org-gcal-request-timeout
                                nil
                                #'org-agenda-maybe-redo))
                 nil
                 t)
       (org-agenda-capture))
 "d" #'org-agenda-deadline
 "f" #'org-attach
 "F" #'org-gcal-sync
 "g" (lambda ()
       (interactive)
       (org-agenda-filter-remove-all)
       (org-save-all-org-buffers)
       (org-agenda-maybe-redo))
 "h" nil
 "j" #'org-agenda-next-item
 "k" #'org-agenda-previous-item
 "l" nil
 "n" #'org-agenda-add-note
 "r" #'org-agenda-refile
 "s" #'org-agenda-schedule
 "T" #'org-agenda-set-tags
 "u" #'org-agenda-undo
 "w" nil
 "x" (lambda ()
       (interactive)
       (save-window-excursion
         (let ((agenda-buffer (current-buffer)))
           (org-agenda-goto)
           (if (mpereira/org-gcal-entry-at-point-p)
               (progn
                 (org-gcal-delete-at-point)
                 ;; org-gcal only removes the calendar headings after the
                 ;; network request finishes.
                 (run-at-time mpereira/org-gcal-request-timeout
                              nil
                              #'org-agenda-maybe-redo))
             (progn
               (quit-window)
               (org-agenda-kill))))))
 "C-j" #'org-agenda-next-item
 "C-k" #'org-agenda-previous-item
 "C-f" #'scroll-up-command
 "C-b" #'scroll-down-command)

(defmacro calendar-action (func)
  `(lambda ()
     "TODO: docstring."
     (interactive)
     (org-eval-in-calendar #'(,func 1))))

(general-define-key
 :keymaps '(org-read-date-minibuffer-local-map)
 "q" 'minibuffer-keyboard-quit
 "h" (calendar-action calendar-backward-day)
 "l" (calendar-action calendar-forward-day)
 "k" (calendar-action calendar-backward-week)
 "j" (calendar-action calendar-forward-week)
 "{" (calendar-action calendar-backward-month)
 "}" (calendar-action calendar-forward-month)
 "[" (calendar-action calendar-backward-year)
 "]" (calendar-action calendar-forward-year)
 "(" (calendar-action calendar-beginning-of-month)
 ")" (calendar-action calendar-end-of-month)
 "0" (calendar-action calendar-beginning-of-week)
 "$" (calendar-action calendar-end-of-week))

counsel-org-clock

(use-package counsel-org-clock
  :config
  (setq counsel-org-clock-default-action 'clock-in))

org-bullets

(use-package org-bullets
  :after org
  :config
  (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))

org-make-toc

(use-package org-make-toc
  :after org)

htmlize

(use-package htmlize)

ox-jira

(use-package ox-jira)

ox-gfm

(use-package ox-gfm)

ox-hugo

(use-package ox-hugo)

ox-pandoc

(use-package ox-pandoc)

ob-async

(use-package ob-async)

org-tree-slide

(use-package org-tree-slide)

Scala

scala-mode

(use-package scala-mode)

Go

go-mode

(use-package go-mode)

SQL

(require 'sql)

(add-hook 'sql-interactive-mode-hook (lambda () (toggle-truncate-lines t)))

Markdown

markdown-mode

(use-package markdown-mode
  :config
  (general-define-key
   :keymaps 'markdown-mode-map
   :states '(normal visual)
   :prefix mpereira/leader
   "oi" 'markdown-insert-link)

  (general-define-key
   :keymaps 'markdown-mode-map
   :states '(normal visual)
   "TAB" 'markdown-cycle
   "(" 'markdown-up-heading
   "k" 'evil-previous-visual-line
   "j" 'evil-next-visual-line
   "C-k" 'markdown-outline-previous-same-level
   "C-j" 'markdown-outline-next-same-level))

TOML

toml-mode

(use-package toml-mode)

YAML

yaml-mode

(use-package yaml-mode
  :config
  (add-to-list 'auto-mode-alist '("\\.yml(?:\\.j2)?\\'" . yaml-mode))

  (general-define-key
   :keymaps '(yaml-mode-map)
   :states '(insert)
   "RET" 'newline-and-indent))

Docker

docker

(use-package docker)

dockerfile-mode

(use-package dockerfile-mode
  :mode "Dockerfile.*\\'")

google-this

(use-package google-this
  :config
  (google-this-mode 1)

  (general-define-key
   :states '(normal)
   :prefix mpereira/leader
   "fg" 'google-this)

  (general-define-key
   :states '(visual)
   :prefix mpereira/leader
   "fg" 'google-this-region))

term

;; 2019-03-01: setting this to bash. Otherwise, org babel shell sessions are
;; forced to run fish.
;; (setq explicit-shell-file-name "/usr/local/bin/fish")
(setq explicit-shell-file-name "/usr/local/bin/bash")

;; Infinite buffer.
(setq term-buffer-maximum-size 0)

;; Emacs 26 has this defaulted to `t', which causes the point to not be movable
;; from the process mark.
(setq term-char-mode-point-at-process-mark nil)

(general-define-key
 :keymaps '(term-raw-map)
 :states '(normal)
 "p" 'term-paste
 "M-x" 'counsel-M-x)

(general-define-key
 :keymaps '(term-raw-map)
 :states '(insert)
 "M-v" 'term-paste)

(general-define-key
 ;; TODO: are both necessary? C-c C-c wasn't working just with `term-raw-map' so
 ;; I added `term-mode-map' and re-evaluated, started working in a term buffer.
 :keymaps '(term-raw-map term-mode-map)
 :prefix "C-c"
 ;; https://github.com/noctuid/general.el
 ;; #how-do-i-prevent-key-sequence-starts-with-non-prefix-key-errors
 "" nil
 "C-c" #'term-interrupt-subjob)

;; FIXME: some eshell commands create term buffers. Make those not be killed.
;; Kill term buffers when term process exits.
;; (defadvice term-sentinel (around my-advice-term-sentinel (proc msg))
;;   (if (memq (process-status proc) '(signal exit))
;;       (let ((buffer (process-buffer proc)))
;;         ad-do-it
;;         (kill-buffer buffer))
;;     ad-do-it))

;; (ad-activate 'term-sentinel)

(add-hook 'term-mode-hook #'mpereira/hide-trailing-whitespace)

eterm-256color

(use-package eterm-256color
  :config
  (add-hook 'term-mode-hook #'eterm-256color-mode))

default-text-scale

(use-package default-text-scale)

transpose-frame

(use-package transpose-frame)

move-text

(use-package move-text)

atomic-chrome

(use-package atomic-chrome
  :config
  (setq atomic-chrome-default-major-mode 'org-mode)
  (atomic-chrome-start-server))

expand-region

(use-package expand-region
  :config
  (general-define-key
   :states '(normal visual)
   "+" 'er/expand-region))

rainbow-delimiters

(use-package rainbow-delimiters
  :config
  (add-hook 'lisp-mode-hook 'rainbow-delimiters-mode))

org-autonum

(use-package org-autonum
  :ensure nil
  :quelpa (org-autonum
           :fetcher github
           :repo "nma83/org-autonum"))

(defun re-seq (regexp string)
  "Get a list of all regexp matches in a string."
  (save-match-data
    (let ((pos 0)
          matches)
      (while (string-match regexp string pos)
        (push (match-string 0 string) matches)
        (setq pos (match-end 0)))
      matches)))

;; FIXME: the `'tree' scope doesn't seem to be working. Calling this
;; function on a heading with subsequent siblings will consider the
;; first heading the root of all the other ones.
;; This is because of the promote/demote hack.
(defun mpereira/org-enumerate-headings ()
  "TODO: docstring."
  (interactive)
  (save-excursion
    (let ((spacing nil)
          (current-level (org-current-level))
          (enumeration '()))
      (org-back-to-heading)
      (dotimes (i (- current-level 1))
        (org-promote-subtree))
      (org-map-entries
       (lambda ()
         ;; We subtract 1 because we want the relevant outlines being
         ;; considered to have level 1.
         (setq level (- (org-outline-level) 1))
         (print (list (list 'level level) (list 'enumeration enumeration)))
         ;; Skip the tree root entry.
         (when (> level 0)
           ;; Move to start of heading text.
           (re-search-forward "\\* " (line-end-position) t)
           (if (< (length enumeration) level)
               ;; Expand enumeration to next level.
               (setq enumeration (append enumeration '(0)))
             (if (not (= (length enumeration) level))
                 ;; Prune enumeration to current level.
                 (setq enumeration (butlast enumeration
                                            (- (length enumeration)
                                               level)))))
           ;; Increment last enumeration number.
           (setq enumeration (append (butlast enumeration 1)
                                     (list (1+ (car (last enumeration 1))))))
           (setq enumeration-string (concat
                                     (mapconcat
                                      'number-to-string enumeration ".")
                                     ". "))
           ;; FIXME: this isn't working.
           (if (re-search-forward (concat "* "
                                          "\\("
                                          "[[:digit:]]+\."
                                          "\\([[:digit:]]+\.\\)*"
                                          "\\)"
                                          " ")
                                  (line-end-position)
                                  t)
               ;; Replace existing enumeration if it's different.
               (unless (string= (match-string 0) enumeration-string)
                 (replace-match enumeration-string nil nil))
             ;; Insert new enumeration.
             (insert enumeration-string))))
       t
       'tree)
      (dotimes (i (- current-level 1))
        (org-demote-subtree)))))

json-snatcher

(use-package json-snatcher
  :ensure nil
  :quelpa (json-snatcher
           :fetcher github
           :repo "Sterlingg/json-snatcher")
  :config
  (setq jsons-path-printer #'jsons-print-path-jq))

osascripts

(use-package osascripts
  :ensure nil
  :quelpa (osascripts
           :fetcher github
           :repo "leoliu/osascripts"))

help-fns+

(use-package help-fns+
  :ensure nil
  :quelpa (help-fns+
           :fetcher github
           :repo "emacsmirror/help-fns-plus"))

helpful

(use-package helpful
  :config
  ;; I had this set to `t' for some reason. What was it?
  ;; (setq-default helpful--view-literal t)
  )

LSP

lsp-mode

(use-package lsp-mode
  :config
  (with-eval-after-load "lsp-ui"
    (add-hook 'lsp-mode-hook 'lsp-ui-mode)))

lsp-ui

(use-package lsp-ui)

lsp-rust

Depends on rls-preview, rust-analysis and rust-src. Please check the RLS install instructions for more details.

Disabled for now. Using flycheck-inline instead.

;; (use-package lsp-rust
;;   :after lsp-mode
;;   :init
;;   (setq lsp-rust-rls-command '("rustup" "run" "nightly" "rls" "--cli")))

eglot

(use-package eglot)

es-mode

(use-package es-mode
  :config
  (add-to-list 'auto-mode-alist '("\\.es$" . es-mode)))

aggressive-indent

(use-package aggressive-indent
  :config
  (add-to-list 'aggressive-indent-excluded-modes 'sql-mode)
  (add-to-list 'aggressive-indent-excluded-modes 'makefile-bsdmake-mode)
  (add-to-list 'aggressive-indent-excluded-modes 'python-mode)
  (add-hook 'prog-mode-hook #'mpereira/maybe-enable-aggressive-indent-mode))

gist

(use-package gist
  :config
  (general-define-key
   :states '(normal visual)
   :prefix mpereira/leader
   :infix "gi"
   "p" 'gist-region-or-buffer-private
   "i" 'gist-region-or-buffer
   "l" 'gist-list)

  (general-define-key
   :keymaps '(gist-list-menu-mode-map)
   "g" nil
   "k" nil)

  ;; TODO: can we use `(evil-set-initial-state 'gist-list-menu-mode 'normal)`
  ;; instead of most of the mappings below?
  (general-define-key
   :keymaps '(gist-list-menu-mode-map)
   "C-j" 'next-line
   "C-k" 'previous-line
   "j" 'next-line
   "k" 'previous-line
   "C-f" 'scroll-up-command
   "C-b" 'scroll-down-command
   "r" 'gist-list-reload
   "gg" 'beginning-of-buffer
   "G" 'end-of-buffer
   "/" 'evil-search-forward
   "n" 'evil-search-next
   "N" 'evil-search-previous
   "X" 'gist-kill-current))

lispy

(use-package lispy
  :config
  (add-hook 'emacs-lisp-mode-hook 'lispy-mode)
  (add-hook 'clojure-mode-hook 'lispy-mode)

  ;; Disable most lispy mappings.
  (setq lispy-mode-map lispy-mode-map-base)
  (setcdr (assq 'lispy-mode minor-mode-map-alist)
          lispy-mode-map)

  (general-define-key
   :keymaps 'lispy-mode-map
   :states '(insert)
   "<backspace>" 'lispy-delete-backward
   "<deletechar>" 'lispy-delete
   ")" 'lispy-right-nostring
   "\"" 'lispy-doublequote
   "[" 'lispy-brackets
   "]" 'lispy-close-square
   "{" 'lispy-braces
   "}" 'lispy-close-curly)

  (general-define-key
   :keymaps 'lispy-mode-map
   :states '(normal)
   :prefix mpereira/leader
   "r" 'lispy-raise-sexp
   "R" 'lispy-raise-some
   "(" 'lispy-wrap-round
   "[" 'lispy-wrap-brackets
   "{" 'lispy-wrap-braces
   "c" 'lispy-clone))

lispyville

(use-package lispyville
  :after (evil lispy)
  :config
  (add-hook 'lispy-mode-hook 'lispyville-mode)

  (lispyville-set-key-theme '(operators))

  (general-define-key
   :keymaps '(lispyville-mode-map)
   :states '(insert)
   "ESC" 'lispyville-normal-state)

  (general-define-key
   :keymaps '(lispyville-mode-map)
   :states '(normal)
   "S" 'lispyville-change-whole-line
   "B" 'mpereira/backward-sexp
   "gA" 'mpereira/append-to-end-of-list
   "gI" 'mpereira/insert-to-beginning-of-list
   "W" 'mpereira/forward-sexp
   "(" 'lispyville-backward-up-list
   ")" 'lispyville-up-list
   "C-(" 'lispyville-beginning-of-defun
   "C-)" 'lispyville-end-of-defun
   "{" 'lispyville-previous-opening
   "}" 'lispyville-next-opening
   ">)" 'lispy-forward-slurp-sexp
   "<)" 'lispy-forward-barf-sexp
   "<(" 'lispy-backward-slurp-sexp
   ">(" 'lispy-backward-barf-sexp
   "|" 'lispy-split
   "_" 'lispy-join
   "<f" 'lispyville-drag-backward
   ">f" 'lispyville-drag-forward
   "C-9" 'lispy-describe-inline
   "C-0" 'lispy-arglist-inline))

which-key

(use-package which-key
  :config
  (which-key-mode))

projectile

(use-package projectile
  :config
  (projectile-mode t)

  (setq projectile-enable-caching nil)
  (setq projectile-require-project-root t)
  ;; With this, do I even need counsel-projectile?
  (setq projectile-completion-system 'ivy)

  ;; Define there here until counsel-projectile is broken.
  ;; TODO: counsel-projectile broken:
  ;; https://github.com/ericdanan/counsel-projectile/issues/93.
  (general-define-key
   :states '(normal visual)
   :prefix mpereira/leader
   :infix "p"
   "s" 'mpereira/ivy-persp-switch-project
   ;; TODO: counsel-projectile broken: https://github.com/ericdanan/counsel-projectile/issues/93.
   ;; "b" 'counsel-projectile-switch-to-buffer
   "b" 'projectile-switch-to-buffer
   "f" 'projectile-find-file
   "g" 'rg-project
   "G" 'rg-dwim-project-dir)

  (general-define-key
   :states '(normal)
   :prefix mpereira/leader
   :infix "s"
   "h" 'mpereira/projectile-eshell
   "H" 'projectile-run-term
   "c" 'projectile-run-async-shell-command-in-root))

term-projectile

(use-package term-projectile
  :after projectile)

perspective

(use-package perspective
  :ensure nil
  :quelpa (perspective
           :fetcher github
           :repo "nex3/perspective-el")
  :init
  (setq persp-show-modestring nil)
  :config
  (persp-mode t))

persp-projectile

(use-package persp-projectile
  :after perspective projectile
  :config
  (general-define-key
   :states '(normal)
   :prefix mpereira/leader
   :infix "p"
   "p" 'persp-switch-last))

avy

(use-package avy
  :config
  (setq avy-all-windows nil))

ivy

(use-package ivy
  :general
  (:keymaps 'ivy-minibuffer-map
   "C-j" 'ivy-next-line
   "C-k" 'ivy-previous-line
   "C-f" 'ivy-scroll-up-command
   "C-b" 'ivy-scroll-down-command
   "C-o" 'ivy-occur
   "C-h" 'ivy-beginning-of-buffer
   "C-l" 'ivy-end-of-buffer
   "C-/" 'ivy-restrict-to-matches
   "<escape>" 'minibuffer-keyboard-quit)
  :config
  (ivy-mode t)

  (setq ivy-use-selectable-prompt t)
  (setq ivy-height 20)
  (setq ivy-wrap t))

ivy-rich

(use-package ivy-rich
  :after counsel
  :init
  (setq ivy-format-function #'ivy-format-function-line)
  ;; Toggle `ivy-rich-mode' after modifying.
  (setq ivy-rich-display-transformers-list
        `(ivy-switch-buffer
          (:columns
           ((ivy-rich-candidate
             (:width 0.3))
            (ivy-rich-switch-buffer-size
             (:width 8))
            (ivy-rich-switch-buffer-indicators
             (:width 4 :face error :align right))
            (ivy-rich-switch-buffer-major-mode
             (:width 15 :face warning))
            (ivy-rich-switch-buffer-project
             (:width 20 :face success))
            (ivy-rich-switch-buffer-path
             (:width (lambda (x)
                       (ivy-rich-switch-buffer-shorten-path
                        x (ivy-rich-minibuffer-width 0.3))))))
           :predicate (lambda (cand) (get-buffer cand)))
          counsel-M-x (:columns
                       ((counsel-M-x-transformer (:width 0.3))
                        (ivy-rich-counsel-function-docstring
                         (:face font-lock-doc-face))))
          counsel-describe-function
          (:columns
           ((counsel-describe-function-transformer (:width 0.3))
            (ivy-rich-counsel-function-docstring (:face font-lock-doc-face))))
          counsel-describe-variable
          (:columns
           ((counsel-describe-variable-transformer (:width 0.3))
            (ivy-rich-counsel-variable-docstring (:face font-lock-doc-face))))
          counsel-recentf
          (:columns
           ((ivy-rich-candidate (:width 0.5))
            (ivy-rich-file-last-modified-time (:face font-lock-comment-face))))))
  :config
  (ivy-rich-mode 1))

ivy-posframe

(use-package ivy-posframe
  :after ivy
  :config
  ;; Override `ivy-display-functions-alist' to have all ivy completions be shown
  ;; at point with a few exceptions.
  ;; (setq ivy-display-functions-alist
  ;;       '((magit-push-current-to-upstream . ivy-display-function-fallback)
  ;;         (magit-reset-soft . ivy-display-function-fallback)
  ;;         (magit-reset-hard . ivy-display-function-fallback)
  ;;         (magit-checkout . ivy-display-function-fallback)
  ;;         (counsel-projectile-find-file . ivy-display-function-fallback)
  ;;         (swiper . ivy-display-function-fallback)
  ;;         (t . ivy-posframe-display-at-point)))

  ;; Disable for now. Width-changing at-point frames are annoying.
  (setq ivy-display-functions-alist
        '((t . ivy-display-function-fallback)))
  (ivy-posframe-enable))

prescient

(use-package prescient
  :config
  (prescient-persist-mode))

ivy-prescient

(use-package ivy-prescient
  :after (ivy prescient)
  :config
  (ivy-prescient-mode))

company-prescient

(use-package company-prescient
  :after (company prescient)
  :config
  (company-prescient-mode))

swiper

(use-package swiper)

counsel

(use-package counsel
  :after (ivy swiper)
  :config
  (counsel-mode 1)

  (setq counsel-find-file-ignore-regexp "/vendor/")

  (setq counsel-describe-function-function #'helpful-callable
        counsel-describe-variable-function #'helpful-variable))

wgrep

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

command-log-mode

(use-package command-log-mode
  :config
  (setq command-log-mode-auto-show t)
  (setq command-log-mode-window-size 60))

counsel-projectile

(use-package counsel-projectile
  :after (counsel projectile)
  :config
  (setq projectile-switch-project-action 'counsel-projectile-find-file))

neotree

(use-package neotree
  :after projectile
  :general
  (:keymaps 'neotree-mode-map
   :states '(normal visual)
   "RET" #'neotree-enter
   "TAB" #'neotree-enter
   "r" #'neotree-refresh
   "q" #'neotree-hide)
  (:keymaps 'neotree-mode-map
   :states '(normal visual)
   :prefix mpereira/leader
   :infix "p"
   "t" #'neotree-hide)
  :config
  (setq neo-smart-open t)
  (setq neo-window-fixed-size nil)
  (setq neo-window-width 60))

all-the-icons

(use-package all-the-icons)

dired-sidebar

(use-package dired-sidebar
  :commands (dired-sidebar-toggle-sidebar))

all-the-icons-dired

Run M-x all-the-icons-install-fonts after installing.

(use-package all-the-icons-dired
  :after (all-the-icons dired)
  :commands (all-the-icons-dired-mode)
  :config
  (add-hook 'dired-mode-hook #'all-the-icons-dired-mode))

diff-hl

(use-package diff-hl
  :config
  (global-diff-hl-mode t)
  (diff-hl-flydiff-mode t)

  (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)

  (set-face-foreground 'diff-hl-insert "none")
  (set-face-background 'diff-hl-insert "green4")
  (set-face-foreground 'diff-hl-change "none")
  (set-face-background 'diff-hl-change "yellow3")
  (set-face-foreground 'diff-hl-delete "none")
  (set-face-background 'diff-hl-delete "red4"))

dimmer

(use-package dimmer
  :config
  (setq dimmer-fraction 0.5))

emojify

Not enabled globally by default for now.

(use-package emojify)

browse-at-remote

(use-package browse-at-remote
  :config
  ;; Permanent SHA link.
  (setq browse-at-remote-prefer-symbolic nil))

git-timemachine

(use-package git-timemachine)

magit

(use-package magit
  :config
  (add-hook 'with-editor-mode-hook 'evil-insert-state)

  (setq magit-diff-refine-hunk t)
  (setq magit-prefer-remote-upstream t)
  (setq magit-display-buffer-function 'magit-display-buffer-fullframe-status-v1)
  ;; FIXME: not working?
  ;; https://github.com/magit/magit/issues/2872#issuecomment-291011191
  (setq magit-list-refs-sortby "-creatordate")

  (transient-bind-q-to-quit)

  ;; "q" is being bound to `quit-window' on the magit status buffer, for some
  ;; reason. It doesn't seem to be coming from transient.
  (general-define-key
   :keymaps '(magit-status-mode-map)
   :states '(normal visual)
   "q" #'magit-mode-bury-buffer)

  ;; This makes magit slow when there are a lot of buffers. See:
  ;; https://github.com/magit/magit/issues/2687#issuecomment-224845496
  (add-hook 'magit-update-uncommitted-buffer-hook 'vc-refresh-state))

forge

Sometimes I have trouble pulling topics/notifications through forge. I just ([2019-03-18 Mon]) did something that fixed it:

  1. Removed related forge GitHub personal access token
  2. Disabled ghub-use-workaround-for-emacs-bug
  3. Tried again

Step #2 seems to have caused that GitHub authentication is done through 2-factor instead of credentials.

I still sometimes see “502 Bad gateway /graphql” errors when trying to pull repositories, but it seems like just trying again a few times gets it done.

(use-package forge
  :after (ghub magit)
  :init
  (setq ghub-use-workaround-for-emacs-bug nil))

magit-todos

(use-package magit-todos
  :ensure nil
  :quelpa (magit-todos
           :fetcher github
           :repo "alphapapa/magit-todos")
  :after magit
  :config
  (add-hook 'magit-mode-hook 'magit-todos-mode))

Wolfram Alpha

(use-package wolfram
  :config
  (load-file (expand-file-name "wolfram-secrets.el" user-emacs-directory))

  (setq wolfram-alpha-app-id mpereira/secret-wolfram-alpha-app-id)

  (general-define-key
   :keymaps '(global-map)
   :states '(normal visual)
   :prefix mpereira/leader
   :infix "e"
   "w" 'wolfram-alpha))

circe

(use-package circe
  :config
  (enable-circe-color-nicks)

  (load-file (expand-file-name "circe-secrets.el" user-emacs-directory))

  ;; Disable name listing when joining channels.
  (circe-set-display-handler "353" 'circe-display-ignore)
  (circe-set-display-handler "366" 'circe-display-ignore)

  ;; Logging.
  (setq lui-logging-directory (expand-file-name
                               "irc" mpereira/dropbox-directory))
  (load "lui-logging" nil t)
  (enable-lui-logging-globally)

  ;; Automatic reconnect.
  (setq circe-lagmon-timer-tick 60)
  (load "circe-lagmon" nil t)
  (circe-lagmon-mode)

  ;; Timestamps in margins.
  (setq lui-time-stamp-position 'right-margin)
  (setq lui-time-stamp-format " %H:%M ")
  (defun mpereira/circe-set-margin ()
    (setq right-margin-width 7))
  (add-hook 'lui-mode-hook #'mpereira/circe-set-margin)

  (setq circe-default-nick "mpereira"
        circe-default-user "mpereira"
        circe-default-realname "mpereira")
  (setq circe-default-part-message "Bye.")
  (setq circe-default-quit-message "Bye.")
  (setq circe-reduce-lurker-spam t)
  (setq circe-network-options
        `(("Freenode"
           :host "chat.freenode.net"
           :nickserv-password ,mpereira/secret-circe-nickserv-password
           :tls t
           :channels (:after-auth
                      "#emacs"
                      "#clojure"
                      "##rust"
                      "#haskell")))))

mingus

(use-package mingus
  :config
  (evil-set-initial-state 'mingus-help-mode 'emacs)
  (evil-set-initial-state 'mingus-playlist-mode 'emacs)
  (evil-set-initial-state 'mingus-browse-mode 'emacs)

  (dolist (hook '(mingus-browse-hook
                  mingus-playlist-hooks))
    (add-hook hook 'mpereira/hide-trailing-whitespace)))

reveal-in-osx-finder

(use-package reveal-in-osx-finder)

synosaurus

Depends on WordNet.

(use-package synosaurus)

unfill

(use-package unfill
  :config
  (general-define-key
   :keymaps '(global-map)
   "M-q" 'unfill-toggle))

smex

(use-package smex
  :config
  (smex-initialize)
  (general-define-key
   :keymaps '(global-map)
   "M-X" 'smex-major-mode-commands))

Mappings

(general-define-key
 :keymaps '(override) ;; Check out `general-override-mode-map'.
 :states '(normal visual insert)
 "M-+" #'default-text-scale-increase
 "M--" #'default-text-scale-decrease
 "M-=" #'default-text-scale-reset
 "M-F" #'toggle-frame-fullscreen
 "M-H" 'buf-move-left
 "M-J" 'buf-move-down
 "M-K" 'buf-move-up
 "M-L" 'buf-move-right
 "M-M" #'mpereira/toggle-maximize-buffer
 "M-N" (lambda () (interactive) (mpereira/toggle-maximize-buffer t))
 "M-O" #'transpose-frame
 "M-h" #'evil-window-left
 "M-j" #'evil-window-down
 "M-k" #'evil-window-up
 "M-l" #'evil-window-right)

(general-define-key
 "<escape>" #'keyboard-quit)

(general-define-key
 :keymaps '(minibuffer-local-map
            minibuffer-local-ns-map
            minibuffer-local-completion-map
            minibuffer-local-must-match-map
            minibuffer-local-isearch-map)
 "<escape>" #'minibuffer-keyboard-quit)

(general-define-key
 :keymaps '(swiper-map
            swiper-all-map
            ivy-minibuffer-map)
 "<escape>" 'minibuffer-keyboard-quit ;; is this still needed?
 "C-r" 'evil-paste-from-register)

;; FIXME: isn't M-x bound in insert mode in the first place and why
;; doesn't this binding work?
(general-define-key
 :keymaps '(global-map)
 :states '(insert)
 "M-x" #'execute-extended-command)

;; FIXME: this doesn't work.
;; Movement bindings for evil-ex-search. Why doesn't `evil-ex-search-keymap'
;; work for this?
(general-define-key
 :keymaps '(minibuffer-inactive-mode-map)
 "C-k" #'previous-line-or-history-element
 "C-j" #'next-line-or-history-element
 "C-?" #'previous-matching-history-element
 "C-/" #'next-matching-history-element)

;; FIXME: is this still needed?
;; Make it possible for other modes to use these bindings (e.g. company mode
;; uses it for navigating completions).
(general-define-key
 :keymaps '(evil-insert-state-map)
 "C-j" nil
 "C-k" nil)

;; Non-leader "master" bindings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(general-define-key
 :keymaps '(global-map)
 :states '(normal visual)
 "C-l" #'evil-ex-nohighlight
 "[c" #'diff-hl-previous-hunk
 "]c" #'diff-hl-next-hunk
 "s" #'avy-goto-char-timer)

;; Non-leader "g" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(general-define-key
 :keymaps '(global-map)
 :states '(normal visual)
 :prefix "g"
 "q" #'fill-paragraph)

;; TODO: make this not override org mode?
;; (general-define-key
;;  :keymaps '(global-map)
;;  :states '(normal visual)
;;  :prefix "C-c"
;;  "C-o" 'browse-url)

(eval-after-load 'evil-ex
  '(evil-ex-define-cmd "bD" #'mpereira/delete-file-and-buffer))

(eval-after-load 'evil-ex
  '(evil-ex-define-cmd "pwd" #'mpereira/pwd))

(general-define-key
 :keymaps '(global-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "e"
 ":" #'eval-expression)

;; "Master" bindings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; This seems to be working for now. `general-override-mode-map' might be of use
;; in the future.
(general-define-key
 :states '(normal visual)
 :prefix mpereira/leader
 "," #'evil-switch-to-windows-last-buffer
 "." #'ivy-resume
 "/" #'swiper
 "<backtab>" #'counsel-descbinds
 "<tab>" #'which-key-show-major-mode
 "=" #'quick-calc
 "B" #'mpereira/bm-counsel-find-bookmark
 "b" #'switch-to-buffer
 "go" 'browse-at-remote
 "gr" 'diff-hl-revert-hunk
 "hs" #'mpereira/split-window-below-and-switch
 "hs" #'mpereira/split-window-below-and-switch
 "hv" #'mpereira/toggle-window-split
 "q" #'evil-quit
 "T" #'bm-toggle
 "vs" #'mpereira/split-window-right-and-switch
 "w" #'save-buffer)

;; d -> describe ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(general-define-key
 :keymaps '(global-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "d"
 "b" #'describe-buffer
 "f" #'find-function-on-key
 "k" #'describe-key
 "m" #'describe-mode)

;; f -> find ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(general-define-key
 :keymaps '(global-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "f"
 ";" #'counsel-minibuffer-history
 ":" #'counsel-expression-history
 "b" #'ivy-switch-buffer
 "c" #'counsel-org-clock-history
 ;; Didn't like `counsel-explorer' too much...
 ;; "f" #'counsel-explorer
 "f" #'counsel-find-file
 "k" #'counsel-descbinds
 "l" #'counsel-find-library
 "m" #'describe-keymap
 "n" #'counsel-describe-function
 "o" #'counsel-imenu
 "p" #'package-list-packages-no-fetch
 "v" #'counsel-describe-variable
 "y" #'counsel-yank-pop)

;; g -> git ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(general-define-key
 :keymaps '(global-map)
 :states '(normal)
 :prefix mpereira/leader
 :infix "g"
 "/" #'counsel-git-log
 "<" #'smerge-keep-mine
 ">" #'smerge-keep-other
 "[" #'git-timemachine-show-previous-revision
 "]" #'git-timemachine-show-next-revision
 "b" #'magit-blame
 "c" #'magit-commit-popup
 "d" #'magit-diff-buffer-file
 "D" #'magit-diff-unstaged
 "f" #'magit-find-file
 "g" #'counsel-git-grep
 "L" #'magit-log-all
 "l" #'magit-log-buffer-file
 "p" #'magit-push-popup
 "s" #'magit-status
 "t" #'git-timemachine-toggle
 "w" #'magit-stage-file
 "W" #'magit-stage-modified)

;; p -> project ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(general-define-key
 :keymaps '(global-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "p"
 "s" #'projectile-persp-switch-project
 "b" #'counsel-projectile-switch-to-buffer
 "f" #'counsel-projectile-find-file
 "g" #'rg-project
 "G" #'rg-dwim-project-dir
 ;; dired-sidebar still a bit quirky. Let's keep using neotree for now.
 ;; "t" #'dired-sidebar-toggle-sidebar
 "t" #'mpereira/neotree-project-dir)

;; t -> toggle ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(general-define-key
 :keymaps '(global-map)
 :states '(normal visual)
 :prefix mpereira/leader
 :infix "t"
 "d" #'toggle-debug-on-error
 "e" #'toggle-debug-on-error
 "l" #'toggle-truncate-lines
 "n" #'mpereira/narrow-or-widen-dwim
 "o" #'mpereira/hs-toggle-all
 "q" #'toggle-debug-on-quit
 "r" #'dired-toggle-read-only
 "t" #'mpereira/narrow-or-widen-dwim
 "w" #'mpereira/narrow-or-widen-dwim)


;; TODO: Make this not override magit's stash popup.
;; (general-define-key
;;  :keymaps '(global-map)
;;  :states '(normal visual)
;;  :infix "z"
;;  "C" 'evil-close-folds
;;  "O" 'evil-open-folds)

(general-define-key
 :keymaps '(hs-minor-mode-map)
 :states '(normal visual)
 :prefix "z"
 "0" #'hs-show-all
 "1" (mpereira/make-hs-hide-level 1)
 "2" (mpereira/make-hs-hide-level 2)
 "3" (mpereira/make-hs-hide-level 3)
 "4" (mpereira/make-hs-hide-level 4)
 "5" (mpereira/make-hs-hide-level 5))

(general-define-key
 :keymaps '(hs-minor-mode-map)
 :states '(normal visual)
 "TAB" #'hs-toggle-hiding
 "<S-tab>" #'mpereira/hs-toggle-all)

;; Return to original cursor position when cancelling search.
(general-define-key
 :keymaps '(isearch-mode-map)
 "<escape>" #'isearch-cancel)

(general-define-key
 :keymaps '(evil-ex-search-keymap)
 "<escape>" #'minibuffer-keyboard-quit)

(general-define-key
 :keymaps '(help-mode-map)
 "<" #'help-go-back
 ">" #'help-go-forward)

(general-define-key
 :states '(normal visual)
 :keymaps '(helpful-mode-map deadgrep-mode-map)
 "q" #'kill-buffer-and-window)

Stuff I keep forgetting

org mode file links to search patterns can’t start with open parens

https://www.mail-archive.com/emacs-orgmode@gnu.org/msg112359.html

EXPRESSION can be used only once per org-agenda-prefix-format

Emulate C-u (universal-argument)

For raw prefix arg (interactive “P”)

(let ((current-prefix-arg '(4)))
  (call-interactively 'some-func))

Otherwise

(let ((current-prefix-arg 4))
  (call-interactively 'some-func))

After modifying PATH

Run exec-path-from-shell-initialize on eshell buffers.

Terminate init.el loading early

(with-current-buffer " *load*"
  (goto-char (point-max)))

Change font: M-x x-select-font

(use-package org-super-agenda)

(let ((org-super-agenda-groups
       '((:deadline (before "2018-09-01"))
         (:discard (:anything t)))))
  (org-todo-list))

(-let* (((sec minute hour day month year dow dst utcoff) (decode-time))
        (last-day-of-month (calendar-last-day-of-month month year))
        (target-date
         ;; A hack that seems to work fine.  Yay, Postel!
         (format "%d-%02d-%02d" year month (1+ last-day-of-month)))
        (org-super-agenda-groups
         `((:deadline (before ,target-date))
           (:discard (:anything t)))))
  (org-todo-list)

  (use-package org-ql
    :ensure nil
    :quelpa (org-ql
             :fetcher github
             :repo "alphapapa/org-ql")
    :config
    (require 'org-habit)
    (require 'org-ql-agenda))

  (defun timestamp->time (timestamp)
    (let* ((year (plist-get timestamp ':year-end))
           (month (plist-get timestamp ':month-end))
           (day (plist-get timestamp ':day-end))
           (hour (plist-get timestamp ':hour-end))
           (minute (plist-get timestamp ':minute-end))
           (second 0))
      (encode-time second minute hour day month year)))

  (defun timestamp->date (timestamp)
    (let* ((year (plist-get timestamp ':year-end))
           (month (plist-get timestamp ':month-end))
           (day (plist-get timestamp ':day-end)))
      (list month day year)))

  (defun date->timestamp (date)
    (let* ((year (calendar-extract-year date))
           (month (calendar-extract-month date))
           (day (calendar-extract-day date))
           (hour 0)
           (minute 0)
           (second 0))
      (list :year-start year
            :year-end year
            :month-start month
            :month-end month
            :day-start day
            :day-end day
            :hour-start hour
            :hour-end hour
            :minute-start minute
            :minute-end minute
            :second-start second
            :second-end second)))

  (defun date->time (date)
    (let* ((year (calendar-extract-year date))
           (month (calendar-extract-month date))
           (day (calendar-extract-day date))
           (hour 0)
           (minute 0)
           (second 0))
      (encode-time second minute hour day month year)))

  (defun time->timestamp (raw-time)
    (let* ((time (decode-time raw-time))
           (year (nth 5 time))
           (month (nth 4 time))
           (day (nth 3 time))
           (hour (nth 2 time))
           (minute (nth 1 time))
           (second (nth 0 time)))
      (list :year-start year
            :year-end year
            :month-start month
            :month-end month
            :day-start day
            :day-end day
            :hour-start hour
            :hour-end hour
            :minute-start minute
            :minute-end minute
            :second-start second
            :second-end second)))

  (defun time->date (raw-time)
    (let* ((time (decode-time raw-time))
           (year (nth 5 time))
           (month (nth 4 time))
           (day (nth 3 time)))
      (list month day year)))

  (time->date (time-subtract (date->time (calendar-current-date))
                             (days-to-time 2)))

  (defun timestamp-in-date-interval-p (date-interval timestamp)
    (let* ((start-date (nth 0 date-interval))
           (end-date (nth 1 date-interval)))
      (<= (calendar-absolute-from-gregorian start-date)
          (calendar-absolute-from-gregorian (timestamp->date timestamp))
          (calendar-absolute-from-gregorian end-date))))

  (timestamp-in-date-interval-p (list (calendar-current-date)
                                      (calendar-current-date))
                                (time->timestamp (current-time)))

  (timestamp-at-day-p (time-to-number-of-days (decode-time (current-time))) (time-to-days (decode-time (timestamp->time (time->timestamp (current-time))))))

  (defun timestamp-filter (path start-date end-date entries)
    (-filter (lambda (x)
               (let* ((timestamp (get-in path x)))
                 (when timestamp
                   (timestamp-in-date-interval-p (list start-date end-date)
                                                 timestamp))))
             entries))

  (defun day-of-week (date)
    (let ((dow (->> date
                    (date->time)
                    (decode-time)
                    (nth 6))))
      (if (= dow 0)
          6
        (- dow 1))))

  (defun beginning-of-week (date)
    (let ((day (day-of-week date)))
      (if (= day 0)
          date
        (time->date (time-subtract (date->time date)
                                   (days-to-time day))))))

  (defun beginning-of-month (date)
    (let ((day (calendar-extract-day date)))
      (if (= day 1)
          date
        (time->date (time-subtract (date->time date)
                                   (days-to-time day))))))

  (defun minus-days (days date)
    (time->date (time-subtract (date->time date) (days-to-time days))))

  (defun closed-today (entries)
    (timestamp-filter '(headline :closed timestamp)
                      (calendar-current-date)
                      (calendar-current-date)
                      entries))

  (defun closed-yesterday (entries)
    (timestamp-filter '(headline :closed timestamp)
                      (minus-days 2 (calendar-current-date))
                      (minus-days 1 (calendar-current-date))
                      entries))

  (defun closed-this-week-before-today (entries)
    (timestamp-filter '(headline :closed timestamp)
                      (beginning-of-week (calendar-current-date))
                      (minus-days 1 (calendar-current-date))
                      entries))

  (defun closed-this-week-before-yesterday (entries)
    (timestamp-filter '(headline :closed timestamp)
                      (beginning-of-week (calendar-current-date))
                      (minus-days 2 (calendar-current-date))
                      entries))

  (defun closed-last-week (entries)
    (let ((last-day-of-last-week (minus-days 1 (beginning-of-week (calendar-current-date)))))
      (timestamp-filter '(headline :closed timestamp)
                        (beginning-of-week last-day-of-last-week)
                        last-day-of-last-week
                        entries)))

  (defun closed-this-month-before-this-week (entries)
    (timestamp-filter '(headline :closed timestamp)
                      (beginning-of-month (calendar-current-date))
                      (beginning-of-week (calendar-current-date))
                      entries))

  (defun closed-this-month (entries)
    (timestamp-filter '(headline :closed timestamp)
                      (beginning-of-month (calendar-current-date))
                      (calendar-current-date)
                      entries))

  (defun get-in (ks coll)
    (if ks
        (get-in (cdr ks) (let ((k (car ks)))
                           (cond
                            ((plist-member coll k) (plist-get coll k))
                            ((integerp k) (nth k coll)))))
      coll))

  (timestamp->date '(:type inactive
                     :raw-value
                     "[2017-11-03 Fri 15:57]"
                     :year-start
                     2017
                     :month-start
                     11
                     :day-start
                     3
                     :hour-start
                     15
                     :minute-start
                     57
                     :year-end
                     2017
                     :month-end
                     11
                     :day-end
                     3
                     :hour-end
                     15
                     :minute-end
                     57
                     :begin
                     15861
                     :end
                     15884
                     :post-blank
                     1))

  (--> (org-ql (org-agenda-files)
               (and (done))
               :sort (date))
       ;; (closed-today it)
       ;; (closed-this-week-before-today it)
       (closed-this-month-before-this-week it)
       ;; (car it)
       (length it)
       ;; (cl-prettyprint it)
       )

  (funcall (get-in '(0 1 1) '((nil #'closed-today))) '())

  (let ((inhibit-read-only t))
    (with-current-buffer (get-buffer-create "*org-ql-complex*")
      (erase-buffer)
      (--each '(("Done today" #'closed-today)
                ("Done yesterday" #'closed-yesterday)
                ("Done this week" #'closed-this-week-before-yesterday)
                ("Done last week" #'closed-last-week)
                ("Done this month" #'closed-this-month))
        (--> (list (car it) (funcall (get-in '(1 1) it)
                                     (org-ql (org-agenda-files)
                                             (done)
                                             :sort (date)
                                             :markers t)))
             (list (car it) (--map (concat (org-ql-agenda--format-element it) "\n")
                                   (nth 1 it)))
             (insert (concat (car it) "\n" (apply #'concat (nth 1 it))) "\n")))
      (pop-to-buffer (current-buffer))))

When melpa.org is down

File-local variables

These need to be at the end of the file.

You can’t perform that action at this time.