This commit is contained in:
2025-08-14 11:58:41 -04:00
commit 97c20626c1
266 changed files with 69552 additions and 0 deletions

BIN
.emacs.d/.DS_Store vendored Normal file

Binary file not shown.

13
.emacs.d/custom.el Executable file
View File

@@ -0,0 +1,13 @@
(custom-set-variables
;; custom-set-variables was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
'(custom-safe-themes
'("5e38acfff91084e4c8174e272d2f8b84808884b4f49c62145b48810ec1766b5e" "eb085d94b1f7bfa54c7003bf7edb40eab1133d90cfebcf1d11be5959635a526f" default)))
(custom-set-faces
;; custom-set-faces was added by Custom.
;; If you edit it by hand, you could mess it up, so be careful.
;; Your init file should contain only one such instance.
;; If there is more than one, they won't work right.
)

251
.emacs.d/init.el Executable file
View File

@@ -0,0 +1,251 @@
;; Max's init.el -*- lexical-binding: t; -*-
;; General Configuration
;; set load paths for lisp evaluation
(add-to-list 'load-path "~/.emacs.d/lisp/")
(let ((default-directory "~/.emacs.d/lisp/"))
(normal-top-level-add-subdirs-to-load-path))
(setq indent-tabs-mode t)
(stupid-indent-mode)
(setq-local stupid-indent-level 4)
))
;; general settings
(setq-default inhibit-startup-screen t)
(show-paren-mode 1)
(delete-selection-mode 1)
(setq cua-auto-tabify-rectangles nil) ;; Don't tabify after rectangle commands
(transient-mark-mode 1) ;; No region when it is not highlighted
(setq cua-keep-region-after-copy nil) ;; don't reselect after copy
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(global-auto-revert-mode t)
(electric-pair-mode -1)
(setq ns-pop-up-frames nil)
(setq initial-major-mode 'text-mode)
(setq initial-scratch-message "Just give him some food, water, and a funny hat." )
(setq ediff-split-window-function 'split-window-horizontally)
(setq-default truncate-lines 1)
(setq column-number-mode t)
(setq dired-dnd-protocol-alist nil)
(global-so-long-mode 1)
(column-number-mode 1)
(blink-cursor-mode 0)
(setq use-dialog-box nil)
(electric-indent-mode 0)
(tooltip-mode -1)
(setq-default select-enable-clipboard nil)
(setq-default x-select-enable-clipboard nil)
(setq-default backward-delete-char-untabify-method nil)
(setq ring-bell-function 'ignore)
(setq custom-file "~/.emacs.d/custom.el") ;; place custom in a separate file
(setq-default require-final-newline t)
(cua-mode t)
;; backup and autosave settings
(setq backup-by-copying t ; don't clobber symlinks
backup-directory-alist '(("." . "~/.emacs.d/saves/")) ; don't litter my fs tree
delete-old-versions t
kept-new-versions 6
kept-old-versions 2
version-control t) ; use versioned backups
(setq auto-save-file-name-transforms
`((".*" "~/.emacs.d/saves/" t)))
(setq create-lockfiles nil)
;; Keybindings / Keybinds
;; global
(global-set-key (kbd "S-<down-mouse-1>") #'my-mouse-start-rectangle)
(global-set-key (kbd "<f5>") 'revert-buffer-quick)
(global-set-key (kbd "<f6>") 'my-file-manager-command)
(global-set-key (kbd "<C-f6>") 'my-terminal-emulator-command)
(global-set-key [f8] 'goto-line)
(global-set-key (kbd "C-\\") 'my-transpose-windows)
(global-unset-key (kbd "C-x C-SPC"))
(global-set-key (kbd "C-x C-SPC") 'rectangle-mark-mode)
(global-set-key [C-return] 'save-buffer)
(global-set-key [?\C-z] 'undo)
(global-set-key (kbd "C-*") 'search-current-word)
(global-set-key (kbd "C-;") 'comment-line)
(global-set-key (kbd "M-<f4>") 'save-buffers-kill-terminal) ;; windows thing
(global-set-key (kbd "C-y") 'clipboard-yank) ;; fix killring messing with system clipboard
(global-set-key (kbd "C-w") 'clipboard-kill-region)
(global-set-key (kbd "M-w") 'clipboard-kill-ring-save)
(global-set-key (kbd "M-j") 'my-duplicate-line)
(global-set-key (kbd "<f9>") 'bookmark-jump)
(global-set-key (kbd "<f10>") 'bookmark-set)
(global-set-key (kbd "<f11>") 'bookmark-delete)
;; custom bind minor mode
;; this allows binding keys that override all other modes
(defvar my-keys-minor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "M-p") 'backward-paragraph)
(define-key map (kbd "M-n") 'forward-paragraph)
(define-key map (kbd "C-o") 'next-multiframe-window)
(define-key map [?\C-0] 'delete-window)
(define-key map [?\C-1] 'delete-other-windows)
(define-key map [?\C-2] 'split-window-vertically)
(define-key map [?\C-3] 'split-window-horizontally)
(define-key map [?\C-4] 'find-file-at-point)
(define-key map [?\C-5] 'switch-to-buffer)
(define-key map (kbd "C-j") 'dabbrev-expand)
map)
"my-keys-minor-mode keymap.")
(define-minor-mode my-keys-minor-mode
"A minor mode so that my key settings override annoying major modes."
:init-value t
:lighter "")
;; don't enable override keymap in minibuf
(defun my-minibuffer-setup-hook ()
(my-keys-minor-mode 0))
(add-hook 'minibuffer-setup-hook 'my-minibuffer-setup-hook)
(my-keys-minor-mode 1)
;; Custom Functions
;; test function
(defun my-test ()
(interactive)
(message "Hello world!"))
;; duplicate line (cleanly)
;; https://stackoverflow.com/questions/88399/how-do-i-duplicate-a-whole-line-in-emacs
(defun my-duplicate-line (arg)
"Duplicate current line, leaving point in lower line."
(interactive "*p")
(setq buffer-undo-list (cons (point) buffer-undo-list))
(let ((bol (save-excursion (beginning-of-line) (point)))
eol)
(save-excursion
(end-of-line)
(setq eol (point))
(let ((line (buffer-substring bol eol))
(buffer-undo-list t)
(count arg))
(while (> count 0)
(newline)
(insert line)
(setq count (1- count)))
)
(setq buffer-undo-list (cons (cons eol (point)) buffer-undo-list)))
)
(next-line arg))
;; select rectangle with shift+mouse
(defun my-mouse-start-rectangle (start-event)
(interactive "e")
(deactivate-mark)
(mouse-set-point start-event)
(rectangle-mark-mode +1)
(let ((drag-event))
(track-mouse
(while (progn
(setq drag-event (read-event))
(mouse-movement-p drag-event))
(mouse-set-point drag-event)))))
;; open file manager
(defun my-file-manager-command ()
(interactive)
(cond ((eq system-type 'windows-nt)
(shell-command "explorer.exe ."))
((eq system-type 'gnu/linux)
(shell-command "setsid -f nautilus . >/dev/null 2>&1"))))
;; open terminal or cmd prompt
(defun my-terminal-emulator-command ()
(interactive)
(cond ((eq system-type 'windows-nt)
(let ((proc (start-process "cmd" nil "cmd.exe" "/C" "start" "cmd.exe")))
(set-process-query-on-exit-flag proc nil)))
((eq system-type 'gnu/linux)
(shell-command "setsid -f gnome-terminal . >/dev/null 2>&1"))))
;; transpose (move) windows
(defun my-transpose-windows (arg)
"Transpose the buffers shown in two windows."
(interactive "p")
(let ((selector (if (>= arg 0) 'next-window 'previous-window)))
(while (/= arg 0)
(let ((this-win (window-buffer))
(next-win (window-buffer (funcall selector))))
(set-window-buffer (selected-window) next-win)
(set-window-buffer (funcall selector) this-win)
(select-window (funcall selector)))
(setq arg (if (plusp arg) (1- arg) (1+ arg))))))
;; search current word
(defun search-current-word ()
(interactive)
(let ($p1 $p2)
(if (region-active-p)
(setq $p1 (region-beginning) $p2 (region-end))
(save-excursion
(skip-chars-backward "-_A-Za-z0-9")
(setq $p1 (point))
(right-char)
(skip-chars-forward "-_A-Za-z0-9")
(setq $p2 (point))))
(setq mark-active nil)
(when (< $p1 (point))
(goto-char $p1))
(isearch-mode t)
(isearch-yank-string (buffer-substring-no-properties $p1 $p2))))
;; Tweaks / Fixes
;; Emacs sucks by default, here are some fixes.
;; fix isearch
(defadvice isearch-search (after isearch-no-fail activate)
(unless isearch-success
(ad-disable-advice 'isearch-search 'after 'isearch-no-fail)
(ad-activate 'isearch-search)
(isearch-repeat (if isearch-forward 'forward))
(ad-enable-advice 'isearch-search 'after 'isearch-no-fail)
(ad-activate 'isearch-search)))
(add-hook 'isearch-mode-end-hook
#'endless/goto-match-beginning)
(defun endless/goto-match-beginning ()
"Go to the start of current isearch match.
Use in `isearch-mode-end-hook'."
(when (and isearch-forward
(number-or-marker-p isearch-other-end)
(not mark-active)
(not isearch-mode-end-hook-quit))
(goto-char isearch-other-end)))
;; appearance
;; (set-face-attribute 'default nil :font "Consolas-15")
;; the name of my emacs color scheme
(custom-set-faces
'(default ((t (:foreground "#d3b58d" :background "#181E2C"))))
'(custom-group-tag-face ((t (:underline t :foreground "lightblue"))) t)
'(custom-variable-tag-face ((t (:underline t :foreground "lightblue"))) t)
'(font-lock-builtin-face ((t nil)))
'(font-lock-comment-face ((t (:foreground "#bf9319"))))
'(font-lock-function-name-face ((((class color) (background dark)) (:foreground "white"))))
'(font-lock-keyword-face ((t (:foreground "white" ))))
'(font-lock-string-face ((t (:foreground "#8fcddb"))))
'(font-lock-variable-name-face ((((class color) (background dark)) (:foreground "#c8d4ec"))))
'(font-lock-warning-face ((t (:foreground "#504038"))))
'(highlight ((t (:foreground "navyblue" :background "darkseagreen2"))))
'(mode-line ((t (:inverse-video t))))
'(region ((t (:background "blue"))))
'(widget-field-face ((t (:foreground "white"))) t)
'(widget-single-line-field-face ((t (:background "darkgray"))) t))
(set-background-color "#181E2C")
(global-font-lock-mode 1)
(set-cursor-color "lightgreen")

3056
.emacs.d/lisp/go-mode.el Executable file

File diff suppressed because it is too large Load Diff

233
.emacs.d/lisp/jai-mode.el Executable file
View File

@@ -0,0 +1,233 @@
;;; jai-mode.el --- Major mode for JAI -*- lexical-binding: t; -*-
;; Copyright (C) 2015-2023 Kristoffer Grönlund
;; Author: Kristoffer Grönlund <k@ziran.se>
;; Maintainer: Kristoffer Grönlund <k@ziran.se>
;; URL: https://github.com/krig/jai-mode
;; Version: 0.0.1
;; Package-Requires: ((emacs "26.1"))
;; Keywords: languages
;; This file is not part of GNU Emacs.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Major mdoe for JAI
;;
;;; Code:
(require 'rx)
(require 'js)
(require 'compile)
(defconst jai-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?\" "\"" table)
(modify-syntax-entry ?\\ "\\" table)
;; additional symbols
(modify-syntax-entry ?_ "w" table)
(modify-syntax-entry ?' "." table)
(modify-syntax-entry ?: "." table)
(modify-syntax-entry ?+ "." table)
(modify-syntax-entry ?- "." table)
(modify-syntax-entry ?% "." table)
(modify-syntax-entry ?& "." table)
(modify-syntax-entry ?| "." table)
(modify-syntax-entry ?^ "." table)
(modify-syntax-entry ?! "." table)
(modify-syntax-entry ?= "." table)
(modify-syntax-entry ?< "." table)
(modify-syntax-entry ?> "." table)
(modify-syntax-entry ?? "." table)
;; Modify some syntax entries to allow nested block comments
(modify-syntax-entry ?/ ". 124b" table)
(modify-syntax-entry ?* ". 23n" table)
(modify-syntax-entry ?\n "> b" table)
(modify-syntax-entry ?\^m "> b" table)
table))
(defconst jai-builtins
'("it" "it_index"))
(defconst jai-keywords
'("if" "ifx" "else" "then" "while" "for" "switch" "case" "struct" "enum"
"return" "remove" "continue" "break" "defer" "inline" "no_inline"
"using" "code_of" "initializer_of" "size_of" "type_of" "cast" "type_info"
"null" "true" "false" "xx" "context" "operator" "push_context" "is_constant"
"enum_flags" "union" "interface"))
(defconst jai-typenames
'("int" "u64" "u32" "u16" "u8"
"s64" "s32" "s16" "s8" "float"
"float32" "float64" "string"
"bool"))
(defun jai-wrap-word-rx (s)
(concat "\\<" s "\\>"))
(defun jai-keywords-rx (keywords)
"build keyword regexp"
(jai-wrap-word-rx (regexp-opt keywords t)))
(defconst jai-hat-type-rx (rx (group (and "^" (1+ word)))))
(defconst jai-dollar-type-rx (rx (group "$" (or (1+ word) (opt "$")))))
(defconst jai-number-rx
(rx (and
symbol-start
(or (and (+ digit) (opt (and (any "eE") (opt (any "-+")) (+ digit))))
(and "0" (any "xX") (+ hex-digit)))
(opt (and (any "_" "A-Z" "a-z") (* (any "_" "A-Z" "a-z" "0-9"))))
symbol-end)))
(defconst jai-font-lock-defaults
`(;; Keywords
(,(jai-keywords-rx jai-keywords) 1 font-lock-keyword-face)
;; single quote characters
("\\('[[:word:]]\\)\\>" 1 font-lock-constant-face)
;; Variables
(,(jai-keywords-rx jai-builtins) 1 font-lock-variable-name-face)
;; Hash directives
("#\\w+" . font-lock-preprocessor-face)
;; At notes
("@\\w+" . font-lock-preprocessor-face)
;; Strings
("\\\".*\\\"" . font-lock-string-face)
;; Numbers
(,(jai-wrap-word-rx jai-number-rx) . font-lock-constant-face)
;; Types
(,(jai-keywords-rx jai-typenames) 1 font-lock-type-face)
(,jai-hat-type-rx 1 font-lock-type-face)
(,jai-dollar-type-rx 1 font-lock-type-face)
("---" . font-lock-constant-face)))
;; add setq-local for older emacs versions
(unless (fboundp 'setq-local)
(defmacro setq-local (var val)
`(set (make-local-variable ',var) ,val)))
(defconst jai--defun-rx "\(.*\).*\{")
(defmacro jai-paren-level ()
`(car (syntax-ppss)))
(defun jai-line-is-defun ()
"return t if current line begins a procedure"
(interactive)
(save-excursion
(beginning-of-line)
(let (found)
(while (and (not (eolp)) (not found))
(if (looking-at jai--defun-rx)
(setq found t)
(forward-char 1)))
found)))
(defun jai-beginning-of-defun ()
"Go to line on which current function starts."
(interactive)
(let ((orig-level (jai-paren-level)))
(while (and
(not (jai-line-is-defun))
(not (bobp))
(> orig-level 0))
(setq orig-level (jai-paren-level))
(while (>= (jai-paren-level) orig-level)
(skip-chars-backward "^{")
(backward-char))))
(when (jai-line-is-defun)
(beginning-of-line)))
(defun jai-end-of-defun ()
"Go to line on which current function ends."
(interactive)
(let ((orig-level (jai-paren-level)))
(when (> orig-level 0)
(jai-beginning-of-defun)
(end-of-line)
(setq orig-level (jai-paren-level))
(skip-chars-forward "^}")
(while (>= (jai-paren-level) orig-level)
(skip-chars-forward "^}")
(forward-char)))))
(defalias 'jai-parent-mode
(if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
;; imenu hookup
(add-hook 'jai-mode-hook
(lambda ()
(setq imenu-generic-expression
'(("type" "^\\(.*:*.*\\) : " 1)
("function" "^\\(.*\\) :: " 1)
("struct" "^\\(.*\\) *:: *\\(struct\\)\\(.*\\){" 1)))))
;; NOTE: taken from the scala-indent package and modified for Jai.
;; Still uses the js-indent-line as a base, which will have to be
;; replaced when the language is more mature.
(defun jai--indent-on-parentheses ()
(when (and (= (char-syntax (char-before)) ?\))
(= (save-excursion (back-to-indentation) (point)) (1- (point))))
(js-indent-line)))
(defun jai--add-self-insert-hooks ()
(add-hook 'post-self-insert-hook
'jai--indent-on-parentheses))
;;;###autoload
(define-derived-mode jai-mode jai-parent-mode "Jai"
:syntax-table jai-mode-syntax-table
:group 'jai
(setq bidi-paragraph-direction 'left-to-right)
(setq-local require-final-newline mode-require-final-newline)
(setq-local parse-sexp-ignore-comments t)
(setq-local comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
(setq-local comment-start "//")
(setq-local block-comment-start "/*")
(setq-local block-comment-end "*/")
(setq-local indent-line-function 'js-indent-line)
(setq-local font-lock-defaults '(jai-font-lock-defaults))
(setq-local beginning-of-defun-function 'jai-beginning-of-defun)
(setq-local end-of-defun-function 'jai-end-of-defun)
;; add indent functionality to some characters
(jai--add-self-insert-hooks)
(font-lock-ensure))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.jai\\'" . jai-mode))
(defconst jai--error-regexp
"^\\([^ \n:]+.*\.jai\\):\\([0-9]+\\),\\([0-9]+\\):")
(push `(jai ,jai--error-regexp 1 2 3 2) compilation-error-regexp-alist-alist)
(push 'jai compilation-error-regexp-alist)
(provide 'jai-mode)
;;; jai-mode.el ends here

View File

@@ -0,0 +1,145 @@
;;; stupid-indent-mode.el --- Plain stupid indentation minor mode
;; Copyright (C) 2013 Mihai Bazon
;; Author: Mihai Bazon <mihai.bazon@gmail.com>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Dumb indentation mode is appropriate for editing buffers that Emacs
;; does not fully understand syntactically, such as HTML/PHP
;; (typically involving multiple languages with different indentation
;; rules in the same buffer). The default indentation level is 2
;; (customize `stupid-indent-level').
;;
;; Key bindings:
;;
;; TAB -- indent current line by the value of `stupid-indent-level'
;; S-TAB -- outdent current line
;; C-c TAB -- indent region
;; C-c S-TAB -- outdent region
;; RET -- newline and indent
;; C-c C-TAB -- indent according to mode
;;; Code:
(defcustom stupid-indent-level 2
"Indentation level for stupid-indent-mode")
(defun %stupid-force-indent-line ()
(let (col)
(save-excursion
(back-to-indentation)
(setq col (+ (current-column) stupid-indent-level))
(indent-line-to col))
(when (< (current-column) col)
(back-to-indentation))))
(defun stupid-indent-line ()
(interactive)
(let ((bt (save-excursion
(back-to-indentation)
(current-column))))
(cond
((< (current-column) bt)
(back-to-indentation))
((looking-at "\\s-*\n")
(let ((col (save-excursion
(previous-line)
(back-to-indentation)
(current-column))))
(if (< (current-column) col)
(indent-line-to col)
(%stupid-force-indent-line))))
(t
(%stupid-force-indent-line)))))
(defun stupid-outdent-line ()
(interactive)
(let (col)
(save-excursion
(back-to-indentation)
(setq col (- (current-column) stupid-indent-level))
(when (>= col 0)
(indent-line-to col)))))
(defun stupid-indent-region (start stop)
(interactive "r")
(setq stop (copy-marker stop))
(goto-char start)
(while (< (point) stop)
(unless (and (bolp) (eolp))
(%stupid-force-indent-line))
(forward-line 1)))
(defun stupid-outdent-region (start stop)
(interactive "r")
(setq stop (copy-marker stop))
(goto-char start)
(while (< (point) stop)
(unless (and (bolp) (eolp))
(stupid-outdent-line))
(forward-line 1)))
(defun stupid-indent ()
(interactive)
(if (use-region-p)
(save-excursion
(stupid-indent-region (region-beginning) (region-end))
(setq deactivate-mark nil))
(stupid-indent-line)))
(defun stupid-outdent ()
(interactive)
(if (use-region-p)
(save-excursion
(stupid-outdent-region (region-beginning) (region-end))
(setq deactivate-mark nil))
(stupid-outdent-line)))
(defun stupid-indent-newline ()
(interactive)
(when (< (point)
(save-excursion
(back-to-indentation)
(point)))
(back-to-indentation))
(let ((col (save-excursion
(back-to-indentation)
(current-column))))
(newline)
(indent-to-column col)))
(define-minor-mode stupid-indent-mode
"Stupid indent mode is just plain stupid."
:init-value nil
:lighter "/SI"
:global nil
:keymap `(
(,(kbd "<tab>") . stupid-indent)
(,(kbd "<backtab>") . stupid-outdent)
(,(kbd "C-c <tab>") . stupid-indent-region)
(,(kbd "C-c <backtab>") . stupid-outdent-region)
(,(kbd "<return>") . stupid-indent-newline)
(,(kbd "C-c C-<tab>") . indent-according-to-mode)
)
(when stupid-indent-mode
(add-hook 'write-contents-functions
'delete-trailing-whitespace)))
(provide 'stupid-indent-mode)
;;; stupid-indent-mode.el ends here

15046
.emacs.d/lisp/web-mode.el Executable file

File diff suppressed because it is too large Load Diff

731
.emacs.d/lisp/xah-find.el Executable file
View File

@@ -0,0 +1,731 @@
;;; xah-find.el --- find replace in pure emacs lisp. Purpose similar to grep/sed. -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2012-2021 by Xah Lee
;; Author: Xah Lee ( http://xahlee.info/ )
;; Version: 5.4.20211014135145
;; Created: 02 April 2012
;; Package-Requires: ((emacs "24.1"))
;; Keywords: convenience, extensions, files, tools, unix
;; License: GPL v3
;; Homepage: http://ergoemacs.org/emacs/elisp-xah-find-text.html
;; This file is not part of GNU Emacs.
;;; Commentary:
;; Provides emacs commands for find/replace text of files in a directory, written entirely in emacs lisp.
;; This package provides these commands:
;; xah-find-text
;; xah-find-text-regex
;; xah-find-count
;; xah-find-replace-text
;; xah-find-replace-text-regex
;; • Pure emacs lisp. No dependencies on unix/linux grep/sed/find. Especially useful on Windows.
;; • Output is highlighted and clickable for jumping to occurrence.
;; • Using emacs regex, not bash/perl etc regex.
;; These commands treats find/replace string as sequence of chars, not as lines as in grep/sed, so it's easier to find or replace a text containing lots newlines, especially programming language source code.
;; • Reliably Find/Replace string that contains newline chars.
;; • Reliably Find/Replace string that contains lots Unicode chars. See http://xahlee.info/comp/unix_uniq_unicode_bug.html and http://ergoemacs.org/emacs/emacs_grep_problem.html
;; • Reliably Find/Replace string that contains lots escape slashes or backslashes. For example, regex in source code, Microsoft Windows' path.
;; The result output is also not based on lines. Instead, visual separators are used for easy reading.
;; For each occurrence or replacement, n chars will be printed before and after. The number of chars to show is defined by `xah-find-context-char-count-before' and `xah-find-context-char-count-after'
;; Each “block of text” in output is one occurrence.
;; For example, if a line in a file has 2 occurrences, then the same line will be reported twice, as 2 “blocks”.
;; so, the number of blocks corresponds exactly to the number of occurrences.
;; Keys
;; -----------------------
;; TAB xah-find-next-match
;; <backtab> xah-find-previous-match
;; RET xah-find--jump-to-place
;; <mouse-1> xah-find--mouse-jump-to-place
;; <left> xah-find-previous-match
;; <right> xah-find-next-match
;; <down> xah-find-next-file
;; <up> xah-find-previous-file
;; M-n xah-find-next-file
;; M-p xah-find-previous-file
;; IGNORE DIRECTORIES
;; By default, .git dir is ignored. You can add to it by adding the following in your init:
;; (setq
;; xah-find-dir-ignore-regex-list
;; [
;; "\\.git/"
;; ; more regex here. regex is matched against file full path
;; ])
;; USE CASE
;; To give a idea what file size, number of files, are practical, here's my typical use pattern:
;; • 5 thousand HTML files match file name regex.
;; • Each HTML file size are usually less than 200k bytes.
;; • search string length have been up to 13 lines of text.
;; Homepage: http://ergoemacs.org/emacs/elisp-xah-find-text.html
;; Like it?
;; Buy Xah Emacs Tutorial
;; http://ergoemacs.org/emacs/buy_xah_emacs_tutorial.html
;; Thank you.
;;; INSTALL
;; To install manually, place this file in the directory [~/.emacs.d/lisp/]
;; Then, place the following code in your emacs init file
;; (add-to-list 'load-path "~/.emacs.d/lisp/")
;; (autoload 'xah-find-text "xah-find" "find replace" t)
;; (autoload 'xah-find-text-regex "xah-find" "find replace" t)
;; (autoload 'xah-find-replace-text "xah-find" "find replace" t)
;; (autoload 'xah-find-replace-text-regex "xah-find" "find replace" t)
;; (autoload 'xah-find-count "xah-find" "find replace" t)
;;; HISTORY
;; version 2.1.0, 2015-05-30 Complete rewrite.
;; version 1.0, 2012-04-02 First version.
;;; CONTRIBUTOR
;; 2015-12-09 Peter Buckley (dx-pbuckley). defcustom for result highlight color.
;; HHH___________________________________________________________________
;;; Code:
(require 'ido)
(require 'seq)
(ido-common-initialization)
;; 2015-07-26 else, when ido-read-directory-name is called, Return key insert line return instead of submit. For some reason i dunno.
(defvar xah-find-context-char-count-before 100 "Number of characters to print before search string." )
(defvar xah-find-context-char-count-after 50 "Number of characters to print after search string." )
(defvar xah-find-dir-ignore-regex-list
[
"\\.git/"
]
"A list or vector of regex patterns, if match, that directory will be ignored.
The regex match is Case Insensitive."
)
(defface xah-find-file-path-highlight
'((t :foreground "black"
:background "pink"
))
"Face of file path where a text match is found."
:group 'xah-find
)
(defface xah-find-match-highlight
'((t :foreground "black"
:background "yellow"
))
"Face for matched text."
:group 'xah-find
)
(defface xah-find-replace-highlight
'((t :foreground "black"
:background "green"
))
"Face for replaced text."
:group 'xah-find
)
(defvar xah-find-file-separator
"ff━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
"A string as visual separator."
)
(defvar xah-find-occur-separator
"oo────────────────────────────────────────────────────────────\n\n"
"A string as visual separator."
)
(defvar xah-find-occur-prefix "" "A left-bracket string that marks matched text and navigate previous/next. This string should basically never occure in your files. If it does, jumping to the location may not work." )
(defvar xah-find-occur-postfix "" "A right-bracket string that marks matched text and navigate previous/next. See also `xah-find-occur-prefix'." )
(defvar xah-find-replace-prefix "" "A left-bracket string that marks matched text and navigate previous/next. See also `xah-find-occur-prefix'." )
(defvar xah-find-replace-postfix "" "A right-bracket string that marks matched text and navigate previous/next. See also `xah-find-occur-prefix'." )
;; more brackets at http://xahlee.info/comp/unicode_matching_brackets.html
(defvar xah-find-filepath-prefix "" "A left-bracket string used to mark file path and navigate previous/next. See also `xah-find-occur-prefix'." )
(defvar xah-find-filepath-postfix "" "A right-bracket string used to mark file path and navigate previous/next. See also `xah-find-occur-prefix'." )
(defvar xah-find-pos-prefix "" "A string of left bracket that marks line column position of occurrence. See also `xah-find-occur-prefix'." )
(defvar xah-find-pos-postfix "" "A string of right bracket that marks line column position of occurrence. See also `xah-find-occur-prefix'." )
;; HHH___________________________________________________________________
(defvar xah-find-file-path-regex-history '() "File path regex history list, used by `xah-find-text' and others.")
(defun xah-find--ignore-dir-p (Path)
"Return true if one of `xah-find-dir-ignore-regex-list' matches PATH. Else, nil.
version 2016-11-16 2021-10-11"
(let ((case-fold-search t))
(catch 'exit25001
(mapc
(lambda ($regex)
(when (string-match $regex Path) (throw 'exit25001 $regex)))
xah-find-dir-ignore-regex-list)
nil
)))
;; HHH___________________________________________________________________
(defvar xah-find-output-mode-map nil "Keybinding for `xah-find.el output'")
(progn
(setq xah-find-output-mode-map (make-sparse-keymap))
(define-key xah-find-output-mode-map (kbd "<left>") 'xah-find-previous-match)
(define-key xah-find-output-mode-map (kbd "<right>") 'xah-find-next-match)
(define-key xah-find-output-mode-map (kbd "<down>") 'xah-find-next-file)
(define-key xah-find-output-mode-map (kbd "<up>") 'xah-find-previous-file)
(define-key xah-find-output-mode-map (kbd "TAB") 'xah-find-next-match)
(define-key xah-find-output-mode-map (kbd "<backtab>") 'xah-find-previous-match)
(define-key xah-find-output-mode-map (kbd "<mouse-1>") 'xah-find--mouse-jump-to-place)
(define-key xah-find-output-mode-map (kbd "M-n") 'xah-find-next-file)
(define-key xah-find-output-mode-map (kbd "M-p") 'xah-find-previous-file)
(define-key xah-find-output-mode-map (kbd "RET") 'xah-find--jump-to-place)
)
(defvar xah-find-output-syntax-table nil "Syntax table for `xah-find-output-mode'.")
(setq xah-find-output-syntax-table
(let ( (synTable (make-syntax-table)))
(modify-syntax-entry ?\" "." synTable)
;; (modify-syntax-entry ?〖 "(〗" synTable)
;; (modify-syntax-entry ?〗 "(〖" synTable)
synTable))
(setq xah-find-font-lock-keywords
(let (
(xMatch (format "%s\\([^%s]+\\)%s" xah-find-occur-prefix xah-find-occur-postfix xah-find-occur-postfix))
(xRep (format "%s\\([^%s]+\\)%s" xah-find-replace-prefix xah-find-replace-postfix xah-find-replace-postfix))
(xfPath (format "%s\\([^%s]+\\)%s" xah-find-filepath-prefix xah-find-filepath-postfix xah-find-filepath-postfix)))
`(
(,xMatch . (1 'xah-find-match-highlight))
(,xRep . (1 'xah-find-replace-highlight))
(,xfPath . (1 'xah-find-file-path-highlight)))))
(define-derived-mode xah-find-output-mode fundamental-mode "∑xah-find"
"Major mode for reading output for xah-find commands.
home page:
URL `http://ergoemacs.org/emacs/elisp-xah-find-text.html'
\\{xah-find-output-mode-map}
Version 2021-06-23"
(setq font-lock-defaults '((xah-find-font-lock-keywords)))
(set-syntax-table xah-find-output-syntax-table))
(defun xah-find-next-match ()
"Put cursor to next occurrence."
(interactive)
(search-forward xah-find-occur-prefix nil t ))
(defun xah-find-previous-match ()
"Put cursor to previous occurrence."
(interactive)
(search-backward xah-find-occur-postfix nil t ))
(defun xah-find-next-file ()
"Put cursor to next file."
(interactive)
(search-forward xah-find-filepath-prefix nil t ))
(defun xah-find-previous-file ()
"Put cursor to previous file."
(interactive)
(search-backward xah-find-filepath-postfix nil t ))
(defun xah-find--mouse-jump-to-place (Event)
"Open file and put cursor at location of the occurrence.
Version 2016-12-18"
(interactive "e")
(let* (
($pos (posn-point (event-end Event)))
($fpath (get-text-property $pos 'xah-find-fpath))
($posJumpTo (get-text-property $pos 'xah-find-pos)))
(when $fpath
(progn
(find-file-other-window $fpath)
(when $posJumpTo (goto-char $posJumpTo))))))
;; (defun xah-find--jump-to-place ()
;; "Open file and put cursor at location of the occurrence.
;; Version 2017-04-07"
;; (interactive)
;; (let (($fpath (get-text-property (point) 'xah-find-fpath))
;; ($posJumpTo (get-text-property (point) 'xah-find-pos)))
;; (if $fpath
;; (if (file-exists-p $fpath)
;; (progn
;; (find-file-other-window $fpath)
;; (when $posJumpTo (goto-char $posJumpTo)))
;; (error "File at 「%s」 does not exist." $fpath))
;; (insert "\n"))))
(defun xah-find--jump-to-place ()
"Open file and put cursor at location of the occurrence.
Version 2019-03-14"
(interactive)
(let (($fpath (get-text-property (point) 'xah-find-fpath))
($posJumpTo (get-text-property (point) 'xah-find-pos))
($p0 (point))
$p1 $p2
)
(if $fpath
(if (file-exists-p $fpath)
(progn
(find-file-other-window $fpath)
(when $posJumpTo (goto-char $posJumpTo)))
(error "File at 「%s」 does not exist." $fpath))
(progn
(save-excursion
(goto-char $p0)
;; (if (eq (char-after (line-beginning-position)) (string-to-char xah-find-filepath-prefix ))
;; (progn )
;; (progn ))
(search-forward xah-find-file-separator)
(search-backward xah-find-filepath-prefix )
(setq $p1 (1+ (point)))
(search-forward xah-find-filepath-postfix)
(setq $p2 (1- (point)))
(setq $fpath (buffer-substring-no-properties $p1 $p2))
(progn
(goto-char $p0)
(if (search-backward xah-find-pos-prefix nil t)
(progn
(setq $p1 (1+ (point)))
(search-forward xah-find-pos-postfix )
(setq $p2 (1- (point)))
(setq $posJumpTo (string-to-number (buffer-substring-no-properties $p1 $p2))))
(setq $posJumpTo nil))))
(if (file-exists-p $fpath)
(progn
(find-file-other-window $fpath)
(when $posJumpTo (goto-char $posJumpTo)))
(error "File at 「%s」 does not exist." $fpath))))))
;; HHH___________________________________________________________________
(defun xah-find--backup-suffix (S)
"Return a string of the form 「~S~date time stamp~」"
(concat "~" S (format-time-string "%Y%m%dT%H%M%S") "~"))
(defun xah-find--current-date-time-string ()
"Return current date-time string in this format 「2012-04-05T21:08:24-07:00」"
(concat
(format-time-string "%Y-%m-%dT%T")
(funcall (lambda (x) (format "%s:%s" (substring x 0 3) (substring x 3 5))) (format-time-string "%z"))))
(defun xah-find--print-header (BufferObj Cmd InputDir PathRegex SearchStr &optional ReplaceStr Write-file-p BackupQ)
"Print things"
(princ
(concat
"-*- coding: utf-8; mode: xah-find-output -*-" "\n"
"Datetime: " (xah-find--current-date-time-string) "\n"
"Result of: " Cmd "\n"
(format "Directory: %s\n" InputDir )
(format "Path regex: %s\n" PathRegex )
(format "Write to file: %s\n" Write-file-p )
(format "Backup: %s\n" BackupQ )
(format "Search string: %s\n" SearchStr )
(when ReplaceStr (format "Replace string [[%s]]\n" ReplaceStr))
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"
)
BufferObj))
(defun xah-find--occur-output (P1 P2 Fpath Buff &optional NoContextString-p AltColor)
"Print result to a output buffer, with text properties (e.g. highlight and link).
P1 P2 are region boundary. Region of current buffer are grabbed. The region typically is the searched text.
Fpath is file path to be used as property value for clickable link.
Buff is the buffer to insert P1 P2 region.
NoContextString-p if true, don't add text before and after the region of interest. Else, `xah-find-context-char-count-before' number of chars are inserted before, and similar for `xah-find-context-char-count-after'.
AltColor if true, use a different highlight color face `xah-find-replace-highlight'. Else, use `xah-find-match-highlight'.
Version 2017-04-07 2021-08-05"
(let* (
($begin (max 1 (- P1 xah-find-context-char-count-before )))
($end (min (point-max) (+ P2 xah-find-context-char-count-after )))
($textBefore (if NoContextString-p "" (buffer-substring-no-properties $begin P1 )))
$textMiddle
($textAfter (if NoContextString-p "" (buffer-substring-no-properties P2 $end)))
($face (if AltColor 'xah-find-replace-highlight 'xah-find-match-highlight))
$bracketL $bracketR
)
(put-text-property P1 P2 'face $face)
(put-text-property P1 P2 'xah-find-fpath Fpath)
(put-text-property P1 P2 'xah-find-pos P1)
(add-text-properties P1 P2 '(mouse-face highlight))
(setq $textMiddle (buffer-substring P1 P2 ))
(if AltColor
(setq $bracketL xah-find-replace-prefix $bracketR xah-find-replace-postfix )
(setq $bracketL xah-find-occur-prefix $bracketR xah-find-occur-postfix ))
(with-current-buffer Buff
(insert
(format "%s%s%s\n" xah-find-pos-prefix P1 xah-find-pos-postfix)
$textBefore
$bracketL
$textMiddle
$bracketR
$textAfter
"\n"
xah-find-occur-separator ))))
;; (defun xah-find--print-replace-block (P1 P2 Buff)
;; "print "
;; (princ (concat "❬" (buffer-substring-no-properties P1 P2 ) "❭" "\n" xah-find-occur-separator) Buff))
(defun xah-find--print-file-count (Filepath4287 Count8086 BuffObj32)
"Print file path and count"
(princ (format "%d %s%s%s\n%s"
Count8086
xah-find-filepath-prefix
Filepath4287
xah-find-filepath-postfix
xah-find-file-separator)
BuffObj32))
(defun xah-find--switch-to-output (Buffer)
"switch to Buffer and highlight stuff"
(let ($p3 $p4)
(switch-to-buffer Buffer)
(progn
(goto-char (point-min))
(while (search-forward xah-find-filepath-prefix nil t)
(setq $p3 (point))
(search-forward xah-find-filepath-postfix nil nil)
(setq $p4 (match-beginning 0))
(put-text-property $p3 $p4 'xah-find-fpath (buffer-substring-no-properties $p3 $p4))
(add-text-properties $p3 $p4 '(mouse-face highlight))
(put-text-property (line-beginning-position) (line-end-position) 'face 'xah-find-file-path-highlight)))
(goto-char (point-min))
(search-forward "" nil t) ; todo, need fix
(search-forward xah-find-occur-prefix nil t)
(xah-find-output-mode)
))
;; HHH___________________________________________________________________
(defun xah-find--get-fpath-regex (&optional DefaultExt)
"Returns a string, that is a regex to match a file extension.
The result is based on current buffer's file extension.
If current file doesn't have extension or current buffer isn't a file, then extension DefaultExt is used.
DefaultExt should be a string, without dot, such as 「\"html\"」.
If DefaultExt is nil, 「\"html\"」 is used.
Example return value: 「ββ.htmlββ'」, where β is a backslash.
"
(let (
($buff-is-file-p (buffer-file-name))
$fname-ext
$default-ext
)
(setq $default-ext (if (null DefaultExt)
(progn "html")
(progn DefaultExt)))
(if $buff-is-file-p
(progn
(setq $fname-ext (file-name-extension (buffer-file-name)))
(if (or (null $fname-ext) (equal $fname-ext ""))
(progn (concat "\\." $default-ext "$"))
(progn (concat "\\." $fname-ext "$"))))
(progn (concat "\\." $default-ext "$")))))
;;;###autoload
(defun xah-find-count (SearchStr CountExpr CountNumber InputDir PathRegex)
"Report how many occurrences of a string, of a given dir.
Similar to `rgrep', but written in pure elisp.
Result is shown in buffer *xah-find output*.
Case sensitivity is determined by `case-fold-search'. Call `toggle-case-fold-search' to change.
`xah-find-dir-ignore-regex-list' is respected.
\\{xah-find-output-mode-map}
Version 2021-10-11"
(interactive
(let ( $operator)
(list
(read-string (format "Search string (default %s): " (current-word)) nil 'query-replace-history (current-word))
(setq $operator (ido-completing-read "Report on: " '("greater than" "greater or equal to" "equal" "not equal" "less than" "less or equal to" )))
(read-string (format "Count %s: " $operator) "0")
(ido-read-directory-name "Directory: " default-directory default-directory "MUSTMATCH")
(read-from-minibuffer "File path regex: " (xah-find--get-fpath-regex "el") nil nil 'dired-regexp-history))))
(let* (($outBufName "*xah-find output*")
$outBuffer
($countOperator
(cond
((string-equal "less than" CountExpr ) '<)
((string-equal "less or equal to" CountExpr ) '<=)
((string-equal "greater than" CountExpr ) '>)
((string-equal "greater or equal to" CountExpr ) '>=)
((string-equal "equal" CountExpr ) '=)
((string-equal "not equal" CountExpr ) '/=)
(t (error "count expression 「%s」 is wrong!" CountExpr ))))
($countNumber (string-to-number CountNumber)))
(when (get-buffer $outBufName) (kill-buffer $outBufName))
(setq $outBuffer (generate-new-buffer $outBufName))
(xah-find--print-header $outBuffer "xah-find-count" InputDir PathRegex SearchStr )
(mapc
(lambda ($f)
(let (($count 0))
(with-temp-buffer
(insert-file-contents $f)
(goto-char (point-min))
(while (search-forward SearchStr nil t) (setq $count (1+ $count)))
(when (funcall $countOperator $count $countNumber)
(xah-find--print-file-count $f $count $outBuffer)))))
(seq-filter (lambda (x) (not (xah-find--ignore-dir-p x)))
(directory-files-recursively InputDir PathRegex)))
(princ "Done." $outBuffer)
(xah-find--switch-to-output $outBuffer)))
;;;###autoload
(defun xah-find-text (SearchStr InputDir PathRegex FixedCaseSearchQ PrintContext-p)
"Report files that contain string.
By default, not case sensitive, and print surrounding text.
If `universal-argument' is called first, prompt to ask.
`xah-find-dir-ignore-regex-list' is respected.
Result is shown in buffer *xah-find output*.
\\{xah-find-output-mode-map}
version 2021-10-11"
(interactive
(let (($defaultInput (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) (current-word))))
(list
(read-string (format "Search string (default %s): " $defaultInput) nil 'query-replace-history $defaultInput)
(ido-read-directory-name "Directory: " default-directory default-directory "MUSTMATCH")
(read-from-minibuffer "File path regex: " (xah-find--get-fpath-regex "html") nil nil 'dired-regexp-history)
(if current-prefix-arg (y-or-n-p "Fixed case in search?") nil )
(if current-prefix-arg (y-or-n-p "Print surrounding Text?") t ))))
(let* ((case-fold-search (not FixedCaseSearchQ))
($count 0)
($outBufName "*xah-find output*")
$outBuffer
)
(setq InputDir (file-name-as-directory InputDir)) ; normalize dir path
(when (get-buffer $outBufName) (kill-buffer $outBufName))
(setq $outBuffer (generate-new-buffer $outBufName))
(xah-find--print-header $outBuffer "xah-find-text" InputDir PathRegex SearchStr )
(mapc
(lambda ($path)
(setq $count 0)
(with-temp-buffer
(insert-file-contents $path)
(while (search-forward SearchStr nil t)
(setq $count (1+ $count))
(when PrintContext-p (xah-find--occur-output (match-beginning 0) (match-end 0) $path $outBuffer)))
(when (> $count 0) (xah-find--print-file-count $path $count $outBuffer))))
(seq-filter (lambda (x) (not (xah-find--ignore-dir-p x)))
(directory-files-recursively InputDir PathRegex)))
(princ "Done." $outBuffer)
(xah-find--switch-to-output $outBuffer)))
(defun xah-find-count-slash (Path)
"Count the number of slash in path.
Useful for finding the level of a nested dir.
Note: you should probably call `expand-file-name' on Path first to canonize path, to make sure dir name always ends in slash.
Version 2021-10-11"
(interactive)
(seq-count (lambda (x) (char-equal x ?/)) Path))
;;;###autoload
(defun xah-find-replace-text (SearchStr ReplaceStr InputDir PathRegex DepthMin DepthMax WriteToFileQ FixedCaseSearchQ FixedCaseReplaceQ BackupQ)
"Find/Replace string in all files of a directory.
Search string can span multiple lines.
Search string is not regex.
`xah-find-dir-ignore-regex-list' is respected.
Backup, if requested, backup filenames has suffix with timestamp, like this: ~xf20150531T233826~
Result is shown in buffer *xah-find output*.
\\{xah-find-output-mode-map}
version 2021-10-11"
(interactive
(let (($searchStr (read-string (format "Search string (default %s): " (current-word)) nil 'query-replace-history (current-word)))
($replaceStr (read-string "Replace string: " nil 'query-replace-history))
($inputDir (ido-read-directory-name "Directory: " default-directory default-directory "MUSTMATCH"))
($pathRegex (read-from-minibuffer "File path regex: " (xah-find--get-fpath-regex "el") nil nil 'dired-regexp-history))
;; ($recurseQ (yes-or-no-p "Recurse to subdirs?"))
($depthMin (read-number "Min dir depth. Start dir has depth 0:" 0))
($depthMax (read-number "Max dir depth. (max+1 depth subdir files are excluded):" 9))
($writeToFileQ (y-or-n-p "Write changes to file?"))
($fixedCaseSearchQ (y-or-n-p "Fixed case in search?"))
($fixedCaseReplaceQ (y-or-n-p "Fixed case in replacement?"))
($backupQ (y-or-n-p "Make backup?")))
(list $searchStr $replaceStr $inputDir $pathRegex
$depthMin $depthMax
$writeToFileQ $fixedCaseSearchQ $fixedCaseReplaceQ $backupQ)))
(let (($outBufName "*xah-find output*")
$outBuffer
($backupSuffix (xah-find--backup-suffix "xf"))
($rootDepth (xah-find-count-slash (expand-file-name InputDir))))
(when (get-buffer $outBufName) (kill-buffer $outBufName))
(setq $outBuffer (generate-new-buffer $outBufName))
(xah-find--print-header $outBuffer "xah-find-replace-text" InputDir PathRegex SearchStr ReplaceStr WriteToFileQ BackupQ)
(mapc
(lambda ($f)
(let ((case-fold-search (not FixedCaseSearchQ))
($count 0))
(with-temp-buffer
(insert-file-contents $f)
(while (search-forward SearchStr nil t)
(setq $count (1+ $count))
(replace-match ReplaceStr FixedCaseReplaceQ "literalreplace")
(xah-find--occur-output (match-beginning 0) (point) $f $outBuffer))
(when (> $count 0)
(when WriteToFileQ
(when BackupQ (copy-file $f (concat $f $backupSuffix) t))
(write-region (point-min) (point-max) $f nil 3))
(xah-find--print-file-count $f $count $outBuffer)))))
(seq-filter
(lambda (x)
(let (($df (- (xah-find-count-slash x) $rootDepth)))
(and (>= $df DepthMin) (<= $df DepthMax))))
(directory-files-recursively InputDir PathRegex)))
(princ "Done." $outBuffer)
(xah-find--switch-to-output $outBuffer)))
;;;###autoload
(defun xah-find-text-regex (SearchRegex InputDir PathRegex RecurseQ FixedCaseSearchQ PrintContextLevel)
"Report files that contain a string pattern, similar to `rgrep'.
Result is shown in buffer *xah-find output*.
`xah-find-dir-ignore-regex-list' is respected.
\\{xah-find-output-mode-map}
Version 2016-12-21 2021-10-11"
(interactive
(list
(read-string (format "Search regex (default %s): " (current-word)) nil 'query-replace-history (current-word))
(ido-read-directory-name "Directory: " default-directory default-directory "MUSTMATCH")
(read-from-minibuffer "File path regex: " (xah-find--get-fpath-regex "el") nil nil 'dired-regexp-history)
(yes-or-no-p "Recurse to subdirs?")
(y-or-n-p "Fixed case search?")
(ido-completing-read "Print context level: " '("with context string" "just matched pattern" "none"))))
(let (($count 0)
($outBufName "*xah-find output*")
$outBuffer
)
(setq InputDir (file-name-as-directory InputDir)) ; add ending slash
(when (get-buffer $outBufName) (kill-buffer $outBufName))
(setq $outBuffer (generate-new-buffer $outBufName))
(xah-find--print-header $outBuffer "xah-find-text-regex" InputDir PathRegex SearchRegex)
(mapc
(lambda ($fp)
(setq $count 0)
(with-temp-buffer
(insert-file-contents $fp)
(setq case-fold-search (not FixedCaseSearchQ))
(while (re-search-forward SearchRegex nil t)
(setq $count (1+ $count))
(cond
((equal PrintContextLevel "none") nil)
((equal PrintContextLevel "just matched pattern")
(xah-find--occur-output (match-beginning 0) (match-end 0) $fp $outBuffer t))
((equal PrintContextLevel "with context string")
(xah-find--occur-output (match-beginning 0) (match-end 0) $fp $outBuffer))))
(when (> $count 0) (xah-find--print-file-count $fp $count $outBuffer))))
(seq-filter (lambda (x) (not (xah-find--ignore-dir-p x)))
(if RecurseQ
(directory-files-recursively InputDir PathRegex)
(directory-files InputDir t PathRegex))))
(princ "Done." $outBuffer)
(xah-find--switch-to-output $outBuffer)))
;;;###autoload
(defun xah-find-replace-text-regex (Regex ReplaceStr InputDir PathRegex WriteToFileQ FixedCaseSearchQ FixedCaseReplaceQ ShowcontexQ BackupQ)
"Find/Replace by regex in all files of a directory.
`xah-find-dir-ignore-regex-list' is respected.
Backup, if requested, backup filenames has suffix with timestamp, like this: ~xf20150531T233826~
When called in lisp code:
Regex is a regex pattern.
ReplaceStr is replacement string.
InputDir is input directory to search (includes all nested subdirectories).
PathRegex is a regex to filter file paths.
WriteToFileQ, when true, write to file, else, print a report of changes only.
FixedCaseSearchQ sets `case-fold-search' for this operation.
FixedCaseReplaceQ if true, then the letter-case in replacement is literal. (this is relevant only if FixedCaseSearchQ is true.)
ShowcontexQ print characters before and after match.
BackupQ if ture does backup.
Result is shown in buffer *xah-find output*.
\\{xah-find-output-mode-map}
Version 2018-08-20 2021-10-11"
(interactive
(list
(read-regexp "Find regex: " )
(read-string (format "Replace string: ") nil 'query-replace-history)
(ido-read-directory-name "Directory: " default-directory default-directory "MUSTMATCH")
(read-from-minibuffer "File path regex: " (xah-find--get-fpath-regex "el") nil nil 'dired-regexp-history)
(y-or-n-p "Write changes to file?")
(y-or-n-p "Fixed case in search?")
(y-or-n-p "Fixed case in replacement?")
(y-or-n-p "Show context before after in output?")
(y-or-n-p "Make backup?")))
(let (($outBufName "*xah-find output*")
$outBuffer
($backupSuffix (xah-find--backup-suffix "xfr")))
(when (get-buffer $outBufName) (kill-buffer $outBufName))
(setq $outBuffer (generate-new-buffer $outBufName))
(xah-find--print-header $outBuffer "xah-find-replace-text-regex" InputDir PathRegex Regex ReplaceStr WriteToFileQ BackupQ )
(mapc
(lambda ($fp)
(let (($count 0))
(with-temp-buffer
(insert-file-contents $fp)
(setq case-fold-search (not FixedCaseSearchQ))
(while (re-search-forward Regex nil t)
(setq $count (1+ $count))
;; (xah-find--print-occur-block (match-beginning 0) (match-end 0) $outBuffer)
(xah-find--occur-output (match-beginning 0) (match-end 0) $fp $outBuffer t)
(replace-match ReplaceStr FixedCaseReplaceQ)
(xah-find--occur-output (match-beginning 0) (point) $fp $outBuffer (not ShowcontexQ) t))
(when (> $count 0)
(xah-find--print-file-count $fp $count $outBuffer)
(when WriteToFileQ
(when BackupQ
(copy-file $fp (concat $fp $backupSuffix) t))
(write-region (point-min) (point-max) $fp nil 3))))))
(seq-filter (lambda (x) (not (xah-find--ignore-dir-p x)))
(directory-files-recursively InputDir PathRegex)))
(princ "Done." $outBuffer)
(xah-find--switch-to-output $outBuffer)))
(provide 'xah-find)
;;; xah-find.el ends here

BIN
.emacs.d/xahemacs_2021-10-17.zip Executable file

Binary file not shown.