├── i ├── xah_fly_keys_qwerty_layout_2024-06-16.png ├── xah_fly_keys_qwerty_layout_2025-07-31.png ├── xah_fly_keys_qwerty_layout_2025-08-02.png └── xah_fly_keys_qwerty_layout_2025-07-11_084953.png ├── .gitignore ├── README.md └── xah-fly-keys.el /i/xah_fly_keys_qwerty_layout_2024-06-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xahlee/xah-fly-keys/HEAD/i/xah_fly_keys_qwerty_layout_2024-06-16.png -------------------------------------------------------------------------------- /i/xah_fly_keys_qwerty_layout_2025-07-31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xahlee/xah-fly-keys/HEAD/i/xah_fly_keys_qwerty_layout_2025-07-31.png -------------------------------------------------------------------------------- /i/xah_fly_keys_qwerty_layout_2025-08-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xahlee/xah-fly-keys/HEAD/i/xah_fly_keys_qwerty_layout_2025-08-02.png -------------------------------------------------------------------------------- /i/xah_fly_keys_qwerty_layout_2025-07-11_084953.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xahlee/xah-fly-keys/HEAD/i/xah_fly_keys_qwerty_layout_2025-07-11_084953.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.elc 3 | [#]*[#] 4 | xx* 5 | .DS_Store 6 | 7 | # ELPA-generated files 8 | /xah-fly-keys-autoloads.el 9 | /xah-fly-keys-pkg.el 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xah-fly-keys 2 | 3 | A modal keybinding for emacs (like vim), but based on command frequency and ergonomics. 4 | 5 | This is the most efficient editing system in the universe. 6 | 7 | Xah Fly Keys home page at 8 | http://xahlee.info/emacs/misc/xah-fly-keys.html 9 | 10 | ## 2020-04-18 News: Key Engine Rewrite 11 | 12 | Major key engine rewrite by Dan Langlois (https://github.com/DanLanglois) and Will Dey (https://github.com/wi11dey) . 13 | 14 | ## Key Diagram (QWERTY) 15 | 16 | ![xah-fly-keys qwerty layout](i/xah_fly_keys_qwerty_layout_2025-08-02.png) 17 | 18 | ## Manual Install 19 | 20 | put the file xah-fly-keys.el in ~/.emacs.d/lisp/ 21 | create the dir if doesn't exist. 22 | 23 | put the following in your emacs init file: 24 | 25 | ```elisp 26 | 27 | (add-to-list 'load-path "~/.emacs.d/lisp/") 28 | 29 | (require 'xah-fly-keys) 30 | 31 | ;; specify a layout 32 | (xah-fly-keys-set-layout "qwerty") 33 | 34 | ;; other popular values 35 | ;; adnw (German) 36 | ;; azerty 37 | ;; azerty-be 38 | ;; bepo 39 | ;; colemak 40 | ;; colemak-dh 41 | ;; dvorak 42 | ;; neo2 (German) 43 | ;; norman 44 | ;; programer-dvorak 45 | ;; pt-nativo (Brazil) 46 | ;; qwerty-abnt (Brazil) 47 | ;; qwerty-no (Norwegian) 48 | ;; qwerty-se (Swedish) 49 | ;; qwertz 50 | ;; russian 51 | ;; workman 52 | ;; and more 53 | 54 | (xah-fly-keys 1) 55 | ``` 56 | 57 | ## Full Documentation 58 | 59 | http://xahlee.info/emacs/misc/xah-fly-keys.html 60 | 61 | Been working on this since 2013, and since 2007 on ergoemacs-mode. 62 | 63 | Professional coders, send me 100 USD https://paypal.com pay to Xah@XahLee.org 64 | 65 | Thank you. 66 | -------------------------------------------------------------------------------- /xah-fly-keys.el: -------------------------------------------------------------------------------- 1 | ;;; xah-fly-keys.el --- ergonomic modal keybinding minor mode. -*- coding: utf-8; lexical-binding: t; -*- 2 | 3 | ;; Copyright © 2013, 2025 by Xah Lee 4 | 5 | ;; Author: Xah Lee ( http://xahlee.info/ ) 6 | ;; Maintainer: Xah Lee 7 | ;; Version: 28.11.20251212190757 8 | ;; Created: 2013-09-10 9 | ;; Package-Requires: ((emacs "28.3")) 10 | ;; Keywords: convenience, vi, vim, ergoemacs, keybinding 11 | ;; License: GPL v3. 12 | ;; Homepage: http://xahlee.info/emacs/misc/xah-fly-keys.html 13 | 14 | ;; This file is not part of GNU Emacs. 15 | 16 | ;;; Commentary: 17 | 18 | ;; xah-fly-keys is a efficient keybinding for emacs. It is modal like 19 | ;; vi, but key choices are based on statistics of command call 20 | ;; frequency. 21 | 22 | ;;; Usage: 23 | 24 | ;; M-x xah-fly-keys to toggle the mode on/off. 25 | 26 | ;; Important command/insert mode switch keys: 27 | 28 | ;; M-x `xah-fly-command-mode-activate' 29 | ;; or press 30 | ;; or Alt+Space 31 | ;; or Ctrl+Space. 32 | ;; Note: if using emacs 28 or before, escape key only works when in emacs is running in graphical user interface mode. 33 | 34 | ;; M-x `xah-fly-insert-mode-activate' 35 | ;; when in command mode, press qwerty letter key f. 36 | 37 | ;; When in command mode: 38 | 39 | ;; "f" calls `xah-fly-insert-mode-activate'. 40 | 41 | ;; Space is a leader key. For example, "SPC r" calls `query-replace'. 42 | ;; Press "SPC C-h" to see the full list. 43 | 44 | ;; "SPC SPC" also activates insertion mode. 45 | 46 | ;; "SPC RET" calls `execute-extended-command'. 47 | 48 | ;; "a" calls `execute-extended-command'. 49 | 50 | ;; The leader key sequence basically supplant ALL emacs commands that 51 | ;; starts with C-x key. 52 | 53 | ;; When using xah-fly-keys, you don't need to press Control or Meta, 54 | ;; with the following exceptions: 55 | 56 | ;; "C-c" for major mode commands. 57 | ;; "C-g" for cancel. 58 | ;; "C-q" for quoted-insert. 59 | ;; "C-h" for getting a list of keys following a prefix/leader key. 60 | 61 | ;; Leader key 62 | 63 | ;; You NEVER need to press "C-x" 64 | 65 | ;; Any emacs command that has a keybinding starting with C-x, has also 66 | ;; a key sequence binding in xah-fly-keys. For example, 67 | 68 | ;; "C-x b" for `switch-to-buffer' is "SPC f" 69 | ;; "C-x C-f" for `find-file' is "SPC i e" 70 | ;; "C-x n n" for `narrow-to-region' is "SPC l l" 71 | 72 | ;; The first key we call it leader key. In the above examples, the SPC 73 | ;; is the leader key. 74 | 75 | ;; When in command mode, the "SPC" is a leader key. 76 | 77 | ;; if you want the following standard keys with Control 78 | ;; "C-TAB" `xah-next-user-buffer' 79 | ;; "C-S-TAB" `xah-previous-user-buffer' 80 | ;; "C-v" paste 81 | ;; "C-w" close 82 | ;; "C-z" undo 83 | ;; "C-n" new 84 | ;; "C-o" open 85 | ;; "C-s" save 86 | ;; "C-S-s" save as 87 | ;; "C-S-t" open last closed 88 | ;; "C-+" `text-scale-increase' 89 | ;; "C--" `text-scale-decrease' 90 | 91 | ;; add 92 | ;; (setq xah-fly-use-control-key t) 93 | 94 | ;; To disable any change to Control keybinding 95 | ;; add this to emacs init 96 | ;; (setq xah-fly-use-control-key nil) 97 | ;; before loading xah-fly-keys 98 | 99 | ;; To disable any change to meta keybinding 100 | ;; add this to emacs init 101 | ;; (setq xah-fly-use-meta-key nil) 102 | ;; before loading xah-fly-keys 103 | 104 | ;; If you have a bug, post on github. 105 | 106 | ;; For detail about design and other info, see home page at 107 | ;; http://xahlee.info/emacs/misc/xah-fly-keys.html 108 | 109 | ;; If you like this project, paypal me $30 to Xah@XahLee.org 110 | 111 | ;;; Installation: 112 | ;; here's how to manual install 113 | ;; 114 | ;; put the file xah-fly-keys.el in ~/.emacs.d/lisp/ 115 | ;; create the dir if doesn't exist. 116 | ;; 117 | ;; put the following in your emacs init file: 118 | ;; (add-to-list 'load-path "~/.emacs.d/lisp/") 119 | ;; (require 'xah-fly-keys) 120 | ;; (xah-fly-keys-set-layout "qwerty") ; optional 121 | ;; (xah-fly-keys 1) 122 | ;; 123 | ;; possible layout values: 124 | 125 | ;; adnw (German) 126 | ;; azerty 127 | ;; azerty-be 128 | ;; bepo (French) 129 | ;; colemak 130 | ;; colemak-dh 131 | ;; dvorak 132 | ;; engrammer 133 | ;; halmak 134 | ;; koy (German) 135 | ;; minimak 136 | ;; neo2 (German) 137 | ;; norman 138 | ;; programer-dvorak 139 | ;; pt-nativo (Brazil) 140 | ;; qfmlwy 141 | ;; qgmlwb 142 | ;; qwerty 143 | ;; qwerty-abnt (Brazil) 144 | ;; qwerty-no (Norwegian) 145 | ;; qwerty-se (Swedish) 146 | ;; qwertz 147 | ;; qwpr 148 | ;; russian 149 | ;; workman 150 | 151 | ;; supported layouts are stored in the variable xah-fly-layout-diagrams 152 | 153 | ;; s------------------------------ 154 | ;;; Code: 155 | 156 | (require 'dired) 157 | (require 'dired-x) 158 | (require 'seq) 159 | 160 | ;; s------------------------------ 161 | 162 | (defgroup xah-fly-keys nil 163 | "Ergonomic modal keybinding minor mode." 164 | :group 'keyboard) 165 | 166 | (defvar xah-fly-command-mode-activate-hook nil "Hook for `xah-fly-command-mode-activate'") 167 | (defvar xah-fly-insert-mode-activate-hook nil "Hook for `xah-fly-insert-mode-activate'") 168 | 169 | (defcustom xah-fly-use-control-key t 170 | "If true, change many emacs keybinding involving control key. 171 | Keys changed: 172 | Standard shortcut for open, close, copy, paste etc. 173 | Remove many redundant emacs default keys 174 | Must be set before loading xah-fly-keys." 175 | :type 'boolean) 176 | 177 | (defcustom xah-fly-remove-control-x-keys nil 178 | "If true, remove the whole C-x prefix keybinding. 179 | Must be set before loading xah-fly-keys." 180 | :type 'boolean) 181 | 182 | (defcustom xah-fly-use-meta-key nil 183 | "If true, change some emacs keybinding involving meta key. 184 | Remove many redundant emacs default keys. 185 | Must be set before loading xah-fly-keys." 186 | :type 'boolean) 187 | 188 | (defcustom xah-fly-use-isearch-arrows t 189 | "If true, set keys in variable `isearch-mode-map' to use 190 | left/right arrow for backward/forward occurrences. 191 | up/down for search history. 192 | and C-v is paste. 193 | Must be set before loading xah-fly-keys." 194 | :type 'boolean) 195 | 196 | (defcustom xah-fly-command-mode-hl-line t 197 | "If true, highlight current line when in command mode. 198 | Must be set before loading xah-fly-keys." 199 | :type 'boolean) 200 | 201 | (defcustom xah-fly-command-mode-cursor-color "red" 202 | "Cursor color when in command mode. 203 | Value should be a string of color name. See `list-colors-display'. 204 | Default is red. 205 | If set to nil, use the color of current theme. 206 | Must be set before loading xah-fly-keys." 207 | :type '(string)) 208 | 209 | (defcustom xah-fly-insert-mode-cursor-color "gray" 210 | "Cursor color when in insert mode. 211 | Value should be a string of color name. See `list-colors-display'. 212 | Default is gray. 213 | If set to nil, use the color of current theme. 214 | Must be set before loading xah-fly-keys." 215 | :type '(string)) 216 | 217 | ;; s------------------------------ 218 | ;; cursor movement 219 | 220 | (defun xah-pop-local-mark-ring () 221 | "Move cursor to last mark position of current buffer. 222 | Repeat call cycles all positions in `mark-ring'. 223 | 224 | After this command is called, press variable `xah-repeat-key' to repeat it. 225 | 226 | URL `http://xahlee.info/emacs/emacs/emacs_cycle_local_mark_ring.html' 227 | Created: 2016-04-04 228 | Version: 2025-07-28" 229 | (interactive) 230 | (set-mark-command t) 231 | (set-transient-map 232 | (let ((xkmap (make-sparse-keymap))) 233 | (define-key xkmap (kbd (if (boundp 'xah-repeat-key) xah-repeat-key ".")) real-this-command) 234 | xkmap))) 235 | 236 | (defun xah-beginning-of-line-or-block () 237 | "Move cursor to beginning of indent or line, end of previous block, in that order. 238 | 239 | If `visual-line-mode' is on, beginning of line means visual line. 240 | 241 | URL `http://xahlee.info/emacs/emacs/emacs_move_by_paragraph.html' 242 | Created: 2018-06-04 243 | Version: 2024-10-30" 244 | (interactive) 245 | (let ((xp (point))) 246 | (if (or (eq (point) (line-beginning-position)) 247 | (eq last-command this-command)) 248 | (when (re-search-backward "\n[\t\n ]*\n+" nil 1) 249 | (skip-chars-backward "\n\t ") 250 | ;; (forward-char) 251 | ) 252 | (if visual-line-mode 253 | (beginning-of-visual-line) 254 | (if (eq major-mode 'eshell-mode) 255 | (beginning-of-line) 256 | (back-to-indentation) 257 | (when (eq xp (point)) 258 | (beginning-of-line))))))) 259 | 260 | (defun xah-end-of-line-or-block () 261 | "Move cursor to end of line or next block. 262 | 263 | • When called first time, move cursor to end of line. 264 | • When called again, move cursor forward by jumping over any sequence of whitespaces containing 2 blank lines. 265 | • if `visual-line-mode' is on, end of line means visual line. 266 | 267 | URL `http://xahlee.info/emacs/emacs/emacs_move_by_paragraph.html' 268 | Created: 2018-06-04 269 | Version: 2024-10-30" 270 | (interactive) 271 | (if (or (eq (point) (line-end-position)) 272 | (eq last-command this-command)) 273 | (re-search-forward "\n[\t\n ]*\n+" nil 1) 274 | (if visual-line-mode 275 | (end-of-visual-line) 276 | (end-of-line)))) 277 | 278 | (defun xah-page-up () 279 | "Call `scroll-down-command'. (page up key.) 280 | Created: 2024-10-09 281 | Version: 2024-10-09" 282 | (interactive) 283 | (progn 284 | (scroll-down-command) 285 | (set-transient-map 286 | (let ((xkmap (make-sparse-keymap))) 287 | (define-key xkmap (kbd "") #'xah-page-up) 288 | (define-key xkmap (kbd "") #'xah-page-down) 289 | xkmap)))) 290 | 291 | (defun xah-page-down () 292 | "Call `scroll-up-command'. (page down key.) 293 | Created: 2024-10-09 294 | Version: 2024-10-09" 295 | (interactive) 296 | (progn 297 | (scroll-up-command) 298 | (set-transient-map 299 | (let ((xkmap (make-sparse-keymap))) 300 | (define-key xkmap (kbd "") #'xah-page-up) 301 | (define-key xkmap (kbd "") #'xah-page-down) 302 | xkmap)))) 303 | 304 | (defun xah-goto-char (&optional zposition) 305 | "Move cursor to a position in current buffer or next buffer, at a number value under cursor. 306 | 307 | If `universal-argument' is called first, goto the specified position. 308 | If `universal-argument' value is 1, ask for a position. 309 | Else use the number under cursor. 310 | If no number is under cursor, ask for a position. 311 | 312 | If there is more than one pane (aka emacs window), goto position in that pane. 313 | 314 | URL `http://xahlee.info/emacs/emacs/emacs_goto_line_other_buffer.html' 315 | Created: 2025-04-10 316 | Version: 2025-11-13" 317 | (interactive 318 | (if current-prefix-arg 319 | (if (eq 1 (prefix-numeric-value current-prefix-arg)) 320 | (list (read-number "goto position:")) 321 | (list (prefix-numeric-value current-prefix-arg))) 322 | nil)) 323 | (let ((xpos 324 | (if zposition 325 | zposition 326 | (let (xbeg xend) 327 | (skip-chars-backward "0-9") 328 | (setq xbeg (point)) 329 | (skip-chars-forward "0-9") 330 | (setq xend (point)) 331 | (if (eq xbeg xend) 332 | (progn (read-number "No number under cursor. goto position:")) 333 | (string-to-number (buffer-substring-no-properties xbeg xend))))))) 334 | (if (one-window-p) 335 | nil 336 | (other-window 1)) 337 | (goto-char xpos))) 338 | 339 | (defun xah-goto-line (&optional zline-num) 340 | "Move cursor to a line in current buffer or next buffer, at a number value under cursor. 341 | 342 | If `universal-argument' is called first, goto the specified line number. 343 | If `universal-argument' value is 1, ask for a line number. 344 | Else use the number under cursor. 345 | If no number is under cursor, ask for a line number. 346 | 347 | If there is more than one pane (aka emacs window), goto the line number in that pane. 348 | 349 | URL `http://xahlee.info/emacs/emacs/emacs_goto_line_other_buffer.html' 350 | Created: 2025-04-10 351 | Version: 2025-11-13" 352 | (interactive 353 | (if current-prefix-arg 354 | (if (eq 1 (prefix-numeric-value current-prefix-arg)) 355 | (list (read-number "goto line number:")) 356 | (list (prefix-numeric-value current-prefix-arg))) 357 | nil)) 358 | (let ((xline-num 359 | (if zline-num 360 | zline-num 361 | (let (xbeg xend) 362 | (skip-chars-backward "0-9") 363 | (setq xbeg (point)) 364 | (skip-chars-forward "0-9") 365 | (setq xend (point)) 366 | (if (eq xbeg xend) 367 | (progn (read-number "No number under cursor. goto line number:")) 368 | (string-to-number (buffer-substring-no-properties xbeg xend))))))) 369 | (if (one-window-p) 370 | nil 371 | (other-window 1)) 372 | (goto-char (point-min)) 373 | (forward-line xline-num))) 374 | 375 | (defvar xah-brackets '( "“”" "()" "[]" "{}" "<>" "<>" "()" "[]" "{}" "⦅⦆" "〚〛" "⦃⦄" "‹›" "«»" "「」" "〈〉" "《》" "【】" "〔〕" "⦗⦘" "『』" "〖〗" "〘〙" "「」" "⟦⟧" "⟨⟩" "⟪⟫" "⟮⟯" "⟬⟭" "⌈⌉" "⌊⌋" "⦇⦈" "⦉⦊" "❛❜" "❝❞" "❨❩" "❪❫" "❴❵" "❬❭" "❮❯" "❰❱" "❲❳" "〈〉" "⦑⦒" "⧼⧽" "﹙﹚" "﹛﹜" "﹝﹞" "⁽⁾" "₍₎" "⦋⦌" "⦍⦎" "⦏⦐" "⁅⁆" "⸢⸣" "⸤⸥" "⟅⟆" "⦓⦔" "⦕⦖" "⸦⸧" "⸨⸩" "⦅⦆") 376 | "A list of strings, each element is a string of 2 chars, the left bracket and a matching right bracket. 377 | Used by `xah-select-text-in-quote' and others.") 378 | 379 | (defconst xah-left-brackets 380 | (mapcar (lambda (x) (substring x 0 1)) xah-brackets) 381 | "List of left bracket chars. Each element is a string.") 382 | 383 | (defconst xah-right-brackets 384 | (mapcar (lambda (x) (substring x 1 2)) xah-brackets) 385 | "List of right bracket chars. Each element is a string.") 386 | 387 | (defun xah-backward-left-bracket () 388 | "Move cursor to the previous occurrence of left bracket. 389 | The list of brackets to jump to is defined by `xah-left-brackets'. 390 | 391 | URL `http://xahlee.info/emacs/emacs/emacs_navigating_keys_for_brackets.html' 392 | Version: 2015-10-01" 393 | (interactive) 394 | (re-search-backward (regexp-opt xah-left-brackets) nil t)) 395 | 396 | (defun xah-forward-right-bracket () 397 | "Move cursor to the next occurrence of right bracket. 398 | The list of brackets to jump to is defined by `xah-right-brackets'. 399 | 400 | URL `http://xahlee.info/emacs/emacs/emacs_navigating_keys_for_brackets.html' 401 | Version: 2015-10-01" 402 | (interactive) 403 | (re-search-forward (regexp-opt xah-right-brackets) nil t)) 404 | 405 | (defun xah-goto-matching-bracket () 406 | "Move cursor to the matching bracket. 407 | If cursor is not on a bracket, call `backward-up-list'. 408 | The list of brackets to jump to is defined by `xah-left-brackets' and `xah-right-brackets'. 409 | 410 | URL `http://xahlee.info/emacs/emacs/emacs_navigating_keys_for_brackets.html' 411 | Created: 2016-11-22 412 | Version: 2025-11-18" 413 | (interactive) 414 | (if (nth 3 (syntax-ppss)) 415 | (backward-up-list 1 t t) 416 | (cond 417 | ((eq (char-after) ?\") (forward-sexp)) 418 | ((eq (char-before) ?\") (backward-sexp)) 419 | ((looking-at (regexp-opt xah-left-brackets)) 420 | (forward-sexp)) 421 | ((if (eq (point-min) (point)) 422 | nil 423 | (prog2 424 | (backward-char) 425 | (looking-at (regexp-opt xah-right-brackets)) 426 | (forward-char))) 427 | (backward-sexp) 428 | (while (looking-at "\\s'") (forward-char))) 429 | (t (backward-up-list 1 t t))))) 430 | 431 | (defvar xah-punctuation-regex nil "A regex string for the purpose of moving cursor to a punctuation.") 432 | (setq xah-punctuation-regex "\"") 433 | 434 | (defun xah-forward-punct () 435 | "Move cursor to the next occurrence of punctuation. 436 | Punctuations is defined by `xah-punctuation-regex' 437 | 438 | URL `http://xahlee.info/emacs/emacs/emacs_jump_to_punctuations.html' 439 | Created: 2017-06-26 440 | Version: 2025-07-18" 441 | (interactive) 442 | (set-transient-map 443 | (let ((xkmap (make-sparse-keymap))) 444 | (define-key xkmap (kbd "") #'xah-backward-punct) 445 | (define-key xkmap (kbd "") #'xah-forward-punct) 446 | xkmap)) 447 | (re-search-forward xah-punctuation-regex nil t)) 448 | 449 | (defun xah-backward-punct () 450 | "Move cursor to the previous occurrence of punctuation. 451 | See `xah-forward-punct' 452 | 453 | URL `http://xahlee.info/emacs/emacs/emacs_jump_to_punctuations.html' 454 | Created: 2017-06-26 455 | Version: 2025-07-18" 456 | (interactive) 457 | (set-transient-map 458 | (let ((xkmap (make-sparse-keymap))) 459 | (define-key xkmap (kbd "") #'xah-backward-punct) 460 | (define-key xkmap (kbd "") #'xah-forward-punct) 461 | xkmap)) 462 | (re-search-backward xah-punctuation-regex nil t)) 463 | 464 | (defun xah-sort-lines () 465 | "Like `sort-lines' but if no region, do the current block. 466 | Created: 2022-01-22 467 | Version: 2025-03-25" 468 | (interactive) 469 | (let (xbeg xend) 470 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 471 | (sort-lines current-prefix-arg xbeg xend))) 472 | 473 | (defun xah-narrow-to-region () 474 | "Same as `narrow-to-region', but if no selection, narrow to the current block. 475 | Created: 2022-01-22 476 | Version: 2025-03-25" 477 | (interactive) 478 | (let (xbeg xend) 479 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 480 | (narrow-to-region xbeg xend))) 481 | 482 | ;; s------------------------------ 483 | ;; editing commands 484 | 485 | (defun xah-copy-line-or-region () 486 | "Copy current line or selection. 487 | 488 | Copy current line. When called repeatedly, append copy subsequent lines. 489 | Except: 490 | 491 | If `universal-argument' is called first, copy whole buffer (respects `narrow-to-region'). 492 | If `rectangle-mark-mode' is on, copy the rectangle. 493 | If `region-active-p', copy the region. 494 | 495 | URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_current_line.html' 496 | Created: 2010-05-21 497 | Version: 2024-06-19" 498 | (interactive) 499 | (cond 500 | (current-prefix-arg (copy-region-as-kill (point-min) (point-max))) 501 | ((and (boundp 'rectangle-mark-mode) rectangle-mark-mode) 502 | (copy-region-as-kill (region-beginning) (region-end) t)) 503 | ((region-active-p) (copy-region-as-kill (region-beginning) (region-end))) 504 | ((eq last-command this-command) 505 | (if (eobp) 506 | nil 507 | (progn 508 | (kill-append "\n" nil) 509 | (kill-append (buffer-substring (line-beginning-position) (line-end-position)) nil) 510 | (end-of-line) 511 | (forward-char)))) 512 | ((eobp) 513 | (if (eq (char-before) 10) 514 | (progn) 515 | (progn 516 | (copy-region-as-kill (line-beginning-position) (line-end-position)) 517 | (end-of-line)))) 518 | (t 519 | (copy-region-as-kill (line-beginning-position) (line-end-position)) 520 | (end-of-line) 521 | (forward-char)))) 522 | 523 | (defun xah-cut-line-or-region () 524 | "Cut current line or selection. 525 | If `universal-argument' is called first, cut whole buffer (respects `narrow-to-region'). 526 | 527 | URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_current_line.html' 528 | Created: 2010-05-21 529 | Version: 2015-06-10" 530 | (interactive) 531 | (if current-prefix-arg 532 | (progn ; not using kill-region because we don't want to include previous kill 533 | (kill-new (buffer-string)) 534 | (delete-region (point-min) (point-max))) 535 | (progn (if (region-active-p) 536 | (kill-region (region-beginning) (region-end) t) 537 | (kill-region (line-beginning-position) (line-beginning-position 2)))))) 538 | 539 | (defun xah-copy-all-or-region () 540 | "Copy buffer or selection content to `kill-ring'. 541 | Respects `narrow-to-region'. 542 | 543 | URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_all_or_region.html' 544 | Created: 2015-08-22 545 | Version: 2015-08-22" 546 | (interactive) 547 | (if (region-active-p) 548 | (progn 549 | (kill-new (buffer-substring (region-beginning) (region-end))) 550 | (message "Text selection copied.")) 551 | (progn 552 | (kill-new (buffer-string)) 553 | (message "Buffer content copied.")))) 554 | 555 | (defun xah-cut-all-or-region () 556 | "Cut buffer or selection content to `kill-ring'. 557 | Respects `narrow-to-region'. 558 | 559 | URL `http://xahlee.info/emacs/emacs/emacs_copy_cut_all_or_region.html' 560 | Created: 2015-08-22 561 | Version: 2025-08-06" 562 | (interactive) 563 | (if (region-active-p) 564 | (progn 565 | (kill-new (buffer-substring (region-beginning) (region-end))) 566 | (delete-region (region-beginning) (region-end))) 567 | (progn 568 | (kill-new (buffer-string)) 569 | (delete-region (point-min) (point-max)) 570 | (message "buffer text cut")))) 571 | 572 | (defun xah-paste-or-paste-previous () 573 | "Paste. When called repeatedly, paste previous. 574 | This command calls `yank', and if repeated, call `yank-pop'. 575 | 576 | If `universal-argument' is called first with a number arg, paste that many times. 577 | 578 | URL `http://xahlee.info/emacs/emacs/emacs_paste_or_paste_previous.html' 579 | Created: 2017-07-25 580 | Version: 2020-09-08" 581 | (interactive) 582 | (progn 583 | (when (and delete-selection-mode (region-active-p)) 584 | (delete-region (region-beginning) (region-end))) 585 | (if current-prefix-arg 586 | (progn 587 | (dotimes (_ (prefix-numeric-value current-prefix-arg)) 588 | (yank))) 589 | (if (eq real-last-command this-command) 590 | (yank-pop 1) 591 | (yank))))) 592 | 593 | (defun xah-show-kill-ring () 594 | "Insert all `kill-ring' content in a new buffer named *copy history*. 595 | 596 | URL `http://xahlee.info/emacs/emacs/emacs_show_kill_ring.html' 597 | Created: 2019-12-02 598 | Version: 2024-05-07" 599 | (interactive) 600 | (let ((xbuf (generate-new-buffer "*copy history*")) 601 | (inhibit-read-only t)) 602 | (progn 603 | (switch-to-buffer xbuf) 604 | (funcall 'fundamental-mode) 605 | (mapc 606 | (lambda (x) 607 | (insert x "\n\nsss97707------------------------------------------------\n\n" )) 608 | kill-ring)) 609 | (goto-char (point-min)))) 610 | 611 | (defun xah-move-block-up () 612 | "Swap the current text block with the previous. 613 | After this command is called, press or to move. Any other key to exit. 614 | Version: 2022-03-04" 615 | (interactive) 616 | (let ((xp0 (point)) 617 | xc1 ; current block begin 618 | xc2 ; current Block End 619 | xbeg ; prev Block Begin 620 | xend ; prev Block end 621 | ) 622 | (if (re-search-forward "\n[ \t]*\n+" nil 1) 623 | (setq xc2 (match-beginning 0)) 624 | (setq xc2 (point))) 625 | (goto-char xp0) 626 | (if (re-search-backward "\n[ \t]*\n+" nil 1) 627 | (progn 628 | (skip-chars-backward "\n \t") 629 | (setq xend (point)) 630 | (skip-chars-forward "\n \t") 631 | (setq xc1 (point))) 632 | (error "No previous block.")) 633 | (goto-char xend) 634 | (if (re-search-backward "\n[ \t]*\n+" nil 1) 635 | (progn 636 | (setq xbeg (match-end 0))) 637 | (setq xbeg (point))) 638 | (transpose-regions xbeg xend xc1 xc2) 639 | (goto-char xbeg) 640 | (set-transient-map 641 | (let ((xkmap (make-sparse-keymap))) 642 | (define-key xkmap (kbd "") #'xah-move-block-up) 643 | (define-key xkmap (kbd "") #'xah-move-block-down) 644 | xkmap)))) 645 | 646 | (defun xah-move-block-down () 647 | "Swap the current text block with the next. 648 | After this command is called, press or to move. Any other key to exit. 649 | Version: 2022-03-04" 650 | (interactive) 651 | (let ((xp0 (point)) 652 | xc1 ; current block begin 653 | xc2 ; current Block End 654 | xn1 ; next Block Begin 655 | xn2 ; next Block end 656 | ) 657 | (if (eq (point-min) (point)) 658 | (setq xc1 (point)) 659 | (if (re-search-backward "\n\n+" nil 1) 660 | (progn 661 | (setq xc1 (match-end 0))) 662 | (setq xc1 (point)))) 663 | (goto-char xp0) 664 | (if (re-search-forward "\n[ \t]*\n+" nil 1) 665 | (progn 666 | (setq xc2 (match-beginning 0)) 667 | (setq xn1 (match-end 0))) 668 | (error "No next block.")) 669 | (if (re-search-forward "\n[ \t]*\n+" nil 1) 670 | (progn 671 | (setq xn2 (match-beginning 0))) 672 | (setq xn2 (point))) 673 | (transpose-regions xc1 xc2 xn1 xn2) 674 | (goto-char xn2)) 675 | (set-transient-map 676 | (let ((xkmap (make-sparse-keymap))) 677 | (define-key xkmap (kbd "") #'xah-move-block-up) 678 | (define-key xkmap (kbd "") #'xah-move-block-down) 679 | xkmap))) 680 | 681 | (defun xah-shrink-whitespaces () 682 | "Remove whitespaces around cursor . 683 | 684 | Shrink neighboring spaces, then newlines, then spaces again, leaving one space or newline at each step, till no more white space. 685 | 686 | URL `http://xahlee.info/emacs/emacs/emacs_shrink_whitespace.html' 687 | Created: 2014-10-21 688 | Version: 2023-07-12" 689 | (interactive) 690 | (let ((xeol-count 0) 691 | (xp0 (point)) 692 | xbeg ; whitespace begin 693 | xend ; whitespace end 694 | (xcharBefore (char-before)) 695 | (xcharAfter (char-after)) 696 | xspace-neighbor-p) 697 | (setq xspace-neighbor-p (or (eq xcharBefore 32) (eq xcharBefore 9) (eq xcharAfter 32) (eq xcharAfter 9))) 698 | (skip-chars-backward " \n\t ") 699 | (setq xbeg (point)) 700 | (goto-char xp0) 701 | (skip-chars-forward " \n\t ") 702 | (setq xend (point)) 703 | (goto-char xbeg) 704 | (while (search-forward "\n" xend t) 705 | (setq xeol-count (1+ xeol-count))) 706 | (goto-char xp0) 707 | (cond 708 | ((eq xeol-count 0) 709 | (if (> (- xend xbeg) 1) 710 | (progn 711 | (delete-horizontal-space) (insert " ")) 712 | (progn (delete-horizontal-space)))) 713 | ((eq xeol-count 1) 714 | (if xspace-neighbor-p 715 | (delete-horizontal-space) 716 | (progn (delete-space--internal "\n" nil) (insert " ")))) 717 | ((eq xeol-count 2) 718 | (if xspace-neighbor-p 719 | (delete-horizontal-space) 720 | (progn 721 | (delete-space--internal "\n" nil) 722 | (insert "\n")))) 723 | ((> xeol-count 2) 724 | (if xspace-neighbor-p 725 | (delete-horizontal-space) 726 | (progn 727 | (goto-char xend) 728 | (search-backward "\n") 729 | (delete-region xbeg (point)) 730 | (insert "\n")))) 731 | (t (progn 732 | (message "nothing done. logic error 40873. shouldn't reach here")))))) 733 | 734 | (defun xah-delete-string-backward (&optional DeleteJustQuote) 735 | "Delete string to the left of cursor. 736 | 737 | Cursor must be on the right of a string delimiter. 738 | e.g. \"▮some\" or \"some\"▮ 739 | Else, do nothing. 740 | 741 | String delimiter is determined by current syntax table. (see `describe-syntax') 742 | 743 | If DeleteJustQuote is true, delete only the quotation marks. 744 | 745 | Created: 2023-11-12 746 | Version: 2024-06-06" 747 | (when (prog2 (backward-char) (looking-at "\\s\"") (forward-char)) 748 | (let ((xp0 (point)) xbeg xend) 749 | ;; xbeg xend are the begin and end pos of the string 750 | (if (nth 3 (syntax-ppss)) 751 | (setq xbeg (1- xp0) 752 | xend 753 | (progn 754 | (backward-char) 755 | (forward-sexp) 756 | (point))) 757 | (setq xend (point) 758 | xbeg 759 | (progn (forward-sexp -1) (point)))) 760 | (if DeleteJustQuote 761 | (progn (goto-char xend) 762 | (delete-char -1) 763 | (goto-char xbeg) 764 | (delete-char 1)) 765 | (if (eq real-this-command real-last-command) 766 | (kill-append (delete-and-extract-region xbeg xend) t) 767 | (kill-region xbeg xend)))))) 768 | 769 | (defvar xah-smart-delete-dispatch 770 | '((xah-wolfram-mode . xah-wolfram-smart-delete-backward) 771 | (xah-html-mode . xah-html-smart-delete-backward)) 772 | "Used by `xah-smart-delete'. 773 | This makes that function behavior dependent on current major-mode. 774 | Value is Alist of pairs, each is of the form 775 | (‹major-mode-name› . ‹function-name›) 776 | If ‹major-mode-name› match current var `major-mode', the paired function is called. 777 | If no major mode matches, `xah-smart-delete' default behavior is used. 778 | Version: 2024-06-05") 779 | 780 | (defun xah-smart-delete (&optional BracketOnly SkipDispatch) 781 | "Smart backward delete. 782 | Typically, delete to the left 1 char or entire bracketed text. 783 | Behavior depends on what's left char, and current `major-mode'. 784 | 785 | If `xah-smart-delete-dispatch' match, call the matched function instead. 786 | If region active, delete region. 787 | If cursor left is space tab newline, delete them. 788 | If cursor left is bracket, delete the whole bracket block. 789 | If cursor left is string quote, delete the string. 790 | Else just delete one char to the left. 791 | 792 | If `universal-argument' is called first, do not delete bracket's innertext. 793 | 794 | In elisp code, arg BracketOnly if true, do not delete innertext. SkipDispatch if true, skip checking `xah-smart-delete-dispatch'. 795 | 796 | Created: 2023-07-22 797 | Version: 2025-07-30" 798 | (interactive (list current-prefix-arg nil)) 799 | (let (xfun) 800 | (cond 801 | ((and (not SkipDispatch) (setq xfun (assq major-mode xah-smart-delete-dispatch))) 802 | (message "calling cdr of %s" xfun) 803 | (funcall (cdr xfun))) 804 | ((region-active-p) 805 | (kill-region (region-beginning) (region-end))) 806 | ((or 807 | ;; 32 is space, 9 is tab, 10 is newline 808 | (eq (char-before) 32) 809 | (eq (char-before) 10) 810 | (eq (char-before) 9)) 811 | (if (minibufferp (current-buffer)) 812 | (while (or (eq (char-before) 32) (eq (char-before) 10) (eq (char-before) 9)) 813 | (delete-char -1)) 814 | (let ((xp0 (point)) xbeg xend) 815 | (skip-chars-backward " \t\n") 816 | (setq xbeg (point) xend xp0) 817 | (if (eq real-this-command real-last-command) 818 | (kill-append (delete-and-extract-region xbeg xend) t) 819 | (kill-region xbeg xend))))) 820 | ((prog2 (backward-char) (looking-at "\\s)") (forward-char)) 821 | ;; (message "cursor left is closing bracket") 822 | (cond 823 | ;; unmatched bracket, just delete it 824 | ((not (condition-case nil (scan-sexps (point) -1) (scan-error nil))) 825 | (warn "There was unmatched bracket: no paired opening bracket on left of cursor") 826 | (delete-char -1)) 827 | ;; delete just the brackets 828 | (BracketOnly 829 | (let ((xp0 (point)) xbeg) 830 | (forward-sexp -1) 831 | (while (looking-at "\\s'") (forward-char)) 832 | (setq xbeg (point)) 833 | (goto-char xp0) 834 | (delete-char -1) 835 | (goto-char xbeg) 836 | (delete-char 1) 837 | (goto-char (- xp0 2)))) 838 | ;; delete the bracket block 839 | (t 840 | (let ((xp0 (point)) xbeg xend) 841 | (forward-sexp -1) 842 | (while (looking-at "\\s'") (forward-char)) 843 | (setq xbeg (point) xend xp0) 844 | (if (eq real-this-command real-last-command) 845 | (kill-append (delete-and-extract-region xbeg xend) t) 846 | (kill-region xbeg xend)))))) 847 | ((prog2 (backward-char) (looking-at "\\s(") (forward-char)) 848 | ;; (message "cursor left is opening bracket") 849 | (cond 850 | ;; unmatched bracket, just delete it 851 | ((save-excursion 852 | (backward-char) 853 | (not (condition-case nil (scan-sexps (point) 1) (scan-error nil)))) 854 | (warn "There was unmatched bracket: no paired closing bracket on right of cursor") 855 | (delete-char -1)) 856 | ;; delete just the brackets 857 | (BracketOnly 858 | (let (xbeg) 859 | (backward-char) 860 | (setq xbeg (point)) 861 | (forward-sexp 1) 862 | (delete-char -1) 863 | (goto-char xbeg) 864 | (delete-char 1))) 865 | ;; delete the bracket block 866 | (t 867 | (let (xbeg xend) 868 | (backward-char) 869 | (setq xbeg (point)) 870 | (forward-sexp 1) 871 | (setq xend (point)) 872 | (if (eq real-this-command real-last-command) 873 | (kill-append (delete-and-extract-region xbeg xend) t) 874 | (kill-region xbeg xend)))))) 875 | ((prog2 (backward-char) (looking-at "\\s\"") (forward-char)) 876 | (message "calling xah-delete-string-backward") 877 | (xah-delete-string-backward BracketOnly)) 878 | (t (delete-char -1))))) 879 | 880 | (defun xah-change-bracket-pairs (FromChars ToChars) 881 | "Change bracket pairs to another type or none. 882 | For example, change all parenthesis () to square brackets []. 883 | Works on current block or selection. 884 | 885 | In lisp code, FromChars is a string with at least 2 spaces. 886 | e.g. 887 | paren ( ) 888 | french angle ‹ › 889 | double bracket [[ ]] 890 | etc. 891 | It is split by space, and last 2 items are taken as left and right brackets. 892 | 893 | ToChars is similar, with a special value of 894 | none 895 | followed by 2 spaces. 896 | ,it means replace by empty string. 897 | 898 | URL `http://xahlee.info/emacs/emacs/elisp_change_brackets.html' 899 | Created: 2020-11-01 900 | Version: 2025-10-13" 901 | (interactive 902 | (let ((xbrackets 903 | '( 904 | "square [ ]" 905 | "brace { }" 906 | "paren ( )" 907 | "less greater than < >" 908 | "QUOTATION MARK \" \"" 909 | "APOSTROPHE ' '" 910 | "emacs ` '" 911 | "GRAVE ACCENT ` `" 912 | "double square [[ ]]" 913 | "tilde ~ ~" 914 | "equal = =" 915 | "double curly quote “ ”" 916 | "single curly quote ‘ ’" 917 | "french angle quote ‹ ›" 918 | "french double angle « »" 919 | "corner 「 」" 920 | "white corner 『 』" 921 | "lenticular 【 】" 922 | "white lenticular 〖 〗" 923 | "title angle 〈 〉" 924 | "double angle 《 》" 925 | "tortoise 〔 〕" 926 | "white tortoise 〘 〙" 927 | "white square 〚 〛" 928 | "white paren ⦅ ⦆" 929 | "white curly bracket ⦃ ⦄" 930 | "pointing angle 〈 〉" 931 | "angle with dot ⦑ ⦒" 932 | "curved angle ⧼ ⧽" 933 | "math square ⟦ ⟧" 934 | "math angle ⟨ ⟩" 935 | "math double angle ⟪ ⟫" 936 | "math flattened parenthesis ⟮ ⟯" 937 | "math white tortoise shell ⟬ ⟭" 938 | "heavy single quotation mark ornament ❛ ❜" 939 | "heavy double turned comma quotation mark ornament ❝ ❞" 940 | "medium parenthesis ornament ❨ ❩" 941 | "medium flattened parenthesis ornament ❪ ❫" 942 | "medium curly ornament ❴ ❵" 943 | "medium pointing angle ornament ❬ ❭" 944 | "heavy pointing angle quotation mark ornament ❮ ❯" 945 | "heavy pointing angle ornament ❰ ❱" 946 | "square bracket with underbar ⦋ ⦌" 947 | "none "))) 948 | (let ((completion-ignore-case t)) 949 | (list 950 | (completing-read "Replace this:" xbrackets nil t nil nil (car xbrackets)) 951 | (completing-read "To:" xbrackets nil t nil nil (car (last xbrackets))))))) 952 | (let (xbeg xend xleft xright xtoL xtoR) 953 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 954 | (let ((xsFrom (last (split-string FromChars " ") 2)) 955 | (xsTo (last (split-string ToChars " ") 2))) 956 | 957 | ;; (when (length< xsFrom 3) 958 | ;; (error "cannot find input brackets %s" xsFrom)) 959 | 960 | ;; (when (length< xsTo 3) 961 | ;; (message "replace blacket is empty string") 962 | ;; (setq xsTo (list "" "" ""))) 963 | 964 | (setq xleft (car xsFrom) xright (car (cdr xsFrom)) 965 | xtoL (car xsTo) xtoR (car (cdr xsTo))) 966 | 967 | (save-excursion 968 | (save-restriction 969 | (narrow-to-region xbeg xend) 970 | (let ((case-fold-search nil)) 971 | (if (string-equal xleft xright) 972 | (let ((xx (regexp-quote xleft))) 973 | (goto-char (point-min)) 974 | (while 975 | (re-search-forward 976 | (format "%s\\([^%s]+?\\)%s" xx xx xx) 977 | nil t) 978 | (overlay-put (make-overlay (match-beginning 0) (match-end 0)) 'face 'highlight) 979 | (replace-match (concat xtoL "\\1" xtoR) t))) 980 | (progn 981 | (progn 982 | (goto-char (point-min)) 983 | (while (search-forward xleft nil t) 984 | (overlay-put (make-overlay (match-beginning 0) (match-end 0)) 'face 'highlight) 985 | (replace-match xtoL t t))) 986 | (progn 987 | (goto-char (point-min)) 988 | (while (search-forward xright nil t) 989 | (overlay-put (make-overlay (match-beginning 0) (match-end 0)) 'face 'highlight) 990 | (replace-match xtoR t t))))))))))) 991 | 992 | (defun xah-toggle-letter-case () 993 | "Toggle the letter case of current word or selection. 994 | Always cycle in this order: Init Caps, ALL CAPS, all lower. 995 | 996 | URL `http://xahlee.info/emacs/emacs/emacs_toggle_letter_case.html' 997 | Created: 2020-06-26 998 | Version: 2024-06-17" 999 | (interactive) 1000 | (let ((deactivate-mark nil) xbeg xend) 1001 | (if (region-active-p) 1002 | (setq xbeg (region-beginning) xend (region-end)) 1003 | (save-excursion 1004 | (skip-chars-backward "[:alnum:]") 1005 | (setq xbeg (point)) 1006 | (skip-chars-forward "[:alnum:]") 1007 | (setq xend (point)))) 1008 | (when (not (eq last-command this-command)) 1009 | (put this-command 'state 0)) 1010 | (cond 1011 | ((equal 0 (get this-command 'state)) 1012 | (upcase-initials-region xbeg xend) 1013 | (put this-command 'state 1)) 1014 | ((equal 1 (get this-command 'state)) 1015 | (upcase-region xbeg xend) 1016 | (put this-command 'state 2)) 1017 | ((equal 2 (get this-command 'state)) 1018 | (downcase-region xbeg xend) 1019 | (put this-command 'state 0))))) 1020 | 1021 | ;; test case 1022 | ;; test_case some 1023 | ;; test-case 1024 | ;; tes▮t-case 1025 | 1026 | (defun xah-toggle-previous-letter-case () 1027 | "Toggle the letter case of the letter to the left of cursor. 1028 | 1029 | URL `http://xahlee.info/emacs/emacs/emacs_toggle_letter_case.html' 1030 | Created: 2015-12-22 1031 | Version: 2023-11-14" 1032 | (interactive) 1033 | (let ((case-fold-search nil)) 1034 | (left-char 1) 1035 | (cond 1036 | ((looking-at "[[:lower:]]") (upcase-region (point) (1+ (point)))) 1037 | ((looking-at "[[:upper:]]") (downcase-region (point) (1+ (point))))) 1038 | (right-char))) 1039 | 1040 | (defun xah-upcase-sentence () 1041 | "Upcase first letters of sentences of current block or selection. 1042 | 1043 | URL `http://xahlee.info/emacs/emacs/emacs_upcase_sentence.html' 1044 | Created: 2020-12-08 1045 | Version: 2025-03-25" 1046 | (interactive) 1047 | (let (xbeg xend) 1048 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1049 | (save-restriction 1050 | (narrow-to-region xbeg xend) 1051 | (let ((case-fold-search nil)) 1052 | ;; after period or question mark or exclamation 1053 | (goto-char (point-min)) 1054 | (while (re-search-forward "\\(\\.\\|\\?\\|!\\)[ \n]+ *\\([a-z]\\)" nil 1) 1055 | (upcase-region (match-beginning 2) (match-end 2)) 1056 | (overlay-put (make-overlay (match-beginning 2) (match-end 2)) 'face 'highlight)) 1057 | ;; after a blank line, after a bullet, or beginning of buffer 1058 | (goto-char (point-min)) 1059 | (while (re-search-forward "\\(\\`\\|• \\|\n\n\\)\\([a-z]\\)" nil 1) 1060 | (upcase-region (match-beginning 2) (match-end 2)) 1061 | (overlay-put (make-overlay (match-beginning 2) (match-end 2)) 'face 'highlight)) 1062 | ;; for HTML. first letter after tag 1063 | (when 1064 | (or 1065 | (eq major-mode 'xah-html-mode) 1066 | (eq major-mode 'html-mode) 1067 | (eq major-mode 'sgml-mode) 1068 | (eq major-mode 'nxml-mode) 1069 | (eq major-mode 'xml-mode) 1070 | (eq major-mode 'mhtml-mode)) 1071 | (goto-char (point-min)) 1072 | (while 1073 | (re-search-forward "\\([ \n]?\\|<h[1-6]>[ \n]?\\|<p>[ \n]?\\|<li>[ \n]?\\|<dd>[ \n]?\\|<td>[ \n]?\\|<br ?/?>[ \n]?\\|<figcaption>[ \n]?\\)\\([a-z]\\)" nil 1) 1074 | (upcase-region (match-beginning 2) (match-end 2)) 1075 | (overlay-put (make-overlay (match-beginning 2) (match-end 2)) 'face 'highlight)))) 1076 | (goto-char (point-max))) 1077 | (skip-chars-forward " \n\t"))) 1078 | 1079 | (defun xah-title-case-region-or-line (&optional Begin End) 1080 | "Title case text between nearest brackets, or current line or selection. 1081 | 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. 1082 | 1083 | When called in a elisp program, Begin End are region boundaries. 1084 | 1085 | URL `http://xahlee.info/emacs/emacs/elisp_title_case_text.html' 1086 | Created: 2017-01-11 1087 | Version: 2021-09-19" 1088 | (interactive) 1089 | (let* ((xskipChars "^\"<>(){}[]“”‘’‹›«»「」『』【】〖〗《》〈〉〔〕") 1090 | (xp0 (point)) 1091 | (xbeg (if Begin 1092 | Begin 1093 | (if (region-active-p) 1094 | (region-beginning) 1095 | (progn 1096 | (skip-chars-backward xskipChars (line-beginning-position)) (point))))) 1097 | (xend (if End 1098 | End 1099 | (if (region-active-p) 1100 | (region-end) 1101 | (progn (goto-char xp0) 1102 | (skip-chars-forward xskipChars (line-end-position)) (point))))) 1103 | (xstrPairs [ 1104 | [" A " " a "] 1105 | [" An " " an "] 1106 | [" And " " and "] 1107 | [" At " " at "] 1108 | [" As " " as "] 1109 | [" By " " by "] 1110 | [" Be " " be "] 1111 | [" Into " " into "] 1112 | [" In " " in "] 1113 | [" Is " " is "] 1114 | [" It " " it "] 1115 | [" For " " for "] 1116 | [" Of " " of "] 1117 | [" Or " " or "] 1118 | [" On " " on "] 1119 | [" Via " " via "] 1120 | [" The " " the "] 1121 | [" That " " that "] 1122 | [" To " " to "] 1123 | [" Vs " " vs "] 1124 | [" With " " with "] 1125 | [" From " " from "] 1126 | ["'S " "'s "] 1127 | ["'T " "'t "] 1128 | ])) 1129 | (save-excursion 1130 | (save-restriction 1131 | (narrow-to-region xbeg xend) 1132 | (upcase-initials-region (point-min) (point-max)) 1133 | (let ((case-fold-search nil)) 1134 | (mapc 1135 | (lambda (xx) 1136 | (goto-char (point-min)) 1137 | (while 1138 | (search-forward (aref xx 0) nil t) 1139 | (replace-match (aref xx 1) t t))) 1140 | xstrPairs)))))) 1141 | 1142 | (defun xah-add-space-after-comma () 1143 | "Add a space after comma of current block or selection. 1144 | and highlight changes made. 1145 | Created: 2022-01-20 1146 | Version: 2025-03-25" 1147 | (interactive) 1148 | (let (xbeg xend) 1149 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1150 | (save-restriction 1151 | (narrow-to-region xbeg xend) 1152 | (goto-char (point-min)) 1153 | (while 1154 | (re-search-forward ",\\b" nil t) 1155 | (replace-match ", ") 1156 | (overlay-put 1157 | (make-overlay 1158 | (match-beginning 0) 1159 | (match-end 0)) 'face 'highlight))))) 1160 | 1161 | (defun xah-fill-or-unfill () 1162 | "Reformat current block or selection to short/long line. 1163 | First call will break into multiple short lines. Repeated call toggles between short and long lines. 1164 | This commands calls `fill-region' to do its work. Set `fill-column' for short line length. 1165 | 1166 | URL `http://xahlee.info/emacs/emacs/emacs_unfill-paragraph.html' 1167 | Created: 2020-11-22 1168 | Version: 2025-08-29" 1169 | (interactive) 1170 | ;; 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 1171 | (let ((xisLongline (if (eq last-command this-command) (get this-command 'longline-p) t)) 1172 | (deactivate-mark nil) 1173 | xbeg xend) 1174 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1175 | (if xisLongline 1176 | (fill-region xbeg xend) 1177 | (let ((fill-column 99999)) 1178 | (fill-region xbeg xend))) 1179 | (put this-command 'longline-p (not xisLongline)))) 1180 | 1181 | (defun xah-reformat-lines (&optional Width) 1182 | "Reformat current block or selection into short lines or 1 long line. 1183 | When called for the first time, change to one line. Second call change it to multi-lines. Repeated call toggles. 1184 | If `universal-argument' is called first, ask user to type max length of line. By default, it is 70. 1185 | 1186 | Note: this command is different from emacs `fill-region' or `fill-paragraph'. 1187 | This command never adds or delete non-whitespace chars. It only exchange whitespace sequence. 1188 | 1189 | URL `http://xahlee.info/emacs/emacs/emacs_reformat_lines.html' 1190 | Created: 2016 1191 | Version: 2025-09-07" 1192 | (interactive 1193 | (list 1194 | (if current-prefix-arg 1195 | (cond 1196 | ((not (numberp current-prefix-arg)) 70) 1197 | (t (prefix-numeric-value current-prefix-arg))) 1198 | 80))) 1199 | ;; 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 1200 | (let ((xisLong (if (eq last-command this-command) (get this-command 'is-long-p) nil)) 1201 | (xwidth (if Width Width 70)) 1202 | xbeg xend) 1203 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1204 | (if xisLong 1205 | (save-excursion 1206 | (save-restriction 1207 | (narrow-to-region xbeg xend) 1208 | (goto-char (point-min)) 1209 | (while (re-search-forward " +" nil 1) 1210 | (when (> (- (point) (line-beginning-position)) xwidth) 1211 | (replace-match "\n"))))) 1212 | (save-excursion 1213 | (save-restriction 1214 | (narrow-to-region xbeg xend) 1215 | (goto-char (point-min)) 1216 | (while (re-search-forward "[ \n\t]+" xend 1) (replace-match " "))))) 1217 | (put this-command 'is-long-p (not xisLong)))) 1218 | 1219 | (defun xah-reformat-to-sentence-lines () 1220 | "Reformat current block or selection into multiple lines by ending period. 1221 | Move cursor to the beginning of next text block. 1222 | After this command is called, press variable `xah-repeat-key' to repeat it. 1223 | 1224 | URL `http://xahlee.info/emacs/emacs/elisp_reformat_to_sentence_lines.html' 1225 | Created: 2020-12-02 1226 | Version: 2025-05-21" 1227 | (interactive) 1228 | (let (xbeg xend) 1229 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1230 | (save-restriction 1231 | (narrow-to-region xbeg xend) 1232 | (goto-char (point-min)) (while (search-forward "。" nil t) (replace-match "。\n")) 1233 | ;; (goto-char (point-min)) (while (search-forward " <a " nil t) (replace-match "\n<a ")) 1234 | ;; (goto-char (point-min)) (while (search-forward "</a> " nil t) (replace-match "</a>\n")) 1235 | (goto-char (point-min)) 1236 | (while (re-search-forward "\\([A-Za-z0-9]+\\)[ \t]*\n[ \t]*\\([A-Za-z0-9]+\\)" nil t) 1237 | (replace-match "\\1 \\2")) 1238 | (goto-char (point-min)) 1239 | (while (re-search-forward "\\([,]\\)[ \t]*\n[ \t]*\\([A-Za-z0-9]+\\)" nil t) 1240 | (replace-match "\\1 \\2")) 1241 | (goto-char (point-min)) 1242 | (while (re-search-forward " +" nil t) (replace-match " ")) 1243 | (goto-char (point-min)) 1244 | (while (re-search-forward "\\([.?!]\\) +\\([(0-9A-Za-z<]+\\)" nil t) (replace-match "\\1\n\\2")) 1245 | (goto-char (point-min)) 1246 | (while (re-search-forward "\\(<br ?/?>\\)" nil t) (replace-match "\\1\n")) 1247 | 1248 | (goto-char (point-min)) 1249 | (while (search-forward "e.g.\n" nil t) (replace-match "e.g. ")) 1250 | 1251 | (goto-char (point-max)) 1252 | (while (eq (char-before) 32) (delete-char -1)))) 1253 | (re-search-forward "\n+" nil 1) 1254 | (set-transient-map (let ((xkmap (make-sparse-keymap))) (define-key xkmap (kbd (if (boundp 'xah-repeat-key) xah-repeat-key ".")) this-command) xkmap))) 1255 | 1256 | (defun xah-space-to-newline () 1257 | "Replace space sequence to a newline char in current block or selection. 1258 | 1259 | URL `http://xahlee.info/emacs/emacs/emacs_space_to_newline.html' 1260 | Created: 2017-08-19 1261 | Version: 2025-03-25" 1262 | (interactive) 1263 | (let (xbeg xend) 1264 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1265 | (save-restriction 1266 | (narrow-to-region xbeg xend) 1267 | (goto-char (point-min)) 1268 | (while (re-search-forward " +" nil t) 1269 | (replace-match "\n"))))) 1270 | 1271 | (defun xah-slash-to-backslash (&optional Begin End) 1272 | "Replace slash by backslash on current line or region. 1273 | Created: 2021-07-14 1274 | Version: 2021-09-12" 1275 | (interactive) 1276 | (let (xbeg xend) 1277 | (if (and Begin End) 1278 | (setq xbeg Begin xend End) 1279 | (if (region-active-p) 1280 | (setq xbeg (region-beginning) xend (region-end)) 1281 | (setq xbeg (line-beginning-position) xend (line-end-position)))) 1282 | (save-restriction 1283 | (narrow-to-region xbeg xend) 1284 | (let ((case-fold-search nil)) 1285 | (goto-char (point-min)) 1286 | (while (search-forward "/" nil t) 1287 | (replace-match "\\\\")))))) 1288 | 1289 | (defun xah-backslash-to-slash (&optional Begin End) 1290 | "Replace backslash by slash on current line or region. 1291 | Version: 2021-09-11" 1292 | (interactive) 1293 | (let (xbeg xend) 1294 | (if (and Begin End) 1295 | (setq xbeg Begin xend End) 1296 | (if (region-active-p) 1297 | (setq xbeg (region-beginning) xend (region-end)) 1298 | (setq xbeg (line-beginning-position) xend (line-end-position)))) 1299 | (save-restriction 1300 | (narrow-to-region xbeg xend) 1301 | (let ((case-fold-search nil)) 1302 | (goto-char (point-min)) 1303 | (while (search-forward "\\" nil t) 1304 | (replace-match "/")))))) 1305 | 1306 | (defun xah-double-backslash (&optional Begin End) 1307 | "Replace backslash by two backslash on current line or region. 1308 | Version: 2021-11-09" 1309 | (interactive) 1310 | (let (xbeg xend) 1311 | (if (and Begin End) 1312 | (setq xbeg Begin xend End) 1313 | (if (region-active-p) 1314 | (setq xbeg (region-beginning) xend (region-end)) 1315 | (setq xbeg (line-beginning-position) xend (line-end-position)))) 1316 | (save-restriction 1317 | (narrow-to-region xbeg xend) 1318 | (let ((case-fold-search nil)) 1319 | (goto-char (point-min)) 1320 | (while (search-forward "\\" nil t) 1321 | (replace-match "\\\\\\\\")))))) 1322 | 1323 | (defun xah-double-backslash-to-single (&optional Begin End) 1324 | "Replace double backslash by single backslash on current line or region. 1325 | Version: 2021-11-09" 1326 | (interactive) 1327 | (let (xbeg xend) 1328 | (if (and Begin End) 1329 | (setq xbeg Begin xend End) 1330 | (if (region-active-p) 1331 | (setq xbeg (region-beginning) xend (region-end)) 1332 | (setq xbeg (line-beginning-position) xend (line-end-position)))) 1333 | (save-restriction 1334 | (narrow-to-region xbeg xend) 1335 | (let ((case-fold-search nil)) 1336 | (goto-char (point-min)) 1337 | (while (search-forward "\\\\" nil t) 1338 | (replace-match "\\\\")))))) 1339 | 1340 | (defun xah-slash-to-double-backslash (&optional Begin End) 1341 | "Replace slash by double backslash on current line or region. 1342 | Version: 2021-07-14" 1343 | (interactive) 1344 | (let (xbeg xend) 1345 | (if (and Begin End) 1346 | (setq xbeg Begin xend End) 1347 | (if (region-active-p) 1348 | (setq xbeg (region-beginning) xend (region-end)) 1349 | (setq xbeg (line-beginning-position) xend (line-end-position)))) 1350 | (save-restriction 1351 | (narrow-to-region xbeg xend) 1352 | (let ((case-fold-search nil)) 1353 | (goto-char (point-min)) 1354 | (while (search-forward "/" nil t) 1355 | (replace-match "\\\\\\\\")))))) 1356 | 1357 | (defun xah-double-backslash-to-slash (&optional Begin End) 1358 | "Replace double backslash by slash on current line or region. 1359 | Version: 2021-07-14" 1360 | (interactive) 1361 | (let (xbeg xend) 1362 | (if (and Begin End) 1363 | (setq xbeg Begin xend End) 1364 | (if (region-active-p) 1365 | (setq xbeg (region-beginning) xend (region-end)) 1366 | (setq xbeg (line-beginning-position) xend (line-end-position)))) 1367 | (save-restriction 1368 | (narrow-to-region xbeg xend) 1369 | (let ((case-fold-search nil)) 1370 | (goto-char (point-min)) 1371 | (while (search-forward "\\\\" nil t) 1372 | (replace-match "/")))))) 1373 | 1374 | (defun xah-comment-dwim () 1375 | "Toggle comment in programing language code. 1376 | 1377 | Like `comment-dwim', but toggle comment if cursor is not at end of line. 1378 | 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. 1379 | 1380 | URL `http://xahlee.info/emacs/emacs/emacs_toggle_comment_by_line.html' 1381 | Created: 2016-10-25 1382 | Version: 2023-07-10" 1383 | (interactive) 1384 | (if (region-active-p) 1385 | (comment-dwim nil) 1386 | (let ((xbegin (line-beginning-position)) 1387 | (xend (line-end-position))) 1388 | (if (eq xbegin xend) 1389 | (progn 1390 | (comment-dwim nil)) 1391 | (if (eq (point) xend) 1392 | (progn 1393 | (comment-dwim nil)) 1394 | (progn 1395 | (comment-or-uncomment-region xbegin xend) 1396 | (forward-line ))))))) 1397 | 1398 | (defun xah-quote-lines (QuoteL QuoteR Sep) 1399 | "Add quotes/brackets and separator (comma) to lines. 1400 | Act on current block or selection. 1401 | 1402 | For example, 1403 | 1404 | cat 1405 | dog 1406 | cow 1407 | 1408 | becomes 1409 | 1410 | \"cat\", 1411 | \"dog\", 1412 | \"cow\", 1413 | 1414 | or 1415 | 1416 | (cat) 1417 | (dog) 1418 | (cow) 1419 | 1420 | In lisp code, QuoteL QuoteR Sep are strings. 1421 | 1422 | URL `http://xahlee.info/emacs/emacs/emacs_quote_lines.html' 1423 | Created: 2020-06-26 1424 | Version: 2025-03-25" 1425 | (interactive 1426 | (let ((xbrackets 1427 | '( 1428 | "\"QUOTATION MARK\"" 1429 | "'APOSTROPHE'" 1430 | "(paren)" 1431 | "{brace}" 1432 | "[square]" 1433 | "<greater>" 1434 | "`emacs'" 1435 | "`markdown`" 1436 | "~tilde~" 1437 | "=equal=" 1438 | "“curly double”" 1439 | "‘curly single’" 1440 | "‹french angle›" 1441 | "«french double angle»" 1442 | "「corner」" 1443 | "none" 1444 | "other" 1445 | )) 1446 | (xcomma '("comma ," "semicolon ;" "none" "other")) 1447 | xbktChoice xsep xsepChoice xquoteL xquoteR) 1448 | (let ((completion-ignore-case t)) 1449 | (setq xbktChoice (completing-read "Quote to use:" xbrackets nil t nil nil (car xbrackets))) 1450 | (setq xsepChoice (completing-read "line separator:" xcomma nil t nil nil (car xcomma)))) 1451 | (cond 1452 | ((string-equal xbktChoice "none") 1453 | (setq xquoteL "" xquoteR "")) 1454 | ((string-equal xbktChoice "other") 1455 | (let ((xx (read-string "Enter 2 chars, for begin/end quote:"))) 1456 | (setq xquoteL (substring xx 0 1) 1457 | xquoteR (substring xx 1 2)))) 1458 | (t (setq xquoteL (substring xbktChoice 0 1) 1459 | xquoteR (substring xbktChoice -1)))) 1460 | (setq xsep 1461 | (cond 1462 | ((string-equal xsepChoice "comma ,") ",") 1463 | ((string-equal xsepChoice "semicolon ;") ";") 1464 | ((string-equal xsepChoice "none") "") 1465 | ((string-equal xsepChoice "other") (read-string "Enter separator:")) 1466 | (t xsepChoice))) 1467 | (list xquoteL xquoteR xsep))) 1468 | (let (xbeg xend (xquoteL QuoteL) (xquoteR QuoteR) (xsep Sep)) 1469 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1470 | (save-excursion 1471 | (save-restriction 1472 | (narrow-to-region xbeg xend) 1473 | (goto-char (point-min)) 1474 | (catch 1111 1475 | (while t 1476 | (skip-chars-forward "\t ") 1477 | (insert xquoteL) 1478 | (end-of-line) 1479 | (insert xquoteR xsep) 1480 | (if (eq (point) (point-max)) 1481 | (throw 1111 t) 1482 | (forward-char)))))))) 1483 | 1484 | (defun xah-escape-quotes (Begin End) 1485 | "Add slash before double quote in current line or selection. 1486 | Double quote is codepoint 34. 1487 | See also: `xah-unescape-quotes' 1488 | URL `http://xahlee.info/emacs/emacs/elisp_escape_quotes.html' 1489 | Created: 2010-08-16 1490 | Version: 2025-11-23" 1491 | (interactive 1492 | (if (region-active-p) 1493 | (list (region-beginning) (region-end)) 1494 | (list (line-beginning-position) (line-end-position)))) 1495 | (save-excursion 1496 | (save-restriction 1497 | (narrow-to-region Begin End) 1498 | (goto-char (point-min)) 1499 | (while (search-forward "\"" nil t) 1500 | (replace-match "\\\"" t t)))) 1501 | (message "done. %s" real-this-command)) 1502 | 1503 | (defun xah-unescape-quotes (&optional Begin End) 1504 | "Replace 「\\\"」 by 「\"」 in current line or selection. 1505 | See also: `xah-escape-quotes' 1506 | URL `http://xahlee.info/emacs/emacs/elisp_escape_quotes.html' 1507 | Created: 2017-01-11 1508 | Version: 2023-11-02" 1509 | (interactive) 1510 | (let (xbeg xend) 1511 | (if (and Begin End) 1512 | (setq xbeg Begin xend End) 1513 | (if (region-active-p) 1514 | (setq xbeg (region-beginning) xend (region-end)) 1515 | (setq xbeg (line-beginning-position) xend (line-end-position)))) 1516 | (save-excursion 1517 | (save-restriction 1518 | (narrow-to-region xbeg xend) 1519 | (goto-char (point-min)) 1520 | (while (search-forward "\\\"" nil t) 1521 | (replace-match "\"" t t))))) 1522 | (message "done. %s" real-this-command)) 1523 | 1524 | (defun xah-cycle-hyphen-lowline-space (&optional Begin End) 1525 | "Cycle {hyphen lowline space} chars. 1526 | 1527 | The region to work on is by this order: 1528 | 1. if there is a selection, use that. 1529 | 2. If cursor is in a string quote or any type of bracket, and is within current line, work on that region. 1530 | 3. else, work on current line. 1531 | 1532 | After this command is called, press variable `xah-repeat-key' to repeat it. 1533 | 1534 | URL `http://xahlee.info/emacs/emacs/elisp_change_space-hyphen_underscore.html' 1535 | Created: 2019-02-12 1536 | Version: 2025-01-24" 1537 | (interactive) 1538 | ;; this function sets a property 'state. Possible values are 0 to length of xcharArray. 1539 | (let (xbeg xend xlen 1540 | (xcharArray ["-" "_" " "]) 1541 | (xregionWasActive-p (region-active-p)) 1542 | (xnowState (if (eq last-command this-command) (get 'xah-cycle-hyphen-lowline-space 'state) 0)) 1543 | xchangeTo) 1544 | (setq 1545 | xlen (length xcharArray) 1546 | xchangeTo (elt xcharArray xnowState)) 1547 | (if (and Begin End) 1548 | (setq xbeg Begin xend End) 1549 | (if (region-active-p) 1550 | (setq xbeg (region-beginning) xend (region-end)) 1551 | (let ((xskipChars "^\"<>(){}[]“”‘’‹›«»「」『』【】〖〗《》〈〉〔〕()")) 1552 | (skip-chars-backward xskipChars (line-beginning-position)) 1553 | (setq xbeg (point)) 1554 | (skip-chars-forward xskipChars (line-end-position)) 1555 | (setq xend (point)) 1556 | (push-mark xbeg)))) 1557 | (save-excursion 1558 | (save-restriction 1559 | (narrow-to-region xbeg xend) 1560 | (goto-char (point-min)) 1561 | (while (re-search-forward (elt xcharArray (% (+ xnowState 2) xlen)) (point-max) 1) 1562 | (replace-match xchangeTo t t)))) 1563 | (when (or (string-equal xchangeTo " ") xregionWasActive-p) 1564 | (goto-char xend) 1565 | (push-mark xbeg) 1566 | (setq deactivate-mark nil)) 1567 | (put 'xah-cycle-hyphen-lowline-space 'state (% (+ xnowState 1) xlen))) 1568 | (set-transient-map (let ((xkmap (make-sparse-keymap))) (define-key xkmap (kbd (if (boundp 'xah-repeat-key) xah-repeat-key ".")) this-command) xkmap))) 1569 | 1570 | (defun xah-copy-file-path (&optional DirPathOnlyQ) 1571 | "Copy current buffer file path or dired path. 1572 | Result is full path. 1573 | If `universal-argument' is called first, copy only the dir path. 1574 | 1575 | If in dired, copy the current or marked files. 1576 | 1577 | If a buffer is not file and not dired, copy value of `default-directory'. 1578 | 1579 | URL `http://xahlee.info/emacs/emacs/emacs_copy_file_path.html' 1580 | Created: 2018-06-18 1581 | Version: 2025-05-23" 1582 | (interactive "P") 1583 | (let (xfpath) 1584 | (setq xfpath 1585 | (if (eq major-mode 'dired-mode) 1586 | (progn 1587 | (let ((xresult (mapconcat #'identity 1588 | (dired-get-marked-files) "\n"))) 1589 | (if (length= xresult 0) 1590 | default-directory 1591 | xresult))) 1592 | (or buffer-file-name default-directory))) 1593 | (kill-new 1594 | (if DirPathOnlyQ 1595 | (progn 1596 | (message "Directory copied: %s" (file-name-directory xfpath)) 1597 | (file-name-directory xfpath)) 1598 | (progn 1599 | (message "File path copied: %s" xfpath) 1600 | xfpath))))) 1601 | 1602 | (defun xah-delete-current-text-block () 1603 | "Delete the current text block plus blank lines, or selection, and copy to `kill-ring'. 1604 | 1605 | If cursor is between blank lines, delete following blank lines. 1606 | 1607 | URL `http://xahlee.info/emacs/emacs/emacs_delete_block.html' 1608 | Created: 2017-07-09 1609 | Version: 2024-10-07" 1610 | (interactive) 1611 | (let (xbeg xend (xp (point))) 1612 | (if (region-active-p) 1613 | (setq xbeg (region-beginning) xend (region-end)) 1614 | (progn 1615 | (setq xbeg 1616 | (if (re-search-backward "\n[ \t]*\n+" nil 1) 1617 | (match-end 0) 1618 | (point))) 1619 | (goto-char xp) 1620 | (setq xend (if (re-search-forward "\n[ \t]*\n+" nil 1) 1621 | (match-end 0) 1622 | (point-max))))) 1623 | (kill-region xbeg xend))) 1624 | 1625 | (defun xah-copy-to-register-1 () 1626 | "Copy selection or text block to register 1. 1627 | URL `http://xahlee.info/emacs/emacs/elisp_copy-paste_register_1.html' 1628 | Created: 2012-07-17 1629 | Version: 2025-11-05" 1630 | (interactive) 1631 | (let (xbeg xend) 1632 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1633 | (copy-to-register ?1 xbeg xend) 1634 | (message "Copied to register 1: %s." (buffer-substring xbeg xend)))) 1635 | 1636 | (defun xah-append-to-register-1 () 1637 | "Append selection or text block to register 1, 1638 | with one added newline char at end. 1639 | URL `http://xahlee.info/emacs/emacs/elisp_copy-paste_register_1.html' 1640 | Created: 2015-12-08 1641 | Version: 2025-11-07" 1642 | (interactive) 1643 | (let (xbeg xend) 1644 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1645 | (let ((register-separator "\n")) 1646 | (append-to-register ?1 xbeg xend) 1647 | (message "Done. Append to register 1.")))) 1648 | 1649 | (defun xah-paste-from-register-1 () 1650 | "Paste text from register 1. 1651 | URL `http://xahlee.info/emacs/emacs/elisp_copy-paste_register_1.html' 1652 | Created: 2015-12-08 1653 | Version: 2025-11-07" 1654 | (interactive) 1655 | (when (region-active-p) 1656 | (delete-region (region-beginning) (region-end))) 1657 | (insert-register ?1 t)) 1658 | 1659 | (defun xah-clear-register-1 () 1660 | "Clear register 1. 1661 | URL `http://xahlee.info/emacs/emacs/elisp_copy-paste_register_1.html' 1662 | Created: 2015-12-08 1663 | Version: 2025-11-07" 1664 | (interactive) 1665 | (progn 1666 | (copy-to-register ?1 (point-min) (point-min)) 1667 | (message "Cleared register 1."))) 1668 | 1669 | ;; s------------------------------ 1670 | ;; insertion commands 1671 | 1672 | (defun xah-insert-date () 1673 | "Insert current date time. 1674 | Insert date in this format: yyyy-mm-dd. 1675 | If `universal-argument' is called first, prompt for a format to use. 1676 | If there is selection, delete it first. 1677 | 1678 | URL `http://xahlee.info/emacs/emacs/elisp_insert-date-time.html' 1679 | Created: 2013-05-10 1680 | Version: 2025-09-04" 1681 | (interactive) 1682 | (let (xmenu xstyle) 1683 | (setq 1684 | xmenu 1685 | (list 1686 | (concat "ISO date⚫" (format-time-string "%Y-%m-%d")) 1687 | (concat "dotted version⚫" (format-time-string "%Y.%m.%d")) 1688 | (concat "coder⚫" (format-time-string "%Y-%m-%d_%H%M%S")) 1689 | (concat "all digits⚫" (format-time-string "%Y%m%d%H%M%S")) 1690 | 1691 | (concat "unix seconds⚫" (number-to-string (car (let ((current-time-list nil)) (current-time))))) 1692 | 1693 | ;; (concat "ISO full⚫" (format-time-string "%Y-%m-%dT%T") (funcall (lambda (xx) (format "%s:%s" (substring xx 0 3) (substring xx 3 5))) (format-time-string "%z"))) 1694 | 1695 | (concat "ISO full⚫" (format-time-string "%FT%T%z")) 1696 | 1697 | (concat "ISO + weekday⚫" (format-time-string "%Y-%m-%d %A")) 1698 | (concat "USA + weekday⚫" (format-time-string "%A, %B %d, %Y")) 1699 | (concat "USA + weekday abbrev⚫" (format-time-string "%a, %b %d, %Y")) 1700 | (concat "USA⚫" (format-time-string "%B %d, %Y")) 1701 | (concat "USA abbrev⚫" (format-time-string "%b %d, %Y")))) 1702 | (setq xstyle 1703 | (if current-prefix-arg 1704 | (let ((completion-ignore-case t)) 1705 | (completing-read "Style:" xmenu nil t)) 1706 | (car xmenu))) 1707 | (when (region-active-p) (delete-region (region-beginning) (region-end))) 1708 | (insert (nth 1 (split-string xstyle "⚫"))))) 1709 | 1710 | (defun xah-insert-bracket-pair (LBracket RBracket &optional WrapMethod) 1711 | "Insert brackets around selection, word, at point, and maybe move cursor in between. 1712 | 1713 | LBracket and RBracket are strings. WrapMethod must be either `line' or `block'. `block' means between empty lines. 1714 | 1715 | • If there is a active region, wrap around region. 1716 | Else 1717 | • If WrapMethod is `line', wrap around line. 1718 | • If WrapMethod is `block', wrap around block. 1719 | Else 1720 | • If cursor is at beginning of line and its not empty line and contain at least 1 space, wrap around the line. 1721 | • If cursor is at end of a word or buffer, one of the following will happen: 1722 | xyz▮ → xyz(▮) 1723 | xyz▮ → (xyz▮) if in one of the lisp modes. 1724 | • wrap brackets around word if any. e.g. xy▮z → (xyz▮). Or just (▮) 1725 | 1726 | URL `http://xahlee.info/emacs/emacs/elisp_insert_brackets_by_pair.html' 1727 | Created: 2017-01-17 1728 | Version: 2025-03-25" 1729 | (if (region-active-p) 1730 | (progn 1731 | (let ((xbeg (region-beginning)) (xend (region-end))) 1732 | (goto-char xend) (insert RBracket) 1733 | (goto-char xbeg) (insert LBracket) 1734 | (goto-char (+ xend 2)))) 1735 | (let (xbeg xend) 1736 | (cond 1737 | ((eq WrapMethod 'line) 1738 | (setq xbeg (line-beginning-position) xend (line-end-position)) 1739 | (goto-char xend) 1740 | (insert RBracket) 1741 | (goto-char xbeg) 1742 | (insert LBracket) 1743 | (goto-char (+ xend (length LBracket)))) 1744 | ((eq WrapMethod 'block) 1745 | (save-excursion 1746 | (setq xbeg (if (region-active-p) (region-beginning) (save-excursion (if (re-search-backward "\n[ \t]*\n" nil 1) (match-end 0) (point)))) xend (if (region-active-p) (region-end) (save-excursion (if (re-search-forward "\n[ \t]*\n" nil 1) (match-beginning 0) (point))))) 1747 | (goto-char xend) 1748 | (insert RBracket) 1749 | (goto-char xbeg) 1750 | (insert LBracket) 1751 | (goto-char (+ xend (length LBracket))))) 1752 | ( ; do line. line must contain space 1753 | (and 1754 | (eq (point) (line-beginning-position)) 1755 | (not (eq (line-beginning-position) (line-end-position)))) 1756 | (insert LBracket) 1757 | (end-of-line) 1758 | (insert RBracket)) 1759 | ((and 1760 | (or ; cursor is at end of word or buffer. i.e. xyz▮ 1761 | (looking-at "[^-_[:alnum:]]") 1762 | (eq (point) (point-max))) 1763 | (not (or 1764 | (eq major-mode 'xah-elisp-mode) 1765 | (eq major-mode 'emacs-lisp-mode) 1766 | (eq major-mode 'lisp-mode) 1767 | (eq major-mode 'lisp-interaction-mode) 1768 | (eq major-mode 'common-lisp-mode) 1769 | (eq major-mode 'clojure-mode) 1770 | (eq major-mode 'xah-clojure-mode) 1771 | (eq major-mode 'scheme-mode)))) 1772 | (progn 1773 | (setq xbeg (point) xend (point)) 1774 | (insert LBracket RBracket) 1775 | (search-backward RBracket))) 1776 | (t (progn 1777 | ;; wrap around “word”. basically, want all alphanumeric, plus hyphen and underscore, but don't want space or punctuations. Also want chinese chars 1778 | ;; 我有一帘幽梦,不知与谁能共。多少秘密在其中,欲诉无人能懂。 1779 | (skip-chars-backward "-_[:alnum:]") 1780 | (setq xbeg (point)) 1781 | (skip-chars-forward "-_[:alnum:]") 1782 | (setq xend (point)) 1783 | (goto-char xend) 1784 | (insert RBracket) 1785 | (goto-char xbeg) 1786 | (insert LBracket) 1787 | (goto-char (+ xend (length LBracket))))))))) 1788 | 1789 | (defun xah-insert-paren () (interactive) (xah-insert-bracket-pair "(" ")")) 1790 | (defun xah-insert-square-bracket () (interactive) (xah-insert-bracket-pair "[" "]")) 1791 | (defun xah-insert-brace () (interactive) (xah-insert-bracket-pair "{" "}")) 1792 | 1793 | (defun xah-insert-ascii-double-quote () (interactive) (xah-insert-bracket-pair "\"" "\"")) 1794 | (defun xah-insert-ascii-single-quote () (interactive) (xah-insert-bracket-pair "'" "'")) 1795 | (defun xah-insert-ascii-angle-bracket () (interactive) (xah-insert-bracket-pair "<" ">")) 1796 | 1797 | (defun xah-insert-emacs-quote () (interactive) (xah-insert-bracket-pair "`" "'")) 1798 | (defun xah-insert-markdown-quote () (interactive) (xah-insert-bracket-pair "`" "`")) 1799 | (defun xah-insert-markdown-triple-quote () (interactive) (xah-insert-bracket-pair "```\n" "\n```")) 1800 | 1801 | (defun xah-insert-double-curly-quote“” () (interactive) (xah-insert-bracket-pair "“" "”")) 1802 | (defun xah-insert-curly-single-quote‘’ () (interactive) (xah-insert-bracket-pair "‘" "’")) 1803 | (defun xah-insert-single-angle-quote‹› () (interactive) (xah-insert-bracket-pair "‹" "›")) 1804 | (defun xah-insert-double-angle-quote«» () (interactive) (xah-insert-bracket-pair "«" "»")) 1805 | 1806 | (defun xah-insert-corner-bracket「」 () (interactive) (xah-insert-bracket-pair "「" "」")) 1807 | (defun xah-insert-white-corner-bracket『』 () (interactive) (xah-insert-bracket-pair "『" "』")) 1808 | (defun xah-insert-angle-bracket〈〉 () (interactive) (xah-insert-bracket-pair "〈" "〉")) 1809 | (defun xah-insert-double-angle-bracket《》 () (interactive) (xah-insert-bracket-pair "《" "》")) 1810 | (defun xah-insert-white-lenticular-bracket〖〗 () (interactive) (xah-insert-bracket-pair "〖" "〗")) 1811 | (defun xah-insert-black-lenticular-bracket【】 () (interactive) (xah-insert-bracket-pair "【" "】")) 1812 | (defun xah-insert-tortoise-shell-bracket〔〕 () (interactive) (xah-insert-bracket-pair "〔" "〕")) 1813 | (defun xah-insert-deco-angle-bracket❮❯ () (interactive) (xah-insert-bracket-pair "❮" "❯")) 1814 | (defun xah-insert-deco-angle-fat-bracket❰❱ () (interactive) (xah-insert-bracket-pair "❰" "❱")) 1815 | 1816 | (defun xah-insert-hyphen () 1817 | "Insert a HYPHEN-MINUS character." 1818 | (interactive) 1819 | (insert "-")) 1820 | 1821 | (defun xah-insert-low-line () 1822 | "Insert a LOW LINE character." 1823 | (interactive) 1824 | (insert "_")) 1825 | 1826 | (defun xah-insert-string-assignment () 1827 | "Insert =\"\"" 1828 | (interactive) 1829 | (progn (insert "=\"\"") 1830 | (left-char))) 1831 | 1832 | (defun xah-insert-space-before () 1833 | "Insert space before cursor." 1834 | (interactive) 1835 | (insert " ")) 1836 | 1837 | (defun xah-insert-space-after () 1838 | "Insert space after cursor" 1839 | (interactive) 1840 | (insert " ") 1841 | (left-char)) 1842 | 1843 | (defun xah-insert-seperator () 1844 | "Insert a visual seperator line. 1845 | Version: 2025-11-13" 1846 | (interactive) 1847 | (cond 1848 | ((and buffer-file-name (string-equal "html" (file-name-extension buffer-file-name))) (insert "<hr />\n")) 1849 | ((not comment-start) 1850 | (insert "\ns------------------------------\n")) 1851 | (t (insert "\ns------------------------------\n") 1852 | (backward-char) 1853 | (comment-line 1)))) 1854 | 1855 | (defvar xah-unicode-list nil 1856 | "A alist. 1857 | Each item is (prompStr . xString). Used by `xah-insert-unicode'. 1858 | prompStr is used for prompt. 1859 | xString is is the char to insert. 1860 | xString can be multiple chars or any string. 1861 | ") 1862 | 1863 | (setq 1864 | xah-unicode-list 1865 | '( 1866 | ;; 1867 | ("smile beaming 😊" . "😊") 1868 | ("tears of joy" . "😂") 1869 | ("hug 🤗" . "🤗") 1870 | ("heart eyes 😍" . "😍") 1871 | ("heart face 🥰" . "🥰") 1872 | ("angry 😠" . "😠") 1873 | ("vomit 🤮" . "🤮") 1874 | ("thumb up 👍" . "👍") 1875 | ("thumb down 👎" . "👎") 1876 | ("tv 📺" . "📺") 1877 | ("lotus 🪷" . "🪷") 1878 | ("checkmark ✅" . "✅") 1879 | ("new 🆕" . "🆕") 1880 | ("glowing star 🌟" . "🌟") 1881 | ("star ⭐" . "⭐") 1882 | ("sparkles ✨" . "✨") 1883 | ("rocket 🚀" . "🚀") 1884 | ("sun 🌞" . "🌞") 1885 | ("heart 🧡" . "🧡") 1886 | ("clown 🤡" . "🤡") 1887 | ("large circle" . "⭕") 1888 | ("cross ❌" . "❌") 1889 | ("red triangle 🔺" . "🔺") 1890 | ("diamond 💠" . "💠") 1891 | ("square ⬛" . "⬛") 1892 | ("SMALL ORANGE DIAMOND 🔸" . "🔸") 1893 | ("BLACK RIGHT-POINTING TRIANGLE ▶" . "▶") 1894 | ("BLACK DIAMOND ◆" . "◆") 1895 | ("LONG RIGHTWARDS ARROW ⟶" . "⟶") 1896 | 1897 | ("script 📜" . "📜") 1898 | ("package 📦" . "📦") 1899 | ("cursor ▮" . "▮") 1900 | ("music 🎵" . "🎵") 1901 | ("ok 🆗" . "🆗") 1902 | 1903 | ("dagger †" . "†") 1904 | ("double dagger ‡" . "‡") 1905 | 1906 | ("double angle bracket" . "《》") 1907 | ("black lenticular bracket" . "【】") 1908 | ("corner-bracket" . "「」") 1909 | ("tortoise shell bracket" . "〔〕") 1910 | ("angle bracket" . "〈〉") 1911 | ("double angle quote" . "«»") 1912 | 1913 | ("bullet •" . "•") 1914 | 1915 | ("...ellipsis …" . "…") 1916 | ("nbsp non breaking space" . " ") 1917 | ("chinese comma 、" . "、") 1918 | ("emdash —" . "—") 1919 | ("fullwidth ampersand &" . "&") 1920 | ("left arrow ←" . "←") 1921 | ("right arrow →" . "→") 1922 | ("up arrow ↑" . "↑") 1923 | ("down arrow ↓" . "↓") 1924 | ("f hook ƒ" . "ƒ") 1925 | ("chinese space" . " ") 1926 | 1927 | ;; 1928 | )) 1929 | 1930 | (defun xah-insert-unicode () 1931 | "Insert a unicode from a custom list `xah-unicode-list'. 1932 | URL `http://xahlee.info/emacs/emacs/emacs_insert_unicode.html' 1933 | Created: 2021-01-05 1934 | Version: 2023-09-19" 1935 | (interactive) 1936 | (let ((xkey 1937 | (let ((completion-ignore-case t)) 1938 | (completing-read "Insert:" xah-unicode-list nil t)))) 1939 | (insert (cdr (assoc xkey xah-unicode-list))))) 1940 | 1941 | ;; s------------------------------ 1942 | ;; text selection 1943 | 1944 | (defun xah-select-block () 1945 | "Select the current/next block. 1946 | 1947 | URL `http://xahlee.info/emacs/emacs/emacs_select_text_block.html' 1948 | Created: 2019-12-26 1949 | Version: 2025-07-07" 1950 | (interactive) 1951 | (if (region-active-p) 1952 | (re-search-forward "\n[ \t]*\n[ \t]*\n*" nil 1) 1953 | (progn 1954 | (when (re-search-backward "\n[ \t]*\n" nil 1) 1955 | (goto-char (match-end 0))) 1956 | (push-mark (point) t t) 1957 | (re-search-forward "\n[ \t]*\n" nil 1) 1958 | (skip-chars-backward " \t\n")))) 1959 | 1960 | (defun xah-select-line () 1961 | "Select current line. If region is active, extend selection downward by line. 1962 | If `visual-line-mode' is on, consider line as visual line. 1963 | 1964 | URL `http://xahlee.info/emacs/emacs/emacs_select_line.html' 1965 | Created: 2017-11-01 1966 | Version: 2023-11-14" 1967 | (interactive) 1968 | (if (region-active-p) 1969 | (if visual-line-mode 1970 | (let ((xbeg (point))) 1971 | (end-of-visual-line 1) 1972 | (when (eq xbeg (point)) 1973 | (end-of-visual-line 2))) 1974 | (progn 1975 | (forward-line 1) 1976 | (end-of-line))) 1977 | (if visual-line-mode 1978 | (progn (beginning-of-visual-line) 1979 | (push-mark (point) t t) 1980 | (end-of-visual-line)) 1981 | (progn 1982 | (push-mark (line-beginning-position) t t) 1983 | (end-of-line))))) 1984 | 1985 | (defun xah-extend-selection () 1986 | "Select the current word, bracket/quote expression, or expand selection. 1987 | Subsequent calls expands the selection. 1988 | 1989 | when there is no selection, 1990 | • If cursor is on any type of bracket (including parenthesis, quotation mark), select whole bracketed thing including bracket 1991 | • else, select current word. 1992 | 1993 | 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. 1994 | 1995 | After this command is called, press variable `xah-repeat-key' to repeat it. 1996 | 1997 | URL `http://xahlee.info/emacs/emacs/emacs_extend_selection.html' 1998 | Created: 2020-02-04 1999 | Version: 2025-11-18" 2000 | (interactive) 2001 | (if (region-active-p) 2002 | (let ((xbeg (region-beginning)) (xend (region-end))) 2003 | (goto-char xbeg) 2004 | (cond 2005 | ((looking-at "\\s(") 2006 | (if (eq (nth 0 (syntax-ppss)) 0) 2007 | (progn 2008 | (message "%s debug: left bracket, depth 0." real-this-command) 2009 | (end-of-line) ; select current line 2010 | (push-mark (line-beginning-position) t t)) 2011 | (progn 2012 | (message "%s debug: left bracket, depth not 0" real-this-command) 2013 | (up-list -1 t t) 2014 | (mark-sexp) (exchange-point-and-mark)))) 2015 | ((eq xbeg (line-beginning-position)) 2016 | (progn 2017 | (goto-char xbeg) 2018 | (let ((xfirstLineEndPos (line-end-position))) 2019 | (cond 2020 | ((eq xend xfirstLineEndPos) 2021 | (progn 2022 | (message "%s debug: exactly 1 line. extend to next whole line." real-this-command) 2023 | (forward-line 1) 2024 | (end-of-line))) 2025 | ((< xend xfirstLineEndPos) 2026 | (progn 2027 | (message "%s debug: less than 1 line. complete the line." real-this-command) 2028 | (end-of-line))) 2029 | ((> xend xfirstLineEndPos) 2030 | (progn 2031 | (message "%s debug: beginning of line, but end is greater than 1st end of line" real-this-command) 2032 | (goto-char xend) 2033 | (if (eq (point) (line-end-position)) 2034 | (progn 2035 | (message "%s debug: exactly multiple lines" real-this-command) 2036 | (forward-line 1) 2037 | (end-of-line)) 2038 | (progn 2039 | (message "%s debug: multiple lines but end is not eol. make it so" real-this-command) 2040 | (goto-char xend) 2041 | (end-of-line))))) 2042 | (t (error "%s: logic error 42946" real-this-command)))))) 2043 | ((and (> (point) (line-beginning-position)) (<= (point) (line-end-position))) 2044 | (progn 2045 | (message "%s debug: less than 1 line" real-this-command) 2046 | (end-of-line) ; select current line 2047 | (push-mark (line-beginning-position) t t))) 2048 | (t 2049 | (message "%s debug: last resort" real-this-command) 2050 | nil))) 2051 | (cond 2052 | ((looking-at "\\s(") 2053 | (message "%s debug: left bracket" real-this-command) 2054 | (mark-sexp) (exchange-point-and-mark)) 2055 | 2056 | ;; ((looking-at "\\s)") 2057 | ;; (message "%s debug: right bracket" real-this-command) 2058 | ;; (backward-up-list) (mark-sexp) (exchange-point-and-mark)) 2059 | 2060 | ((looking-at "\\s\"") 2061 | (message "%s debug: string quote" real-this-command) 2062 | 2063 | (if (nth 3 (syntax-ppss)) 2064 | (progn 2065 | (forward-char) 2066 | (backward-sexp) 2067 | (mark-sexp) 2068 | (exchange-point-and-mark)) 2069 | (progn 2070 | (mark-sexp) 2071 | (exchange-point-and-mark)))) 2072 | 2073 | ((looking-at "[ \t\n]") 2074 | (message "%s debug: is white space" real-this-command) 2075 | ;; (skip-chars-backward " \t\n") 2076 | (push-mark) 2077 | (skip-chars-forward " \t\n") 2078 | (setq mark-active t)) 2079 | 2080 | ((looking-at "[-_[:word:]]") 2081 | (message "%s debug: right is word or symbol" real-this-command) 2082 | (skip-chars-backward "-_[:word:]") 2083 | (push-mark) 2084 | (skip-chars-forward "-_[:word:]") 2085 | (setq mark-active t)) 2086 | 2087 | ;; ((and (looking-at "[[:blank:]]") 2088 | ;; (prog2 (backward-char) (looking-at "[[:blank:]]") (forward-char))) 2089 | ;; ;; (message "%s debug: left and right both space" real-this-command) 2090 | ;; (skip-chars-backward "[[:blank:]]") (push-mark (point) t t) 2091 | ;; (skip-chars-forward "[[:blank:]]")) 2092 | 2093 | ((and (looking-at "\n") 2094 | (eq (char-before) 10)) 2095 | (message "%s debug: left and right both newline" real-this-command) 2096 | (skip-chars-forward "\n") 2097 | (push-mark (point) t t) 2098 | (re-search-forward "\n[ \t]*\n")) 2099 | 2100 | (t 2101 | (message "%s debug: no condition met. just select 1 char" real-this-command) 2102 | (push-mark (point) t t) 2103 | (forward-char)))) 2104 | 2105 | (set-transient-map 2106 | (let ((xkmap (make-sparse-keymap))) 2107 | (define-key xkmap (kbd (if (boundp 'xah-repeat-key) xah-repeat-key ".")) real-this-command) 2108 | xkmap))) 2109 | 2110 | (defun xah-select-text-in-quote () 2111 | "Select text between the nearest left and right delimiters. 2112 | Delimiters here includes QUOTATION MARK, GRAVE ACCENT, and anything in variable `xah-brackets'. 2113 | This command ignores nesting. For example, if text is 2114 | 「(a(b)c▮)」 2115 | the selected char is 「c」, not 「a(b)c」. 2116 | 2117 | URL `http://xahlee.info/emacs/emacs/emacs_select_quote_text.html' 2118 | Created: 2020-11-24 2119 | Version: 2023-11-14" 2120 | (interactive) 2121 | (let ((xskipChars (concat "^\"`" (mapconcat #'identity xah-brackets "")))) 2122 | (skip-chars-backward xskipChars) 2123 | (push-mark (point) t t) 2124 | (skip-chars-forward xskipChars))) 2125 | 2126 | (defun xah-cut-text-in-quote () 2127 | "Cut text between the nearest left and right delimiters. 2128 | See `xah-select-text-in-quote' 2129 | 2130 | Created: 2023-07-23 2131 | Version: 2024-10-02" 2132 | (interactive) 2133 | (let (xbeg xend 2134 | (xskipChars (concat "^\"`" (mapconcat #'identity xah-brackets "")))) 2135 | (skip-chars-backward xskipChars) 2136 | (setq xbeg (point)) 2137 | (skip-chars-forward xskipChars) 2138 | (setq xend (point)) 2139 | (kill-region xbeg xend))) 2140 | 2141 | ;; s------------------------------ 2142 | ;; misc 2143 | 2144 | (defun xah-user-buffer-p () 2145 | "Return t if current buffer is a user buffer, else nil. 2146 | A user buffer has buffer name NOT starts with * or space, and is not dired mode, help mode, etc. 2147 | This function is used by buffer switching command and close buffer command, so that next buffer shown is a user buffer. 2148 | You can override this function to get your idea of “user buffer”. 2149 | URL `http://xahlee.info/emacs/emacs/elisp_next_prev_user_buffer.html' 2150 | Created: 2016-06-18 2151 | Version: 2024-09-23" 2152 | (interactive) 2153 | (cond 2154 | ((string-match "^\*" (buffer-name)) nil) 2155 | ;; ((eq major-mode 'dired-mode) nil) 2156 | ;; ((eq major-mode 'eww-mode) nil) 2157 | ;; ((eq major-mode 'help-mode) nil) 2158 | (t t))) 2159 | 2160 | (defun xah-next-user-buffer () 2161 | "Switch to the next user buffer. 2162 | User Buffer here is determined by `xah-user-buffer-p'. 2163 | URL `http://xahlee.info/emacs/emacs/elisp_next_prev_user_buffer.html' 2164 | Created: 2016-06-19 2165 | Version: 2025-09-08" 2166 | (interactive) 2167 | (seq-some 2168 | (lambda (_) 2169 | (next-buffer) 2170 | (xah-user-buffer-p)) 2171 | (make-list 50 0))) 2172 | 2173 | (defun xah-previous-user-buffer () 2174 | "Switch to the previous user buffer. 2175 | User Buffer here is determined by `xah-user-buffer-p'. 2176 | URL `http://xahlee.info/emacs/emacs/elisp_next_prev_user_buffer.html' 2177 | Created: 2016-06-19 2178 | Version: 2025-09-08" 2179 | (interactive) 2180 | (seq-some 2181 | (lambda (_) 2182 | (previous-buffer) 2183 | (xah-user-buffer-p)) 2184 | (make-list 50 0))) 2185 | 2186 | (defun xah-new-empty-buffer () 2187 | "Create a new empty buffer. 2188 | Returns the buffer object. 2189 | New buffer is named untitled, untitled<2>, etc. 2190 | 2191 | Warning: new buffer is not prompted for save when killed, see `kill-buffer'. 2192 | Or manually `save-buffer' 2193 | 2194 | URL `http://xahlee.info/emacs/emacs/emacs_new_empty_buffer.html' 2195 | Created: 2017-11-01 2196 | Version: 2022-04-05" 2197 | (interactive) 2198 | (let ((xbuf (generate-new-buffer "untitled"))) 2199 | (switch-to-buffer xbuf) 2200 | (funcall initial-major-mode) 2201 | xbuf 2202 | )) 2203 | 2204 | (declare-function minibuffer-keyboard-quit "delsel" ()) 2205 | (declare-function org-edit-src-save "org-src" ()) 2206 | 2207 | (defcustom xah-recently-closed-buffers-max 100 "The maximum length for `xah-recently-closed-buffers'." 2208 | :type 'integer) 2209 | 2210 | (defvar xah-recently-closed-buffers nil "A Alist of recently closed buffers. 2211 | Each element is (bufferName . filePath). 2212 | The max number to track is controlled by the variable `xah-recently-closed-buffers-max'.") 2213 | 2214 | (defun xah-add-to-recently-closed (&optional BufferName BufferFileName) 2215 | "Add to `xah-recently-closed-buffers'. 2216 | Created: 2023-03-02 2217 | Version: 2025-06-05" 2218 | (let ((bufname (if BufferName BufferName (buffer-name))) 2219 | (fpath (if BufferFileName BufferFileName buffer-file-name))) 2220 | (setq xah-recently-closed-buffers (cons (cons bufname fpath) xah-recently-closed-buffers))) 2221 | (when (length> xah-recently-closed-buffers xah-recently-closed-buffers-max) 2222 | (setq xah-recently-closed-buffers (butlast xah-recently-closed-buffers 1)))) 2223 | 2224 | (defvar xah-create-buffer-backup nil "If true, `xah-close-current-buffer' creates a backup file when closing non-file buffer. Version: 2024-11-09") 2225 | 2226 | (setq xah-create-buffer-backup t) 2227 | 2228 | (defvar xah-temp-dir-path nil "Path to temp dir used by xah commands. 2229 | by default, the value is dir named temp at `user-emacs-directory'. 2230 | Version: 2023-03-21") 2231 | (setq xah-temp-dir-path (concat user-emacs-directory "temp/")) 2232 | 2233 | (defun xah-close-current-buffer () 2234 | "Close the current buffer with possible backup. 2235 | 2236 | • If the buffer is a file and not modified, kill it. If is modified, do nothing. Print a message. 2237 | • If the buffer is not a file, and variable `xah-create-buffer-backup' is true, then save a backup to `xah-temp-dir-path' named untitled_‹datetime›_‹randomhex›.txt. 2238 | 2239 | If `universal-argument' is called first, call `kill-buffer'. (this is useful to force kill.) 2240 | 2241 | If the buffer is a file, add the path to the list `xah-recently-closed-buffers'. 2242 | 2243 | URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html' 2244 | Created: 2016-06-19 2245 | Version: 2025-09-08" 2246 | (interactive) 2247 | (widen) 2248 | (cond 2249 | (current-prefix-arg (kill-buffer)) 2250 | ;; ((eq major-mode 'minibuffer-inactive-mode) (minibuffer-keyboard-quit)) 2251 | ;; ((active-minibuffer-window) (minibuffer-keyboard-quit)) 2252 | ((minibufferp (current-buffer)) (minibuffer-keyboard-quit)) 2253 | 2254 | ((eq major-mode 'dired-mode) 2255 | (xah-add-to-recently-closed (buffer-name) default-directory) 2256 | (kill-buffer)) 2257 | 2258 | ((and buffer-file-name (not (buffer-modified-p))) 2259 | (xah-add-to-recently-closed (buffer-name) buffer-file-name) 2260 | (kill-buffer)) 2261 | 2262 | ((and buffer-file-name (buffer-modified-p)) 2263 | (message "buffer file modified. Save it first.\n%s" buffer-file-name)) 2264 | ((and xah-create-buffer-backup (not buffer-file-name) (xah-user-buffer-p) (not (eq (point-max) 1))) 2265 | (let ((xnewName (format "%suntitled_%s_%x.txt" 2266 | xah-temp-dir-path 2267 | (format-time-string "%Y-%m-%d_%H%M%S") 2268 | (random #xfffff)))) 2269 | (when (not (file-exists-p xah-temp-dir-path)) (make-directory xah-temp-dir-path)) 2270 | (write-region (point-min) (point-max) xnewName) 2271 | (xah-add-to-recently-closed (buffer-name) xnewName) 2272 | (kill-buffer))) 2273 | (t (kill-buffer)))) 2274 | 2275 | (defun xah-open-last-closed () 2276 | "Open the last closed file. 2277 | URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html' 2278 | Created: 2016-06-19 2279 | Version: 2022-03-22" 2280 | (interactive) 2281 | (if (length> xah-recently-closed-buffers 0) 2282 | (find-file (cdr (pop xah-recently-closed-buffers))) 2283 | (progn (message "No recently close buffer in this session.")))) 2284 | 2285 | (defun xah-open-recently-closed () 2286 | "Open recently closed file. 2287 | Prompt for a choice. 2288 | 2289 | URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html' 2290 | Created: 2016-06-19 2291 | Version: 2023-09-19" 2292 | (interactive) 2293 | (find-file 2294 | (let ((completion-ignore-case t)) 2295 | (completing-read 2296 | "Open:" 2297 | (mapcar (lambda (f) (cdr f)) xah-recently-closed-buffers) 2298 | nil t 2299 | )))) 2300 | 2301 | (defun xah-list-recently-closed () 2302 | "List recently closed file. 2303 | 2304 | URL `http://xahlee.info/emacs/emacs/elisp_close_buffer_open_last_closed.html' 2305 | Version: 2016-06-19" 2306 | (interactive) 2307 | (let ((xbuf (generate-new-buffer "*recently closed*"))) 2308 | (switch-to-buffer xbuf) 2309 | (mapc (lambda (xf) (insert (cdr xf) "\n")) 2310 | xah-recently-closed-buffers))) 2311 | 2312 | (defvar xah-open-file-at-cursor-pre-hook nil "Hook for `xah-open-file-at-cursor'. 2313 | Functions in the hook is called in order, each given the raw input text (path) as arg. 2314 | The first return non-nil, its value is given to `xah-open-file-at-cursor' as input. rest functions in hook is ignored. 2315 | This is useful for transforming certain url into file path. e.g. change 2316 | http://xahlee.info/emacs/index.html 2317 | to 2318 | C:/Users/xah/web/xahlee_info/emacs/index.html 2319 | , so instead of opening in browser, it opens in emacs as file.") 2320 | 2321 | (defun xah-open-file-at-cursor () 2322 | "Open the file path under cursor. 2323 | 2324 | • If there is selection, use it for path. 2325 | • Path can be {relative, full path, URL}. 2326 | • If the path starts with 「https*://」, open the URL in browser. 2327 | • 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. 2328 | 2329 | If path does not have a file extension, automatically try with .el for elisp files. 2330 | 2331 | See also `xah-open-file-at-cursor-pre-hook'. 2332 | 2333 | This command is similar to `find-file-at-point' but without prompting for confirmation. 2334 | 2335 | URL `http://xahlee.info/emacs/emacs/emacs_open_file_path_fast.html' 2336 | Created: 2020-10-17 2337 | Version: 2024-09-25" 2338 | (interactive) 2339 | (let (xinput xinput2 xpath) 2340 | (setq xinput (if (region-active-p) 2341 | (buffer-substring-no-properties (region-beginning) (region-end)) 2342 | (let ((xp0 (point)) xbeg xend 2343 | (xpathStops "^  \t\n\"`'‘’“”|()[]{}「」<>〔〕〈〉《》【】〖〗«»‹›❮❯❬❭〘〙·。\\")) 2344 | (skip-chars-backward xpathStops) 2345 | (setq xbeg (point)) 2346 | (goto-char xp0) 2347 | (skip-chars-forward xpathStops) 2348 | (setq xend (point)) 2349 | (goto-char xp0) 2350 | (buffer-substring-no-properties xbeg xend)))) 2351 | (setq xinput2 (if (length> xah-open-file-at-cursor-pre-hook 0) 2352 | (let ((xprehook (run-hook-with-args-until-success 'xah-open-file-at-cursor-pre-hook xinput))) 2353 | (if xprehook xprehook xinput)) 2354 | xinput)) 2355 | 2356 | (setq xpath 2357 | (cond 2358 | ((string-match "\\`file:///[A-Za-z]:/" xinput2) (substring xinput2 8)) 2359 | ((string-match "\\`file://[A-Za-z]:/" xinput2) (substring xinput2 7)) 2360 | (t xinput2))) 2361 | 2362 | (if (string-match-p "\\`https?://" xpath) 2363 | (browse-url xpath) 2364 | (let ((xpathNoQ 2365 | (let ((xHasQuery (string-match "\?[a-z]+=" xpath))) 2366 | (if xHasQuery 2367 | (substring xpath 0 xHasQuery) 2368 | xpath)))) 2369 | (cond 2370 | ((string-match "#" xpathNoQ) 2371 | (let ((xfpath (substring xpathNoQ 0 (match-beginning 0))) 2372 | (xfractPart (substring xpathNoQ (1+ (match-beginning 0))))) 2373 | (if (file-exists-p xfpath) 2374 | (progn 2375 | (find-file xfpath) 2376 | (goto-char (point-min)) 2377 | (search-forward xfractPart)) 2378 | (progn 2379 | (message "File does not exist. Created at\n%s" xfpath) 2380 | (find-file xfpath))))) 2381 | ((string-match "\\`\\(.+?\\):\\([0-9]+\\)\\(:[0-9]+\\)?\\'" xpathNoQ) 2382 | (let ((xfpath (match-string-no-properties 1 xpathNoQ)) 2383 | (xlineNum (string-to-number (match-string-no-properties 2 xpathNoQ)))) 2384 | (if (file-exists-p xfpath) 2385 | (progn 2386 | (find-file xfpath) 2387 | (goto-char (point-min)) 2388 | (forward-line (1- xlineNum))) 2389 | (progn 2390 | (message "File does not exist. Created at\n%s" xfpath) 2391 | (find-file xfpath))))) 2392 | ((file-exists-p xpathNoQ) 2393 | (progn ; open f.ts instead of f.js 2394 | (let ((xext (file-name-extension xpathNoQ)) 2395 | (xfnamecore (file-name-sans-extension xpathNoQ))) 2396 | (if (and (string-equal xext "js") 2397 | (file-exists-p (concat xfnamecore ".ts"))) 2398 | (progn 2399 | (find-file (concat xfnamecore ".ts")) 2400 | (warn "Typescript file .ts exist, opening it")) 2401 | 2402 | (find-file xpathNoQ))))) 2403 | ((file-exists-p (concat xpathNoQ ".el")) 2404 | (find-file (concat xpathNoQ ".el"))) 2405 | (t (progn 2406 | (message "File does not exist. Created at\n%s" xpathNoQ) 2407 | (find-file xpathNoQ)))))))) 2408 | 2409 | ;; s------------------------------ 2410 | 2411 | (defun xah-java-compile-and-run (Filename) 2412 | "Compile and run java of current buffer. 2413 | Buffer is saved first if modified. 2414 | 2415 | This command is designed for a simple single java class source file. 2416 | requires the commands 「javac」 and 「java」. 2417 | 2418 | If compile fails, the error is displayed in a buffer. 2419 | 2420 | URL `http://xahlee.info/emacs/emacs/elisp_run_current_file.html' 2421 | Created: 2024-12-20 2422 | Version: 2024-12-20" 2423 | (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."))) 2424 | (let ((xoutbuf (get-buffer-create "*xah java output*" t)) 2425 | (xjavac-buf (get-buffer-create "*xah java compile output*" t))) 2426 | (with-current-buffer xjavac-buf (erase-buffer)) 2427 | (call-process "javac" nil xjavac-buf nil Filename) 2428 | (if (eq 1 (with-current-buffer xjavac-buf (point-max))) 2429 | (progn 2430 | (with-current-buffer xoutbuf (erase-buffer)) 2431 | (call-process "java" nil xoutbuf nil (file-name-nondirectory (file-name-sans-extension Filename))) 2432 | (display-buffer xoutbuf)) 2433 | (display-buffer xjavac-buf)))) 2434 | 2435 | (defvar xah-run-current-file-dispatch 2436 | (list (cons "el" 'load) 2437 | (cons "elc" 'load) 2438 | (cons "java" 'xah-java-compile-and-run)) 2439 | "A dispatch table used by `xah-run-current-file' to call dedicated function to run code. 2440 | Value is a association list. 2441 | Each item is (EXT . FUNCTION). 2442 | EXT is filename extension (sans the dot), of type string. 2443 | FUNCTION is a elisp function name to call, of type symbol. 2444 | If file extension match, and FUNCTION is defined, call it, pass current buffer's filepath as arg. 2445 | Else, `xah-run-current-file-map' is looked up. 2446 | URL `http://xahlee.info/emacs/emacs/elisp_run_current_file.html' 2447 | ") 2448 | 2449 | (defvar xah-run-current-file-map 2450 | '( 2451 | ;; following are tested as of 2024-12-20 2452 | ("fs" . "dotnet fsi") 2453 | ("fsx" . "dotnet fsi") 2454 | ("go" . "go run") 2455 | ("js" . "deno run") 2456 | ("php" . "php") 2457 | ("pl" . "perl") 2458 | ("ps1" . "pwsh") 2459 | ("py" . "python") 2460 | ("py2" . "python2") 2461 | ("py3" . "python3") 2462 | ("rb" . "ruby") 2463 | ("ts" . "deno run") 2464 | ("m" . "wolframscript -print all -file") 2465 | ("wl" . "wolframscript -print all -file") 2466 | ("wls" . "wolframscript -print all -file") 2467 | 2468 | ;; following may be outdated 2469 | 2470 | ("clj" . "clj") 2471 | ("hs" . "runhaskell") 2472 | ("latex" . "pdflatex") 2473 | ("ml" . "ocaml") 2474 | ("rkt" . "racket") 2475 | ("sh" . "bash") 2476 | ("tex" . "pdflatex") 2477 | ("tsx" . "tsc") 2478 | ("vbs" . "cscript") 2479 | ("pov" . "povray +R2 +A0.1 +J1.2 +Am2 +Q9 +H480 +W640")) 2480 | "A association list that maps file extension to a command for running the file, used by `xah-run-current-file'. 2481 | Each item is (EXT . PROGRAM). 2482 | EXT is filename extension (sans the dot), type string. 2483 | PROGRAM is program name or path, with command options to run a file, type string. 2484 | A filename is appended after the PROGRAM string as external command to call. 2485 | URL `http://xahlee.info/emacs/emacs/elisp_run_current_file.html' 2486 | ") 2487 | 2488 | (defun xah-run-current-file (Filename) 2489 | "Execute the current file. 2490 | Output is printed to buffer *xah-run output*. 2491 | 2492 | File suffix is used to determine what external command to run, in the variable `xah-run-current-file-map'. 2493 | 2494 | If file is modified, it is auto saved before run. 2495 | 2496 | The variable `xah-run-current-file-dispatch' allows you to customize this command to call other function to run the current file. 2497 | 2498 | URL `http://xahlee.info/emacs/emacs/elisp_run_current_file.html' 2499 | Created: 2020-09-24 2500 | Version: 2025-08-07" 2501 | (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."))) 2502 | (let ((xoutbuf (get-buffer-create "*xah-run output*" t)) 2503 | (xext (file-name-extension Filename)) 2504 | xdispatch) 2505 | (setq xdispatch (assoc xext xah-run-current-file-dispatch)) 2506 | (if xdispatch 2507 | (if (fboundp (cdr xdispatch)) 2508 | (progn 2509 | (message "%s dispatch call %s" real-this-command xdispatch) 2510 | (funcall (cdr xdispatch) Filename)) 2511 | (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)) 2512 | (let ((xappCmdStr (cdr (assoc xext xah-run-current-file-map)))) 2513 | (when (not xappCmdStr) (error "%s: Unknown file extension: %s. check `xah-run-current-file-map'" real-this-command xext)) 2514 | (progn 2515 | (with-current-buffer xoutbuf (erase-buffer)) 2516 | (apply 'start-process (append (list "xah-run" xoutbuf) (split-string xappCmdStr " +" t) (list Filename) nil)) 2517 | ;; (display-buffer xoutbuf) 2518 | (pop-to-buffer xoutbuf) 2519 | ;; (with-current-buffer xoutbuf (goto-char (point-min))) 2520 | ))))) 2521 | 2522 | (defun xah-clean-empty-lines () 2523 | "Replace repeated blank lines to just 1, in whole buffer or selection. 2524 | Respects `narrow-to-region'. 2525 | 2526 | URL `http://xahlee.info/emacs/emacs/elisp_compact_empty_lines.html' 2527 | Created: 2017-09-22 2528 | Version: 2020-09-08" 2529 | (interactive) 2530 | (let (xbegin xend) 2531 | (if (region-active-p) 2532 | (setq xbegin (region-beginning) xend (region-end)) 2533 | (setq xbegin (point-min) xend (point-max))) 2534 | (save-excursion 2535 | (save-restriction 2536 | (narrow-to-region xbegin xend) 2537 | (progn 2538 | (goto-char (point-min)) 2539 | (while (re-search-forward "\n\n\n+" nil 1) 2540 | (replace-match "\n\n"))))))) 2541 | 2542 | (defun xah-clean-whitespace (&optional Begin End) 2543 | "Delete trailing whitespace, and replace repeated blank lines to just 1. 2544 | Only space and tab is considered whitespace here. 2545 | Works on whole buffer or selection, respects `narrow-to-region'. 2546 | 2547 | URL `http://xahlee.info/emacs/emacs/elisp_compact_empty_lines.html' 2548 | Created: 2017-09-22 2549 | Version: 2025-05-04" 2550 | (interactive) 2551 | (let (xbeg xend) 2552 | (seq-setq (xbeg xend) 2553 | (if (and Begin End) 2554 | (list Begin End) 2555 | (if (region-active-p) 2556 | (list (region-beginning) (region-end)) 2557 | (list (point-min) (point-max))))) 2558 | (save-excursion 2559 | (save-restriction 2560 | (narrow-to-region xbeg xend) 2561 | (progn 2562 | (goto-char (point-min)) 2563 | (while (re-search-forward "[ \t]+\n" nil t) (replace-match "\n"))) 2564 | (progn 2565 | (goto-char (point-min)) 2566 | (while (re-search-forward "\n\n\n+" nil t) (replace-match "\n\n"))) 2567 | (progn 2568 | (goto-char (point-max)) 2569 | (while (eq (char-before) 32) (delete-char -1)))))) 2570 | (message "done xah-clean-whitespace")) 2571 | 2572 | (defun xah-make-backup () 2573 | "Make a backup copy of current file or dired marked files. 2574 | If in dired, backup current file or marked files. 2575 | 2576 | The backup file name is the original name with the datetime .yyyymmddhhmmss~ appended. 2577 | 2578 | Overwrite existing file. 2579 | 2580 | If the current buffer is not associated with a file, do nothing. 2581 | 2582 | URL `http://xahlee.info/emacs/emacs/elisp_make-backup.html' 2583 | Created: 2018-06-06 2584 | Version: 2025-07-22" 2585 | (interactive) 2586 | (let ((xtimestamp (format-time-string "%Y%m%d%H%M%S"))) 2587 | (if buffer-file-name 2588 | (let ((xbackupName (concat buffer-file-name "." xtimestamp "~"))) 2589 | (copy-file buffer-file-name xbackupName t) 2590 | (message "\nBackup saved at: %s" xbackupName)) 2591 | (if (eq major-mode 'dired-mode) 2592 | (progn 2593 | (mapc (lambda (xx) 2594 | (let ((xbackupName 2595 | (concat xx "." xtimestamp "~"))) 2596 | (copy-file xx xbackupName t))) 2597 | (dired-get-marked-files)) 2598 | (revert-buffer)) 2599 | (user-error "xah-make-backup: buffer not file nor dired"))))) 2600 | 2601 | (defun xah-make-backup-and-save () 2602 | "Backup of current file and save, or backup dired marked files. 2603 | For detail, see `xah-make-backup'. 2604 | If the current buffer is not associated with a file nor dired, nothing's done. 2605 | 2606 | URL `http://xahlee.info/emacs/emacs/elisp_make-backup.html' 2607 | Created: 2015-10-14 2608 | Version: 2025-09-26" 2609 | (interactive) 2610 | (if buffer-file-name 2611 | (progn 2612 | (let ((xtimestamp (format-time-string "%Y%m%d%H%M%S"))) 2613 | (let ((xbackupName (concat buffer-file-name "." xtimestamp "~"))) 2614 | (copy-file buffer-file-name xbackupName t) 2615 | (message "\nBackup saved at: %s" xbackupName))) 2616 | (when (buffer-modified-p) 2617 | (save-buffer))) 2618 | (progn 2619 | (xah-make-backup)))) 2620 | 2621 | (defun xah-delete-current-file-make-backup () 2622 | "Makes a backup~, delete current file, close the buffer. 2623 | 2624 | Backup filename is ‹name›.‹dateTimeStamp›~ 2625 | Overwrite existing file. 2626 | 2627 | If buffer is not a file, copy content to `kill-ring', delete buffer. 2628 | 2629 | If buffer is not a file, the backup file name starts with “xx_”. 2630 | 2631 | Call `xah-open-last-closed' to open the backup file. 2632 | 2633 | URL `http://xahlee.info/emacs/emacs/elisp_delete-current-file.html' 2634 | Created: 2018-05-15 2635 | Version: 2024-10-21" 2636 | (interactive) 2637 | (when (eq major-mode 'dired-mode) 2638 | (user-error "%s: In dired. Nothing is done." real-this-command)) 2639 | (let ((xfname buffer-file-name) 2640 | (xbuffname (buffer-name))) 2641 | (if xfname 2642 | (let ((xbackupPath 2643 | (concat 2644 | xfname "." 2645 | (format-time-string "%Y%m%d%H%M%S") "~"))) 2646 | (save-buffer xfname) 2647 | (kill-buffer xbuffname) 2648 | (rename-file xfname xbackupPath t) 2649 | (message "File deleted. 2650 | Backup at 2651 | %s 2652 | Call `xah-open-last-closed' to open." xbackupPath) 2653 | (when (boundp 'xah-recently-closed-buffers) 2654 | (push (cons nil xbackupPath) xah-recently-closed-buffers))) 2655 | (progn 2656 | (widen) 2657 | (kill-new (buffer-string)) 2658 | (kill-buffer xbuffname) 2659 | (message "non-file buffer killed. buffer text copied to `kill-ring'.")))) 2660 | (when (eq major-mode 'dired-mode) (revert-buffer))) 2661 | 2662 | ;; s------------------------------ 2663 | 2664 | (defun xah-search-current-word () 2665 | "Call `isearch' on current word or selection. 2666 | “word” here is A to Z, a to z, and hyphen [-] and lowline [_], independent of syntax table. 2667 | 2668 | URL `http://xahlee.info/emacs/emacs/emacs_search_current_word.html' 2669 | Created: 2010-05-29 2670 | Version: 2025-09-15" 2671 | (interactive) 2672 | (let (xbeg xend) 2673 | (if (region-active-p) 2674 | (setq xbeg (region-beginning) xend (region-end)) 2675 | (save-excursion 2676 | (skip-chars-backward "-_A-Za-z0-9") 2677 | (setq xbeg (point)) 2678 | (right-char) 2679 | (skip-chars-forward "-_A-Za-z0-9") 2680 | (setq xend (point)))) 2681 | (deactivate-mark) 2682 | (when (< xbeg (point)) (goto-char xbeg)) 2683 | (isearch-mode t) 2684 | (isearch-yank-string (buffer-substring-no-properties xbeg xend)))) 2685 | 2686 | (defun xah-fly-cancel () 2687 | "Cancle selection or call `minibuffer-keyboard-quit' and `keyboard-quit'. 2688 | This command is intended to replace key C-g , but not always work. Sometimes you still need to press C-g to cancel or abort or exit some commands. 2689 | 2690 | Created: 2025-08-01 2691 | Version: 2025-08-03" 2692 | (interactive) 2693 | (if (minibufferp (current-buffer)) 2694 | (progn (minibuffer-keyboard-quit)) 2695 | (if (region-active-p) 2696 | (progn (deactivate-mark)) 2697 | (progn (keyboard-quit))))) 2698 | 2699 | (declare-function w32-shell-execute "w32fns.c" (operation document &optional parameters show-flag)) ; (w32-shell-execute "open" default-directory) 2700 | 2701 | (defun xah-show-in-desktop () 2702 | "Show current file in desktop. 2703 | (Mac Finder, Microsoft Windows File Explorer, Linux file manager) 2704 | This command can be called when in a file buffer or in `dired'. 2705 | 2706 | URL `http://xahlee.info/emacs/emacs/emacs_show_in_desktop.html' 2707 | Created: 2020-11-20 2708 | Version: 2023-09-09" 2709 | (interactive) 2710 | (let ((xpath (if (eq major-mode 'dired-mode) 2711 | (if (eq nil (dired-get-marked-files)) 2712 | default-directory 2713 | (car (dired-get-marked-files))) 2714 | (if buffer-file-name buffer-file-name default-directory)))) 2715 | (cond 2716 | ((eq system-type 'windows-nt) 2717 | (shell-command (format "PowerShell -Command invoke-item '%s'" default-directory )) 2718 | ;; (let ((xcmd (format "Explorer /select,%s" 2719 | ;; (replace-regexp-in-string "/" "\\" xpath t t) 2720 | ;; ;; (shell-quote-argument (replace-regexp-in-string "/" "\\" xpath t t )) 2721 | ;; ))) 2722 | ;; (shell-command xcmd)) 2723 | ) 2724 | ((eq system-type 'darwin) 2725 | (shell-command 2726 | (concat "open -R " (shell-quote-argument xpath)))) 2727 | ((eq system-type 'gnu/linux) 2728 | (call-process shell-file-name nil 0 nil 2729 | shell-command-switch 2730 | (format "%s %s" 2731 | "xdg-open" 2732 | (file-name-directory xpath))) 2733 | ;; (shell-command "xdg-open .") ;; 2013-02-10 this sometimes froze emacs till the folder is closed. eg with nautilus 2734 | )))) 2735 | 2736 | (defun xah-open-in-vscode () 2737 | "Open current file or dir in vscode. 2738 | URL `http://xahlee.info/emacs/emacs/emacs_open_in_vscode.html' 2739 | 2740 | Created: 2020-02-13 2741 | Version: 2024-12-15" 2742 | (interactive) 2743 | (let ((xpath (or buffer-file-name default-directory))) 2744 | (message "path is %s" xpath) 2745 | (cond 2746 | ((eq system-type 'darwin) 2747 | (shell-command (format "open -a Visual\\ Studio\\ Code.app %s" (shell-quote-argument xpath)))) 2748 | ((eq system-type 'windows-nt) 2749 | (shell-command (format "code.cmd %s" (shell-quote-argument xpath)))) 2750 | ((eq system-type 'gnu/linux) 2751 | (shell-command (format "code %s" (shell-quote-argument xpath))))))) 2752 | 2753 | (defun xah-open-in-external-app (&optional Fname) 2754 | "Open the current file or dired marked files in external app. 2755 | When called in emacs lisp, if Fname is given, open that. 2756 | 2757 | URL `http://xahlee.info/emacs/emacs/emacs_dired_open_file_in_ext_apps.html' 2758 | Created: 2019-11-04 2759 | Version: 2025-04-18" 2760 | (interactive) 2761 | (let (xfileList xdoIt) 2762 | (setq xfileList 2763 | (if Fname 2764 | (list Fname) 2765 | (if (eq major-mode 'dired-mode) 2766 | (dired-get-marked-files) 2767 | (list buffer-file-name)))) 2768 | (setq xdoIt (if (length< xfileList 10) t (y-or-n-p "Open more than 10 files? "))) 2769 | (when xdoIt 2770 | (cond 2771 | ((eq system-type 'windows-nt) 2772 | (let ((xoutbuf (get-buffer-create "*xah open in external app*")) 2773 | (xcmdlist (list "PowerShell" "-Command" "Invoke-Item" "-LiteralPath"))) 2774 | (mapc 2775 | (lambda (x) 2776 | (apply 'start-process (append (list "xah open in external app" xoutbuf) xcmdlist (list (format "'%s'" (string-replace "'" "`'" x))) nil))) 2777 | xfileList))) 2778 | ((eq system-type 'darwin) 2779 | (mapc (lambda (xfpath) (shell-command (concat "open " (shell-quote-argument xfpath)))) xfileList)) 2780 | ((eq system-type 'gnu/linux) 2781 | (mapc (lambda (xfpath) 2782 | (call-process shell-file-name nil 0 nil 2783 | shell-command-switch 2784 | (format "%s %s" 2785 | "xdg-open" 2786 | (shell-quote-argument xfpath)))) 2787 | xfileList)) 2788 | ((eq system-type 'berkeley-unix) 2789 | (mapc (lambda (xfpath) (let ((process-connection-type nil)) (start-process "" nil "xdg-open" xfpath))) xfileList)))))) 2790 | 2791 | (defvar xah-fly-mswin-terminal 2792 | "wt" 2793 | "A string. Value should be one of: wt (for Windows Terminal) or pwsh (for PowerShell Core (cross-platform)) or powershell (for Microsoft PowerShell).") 2794 | 2795 | (defun xah-open-in-terminal () 2796 | "Open the current dir in a new terminal window. 2797 | On Microsoft Windows, which terminal it starts depends on `xah-fly-mswin-terminal'. 2798 | 2799 | URL `http://xahlee.info/emacs/emacs/emacs_open_in_terminal.html' 2800 | Created: 2020-11-21 2801 | Version: 2023-06-26" 2802 | (interactive) 2803 | (cond 2804 | ((eq system-type 'windows-nt) 2805 | (cond 2806 | ((string-equal xah-fly-mswin-terminal "wt") 2807 | (shell-command (format "wt -d \"%s\"" default-directory))) 2808 | ((string-equal xah-fly-mswin-terminal "pwsh") 2809 | (shell-command 2810 | (format "pwsh -Command Start-Process pwsh -WorkingDirectory '%s'" (shell-quote-argument default-directory)))) 2811 | ((string-equal xah-fly-mswin-terminal "powershell") 2812 | (shell-command 2813 | (format "powershell -Command Start-Process powershell -WorkingDirectory '%s'" (shell-quote-argument default-directory)))) 2814 | (t (error "Error 702919: value of `xah-fly-mswin-terminal' is not expected. Its value is %s" xah-fly-mswin-terminal)))) 2815 | ((eq system-type 'darwin) 2816 | (shell-command (concat "open -a terminal " (shell-quote-argument default-directory)))) 2817 | ((eq system-type 'gnu/linux) 2818 | (let ((process-connection-type nil)) (start-process "" nil "x-terminal-emulator" (concat "--working-directory=" default-directory)))) 2819 | ((eq system-type 'berkeley-unix) 2820 | (let ((process-connection-type nil)) (start-process "" nil "x-terminal-emulator" (concat "--working-directory=" default-directory)))))) 2821 | 2822 | (defun xah-next-window-or-frame () 2823 | "Switch to next window or frame. 2824 | If current frame has only one window, switch to next frame. 2825 | If `universal-argument' is called first, do switch frame. 2826 | Version: 2017-01-27" 2827 | (interactive) 2828 | (if current-prefix-arg 2829 | (other-frame 1) 2830 | (if (one-window-p) 2831 | (other-frame 1) 2832 | (other-window 1)))) 2833 | 2834 | (defun xah-unsplit-window-or-next-frame () 2835 | "Unsplit window. If current frame has only one window, switch to next frame. 2836 | Version: 2017-01-29" 2837 | (interactive) 2838 | (if (one-window-p) 2839 | (other-frame 1) 2840 | (delete-other-windows))) 2841 | 2842 | ;; s------------------------------ 2843 | ;; layout lookup tables for key conversion 2844 | 2845 | (defvar xah-fly-layout-diagrams (make-hash-table :test 'equal) 2846 | "A hashtable. 2847 | Key is string, of keyboard layout name. 2848 | Value is a string, of text-art (aka ASCII-art) form of the layout. 2849 | The text-art string, each key (sequence of chars) is separated by whitespace. 2850 | The string is split by white space, and each item is considered a key. 2851 | The key is usually a single char, can be unicode, but may also be alt ctrl tab shift return and other. 2852 | It is used to generate key conversion table of a key from layout to layout. 2853 | ") 2854 | 2855 | (progn 2856 | 2857 | (puthash "adnw" " 2858 | ~ ! @ # $ % ^ & * ( ) { } 2859 | ` 1 2 3 4 5 6 7 8 9 0 [ ] 2860 | 2861 | k u ü . ä v g c l j f = \\ 2862 | h i e a o d t r n s ß 2863 | x y ö , q b p w m z 2864 | 2865 | K U Ü > Ä V G C L J F + | 2866 | H I E A O D T R N S ẞ 2867 | X Y Ö < Q B P W M Z 2868 | " xah-fly-layout-diagrams) 2869 | 2870 | (puthash "azerty" 2871 | " 2872 | ~ ! @ # $ % ^ & * ( ) { } 2873 | ² & é \" ' ( - è _ ç à ) = 2874 | 2875 | a z e r t y u i o p ^ $ * 2876 | q s d f g h j k l m ù 2877 | w x c v b n , ; : ! 2878 | 2879 | A Z E R T Y U I O P ? + | 2880 | Q S D F G H J K L M Ù 2881 | W X C V B N ? . / § 2882 | " xah-fly-layout-diagrams) 2883 | 2884 | (puthash "azerty-be" 2885 | " 2886 | ~ ! @ # $ % ^ & * ( ) { } 2887 | ² & é \" ' ( § è ! ç à ) - 2888 | 2889 | a z e r t y u i o p ^ $ µ 2890 | q s d f g h j k l m ù 2891 | w x c v b n , ; : = 2892 | 2893 | A Z E R T Y U I O P ^ $ Μ 2894 | Q S D F G H J K L M Ù 2895 | W X C V B N ? . / + 2896 | " xah-fly-layout-diagrams) 2897 | 2898 | (puthash "bepo" " 2899 | # 1 2 3 4 5 6 7 8 9 0 ° ` 2900 | $ \" « » ( ) @ + - / * = % 2901 | 2902 | b é p o è ^ v d l j z w \\ 2903 | a u i e , c t s r n m 2904 | à y x . k ' q g h f 2905 | 2906 | B É P O È ! V D L J Z W | 2907 | A U I E ; C T S R N M 2908 | À Y X : K ? Q G H F 2909 | " xah-fly-layout-diagrams) 2910 | 2911 | (puthash "colemak" " 2912 | ~ ! @ # $ % ^ & * ( ) _ + 2913 | ` 1 2 3 4 5 6 7 8 9 0 - = 2914 | 2915 | q w f p g j l u y ; [ ] \\ 2916 | a r s t d h n e i o ' 2917 | z x c v b k m , . / 2918 | 2919 | Q W F P G J L U Y : { } | 2920 | A R S T D H N E I O \" 2921 | Z X C V B K M < > ? 2922 | " xah-fly-layout-diagrams) 2923 | 2924 | (puthash "colemak-dh" " 2925 | ~ ! @ # $ % ^ & * ( ) _ + 2926 | ` 1 2 3 4 5 6 7 8 9 0 - = 2927 | 2928 | q w f p b j l u y ; [ ] \\ 2929 | a r s t g m n e i o ' 2930 | z x c d v k h , . / 2931 | 2932 | Q W F P B J L U Y : { } | 2933 | A R S T G M N E I O \" 2934 | Z X C D V K H < > ? 2935 | " xah-fly-layout-diagrams) 2936 | 2937 | (puthash "dvorak" " 2938 | ~ ! @ # $ % ^ & * ( ) { } 2939 | ` 1 2 3 4 5 6 7 8 9 0 [ ] 2940 | 2941 | ' , . p y f g c r l / = \\ 2942 | a o e u i d h t n s - 2943 | ; q j k x b m w v z 2944 | 2945 | \" < > P Y F G C R L ? + | 2946 | A O E U I D H T N S _ 2947 | : Q J K X B M W V Z 2948 | " xah-fly-layout-diagrams) 2949 | 2950 | (puthash "engrammer" " 2951 | ~ ! @ # $ % ^ & * ( ) { } 2952 | ` 1 2 3 4 5 6 7 8 9 0 [ ] 2953 | 2954 | b y o u ' ; l d w v z = \\ 2955 | c i e a , . h t s n q 2956 | g x j k - / r m f p 2957 | 2958 | B Y O U \" : L D W V Z + | 2959 | C I E A < > H T S N Q 2960 | G X J K _ ? R M F P 2961 | " xah-fly-layout-diagrams) 2962 | 2963 | (puthash "koy" " 2964 | ^ ! @ # $ % ^ & * ( ) _ ~ 2965 | ˘ 1 2 3 4 5 6 7 8 9 0 - ` 2966 | 2967 | k . o , y v g c l ß / = \\ 2968 | h a e i u d t r n s f 2969 | x q ä ü ö b p w m j 2970 | 2971 | K > O < Y V G C L ẞ ? + | 2972 | H A E I U D T R N S F 2973 | X Q Ä Ü Ö B P W M J 2974 | " xah-fly-layout-diagrams) 2975 | 2976 | (puthash "halmak" " 2977 | ~ ! @ # $ % ^ & * ( ) _ + 2978 | ` 1 2 3 4 5 6 7 8 9 0 - = 2979 | 2980 | w l r b z ; q u d j [ ] \\ 2981 | s h n t , . a e o i ' 2982 | f m v c / g p x k y 2983 | 2984 | W L R B Z : Q U D J { } | 2985 | S H N T < > A E O I \" 2986 | F M V C ? G P X K Y 2987 | " xah-fly-layout-diagrams) 2988 | 2989 | (puthash "minimak" " 2990 | ~ ! @ # $ % ^ & * ( ) _ + 2991 | ` 1 2 3 4 5 6 7 8 9 0 - = 2992 | 2993 | q w d r k y u i o p [ ] \\ 2994 | a s t f g h j e l ; ' 2995 | z x c v b n m , . / 2996 | 2997 | Q W D R K Y U I O P { } | 2998 | A S T F G H J E L : \" 2999 | Z X C V B N M < > ? 3000 | " xah-fly-layout-diagrams) 3001 | 3002 | (puthash "neo2" " 3003 | ˇ ° § ℓ » « $ € „ “ ” — ¸ 3004 | ^ 1 2 3 4 5 6 7 8 9 0 - ` 3005 | 3006 | x v l c w k h g f q ß ´ \\ 3007 | u i a e o s n r t d y 3008 | ü ö ä p z b m , . j 3009 | 3010 | X V L C W K H G F Q ẞ ~ | 3011 | U I A E O S N R T D Y 3012 | Ü Ö Ä P Z B M – • J 3013 | " xah-fly-layout-diagrams) 3014 | 3015 | (puthash "norman" " 3016 | ~ ! @ # $ % ^ & * ( ) _ + 3017 | ` 1 2 3 4 5 6 7 8 9 0 - = 3018 | 3019 | q w d f k j u r l ; [ ] \\ 3020 | a s e t g y n i o h ' 3021 | z x c v b p m , . / 3022 | 3023 | Q W D F K J U R L : { } | 3024 | A S E T G Y N I O H \" 3025 | Z X C V B P M < > ? 3026 | " xah-fly-layout-diagrams) 3027 | 3028 | (puthash "programer-dvorak" " 3029 | ~ % 7 5 3 1 9 0 2 4 6 8 ` 3030 | $ & [ { } ( = * ) + ] ! # 3031 | 3032 | ; , . p y f g c r l / @ \\ 3033 | a o e u i d h t n s - 3034 | ' q j k x b m w v z 3035 | 3036 | : < > P Y F G C R L ? ^ | 3037 | A O E U I D H T N S _ 3038 | \" Q J K X B M W V Z 3039 | " xah-fly-layout-diagrams) 3040 | 3041 | (puthash "pt-nativo" " 3042 | * ! \" # $ % & / ( ) = ª > 3043 | + 1 2 3 4 5 6 7 8 9 0 º < 3044 | 3045 | ' , . h x w l t c p ~ - \\ 3046 | i e a o u m d s r n ´ 3047 | « ç j b k q v g f z 3048 | 3049 | ? ; : H X W L T C P ^ _ | 3050 | I E A O U M D S R N ` 3051 | Y Ç J B K Q V G F Z 3052 | " xah-fly-layout-diagrams) 3053 | 3054 | (puthash "qfmlwy" " 3055 | ~ ! @ # $ % ^ & * ( ) _ + 3056 | ` 1 2 3 4 5 6 7 8 9 0 - = 3057 | 3058 | q f m l w y u o b j [ ] \\ 3059 | d s t n r i a e h ; ' 3060 | z v g c x p k , . / 3061 | 3062 | Q F M L W Y U O B J { } | 3063 | D S T N R I A E H : \" 3064 | Z V G C X P K < > ? 3065 | " xah-fly-layout-diagrams) 3066 | 3067 | (puthash "qgmlwb" " 3068 | ~ ! @ # $ % ^ & * ( ) _ + 3069 | ` 1 2 3 4 5 6 7 8 9 0 - = 3070 | 3071 | q g m l w b y u v ; [ ] \\ 3072 | d s t n r i a e o h ' 3073 | z x c f j k p , . / 3074 | 3075 | Q G M L W B Y U V : { } | 3076 | D S T N R I A E O H \" 3077 | Z X C F J K P < > ? 3078 | " xah-fly-layout-diagrams) 3079 | 3080 | (puthash "qwerty" " 3081 | ~ ! @ # $ % ^ & * ( ) _ + 3082 | ` 1 2 3 4 5 6 7 8 9 0 - = 3083 | 3084 | q w e r t y u i o p [ ] \\ 3085 | a s d f g h j k l ; ' 3086 | z x c v b n m , . / 3087 | 3088 | Q W E R T Y U I O P { } | 3089 | A S D F G H J K L : \" 3090 | Z X C V B N M < > ? 3091 | " xah-fly-layout-diagrams) 3092 | 3093 | (puthash "qwerty-abnt" " 3094 | \" ! @ # $ % ^ & * ( ) _ + 3095 | ' 1 2 3 4 5 6 7 8 9 0 - = 3096 | 3097 | q w e r t y u i o p ´ [ ] 3098 | a s d f g h j k l ç ~ 3099 | z x c v b n m , . ; 3100 | 3101 | Q W E R T Y U I O P ` + | 3102 | A S D F G H J K L Ç ^ 3103 | Z X C V B N M < > : 3104 | " xah-fly-layout-diagrams) 3105 | 3106 | (puthash "qwerty-no" " 3107 | § ! \" # ¤ % & / ( ) = ? ` 3108 | | 1 2 3 4 5 6 7 8 9 0 + \\ 3109 | 3110 | q w e r t y u i o p å ¨ ' 3111 | a s d f g h j k l ø æ 3112 | z x c v b n m , . - 3113 | 3114 | Q W E R T Y U I O P Å ^ * 3115 | A S D F G H J K L Ø Æ 3116 | Z X C V B N M < > _ 3117 | " xah-fly-layout-diagrams) 3118 | 3119 | (puthash "qwerty-se" " 3120 | § ! \" # ¤ % & / ( ) = ? ` 3121 | | 1 2 3 4 5 6 7 8 9 0 + \\ 3122 | 3123 | q w e r t y u i o p å ¨ ' 3124 | a s d f g h j k l ö ä 3125 | z x c v b n m , . - 3126 | 3127 | Q W E R T Y U I O P Å ^ * 3128 | A S D F G H J K L Ö Ä 3129 | Z X C V B N M < > _ 3130 | " xah-fly-layout-diagrams) 3131 | 3132 | (puthash "qwertz" " 3133 | ~ ! @ # $ % ^ & * ( ) _ + 3134 | ` 1 2 3 4 5 6 7 8 9 0 - = 3135 | 3136 | q w e r t z u i o p [ ] \\ 3137 | a s d f g h j k l ; ' 3138 | y x c v b n m , . / 3139 | 3140 | Q W E R T Z U I O P { } | 3141 | A S D F G H J K L : \" 3142 | Y X C V B N M < > ? 3143 | " xah-fly-layout-diagrams) 3144 | 3145 | (puthash "qwpr" " 3146 | ~ ! @ # $ % ^ & * ( ) _ + 3147 | ` 1 2 3 4 5 6 7 8 9 0 - = 3148 | 3149 | q w p r f y u k l ; [ ] \\ 3150 | a s d t g h n i o e ' 3151 | z x c v b j m , . / 3152 | 3153 | Q W P R F Y U K L : { } | 3154 | A S D T G H N I O E \" 3155 | Z X C V B J M < > ? 3156 | " xah-fly-layout-diagrams) 3157 | 3158 | (puthash "russian" " 3159 | Ё ! \" № ; % : ? * ( ) _ + 3160 | ё 1 2 3 4 5 6 7 8 9 0 - = 3161 | 3162 | й ц у к е н г ш щ з х ъ \\ 3163 | ф ы в а п р о л д ж э 3164 | я ч с м и т ь б ю . 3165 | 3166 | Й Ц У К Е Н Г Ш Щ З Х Ъ | 3167 | Ф Ы В А П Р О Л Д Ж Э 3168 | Я Ч С М И Т Ь Б Ю , 3169 | " xah-fly-layout-diagrams) 3170 | 3171 | (puthash "workman" " 3172 | ~ ! @ # $ % ^ & * ( ) _ + 3173 | ` 1 2 3 4 5 6 7 8 9 0 - = 3174 | 3175 | q d r w b j f u p ; [ ] \\ 3176 | a s h t g y n e o i ' 3177 | z x m c v k l , . / 3178 | 3179 | Q D R W B J F U P : { } | 3180 | A S H T G Y N E O I \" 3181 | Z X M C V K L < > ? 3182 | " xah-fly-layout-diagrams)) 3183 | 3184 | (defun xah-fly-create-key-conv-table (Layout1 Layout2) 3185 | "Takes two text diagrams Layout1 Layout2, return a hashtable. 3186 | For remapping key from layout1 to layout2. 3187 | 3188 | example: 3189 | 3190 | Layout1 is a string. e.g. 3191 | 3192 | a b c d 3193 | e f shift 3194 | 3195 | Layout2 is a string. e.g. 3196 | 3197 | a o e i 3198 | m n ctrl 3199 | 3200 | return a hashtable, e.g. 3201 | 3202 | b → o 3203 | c → e 3204 | d → i 3205 | e → m 3206 | f → n 3207 | shift → ctrl 3208 | 3209 | If the keys in layouts are the same, it's not in the table. 3210 | 3211 | Created: 2024-04-19 3212 | Version: 2025-06-05" 3213 | (let (xkeys1 xkeys2 (xtable (make-hash-table :test 'equal))) 3214 | (setq xkeys1 (split-string Layout1 "[ \n]+" t)) 3215 | (setq xkeys2 (split-string Layout2 "[ \n]+" t)) 3216 | (when (not (eq (length xkeys1) (length xkeys2))) 3217 | (error "layout %s and %s lengths not same." (length Layout1) (length Layout2))) 3218 | (seq-mapn 3219 | (lambda (x y) 3220 | (if (string-equal x y) 3221 | nil 3222 | (puthash x y xtable))) 3223 | xkeys1 xkeys2) 3224 | xtable)) 3225 | 3226 | ;; (xah-fly-create-key-conv-table 3227 | ;; (gethash "qwerty" xah-fly-layout-diagrams) 3228 | ;; (gethash "dvorak" xah-fly-layout-diagrams)) 3229 | 3230 | (defvar xah-fly-key-current-layout nil 3231 | "The current keyboard layout. 3232 | Value is a key in `xah-fly-layout-diagrams'. 3233 | Do not set this variable manually. 3234 | Use `xah-fly-keys-set-layout' to set it. 3235 | Default to qwerty. 3236 | Version: 2022-10-22") 3237 | 3238 | (if xah-fly-key-current-layout nil (setq xah-fly-key-current-layout "qwerty")) 3239 | 3240 | (defvar xah-fly--key-convert-table nil 3241 | "A hashtable that's the conversion table from dvorak to current layout. 3242 | Value is a hashtable. 3243 | Created: 2019-02-12 3244 | Version: 2025-07-11" ) 3245 | 3246 | (setq 3247 | xah-fly--key-convert-table 3248 | (xah-fly-create-key-conv-table 3249 | (gethash "dvorak" xah-fly-layout-diagrams) 3250 | (gethash xah-fly-key-current-layout xah-fly-layout-diagrams))) 3251 | 3252 | (defun xah-fly--convert-key (Keystr) 3253 | "Return the corresponding Keystr according to variable `xah-fly--key-convert-table'. 3254 | Keystr must be a string that is the valid argument to `kbd'. 3255 | Keystr may be a single key, or key sequence separated by whitespaces. 3256 | Created: 2022-10-25 3257 | Version: 2024-04-22" 3258 | (interactive) 3259 | (mapconcat 3260 | 'identity 3261 | (mapcar 3262 | (lambda (x) 3263 | (let ((xnew (gethash x xah-fly--key-convert-table))) 3264 | (if xnew xnew x))) 3265 | (split-string Keystr " +")) 3266 | " ")) 3267 | 3268 | (defun xah-fly--define-keys (KeymapName KeyCmdAlist &optional Direct-p) 3269 | "Map `define-key' over a alist KeyCmdAlist, with key layout remap. 3270 | The key is remapped from Dvorak to the current keyboard layout by `xah-fly--convert-key'. 3271 | If Direct-p is t, do not remap key to current keyboard layout. 3272 | Example usage: 3273 | (xah-fly--define-keys 3274 | (define-prefix-command 'xyz-map) 3275 | '( 3276 | (\"h\" . highlight-symbol-at-point) 3277 | (\".\" . isearch-forward-symbol-at-point) 3278 | (\"w\" . isearch-forward-word))) 3279 | Created: 2020-04-18 3280 | Version: 2025-10-07" 3281 | (mapcar 3282 | (lambda (x) 3283 | (define-key 3284 | KeymapName 3285 | (kbd (if Direct-p (car x) (xah-fly--convert-key (car x)))) 3286 | (cdr x))) 3287 | KeyCmdAlist)) 3288 | 3289 | ;; s------------------------------ 3290 | ;; keymaps 3291 | 3292 | (defvar xah-fly-key-map (make-sparse-keymap) 3293 | "If `xah-fly-insert-state-p' is true, point to `xah-fly-insert-map', else, points to `xah-fly-command-map'.") 3294 | 3295 | (defvar xah-fly-command-map (make-sparse-keymap) 3296 | "Keymap when in command mode.") 3297 | 3298 | (defvar xah-fly-insert-map (make-sparse-keymap) 3299 | "Keymap when in insert mode.") 3300 | 3301 | (defvar xah-fly--deactivate-command-mode-func nil) 3302 | 3303 | ;; s------------------------------ 3304 | ;; setting keys 3305 | 3306 | (defun xah-fly-define-keys () 3307 | "Define the keys for xah-fly-keys. 3308 | Created: 2022-10-31 3309 | Version: 2024-04-22" 3310 | (interactive) 3311 | (let () 3312 | 3313 | ;; Movement key integrations with built-in Emacs packages 3314 | (xah-fly--define-keys 3315 | indent-rigidly-map 3316 | '(("h" . indent-rigidly-left) 3317 | ("n" . indent-rigidly-right))) 3318 | 3319 | (xah-fly--define-keys 3320 | xah-fly-command-map 3321 | '(("<escape>" . xah-fly-command-mode-activate)) 3322 | :direct) 3323 | 3324 | (xah-fly--define-keys 3325 | xah-fly-insert-map 3326 | '(("<escape>" . xah-fly-command-mode-activate)) 3327 | :direct) 3328 | 3329 | (when xah-fly-use-isearch-arrows 3330 | (xah-fly--define-keys 3331 | isearch-mode-map 3332 | '(("<up>" . isearch-ring-retreat) 3333 | ("<down>" . isearch-ring-advance) 3334 | ("<left>" . isearch-repeat-backward) 3335 | ("<right>" . isearch-repeat-forward) 3336 | ("C-v" . isearch-yank-kill)) 3337 | :direct) 3338 | (xah-fly--define-keys 3339 | minibuffer-local-isearch-map 3340 | '(("<left>" . isearch-reverse-exit-minibuffer) 3341 | ("<right>" . isearch-forward-exit-minibuffer)) 3342 | :direct)) 3343 | 3344 | (xah-fly--define-keys 3345 | (define-prefix-command 'xah-fly-leader-key-map) 3346 | '(("SPC" . xah-fly-insert-mode-activate) 3347 | ("RET" . execute-extended-command) 3348 | 3349 | ("TAB" . nil) 3350 | 3351 | ("TAB TAB" . indent-for-tab-command) 3352 | 3353 | ("TAB i" . complete-symbol) 3354 | ("TAB g" . indent-rigidly) 3355 | ("TAB r" . indent-region) 3356 | ("TAB s" . indent-sexp) 3357 | 3358 | (". ." . highlight-symbol-at-point) 3359 | (". g" . unhighlight-regexp) 3360 | (". c" . highlight-lines-matching-regexp) 3361 | (". h" . highlight-regexp) 3362 | (". t" . highlight-phrase) 3363 | (". p" . isearch-forward-symbol-at-point) 3364 | (". u" . isearch-forward-symbol) 3365 | (". e" . isearch-forward-word) 3366 | 3367 | ("'" . xah-fill-or-unfill) 3368 | 3369 | (", t" . xref-find-definitions) 3370 | (", n" . xref-pop-marker-stack) 3371 | 3372 | ;; - / ; = [ 3373 | ("\\" . toggle-input-method) 3374 | ;; ` 3375 | 3376 | ("a" . mark-whole-buffer) 3377 | ("b" . end-of-buffer) 3378 | 3379 | ("c ," . xah-open-in-external-app) 3380 | ("c ." . find-file) 3381 | ("c c" . bookmark-bmenu-list) 3382 | ("c e" . ibuffer) 3383 | ("c f" . xah-open-recently-closed) 3384 | ("c g" . xah-open-in-terminal) 3385 | ("c h" . recentf-open-files) 3386 | ("c j" . xah-copy-file-path) 3387 | ("c l" . bookmark-set) 3388 | ("c n" . xah-new-empty-buffer) 3389 | ("c o" . xah-show-in-desktop) 3390 | ("c p" . xah-open-last-closed) 3391 | ("c r" . bookmark-jump) 3392 | ("c s" . write-file) 3393 | ("c u" . xah-open-file-at-cursor) 3394 | ("c x" . set-buffer-file-coding-system) 3395 | ("c y" . xah-list-recently-closed) 3396 | ("c z" . revert-buffer-with-coding-system) 3397 | 3398 | ;; set-buffer-process-coding-system 3399 | ;; set-file-name-coding-system 3400 | ;; set-keyboard-coding-system 3401 | ;; set-language-environment 3402 | ;; set-next-selection-coding-system 3403 | ;; set-selection-coding-system 3404 | ;; set-terminal-coding-system 3405 | ;; universal-coding-system-argument 3406 | 3407 | ("d" . beginning-of-buffer) 3408 | 3409 | ("e 6" . xah-insert-white-corner-bracket『』) 3410 | 3411 | ("e a" . xah-insert-double-angle-bracket《》) 3412 | ("e b" . xah-insert-black-lenticular-bracket【】) 3413 | 3414 | ("e c r" . expand-region-abbrevs) 3415 | ("e c t" . edit-abbrevs) 3416 | ("e c u" . expand-abbrev) 3417 | 3418 | ("e c g" . add-mode-abbrev) 3419 | ("e c c" . add-global-abbrev) 3420 | ("e c m" . inverse-add-mode-abbrev) 3421 | ("e c w" . inverse-add-global-abbrev) 3422 | 3423 | ("e c f" . unexpand-abbrev) 3424 | 3425 | ("e c h" . expand-jump-to-previous-slot) 3426 | ("e c n" . expand-jump-to-next-slot) 3427 | ("e c y" . abbrev-prefix-mark) 3428 | 3429 | ("e d" . xah-insert-double-curly-quote“”) 3430 | ("e e" . xah-insert-unicode) 3431 | ("e f" . xah-insert-emacs-quote) 3432 | ("e g" . xah-insert-ascii-double-quote) 3433 | 3434 | ("e h" . xah-insert-brace) 3435 | ("e i" . xah-insert-curly-single-quote‘’) 3436 | ("e j" . insert-char) 3437 | ("e k" . xah-insert-markdown-quote) 3438 | ("e l" . xah-insert-seperator) 3439 | ("e m" . xah-insert-corner-bracket「」) 3440 | ("e n" . xah-insert-square-bracket) 3441 | ("e o" . xah-insert-ascii-single-quote) 3442 | ("e p" . xah-insert-single-angle-quote‹›) 3443 | ("e q" . xah-insert-deco-angle-fat-bracket❰❱) 3444 | ("e r" . xah-insert-tortoise-shell-bracket〔〕) 3445 | ("e s" . xah-insert-ascii-angle-bracket) 3446 | ("e t" . xah-insert-paren) 3447 | ("e u" . xah-insert-date) 3448 | ("e v" . xah-insert-markdown-triple-quote) 3449 | ("e w" . xah-insert-angle-bracket〈〉) 3450 | ("e x" . xah-insert-white-lenticular-bracket〖〗) 3451 | ("e y" . xah-insert-double-angle-quote«») 3452 | ("e z" . xah-insert-deco-angle-bracket❮❯) 3453 | 3454 | ("f" . xah-search-current-word) 3455 | ("g" . xah-close-current-buffer) 3456 | 3457 | ("h a" . apropos-command) 3458 | ("h b" . describe-bindings) 3459 | ("h c" . describe-char) 3460 | ("h d" . apropos-documentation) 3461 | ("h e" . view-echo-area-messages) 3462 | ("h f" . describe-face) 3463 | ("h g" . info-lookup-symbol) 3464 | ;; ("h h" . describe-function) 3465 | ("h i" . info) 3466 | ("h j" . man) 3467 | ("h k" . describe-key) 3468 | ("h l" . view-lossage) 3469 | ("h m" . describe-mode) 3470 | ("h n" . describe-variable) 3471 | ("h o" . describe-language-environment) 3472 | ;; p q 3473 | ("h r" . apropos-variable) 3474 | ("h s" . describe-syntax) 3475 | ("h t" . describe-function) 3476 | ("h u" . elisp-index-search) 3477 | ("h v" . apropos-value) 3478 | ("h x" . describe-command) ; emacs 28 3479 | 3480 | ("h z" . describe-coding-system) 3481 | 3482 | ("i SPC" . set-mark-command) 3483 | ("i w" . kill-line) 3484 | ("i i" . exchange-point-and-mark) 3485 | ("i m" . xah-pop-local-mark-ring) 3486 | ("i j" . xah-show-kill-ring) 3487 | ("i d" . xah-delete-current-text-block) 3488 | ("i h" . xah-select-block) 3489 | ("i t" . xah-select-text-in-quote) 3490 | ("i s" . xah-select-line) 3491 | 3492 | ("j" . xah-copy-all-or-region) 3493 | 3494 | ("k b" . xah-upcase-sentence) 3495 | ("k c" . enlarge-window) 3496 | ("k h" . delete-other-windows) 3497 | ("k m" . split-window-below) 3498 | ("k n" . balance-windows) 3499 | ("k s" . ispell-word) 3500 | ("k t" . delete-window) 3501 | ("k w" . split-window-right) 3502 | 3503 | ("l" . recenter-top-bottom) 3504 | ("m" . dired-jump) 3505 | 3506 | ;; commands here are harmless. they don't modify text etc. they turn on modes, change display, prompt, start shell, etc. 3507 | 3508 | ("n SPC SPC" . whitespace-mode) 3509 | 3510 | ("n SPC t" . visual-line-mode) 3511 | ("n SPC h" . toggle-word-wrap) 3512 | ("n SPC n" . variable-pitch-mode) 3513 | ("n SPC s" . toggle-truncate-lines) 3514 | ("n SPC d" . display-line-numbers-mode) 3515 | 3516 | ("n SPC ," . global-hl-line-mode) 3517 | 3518 | ("n ," . abbrev-mode) 3519 | ("n ." . toggle-frame-maximized) ; xxwindow 3520 | ("n 1" . set-input-method) 3521 | ("n 6" . calendar) 3522 | ("n 7" . calc) 3523 | ("n 9" . nil) 3524 | ("n 0" . nil) 3525 | 3526 | ("n <up>" . xah-page-up) 3527 | ("n <down>" . xah-page-down) 3528 | 3529 | ("n a" . global-text-scale-adjust) 3530 | ("n b" . toggle-debug-on-error) 3531 | ("n c" . toggle-case-fold-search) 3532 | ("n d" . nil) 3533 | ("n e" . eshell) 3534 | ("n f" . shell-command-on-region) 3535 | ("n g" . shell-command) 3536 | ("n h" . widen) 3537 | ("n i" . make-frame-command) 3538 | ("n j" . flyspell-buffer) 3539 | ("n k" . menu-bar-open) 3540 | ("n l" . nil) 3541 | ("n m" . jump-to-register) ; bad orig 3542 | ("n n" . xah-narrow-to-region) 3543 | ("n o" . nil) 3544 | ("n p" . read-only-mode) 3545 | ("n q" . nil) 3546 | ("n r" . count-words) 3547 | ("n s" . count-matches) 3548 | ("n t" . narrow-to-defun) 3549 | ("n u" . shell) 3550 | ("n v" . nil) 3551 | ("n w" . eww) ; xxmode 3552 | ("n x" . save-some-buffers) 3553 | ("n y" . nil) 3554 | ("n z" . abort-recursive-edit) 3555 | 3556 | ("o" . query-replace) 3557 | ("p" . query-replace) 3558 | 3559 | ("q" . xah-cut-all-or-region) 3560 | 3561 | ;; roughly text replacement related 3562 | 3563 | ("r ," . apply-macro-to-region-lines) 3564 | ("r ." . kmacro-start-macro) 3565 | 3566 | ("r 3" . number-to-register) 3567 | ("r 4" . increment-register) 3568 | ("r RET" . nil) 3569 | ("r SPC" . nil) 3570 | 3571 | ("r a" . nil) 3572 | ("r b" . nil) 3573 | ("r c" . string-rectangle) 3574 | ("r d" . delete-rectangle) 3575 | ("r e" . call-last-kbd-macro) 3576 | ("r f" . nil) 3577 | ("r g" . nil) 3578 | ("r h" . xah-change-bracket-pairs) 3579 | ("r i" . xah-space-to-newline) 3580 | ("r j" . copy-rectangle-to-register) 3581 | ("r k" . yank-rectangle) 3582 | ("r l" . clear-rectangle) 3583 | 3584 | ("r m e" . xah-append-to-register-1) 3585 | ("r m j" . xah-copy-to-register-1) 3586 | ("r m k" . xah-paste-from-register-1) 3587 | ("r m u" . xah-clear-register-1) 3588 | 3589 | ("r n" . rectangle-number-lines) 3590 | ("r o" . open-rectangle) 3591 | ("r p" . kmacro-end-macro) 3592 | ("r q" . kill-rectangle) 3593 | 3594 | ("r r" . rectangle-mark-mode) 3595 | 3596 | ("r s e" . xah-slash-to-backslash) 3597 | ("r s h" . xah-slash-to-double-backslash) 3598 | ("r s m" . xah-double-backslash-to-slash) 3599 | ("r s s" . xah-double-backslash-to-single) 3600 | ("r s t" . xah-double-backslash) 3601 | ("r s u" . xah-backslash-to-slash) 3602 | 3603 | ;; kmacro-end-and-call-macro 3604 | ("r t RET" . kmacro-edit-macro) 3605 | ("r t SPC" . kmacro-step-edit-macro) 3606 | ("r t TAB" . kmacro-insert-counter) 3607 | 3608 | ("r t a" . kmacro-add-counter) 3609 | ("r t b" . kmacro-bind-to-key) 3610 | ("r t c" . kmacro-set-counter) 3611 | ("r t d" . kmacro-redisplay) 3612 | ("r t e" . edit-kbd-macro) 3613 | ("r t f" . kmacro-set-format) 3614 | ("r t g" . kmacro-delete-ring-head) 3615 | ("r t h" . kmacro-edit-macro-repeat) 3616 | ("r t i" . kmacro-call-ring-2nd-repeat) 3617 | ("r t j" . kmacro-cycle-ring-next) 3618 | ("r t k" . kmacro-end-or-call-macro-repeat) 3619 | ("r t l" . kmacro-edit-lossage) 3620 | 3621 | ("r t n" . kmacro-name-last-macro) 3622 | ("r t p" . kmacro-cycle-ring-previous) 3623 | ("r t q" . kbd-macro-query) 3624 | ("r t t" . kmacro-swap-ring) 3625 | ("r t u" . nil) 3626 | 3627 | ("r t v" . kmacro-view-macro-repeat) 3628 | ("r t x" . kmacro-to-register) 3629 | ("r u" . xah-quote-lines) 3630 | ("r v" . nil) 3631 | ("r w" . nil) 3632 | ("r x" . nil) 3633 | ("r y" . delete-whitespace-rectangle) 3634 | ("r z" . nil) 3635 | 3636 | ("s" . save-buffer) 3637 | 3638 | ;; most frequently used 3639 | ("t <up>" . xah-move-block-up) 3640 | ("t <down>" . xah-move-block-down) 3641 | 3642 | ("t ," . sort-numeric-fields) 3643 | ("t ." . xah-sort-lines) 3644 | ("t a" . nil) 3645 | ("t b" . xah-backward-punct) 3646 | ("t c" . copy-matching-lines) 3647 | ("t d" . mark-defun) 3648 | ("t e" . list-matching-lines) 3649 | ("t f" . move-to-column) 3650 | ("t g" . xah-goto-line) 3651 | ("t h" . repeat-complex-command) 3652 | ("t i" . delete-non-matching-lines) 3653 | ("t j" . copy-to-register) 3654 | ("t k" . insert-register) 3655 | ("t l" . xah-cycle-hyphen-lowline-space) 3656 | ("t m" . xah-make-backup-and-save) 3657 | ("t n" . xah-goto-char) 3658 | ("t o" . xah-clean-whitespace) 3659 | ("t p" . query-replace-regexp) 3660 | ("t q" . xah-cut-text-in-quote) 3661 | ("t r" . xah-escape-quotes) 3662 | ("t s" . xah-reformat-to-sentence-lines) 3663 | ("t t" . repeat) 3664 | ("t u" . kill-matching-lines) 3665 | ("t v" . nil) 3666 | ("t w" . xah-next-window-or-frame) 3667 | ("t x" . xah-title-case-region-or-line) 3668 | ("t y" . delete-duplicate-lines) 3669 | ("t z" . nil) 3670 | ("u" . switch-to-buffer) 3671 | ("v" . universal-argument) 3672 | 3673 | ;; dangerous map. run program, delete file, etc 3674 | ("w d" . xah-delete-current-file-make-backup) 3675 | ("w ." . eval-buffer) 3676 | ("w e" . eval-defun) 3677 | ("w m" . eval-last-sexp) 3678 | ("w p" . eval-expression) 3679 | ("w u" . eval-region) 3680 | ("w q" . save-buffers-kill-terminal) 3681 | ("w w" . delete-frame) 3682 | ("w j" . xah-run-current-file) 3683 | 3684 | ("x" . xah-toggle-previous-letter-case) 3685 | 3686 | ("y" . nil) 3687 | 3688 | ;; vc command keys subject to change. need a frequency stat of the commands. 3689 | 3690 | ("z b" . vc-root-diff) ; D 3691 | ("z c" . vc-update) ; git pull, + 3692 | ("z d" . vc-annotate) ; g 3693 | ("z f" . vc-revert) ; u 3694 | ("z g" . vc-push) ; git push, P 3695 | ("z h" . vc-diff) ; git diff, = 3696 | ("z l" . vc-print-root-log) ; L 3697 | ("z m" . vc-dir) ; git status, C-x v d 3698 | ("z n" . vc-print-log) ; git log, l 3699 | ("z r" . vc-merge) ; m 3700 | ("z t" . vc-register) ; git add, i 3701 | ("z z" . vc-next-action) ; v 3702 | 3703 | ("z 1" . vc-create-tag) ; s 3704 | ("z 2" . vc-insert-headers) ; h 3705 | ("z 4" . vc-retrieve-tag) ; r 3706 | ("z 5" . vc-revision-other-window) ; ~ 3707 | ("z 6" . vc-switch-backend) ; b 3708 | ("z 7" . vc-update-change-log) ; a 3709 | 3710 | ;; 3711 | )) 3712 | 3713 | (xah-fly--define-keys 3714 | xah-fly-command-map 3715 | '(("SPC" . xah-fly-leader-key-map) 3716 | ("'" . xah-reformat-lines) 3717 | ("," . xah-shrink-whitespaces) 3718 | ("-" . delete-other-windows) 3719 | ("." . backward-kill-word) 3720 | ("/" . hippie-expand) 3721 | (";" . xah-comment-dwim) 3722 | ("[" . split-window-below) 3723 | ("\\" . xah-cycle-hyphen-lowline-space) 3724 | ("]" . split-window-right) 3725 | ("`" . other-frame) 3726 | 3727 | ("a" . execute-extended-command) 3728 | ("b" . isearch-forward) 3729 | ("c" . previous-line) 3730 | ("d" . xah-beginning-of-line-or-block) 3731 | ("e" . xah-smart-delete) 3732 | ("E" . delete-backward-char) 3733 | ("f" . undo) 3734 | ("g" . backward-word) 3735 | ("h" . backward-char) 3736 | ("i" . xah-extend-selection) 3737 | 3738 | ("j" . xah-copy-line-or-region) 3739 | ("k" . xah-paste-or-paste-previous) 3740 | ("l" . xah-insert-space-before) 3741 | ("m" . xah-backward-left-bracket) 3742 | ("n" . forward-char) 3743 | ("o" . open-line) 3744 | ("p" . kill-word) 3745 | ("q" . xah-cut-line-or-region) 3746 | ("r" . forward-word) 3747 | ("s" . xah-end-of-line-or-block) 3748 | ("t" . next-line) 3749 | ("u" . xah-fly-insert-mode-activate) 3750 | ("v" . xah-forward-right-bracket) 3751 | ("w" . xah-next-window-or-frame) 3752 | ("x" . xah-toggle-letter-case) 3753 | ("y" . xah-fly-cancel) 3754 | ("z" . xah-goto-matching-bracket))) 3755 | 3756 | ;; 3757 | )) 3758 | 3759 | (xah-fly-define-keys) 3760 | 3761 | ;; s------------------------------ 3762 | ;; set control meta, etc keys 3763 | 3764 | (defcustom xah-fly-unset-useless-key t 3765 | "If true, unbind many obsolete or useless or redundant 3766 | keybinding. e.g. <help>, <f1>." 3767 | :type 'boolean) 3768 | 3769 | (when xah-fly-unset-useless-key 3770 | (global-set-key (kbd "<help>") nil) 3771 | (global-set-key (kbd "<f1>") nil)) 3772 | 3773 | (global-set-key (kbd "M-SPC") #'xah-fly-command-mode-activate) 3774 | 3775 | (when xah-fly-use-meta-key 3776 | 3777 | (global-set-key (kbd "M-<home>") nil) ; beginning-of-buffer-other-window 3778 | (global-set-key (kbd "M-<end>") nil) ; end-of-buffer-other-window 3779 | 3780 | (global-set-key (kbd "M-\\") nil) ; delete-horizontal-space 3781 | (global-set-key (kbd "M-!") nil) ; shell-command 3782 | (global-set-key (kbd "M-$") nil) ; ispell-word 3783 | (global-set-key (kbd "M-%") nil) ; query-replace 3784 | (global-set-key (kbd "M-&") nil) ; async-shell-command 3785 | (global-set-key (kbd "M-'") nil) ; abbrev-prefix-mark 3786 | (global-set-key (kbd "M-(") nil) ; insert-parentheses 3787 | (global-set-key (kbd "M-)") nil) ; move-past-close-and-reindent 3788 | ;; (global-set-key (kbd "M-,") nil) ; xref-pop-marker-stack 3789 | ;; (global-set-key (kbd "M-.") nil) ; xref-find-definitions 3790 | (global-set-key (kbd "M-/") nil) ; dabbrev-expand 3791 | (global-set-key (kbd "M-:") nil) ; eval-expression 3792 | ;; (global-set-key (kbd "M-;") nil) ; comment-dwim 3793 | (global-set-key (kbd "M-<") nil) ; beginning-of-buffer 3794 | (global-set-key (kbd "M-=") nil) ; count-words-region 3795 | (global-set-key (kbd "M->") nil) ; end-of-buffer 3796 | ;; (global-set-key (kbd "M-?") nil) ; xref-find-references 3797 | (global-set-key (kbd "M-@") nil) ; mark-word 3798 | (global-set-key (kbd "M-^") nil) ; delete-indentation 3799 | (global-set-key (kbd "M-`") nil) ; tmm-menubar 3800 | (global-set-key (kbd "M-a") nil) ; backward-sentence 3801 | (global-set-key (kbd "M-b") nil) ; backward-word 3802 | (global-set-key (kbd "M-c") nil) ; capitalize-word 3803 | (global-set-key (kbd "M-d") nil) ; kill-word 3804 | (global-set-key (kbd "M-e") nil) ; forward-sentence 3805 | (global-set-key (kbd "M-f") nil) ; forward-word 3806 | (global-set-key (kbd "M-g") nil) ; Prefix Command 3807 | (global-set-key (kbd "M-h") nil) ; mark-paragraph 3808 | (global-set-key (kbd "M-i") nil) ; tab-to-tab-stop 3809 | (global-set-key (kbd "M-j") nil) ; default-indent-new-line 3810 | (global-set-key (kbd "M-k") nil) ; kill-sentence 3811 | (global-set-key (kbd "M-l") nil) ; downcase-word 3812 | (global-set-key (kbd "M-m") nil) ; back-to-indentation 3813 | (global-set-key (kbd "M-o") nil) ; facemenu-keymap 3814 | (global-set-key (kbd "M-q") nil) ; fill-paragraph 3815 | (global-set-key (kbd "M-r") nil) ; move-to-window-line-top-bottom 3816 | ;; (global-set-key (kbd "M-s") nil) ; Prefix Command 3817 | (global-set-key (kbd "M-t") nil) ; transpose-words 3818 | (global-set-key (kbd "M-u") nil) ; upcase-word 3819 | (global-set-key (kbd "M-v") nil) ; scroll-down-command 3820 | (global-set-key (kbd "M-w") nil) ; kill-ring-save 3821 | ;; (global-set-key (kbd "M-x") nil) ; execute-extended-command 3822 | ;; (global-set-key (kbd "M-y") nil) ; yank-pop 3823 | (global-set-key (kbd "M-z") nil) ; zap-to-char 3824 | (global-set-key (kbd "M-{") nil) ; backward-paragraph 3825 | (global-set-key (kbd "M-|") nil) ; shell-command-on-region 3826 | (global-set-key (kbd "M-}") nil) ; forward-paragraph 3827 | (global-set-key (kbd "M-~") nil) ; not-modified 3828 | (global-set-key (kbd "M-DEL") nil) ; backward-kill-word 3829 | 3830 | ;; 3831 | ) 3832 | 3833 | (when xah-fly-remove-control-x-keys (global-set-key (kbd "C-x") nil)) 3834 | 3835 | (when xah-fly-use-control-key 3836 | 3837 | (global-set-key (kbd "C-<tab>") #'xah-next-user-buffer) 3838 | (global-set-key (kbd "C-S-<tab>") #'xah-previous-user-buffer) 3839 | 3840 | (global-set-key (kbd "C-<prior>") #'xah-previous-user-buffer) 3841 | (global-set-key (kbd "C-<next>") #'xah-next-user-buffer) 3842 | 3843 | (global-set-key (kbd "C-2") #'pop-global-mark) 3844 | (global-set-key (kbd "C-3") #'previous-error) 3845 | (global-set-key (kbd "C-4") #'next-error) 3846 | 3847 | (global-set-key (kbd "C-7") #'xah-previous-user-buffer) 3848 | (global-set-key (kbd "C-8") #'xah-next-user-buffer) 3849 | (global-set-key (kbd "C-9") #'xah-page-up) 3850 | (global-set-key (kbd "C-0") #'xah-page-down) 3851 | 3852 | (global-set-key (kbd "C--") #'text-scale-decrease) 3853 | (global-set-key (kbd "C-=") #'text-scale-increase) 3854 | 3855 | (global-set-key (kbd "C-SPC") #'xah-fly-command-mode-activate) 3856 | 3857 | (global-set-key (kbd "C-S-n") #'make-frame-command) 3858 | (global-set-key (kbd "C-S-s") #'write-file) 3859 | (global-set-key (kbd "C-S-t") #'xah-open-last-closed) 3860 | 3861 | (global-set-key (kbd "C-a") #'mark-whole-buffer) 3862 | 3863 | (global-set-key (kbd "C-n") #'xah-new-empty-buffer) 3864 | (global-set-key (kbd "C-o") #'find-file) 3865 | 3866 | (global-set-key (kbd "C-s") #'save-buffer) 3867 | (global-set-key (kbd "C-t") #'hippie-expand) 3868 | (global-set-key (kbd "C-v") #'yank) 3869 | (global-set-key (kbd "C-w") #'xah-close-current-buffer) 3870 | (global-set-key (kbd "C-y") #'undo-redo) 3871 | (global-set-key (kbd "C-z") #'undo) 3872 | ;; 3873 | ) 3874 | 3875 | (global-set-key (kbd "<f7>") 'xah-fly-leader-key-map) 3876 | 3877 | ;; s------------------------------ 3878 | 3879 | (when (< emacs-major-version 28) 3880 | (defalias 'execute-extended-command-for-buffer #'execute-extended-command)) 3881 | 3882 | ;; s------------------------------ 3883 | ;;;; misc 3884 | 3885 | ;; 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 3886 | 3887 | ;; C-x $ → set-selective-display 3888 | ;; C-x * → calc-dispatch 3889 | ;; C-x - → shrink-window-if-larger-than-buffer 3890 | ;; C-x . → set-fill-prefix 3891 | ;; C-x 4 . → find-tag-other-window 3892 | ;; C-x 4 0 → kill-buffer-and-window 3893 | ;; C-x 4 C-o → display-buffer 3894 | ;; C-x 4 a → add-change-log-entry-other-window 3895 | ;; C-x 4 b → switch-to-buffer-other-window 3896 | ;; C-x 4 c → clone-indirect-buffer-other-window 3897 | ;; C-x 4 d → dired-other-window 3898 | ;; C-x 4 f → find-file-other-window 3899 | ;; C-x 4 m → compose-mail-other-window 3900 | ;; C-x 4 r → find-file-read-only-other-window 3901 | ;; C-x 5 . xref-find-definitions-other-frame 3902 | ;; C-x 5 1 delete-other-frames 3903 | ;; C-x 5 5 other-frame-prefix 3904 | ;; C-x 5 C-o display-buffer-other-frame 3905 | ;; C-x 5 b switch-to-buffer-other-frame 3906 | ;; C-x 5 c clone-frame 3907 | ;; C-x 5 d dired-other-frame 3908 | ;; C-x 5 f find-file-other-frame 3909 | ;; C-x 5 m compose-mail-other-frame 3910 | ;; C-x 5 p project-other-frame-command 3911 | ;; C-x 5 r find-file-read-only-other-frame 3912 | ;; C-x 5 u undelete-frame 3913 | ;; C-x 6 2 → 2C-two-columns 3914 | ;; C-x 6 b → 2C-associate-buffer 3915 | ;; C-x 6 s → 2C-split 3916 | ;; C-x ; → comment-set-column 3917 | ;; C-x < → scroll-left 3918 | ;; C-x = → what-cursor-position, use describe-char instead 3919 | ;; C-x > → scroll-right 3920 | ;; C-x C-d → list-directory 3921 | ;; C-x C-l → downcase-region 3922 | ;; C-x C-n → set-goal-column 3923 | ;; C-x C-o → delete-blank-lines 3924 | ;; C-x C-p → mark-page 3925 | ;; C-x C-r → find-file-read-only 3926 | ;; C-x C-t → transpose-lines 3927 | ;; C-x C-u → upcase-region 3928 | ;; C-x C-v → find-alternate-file 3929 | ;; C-x C-z → suspend-frame 3930 | ;; C-x DEL → backward-kill-sentence 3931 | ;; C-x ESC → Prefix Command 3932 | ;; C-x [ → backward-page 3933 | ;; C-x ] → forward-page 3934 | ;; C-x f → set-fill-column 3935 | ;; C-x i → insert-file 3936 | ;; C-x k → kill-buffer 3937 | ;; C-x l → count-lines-page 3938 | ;; C-x m → compose-mail 3939 | ;; C-x n g goto-line-relative 3940 | ;; C-x n p narrow-to-page 3941 | ;; C-x r M bookmark-set-no-overwrite 3942 | ;; C-x r M-w copy-rectangle-as-kill 3943 | ;; C-x r SPC point-to-register 3944 | ;; C-x r f frameset-to-register 3945 | ;; C-x r j jump-to-register 3946 | ;; C-x r w window-configuration-to-register 3947 | ;; C-x { → shrink-window-horizontally 3948 | ;; C-x } → enlarge-window-horizontally 3949 | 3950 | ;; s------------------------------ 3951 | 3952 | (defvar xah-fly-insert-state-p t "non-nil means insertion mode is on.") 3953 | 3954 | (defun xah-fly--update-key-map () 3955 | (setq xah-fly-key-map (if xah-fly-insert-state-p 3956 | xah-fly-insert-map 3957 | xah-fly-command-map))) 3958 | 3959 | (defun xah-fly-keys-set-layout (Layout) 3960 | "Set a keyboard layout. 3961 | Argument must be one of the key name in `xah-fly-layout-diagrams' 3962 | Created: 2021-05-19 3963 | Version: 2024-05-23" 3964 | (interactive 3965 | (list 3966 | (completing-read 3967 | "Choose a layout: " 3968 | (seq-sort 'string< (hash-table-keys xah-fly-layout-diagrams)) 3969 | nil t))) 3970 | (let ((xold xah-fly-key-current-layout) 3971 | (xkeydiagram (gethash Layout xah-fly-layout-diagrams))) 3972 | (when (not xkeydiagram) 3973 | (user-error 3974 | "xah-fly-keys-set-layout error: layout must be one of string:\n%s" 3975 | (mapconcat 'identity (hash-table-keys xah-fly-layout-diagrams) "\n"))) 3976 | (setq xah-fly-key-current-layout Layout) 3977 | (setq 3978 | xah-fly--key-convert-table 3979 | (xah-fly-create-key-conv-table 3980 | (gethash "dvorak" xah-fly-layout-diagrams) 3981 | xkeydiagram)) 3982 | (when (not (equal xold Layout)) (xah-fly-define-keys)))) 3983 | 3984 | (defun xah-fly-space-key () 3985 | "Switch to command mode if the char before cursor is a space. 3986 | experimental 3987 | Version: 2018-05-07" 3988 | (interactive) 3989 | (if (eq (char-before ) 32) 3990 | (xah-fly-command-mode-activate) 3991 | (insert " "))) 3992 | 3993 | (defun xah-fly-command-mode-init () 3994 | "Set command mode keys. 3995 | Version: 2022-07-06" 3996 | (interactive) 3997 | (setq xah-fly-insert-state-p nil) 3998 | (xah-fly--update-key-map) 3999 | (when xah-fly--deactivate-command-mode-func 4000 | (funcall xah-fly--deactivate-command-mode-func)) 4001 | (setq xah-fly--deactivate-command-mode-func 4002 | (set-transient-map xah-fly-command-map (lambda () t))) 4003 | (modify-all-frames-parameters (list (cons 'cursor-type 'box))) 4004 | (if xah-fly-command-mode-cursor-color 4005 | (set-face-background 'cursor xah-fly-command-mode-cursor-color)) 4006 | (force-mode-line-update)) 4007 | 4008 | (defun xah-fly-insert-mode-init (&optional no-indication) 4009 | "Enter insertion mode." 4010 | (interactive) 4011 | (setq xah-fly-insert-state-p t) 4012 | (xah-fly--update-key-map) 4013 | (funcall xah-fly--deactivate-command-mode-func) 4014 | (unless no-indication 4015 | (modify-all-frames-parameters '((cursor-type . bar))) 4016 | (if xah-fly-insert-mode-cursor-color 4017 | (set-face-background 'cursor xah-fly-insert-mode-cursor-color))) 4018 | (force-mode-line-update)) 4019 | 4020 | (defun xah-fly-mode-toggle () 4021 | "Switch between {insertion, command} modes." 4022 | (interactive) 4023 | (if xah-fly-insert-state-p 4024 | (xah-fly-command-mode-activate) 4025 | (xah-fly-insert-mode-activate))) 4026 | 4027 | (defun xah-fly-save-buffer-if-file () 4028 | "Save current buffer if it is a file." 4029 | (interactive) 4030 | (when buffer-file-name 4031 | (save-buffer))) 4032 | 4033 | (defun xah-fly-command-mode-activate () 4034 | "Activate command mode and run `xah-fly-command-mode-activate-hook' 4035 | Version: 2017-07-07" 4036 | (interactive) 4037 | (xah-fly-command-mode-init) 4038 | (when xah-fly-command-mode-hl-line (progn (global-hl-line-mode 1))) 4039 | (run-hooks 'xah-fly-command-mode-activate-hook)) 4040 | 4041 | (defun xah-fly-command-mode-activate-no-hook () 4042 | "Activate command mode. Does not run `xah-fly-command-mode-activate-hook' 4043 | Version: 2017-07-07" 4044 | (interactive) 4045 | (xah-fly-command-mode-init)) 4046 | 4047 | (defun xah-fly-insert-mode-activate () 4048 | "Activate insertion mode. 4049 | Version: 2017-07-07" 4050 | (interactive) 4051 | (xah-fly-insert-mode-init) 4052 | (when xah-fly-command-mode-hl-line (progn (global-hl-line-mode 0))) 4053 | (run-hooks 'xah-fly-insert-mode-activate-hook)) 4054 | 4055 | (defun xah-fly-insert-mode-activate-newline () 4056 | "Activate insertion mode, insert newline below." 4057 | (interactive) 4058 | (xah-fly-insert-mode-activate) 4059 | (open-line 1)) 4060 | 4061 | ;; s------------------------------ 4062 | 4063 | ;;;###autoload 4064 | (define-minor-mode xah-fly-keys 4065 | "A modal keybinding set, like vim, but based on ergonomic principles, like Dvorak layout. 4066 | 4067 | URL `http://xahlee.info/emacs/misc/xah-fly-keys.html'" 4068 | :global t 4069 | :lighter "XFK" 4070 | :keymap xah-fly-insert-map 4071 | (delete-selection-mode 1) 4072 | (setq shift-select-mode nil) 4073 | (if xah-fly-keys 4074 | (progn 4075 | (when (fboundp 'which-key-mode) (which-key-mode 1)) 4076 | (add-hook 'minibuffer-setup-hook 'xah-fly-insert-mode-activate) 4077 | (add-hook 'minibuffer-exit-hook 'xah-fly-command-mode-activate) 4078 | (add-hook 'isearch-mode-end-hook 'xah-fly-command-mode-activate) 4079 | (xah-fly-command-mode-activate)) 4080 | (progn 4081 | (when (fboundp 'which-key-mode) (which-key-mode 0)) 4082 | (remove-hook 'minibuffer-setup-hook 'xah-fly-insert-mode-activate) 4083 | (remove-hook 'minibuffer-exit-hook 'xah-fly-command-mode-activate) 4084 | (remove-hook 'isearch-mode-end-hook 'xah-fly-command-mode-activate) 4085 | (xah-fly-insert-mode-init :no-indication)))) 4086 | 4087 | (provide 'xah-fly-keys) 4088 | 4089 | ;;; xah-fly-keys.el ends here 4090 | --------------------------------------------------------------------------------