Skip to content

Commit

Permalink
Various sequence fixes in Ivy
Browse files Browse the repository at this point in the history
* ivy.el (ivy-sort-file-function-default): Use cached
directory-file-name.
(ivy--string-search, ivy--string-replace): New polyfills.
(xor): Copy autoload from Counsel for later transition.
(ivy--sorted-files): Limit scope of condition-case and
default-directory.  Consolidate multiple file list passes into a
single mapcan.  Use substring rather than regexp, and destructive,
operations when applicable.  Avoid unnecessary propertize copies.
Use prefixed text property name.  Ensure '.' and '..' are removed
even when falling back to directory-files.
(ivy--update-prompt, ivy--quote-format-string, ivy-completing-read)
(ivy--split-negation, ivy--magic-file-slash)
(ivy--update-minibuffer, ivy--filter): Use string rather than regexp
operations.
(ivy--switch-buffer-matcher): Ditto.  Don't assume cl-remove returns
a copy; use copy-sequence + cl-delete instead.
(ivy-re-match, ivy--prefix-sort, ivy-sort-function-buffer)
(ivy-occur-press): Simplify.
(ivy--split): Prefer \s over invisible \ .
(ivy--regex): Replace regexp with fixed case.
(ivy--positive-regexps): New function.
(ivy--highlight-default): Use it.
(ivy--action-insert): Remove no-op copypasta.
  • Loading branch information
basil-conto committed May 19, 2024
1 parent 9180550 commit 491a928
Showing 1 changed file with 102 additions and 98 deletions.
200 changes: 102 additions & 98 deletions ivy.el
Original file line number Diff line number Diff line change
Expand Up @@ -1766,13 +1766,11 @@ This string is inserted into the minibuffer."
(defun ivy-sort-file-function-default (x y)
"Compare two files X and Y.
Prioritize directories."
(if (get-text-property 0 'dirp x)
(if (get-text-property 0 'dirp y)
(string< (directory-file-name x) (directory-file-name y))
t)
(if (get-text-property 0 'dirp y)
nil
(string< x y))))
(let ((xdir (get-text-property 0 'ivy--dir x))
(ydir (get-text-property 0 'ivy--dir y)))
(if xdir
(or (not ydir) (string< xdir ydir))
(and (not ydir) (string< x y)))))

(defun ivy-string< (x y)
"Like `string<', but operate on CARs when given cons cells."
Expand Down Expand Up @@ -2011,29 +2009,49 @@ The child caller inherits and can override the settings of the parent.")
"Return non-nil if NAME ends with a directory separator."
(string-suffix-p "/" name))))

(defalias 'ivy--string-search
(if (fboundp 'string-search)
#'string-search
(lambda (needle haystack) ;; Faster than `cl-search'.
(string-match-p (regexp-quote needle) haystack)))
"Compatibility shim for Emacs 28 `string-search'.
\n(fn NEEDLE HAYSTACK)")

(defalias 'ivy--string-replace
(if (fboundp 'string-replace)
#'string-replace
(lambda (from to in)
(replace-regexp-in-string (regexp-quote from) to in t t)))
"Compatibility shim for Emacs 28 `string-replace'.
\n(fn FROM TO IN)")

;; Moved to subr.el in Emacs 27.1.
(autoload 'xor "array")

(defun ivy--sorted-files (dir)
"Return the list of files in DIR.
Directories come first."
(let* ((default-directory dir)
(seq (condition-case nil
(mapcar (lambda (s) (replace-regexp-in-string "\\$\\$" "$" s))
(all-completions "" #'read-file-name-internal
(ivy-state-predicate ivy-last)))
(error
(directory-files dir))))
sort-fn)
(setq seq (delete "./" (delete "../" seq)))
(when (eq (setq sort-fn (ivy--sort-function #'read-file-name-internal))
#'ivy-sort-file-function-default)
(setq seq (mapcar (lambda (x)
(propertize x 'dirp (ivy--dirname-p x)))
seq)))
(let* ((coll #'read-file-name-internal)
(sort-fn (ivy--sort-function coll))
(dirs-first (eq sort-fn #'ivy-sort-file-function-default))
(seq (cl-mapcan
(lambda (f)
(unless (member f '("./" "../"))
(setq f (ivy--string-replace "$$" "$" f))
(list (if (and dirs-first (ivy--dirname-p f))
(propertize f 'ivy--dir (directory-file-name f))
f))))
(condition-case nil
(let ((default-directory dir))
(all-completions "" coll (ivy-state-predicate ivy-last)))
(error (directory-files
dir nil directory-files-no-dot-files-regexp))))))
(when sort-fn
(setq seq (sort seq sort-fn)))
(dolist (dir ivy-extra-directories)
(push dir seq))
(dolist (extra ivy-extra-directories)
(push extra seq))
(if (string= dir "/")
(cl-remove-if (lambda (s) (string-match ":$" s)) (delete "../" seq))
(cl-delete-if (lambda (s) (string-suffix-p ":" s)) (delete "../" seq))
seq)))

(defun ivy-alist-setting (alist &optional key)
Expand Down Expand Up @@ -2069,7 +2087,7 @@ found, it falls back to the key t."
"Keyword (C-M-j to end): ")
(t
;; misearch.el
(replace-regexp-in-string "RET to end" "C-M-j to end" prompt))))
(ivy--string-replace "RET to end" "C-M-j to end" prompt))))

;;** Entry Point
;;;###autoload
Expand Down Expand Up @@ -2495,7 +2513,7 @@ This is useful for recursive `ivy-read'."

(defun ivy--quote-format-string (str)
"Make STR suitable for `format' with no extra arguments."
(replace-regexp-in-string "%" "%%" str t t))
(ivy--string-replace "%" "%%" str))

;;;###autoload
(defun ivy-completing-read (prompt collection
Expand Down Expand Up @@ -2537,15 +2555,13 @@ INHERIT-INPUT-METHOD is currently ignored."
prompt collection
:predicate predicate
:require-match (and collection require-match)
:initial-input (cond ((consp initial-input)
(car initial-input))
((and (stringp initial-input)
(not (eq collection #'read-file-name-internal))
(string-match-p "\\+" initial-input))
(replace-regexp-in-string
"\\+" "\\\\+" initial-input))
(t
initial-input))
:initial-input
(cond ((consp initial-input)
(car initial-input))
((and (stringp initial-input)
(not (eq collection #'read-file-name-internal)))
(ivy--string-replace "+" "\\\\+" initial-input))
(initial-input))
:preselect def
:def def
:history history
Expand Down Expand Up @@ -2789,13 +2805,10 @@ Each element of RE-SEQ must match for the function to return true.

This concept is used to generalize regular expressions for
`ivy--regex-plus' and `ivy--regex-ignore-order'."
(let ((res t)
re)
(while (and res (setq re (pop re-seq)))
(setq res
(if (cdr re)
(string-match-p (car re) str)
(not (string-match-p (car re) str)))))
(let ((res t))
(while (let ((pair (pop re-seq)))
(and pair (setq res (xor (not (cdr pair))
(string-match-p (car pair) str))))))
res))

(defvar ivy--regex-hash
Expand Down Expand Up @@ -2840,7 +2853,7 @@ regexp is passed to `regexp-quote'."
(setq ivy--input-garbage (substring str i))
(throw 'break nil)))
(cl-incf i)))
((= ?\ c)
((= ?\s c)
(string-match " +" str i)
(setq match-len (- (match-end 0) (match-beginning 0)))
(if (= match-len 1)
Expand All @@ -2849,7 +2862,7 @@ regexp is passed to `regexp-quote'."
(push (substring str start i) res))
(setq start (1+ i)))
(setq str (replace-match
(make-string (1- match-len) ?\ )
(make-string (1- match-len) ?\s)
nil nil str))
(setq len (length str))
(cl-incf i (1- match-len)))
Expand Down Expand Up @@ -2917,7 +2930,7 @@ When GREEDY is non-nil, join words in a greedy way."
(format "\\(%s\\)" x)))
subs
(if greedy ".*" ".*?"))
nil t))))
t t))))
ivy--regex-hash)))))

(defun ivy--regex-p (object)
Expand All @@ -2935,10 +2948,10 @@ Do not split if the delimiter is escaped as \\!.
Assumes there is at most one un-escaped delimiter and discards
text after delimiter if it is empty. Modifies match data."
(unless (string= str "")
(let ((delim "\\(?:\\`\\|[^\\]\\)\\(!\\)"))
(mapcar (lambda (split)
;; Store "\!" as "!".
(replace-regexp-in-string "\\\\!" "!" split t t))
(mapcar (lambda (split)
;; Store "\!" as "!".
(ivy--string-replace "\\!" "!" split))
(let ((delim "\\(?:\\`\\|[^\\]\\)\\(!\\)"))
(if (string-match delim str)
;; Ignore everything past first un-escaped ! rather than
;; crashing. We can't warn or error because the minibuffer is
Expand Down Expand Up @@ -3285,7 +3298,7 @@ Possible choices are `ivy-magic-slash-non-match-cd-selected',
((string-suffix-p "//" ivy-text)
(ivy--cd
(ivy--magic-file-doubleslash-directory)))
((string-match-p "\\`/ssh:" ivy-text)
((string-prefix-p "/ssh:" ivy-text)
(ivy--cd (file-name-directory ivy-text)))
((string-match "[[:alpha:]]:/\\'" ivy-text)
(let ((drive-root (match-string 0 ivy-text)))
Expand Down Expand Up @@ -3445,27 +3458,25 @@ Should be run via minibuffer `post-command-hook'."
(setq ivy--old-re nil)
(setq in-progress t))
(when (or ivy--all-candidates
(and (not (get-process " *counsel*"))
(not in-progress)))
(not (or (get-process " *counsel*")
in-progress)))
(ivy--set-index-dynamic-collection)
(ivy--format ivy--all-candidates)))
(cond (ivy--directory
(cond ((or (string= "~/" ivy-text)
(and (string= "~" ivy-text)
ivy-magic-tilde))
(ivy--cd (ivy--magic-tilde-directory ivy--directory)))
((string-match "/\\'" ivy-text)
((string-suffix-p "/" ivy-text)
(ivy--magic-file-slash))))
((eq (ivy-state-collection ivy-last) #'internal-complete-buffer)
(when (or (and (string-match "\\` " ivy-text)
(not (string-match "\\` " ivy--old-text)))
(and (string-match "\\` " ivy--old-text)
(not (string-match "\\` " ivy-text))))
(setq ivy--all-candidates
(if (= (string-to-char ivy-text) ?\s)
(ivy--buffer-list " ")
(ivy--buffer-list "" ivy-use-virtual-buffers)))
(setq ivy--old-re nil))))
(let ((spaced (= (string-to-char ivy-text) ?\s)))
(when (xor spaced (= (string-to-char ivy--old-text) ?\s))
(setq ivy--all-candidates
(if spaced
(ivy--buffer-list " ")
(ivy--buffer-list "" ivy-use-virtual-buffers)))
(setq ivy--old-re nil)))))
(with-current-buffer (ivy-state-buffer ivy-last)
(ivy--format
(ivy--filter ivy-text ivy--all-candidates))))
Expand Down Expand Up @@ -3637,12 +3648,10 @@ CANDIDATES are assumed to be static."
((and ivy--old-re
(stringp re)
(stringp ivy--old-re)
(not (string-match-p "\\\\" ivy--old-re))
(not (ivy--string-search "\\" ivy--old-re))
(not (equal ivy--old-re ""))
(memq (cl-search
(if (string-match-p "\\\\)\\'" ivy--old-re)
(substring ivy--old-re 0 -2)
ivy--old-re)
(memq (ivy--string-search
(string-remove-suffix "\\)" ivy--old-re)
re)
'(0 2))
ivy--old-cands
Expand Down Expand Up @@ -3745,9 +3754,9 @@ Prefix matches to NAME are put ahead of the list."
res-prefix
res-noprefix)
(dolist (s candidates)
(if (string-match-p re-prefix s)
(push s res-prefix)
(push s res-noprefix)))
(push s (if (string-match-p re-prefix s)
res-prefix
res-noprefix)))
(nconc
(nreverse res-prefix)
(nreverse res-noprefix)))))
Expand All @@ -3771,25 +3780,18 @@ before substring matches."
(= (aref name 0) ?^))
candidates
(let* ((base-re (ivy-re-to-str (funcall ivy--regex-function name)))
(re-star-prefix (concat "\\`\\*" base-re))
(re-prefix (concat "\\`" base-re))
(re-prefix (concat "\\`\\*?" base-re))
res-prefix
res-noprefix
res-virtual-prefix
res-virtual-noprefix)
(dolist (s candidates)
(cond
((and (assoc s ivy--virtual-buffers)
(or (string-match-p re-star-prefix s)
(string-match-p re-prefix s)))
(push s res-virtual-prefix))
((assoc s ivy--virtual-buffers)
(push s res-virtual-noprefix))
((or (string-match-p re-star-prefix s)
(string-match-p re-prefix s))
(push s res-prefix))
(t
(push s res-noprefix))))
(let ((virtual (assoc s ivy--virtual-buffers))
(prefixed (string-match-p re-prefix s)))
(push s (cond ((and virtual prefixed) res-virtual-prefix)
(virtual res-virtual-noprefix)
(prefixed res-prefix)
(t res-noprefix)))))
(nconc
(nreverse res-prefix)
(nreverse res-noprefix)
Expand Down Expand Up @@ -4125,12 +4127,16 @@ of `ivy-minibuffer-faces'. Otherwise, always use the first face
in this case."
:type 'boolean)

(defun ivy--positive-regexps ()
"Return a list of the positive regexps in `ivy-regex'."
(let ((re ivy-regex))
(if (listp re)
(cl-mapcan (lambda (x) (and (cdr x) (list (car x)))) re)
(list re))))

(defun ivy--highlight-default (str)
"Highlight STR, using the default method."
(let ((regexps
(if (listp ivy-regex)
(mapcar #'car (cl-remove-if-not #'cdr ivy-regex))
(list ivy-regex)))
(let ((regexps (ivy--positive-regexps))
start)
(dolist (re regexps)
(ignore-errors
Expand Down Expand Up @@ -4639,7 +4645,7 @@ Otherwise, forward to `ivy-kill-line'."
(insert
(if (stringp x)
(ivy--trim-grep-line-number x)
x (car x))))
(car x))))

(defun ivy--action-copy (x)
(kill-new
Expand All @@ -4652,13 +4658,13 @@ Otherwise, forward to `ivy-kill-line'."
Skip buffers that match `ivy-ignore-buffers'."
(if (string-match-p "^:" ivy-text)
(delete-dups
(cl-remove-if-not
(cl-delete-if-not
(lambda (s)
(let ((b (get-buffer s)))
(and b
(string-match-p regexp (buffer-local-value 'default-directory b))
(not (string-match-p "^\\*" s)))))
candidates))
(when (/= (string-to-char s) ?*)
(let ((b (get-buffer s)))
(and b (string-match-p
regexp (buffer-local-value 'default-directory b))))))
(copy-sequence candidates)))
(let ((res (ivy--re-filter regexp candidates)))
(if (or (null ivy-use-ignore)
(null ivy-ignore-buffers))
Expand Down Expand Up @@ -5314,9 +5320,7 @@ EVENT gives the mouse position."
(looking-at "\\(?:.[/\\]\\| \\)\\(.*\\)$"))
(let* ((ivy-last ivy-occur-last)
(ivy-text (ivy-state-text ivy-last))
(str (buffer-substring
(match-beginning 1)
(match-end 1)))
(str (match-string 1))
(offset (or (get-text-property 0 'offset str) 0))
(coll (ivy-state-collection ivy-last))
(action (ivy--get-action ivy-last))
Expand Down

0 comments on commit 491a928

Please sign in to comment.