;;; xah-fly-keys.el --- ergonomic modal keybinding minor mode. -*- coding: utf-8; lexical-binding: t; -*-

;; Copyright © 2013, 2024 by Xah Lee

;; Author: Xah Lee ( http://xahlee.info/ )
;; Maintainer: Xah Lee <xah@xahlee.org>
;; Version: 24.24.20240406145635
;; Created: 2013-09-10
;; Package-Requires: ((emacs "27"))
;; Keywords: convenience, vi, vim, ergoemacs, keybinding
;; License: GPL v3.
;; Homepage: http://xahlee.info/emacs/misc/xah-fly-keys.html

;; This file is not part of GNU Emacs.

;;; Commentary:

;; xah-fly-keys is a efficient keybinding for emacs. It is modal like
;; vi, but key choices are based on statistics of command call
;; frequency.

;;; Usage:

;; M-x xah-fly-keys to toggle the mode on/off.

;; Important command/insert mode switch keys:

;; `xah-fly-command-mode-activate' (press <escape> or <home> or F8 or Alt+Space or Ctrl+Space or menu key. Note: escape only works when in GUI mode, by design.)

;; `xah-fly-insert-mode-activate' (when in command mode, press qwerty letter key f.)

;; When in command mode:

;; "f" calls `xah-fly-insert-mode-activate'.

;; Space is a leader key. For example, "SPC r" calls `query-replace'.
;; Press "SPC C-h" to see the full list.

;; "SPC SPC" also activates insertion mode.

;; "SPC RET" calls `execute-extended-command'.

;; "a" calls `execute-extended-command'.

;; The leader key sequence basically supplant ALL emacs commands that
;; starts with C-x key.

;; When using xah-fly-keys, you don't need to press Control or Meta,
;; with the following exceptions:

;; "C-c" for major mode commands.
;; "C-g" for cancel.
;; "C-q" for quoted-insert.
;; "C-h" for getting a list of keys following a prefix/leader key.

;; Leader key

;; You NEVER need to press "C-x"

;; Any emacs command that has a keybinding starting with C-x, has also
;; a key sequence binding in xah-fly-keys. For example,

;; "C-x b" for `switch-to-buffer' is "SPC f"
;; "C-x C-f" for `find-file' is "SPC i e"
;; "C-x n n" for `narrow-to-region' is "SPC l l"

;; The first key we call it leader key. In the above examples, the SPC
;; is the leader key.

;; When in command mode, the "SPC" is a leader key.

;; the following standard keys with Control are supported:

;; "C-TAB" `xah-next-user-buffer'
;; "C-S-TAB" `xah-previous-user-buffer'
;; "C-v" paste
;; "C-w" close
;; "C-z" undo
;; "C-n" new
;; "C-o" open
;; "C-s" save
;; "C-S-s" save as
;; "C-S-t" open last closed
;; "C-+" `text-scale-increase'
;; "C--" `text-scale-decrease'

;; To disable both Control and Meta shortcut keys, add the following
;; lines to you init.el BEFORE loading xah-fly-keys:

;; (setq xah-fly-use-control-key nil)
;; (setq xah-fly-use-meta-key nil)

;; If you have a bug, post on github.

;; For detail about design and other info, see home page at
;; http://xahlee.info/emacs/misc/xah-fly-keys.html

;; If you like this project, Buy Xah Emacs Tutorial
;; http://xahlee.info/emacs/emacs/buy_xah_emacs_tutorial.html or make
;; a donation. Thanks.

;;; Installation:
;; here's how to manual install
;;
;; put the file xah-fly-keys.el in ~/.emacs.d/lisp/
;; create the dir if doesn't exist.
;;
;; put the following in your emacs init file:
;; (add-to-list 'load-path "~/.emacs.d/lisp/")
;; (require 'xah-fly-keys)
;; (xah-fly-keys-set-layout "qwerty") ; required
;; (xah-fly-keys 1)
;;
;; possible layout values:
;; adnw
;; azerty
;; azerty-be
;; beopy
;; bepo
;; carpalx-qfmlwy
;; carpalx-qgmlwb
;; carpalx-qgmlwy
;; colemak
;; colemak-dhm
;; colemak-dhm-angle
;; colemak-dhk
;; dvorak
;; koy
;; neo2
;; norman
;; programer-dvorak
;; pt-nativo
;; qwerty
;; qwerty-abnt
;; qwerty-no (qwerty Norwegian)
;; qwertz
;; workman
;;
;; supported layouts are stored in the variable xah-fly-layouts


;;; Code:

(require 'dired)
(require 'dired-x)
(require 'seq)



(defgroup xah-fly-keys nil
  "Ergonomic modal keybinding minor mode."
  :group 'keyboard)

(defvar xah-fly-command-mode-activate-hook nil "Hook for `xah-fly-command-mode-activate'")
(defvar xah-fly-insert-mode-activate-hook nil "Hook for `xah-fly-insert-mode-activate'")

(defvar xah-fly-command-mode-indicator "c"
  "Character in mode line indicating command mode is active.")
(defvar xah-fly-insert-mode-indicator "i"
  "Character in mode line indicating insert mode is active.")

(defcustom xah-fly-use-control-key t
  "If nil, do not bind any control key. When t, standard keys for open, close, copy, paste etc, are bound."
  :type 'boolean)

(defcustom xah-fly-use-meta-key t
  "If nil, do not bind any meta key."
  :type 'boolean)

(defcustom xah-fly-use-isearch-arrows t
  "If nil, no change to any key in isearch (`isearch-forward'). Otherwise, arrow keys are for moving between occurrences, and C-v is paste."
  :type 'boolean)

(when (not (boundp 'xah-repeat-key))
  (defvar xah-repeat-key nil "A key that some xah command use as a key to repeat the command, pressed right after command call. Value should be the same format that `kbd' returns. e.g. (kbd \"m\")")
  (if xah-repeat-key nil (setq xah-repeat-key (kbd "m"))))

(defun xah-get-pos-of-block ()
  "Return the [begin end] positions of current text block.
Return value is a vector.
Version: 2024-03-23"
  (let (xp1 xp2 (xblankRegex "\n[ \t]*\n"))
    (save-excursion
      (setq xp1 (if (re-search-backward xblankRegex nil 1)
                    (goto-char (match-end 0))
                  (point)))
      (setq xp2 (if (re-search-forward xblankRegex nil 1)
                    (match-beginning 0)
                  (point))))
    (vector xp1 xp2)))

(defun xah-get-pos-of-block-or ()
  "If region is active, return its [begin end] positions, else same as `xah-get-pos-of-block'.
Return value is a vector.
Version: 2024-03-23"
  (if (region-active-p)
      (vector (region-beginning) (region-end))
    (xah-get-pos-of-block)))


;; cursor movement

(defun xah-pop-local-mark-ring ()
  "Move cursor to last mark position of current buffer.
Repeat call cycles all positions in `mark-ring'.

URL `http://xahlee.info/emacs/emacs/emacs_cycle_local_mark_ring.html'
Version: 2016-04-04 2023-09-03"
  (interactive)
  (set-mark-command t))

(defun xah-beginning-of-line-or-block ()
  "Move cursor to beginning of indent or line, end of previous block, in that order.

If `visual-line-mode' is on, beginning of line means visual line.

URL `http://xahlee.info/emacs/emacs/emacs_keybinding_design_beginning-of-line-or-block.html'
Version: 2018-06-04 2022-07-03 2022-07-06 2023-10-04"
  (interactive)
  (let ((xp (point)))
    (if (or (eq (point) (line-beginning-position))
            (eq last-command this-command))
        (when (re-search-backward "\n[\t\n ]*\n+" nil :move)
          (skip-chars-backward "\n\t ")
          ;; (forward-char)
          )
      (if visual-line-mode
          (beginning-of-visual-line)
        (if (eq major-mode 'eshell-mode)
            (progn
              (declare-function eshell-bol "esh-mode.el" ())
              (eshell-bol))
          (back-to-indentation)
          (when (eq xp (point))
            (beginning-of-line)))))))

(defun xah-end-of-line-or-block ()
  "Move cursor to end of line or next block.

• When called first time, move cursor to end of line.
• When called again, move cursor forward by jumping over any sequence of whitespaces containing 2 blank lines.
• if `visual-line-mode' is on, end of line means visual line.

URL `http://xahlee.info/emacs/emacs/emacs_keybinding_design_beginning-of-line-or-block.html'
Version: 2018-06-04 2022-03-05 2023-10-04"
  (interactive)
  (if (or (eq (point) (line-end-position))
          (eq last-command this-command))
      (re-search-forward "\n[\t\n ]*\n+" nil :move)
    (if visual-line-mode
        (end-of-visual-line)
      (end-of-line))))

(defvar xah-brackets '( "“”" "()" "[]" "{}" "<>" "＜＞" "（）" "［］" "｛｝" "⦅⦆" "〚〛" "⦃⦄" "‹›" "«»" "「」" "〈〉" "《》" "【】" "〔〕" "⦗⦘" "『』" "〖〗" "〘〙" "｢｣" "⟦⟧" "⟨⟩" "⟪⟫" "⟮⟯" "⟬⟭" "⌈⌉" "⌊⌋" "⦇⦈" "⦉⦊" "❛❜" "❝❞" "❨❩" "❪❫" "❴❵" "❬❭" "❮❯" "❰❱" "❲❳" "〈〉" "⦑⦒" "⧼⧽" "﹙﹚" "﹛﹜" "﹝﹞" "⁽⁾" "₍₎" "⦋⦌" "⦍⦎" "⦏⦐" "⁅⁆" "⸢⸣" "⸤⸥" "⟅⟆" "⦓⦔" "⦕⦖" "⸦⸧" "⸨⸩" "｟｠")
 "A list of strings, each element is a string of 2 chars, the left bracket and a matching right bracket.
Used by `xah-select-text-in-quote' and others.
Version: 2024-01-01")

(defconst xah-left-brackets
  (mapcar (lambda (x) (substring x 0 1)) xah-brackets)
  "List of left bracket chars. Each element is a string.")

(defconst xah-right-brackets
  (mapcar (lambda (x) (substring x 1 2)) xah-brackets)
  "List of right bracket chars. Each element is a string.")

(defun xah-backward-left-bracket ()
  "Move cursor to the previous occurrence of left bracket.
The list of brackets to jump to is defined by `xah-left-brackets'.

URL `http://xahlee.info/emacs/emacs/emacs_navigating_keys_for_brackets.html'
Version: 2015-10-01"
  (interactive)
  (re-search-backward (regexp-opt xah-left-brackets) nil t))

(defun xah-forward-right-bracket ()
  "Move cursor to the next occurrence of right bracket.
The list of brackets to jump to is defined by `xah-right-brackets'.

URL `http://xahlee.info/emacs/emacs/emacs_navigating_keys_for_brackets.html'
Version: 2015-10-01"
  (interactive)
  (re-search-forward (regexp-opt xah-right-brackets) nil t))

(defun xah-goto-matching-bracket ()
  "Move cursor to the matching bracket.
If cursor is not on a bracket, call `backward-up-list'.
The list of brackets to jump to is defined by `xah-left-brackets' and `xah-right-brackets'.

URL `http://xahlee.info/emacs/emacs/emacs_navigating_keys_for_brackets.html'
Version: 2016-11-22 2023-07-22 2023-08-02"
  (interactive)
  (if (nth 3 (syntax-ppss))
      (backward-up-list 1 'ESCAPE-STRINGS 'NO-SYNTAX-CROSSING)
    (cond
     ((eq (char-after) ?\") (forward-sexp))
     ((eq (char-before) ?\") (backward-sexp))
     ((looking-at (regexp-opt xah-left-brackets))
      (forward-sexp))
     ((if (eq (point-min) (point))
          nil
        (prog2
            (backward-char)
            (looking-at (regexp-opt xah-right-brackets))
          (forward-char)))
      ;; (prog2 (backward-char) (looking-at (regexp-opt xah-right-brackets)) (forward-char))
      (backward-sexp))
     (t (backward-up-list 1 'ESCAPE-STRINGS 'NO-SYNTAX-CROSSING)))))

(defvar xah-punctuation-regex nil "A regex string for the purpose of moving cursor to a punctuation.")
(setq xah-punctuation-regex "[\"]")

(defun xah-forward-punct ()
  "Move cursor to the next occurrence of punctuation.
Punctuations is defined by `xah-punctuation-regex'

URL `http://xahlee.info/emacs/emacs/emacs_jump_to_punctuations.html'
Version 2017-06-26 2024-01-20"
  (interactive)
  (re-search-forward xah-punctuation-regex nil t))

(defun xah-backward-punct ()
  "Move cursor to the previous occurrence of punctuation.
See `xah-forward-punct'

URL `http://xahlee.info/emacs/emacs/emacs_jump_to_punctuations.html'
Version 2017-06-26 2024-01-20"
  (interactive)
  (re-search-backward xah-punctuation-regex nil t))

(defun xah-sort-lines ()
  "Like `sort-lines' but if no region, do the current block.
Version: 2022-01-22 2024-03-19"
  (interactive)
  (let (xp1 xp2)
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (sort-lines current-prefix-arg xp1 xp2)))

(defun xah-narrow-to-region ()
  "Same as `narrow-to-region', but if no selection, narrow to the current block.
Version: 2022-01-22 2024-03-19"
  (interactive)
  (let (xp1 xp2)
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (narrow-to-region xp1 xp2)))


;; editing commands

(defun xah-copy-line-or-region ()
  "Copy current line or selection.
When called repeatedly, append copy subsequent lines.
If `universal-argument' is called first, copy whole buffer (respects `narrow-to-region').

URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_current_line.html'
Version: 2010-05-21 2022-10-03"
  (interactive)
  (let ((inhibit-field-text-motion nil))
    (if current-prefix-arg
        (progn
          (copy-region-as-kill (point-min) (point-max)))
      (if (region-active-p)
          (progn
            (copy-region-as-kill (region-beginning) (region-end)))
        (if (eq last-command this-command)
            (if (eobp)
                (progn )
              (progn
                (kill-append "\n" nil)
                (kill-append
                 (buffer-substring (line-beginning-position) (line-end-position))
                 nil)
                (progn
                  (end-of-line)
                  (forward-char))))
          (if (eobp)
              (if (eq (char-before) 10 )
                  (progn )
                (progn
                  (copy-region-as-kill (line-beginning-position) (line-end-position))
                  (end-of-line)))
            (progn
              (copy-region-as-kill (line-beginning-position) (line-end-position))
              (end-of-line)
              (forward-char))))))))

(defun xah-cut-line-or-region ()
  "Cut current line or selection.
If `universal-argument' is called first, cut whole buffer (respects `narrow-to-region').

URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_current_line.html'
Version: 2010-05-21 2015-06-10"
  (interactive)
  (if current-prefix-arg
      (progn ; not using kill-region because we don't want to include previous kill
        (kill-new (buffer-string))
        (delete-region (point-min) (point-max)))
    (progn (if (region-active-p)
               (kill-region (region-beginning) (region-end) t)
             (kill-region (line-beginning-position) (line-beginning-position 2))))))

(defun xah-copy-all-or-region ()
  "Copy buffer or selection content to `kill-ring'.
Respects `narrow-to-region'.

URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_all_or_region.html'
Version: 2015-08-22"
  (interactive)
  (if (region-active-p)
      (progn
        (kill-new (buffer-substring (region-beginning) (region-end)))
        (message "Text selection copied."))
    (progn
      (kill-new (buffer-string))
      (message "Buffer content copied."))))

(defun xah-cut-all-or-region ()
  "Cut buffer or selection content to `kill-ring'.
Respects `narrow-to-region'.

URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_all_or_region.html'
Version: 2015-08-22"
  (interactive)
  (if (region-active-p)
      (progn
        (kill-new (buffer-substring (region-beginning) (region-end)))
        (delete-region (region-beginning) (region-end)))
    (progn
      (kill-new (buffer-string))
      (delete-region (point-min) (point-max)))))

(defun xah-copy-all ()
  "Put the whole buffer content into the `kill-ring'.
(respects `narrow-to-region')
Version: 2016-10-06"
  (interactive)
  (kill-new (buffer-string))
  (message "Buffer content copied."))

(defun xah-cut-all ()
  "Cut the whole buffer content into the `kill-ring'.
Respects `narrow-to-region'.
Version: 2017-01-03"
  (interactive)
  (kill-new (buffer-string))
  (delete-region (point-min) (point-max)))

(defun xah-paste-or-paste-previous ()
  "Paste. When called repeatedly, paste previous.
This command calls `yank', and if repeated, call `yank-pop'.

If `universal-argument' is called first with a number arg, paste that many times.

URL `http://xahlee.info/emacs/emacs/emacs_paste_or_paste_previous.html'
Version: 2017-07-25 2020-09-08"
  (interactive)
  (progn
    (when (and delete-selection-mode (region-active-p))
      (delete-region (region-beginning) (region-end)))
    (if current-prefix-arg
        (progn
          (dotimes (_ (prefix-numeric-value current-prefix-arg))
            (yank)))
      (if (eq real-last-command this-command)
          (yank-pop 1)
        (yank)))))

(defconst xah-show-kill-ring-separator "\n\nSfR2h________________________________________________________________________\n\n"
  "A line divider for `xah-show-kill-ring'.")

(defun xah-show-kill-ring ()
  "Insert all `kill-ring' content in a new buffer named *copy history*.

URL `http://xahlee.info/emacs/emacs/emacs_show_kill_ring.html'
Version: 2019-12-02 2021-07-03"
  (interactive)
  (let ((xbuf (generate-new-buffer "*copy history*"))
        (inhibit-read-only t))
    (progn
      (switch-to-buffer xbuf)
      (funcall 'fundamental-mode)
      (mapc
       (lambda (x)
         (insert x xah-show-kill-ring-separator ))
       kill-ring))
    (goto-char (point-min))))

(defun xah-move-block-up ()
  "Swap the current text block with the previous.
After this command is called, press <up> or <down> to move. Any other key to exit.
Version: 2022-03-04"
  (interactive)
  (let ((xp0 (point))
        xc1 ; current block begin
        xc2 ; current Block End
        xp1 ; prev Block Begin
        xp2 ; prev Block end
        )
    (if (re-search-forward "\n[ \t]*\n+" nil "move")
        (setq xc2 (match-beginning 0))
      (setq xc2 (point)))
    (goto-char xp0)
    (if (re-search-backward "\n[ \t]*\n+" nil "move")
        (progn
          (skip-chars-backward "\n \t")
          (setq xp2 (point))
          (skip-chars-forward "\n \t")
          (setq xc1 (point)))
      (error "No previous block."))
    (goto-char xp2)
    (if (re-search-backward "\n[ \t]*\n+" nil "move")
        (progn
          (setq xp1 (match-end 0)))
      (setq xp1 (point)))
    (transpose-regions xp1 xp2 xc1 xc2)
    (goto-char xp1)
    (set-transient-map
     (let ((xkmap (make-sparse-keymap)))
       (define-key xkmap (kbd "<up>") #'xah-move-block-up)
       (define-key xkmap (kbd "<down>") #'xah-move-block-down)
       xkmap))))

(defun xah-move-block-down ()
  "Swap the current text block with the next.
After this command is called, press <up> or <down> to move. Any other key to exit.
Version: 2022-03-04"
  (interactive)
  (let ((xp0 (point))
        xc1 ; current block begin
        xc2 ; current Block End
        xn1 ; next Block Begin
        xn2 ; next Block end
        )
    (if (eq (point-min) (point))
        (setq xc1 (point))
      (if (re-search-backward "\n\n+" nil "move")
          (progn
            (setq xc1 (match-end 0)))
        (setq xc1 (point))))
    (goto-char xp0)
    (if (re-search-forward "\n[ \t]*\n+" nil "move")
        (progn
          (setq xc2 (match-beginning 0))
          (setq xn1 (match-end 0)))
      (error "No next block."))
    (if (re-search-forward "\n[ \t]*\n+" nil "move")
        (progn
          (setq xn2 (match-beginning 0)))
      (setq xn2 (point)))
    (transpose-regions xc1 xc2 xn1 xn2)
    (goto-char xn2))
  (set-transient-map
   (let ((xkmap (make-sparse-keymap)))
     (define-key xkmap (kbd "<up>") #'xah-move-block-up)
     (define-key xkmap (kbd "<down>") #'xah-move-block-down)
     xkmap)))

(defun xah-delete-left-char-or-selection ()
  "Delete backward 1 character, or selection.
Version: 2022-01-22"
  (interactive)
  (if (region-active-p)
      (progn (delete-region (region-beginning) (region-end)))
    (delete-char -1)))

(defun xah-delete-backward-char ()
  "Delete one char backward.
Version: 2023-07-22"
  (interactive)
  (delete-char -1))

(defun xah-delete-forward-bracket-pairs (&optional DeleteInnerTextQ)
  "Delete the matching bracket/quote text to the right of cursor.
e.g. ▮(a b c)

In lisp code, if DeleteInnerTextQ is true, also delete the inner text.

After the command, mark is set at the left matching bracket position, so you can `exchange-point-and-mark' to select it.

This command assumes the char to the right of point is a left bracket or quote, and have a matching one after.

What char is considered bracket or quote is determined by current syntax table.

URL `http://xahlee.info/emacs/emacs/emacs_delete_backward_char_or_bracket_text.html'
Version: 2017-07-02 2023-07-30"
  (interactive (list t))
  (if DeleteInnerTextQ
      (progn
        (mark-sexp)
        (kill-region (region-beginning) (region-end)))
    (let ((xpt (point)))
      (forward-sexp)
      (delete-char -1)
      (push-mark (point) t)
      (goto-char xpt)
      (delete-char 1))))

(defun xah-delete-string-backward ()
  "Delete string to the left of cursor.
e.g. 「\"▮some\"▮」
If `universal-argument' is called first, just delete the quotation marks.

Version: 2023-11-12 2024-03-14"
  (interactive)
  (when (prog2 (backward-char) (looking-at "\\s\"") (forward-char))
    (let ((xp0 (point)) xp1 xp2)
      ;; xp1 xp2 are the begin and end pos of the string
      (if (nth 3 (syntax-ppss))
          (setq xp1 (1- xp0)
                xp2
                (progn
                  (backward-char)
                  (forward-sexp)
                  (point)))
        (setq xp2 (point)
              xp1
              (progn (forward-sexp -1) (point))))
      (if current-prefix-arg
          (progn (goto-char xp2)
                 (delete-char -1)
                 (goto-char xp1)
                 (delete-char 1))
        (kill-region xp1 xp2)))))

(defun xah-delete-backward-bracket-text ()
  "Delete the matching bracket/quote text to the left of cursor.
e.g. (a b c)▮

This command assumes the left of cursor is a right bracket, and there is a matching one before it.

What char is considered bracket or quote is determined by current syntax table.

URL `http://xahlee.info/emacs/emacs/emacs_delete_backward_char_or_bracket_text.html'
Version: 2017-09-21 2023-07-30"
  (interactive)
  (progn
    (forward-sexp -1)
    (mark-sexp)
    (kill-region (region-beginning) (region-end))))

(defun xah-delete-backward-bracket-pair ()
  "Delete the matching brackets/quotes to the left of cursor.
After call, mark is set at the matching bracket position, so you can `exchange-point-and-mark' to select it.

This command assumes the left of point is a right bracket, and there is a matching one before it.

What char is considered bracket or quote is determined by current syntax table.

URL `http://xahlee.info/emacs/emacs/emacs_delete_backward_char_or_bracket_text.html'
Version: 2017-07-02"
  (interactive)
  (let ((xp0 (point)) xp1)
    (forward-sexp -1)
    (setq xp1 (point))
    (goto-char xp0)
    (delete-char -1)
    (goto-char xp1)
    (delete-char 1)
    (push-mark (point) t)
    (goto-char (- xp0 2))))

(defun xah-delete-bracket-text-backward ()
  "Delete bracket pair and inner text to the left of cursor.
e.g.  「(▮some)▮」
The bracket can be paren, square bracket, curly bracket, or any matching pair in syntax table.

The deleted text can be pasted later.

What char is considered bracket is determined by current syntax table.

If cursor left is not a bracket, nothing is done.

If `universal-argument' is called first, do not delete inner text.

URL `http://xahlee.info/emacs/emacs/emacs_delete_backward_char_or_bracket_text.html'
Version: 2017-07-02 2023-07-22 2023-07-30"
  (interactive)
  (cond
   ((prog2 (backward-char) (looking-at "\\s)") (forward-char))
    (if current-prefix-arg
        (xah-delete-backward-bracket-pair)
      (xah-delete-backward-bracket-text)))
   ((prog2 (backward-char) (looking-at "\\s(") (forward-char))
    (let ((xp0 (point)))
      (progn
        (goto-char (1- xp0))
        (forward-sexp)
        (if current-prefix-arg
            (progn
              (delete-char -1)
              (goto-char xp0)
              (delete-char -1))
          (kill-region (1- xp0) (point))))))
   ))

(defun xah-delete-blank-lines ()
  "Delete all newline around cursor.

URL `http://xahlee.info/emacs/emacs/emacs_shrink_whitespace.html'
Version: 2018-04-02"
  (interactive)
  (let (xp3 xp4)
          (skip-chars-backward "\n")
          (setq xp3 (point))
          (skip-chars-forward "\n")
          (setq xp4 (point))
          (delete-region xp3 xp4)))

(defun xah-fly-delete-spaces ()
  "Delete space, tab, IDEOGRAPHIC SPACE (U+3000) around cursor.
Version: 2019-06-13"
  (interactive)
  (let (xp1 xp2)
    (skip-chars-forward " \t　")
    (setq xp2 (point))
    (skip-chars-backward " \t　")
    (setq xp1 (point))
    (delete-region xp1 xp2)))

(defun xah-shrink-whitespaces ()
  "Remove whitespaces around cursor .

Shrink neighboring spaces, then newlines, then spaces again, leaving one space or newline at each step, till no more white space.

URL `http://xahlee.info/emacs/emacs/emacs_shrink_whitespace.html'
Version: 2014-10-21 2021-11-26 2021-11-30 2023-07-12"
  (interactive)
  (let ((xeol-count 0)
        (xp0 (point))
        xp1  ; whitespace begin
        xp2  ; whitespace end
        (xcharBefore (char-before))
        (xcharAfter (char-after))
        xspace-neighbor-p)

    (setq xspace-neighbor-p (or (eq xcharBefore 32) (eq xcharBefore 9) (eq xcharAfter 32) (eq xcharAfter 9)))

    (skip-chars-backward " \n\t　")
    (setq xp1 (point))
    (goto-char xp0)
    (skip-chars-forward " \n\t　")
    (setq xp2 (point))
    (goto-char xp1)
    (while (search-forward "\n" xp2 t)
      (setq xeol-count (1+ xeol-count)))
    (goto-char xp0)
    (cond
     ((eq xeol-count 0)
      (if (> (- xp2 xp1) 1)
          (progn
            (delete-horizontal-space) (insert " "))
        (progn (delete-horizontal-space))))
     ((eq xeol-count 1)
      (if xspace-neighbor-p
          (xah-fly-delete-spaces)
        (progn (xah-delete-blank-lines) (insert " "))))
     ((eq xeol-count 2)
      (if xspace-neighbor-p
          (xah-fly-delete-spaces)
        (progn
          (xah-delete-blank-lines)
          (insert "\n"))))
     ((> xeol-count 2)
      (if xspace-neighbor-p
          (xah-fly-delete-spaces)
        (progn
          (goto-char xp2)
          (search-backward "\n")
          (delete-region xp1 (point))
          (insert "\n"))))
     (t (progn
          (message "nothing done. logic error 40873. shouldn't reach here"))))))

;; (defun xah-shrink-whitespaces ()
;;   "Remove whitespaces around cursor.

;; Shrink neighboring whitespace.
;; First shrink space or tab, then newlines.
;; Repeated calls eventually results in no whitespace around cursor.

;; URL `http://xahlee.info/emacs/emacs/emacs_shrink_whitespace.html'
;; Version: 2014-10-21 2023-07-26 2023-08-02"
;;   (interactive)
;;   (cond
;;    ((if (eq (point-min) (point))
;;         nil
;;       (prog2 (backward-char) (looking-at "[ \t]") (forward-char)))
;;     (progn
;;       ;; (print (format "space on left"))
;;       (delete-char (- (skip-chars-backward " \t")))))
;;    ((looking-at "[ \t]")
;;     (progn
;;       ;; (print (format "space on right"))
;;       (delete-char (- (skip-chars-forward " \t")))))
;;    ((or
;;      (and (eq (char-before) 10) (eq (char-after) 10))
;;      (looking-at "\n\n")
;;      (and (eq (char-before (point)) 10) (eq (char-before (1- (point))) 10)))
;;     (progn
;;       ;; (print (format "2 newlines on left or right, or one each"))
;;       (delete-char (- (skip-chars-backward "\n")))
;;       (delete-char (- (skip-chars-forward "\n")))
;;       (insert "\n")))
;;    (t
;;     (progn
;;       ;; (print (format "catch all"))
;;       (delete-char (- (skip-chars-backward " \n")))
;;       (delete-char (- (skip-chars-forward " \n")))))))

(defvar xah-smart-delete-dispatch
  nil
  "Used by `xah-smart-delete'.
This makes that function behavior `major-mode' dependent.
Value is Alist of pairs, each is of the form
(‹major-mode-name› . ‹function-name›)
If the major mode name match current buffer, the paired function is called.
If nothing match, `xah-smart-delete' default behavior is used.
Version: 2023-11-12")

(setq xah-smart-delete-dispatch
      '((xah-wolfram-mode . xah-wolfram-smart-delete-backward)
        (xah-html-mode . xah-html-smart-delete-backward)))

(defun xah-smart-delete ()
  "Smart backward delete.
Typically, delete to the left 1 char or entire bracketed text.
Behavior depends on what's left char, and current `major-mode'.
This command never delete text to the right of cursor.

If region active, delete region.
If cursor left is space tab linefeed, delete continuous sequence of them.
If `xah-smart-delete-dispatch' match, call the matched function.
If cursor left is string quote, delete the string.
If cursor left is bracket, delete the bracketed text.
Else just delete one char to the left.

Version: 2023-07-22 2023-08-10 2023-08-23 2023-11-12"
  (interactive)
  (let (xfun)
    (cond
     ((region-active-p) (delete-region (region-beginning) (region-end)))
     ;; 32 is space, 9 is tab, 10 is linefeed
     ((eq (char-before) 32) (while (eq (char-before) 32) (delete-char -1)))
     ((eq (char-before) 9) (while (eq (char-before) 9) (delete-char -1)))
     ((eq (char-before) 10) (while (eq (char-before) 10) (delete-char -1)))
     ((setq xfun (assq major-mode xah-smart-delete-dispatch))
      (message "calling cdr of %s" xfun)
      (funcall (cdr xfun)))
     ((prog2 (backward-char) (looking-at "\\s(\\|\\s)") (forward-char))
      (message "calling xah-delete-bracket-text-backward")
      (xah-delete-bracket-text-backward))
     ((prog2 (backward-char) (looking-at "\\s\"") (forward-char))
      (message "calling xah-delete-string-backward")
      (xah-delete-string-backward))
     (t (delete-char -1)))))

(defun xah-change-bracket-pairs (FromChars ToChars)
  "Change bracket pairs to another type or none.
For example, change all parenthesis () to square brackets [].
Works on current block or selection.

In lisp code, FromChars is a string with at least 2 spaces.
e.g.
paren ( )
french angle ‹ ›
double bracket [[ ]]
etc.
It is split by space, and last 2 items are taken as left and right brackets.

ToChars is similar, with a special value of
none
followed by 2 spaces.
,it means replace by empty string.

URL `http://xahlee.info/emacs/emacs/elisp_change_brackets.html'
Version: 2020-11-01 2023-09-29 2024-03-19"
  (interactive
   (let ((xbrackets
          '(
            "square [ ]"
            "brace { }"
            "paren ( )"
            "greater < >"
            "double quote \" \""
            "single quote ' '"
            "emacs ` '"
            "markdown grave accent ` `"
            "double square [[ ]]"
            "tilde ~ ~"
            "equal = ="
            "curly double quote “ ”"
            "curly single quote ‘ ’"
            "french angle ‹ ›"
            "french double angle « »"
            "corner 「 」"
            "white corner 『 』"
            "lenticular 【 】"
            "white lenticular 〖 〗"
            "angle 〈 〉"
            "double angle 《 》"
            "tortoise 〔 〕"
            "white tortoise 〘 〙"
            "white square 〚 〛"
            "white paren ⦅ ⦆"
            "white curly bracket ⦃ ⦄"
            "pointing angle 〈 〉"
            "angle with dot ⦑ ⦒"
            "curved angle ⧼ ⧽"
            "math square ⟦ ⟧"
            "math angle ⟨ ⟩"
            "math double angle ⟪ ⟫"
            "math flattened parenthesis ⟮ ⟯"
            "math white tortoise shell ⟬ ⟭"
            "heavy single quotation mark ornament ❛ ❜"
            "heavy double turned comma quotation mark ornament ❝ ❞"
            "medium parenthesis ornament ❨ ❩"
            "medium flattened parenthesis ornament ❪ ❫"
            "medium curly ornament ❴ ❵"
            "medium pointing angle ornament ❬ ❭"
            "heavy pointing angle quotation mark ornament ❮ ❯"
            "heavy pointing angle ornament ❰ ❱"
            "none  "
            )))
     (let ((completion-ignore-case t))
       (list
        (completing-read "Replace this:" xbrackets nil t nil nil (car xbrackets))
        (completing-read "To:" xbrackets nil t nil nil (car (last xbrackets)))))))
  (let (xp1 xp2 xleft xright xtoL xtoR)
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (let ((xsFrom (last (split-string FromChars " ") 2))
          (xsTo (last (split-string ToChars " ") 2)))

      ;; (when (< (length xsFrom) 3)
      ;; (error "cannot find input brackets %s" xsFrom))

      ;; (when (< (length xsTo) 3)
      ;;   (message "replace blacket is empty string")
      ;;   (setq xsTo (list "" "" "")))

      (setq xleft (car xsFrom)  xright (car (cdr xsFrom))
            xtoL (car xsTo) xtoR (car (cdr xsTo)))

      (save-excursion
        (save-restriction
          (narrow-to-region xp1 xp2)
          (let ((case-fold-search nil))
            (if (string-equal xleft xright)
                (let ((xx (regexp-quote xleft)))
                  (goto-char (point-min))
                  (while
                      (re-search-forward
                       (format "%s\\([^%s]+?\\)%s" xx xx xx)
                       nil t)
                    (overlay-put (make-overlay (match-beginning 0) (match-end 0)) 'face 'highlight)
                    (replace-match (concat xtoL "\\1" xtoR) t)))
              (progn
                (progn
                  (goto-char (point-min))
                  (while (search-forward xleft nil t)
                    (overlay-put (make-overlay (match-beginning 0) (match-end 0)) 'face 'highlight)
                    (replace-match xtoL t t)))
                (progn
                  (goto-char (point-min))
                  (while (search-forward xright nil t)
                    (overlay-put (make-overlay (match-beginning 0) (match-end 0)) 'face 'highlight)
                    (replace-match xtoR t t)))))))))))

(defun xah-toggle-letter-case ()
  "Toggle the letter case of current word or selection.
Always cycle in this order: Init Caps, ALL CAPS, all lower.

URL `http://xahlee.info/emacs/emacs/emacs_toggle_letter_case.html'
Version: 2020-06-26 2023-11-14"
  (interactive)
  (let ( (deactivate-mark nil) xp1 xp2)
    (if (region-active-p)
        (setq xp1 (region-beginning) xp2 (region-end))
      (save-excursion
        (skip-chars-backward "[:alpha:]")
        (setq xp1 (point))
        (skip-chars-forward "[:alpha:]")
        (setq xp2 (point))))
    (when (not (eq last-command this-command))
      (put this-command 'state 0))
    (cond
     ((equal 0 (get this-command 'state))
      (upcase-initials-region xp1 xp2)
      (put this-command 'state 1))
     ((equal 1 (get this-command 'state))
      (upcase-region xp1 xp2)
      (put this-command 'state 2))
     ((equal 2 (get this-command 'state))
      (downcase-region xp1 xp2)
      (put this-command 'state 0)))))

;; test case
;; test_case some
;; test-case
;; tes▮t-case

(defun xah-toggle-previous-letter-case ()
  "Toggle the letter case of the letter to the left of cursor.

URL `http://xahlee.info/emacs/emacs/emacs_toggle_letter_case.html'
Version: 2015-12-22 2023-11-14"
  (interactive)
  (let ((case-fold-search nil))
    (left-char 1)
    (cond
     ((looking-at "[[:lower:]]") (upcase-region (point) (1+ (point))))
     ((looking-at "[[:upper:]]") (downcase-region (point) (1+ (point)))))
    (right-char)))

(defun xah-upcase-sentence ()
  "Upcase first letters of sentences of current block or selection.

URL `http://xahlee.info/emacs/emacs/emacs_upcase_sentence.html'
Version: 2020-12-08 2022-08-27 2024-03-19"
  (interactive)
  (let (xp1 xp2)
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (let ((case-fold-search nil))
        ;; after period or question mark or exclamation
        (goto-char (point-min))
        (while (re-search-forward "\\(\\.\\|\\?\\|!\\)[ \n]+ *\\([a-z]\\)" nil :move)
          (upcase-region (match-beginning 2) (match-end 2))
          (overlay-put (make-overlay (match-beginning 2) (match-end 2)) 'face 'highlight))
        ;; after a blank line, after a bullet, or beginning of buffer
        (goto-char (point-min))
        (while (re-search-forward "\\(\\`\\|• \\|\n\n\\)\\([a-z]\\)" nil :move)
          (upcase-region (match-beginning 2) (match-end 2))
          (overlay-put (make-overlay (match-beginning 2) (match-end 2)) 'face 'highlight))
        ;; for HTML. first letter after tag
        (when
            (or
             (eq major-mode 'xah-html-mode)
             (eq major-mode 'html-mode)
             (eq major-mode 'sgml-mode)
             (eq major-mode 'nxml-mode)
             (eq major-mode 'xml-mode)
             (eq major-mode 'mhtml-mode))
          (goto-char (point-min))
          (while
              (re-search-forward "\\(<title>[ \n]?\\|<h[1-6]>[ \n]?\\|<p>[ \n]?\\|<li>[ \n]?\\|<dd>[ \n]?\\|<td>[ \n]?\\|<br ?/?>[ \n]?\\|<figcaption>[ \n]?\\)\\([a-z]\\)" nil :move)
            (upcase-region (match-beginning 2) (match-end 2))
            (overlay-put (make-overlay (match-beginning 2) (match-end 2)) 'face 'highlight))))
      (goto-char (point-max)))
    (skip-chars-forward " \n\t")))

(defun xah-title-case-region-or-line (&optional Begin End)
  "Title case text between nearest brackets, or current line or selection.
Capitalize first letter of each word, except words like {to, of, the, a, in, or, and}. If a word already contains cap letters such as HTTP, URL, they are left as is.

When called in a elisp program, Begin End are region boundaries.

URL `http://xahlee.info/emacs/emacs/elisp_title_case_text.html'
Version: 2017-01-11 2021-03-30 2021-09-19"
  (interactive)
  (let* ((xskipChars "^\"<>(){}[]“”‘’‹›«»「」『』【】〖〗《》〈〉〔〕")
         (xp0 (point))
         (xp1 (if Begin
                  Begin
                (if (region-active-p)
                    (region-beginning)
                  (progn
                    (skip-chars-backward xskipChars (line-beginning-position)) (point)))))
         (xp2 (if End
                  End
                (if (region-active-p)
                    (region-end)
                  (progn (goto-char xp0)
                         (skip-chars-forward xskipChars (line-end-position)) (point)))))
         (xstrPairs [
                     [" A " " a "]
                     [" An " " an "]
                     [" And " " and "]
                     [" At " " at "]
                     [" As " " as "]
                     [" By " " by "]
                     [" Be " " be "]
                     [" Into " " into "]
                     [" In " " in "]
                     [" Is " " is "]
                     [" It " " it "]
                     [" For " " for "]
                     [" Of " " of "]
                     [" Or " " or "]
                     [" On " " on "]
                     [" Via " " via "]
                     [" The " " the "]
                     [" That " " that "]
                     [" To " " to "]
                     [" Vs " " vs "]
                     [" With " " with "]
                     [" From " " from "]
                     ["'S " "'s "]
                     ["'T " "'t "]
                     ]))
    (save-excursion
      (save-restriction
        (narrow-to-region xp1 xp2)
        (upcase-initials-region (point-min) (point-max))
        (let ((case-fold-search nil))
          (mapc
           (lambda (xx)
             (goto-char (point-min))
             (while
                 (search-forward (aref xx 0) nil t)
               (replace-match (aref xx 1) t t)))
           xstrPairs))))))

(defun xah-add-space-after-comma ()
  "Add a space after comma of current block or selection.
and highlight changes made.
Version: 2022-01-20 2024-03-19"
  (interactive)
  (let (xp1 xp2)
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (goto-char (point-min))
      (while
          (re-search-forward ",\\b" nil t)
        (replace-match ", ")
        (overlay-put
         (make-overlay
          (match-beginning 0)
          (match-end 0)) 'face 'highlight)))))

(defun xah-toggle-read-novel-mode ()
  "Setup current frame to be suitable for reading long novel/article text.
• Set frame width to 70
• Line wrap at word boundaries.
• Line spacing is increased.
• Proportional width font is used.
Call again to toggle back.

URL `http://xahlee.info/emacs/emacs/emacs_novel_reading_mode.html'
Version: 2019-01-30 2021-01-16"
  (interactive)
  (if (eq (frame-parameter (selected-frame) 'width) 70)
      (progn
        (set-frame-parameter (selected-frame) 'width 106)
        (variable-pitch-mode 0)
        (setq line-spacing nil)
        (setq word-wrap nil))
    (progn
      (set-frame-parameter (selected-frame) 'width 70)
      (variable-pitch-mode 1)
      (setq line-spacing 0.5)
      (setq word-wrap t)))
  (redraw-frame (selected-frame)))

(defun xah-fill-or-unfill ()
  "Reformat current block or selection to short/long line.
First call will break into multiple short lines. Repeated call toggles between short and long lines.
This commands calls `fill-region' to do its work. Set `fill-column' for short line length.

URL `http://xahlee.info/emacs/emacs/modernization_fill-paragraph.html'
Version: 2020-11-22 2021-08-13 2024-03-19"
  (interactive)
  ;; This command symbol has a property “'longline-p”, the possible values are t and nil. This property is used to easily determine whether to compact or uncompact, when this command is called again
  (let ( (xisLongline (if (eq last-command this-command) (get this-command 'longline-p) t))
         (deactivate-mark nil)
         xp1 xp2 )
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (if xisLongline
        (fill-region xp1 xp2)
      (let ((fill-column 99999 ))
        (fill-region xp1 xp2)))
    (put this-command 'longline-p (not xisLongline))))

(defun xah-unfill-paragraph ()
  "Replace newline chars in current paragraph by single spaces.
This command does the inverse of `fill-paragraph'.

URL `http://xahlee.info/emacs/emacs/emacs_unfill-paragraph.html'
Version: 2010-05-12 2022-05-20"
  (interactive)
  (let ((fill-column 90002000))
    (fill-paragraph)))

(defun xah-unfill-region (Begin End)
  "Replace newline chars in region by single spaces.
This command does the inverse of `fill-region'.

URL `http://xahlee.info/emacs/emacs/emacs_unfill-paragraph.html'
Version: 2010-05-12 2022-05-20"
  (interactive "r")
  (let ((fill-column 90002000))
    (fill-region Begin End)))

(defun xah-change-newline-chars-to-one (Begin End)
  "Replace newline char sequence by just one.

URL `http://xahlee.info/emacs/emacs/emacs_reformat_lines.html'
Version: 2021-07-06"
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region Begin End)
      (goto-char (point-min))
      (while (re-search-forward "\n\n+" nil :move) (replace-match "\n")))))

(defun xah-reformat-whitespaces-to-one-space (Begin End)
  "Replace whitespaces by one space.

URL `http://xahlee.info/emacs/emacs/emacs_reformat_lines.html'
Version: 2017-01-11 2022-01-08"
  (interactive "r")
  (save-restriction
      (narrow-to-region Begin End)
      (goto-char (point-min))
      (while (search-forward "\n" nil :move) (replace-match " "))
      (goto-char (point-min))
      (while (search-forward "\t" nil :move) (replace-match " "))
      (goto-char (point-min))
      (while (re-search-forward " +" nil :move) (replace-match " "))
      (goto-char (point-max))))

(defun xah-reformat-to-multi-lines ( &optional Begin End MinLength)
  "Replace spaces by a newline at ~70 chars, on current block or selection.
If `universal-argument' is called first, ask user for max width.

URL `http://xahlee.info/emacs/emacs/emacs_reformat_lines.html'
Version: 2018-12-16 2021-08-12 2024-03-19"
  (interactive)
  (let ( xp1 xp2 xminlen )
    (setq xminlen (if MinLength MinLength (if current-prefix-arg (prefix-numeric-value current-prefix-arg) fill-column)))
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (seq-setq (xp1 xp2) (xah-get-pos-of-block-or)))
    (save-excursion
      (save-restriction
        (narrow-to-region xp1 xp2)
        (goto-char (point-min))
        (while (re-search-forward " +" nil :move)
          (when (> (- (point) (line-beginning-position)) xminlen)
            (replace-match "\n" )))))))

(defun xah-reformat-lines (&optional Width)
  "Reformat current block or selection into short lines or 1 long line.
When called for the first time, change to one line. Second call change it to multi-lines. Repeated call toggles.
If `universal-argument' is called first, ask user to type max length of line. By default, it is 66.

Note: this command is different from emacs `fill-region' or `fill-paragraph'.
This command never adds or delete non-whitespace chars. It only exchange whitespace sequence.

URL `http://xahlee.info/emacs/emacs/emacs_reformat_lines.html'
Created 2016 or before.
Version: 2021-07-05 2022-12-24 2024-03-19"
  (interactive)
  ;; This symbol has a property 'is-long-p, the possible values are t and nil. This property is used to easily determine whether to compact or uncompact, when this command is called again
  (let (xisLong xwidth xp1 xp2)
    (setq xwidth (if Width Width (if current-prefix-arg (prefix-numeric-value current-prefix-arg) 66)))
    (setq xisLong (if (eq last-command this-command) (get this-command 'is-long-p) nil))
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (if current-prefix-arg
        (xah-reformat-to-multi-lines xp1 xp2 xwidth)
      (if xisLong
          (xah-reformat-to-multi-lines xp1 xp2 xwidth)
        (progn
          (xah-reformat-whitespaces-to-one-space xp1 xp2))))
    (put this-command 'is-long-p (not xisLong))))

(defun xah-reformat-to-sentence-lines ()
  "Reformat current block or selection into multiple lines by ending period.
Move cursor to the beginning of next text block.
After this command is called, press `xah-repeat-key' to repeat it.

URL `http://xahlee.info/emacs/emacs/elisp_reformat_to_sentence_lines.html'
Version: 2020-12-02 2023-11-09 2024-03-19"
  (interactive)
  (let (xp1 xp2)
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (goto-char (point-min)) (while (search-forward "。" nil t) (replace-match "。\n"))
      ;; (goto-char (point-min)) (while (search-forward " <a " nil t) (replace-match "\n<a "))
      ;; (goto-char (point-min)) (while (search-forward "</a> " nil t) (replace-match "</a>\n"))
      (goto-char (point-min))
      (while (re-search-forward "\\([A-Za-z0-9]+\\)[ \t]*\n[ \t]*\\([A-Za-z0-9]+\\)" nil t)
        (replace-match "\\1 \\2"))
      (goto-char (point-min))
      (while (re-search-forward "\\([,]\\)[ \t]*\n[ \t]*\\([A-Za-z0-9]+\\)" nil t)
        (replace-match "\\1 \\2"))
      (goto-char (point-min))
      (while (re-search-forward "  +" nil t) (replace-match " "))
      (goto-char (point-min))
      (while (re-search-forward "\\([.?!]\\) +\\([(0-9A-Za-z]+\\)" nil t) (replace-match "\\1\n\\2"))
      (goto-char (point-max))
      (while (eq (char-before) 32) (delete-char -1))))
  (re-search-forward "\n+" nil :move)
  (set-transient-map (let ((xkmap (make-sparse-keymap))) (define-key xkmap (or xah-repeat-key (kbd "DEL")) this-command) xkmap))
  (set-transient-map (let ((xkmap (make-sparse-keymap))) (define-key xkmap (kbd "DEL") this-command) xkmap)))

(defun xah-space-to-newline ()
  "Replace space sequence to a newline char in current block or selection.

URL `http://xahlee.info/emacs/emacs/emacs_space_to_newline.html'
Version: 2017-08-19 2021-11-28 2024-03-19"
  (interactive)
  (let (xp1 xp2)
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (goto-char (point-min))
      (while (re-search-forward " +" nil t)
        (replace-match "\n")))))

(defun xah-slash-to-backslash (&optional Begin End)
  "Replace slash by backslash on current line or region.
Version: 2021-07-14 2021-09-12"
  (interactive)
  (let (xp1 xp2)
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (setq xp1 (line-beginning-position) xp2 (line-end-position))))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (let ((case-fold-search nil))
        (goto-char (point-min))
        (while (search-forward "/" nil t)
          (replace-match "\\\\"))))))

(defun xah-backslash-to-slash (&optional Begin End)
  "Replace backslash by slash on current line or region.
Version: 2021-09-11"
  (interactive)
  (let (xp1 xp2)
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (setq xp1 (line-beginning-position) xp2 (line-end-position))))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (let ((case-fold-search nil))
        (goto-char (point-min))
        (while (search-forward "\\" nil t)
          (replace-match "/"))))))

(defun xah-double-backslash (&optional Begin End)
  "Replace backslash by two backslash on current line or region.
Version: 2021-11-09"
  (interactive)
  (let (xp1 xp2)
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (setq xp1 (line-beginning-position) xp2 (line-end-position))))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (let ((case-fold-search nil))
        (goto-char (point-min))
        (while (search-forward "\\" nil t)
          (replace-match "\\\\\\\\"))))))

(defun xah-double-backslash-to-single (&optional Begin End)
  "Replace double backslash by single backslash on current line or region.
Version: 2021-11-09"
  (interactive)
  (let (xp1 xp2)
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (setq xp1 (line-beginning-position) xp2 (line-end-position))))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (let ((case-fold-search nil))
        (goto-char (point-min))
        (while (search-forward "\\\\"  nil t)
          (replace-match "\\\\"))))))

(defun xah-slash-to-double-backslash (&optional Begin End)
  "Replace slash by double backslash on current line or region.
Version: 2021-07-14"
  (interactive)
  (let (xp1 xp2)
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (setq xp1 (line-beginning-position) xp2 (line-end-position))))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (let ((case-fold-search nil))
        (goto-char (point-min))
        (while (search-forward "/" nil t)
          (replace-match "\\\\\\\\"))))))

(defun xah-double-backslash-to-slash (&optional Begin End)
  "Replace double backslash by slash on current line or region.
Version: 2021-07-14"
  (interactive)
  (let (xp1 xp2)
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (setq xp1 (line-beginning-position) xp2 (line-end-position))))
    (save-restriction
      (narrow-to-region xp1 xp2)
      (let ((case-fold-search nil))
        (goto-char (point-min))
        (while (search-forward "\\\\" nil t)
          (replace-match "/"))))))

(defun xah-comment-dwim ()
  "Toggle comment in programing language code.

Like `comment-dwim', but toggle comment if cursor is not at end of line.
If cursor is at end of line, either add comment at the line end or move cursor to start of line end comment. call again to comment out whole line.

URL `http://xahlee.info/emacs/emacs/emacs_toggle_comment_by_line.html'
Version: 2016-10-25 2023-07-10"
  (interactive)
  (if (region-active-p)
      (comment-dwim nil)
    (let ((xbegin (line-beginning-position))
          (xend (line-end-position)))
      (if (eq xbegin xend)
          (progn
            (comment-dwim nil))
        (if (eq (point) xend)
            (progn
              (comment-dwim nil))
          (progn
            (comment-or-uncomment-region xbegin xend)
            (forward-line )))))))

(defun xah-quote-lines (QuoteL QuoteR Sep)
  "Add quotes/brackets and separator (comma) to lines.
Act on current block or selection.

For example,

 cat
 dog
 cow

becomes

 \"cat\",
 \"dog\",
 \"cow\",

or

 (cat)
 (dog)
 (cow)

In lisp code, QuoteL QuoteR Sep are strings.

URL `http://xahlee.info/emacs/emacs/emacs_quote_lines.html'
Version: 2020-06-26 2023-10-29 2024-03-19"
  (interactive
   (let ((xbrackets
          '(
            "\"double quote\""
            "'single quote'"
            "(paren)"
            "{brace}"
            "[square]"
            "<greater>"
            "`emacs'"
            "`markdown`"
            "~tilde~"
            "=equal="
            "“curly double”"
            "‘curly single’"
            "‹french angle›"
            "«french double angle»"
            "「corner」"
            "none"
            "other"
            ))
         (xcomma '("comma ," "semicolon ;" "none" "other"))
         xbktChoice xsep xsepChoice xquoteL xquoteR)
     (let ((completion-ignore-case t))
       (setq xbktChoice (completing-read "Quote to use:" xbrackets nil t nil nil (car xbrackets)))
       (setq xsepChoice (completing-read "line separator:" xcomma nil t nil nil (car xcomma))))
     (cond
      ((string-equal xbktChoice "none")
       (setq xquoteL "" xquoteR ""))
      ((string-equal xbktChoice "other")
       (let ((xx (read-string "Enter 2 chars, for begin/end quote:")))
         (setq xquoteL (substring xx 0 1)
               xquoteR (substring xx 1 2))))
      (t (setq xquoteL (substring xbktChoice 0 1)
               xquoteR (substring xbktChoice -1))))
     (setq xsep
           (cond
            ((string-equal xsepChoice "comma ,") ",")
            ((string-equal xsepChoice "semicolon ;") ";")
            ((string-equal xsepChoice "none") "")
            ((string-equal xsepChoice "other") (read-string "Enter separator:"))
            (t xsepChoice)))
     (list xquoteL xquoteR xsep)))
  (let (xp1 xp2 (xquoteL QuoteL) (xquoteR QuoteR) (xsep Sep))
    (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
    (save-excursion
      (save-restriction
        (narrow-to-region xp1 xp2)
        (goto-char (point-min))
        (catch 'EndReached
          (while t
            (skip-chars-forward "\t ")
            (insert xquoteL)
            (end-of-line)
            (insert xquoteR xsep)
            (if (eq (point) (point-max))
                (throw 'EndReached t)
              (forward-char))))))))

(defun xah-escape-quotes (Begin End)
  "Add slash before double quote in current line or selection.
Double quote is codepoint 34.
See also: `xah-unescape-quotes'
URL `http://xahlee.info/emacs/emacs/elisp_escape_quotes.html'
Version: 2017-01-11"
  (interactive
   (if (region-active-p)
       (list (region-beginning) (region-end))
     (list (line-beginning-position) (line-end-position))))
  (save-excursion
      (save-restriction
        (narrow-to-region Begin End)
        (goto-char (point-min))
        (while (search-forward "\"" nil t)
          (replace-match "\\\"" t t)))))

(defun xah-unescape-quotes (&optional Begin End)
  "Replace  「\\\"」 by 「\"」 in current line or selection.
See also: `xah-escape-quotes'

URL `http://xahlee.info/emacs/emacs/elisp_escape_quotes.html'
Version: 2017-01-11 2023-11-02"
  (interactive)
  (let (xp1 xp2)
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (setq xp1 (line-beginning-position) xp2 (line-end-position))))
    (save-excursion
      (save-restriction
        (narrow-to-region xp1 xp2)
        (goto-char (point-min))
        (while (search-forward "\\\"" nil t)
          (replace-match "\"" t t))))))

(defun xah-cycle-hyphen-lowline-space (&optional Begin End)
  "Cycle {hyphen lowline space} chars.

The region to work on is by this order:
 1. if there is a selection, use that.
 2. If cursor is in a string quote or any type of bracket, and is within current line, work on that region.
 3. else, work on current line.

After this command is called, press `xah-repeat-key' to repeat it.

URL `http://xahlee.info/emacs/emacs/elisp_change_space-hyphen_underscore.html'
Version: 2019-02-12 2023-07-16 2024-01-04"
  (interactive)
  ;; this function sets a property 'state. Possible values are 0 to length of xcharArray.
  (let (xp1 xp2 xlen
            (xcharArray ["-" "_" " "])
            (xregionWasActive-p (region-active-p))
            (xnowState (if (eq last-command this-command) (get 'xah-cycle-hyphen-lowline-space 'state) 0))
            xchangeTo)
    (setq
     xlen (length xcharArray)
     xchangeTo (elt xcharArray xnowState))
    (if (and Begin End)
        (setq xp1 Begin xp2 End)
      (if (region-active-p)
          (setq xp1 (region-beginning) xp2 (region-end))
        (let ((xskipChars "^\"<>(){}[]“”‘’‹›«»「」『』【】〖〗《》〈〉〔〕（）"))
          (skip-chars-backward xskipChars (line-beginning-position))
          (setq xp1 (point))
          (skip-chars-forward xskipChars (line-end-position))
          (setq xp2 (point))
          (push-mark xp1))))
    (save-excursion
      (save-restriction
        (narrow-to-region xp1 xp2)
        (goto-char (point-min))
        (while (re-search-forward (elt xcharArray (% (+ xnowState 2) xlen)) (point-max) 1)
          (replace-match xchangeTo t t))))
    (when (or (string-equal xchangeTo " ") xregionWasActive-p)
      (goto-char xp2)
      (push-mark xp1)
      (setq deactivate-mark nil))
    (put 'xah-cycle-hyphen-lowline-space 'state (% (+ xnowState 1) xlen)))
  (set-transient-map (let ((xkmap (make-sparse-keymap))) (define-key xkmap (or xah-repeat-key (kbd "DEL")) this-command) xkmap)))

(defun xah-copy-file-path (&optional DirPathOnlyQ)
  "Copy current buffer file path or dired path.
Result is full path.
If `universal-argument' is called first, copy only the dir path.

If in dired, copy the current or marked files.

If a buffer is not file and not dired, copy value of `default-directory'.

URL `http://xahlee.info/emacs/emacs/emacs_copy_file_path.html'
Version: 2018-06-18 2021-09-30"
  (interactive "P")
  (let ((xfpath
         (if (eq major-mode 'dired-mode)
             (progn
               (let ((xresult (mapconcat #'identity
                                         (dired-get-marked-files) "\n")))
                 (if (equal (length xresult) 0)
                     (progn default-directory )
                   (progn xresult))))
           (if buffer-file-name
               buffer-file-name
             (expand-file-name default-directory)))))
    (kill-new
     (if DirPathOnlyQ
         (progn
           (message "Directory copied: %s" (file-name-directory xfpath))
           (file-name-directory xfpath))
       (progn
         (message "File path copied: %s" xfpath)
         xfpath )))))

(defun xah-delete-current-text-block ()
  "Delete the current text block plus blank lines, or selection, and copy to `kill-ring'.

If cursor is between blank lines, delete following blank lines.

URL `http://xahlee.info/emacs/emacs/emacs_delete_block.html'
Version: 2017-07-09 2023-06-07 2023-10-09"
  (interactive)
  (let (xp1 xp2)
    (if (region-active-p)
        (setq xp1 (region-beginning) xp2 (region-end))
      (progn
        (if (re-search-backward "\n[ \t]*\n+" nil :move)
            (setq xp1 (goto-char (match-end 0)))
          (setq xp1 (point)))
        (if (re-search-forward "\n[ \t]*\n+" nil :move)
            (setq xp2 (match-end 0))
          (setq xp2 (point-max)))))
    (kill-region xp1 xp2)))

(defun xah-copy-to-register-1 ()
  "Copy current line or selection to register 1.

See also:
`xah-copy-to-register-1'
`xah-append-to-register-1'
`xah-paste-from-register-1'
`xah-clear-register-1'

URL `http://xahlee.info/emacs/emacs/elisp_copy-paste_register_1.html'
Version: 2012-07-17 2023-04-07 2023-08-05"
  (interactive)
  (let (xp1 xp2)
    (if (region-active-p)
         (setq xp1 (region-beginning) xp2 (region-end))
      (setq xp1 (line-beginning-position) xp2 (line-end-position)))
    (copy-to-register ?1 xp1 xp2)
    (message "Copied to register 1: [%s]." (buffer-substring xp1 xp2))))

(defun xah-append-to-register-1 ()
  "Append current line or selection to register 1.
When no selection, append current line, with newline char.

See also:
`xah-copy-to-register-1'
`xah-append-to-register-1'
`xah-paste-from-register-1'
`xah-clear-register-1'

URL `http://xahlee.info/emacs/emacs/emacs_copy_append.html'
Version: 2015-12-08 2023-04-07 2023-08-05"
  (interactive)
  (let (xp1 xp2)
    (if (region-active-p)
         (setq xp1 (region-beginning) xp2 (region-end))
      (setq xp1 (line-beginning-position) xp2 (line-end-position)))
    (append-to-register ?1 xp1 xp2)
    (with-temp-buffer (insert "\n")
                      (append-to-register ?1 (point-min) (point-max)))
    (message "Appended to register 1: [%s]." (buffer-substring xp1 xp2))))

(defun xah-paste-from-register-1 ()
  "Paste text from register 1.

See also:
`xah-copy-to-register-1'
`xah-append-to-register-1'
`xah-paste-from-register-1'
`xah-clear-register-1'

URL `http://xahlee.info/emacs/emacs/elisp_copy-paste_register_1.html'
Version: 2015-12-08 2023-04-07"
  (interactive)
  (when (region-active-p)
    (delete-region (region-beginning) (region-end)))
  (insert-register ?1 t))

(defun xah-clear-register-1 ()
  "Clear register 1.

See also:
`xah-copy-to-register-1'
`xah-append-to-register-1'
`xah-paste-from-register-1'
`xah-clear-register-1'

URL `http://xahlee.info/emacs/emacs/elisp_copy-paste_register_1.html'
Version: 2015-12-08 2023-04-07"
  (interactive)
  (progn
    (copy-to-register ?1 (point-min) (point-min))
    (message "Cleared register 1.")))


;; insertion commands

(defun xah-insert-date ()
  "Insert current date time.
Insert date in this format: yyyy-mm-dd.
If `universal-argument' is called first, prompt for a format to use.
If there is selection, delete it first.

URL `http://xahlee.info/emacs/emacs/elisp_insert-date-time.html'
Version: 2013-05-10 2023-09-30 2023-10-01"
  (interactive)
  (let (xmenu xstyle)
    (setq
     xmenu
     '(("ISO date • 2018-04-12" . (format-time-string "%Y-%m-%d"))
       ("all digits datetime • 20180412224611" . (format-time-string "%Y%m%d%H%M%S"))
       ("date _ time digits • 2018-04-12_224611" . (format-time-string "%Y-%m-%d_%H%M%S"))
       ("ISO datetime full • 2018-04-12T22:46:11-07:00" .
        (concat
         (format-time-string "%Y-%m-%dT%T")
         ((lambda (xx) (format "%s:%s" (substring xx 0 3) (substring xx 3 5)))
          (format-time-string "%z"))))
       ("ISO datetime w space • 2018-04-12 22:46:11-07:00" .
        (concat
         (format-time-string "%Y-%m-%d %T")
         ((lambda (xx) (format "%s:%s" (substring xx 0 3) (substring xx 3 5)))
          (format-time-string "%z"))))
       ("ISO date + weekday • 2018-04-12 Thursday" . (format-time-string "%Y-%m-%d %A"))
       ("USA date + weekday • Thursday, April 12, 2018" . (format-time-string "%A, %B %d, %Y"))
       ("USA date + weekday abbrev • Thu, Apr 12, 2018" . (format-time-string "%a, %b %d, %Y"))
       ("USA date • April 12, 2018" . (format-time-string "%B %d, %Y"))
       ("USA date abbrev • Apr 12, 2018" . (format-time-string "%b %d, %Y")))

     xstyle
     (if current-prefix-arg
         (let ((completion-ignore-case t))
           (completing-read "Style:" xmenu nil t nil nil (caar xmenu)))
       (caar xmenu)))
    (when (region-active-p) (delete-region (region-beginning) (region-end)))
    (insert (eval (cdr (assoc xstyle xmenu))))))

(defun xah-insert-bracket-pair (LBracket RBracket &optional WrapMethod)
  "Insert brackets around selection, word, at point, and maybe move cursor in between.

 LBracket and RBracket are strings. WrapMethod must be either `line' or `block'. `block' means between empty lines.

• If there is a active region, wrap around region.
Else
• If WrapMethod is `line', wrap around line.
• If WrapMethod is `block', wrap around block.
Else
• If cursor is at beginning of line and its not empty line and contain at least 1 space, wrap around the line.
• If cursor is at end of a word or buffer, one of the following will happen:
 xyz▮ → xyz(▮)
 xyz▮ → (xyz▮)       if in one of the lisp modes.
• wrap brackets around word if any. e.g. xy▮z → (xyz▮). Or just (▮)

URL `http://xahlee.info/emacs/emacs/elisp_insert_brackets_by_pair.html'
Version: 2017-01-17 2021-08-12 2024-03-19"
  (if (region-active-p)
      (progn
        (let ((xp1 (region-beginning)) (xp2 (region-end)))
          (goto-char xp2) (insert RBracket)
          (goto-char xp1) (insert LBracket)
          (goto-char (+ xp2 2))))
    (let (xp1 xp2)
      (cond
       ((eq WrapMethod 'line)
        (setq xp1 (line-beginning-position) xp2 (line-end-position))
        (goto-char xp2)
        (insert RBracket)
        (goto-char xp1)
        (insert LBracket)
        (goto-char (+ xp2 (length LBracket))))
       ((eq WrapMethod 'block)
        (save-excursion
          (seq-setq (xp1 xp2) (xah-get-pos-of-block-or))
          (goto-char xp2)
          (insert RBracket)
          (goto-char xp1)
          (insert LBracket)
          (goto-char (+ xp2 (length LBracket)))))
       ( ; do line. line must contain space
        (and
         (eq (point) (line-beginning-position))
         (not (eq (line-beginning-position) (line-end-position))))
        (insert LBracket)
        (end-of-line)
        (insert  RBracket))
       ((and
         (or ; cursor is at end of word or buffer. i.e. xyz▮
          (looking-at "[^-_[:alnum:]]")
          (eq (point) (point-max)))
         (not (or
               (eq major-mode 'xah-elisp-mode)
               (eq major-mode 'emacs-lisp-mode)
               (eq major-mode 'lisp-mode)
               (eq major-mode 'lisp-interaction-mode)
               (eq major-mode 'common-lisp-mode)
               (eq major-mode 'clojure-mode)
               (eq major-mode 'xah-clojure-mode)
               (eq major-mode 'scheme-mode))))
        (progn
          (setq xp1 (point) xp2 (point))
          (insert LBracket RBracket)
          (search-backward RBracket)))
       (t (progn
            ;; wrap around “word”. basically, want all alphanumeric, plus hyphen and underscore, but don't want space or punctuations. Also want chinese chars
            ;; 我有一帘幽梦，不知与谁能共。多少秘密在其中，欲诉无人能懂。
            (skip-chars-backward "-_[:alnum:]")
            (setq xp1 (point))
            (skip-chars-forward "-_[:alnum:]")
            (setq xp2 (point))
            (goto-char xp2)
            (insert RBracket)
            (goto-char xp1)
            (insert LBracket)
            (goto-char (+ xp2 (length LBracket)))))))))

(defun xah-insert-paren () (interactive) (xah-insert-bracket-pair "(" ")") )
(defun xah-insert-square-bracket () (interactive) (xah-insert-bracket-pair "[" "]") )
(defun xah-insert-brace () (interactive) (xah-insert-bracket-pair "{" "}") )

(defun xah-insert-markdown-quote () (interactive) (xah-insert-bracket-pair "`" "`") )
(defun xah-insert-markdown-triple-quote () (interactive) (xah-insert-bracket-pair "```\n" "\n```"))

(defun xah-insert-ascii-angle-bracket () (interactive) (xah-insert-bracket-pair "<" ">"))

(defun xah-insert-double-curly-quote () (interactive) (xah-insert-bracket-pair "“" "”") )
(defun xah-insert-curly-single-quote () (interactive) (xah-insert-bracket-pair "‘" "’") )
(defun xah-insert-single-angle-quote () (interactive) (xah-insert-bracket-pair "‹" "›") )
(defun xah-insert-double-angle-quote () (interactive) (xah-insert-bracket-pair "«" "»") )
(defun xah-insert-ascii-double-quote () (interactive) (xah-insert-bracket-pair "\"" "\"") )
(defun xah-insert-ascii-single-quote () (interactive) (xah-insert-bracket-pair "'" "'") )
(defun xah-insert-emacs-quote () (interactive) (xah-insert-bracket-pair "`" "'") )
(defun xah-insert-corner-bracket () (interactive) (xah-insert-bracket-pair "「" "」" ) )
(defun xah-insert-white-corner-bracket () (interactive) (xah-insert-bracket-pair "『" "』") )
(defun xah-insert-angle-bracket () (interactive) (xah-insert-bracket-pair "〈" "〉") )
(defun xah-insert-double-angle-bracket () (interactive) (xah-insert-bracket-pair "《" "》") )
(defun xah-insert-white-lenticular-bracket () (interactive) (xah-insert-bracket-pair "〖" "〗") )
(defun xah-insert-black-lenticular-bracket () (interactive) (xah-insert-bracket-pair "【" "】") )
(defun xah-insert-tortoise-shell-bracket () (interactive) (xah-insert-bracket-pair "〔" "〕" ) )

(defun xah-insert-hyphen ()
  "Insert a HYPHEN-MINUS character."
  (interactive)
  (insert "-"))

(defun xah-insert-low-line ()
  "Insert a LOW LINE character."
  (interactive)
  (insert "_"))

(defun xah-insert-string-assignment ()
  "Insert =\"\""
  (interactive)
  (progn (insert "=\"\"")
         (left-char)))

(defun xah-insert-space-before ()
  "Insert space before cursor."
  (interactive)
  (insert " "))

(defun xah-insert-space-after ()
  "Insert space after cursor"
  (interactive)
  (insert " ")
  (left-char))

(defun xah-insert-formfeed ()
  "Insert a form feed char (codepoint 12)"
  (interactive)
  (insert "\n\u000c\n"))

(defun xah-show-formfeed-as-line ()
  "Display the formfeed ^L char as line.
URL `http://xahlee.info/emacs/emacs/emacs_show_form_feed_as_line.html'
Version: 2018-08-30 2023-07-29"
  (interactive)
  ;; 2016-10-11 thanks to Steve Purcell's page-break-lines.el
  (progn
    (when (not buffer-display-table)
      (setq buffer-display-table (make-display-table)))
    (aset buffer-display-table ?\^L
          (vconcat (make-list 70 (make-glyph-code ?─ 'font-lock-comment-face))))
    (redraw-frame)))

(defun xah-insert-column-az ()
  "Insert letters A to Z vertically, similar to `rectangle-number-lines'.
The commpand will prompt for a start char, and number of chars to insert.
The start char can be any char in Unicode.

URL `http://xahlee.info/emacs/emacs/emacs_insert-alphabets.html'
Version: 2013-06-12 2019-03-07"
  (interactive)
  (let (
        (xstartChar (string-to-char (read-string "Start char: " "a")))
        (xhowmany (string-to-number (read-string "How many: " "26")))
        (xcolpos (- (point) (line-beginning-position))))
    (dotimes (xi xhowmany )
      (progn
        (insert-char (+ xi xstartChar))
        (forward-line)
        (beginning-of-line)
        (forward-char xcolpos)))))

(defvar xah-unicode-list nil
 "A alist.
Each item is (prompStr . xString). Used by `xah-insert-unicode'.
prompStr is used for prompt.
xString is used for insert a unicode.
xString can be any string, needs not be a char or emoji.
")

(setq
 xah-unicode-list
 '(
   ;;
   ("smile beaming 😊" . "😊")
   ("tears of joy" . "😂")
   ("hug 🤗" . "🤗")
   ("heart eyes 😍" . "😍")
   ("heart face 🥰" . "🥰")
   ("angry 😠" . "😠")
   ("vomit 🤮" . "🤮")
   ("thumb up 👍" . "👍")
   ("thumb down 👎" . "👎")
   ("checkmark ✅" . "✅")
   ("new 🆕" . "🆕")
   ("glowing star 🌟" . "🌟")
   ("star ⭐" . "⭐")
   ("sparkles ✨" . "✨")
   ("rocket 🚀" . "🚀")
   ("sun 🌞" . "🌞")
   ("heart 🧡" . "🧡")
   ("clown 🤡" . "🤡")
   ("large circle" . "⭕")
   ("cross ❌" . "❌")
   ("red triangle 🔺" . "🔺")
   ("diamond 💠" . "💠")
   ("square" . "⬛")
   ("cursor ▮" . "▮")
   ("dagger †" . "†")
   ("double dagger ‡" . "‡")

   ("double angle bracket" . "《》")
   ("black lenticular bracket" . "【】")
   ("corner-bracket" . "「」")
   ("tortoise shell bracket" . "〔〕")
   ("angle bracket" . "〈〉")
   ("double angle quote" . "«»")

   ("bullet •" . "•")
   ("diamond ◆" . "◆")
   ("...ellipsis …" . "…")
   ("nbsp non breaking space" . " ")
   ("chinese comma 、" . "、")
   ("emdash —" . "—")
   ("fullwidth ampersand ＆" . "＆")
   ("left arrow ←" . "←")
   ("right arrow →" . "→")
   ("up arrow ↑" . "↑")
   ("down arrow ↓" . "↓")
   ("f hook ƒ" . "ƒ")
   ;;
   ))

(defun xah-insert-unicode ()
  "Insert a unicode from a custom list `xah-unicode-list'.
URL `http://xahlee.info/emacs/emacs/emacs_insert_unicode.html'
Version: 2021-01-05 2023-08-25 2023-08-31 2023-09-19"
  (interactive)
  (let ((xkey
         (let ((completion-ignore-case t))
           (completing-read "Insert:" xah-unicode-list nil t))))
    (insert (cdr (assoc xkey xah-unicode-list)))))


;; text selection

(defun xah-select-block ()
  "Select the current/next block plus 1 blankline.
If region is active, extend selection downward by block.

URL `http://xahlee.info/emacs/emacs/emacs_select_text_block.html'
Version: 2019-12-26 2021-08-13 2023-11-14"
  (interactive)
  (if (region-active-p)
      (re-search-forward "\n[ \t]*\n[ \t]*\n*" nil :move)
    (progn
      (skip-chars-forward " \n\t")
      (when (re-search-backward "\n[ \t]*\n" nil :move)
        (goto-char (match-end 0)))
      (push-mark (point) t t)
      (re-search-forward "\n[ \t]*\n" nil :move))))

(defun xah-select-line ()
  "Select current line. If region is active, extend selection downward by line.
If `visual-line-mode' is on, consider line as visual line.

URL `http://xahlee.info/emacs/emacs/emacs_select_line.html'
Version: 2017-11-01 2023-07-16 2023-11-14"
  (interactive)
  (if (region-active-p)
      (if visual-line-mode
          (let ((xp1 (point)))
            (end-of-visual-line 1)
            (when (eq xp1 (point))
              (end-of-visual-line 2)))
        (progn
          (forward-line 1)
          (end-of-line)))
    (if visual-line-mode
        (progn (beginning-of-visual-line)
               (push-mark (point) t t)
               (end-of-visual-line))
      (progn
        (push-mark (line-beginning-position) t t)
        (end-of-line)))))

(defun xah-extend-selection ()
  "Select the current word, bracket/quote expression, or expand selection.
Subsequent calls expands the selection.

when there is no selection,
• If cursor is on any type of bracket (including parenthesis, quotation mark), select whole bracketed thing including bracket
• else, select current word.

when there is a selection, the selection extension behavior is still experimental. But when cursor is on a any type of bracket (parenthesis, quote), it extends selection to outer bracket.

URL `http://xahlee.info/emacs/emacs/emacs_extend_selection.html'
Version: 2020-02-04 2023-08-24 2023-11-14"
  (interactive)

  (cond
   ((region-active-p)
    (let ((xp1 (region-beginning)) (xp2 (region-end)))
      (goto-char xp1)
      (cond
       ((looking-at "\\s(")
        (if (eq (nth 0 (syntax-ppss)) 0)
            (progn
              ;; (message "debug: left bracket, depth 0.")
              (end-of-line) ; select current line
              (push-mark (line-beginning-position) t t))
          (progn
            ;; (message "debug: left bracket, depth not 0")
            (up-list -1 t t)
            (mark-sexp))))
       ((eq xp1 (line-beginning-position))
        (progn
          (goto-char xp1)
          (let ((xfirstLineEndPos (line-end-position)))
            (cond
             ((eq xp2 xfirstLineEndPos)
              (progn
                ;; (message "debug: exactly 1 line. extend to next whole line." )
                (forward-line 1)
                (end-of-line)))
             ((< xp2 xfirstLineEndPos)
              (progn
                ;; (message "debug: less than 1 line. complete the line." )
                (end-of-line)))
             ((> xp2 xfirstLineEndPos)
              (progn
                ;; (message "debug: beginning of line, but end is greater than 1st end of line" )
                (goto-char xp2)
                (if (eq (point) (line-end-position))
                    (progn
                      ;; (message "debug: exactly multiple lines" )
                      (forward-line 1)
                      (end-of-line))
                  (progn
                    ;; (message "debug: multiple lines but end is not eol. make it so" )
                    (goto-char xp2)
                    (end-of-line)))))
             (t (error "%s: logic error 42946" real-this-command))))))
       ((and (> (point) (line-beginning-position)) (<= (point) (line-end-position)))
        (progn
          ;; (message "debug: less than 1 line" )
          (end-of-line) ; select current line
          (push-mark (line-beginning-position) t t)))
       (t
        ;; (message "debug: last resort" )
        nil))))

   ((looking-at "\\s(")
    ;; (message "debug: left bracket")
    (mark-sexp))

   ((looking-at "\\s)")
    ;; (message "debug: right bracket")
    (backward-up-list) (mark-sexp))

   ((looking-at "\\s\"")
    ;; (message "debug: string quote")
    (mark-sexp))

   ((looking-at "[ \t\n]")
    ;; (message "debug: is white space")
    (skip-chars-backward " \t\n")
    (push-mark)
    (skip-chars-forward " \t\n")
    (setq mark-active t))

   ((looking-at "[-_a-zA-Z0-9]")
    ;; (message "debug: left is word or symbol")
    (skip-chars-backward "-_a-zA-Z0-9")
    (push-mark)
    (skip-chars-forward "-_a-zA-Z0-9")
    (setq mark-active t))

   ((and (looking-at "[:blank:]")
         (prog2 (backward-char) (looking-at "[:blank:]") (forward-char)))
    ;; (message "debug: left and right both space" )
    (skip-chars-backward "[:blank:]") (push-mark (point) t t)
    (skip-chars-forward "[:blank:]"))

   ((and (looking-at "\n")
         (eq (char-before) 10))
    ;; (message "debug: left and right both newline")
    (skip-chars-forward "\n")
    (push-mark (point)  t t)
    (re-search-forward "\n[ \t]*\n"))

   (t
    ;; (message "debug: just mark sexp" )
    (mark-sexp)
    (exchange-point-and-mark))))

(defun xah-select-text-in-quote ()
  "Select text between the nearest left and right delimiters.
Delimiters here includes QUOTATION MARK, GRAVE ACCENT, and anything in `xah-brackets'.
This command ignores nesting. For example, if text is
「(a(b)c▮)」
the selected char is 「c」, not 「a(b)c」.

URL `http://xahlee.info/emacs/emacs/emacs_select_quote_text.html'
Version: 2020-11-24 2023-07-23 2023-11-14"
  (interactive)
  (let ((xskipChars (concat "^\"`" (mapconcat #'identity xah-brackets ""))))
    (skip-chars-backward xskipChars)
    (push-mark (point) t t)
    (skip-chars-forward xskipChars)))

(defun xah-cut-text-in-quote ()
  "Cut text between the nearest left and right delimiters.
See `xah-select-text-in-quote'

Version: 2023-07-23 2023-11-14"
  (interactive)
  (let ((xskipChars (concat "^\"`" (mapconcat #'identity xah-brackets ""))))
    (skip-chars-backward xskipChars)
    (push-mark (point) t t)
    (skip-chars-forward xskipChars)
    (kill-region nil nil t)))


;; misc

(defun xah-user-buffer-p ()
  "Return t if current buffer is a user buffer, else nil.
A user buffer has buffer name NOT starts with * or space.
This function is used by buffer switching command and close buffer command, so that next buffer shown is a user buffer.
You can override this function to get your idea of “user buffer”.
Version: 2016-06-18 2022-05-19 2023-10-18"
  (interactive)
  (cond
   ((string-match "^\*" (buffer-name)) nil)
   ((eq major-mode 'dired-mode) nil)
   ((eq major-mode 'eww-mode) nil)
   ((eq major-mode 'help-mode) nil)
   (t t)))

(defun xah-next-user-buffer ()
  "Switch to the next user buffer.
“user buffer” is determined by `xah-user-buffer-p'.

URL `http://xahlee.info/emacs/emacs/elisp_next_prev_user_buffer.html'
Version: 2016-06-19"
  (interactive)
  (next-buffer)
  (let ((i 0))
    (while (< i 30)
      (if (not (xah-user-buffer-p))
          (progn (next-buffer)
                 (setq i (1+ i)))
        (progn (setq i 100))))))

(defun xah-previous-user-buffer ()
  "Switch to the previous user buffer.
“user buffer” is determined by `xah-user-buffer-p'.

URL `http://xahlee.info/emacs/emacs/elisp_next_prev_user_buffer.html'
Version: 2016-06-19"
  (interactive)
  (previous-buffer)
  (let ((i 0))
    (while (< i 20)
      (if (not (xah-user-buffer-p))
          (progn (previous-buffer)
                 (setq i (1+ i)))
        (progn (setq i 100))))))

(defun xah-next-emacs-buffer ()
  "Switch to the next emacs buffer.
“emacs buffer” here is buffer whose name starts with *.

URL `http://xahlee.info/emacs/emacs/elisp_next_prev_user_buffer.html'
Version: 2016-06-19"
  (interactive)
  (next-buffer)
  (let ((i 0))
    (while (and (not (string-equal "*" (substring (buffer-name) 0 1))) (< i 20))
      (setq i (1+ i)) (next-buffer))))

(defun xah-previous-emacs-buffer ()
  "Switch to the previous emacs buffer.
“emacs buffer” here is buffer whose name starts with *.

URL `http://xahlee.info/emacs/emacs/elisp_next_prev_user_buffer.html'
Version: 2016-06-19"
  (interactive)
  (previous-buffer)
  (let ((i 0))
    (while (and (not (string-equal "*" (substring (buffer-name) 0 1))) (< i 20))
      (setq i (1+ i)) (previous-buffer))))

(defun xah-new-empty-buffer ()
  "Create a new empty buffer.
Returns the buffer object.
New buffer is named untitled, untitled<2>, etc.

Warning: new buffer is not prompted for save when killed, see `kill-buffer'.
Or manually `save-buffer'

URL `http://xahlee.info/emacs/emacs/emacs_new_empty_buffer.html'
Version: 2017-11-01 2022-04-05"
  (interactive)
  (let ((xbuf (generate-new-buffer "untitled")))
    (switch-to-buffer xbuf)
    (funcall initial-major-mode)
    xbuf
    ))

(declare-function minibuffer-keyboard-quit "delsel" ())
(declare-function org-edit-src-save "org-src" ())

(defcustom xah-recently-closed-buffers-max 40 "The maximum length for `xah-recently-closed-buffers'."
  :type 'integer)

(defvar xah-recently-closed-buffers nil "A Alist of recently closed buffers.
Each element is (bufferName . filePath).
The max number to track is controlled by the variable `xah-recently-closed-buffers-max'.")

(defun xah-add-to-recently-closed (&optional BufferName BufferFileName)
  "Add to `xah-recently-closed-buffers'.
Version: 2023-03-02"
  (let ((xbn (if BufferName BufferName (buffer-name)))
        (xbfn (if BufferFileName BufferFileName buffer-file-name)))
    (setq xah-recently-closed-buffers (cons (cons xbn xbfn) xah-recently-closed-buffers)))
  (when (> (length xah-recently-closed-buffers) xah-recently-closed-buffers-max)
    (setq xah-recently-closed-buffers (butlast xah-recently-closed-buffers 1))))

(defvar xah-temp-dir-path nil "Path to temp dir used by xah commands.
by default, the value is dir named temp at `user-emacs-directory'.
Version: 2023-03-21")

(setq xah-temp-dir-path (expand-file-name (concat user-emacs-directory "temp/")))

(defun xah-close-current-buffer ()
  "Close the current buffer with possible backup of modified file.

• If the buffer is a file and not modified, kill it. If is modified, do nothing. Print a message.
• If the buffer is not a file, first save it to `xah-temp-dir-path' named untitled_‹datetime›_‹randomhex›.txt.

If `universal-argument' is called first, call `kill-buffer'.
(this is useful when a file is modified, and then it is is changed
by some app outside emacs, and `auto-revert-mode' is on, then, emacs
goes into a loop asking to revert or save.)

If the buffer is a file, add the path to the list `xah-recently-closed-buffers'.

URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html'
Version: 2016-06-19 2023-09-27 2023-10-25"
  (interactive)
  (widen)
  (cond
   (current-prefix-arg (kill-buffer))
   ;; ((eq major-mode 'minibuffer-inactive-mode) (minibuffer-keyboard-quit))
   ;; ((active-minibuffer-window) (minibuffer-keyboard-quit))
   ((minibufferp (current-buffer)) (minibuffer-keyboard-quit))
   ((and buffer-file-name (not (buffer-modified-p)))
    (xah-add-to-recently-closed (buffer-name) buffer-file-name)
    (kill-buffer))
   ((and buffer-file-name (buffer-modified-p))
    (message "buffer file modified. Save it first.\n%s" buffer-file-name)
    ;; (let ((xnewName
    ;;            (format "%s~%s~"
    ;;                    buffer-file-name
    ;;                    (format-time-string "%Y-%m-%d_%H%M%S"))))
    ;;       (write-region (point-min) (point-max) xnewName)
    ;;       (print (format "The modified version is saved at
    ;; %s
    ;; call xah-open-last-closed twice to open." xnewName))
    ;;       (xah-add-to-recently-closed (buffer-name) xnewName)
    ;;       (xah-add-to-recently-closed (buffer-name) buffer-file-name)
    ;;       (kill-buffer))
    )
   ((and (not buffer-file-name) (xah-user-buffer-p) (not (eq (point-max) 1)))
    (let ((xnewName (format "%suntitled_%s_%x.txt"
                            xah-temp-dir-path
                            (format-time-string "%Y%m%d_%H%M%S")
                            (random #xfffff))))
      (when (not (file-exists-p xah-temp-dir-path)) (make-directory xah-temp-dir-path))
      (write-region (point-min) (point-max) xnewName)
      (xah-add-to-recently-closed (buffer-name) xnewName)
      (kill-buffer)))
   (t (kill-buffer))))

(defun xah-open-last-closed ()
  "Open the last closed file.
URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html'
Version: 2016-06-19 2022-03-22"
  (interactive)
  (if (> (length xah-recently-closed-buffers) 0)
      (find-file (cdr (pop xah-recently-closed-buffers)))
    (progn (message "No recently close buffer in this session."))))

(defun xah-open-recently-closed ()
  "Open recently closed file.
Prompt for a choice.

URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html'
Version: 2016-06-19 2023-08-25 2023-09-19"
  (interactive)
  (find-file
   (let ((completion-ignore-case t))
     (completing-read
      "Open:"
      (mapcar (lambda (f) (cdr f)) xah-recently-closed-buffers)
      nil t
      ))))

(defun xah-list-recently-closed ()
  "List recently closed file.

URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html'
Version: 2016-06-19"
  (interactive)
  (let ((xbuf (generate-new-buffer "*recently closed*")))
    (switch-to-buffer xbuf)
    (mapc (lambda (xf) (insert (cdr xf) "\n"))
          xah-recently-closed-buffers)))

(defvar xah-open-file-at-cursor-pre-hook nil "Hook for `xah-open-file-at-cursor'.
Functions in the hook is called in order, each given the raw input text (path) as arg.
The first return non-nil, its value is given to `xah-open-file-at-cursor' as input. rest functions in hook is ignored.
This is useful for transforming certain url into file path. e.g. change
http://xahlee.info/emacs/index.html
to
C:/Users/xah/web/xahlee_info/emacs/index.html
, so instead of opening in browser, it opens in emacs as file.")

(defun xah-open-file-at-cursor ()
  "Open the file path under cursor.

• If there is selection, use it for path.
• Path can be {relative, full path, URL}.
• If the path starts with 「https*://」, open the URL in browser.
• Path may have a trailing 「:‹n›」 that indicates line number, or 「:‹n›:‹m›」 with line and column number. If so, jump to that line number.

If path does not have a file extension, automatically try with .el for elisp files.

See also `xah-open-file-at-cursor-pre-hook'.

This command is similar to `find-file-at-point' but without prompting for confirmation.

URL `http://xahlee.info/emacs/emacs/emacs_open_file_path_fast.html'
Version: 2020-10-17 2023-09-29 2024-04-06"
  (interactive)
  (let (xinput xinput2 xpath)
    (setq xinput (if (region-active-p)
                     (buffer-substring-no-properties (region-beginning) (region-end))
                   (let ((xp0 (point)) xp1 xp2
                         (xpathStops "^  \t\n\"`'‘’“”|()[]{}「」<>〔〕〈〉《》【】〖〗«»‹›❮❯❬❭〘〙·。\\"))
                     (skip-chars-backward xpathStops)
                     (setq xp1 (point))
                     (goto-char xp0)
                     (skip-chars-forward xpathStops)
                     (setq xp2 (point))
                     (goto-char xp0)
                     (buffer-substring-no-properties xp1 xp2))))
    (setq xinput2 (if (> (length xah-open-file-at-cursor-pre-hook) 0)
                      (let ((xprehook (run-hook-with-args-until-success 'xah-open-file-at-cursor-pre-hook xinput)))
                        (if xprehook xprehook xinput))
                    xinput))

    (setq xpath
          (cond
           ((string-match "^file:///[A-Za-z]:/" xinput2) (substring xinput2 8))
           ((string-match "^file://[A-Za-z]:/" xinput2) (substring xinput2 7))
           (t xinput2)))

    (if (string-match-p "\\`https?://" xpath)
        (browse-url xpath)
      (let ((xpathNoQ
             (let ((xHasQuery (string-match "\?[a-z]+=" xpath)))
               (if xHasQuery
                   (substring xpath 0 xHasQuery)
                 xpath))))
        (cond
         ((string-match "#" xpathNoQ)
          (let ((xfpath (substring xpathNoQ 0 (match-beginning 0)))
                (xfractPart (substring xpathNoQ (1+ (match-beginning 0)))))
            (if (file-exists-p xfpath)
                (progn
                  (find-file xfpath)
                  (goto-char (point-min))
                  (search-forward xfractPart))
              (progn
                (message "File does not exist. Created at\n%s" xfpath)
                (find-file xfpath)))))
         ((string-match "^\\`\\(.+?\\):\\([0-9]+\\)\\(:[0-9]+\\)?\\'" xpathNoQ)
          (let ((xfpath (match-string-no-properties 1 xpathNoQ))
                (xlineNum (string-to-number (match-string-no-properties 2 xpathNoQ))))
            (if (file-exists-p xfpath)
                (progn
                  (find-file xfpath)
                  (goto-char (point-min))
                  (forward-line (1- xlineNum)))
              (progn
                (message "File does not exist. Created at\n%s" xfpath)
                (find-file xfpath)))))
         ((file-exists-p xpathNoQ)
          (progn ; open f.ts instead of f.js
            (let ((xext (file-name-extension xpathNoQ))
                  (xfnamecore (file-name-sans-extension xpathNoQ)))
              (if (and (string-equal xext "js")
                       (file-exists-p (concat xfnamecore ".ts")))
                  (find-file (concat xfnamecore ".ts"))
                (find-file xpathNoQ)))))
         ((file-exists-p (concat xpathNoQ ".el"))
          (find-file (concat xpathNoQ ".el")))
         (t (progn
              (message "File does not exist. Created at\n%s" xpathNoQ)
              (find-file xpathNoQ))))))))



(defvar xah-run-current-file-dispatch nil
"A dispatch table used by `xah-run-current-file' to call a dedicated elisp function to do the job, if any.
Value is a association list.
Each item is (EXT . FUNCTION).
EXT is file suffix (without the dot prefix) (type string),
FUNCTION is a elisp function name to call (type symbol).
If file extension is found, call the associated function pass current buffer's filepath as arg, else `xah-run-current-file' continues.
You can customize this variable." )

(setq
 xah-run-current-file-dispatch
 '(
   ;;

   ("el" . load)
   ("elc" . load)
   ("go" . xah-go-run-current-file)
   ("m" . xah-wolfram-run-script)
   ("wl" . xah-wolfram-run-script)
   ("wls" . xah-wolfram-run-script)

   ;;
   ))

(defvar xah-run-current-file-map
  "A association list that maps file extension to program name, used by `xah-run-current-file'.
Each item is (EXT . PROGRAM), both strings.
EXT is file suffix (without the dot prefix), PROGRAM is program name or path, with possibly command options.
You can customize this alist.")

(setq
 xah-run-current-file-map
 '(("clj" . "clj")
   ("fs" . "dotnet fsi")
   ("fsx" . "dotnet fsi")
   ("go" . "go run")
   ("hs" . "runhaskell")
   ("js" . "deno run")
   ("latex" . "pdflatex")
   ("m" . "wolframscript -file")
   ("mjs" . "node --experimental-modules ")
   ("ml" . "ocaml")
   ("php" . "php")
   ("pl" . "perl")
   ("ps1" . "pwsh")
   ("py" . "python")
   ("py2" . "python2")
   ("py3" . "python3")
   ("rb" . "ruby")
   ("rkt" . "racket")
   ("sh" . "bash")
   ("tex" . "pdflatex")
   ("ts" . "deno run")
   ("tsx" . "tsc")
   ("vbs" . "cscript")
   ("wl" . "wolframscript -file")
   ("wls" . "wolframscript -file")
   ("pov" . "povray +R2 +A0.1 +J1.2 +Am2 +Q9 +H480 +W640")))

(defun xah-run-current-file (Filename)
  "Execute the current file.
Output is printed to buffer *xah-run output*.

File suffix is used to determine what external command to run, in the variable `xah-run-current-file-map'.

If file is modified, it is auto saved before run.

The variable `xah-run-current-file-dispatch' allows you to customize this command to call other function to run the current file.

URL `http://xahlee.info/emacs/emacs/elisp_run_current_file.html'
Version: 2020-09-24 2024-03-21 2024-03-22"
  (interactive
   (if buffer-file-name
       (progn
         (when (buffer-modified-p) (save-buffer))
         (list buffer-file-name))
     (user-error "Buffer is not file. Save it first.")))
  ;; (setenv "NO_COLOR" "1") ; 2022-09-10 for deno. default color has yellow parts, hard to see
  (let ((xoutBuffer (get-buffer-create "*xah-run output*" t))
        (xext (file-name-extension Filename))
        xdispatch)
    (setq xdispatch (assoc xext xah-run-current-file-dispatch))
    (if (and xdispatch (fboundp (cdr xdispatch)))
        (progn
          (message "calling %s" (cdr xdispatch))
          (funcall (cdr xdispatch) Filename))
      (let ((xappCmdStr (cdr (assoc xext xah-run-current-file-map))))
        (when (and xdispatch (not (fboundp (cdr xdispatch))))
          (warn "`xah-run-current-file' found function %s in xah-run-current-file-dispatch but it is unbound. Normal run continues using `xah-run-current-file-map'." xdispatch))
        (when (not xappCmdStr) (error "%s: Unknown file extension: %s. check `xah-run-current-file-map'" real-this-command xext))
        (let ((xcmdStr (format "%s %s &" xappCmdStr (shell-quote-argument Filename))))
          (message "Running 「%s」" xcmdStr)
          ;; note, not using make-process or start-process. problem is, they are too complex to create, and have issues or variation on different operating system.
          (shell-command xcmdStr xoutBuffer)))))
  ;; (setenv "NO_COLOR")
  )

(defun xah-clean-empty-lines ()
  "Replace repeated blank lines to just 1, in whole buffer or selection.
Respects `narrow-to-region'.

URL `http://xahlee.info/emacs/emacs/elisp_compact_empty_lines.html'
Version: 2017-09-22 2020-09-08"
  (interactive)
  (let (xbegin xend)
    (if (region-active-p)
        (setq xbegin (region-beginning) xend (region-end))
      (setq xbegin (point-min) xend (point-max)))
    (save-excursion
      (save-restriction
        (narrow-to-region xbegin xend)
        (progn
          (goto-char (point-min))
          (while (re-search-forward "\n\n\n+" nil :move)
            (replace-match "\n\n")))))))

(defun xah-clean-whitespace ()
  "Delete trailing whitespace, and replace repeated blank lines to just 1.
Only space and tab is considered whitespace here.
Works on whole buffer or selection, respects `narrow-to-region'.

URL `http://xahlee.info/emacs/emacs/elisp_compact_empty_lines.html'
Version: 2017-09-22 2021-08-27 2022-08-06"
  (interactive)
  (let (xbegin xend)
    (if (region-active-p)
        (setq xbegin (region-beginning) xend (region-end))
      (setq xbegin (point-min) xend (point-max)))
    (save-excursion
      (save-restriction
        (narrow-to-region xbegin xend)
        (goto-char (point-min))
        (while (re-search-forward "[ \t]+\n" nil :move) (replace-match "\n"))
        (goto-char (point-min))
        (while (re-search-forward "\n\n\n+" nil :move) (replace-match "\n\n"))
        (goto-char (point-max))
        (while (eq (char-before) 32) (delete-char -1)))))
  (message "%s done" real-this-command))

(defun xah-make-backup ()
  "Make a backup copy of current file or dired marked files.
If in dired, backup current file or marked files.
The backup file name is in this format
 x.html~2018-05-15_133429~
 The last part is hour, minutes, seconds.
in the same dir. If such a file already exist, it is overwritten.
If the current buffer is not associated with a file, nothing's done.

URL `http://xahlee.info/emacs/emacs/elisp_make-backup.html'
Version: 2018-06-06 2020-12-18 2022-06-13"
  (interactive)
  (let ((xfname buffer-file-name)
        (xdateTimeFormat "%Y-%m-%d_%H%M%S"))
    (if xfname
        (let ((xbackupName
               (concat xfname "~" (format-time-string xdateTimeFormat) "~")))
          (copy-file xfname xbackupName t)
          (message (concat "\nBackup saved at: " xbackupName)))
      (if (eq major-mode 'dired-mode)
          (progn
            (mapc (lambda (xx)
                    (let ((xbackupName
                           (concat xx "~" (format-time-string xdateTimeFormat) "~")))
                      (copy-file xx xbackupName t)))
                  (dired-get-marked-files))
            (revert-buffer))
        (user-error "%s: buffer not file nor dired" real-this-command)))))

(defun xah-make-backup-and-save ()
  "Backup of current file and save, or backup dired marked files.
For detail, see `xah-make-backup'.
If the current buffer is not associated with a file nor dired, nothing's done.

URL `http://xahlee.info/emacs/emacs/elisp_make-backup.html'
Version: 2015-10-14"
  (interactive)
  (if buffer-file-name
      (progn
        (xah-make-backup)
        (when (buffer-modified-p)
          (save-buffer)))
    (progn
      (xah-make-backup))))

(defun xah-delete-current-file-make-backup ()
  "Delete current file, makes a backup~, close the buffer.
If buffer is not a file, copy content to `kill-ring', delete buffer.
If buffer is a file, the file's directory is shown with cursor at the next file.

Backup filename is “‹name›~‹dateTimeStamp›~”. Existing file of the same name is overwritten. If buffer is not a file, the backup file name starts with “xx_”.

Call `xah-open-last-closed' to open the backup file.

URL `http://xahlee.info/emacs/emacs/elisp_delete-current-file.html'
Version: 2018-05-15 2023-08-11 2023-10-28"
  (interactive)
  (when (eq major-mode 'dired-mode)
    (user-error "%s: In dired. Nothing is done." real-this-command))
  (let ((xfname buffer-file-name)
        (xbuffname (buffer-name))
        xbackupPath)
    (setq xbackupPath
          (concat (if xfname xfname (format "%sxx" default-directory))
                  (format "~%s~" (format-time-string "%Y-%m-%d_%H%M%S"))))
    (if xfname
        (progn
          (save-buffer xfname)
          (rename-file xfname xbackupPath t)
          (kill-buffer xbuffname)
          ;; (dired-jump nil xbackupPath)
          ;; (revert-buffer t t t)
          ;; (dired-goto-file xbackupPath)
          ;; (dired-next-line 1)
          (message "File deleted.
Backup at
%s
Call `xah-open-last-closed' to open." xbackupPath)
          (when (boundp 'xah-recently-closed-buffers)
            (push (cons nil xbackupPath) xah-recently-closed-buffers)))
      (progn
        (widen)
        (kill-new (buffer-string))
        (kill-buffer xbuffname)
        (message "non-file buffer killed. buffer text copied to `kill-ring'."))))
  (when (eq major-mode 'dired-mode) (revert-buffer)))



(defun xah-search-current-word ()
  "Call `isearch' on current word or selection.
“word” here is A to Z, a to z, and hyphen [-] and lowline [_], independent of syntax table.

URL `http://xahlee.info/emacs/emacs/modernization_isearch.html'
Version: 2015-04-09"
  (interactive)
  (let (xp1 xp2)
    (if (region-active-p)
        (setq xp1 (region-beginning) xp2 (region-end))
      (save-excursion
        (skip-chars-backward "-_A-Za-z0-9")
        (setq xp1 (point))
        (right-char)
        (skip-chars-forward "-_A-Za-z0-9")
        (setq xp2 (point))))
    (setq mark-active nil)
    (when (< xp1 (point))
      (goto-char xp1))
    (isearch-mode t)
    (isearch-yank-string (buffer-substring-no-properties xp1 xp2))))

(declare-function w32-shell-execute "w32fns.c" (operation document &optional parameters show-flag)) ; (w32-shell-execute "open" default-directory)

(defun xah-show-in-desktop ()
  "Show current file in desktop.
 (Mac Finder, Microsoft Windows File Explorer, Linux file manager)
This command can be called when in a file buffer or in `dired'.

URL `http://xahlee.info/emacs/emacs/emacs_show_in_desktop.html'
Version: 2020-11-20 2022-08-19 2023-06-26 2023-09-09"
  (interactive)
  (let ((xpath (if (eq major-mode 'dired-mode)
                   (if (eq nil (dired-get-marked-files))
                       default-directory
                     (car (dired-get-marked-files)))
                 (if buffer-file-name buffer-file-name default-directory))))
    (cond
     ((eq system-type 'windows-nt)
      (shell-command (format "PowerShell -Command invoke-item '%s'" (expand-file-name default-directory )))
      ;; (let ((xcmd (format "Explorer /select,%s"
      ;;                     (replace-regexp-in-string "/" "\\" xpath t t)
      ;;                     ;; (shell-quote-argument (replace-regexp-in-string "/" "\\" xpath t t ))
      ;;                     )))
      ;;   (shell-command xcmd))
      )
     ((eq system-type 'darwin)
      (shell-command
       (concat "open -R " (shell-quote-argument xpath))))
     ((eq system-type 'gnu/linux)
      (call-process shell-file-name nil 0 nil
                    shell-command-switch
                    (format "%s %s"
                            "xdg-open"
                            (file-name-directory xpath)))
      ;; (shell-command "xdg-open .") ;; 2013-02-10 this sometimes froze emacs till the folder is closed. eg with nautilus
      ))))

(defun xah-open-in-vscode ()
  "Open current file or dir in vscode.
URL `http://xahlee.info/emacs/emacs/emacs_open_in_vscode.html'

Version: 2020-02-13 2021-01-18 2022-08-04 2023-06-26"
  (interactive)
  (let ((xpath (if buffer-file-name buffer-file-name (expand-file-name default-directory))))
    (message "path is %s" xpath)
    (cond
     ((eq system-type 'darwin)
      (shell-command (format "open -a Visual\\ Studio\\ Code.app %s" (shell-quote-argument xpath))))
     ((eq system-type 'windows-nt)
      (shell-command (format "code.cmd %s" (shell-quote-argument xpath))))
     ((eq system-type 'gnu/linux)
      (shell-command (format "code %s" (shell-quote-argument xpath)))))))

(defun xah-open-in-external-app (&optional Fname)
  "Open the current file or dired marked files in external app.
When called in emacs lisp, if Fname is given, open that.

URL `http://xahlee.info/emacs/emacs/emacs_dired_open_file_in_ext_apps.html'
Version: 2019-11-04 2023-04-05 2023-06-26"
  (interactive)
  (let (xfileList xdoIt)
    (setq xfileList
          (if Fname
              (list Fname)
            (if (eq major-mode 'dired-mode)
                (dired-get-marked-files)
              (list buffer-file-name))))
    (setq xdoIt (if (<= (length xfileList) 10) t (y-or-n-p "Open more than 10 files? ")))
    (when xdoIt
      (cond
       ((eq system-type 'windows-nt)
        (let ((xoutBuf (get-buffer-create "*xah open in external app*"))
              (xcmdlist (list "PowerShell" "-Command" "Invoke-Item" "-LiteralPath")))
          (mapc
           (lambda (x)
             (message "%s" x)
             (apply 'start-process (append (list "xah open in external app" xoutBuf) xcmdlist (list (format "'%s'" (if (string-match "'" x) (replace-match "`'" t t x) x))) nil)))
           xfileList)
          ;; (switch-to-buffer-other-window xoutBuf)
          )
        ;; old code. calling shell. also have a bug if filename contain apostrophe
        ;; (mapc (lambda (xfpath) (shell-command (concat "PowerShell -Command \"Invoke-Item -LiteralPath\" " "'" (shell-quote-argument (expand-file-name xfpath)) "'"))) xfileList)
        )
       ((eq system-type 'darwin)
        (mapc (lambda (xfpath) (shell-command (concat "open " (shell-quote-argument xfpath)))) xfileList))
       ((eq system-type 'gnu/linux)
        (mapc (lambda (xfpath)
                (call-process shell-file-name nil 0 nil
                              shell-command-switch
                              (format "%s %s"
                                      "xdg-open"
                                      (shell-quote-argument xfpath))))
              xfileList))
       ((eq system-type 'berkeley-unix)
        (mapc (lambda (xfpath) (let ((process-connection-type nil)) (start-process "" nil "xdg-open" xfpath))) xfileList))))))

(defvar xah-fly-mswin-terminal
  "wt"
  "A string. Value should be one of: wt (for Windows Terminal) or pwsh (for PowerShell Core (cross-platform)) or powershell (for Microsoft PowerShell).")

(defun xah-open-in-terminal ()
  "Open the current dir in a new terminal window.
On Microsoft Windows, which terminal it starts depends on `xah-fly-mswin-terminal'.

URL `http://xahlee.info/emacs/emacs/emacs_open_in_terminal.html'
Version: 2020-11-21 2022-08-04 2023-03-01 2023-06-26"
  (interactive)
  (cond
   ((eq system-type 'windows-nt)
    (cond
     ((string-equal xah-fly-mswin-terminal "wt")
      (shell-command (format "wt -d \"%s\"" default-directory)))
     ((string-equal xah-fly-mswin-terminal "pwsh")
      (shell-command
       (format "pwsh -Command Start-Process pwsh -WorkingDirectory '%s'" (shell-quote-argument default-directory))))
     ((string-equal xah-fly-mswin-terminal "powershell")
      (shell-command
       (format "powershell -Command Start-Process powershell -WorkingDirectory '%s'" (shell-quote-argument default-directory))))
     (t (error "Error 702919: value of `xah-fly-mswin-terminal' is not expected. Its value is %s" xah-fly-mswin-terminal))))
   ((eq system-type 'darwin)
    (shell-command (concat "open -a terminal " (shell-quote-argument (expand-file-name default-directory)))))
   ((eq system-type 'gnu/linux)
    (let ((process-connection-type nil)) (start-process "" nil "x-terminal-emulator" (concat "--working-directory=" default-directory))))
   ((eq system-type 'berkeley-unix)
    (let ((process-connection-type nil)) (start-process "" nil "x-terminal-emulator" (concat "--working-directory=" default-directory))))))

(defun xah-next-window-or-frame ()
  "Switch to next window or frame.
If current frame has only one window, switch to next frame.
If `universal-argument' is called first, do switch frame.
Version: 2017-01-27"
  (interactive)
  (if current-prefix-arg
      (other-frame 1)
    (if (one-window-p)
        (other-frame 1)
      (other-window 1))))

(defun xah-unsplit-window-or-next-frame ()
  "Unsplit window. If current frame has only one window, switch to next frame.
Version: 2017-01-29"
  (interactive)
  (if (one-window-p)
      (other-frame 1)
    (delete-other-windows)))


;; layout lookup tables for key conversion

(defvar xah-fly-layouts nil "A alist.
Key is layout name, string type.
Value is a alist, each element is of the form (\"e\" . \"d\").
First char is Dvorak, second is corresponding char of the destination layout.
When a char is not in this alist, they are assumed to be the same. ")

(push '("azerty" . (("." . "e") ("," . "z") ("'" . "a") (";" . "w") ("/" . "^") ("[" . ")") ("]" . "=") ("=" . "$") ("-" . "ù") ("a" . "q") ("b" . "n") ("c" . "i") ("d" . "h") ("e" . "d") ("f" . "y") ("g" . "u") ("h" . "j") ("i" . "g") ("j" . "c") ("k" . "v") ("l" . "p") ("m" . ",") ("n" . "l") ("o" . "s") ("p" . "r") ("q" . "x") ("r" . "o") ("s" . "m") ("t" . "k") ("u" . "f") ("v" . ":") ("w" . ";") ("x" . "b") ("y" . "t") ("z" . "!") ("1" . "&") ("2" . "é") ("3" . "\"") ("4" . "'") ("5" . "(") ("6" . "-") ("7" . "è") ("8" . "_") ("9" . "ç") ("0" . "à") ("\\" . "*") ("`" . "²"))) xah-fly-layouts)

(push '("azerty-be" . (("." . "e") ("," . "z") ("'" . "a") (";" . "w") ("/" . "^") ("[" . ")") ("]" . "-") ("=" . "$") ("-" . "ù") ("a" . "q") ("b" . "n") ("c" . "i") ("d" . "h") ("e" . "d") ("f" . "y") ("g" . "u") ("h" . "j") ("i" . "g") ("j" . "c") ("k" . "v") ("l" . "p") ("m" . ",") ("n" . "l") ("o" . "s") ("p" . "r") ("q" . "x") ("r" . "o") ("s" . "m") ("t" . "k") ("u" . "f") ("v" . ":") ("w" . ";") ("x" . "b") ("y" . "t") ("z" . "=") ("1" . "&") ("2" . "é") ("3" . "\"") ("4" . "'") ("5" . "(") ("6" . "§") ("7" . "è") ("8" . "!") ("9" . "ç") ("0" . "à") ("\\" . "µ") ("`" . "²"))) xah-fly-layouts)

(push '("colemak" . (("'" . "q") ("," . "w") ("." . "f") ("y" . "g") ("f" . "j") ("g" . "l") ("c" . "u") ("r" . "y") ("l" . ";") ("o" . "r") ("e" . "s") ("u" . "t") ("i" . "d") ("d" . "h") ("h" . "n") ("t" . "e") ("n" . "i") ("s" . "o") (";" . "z") ("q" . "x") ("j" . "c") ("k" . "v") ("x" . "b") ("b" . "k") ("w" . ",") ("v" . ".") ("z" . "/"))) xah-fly-layouts)

(push '("colemak-dhm" . (("'" . "q") ("," . "w") ("." . "f") (";" . "z") ("b" . "k") ("c" . "u") ("d" . "m") ("e" . "s") ("f" . "j") ("g" . "l") ("h" . "n") ("i" . "g") ("j" . "c") ("k" . "d") ("l" . ";") ("m" . "h") ("n" . "i") ("o" . "r") ("q" . "x") ("r" . "y") ("s" . "o") ("t" . "e") ("u" . "t") ("v" . ".") ("w" . ",") ("x" . "v") ("y" . "b") ("z" . "/"))) xah-fly-layouts)

(push '("colemak-dhm-angle" . (("'" . "q") ("," . "w") ("." . "f") (";" . "x") ("b" . "k") ("c" . "u") ("d" . "m") ("e" . "s") ("f" . "j") ("g" . "l") ("h" . "n") ("i" . "g") ("j" . "d") ("k" . "v") ("l" . ";") ("m" . "h") ("n" . "i") ("o" . "r") ("q" . "c") ("r" . "y") ("s" . "o") ("t" . "e") ("u" . "t") ("v" . ".") ("w" . ",") ("x" . "\\") ("y" . "b") ("z" . "/"))) xah-fly-layouts)

(push '("colemak-dhk" . (("'" . "q") ("," . "w") ("." . "f") (";" . "z") ("b" . "m") ("c" . "u") ("d" . "k") ("e" . "s") ("f" . "j") ("g" . "l") ("h" . "n") ("i" . "g") ("j" . "c") ("k" . "d") ("l" . ";") ("m" . "h") ("n" . "i") ("o" . "r") ("q" . "x") ("r" . "y") ("s" . "o") ("t" . "e") ("u" . "t") ("v" . ".") ("w" . ",") ("x" . "v") ("y" . "b") ("z" . "/"))) xah-fly-layouts)

(push '("dvorak" . nil) xah-fly-layouts)

(push '("optimot" . (("-" . "^") ("'" . "à") ("," . "j") ("." . "o") (";" . "k") ("/" . "x") ("[" . "#") ("]" . "@") ("=" . "ç") ("a" . "a") ("b" . "g") ("c" . "l") ("d" . "p") ("e" . "e") ("f" . "f") ("g" . "d") ("h" . "t") ("i" . ",") ("j" . "è") ("k" . ".") ("l" . "q") ("m" . "c") ("n" . "r") ("o" . "i") ("p" . "é") ("q" . "y") ("r" . "'") ("s" . "n") ("t" . "s") ("u" . "u") ("v" . "h") ("w" . "m") ("x" . "w") ("y" . "b") ("z" . "v") ("1" . "«") ("2" . "»") ("3" . "\"") ("4" . "-") ("5" . "+") ("6" . "*") ("7" . "/") ("8" . "=") ("9" . "(") ("0" . ")") ("\\" . "ç") ("`" . "$"))) xah-fly-layouts)

(push '("programer-dvorak" . ( ("`" . "$") ("1" . "&") ("2" . "[") ("3" . "{") ("4" . "}") ("5" . "(") ("6" . "=") ("7" . "*") ("8" . ")") ("9" . "+") ("0" . "]") ("[" . "!") ("]" . "#") ("!" . "%") ("@" . "7") ("#" . "5") ("$" . "3") ("%" . "1") ("^" . "9") ("&" . "0") ("*" . "2") ("(" . "4") (")" . "6") ("{" . "8") ("}" . "`") ("'" . ";") ("\"" . ":") (";" . "'") (":" . "\"") ("=" . "@") ("+" . "^") )) xah-fly-layouts)

(push '("qwerty" . (("." . "e") ("," . "w") ("'" . "q") (";" . "z") ("/" . "[") ("[" . "-") ("]" . "=") ("=" . "]") ("-" . "'") ("a" . "a") ("b" . "n") ("c" . "i") ("d" . "h") ("e" . "d") ("f" . "y") ("g" . "u") ("h" . "j") ("i" . "g") ("j" . "c") ("k" . "v") ("l" . "p") ("n" . "l") ("o" . "s") ("p" . "r") ("q" . "x") ("r" . "o") ("s" . ";") ("t" . "k") ("u" . "f") ("v" . ".") ("w" . ",") ("x" . "b") ("y" . "t") ("z" . "/"))) xah-fly-layouts)

;; QWERTY Norwegian
(push '("qwerty-no" . (("." . "e") ("," . "w") ("'" . "q") (";" . "z") ("/" . "å") ("[" . "+") ("]" . "´") ("=" . "¨") ("-" . "æ") ("b" . "n") ("c" . "i") ("d" . "h") ("e" . "d") ("f" . "y") ("g" . "u") ("h" . "j") ("i" . "g") ("j" . "c") ("k" . "v") ("l" . "p") ("n" . "l") ("o" . "s") ("p" . "r") ("q" . "x") ("r" . "o") ("s" . "ø") ("t" . "k") ("u" . "f") ("v" . ".") ("w" . ",") ("x" . "b") ("y" . "t") ("z" . "-"))) xah-fly-layouts)

(push '("qwerty-abnt" . (("." . "e") ("," . "w") ("'" . "q") (";" . "z") ("/" . "'") ("[" . "-") ("]" . "=") ("=" . "[") ("-" . "~") ("b" . "n") ("c" . "i") ("d" . "h") ("e" . "d") ("f" . "y") ("g" . "u") ("h" . "j") ("i" . "g") ("j" . "c") ("k" . "v") ("l" . "p") ("n" . "l") ("o" . "s") ("p" . "r") ("q" . "x") ("r" . "o") ("s" . "ç") ("t" . "k") ("u" . "f") ("v" . ".") ("w" . ",") ("x" . "b") ("y" . "t") ("z" . ";"))) xah-fly-layouts)

(push '("qwertz" . (("." . "e") ("," . "w") ("'" . "q") (";" . "y") ("/" . "ü") ("[" . "ß") ("]" . "´") ("=" . "+") ("-" . "ä") ("b" . "n") ("c" . "i") ("d" . "h") ("e" . "d") ("f" . "z") ("g" . "u") ("h" . "j") ("i" . "g") ("j" . "c") ("k" . "v") ("l" . "p") ("n" . "l") ("o" . "s") ("p" . "r") ("q" . "x") ("r" . "o") ("s" . "ö") ("t" . "k") ("u" . "f") ("v" . ".") ("w" . ",") ("x" . "b") ("y" . "t") ("z" . "-"))) xah-fly-layouts)

(push '("workman" . (("[" . "-") ("]" . "=") ("'" . "q") ("," . "d") ("." . "r") ("p" . "w") ("y" . "b") ("f" . "j") ("g" . "f") ("c" . "u") ("r" . "p") ("l" . ";") ("/" . "[") ("=" . "]") ("o" . "s") ("e" . "h") ("u" . "t") ("i" . "g") ("d" . "y") ("h" . "n") ("t" . "e") ("n" . "o") ("s" . "i") ("-" . "'") (";" . "z") ("q" . "x") ("j" . "m") ("k" . "c") ("x" . "v") ("b" . "k") ("m" . "l") ("w" . ",") ("v" . ".") ("z" . "/"))) xah-fly-layouts)

(push '("norman" . (("'" . "q") ("," . "w") ("." . "d") ("p" . "f") ("y" . "k") ("f" . "j") ("g" . "u") ("c" . "r") ("r" . "l") ("l" . ";") ("o" . "s") ("u" . "t") ("i" . "g") ("d" . "y") ("h" . "n") ("t" . "i") ("n" . "o") ("s" . "h") (";" . "z") ("q" . "x") ("j" . "c") ("k" . "v") ("x" . "b") ("b" . "p") ("w" . ",") ("v" . ".") ("z" . "/"))) xah-fly-layouts)

(push '("neo2" . (("'" . "x") ("," . "v") ("." . "l") ("p" . "c") ("y" . "w") ("f" . "k") ("g" . "h") ("c" . "g") ("r" . "f") ("l" . "q") ("a" . "u") ("o" . "i") ("e" . "a") ("u" . "e") ("i" . "o") ("d" . "s") ("h" . "n") ("t" . "r") ("n" . "t") ("s" . "d") (";" . "ü") ("q" . "ö") ("j" . "ä") ("k" . "p") ("x" . "z") ("w" . ",") ("v" . ".") ("z" . "j") ("/" . "ß") ("[" . "-") ("-" . "y"))) xah-fly-layouts)

(push '("koy" . (("'" . "k") ("," . ".") ("." . "o") ("p" . ",") ("f" . "v") ("r" . "l") ("l" . "ß") ("a" . "h") ("o" . "a") ("u" . "i") ("i" . "u") ("h" . "t") ("t" . "r") (";" . "x") ("j" . "ä") ("k" . "ü") ("x" . "ö") ("m" . "p") ("v" . "m") ("z" . "j"))) xah-fly-layouts)

(push '("adnw" . (("'" . "k") ("," . "u") ("." . "ü") ("p" . ".") ("y" . "ä") ("f" . "v") ("r" . "l") ("l" . "j") ("/" . "f") ("a" . "h") ("o" . "i") ("u" . "a") ("i" . "o") ("h" . "t") ("t" . "r") ("-" . "ß") (";" . "x") ("q" . "y") ("j" . "ö") ("k" . ",") ("x" . "q") ("m" . "p") ("v" . "m"))) xah-fly-layouts)

(push '("pt-nativo" . ((";" . "«") ("/" . "~") ("[" . "º") ("]" . "<") ("=" . "-") ("-" . "´") ("a" . "i") ("b" . "q") ("c" . "t") ("d" . "m") ("e" . "a") ("f" . "w") ("g" . "l") ("h" . "d") ("i" . "u") ("k" . "b") ("l" . "p") ("m" . "v") ("n" . "r") ("o" . "e") ("p" . "h") ("q" . "ç") ("r" . "c") ("s" . "n") ("t" . "s") ("u" . "o") ("v" . "f") ("w" . "g") ("x" . "k") ("y" . "x"))) xah-fly-layouts)

(push '("carpalx-qgmlwy" . (("." . "m") ("," . "g") ("'" . "q") (";" . "z") ("/" . "[") ("[" . "-") ("]" . "=") ("=" . "]") ("-" . "'") ("a" . "d") ("b" . "k") ("c" . "u") ("d" . "i") ("e" . "t") ("f" . "y") ("g" . "f") ("h" . "a") ("i" . "r") ("j" . "c") ("k" . "v") ("l" . ";") ("m" . "p") ("n" . "o") ("o" . "s") ("p" . "l") ("q" . "x") ("r" . "b") ("s" . "h") ("t" . "e") ("u" . "n") ("v" . ".") ("w" . ",") ("x" . "j") ("y" . "w") ("z" . "/"))) xah-fly-layouts)

(push '("carpalx-qgmlwb" . (("." . "m") ("," . "g") ("'" . "q") (";" . "z") ("/" . "[") ("[" . "-") ("]" . "=") ("=" . "]") ("-" . "'") ("a" . "d") ("b" . "k") ("c" . "u") ("d" . "i") ("e" . "t") ("f" . "b") ("g" . "y") ("h" . "a") ("i" . "r") ("j" . "c") ("k" . "f") ("l" . ";") ("m" . "p") ("n" . "o") ("o" . "s") ("p" . "l") ("q" . "x") ("r" . "v") ("s" . "h") ("t" . "e") ("u" . "n") ("v" . ".") ("w" . ",") ("x" . "j") ("y" . "w") ("z" . "/"))) xah-fly-layouts)

(push '("carpalx-qfmlwy" . (("." . "m") ("," . "f") ("'" . "q") (";" . "z") ("/" . "[") ("[" . "-") ("]" . "=") ("=" . "]") ("-" . "'") ("a" . "d") ("b" . "p") ("c" . "o") ("d" . "i") ("e" . "t") ("f" . "y") ("g" . "u") ("h" . "a") ("i" . "r") ("j" . "g") ("k" . "c") ("l" . "j") ("m" . "k") ("n" . "h") ("o" . "s") ("p" . "l") ("q" . "v") ("r" . "b") ("s" . ";") ("t" . "e") ("u" . "n") ("v" . ".") ("w" . ",") ("y" . "w") ("z" . "/"))) xah-fly-layouts)

(push '("bepo" . (("'" . "b") ("," . "é") ("." . "p") ("p" . "o") ("y" . "è") ("f" . "^") ("g" . "v") ("c" . "d") ("r" . "l") ("l" . "j") ("o" . "u") ("e" . "i") ("u" . "e") ("i" . ",") ("d" . "c") ("h" . "t") ("t" . "s") ("n" . "r") ("s" . "n") (":" . "à") ("q" . "y") ("j" . "x") ("k" . ".") ("x" . "k") ("b" . "’") ("m" . "q") ("w" . "g") ("v" . "h") ("z" . "f") ("3" . "»") ("4" . "(") ("5" . ")") ("6" . "@") ("7" . "+") ("8" . "-") ("9" . "/"))) xah-fly-layouts)

(defvar xah-fly-key-current-layout nil
  "The current keyboard layout. Value is a key in `xah-fly-layouts'.
Do not set this variable manually. Use `xah-fly-keys-set-layout' to set it.
If the value is nil, it is automatically set to \"qwerty\".
When this variable changes, suitable change must also be done to `xah-fly--key-convert-table'.
Version: 2022-10-22")

(if xah-fly-key-current-layout nil (setq xah-fly-key-current-layout "qwerty"))

(defvar xah-fly--key-convert-table nil
  "A alist that's the conversion table from dvorak to current layout.
Value structure is one of the key's value of `xah-fly-layouts'.
Value is programtically set from value of `xah-fly-key-current-layout'.
Do not manually set this variable.
Version: 2019-02-12 2022-10-22" )

(setq xah-fly--key-convert-table
      (cdr (assoc xah-fly-key-current-layout xah-fly-layouts)))

(defun xah-fly--convert-kbd-str (Charstr)
  "Return the corresponding char Charstr according to
`xah-fly--key-convert-table'. Charstr must be a string that is the argument to `kbd'. e.g. \"a\" and \"a b c\"
Each space separated token is converted according to `xah-fly--key-convert-table'.
Version: 2022-10-25"
  (interactive)
  (mapconcat
   'identity
   (mapcar
    (lambda (x)
      (let ((xresult (assoc x xah-fly--key-convert-table)))
        (if xresult (cdr xresult) x)))
    (split-string Charstr " +"))
   " "))

(defun xah-fly--define-keys (KeymapName KeyCmdAlist &optional Direct-p)
  "Map `define-key' over a alist KeyCmdAlist, with key layout remap.
The key is remapped from Dvorak to the current keyboard layout by `xah-fly--convert-kbd-str'.
If Direct-p is t, do not remap key to current keyboard layout.
Example usage:
 (xah-fly--define-keys
  (define-prefix-command \\='xyz-map)
  \\='(
    (\"h\" . highlight-symbol-at-point)
    (\".\" . isearch-forward-symbol-at-point)
    (\"w\" . isearch-forward-word)))
Version: 2020-04-18 2022-10-25 2023-08-21"
  (mapcar
   (lambda (x)
     (define-key
      KeymapName
      (kbd (if Direct-p (car x) (xah-fly--convert-kbd-str (car x))))
      (cdr x)))
   KeyCmdAlist))


;; keymaps

(defvar xah-fly-key-map (make-sparse-keymap)
  "Backward-compatibility map for `xah-fly-keys' minor mode. If
`xah-fly-insert-state-p' is true, point to `xah-fly-insert-map', else,
point to points to `xah-fly-command-map'.")

(make-obsolete-variable
 'xah-fly-key-map
 "Put bindings for command mode in `xah-fly-command-map', bindings for
insert mode in `xah-fly-insert-map' and bindings that are common to both
command and insert modes in `xah-fly-shared-map'."
 "2020-04-16")

(defvar xah-fly-shared-map (make-sparse-keymap)
  "Parent keymap of `xah-fly-command-map' and `xah-fly-insert-map'.

Define keys that are available in both command and insert modes here, like
`xah-fly-mode-toggle'")

;; (cons 'keymap xah-fly-shared-map) makes a new keymap with `xah-fly-shared-map' as its parent. See info node (elisp)Inheritance and Keymaps.
(defvar xah-fly-command-map (cons 'keymap xah-fly-shared-map)
  "Keymap that takes precedence over all other keymaps in command mode.

Inherits bindings from `xah-fly-shared-map'.
In command mode, if no binding is found in this map `xah-fly-shared-map' is checked, then if there is still no binding, the other active keymaps are checked like normal.
However, if a key is explicitly bound to nil in this map, it will not be looked up in `xah-fly-shared-map' and lookup will skip directly to the normally active maps.
In this way, bindings in `xah-fly-shared-map' can be disabled by this map.

Effectively, this map takes precedence over all others when command mode
is enabled.")

(defvar xah-fly-insert-map (cons 'keymap xah-fly-shared-map)
  "Keymap for bindings that is checked in insert mode. Active whenever `xah-fly-keys' is non-nil.

Inherits bindings from `xah-fly-shared-map'.
In insert mode, if no binding is found in this map `xah-fly-shared-map' is checked, then if there is still no binding, the other active keymaps are checked like normal.
However, if a key is explicitly bound to nil in this map, it will not be looked up in `xah-fly-shared-map' and lookup will skip directly to the normally active maps.
In this way, bindings in `xah-fly-shared-map' can be disabled by this map.

Keep in mind that this acts like a normal global minor mode map, so other minor modes loaded later may override bindings in this map.")

(defvar xah-fly--deactivate-command-mode-func nil)


;; setting keys

(defun xah-fly-define-keys ()
  "Define the keys for xah-fly-keys.
Used by `xah-fly-keys-set-layout' for changing layout.
Version: 2022-10-31"
  (interactive)
  (let ()

    ;; Movement key integrations with built-in Emacs packages
    (xah-fly--define-keys
     indent-rigidly-map
     '(("h" . indent-rigidly-left)
       ("n" . indent-rigidly-right)))

    (xah-fly--define-keys
     xah-fly-shared-map
     '(("<home>" . xah-fly-command-mode-activate)
       ("<menu>" . xah-fly-command-mode-activate)
       ("<escape>" . xah-fly-command-mode-activate)
       ("<f8>" . xah-fly-command-mode-activate))
     :direct)

    (when xah-fly-use-isearch-arrows
      (xah-fly--define-keys
       isearch-mode-map
       '(("<up>" . isearch-ring-retreat)
         ("<down>" . isearch-ring-advance)
         ("<left>" . isearch-repeat-backward)
         ("<right>" . isearch-repeat-forward)
         ("C-v" . isearch-yank-kill))
       :direct)
      (xah-fly--define-keys
       minibuffer-local-isearch-map
       '(("<left>" . isearch-reverse-exit-minibuffer)
         ("<right>" . isearch-forward-exit-minibuffer))
       :direct))

    (xah-fly--define-keys
     (define-prefix-command 'xah-fly-leader-key-map)
     '(("SPC" . xah-fly-insert-mode-activate)
       ("RET" . execute-extended-command)

       ("TAB" . nil)

       ("TAB TAB" . indent-for-tab-command)

       ("TAB i" . complete-symbol)
       ("TAB g" . indent-rigidly)
       ("TAB r" . indent-region)
       ("TAB s" . indent-sexp)

       (". ." . highlight-symbol-at-point)
       (". g" . unhighlight-regexp)
       (". c" . highlight-lines-matching-regexp)
       (". h" . highlight-regexp)
       (". t" . highlight-phrase)
       (". e" . isearch-forward-symbol-at-point)
       (". u" . isearch-forward-symbol)
       (". p" . isearch-forward-word)

       ("'" . xah-fill-or-unfill)

       (", t" . xref-find-definitions)
       (", n" . xref-pop-marker-stack)

       ;; - / ; = [
       ("\\" . toggle-input-method)
       ;; `

       ("3" . delete-window)
       ("4" . split-window-right)
       ("5" . balance-windows)
       ("6" . xah-upcase-sentence)

       ("9" . ispell-word)

       ("a" . mark-whole-buffer)
       ("b" . end-of-buffer)

       ("c ," . xah-open-in-external-app)
       ("c ." . find-file)
       ("c c" . bookmark-bmenu-list)
       ("c e" . ibuffer)
       ("c f" . xah-open-recently-closed)
       ("c g" . xah-open-in-terminal)
       ("c h" . recentf-open-files)
       ("c j" . xah-copy-file-path)
       ("c l" . bookmark-set)
       ("c n" . xah-new-empty-buffer)
       ("c o" . xah-show-in-desktop)
       ("c p" . xah-open-last-closed)
       ("c r" . bookmark-jump)
       ("c s" . write-file)
       ("c u" . xah-open-file-at-cursor)
       ("c x" . set-buffer-file-coding-system)
       ("c y" . xah-list-recently-closed)
       ("c z" . revert-buffer-with-coding-system)

       ;; set-buffer-process-coding-system
       ;; set-file-name-coding-system
       ;; set-keyboard-coding-system
       ;; set-language-environment
       ;; set-next-selection-coding-system
       ;; set-selection-coding-system
       ;; set-terminal-coding-system
       ;; universal-coding-system-argument

       ("d" . beginning-of-buffer)

       ("e a" . xah-insert-double-angle-bracket)     ; 《》
       ("e b" . xah-insert-black-lenticular-bracket) ; 【】

       ("e c r" . expand-region-abbrevs)
       ("e c t" . edit-abbrevs)
       ("e c u" . expand-abbrev)

       ("e c g" . add-mode-abbrev)
       ("e c c" . add-global-abbrev)
       ("e c m" . inverse-add-mode-abbrev)
       ("e c w" . inverse-add-global-abbrev)

       ("e c f" . unexpand-abbrev)

       ("e c h" . expand-jump-to-previous-slot)
       ("e c n" . expand-jump-to-next-slot)
       ("e c y" . abbrev-prefix-mark)

       ("e d" . xah-insert-double-curly-quote) ; “”
       ("e e" . xah-insert-unicode)
       ("e f" . xah-insert-emacs-quote)        ; `'
       ("e g" . xah-insert-ascii-double-quote) ; ""

       ("e h" . xah-insert-brace)              ; {}
       ("e i" . xah-insert-curly-single-quote) ; ‘’
       ("e j" . insert-char)
       ("e k" . xah-insert-markdown-quote) ; ``
       ("e l" . xah-insert-formfeed)
       ("e m" . xah-insert-corner-bracket)      ; 「」
       ("e n" . xah-insert-square-bracket)      ; []
       ("e o" . xah-insert-ascii-single-quote)  ; ''
       ("e s" . xah-insert-ascii-angle-bracket) ; <>

       ("e p" . xah-insert-single-angle-quote)     ; ‹›
       ;; q
       ("e r" . xah-insert-tortoise-shell-bracket) ; 〔〕
       ;; s
       ("e t" . xah-insert-paren) ; ()
       ("e u" . xah-insert-date)
       ("e v" . xah-insert-markdown-triple-quote) ;
       ("e w" . xah-insert-angle-bracket)         ; 〈〉
       ;; x
       ("e y" . xah-insert-double-angle-quote) ; «»
       ;; z

       ("f" . xah-search-current-word)
       ("g" . xah-close-current-buffer)

       ("h a" . apropos-command)
       ("h b" . describe-bindings)
       ("h c" . describe-char)
       ("h d" . apropos-documentation)
       ("h e" . view-echo-area-messages)
       ("h f" . describe-face)
       ("h g" . info-lookup-symbol)
       ("h h" . describe-function)
       ("h i" . info)
       ("h j" . man)
       ("h k" . describe-key)
       ("h l" . view-lossage)
       ("h m" . describe-mode)
       ("h n" . describe-variable)
       ("h o" . describe-language-environment)
       ("h r" . apropos-variable)
       ("h s" . describe-syntax)
       ("h u" . elisp-index-search)
       ("h v" . apropos-value)
       ("h x" . describe-command) ; emacs 28
       ("h z" . describe-coding-system)

       ("i" . kill-line)
       ("j" . xah-copy-all-or-region)

       ("l" . recenter-top-bottom)

       ("m" . dired-jump)

       ;; ("m t" . dired-jump)
       ;; ("m e" . delete-other-windows)
       ;; ("m u" . split-window-below)
       ;; ("m w" . universal-argument)

       ;; commands here are “harmless”, they don't modify text etc. they turn on modes, change display, prompt, start shell, etc.
       ("n SPC" . whitespace-mode)
       ("n ," . abbrev-mode)
       ("n ." . toggle-frame-maximized)
       ("n 1" . set-input-method)
       ("n 2" . global-hl-line-mode)
       ("n 4" . global-display-line-numbers-mode)
       ("n 6" . calendar)
       ("n 7" . calc)
       ("n 9" . shell-command)
       ("n 0" . shell-command-on-region)

       ("n a" . text-scale-adjust)
       ("n b" . toggle-debug-on-error)
       ("n c" . toggle-case-fold-search)
       ("n d" . narrow-to-page)
       ("n e" . eshell)
       ;; f
       ("n g" . xah-toggle-read-novel-mode)
       ("n h" . widen)
       ("n i" . make-frame-command)
       ("n j" . flyspell-buffer)
       ("n k" . menu-bar-open)
       ("n l" . toggle-word-wrap)
       ("n m" . jump-to-register)
       ("n n" . xah-narrow-to-region)
       ("n o" . variable-pitch-mode)
       ("n p" . read-only-mode)
       ;; q
       ("n r" . count-words)
       ("n s" . count-matches)
       ("n t" . narrow-to-defun)
       ("n u" . shell)
       ("n v" . visual-line-mode)
       ("n w" . eww)
       ("n x" . save-some-buffers)
       ("n y" . toggle-truncate-lines)
       ("n z" . abort-recursive-edit)

       ("o" . exchange-point-and-mark)
       ("p" . query-replace)
       ("q" . xah-cut-all-or-region)

       ;; roughly text replacement related
       ("r SPC" . rectangle-mark-mode)
       ("r ," . apply-macro-to-region-lines)
       ("r ." . kmacro-start-macro)
       ("r 3" . number-to-register)
       ("r 4" . increment-register)

       ;; a
       ;; b
       ("r c" . replace-rectangle)
       ("r d" . delete-rectangle)
       ("r e" . call-last-kbd-macro)
       ;; f
       ("r q" . kill-rectangle) ; changed
       ("r h" . xah-change-bracket-pairs)
       ("r i" . xah-space-to-newline)
       ("r j" . copy-rectangle-to-register)
       ("r k" . yank-rectangle)                ; changed
       ("r /" . xah-slash-to-double-backslash) ; changed
       ("r l" . clear-rectangle)

       ("r m" . xah-slash-to-backslash)
       ("r n" . rectangle-number-lines)
       ("r o" . open-rectangle)
       ("r p" . kmacro-end-macro)
       ;; r
       ;; s t
       ("r u" . xah-quote-lines)
       ;; v w
       ("r x" . xah-double-backslash-to-slash)
       ("r y" . delete-whitespace-rectangle)
       ;; z

       ("s" . save-buffer)

       ;; most frequently used
       ("t <up>"  . xah-move-block-up)
       ("t <down>"  . xah-move-block-down)

       ("t ," . sort-numeric-fields)
       ("t ." . xah-sort-lines)
       ("t 1" . xah-append-to-register-1)
       ("t 2" . xah-clear-register-1)
       ("t 3" . xah-copy-to-register-1)
       ("t 4" . xah-paste-from-register-1)
       ("t 7" . xah-append-to-register-1)
       ("t 8" . xah-clear-register-1)
       ;; a
       ;; b c
       ("t d" . mark-defun)
       ("t e" . list-matching-lines)
       ("t f" . move-to-column)
       ("t g" . goto-line)
       ("t h" . repeat-complex-command)
       ("t i" . delete-non-matching-lines)
       ("t j" . copy-to-register)
       ("t k" . insert-register)

       ;; l
       ("t m" . xah-make-backup-and-save)
       ("t n" . goto-char)
       ("t o" . xah-clean-whitespace)
       ("t p" . query-replace-regexp)
       ("t q" . xah-cut-text-in-quote)
       ("t r" . xah-escape-quotes)

       ("t s" . xah-reformat-to-sentence-lines)
       ("t t" . repeat)
       ("t u" . delete-matching-lines)

       ("t w" . xah-next-window-or-frame)
       ("t x" . xah-title-case-region-or-line)
       ("t y" . delete-duplicate-lines)

       ("u" . switch-to-buffer)
       ("v" . universal-argument)

       ;; dangerous map. run program, delete file, etc
       ("w DEL" . xah-delete-current-file-make-backup)
       ("w ." . eval-buffer)
       ("w e" . eval-defun)
       ("w m" . eval-last-sexp)
       ("w p" . eval-expression)
       ("w u" . eval-region)
       ("w q" . save-buffers-kill-terminal)
       ("w w" . delete-frame)
       ("w j" . xah-run-current-file)

       ("x" . xah-toggle-previous-letter-case)
       ("y" . xah-show-kill-ring)

       ;; vc command keys subject to change. need a frequency stat of the commands.

       ("z b" . vc-root-diff)      ; D
       ("z c" . vc-update)         ; git pull, +
       ("z d" . vc-annotate)       ; g
       ("z f" . vc-revert)         ; u
       ("z g" . vc-push)           ; git push, P
       ("z h" . vc-diff)           ; git diff, =
       ("z l" . vc-print-root-log) ; L
       ("z m" . vc-dir)            ; git status, C-x v d
       ("z n" . vc-print-log)      ; git log, l
       ("z r" . vc-merge)          ; m
       ("z t" . vc-register)       ; git add, i
       ("z z" . vc-next-action)    ; v

       ("z 1" . vc-create-tag)            ; s
       ("z 2" . vc-insert-headers)        ; h
       ("z 4" . vc-retrieve-tag)          ; r
       ("z 5" . vc-revision-other-window) ; ~
       ("z 6" . vc-switch-backend)        ; b
       ("z 7" . vc-update-change-log)     ; a

       ;;
       ))

    (xah-fly--define-keys
     xah-fly-command-map
     '(("SPC" . xah-fly-leader-key-map)
       ("'" . xah-reformat-lines)
       ("," . xah-shrink-whitespaces)
       ("-" . delete-other-windows)
       ("." . backward-kill-word)
       ("/" . hippie-expand)
       (";" . xah-comment-dwim)
       ("[" . split-window-below)
       ("\\" . xah-cycle-hyphen-lowline-space)
       ("]" . split-window-right)
       ("`" . other-frame)

       ("1" . xah-backward-punct)
       ("2" . xah-forward-punct)
       ("3" . delete-other-windows)
       ("4" . split-window-below)
       ("5" . delete-char)
       ("6" . xah-select-block)
       ("7" . xah-select-line)
       ("8" . xah-extend-selection)
       ("9" . xah-select-text-in-quote)
       ("0" . xah-pop-local-mark-ring)

       ("a" . execute-extended-command)
       ("b" . isearch-forward)
       ("c" . previous-line)
       ("d" . xah-beginning-of-line-or-block)
       ("e" . xah-smart-delete)
       ("f" . undo)
       ("g" . backward-word)
       ("h" . backward-char)
       ("i" . xah-delete-current-text-block)
       ("j" . xah-copy-line-or-region)
       ("k" . xah-paste-or-paste-previous)
       ;; ("l" . xah-fly-insert-mode-activate-space-before)
       ("l" . xah-insert-space-before)
       ("m" . xah-backward-left-bracket)
       ("n" . forward-char)
       ("o" . open-line)
       ("p" . kill-word)
       ("q" . xah-cut-line-or-region)
       ("r" . forward-word)
       ("s" . xah-end-of-line-or-block)
       ("t" . next-line)
       ("u" . xah-fly-insert-mode-activate)
       ("v" . xah-forward-right-bracket)
       ("w" . xah-next-window-or-frame)
       ("x" . xah-toggle-letter-case)
       ("y" . set-mark-command)
       ("z" . xah-goto-matching-bracket)))

    ;;
    ))

(xah-fly-define-keys)


;; set control meta, etc keys

(defcustom xah-fly-unset-useless-key t
  "If true, unbind many obsolete or useless or redundant
  keybinding. e.g. <help>, <f1>."
  :type 'boolean)

(when xah-fly-unset-useless-key
  (global-set-key (kbd "<help>") nil)
  (global-set-key (kbd "<f1>") nil))

(global-set-key (kbd "<f7>") 'xah-fly-leader-key-map)

(when xah-fly-use-meta-key

  (global-set-key (kbd "M-<home>") nil) ; beginning-of-buffer-other-window
  (global-set-key (kbd "M-<end>") nil) ; end-of-buffer-other-window

  (global-set-key (kbd "M-SPC") #'xah-fly-command-mode-activate)
  (global-set-key (kbd "M-\\") nil) ; delete-horizontal-space
  (global-set-key (kbd "M-!") nil)  ; shell-command
  (global-set-key (kbd "M-$") nil)  ; ispell-word
  (global-set-key (kbd "M-%") nil)  ; query-replace
  (global-set-key (kbd "M-&") nil)  ; async-shell-command
  (global-set-key (kbd "M-'") nil)  ; abbrev-prefix-mark
  (global-set-key (kbd "M-(") nil)  ; insert-parentheses
  (global-set-key (kbd "M-)") nil)  ; move-past-close-and-reindent
  ;; (global-set-key (kbd "M-,") nil) ; xref-pop-marker-stack
  ;; (global-set-key (kbd "M-.") nil) ; xref-find-definitions
  (global-set-key (kbd "M-/") nil) ; dabbrev-expand
  (global-set-key (kbd "M-:") nil) ; eval-expression
  ;; (global-set-key (kbd "M-;") nil) ; comment-dwim
  (global-set-key (kbd "M-<") nil) ; beginning-of-buffer
  (global-set-key (kbd "M-=") nil) ; count-words-region
  (global-set-key (kbd "M->") nil) ; end-of-buffer
  ;; (global-set-key (kbd "M-?") nil) ; xref-find-references
  (global-set-key (kbd "M-@") nil) ; mark-word
  (global-set-key (kbd "M-^") nil) ; delete-indentation
  (global-set-key (kbd "M-`") nil) ; tmm-menubar
  (global-set-key (kbd "M-a") nil) ; backward-sentence
  (global-set-key (kbd "M-b") nil) ; backward-word
  (global-set-key (kbd "M-c") nil) ; capitalize-word
  (global-set-key (kbd "M-d") nil) ;  kill-word
  (global-set-key (kbd "M-e") nil) ; forward-sentence
  (global-set-key (kbd "M-f") nil) ; forward-word
  (global-set-key (kbd "M-g") nil) ; Prefix Command
  (global-set-key (kbd "M-h") nil) ; mark-paragraph
  (global-set-key (kbd "M-i") nil) ; tab-to-tab-stop
  (global-set-key (kbd "M-j") nil) ; default-indent-new-line
  (global-set-key (kbd "M-k") nil) ; kill-sentence
  (global-set-key (kbd "M-l") nil) ; downcase-word
  (global-set-key (kbd "M-m") nil) ; back-to-indentation
  (global-set-key (kbd "M-o") nil) ; facemenu-keymap
  (global-set-key (kbd "M-q") nil) ; fill-paragraph
  (global-set-key (kbd "M-r") nil) ; move-to-window-line-top-bottom
  (global-set-key (kbd "M-s") nil) ; Prefix Command
  (global-set-key (kbd "M-t") nil) ; transpose-words
  (global-set-key (kbd "M-u") nil) ; upcase-word
  (global-set-key (kbd "M-v") nil) ; scroll-down-command
  (global-set-key (kbd "M-w") nil) ; kill-ring-save
  ;; (global-set-key (kbd "M-x") nil) ; execute-extended-command
  ;; (global-set-key (kbd "M-y") nil) ; yank-pop
  (global-set-key (kbd "M-z") nil)   ; zap-to-char
  (global-set-key (kbd "M-{") nil)   ; backward-paragraph
  (global-set-key (kbd "M-|") nil)   ; shell-command-on-region
  (global-set-key (kbd "M-}") nil)   ; forward-paragraph
  (global-set-key (kbd "M-~") nil)   ; not-modified
  (global-set-key (kbd "M-DEL") nil) ; backward-kill-word
  )

(when xah-fly-use-control-key

  ;; 2021-08-07 was
  ;; (xah-fly--define-keys
  ;;  xah-fly-shared-map
  ;;  '(
  ;;    ("C-1" . cmd)
  ;;    ("C-2" . cmd)
  ;;    )
  ;;  :direct)

 ;; define control combo in xah-fly-shared-map may be a problem.
;; by setting them in xah-fly-shared-map, it becomes unchangeable, that is, if a major mode set a key for C-t, it will have no effect.
;; Current solution is just to use global-set-key.
;; The disadvantage is that these changes leak, that is, xah-fly-keys is turned off, these ctrl keys are still changed. Still, this is better, because xah fly keys is not really meant to be turned off temporarily.
;; Removing the tons of default emacs control and meta keys is desirable.
;; because there are hundreds of them, confusing, and mostly useless.

  (global-set-key (kbd "<C-S-prior>") #'xah-previous-emacs-buffer)
  (global-set-key (kbd "<C-S-next>") #'xah-next-emacs-buffer)

  (global-set-key (kbd "<C-tab>") #'xah-next-user-buffer)
  (global-set-key (kbd "<C-S-tab>") #'xah-previous-user-buffer)
  (global-set-key (kbd "<C-S-iso-lefttab>") #'xah-previous-user-buffer)

  (global-set-key (kbd "<C-prior>") #'xah-previous-user-buffer)
  (global-set-key (kbd "<C-next>") #'xah-next-user-buffer)

  ;; (global-set-key (kbd "C-1") nil)
  (global-set-key (kbd "C-2") #'pop-global-mark)
  (global-set-key (kbd "C-3") #'previous-error)
  (global-set-key (kbd "C-4") #'next-error)
  (global-set-key (kbd "C-5") #'xah-previous-emacs-buffer)
  (global-set-key (kbd "C-6") #'xah-next-emacs-buffer)
  (global-set-key (kbd "C-7") #'xah-previous-user-buffer)
  (global-set-key (kbd "C-8") #'xah-next-user-buffer)
  (global-set-key (kbd "C-9") #'scroll-down-command)
  (global-set-key (kbd "C-0") #'scroll-up-command)

  (global-set-key (kbd "C--") #'text-scale-decrease)
  (global-set-key (kbd "C-=") #'text-scale-increase)

  (global-set-key (kbd "C-SPC") #'xah-fly-command-mode-activate)

  (global-set-key (kbd "C-S-n") #'make-frame-command)
  (global-set-key (kbd "C-S-s") #'write-file)
  (global-set-key (kbd "C-S-t") #'xah-open-last-closed)

  ;; (global-set-key (kbd "C-@") nil)

  (global-set-key (kbd "C-a") #'mark-whole-buffer)
  ;; (global-set-key (kbd "C-b") nil)
  ;; (global-set-key (kbd "C-c") nil)
  ;; (global-set-key (kbd "C-d") nil)
  ;; (global-set-key (kbd "C-e") nil)
  ;; (global-set-key (kbd "C-f") nil)
  ;; (global-set-key (kbd "C-g") nil) ; cancel
  ;; (global-set-key (kbd "C-h") nil) ; help
  ;; (global-set-key (kbd "C-i") nil) ; tab
  ;; (global-set-key (kbd "C-j") nil) ; newline
  ;; (global-set-key (kbd "C-k") nil)
  ;; (global-set-key (kbd "C-l") nil)
  ;; (global-set-key (kbd "C-m") nil)
  (global-set-key (kbd "C-n") #'xah-new-empty-buffer)
  (global-set-key (kbd "C-o") #'find-file)
  ;; (global-set-key (kbd "C-p") nil)
  ;; (global-set-key (kbd "C-q") nil)
  ;; (global-set-key (kbd "C-r") nil)
  (global-set-key (kbd "C-s") #'save-buffer)
  (global-set-key (kbd "C-t") #'hippie-expand)
  ;; (global-set-key (kbd "C-u") nil)
  (global-set-key (kbd "C-v") #'yank)
  (global-set-key (kbd "C-w") #'xah-close-current-buffer)
  ;; (global-set-key (kbd "C-x") nil)

  (when (>= emacs-major-version 28)
    (global-set-key (kbd "C-y") #'undo-redo))
  (global-set-key (kbd "C-z") #'undo)
  ;;
  )



(when (< emacs-major-version 28)
  (defalias 'execute-extended-command-for-buffer #'execute-extended-command))


;;;; misc

;; the following have keys in gnu emacs, but i decided not to give them a key, because either they are rarely used (say, 95% of emacs users use them less than once a month ), or there is a more efficient command/workflow with key in xah-fly-keys

;; C-x r w → window-configuration-to-register
;; C-x r f → frameset-to-register

;; C-x C-p   →   mark-page
;; C-x C-l   →   downcase-region
;; C-x C-u   →   upcase-region

;; C-x C-t   →   transpose-lines
;; C-x C-o   →   delete-blank-lines

;; C-x C-r   →   find-file-read-only
;; C-x C-v   →   find-alternate-file

;; C-x =   →   what-cursor-position, use describe-char instead
;; C-x <   →   scroll-left
;; C-x >   →   scroll-right
;; C-x [   →   backward-page
;; C-x ]   →   forward-page
;; C-x ^   →   enlarge-window

;; C-x {   →   shrink-window-horizontally
;; C-x }   →   enlarge-window-horizontally
;; C-x DEL   →   backward-kill-sentence

;; C-x C-z   →   suspend-frame

;; C-x k   →   kill-buffer , use xah-close-current-buffer
;; C-x l   →   count-lines-page
;; C-x m   →   compose-mail


;; undecided yet

;; C-x e   →   kmacro-end-and-call-macro
;; C-x q   →   kbd-macro-query
;; C-x C-k   →   kmacro-keymap

;; C-x C-d   →   list-directory
;; C-x C-n   →   set-goal-column
;; C-x ESC   →   Prefix Command
;; C-x $   →   set-selective-display
;; C-x *   →   calc-dispatch
;; C-x -   →   shrink-window-if-larger-than-buffer
;; C-x .   →   set-fill-prefix

;; C-x 4   →   ctl-x-4-prefix
;; C-x 5   →   ctl-x-5-prefix
;; C-x 6   →   2C-command
;; C-x ;   →   comment-set-column

;; C-x f   →   set-fill-column
;; C-x i   →   insert-file
;; C-x n   →   Prefix Command
;; C-x r   →   Prefix Command

;; C-x C-k C-a   →   kmacro-add-counter
;; C-x C-k C-c   →   kmacro-set-counter
;; C-x C-k C-d   →   kmacro-delete-ring-head
;; C-x C-k C-e   →   kmacro-edit-macro-repeat
;; C-x C-k C-f   →   kmacro-set-format
;; C-x C-k TAB   →   kmacro-insert-counter
;; C-x C-k C-k   →   kmacro-end-or-call-macro-repeat
;; C-x C-k C-l   →   kmacro-call-ring-2nd-repeat
;; C-x C-k RET   →   kmacro-edit-macro
;; C-x C-k C-n   →   kmacro-cycle-ring-next
;; C-x C-k C-p   →   kmacro-cycle-ring-previous
;; C-x C-k C-t   →   kmacro-swap-ring
;; C-x C-k C-v   →   kmacro-view-macro-repeat
;; C-x C-k SPC   →   kmacro-step-edit-macro
;; C-x C-k b   →   kmacro-bind-to-key
;; C-x C-k e   →   edit-kbd-macro
;; C-x C-k l   →   kmacro-edit-lossage
;; C-x C-k n   →   kmacro-name-last-macro
;; C-x C-k q   →   kbd-macro-query



;; C-x 4 C-f   →   find-file-other-window
;; C-x 4 C-o   →   display-buffer
;; C-x 4 .   →   find-tag-other-window
;; C-x 4 0   →   kill-buffer-and-window
;; C-x 4 a   →   add-change-log-entry-other-window
;; C-x 4 b   →   switch-to-buffer-other-window
;; C-x 4 c   →   clone-indirect-buffer-other-window
;; C-x 4 d   →   dired-other-window
;; C-x 4 f   →   find-file-other-window
;; C-x 4 m   →   compose-mail-other-window
;; C-x 4 r   →   find-file-read-only-other-window

;; C-x 6 2   →   2C-two-columns
;; C-x 6 b   →   2C-associate-buffer
;; C-x 6 s   →   2C-split

;; ctl-x-5-map

;; r C-f   →   find-file-other-frame
;; r C-o   →   display-buffer-other-frame
;; r .   →   find-tag-other-frame
;; r 0   →   delete-frame
;; r 1   →   delete-other-frames
;; r 2   →   make-frame-command
;; r b   →   switch-to-buffer-other-frame
;; r d   →   dired-other-frame
;; r f   →   find-file-other-frame
;; r m   →   compose-mail-other-frame
;; r o   →   other-frame
;; r r   →   find-file-read-only-other-frame



(defvar xah-fly-insert-state-p t "non-nil means insertion mode is on.")

(defun xah-fly--update-key-map ()
  (setq xah-fly-key-map (if xah-fly-insert-state-p
                            xah-fly-insert-map
                          xah-fly-command-map)))

(defun xah-fly-keys-set-layout (Layout)
  "Set a keyboard layout.
Argument must be one of the key name in `xah-fly-layouts'
Version: 2021-05-19 2022-09-11 2022-10-22 2022-10-31"
  (interactive "sType a layout: ")
  (let ((xnewlout
         (cond
          ((stringp Layout) Layout)
          ((symbolp Layout) (symbol-name Layout))
          (t (user-error "Layout %s must be a string." Layout))))
        (xoldlout xah-fly-key-current-layout))
    (setq xah-fly-key-current-layout xnewlout)
    (setq xah-fly--key-convert-table
          (cdr (assoc xah-fly-key-current-layout xah-fly-layouts)))
    (when (and (featurep 'xah-fly-keys)
               (not (string-equal xoldlout xnewlout)))
      (xah-fly-define-keys))))

(defun xah-fly-space-key ()
  "Switch to command mode if the char before cursor is a space.
experimental
Version: 2018-05-07"
  (interactive)
  (if (eq (char-before ) 32)
      (xah-fly-command-mode-activate)
    (insert " ")))

(defun xah-fly-command-mode-init ()
  "Set command mode keys.
Version: 2022-07-06"
  (interactive)
  (setq xah-fly-insert-state-p nil)
  (xah-fly--update-key-map)
  (when xah-fly--deactivate-command-mode-func
    (funcall xah-fly--deactivate-command-mode-func))
  (setq xah-fly--deactivate-command-mode-func
        (set-transient-map xah-fly-command-map (lambda () t)))
  (modify-all-frames-parameters (list (cons 'cursor-type 'box)))
  ;; (set-face-background 'cursor "red")
  (setq mode-line-front-space xah-fly-command-mode-indicator)
  (force-mode-line-update))

(defun xah-fly-insert-mode-init (&optional no-indication)
  "Enter insertion mode."
  (interactive)
  (setq xah-fly-insert-state-p t)
  (xah-fly--update-key-map)
  (funcall xah-fly--deactivate-command-mode-func)
  (unless no-indication
    (modify-all-frames-parameters '((cursor-type . bar)))
    ;; (set-face-background 'cursor "black")
    (setq mode-line-front-space xah-fly-insert-mode-indicator))
  (force-mode-line-update))

(defun xah-fly-mode-toggle ()
  "Switch between {insertion, command} modes."
  (interactive)
  (if xah-fly-insert-state-p
      (xah-fly-command-mode-activate)
    (xah-fly-insert-mode-activate)))

(defun xah-fly-save-buffer-if-file ()
  "Save current buffer if it is a file."
  (interactive)
  (when buffer-file-name
    (save-buffer)))

(defun xah-fly-command-mode-activate ()
  "Activate command mode and run `xah-fly-command-mode-activate-hook'
Version: 2017-07-07"
  (interactive)
  (xah-fly-command-mode-init)
  (run-hooks 'xah-fly-command-mode-activate-hook))

(defun xah-fly-command-mode-activate-no-hook ()
  "Activate command mode. Does not run `xah-fly-command-mode-activate-hook'
Version: 2017-07-07"
  (interactive)
  (xah-fly-command-mode-init))

(defun xah-fly-insert-mode-activate ()
  "Activate insertion mode.
Version: 2017-07-07"
  (interactive)
  (xah-fly-insert-mode-init)
  (run-hooks 'xah-fly-insert-mode-activate-hook))

(defun xah-fly-insert-mode-activate-newline ()
  "Activate insertion mode, insert newline below."
  (interactive)
  (xah-fly-insert-mode-activate)
  (open-line 1))

(defun xah-fly-insert-mode-activate-space-before ()
  "Insert a space, then activate insertion mode."
  (interactive)
  (insert " ")
  (xah-fly-insert-mode-activate))

(defun xah-fly-insert-mode-activate-space-after ()
  "Insert a space, then activate insertion mode."
  (interactive)
  (insert " ")
  (xah-fly-insert-mode-activate)
  (left-char))



;;;###autoload
(define-minor-mode xah-fly-keys
  "A modal keybinding set, like vim, but based on ergonomic
  principles, like Dvorak layout.

URL `http://xahlee.info/emacs/misc/xah-fly-keys.html'"
  :global t
  :lighter " ∑flykeys"
  :keymap xah-fly-insert-map
  (delete-selection-mode 1)
  (setq shift-select-mode nil)

  (if xah-fly-keys
      ;; Construction:
      (progn
        (add-hook 'minibuffer-setup-hook 'xah-fly-insert-mode-activate)
        (add-hook 'minibuffer-exit-hook 'xah-fly-command-mode-activate)
        (add-hook 'isearch-mode-end-hook 'xah-fly-command-mode-activate)
        (when (and (keymapp xah-fly-key-map)
                   (not (memq xah-fly-key-map (list xah-fly-command-map
                                                    xah-fly-insert-map))))
          (set-keymap-parent xah-fly-key-map xah-fly-shared-map)
          (setq xah-fly-shared-map xah-fly-key-map))
        (xah-fly-command-mode-activate))
    (progn
      ;; Teardown:
      (remove-hook 'minibuffer-setup-hook 'xah-fly-insert-mode-activate)
      (remove-hook 'minibuffer-exit-hook 'xah-fly-command-mode-activate)
      (remove-hook 'isearch-mode-end-hook 'xah-fly-command-mode-activate)
      (remove-hook 'eshell-mode-hook 'xah-fly-insert-mode-activate)
      (remove-hook 'shell-mode-hook 'xah-fly-insert-mode-activate)
      (xah-fly-insert-mode-init :no-indication)
      (setq mode-line-front-space '(:eval (if (display-graphic-p) " " "-")))

      ;;
      )))

(provide 'xah-fly-keys)



;; Local Variables:
;; byte-compile-docstring-max-column: 999
;; End:

;;; xah-fly-keys.el ends here
