;;; hel-integration.el --- Integration with other packages -*- lexical-binding: t -*-
;;
;; Copyright © 2025-2026 Yuriy Artemyev
;;
;; Author: Yuriy Artemyev <anuvyklack@gmail.com>
;; Maintainer: Yuriy Artemyev <anuvyklack@gmail.com>
;; Homepage: https://github.com/helheim-emacs/hel
;; SPDX-License-Identifier: GPL-3.0-or-later
;;
;; This file is not part of GNU Emacs.
;;
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
;;
;;; Commentary:
;;
;;  Hel integration with other Emacs packages.
;;
;;; Code:

(eval-when-compile (require 'hel-macros))
(require 'dash)
(require 'hel-vars)
(require 'hel-multiple-cursors-core)
(require 'hel-lib)
(require 'hel-core)
(require 'hel-commands)

;;; Integration multiple cursors with Emacs functionality

;; M-x
(hel-advice-add 'execute-extended-command :after #'hel--execute-for-all-cursors-a)
(hel-set-single-cursor-command 'execute-extended-command)

;; M-X
(hel-advice-add 'execute-extended-command-for-buffer :after #'hel--execute-for-all-cursors-a)
(hel-set-single-cursor-command 'execute-extended-command-for-buffer)

(hel-define-advice current-kill (:before (n &optional _do-not-move) hel)
  "Make sure pastes from other programs are added to `kill-ring's
of all cursors when yanking."
  (when-let* ((interprogram-paste (and (= n 0)
                                       interprogram-paste-function
                                       (funcall interprogram-paste-function))))
    (when (listp interprogram-paste)
      ;; Use `reverse' to avoid modifying external data.
      (cl-callf reverse interprogram-paste))
    ;; Add `interprogram-paste' to `kill-ring's of all cursors real and
    ;; fake. This is what `current-kill' do internally, but we have to do
    ;; it ourselves, because `interprogram-paste-function' is not a pure
    ;; function — it returns something only once.
    (let ((interprogram-cut-function nil)
          (interprogram-paste-function nil))
      ;; real cursor
      (if (listp interprogram-paste)
          (mapc 'kill-new interprogram-paste)
        (kill-new interprogram-paste))
      ;; fake cursors
      (dolist (cursor (hel-all-fake-cursors))
        (let ((kill-ring (overlay-get cursor 'kill-ring))
              (kill-ring-yank-pointer (overlay-get cursor 'kill-ring-yank-pointer)))
          (if (listp interprogram-paste)
              (mapc 'kill-new interprogram-paste)
            (kill-new interprogram-paste))
          (overlay-put cursor 'kill-ring kill-ring)
          (overlay-put cursor 'kill-ring-yank-pointer kill-ring-yank-pointer))))))

(hel-define-advice execute-kbd-macro (:around (orig-fun &rest args))
  "`execute-kbd-macro' should never be run for fake cursors.
The real cursor will execute the keyboard macro, resulting in new commands
in the command loop, and the fake cursors can pick up on those instead."
  (unless hel-executing-command-for-fake-cursor
    (apply orig-fun args)))

(hel-cache-input read-char)
(hel-cache-input read-quoted-char)
(hel-cache-input read-from-kill-ring)
(hel-cache-input read-char-from-minibuffer)
(hel-cache-input read-char-by-name) ; is used by `insert-char'
(hel-cache-input register-read-with-preview)  ; is used by `read-string'

;;; Commands that don't work with multiple-cursors

(hel-set-unsupported-command 'isearch-forward)
(hel-set-unsupported-command 'isearch-backward)

;; Between invocations, `cycle-spacing' stores internal data in the
;; `cycle-spacing--context' variable. The original position is stored
;; as a number rather than a marker, and invalidates when other cursors
;; modify the buffer content.
(hel-set-unsupported-command 'cycle-spacing)

;; Replace it with `just-one-space' while multiple-cursors are active.
(hel-keymap-set hel-multiple-cursors-mode-map
  "<remap> <cycle-spacing>" #'just-one-space)

;;; Advices for general commands not related to particular packages

(dolist (cmd '(fill-region    ; gq
               indent-region  ; =
               comment-dwim)) ; gc
  (hel-advice-add cmd :around #'hel-keep-selection-a))

(hel-advice-add 'clone-indirect-buffer :before #'hel-deactivate-mark-a)

;;; Built-in packages
;;;; Button

(hel-advice-add 'forward-button  :before #'hel-deactivate-mark-a)
(hel-advice-add 'backward-button :before #'hel-deactivate-mark-a)

;;;; Backtrace and Debug

(defvar backtrace-mode-map)
(defvar debugger-mode-map)

(with-eval-after-load 'backtrace
  (hel-keymap-set backtrace-mode-map :state 'emacs
    "j"   'backtrace-forward-frame   ;; "n"
    "k"   'backtrace-backward-frame) ;; "p"
  ;; <local leader>
  (hel-keymap-set backtrace-mode-map
    ", v" 'backtrace-toggle-locals
    ", #" 'backtrace-toggle-print-circle
    ", :" 'backtrace-toggle-print-gensym
    ", +" 'backtrace-multi-line
    ", -" 'backtrace-single-line
    ", ." 'backtrace-expand-ellipses))

(with-eval-after-load 'debug
  ;; `debugger-mode-map' is inherited from `backtrace-mode-map'
  (hel-keymap-set debugger-mode-map
    "J"   'debugger-jump
    ;; <local leader>
    ", b" 'debugger-frame
    ", c" 'debugger-continue
    ", j" 'debugger-jump
    ", r" 'debugger-return-value
    ", u" 'debugger-frame-clear
    ", d" 'debugger-step-through
    ", l" 'debugger-list-functions
    ", e" 'debugger-eval-expression
    ", R" 'debugger-record-expression))

;;;; Edebug

(defvar edebug-mode-map)

(with-eval-after-load 'edebug
  ;; Edebug enters a recursive edit in the middle of the command that reached
  ;; the instrumented code, so `hel--post-command-hook' has not run by the time
  ;; edebug reads its first key.
  (add-hook 'edebug-mode-hook #'hel-update-active-keymaps)
  (hel-keymap-set edebug-mode-map
    "SPC"    nil ; unding `edebug-step-mode'
    "h"      nil ; unding `edebug-goto-here'
    "s"     'edebug-step-mode
    "H"     'edebug-goto-here
    "C-c h" 'edebug-goto-here) ; <leader> h
  (hel-keymap-set edebug-mode-map :state 'normal
    "<escape>" #'hel-normal-state-escape))

;;;; Eglot

(with-eval-after-load 'eglot
  (add-hook 'eglot-managed-mode-hook #'hel-maybe-update-active-keymaps))

;;;; Eldoc

(with-eval-after-load 'eldoc
  ;; Add motion commands to the `eldoc-message-commands' obarray.
  (eldoc-add-command 'hel-backward-char        ; h
                     'hel-forward-char         ; l
                     'hel-next-line            ; j
                     'hel-previous-line        ; k
                     'hel-forward-word-start   ; w
                     'hel-forward-WORD-start   ; W
                     'hel-backward-word-start  ; b
                     'hel-backward-WORD-start  ; B
                     'hel-forward-word-end     ; e
                     'hel-forward-WORD-end     ; E
                     'hel-first-non-blank      ; gh
                     'hel-end-of-line-command  ; gl
                     'hel-beginning-of-buffer  ; gg
                     'hel-end-of-buffer        ; G
                     'hel-search-forward       ; /
                     'hel-search-backward      ; ?
                     'hel-search-next          ; n
                     'hel-search-previous      ; N
                     'hel-find-char-forward    ; f
                     'hel-find-char-backward   ; F
                     'hel-till-char-forward    ; t
                     'hel-till-char-backward)) ; T

;;;; Elisp (emacs-lisp-mode)

;; Fontification for Hel macros.
(font-lock-add-keywords
 'emacs-lisp-mode (eval-when-compile
                    `((,(concat "^\\s-*("
                                (regexp-opt '("hel-define-command") t)
                                "\\s-+\\(" (rx lisp-mode-symbol) "\\)")
                       (1 'font-lock-keyword-face)
                       (2 'font-lock-function-name-face nil t))
                      (,(concat "^\\s-*("
                                (regexp-opt '("hel-defvar-local") t)
                                "\\s-+\\(" (rx lisp-mode-symbol) "\\)")
                       (1 'font-lock-keyword-face)
                       (2 'font-lock-variable-name-face nil t)))))

;; `emacs-lisp-mode' is inherited from `lisp-data-mode'.
(add-hook 'lisp-data-mode-hook #'hel--emacs-lisp-mode-h)

(defun hel--emacs-lisp-mode-h ()
  ;; Add legacy quotes marks to Hel surround functionality.
  (let ((spec '(:insert ("`" . "'") :remove ("`" . "'"))))
    (setf (alist-get ?` hel-surround-alist) spec)
    (setf (alist-get ?' hel-surround-alist) spec))
  ;;
  ;; Teach `imenu' about Hel macros.
  (dolist (i (eval-when-compile
               `(("Variables"
                  ,(concat "^\\s-*("
                           (regexp-opt '("hel-defvar-local") t)
                           "\\s-+\\(" (rx lisp-mode-symbol) "\\)")
                  2)
                 (nil ;; top level
                  ,(concat "^\\s-*("
                           (regexp-opt '("hel-define-command") t)
                           "\\s-+'?\\(" (rx lisp-mode-symbol) "\\)")
                  2))))
    (cl-pushnew i imenu-generic-expression :test #'equal)))

(dolist (keymap (list emacs-lisp-mode-map
                      lisp-data-mode-map))
  (hel-keymap-set keymap :state 'normal
    "m `"   #'hel-mark-inner-legacy-quoted
    "m '"   #'hel-mark-inner-legacy-quoted
    "m i `" #'hel-mark-inner-legacy-quoted
    "m i '" #'hel-mark-inner-legacy-quoted
    "m a `" #'hel-mark-a-legacy-quoted
    "m a '" #'hel-mark-a-legacy-quoted))

(hel-keymap-set emacs-lisp-compilation-mode-map
  "g"   nil
  ", r" #'emacs-lisp-compilation-recompile)

(hel-define-command hel-mark-inner-legacy-quoted ()
  :multiple-cursors t
  :merge-selections t
  (interactive)
  (pcase (hel-surround-4-bounds-at-point "`" "'")
    (`(,_ ,beg ,end ,_) (hel-set-region beg end))))

(hel-define-command hel-mark-a-legacy-quoted ()
  :multiple-cursors t
  :merge-selections t
  (interactive)
  (-when-let ((beg _ _ end) (hel-surround-4-bounds-at-point "`" "'"))
    (hel-set-region beg end)))

;;;; Help

(with-eval-after-load 'help-mode
  (hel-set-initial-state 'help-mode 'normal))

(with-eval-after-load 'helpful
  (hel-set-initial-state 'helpful-mode 'normal)
  (hel-set-single-cursor-command 'helpful-at-point)
  ;; `helpful-mode' is derived from `special-mode', in which ESC is bound
  ;; to switching Hel to Emacs state. We don't need Emacs state in
  ;; `helpful-mode', so override it.
  (defvar helpful-mode-map)
  (hel-keymap-set helpful-mode-map :state 'normal
    "<escape>" #'hel-normal-state-escape))

;;;; Comint

(with-eval-after-load 'comint
  (defvar comint-mode-map)
  (hel-keymap-set comint-mode-map
    "<remap> <hel-insert>"      'hel-comint-insert      ; "i"
    "<remap> <hel-append>"      'hel-comint-append      ; "a"
    "<remap> <hel-insert-line>" 'hel-comint-insert-line ; "I"
    "<remap> <hel-append-line>" 'hel-comint-append-line ; "A"
    "<remap> <hel-change>"      'hel-comint-change))    ; "c"

(declare-function comint-after-pmark-p "comint")

(defun hel-comint--goto-process-mark ()
  "Move point to the beginning of writable input start."
  (-> (current-buffer)
      (get-buffer-process)
      (process-mark)
      (goto-char)))

;; i
(hel-define-command hel-comint-insert ()
  "Switch to Insert state, jumping to prompt if cursor is in read-only area."
  :multiple-cursors nil
  (interactive)
  (unless (comint-after-pmark-p)
    (hel-disable-multiple-cursors-mode)
    (deactivate-mark)
    (hel-comint--goto-process-mark))
  (hel-insert))

;; a
(hel-define-command hel-comint-append ()
  "Switch to Insert state after selection, jumping to prompt if in read-only."
  :multiple-cursors nil
  (interactive)
  (unless (comint-after-pmark-p)
    (hel-disable-multiple-cursors-mode)
    (deactivate-mark)
    (hel-comint--goto-process-mark))
  (hel-append))

;; I
(hel-define-command hel-comint-insert-line ()
  "Go to beginning of writable input and switch to Insert state."
  :multiple-cursors nil
  (interactive)
  (unless (comint-after-pmark-p)
    (hel-disable-multiple-cursors-mode)
    (deactivate-mark)
    (hel-comint--goto-process-mark))
  (hel-insert-line))

;; A
(hel-define-command hel-comint-append-line ()
  "Go to end of input line and switch to Insert state."
  :multiple-cursors nil
  (interactive)
  (unless (comint-after-pmark-p)
    (hel-disable-multiple-cursors-mode)
    (deactivate-mark)
    (hel-comint--goto-process-mark))
  (hel-append-line))

;; c
(hel-define-command hel-comint-change ()
  "Delete selection and switch to Insert state.
If cursor is in read-only area, jump to prompt instead of deleting."
  :multiple-cursors nil
  (interactive)
  (if (comint-after-pmark-p)
      (hel-change)
    (hel-disable-multiple-cursors-mode)
    (deactivate-mark)
    (hel-comint--goto-process-mark)
    (hel-insert-state 1)))

;;;; Compile

(hel-advice-add 'next-error         :around #'hel-jump-command-a)
(hel-advice-add 'previous-error     :around #'hel-jump-command-a)
(hel-advice-add 'compile-goto-error :around #'hel-jump-command-a)

;;;; completion-preview
;;
;; https://eshelyaron.com/posts/2023-11-17-completion-preview-in-emacs.html

(defvar completion-preview-active-mode-map)

(with-eval-after-load 'completion-preview
  (add-hook 'hel-insert-state-exit-hook 'completion-preview-hide)
  (hel-keymap-set completion-preview-active-mode-map
    "C-j"  'completion-preview-next-candidate
    "C-k"  'completion-preview-prev-candidate))

;;;; grep

;; `grep-edit-mode' was introduced in Emacs 31
(with-eval-after-load 'grep
  (when (fboundp 'grep-change-to-grep-edit-mode)
    (defvar grep-edit-mode-map)
    (declare-function grep-edit-save-changes "grep")

    (hel-keymap-set grep-edit-mode-map :state 'normal
      "Z Z" #'grep-edit-save-changes)
    (hel-keymap-set grep-edit-mode-map
      "<remap> <save-buffer>" #'grep-edit-save-changes)

    (hel-advice-add 'grep-change-to-grep-edit-mode :after #'hel-switch-to-initial-state)

    (hel-set-single-cursor-command 'grep-edit-save-changes)
    (hel-advice-add 'grep-edit-save-changes :before #'hel-deactivate-mark-a)
    (hel-advice-add 'grep-edit-save-changes :before #'hel-disable-multiple-cursors-mode)
    (hel-advice-add 'grep-edit-save-changes :after #'hel-switch-to-initial-state)))

;;;; wgrep

(hel-advice-add 'wgrep-change-to-wgrep-mode :after #'hel-switch-to-initial-state)
(hel-advice-add 'wgrep-to-original-mode :before #'hel-deactivate-mark-a)
(hel-advice-add 'wgrep-to-original-mode :before #'hel-disable-multiple-cursors-mode)
(hel-advice-add 'wgrep-to-original-mode :after  #'hel-switch-to-initial-state)

;;;; occur-mode

(hel-advice-add 'occur-mode-goto-occurrence    :around #'hel-jump-command-a)
(hel-advice-add 'occur-mode-display-occurrence :around #'hel-jump-command-a)

;;;; wdired

(defvar wdired-mode-map)
(declare-function wdired-exit "wdired")

(with-eval-after-load 'wdired
  (hel-advice-add 'wdired-change-to-wdired-mode :after #'hel-switch-to-initial-state)
  (hel-advice-add 'wdired-change-to-dired-mode  :after #'hel-switch-to-initial-state)

  (hel-keymap-set wdired-mode-map :state 'normal
    "<escape>" 'helheim-wdired-exit
    "Z Z"      'wdired-finish-edit
    "Z Q"      'wdired-abort-changes
    ;;
    "j"        'wdired-next-line
    "k"        'wdired-previous-line
    "<up>"     'wdired-next-line
    "<down>"   'wdired-previous-line
    ;; Commands bound to these keys have no sense for wdired.
    "o" 'undefined
    "O" 'undefined
    "J" 'undefined)

  (hel-keymap-set wdired-mode-map
    "<remap> <save-buffer>" 'wdired-finish-edit
    "C-g" 'wdired-abort-changes)

  (hel-set-multiple-cursors-command 'wdired--self-insert)
  (hel-set-multiple-cursors-command 'wdired-next-line)
  (hel-set-multiple-cursors-command 'wdired-previous-line)
  (hel-set-single-cursor-command 'wdired-finish-edit)
  (hel-set-single-cursor-command 'wdired-abort-changes)
  (hel-set-single-cursor-command 'wdired-exit)

  (hel-advice-add 'wdired-change-to-dired-mode :before #'hel-deactivate-mark-a)
  (hel-advice-add 'wdired-change-to-dired-mode :before #'hel-disable-multiple-cursors-mode)

  (hel-advice-add 'wdired-next-line     :before #'hel-deactivate-mark-a)
  (hel-advice-add 'wdired-previous-line :before #'hel-deactivate-mark-a))

(hel-define-command helheim-wdired-exit ()
  :multiple-cursors t
  (interactive)
  (cond (hel--extend-selection
         (hel-extend-selection -1))
        ((use-region-p)
         (deactivate-mark))
        (t
         (wdired-exit))))

;;;; image-mode

(with-eval-after-load 'image-mode
  (defvar image-mode-map)
  (hel-keymap-set image-mode-map :state 'emacs
    ;; scroll
    "C-y" 'image-previous-line
    "C-e" 'image-next-line
    "C-b" 'image-scroll-right
    "C-f" 'image-scroll-left
    "C-d" 'image-scroll-up
    "C-u" 'image-scroll-down
    "z h" 'image-scroll-right
    "z l" 'image-scroll-left))

;;;; Messages buffer

(hel-set-initial-state 'messages-buffer-mode 'normal)

;;;; Minibuffer

(hel-keymap-set minibuffer-mode-map :state 'normal
  ;; "ESC" #'abort-minibuffers
  "<escape>" #'abort-recursive-edit
  "<down>"   #'next-line-or-history-element
  "<up>"     #'previous-line-or-history-element
  "j"        #'next-line-or-history-element
  "k"        #'previous-line-or-history-element)

(hel-keymap-set minibuffer-mode-map :state 'insert
  "C-w" #'hel-delete-backward-word)

(hel-keymap-set minibuffer-mode-map
  "C-j" #'next-history-element
  "C-k" #'previous-history-element
  "M-j" #'next-history-element
  "M-k" #'previous-history-element)

;; C-j in `read--expression-map' is bound to `read--expression-try-read'
;; which is also bound to RET. Remove it, to make the binding from the
;; parent `read-expression-map' keymap available.
(keymap-unset read--expression-map "C-j" :remove)

;;;; outline

(hel-advice-add 'outline-insert-heading :after #'hel-switch-to-insert-state-a)

(hel-advice-add 'outline-up-heading               :before #'hel-maybe-deactivate-mark-a)
(hel-advice-add 'outline-next-visible-heading     :before #'hel-maybe-deactivate-mark-a)
(hel-advice-add 'outline-previous-visible-heading :before #'hel-maybe-deactivate-mark-a)
(hel-advice-add 'outline-forward-same-level       :before #'hel-maybe-deactivate-mark-a)
(hel-advice-add 'outline-backward-same-level      :before #'hel-maybe-deactivate-mark-a)

(hel-advice-add 'outline-promote :around #'hel-keep-selection-a)
(hel-advice-add 'outline-demote  :around #'hel-keep-selection-a)

;;;; repeat-mode

(setopt repeat-exit-key "<escape>")

(put 'undo 'repeat-map nil) ; Do not repeat `undo'.

(hel-keymap-set buffer-navigation-repeat-map
  "]" #'next-buffer
  "[" #'previous-buffer)

;;;; page

(hel-advice-add 'forward-page  :around #'hel-jump-command-a)
(hel-advice-add 'backward-page :around #'hel-jump-command-a)

;;;; shortdoc

(with-eval-after-load 'shortdoc
  (defvar shortdoc-mode-map)
  (keymap-set shortdoc-mode-map "y" 'shortdoc-copy-function-as-kill))

;;;; special-mode

(hel-keymap-set special-mode-map
  "h"   #'left-char
  "j"   #'next-line
  "k"   #'previous-line
  "l"   #'right-char
  ;;
  "g"   nil ; Unbind `revert-buffer' and convert to prefix.
  "g a" #'describe-char
  "g r" #'revert-buffer          ; also "C-w r"
  "g h" #'move-beginning-of-line ; native "C-a"
  "g l" #'move-end-of-line       ; native "C-e"
  "g g" #'beginning-of-buffer    ; native "<"
  "G"   #'end-of-buffer          ; native ">"
  ;;
  ;; Switch to Normal state. This allows you to select and copy arbitrary text
  ;; in special modes.
  ;;   Bind it in base keymap instead of Emacs state keymap, to not override
  ;; the "i" key for major modes that inherit from special-mode.
  "i"   #'hel-normal-state)

(hel-keymap-set special-mode-map :state 'normal
  "<escape>" #'hel-emacs-state
  ;; Use "zx" or "C-x C-s" to switch back to Emacs state.
  ;; Saving special buffer has little sense, so we can use it.
  "<remap> <save-buffer>" #'hel-emacs-state)

;;;; prog-mode

(hel-keymap-set prog-mode-map :state 'normal
  ")"   #'hel-mark-function-forward
  "("   #'hel-mark-function-backward)

(when (fboundp 'prog-fill-reindent-defun) ;; Emacs 30.1
  (hel-keymap-set prog-mode-map :state 'normal
    "g q" #'prog-fill-reindent-defun))

(hel-keymap-set prog-mode-map :state 'insert
  "C-w" #'hel-delete-backward-word)

;;;; text-mode

(hel-keymap-set text-mode-map :state 'normal
  ")"   #'hel-mark-sentence-forward
  "("   #'hel-mark-sentence-backward)

(hel-keymap-set text-mode-map :state 'insert
  "C-w" #'hel-delete-backward-word)

;;;; conf-mode

(with-eval-after-load 'conf-mode
  (defvar conf-mode-map)
  (hel-keymap-set conf-mode-map :state 'insert
    "C-w" #'hel-delete-backward-word))

;;;; winner-mode & tab-bar-history-mode

(add-hook 'winner-mode-hook
          (defun hel-setup-winner-mode-keys ()
            "Setup undo window configuration keys."
            (when winner-mode
              (hel-keymap-set hel-window-map
                "u" 'winner-undo
                "U" 'winner-redo))))

(add-hook 'tab-bar-history-mode-hook
          (defun hel-setup-tab-bar-history-mode-keys ()
            "Setup undo window configuration keys."
            (when tab-bar-history-mode
              (hel-keymap-set hel-window-map
                "u" 'tab-bar-history-back
                "U" 'tab-bar-history-forward))))

;;;; VC

(with-eval-after-load 'log-view
  (defvar log-view-mode-map)
  (hel-keymap-set log-view-mode-map
    "j" 'log-view-msg-next
    "k" 'log-view-msg-prev))

;;;; which-key

(with-eval-after-load 'which-key
  ;; Do not display "C-w C-" and "C-c w C-" keys duplicates in which-key popup.
  (defvar which-key-replacement-alist)
  (add-to-list 'which-key-replacement-alist
               '(("^\\(?:C-c w\\|C-w\\) C-[a-z]" . nil) . ignore))
  ;; Do not display keys bound to following commands in which-key popup.
  (let ((regexp (eval-when-compile
                  (regexp-opt (-map #'symbol-name
                                    '(hel-m-digit-argument
                                      hel-mi-digit-argument
                                      hel-ma-digit-argument
                                      hel-m-negative-argument
                                      hel-mi-negative-argument
                                      hel-ma-negative-argument))))))
    (add-to-list 'which-key-replacement-alist
                 `((nil . ,regexp) . ignore)))
  (let ((regexp (eval-when-compile
                  (regexp-opt (-map #'symbol-name
                                    '(hel-mark-a-surround
                                      hel-mark-a-paren
                                      hel-mark-a-bracket
                                      hel-mark-a-curly
                                      hel-mark-an-angle
                                      hel-mark-a-double-quoted
                                      hel-mark-a-single-quoted
                                      hel-mark-a-back-quoted
                                      hel-mark-inner-surround
                                      hel-mark-inner-paren
                                      hel-mark-inner-bracket
                                      hel-mark-inner-curly
                                      hel-mark-inner-angle
                                      hel-mark-inner-double-quoted
                                      hel-mark-inner-single-quoted
                                      hel-mark-inner-back-quoted))))))
    (add-to-list 'which-key-replacement-alist
                 `((nil . ,regexp) . ignore))))

;;;; widget

(defun hel-switch-to-normal-state-in-field-widget ()
  "This function intended to be called from mode hook of major modes that use
field widgets (like `Custom-mode' or `notmuch-hello-mode')."
  (add-hook 'post-command-hook #'hel--widget-field-h nil t))

(defun hel--widget-field-h ()
  (cond ((widget-field-at (point))
         (when (eq hel-state 'emacs)
           (hel-normal-state)))
        ((not (eq hel-state 'emacs))
         (when hel-multiple-cursors-mode (hel-multiple-cursors-mode -1))
         (hel-emacs-state))))

(with-eval-after-load 'wid-edit
  (hel-keymap-set widget-field-keymap :state 'normal
    "g h" 'hel-beginning-of-line-command
    "g l" 'hel-widget-end-of-line)
  ;;
  (widget-put (get 'editable-field 'widget-type) :keymap widget-field-keymap))

;; gl
(hel-define-command hel-widget-end-of-line ()
  "Move point to end of field or end of line, whichever is first."
  :multiple-cursors t
  :merge-selections t
  (interactive)
  (hel-set-region (if hel--extend-selection (mark) (point))
                  (progn (widget-end-of-line) (point))))

;;;; Xref

(hel-advice-add 'xref-find-definitions :around #'hel-jump-command-a)
(hel-advice-add 'xref-find-references  :around #'hel-jump-command-a)
(hel-advice-add 'xref-go-back          :around #'hel-jump-command-a)
(hel-advice-add 'xref-go-forward       :around #'hel-jump-command-a)
(hel-advice-add 'xref-goto-xref        :around #'hel-jump-command-a)
(hel-advice-add 'xref--show-xrefs      :around #'hel-jump-command-a)
(hel-advice-add 'xref--show-defs       :around #'hel-jump-command-a)

;; `xref-edit-mode' was introduced in Emacs 31
(with-eval-after-load 'xref
  (when (fboundp 'xref-change-to-xref-edit-mode)
    (defvar xref--xref-buffer-mode-map)
    (defvar xref-edit-mode-map)
    (declare-function xref-change-to-xref-edit-mode "xref")
    (declare-function xref-edit-save-changes "xref")

    (hel-keymap-set xref--xref-buffer-mode-map
      "i" #'xref-change-to-xref-edit-mode)
    (hel-keymap-set xref-edit-mode-map :state 'normal
      "Z Z" #'xref-edit-save-changes)
    (hel-keymap-set xref-edit-mode-map
      "<remap> <save-buffer>" #'xref-edit-save-changes)

    (hel-set-single-cursor-command 'xref-edit-save-changes)
    (hel-advice-add 'xref-edit-save-changes :before #'hel-deactivate-mark-a)
    (hel-advice-add 'xref-edit-save-changes :before #'hel-disable-multiple-cursors-mode)
    ;; `xref-edit-save-changes' assigns `major-mode' by hand and does not run
    ;; `after-change-major-mode-hook', so Hel never learns that the buffer went
    ;; back to Xref mode.
    (hel-advice-add 'xref-edit-save-changes :after #'hel-switch-to-initial-state)))

;;; External packages
;;;; corfu

(declare-function corfu-quit "ext:corfu")

(with-eval-after-load 'corfu
  ;; Close corfu popup on Insert state exit.
  (add-hook 'hel-insert-state-exit-hook #'corfu-quit))

;;;; consult

(hel-cache-input consult--read)

(hel-set-multiple-cursors-command 'consult-yank-pop)

(hel-advice-add 'consult-line        :before #'hel-deactivate-mark-a)
(hel-advice-add 'consult-mark        :before #'hel-deactivate-mark-a)
(hel-advice-add 'consult-global-mark :before #'hel-deactivate-mark-a)
(hel-advice-add 'consult-imenu       :before #'hel-deactivate-mark-a)
(hel-advice-add 'consult-outline     :before #'hel-deactivate-mark-a)
(hel-advice-add 'consult-grep        :before #'hel-deactivate-mark-a)
(hel-advice-add 'consult-git-grep    :before #'hel-deactivate-mark-a)
(hel-advice-add 'consult-ripgrep     :before #'hel-deactivate-mark-a)

;;;; diff-hl

(defvar diff-hl-show-hunk-inline-transient-mode)
(defvar diff-hl-show-hunk-inline-transient-mode-map)

(add-hook 'diff-hl-show-hunk-inline-transient-mode-hook
          (defun hel-switch-to-emacs-state-while-diff-hl-show-hunk-h ()
            "Switch to Emacs state while VC diff hunk is shown."
            (when hel-local-mode
              (if diff-hl-show-hunk-inline-transient-mode
                  (hel-emacs-state)
                (hel-normal-state)))))

(with-eval-after-load 'diff-hl-show-hunk-inline
  (hel-keymap-set diff-hl-show-hunk-inline-transient-mode-map
    "<remap> <self-insert-command>" 'ignore))

;;;; embark

;; The "ext:" prefix marks an optional external package.
(declare-function embark--targets "ext:embark")
(declare-function embark--act     "ext:embark")
(declare-function embark--restart "ext:embark")
(declare-function embark--confirm "ext:embark")

(hel-set-multiple-cursors-command 'embark-act)
(hel-set-multiple-cursors-command 'embark-dwim)

(hel-advice-add 'embark-act  :around 'hel-embark-act-a)
(hel-advice-add 'embark-dwim :around 'hel-embark-act-a)

(defvar hel--embark-action nil
  "Embark action chosen for the real cursor.")

(hel-define-advice embark--act (:before (action &rest _))
  "Remember the Embark ACTION chosen for the real cursor."
  (unless hel-executing-command-for-fake-cursor
    (setq hel--embark-action action)))

(defun hel-embark-act-a (orig-fun &rest args)
  "Around advice for `embark-act'/`embark-dwim' fanning them out to all cursors.
For the real cursor, forget any previously recorded action and run
ORIG-FUN normally (which records the chosen action, see the advice on
`embark--act').  When Hel replays the command for a fake cursor, skip
the prompter: recompute the target at that cursor's point and run the
recorded action on it, suppressing confirmations and restarts."
  (if hel-executing-command-for-fake-cursor
      (when hel--embark-action
        (when-let* ((targets (embark--targets)))
          (cl-letf (((symbol-function 'embark--restart) #'ignore)
                    ((symbol-function 'embark--confirm) #'ignore))
            (embark--act hel--embark-action (car targets)))))
    ;; else
    (setq hel--embark-action nil)
    (apply orig-fun args)))

;;;; magit

(defvar magit-blame-mode)

(add-hook 'magit-blame-mode-hook
          (defun hel-magit-blame-h ()
            "Switch to Emacs state while `magit-blame-mode' is enabled."
            (when hel-local-mode
              (if magit-blame-mode
                  (hel-emacs-state)
                (hel-normal-state)))))

;;; .
(provide 'hel-integration)
;;; hel-integration.el ends here
