aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/eshell
diff options
context:
space:
mode:
authorJim Porter2022-06-24 08:39:42 -0700
committerLars Ingebrigtsen2022-06-26 16:52:36 +0200
commitea3681575f24ab6766931d0c86f080c52f2ce2d7 (patch)
treeb34466ad22ff94bd3a26aa0d9e98e43b78393d67 /lisp/eshell
parent598d7c5d1c10bfb161cb53aa76d480864414487c (diff)
downloademacs-ea3681575f24ab6766931d0c86f080c52f2ce2d7.tar.gz
emacs-ea3681575f24ab6766931d0c86f080c52f2ce2d7.zip
Convert Eshell globs ahead of time instead of doing it repeatedly
* lisp/eshell/em-glob.el (eshell-glob-recursive): New variable. (eshell-glob-convert-1, eshell-glob-convert): New functions. (eshell-extended-glob): Use 'eshell-glob-convert'. (eshell-glob-entries): Adapt function to use pre-converted globs. * test/lisp/eshell-em-glob-tests.el (em-glob-test/match-dot-files): New test.
Diffstat (limited to 'lisp/eshell')
-rw-r--r--lisp/eshell/em-glob.el204
1 files changed, 114 insertions, 90 deletions
diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index 52531ff8939..8acdaee2331 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -183,6 +183,10 @@ interpretation."
183(defvar eshell-glob-matches) 183(defvar eshell-glob-matches)
184(defvar message-shown) 184(defvar message-shown)
185 185
186(defvar eshell-glob-recursive-alist
187 '(("**/" . recurse)
188 ("***/" . recurse-symlink)))
189
186(defun eshell-glob-regexp (pattern) 190(defun eshell-glob-regexp (pattern)
187 "Convert glob-pattern PATTERN to a regular expression. 191 "Convert glob-pattern PATTERN to a regular expression.
188The basic syntax is: 192The basic syntax is:
@@ -232,6 +236,74 @@ resulting regular expression."
232 (regexp-quote (substring pattern matched-in-pattern)) 236 (regexp-quote (substring pattern matched-in-pattern))
233 "\\'"))) 237 "\\'")))
234 238
239(defun eshell-glob-convert-1 (glob &optional last)
240 "Convert a GLOB matching a single element of a file name to regexps.
241If LAST is non-nil, this glob is the last element of a file name.
242
243The result is a pair of regexps, the first for file names to
244include, and the second for ones to exclude."
245 (let ((len (length glob)) (index 1) (incl glob) excl)
246 ;; We can't use `directory-file-name' because it strips away text
247 ;; properties in the string.
248 (let ((last (1- (length incl))))
249 (when (eq (aref incl last) ?/)
250 (setq incl (substring incl 0 last))))
251 ;; Split the glob if it contains a negation like x~y.
252 (while (and (eq incl glob)
253 (setq index (string-search "~" glob index)))
254 (if (or (get-text-property index 'escaped glob)
255 (or (= (1+ index) len)))
256 (setq index (1+ index))
257 (setq incl (substring glob 0 index)
258 excl (substring glob (1+ index)))))
259 (setq incl (eshell-glob-regexp incl)
260 excl (and excl (eshell-glob-regexp excl)))
261 ;; Exclude dot files if requested.
262 (if (or eshell-glob-include-dot-files
263 (eq (aref glob 0) ?.))
264 (unless (or eshell-glob-include-dot-dot
265 (not last))
266 (setq excl (if excl
267 (concat "\\(\\`\\.\\.?\\'\\|" excl "\\)")
268 "\\`\\.\\.?\\'")))
269 (setq excl (if excl
270 (concat "\\(\\`\\.\\|" excl "\\)")
271 "\\`\\.")))
272 (cons incl excl)))
273
274(defun eshell-glob-convert (glob)
275 "Convert an Eshell glob-pattern GLOB to regexps.
276The result is a list, where the first element is the base
277directory to search in, and the second is a list containing
278elements of the following forms:
279
280* Regexp pairs as generated by `eshell-glob-convert-1'.
281
282* `recurse', indicating that searches should recurse into
283 subdirectories.
284
285* `recurse-symlink', like `recurse', but also following symlinks."
286 (let ((globs (eshell-split-path glob))
287 start-dir result last-saw-recursion)
288 (if (and (cdr globs)
289 (file-name-absolute-p (car globs)))
290 (setq start-dir (car globs)
291 globs (cdr globs))
292 (setq start-dir "."))
293 (while globs
294 (if-let ((recurse (cdr (assoc (car globs)
295 eshell-glob-recursive-alist))))
296 (if last-saw-recursion
297 (setcar result recurse)
298 (push recurse result)
299 (setq last-saw-recursion t))
300 (push (eshell-glob-convert-1 (car globs) (null (cdr globs)))
301 result)
302 (setq last-saw-recursion nil))
303 (setq globs (cdr globs)))
304 (list (file-name-as-directory start-dir)
305 (nreverse result))))
306
235(defun eshell-extended-glob (glob) 307(defun eshell-extended-glob (glob)
236 "Return a list of files matched by GLOB. 308 "Return a list of files matched by GLOB.
237If no files match, signal an error (if `eshell-error-if-no-glob' 309If no files match, signal an error (if `eshell-error-if-no-glob'
@@ -247,14 +319,10 @@ syntax. Things that are not supported are:
247 319
248Mainly they are not supported because file matching is done with Emacs 320Mainly they are not supported because file matching is done with Emacs
249regular expressions, and these cannot support the above constructs." 321regular expressions, and these cannot support the above constructs."
250 (let ((paths (eshell-split-path glob)) 322 (let ((globs (eshell-glob-convert glob))
251 eshell-glob-matches message-shown) 323 eshell-glob-matches message-shown)
252 (unwind-protect 324 (unwind-protect
253 (if (and (cdr paths) 325 (apply #'eshell-glob-entries globs)
254 (file-name-absolute-p (car paths)))
255 (eshell-glob-entries (file-name-as-directory (car paths))
256 (cdr paths))
257 (eshell-glob-entries (file-name-as-directory ".") paths))
258 (if message-shown 326 (if message-shown
259 (message nil))) 327 (message nil)))
260 (or (and eshell-glob-matches (sort eshell-glob-matches #'string<)) 328 (or (and eshell-glob-matches (sort eshell-glob-matches #'string<))
@@ -263,94 +331,50 @@ regular expressions, and these cannot support the above constructs."
263 glob)))) 331 glob))))
264 332
265;; FIXME does this really need to abuse eshell-glob-matches, message-shown? 333;; FIXME does this really need to abuse eshell-glob-matches, message-shown?
266(defun eshell-glob-entries (path globs &optional recurse-p) 334(defun eshell-glob-entries (path globs)
267 "Glob the entries in PATH, possibly recursing if RECURSE-P is non-nil." 335 "Match the entries in PATH against GLOBS.
336GLOBS is a list of globs as converted by `eshell-glob-convert',
337which see."
268 (let* ((entries (ignore-errors 338 (let* ((entries (ignore-errors
269 (file-name-all-completions "" path))) 339 (file-name-all-completions "" path)))
270 (case-fold-search eshell-glob-case-insensitive) 340 (case-fold-search eshell-glob-case-insensitive)
271 (glob (car globs)) 341 glob glob-remainder recurse-p)
272 (len (length glob)) 342 (if (rassq (car globs) eshell-glob-recursive-alist)
273 dirs rdirs 343 (setq recurse-p (car globs)
274 incl excl 344 glob (cadr globs)
275 name isdir pathname) 345 glob-remainder (cddr globs))
276 (while (cond 346 (setq glob (car globs)
277 ((and (= len 3) (equal glob "**/")) 347 glob-remainder (cdr globs)))
278 (setq recurse-p 2
279 globs (cdr globs)
280 glob (car globs)
281 len (length glob)))
282 ((and (= len 4) (equal glob "***/"))
283 (setq recurse-p 3
284 globs (cdr globs)
285 glob (car globs)
286 len (length glob)))))
287 (if (and recurse-p (not glob))
288 (error "`**/' cannot end a globbing pattern"))
289 (let ((index 1))
290 (setq incl glob)
291 (while (and (eq incl glob)
292 (setq index (string-search "~" glob index)))
293 (if (or (get-text-property index 'escaped glob)
294 (or (= (1+ index) len)))
295 (setq index (1+ index))
296 (setq incl (substring glob 0 index)
297 excl (substring glob (1+ index))))))
298 ;; can't use `directory-file-name' because it strips away text
299 ;; properties in the string
300 (let ((len (1- (length incl))))
301 (if (eq (aref incl len) ?/)
302 (setq incl (substring incl 0 len)))
303 (when excl
304 (setq len (1- (length excl)))
305 (if (eq (aref excl len) ?/)
306 (setq excl (substring excl 0 len)))))
307 (setq incl (eshell-glob-regexp incl)
308 excl (and excl (eshell-glob-regexp excl)))
309 (if (or eshell-glob-include-dot-files
310 (eq (aref glob 0) ?.))
311 (unless (or eshell-glob-include-dot-dot
312 (cdr globs))
313 (setq excl (if excl
314 (concat "\\(\\`\\.\\.?\\'\\|" excl "\\)")
315 "\\`\\.\\.?\\'")))
316 (setq excl (if excl
317 (concat "\\(\\`\\.\\|" excl "\\)")
318 "\\`\\.")))
319 (when (and recurse-p eshell-glob-show-progress) 348 (when (and recurse-p eshell-glob-show-progress)
320 (message "Building file list...%d so far: %s" 349 (message "Building file list...%d so far: %s"
321 (length eshell-glob-matches) path) 350 (length eshell-glob-matches) path)
322 (setq message-shown t)) 351 (setq message-shown t))
323 (if (equal path "./") (setq path "")) 352 (when (equal path "./") (setq path ""))
324 (while entries 353 (let ((incl (car glob))
325 (setq name (car entries) 354 (excl (cdr glob))
326 len (length name) 355 dirs rdirs)
327 isdir (eq (aref name (1- len)) ?/)) 356 (dolist (name entries)
328 (if (let ((fname (directory-file-name name))) 357 (let* ((len (length name))
329 (and (not (and excl (string-match excl fname))) 358 (isdir (eq (aref name (1- len)) ?/))
330 (string-match incl fname))) 359 pathname)
331 (if (cdr globs) 360 (when (let ((fname (directory-file-name name)))
332 (if isdir 361 (and (not (and excl (string-match excl fname)))
333 (setq dirs (cons (concat path name) dirs))) 362 (string-match incl fname)))
334 (setq eshell-glob-matches 363 (if glob-remainder
335 (cons (concat path name) eshell-glob-matches)))) 364 (when isdir
336 (if (and recurse-p isdir 365 (push (concat path name) dirs))
337 (or (> len 3) 366 (push (concat path name) eshell-glob-matches)))
338 (not (or (and (= len 2) (equal name "./")) 367 (when (and recurse-p isdir
339 (and (= len 3) (equal name "../"))))) 368 (not (member name '("./" "../")))
340 (setq pathname (concat path name)) 369 (setq pathname (concat path name))
341 (not (and (= recurse-p 2) 370 (not (and (eq recurse-p 'recurse)
342 (file-symlink-p 371 (file-symlink-p
343 (directory-file-name pathname))))) 372 (directory-file-name pathname)))))
344 (setq rdirs (cons pathname rdirs))) 373 (push pathname rdirs))))
345 (setq entries (cdr entries))) 374 (dolist (dir (nreverse dirs))
346 (setq dirs (nreverse dirs) 375 (eshell-glob-entries dir glob-remainder))
347 rdirs (nreverse rdirs)) 376 (dolist (rdir (nreverse rdirs))
348 (while dirs 377 (eshell-glob-entries rdir globs)))))
349 (eshell-glob-entries (car dirs) (cdr globs))
350 (setq dirs (cdr dirs)))
351 (while rdirs
352 (eshell-glob-entries (car rdirs) globs recurse-p)
353 (setq rdirs (cdr rdirs)))))
354 378
355(provide 'em-glob) 379(provide 'em-glob)
356 380