Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use vim.uv.spawn instead of vim.fn.jobstart #53

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions doc/cmp-rg.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ additional_arguments *cmp-rg-additional_arguments*

Any additional arguments you want to pass to ripgrep.

Default: "" ~
Default: {} ~

Example: >

{ name = 'rg', option = { additional_arguments = "--smart-case" } }
{ name = 'rg', option = { additional_arguments = { "--smart-case" } } }
<
Search in hidden files (starting with a `.`): >

{ name = 'rg', option = { additional_arguments = "--hidden" } }
{ name = 'rg', option = { additional_arguments = { "--hidden" } } }
<

Reduce the level of recursion into directories: >

{ name = 'rg', option = { additional_arguments = "--max-depth 4" } }
{ name = 'rg', option = { additional_arguments = { "--max-depth", "4" } } }
<

------------------------------------------------------------------------------
Expand Down
83 changes: 42 additions & 41 deletions lua/cmp-rg/init.lua
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
require "cmp-rg.types"

local Process = require "cmp-rg.process"
local uv = vim.uv or vim.loop

local function show_error(msg)
vim.notify("[cmp-rg] " .. msg, vim.log.levels.ERROR)
end

---@class Source
---@field public running_job_id number
---@field public json_decode fun(s: string): rg.Message
---@field public process table
---@field public timer any
local source = {}

source.new = function()
local timer = vim.loop.new_timer()
local timer = uv.new_timer()
vim.api.nvim_create_autocmd("VimLeavePre", {
callback = function()
if timer and not timer:is_closing() then
Expand All @@ -17,27 +23,25 @@ source.new = function()
end,
})
return setmetatable({
running_job_id = 0,
timer = timer,
json_decode = vim.fn.has "nvim-0.6" == 1 and vim.json.decode or vim.fn.json_decode,
}, { __index = source })
end

source.complete = function(self, request, callback)
local q = string.sub(request.context.cursor_before_line, request.offset)
local pattern = request.option.pattern or "[\\w_-]+"
local additional_arguments = request.option.additional_arguments or ""
local additional_arguments = request.option.additional_arguments or {}
if type(additional_arguments) == "string" then
show_error('Now additional_arguments must be an array of string')
additional_arguments = {}
end
local context_before = request.option.context_before or 1
local context_after = request.option.context_after or 3
local quote = "'"
if vim.o.shell == "cmd.exe" then
quote = '"'
end
local seen = {}
local items = {}
local chunk_size = 5

local function on_event(_, data, event)
local function on_event(data, event)
if event == "stdout" then
---@type (string|rg.Message)[]
local messages = data
Expand All @@ -56,7 +60,7 @@ source.complete = function(self, request, callback)
return nil
end
if type(m) == "string" then
local ok, decoded = pcall(self.json_decode, m)
local ok, decoded = pcall(vim.json.decode, m)
if not ok then
return nil
end
Expand Down Expand Up @@ -123,7 +127,7 @@ source.complete = function(self, request, callback)
end

if request.max_item_count ~= nil and #items >= request.max_item_count then
vim.fn.jobstop(self.running_job_id)
self.process:kill()
callback { items = items, isIncomplete = false }
return
end
Expand All @@ -135,9 +139,7 @@ source.complete = function(self, request, callback)
end

if event == "stderr" and request.option.debug then
vim.cmd "echohl Error"
vim.cmd('echomsg "' .. table.concat(data, "") .. '"')
vim.cmd "echohl None"
show_error(table.concat(data, ""))
end

if event == "exit" then
Expand All @@ -146,31 +148,30 @@ source.complete = function(self, request, callback)
end

self.timer:stop()
self.timer:start(
request.option.debounce or 100,
0,
vim.schedule_wrap(function()
vim.fn.jobstop(self.running_job_id)
self.running_job_id = vim.fn.jobstart(
string.format(
"rg --heading --json --word-regexp -B %d -A %d --color never %s %s%s%s%s .",
context_before,
context_after,
additional_arguments,
quote,
q,
pattern,
quote
),
{
on_stderr = on_event,
on_stdout = on_event,
on_exit = on_event,
cwd = request.option.cwd or vim.fn.getcwd(),
}
)
end)
)
self.timer:start(request.option.debounce or 100, 0, function()
if self.process then
self.process:kill()
end
local args = {
"--heading",
"--json",
"--word-regexp",
"-B",
tostring(context_before),
"-A",
tostring(context_after),
"--color",
"never",
}
if #additional_arguments > 0 then
for _, v in ipairs(additional_arguments) do
table.insert(args, v)
end
end
table.insert(args,q .. pattern)
table.insert(args,".")
self.process = Process.new("rg", args, on_event, { cwd = request.option.cwd }):run()
end)
end

return source
112 changes: 112 additions & 0 deletions lua/cmp-rg/process.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
local uv = vim.uv or vim.loop
local debug = require "cmp.utils.debug"

---@alias rg.Callback fun(data: string[], event: "stdout"|"stderr"|"exit"): nil

---@class rg.Pipe
---@field pipe uv_pipe_t
---@field private event string
---@field private callback rg.Callback
---@field private is_closed boolean
---@field private tmp_out string
local Pipe = {}

---@param event string
---@param callback rg.Callback
---@return rg.Pipe
Pipe.new = function(event, callback)
return setmetatable({ event = event, callback = callback, pipe = uv.new_pipe(), tmp_out = "" }, { __index = Pipe })
end

function Pipe:close()
if not self.is_closed then
self.pipe:close()
self.is_closed = true
end
end

function Pipe:read_start()
self.pipe:read_start(function(err, chunk)
assert(not err, err)
if not chunk then
return
end
self.tmp_out = self.tmp_out .. chunk:gsub("\r\n", "\n")
local lines = vim.split(self.tmp_out, "\n", { plain = true })
if #lines > 1 then
self.tmp_out = lines[#lines]
local data = {}
for i = 1, #lines - 1 do
data[i] = lines[i]
end
self.callback(data, self.event)
end
end)
end

---@class rg.Process
---@field callback rg.Callback
---@field private handle uv_process_t?
---@field private cmd string
---@field private args string[]
---@field private stdout rg.Pipe
---@field private stderr rg.Pipe
---@field private cwd string
local Process = {}

---@param cmd string
---@param args string[]
---@param callback rg.Callback
---@param options { cwd: string }?
---@return rg.Process
Process.new = function(cmd, args, callback, options)
options = vim.tbl_extend("force", { cwd = uv.cwd() }, options or {})
return setmetatable({
cmd = cmd,
args = args,
callback = callback,
stdout = Pipe.new("stdout", callback),
stderr = Pipe.new("stderr", callback),
cwd = options.cwd,
}, { __index = Process })
end

---@return rg.Process
function Process:run()
local err
self.handle, err = uv.spawn(self.cmd, {
args = self.args,
cwd = self.cwd,
stdio = { nil, self.stdout.pipe, self.stderr.pipe },
}, function(_, _)
self:pipe_close()
if self.handle and not self.handle:is_closing() then
self.handle:close()
end
self.callback({}, "exit")
end)
if not self.handle then
self:pipe_close()
debug.log("rg", "process cannot spawn", { cmd = self.cmd, args = self.args, err = err })
else
self.stdout:read_start()
self.stderr:read_start()
end
return self
end

function Process:kill()
if self.handle then
self:pipe_close()
if not self.handle:is_closing() then
self.handle:close()
end
end
end

function Process:pipe_close()
self.stdout:close()
self.stderr:close()
end

return Process