;;; init.el --- my emacs configuration  -*- lexical-binding: t; -*-

;; Todo:
;; - Look at completion-preview-mode
;; - Look at grep-use-headings
;; - Look at elpa-openwith

;; (profiler-start 'cpu+mem)

(add-to-list 'load-path (expand-file-name (concat user-emacs-directory "site-lisp")))

(add-to-list 'exec-path "/home/noa/.config/Scripts")

(use-package use-package
  :custom (use-package-compute-statistics t))

;; Give me stats on garbage collection and startup time
(setopt garbage-collection-messages t)
(add-hook 'emacs-startup-hook
	  #'(lambda ()
	      (message (format "Initialised in %s seconds with %s garbage collections." (emacs-init-time) gcs-done))))

;; Use the gcmh to collect garbage when i'm not using emacs
(use-package gcmh
  :disabled t
  :ensure t
  :custom
  (gcmh-mode t))

;;; Modern keybindings
;; A lot of people say that you get used to emacs keybindings over time, and this is true.  But i find it hard to maintain two sets of keybindings in my head: those for emacs, and those for everything else.  So i think that it's best to make emacs use the same bindings as everything else.  For now, we do that with wakib mode.
(use-package wakib-keys
  :disabled t
  :ensure t
  :custom
  (wakib-keys t)

  :hook
  ((after-change-major-mode . wakib-update-major-mode-map)
   (menu-bar-update . wakib-update-minor-mode-maps))

  :bind (:map isearch-mode-map
              ("C-f" . isearch-repeat-forward)
              ("C-S-f" . isearch-repeat-backward)
              ("M-;" . isearch-repeat-forward)
              ("M-:" . isearch-repeat-backward)
              ("C-v" . isearch-yank-kill)
              ("M-d" . isearch-delete-char)))

;; It is often useful to be able to run a command while i am already in the process of running a command in the minibuffer.  This is rarely two extended commands; usually it is completion.
(use-package mb-depth
  :custom
  (minibuffer-depth-indicate-mode t))

;; This is my emacs theme.  It's a monochrome theme which, unlike most monochrome themes, really does have only two colours.  I define a few faces, and set every other face as one of them.  There are a few things i want to do with it before i make it properly public: make the colours configurable and able to update on the fly, and in general iron out some of the janky parts.  A few things defined it are quite specific to this configuration, like the way i define the borders for the tab and header bars, and there is no mode line configuration because i don't use it.
(use-package tubthumping-theme
  :load-path"~/Documents/Projects/2024-09-17 Tubthumping theme"
  :config
  (load-theme 'tubthumping t))

;;; Fonts
;; Using a proportional font is the right way to do things, but emacs is very old and comes from a time before the innovation of legibility.  As a result, there are some things that require a monospaced font, so i set one here.
(custom-set-faces
 '(default ((t (:family "Noto Serif" :height 120))))
 '(variable-pitch ((t (:family "Noto Serif" :height 120))))
 '(fixed-pitch ((t (:family "Noto Sans Mono" :height 120)))))

;; Use simplified not korean hanzi
(when (display-graphic-p)
  (dolist (charset '(han cjk-misc))
    (set-fontset-font t charset (font-spec :family "Noto Serif CJK SC"))))

;; Prioritise fsd and noto emoji over coloured variants
(when (display-graphic-p)
  (set-fontset-font t 'emoji (font-spec :family "FSD Emoji") nil 'prepend)
  (set-fontset-font t 'emoji (font-spec :family "Noto Emoji") nil 'append))

;; While we're here, let's set up emoji input.
(use-package emoji
  :bind ("C-." . emoji-search))

;; Update the calendar.  We want weeks to start on a monday, the first day of the week.  Holidays should be highlighted, and the date format should put the year first.
(use-package calendar
  :defer t
  :custom
  (calendar-week-start-day 1)
  (calendar-date-style 'iso)

  :config
  ;; Ensure the calendar always displays at the bottom of the screen, rather than wrapping weirdly and looking bad when it shows up in a side window.
  (add-to-list 'display-buffer-alist
             '("\\*Calendar\\*"
               (display-buffer-in-side-window)
               (side . bottom))))



;; This is my own version of fixed-pitch, which has some changes to it.  No hooks are added by default.  Updating the whitelist automatically applies the hooks.  And there is no functionality for changing the cursor type.
(use-package fixed-pitch
  :custom
  (fixed-pitch-whitelist-hooks '(calendar-mode-hook
				 dired-mode-hook
				 ibuffer-mode-hook
				 magit-mode-hook
				 profiler-report-mode-hook
				 jabber-roster-mode-hook
				 mu4e-headers-mode-hook
				 proced-mode-hook
				 rmail-summary-mode-hook
				 ebib-index-mode-hook
                                 which-key-init-buffer-hook
                                 ebib-entry-mode-hook
                                 eshell-mode-hook
                                 minibuffer-setup-hook
                                 qrencode-mode-hook)))

(use-package transient
  :defer t
  :custom
  ;; Ensure that transients look nice with a variable pitch font.
  (transient-align-variable-pitch t))

;; Squeeze text into a more legible sliver.
(use-package visual-fill-column
  :ensure t
  :custom
  (visual-fill-column-enable-sensible-window-split t)
  (visual-fill-column-fringes-outside-margins nil) ; Keep fringes on the inside so relevant icons are in the right place
  (visual-fill-column-center-text t)
  :hook
  ((text-mode . visual-line-fill-column-mode)
   (eww-after-render . visual-line-fill-column-mode))
  )
;; (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)

;; Doesn't indent nicely with proportional fonts
;; (use-package adaptive-wrap :ensure t)
;; (add-hook 'visual-fill-column-mode-hook #'visual-wrap-prefix-mode)

;; At the moment, explicitly disabling the menu bar and tool bar does nothing, because i already set there to be no lines displayed for the tool and menu bars in my early-init.el file.
(setopt menu-bar-mode nil
	tool-bar-mode nil)

;; Make tooltip information appear consistently in the echo area
(setopt tooltip-mode nil)

;; (Almost) immediately show which chords in a key sequence i have already pressed.
(setopt echo-keystrokes 0.1)

(use-package paren
  :custom
  (show-paren-mode t)
  (show-paren-when-point-in-periphery t) ; Highlight nearby paren if only whitespace separates paren from point
  (show-paren-when-point-inside-paren t) ; Highlight paren if point is inside, not just on
  )

;; Syntax highlighting
(setopt global-font-lock-mode t)
(setopt font-lock-maximum-decoration nil)

;;; Further options
(setopt inhibit-startup-screen t
	mouse-drag-and-drop-region nil
	mouse-yank-at-point t
	;; deleting should be an explicit action
	delete-selection-mode nil)

;; Shift click to select region with the mouse (https://christiantietze.de/posts/2022/07/shift-click-in-emacs-to-select/)
(global-set-key (kbd "S-<down-mouse-1>") #'mouse-set-mark)
;; Doesn't work great with a proportional font but could still be useful.
(global-set-key (kbd "C-S-<down-mouse-1>") #'mouse-drag-region-rectangle)

;; Nicer tables
(use-package valign
  :after markdown-mode
  :ensure t
  :hook
  ((markdown-mode . valign-mode))
  :custom
  (valign-fancy-bar t)
  (valign-max-table-size 0))

;; Markdown
(use-package markdown-mode
  :ensure t
  :mode ("\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" . markdown-mode)
  :custom
  (markdown-disable-tooltip-prompt t) ; When inserting a link, only prompt for url and link text
  (markdown-enable-html nil) ; I don't believe markdown should have html
  (markdown-hide-urls t) ; Make inline urls look a bit neater
  (markdown-url-compose-char ?~)

  ;; :hook ((markdown-mode . noa/set-buffer-name-to-markdown-title))
  )

;; Use abbrev as a simple autocorrect mode.  M-x list-abbrevs for stats.
;; Check  ~/.config/emacs/abbrev_defs to update any mistakes
(use-package abbrev
  :hook (text-mode . abbrev-mode))

;; A nicer package for spellchecking
(use-package jinx
  :ensure t
  :hook (text-mode . jinx-mode)
  :bind
  ([remap ispell-word] . jinx-correct) ;; replaces ispell-word
  ("C-M-$" . jinx-languages)
  :config
  (put 'jinx-overlay 'mouse-face '(jinx-misspelled highlight)) ;; Use standard mouse face for jinx overlays
  (add-to-list 'jinx-exclude-regexps '(t "\\cc")) ;; Don't check chinese characters
  )

;; Automatically creates an abbrev for words i correct, so i don't make the same mistake twice
(defun jinx--add-to-abbrev (overlay word)
  "Add abbreviation to `global-abbrev-table'.
The misspelled word is taken from OVERLAY.  WORD is the corrected word."
  (let ((abbrev (buffer-substring-no-properties
                 (overlay-start overlay)
                 (overlay-end overlay))))
    (message "Abbrev: %s -> %s" abbrev word)
    (define-abbrev global-abbrev-table abbrev word)))
(advice-add 'jinx--correct-replace :before #'jinx--add-to-abbrev)

(use-package help
  :defer t
  :custom
  (help-window-select t) ;; Make it easier to close help windows
  ;; When opening source files from a help window, use the same window
  (help-window-keep-selected t))

;; Despite the name, eww is a delight to use for text-heavy websites.
(use-package browse-url
  :defer t
  :custom
  (browse-url-browser-function 'eww-browse-url)
	(browse-url-secondary-browser-function 'browse-url-default-browser))

;; I've had no use for cookies in eww
(use-package url-cookie
  :defer t
  :custom
  (url-cookie-trusted-urls '())
  (url-cookie-untrusted-urls '(".*")))

(use-package shr
  :defer t
  :custom
  (shr-cookie-policy nil)
  (shr-use-colors nil) ;; I like the colours i already have set
  (shr-max-width nil) ;; Don't insert hard linebreaks to wrap paragraphs
  (shr-bullet " • ")
  (shr-discard-aria-hidden t))	

(use-package eww
  :defer t

  :custom
  ;; Better buffer naming for eww buffers.  See the help for a function which gives the page title and the url.
  (eww-auto-rename-buffer 'title))

;; Goto address mode makes urls and email address in a buffer clickable.  I want these clickable links to look like links, because that's what they are.  The two mouse face variables are what face is used on hover, which at the moment i ignore.  It might also be worth setting them to 'highlight.
(use-package goto-addr
  :custom
  (global-goto-address-mode nil)
  (goto-address-mail-face 'link)
  (goto-address-mail-mouse-face 'highlight)
  (goto-address-url-face 'link)
  (goto-address-url-mouse-face 'highlight))

(use-package eldoc
  :custom
  (global-eldoc-mode t))

;; Use a bar cursor and blink it and don't stop blinking it.
(setopt cursor-type 'bar
        blink-cursor-mode t
        blink-cursor-interval 0.7)

;; I have quite a bit of storage and also make bad decisions regularly
;; People who have used computers in the last forty years or so will likely be familiar with this innovation.
(setopt delete-by-moving-to-trash t)

(use-package dired
  :defer t
  :custom
  (dired-recursive-deletes 'always) ;; Don't ask me things
  (dired-recursive-copies 'always) ;; Don't ask me things
  (dired-dwim-target t) ;; Synergy between two dired windows

  ;; -l is required by dired
  ;; -a to also show dotfiles
  ;; -v to sort numebrs properly
  ;; -F to visually distinguish different types of file
  ;; -h for human readable file sizes
  ;; I won't explain the meaning of the long flag.
  (dired-listing-switches "-alvFh --group-directories-first")

;; By default, don't show dired details
:hook (dired-mode . dired-hide-details-mode))

;; Show the recursive size of directories
(use-package dired-du
  :disabled t
  :ensure t
  :after dired
  :custom
  (dired-du-size-format t)
  :hook (dired-mode . dired-du-mode))

;; Some tramp settings.
(setopt remote-file-name-inhibit-locks t)
(use-package tramp
  :defer t
  :custom
(tramp-inline-compress-start-size 1000)
(tramp-verbose 3))
;; (add-to-list 'tramp-remote-path 'tramp-own-remote-path)

;; I only use git, and this apparently speeds up tramp.
(use-package vc-hooks
  :after vc
  :custom
(vc-handled-backends '(Git)))
;; (setopt vc-handled-backends '(Git RCS CVS SVN SCCS SRC Bzr Hg))

;;  It seems that tramp can also be made faster with these .ssh/config settings.

;; Host *
;;      ControlMaster auto
;;      ControlPath ~/.ssh/master-%h:%p
;;      ControlPersist 10m
;;      ForwardAgent yes
;;      ServerAliveInterval 60

;; Don't override display actions when i manually initiate a buffer switch.
(setopt switch-to-buffer-obey-display-actions t)

;; History
(setopt history-length 250
        kill-ring-max 25)

(setopt savehist-file "~/.config/emacs/savehist")
(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))
(setopt savehist-mode t)

(setopt window-divider-mode t)
(setopt window-divider-default-right-width 1)
(setopt window-divider-default-bottom-width 1)
(setopt window-divider-default-places t)

;; Taken from configuration for the vertico stack:
;; Add prompt indicator to `completing-read-multiple'.
;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
(defun crm-indicator (args)
  (cons (format "[CRM%s] %s"
		(replace-regexp-in-string
		 "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
		 crm-separator)
		(car args))
	(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)

;; Do not allow the cursor in the minibuffer prompt
(setopt minibuffer-prompt-properties
	'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

;; Support opening new minibuffers from inside existing minibuffers.
(setopt enable-recursive-minibuffers t)

;; Whether to drop into the debugger on any error.  This seems cool, but in practice is a bit annoying.
(setopt debug-on-error nil)

;; Hide commands in M-x which do not work in the current mode.
(setopt read-extended-command-predicate 'command-completion-default-include-p)

(setopt recentf-max-menu-items 25
        recentf-save-file "~/.config/emacs/recentf"
        recentf-mode t
        bookmark-default-file "~/.config/emacs/bookmarks")

;; My brain is not ready for advanced emacs undo
(setopt undo-no-redo t)
(global-unset-key (kbd "C-z"))
(global-set-key (kbd "C-z") 'undo)
(global-set-key (kbd "C-S-z") 'undo-redo)

;; Saving
;; Backups are pointless in long emacs sessions, but autosaves are useful.
(setopt remote-file-name-inhibit-auto-save t)
(setopt remote-file-name-inhibit-auto-save-visited t)
(setopt backup-directory-alist '(("." . "~/.config/emacs/backups/"))
	make-backup-files nil
	backup-by-copying t
	create-lockfiles nil
	auto-save-mode t
	auto-save-interval 6  ;; every six keystrokes
	auto-save-timeout 5 ;; every 5 seconds
	auto-save-default t
	auto-save-no-message t
	save-silently t
	version-control t
	;; this will auto save to the current file
	auto-save-visited-mode t)
(add-hook 'focus-out-hook (lambda () (interactive) (save-some-buffers t)))
(add-hook 'mouse-leave-buffer-hook (lambda () (interactive) (save-some-buffers t)))

;; Unfill commands
(defun unfill-paragraph ()
  "Takes a multi-line paragraph and makes it into a single line of text."
  (interactive)
  (let ((fill-column (point-max)))
    (fill-paragraph nil)))
(global-set-key (kbd "M-Q") #'unfill-paragraph)

;; C-l from top to bottom
(setopt recenter-positions '(top middle bottom))
;; Always give me a line of text when i'm near the edges of the screen
(setopt scroll-margin 1)

;; This doesn't work well sometimes for some reason, so i've disabled it for now.
;; (setopt pixel-scroll-precision-mode t
;;         pixel-scroll-precision-use-momentum t)

;; Remember my position in files
(setopt save-place-mode t)

;; Use single character abbreviations to answer prompts.
(setopt read-answer-short t)
(setopt use-short-answers t)

;; Disable disabled commands
(setq disabled-command-function nil)

;; Don't save changes in the customize interface
(setopt custom-file (make-temp-file "custom"))

;; Scroll along with text in compilation mode, and stop scrolling at the first error.
(setopt compilation-scroll-output 'first-error)

;; Don't advertise gnu on startup
(setq inhibit-startup-echo-area-message "noa")  ;; #userfreedom

;; Better buffer naming
(setopt uniquify-after-kill-buffer-p t
	uniquify-buffer-name-style 'forward
	uniquify-ignore-buffers-re "^\\*"
	uniquify-separator "/")

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

;; This puts some help in the minibuffer when we leave the point on some interactive text.
(setopt help-at-pt-display-when-idle 'never)

;; Better support for long lines.
(setopt global-so-long-mode t)

;; This will stop us being prompted before killing a buffer with a running process:
(setopt kill-buffer-query-functions
	(remq 'process-kill-buffer-query-function
	      kill-buffer-query-functions))

;; Automatically revert buffers when they change on disk.  This doesn't apply to tramp.
(setopt global-auto-revert-mode t)
(setopt global-auto-revert-non-file-buffers t)

;; This behaviour changes how we visit symlinks.
(setopt find-file-visit-truename t)
(setopt vc-follow-symlinks 'ask)

;; Clicking around
;; Allow for slightly sloppier clicking.
(setopt mouse-prefer-closest-glyph t)

(use-package consult
  :ensure t
  :bind
  (([remap switch-to-buffer] . consult-buffer)
   ([remap yank-pop] . consult-yank-pop)
   ([remap goto-line] . consult-goto-line)
   ([remap imenu] . consult-imenu)
   ([remap info] . consult-info)
   ([remap repeat-complex-command] . consult-complex-command)
   ([remap bookmark-jump] . consult-bookmark))
  :custom
  ;; Use consult for completion at point
  (completion-in-region-function 'consult-completion-in-region))

;; Annotations for completing-read
;; (package-ensure 'marginalia)
(use-package marginalia
  :ensure t
  :defer t
  :custom
  (marginalia-mode t)
  (marginalia-max-relative-age most-positive-fixnum) ;; Always show the relative age of file
  )

;; Minibuffer candidate completion
;; Display completion candidates vertically
(use-package vertico
  :ensure t
  :custom
  (vertico-mode t)
  (vertico-count 12) ;; Use about half the screen to show candidates
  (vertico-group-format #("%s " 0 3 (face vertico-group-title))) ;; Don't put a vertical line across group titles
  (vertico-cycle t)) 

;; Interact with vertico with the mouse.
(use-package vertico-mouse
  :after vertico
  :custom (vertico-mouse-mode t))

;; More comfortable directory navigation
(use-package vertico-directory
  :after vertico
  :bind (:map vertico-map
              ("RET" . vertico-directory-enter)
              ("<backspace>" . vertico-directory-delete-char)
              ("C-<backspace>" . vertico-directory-delete-word))
  ;; If i type ~/ etc in a find-file prompt, get rid of the preceding directory names for a cleaner look.
  :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))

;; Some settings for nicer completion with the default emacs completion buffer.  I don't use this, because i use vertico.
;; (setopt completion-auto-help 'lazy
;;         completion-auto-select 'second-tab
;;         completion-show-help nil
;;         completions-sort nil
;;         completions-header-format nil)

;; Completion styles
;; When given a prompt to select from a list of candidates, there are quite a lot of things we can tweak to improve the experience.

;; I don't actually know if these make any difference when i've specified a different completion style.
(setopt read-buffer-completion-ignore-case t)
(setopt read-file-name-completion-ignore-case t)
(setopt completion-ignore-case t)

;; Basic matches candidates with the same text before the point, and the text after the point as a substring.
;; Orderless takes any number of space separated components and displays candidates that much every component in any order.
(use-package orderless
  :ensure t
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles basic partial-completion)))))

;; Make leading whitespace a uniform width, so indentation still works with proportional fonts
(require 'elastic-indent)
(add-hook 'prog-mode-hook #'elastic-indent-mode)

;; Change the width of tab characters within lines to make subsequent lines form a table-like layout.
(require 'elastic-table)
(add-hook 'prog-mode-hook #'elastic-table-mode)

;; Use tab for completion if the region is already indented
(setopt tab-always-indent 'complete)

;; Indenting lisp with tabs is horrible.  H/t acdw
(defvar space-indent-modes '(emacs-lisp-mode
                             lisp-interaction-mode
                             lisp-mode
                             scheme-mode
                             python-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))))

;; Make sentence navigation commands work for people who are wrong
(setopt sentence-end-double-space nil)

;; If i write a script, i will always run chmod +x after saving it.  This command means i don't have to do that.
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)

;; We are on a unix system, so it makes sense to end files in the unix system way.  I'm surprised this isn't the default.
(setopt require-final-newline t)

;; Zap up to char
(global-set-key (kbd "M-z") 'zap-up-to-char)

;; Insert date and time at point
;; Sometimes it's useful to be able to add a timestamp.  I use this for notes.  Use the same binding as notepad.exe.
(defun noa/insert-date-time ()
  (interactive)
  (insert (format-time-string "<%Y-%m-%d>")))

(global-set-key (kbd "<f5>") 'noa/insert-date-time)

;; Improve incremental search
(use-package isearch
  :bind (:map isearch-mode-map
              ("<return>" . noa/isearch-done-goto-start) ; always put point at the beginning of the match
              ("C-g" . isearch-cancel) ; always quit on C-g
              ("C-o" . isearch-occur))
  :init (defun noa/isearch-done-goto-start (&optional nopush edit)
          "End isearch at the start of the match."
          (interactive)
          (funcall #'isearch-done nopush edit)
          (when isearch-forward (goto-char isearch-other-end)))
  :custom
  (isearch-lax-whitespace t)
  (isearch-lazy-count t)
  (isearch-allow-motion t)
  (isearch-repeat-on-direction-change t)
  (isearch-wrap-pause 'no))

;; I don't like the fact that isearch doesn't respect the minibuffer
(use-package isearch-mb
  :ensure t
  :custom
  (isearch-mb-mode t))

(use-package visual-regexp
  :ensure t)

;; Make window management commands easier to press
(global-set-key (kbd "M-o") 'other-window)
(global-set-key (kbd "M-1") 'delete-other-windows)
(global-set-key (kbd "M-2") 'split-window-below)
(global-set-key (kbd "M-3") 'split-window-right)
(global-set-key (kbd "M-0") 'delete-window)

(global-set-key (kbd "C-x k") 'kill-current-buffer)

;; Window management
;; My computer has a small screen, so i find that it's more beneficial for me to split the frame into columns, so i get more context.  However, splitting in this way only gives me a (window-width) of 61, so emacs will always split into vertically stacked windows.  By setting this to 80, the first split should always be vertical.
(setopt split-width-threshold 80)

;; Full text search of every file in my home directory.
(defun noa/consult-rg-home ()
  (interactive)
  (consult-ripgrep "~/"))
(global-set-key (kbd "M-<menu>") #'noa/consult-rg-home)

(setopt shell-file-name "/bin/sh")

(defun snarf-song (url)
  (interactive "sYoutube url:")
  (async-shell-command
   (concat "yt-dlp -x --audio-format=mp3 -o "
           (shell-quote-argument "~/media/music/%(title)s [%(id)s].%(ext)s")
           " "
           (shell-quote-argument url))))

;;; Other
(setopt confirm-kill-emacs 'y-or-n-p)

(global-set-key (kbd "C-=") #'calc)

;;; Email

;; I keep coming back to rmail, despite its many, many warts.
(use-package rmail
  :custom
  (rmail-primary-inbox-list
   (directory-files "~/Documents/Library/Mail/inbox" t "^[^\.]"))
  (rmail-file-name "~/Documents/Library/Mail/rmail.mbox")
  (rmail-user-mail-address-regexp
   (rx "noa@noa.pub"))
  (rmail-mime-prefer-html nil)
  (rmail-mime-attachment-dirs-alist '(("" "~/media")))
  (rmail-displayed-headers
   (rx bol (or "To" "Cc" "From" "Date" "Subject") ":"))
  (rmail-secondary-file-directory "~/Documents/Library/Mail/archive/")
  (rmail-secondary-file-regexp "\\.mbox\\'")
  (rmail-delete-after-output t)
  (rmail-default-file "~/Documents/Library/Mail/archive/")
  (mail-dont-reply-to-names rmail-user-mail-address-regexp)

  (rmail-display-summary nil)
  (rmail-redisplay-redisplay-summary t)
  (rmail-summary-line-count-flag nil)
  (rmail-summary-window-size 12)

  :hook
  ((rmail-show-message . visual-line-fill-column-mode)))

(use-package message
  :defer t
  :init
  (defun noa/message-default-headers ()
    (format "Fcc: ~/Documents/Library/Mail/outbox/%s.mbox"
            (format-time-string "%Y-%m")))

  :custom
  (message-default-headers #'noa/message-default-headers)
  (message-fill-column nil) ;; Don't enable auto-fill
  (user-full-name "noa")
  (user-mail-address "noa@noa.pub")
  (message-signature t)
  (message-signature-file "~/.config/signature")
  (message-send-mail-function 'message-send-mail-with-sendmail)
  (sendmail-program (executable-find "msmtpq"))
  (message-sendmail-extra-arguments '("--read-envelope-from"))
  (message-sendmail-envelope-from 'header)
  (message-kill-buffer-on-exit t)
  (message-sendmail-f-is-evil t)
  (message-forward-as-mime t)
  (message-interactive t)
  (message-auto-save-directory "~/Documents/Library/Mail/drafts"))

;; Environment variables
(setenv "PAGER" "cat")
(setenv "TERM" "dumb")
(setenv "NO_COLOR")
(setenv "GPG_AGENT_INFO" nil)

;; Dictionary
;; Emacs has built in support for interfacing with dictd.  With dictd and some dictionaries installed on debian, this works fine out of the box.
;; Unfortunately, dictionaries in this format tend to be hard wrapped and there isn't a lot of coverage outside of english
(setopt dictionary-search-interface nil)

;; Dictionary tooltip mode lets me hover over a word to view the definition.
(setopt dictionary-tooltip-mode t)

;; Unsorted
;; Just a few settings i haven't put into another category yet.
(setopt save-interprogram-paste-before-kill t
	mouse-yank-at-point t
	require-final-newline t
	load-prefer-newer t
	ediff-window-setup-function 'ediff-setup-windows-plain)

;; Qr codes
(use-package qrencode
  :ensure t)

;;; Bibliography management
(use-package ebib
  :ensure t
  :defer t
  :custom
  (ebib-bibtex-dialect 'biblatex)
  (ebib-index-columns '(("Author/Editor" 40 t)
                        ("Year" 6 t)
                        ("Title" 50 t)
                        ("Entry Key" 40 t)))
  ;; Put notes in my markdown notes directory.  I still need to find a way to make ebib-notes-name-transform-function return the appropriate date-based filename format.
  (ebib-notes-storage 'one-file-per-note)
  (ebib-notes-directory "~/Documents/Notes/References")
  (ebib-notes-show-note-method 'all)
  (ebib-notes-file-extension "md")
  (ebib-notes-template "# %T\n\n%%?")
  )

;; https://juanjose.garciaripoll.com/blog/managing-bibliographies-from-emacs/index.html


(defun noa/yank-buffer-file-name ()
  (interactive)
  (when (buffer-file-name)
    (kill-new (file-name-nondirectory (buffer-file-name)))))

(use-package ctrlj
  :load-path "~/Documents/Projects/2024-10-08 Ctrlj"
  :init (global-unset-key (kbd "C-j"))
  :bind* (("<f2>" . ctrlj/open-note)
         ("C-j C-j" . ctrlj/open-note)
         ("C-j C-n" . ctrlj/new-note)
         ("C-j C-l" . ctrlj/insert-link-to-note)
         ("C-j C-d" . ctrlj/open-todays-diary)
         ("C-j C-a" . ctrlj/view-agenda)
         ("C-j C-o" . ctrlj/jump-to-other-related-note)))

(defun noa/consult-rg-notes ()
  (interactive)
  (consult-ripgrep "~/Documents/Notes"))
(global-set-key (kbd "C-j C-s") #'noa/consult-rg-notes)

(defun noa/agenda ()
  "List agenda."
  (interactive)
  (async-shell-command "~/.config/Scripts/agenda" "*agenda*"))

;; so i can search for synonyms without using the internet
(use-package wordnut
  :if (executable-find "wordnet")
  :ensure t
  :defer t)

;; Experiment with embark
(use-package embark
  :ensure t

  :bind
  (("C-<return>" . embark-act)
   ("M-<return>" . embark-dwim)
   ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'

  :init
  ;; Optionally replace the key help with a completing-read interface
  (setq prefix-help-command #'embark-prefix-help-command))

(use-package embark-consult
  :disabled t
  :ensure t
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

(use-package which-key
  :ensure t
  :custom
  (which-key-mode t))


(use-package windmove
  :disabled t
  :bind*
  (("M-b" . windmove-left)
   ("M-f" . windmove-right)
   ("M-p" . windmove-up)
   ("M-n" . windmove-down)))

;; for whatever reason, window-combination-resize doesn't work with this
(global-set-key (kbd "C-x 3") #'split-root-window-right)
(setopt window-min-height 1
        window-combination-resize t
        window-resize-pixelwise t
        frame-resize-pixelwise t)

(use-package pdf-tools
  :when (display-graphic-p)
  :ensure t
  :magic ("%PDF" . pdf-view-mode)
  :config
  (pdf-tools-install :no-query))

(use-package webjump
  :custom
  (webjump-sites '(("Instagram direct messages" . "https://www.instagram.com/direct/")
                   ("Lute" . "http://localhost:5000/")
                   ("SJTU Canvas" . "https://oc.sjtu.edu.cn/")
                   ("Zdic" .
                    [simple-query "www.zdic.net" "https://www.zdic.net/search/?sclb=tm&q=" ""])
                   ("Marginalia search" .
                    [simple-query "search.marginalia.nu" "https://search.marginalia.nu/search?query=" ""])
                   ("Langcorrect" . "https://www.langcorrect.com/journals/")
                   ("Emacs Wiki" .
                    [simple-query "www.emacswiki.org" "www.emacswiki.org/cgi-bin/wiki/" #1=""])
                   ("DuckDuckGo" .
                    [simple-query "duckduckgo.com" "duckduckgo.com/lite?q=" #1#])
                   ("Wikipedia" .
                    [simple-query "wikipedia.org" "wikipedia.org/wiki/" #1#]))))

(use-package hyperspace
  :ensure t
  :bind ("C-c s" . hyperspace)
  :custom (hyperspace-actions
           '(("z" . "https://www.zdic.net/search/?sclb=tm&q=%s")
             ("ddg" . "https://duckduckgo.com/lite?q=%s")
             ("dis" . "https://duckduckgo.com/?q=%s&iax=images&ia=images")
             ("gg" . "https://www.google.com/search?q=%s")
             ("ggm" . "https://www.google.com/maps/search/%s")
             ("gis" . "https://www.google.com/search?tbm=isch&q=%s")
             ("osm" . "https://www.openstreetmap.org/search?query=%s")
             ("az" . "https://www.amazon.com/s?k=%s")
             ("eb" . "https://www.ebay.com/sch/i.html?_nkw=%s")
             ("alix" . "https://www.aliexpress.com/wholesale?SearchText=%s")
             ("wp" . "https://en.wikipedia.org/w/index.php?search=%s&title=Special:Search&go=Go")
             ("pg" . "https://www.gutenberg.org/ebooks/search/?query=%s")
             ("bc" . "https://bandcamp.com/search?q=%s")
             ("dgs" . "https://www.discogs.com/search/?q=%s&type=all")
             ("imdb" . "https://www.imdb.com/find?q=%s&s=all")
             ("iv" . "https://invidio.us/search?q=%s")
             ("yt" . "https://www.youtube.com/results?search_query=%s")
             ("dp" . "https://packages.debian.org/%s")
             ("ac" . apropos-command)
             ("af" lambda
              (query)
              (apropos-command query t))
             ("av" . apropos-variable)
             ("bb" . bbdb-search-name)
             ("el" apply-partially #'hyperspace-action->info "(elisp)Top"))))

(setopt view-read-only t)

;; Common lisp development

(use-package sly
  :if (executable-find "sbcl")
  :ensure t
  :commands 'sly
  :custom ((inferior-lisp-program (executable-find "sbcl"))
           (common-lisp-hyperspec-root "file:///usr/share/doc/hyperspec/"))
  :mode ((rx (or ".lisp" ".cl") eos) . common-lisp-mode))

;; (profiler-stop)