|
1 | 1 | local async = require('plenary.async') |
2 | 2 | local curl = require('plenary.curl') |
3 | | -local scandir = require('plenary.scandir') |
4 | 3 | local log = require('plenary.log') |
5 | 4 |
|
6 | 5 | local M = {} |
|
479 | 478 | ---@class CopilotChat.utils.ScanOpts |
480 | 479 | ---@field max_count number? The maximum number of files to scan |
481 | 480 | ---@field max_depth number? The maximum depth to scan |
482 | | ----@field glob? string The glob pattern to match files |
| 481 | +---@field pattern? string The glob pattern to match files |
483 | 482 | ---@field hidden? boolean Whether to include hidden files |
484 | 483 | ---@field no_ignore? boolean Whether to respect or ignore .gitignore |
485 | 484 |
|
@@ -527,19 +526,69 @@ M.glob = async.wrap(function(path, opts, callback) |
527 | 526 | return |
528 | 527 | end |
529 | 528 |
|
530 | | - -- Fall back to scandir if rg is not available or fails |
531 | | - scandir.scan_dir_async( |
532 | | - path, |
533 | | - vim.tbl_deep_extend('force', opts, { |
534 | | - depth = opts.max_depth, |
535 | | - add_dirs = false, |
536 | | - search_pattern = opts.glob and M.glob_to_pattern(opts.glob) or nil, |
537 | | - respect_gitignore = not opts.no_ignore, |
538 | | - on_exit = function(files) |
539 | | - callback(filter_files(files, opts.max_count)) |
540 | | - end, |
541 | | - }) |
542 | | - ) |
| 529 | + -- Fallback to vim.uv.fs_scandir |
| 530 | + local matchers = {} |
| 531 | + if opts.pattern then |
| 532 | + local file_pattern = vim.glob.to_lpeg(opts.pattern) |
| 533 | + local path_pattern = vim.lpeg.P(path .. '/') * file_pattern |
| 534 | + |
| 535 | + table.insert(matchers, function(name, dir) |
| 536 | + return file_pattern:match(name) or path_pattern:match(dir .. '/' .. name) |
| 537 | + end) |
| 538 | + end |
| 539 | + |
| 540 | + if not opts.hidden then |
| 541 | + table.insert(matchers, function(name) |
| 542 | + return not name:match('^%.') |
| 543 | + end) |
| 544 | + end |
| 545 | + |
| 546 | + local data = {} |
| 547 | + local next_dir = { path } |
| 548 | + local current_depths = { [path] = 1 } |
| 549 | + |
| 550 | + local function read_dir(err, fd) |
| 551 | + local current_dir = table.remove(next_dir, 1) |
| 552 | + local depth = current_depths[current_dir] or 1 |
| 553 | + |
| 554 | + if not err and fd then |
| 555 | + while true do |
| 556 | + local name, typ = vim.uv.fs_scandir_next(fd) |
| 557 | + if name == nil then |
| 558 | + break |
| 559 | + end |
| 560 | + |
| 561 | + local full_path = current_dir .. '/' .. name |
| 562 | + |
| 563 | + if typ == 'directory' and not name:match('^%.git') then |
| 564 | + if not opts.max_depth or depth < opts.max_depth then |
| 565 | + table.insert(next_dir, full_path) |
| 566 | + current_depths[full_path] = depth + 1 |
| 567 | + end |
| 568 | + else |
| 569 | + local match = true |
| 570 | + for _, matcher in ipairs(matchers) do |
| 571 | + if not matcher(name, current_dir) then |
| 572 | + match = false |
| 573 | + break |
| 574 | + end |
| 575 | + end |
| 576 | + |
| 577 | + if match then |
| 578 | + table.insert(data, full_path) |
| 579 | + end |
| 580 | + end |
| 581 | + end |
| 582 | + end |
| 583 | + |
| 584 | + if #next_dir == 0 then |
| 585 | + callback(data) |
| 586 | + else |
| 587 | + vim.uv.fs_scandir(next_dir[1], read_dir) |
| 588 | + end |
| 589 | + end |
| 590 | + |
| 591 | + vim.uv.fs_scandir(path, read_dir) |
543 | 592 | end, 3) |
544 | 593 |
|
545 | 594 | --- Grep a directory |
@@ -783,136 +832,4 @@ function M.split_lines(text) |
783 | 832 | return vim.split(text, '\r?\n', { trimempty = false }) |
784 | 833 | end |
785 | 834 |
|
786 | | ---- Convert glob pattern to regex pattern |
787 | | ---- https://github.com/davidm/lua-glob-pattern/blob/master/lua/globtopattern.lua |
788 | | ----@param g string The glob pattern |
789 | | ----@return string |
790 | | -function M.glob_to_pattern(g) |
791 | | - local p = '^' -- pattern being built |
792 | | - local i = 0 -- index in g |
793 | | - local c -- char at index i in g. |
794 | | - |
795 | | - -- unescape glob char |
796 | | - local function unescape() |
797 | | - if c == '\\' then |
798 | | - i = i + 1 |
799 | | - c = g:sub(i, i) |
800 | | - if c == '' then |
801 | | - p = '[^]' |
802 | | - return false |
803 | | - end |
804 | | - end |
805 | | - return true |
806 | | - end |
807 | | - |
808 | | - -- escape pattern char |
809 | | - local function escape(c) |
810 | | - return c:match('^%w$') and c or '%' .. c |
811 | | - end |
812 | | - |
813 | | - -- Convert tokens at end of charset. |
814 | | - local function charset_end() |
815 | | - while 1 do |
816 | | - if c == '' then |
817 | | - p = '[^]' |
818 | | - return false |
819 | | - elseif c == ']' then |
820 | | - p = p .. ']' |
821 | | - break |
822 | | - else |
823 | | - if not unescape() then |
824 | | - break |
825 | | - end |
826 | | - local c1 = c |
827 | | - i = i + 1 |
828 | | - c = g:sub(i, i) |
829 | | - if c == '' then |
830 | | - p = '[^]' |
831 | | - return false |
832 | | - elseif c == '-' then |
833 | | - i = i + 1 |
834 | | - c = g:sub(i, i) |
835 | | - if c == '' then |
836 | | - p = '[^]' |
837 | | - return false |
838 | | - elseif c == ']' then |
839 | | - p = p .. escape(c1) .. '%-]' |
840 | | - break |
841 | | - else |
842 | | - if not unescape() then |
843 | | - break |
844 | | - end |
845 | | - p = p .. escape(c1) .. '-' .. escape(c) |
846 | | - end |
847 | | - elseif c == ']' then |
848 | | - p = p .. escape(c1) .. ']' |
849 | | - break |
850 | | - else |
851 | | - p = p .. escape(c1) |
852 | | - i = i - 1 -- put back |
853 | | - end |
854 | | - end |
855 | | - i = i + 1 |
856 | | - c = g:sub(i, i) |
857 | | - end |
858 | | - return true |
859 | | - end |
860 | | - |
861 | | - -- Convert tokens in charset. |
862 | | - local function charset() |
863 | | - i = i + 1 |
864 | | - c = g:sub(i, i) |
865 | | - if c == '' or c == ']' then |
866 | | - p = '[^]' |
867 | | - return false |
868 | | - elseif c == '^' or c == '!' then |
869 | | - i = i + 1 |
870 | | - c = g:sub(i, i) |
871 | | - if c == ']' then |
872 | | - -- ignored |
873 | | - else |
874 | | - p = p .. '[^' |
875 | | - if not charset_end() then |
876 | | - return false |
877 | | - end |
878 | | - end |
879 | | - else |
880 | | - p = p .. '[' |
881 | | - if not charset_end() then |
882 | | - return false |
883 | | - end |
884 | | - end |
885 | | - return true |
886 | | - end |
887 | | - |
888 | | - -- Convert tokens. |
889 | | - while 1 do |
890 | | - i = i + 1 |
891 | | - c = g:sub(i, i) |
892 | | - if c == '' then |
893 | | - p = p .. '$' |
894 | | - break |
895 | | - elseif c == '?' then |
896 | | - p = p .. '.' |
897 | | - elseif c == '*' then |
898 | | - p = p .. '.*' |
899 | | - elseif c == '[' then |
900 | | - if not charset() then |
901 | | - break |
902 | | - end |
903 | | - elseif c == '\\' then |
904 | | - i = i + 1 |
905 | | - c = g:sub(i, i) |
906 | | - if c == '' then |
907 | | - p = p .. '\\$' |
908 | | - break |
909 | | - end |
910 | | - p = p .. escape(c) |
911 | | - else |
912 | | - p = p .. escape(c) |
913 | | - end |
914 | | - end |
915 | | - return p |
916 | | -end |
917 | | - |
918 | 835 | return M |
0 commit comments