Skip to content

Commit 1c450db

Browse files
feat(search)!: use ripgrep when available for file scanning (CopilotC-Nvim#953)
* feat(search)!: use ripgrep when available for file scanning Adds ripgrep integration to improve search performance when scanning directories. Falls back to the existing scandir implementation when ripgrep is not available. Updates the configuration options to use more consistent naming and adds health check for the ripgrep dependency. Also cleans up installation instructions in README. BREAKING CHANGE: Due to previous incorrect glob handling, .lua patterns etc need to be specified as *.lua Signed-off-by: Tomas Slusny <slusnucky@gmail.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Tomas Slusny <slusnucky@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent f2205ec commit 1c450db

File tree

5 files changed

+201
-32
lines changed

5 files changed

+201
-32
lines changed

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,8 @@ CopilotChat.nvim is a Neovim plugin that brings GitHub Copilot Chat capabilities
4646
- Manual: Download from [lua-tiktoken releases](https://github.com/gptlang/lua-tiktoken/releases) and save as `tiktoken_core.so` in your Lua path
4747

4848
- [git](https://git-scm.com/) - For git diff context features
49-
50-
- Arch Linux: Install from official repositories
51-
- Other systems: Use system package manager or official installer
52-
49+
- [ripgrep](https://github.com/BurntSushi/ripgrep) - For improved search performance
5350
- [lynx](https://lynx.invisible-island.net/) - For improved URL context features
54-
- Arch Linux: Install from official repositories
55-
- Other systems: Use system package manager or official installer
5651

5752
## Integration with pickers
5853

@@ -331,7 +326,7 @@ Examples:
331326
```markdown
332327
> #buffer
333328
> #buffer:2
334-
> #files:.lua
329+
> #files:\*.lua
335330
> #filenames
336331
> #git:staged
337332
> #url:https://example.com

lua/CopilotChat/config/contexts.lua

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ return {
5757
input = function(callback, source)
5858
local cwd = utils.win_cwd(source.winnr)
5959
local files = utils.scan_dir(cwd, {
60-
add_dirs = false,
61-
respect_gitignore = true,
6260
max_files = 1000,
6361
})
6462

@@ -83,7 +81,7 @@ return {
8381
end,
8482
resolve = function(input, source)
8583
return context.files(source.winnr, true, {
86-
search_pattern = input and utils.glob_to_regex(input),
84+
glob = input,
8785
})
8886
end,
8987
},
@@ -97,7 +95,7 @@ return {
9795
end,
9896
resolve = function(input, source)
9997
return context.files(source.winnr, false, {
100-
search_pattern = input and utils.glob_to_regex(input),
98+
glob = input,
10199
})
102100
end,
103101
},

lua/CopilotChat/context.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ local MIN_SYMBOL_SIMILARITY = 0.3
7272
local MIN_SEMANTIC_SIMILARITY = 0.4
7373
local MULTI_FILE_THRESHOLD = 5
7474
local MAX_FILES = 2500
75+
local MAX_DEPTH = 50
7576

7677
--- Compute the cosine similarity between two vectors
7778
---@param a table<number>
@@ -372,7 +373,7 @@ end
372373
--- Get list of all files in workspace
373374
---@param winnr number?
374375
---@param with_content boolean?
375-
---@param search_options table?
376+
---@param search_options CopilotChat.utils.scan_dir_opts?
376377
---@return table<CopilotChat.context.embed>
377378
function M.files(winnr, with_content, search_options)
378379
local cwd = utils.win_cwd(winnr)
@@ -382,9 +383,8 @@ function M.files(winnr, with_content, search_options)
382383
local files = utils.scan_dir(
383384
cwd,
384385
vim.tbl_extend('force', {
385-
add_dirs = false,
386-
respect_gitignore = true,
387-
max_files = MAX_FILES,
386+
max_count = MAX_FILES,
387+
max_depth = MAX_DEPTH,
388388
}, search_options)
389389
)
390390

lua/CopilotChat/health.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ function M.check()
7272
ok('git: ' .. git_version)
7373
end
7474

75+
local rg_version = run_command('rg', '--version')
76+
if rg_version == false then
77+
warn(
78+
'rg: missing, optional for improved search performance. See "https://github.com/BurntSushi/ripgrep".'
79+
)
80+
else
81+
ok('rg: ' .. rg_version)
82+
end
83+
7584
local lynx_version = run_command('lynx', '-version')
7685
if lynx_version == false then
7786
warn(

lua/CopilotChat/utils.lua

Lines changed: 184 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -394,17 +394,73 @@ M.curl_post = async.wrap(function(url, opts, callback)
394394
curl.post(url, args)
395395
end, 3)
396396

397+
---@class CopilotChat.utils.scan_dir_opts
398+
---@field max_count number? The maximum number of files to scan
399+
---@field max_depth number? The maximum depth to scan
400+
---@field glob? string The glob pattern to match files
401+
---@field hidden? boolean Whether to include hidden files
402+
---@field no_ignore? boolean Whether to respect or ignore .gitignore
403+
397404
--- Scan a directory
398405
---@param path string The directory path
399-
---@param opts table The options
406+
---@param opts CopilotChat.utils.scan_dir_opts The options
400407
---@async
401408
M.scan_dir = async.wrap(function(path, opts, callback)
409+
-- Use ripgrep if available
410+
if vim.fn.executable('rg') == 1 then
411+
local cmd = { 'rg' }
412+
413+
if opts.glob then
414+
table.insert(cmd, '-g')
415+
table.insert(cmd, opts.glob)
416+
end
417+
418+
if opts.max_count then
419+
table.insert(cmd, '-m')
420+
table.insert(cmd, tostring(opts.max_count))
421+
end
422+
423+
if opts.max_depth then
424+
table.insert(cmd, '-d')
425+
table.insert(cmd, tostring(opts.max_depth))
426+
end
427+
428+
if opts.no_ignore then
429+
table.insert(cmd, '--no-ignore')
430+
end
431+
432+
if opts.hidden then
433+
table.insert(cmd, '--hidden')
434+
end
435+
436+
table.insert(cmd, '--files')
437+
table.insert(cmd, path)
438+
439+
vim.system(cmd, { text = true }, function(result)
440+
local files = {}
441+
if result and result.code == 0 and result.stdout ~= '' then
442+
files = vim.tbl_filter(function(file)
443+
return file ~= ''
444+
end, vim.split(result.stdout, '\n'))
445+
end
446+
447+
callback(files)
448+
end)
449+
450+
return
451+
end
452+
453+
-- Fall back to scandir if rg is not available or fails
402454
scandir.scan_dir_async(
403455
path,
404456
vim.tbl_deep_extend('force', opts, {
457+
depth = opts.max_depth,
458+
add_dirs = false,
459+
search_pattern = M.glob_to_pattern(opts.glob),
460+
respect_gitignore = not opts.no_ignore,
405461
on_exit = function(files)
406-
if opts.max_files then
407-
files = vim.list_slice(files, 1, opts.max_files)
462+
if opts.max_count then
463+
files = vim.list_slice(files, 1, opts.max_count)
408464
end
409465
callback(files)
410466
end,
@@ -517,24 +573,135 @@ function M.empty(v)
517573
end
518574

519575
--- Convert glob pattern to regex pattern
520-
---@param glob string The glob pattern
521-
---@return string?
522-
function M.glob_to_regex(glob)
523-
if not glob or glob == '' then
524-
return nil
576+
--- https://github.com/davidm/lua-glob-pattern/blob/master/lua/globtopattern.lua
577+
---@param g string The glob pattern
578+
---@return string
579+
function M.glob_to_pattern(g)
580+
local p = '^' -- pattern being built
581+
local i = 0 -- index in g
582+
local c -- char at index i in g.
583+
584+
-- unescape glob char
585+
local function unescape()
586+
if c == '\\' then
587+
i = i + 1
588+
c = g:sub(i, i)
589+
if c == '' then
590+
p = '[^]'
591+
return false
592+
end
593+
end
594+
return true
525595
end
526596

527-
-- Escape regex special chars except * and ?
528-
local pattern = glob:gsub('[%^%$%(%)%%%.%[%]%+%-]', '%%%1')
597+
-- escape pattern char
598+
local function escape(c)
599+
return c:match('^%w$') and c or '%' .. c
600+
end
529601

530-
-- Convert glob to regex pattern
531-
pattern = pattern
532-
:gsub('%*%*/%*', '.*') -- **/* -> .*
533-
:gsub('%*%*', '.*') -- ** -> .*
534-
:gsub('([^%.])%*', '%1[^/]*') -- * -> [^/]* (when not preceded by .)
535-
:gsub('%?', '.') -- ? -> .
602+
-- Convert tokens at end of charset.
603+
local function charset_end()
604+
while 1 do
605+
if c == '' then
606+
p = '[^]'
607+
return false
608+
elseif c == ']' then
609+
p = p .. ']'
610+
break
611+
else
612+
if not unescape() then
613+
break
614+
end
615+
local c1 = c
616+
i = i + 1
617+
c = g:sub(i, i)
618+
if c == '' then
619+
p = '[^]'
620+
return false
621+
elseif c == '-' then
622+
i = i + 1
623+
c = g:sub(i, i)
624+
if c == '' then
625+
p = '[^]'
626+
return false
627+
elseif c == ']' then
628+
p = p .. escape(c1) .. '%-]'
629+
break
630+
else
631+
if not unescape() then
632+
break
633+
end
634+
p = p .. escape(c1) .. '-' .. escape(c)
635+
end
636+
elseif c == ']' then
637+
p = p .. escape(c1) .. ']'
638+
break
639+
else
640+
p = p .. escape(c1)
641+
i = i - 1 -- put back
642+
end
643+
end
644+
i = i + 1
645+
c = g:sub(i, i)
646+
end
647+
return true
648+
end
536649

537-
return pattern .. '$'
650+
-- Convert tokens in charset.
651+
local function charset()
652+
i = i + 1
653+
c = g:sub(i, i)
654+
if c == '' or c == ']' then
655+
p = '[^]'
656+
return false
657+
elseif c == '^' or c == '!' then
658+
i = i + 1
659+
c = g:sub(i, i)
660+
if c == ']' then
661+
-- ignored
662+
else
663+
p = p .. '[^'
664+
if not charset_end() then
665+
return false
666+
end
667+
end
668+
else
669+
p = p .. '['
670+
if not charset_end() then
671+
return false
672+
end
673+
end
674+
return true
675+
end
676+
677+
-- Convert tokens.
678+
while 1 do
679+
i = i + 1
680+
c = g:sub(i, i)
681+
if c == '' then
682+
p = p .. '$'
683+
break
684+
elseif c == '?' then
685+
p = p .. '.'
686+
elseif c == '*' then
687+
p = p .. '.*'
688+
elseif c == '[' then
689+
if not charset() then
690+
break
691+
end
692+
elseif c == '\\' then
693+
i = i + 1
694+
c = g:sub(i, i)
695+
if c == '' then
696+
p = p .. '\\$'
697+
break
698+
end
699+
p = p .. escape(c)
700+
else
701+
p = p .. escape(c)
702+
end
703+
end
704+
return p
538705
end
539706

540707
---@class CopilotChat.Diagnostic

0 commit comments

Comments
 (0)