summaryrefslogtreecommitdiff
path: root/lua/telescope/_.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/telescope/_.lua')
-rw-r--r--lua/telescope/_.lua296
1 files changed, 296 insertions, 0 deletions
diff --git a/lua/telescope/_.lua b/lua/telescope/_.lua
new file mode 100644
index 0000000..a325c7d
--- /dev/null
+++ b/lua/telescope/_.lua
@@ -0,0 +1,296 @@
+local uv = vim.loop
+
+local Object = require "plenary.class"
+local log = require "plenary.log"
+
+local async = require "plenary.async"
+local channel = require("plenary.async").control.channel
+
+local M = {}
+
+local AsyncJob = {}
+AsyncJob.__index = AsyncJob
+
+function AsyncJob.new(opts)
+ local self = setmetatable({}, AsyncJob)
+
+ self.command, self.uv_opts = M.convert_opts(opts)
+
+ self.stdin = opts.stdin or M.NullPipe()
+ self.stdout = opts.stdout or M.NullPipe()
+ self.stderr = opts.stderr or M.NullPipe()
+
+ if opts.cwd then
+ -- TODO: not vim.fn
+ self.uv_opts.cwd = vim.fn.expand(opts.cwd)
+ end
+
+ self.uv_opts.stdio = {
+ self.stdin.handle,
+ self.stdout.handle,
+ self.stderr.handle,
+ }
+
+ return self
+end
+
+function AsyncJob:_for_each_pipe(f, ...)
+ for _, pipe in ipairs { self.stdin, self.stdout, self.stderr } do
+ f(pipe, ...)
+ end
+end
+
+function AsyncJob:close(force)
+ if force == nil then
+ force = true
+ end
+
+ self:_for_each_pipe(function(p)
+ p:close(force)
+ end)
+ if not self.handle:is_closing() then
+ self.handle:close()
+ end
+
+ log.debug "[async_job] closed"
+end
+
+M.spawn = function(opts)
+ local self = AsyncJob.new(opts)
+
+ self.handle = uv.spawn(
+ self.command,
+ self.uv_opts,
+ async.void(function()
+ self:close(false)
+ end)
+ )
+
+ return self
+end
+
+---@class uv_pipe_t
+--- A pipe handle from libuv
+---@field read_start function: Start reading
+---@field read_stop function: Stop reading
+---@field close function: Close the handle
+---@field is_closing function: Whether handle is currently closing
+---@field is_active function: Whether the handle is currently reading
+
+---@class BasePipe
+---@field super Object: Always available
+---@field handle uv_pipe_t: A pipe handle
+---@field extend function: Extend
+local BasePipe = Object:extend()
+
+function BasePipe:new()
+ self.eof_tx, self.eof_rx = channel.oneshot()
+end
+
+function BasePipe:close(force)
+ if force == nil then
+ force = true
+ end
+
+ assert(self.handle, "Must have a pipe to close. Otherwise it's weird!")
+
+ if self.handle:is_closing() then
+ return
+ end
+
+ -- If we're not forcing the stop, allow waiting for eof
+ -- This ensures that we don't end up with weird race conditions
+ if not force then
+ self.eof_rx()
+ end
+
+ self.handle:read_stop()
+ if not self.handle:is_closing() then
+ self.handle:close()
+ end
+
+ self._closed = true
+end
+
+---@class LinesPipe : BasePipe
+local LinesPipe = BasePipe:extend()
+
+function LinesPipe:new()
+ LinesPipe.super.new(self)
+ self.handle = uv.new_pipe(false)
+end
+
+function LinesPipe:read()
+ local read_tx, read_rx = channel.oneshot()
+
+ self.handle:read_start(function(err, data)
+ assert(not err, err)
+ self.handle:read_stop()
+
+ read_tx(data)
+ if data == nil then
+ self.eof_tx()
+ end
+ end)
+
+ return read_rx()
+end
+
+function LinesPipe:iter(schedule)
+ if schedule == nil then
+ schedule = true
+ end
+
+ local text = nil
+ local index = nil
+
+ local get_next_text = function(previous)
+ index = nil
+
+ local read = self:read()
+ if previous == nil and read == nil then
+ return
+ end
+
+ return (previous or "") .. (read or "")
+ end
+
+ local next_value = nil
+ next_value = function()
+ if schedule then
+ async.util.scheduler()
+ end
+
+ if text == nil or (text == "" and index == nil) then
+ return nil
+ end
+
+ local start = index
+ index = string.find(text, "\n", index, true)
+
+ if index == nil then
+ text = get_next_text(string.sub(text, start or 1))
+ return next_value()
+ end
+
+ index = index + 1
+
+ return string.sub(text, start or 1, index - 2)
+ end
+
+ text = get_next_text()
+
+ return function()
+ return next_value()
+ end
+end
+
+---@class NullPipe : BasePipe
+local NullPipe = BasePipe:extend()
+
+function NullPipe:new()
+ NullPipe.super.new(self)
+ self.start = function() end
+ self.read_start = function() end
+ self.close = function() end
+
+ -- This always has eof tx done, so can just call it now
+ self.eof_tx()
+end
+
+---@class ChunkPipe : BasePipe
+local ChunkPipe = BasePipe:extend()
+
+function ChunkPipe:new()
+ ChunkPipe.super.new(self)
+ self.handle = uv.new_pipe(false)
+end
+
+function ChunkPipe:read()
+ local read_tx, read_rx = channel.oneshot()
+
+ self.handle:read_start(function(err, data)
+ assert(not err, err)
+ self.handle:read_stop()
+
+ read_tx(data)
+ if data == nil then
+ self.eof_tx()
+ end
+ end)
+
+ return read_rx()
+end
+
+function ChunkPipe:iter()
+ return function()
+ if self._closed then
+ return nil
+ end
+
+ return self:read()
+ end
+end
+
+---@class ErrorPipe : BasePipe
+local ErrorPipe = BasePipe:extend()
+
+function ErrorPipe:new()
+ ErrorPipe.super.new(self)
+ self.handle = uv.new_pipe(false)
+end
+
+function ErrorPipe:start()
+ self.handle:read_start(function(err, data)
+ if not err and not data then
+ return
+ end
+
+ self.handle:read_stop()
+ self.handle:close()
+
+ error(string.format("Err: %s, Data: '%s'", err, data))
+ end)
+end
+
+M.NullPipe = NullPipe
+M.LinesPipe = LinesPipe
+M.ChunkPipe = ChunkPipe
+M.ErrorPipe = ErrorPipe
+
+M.convert_opts = function(o)
+ if not o then
+ error(debug.traceback "Options are required for Job:new")
+ end
+
+ local command = o.command
+ if not command then
+ if o[1] then
+ command = o[1]
+ else
+ error(debug.traceback "'command' is required for Job:new")
+ end
+ elseif o[1] then
+ error(debug.traceback "Cannot pass both 'command' and array args")
+ end
+
+ local args = o.args
+ if not args then
+ if #o > 1 then
+ args = { select(2, unpack(o)) }
+ end
+ end
+
+ local ok, is_exe = pcall(vim.fn.executable, command)
+ if not o.skip_validation and ok and 1 ~= is_exe then
+ error(debug.traceback(command .. ": Executable not found"))
+ end
+
+ local obj = {}
+
+ obj.args = args
+
+ return command, obj
+end
+
+return M