;;; init --- user init file  -*- lexical-binding: t; -*-
(eval '(setq inhibit-startup-echo-area-message "alan"))
(defvar default-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)

(defun restore-file-name-handler-alist ()
  (setq file-name-handler-alist default-file-name-handler-alist))
(add-hook 'emacs-startup-hook #'restore-file-name-handler-alist)

(defun maybe-start-server ()
  (require 'server)
  (unless (server-running-p)
    (server-start)))
(add-hook 'after-init-hook #'maybe-start-server)

(eval-when-compile (require 'fringe-helper))

(defun reload-user-init-file ()
  "Reload init file."
  (interactive)
  (load-file user-init-file))

(setq use-package-enable-imenu-support t)
(require 'use-package)
(setq use-package-always-demand (daemonp)
      use-package-compute-statistics t)

(defmacro quietly (&rest body)
  `(let ((inhibit-message t))
		 ,@body))
(defun quiet (original-function &rest args)
  (quietly (apply original-function args)))

(use-package benchmark-init
  :config (progn
            (add-hook 'after-init-hook #'benchmark-init/deactivate 99)))

;;; Customize

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

(use-package crux
  :defer 1)

(use-package general
  :functions (general-unbind general-define-key)
  :config (progn
            (general-override-mode +1)
            (when (eq system-type 'darwin)
              (general-unbind "s-x"))))

;;; Styles

;; I prefer an always-visible cursor.  Feels less distracting.
(blink-cursor-mode -1)

(setq-default truncate-lines t)

;; Don't ring the bell
(setq ring-bell-function #'ignore)

(when (or (daemonp)
          window-system)
  (use-package stimmung-themes
    :config (progn
              (require 'stimmung-themes-light-theme)
              (let ((light-mode-theme 'stimmung-themes-light)
                    (dark-mode-theme 'stimmung-themes-dark)
                    (original-stimmung-themes-string stimmung-themes-string))
                (load-theme light-mode-theme :noconfirm :noenable)
                (load-theme dark-mode-theme :noconfirm :noenable)
                (enable-theme light-mode-theme)
                (defun toggle-stimmung-string-highlighting ()
                  (interactive)
                  (let ((current-theme (car custom-enabled-themes)))
                    (setq stimmung-themes-string (if (eq stimmung-themes-string original-stimmung-themes-string)
                                                     'none
                                                   original-stimmung-themes-string))
                    (load-theme current-theme :noconfirm)))))))

(setq font-lock-maximum-decoration '((t . 1))
      jit-lock-stealth-time 1.25
      jit-lock-stealth-nice 0.5
      jit-lock-chunk-size 4096)

(when (and (eq system-type 'darwin)
           (fboundp 'mac-mouse-wheel-mode))
  (mac-mouse-wheel-mode +1))
(context-menu-mode +1)

;;; Chrome
(column-number-mode -1)
(line-number-mode -1)

(use-package nerd-icons
  :config (progn
            (setq nerd-icons-color-icons t
                  nerd-icons-scale-factor 1.3)))

(use-package doom-modeline
  :hook (emacs-startup . doom-modeline-mode)
  :config (progn
            (setq doom-modeline-buffer-file-name-style 'relative-from-project
                  doom-modeline-buffer-encoding 'nondefault
                  doom-modeline-buffer-modification-icon nil
                  doom-modeline-checker-simple-format t
                  doom-modeline-percent-position nil
                  doom-modeline-project-detection 'project
                  doom-modeline-vcs-max-length 24
                  doom-modeline-height 28)
            (let ((foreground (face-attribute 'font-lock-comment-face :foreground)))
              (set-face-attribute 'doom-modeline-buffer-modified nil :foreground foreground))))

(when (eq system-type 'darwin)
  (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
  (add-to-list 'default-frame-alist '(ns-appearance . 'light)))

(add-to-list 'default-frame-alist '(width . 100))
(add-to-list 'default-frame-alist '(height . 40))
(setq-default display-line-numbers 'relative
              display-line-numbers-widen t
              display-line-numbers-width 4)
(setq frame-resize-pixelwise t)

(defun noct-relative ()
  "Show relative line numbers."
  (when display-line-numbers
    (setq-local display-line-numbers 'relative)))

(defun noct-absolute ()
  "Show absolute line numbers."
  (when display-line-numbers
    (setq-local display-line-numbers t)))

(add-hook 'evil-insert-state-entry-hook #'noct-absolute)
(add-hook 'evil-insert-state-exit-hook #'noct-relative)

;;; Encoding

(setq-default bidi-paragraph-direction 'left-to-right
              bidi-display-reordering 'left-to-right)
(setq bidi-inhibit-bpa t
      require-final-newline t)

;;; Dates & Times

(defvar calendar-week-start-day 1)
(defvar calendar-date-style 'iso)

(defun insert-date (prefix)
  "Insert the current date.
With PREFIX, use British format.
With two prefix arguments, write out the day and month name."
  (interactive "P")
  (let ((format (cond
                 ((not prefix) "%Y-%m-%d")
                 ((equal prefix '(4)) "%d/%m/%Y")
                 ((equal prefix '(16)) "%A, %d %B %Y"))))
    (insert (format-time-string format))))

(defun insert-datetime (prefix)
  "Insert current date and time.  With PREFIX, use ISO8601 format."
  (interactive "P")
  (let ((format (cond
                 ((not prefix) "%Y-%m-%d %H:%M:%S")
                 ((equal prefix '(4)) "%Y-%m-%dT%H:%M:%SZ"))))
    (insert (format-time-string format))))

;;; Keybindings

(when (eq system-type 'darwin)
  (setq mac-option-modifier 'meta
        mac-right-option-modifier 'none
        mac-control-modifier 'control
        mac-right-control-modifier 'left
        mac-command-modifier 'super
        mac-right-command-modifier 'left
        mac-function-modifier 'hyper))

(use-package avy
  :defer 2
  :config (setq avy-all-windows nil))
(use-package ace-link
  :after avy
  :commands (ace-link-setup-default)
  :config (ace-link-setup-default))

;; Popup keybindings following a prefix automatically.

(use-package which-key
  :defer 5
  :config (progn
            (which-key-mode +1)
            (which-key-setup-side-window-right-bottom)))

;;; Minibuffer

(setq enable-recursive-minibuffers t
      save-silently t
      uniquify-buffer-name-style 'forward
      read-extended-command-predicate #'command-completion-default-include-p)
(minibuffer-depth-indicate-mode t)

(use-package hydra
  :defer 2)

(use-package recentf
  :defer 1
  :custom (recentf-auto-cleanup 1800)
  :config (progn
            (quietly (recentf-mode +1))
            (advice-add 'recentf-cleanup :around #'quiet)))

(use-package savehist
  :init (savehist-mode +1)
  :config (progn
            (add-to-list 'savehist-additional-variables 'command-history)))

(use-package persist-state
  :defer 1
  :ghook ('server-mode-hook #'persist-state-mode))

(use-package vertico
  :config (progn
            (vertico-mode +1)))

(use-package prescient
  :defer 1
  :config (progn
            (setq prescient-history-length 10000)
            (prescient-persist-mode +1)))

(use-package vertico-prescient
  :after prescient
  :ghook '(vertico-mode-hook))

(use-package marginalia
  :general (:keymaps 'minibuffer-local-map
                     "M-A" #'marginalia-cycle)
  :init (marginalia-mode +1))

(setq completion-ignore-case t
      read-buffer-completion-ignore-case t
      read-file-name-completion-ignore-case t
      completion-styles '(flex substring basic)
			completion-category-defaults nil
			completion-category-overrides '((file (styles basic partial-completion))))

(use-package orderless
  :config (progn
            (add-to-list 'completion-styles 'orderless)))

(use-package consult
  :general ([remap isearch-forward] #'consult-line
            [remap isearch-backward] #'consult-line
            [remap project-switch-to-buffer] #'consult-project-buffer
            [remap project-search] #'consult-ripgrep)
  :config (progn
            (setq consult-ripgrep-args
                  "rg --null --line-buffered --color=never --max-columns=1000 --path-separator /   --smart-case --no-heading --with-filename --line-number --search-zip --follow")))

(use-package consult-dir
  :defer 10
  :general ("C-x C-d" #'consult-dir)
  (:keymaps 'vertico-map
            "C-x C-d" #'consult-dir
            "C-x C-j" #'consult-jump))

(use-package embark
  :general ("C-." #'embark-act
            "M-." #'embark-dwim
            "C-h B" #'embark-bindings)
  :config (progn
            (setq embark-prompter #'embark-keymap-prompter)
            (add-to-list 'display-buffer-alist
                         '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                           nil
                           (window-parameters (mode-line-format . none))))))

(use-package embark-consult
  :ghook ('consult-preview-at-point-mode-hook #'embark-collect-mode))

(use-package smerge-mode
  :after magit
  :config
  (defhydra unpackaged/smerge-hydra
    (:color pink :hint nil :post (smerge-auto-leave))
    "
^Move^       ^Keep^               ^Diff^                 ^Other^
^^-----------^^-------------------^^---------------------^^-------
_n_ext       _b_ase               _<_: upper/base        _C_ombine
_p_rev       _u_pper              _=_: upper/lower       _r_esolve
^^           _l_ower              _>_: base/lower        _k_ill current
^^           _a_ll                _R_efine
^^           _RET_: current       _E_diff
"
    ("n" smerge-next)
    ("p" smerge-prev)
    ("b" smerge-keep-base)
    ("u" smerge-keep-upper)
    ("l" smerge-keep-lower)
    ("a" smerge-keep-all)
    ("RET" smerge-keep-current)
    ("\C-m" smerge-keep-current)
    ("<" smerge-diff-base-upper)
    ("=" smerge-diff-upper-lower)
    (">" smerge-diff-base-lower)
    ("R" smerge-refine)
    ("E" smerge-ediff)
    ("C" smerge-combine-with-next)
    ("r" smerge-resolve)
    ("k" smerge-kill-current)
    ("w" (lambda ()
           (interactive)
           (save-buffer)
           (bury-buffer))
     "Save and bury buffer" :color blue)
    ("q" nil "cancel" :color blue))
  :hook (magit-diff-visit-file . (lambda ()
                                   (when (bound-and-true-p smerge-mode)
                                     (unpackaged/smerge-hydra/body)))))

;;; Windows

(defun split-window-properly (&optional window)
  (let ((window (or window (selected-window))))
    (or (and (window-splittable-p window)
             ;; Split window vertically.
             (with-selected-window window
               (split-window-below)))
        (and (window-splittable-p window t)
             ;; Split window horizontally.
             (with-selected-window window
               (split-window-right))))))

(setq split-window-preferred-function #'split-window-properly
      split-height-threshold 20
      split-width-threshold 160)

(use-package eyebrowse
  :after (evil)
  :config (progn
            (setq eyebrowse-new-workspace t
                  eyebrowse-mode-line-left-delimiter ""
                  eyebrowse-mode-line-right-delimiter ""
                  eyebrowse-mode-line-separator " "
                  eyebrowse-mode-line-style 'always)
            (eyebrowse-mode +1))
  :general (:keymaps 'evil-window-map
                     "0" #'eyebrowse-switch-to-window-config-0
                     "1" #'eyebrowse-switch-to-window-config-1
                     "2" #'eyebrowse-switch-to-window-config-2
                     "3" #'eyebrowse-switch-to-window-config-3
                     "4" #'eyebrowse-switch-to-window-config-4
                     "5" #'eyebrowse-switch-to-window-config-5
                     "6" #'eyebrowse-switch-to-window-config-6
                     "7" #'eyebrowse-switch-to-window-config-7
                     "8" #'eyebrowse-switch-to-window-config-8
                     "9" #'eyebrowse-switch-to-window-config-9)
  :ghook ('after-init-hook #'eyebrowse-switch-to-window-config-1)
  :ghook ('evil-after-load-hook #'eyebrowse-setup-evil-keys))

(use-package winner
  :after evil
  :defer 8
  :config (progn
            (setq winner-boring-buffers '("*Completions*" "*Help*" "*Apropos*" "*Buffer List*" "*info*" "*Compile-Log*"))
            (winner-mode +1))
  :general (:keymaps 'evil-window-map
                     "u" #'winner-undo
                     "r" #'winner-redo
                     "C-r" #'winner-redo))
;;; Evil

(eval-and-compile
  (defvar evil-want-keybinding nil))
(use-package evil
  :demand t
  :commands (evil-mode evil-delete-buffer evil-ex-define-cmd)
  :config (progn
            (setq-default evil-shift-width 2)
            (setq evil-mode-line-format nil)
            (evil-set-undo-system 'undo-redo)
            (add-to-list 'evil-emacs-state-modes 'eshell-mode)
            (evil-mode +1))
  :general
  (:states 'motion
           "C-;" #'evil-avy-goto-line)
  (:states 'normal
           ";" #'evil-ex)
  (:states '(normal motion)
           "g s" #'evil-avy-goto-symbol-1))

(add-hook 'c-mode-common-hook ; make b/w/e include underscore as *part* of a word
          (lambda () (modify-syntax-entry ?_ "w")))

(use-package evil-anzu
  :defer 2
  :after evil
  :config (global-anzu-mode +1))

(use-package evil-collection
  :demand t
  :config (progn
            (setq evil-collection-magit-use-y-for-yank nil
                  evil-collection-corfu-key-themes '(tab-n-go
                                                     magic-return))
            (general-unbind 'normal magit-mode-map
              "<escape>")
            (evil-collection-init)))

(general-create-definer my-leader-def
  :keymaps 'override
  :states '(normal motion)
  :prefix ",")

(use-package evil-space
  :defer 1
  :after evil
  :config (evil-space-mode +1))

;; this macro was copied from here: https://stackoverflow.com/a/22418983/4921402
(defmacro define-and-bind-quoted-text-object (name key start-regex end-regex)
  (let ((inner-name (make-symbol (concat "evil-inner-" name)))
        (outer-name (make-symbol (concat "evil-a-" name))))
    `(progn
       (evil-define-text-object ,inner-name (count &optional beg end type)
         (evil-select-paren ,start-regex ,end-regex beg end type count nil))
       (evil-define-text-object ,outer-name (count &optional beg end type)
         (evil-select-paren ,start-regex ,end-regex beg end type count t))
       (define-key evil-inner-text-objects-map ,key #',inner-name)
       (define-key evil-outer-text-objects-map ,key #',outer-name))))

(use-package evil-surround
  :after evil
  :defer 2
  :config (progn
            (add-hook 'js-ts-mode-hook (lambda ()
                                         (define-and-bind-quoted-text-object "slash" "/" "\\/" "\\/")
                                         (push '(?\/ . ("/" . "/")) evil-surround-pairs-alist)))
            (add-hook 'emacs-lisp-mode-hook (lambda ()
                                              (push '(?` . ("`" . "'")) evil-surround-pairs-alist)))
            (global-evil-surround-mode +1)))


(use-package evil-embrace
  :after evil-surround
  :ghook ('LaTex-mode-hook #'embrace-LaTeX-mode-hook)
  :ghook ('org-mode-hook #'embrace-org-mode-hook)
  :ghook ('ruby-ts-mode-hook #'embrace-ruby-mode-hook)
  :ghook ('emacs-lisp-mode-hook #'embrace-emacs-lisp-mode-hook)
  :config (progn
            (setq evil-embrace-show-help-p nil)
            (push ?\/ evil-embrace-evil-surround-keys)
            (evil-embrace-enable-evil-surround-integration)))

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

(use-package evil-commentary
  :after evil
  :defer 2
  :config (evil-commentary-mode +1))

(use-package evil-matchit
  :after evil
  :defer 2
  :config (progn
            (global-evil-matchit-mode +1)))

(use-package evil-quickscope
  :after evil
  :commands (evil-quickscope-mode)
  :ghook ('(magit-mode-hook git-rebase-mode-hook) #'turn-off-evil-quickscope-mode)
  :config (global-evil-quickscope-mode +1))

(use-package evil-numbers
  :after evil
  :general (:states '(normal visual)
                    "C-a" #'evil-numbers/inc-at-pt
                    "C-q" #'evil-numbers/dec-at-pt))

(use-package evil-org
  :after org
  :commands (evil-org-set-key-theme)
  :init (progn
          (add-hook 'org-agenda-mode-hook #'evil-org-agenda-set-keys))
  :ghook ('org-mode-hook #'evil-org-mode)
  :gfhook #'evil-org-set-key-theme)

(use-package evil-textobj-tree-sitter
  :defer 5
  :config (progn
            (defun etts/start-of-next-function ()
              (interactive)
              (evil-textobj-tree-sitter-goto-textobj "function.outer" nil nil))
            (defun etts/start-of-prev-function ()
              (interactive)
              (evil-textobj-tree-sitter-goto-textobj "function.outer" t nil))
            (defun etts/end-of-next-function ()
              (interactive)
              (evil-textobj-tree-sitter-goto-textobj "function.outer" nil t))
            (defun etts/end-of-prev-function ()
              (interactive)
              (evil-textobj-tree-sitter-goto-textobj "function.outer" t t))
            (general-define-key :keymaps 'evil-outer-text-objects-map
                                "f" (evil-textobj-tree-sitter-get-textobj "function.outer")
                                "a" (evil-textobj-tree-sitter-get-textobj ("conditional.outer" "loop.outer")))
            (general-define-key :keymaps 'evil-inner-text-objects-map
                                "f" (evil-textobj-tree-sitter-get-textobj "function.inner")
                                "a" (evil-textobj-tree-sitter-get-textobj ("conditional.inner" "loop.inner"))))
  :general
  (:states 'normal
           "]f" #'etts/start-of-next-function
           "[f" #'etts/start-of-prev-function
           "]F" #'etts/end-of-next-function
           "[F" #'etts/end-of-prev-function))

;;; Completion

(use-package corfu
  :defer 1
  :config (progn
            (global-corfu-mode +1)
            (setq corfu-auto t
                  corfu-auto-delay 0.1
                  corfu-auto-prefix 3)
            (defun corfu-enable-in-minibuffer ()
              "Enable Corfu in the minibuffer if `completion-at-point' is bound."
              (when (where-is-internal #'completion-at-point (list (current-local-map)))
                (setq-local corfu-auto nil) ;; Enable/disable auto completion
                (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
                            corfu-popupinfo-delay nil)
                (corfu-mode 1)))
            (add-hook 'minibuffer-setup-hook #'corfu-enable-in-minibuffer)
            (add-hook 'eshell-mode-hook (lambda ()
                                          (setq-local corfu-auto nil)
                                          (corfu-mode +1)))))

(use-package cape
  :after (corfu)
  :general (:states 'insert
                    "C-x C-f" #'cape-file))

(use-package kind-icon
  :after (corfu)
  :config (progn
            (setq kind-icon-default-face 'corfu-default)
            (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)))

(use-package tabnine
  :config (progn
            (setq tabnine-binaries-folder "~/.local/tabnine")
            (global-tabnine-mode)

            (define-key tabnine-completion-map (kbd "TAB") #'tabnine-accept-completion)
            (define-key tabnine-completion-map (kbd "<tab>") #'tabnine-accept-completion)

            (define-key tabnine-completion-map (kbd "M-f") #'tabnine-accept-completion-by-word)
            (define-key tabnine-completion-map (kbd "M-<return>") #'tabnine-accept-completion-by-line)
            (define-key tabnine-completion-map (kbd "C-e") #'tabnine-accept-completion-by-line)
            (define-key tabnine-completion-map (kbd "<right>") #'tabnine-accept-completion-by-line)

            (define-key tabnine-completion-map (kbd "C-g") #'tabnine-clear-overlay)
            (define-key tabnine-completion-map (kbd "M-[") #'tabnine-next-completion)
            (define-key tabnine-completion-map (kbd "M-]") #'tabnine-previous-completion))
  :init (progn
          (advice-add 'tabnine-start-process :around #'quiet)

          (add-hook 'kill-emacs-hook #'tabnine-kill-process)))

;;; Documentation

(use-package eldoc
  :defer 5
  :config (progn
            (setq eldoc-idle-delay 0.5
                  eldoc-echo-area-use-multiline-p nil)
            (global-eldoc-mode +1)))

(use-package eldoc-box
  :defer t)

(use-package ehelp
  :defer 15
  :general ([remap help-map] 'electric-help-map))

(use-package helpful
  :general (ehelp-map
            "k" #'helpful-key
            "v" #'helpful-variable
            "f" #'helpful-callable))

;;; Files

;;;; Auto-saving

(setq auto-save-default nil
      make-backup-files nil
      create-lockfiles nil)

;;;; Auto-reloading

(use-package autorevert
  :defer 1
  :config (progn
            (setq auto-revert-verbose nil
                  auto-revert-use-notify t)
            (global-auto-revert-mode t)))

(setq delete-by-moving-to-trash t)

(use-package goto-chg
  :defer 1)

;;;; TRAMP

(use-package tramp
  :defer 10
  :config (progn
            (add-to-list 'tramp-default-proxies-alist
                         '(nil "\\`root\\'" "/ssh:%h:"))
            (add-to-list 'tramp-default-proxies-alist
                         `(,(regexp-quote (system-name)) nil nil))))

;;; Directories

(setq dired-dwim-target t
      dired-recursive-copies 'top
      dired-listing-switches "-alh"
      dired-recursive-deletes (if delete-by-moving-to-trash
                                  'always
                                'top))

(add-hook 'dired-mode-hook
          (lambda ()
            (dired-hide-details-mode)))

(use-package dired-git-info
  :general (:keymaps 'dired-mode-map
                     :states 'normal
                     ")" #'dired-git-info-mode))

;;; Shells

(use-package eshell
  :defer 5
  :commands (eshell)
  :functions (eshell/pwd)
  :general (:keymaps 'eshell-command-map
                     "C-r" #'eshell-history-backwards
                     "C-s" #'eshell-history-forwards)
  :init (progn
          (with-eval-after-load 'evil-ex
            (evil-ex-define-cmd "esh[ell]" #'eshell)))
  :config (progn
            (setq eshell-prompt-function (lambda ()
                                           (concat (eshell/pwd) "\n$ "))
                  eshell-prompt-regexp "^[$][[:blank:]]"
                  eshell-cmpl-cycle-completions nil)
            (add-hook 'eshell-exit-hook (lambda ()
                                          (when (window-deletable-p)
                                            (delete-window))))))

(use-package eshell-toggle
  :commands (eshell-toggle)
  :general ("C-`" #'eshell-toggle))

(declare-function eshell-push-command "esh-buf-stack" (CMD))
(defun my-bind-esh-push ()
  (general-define-key
   :states '(normal insert)
   :keymaps 'local
   "M-q" #'eshell-push-command))

(use-package esh-buf-stack
  :after (eshell)
  :ghook ('eshell-mode-hook #'my-bind-esh-push)
  :config (setup-eshell-buf-stack))

(use-package esh-help
  :after (eshell)
  :config (setup-esh-help-eldoc))

(use-package eshell-fringe-status
  :after eshell
  :ghook '(eshell-mode-hook))

(use-package eshell-up
  :after (eshell))

(use-package shell
  :defer t
  :general (:keymaps 'shell-mode-map
                     "C-d" #'comint-delchar-or-maybe-eof))

(use-package comint
  :defer t
  :general (:keymaps 'comint-mode-map
                     "C-c C-l" #'counsel-shell-history))

;;; Editing

(setq-default indent-tabs-mode nil
              tab-width 2
              tab-always-indent 'complete)

(electric-pair-mode +1)

(use-package ws-butler
  :ghook ('prog-mode-hook))

(use-package dtrt-indent
  :commands (dtrt-indent-mode))

(use-package rainbow-mode
  :ghook ('(css-mode-hook
            conf-xdefaults-mode-hook)
          #'rainbow-mode))

(use-package expand-region
  :general (:states 'visual
                    "SPC" #'er/expand-region))

;;; Major modes

(setq major-mode-remap-alist '((c-mode . c-ts-mode)
                               (c++-mode . c++-ts-mode)
                               (c-or-c++-mode . c-or-c++-ts-mode)
                               (cmake-mode . cmake-ts-mode)
                               (csharp-mode . csharp-ts-mode)
                               (dockerfile-mode . dockerfile-ts-mode)
                               (java-mode . java-ts-mode)
                               (python-mode . python-ts-mode)
                               (ruby-mode . ruby-ts-mode)
                               (toml-mode . toml-ts-mode)
                               (yaml-mode . yaml-ts-mode)))

;;;; golang
(use-package go-mode
  :defer t)
(add-to-list 'major-mode-remap-alist '(go-mode . go-ts-mode))

;;;; nim
(use-package nim-mode
  :defer t
  :config (progn
            (add-to-list 'eglot-server-programs
                         '(nim-mode "nimlsp"))))

;;;; rust
(use-package rustic
  :mode (("\\.rs\\'" . rustic-mode))
  :if (package-installed-p 'rustic)
  :config (progn
            (setq rustic-format-on-save t)
            (with-eval-after-load 'flycheck
              (add-to-list 'flycheck-checkers 'rustic-clippy))))
(add-to-list 'major-mode-remap-alist '(rust-mode . rust-ts-mode))

;;;; js
(setq js-indent-level 2
      js-enabled-frameworks '(javascript))
(add-to-list 'major-mode-remap-alist '(js-mode . js-ts-mode))
(add-to-list 'major-mode-remap-alist '(json-mode . json-ts-mode))

;;;; typescript
(use-package typescript-mode
  :mode (("\\.tsx\\'" . tsx-ts-mode))
  :config (progn
            (setq typescript-indent-level 2
                  typescript-ts-mode-indent-offset 2)))
(add-to-list 'major-mode-remap-alist '(typescript-mode . typescript-ts-mode))

(autoload 'ansi-color-apply-on-region "ansi-color")
(defun colourise-compilation-buffer ()
  (ansi-color-apply-on-region compilation-filter-start (point-max)))
(add-hook 'compilation-filter-hook #'colourise-compilation-buffer)

;;;; shell
(general-add-hook 'sh-mode-hook
                  (lambda ()
                    (general-add-hook 'after-save-hook
                                      #'executable-make-buffer-file-executable-if-script-p :append :local)))

(add-to-list 'auto-mode-alist '("\\.env\\'" . conf-unix-mode))
(add-to-list 'auto-mode-alist '("\\.zsh\\'" . shell-script-mode))
(add-to-list 'auto-mode-alist '("zshenv\\'" . shell-script-mode))
(add-to-list 'auto-mode-alist '("zshrc\\'"  . shell-script-mode))
(setq sh-shell-file "/usr/bin/env zsh"
      sh-basic-offset 2)

(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)

(when (eq system-type 'gnu/linux)
  (setenv "SSH_AUTH_SOCK" (expand-file-name "ssh-agent" (getenv "XDG_RUNTIME_DIR"))))

(use-package fish-mode
  :mode (("\\.fish\\'" . fish-mode))
  :config (progn
            (setq fish-enable-auto-indent t)))

;;;; make
(general-add-hook 'makefile-mode-hook
                  (lambda ()
                    (setq-local indent-tabs-mode t)))

;;;; nix
(with-eval-after-load 'nix-mode
  (setq nix-mode-use-smie t
        nix-indent-function #'smie-indent-line))

(use-package nix-update
  :commands (nix-update-fetch))

;;;; gitlab-ci.yml
(with-eval-after-load 'git-gutter-fringe
  (fringe-helper-define 'flycheck-fringe-bitmap-double-arrow '(center repeated)
    "XXX....."))

(use-package gitlab-ci-mode-flycheck
  :ghook ('gitlab-ci-mode-hook (list #'gitlab-ci-mode-flycheck-enable
                                     #'flycheck-mode)))

;;;; *ignore
(use-package gitignore-mode
  :mode ((".dockerignore\\'" . gitignore-mode)))

;;;; lisps

(use-package racket-mode
  :ghook ('racket-mode-hook #'racket-xp-mode))

(use-package clojure-mode
  :defer t)
(use-package cider
  :defer t
  :after clojure-mode)
(use-package rainbow-delimiters
  :ghook '(clojure-mode-hook
           emacs-lisp-mode-hook))

(use-package lispy
  :ghook '(emacs-lisp-mode-hook
           clojure-mode-hook
           racket-mode-hook))

(use-package lispyville
  :ghook '(emacs-lisp-mode-hook
           clojure-mode-hook
           racket-mode-hook))

;;;; kubernetes
(setq k8s-site-docs-version "v1.13")

(use-package kubel
  :defer t)

(use-package kubel-evil
  :ghook 'kubel-mode-hook)

;;;; org

(use-package org
  :defer 10
  :config (progn
            (setq org-ellipsis "…"
                  org-modules nil
                  org-directory "~/Documents/org")))

;;;; web modes (html)

(use-package css-mode
  :defer t
  :config (progn
            (setq css-indent-offset 2)))
(add-to-list 'major-mode-remap-alist '(css-mode . css-ts-mode))

(use-package web-mode
  :mode (("\\.html?\\'" . web-mode)
         ("\\.html?.erb\\'" . web-mode))
  :config (setq web-mode-enable-auto-pairing nil
                web-mode-code-indent-offset 2
                web-mode-markup-indent-offset 2
                web-mode-css-indent-offset 2
                web-mode-style-padding 2
                web-mode-script-padding 2
                web-mode-engines-alist '(("go" . "\\.html?\\'"))))

(use-package emmet-mode
  :ghook '(web-mode-hook sgml-mode-hook))

;;; IDE features

(fringe-helper-define 'left-vertical-bar '(center repeated)
  "XXX.....")
(use-package flymake
  :defer 5
  :config (setq flymake-error-bitmap '(left-vertical-bar compilation-error)
                flymake-warning-bitmap '(left-vertical-bar compilation-warning)))
(use-package flymake-popon
  :after flymake
  :ghook ('flymake-mode-hook #'flymake-popon-mode))

(use-package flycheck
  :defer t
  :config (progn
            (setq flycheck-check-syntax-automatically '(save idle-buffer-switch mode-enabled)
                  flycheck-highlighting-mode 'sexps)
            (flycheck-define-error-level 'error
              :severity 100
              :compilation-level 2
              :overlay-category 'flycheck-error-overlay
              :fringe-bitmap 'left-vertical-bar
              :fringe-face 'flycheck-fringe-error
              :error-list-face 'flycheck-error-list-error)
            (flycheck-define-error-level 'warning
              :severity 10
              :compilation-level 1
              :overlay-category 'flycheck-warning-overlay
              :fringe-bitmap 'left-vertical-bar
              :fringe-face 'flycheck-fringe-warning
              :warning-list-face 'flycheck-error-list-warning)
            (flycheck-define-error-level 'info
              :severity -10
              :compilation-level 0
              :overlay-category 'flycheck-info-overlay
              :fringe-bitmap 'left-vertical-bar
              :fringe-face 'flycheck-fringe-info
              :info-list-face 'flycheck-error-list-info)))

;;; Projects

(use-package project
  :general (:keymaps 'project-prefix-map
                     "s" #'project-search)
  :config (progn
            (with-eval-after-load 'evil-ex
              (evil-ex-define-cmd "pesh[ell]" #'project-eshell)
              (evil-ex-define-cmd "pb" #'project-switch-to-buffer)
              (evil-ex-define-cmd "psw[itch]" #'project-switch-project))))

(use-package ibuffer-project
  :config (progn
            (defun ibuffer-project-set-filter-groups ()
              (setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))
              (unless (eq ibuffer-sorting-mode 'project-file-relative)
                (ibuffer-do-sort-by-project-file-relative)))
            (add-hook 'ibuffer-hook #'ibuffer-project-set-filter-groups)
            (add-to-list 'ibuffer-project-root-functions '(file-remote-p . "Remote"))
            (setq ibuffer-formats '((mark modified read-only locked " "
                                          (name 18 18 :left :elide)
                                          " "
                                          (size 9 -1 :right)
                                          " "
                                          (mode 16 16 :left :elide)
                                          " " project-file-relative)))))
(use-package consult-ghq
  :defer 5
  :general (:keymaps 'project-prefix-map
                     "o" #'consult-ghq-switch-project)
  :config (progn
            (setq consult-ghq-grep-function #'consult-grep
                  consult-ghq-find-function #'consult-find)))

(use-package envrc
  :defer 2
  :config (progn
            (envrc-global-mode)))

(use-package magit
  :defer 5
  :commands (magit-status magit-dispatch)
  :init (progn
          (customize-set-value 'magit-auto-revert-mode nil))
  :general ([remap project-vc-dir] #'magit-project-status)
  :config (progn
            (setq magit-section-visibility-indicator nil
                  magit-diff-refine-hunk 'all
                  magit-display-buffer-function #'magit-display-buffer-fullcolumn-most-v1)
            (remove-hook 'magit-status-sections-hook 'magit-insert-tags-header)
            (remove-hook 'magit-section-highlight-hook 'magit-section-highlight)
            (remove-hook 'magit-section-highlight-hook 'magit-section-highlight-selection)
            (remove-hook 'magit-section-highlight-hook 'magit-diff-highlight)
            (require 'magit-extras)))

(use-package magit-filenotify
  :after magit
  :ghook '(magit-status-mode-hook))

(use-package git-gutter-fringe
  :defer 5
  :config (progn
            (add-hook 'magit-post-refresh-hook
                      #'git-gutter:update-all-windows)
            ;; places the git gutter outside the margins.
            (setq-default fringes-outside-margins nil)
            ;; thin fringe bitmaps
            (fringe-helper-define 'git-gutter-fr:added '(center repeated)
              ".XXX....")
            (fringe-helper-define 'git-gutter-fr:modified '(center repeated)
              ".XXX....")
            (fringe-helper-define 'git-gutter-fr:deleted '(center repeated)
              ".XXX....")
            (setq git-gutter-fr:side 'left-fringe)
            (global-git-gutter-mode 1)))

(use-package vc-msg
  :defer 30)

(use-package git-timemachine
  :commands (git-timemachine))

(use-package editorconfig
  :defer 2
  :config (editorconfig-mode +1))

(setq-default ispell-dictionary "en_GB-ise-w_accents")
(setq ispell-extra-args '("--sug-mode=ultra" "--camel-case"))

(use-package flyspell
  :ghook ('prog-mode-hook #'flyspell-prog-mode)
  :ghook ('text-mode-hook #'flyspell-mode))

(use-package feature-mode
  :defer t
  :config (progn
            (setq feature-cucumber-command "cucumber-js {options} \"{feature}\"")))

(use-package treemacs
  :defer t
  :config (progn
            (setq treemacs-no-png-images t)))

(use-package eglot
  :defer 3
  :general (:states 'normal :keymaps 'eglot-mode-map
                    "gd" #'xref-find-definitions
                    "gr" #'xref-find-references
                    "C-t" #'xref-pop-marker-stack)
  :ghook ('(typescript-ts-mode-hook
            dockerfile-ts-mode-hook
            yaml-ts-mode-hook
            js-ts-mode-hook
            css-ts-mode-hook
            go-ts-mode-hook
            lua-mode-hook
            nim-mode-hook
            scss-mode-hook
            html-mode-hook
            nix-mode-hook
            haskell-mode-hook)
          #'eglot-ensure)
  :config (progn
            (defun my/setup-eglot-eldoc ()
              (push 'flymake-eldoc-function eldoc-documentation-functions))
            (add-hook 'eglot-managed-mode-hook 'my/setup-eglot-eldoc)
            (setq-default eglot-workspace-configuration
                          '(:yaml (:keyOrdering nil)
                                  :nix (:autoArchive t))
                          eglot-ignored-server-capabilities '(:documentHighlightProvider))))

(use-package consult-eglot
  :commands (consult-eglot-symbols)
  :after eglot)

;; Inside a javascript project, it's common to install tools locally to
;; the project.  This will allows emacs to find their executables.

(use-package add-node-modules-path
  :config (setq add-node-modules-max-depth 6)
  :ghook ('(feature-mode-hook js2-mode-hook json-ts-mode-hook typescript-ts-mode-hook) #'add-node-modules-path))

;;;; Reformat on save

(use-package format-all
  :defer 10
  :ghook ('(clojure-mode-hook
            dockerfile-ts-mode-hook
            emacs-lisp-mode-hook
            json-ts-mode-hook
            markdown-mode-hook
            sql-mode-hook
            toml-mode-hook))
  :gfhook #'format-all-ensure-formatter
  :init (progn
          (advice-add 'format-all-ensure-formatter
                      :around #'quiet)
          (defun turn-off-format-all-mode ()
            (format-all-mode -1)))
  :config (progn
            (setq format-all-show-errors 'never)))

(use-package apheleia
  :defer 10
  :config (progn
            (setf (alist-get 'shfmt apheleia-formatters)
                  '("shfmt"))
            (setf (alist-get 'nixfmt apheleia-formatters)
                  '("nixpkgs-fmt"))
            (add-hook 'apheleia-mode-hook #'turn-off-format-all-mode))
  :init (progn
          (apheleia-global-mode +1)))

;;; Take me to my leader

(my-leader-def
  "" nil
  "`" #'eshell-toggle
  "h" '(:keymap ehelp-map :package ehelp)
  "w" '(:keymap evil-window-map :package evil)
  "x" '(:keymap ctl-x-map)
  "c" (general-simulate-key "C-c")
  "j" #'consult-eglot-symbols
  "q" #'evil-delete-buffer
  "p" '(:keymap project-prefix-map :package project)
  "v" #'split-window-right
  "o" #'other-window
  "u" #'universal-argument
  ";" #'execute-extended-command
  "bb" #'consult-buffer
  "bx" #'kill-this-buffer
  "br" #'revert-buffer
  "bk" #'kill-buffer
  "dd" #'dired
  "D" #'consult-dir
  "e" '(:keymap envrc-command-map :package envrc)
  "fs" #'save-buffer
  "ff" #'find-file
  "fw" #'write-file
  "fd" #'delete-file
  "fr" #'crux-rename-file-and-buffer
  "gs" #'magit-status
  "gm" #'vc-msg-show
  "gg" #'magit-dispatch
  "gn" #'git-gutter:next-hunk
  "gp" #'git-gutter:previous-hunk
  "gi" #'git-gutter:popup-hunk
  "gs" #'git-gutter:stage-hunk
  "go" #'git-gutter:revert-hunk
  "gt" #'git-timemachine
  "gl" #'magit-log-buffer-file
  "bi" #'ibuffer
  "bz" #'bury-buffer
  "iu" #'insert-char
  "xe" #'eval-last-sexp
  "xx" #'eval-defun
  "xi" #'consult-imenu)

(let ((mail-config (expand-file-name "mail.el" user-emacs-directory)))
  (if (file-readable-p mail-config)
      (load mail-config :noerror :nomessage)))


;; # Local Variables:
;; # flycheck-disabled-checkers: 'emacs-lisp-checkdoc
;; # End: