X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/13d0c5096cd67d8398f145fbd30838858ce6f632..5cfe6e6a765b04bacbe4a72f64b2d028601a9611:/counsel.el diff --git a/counsel.el b/counsel.el index f771ab789..0ced4b902 100644 --- a/counsel.el +++ b/counsel.el @@ -33,6 +33,7 @@ ;;; Code: (require 'swiper) +(require 'etags) (defvar counsel-completion-beg nil "Completion bounds start.") @@ -81,18 +82,73 @@ :initial-input str :action #'counsel--el-action))) +(declare-function slime-symbol-start-pos "ext:slime") +(declare-function slime-symbol-end-pos "ext:slime") +(declare-function slime-contextual-completions "ext:slime-c-p-c") + +;;;###autoload +(defun counsel-cl () + "Common Lisp completion at point." + (interactive) + (setq counsel-completion-beg (slime-symbol-start-pos)) + (setq counsel-completion-end (slime-symbol-end-pos)) + (ivy-read "Symbol name: " + (car (slime-contextual-completions + counsel-completion-beg + counsel-completion-end)) + :action #'counsel--el-action)) + (defun counsel--el-action (symbol) "Insert SYMBOL, erasing the previous one." (when (stringp symbol) - (when counsel-completion-beg - (delete-region - counsel-completion-beg - counsel-completion-end)) - (setq counsel-completion-beg - (move-marker (make-marker) (point))) - (insert symbol) - (setq counsel-completion-end - (move-marker (make-marker) (point))))) + (with-ivy-window + (when counsel-completion-beg + (delete-region + counsel-completion-beg + counsel-completion-end)) + (setq counsel-completion-beg + (move-marker (make-marker) (point))) + (insert symbol) + (setq counsel-completion-end + (move-marker (make-marker) (point)))))) + +(declare-function deferred:sync! "ext:deferred") +(declare-function jedi:complete-request "ext:jedi-core") +(declare-function jedi:ac-direct-matches "ext:jedi") + +(defun counsel-jedi () + "Python completion at point." + (interactive) + (let ((bnd (bounds-of-thing-at-point 'symbol))) + (if bnd + (progn + (setq counsel-completion-beg (car bnd)) + (setq counsel-completion-end (cdr bnd))) + (setq counsel-completion-beg nil) + (setq counsel-completion-end nil))) + (deferred:sync! + (jedi:complete-request)) + (ivy-read "Symbol name: " (jedi:ac-direct-matches) + :action #'counsel--py-action)) + +(defun counsel--py-action (symbol) + "Insert SYMBOL, erasing the previous one." + (when (stringp symbol) + (with-ivy-window + (when counsel-completion-beg + (delete-region + counsel-completion-beg + counsel-completion-end)) + (setq counsel-completion-beg + (move-marker (make-marker) (point))) + (insert symbol) + (setq counsel-completion-end + (move-marker (make-marker) (point))) + (when (equal (get-text-property 0 'symbol symbol) "f") + (insert "()") + (setq counsel-completion-end + (move-marker (make-marker) (point))) + (backward-char 1))))) (defvar counsel-describe-map (let ((map (make-sparse-keymap))) @@ -103,25 +159,29 @@ (defun counsel-find-symbol () "Jump to the definition of the current symbol." (interactive) - (ivy-set-action #'counsel--find-symbol) - (ivy-done)) + (ivy-exit-with-action #'counsel--find-symbol)) (defun counsel--info-lookup-symbol () "Lookup the current symbol in the info docs." (interactive) - (ivy-set-action #'counsel-info-lookup-symbol) - (ivy-done)) + (ivy-exit-with-action #'counsel-info-lookup-symbol)) (defun counsel--find-symbol (x) "Find symbol definition that corresponds to string X." + (with-no-warnings + (ring-insert find-tag-marker-ring (point-marker))) (let ((full-name (get-text-property 0 'full-name x))) (if full-name (find-library full-name) (let ((sym (read x))) - (cond ((boundp sym) + (cond ((and (eq (ivy-state-caller ivy-last) + 'counsel-describe-variable) + (boundp sym)) (find-variable sym)) ((fboundp sym) (find-function sym)) + ((boundp sym) + (find-variable sym)) ((or (featurep sym) (locate-library (prin1-to-string sym))) @@ -138,7 +198,7 @@ "Return current symbol at point as a string." (let ((s (thing-at-point 'symbol))) (and (stringp s) - (if (string-match "\\`[`']?\\(.*\\)'?\\'" s) + (if (string-match "\\`[`']?\\(.*?\\)'?\\'" s) (match-string 1 s) s)))) @@ -167,7 +227,8 @@ :sort t :action (lambda (x) (describe-variable - (intern x)))))) + (intern x))) + :caller 'counsel-describe-variable))) (ivy-set-actions 'counsel-describe-variable @@ -179,6 +240,10 @@ '(("i" counsel-info-lookup-symbol "info") ("d" counsel--find-symbol "definition"))) +(ivy-set-actions + 'counsel-M-x + '(("d" counsel--find-symbol "definition"))) + ;;;###autoload (defun counsel-describe-function () "Forward to `describe-function'." @@ -198,7 +263,8 @@ :sort t :action (lambda (x) (describe-function - (intern x)))))) + (intern x))) + :caller 'counsel-describe-function))) (defvar info-lookup-mode) (declare-function info-lookup->completions "info-look") @@ -230,11 +296,16 @@ (require 'info-look) (info-lookup 'symbol symbol mode)) +(defvar counsel-unicode-char-history nil + "History for `counsel-unicode-char'.") + ;;;###autoload (defun counsel-unicode-char () "Insert a Unicode character at point." (interactive) (let ((minibuffer-allow-text-properties t)) + (setq counsel-completion-beg (point)) + (setq counsel-completion-end (point)) (ivy-read "Unicode name: " (mapcar (lambda (x) (propertize @@ -242,7 +313,12 @@ 'result (cdr x))) (ucs-names)) :action (lambda (char) - (insert-char (get-text-property 0 'result char)))))) + (with-ivy-window + (delete-region counsel-completion-beg counsel-completion-end) + (setq counsel-completion-beg (point)) + (insert-char (get-text-property 0 'result char)) + (setq counsel-completion-end (point)))) + :history 'counsel-unicode-char-history))) (declare-function cider-sync-request:complete "ext:cider-client") ;;;###autoload @@ -266,7 +342,9 @@ "git ls-files --full-name --") "\n" t)) - (action (lambda (x) (find-file x)))) + (action `(lambda (x) + (let ((default-directory ,default-directory)) + (find-file x))))) (ivy-read "Find file: " cands :action action))) @@ -281,14 +359,17 @@ (list "" (format "%d chars more" (- n (length ivy-text))))) +(defvar counsel-git-grep-cmd "git --no-pager grep --full-name -n --no-color -i -e %S" + "Store the command for `counsel-git-grep'.") + (defun counsel-git-grep-function (string &optional _pred &rest _unused) "Grep in the current git repository for STRING." (if (and (> counsel--git-grep-count 20000) (< (length string) 3)) (counsel-more-chars 3) (let* ((default-directory counsel--git-grep-dir) - (cmd (format "git --no-pager grep --full-name -n --no-color -i -e %S" - (ivy--regex string t)))) + (cmd (format counsel-git-grep-cmd + (setq ivy--old-re (ivy--regex string t))))) (if (<= counsel--git-grep-count 20000) (split-string (shell-command-to-string cmd) "\n" t) (counsel--gg-candidates (ivy--regex string)) @@ -297,17 +378,41 @@ (defvar counsel-git-grep-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-l") 'counsel-git-grep-recenter) + (define-key map (kbd "M-q") 'counsel-git-grep-query-replace) map)) +(defun counsel-git-grep-query-replace () + "Start `query-replace' with string to replace from last search string." + (interactive) + (if (null (window-minibuffer-p)) + (user-error + "Should only be called in the minibuffer through `counsel-git-grep-map'") + (let* ((enable-recursive-minibuffers t) + (from (ivy--regex ivy-text)) + (to (query-replace-read-to from "Query replace" t))) + (ivy-exit-with-action + (lambda (_) + (let (done-buffers) + (dolist (cand ivy--old-cands) + (when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" cand) + (with-ivy-window + (let ((file-name (match-string-no-properties 1 cand))) + (setq file-name (expand-file-name file-name counsel--git-grep-dir)) + (unless (member file-name done-buffers) + (push file-name done-buffers) + (find-file file-name) + (goto-char (point-min))) + (perform-replace from to t t nil))))))))))) + (defun counsel-git-grep-recenter () (interactive) - (with-selected-window (ivy-state-window ivy-last) + (with-ivy-window (counsel-git-grep-action ivy--current) (recenter-top-bottom))) (defun counsel-git-grep-action (x) (when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" x) - (with-selected-window (ivy-state-window ivy-last) + (with-ivy-window (let ((file-name (match-string-no-properties 1 x)) (line-number (match-string-no-properties 2 x))) (find-file (expand-file-name file-name counsel--git-grep-dir)) @@ -315,18 +420,34 @@ (forward-line (1- (string-to-number line-number))) (re-search-forward (ivy--regex ivy-text t) (line-end-position) t) (unless (eq ivy-exit 'done) - (setq swiper--window (selected-window)) (swiper--cleanup) (swiper--add-overlays (ivy--regex ivy-text))))))) (defvar counsel-git-grep-history nil "History for `counsel-git-grep'.") +(defvar counsel-git-grep-cmd-history + '("git --no-pager grep --full-name -n --no-color -i -e %S") + "History for `counsel-git-grep' shell commands.") + ;;;###autoload -(defun counsel-git-grep (&optional initial-input) +(defun counsel-git-grep (&optional cmd initial-input) "Grep for a string in the current git repository. +When CMD is a string, use it as a \"git grep\" command. +When CMD is non-nil, prompt for a specific \"git grep\" command. INITIAL-INPUT can be given as the initial minibuffer input." - (interactive) + (interactive "P") + (cond + ((stringp cmd) + (setq counsel-git-grep-cmd cmd)) + (cmd + (setq counsel-git-grep-cmd + (ivy-read "cmd: " counsel-git-grep-cmd-history + :history 'counsel-git-grep-cmd-history)) + (setq counsel-git-grep-cmd-history + (delete-dups counsel-git-grep-cmd-history))) + (t + (setq counsel-git-grep-cmd "git --no-pager grep --full-name -n --no-color -i -e %S"))) (setq counsel--git-grep-dir (locate-dominating-file default-directory ".git")) (if (null counsel--git-grep-dir) @@ -339,7 +460,8 @@ INITIAL-INPUT can be given as the initial minibuffer input." :keymap counsel-git-grep-map :action #'counsel-git-grep-action :unwind #'swiper--cleanup - :history 'counsel-git-grep-history))) + :history 'counsel-git-grep-history + :caller 'counsel-git-grep))) (defcustom counsel-find-file-at-point nil "When non-nil, add file-at-point to the list of candidates." @@ -358,7 +480,8 @@ INITIAL-INPUT can be given as the initial minibuffer input." :matcher #'counsel--find-file-matcher :action (lambda (x) - (find-file (expand-file-name x ivy--directory))) + (with-ivy-window + (find-file (expand-file-name x ivy--directory)))) :preselect (when counsel-find-file-at-point (require 'ffap) (ffap-guesser)) @@ -412,6 +535,10 @@ Skip some dotfiles unless `ivy-text' requires them." candidates)) (setq ivy--old-re regexp)))) +(defvar counsel--async-time nil + "Store the time when a new process was started. +Or the time of the last minibuffer update.") + (defun counsel--async-command (cmd) (let* ((counsel--process " *counsel*") (proc (get-process counsel--process)) @@ -424,13 +551,27 @@ Skip some dotfiles unless `ivy-text' requires them." counsel--process counsel--process cmd)) - (set-process-sentinel proc #'counsel--async-sentinel))) + (setq counsel--async-time (current-time)) + (set-process-sentinel proc #'counsel--async-sentinel) + (set-process-filter proc #'counsel--async-filter))) (defun counsel--async-sentinel (process event) (if (string= event "finished\n") (progn (with-current-buffer (process-buffer process) - (setq ivy--all-candidates (split-string (buffer-string) "\n" t)) + (setq ivy--all-candidates + (ivy--sort-maybe + (split-string (buffer-string) "\n" t))) + (if (null ivy--old-cands) + (setq ivy--index + (or (ivy--preselect-index + (ivy-state-preselect ivy-last) + ivy--all-candidates) + 0)) + (ivy--recompute-index + ivy-text + (funcall ivy--regex-function ivy-text) + ivy--all-candidates)) (setq ivy--old-cands ivy--all-candidates)) (ivy--exhibit)) (if (string= event "exited abnormally with code 1\n") @@ -439,12 +580,34 @@ Skip some dotfiles unless `ivy-text' requires them." (setq ivy--old-cands ivy--all-candidates) (ivy--exhibit))))) +(defun counsel--async-filter (process str) + "Receive from PROCESS the output STR. +Update the minibuffer with the amount of lines collected every +0.5 seconds since the last update." + (with-current-buffer (process-buffer process) + (insert str)) + (let (size) + (when (time-less-p + ;; 0.5s + '(0 0 500000 0) + (time-since counsel--async-time)) + (with-current-buffer (process-buffer process) + (goto-char (point-min)) + (setq size (- (buffer-size) (forward-line (buffer-size))))) + (ivy--insert-minibuffer + (format "\ncollected: %d" size)) + (setq counsel--async-time (current-time))))) + (defun counsel-locate-action-extern (x) "Use xdg-open shell command on X." (call-process shell-file-name nil nil nil shell-command-switch - (format "xdg-open %s" (shell-quote-argument x)))) + (format "%s %s" + (if (eq system-type 'darwin) + "open" + "xdg-open") + (shell-quote-argument x)))) (declare-function dired-jump "dired-x") (defun counsel-locate-action-dired (x) @@ -454,28 +617,54 @@ Skip some dotfiles unless `ivy-text' requires them." (defvar counsel-locate-history nil "History for `counsel-locate'.") +(defcustom counsel-locate-options (if (eq system-type 'darwin) + '("-i") + '("-i" "--regex")) + "Command line options for `locate`." + :group 'ivy + :type '(repeat string)) + (ivy-set-actions 'counsel-locate '(("x" counsel-locate-action-extern "xdg-open") ("d" counsel-locate-action-dired "dired"))) +(defun counsel-unquote-regex-parens (str) + (replace-regexp-in-string + "\\\\)" ")" + (replace-regexp-in-string + "\\\\(" "(" + str))) + (defun counsel-locate-function (str &rest _u) (if (< (length str) 3) (counsel-more-chars 3) (counsel--async-command - (concat "locate -i --regex " (ivy--regex str))) + (format "locate %s '%s'" + (mapconcat #'identity counsel-locate-options " ") + (counsel-unquote-regex-parens + (ivy--regex str)))) '("" "working..."))) +(defun counsel-delete-process () + (let ((process (get-process " *counsel*"))) + (when process + (delete-process process)))) + ;;;###autoload -(defun counsel-locate () - "Call locate shell command." +(defun counsel-locate (&optional initial-input) + "Call the \"locate\" shell command. +INITIAL-INPUT can be given as the initial minibuffer input." (interactive) (ivy-read "Locate: " #'counsel-locate-function + :initial-input initial-input :dynamic-collection t :history 'counsel-locate-history :action (lambda (file) - (when file - (find-file file))))) + (with-ivy-window + (when file + (find-file file)))) + :unwind #'counsel-delete-process)) (defun counsel--generic (completion-fn) "Complete thing at point with COMPLETION-FN." @@ -573,8 +762,9 @@ The libraries are offered from `load-path'." (setq proc (start-process-shell-command counsel-gg-process counsel-gg-process - (format "git --no-pager grep --full-name -n --no-color -i -e %S | head -n 200" - regex))) + (concat + (format counsel-git-grep-cmd regex) + " | head -n 200"))) (set-process-sentinel proc #'counsel--gg-sentinel))) @@ -583,7 +773,9 @@ The libraries are offered from `load-path'." (if (string= event "finished\n") (progn (with-current-buffer (process-buffer process) - (setq ivy--all-candidates (split-string (buffer-string) "\n" t)) + (setq ivy--all-candidates + (or (split-string (buffer-string) "\n" t) + '(""))) (setq ivy--old-cands ivy--all-candidates)) (when (= 0 (cl-incf counsel-gg-state)) (ivy--exhibit))) @@ -597,8 +789,17 @@ The libraries are offered from `load-path'." "Quickly and asynchronously count the amount of git grep REGEX matches. When NO-ASYNC is non-nil, do it synchronously." (let ((default-directory counsel--git-grep-dir) - (cmd (format "git grep -i -c '%s' | sed 's/.*:\\(.*\\)/\\1/g' | awk '{s+=$1} END {print s}'" - regex)) + (cmd + (concat + (format + (replace-regexp-in-string + "--full-name" "-c" + counsel-git-grep-cmd) + ;; "git grep -i -c '%s'" + (replace-regexp-in-string + "-" "\\\\-" + (replace-regexp-in-string "'" "''" regex))) + " | sed 's/.*:\\(.*\\)/\\1/g' | awk '{s+=$1} END {print s}'")) (counsel-ggc-process " *counsel-gg-count*")) (if no-async (string-to-number (shell-command-to-string cmd)) @@ -621,15 +822,19 @@ When NO-ASYNC is non-nil, do it synchronously." (when (= 0 (cl-incf counsel-gg-state)) (ivy--exhibit))))))))) -(defun counsel--M-x-transformer (cmd) - "Add a binding to CMD if it's bound in the current window. -CMD is a command name." - (let ((binding (substitute-command-keys (format "\\[%s]" cmd)))) +(defun counsel--M-x-transformer (cand-pair) + "Add a binding to CAND-PAIR cdr if the car is bound in the current window. +CAND-PAIR is (command-name . extra-info)." + (let* ((command-name (car cand-pair)) + (extra-info (cdr cand-pair)) + (binding (substitute-command-keys (format "\\[%s]" command-name)))) (setq binding (replace-regexp-in-string "C-x 6" "" binding)) (if (string-match "^M-x" binding) - cmd - (format "%s (%s)" cmd - (propertize binding 'face 'font-lock-keyword-face))))) + cand-pair + (cons command-name + (if extra-info + (format " %s (%s)" extra-info (propertize binding 'face 'font-lock-keyword-face)) + (format " (%s)" (propertize binding 'face 'font-lock-keyword-face))))))) (defvar smex-initialized-p) (defvar smex-ido-cache) @@ -637,7 +842,20 @@ CMD is a command name." (declare-function smex-detect-new-commands "ext:smex") (declare-function smex-update "ext:smex") (declare-function smex-rank "ext:smex") -(declare-function package-installed-p "package") + +(defun counsel--M-x-prompt () + "M-x plus the string representation of `current-prefix-arg'." + (if (not current-prefix-arg) + "M-x " + (concat + (if (eq current-prefix-arg '-) + "- " + (if (integerp current-prefix-arg) + (format "%d " current-prefix-arg) + (if (= (car current-prefix-arg) 4) + "C-u " + (format "%d " (car current-prefix-arg))))) + "M-x "))) ;;;###autoload (defun counsel-M-x (&optional initial-input) @@ -649,17 +867,15 @@ Optional INITIAL-INPUT is the initial input in the minibuffer." ivy-initial-inputs-alist)))) (let* ((store ivy-format-function) (ivy-format-function - (lambda (cands) + (lambda (cand-pairs) (funcall store - (with-selected-window (ivy-state-window ivy-last) - (mapcar #'counsel--M-x-transformer cands))))) + (with-ivy-window + (mapcar #'counsel--M-x-transformer cand-pairs))))) (cands obarray) (pred 'commandp) (sort t)) - (when (or (featurep 'smex) - (package-installed-p 'smex)) - (require 'smex) + (when (require 'smex nil 'noerror) (unless smex-initialized-p (smex-initialize)) (smex-detect-new-commands) @@ -667,7 +883,7 @@ Optional INITIAL-INPUT is the initial input in the minibuffer." (setq cands smex-ido-cache) (setq pred nil) (setq sort nil)) - (ivy-read "M-x " cands + (ivy-read (counsel--M-x-prompt) cands :predicate pred :require-match t :history 'extended-command-history @@ -675,7 +891,8 @@ Optional INITIAL-INPUT is the initial input in the minibuffer." (lambda (cmd) (when (featurep 'smex) (smex-rank (intern cmd))) - (let ((prefix-arg current-prefix-arg)) + (let ((prefix-arg current-prefix-arg) + (ivy-format-function store)) (command-execute (intern cmd) 'record))) :sort sort :keymap counsel-describe-map @@ -737,7 +954,8 @@ Usable with `ivy-resume', `ivy-next-line-and-call' and :action '(1 ("p" helm-rhythmbox-play-song "Play song") - ("e" counsel-rhythmbox-enqueue-song "Enqueue song")))) + ("e" counsel-rhythmbox-enqueue-song "Enqueue song")) + :caller 'counsel-rhythmbox)) (defvar counsel-org-tags nil "Store the current list of tags.") @@ -783,22 +1001,50 @@ Usable with `ivy-resume', `ivy-next-line-and-call' and (t (error "Tags alignment failed"))) (org-move-to-column col))) +(defun counsel-org--set-tags () + (counsel-org-change-tags + (if counsel-org-tags + (format ":%s:" + (mapconcat #'identity counsel-org-tags ":")) + ""))) + +(defvar org-agenda-bulk-marked-entries) + +(declare-function org-get-at-bol "org") +(declare-function org-agenda-error "org-agenda") + (defun counsel-org-tag-action (x) (if (member x counsel-org-tags) (progn (setq counsel-org-tags (delete x counsel-org-tags))) - (setq counsel-org-tags (append counsel-org-tags (list x))) - (unless (member x ivy--all-candidates) - (setq ivy--all-candidates (append ivy--all-candidates (list x))))) + (unless (equal x "") + (setq counsel-org-tags (append counsel-org-tags (list x))) + (unless (member x ivy--all-candidates) + (setq ivy--all-candidates (append ivy--all-candidates (list x)))))) (let ((prompt (counsel-org-tag-prompt))) (setf (ivy-state-prompt ivy-last) prompt) (setq ivy--prompt (concat "%-4d " prompt))) - (cond ((memq this-command '(ivy-done ivy-alt-done)) - (counsel-org-change-tags - (if counsel-org-tags - (format ":%s:" - (mapconcat #'identity counsel-org-tags ":")) - ""))) + (cond ((memq this-command '(ivy-done + ivy-alt-done + ivy-immediate-done)) + (if (eq major-mode 'org-agenda-mode) + (if (null org-agenda-bulk-marked-entries) + (let ((hdmarker (or (org-get-at-bol 'org-hd-marker) + (org-agenda-error)))) + (with-current-buffer (marker-buffer hdmarker) + (goto-char hdmarker) + (counsel-org--set-tags))) + (let ((add-tags (copy-sequence counsel-org-tags))) + (dolist (m org-agenda-bulk-marked-entries) + (with-current-buffer (marker-buffer m) + (save-excursion + (goto-char m) + (setq counsel-org-tags + (delete-dups + (append (split-string (org-get-tags-string) ":" t) + add-tags))) + (counsel-org--set-tags)))))) + (counsel-org--set-tags))) ((eq this-command 'ivy-call) (delete-minibuffer-contents)))) @@ -817,21 +1063,32 @@ Usable with `ivy-resume', `ivy-next-line-and-call' and (declare-function org-get-buffer-tags "org") (declare-function org-global-tags-completion-table "org") (declare-function org-agenda-files "org") +(declare-function org-agenda-set-tags "org-agenda") ;;;###autoload (defun counsel-org-tag () "Add or remove tags in org-mode." (interactive) (save-excursion - (unless (org-at-heading-p) - (org-back-to-heading t)) - (setq counsel-org-tags (split-string (org-get-tags-string) ":" t)) + (if (eq major-mode 'org-agenda-mode) + (if org-agenda-bulk-marked-entries + (setq counsel-org-tags nil) + (let ((hdmarker (or (org-get-at-bol 'org-hd-marker) + (org-agenda-error)))) + (with-current-buffer (marker-buffer hdmarker) + (goto-char hdmarker) + (setq counsel-org-tags + (split-string (org-get-tags-string) ":" t))))) + (unless (org-at-heading-p) + (org-back-to-heading t)) + (setq counsel-org-tags (split-string (org-get-tags-string) ":" t))) (let ((org-setting-tags t) (org-last-tags-completion-table (append org-tag-persistent-alist (or org-tag-alist (org-get-buffer-tags)) (and - org-complete-tags-always-offer-all-agenda-tags + (or org-complete-tags-always-offer-all-agenda-tags + (eq major-mode 'org-agenda-mode)) (org-global-tags-completion-table (org-agenda-files)))))) (ivy-read (counsel-org-tag-prompt) @@ -857,26 +1114,73 @@ Usable with `ivy-resume', `ivy-next-line-and-call' and "Grep in the current directory for STRING." (if (< (length string) 3) (counsel-more-chars 3) - (let ((regex (replace-regexp-in-string - "\\\\)" ")" - (replace-regexp-in-string - "\\\\(" "(" - (ivy--regex string))))) + (let ((default-directory counsel--git-grep-dir) + (regex (counsel-unquote-regex-parens + (setq ivy--old-re + (ivy--regex string))))) (counsel--async-command - (format "ag --noheading --nocolor %S" regex)) + (format "ag --vimgrep %S" regex)) nil))) -(defun counsel-ag (&optional initial-input) +;;;###autoload +(defun counsel-ag (&optional initial-input initial-directory) "Grep for a string in the current directory using ag. INITIAL-INPUT can be given as the initial minibuffer input." (interactive) - (setq counsel--git-grep-dir default-directory) + (setq counsel--git-grep-dir (or initial-directory default-directory)) (ivy-read "ag: " 'counsel-ag-function :initial-input initial-input :dynamic-collection t :history 'counsel-git-grep-history :action #'counsel-git-grep-action - :unwind #'swiper--cleanup)) + :unwind (lambda () + (counsel-delete-process) + (swiper--cleanup)))) + +;;;###autoload +(defun counsel-grep () + "Grep for a string in the current file." + (interactive) + (setq counsel--git-grep-dir (buffer-file-name)) + (ivy-read "grep: " 'counsel-grep-function + :dynamic-collection t + :preselect (format "%d:%s" + (line-number-at-pos) + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position))) + :history 'counsel-git-grep-history + :update-fn (lambda () + (counsel-grep-action ivy--current)) + :action #'counsel-grep-action + :unwind (lambda () + (counsel-delete-process) + (swiper--cleanup)) + :caller 'counsel-grep)) + +(defun counsel-grep-function (string &optional _pred &rest _unused) + "Grep in the current directory for STRING." + (if (< (length string) 3) + (counsel-more-chars 3) + (let ((regex (counsel-unquote-regex-parens + (setq ivy--old-re + (ivy--regex string))))) + (counsel--async-command + (format "grep -nP --ignore-case '%s' %s" regex counsel--git-grep-dir)) + nil))) + +(defun counsel-grep-action (x) + (when (string-match "\\`\\([0-9]+\\):\\(.*\\)\\'" x) + (with-ivy-window + (let ((file-name counsel--git-grep-dir) + (line-number (match-string-no-properties 1 x))) + (find-file file-name) + (goto-char (point-min)) + (forward-line (1- (string-to-number line-number))) + (re-search-forward (ivy--regex ivy-text t) (line-end-position) t) + (unless (eq ivy-exit 'done) + (swiper--cleanup) + (swiper--add-overlays (ivy--regex ivy-text))))))) (defun counsel-recoll-function (string &optional _pred &rest _unused) "Grep in the current directory for STRING." @@ -911,6 +1215,54 @@ INITIAL-INPUT can be given as the initial minibuffer input." (unless (string-match "pdf$" x) (swiper ivy-text))))))) +(defcustom counsel-yank-pop-truncate nil + "When non-nil, truncate the display of long strings." + :group 'ivy) + +;;;###autoload +(defun counsel-yank-pop () + "Ivy replacement for `yank-pop'." + (interactive) + (if (eq last-command 'yank) + (progn + (setq counsel-completion-end (point)) + (setq counsel-completion-beg + (save-excursion + (search-backward (car kill-ring)) + (point)))) + (setq counsel-completion-beg (point)) + (setq counsel-completion-end (point))) + (let ((candidates (cl-remove-if + (lambda (s) + (or (< (length s) 3) + (string-match "\\`[\n[:blank:]]+\\'" s))) + (delete-dups kill-ring)))) + (when counsel-yank-pop-truncate + (setq candidates + (mapcar (lambda (s) + (if (string-match "\\`\\(.*\n.*\n.*\n.*\\)\n" s) + (progn + (let ((s (copy-sequence s))) + (put-text-property + (match-end 1) + (length s) + 'display + " [...]" + s) + s)) + s)) + candidates))) + (ivy-read "kill-ring: " candidates + :action 'counsel-yank-pop-action))) + +(defun counsel-yank-pop-action (s) + "Insert S into the buffer, overwriting the previous yank." + (with-ivy-window + (delete-region counsel-completion-beg + counsel-completion-end) + (insert (substring-no-properties s)) + (setq counsel-completion-end (point)))) + (provide 'counsel) ;;; counsel.el ends here