;;; app/regex/autoload/regex.el (defvar +regex--text-buffer nil) (defvar +regex--text-replace-buffer nil) (defvar +regex--expr-buffer nil) (defvar +regex--expr-replace-buffer nil) (defvar +regex--groups-buffer nil) (defvar +regex--replace-buffer nil) ;; (defface +regex-match-0-face `((t (:foreground "Black" :background ,(doom-color 'magenta) :bold t))) "TODO" :group 'doom) (defface +regex-match-1-face `((t (:foreground "Black" :background ,(doom-color 'blue) :bold t))) "TODO" :group 'doom) (defface +regex-match-2-face `((t (:foreground "Black" :background ,(doom-color 'green) :bold t))) "TODO" :group 'doom) (defvar +regex-faces '(+regex-match-0-face +regex-match-1-face +regex-match-2-face) "TODO") ;; (defvar +regex-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c\C-c" #'+regex-update-buffers) (define-key map "\C-c\C-r" #'=regex/replace) (define-key map "\C-c\C-k" #'+regex/quit) (define-key map [remap doom-kill-buffer] #'+regex/quit) (define-key map [remap doom/kill-this-buffer] #'+regex/quit) (define-key map [remap kill-this-buffer] #'+regex/quit) (define-key map [remap kill-buffer] #'+regex/quit) map) "TODO") ;;;###autoload (define-minor-mode +regex-mode "TODO" :init-value nil :global nil :lighter "" :keymap +regex-mode-map (if +regex-mode (add-hook 'after-change-functions #'+regex-update-buffers nil t) (remove-hook 'after-change-functions #'+regex-update-buffers t))) ;;;###autoload (defun =regex (&optional dummy-text) "Start the Regex IDE." (interactive "P") (unless (buffer-live-p +regex--expr-buffer) (condition-case ex (progn (setq +regex--expr-buffer (get-buffer-create "*doom-regex*") +regex--text-buffer (if dummy-text (get-buffer-create "*doom-regex-text*") (current-buffer)) +regex--groups-buffer (get-buffer-create "*doom-regex-groups*")) (when dummy-text (+workspace-switch +regex-workspace-name t) (switch-to-buffer +regex--text-buffer) (with-current-buffer +regex--text-buffer (insert +regex-dummy-text))) (doom-popup-buffer +regex--groups-buffer) (doom-popup-buffer +regex--expr-buffer) (with-current-buffer +regex--expr-buffer (conf-mode) (rainbow-delimiters-mode +1) (linum-mode -1) (setq-local require-final-newline nil) (+regex-mode +1) (text-scale-set 3))) ('error (+regex/quit) (error "Failed to open the Regexp IDE: %s" ex))))) ;;;###autoload (defun =regex/replace () (interactive) (unless (buffer-live-p +regex--replace-buffer) (let (text) (=regex t) (with-selected-window (get-buffer-window +regex--text-buffer) (setq text (buffer-string)) (select-window (split-window-right)) (switch-to-buffer (get-buffer-create "*doom-regex-text-repl*")) (erase-buffer) (insert text) (read-only-mode +1) (setq +regex--text-replace-buffer (current-buffer))) (with-current-buffer +regex--expr-buffer (select-window (split-window-right)) (switch-to-buffer (get-buffer-create "*doom-regex-repl*")) (conf-mode) (rainbow-delimiters-mode +1) (linum-mode -1) (setq-local require-final-newline nil) (setq mode-line-format nil +regex--expr-replace-buffer (current-buffer)) (+regex-mode +1) (text-scale-set 3))))) ;;;###autoload (defun +regex/quit () "TODO" (interactive) (when (and +regex--text-buffer (buffer-live-p +regex--text-buffer)) (with-current-buffer +regex--text-buffer (+regex-mode -1) (remove-overlays nil nil 'category '+regex)) (when (equal (buffer-name +regex--text-buffer) "*doom-regex-text*") (kill-buffer +regex--text-buffer))) (when (equal (+workspace-current-name) +regex-workspace-name) (+workspace/delete +regex-workspace-name)) (mapc (lambda (bufname) (let ((buf (symbol-value bufname))) (when (and buf (buffer-live-p buf)) (kill-buffer buf) (set bufname nil)))) (list '+regex--text-replace-buffer '+regex--expr-replace-buffer '+regex--expr-buffer '+regex--groups-buffer '+regex--replace-buffer))) (defun +regex--expr () (when (buffer-live-p +regex--expr-buffer) (with-current-buffer +regex--expr-buffer (string-trim (buffer-string))))) (defun +regex--expr-replace () (when (buffer-live-p +regex--expr-replace-buffer) (with-current-buffer +regex--expr-replace-buffer (string-trim (buffer-string))))) ;;;###autoload (defun +regex-update-buffers (&optional beg end len) (interactive) (let* ((inhibit-read-only t) (regex (or (+regex--expr) "")) (replace (or (+regex--expr-replace) "")) (str (or (with-current-buffer +regex--text-buffer (buffer-string)) ""))) (with-current-buffer +regex--groups-buffer (erase-buffer)) (with-current-buffer +regex--text-buffer (remove-overlays nil nil 'category '+regex) (when (> (length regex) 0) (save-excursion (goto-char (point-min)) (pcase +regex-default-backend ('emacs (+regex-backend-emacs regex replace str)) ('perl (+regex-backend-perl regex replace str)))))) (with-current-buffer +regex--groups-buffer (goto-char (point-min))))) ;; --- backends --------------------------- (defun +regex--render-perl (regex text) "From " (with-temp-buffer (insert (format "@lines = ; $line = join(\"\", @lines); print \"(\"; while ($line =~ m/%s/gm) { print \"(\", length($`), \" \", length($&), \" \"; for $i (1 .. 20) { if ($$i) { my $group = $$i; $group =~ s/([\\\\\"])/\\\\\\1/g; print \"(\", $i, \" . \\\"\", $group, \"\\\") \"; } } print \")\"; } print \")\"; __DATA__ %s" (replace-regexp-in-string "/" "\\/" regex nil t) text)) (call-process-region (point-min) (point-max) "perl" t t) (goto-char (point-min)) (read (current-buffer)))) (defun +regex--replace-perl (regex replace text) (unless (or (string-empty-p regex) (string-empty-p replace) (string-empty-p text)) (with-temp-buffer (insert (format "@lines = ; $line = join(\"\", @lines); $line =~ s/%s/%s/gm; print $line; __DATA__ %s" (replace-regexp-in-string "/" "\\/" regex nil t) (replace-regexp-in-string "/" "\\/" replace nil t) text)) (call-process-region (point-min) (point-max) "perl" t t) (buffer-string)))) ;;;###autoload (defun +regex-backend-perl (regex replace str) "TODO" (cl-assert (stringp regex)) (cl-assert (stringp replace)) (cl-assert (stringp str)) (let ((i 0) (results (+regex--render-perl regex str))) (when (buffer-live-p +regex--text-replace-buffer) (let ((replace (+regex--replace-perl regex replace str))) (with-current-buffer +regex--text-replace-buffer (erase-buffer) (insert (if (and (listp results) replace (not (string-empty-p replace))) replace str))))) (dolist (result (if (listp results) results)) (let* ((offset (nth 0 result)) (length (nth 1 result)) (matches (nthcdr 2 result)) (ov (make-overlay (1+ offset) (+ offset length 1)))) (overlay-put ov 'face (nth (mod i 3) +regex-faces)) (overlay-put ov 'category '+regex) (cl-incf i) (let* ((match-zero (buffer-substring (1+ offset) (+ offset length 1))) (line (format "Match: %s\n" (propertize match-zero 'face 'font-lock-string-face)))) (with-current-buffer +regex--groups-buffer (insert line))) (dolist (match matches) (with-current-buffer +regex--groups-buffer (goto-char (point-max)) (insert (format "Group %d: %s\n" (propertize (car match) 'face 'font-lock-constant-face) (propertize (cdr match) 'face 'font-lock-string-face))))) (with-current-buffer +regex--groups-buffer (insert ?\n)))))) ;;;###autoload (defun +regex-backend-emacs (regex replace str) "TODO" (cl-assert (stringp regex)) (cl-assert (stringp replace)) (cl-assert (stringp str)) (let ((i 0) pos) (when (buffer-live-p +regex--text-replace-buffer) (with-current-buffer +regex--text-replace-buffer (erase-buffer) (insert str) (when (and (listp results) (string-empty-p replace)) (replace-regexp regex replace)))) (while (and (setq pos (point)) (re-search-forward regex nil t)) (if (= (point) pos) (forward-char 1) (let ((ov (make-overlay (match-beginning 0) (match-end 0)))) (overlay-put ov 'face (nth (mod i 3) +regex-faces)) (overlay-put ov 'category '+regex)) (cl-incf i) (dotimes (i 10) (when-let (text (match-string i)) (save-match-data (with-current-buffer +regex--groups-buffer (goto-char (point-max)) (insert (format "Group %d: %s\n" (propertize i 'face 'font-lock-constant-face) (propertize text 'face 'font-lock-string-face))))))) (with-current-buffer +regex--groups-buffer (insert ?\n))))))