;;; init.el -*- lexical-binding: t; -*-
(defun chris/display-startup-time ()
  (message "Emacs loaded in %s with %d garbage collections."
           (format "%.2f seconds"
                 (float-time
                (time-subtract after-init-time before-init-time)))
         gcs-done))
(add-hook 'emacs-startup-hook #'chris/display-startup-time)

(setq inhibit-startup-message t)

(scroll-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(set-fringe-mode +1)

(menu-bar-mode -1)
(blink-cursor-mode -1)
(column-number-mode +1)

(if (string-equal (system-name) "syl")
    (defvar chris/default-font-size 240)
  (defvar chris/default-font-size 120))

(defun chris/set-font-faces ()
  "Set the faces for our fonts"
  (message "Setting faces!")
  (set-face-attribute 'default nil :font "VictorMono Nerd Font"
                      :height chris/default-font-size)
  (set-face-attribute 'fixed-pitch nil :font "VictorMono Nerd Font"
                      :height chris/default-font-size)
  (set-face-attribute 'variable-pitch nil :font "Cantarell"
                      :height (+ chris/default-font-size (/ chris/default-font-size 8)) :weight 'regular))

(if (daemonp)
    (add-hook 'after-make-frame-functions
	      (lambda (frame)
		(with-selected-frame frame
		  (chris/set-font-faces))))
  (chris/set-font-faces))

(setq display-line-numbers-type 'relative)
(global-display-line-numbers-mode +1)
(add-hook 'prog-mode-hook (display-line-numbers-mode +1))
(global-visual-line-mode +1)

(setq doc-view-resolution 192)

(global-set-key (kbd "<escape>") 'keyboard-escape-quit)

(recentf-mode +1)

(setq straight-fix-org t)
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(setq straight-use-package-by-default t)

(straight-use-package 'use-package)
(setq use-package-verbose t)

(use-package command-log-mode
  :commands command-log-mode)

(use-package all-the-icons)

(use-package doom-modeline
  :ensure t
  :init
  (doom-modeline-mode 1)
  (setq doom-modeline-height 35
        doom-modeline-bar-width 3
        all-the-icons-scale-factor 0.9)
  (if (daemonp)
      (add-hook 'after-make-frame-functions
		(lambda (frame)
		  (with-selected-frame frame
		    (setq doom-modeline-icon t))))))

(use-package doom-themes
  :ensure t
  :init (load-theme 'doom-snazzy t))

(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

(use-package smartparens
  :defer 1
  :config
  (smartparens-global-mode +1))

(use-package aggressive-indent
  :defer 1
  :config
  (global-aggressive-indent-mode +1))

(use-package adaptive-wrap
  :defer t)

(use-package which-key
  :config
  (setq which-key-idle-delay 0.3)
  (which-key-mode)
  :defer 1)

(use-package no-littering)

;; no-littering doesn't set this by default so we must place
;; auto save files in the same path as it uses for sessions
(setq auto-save-file-name-transforms
      `((".*" ,(no-littering-expand-var-file-name "auto-save/") t)))

(let ((alist '((?! . "\\(?:!\\(?:==\\|[!=]\\)\\)")
               (?# . "\\(?:#\\(?:###?\\|_(\\|[!#(:=?[_{]\\)\\)")
               (?$ . "\\(?:\\$>\\)")
               (?& . "\\(?:&&&?\\)")
               (?* . "\\(?:\\*\\(?:\\*\\*\\|[/>]\\)\\)")
               (?+ . "\\(?:\\+\\(?:\\+\\+\\|[+>]\\)\\)")
               (?- . "\\(?:-\\(?:-[>-]\\|<<\\|>>\\|[<>|~-]\\)\\)")
               (?. . "\\(?:\\.\\(?:\\.[.<]\\|[.=?-]\\)\\)")
               (?/ . "\\(?:/\\(?:\\*\\*\\|//\\|==\\|[*/=>]\\)\\)")
               (?: . "\\(?::\\(?:::\\|\\?>\\|[:<-?]\\)\\)")
               (?\; . "\\(?:;;\\)")
               (?< . "\\(?:<\\(?:!--\\|\\$>\\|\\*>\\|\\+>\\|-[<>|]\\|/>\\|<[<=-]\\|=\\(?:=>\\|[<=>|]\\)\\||\\(?:||::=\\|[>|]\\)\\|~[>~]\\|[$*+/:<=>|~-]\\)\\)")
               (?= . "\\(?:=\\(?:!=\\|/=\\|:=\\|=[=>]\\|>>\\|[=>]\\)\\)")
               (?> . "\\(?:>\\(?:=>\\|>[=>-]\\|[]:=-]\\)\\)")
               (?? . "\\(?:\\?[.:=?]\\)")
               (?\[ . "\\(?:\\[\\(?:||]\\|[<|]\\)\\)")
               (?\ . "\\(?:\\\\/?\\)")
               (?\] . "\\(?:]#\\)")
               (?^ . "\\(?:\\^=\\)")
               (?_ . "\\(?:_\\(?:|?_\\)\\)")
               (?{ . "\\(?:{|\\)")
               (?| . "\\(?:|\\(?:->\\|=>\\||\\(?:|>\\|[=>-]\\)\\|[]=>|}-]\\)\\)")
               (?~ . "\\(?:~\\(?:~>\\|[=>@~-]\\)\\)"))))
  (dolist (char-regexp alist)
    (set-char-table-range composition-function-table (car char-regexp)
                          `([,(cdr char-regexp) 0 font-shape-gstring]))))

(use-package evil
  :init
  (setq evil-want-integration t
        evil-want-keybinding nil
        evil-want-C-i-jump nil
        evil-want-C-u-scroll t
        evil-respect-visual-line-mode t
        evil-want-C-u-delete t)
  :config
  (evil-mode +1)
  (setq evil-undo-system 'undo-tree))

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

(use-package general
  :init
  (general-evil-setup)
  :config
  (general-create-definer chris/leader-keys
    :keymaps '(normal visual emacs)
    :prefix "SPC")
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "b" '(:ignore t :which-key "buffer")
    "t" '(:ignore t :which-key "toggle")
    "f" '(:ignore t :which-key "file")
    "w" '(:ignore t :which-key "window")
    "s" '(:ignore t :which-key "search")
    "o" '(:ignore t :which-key "open")
    "h" '(:ignore t :which-key "help")
    "n" '(:ignore t :which-key "notes")
    "bs" '(consult-buffer :which-key "buffer search")
    "bd" '(kill-this-buffer :which-key "kill buffer")
    "bi" '(ibuffer :which-key "ibuffer")
    "tt" '(consult-theme :which-key "choose theme")
    "ff" '(find-file :which-key "find file")
    "fb" '((find-file ~/org/bibles/) :which-key "find bible book")
    "fr" '(consult-recent-file :which-key "recent file")
    "fs" '(save-buffer :which-key "save")
    "hf" '(helpful-callable :which-key "describe-function")
    "hv" '(helpful-variable :which-key "describe-variable")
    "hk" '(helpful-key :which-key "describe-key")
    "hb" '(general-describe-keybindings :which-key "describe-bindings")
    "hi" '(info :which-key "info manual")
    "ss" '(consult-line :which-key "consult search")
    "ww" '(other-window :which-key "other window")
    "wd" '(delete-window :which-key "other window")
    ";" '(execute-extended-command :which-key "execute command")
    ":" '(eval-expression :which-key "evaluate expression")
    )
  (general-def 'minibuffer-local-map
    "C-v" 'evil-paste-after)
  (general-def 'normal
    "gcc" 'comment-line))

(use-package evil-escape
  :after evil
  :init (evil-escape-mode +1)
  :config
  (setq evil-escape-key-sequence "fd"
	evil-escape-delay 0.3))

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

(use-package undo-tree
  :defer 1
  :config
  (global-undo-tree-mode +1)
  :general
  (general-def 'normal undo-tree-visualize-mode-map
   "j" 'undo-tree-visualize-redo
   "k" 'undo-tree-visualize-undo))

(use-package olivetti
  :after org
  :config
  (setq olivetti-body-width 0.6
  olivetti-minimum-body-width 100))

(use-package toc-org
  :after org)

(use-package selectrum
  :init
  (selectrum-mode +1)
  :config
  (setq selectrum-max-window-height 8)
  (add-hook 'selectrum-mode-hook 'selectrum-exhibit)

  ;; We need to fix selectrums minibuffer handling for Emacs 28
  (defun selectrum--set-window-height (window &optional height)
  "Set window height of WINDOW to HEIGHT pixel.
If HEIGHT is not given WINDOW will be updated to fit its content
vertically."
  (let* ((lines (length
                 (split-string
                  (overlay-get selectrum--candidates-overlay 'after-string)
                  "\n" t)))
         (dheight (or height
                      (* lines selectrum--line-height)))
         (wheight (window-pixel-height window))
         (window-resize-pixelwise t))
    (window-resize
     window (- dheight wheight) nil nil 'pixelwise)))
  :general
  ('selectrum-minibuffer-map
   "C-j" 'selectrum-next-candidate
   "C-k" 'selectrum-previous-candidate
   "C-S-j" 'selectrum-goto-end
   "C-S-k" 'selectrum-goto-beginning
   "TAB" 'selectrum-insert-current-candidate)
  :commands completing-read)

(use-package prescient
  :config
  (prescient-persist-mode +1)
  :after selectrum)

(use-package selectrum-prescient
  :init
  (selectrum-prescient-mode +1)
  :after selectrum)

(use-package consult
  :after selectrum
  :config
  (setq consult-narrow-key "<")
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "si" 'consult-imenu
    "so" 'consult-outline))

(use-package marginalia
  :bind (:map minibuffer-local-map
         ("C-M-a" . marginalia-cycle)
         ;; :map embark-general-map
         ;;     ("A" . marginalia-cycle)
         )

  ;; The :init configuration is always executed (Not lazy!)
  :init

  ;; Must be in the :init section of use-package such that the mode gets
  ;; enabled right away. Note that this forces loading the package.
  (marginalia-mode)

  ;; When using Selectrum, ensure that Selectrum is refreshed when cycling annotations.
  (advice-add #'marginalia-cycle :after
              (lambda () (when (bound-and-true-p selectrum-mode) (selectrum-exhibit))))

  ;; Prefer richer, more heavy, annotations over the lighter default variant.
  (setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
  :after selectrum)

(use-package company
  :defer 1
  :config (global-company-mode +1))

(use-package company-dict
  :defer t)

(use-package helpful
  :commands (helpful-callable helpful-variable helpful-command helpful-key))

(use-package format-all
  :config
  (format-all-mode +1)
  (setq format-all-formatters '("Emacs Lisp" emacs-lisp))
  :defer 1)

(use-package fennel-mode
  :mode ("\\.fnl\\'" . fennel-mode))

(use-package yaml-mode
  :mode ("\\.yml\\'" . yaml-mode))

(use-package docker
  :defer t)

(use-package docker-tramp
  :after docker)

(use-package fish-mode
  :mode ("\\.fish\\'" . fish-mode))

(use-package dired
  :ensure nil
  :straight nil
  :config
  (defun chris/dired-open-xdg ()
    "Open the file-at-point in the appropriate program"
    (interactive)
    (let ((file (ignore-errors (dired-get-file-for-visit))))
      (browse-url (file-truename file))))
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "od" '(dired-jump :which-key "open dired here"))
  (general-def 'normal dired-mode-map
    "q" 'kill-this-buffer
    "C-<return>" 'chris/dired-open-xdg))

(defun chris/dired-yank-filename ()
  "Get the full filename from file at point and put into kill-ring"
  (interactive)
  (let* ((file (dired-get-filename)))
    (clipboard-kill-ring-save nil nil file)))

(use-package all-the-icons-dired
  :hook (dired-mode . all-the-icons-dired-mode))

(use-package dired-single
  :after dired
  :general
  (general-def 'normal dired-mode-map
    "h" 'dired-single-up-directory
    "l" 'dired-single-buffer))

(use-package diredfl
  :after dired
  :config (diredfl-global-mode +1))

(require 'tramp)
(add-to-list 'tramp-default-proxies-alist
             '(nil "\\`root\\'" "/ssh:%h:"))
(add-to-list 'tramp-default-proxies-alist
             '((regexp-quote (system-name)) nil nil))

(defun chris/org-mode-setup ()
  (interactive)
  (org-indent-mode +1)
  (toc-org-mode +1)
  (olivetti-mode +1)
  (display-line-numbers-mode -1)
  (variable-pitch-mode +1))

(use-package org
  :config
  (setq org-startup-indented t
	org-edit-src-content-indentation 0
	org-agenda-sticky t
	org-fontify-quote-and-verse-blocks t)

  (add-hook 'org-mode-hook 'chris/org-mode-setup)

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

  (require 'org-tempo)
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("py" . "src python"))
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("q" . "quote"))

  (setq org-capture-templates
	'(("t" "Personal todo" entry
	   (file+headline "todo.org" "Inbox")
	   "* TODO %^{TODO name}\nSCHEDULED: %T\n%a\n%i%?" :prepend t)
	  ("n" "Personal notes" entry
	   (file+headline "notes.org" "Inbox")
	   "* %u %?\n%i\n%a" :prepend t)
	  ("j" "Journal" entry
	   (file+olp+datetree +org-capture-journal-file)
	   "* %U %?\n%i\n%a" :prepend t)
	  ("p" "TFC Plan" entry
	   (function chris/org-roam-capture-lesson-file)
	   (file ".templates/tfcplantemplate.org")
	   :prepend nil
	   :jump-to-captured t
	   :empty-lines 1)
	  ("P" "TFC Posts" entry
	   (file+headline "/home/chris/org/nvtfc_social_media.org" "Posts")
	   (file ".templates/posts.org")
	   :prepend t
	   :jump-to-captured t)
	  ("r" "Templates for projects")
	  ("rt" "Project-local todo" entry
	   (file+headline +org-capture-project-todo-file "Inbox")
	   "* TODO %?\n%i\n%a" :prepend t)
	  ("rn" "Project-local notes" entry
	   (file+headline +org-capture-project-notes-file "Inbox")
	   "* %U %?\n%i\n%a" :prepend t)
	  ("rc" "Project-local changelog" entry
	   (file+headline +org-capture-project-changelog-file "Unreleased")
	   "* %U %?\n%i\n%a" :prepend t)
	  ("o" "Centralized templates for projects")
	  ("ot" "Project todo" entry #'+org-capture-central-project-todo-file
	   "* TODO %?\n %i\n %a" :heading "Tasks" :prepend nil)
	  ("on" "Project notes" entry #'+org-capture-central-project-notes-file
	   "* %U %?\n %i\n %a" :heading "Notes" :prepend t)
	  ("oc" "Project changelog" entry #'+org-capture-central-project-changelog-file
	   "* %U %?\n %i\n %a" :heading "Changelog" :prepend t))
	org-capture-use-agenda-date t)

  ;;(setq org-superstar-headline-bullets-list '("◉" "◈" "▸" "✬" "◎" "◇" "❉" "✙" "❖"))
  (setq org-imenu-depth 4)
  (setq org-odt-styles-file "/home/chris/org/style.odt")

  (setq org-export-with-toc nil)
  (setq org-export-with-author nil)

  (setq org-todo-keywords
	'((sequence "TODO(t)" "PROJ(p)" "STRT(s)" "WAIT(w)" "HOLD(h)" "|" "DONE(d)" "CNCL(c)")
	  (sequence "[ ](T)" "[-](S)" "[?](W)" "|" "[X](D)")))


  (setq org-agenda-files
	'("/home/chris/org/inbox.org"
	  "/home/chris/org/notes.org"
	  "/home/chris/org/repetition.org"
	  "/home/chris/org/tasks.org"
	  "/home/chris/org/tfc_plans.org"
	  "/home/chris/org/ministry_team.org"
	  "/home/chris/org/todo.org"
	  "/home/chris/org/newsletter.org"))

  (setq org-id-method 'ts)

  (defun chris/org-columns-view ()
    "Turn on org-columns overlay and turn off olivetti-mode"
    (interactive)
    (goto-char (point-min))
    (org-content)
    (org-columns)
    (olivetti-mode -1))

  (defun chris/org-columns-quit ()
    "Remove the org-columns overlay and turn on olivetti-mode"
    (interactive)
    (org-columns-quit)
    (chris/org-mode-setup))

  (add-hook 'org-agenda-finalize-hook 'evil-normal-state)

  ;;Let's make sure org-mode faces are inheriting fixed pitch faces.
  (dolist (face '(org-block
		  org-block-begin-line
		  org-block-end-line
		  org-code
		  org-document-info-keyword
		  org-meta-line
		  org-table
		  org-verbatim))
    (set-face-attribute `,face nil :inherit 'fixed-pitch))
  
  (set-face-attribute 'org-block-end-line nil :inherit 'org-block-begin-line)
  (set-face-attribute 'org-quote nil :background "#242631" :inherit 'fixed-pitch)

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "o a" 'org-agenda
    "c" 'org-capture)
  ('normal org-agenda-mode-map
	   "q" 'org-agenda-quit
	   "r" 'org-agenda-redo)
  ('normal org-columns-map
	   "j" 'outline-next-heading
	   "h" 'outline-previous-heading
	   "q" 'chris/org-columns-quit)
  ('normal org-mode-map
	   "RET" 'chris/org-dwim-at-point
	   "gC" 'chris/org-columns-view))

(defun chris/org-roam-capture-lesson-file ()
  "Function to return the lesson file that is needed for TFC plan capture and move to correct position for plan insertion"
  (interactive)
  (unless org-roam-mode (org-roam-mode))
  (let* ((completions (org-roam--get-title-path-completions))
         (title-with-tags (org-roam-completion--completing-read "Lesson: " completions))
         (res (cdr (assoc title-with-tags completions)))
         (file-path (plist-get res :path)))
    (find-file file-path)
    (goto-char (point-min))
    (search-forward "PLAN")))

(defun chris/org-babel-tangle-config ()
  (when (string-equal (buffer-file-name)
                      (expand-file-name "~/.emacs.d/README.org"))
    (let ((org-confirm-babel-evaluate nil))
      (org-babel-tangle))))

(add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook #'chris/org-babel-tangle-config
                                              :append :local)))

(use-package evil-org
  :after org)

(use-package org-super-agenda
  :after org-agenda
  :init
  (setq org-super-agenda-groups '((:name "Today"
                                   :time-grid t
                                   :scheduled today)
                                  (:name "Due Today"
                                   :deadline today)
                                  (:name "Important"
                                   :priority "A")
                                  (:name "Overdue"
                                   :time-grid t
                                   :scheduled today)
                                  (:name "Due soon"
                                   :deadline future)))
  :config
  (org-super-agenda-mode)
  (setq org-super-agenda-header-map nil))

(use-package org-roam
  :after org
  :hook org-load
  :commands (org-roam org-roam-find-file)
  :config
  (setq org-roam-directory "~/org")
  (setq org-roam-buffer-width 0.25)
  (setq org-roam-file-exclude-regexp ".stversion.*\|.stfolder.*\|.*~.*\|.*sync.*")
  (setq org-roam-db-location "~/.dotemacs/org-roam.db")
  (setq org-roam-capture-templates
        '(("d" "default" plain (function org-roam--capture-get-point)
           "%?"
           :file-name "${slug}"
           :head "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\nj ")
          ("b" "bible" plain (function org-roam--capture-get-point)
           "%?"
           :file-name "${slug}"
           :head "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n- tags %^G\n\n* ")
          ("l" "TFC Lesson" plain (function org-roam--capture-get-point)
           (file ".templates/lessontemplate.org")
           :file-name "${slug}"
           :head "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n")))

  (setq org-roam-dailies-capture-templates
        '(("d" "daily" plain #'org-roam-capture--get-point ""
           :immediate-finish t
           :file-name "%<%Y-%m-%d>"
           :head "#+TITLE: %<%Y-%m-%d>\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\n* HFL\n* Tasks\n* Family\n** How Do I Love Abbie?")
          ("b" "biblical daily" plain #'org-roam-capture--get-point ""
           :immediate-finish t
           :file-name "%<%Y-%m-%d>-bib"
           :head "#+TITLE: %<%Y-%m-%d> - Biblical\n#+AUTHOR: Chris Cochrun")))
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
   "nf" '(org-roam-find-file :which-key "org roam ff")
   "nr" 'org-roam))

(use-package org-roam-server
  :config
  (setq org-roam-server-host "127.0.0.1"
        org-roam-server-port 8080
        org-roam-server-export-inline-images t
        org-roam-server-authenticate nil
        org-roam-server-serve-files t
        org-roam-server-network-label-truncate t
        org-roam-server-network-label-truncate-length 60
        org-roam-server-network-label-wrap-length 20)
  :after org-roam)

(add-hook 'org-roam-mode-hook org-roam-server-mode t)

(use-package org-superstar
  :after org
  :config
  (org-superstar-mode +1)
  (setq org-superstar-headline-bullets-list '("\u25c9" "\u25c8" "盛" "\u25ce" "\u272c" "\u25c7" "\u2749" "\u2719" "\u2756"))
  (setq org-superstar-item-bullet-alist '((?- . ?\u25b8)
					  (?+ . ?\u2749)
					  (?* . ?\u25c9)))
  (set-face-attribute 'org-superstar-item nil :inherit 'org-level-3)
  (add-hook 'org-mode-hook 'org-superstar-mode))

(defun chris/org-dwim-at-point (&optional arg)
  "Do-what-I-mean at point.

If on a:
- checkbox list item or todo heading: toggle it.
- clock: update its time.
- headline: cycle ARCHIVE subtrees, toggle latex fragments and inline images in
  subtree; update statistics cookies/checkboxes and ToCs.
- footnote reference: jump to the footnote's definition
- footnote definition: jump to the first reference of this footnote
- table-row or a TBLFM: recalculate the table's formulas
- table-cell: clear it and go into insert mode. If this is a formula cell,
  recaluclate it instead.
- babel-call: execute the source block
- statistics-cookie: update it.
- latex fragment: toggle it.
- link: follow it
- otherwise, refresh all inline images in current tree."
  (interactive "P")
  (let* ((context (org-element-context))
         (type (org-element-type context)))
    ;; skip over unimportant contexts
    (while (and context (memq type '(verbatim code bold italic underline strike-through subscript superscript)))
      (setq context (org-element-property :parent context)
            type (org-element-type context)))
    (pcase type
      (`headline
       (cond ((memq (bound-and-true-p org-goto-map)
                    (current-active-maps))
              (org-goto-ret))
             ((and (fboundp 'toc-org-insert-toc)
                   (member "TOC" (org-get-tags)))
              (toc-org-insert-toc)
              (message "Updating table of contents"))
             ((string= "ARCHIVE" (car-safe (org-get-tags)))
              (org-force-cycle-archived))
             ((or (org-element-property :todo-type context)
                  (org-element-property :scheduled context))
              (org-todo
               (if (eq (org-element-property :todo-type context) 'done)
                   (or (car (chris/org-get-todo-keywords-for (org-element-property :todo-keyword context)))
                       'todo)
                 'done))))
       ;; Update any metadata or inline previews in this subtree
       (org-update-checkbox-count)
       (org-update-parent-todo-statistics)
       (when (and (fboundp 'toc-org-insert-toc)
                  (member "TOC" (org-get-tags)))
         (toc-org-insert-toc)
         (message "Updating table of contents"))
       (let* ((beg (if (org-before-first-heading-p)
                       (line-beginning-position)
                     (save-excursion (org-back-to-heading) (point))))
              (end (if (org-before-first-heading-p)
                       (line-end-position)
                     (save-excursion (org-end-of-subtree) (point))))
              (overlays (ignore-errors (overlays-in beg end)))
              (latex-overlays
               (cl-find-if (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay))
                           overlays))
              (image-overlays
               (cl-find-if (lambda (o) (overlay-get o 'org-image-overlay))
                           overlays)))
         (chris/org--toggle-inline-images-in-subtree beg end)
         (if (or image-overlays latex-overlays)
             (org-clear-latex-preview beg end)
           (org--latex-preview-region beg end))))

      (`clock (org-clock-update-time-maybe))

      (`footnote-reference
       (org-footnote-goto-definition (org-element-property :label context)))

      (`footnote-definition
       (org-footnote-goto-previous-reference (org-element-property :label context)))

      ((or `planning `timestamp)
       (org-follow-timestamp-link))

      ((or `table `table-row)
       (if (org-at-TBLFM-p)
           (org-table-calc-current-TBLFM)
         (ignore-errors
           (save-excursion
             (goto-char (org-element-property :contents-begin context))
             (org-call-with-arg 'org-table-recalculate (or arg t))))))

      (`table-cell
       (org-table-blank-field)
       (org-table-recalculate arg)
       (when (and (string-empty-p (string-trim (org-table-get-field)))
                  (bound-and-true-p evil-local-mode))
         (evil-change-state 'insert)))

      (`babel-call
       (org-babel-lob-execute-maybe))

      (`statistics-cookie
       (save-excursion (org-update-statistics-cookies arg)))

      ((or `src-block `inline-src-block)
       (org-babel-execute-src-block))

      ((or `latex-fragment `latex-environment)
       (org-latex-preview))

      (`link
       (let* ((lineage (org-element-lineage context '(link) t))
              (path (org-element-property :path lineage)))
         (if (or (equal (org-element-property :type lineage) "img")
                 (and path (image-type-from-file-name path)))
             (chris/org--toggle-inline-images-in-subtree
              (org-element-property :begin lineage)
              (org-element-property :end lineage))
           (org-open-at-point arg))))

      (`paragraph
       (let ((match (and (org-at-item-checkbox-p) (match-string 1))))
         (org-toggle-checkbox)))

      (_
       (if (or (org-in-regexp org-ts-regexp-both nil t)
               (org-in-regexp org-tsr-regexp-both nil  t)
               (org-in-regexp org-link-any-re nil t))
           (call-interactively #'org-open-at-point)
         (chris/org--toggle-inline-images-in-subtree
          (org-element-property :begin context)
          (org-element-property :end context)))))))


(defun chris/org-get-todo-keywords-for (&optional keyword)
  "Returns the list of todo keywords that KEYWORD belongs to."
  (when keyword
    (cl-loop for (type . keyword-spec)
             in (cl-remove-if-not #'listp org-todo-keywords)
             for keywords =
             (mapcar (lambda (x) (if (string-match "^\\([^(]+\\)(" x)
                                     (match-string 1 x)
                                   x))
                     keyword-spec)
             if (eq type 'sequence)
             if (member keyword keywords)
             return keywords)))

(defun chris/org--toggle-inline-images-in-subtree (&optional beg end refresh)
  "Refresh inline image previews in the current heading/tree."
  (let ((beg (or beg
                 (if (org-before-first-heading-p)
                     (line-beginning-position)
                   (save-excursion (org-back-to-heading) (point)))))
        (end (or end
                 (if (org-before-first-heading-p)
                     (line-end-position)
                   (save-excursion (org-end-of-subtree) (point)))))
        (overlays (cl-remove-if-not (lambda (ov) (overlay-get ov 'org-image-overlay))
                                    (ignore-errors (overlays-in beg end)))))
    (dolist (ov overlays nil)
      (delete-overlay ov)
      (setq org-inline-image-overlays (delete ov org-inline-image-overlays)))
    (when (or refresh (not overlays))
      (org-display-inline-images t t beg end)
      t)))

(use-package mu4e
  :ensure nil
  :config
  (setq mail-user-agent 'mu4e-user-agent)
  (setq mu4e-maildir "~/Maildir"
	user-full-name "Chris Cochrun"
	mu4e-change-filenames-when-moving t
	mu4e-get-mail-command "mbsync -a"
	mu4e-update-interval (* 15 60)
	mu4e-attachment-dir "/home/chris/Documents/PersonalImportant/attachments"
	mu4e-completing-read-function #'completing-read)

  (setq mu4e-contexts
	(list
	 (make-mu4e-context
	  :name "office"
	  :match-func
	  (lambda (msg)
	    (when msg
	      (string-prefix-p "/office" (mu4e-message-field msg :maildir))))
	  :vars '((user-mail-address      . "chris@tfcconnection.org")
		  (mu4e-sent-folder       . "/office/Sent Items/")
		  (mu4e-drafts-folder     . "/office/Drafts")
		  (mu4e-trash-folder      . "/office/Deleted Items")
		  (mu4e-refile-folder     . "/office/Archive")
		  (smtpmail-smtp-user     . "chris@tfcconnection.org")
		  (mu4e-compose-signature . "---\nChris Cochrun")))
	 (make-mu4e-context
	  :name "outlook"
	  :match-func
	  (lambda (msg)
	    (when msg
	      (string-prefix-p "/outlook" (mu4e-message-field msg :maildir))))
	  :vars '((user-mail-address      . "chris.cochrun@outlook.com")
		  (mu4e-sent-folder       . "/outlook/Sent/")
		  (mu4e-drafts-folder     . "/outlook/Drafts")
		  (mu4e-trash-folder      . "/outlook/Deleted")
		  (mu4e-refile-folder     . "/outlook/Archive")
		  (smtpmail-smtp-user     . "chris.cochrun@outlook.com")
		  (mu4e-compose-signature . "---\nChris Cochrun")))
	 (make-mu4e-context
	  :name "gmail"
	  :match-func
	  (lambda (msg)
	    (when msg
	      (string-prefix-p "/gmail" (mu4e-message-field msg :maildir))))
	  :vars '((user-mail-address      . "ccochrun21@gmail.com")
		  (mu4e-sent-folder       . "/gmail/[Gmail].Sent Mail/")
		  (smtpmail-smtp-user     . "ccochrun21@gmail.com")
		  (mu4e-compose-signature . "---\nChris Cochrun")))))

  ;; Add the ability to send email for o365
  (setq message-send-mail-function 'smtpmail-send-it
	starttls-use-gnutls t
	smtpmail-starttls-credentials '(("smtp.office365.com" 587 nil nil))
	smtpmail-auth-credentials
	'(("smtp.office365.com" 587 "chris@tfcconnection.org" nil))
	smtpmail-default-smtp-server "smtp.office365.com"
	smtpmail-smtp-server "smtp.office365.com"
	smtpmail-smtp-service 587)

  ;; shortcuts in the jumplist by pressing "J" in the mu4e buffer
  (setq mu4e-maildir-shortcuts
	'((:maildir "/office/Archive"                :key ?a)
	  (:maildir "/office/INBOX"                  :key ?i)
	  (:maildir "/outlook/INBOX"                 :key ?l)
	  (:maildir "/office/Junk Email"             :key ?j)
	  (:maildir "/office/INBOX/Website Forms"    :key ?f)
	  (:maildir "/gmail/INBOX"                   :key ?g)
	  (:maildir "/office/Sent Items"             :key ?s)))

  ;; (add-to-list mu4e-headers-actions ("org capture message" . mu4e-org-store-and-capture))

  (setq mu4e-bookmarks
	'((:name "Unread messages"
		 :query "flag:unread AND NOT flag:trashed AND NOT maildir:\"/outlook/Junk\" AND NOT maildir:\"/office/Junk Email\" AND NOT maildir:\"/outlook/Deleted\" AND NOT maildir:\"/office/Deleted Items\""
		 :key 117)
	  (:name "Today's messages"
		 :query "date:today..now"
		 :key 116)
	  (:name "Last 7 days"
		 :query "date:7d..now"
		 :hide-unread t
		 :key 119)
	  (:name "Messages with images"
		 :query "mime:image/*"
		 :key 112)))

  (setq mu4e-mu-binary "/usr/bin/mu")
  (setq mu4e-use-fancy-chars t
        mu4e-headers-draft-mark '("D" . "")
        mu4e-headers-flagged-mark '("F" . "")
        mu4e-headers-new-mark '("N" . " ")
        mu4e-headers-passed-mark '("P" . "")
        mu4e-headers-replied-mark '("R" . "")
        mu4e-headers-seen-mark '("S" . " ")
        mu4e-headers-trashed-mark '("T" . " ")
        mu4e-headers-attach-mark '("a" . " ")
        mu4e-headers-encrypted-mark '("x" . "")
        mu4e-headers-signed-mark '("s" . " ")
        mu4e-headers-unread-mark '("u" . " "))

  (setq mu4e-headers-fields
	'((:human-date . 12)
	  (:flags . 6)
	  (:from . 22)
	  (:subject)))

  (setq mu4e-view-actions
	'(("capture message" . mu4e-action-capture-message)
	  ("view in browser" . mu4e-action-view-in-browser)
	  ("show this thread" . mu4e-action-show-thread)))

  (defun chris/setup-mu4e-headers ()
    (toggle-truncate-lines +1)
    (display-line-numbers-mode -1))

  (remove-hook 'mu4e-main-mode-hook '(display-line-numbers-mode -1))
  (add-hook 'mu4e-headers-mode-hook #'chris/setup-mu4e-headers)
  
  (mu4e t)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "om" 'mu4e))

(use-package org-mime
  :ensure t)

(use-package org-msg
  :hook (mu4e-compose-mode . org-msg-edit-mode)
  :config
  (org-msg-mode)
  (setq org-msg-startup "inlineimages"
        org-msg-greeting-name-limit 3
        org-msg-default-alternatives '(html text)))

(use-package calfw
  :commands chris/calfw-calendar-open
  :config
  (defun chris/calfw-calendar-open ()
    (interactive)
    (cfw:open-calendar-buffer
     :contents-sources
     (list
      (cfw:org-create-source
       "Cyan")  ; org-agenda source
      (cfw:ical-create-source
       "NV" "https://www.nvhuskies.org/vnews/display.v?ical" "Green")  ; School Calendar
      (cfw:ical-create-source
       "Outlook" "https://outlook.office365.com/owa/calendar/62a0d491bec4430e825822afd2fd1c01@tfcconnection.org/9acc5bc27ca24ce7a900c57284959f9d8242340735661296952/S-1-8-2197686000-2519837503-3687200543-3873966527/reachcalendar.ics" "Yellow")  ; Outlook Calendar
      )))
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "oc" 'chris/calfw-calendar-open)
  (general-def cfw:calendar-mode-map
    "q" 'kill-this-buffer
    "RET" 'cfw:show-details-command)
  (general-def 'normal cfw:details-mode-map
    "q" 'cfw:details-kill-buffer-command))

(use-package calfw-org
  :after calfw)

(use-package calfw-ical
  :after calfw)

(use-package magit
  :commands (magit-status magit-get-current-branch)
  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "g g" 'magit-status)
  :custom
  (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

(use-package eshell
  :ensure nil
  :straight nil
  :config
  (require 'em-tramp)

  (with-eval-after-load 'esh-module   ;; REVIEW: It used to work, but now the early `provide' seems to backfire.
    (unless (boundp 'eshell-modules-list)
      (load "esh-module"))   ;; Don't print the banner.
    (push 'eshell-tramp eshell-modules-list))

  (setq password-cache t
        password-cache-expiry 3600)

  (setq eshell-history-size 1024)

  ;;; Extra execution information
  (defvar chris/eshell-status-p t
    "If non-nil, display status before prompt.")
  (defvar chris/eshell-status--last-command-time nil)
  (make-variable-buffer-local 'chris/eshell-status--last-command-time)
  (defvar chris/eshell-status-min-duration-before-display 0
    "If a command takes more time than this, display its duration.")

  (defun chris/eshell-status-display ()
    (if chris/eshell-status--last-command-time
        (let ((duration (time-subtract (current-time) chris/eshell-status--last-command-time)))
          (setq chris/eshell-status--last-command-time nil)
          (when (> (time-to-seconds duration) chris/eshell-status-min-duration-before-display)
            (format "  %.3fs %s"
                    (time-to-seconds duration)
                    (format-time-string "| %F %T" (current-time)))))
      (format "  0.000s")))

  (defun chris/eshell-status-record ()
    (setq chris/eshell-status--last-command-time (current-time)))

  (add-hook 'eshell-pre-command-hook 'chris/eshell-status-record)

  (setq eshell-prompt-function
        (lambda nil
          (let ((path (abbreviate-file-name (eshell/pwd))))
            (concat
             (if (or (string= system-name "archdesktop") (string= system-name "syl"))
                 nil
               (format
                (propertize "\n(%s@%s)" 'face '(:foreground "#606580"))
                (propertize (user-login-name) 'face '(:inherit compilation-warning))
                (propertize (system-name) 'face '(:inherit compilation-warning))))
             (if (and (require 'magit nil t) (or (magit-get-current-branch) (magit-get-current-tag)))
                 (let* ((root (abbreviate-file-name (magit-rev-parse "--show-toplevel")))
                        (after-root (substring-no-properties path (min (length path) (1+ (length root))))))
                   (format
                    (propertize "\n[ %s | %s@%s ]" 'face font-lock-comment-face)
                    (propertize root 'face `(:inherit org-warning))
                    (propertize after-root 'face `(:inherit org-level-1))
                    (propertize (or (magit-get-current-branch) (magit-get-current-tag)) 'face `(:inherit org-macro))))
               (format
                (propertize "\n[%s]" 'face font-lock-comment-face)
                (propertize path 'face `(:inherit org-level-1))))
             (when chris/eshell-status-p
               (propertize (or (chris/eshell-status-display) "") 'face font-lock-comment-face))
             (propertize "\n" 'face '(:inherit org-todo :weight ultra-bold))
             " "))))

  ;;; If the prompt spans over multiple lines, the regexp should match
  ;;; last line only.
  (setq-default eshell-prompt-regexp "^ ")
  (setq eshell-destroy-buffer-when-process-dies t)

  (defun chris/pop-eshell ()
    "Make an eshell frame on the bottom"
    (interactive)
    (unless pop-eshell
      (setq pop-eshell (eshell 100))
      (with-current-buffer pop-eshell
	(eshell/clear-scrollback)
	(rename-buffer "*eshell-pop*")
	(display-buffer-in-side-window pop-eshell '((side . bottom))))))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "oe" 'eshell)
  (general-def '(normal insert) eshell-mode-map
    "C-d" 'kill-this-buffer))

(use-package sly
  :mode ("\\.lisp\\'" . sly-mode))

(use-package pdf-tools
  :straight (:host github
		 :repo "flatwhatson/pdf-tools"
		 :branch "fix-macros")
  :defer 1
  :config
  (pdf-tools-install))

(use-package nov
  :mode ("\\.epub\\'" . nov-mode)
  :config
  (add-hook 'nov-mode-hook 'olivetti-mode))

(setq display-buffer-alist
      '(("\\*e?shell\\*"
	 (display-buffer-in-side-window)
	 (window-width . 0.4)
	 (side . bottom))
	("*Bongo-Elfeed Queue*"
	 (display-buffer-in-side-window)
	 (window-height . 0.25)
	 (side . bottom))))

(use-package elfeed
  :commands (elfeed)
  :config
  (defvar chris/elfeed-bongo-playlist "*Bongo-Elfeed Queue*"
    "Name of the Elfeed+Bongo multimedia playlist.")

  (defun chris/elfeed-bongo-insert-item ()
    "Insert `elfeed' multimedia links in `bongo' playlist buffer.

The playlist buffer has a unique name so that it will never
interfere with the default `bongo-playlist-buffer'."
    (interactive)
    (let* ((entry (elfeed-search-selected :ignore-region))
           (link (elfeed-entry-link entry))
           (enclosure (elt (car (elfeed-entry-enclosures entry)) 0))
           (url (if (string-prefix-p "https://thumbnails" enclosure)
                    link
		  enclosure))
	   (title (elfeed-entry-title entry))
	   (bongo-pl chris/elfeed-bongo-playlist)
	   (buffer (get-buffer-create bongo-pl)))
      (message "link is %s" link)
      (message "enclosure is %s" enclosure)
      (message "url is %s" url)
      (message "title is %s" title)
      (elfeed-search-untag-all-unread)
      (unless (bongo-playlist-buffer)
	(bongo-playlist-buffer))
      (display-buffer buffer)
      (with-current-buffer buffer
	(when (not (bongo-playlist-buffer-p))
          (bongo-playlist-mode)
          (setq-local bongo-library-buffer (get-buffer "*elfeed-search*"))
          (setq-local bongo-enabled-backends '(mpv))
          (bongo-progressive-playback-mode))
	(goto-char (point-max))
	(bongo-insert-uri url (format "%s ==> %s" title url))
	(let ((inhibit-read-only t))
          (delete-duplicate-lines (point-min) (point-max)))
	(bongo-recenter))
      (message "Enqueued %s “%s” in %s"
	       (if enclosure "podcast" "video")
	       (propertize title 'face 'italic)
	       (propertize bongo-pl 'face 'bold))))
  
  (defun chris/elfeed-bongo-switch-to-playlist ()
    (interactive)
    (let* ((bongo-pl chris/elfeed-bongo-playlist)
           (buffer (get-buffer bongo-pl)))
      (if buffer
          (switch-to-buffer buffer)
	(message "No `bongo' playlist is associated with `elfeed'."))))


  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "of" 'elfeed)

  (general-def 'normal elfeed-search-mode-map
    "v" 'chris/elfeed-bongo-insert-item
    "h" 'chris/elfeed-bongo-switch-to-playlist))

(use-package elfeed-org
  :after elfeed
  :config
  (setq rmh-elfeed-org-files (list "~/org/elfeed.org"))
  (elfeed-org)
  (elfeed-update))

(use-package bongo
  :commands (bongo bongo-playlist-buffer)
  :config
  (define-bongo-backend mpv
    :program-name 'mpv
    :constructor 'bongo-start-mpv-player
    :extra-program-arguments '("--input-ipc-server=/tmp/mpvsocket")
    :matcher '((local-file "file:" "http:" "ftp:" "lbry:")
               "ogg" "flac" "mp3" "mka" "wav" "wma"
               "mpg" "mpeg" "vob" "avi" "ogm" "opus" "mp4"
	       "mkv" "mov" "asf" "wmv" "rm" "rmvb" "ts")
    :matcher '(("mms:" "mmst:" "rtp:" "rtsp:" "udp:" "unsv:"
                "dvd:" "vcd:" "tv:" "dvb:" "mf:" "cdda:" "cddb:"
                "cue:" "sdp:" "mpst:" "tivo:") . t)
    :matcher '(("http:" "https:" "lbry:") . t))

  (setq bongo-enabled-backends '(mpv)
	bongo-track-mark-icon-file-name "track-mark-icon.png")

  (defun chris/bongo-mark-line-forward ()
    (interactive)
    (bongo-mark-line)
    (goto-char (bongo-point-after-object))
    (next-line))

  (defun chris/bongo-mpv-pause/resume ()
    (interactive)
    (bongo-mpv-player-pause/resume bongo-player))

  :general
  (chris/leader-keys
    :states 'normal
    :keymaps 'override
    "ob" 'bongo)
  (general-def 'normal bongo-playlist-mode-map
    "RET" 'bongo-play
    "d" 'bongo-kill-line
    "u" 'bongo-unmark-region
    "p" 'bongo-pause/resume
    "H" 'bongo-switch-buffers
    "m" 'chris/bongo-mark-line-forward))

(setq gc-cons-threshold 2000000)
(setq garbage-collection-messages nil)