summaryrefslogtreecommitdiff
path: root/helm-occur.el
diff options
context:
space:
mode:
authorThierry Volpiatto <thierry.volpiatto@gmail.com>2019-03-28 12:48:24 +0100
committerThierry Volpiatto <thierry.volpiatto@gmail.com>2019-03-30 19:42:07 +0100
commitf0f174138f6f3198a6b970fd29e7caa94236e3bc (patch)
treebccf947aa235ce032e97918b8f8a02644872b159 /helm-occur.el
parent0eb20f2383fa89105b4d207b1164b5e3f86b5508 (diff)
Rewrite helm-occur in new file helm-occur.el (#2146)
The functionalities are the same as old code but it is much faster.
Diffstat (limited to 'helm-occur.el')
-rw-r--r--helm-occur.el553
1 files changed, 553 insertions, 0 deletions
diff --git a/helm-occur.el b/helm-occur.el
new file mode 100644
index 00000000..1832f093
--- /dev/null
+++ b/helm-occur.el
@@ -0,0 +1,553 @@
+;;; helm-occur.el --- Incremental Occur for Helm. -*- lexical-binding: t -*-
+
+;; Copyright (C) 2012 ~ 2019 Thierry Volpiatto <thierry.volpiatto@gmail.com>
+
+;; 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/>.
+
+;; TODO
+;; [X] Save results for further editing with wgrep
+;; [X] Add the mode for editing
+;; [X] Add switch OF and OW actions
+;; [X] Add keymap
+;; [X] Add history
+;; [X] Prevent marking
+;; [X] Migemo
+;; [X] Add group attr
+;; [X] Write revert buffer action for saved buffer
+;; [X] Autoupdate on resume
+;; [X] Prefix all with helm-occur instead of helm-occur
+;; [X] Remove old code from helm-regexp when all done
+;; [] Add the from isearch functions
+;; [] Provide patch to emacs-wgrep for this
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'helm)
+(require 'helm-help)
+(require 'helm-utils)
+
+;;; Internals
+;;
+(defvar helm-source-occur nil)
+(defvar helm-source-moccur nil
+ "This is just a flag to add to `helm-sources-using-default-as-input'.
+Don't set it to any value, it will have no effect.")
+(defvar helm-occur--buffer-list nil)
+(defvar helm-occur--buffer-tick nil)
+(defvar helm-occur-history nil)
+
+(defgroup helm-occur nil
+ "Regexp related Applications and libraries for Helm."
+ :group 'helm)
+
+(defcustom helm-occur-actions
+ '(("Go to Line" . helm-occur-goto-line)
+ ("Goto line other window (C-u vertically)" . helm-occur-goto-line-ow)
+ ("Goto line new frame" . helm-occur-goto-line-of)
+ ("Save buffer" . helm-occur-save-results)
+ )
+ "Actions for helm-occur."
+ :group 'helm-occur
+ :type '(alist :key-type string :value-type function))
+
+(defcustom helm-occur-use-ioccur-style-keys t
+ "Similar to `helm-grep-use-ioccur-style-keys' but for multi occur."
+ :group 'helm-occur
+ :type 'boolean)
+
+(defcustom helm-occur-always-search-in-current nil
+ "Helm multi occur always search in current buffer when non--nil."
+ :group 'helm-occur
+ :type 'boolean)
+
+(defcustom helm-occur-truncate-lines t
+ "Truncate lines in occur buffer when non nil."
+ :group 'helm-occur
+ :type 'boolean)
+
+(defcustom helm-occur-auto-update-on-resume nil
+ "Allow auto updating helm-occur buffer when outdated.
+noask => Always update without asking
+nil => Don't update but signal buffer needs update
+never => Never update and do not signal buffer needs update
+Any other non--nil value update after confirmation."
+ :group 'helm-regexp
+ :type '(radio :tag "Allow auto updating helm-occur buffer when outdated."
+ (const :tag "Always update without asking" noask)
+ (const :tag "Never update and do not signal buffer needs update" never)
+ (const :tag "Don't update but signal buffer needs update" nil)
+ (const :tag "Update after confirmation" t)))
+
+(defcustom helm-occur-candidate-number-limit 99999
+ "Value of `helm-candidate-number-limit' for helm-occur."
+ :group 'helm-occur
+ :type 'integer)
+
+(defvar helm-occur-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map helm-map)
+ (define-key map (kbd "C-c o") 'helm-occur-run-goto-line-ow)
+ (define-key map (kbd "C-c C-o") 'helm-occur-run-goto-line-of)
+ (define-key map (kbd "C-x C-s") 'helm-occur-run-save-buffer)
+ (when helm-occur-use-ioccur-style-keys
+ (define-key map (kbd "<right>") 'helm-execute-persistent-action)
+ (define-key map (kbd "<left>") 'helm-occur-run-default-action))
+ (delq nil map))
+ "Keymap used in Moccur source.")
+
+(defface helm-moccur-buffer
+ '((t (:foreground "DarkTurquoise" :underline t)))
+ "Face used to highlight moccur buffer names."
+ :group 'helm-regexp)
+
+(defface helm-resume-need-update
+ '((t (:background "red")))
+ "Face used to flash moccur buffer when it needs update."
+ :group 'helm-regexp)
+
+
+;;;###autoload
+(defun helm-occur ()
+ (interactive)
+ (setq helm-source-occur
+ (helm-make-source "Helm occur" 'helm-moccur-class
+ :buffer-name (buffer-name)
+ :candidates
+ (lambda ()
+ (with-helm-current-buffer
+ ;; Don't use OMMIT-NULLS arg of split-string to
+ ;; collect empty lines as well to have right line
+ ;; numbers.
+ (split-string (buffer-substring-no-properties
+ (point-min) (point-max)) "\n")))
+ :candidate-transformer 'helm-occur-transformer
+ :nomark t
+ :migemo t
+ :history 'helm-occur-history
+ :candidate-number-limit helm-occur-candidate-number-limit
+ :action 'helm-occur-actions
+ :requires-pattern 2
+ :group 'helm-occur
+ :keymap helm-occur-map
+ :resume 'helm-occur-resume-fn
+ :moccur-buffers (list (current-buffer))))
+ (helm-set-local-variable 'helm-occur--buffer-list (list (current-buffer))
+ 'helm-occur--buffer-tick
+ (list (buffer-chars-modified-tick (current-buffer))))
+ (save-restriction
+ (let (def pos)
+ (when (use-region-p)
+ ;; When user mark defun with `mark-defun' with intention of
+ ;; using helm-occur on this region, it is relevant to use the
+ ;; thing-at-point located at previous position which have been
+ ;; pushed to `mark-ring'.
+ (setq def (save-excursion
+ (goto-char (setq pos (car mark-ring)))
+ (helm-aif (thing-at-point 'symbol) (regexp-quote it))))
+ (narrow-to-region (region-beginning) (region-end)))
+ (unwind-protect
+ (helm :sources 'helm-source-occur
+ :buffer "*helm occur*"
+ :default (or def (helm-aif (thing-at-point 'symbol) (regexp-quote it)))
+ :preselect (and (memq 'helm-source-occur
+ helm-sources-using-default-as-input)
+ (format "^%d:" (line-number-at-pos (or pos (point)))))
+ :truncate-lines helm-occur-truncate-lines)
+ (deactivate-mark t)))))
+
+(defun helm-occur-transformer (candidates)
+ "Returns CANDIDATES prefixed with line number."
+ (cl-loop for i in candidates
+ for n from 1
+ collect (cons (format "%s:%s"
+ (propertize
+ (number-to-string n)
+ 'face 'helm-grep-lineno)
+ i)
+ n)))
+
+(defclass helm-moccur-class (helm-source-sync)
+ ((buffer-name :initarg :buffer-name
+ :initform nil)
+ (moccur-buffers :initarg :moccur-buffers
+ :initform nil)))
+
+(defun helm-occur-build-sources (buffers)
+ (cl-loop for buf in buffers
+ collect (helm-make-source (format "Helm moccur in `%s'" (buffer-name buf))
+ 'helm-moccur-class
+ :buffer-name (buffer-name buf)
+ ;; By using :candidates+:candidate-transformer we
+ ;; ensure candidates are cached and no more computed.
+ :candidates
+ `(lambda ()
+ (with-current-buffer ,buf
+ ;; Don't use OMMIT-NULLS arg of split-string to
+ ;; collect empty lines to ensure right line numbers.
+ (split-string (buffer-substring-no-properties
+ (point-min) (point-max)) "\n")))
+ :candidate-transformer 'helm-occur-transformer
+ :nomark t
+ :migemo t
+ :history 'helm-occur-history
+ :candidate-number-limit helm-occur-candidate-number-limit
+ :action 'helm-occur-actions
+ :requires-pattern 2
+ :follow 1
+ :group 'helm-occur
+ :keymap helm-occur-map
+ :resume 'helm-occur-resume-fn
+ :moccur-buffers buffers)))
+
+(defun helm-multi-occur-1 (buffers &optional input)
+ (let* ((curbuf (current-buffer))
+ (bufs (if helm-occur-always-search-in-current
+ (cons curbuf (remove curbuf buffers))
+ buffers))
+ (sources (helm-occur-build-sources bufs))
+ (helm--maybe-use-default-as-input
+ (not (null (memq 'helm-source-moccur
+ helm-sources-using-default-as-input)))))
+ (helm-set-local-variable 'helm-occur--buffer-list bufs
+ 'helm-occur--buffer-tick
+ (cl-loop for b in bufs collect
+ (buffer-chars-modified-tick
+ (get-buffer b))))
+ (helm :sources sources
+ :buffer "*helm moccur*"
+ :default (helm-aif (thing-at-point 'symbol) (regexp-quote it))
+ :input input
+ :truncate-lines helm-occur-truncate-lines)))
+
+;;; Actions
+;;
+(cl-defun helm-occur-action (lineno
+ &optional (method (quote buffer)))
+ "Jump to line number LINENO with METHOD.
+arg METHOD can be one of buffer, buffer-other-window, buffer-other-frame."
+ (require 'helm-grep)
+ (let ((buf (if (eq major-mode 'helm-occur-mode)
+ (get-text-property (point) 'buffer-name)
+ (helm-attr 'buffer-name)))
+ (split-pat (helm-mm-split-pattern helm-input)))
+ (cl-case method
+ (buffer (switch-to-buffer buf))
+ (buffer-other-window (helm-window-show-buffers (list buf) t))
+ (buffer-other-frame (switch-to-buffer-other-frame buf)))
+ (with-current-buffer buf
+ (helm-goto-line lineno)
+ ;; Move point to the nearest matching regexp from bol.
+ (cl-loop for reg in split-pat
+ when (save-excursion
+ (condition-case _err
+ (if helm-migemo-mode
+ (helm-mm-migemo-forward reg (point-at-eol) t)
+ (re-search-forward reg (point-at-eol) t))
+ (invalid-regexp nil)))
+ collect (match-beginning 0) into pos-ls
+ finally (when pos-ls (goto-char (apply #'min pos-ls)))))))
+
+(defun helm-occur-goto-line (candidate)
+ "From multi occur, switch to buffer and CANDIDATE line."
+ (helm-occur-action
+ candidate 'buffer))
+
+(defun helm-occur-goto-line-ow (candidate)
+ "Go to CANDIDATE line in other window.
+Same as `helm-occur-goto-line' but go in other window."
+ (helm-occur-action
+ candidate 'buffer-other-window))
+
+(defun helm-occur-goto-line-of (candidate)
+ "Go to CANDIDATE line in new frame.
+Same as `helm-occur-goto-line' but go in new frame."
+ (helm-occur-action
+ candidate 'buffer-other-frame))
+
+(defun helm-occur-run-goto-line-ow ()
+ "Run goto line other window action from `helm-occur'."
+ (interactive)
+ (with-helm-alive-p
+ (helm-exit-and-execute-action 'helm-occur-goto-line-ow)))
+(put 'helm-occur-run-goto-line-ow 'helm-only t)
+
+(defun helm-occur-run-goto-line-of ()
+ "Run goto line new frame action from `helm-occur'."
+ (interactive)
+ (with-helm-alive-p
+ (helm-exit-and-execute-action 'helm-occur-goto-line-of)))
+(put 'helm-occur-run-goto-line-of 'helm-only t)
+
+(defun helm-occur-run-default-action ()
+ (interactive)
+ (with-helm-alive-p
+ (helm-exit-and-execute-action 'helm-occur-goto-line)))
+(put 'helm-occur-run-default-action 'helm-only t)
+
+(defun helm-occur-run-save-buffer ()
+ "Run moccur save results action from `helm-moccur'."
+ (interactive)
+ (with-helm-alive-p
+ (helm-exit-and-execute-action 'helm-occur-save-results)))
+(put 'helm-moccur-run-save-buffer 'helm-only t)
+
+
+;;; helm-occur-mode
+;;
+;;
+(defvar helm-occur-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "RET") 'helm-occur-mode-goto-line)
+ (define-key map (kbd "C-o") 'helm-occur-mode-goto-line-ow)
+ (define-key map (kbd "<C-down>") 'helm-occur-mode-goto-line-ow-forward)
+ (define-key map (kbd "<C-up>") 'helm-occur-mode-goto-line-ow-backward)
+ (define-key map (kbd "<M-down>") 'helm-gm-next-file)
+ (define-key map (kbd "<M-up>") 'helm-gm-precedent-file)
+ (define-key map (kbd "M-n") 'helm-occur-mode-goto-line-ow-forward)
+ (define-key map (kbd "M-p") 'helm-occur-mode-goto-line-ow-backward)
+ (define-key map (kbd "M-N") 'helm-gm-next-file)
+ (define-key map (kbd "M-P") 'helm-gm-precedent-file)
+ map))
+
+(defun helm-occur-mode-goto-line ()
+ (interactive)
+ (helm-aif (get-text-property (point) 'helm-realvalue)
+ (progn (helm-occur-goto-line it) (helm-match-line-cleanup-pulse))))
+
+(defun helm-occur-mode-goto-line-ow ()
+ (interactive)
+ (helm-aif (get-text-property (point) 'helm-realvalue)
+ (progn (helm-occur-goto-line-ow it) (helm-match-line-cleanup-pulse))))
+
+(defun helm-occur-mode-goto-line-ow-forward-1 (arg)
+ (condition-case nil
+ (progn
+ (save-selected-window
+ (helm-occur-mode-goto-line-ow)
+ (recenter))
+ (forward-line arg))
+ (error nil)))
+
+(defun helm-occur-mode-goto-line-ow-forward ()
+ (interactive)
+ (helm-occur-mode-goto-line-ow-forward-1 1))
+
+(defun helm-occur-mode-goto-line-ow-backward ()
+ (interactive)
+ (helm-occur-mode-goto-line-ow-forward-1 -1))
+
+(defun helm-occur-save-results (_candidate)
+ "Save helm moccur results in a `helm-moccur-mode' buffer."
+ (let ((buf "*hmoccur*")
+ new-buf)
+ (when (get-buffer buf)
+ (setq new-buf (helm-read-string "OccurBufferName: " buf))
+ (cl-loop for b in (helm-buffer-list)
+ when (and (string= new-buf b)
+ (not (y-or-n-p
+ (format "Buffer `%s' already exists overwrite? "
+ new-buf))))
+ do (setq new-buf (helm-read-string "OccurBufferName: " "*hmoccur ")))
+ (setq buf new-buf))
+ (with-current-buffer (get-buffer-create buf)
+ (setq buffer-read-only t)
+ (let ((inhibit-read-only t)
+ (map (make-sparse-keymap)))
+ (erase-buffer)
+ (insert "-*- mode: helm-moccur -*-\n\n"
+ (format "Moccur Results for `%s':\n\n" helm-input))
+ (save-excursion
+ (insert (with-current-buffer helm-buffer
+ (goto-char (point-min))
+ (cl-loop with buf
+ while (re-search-forward "^[0-9]*:" nil t)
+ for line = (buffer-substring (point-at-bol) (point-at-eol))
+ for lineno = (get-text-property (point) 'helm-realvalue)
+ do (setq buf (helm-attr 'buffer-name))
+ concat (propertize
+ (concat (propertize buf 'face 'helm-moccur-buffer)
+ ":" line "\n")
+ 'buffer-name buf
+ 'helm-realvalue lineno)))))
+ (save-excursion
+ (while (not (eobp))
+ (add-text-properties
+ (point-at-bol) (point-at-eol)
+ `(keymap ,map
+ help-echo ,(concat
+ (buffer-file-name
+ (get-buffer (get-text-property
+ (point) 'buffer-name)))
+ "\nmouse-1: set point\nmouse-2: jump to selection")
+ mouse-face highlight
+ invisible nil))
+ (define-key map [mouse-1] 'mouse-set-point)
+ (define-key map [mouse-2] 'helm-occur-mode-mouse-goto-line)
+ (define-key map [mouse-3] 'ignore)
+ (forward-line 1))))
+ (helm-occur-mode))
+ (pop-to-buffer buf)
+ (message "Helm Moccur Results saved in `%s' buffer" buf)))
+
+(defun helm-occur-mode-mouse-goto-line (event)
+ (interactive "e")
+ (let* ((window (posn-window (event-end event)))
+ (pos (posn-point (event-end event))))
+ (with-selected-window window
+ (when (eq major-mode 'helm-occur-mode)
+ (goto-char pos)
+ (helm-occur-mode-goto-line)))))
+(put 'helm-moccur-mode-mouse-goto-line 'helm-only t)
+
+(defun helm-occur-mode--revert-buffer-function (&optional _ignore-auto _noconfirm)
+ (goto-char (point-min))
+ (let (pattern)
+ (when (re-search-forward "^Moccur Results for `\\(.*\\)'" nil t)
+ (setq pattern (match-string 1))
+ (forward-line 0)
+ (when (re-search-forward "^$" nil t)
+ (forward-line 1))
+ (let ((inhibit-read-only t)
+ (buffer (current-buffer))
+ (buflst helm-occur--buffer-list)
+ (bsubstring #'buffer-substring-no-properties))
+ (delete-region (point) (point-max))
+ (message "Reverting buffer...")
+ (save-excursion
+ (with-temp-buffer
+ (insert
+ "\n"
+ (cl-loop for buf in buflst
+ for bufstr = (or (and (buffer-live-p (get-buffer buf))
+ (with-current-buffer buf
+ (funcall bsubstring
+ (point-min) (point-max))))
+ "")
+ unless (string= bufstr "")
+ do (add-text-properties
+ 0 (length bufstr)
+ `(buffer-name ,(buffer-name (get-buffer buf)))
+ bufstr)
+ concat bufstr)
+ "\n")
+ (goto-char (point-min))
+ (cl-loop with helm-pattern = pattern
+ while (helm-mm-search pattern)
+ for linum = (line-number-at-pos)
+ for line = (format "%s:%d:%s"
+ (get-text-property (point) 'buffer-name)
+ linum
+ (buffer-substring-no-properties
+ (point-at-bol) (point-at-eol)))
+ when line
+ do (with-current-buffer buffer
+ (insert
+ (propertize
+ (car (helm-occur-filter-one-by-one line))
+ 'helm-realvalue linum)
+ "\n"))))
+ (message "Reverting buffer done"))))))
+
+(defun helm-occur-filter-one-by-one (candidate)
+ "`filter-one-by-one' function for `helm-source-moccur'."
+ (require 'helm-grep)
+ (let* ((split (helm-grep-split-line candidate))
+ (buf (car split))
+ (lineno (nth 1 split))
+ (str (nth 2 split)))
+ (cons (concat (propertize
+ buf
+ 'face 'helm-moccur-buffer
+ 'help-echo (buffer-file-name
+ (get-buffer buf))
+ 'buffer-name buf)
+ ":"
+ (propertize lineno 'face 'helm-grep-lineno)
+ ":"
+ (helm-grep-highlight-match str t))
+ candidate)))
+
+;;;###autoload
+(define-derived-mode helm-occur-mode
+ special-mode "helm-moccur"
+ "Major mode to provide actions in helm moccur saved buffer.
+
+Special commands:
+\\{helm-occur-mode-map}"
+ (set (make-local-variable 'helm-occur--buffer-list)
+ (with-helm-buffer helm-occur--buffer-list))
+ (set (make-local-variable 'revert-buffer-function)
+ #'helm-occur-mode--revert-buffer-function))
+(put 'helm-moccur-mode 'helm-only t)
+
+
+;;; Resume
+;;
+(defun helm-occur-resume-fn ()
+ (with-helm-buffer
+ (let (new-tick-ls buffer-is-modified)
+ (set (make-local-variable 'helm-occur--buffer-list)
+ (cl-loop for b in helm-occur--buffer-list
+ when (buffer-live-p (get-buffer b))
+ collect b))
+ (setq buffer-is-modified (/= (length helm-occur--buffer-list)
+ (length (helm-attr 'moccur-buffers))))
+ (helm-attrset 'moccur-buffers helm-occur--buffer-list)
+ (setq new-tick-ls (cl-loop for b in helm-occur--buffer-list
+ collect (buffer-chars-modified-tick (get-buffer b))))
+ (when buffer-is-modified
+ (setq helm-occur--buffer-tick new-tick-ls))
+ (cl-assert (> (length helm-occur--buffer-list) 0) nil
+ "helm-resume error: helm-(m)occur buffer list is empty")
+ (unless (eq helm-occur-auto-update-on-resume 'never)
+ (when (or buffer-is-modified
+ (cl-loop for b in helm-occur--buffer-list
+ for new-tick = (buffer-chars-modified-tick (get-buffer b))
+ for tick in helm-occur--buffer-tick
+ thereis (/= tick new-tick)))
+ (helm-aif helm-occur-auto-update-on-resume
+ (when (or (eq it 'noask)
+ (y-or-n-p "Helm (m)occur Buffer outdated, update? "))
+ (run-with-idle-timer 0.1 nil (lambda ()
+ (with-helm-buffer
+ (helm-force-update)
+ (message "Helm (m)occur Buffer have been udated")
+ (sit-for 1) (message nil))))
+ (unless buffer-is-modified (setq helm-occur--buffer-tick new-tick-ls)))
+ (run-with-idle-timer 0.1 nil (lambda ()
+ (with-helm-buffer
+ (let ((ov (make-overlay (save-excursion
+ (goto-char (point-min))
+ (forward-line 1)
+ (point))
+ (point-max))))
+ (overlay-put ov 'face 'helm-resume-need-update)
+ (sit-for 0.3) (delete-overlay ov)
+ (message "[Helm occur Buffer outdated (C-c C-u to update)]")))))
+ (unless buffer-is-modified
+ (with-helm-after-update-hook
+ (setq helm-occur--buffer-tick new-tick-ls)
+ (message "Helm (m)occur Buffer have been udated")))))))))
+
+
+(provide 'helm-occur)
+
+;; Local Variables:
+;; byte-compile-warnings: (not obsolete)
+;; coding: utf-8
+;; indent-tabs-mode: nil
+;; End:
+
+;;; helm-occur.el ends here