#+TITLE: Keymap

Global keybindings.

* Preamble
#+begin_src emacs-lisp
;;; keymap.el --- Key maps -*- lexical-binding: t; -*-
#+end_src

* Global keybindings

#+begin_src emacs-lisp
(defvar circ/global-map (make-sparse-keymap))

(define-key circ/global-map (kbd "M-j") #'windmove-left)
(define-key circ/global-map (kbd "M-k") #'windmove-down)
(define-key circ/global-map (kbd "M-i") #'windmove-up)
(define-key circ/global-map (kbd "M-l") #'windmove-right)

(define-key circ/global-map (kbd "M-L") #'tab-bar-switch-to-next-tab)
(define-key circ/global-map (kbd "M-J") #'tab-bar-switch-to-prev-tab)

(define-key circ/global-map (kbd "M-o") #'circ/tab-line-switch-to-next-tab)
(define-key circ/global-map (kbd "M-u") #'circ/tab-line-switch-to-prev-tab)

(define-key circ/global-map (kbd "C-s") #'isearch-forward-thing-at-point)

(define-minor-mode circ/global-map-mode
  "Global override mode."
  :global t
  :init-value t)

(add-to-list 'emulation-mode-map-alists
             `((circ/global-map-mode . ,circ/global-map)))
#+end_src

* Prefixed Keys
** Helper Function
#+begin_src emacs-lisp
(defvar circ/keybind-help-key "M-/"
  "Key to bind the filtered picker at each prefix level.")

(defvar circ/keybind-registry nil
  "List of (PREFIX . KEYTREE) pairs registered by `circ/set-keybinds'.")

(defun circ/set-keybinds (keymap prefix keytree)
  "Bind KEYTREE into KEYMAP at PREFIX."
  (push (cons prefix keytree) circ/keybind-registry)
  (define-key keymap (kbd prefix) (circ/keytree-to-keymap keytree prefix)))

(defun circ/keybind-mx--collect (keytree &optional prefix)
  "Walk KEYTREE and return a list of (function-symbol . key-path-or-nil)."
  (let (result)
    (dolist (entry keytree)
      (let ((key (car entry))
            (value (cadr entry)))
        (cond
         ((null key)
          (push (cons value nil) result))
         ((and (listp value) (listp (car value)))
          (setq result
                (append (circ/keybind-mx--collect
                         value (if prefix (concat prefix " " key) key))
                        result)))
         (t
          (let ((path (if prefix (concat prefix " " key) key)))
            (push (cons value path) result))))))
    (nreverse result)))

(defun circ/keybind-mx--pick (entries prompt)
  "Show a completing-read picker for ENTRIES.
ENTRIES is a list of (function-symbol . key-path-or-nil)."
  (let* ((max-len (apply #'max (mapcar (lambda (e) (length (format "%s" (car e)))) entries)))
         (commands
          (mapcar (lambda (e)
                    (let* ((func (car e))
                           (path (cdr e))
                           (name (format "%s" func))
                           (display
                            (if path
                                (concat (format (format "%%-%ds  " max-len) name)
                                        (propertize (format "(%s)" path) 'face 'shadow))
                              name)))
                      (cons display func)))
                  entries))
         (choice (completing-read prompt commands nil t))
         (func (cdr (assoc choice commands))))
    (when func
      (call-interactively func))))

(defun circ/keybind-mx (keytree prefix)
  "Show a completing-read picker for all commands reachable from KEYTREE."
  (circ/keybind-mx--pick (circ/keybind-mx--collect keytree)
                         (format "%s: " prefix)))

(defun circ/keytree-to-keymap (keytree &optional prefix no-help-key)
  "Build a sparse keymap from KEYTREE.
PREFIX is used only for display in `circ/keybind-mx'.
When NO-HELP-KEY is non-nil, do not bind the help key at this level."
  (let ((map (make-sparse-keymap))
        (prefix (or prefix "")))
    (unless no-help-key
      (define-key map (kbd circ/keybind-help-key)
        (lambda () (interactive) (circ/keybind-mx keytree prefix))))
    (dolist (entry keytree)
      (let ((key (car entry))
            (value (cadr entry)))
        (cond
         ((null key) nil)
         ((and (listp value) (listp (car value)))
          (define-key map (kbd key)
            (circ/keytree-to-keymap value
              (if (string-empty-p prefix) key (concat prefix " " key)))))
         (t
          (define-key map (kbd key) value)))))
    map))

(defun circ/keybind-mx-all ()
  "Show a picker for all registered keybindings.
Includes global prefixed bindings and arf-mode bindings for the
current state and major mode."
  (interactive)
  (let (entries)
    ;; Global prefixed bindings
    (dolist (reg circ/keybind-registry)
      (let ((prefix (car reg))
            (keytree (cdr reg)))
        (setq entries (append entries
                              (circ/keybind-mx--collect keytree prefix)))))
    ;; Arf default bindings for current state
    (when (bound-and-true-p arf-state)
      (let ((keytree (alist-get arf-state arf-default-keytrees)))
        (when keytree
          (setq entries (append entries
                                (circ/keybind-mx--collect keytree)))))
      ;; Arf mode-specific bindings for current state + major mode
      (let* ((state-modes (alist-get arf-state arf-mode-keytrees))
             (keytree (alist-get major-mode state-modes)))
        (when keytree
          (setq entries (append entries
                                (circ/keybind-mx--collect keytree))))))
    (if entries
        (circ/keybind-mx--pick entries "All bindings: ")
      (message "No bindings registered."))))
#+end_src

** Buffer (M-b)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-b"
  '(("b" switch-to-buffer)
    ("r"
     (("v" circ/revert-buffer)
      ("n" circ/rename-buffer)))
    ("m"
     (("j" circ/move-buffer-left)
      ("k" circ/move-buffer-down)
      ("i" circ/move-buffer-up)
      ("l" circ/move-buffer-right)
      ("<left>" circ/move-buffer-left)
      ("<down>" circ/move-buffer-down)
      ("<up>" circ/move-buffer-up)
      ("<right>" circ/move-buffer-right)))
    ("t"
     (("j" circ/transpose-buffer-left)
      ("k" circ/transpose-buffer-down)
      ("i" circ/transpose-buffer-up)
      ("l" circ/transpose-buffer-right)
      ("<left>" circ/transpose-buffer-left)
      ("<down>" circ/transpose-buffer-down)
      ("<up>" circ/transpose-buffer-up)
      ("<right>" circ/transpose-buffer-right)))
    ("d"
     (("j" circ/duplicate-buffer-left)
      ("k" circ/duplicate-buffer-down)
      ("i" circ/duplicate-buffer-up)
      ("l" circ/duplicate-buffer-right)
      ("<left>" circ/duplicate-buffer-left)
      ("<down>" circ/duplicate-buffer-down)
      ("<up>" circ/duplicate-buffer-up)
      ("<right>" circ/duplicate-buffer-right)))
    ("q" circ/tab-line-close-tab)
    ("Q" kill-current-buffer)
    ("s" circ/move-buffer-to-side-window)
    ("l" ibuffer)))
#+end_src

** Edit (M-e)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-e"
  `(("d" duplicate-line)
    ("TAB" dabbrev-completion)
    ("s"
     (("(" circ/surround-parentheses)
      ("[" circ/surround-brackets)
      ("{" circ/surround-braces)
      ("*" circ/surround-asterisk)
      ("\"" circ/surround-d-quote)
      ("'" circ/surround-s-quote)
      ("/" circ/surround-slash)
      ("=" circ/surround-equals)
      ("+" circ/surround-plus)
      ("-" circ/surround-minus)))
    ("a"
     (("h" circ/align-once-head)
      ("t" circ/align-once-tail)
      ("H" circ/align-repeat-head)
      ("T" circ/align-repeat-tail)))
    ("p"
     (("f" circ/insert-project-filepath)))
    ("f"
     (("p" circ/insert-project-filepath)))
    (":" live-replace-regexp)
    (nil circ/repeat-replace-index)))
#+end_src

** Find/Open (M-f)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-f"
  '(("f" find-file)
    ("t" circ/new-vterm)
    ("d" dired-jump)
    ("D" dired)
    ("i" info)
    ("p" project-find-file)
    ("b"
     (("s" bookmark-set)
      ("j" bookmark-jump)
      ("l" bookmark-bmenu-list)))))
#+end_src

** Project (M-p)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-p"
  '(("f" project-find-file)
    (nil project-find-dir)
    (nil project-kill-buffers)
    (nil project-switch-to-project)
    (nil vc-retrieve-tag)
    ("v" project-vc-dir)
    ("b" project-switch-to-buffer)
    ("/" project-find-regexp)
    ("l" vc-print-log)))
#+end_src

** Toggle (M-t)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-t"
  '(("s" window-toggle-side-windows)
    ("w" whitespace-mode)
    ("c" comment-line)
    ("h" hl-line-mode)
    (nil abbrev-mode)
    (nil vc-annotate)
    ("f" follow-mode)
    ("r" auto-revert-mode)
    ("m" highlight-symbol-highlight)
    ("M" highlight-symbol-unhighlight)
    (nil highlight-symbol-mode)))
#+end_src

** Window (M-w)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-w"
  '(("h" circ/split-right)
    ("v" circ/split-below)
    ("e" balance-windows)
    ("f" circ/focus-region)
    ("r" circ/window-resize-mode)
    ("s" circ/scroll-mode)
    ("q" delete-window)
    ("<left>" windmove-left)
    ("<right>" windmove-right)
    ("<up>" windmove-up)
    ("<down>" windmove-down)
    ("o" circ/tab-line-close-other-tabs)
    ("z"
     (("h" circ/maximise-window-horizontal)
      ("H" circ/minimise-window-horizontal)
      ("v" circ/maximise-window-vertical)
      ("V" circ/minimise-window-vertical)
      ("z" maximize-window)))
    ("t"
     (("c" circ/tab-bar-new-tab)
      ("q" tab-bar-close-tab)
      ("e" tab-bar-rename-tab)
      ("<left>" tab-bar-switch-to-next-tab)
      ("<right>" tab-bar-switch-to-prev-tab)
      ("1" circ/tab-bar-select-tab-1)
      ("2" circ/tab-bar-select-tab-2)
      ("3" circ/tab-bar-select-tab-3)
      ("4" circ/tab-bar-select-tab-4)
      ("5" circ/tab-bar-select-tab-5)
      ("6" circ/tab-bar-select-tab-6)
      ("7" circ/tab-bar-select-tab-7)
      ("8" circ/tab-bar-select-tab-8)
      ("9" circ/tab-bar-select-tab-9)
      ("," circ/move-tab-left)
      ("." circ/move-tab-right)))))
#+end_src

** Agenda (M-a)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-a"
  '(("c" org-capture)
    ("a" org-agenda)
    ("A" circ/setup-agenda-view)
    ("m"
     (("f" circ/pims/find-reference)))
    ("i" circ/open-org-inbox-file)
    ("t" circ/open-org-todo-file)))
#+end_src

** Help Picker (M-/)
#+begin_src emacs-lisp
(define-key circ/global-map (kbd "M-/") #'circ/keybind-mx-all)
#+end_src

** Arf Keybindings (M-?)
#+begin_src emacs-lisp
(define-key circ/global-map (kbd "M-?") #'arf-show-keybindings)
#+end_src

** Goto (M-g)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-g"
  '(("d" xref-find-definitions)
    ("r" xref-find-references)
    ("a" xref-find-apropos)
    ("." xref-go-forward)
    ("," xref-go-back)
    ("c" completion-at-point)
    ("h" eldoc-doc-buffer)
    ("A" eglot-code-actions)
    ("f" eglot-format-buffer)
    (nil eglot-rename)
    (nil flymake-show-buffer-diagnostics)))
#+end_src

** Agent (M-y)
#+begin_src emacs-lisp
(circ/set-keybinds circ/global-map "M-y"
  '(("y" circ/agent-chat-helper-send-file-path)
    ("Y" circ/agent-chat-helper-send-absolute-file-path)))
#+end_src

