diff options
| author | Jim Porter | 2022-06-24 08:39:42 -0700 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-06-26 16:52:36 +0200 |
| commit | ea3681575f24ab6766931d0c86f080c52f2ce2d7 (patch) | |
| tree | b34466ad22ff94bd3a26aa0d9e98e43b78393d67 /lisp/eshell | |
| parent | 598d7c5d1c10bfb161cb53aa76d480864414487c (diff) | |
| download | emacs-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.el | 204 |
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. |
| 188 | The basic syntax is: | 192 | The 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. | ||
| 241 | If LAST is non-nil, this glob is the last element of a file name. | ||
| 242 | |||
| 243 | The result is a pair of regexps, the first for file names to | ||
| 244 | include, 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. | ||
| 276 | The result is a list, where the first element is the base | ||
| 277 | directory to search in, and the second is a list containing | ||
| 278 | elements 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. |
| 237 | If no files match, signal an error (if `eshell-error-if-no-glob' | 309 | If 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 | ||
| 248 | Mainly they are not supported because file matching is done with Emacs | 320 | Mainly they are not supported because file matching is done with Emacs |
| 249 | regular expressions, and these cannot support the above constructs." | 321 | regular 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. |
| 336 | GLOBS is a list of globs as converted by `eshell-glob-convert', | ||
| 337 | which 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 | ||