;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*-
;; Author: Case Duckworth <acdw@acdw.net>
;; Bankruptcy: 12

;;; Initialization -- see also ~/.emacs.d/early-init.el

(setopt custom-file (locate-user-emacs-file "custom.el"))
(load custom-file :no-error)
(add-hook 'Custom-mode-hook
          (lambda () (run-with-idle-timer 0.25 nil #'custom-show-all-widgets)))

(defvar user-private-file (locate-user-emacs-file "private.el")
  "Private customizations")
;; make sure it's really private!
(and (= (file-attribute-user-id (file-attributes user-private-file))
        (user-uid)) ; is it owned by this me?
     (set-file-modes user-private-file #o600))
(load user-private-file :no-error)

(load-theme 'brianna :no-confirm) ; see ~/.emacs.d/brianna-theme.el
(add-hook 'after-init-hook #'setup-faces)

(define-advice startup-echo-area-message (:override ())
  (if (get-buffer "*Warnings*")
      ";_;"
    "^_^"))

;;; Basic settings

;; Auth
(package-ensure '(keepassxc-shim
                  :url "https://codeberg.org/acdw/keepassxc-shim.el"))
(keepassxc-shim-activate)
(setopt auth-sources '("secrets:default"))
(add-hook 'auth-info-hook #'truncate-lines-local-mode)

;; Environment
(setenv "PAGER" "cat")  ; emacs is a pager
(setenv "TERM" "dumb")  ; no fancy graphics!
(setenv "NO_COLOR" "1") ; no color!

;; Startup
(setopt inhibit-startup-screen t)
(setopt initial-buffer-choice #'eshell)
(setopt initial-scratch-message nil)

;; Dialogs
(setopt use-dialog-box nil)
(setopt use-file-dialog nil)
(setopt read-answer-short t)
(setopt use-short-answers t)
(setopt echo-keystrokes 0.01)

;; Cursor
(blink-cursor-mode -1)

;; Whitespace
(add-hook 'before-save-hook #'delete-trailing-whitespace-except-current-line)

(setopt whitespace-style '(face trailing tabs tab-mark))
(setopt whitespace-global-modes '(not rcirc-mode jabber-chat-mode))
(global-whitespace-mode)
(hide-minor-mode 'global-whitespace-mode)
(with-eval-after-load 'whitespace
  (setf/alist whitespace-display-mappings 'tab-mark
              '(9 [?· 9] [?» 9] [?\\ 9])))

;; Automatically indent buffer when saving
(define-minor-mode indent-on-save-mode
  "Automatically re-indent the buffer on save."
  :lighter " >"
  (if indent-on-save-mode
      (add-hook 'before-save-hook #'fixup-whitespace nil t)
    (remove-hook 'before-save-hook #'fixup-whitespace t)))

(add-hook 'prog-mode-hook #'indent-on-save-mode)

;; Comments
(setopt comment-column 0)
(setopt comment-indent-offset 1)

;;; UI stuff


;; Fixed-pitch
(define-minor-mode fixed-pitch-mode
  "Use a monospace typeface."
  :lighter " f"
  (setq cursor-type (if fixed-pitch-mode 'box 'bar))
  (buffer-face-set (and fixed-pitch-mode 'fixed-pitch)))
(define-globalized-minor-mode auto-fixed-pitch-mode
  fixed-pitch-mode fixed-pitch-mode
  :predicate '(special-mode
               prog-mode
               comint-mode))

(setopt cursor-type 'bar)
(hide-minor-mode 'buffer-face-mode)
(add-hook 'fixed-pitch-mode-hook #'display-fill-column-indicator-mode)
(auto-fixed-pitch-mode)

(package-ensure 'valign)                ; needed for variable-pitch org-mode
(add-hook 'org-mode-hook #'valign-mode)
(setopt valign-fancy-bar t)

;;; Mode line

(setopt mode-line-position-line-format '("%l"))
(setopt mode-line-position-column-line-format '("%l:%c"))

(package-ensure 'minions)

(defvar mode-line-major-mode-keymap*
  (let ((map (make-sparse-keymap)))
    (bindings--define-key map [mode-line down-mouse-1]
      `(menu-item "Menu Bar" ignore
                  :filter ,(lambda (_) (mouse-menu-major-mode-map))))
    (define-key map [mode-line mouse-2] #'describe-mode)
    (define-key map [mode-line mouse-3] #'minions-minor-modes-menu)
    map)
  "Keymap to display on major mode.")

(defun make-mode-line-mode-disabler (lighter help mode)
  (propertize lighter
              'help-echo (concat help "\n1:cancel")
              'face 'italic
              'mouse-face 'mode-line-highlight
              'local-map
              (make-mode-line-mouse-map
               'mouse-1 (lambda (ev) (interactive "e")
                          (with-selected-window
                              (posn-window (event-start ev))
                            (funcall mode -1)
                            (force-mode-line-update))))))

(setopt mode-line-format
        `("%e"
          mode-line-front-space
          (:propertize ("" mode-line-modified mode-line-remote)
                       display (min-width (5.0)))
          " %[" mode-line-buffer-identification " %]"
          (vc-mode (" [" vc-mode "]"))
          " ( "
          (:propertize ("" mode-name)
                       help-echo "Major mode\n1:menu\n2:help\n3:minor"
                       mouse-face mode-line-highlight
                       local-map ,mode-line-major-mode-keymap*)
          (auto-fill-function
           ,(make-mode-line-mode-disabler "-f" "Auto-filling"
                                          #'auto-fill-mode))
          (visual-line-mode
           ,(make-mode-line-mode-disabler "-v" "Visual lines"
                                          #'visual-line-mode))
          (truncate-lines-local-mode
           ,(make-mode-line-mode-disabler "-t" "Truncating lines"
                                          #'truncate-lines-local-mode))
          " "
          (defining-kbd-macro (:propertize "🔴" help-echo "Defining kbd macro"))
          (isearch-mode (:propertize "🔍" help-echo "Searching"))
          (overwrite-mode
           ,(make-mode-line-mode-disabler "✒️" "Overwriting"
                                          #'overwrite-mode))
          (debug-on-error
           ,(make-mode-line-mode-disabler "‼️" "Debug on error"
                                          (lambda (_)
                                            (setq debug-on-error nil))))
          (debug-on-quit
           ,(make-mode-line-mode-disabler "🚫" "Debug on quit"
                                          (lambda (_)
                                            (setq debug-on-quit nil))))
          ") "
          (mode-line-process (" " mode-line-process " "))
          "  " (:propertize (line-number-mode
                             (column-number-mode
                              (:propertize "%l/%c" help-echo "Line/column")
                              (:propertize "%l" help-echo "Line"))
                             (column-number-mode
                              (:propertize "/%c" help-echo "Column")
                              ""))
                            display (min-width (5.0)))
          (:propertize (" [" (-3 "%o") "]")
                       help-echo "Position in buffer"
                       display (min-width (6.0)))
          ,(propertize "%n"
                       'help-echo "Narrowed\n1:widen"
                       'mouse-face 'mode-line-highlight
                       'local-map (make-mode-line-mouse-map
                                   'mouse-1 #'mode-line-widen))
          (so-long-mode-line-info
           (" -- " so-long-mode-line-info))))

;;; Completions

(setopt tab-always-indent 'complete)
(setopt completion-styles '(basic partial-completion substring flex))

(setopt completion-ignore-case t)
(setopt read-buffer-completion-ignore-case t)
(setopt read-file-name-completion-ignore-case t)
(setopt completion-flex-nospace t)

(setopt completion-show-help nil)
(setopt completions-detailed t)
(setopt completions-group t)
(setopt completion-auto-help 'visible)
(setopt completion-auto-select 'second-tab)
(setopt completions-header-format nil)
(setopt completions-format 'one-column)
(setopt completions-max-height 10)

(setf/alist display-buffer-alist "\\`\\*Completions\\*\\'"
            '(nil (window-parameters (mode-line-format . " --- %b"))))

(keymap-set minibuffer-local-map "C-p" #'minibuffer-previous-completion)
(keymap-set minibuffer-local-map "C-n" #'minibuffer-next-completion)
(keymap-set minibuffer-local-map "M-DEL" #'minibuffer-delete-directory)
(keymap-set completion-list-mode-map "C-g" #'quit-window) ; is this a good idea?


;;; Minibuffer

(setopt enable-recursive-minibuffers t)
(setopt minibuffer-default-prompt-format " [%s]")
(minibuffer-depth-indicate-mode)
(minibuffer-electric-default-mode)

(setopt minibuffer-prompt-properties '( read-only t
                                        cursor-intangible t
                                        face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

(setopt file-name-shadow-properties '(invisible t intangible t))
(file-name-shadow-mode)

(setopt history-length t)
(setopt history-delete-duplicates t)
(setopt savehist-save-minibuffer-history t)
(setopt savehist-autosave-interval 5)
(setopt savehist-additional-variables
        '(kill-ring
          command-history
          set-variable-value-history
          custom-variable-history
          query-replace-history
          read-expression-history
          minibuffer-history
          read-char-history
          face-name-history
          bookmark-history
          file-name-history))
(savehist-mode)

(define-minor-mode truncate-lines-local-mode
  "Toggle `truncate-lines' in the current buffer."
  :lighter ""
  (setq-local truncate-lines truncate-lines-local-mode))

(add-hook 'completion-list-mode-hook #'truncate-lines-local-mode)
(add-hook 'minibuffer-setup-hook #'truncate-lines-local-mode)

(package-ensure 'visual-fill-column)
(setopt visual-fill-column-enable-sensible-window-split t)
(add-hook 'visual-line-mode-hook #'visual-fill-column-mode)
(advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)

(package-ensure 'adaptive-wrap)
(add-hook 'visual-line-mode #'adaptive-wrap-prefix-mode)

;;; Completing-read and friends

;; Consult
(package-ensure 'consult t)
(keymap-global-set "C-x b" #'consult-buffer)
(keymap-global-set "C-x 4 b" #'consult-buffer-other-window)
(keymap-global-set "C-x 5 b" #'consult-buffer-other-frame)
(keymap-global-set "C-x r b" #'consult-bookmark)
(keymap-global-set "M-y" #'consult-yank-pop)
(keymap-global-set "M-g g" #'consult-goto-line)
(keymap-global-set "M-g M-g" #'consult-goto-line)
(keymap-global-set "M-g o" #'consult-outline)
(keymap-global-set "M-g m" #'consult-mark)
(keymap-global-set "M-g i" #'consult-imenu)
(keymap-global-set "M-s d" #'consult-find)
(keymap-global-set "M-s D" #'consult-locate)
(keymap-global-set "M-s l" #'consult-line)
(keymap-global-set "M-s k" #'consult-keep-lines)
(keymap-global-set "M-s u" #'consult-focus-lines)
(keymap-global-set "M-s e" #'consult-isearch-history)
(keymap-set isearch-mode-map "M-e" #'consult-isearch-history)
(keymap-set isearch-mode-map "M-s e" #'consult-isearch-history)
(keymap-set isearch-mode-map "M-s l" #'consult-line)
(setopt xref-show-xrefs-function #'consult-xref)
(setopt xref-show-definitions-function #'xref-show-definitions-completing-read)
(setopt consult-preview-key "M-.")

(define-advice completing-read-multiple (:filter-args (args) indicator)
  (cons (format "[CRM%s] %s"
                (replace-regexp-in-string
                 "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
                 crm-separator)
                (car args))
        (cdr args)))

;; Marginalia
(package-ensure 'marginalia)
(marginalia-mode)

;; Embark
(package-ensure 'embark)
(package-ensure 'embark-consult)
(keymap-global-set "M-." #'embark-dwim)
(keymap-global-set "C-." #'embark-act)
(keymap-global-set "C-h B" #'embark-bindings)
(add-to-list 'display-buffer-alist
             '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil
               (window-parameters (mode-line-format . none))))
(add-hook 'embark-collect-mode #'consult-preview-at-point-mode)
(setopt embark-indicators '(embark-mixed-indicator
                            embark-highlight-indicator
                            embark-isearch-highlight-indicator))

;;; Frames / Windows

(winner-mode)

;;; Files

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

(setopt create-lockfiles nil)
(setopt require-final-newline t)
(setopt view-read-only t)
(setopt save-silently t)
(setopt delete-by-moving-to-trash t)
(setopt auto-save-default t)
(setopt auto-save-no-message t)
(setopt auto-save-interval 30)
(setopt auto-save-timeout 5)
(setopt auto-save-visited-interval 5)
(setopt remote-file-name-inhibit-auto-save t)
(setopt remote-file-name-inhibit-auto-save-visited t)
(add-to-list 'auto-save-file-name-transforms
             `(".*" ,(locate-user-emacs-file "auto-save/") t))
(auto-save-visited-mode)

(add-hook 'window-selection-change-functions
          (defun save-old-selected-window-buffer (frame)
            (with-current-buffer
                (window-buffer (frame-old-selected-window))
              (when (and (buffer-file-name) (buffer-modified-p))
                (save-buffer)))))

(add-hook 'buffer-list-update-hook
          (defun save-other-buffer ()
            (with-current-buffer (other-buffer)
              (when (and (buffer-file-name) (buffer-modified-p))
                (save-buffer)))))

(add-function :after after-focus-change-function
              (defun focus-out-save ()
                (save-some-buffers t)))

(setopt backup-by-copying t)
(setopt version-control t)
(setopt kept-new-versions 3)
(setopt kept-old-versions 3)
(setopt delete-old-versions t)
(add-to-list 'backup-directory-alist '("^/dev/shm/" . nil))
(add-to-list 'backup-directory-alist '("^/tmp/" . nil))
(when-let ((xrd (getenv "XDG_RUNTIME_DIR")))
  (add-to-list 'backup-directory-alist (cons xrd nil)))
(add-to-list 'backup-directory-alist
             (cons "." (locate-user-emacs-file "backup/"))
             :append)

(setopt recentf-max-menu-items 100)
(setopt recentf-max-saved-items nil)
(setopt recentf-case-fold-search t)
(with-eval-after-load 'recentf
  (add-to-list 'recentf-exclude "-autoloads.el\\'"))
(add-hook 'buffer-list-update-hook #'recentf-track-opened-file)
(add-hook 'after-save-hook #'recentf-save-list)
(recentf-mode)

(setopt save-place-forget-unreadable-files (eq system-type 'gnu/linux))
(save-place-mode)

(add-hook 'find-file-not-found-functions #'create-missing-directories)

;;; Buffers

;; Unique names
(setopt uniquify-buffer-name-style 'forward)
(setopt uniquify-after-kill-buffer-p t)
(setopt uniquify-ignore-buffers-re "^\\*")

;; Persistent undo
(package-ensure 'undo-fu-session)
(setopt undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'"
                                             "/git-rebase-todo\\'"))
(undo-fu-session-global-mode)

;; Encodings
(set-language-environment "UTF-8")
(setopt buffer-file-coding-system 'utf-8-unix)
(setopt coding-system-for-read 'utf-8-unix)
(setopt coding-system-for-write 'utf-8-unix)
(setopt default-process-coding-system '(utf-8-unix . utf-8-unix))
(setopt locale-coding-system 'utf-8-unix)
(set-charset-priority 'unicode)
(prefer-coding-system 'utf-8-unix)
(set-default-coding-systems 'utf-8-unix)
(set-terminal-coding-system 'utf-8-unix)
(set-keyboard-coding-system 'utf-8-unix)
(pcase system-type
  ((or 'ms-dos 'windows-nt)
   (set-clipboard-coding-system 'utf-16-le)
   (set-selection-coding-system 'utf-16-le))
  (_
   (set-selection-coding-system 'utf-8)
   (set-clipboard-coding-system 'utf-8)))

;;; Search

(setopt isearch-lazy-count nil)
(setopt isearch-regexp-lax-whitespace t)
(setopt isearch-wrap-pause 'no)
(setopt search-whitespace-regexp "[       ]+")
(setopt search-ring-max 256)
(setopt regexp-search-ring-max 256)

(define-advice isearch-cancel (:before () add-to-history)
  "Add search string to history when canceling isearch."
  (unless (string-equal "" isearch-string)
    (isearch-update-ring isearch-string isearch-regexp)))

(package-ensure 'isearch-mb t)
(add-to-list 'isearch-mb--with-buffer #'consult-isearch-history)
(keymap-set isearch-mb-minibuffer-map "M-r" #'consult-isearch-history)
(add-to-list 'isearch-mb--after-exit #'consult-line)
(keymap-set isearch-mb-minibuffer-map "M-s l" #'consult-line)
(isearch-mb-mode)

;; Default to regexen
(setopt search-default-mode t) ; Isearch
(keymap-global-set "M-%" #'query-replace-regexp)
(keymap-global-set "C-M-%" #'query-replace)

;;; Keybindings

(repeat-mode)

;; Separate C-<key> and control keys from halcyon ascii days
;; (define-key input-decode-map [?\C-i] [C-i])
;; (define-key input-decode-map [?\C-m] [C-m])

;; Modified default keybindings
(keymap-global-set "C-x C-k" #'kill-buffer-dwim)
(keymap-global-set "M-o" #'other-window-dwim)
(keymap-global-set "C-x o" #'other-window-dwim)
(keymap-global-set "C-x 0" #'delete-window-dwim)
(keymap-global-set "M-SPC" #'cycle-spacing*)
(keymap-global-set "C-x C-c" #'save-buffers-kill*)
(keymap-global-set "C-g" #'keyboard-quit*)
(keymap-global-set "C-M-\\" #'fixup-whitespace)

;; New bindings for existing stuff
(keymap-global-set "C-x C-b" #'ibuffer)
(keymap-global-set "M-/" #'hippie-expand)
(keymap-global-set "M-u" #'universal-argument)
(keymap-set universal-argument-map "M-u" #'universal-argument-more)

;; Prefix maps
(keymap-global-set "C-c d"
                   (defun insert-current-iso8601 ()
                     (interactive)
                     (insert (format-time-string "%FT%TZ" (current-time) t))))

(keymap-global-set "C-c i"
                   (define-keymap
                     :prefix 'find-init-map
                     "i" (find-user-file init)
                     "e" (find-user-file early-init
                           (locate-user-emacs-file "early-init.el"))
                     "c" (find-user-file custom custom-file)
                     "p" (find-user-file private)
                     "t" (find-user-file brianna
                           (locate-user-emacs-file "brianna-theme.el"))
                     "x" (find-user-file exwm
                           (expand-file-name "~/.exwm"))
                     "s" #'scratch-buffer))

(keymap-global-set "C-c t"
                   (define-keymap
                     :prefix 'toggle-map
                     "e" #'toggle-debug-on-error
                     "q" #'toggle-debug-on-quit
                     "c" #'column-number-mode
                     "l" #'line-number-mode
                     "L" #'display-line-numbers-mode
                     "t" #'truncate-lines-local-mode
                     "o" #'overwrite-mode
                     "f" #'auto-fill-mode))

(keymap-global-set "M-c"
                   (define-keymap
                     :prefix 'case-map
                     "M-u" #'upcase-dwim "u"    #'upcase-dwim
                     "M-c" #'capitalize-dwim "c" #'capitalize-dwim
                     "M-l" #'downcase-dwim "l" #'downcase-dwim))
(put 'upcase-dwim 'repeat-map 'case-map)
(put 'capitalize-dwim 'repeat-map 'case-map)
(put 'downcase-dwim 'repeat-map 'case-map)

;;; Un-keybinds
;; Why do I want to zoom with the mouse?
(keymap-global-unset "C-<wheel-down>" t)
(keymap-global-unset "C-<wheel-up>" t)
;; These are ripe for re-binding
(keymap-global-unset "C-\\" t)
(keymap-global-unset "M-l" t)
(keymap-global-unset "<f2>" t)

;; Key settings
(setopt set-mark-command-repeat-pop t)

;;; Text-editing packages

;; Hungry delete
(package-ensure 'hungry-delete)
(setopt hungry-delete-chars-to-skip " \t")
(with-eval-after-load 'hungry-delete
  (dolist (m '( eshell-mode
                eww-mode
                special-mode
                jabber-chat-mode))
    (add-to-list 'hungry-delete-except-modes m)))
(global-hungry-delete-mode)

;;; Writing

(add-hook 'text-mode-hook #'visual-line-mode)

;;; Programming

(add-hook 'prog-mode-hook #'electric-pair-local-mode)
(setopt tab-width 8)
(setopt sh-basic-offset tab-width)
(setopt perl-indent-level tab-width)
(setopt c-basic-offset tab-width)

(defvar space-indent-modes '(emacs-lisp-mode
                             lisp-interaction-mode
                             lisp-mode
                             scheme-mode
                             python-mode
                             haskell-mode
                             text-mode
                             web-mode
                             css-mode)
  "Modes to indent with spaces, not tabs.")

(add-hook 'prog-mode-hook
          (defun indent-tabs-mode-maybe ()
            (setq indent-tabs-mode
                  (if (apply #'derived-mode-p space-indent-modes) nil t))))

;; Corfu
(package-ensure 'corfu t)
(keymap-set corfu-map "TAB" #'corfu-next)
(keymap-set corfu-map "<tab>" #'corfu-next)
(keymap-set corfu-map "S-TAB" #'corfu-previous)
(keymap-set corfu-map "<backtab>" #'corfu-previous)
(global-corfu-mode)

;; Eldoc
(setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)

;; Elisp
(keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun)
(keymap-set emacs-lisp-mode-map "C-c C-b"
            (defun eval-buffer@pulse () (interactive)
                   (eval-buffer)
                   (pulse-momentary-highlight-region (point-min) (point-max))))
(advice-add 'eval-region :after #'pulse@eval)
(add-hook 'emacs-lisp-mode-hook #'elisp-enable-lexical-binding)

;; Makefile
(setopt makefile-backslash-align nil)
(setopt makefile-cleanup-continuations t)

(add-hook 'makefile-mode-hook
          (^local-unhook 'write-file-functions 'makefile-warn-suspicious-lines))
(add-hook 'makefile-mode-hook
          (^local-unhook 'write-file-functions 'makefile-warn-continuations))

;; Scheme -- CHICKEN
(setopt scheme-program-name (or (executable-find "csi")))
(add-to-list 'auto-mode-alist '("\\.egg\\'" . scheme-mode))

;; Scheme Indentation
(defun scheme-module-indent (state indent-point normal-indent) 0)
(put 'module 'scheme-indent-function 'scheme-module-indent)
(put 'and-let* 'scheme-indent-function 1)
(put 'parameterize 'scheme-indent-function 1)
(put 'handle-exceptions 'scheme-indent-function 1)
(put 'when 'scheme-indent-function 1)
(put 'unless 'scheme-indent-function 1)
(put 'match 'scheme-indent-function 1)

;; Geiser
(package-ensure 'geiser)
(package-ensure 'geiser-chicken)
(setopt geiser-mode-auto-p nil)
(setopt geiser-repl-history-filename "~/.emacs.d/geiser-history")
(setopt geiser-chicken-init-file "~/.csirc")
(add-hook 'scheme-mode-hook #'geiser-mode)
(add-hook 'geiser-repl-mode-hook #'electric-pair-local-mode)
(advice-add 'geiser-eval-region :after #'pulse@eval)

;; Lisp
(package-ensure 'sly)
(setopt inferior-lisp-program (executable-find "sbcl"))
(after sly-completion                   ; Follow my other completion setup
  (keymap-set sly--completion-transient-mode-map
              "TAB" #'sly-next-completion)
  (keymap-set sly--completion-transient-mode-map
              "<backtab>" #'sly-prev-completion))

;; VC
(keymap-global-set "C-x m" #'vc-jump)

;;; Compilation

(setopt compilation-always-kill t)
(setopt compilation-ask-about-save nil)

;;; Languages

(package-ensure 'gemtext-mode)
(package-ensure 'markdown-mode)

;;; Miscellaneous

;; Settings
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
(add-hook 'messages-buffer-mode-hook
          (^turn-off 'display-fill-column-indicator-mode))
(add-hook 'prog-mode-hook #'auto-fill-mode)
(add-hook 'prog-mode-hook #'electric-pair-local-mode)
(context-menu-mode)
(delete-selection-mode)
(global-goto-address-mode)
(global-so-long-mode)
(pixel-scroll-precision-mode)
(setopt bookmark-save-flag 1)
(setopt disabled-command-function nil)
(setopt display-fill-column-indicator-character ?·)
(setopt electric-pair-skip-whitespace 'chomp)
(setopt eval-expression-print-length nil)
(setopt eval-expression-print-level nil)
(setopt fill-column 80)
(setopt finger-X.500-host-regexps '(".*tilde.*"))
(setopt help-window-keep-selected t)
(setopt help-window-select t)
(setopt read-extended-command-predicate #'command-completion-default-include-p)
(setopt recenter-positions '(top middle bottom))
(setopt scroll-conservatively 101)
(setopt show-paren-delay 0.01)
(setopt show-paren-style 'parenthesis)
(setopt show-paren-when-point-in-periphery t)
(setopt show-paren-when-point-inside-paren t)
(setopt switch-to-buffer-obey-display-actions t)
(setopt tmm-completion-prompt nil)
(setopt tmm-mid-prompt " -- ")
(setopt x-underline-at-descent-line t)
(show-paren-mode)
(tooltip-mode -1)

;; Advice & Hooks

(define-advice canonically-space-region
    (:around (orig &rest args) double-space-sentences)
  "Always double-space sentences canonically."
  (let ((sentence-end-double-space t))
    (apply orig args)))

(define-advice switch-to-buffer (:after (&rest _) normal-mode)
  "Automatically determine the mode for non-file buffers."
  (when-let ((_ (and (eq major-mode 'fundamental-mode)
                     (not buffer-file-name)))
             (buffer-file-name (buffer-name)))
    (normal-mode)))

(add-hook 'special-mode-hook
          (defun hl-line@special-mode ()
            (unless (derived-mode-p 'help-mode ; add other modes here
                                    'Info-mode
                                    'Man-mode
                                    'eww-mode
                                    'elpher-mode)
              (hl-line-mode))))
(add-hook 'dired-mode-hook #'hl-line-mode)

;;; Jabber

(package-ensure 'jabber)
(with-eval-after-load 'jabber
  (require 'jabber-httpupload nil t)
  ;; I wish jabber.el didn't clobber C-x C-j ...
  (keymap-global-set "C-x C-j" #'dired-jump)
  (keymap-global-set "C-c j" jabber-global-keymap)
  (map-keymap (lambda (key cmd)
                (define-key jabber-global-keymap (vector (+ key #x60)) cmd))
              jabber-global-keymap)
  ;; This is just a dang good idea
  (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to))

(setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten)
(setopt jabber-activity-query-unread nil)
(setopt jabber-auto-reconnect t)
(setopt jabber-browse-buffer-format "%n<browse>")
(setopt jabber-chat-buffer-format "%n")
(setopt jabber-groupchat-buffer-format "%b")
(setopt jabber-muc-private-buffer-format "%n<%g>")

(defun esc/mls (str)                      ; escape-mode-line-string
  (string-replace "%" "%%" str))

(setopt jabber-chat-header-line-format
        '(" " (:eval (esc/mls (jabber-jid-displayname jabber-chatting-with)))
          " :: " (:eval (esc/mls (jabber-fix-status
                                  (get (jabber-jid-symbol jabber-chatting-with)
                                       'status))))
          " :: " (:eval (esc/mls jabber-events-message)) ;see jabber-events.el
          " :: " (:eval (esc/mls jabber-chatstates-message))))
(setopt jabber-muc-header-line-format
        '(" " (:eval (esc/mls (jabber-jid-displayname jabber-group)))
          " :: " (:eval (esc/mls jabber-muc-topic))))
(setopt jabber-muc-private-header-line-format
        '(" " (:eval (esc/mls (jabber-jid-resource jabber-chatting-with)))
          " in " (:eval (esc/mls (jabber-jid-displayname
                                  (jabber-jid-user jabber-chatting-with))))
          " :: " (:eval (esc/mls jabber-events-message))
          " :: " (:eval (esc/mls jabber-chatstates-message))))

(add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons)
(add-hook 'jabber-chat-mode-hook #'visual-line-mode)
(remove-hook 'jabber-alert-muc-hooks #'jabber-muc-echo)
(remove-hook 'jabber-alert-presence-hooks #'jabber-presence-echo)

;;; Eshell

(setopt cookie-file (expand-file-name "~/cloud/oblique.txt"))
(setopt eshell-banner-message (format "%s\n" (cookie cookie-file)))
(setopt eshell-prompt-function
        (defun @eshell-prompt ()
          (let ((rootp (zerop (user-uid))))
            (concat "( "
                    (unless (= 0 eshell-last-command-status)
                      (format "*%d " eshell-last-command-status))
                    (abbreviate-file-name (eshell/pwd))
                    (if rootp ":root" "")
                    " ) "))))
(setopt eshell-prompt-regexp "^(.*) ")
(setopt eshell-destroy-buffer-when-process-dies t)
(setopt eshell-error-if-no-glob t)
(setopt eshell-hist-ignoredups 'erase)
(setopt eshell-kill-on-exit t)
(setopt eshell-prefer-lisp-functions t)
(setopt eshell-prefer-lisp-variables t)
(setopt eshell-scroll-to-bottom-on-input 'this)
(setopt eshell-history-size nil)

(keymap-global-set "C-z" #'popup-eshell)
(keymap-global-set "C-c C-z" #'popup-eshell)
(add-hook 'eshell-first-time-mode-hook
          (defun @eshell-once ()
            (keymap-set eshell-mode-map "C-z" #'quit-window)))

;;; Browsing

;; Dired (files)
(add-hook 'dired-mode-hook #'dired-hide-details-mode)
(add-hook 'dired-mode-hook #'truncate-lines-local-mode)
(setopt dired-auto-revert-buffer t)
(setopt dired-clean-confirm-killing-deleted-buffers nil)
(setopt dired-create-destination-dirs 'always)
(setopt dired-do-revert-buffer t)
(setopt dired-dwim-target t)
(setopt dired-hide-details-hide-symlink-targets nil)
(setopt dired-listing-switches "-AlFhv --group-directories-first")
(setopt dired-ls-F-marks-symlinks t)
(setopt dired-no-confirm '(byte-compile load chgrp chmod chown copy move
                                        hardlink symlink shell touch))
(setopt dired-recursive-copies 'always)
(setopt dired-recursive-deletes 'always)
(with-eval-after-load 'dired
  (require 'dired-x)
  (setopt dired-omit-files (regexp-concat dired-omit-files
                                          "^\\..+$"
                                          ;; CHICKEN ... this may be overkill
                                          "\\.s?o$"
                                          "\\.import\\.scm$"
                                          "\\.\\(build\\|install\\)\\.sh$"
                                          "\\.link$"))
  (keymap-set dired-mode-map "C-j" #'dired-up-directory)
  (add-hook 'dired-mode-hook #'dired-omit-mode)
  (keymap-set dired-mode-map ")" #'dired-omit-mode))

;; Elpher (gemini/gopher)
(package-ensure 'elpher)
(with-eval-after-load 'elpher
  ;; Try to emulate eww bindings if possible
  (keymap-set elpher-mode-map "l" #'elpher-back)
  (keymap-set elpher-mode-map "g" #'elpher-reload)
  (keymap-set elpher-mode-map "G" #'elpher-go)
  (keymap-set elpher-mode-map "v" #'elpher-view-raw)
  (keymap-set elpher-mode-map "E" #'elpher-set-gopher-coding-system))

;;; HTTP browsing

;; Eww / Shr
(setopt shr-max-width nil)                ; covered in hook below
(setopt shr-max-image-proportion 0.9)
(setopt shr-discard-aria-hidden t)
(setopt eww-auto-rename-buffer
        (defun title+url ()
          (when (eq major-mode 'eww-mode)
            (let ((title (plist-get eww-data :title))
                  (url (plist-get eww-data :url)))
              (cond
               ((and title url) (format "*eww: %s :: %s" title url))
               ((or title url) (format "*eww: %s") (or title url)))))))
(add-hook 'eww-after-render-hook
          (defun eww@visual-line ()
            (visual-fill-column-mode)
            (eww-reload t)))
(with-eval-after-load 'eww
  (setopt eww-use-browse-url ".")
  (keymap-set eww-mode-map "B" #'bookmark-jump)
  (keymap-set eww-mode-map "b" #'bookmark-set)
  (keymap-unset eww-mode-map "M-n" t)
  (keymap-unset eww-mode-map "M-p" t))

;; Browse-url
(setopt browse-url-browser-function #'eww-browse-url)
(setopt browse-url-firefox-arguments '("--new-tab"))
(setopt browse-url-firefox-new-window-is-tab t)
(setopt browse-url-generic-args browse-url-firefox-arguments)
(setopt browse-url-firefox-program (executable-find "librewolf"))
(setopt browse-url-generic-program browse-url-firefox-program)


(setopt browse-url-secondary-browser-function #'browse-url-generic)

(package-ensure 'link-hint)
(keymap-global-set "M-l"
                   (define-keymap
                     :prefix 'link-map
                     "M-l" #'link-hint-open-link "l" #'link-hint-open-link
                     "M-w" #'link-hint-copy-link "w" #'link-hint-copy-link))
;; With link-hint we get avy "for free"
(keymap-global-set "M-j" #'avy-goto-char-timer)

;; PDFs
(package-ensure 'pdf-tools)
(pdf-loader-install)

;;; EXWM

(setf/alist display-buffer-alist shell-command-buffer-name-async
            '(display-buffer-no-window))

(when (getenv "DISPLAY")
  (package-ensure 'exwm t)
  (load (expand-file-name "~/.exwm")))

;;; Mu4e

;;; Mastodon (uh oh)

(package-ensure 'mastodon)
(add-hook 'mastodon-mode-hook #'visual-line-mode)
(add-hook 'mastodon-mode-hook (^turn-off #'fixed-pitch-mode))