From 6469fe1d6d264f89eb308ada81ddd92578e10dc4 Mon Sep 17 00:00:00 2001 From: linrongbin16 Date: Thu, 9 May 2024 09:37:44 +0800 Subject: [PATCH] fix(previewer): fix extra new line at bottom of buffer previewer (#692) fix(exec): prioritize executables nvim/fzf for windows (#692) --- .github/workflows/ci.yml | 1 - lua/fzfx/commons/fileio.lua | 8 +- lua/fzfx/commons/logging.lua | 3 +- lua/fzfx/commons/micro-async/init.lua | 287 -------------- lua/fzfx/commons/micro-async/lsp.lua | 59 --- lua/fzfx/commons/micro-async/uv.lua | 125 ------ lua/fzfx/commons/num.lua | 3 +- lua/fzfx/commons/path.lua | 13 +- lua/fzfx/commons/promise/init.lua | 369 ------------------ lua/fzfx/commons/promise/test/helper.lua | 39 -- lua/fzfx/commons/version.txt | 2 +- lua/fzfx/detail/fzf_helpers.lua | 4 +- lua/fzfx/detail/popup/buffer_popup_window.lua | 48 ++- 13 files changed, 55 insertions(+), 906 deletions(-) delete mode 100644 lua/fzfx/commons/micro-async/init.lua delete mode 100644 lua/fzfx/commons/micro-async/lsp.lua delete mode 100644 lua/fzfx/commons/micro-async/uv.lua delete mode 100644 lua/fzfx/commons/promise/init.lua delete mode 100644 lua/fzfx/commons/promise/test/helper.lua diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0a4dd629..ee6fd2da7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,6 @@ jobs: ls -l . echo "run luacov" luacov -r lcov - luacov echo "ls ." ls -l . echo "tail ./luacov.report.out" diff --git a/lua/fzfx/commons/fileio.lua b/lua/fzfx/commons/fileio.lua index fef09e276..3204aa758 100644 --- a/lua/fzfx/commons/fileio.lua +++ b/lua/fzfx/commons/fileio.lua @@ -1,3 +1,5 @@ +local uv = vim.uv or vim.loop + local M = {} -- FileLineReader { @@ -15,7 +17,6 @@ local FileLineReader = {} --- @param batchsize integer? --- @return commons.FileLineReader? function FileLineReader:open(filename, batchsize) - local uv = require("fzfx.commons.uv") local handler = uv.fs_open(filename, "r", 438) --[[@as integer]] if type(handler) ~= "number" then error( @@ -54,7 +55,6 @@ end --- @private --- @return integer function FileLineReader:_read_chunk() - local uv = require("fzfx.commons.uv") local chunksize = (self.filesize >= self.offset + self.batchsize) and self.batchsize or (self.filesize - self.offset) if chunksize <= 0 then @@ -125,7 +125,6 @@ end -- Close the file reader. function FileLineReader:close() - local uv = require("fzfx.commons.uv") if self.handler then uv.fs_close(self.handler) self.handler = nil @@ -201,7 +200,6 @@ end --- @param on_complete fun(data:string?):any --- @param opts {trim:boolean?}? M.asyncreadfile = function(filename, on_complete, opts) - local uv = require("fzfx.commons.uv") opts = opts or { trim = false } opts.trim = type(opts.trim) == "boolean" and opts.trim or false @@ -313,7 +311,6 @@ M.asyncreadlines = function(filename, opts) end end - local uv = require("fzfx.commons.uv") local open_result, open_err = uv.fs_open(filename, "r", 438, function(open_complete_err, fd) if open_complete_err then _handle_error(open_complete_err, "fs_open complete") @@ -442,7 +439,6 @@ end --- @param on_complete fun(bytes:integer?):any callback on write complete. --- 1. `bytes`: written data bytes. M.asyncwritefile = function(filename, content, on_complete) - local uv = require("fzfx.commons.uv") uv.fs_open(filename, "w", 438, function(open_err, fd) if open_err then error( diff --git a/lua/fzfx/commons/logging.lua b/lua/fzfx/commons/logging.lua index ef7fb6d5b..74552ca69 100644 --- a/lua/fzfx/commons/logging.lua +++ b/lua/fzfx/commons/logging.lua @@ -1,4 +1,5 @@ local IS_WINDOWS = vim.fn.has("win32") > 0 or vim.fn.has("win64") > 0 +local uv = vim.uv or vim.loop local M = {} @@ -322,8 +323,6 @@ end function Logger:_log(dbg, lvl, msg) assert(type(lvl) == "number" and LogLevelNames[lvl] ~= nil) - local uv = require("fzfx.commons.uv") - if lvl < self.level then return end diff --git a/lua/fzfx/commons/micro-async/init.lua b/lua/fzfx/commons/micro-async/init.lua deleted file mode 100644 index 6d6562ed3..000000000 --- a/lua/fzfx/commons/micro-async/init.lua +++ /dev/null @@ -1,287 +0,0 @@ ----@diagnostic disable ----@mod micro-async - ----@alias micro-async.SelectOpts { prompt: string?, format_item: nil|fun(item: any): string, kind: string? } - ----@alias micro-async.InputOpts { prompt: string?, default: string?, completion: string?, highlight: fun(text: string) } - ----@class micro-async.Cancellable ----@field cancel fun(self: micro-async.Cancellable) ----@field is_cancelled fun(self: micro-async.Cancellable): boolean - ----@class micro-async.Task: micro-async.Cancellable ----@field thread thread ----@field resume fun(self: micro-async.Task, ...: any):micro-async.Cancellable? - -local yield = coroutine.yield -local resume = coroutine.resume -local running = coroutine.running - ----@type table ----@private -local handles = setmetatable({}, { - __mode = "k", -}) - ----@private -local function is_cancellable(task) - return type(task) == "table" - and vim.is_callable(task.cancel) - and vim.is_callable(task.is_cancelled) -end - ----@param fn fun(...): ... ----@return micro-async.Task ----@private -local function new_task(fn) - local thread = coroutine.create(fn) - local cancelled = false - - local task = {} - local current = nil - - function task:cancel() - if not cancelled then - cancelled = true - if current and not current:is_cancelled() then - current:cancel() - end - end - end - - function task:resume(...) - if not cancelled then - local ok, rv = resume(thread, ...) - if not ok then - self:cancel() - error(rv) - end - if is_cancellable(rv) then - current = rv - end - end - end - - handles[thread] = task - - return task -end - -local Async = {} - ----@text Create a callback function that resumes the current or specified coroutine when called. ---- ----@param co thread | nil The thread to resume, defaults to the running one. ----@return fun(args:...) -function Async.callback(co) - co = co or running() - return function(...) - if co and handles[co] then - handles[co]:resume(...) - end - end -end - ----@text Create a callback function that resumes the current or specified coroutine when called, ----and is wrapped in `vim.schedule` to ensure the API is safe to call. ---- ----@param co thread | nil The thread to resume, defaults to the running one. ----@return fun(args:...) -function Async.scheduled_callback(co) - co = co or running() - return function(...) - handles[co]:resume(...) - end -end - ----@text Create an async function that can be called from a synchronous context. ----Cannot return values as it is non-blocking. ---- ----@param fn fun(...):... ----@return fun(...): micro-async.Task -function Async.void(fn) - local task = new_task(fn) - return function(...) - task:resume(...) - return task - end -end - ----@text Run a function asynchronously and call the callback with the result. ---- ----@param fn fun(...):... ----@param cb fun(...) ----@param ... any ----@return micro-async.Task -function Async.run(fn, cb, ...) - local task = new_task(function(...) - cb(fn(...)) - end) - task:resume(...) - return task -end - ----@text Run an async function syncrhonously and return the result. ----@text WARNING: This will completely block Neovim's main thread! ---- ----@param fn fun(...):... ----@param timeout_ms integer? ----@param ... any ----@return boolean ----@return any ... -function Async.block_on(fn, timeout_ms, ...) - local done, result = false, nil - Async.run(fn, function(...) - result, done = { ... }, true - end, ...) - vim.wait(timeout_ms or 1000, function() - return done - end) - return done, result and unpack(result) -end - ----@text Wrap a callback-style function to be async. Add an additional `callback` parameter at the ----end of function, to yield value on its callback. And the `argc` parameter should be parameters ----count + 1 (with an additional `callback` parameter). ---- ----@param fn fun(...): ...any ----@param argc integer ----@return fun(...): ... -function Async.wrap(fn, argc) - return function(...) - local args = { ... } - args[argc] = Async.callback() - return yield(fn(unpack(args))) - end -end - ----@text Wrap a callback-style function to be async, with the callback wrapped in `vim.schedule_wrap` ----to ensure it is safe to call the nvim API. ---- ----@param fn fun(...): ...any ----@param argc integer ----@return fun(...): ... -function Async.scheduled_wrap(fn, argc) - return function(...) - local args = { ... } - args[argc] = Async.scheduled_callback() - return yield(fn(unpack(args))) - end -end - ----@text Yields to the Neovim scheduler ---- ----@async -function Async.schedule() - return yield(vim.schedule(Async.callback())) -end - ----@text Yields the current task, resuming when the specified timeout has elapsed. ---- ----@async ----@param timeout integer -function Async.defer(timeout) - yield({ - ---@type uv_timer_t - timer = vim.defer_fn(Async.callback(), timeout), - cancel = function(self) - if not self.timer:is_closing() then - if self.timer:is_active() then - self.timer:stop() - end - self.timer:close() - end - end, - is_cancelled = function(self) - return self.timer:is_closing() - end, - }) -end - ----@text Wrapper that creates and queues a work request, yields, and resumes the current task with the results. ---- ----@async ----@param fn fun(...):... ----@param ... ...uv.aliases.threadargs ----@return ...uv.aliases.threadargs -function Async.work(fn, ...) - local uv = require("fzfx.commons.micro-async.uv") - return uv.queue_work(uv.new_work(fn), ...) -end - ----@text Async vim.system ---- ----@async ----@param cmd string[] Command to run ----@param opts table Options to pass to `vim.system` -Async.system = function(cmd, opts) - return yield(vim.system(cmd, opts, Async.callback())) -end - ----@text Join multiple async functions and call the callback with the results. ----@param ... fun():... -function Async.join(...) - local thunks = { ... } - local remaining = #thunks - local results = {} - local wrapped = function() - for i, thunk in ipairs(thunks) do - results[i] = { thunk() } - remaining = remaining - 1 - if remaining == 0 then - return unpack(results) - end - end - end - return wrapped() -end - ----@module "commons.micro-async.lsp" ----@private -Async.lsp = nil - ----@module "commons.micro-async.uv" ----@private -Async.uv = nil - ----@private -Async.ui = {} - ----@async ----@param items any[] ----@param opts micro-async.SelectOpts ----@return any|nil, integer|nil -Async.ui.select = function(items, opts) - vim.ui.select(items, opts, Async.callback()) - - local win = vim.api.nvim_get_current_win() - - local cancelled = false - return yield({ - cancel = function() - vim.api.nvim_win_close(win, true) - cancelled = true - end, - is_cancelled = function() - return cancelled - end, - }) -end - ----@async ----@param opts micro-async.InputOpts ----@return string|nil -Async.ui.input = function(opts) - return yield(vim.ui.input(opts, Async.scheduled_callback())) -end - -setmetatable(Async, { - __index = function(_, k) - local ok, mod = pcall(require, "commons.micro-async." .. k) - if ok then - return mod - end - end, -}) - -return Async diff --git a/lua/fzfx/commons/micro-async/lsp.lua b/lua/fzfx/commons/micro-async/lsp.lua deleted file mode 100644 index 4f7dd4dba..000000000 --- a/lua/fzfx/commons/micro-async/lsp.lua +++ /dev/null @@ -1,59 +0,0 @@ ----@diagnostic disable ----@mod micro-async.lsp - -local wrap = require("fzfx.commons.micro-async").wrap - ----Async wrapper for LSP requests -local lsp = {} - ----Async wrapper around `vim.lsp.buf_request`. ----@type async fun(bufnr: integer, method: string, params: table?): error: lsp.ResponseError?, result: table, context: lsp.HandlerContext, config: table? -lsp.buf_request = wrap(vim.lsp.buf_request, 4) - ----Async wrapper around `vim.lsp.buf_request_all`. ----@type async fun(bufnr: integer, method: string, params: table?): table -lsp.buf_request_all = wrap(vim.lsp.buf_request_all, 4) - -lsp.request = {} - -lsp.request.references = function(buf, params) - return lsp.buf_request(buf, "textDocument/references", params) -end - -lsp.request.definition = function(buf, params) - return lsp.buf_request(buf, "textDocument/definition", params) -end - -lsp.request.type_definition = function(buf, params) - return lsp.buf_request(buf, "textDocument/typeDefinition", params) -end - -lsp.request.implementation = function(buf, params) - return lsp.buf_request(buf, "textDocument/implementation", params) -end - -lsp.request.rename = function(buf, params) - return lsp.buf_request(buf, "textDocument/rename", params) -end - -lsp.request.signature_help = function(buf, params) - return lsp.buf_request(buf, "textDocument/signatureHelp", params) -end - -lsp.request.document_symbols = function(buf, params) - return lsp.buf_request(buf, "textDocument/documentSymbol", params) -end - -lsp.request.hover = function(buf, params) - return lsp.buf_request(buf, "textDocument/hover", params) -end - -lsp.request.inlay_hint = function(buf, params) - return lsp.buf_request(buf, "textDocument/inlayHint", params) -end - -lsp.request.code_actions = function(buf, params) - return lsp.buf_request(buf, "textDocument/codeAction", params) -end - -return lsp diff --git a/lua/fzfx/commons/micro-async/uv.lua b/lua/fzfx/commons/micro-async/uv.lua deleted file mode 100644 index 82749b282..000000000 --- a/lua/fzfx/commons/micro-async/uv.lua +++ /dev/null @@ -1,125 +0,0 @@ ----@diagnostic disable ----@mod micro-async.uv - -local a = require("fzfx.commons.micro-async") - -local yield, callback, scheduled_wrap = a.yield, a.callback, a.scheduled_wrap - -local luv = vim.uv or vim.loop - ----Async vim.uv wrapper -local uv = {} - ----@type async fun(path: string, entries: integer): err: string?, dir: luv_dir_t -uv.fs_opendir = function(path, entries) - ---@diagnostic disable-next-line: param-type-mismatch - return yield(luv.fs_opendir(path, callback(), entries)) -end - ----@type async fun(dir: luv_dir_t): err: string?, entries: uv.aliases.fs_readdir_entries[] -uv.fs_readdir = scheduled_wrap(luv.fs_readdir, 2) - ----@type async fun(path: string): err: string?, path: string? -uv.fs_realpath = scheduled_wrap(luv.fs_realpath, 2) - ----@type async fun(path: string): err: string?, path: string? -uv.fs_readlink = scheduled_wrap(luv.fs_readlink, 2) - ----@type async fun(path: string, mode: integer): err: string?, permissions: boolean? -uv.fs_access = scheduled_wrap(luv.fs_access, 3) - ----@type async fun(fd: integer, size: integer, offset: integer?): err: string?, data: string? -uv.fs_read = scheduled_wrap(luv.fs_read, 4) - ----@type async fun(path: string, new_path: string): err: string?, success: boolean? -uv.fs_rename = scheduled_wrap(luv.fs_rename, 2) - ----@type async fun(path: string, flags: integer | uv.aliases.fs_access_flags, mode: integer): err: string?, fs: integer? -uv.fs_open = scheduled_wrap(luv.fs_open, 4) - ----@type async fun(template: string): err: string?, fd: integer?, path: string? -uv.fs_mkstemp = scheduled_wrap(luv.fs_mkstemp, 2) - ----@type async fun(template: string): err: string?, path: string? -uv.fs_mkdtemp = scheduled_wrap(luv.fs_mkdtemp, 2) - ----@type async fun(path: string): err: string?, success: boolean? -uv.fs_rmdir = scheduled_wrap(luv.fs_rmdir, 2) - ----@type async fun(path: string, mode: integer): err: string?, success: boolean? -uv.fs_mkdir = scheduled_wrap(luv.fs_mkdir, 3) - ----@type async fun(path: string, mode: integer): err: string?, success: boolean? -uv.fs_chmod = scheduled_wrap(luv.fs_chmod, 3) - ----@type async fun(path: string, uid: integer, gid: integer): err: string?, success: boolean? -uv.fs_chown = scheduled_wrap(luv.fs_chown, 4) - ----@type async fun(fd: integer, mode: integer): err: string?, success: boolean? -uv.fs_fchmod = scheduled_wrap(luv.fs_fchmod, 3) - ----@type async fun(fd: integer, uid: integer, gid: integer): err: string?, success: boolean? -uv.fs_fchown = scheduled_wrap(luv.fs_fchown, 4) - ----@type async fun(address: uv.aliases.getsockname_rtn) -uv.getnameinfo = scheduled_wrap(luv.getnameinfo, 2) - ----@type async fun(path: string, new_path: string, flags: uv.aliases.fs_copyfile_flags): err: string?, success: boolean? -uv.fs_copyfile = scheduled_wrap(luv.fs_copyfile, 4) - ----@type async fun(fd: integer, atime: integer, mtime: integer): err: string?, success: boolean? -uv.fs_futime = scheduled_wrap(luv.fs_futime, 4) - ----@type async fun(fd: integer, offset: integer): err: string?, success: boolean? -uv.fs_ftruncate = scheduled_wrap(luv.fs_ftruncate, 3) - ----@type async fun(path: string, new_path: string): err: string?, success: boolean? -uv.fs_link = scheduled_wrap(luv.fs_link, 3) - ----@type async fun(path: string): err: string?, success: boolean? -uv.fs_unlink = scheduled_wrap(luv.fs_unlink, 2) - ----@type async fun(fd: integer, data: string | string[], offset: integer?): err: string?, success: boolean? -uv.fs_write = scheduled_wrap(luv.fs_write, 4) - ----@type async fun(path: string, newpath: string, flags: integer | uv.aliases.fs_symlink_flags): err: string?, success: boolean? -uv.fs_symlink = scheduled_wrap(luv.fs_symlink, 3) - ----@type async fun(path: string, atime: integer, mtime: integer): err: string?, success: boolean? -uv.fs_lutime = scheduled_wrap(luv.fs_lutime, 4) - ----@type async fun(path: string): err: string?, stat: uv.aliases.fs_stat_table -uv.fs_stat = scheduled_wrap(luv.fs_stat, 2) - ----@type async fun(path: string): err: string?, stat: uv_fs_t? -uv.fs_scandir = scheduled_wrap(luv.fs_scandir, 2) - ----@type async fun(path: string): err: string?, stat: uv.aliases.fs_statfs_stats -uv.fs_statfs = scheduled_wrap(luv.fs_statfs, 2) - ----@type async fun(fd: integer): err: string?, success: boolean? -uv.fs_fsync = scheduled_wrap(luv.fs_fsync, 2) - ----@type async fun(out_fd: integer, in_fd: integer, in_offset: integer, size: integer): err: string?, bytes: integer? -uv.fs_sendfile = scheduled_wrap(luv.fs_sendfile, 4) - ----@type async fun(fd: integer): err: string?, success: boolean? -uv.fs_fdatasync = scheduled_wrap(luv.fs_fdatasync, 2) - ----@type async fun(fd: integer): err: string?, success: boolean? -uv.fs_close = scheduled_wrap(luv.fs_close, 2) - ----@type async fun(path: string, atime: integer, mtime: integer): err: string?, success: boolean? -uv.fs_utime = scheduled_wrap(luv.fs_utime, 4) - ----@type async fun(fn: fun(...:uv.aliases.threadargs):...: uv.aliases.threadargs): luv_work_ctx_t -uv.new_work = function(fn) - return luv.new_work(fn, callback()) -end - ----@type async fun(work: luv_work_ctx_t, ...:uv.aliases.threadargs): ...: uv.aliases.threadargs -uv.queue_work = function(work, ...) - return yield(luv.queue_work(work, ...)) -end - -return uv diff --git a/lua/fzfx/commons/num.lua b/lua/fzfx/commons/num.lua index 18befa741..1c076873b 100644 --- a/lua/fzfx/commons/num.lua +++ b/lua/fzfx/commons/num.lua @@ -155,7 +155,8 @@ end --- @param n integer? --- @return number M.random = function(m, n) - local rand_result, rand_err = require("fzfx.commons.uv").random(4) + local uv = vim.uv or vim.loop + local rand_result, rand_err = uv.random(4) assert(rand_result ~= nil, rand_err) local bytes = { diff --git a/lua/fzfx/commons/path.lua b/lua/fzfx/commons/path.lua index 0f79515e9..b641a6dc2 100644 --- a/lua/fzfx/commons/path.lua +++ b/lua/fzfx/commons/path.lua @@ -1,4 +1,5 @@ local IS_WINDOWS = vim.fn.has("win32") > 0 or vim.fn.has("win64") > 0 +local uv = vim.uv or vim.loop local M = {} @@ -8,7 +9,7 @@ M.SEPARATOR = IS_WINDOWS and "\\" or "/" --- @return boolean M.exists = function(p) assert(type(p) == "string") - local result, _ = require("fzfx.commons.uv").fs_lstat(p) + local result, _ = uv.fs_lstat(p) return result ~= nil end @@ -16,7 +17,7 @@ end --- @return boolean M.isfile = function(p) assert(type(p) == "string") - local result, _ = require("fzfx.commons.uv").fs_lstat(p) + local result, _ = uv.fs_lstat(p) -- print( -- string.format( -- "|paths.isfile| p:%s, result:%s\n", @@ -31,7 +32,7 @@ end --- @return boolean M.isdir = function(p) assert(type(p) == "string") - local result, _ = require("fzfx.commons.uv").fs_lstat(p) + local result, _ = uv.fs_lstat(p) -- print( -- string.format( -- "|paths.isdir| p:%s, result:%s\n", @@ -46,7 +47,7 @@ end --- @return boolean M.islink = function(p) assert(type(p) == "string") - local result, _ = require("fzfx.commons.uv").fs_lstat(p) + local result, _ = uv.fs_lstat(p) -- print( -- string.format( -- "|paths.issymlink| p:%s, result:%s\n", @@ -95,7 +96,7 @@ end M.expand = function(p) assert(type(p) == "string") if string.len(p) >= 1 and string.sub(p, 1, 1) == "~" then - return require("fzfx.commons.uv").os_homedir() .. string.sub(p, 2) + return uv.os_homedir() .. string.sub(p, 2) else return p end @@ -108,7 +109,7 @@ M.resolve = function(p) if not M.islink(p) then return p end - local result, _ = require("fzfx.commons.uv").fs_realpath(p) + local result, _ = uv.fs_realpath(p) -- print( -- string.format( -- "|paths.resolve|-4 p:%s, result:%s\n", diff --git a/lua/fzfx/commons/promise/init.lua b/lua/fzfx/commons/promise/init.lua deleted file mode 100644 index 50d527e52..000000000 --- a/lua/fzfx/commons/promise/init.lua +++ /dev/null @@ -1,369 +0,0 @@ ----@diagnostic disable -local vim = vim - -local PackedValue = {} -PackedValue.__index = PackedValue - -function PackedValue.new(...) - local values = vim.F.pack_len(...) - local tbl = { _values = values } - return setmetatable(tbl, PackedValue) -end - -function PackedValue.pcall(self, f) - local ok_and_value = function(ok, ...) - return ok, PackedValue.new(...) - end - return ok_and_value(pcall(f, self:unpack())) -end - -function PackedValue.unpack(self) - return vim.F.unpack_len(self._values) -end - -function PackedValue.first(self) - local first = self:unpack() - return first -end - ---- @class Promise -local Promise = {} -Promise.__index = Promise - -local PromiseStatus = { Pending = "pending", Fulfilled = "fulfilled", Rejected = "rejected" } - -local is_promise = function(v) - return getmetatable(v) == Promise -end - -local new_empty_userdata = function() - return newproxy(true) -end - -local new_pending = function(on_fullfilled, on_rejected) - vim.validate({ - on_fullfilled = { on_fullfilled, "function", true }, - on_rejected = { on_rejected, "function", true }, - }) - local tbl = { - _status = PromiseStatus.Pending, - _queued = {}, - _value = nil, - _on_fullfilled = on_fullfilled, - _on_rejected = on_rejected, - _handled = false, - } - local self = setmetatable(tbl, Promise) - - local userdata = new_empty_userdata() - self._unhandled_detector = setmetatable({ [self] = userdata }, { __mode = "k" }) - getmetatable(userdata).__gc = function() - if self._status ~= PromiseStatus.Rejected or self._handled then - return - end - self._handled = true - vim.schedule(function() - local values = vim.inspect({ self._value:unpack() }, { newline = "", indent = "" }) - error("unhandled promise rejection: " .. values, 0) - end) - end - - return self -end - ---- Equivalents to JavaScript's Promise.new. ---- @param executor fun(resolve:fun(...:any),reject:fun(...:any)) ---- @return Promise -function Promise.new(executor) - vim.validate({ executor = { executor, "function" } }) - - local self = new_pending() - - local resolve = function(...) - local first = ... - if is_promise(first) then - first - :next(function(...) - self:_resolve(...) - end) - :catch(function(...) - self:_reject(...) - end) - return - end - self:_resolve(...) - end - local reject = function(...) - self:_reject(...) - end - executor(resolve, reject) - - return self -end - ---- Returns a fulfilled promise. ---- But if the first argument is promise, returns the promise. ---- @param ... any: one promise or non-promises ---- @return Promise -function Promise.resolve(...) - local first = ... - if is_promise(first) then - return first - end - local value = PackedValue.new(...) - return Promise.new(function(resolve, _) - resolve(value:unpack()) - end) -end - ---- Returns a rejected promise. ---- But if the first argument is promise, returns the promise. ---- @param ... any: one promise or non-promises ---- @return Promise -function Promise.reject(...) - local first = ... - if is_promise(first) then - return first - end - local value = PackedValue.new(...) - return Promise.new(function(_, reject) - reject(value:unpack()) - end) -end - -function Promise._resolve(self, ...) - if self._status == PromiseStatus.Rejected then - return - end - self._status = PromiseStatus.Fulfilled - self._value = PackedValue.new(...) - for _ = 1, #self._queued do - local promise = table.remove(self._queued, 1) - promise:_start_resolve(self._value) - end -end - -function Promise._start_resolve(self, value) - if not self._on_fullfilled then - return vim.schedule(function() - self:_resolve(value:unpack()) - end) - end - local ok, result = value:pcall(self._on_fullfilled) - if not ok then - return vim.schedule(function() - self:_reject(result:unpack()) - end) - end - local first = result:first() - if not is_promise(first) then - return vim.schedule(function() - self:_resolve(result:unpack()) - end) - end - first - :next(function(...) - self:_resolve(...) - end) - :catch(function(...) - self:_reject(...) - end) -end - -function Promise._reject(self, ...) - if self._status == PromiseStatus.Fulfilled then - return - end - self._status = PromiseStatus.Rejected - self._value = PackedValue.new(...) - self._handled = self._handled or #self._queued > 0 - for _ = 1, #self._queued do - local promise = table.remove(self._queued, 1) - promise:_start_reject(self._value) - end -end - -function Promise._start_reject(self, value) - if not self._on_rejected then - return vim.schedule(function() - self:_reject(value:unpack()) - end) - end - local ok, result = value:pcall(self._on_rejected) - local first = result:first() - if ok and not is_promise(first) then - return vim.schedule(function() - self:_resolve(result:unpack()) - end) - end - if not is_promise(first) then - return vim.schedule(function() - self:_reject(result:unpack()) - end) - end - first - :next(function(...) - self:_resolve(...) - end) - :catch(function(...) - self:_reject(...) - end) -end - ---- Equivalents to JavaScript's Promise.then. ---- @param on_fullfilled (fun(...:any):any)?: A callback on fullfilled. ---- @param on_rejected (fun(...:any):any)?: A callback on rejected. ---- @return Promise -function Promise.next(self, on_fullfilled, on_rejected) - vim.validate({ - on_fullfilled = { on_fullfilled, "function", true }, - on_rejected = { on_rejected, "function", true }, - }) - local promise = new_pending(on_fullfilled, on_rejected) - table.insert(self._queued, promise) - vim.schedule(function() - if self._status == PromiseStatus.Fulfilled then - return self:_resolve(self._value:unpack()) - end - if self._status == PromiseStatus.Rejected then - return self:_reject(self._value:unpack()) - end - end) - return promise -end - ---- Equivalents to JavaScript's Promise.catch. ---- @param on_rejected (fun(...:any):any)?: A callback on rejected. ---- @return Promise -function Promise.catch(self, on_rejected) - return self:next(nil, on_rejected) -end - ---- Equivalents to JavaScript's Promise.finally. ---- @param on_finally fun() ---- @return Promise -function Promise.finally(self, on_finally) - vim.validate({ on_finally = { on_finally, "function", true } }) - return self - :next(function(...) - on_finally() - return ... - end) - :catch(function(...) - on_finally() - return Promise.reject(...) - end) -end - ---- Equivalents to JavaScript's Promise.all. ---- Even if multiple value are resolved, results include only the first value. ---- @param list any[]: promise or non-promise values ---- @return Promise -function Promise.all(list) - vim.validate({ list = { list, "table" } }) - return Promise.new(function(resolve, reject) - local remain = #list - if remain == 0 then - return resolve({}) - end - - local results = {} - for i, e in ipairs(list) do - Promise.resolve(e) - :next(function(...) - local first = ... - results[i] = first - if remain == 1 then - return resolve(results) - end - remain = remain - 1 - end) - :catch(function(...) - reject(...) - end) - end - end) -end - ---- Equivalents to JavaScript's Promise.race. ---- @param list any[]: promise or non-promise values ---- @return Promise -function Promise.race(list) - vim.validate({ list = { list, "table" } }) - return Promise.new(function(resolve, reject) - for _, e in ipairs(list) do - Promise.resolve(e) - :next(function(...) - resolve(...) - end) - :catch(function(...) - reject(...) - end) - end - end) -end - ---- Equivalents to JavaScript's Promise.any. ---- Even if multiple value are rejected, errors include only the first value. ---- @param list any[]: promise or non-promise values ---- @return Promise -function Promise.any(list) - vim.validate({ list = { list, "table" } }) - return Promise.new(function(resolve, reject) - local remain = #list - if remain == 0 then - return reject({}) - end - - local errs = {} - for i, e in ipairs(list) do - Promise.resolve(e) - :next(function(...) - resolve(...) - end) - :catch(function(...) - local first = ... - errs[i] = first - if remain == 1 then - return reject(errs) - end - remain = remain - 1 - end) - end - end) -end - ---- Equivalents to JavaScript's Promise.allSettled. ---- Even if multiple value are resolved/rejected, value/reason is only the first value. ---- @param list any[]: promise or non-promise values ---- @return Promise -function Promise.all_settled(list) - vim.validate({ list = { list, "table" } }) - return Promise.new(function(resolve) - local remain = #list - if remain == 0 then - return resolve({}) - end - - local results = {} - for i, e in ipairs(list) do - Promise.resolve(e) - :next(function(...) - local first = ... - results[i] = { status = PromiseStatus.Fulfilled, value = first } - end) - :catch(function(...) - local first = ... - results[i] = { status = PromiseStatus.Rejected, reason = first } - end) - :finally(function() - if remain == 1 then - return resolve(results) - end - remain = remain - 1 - end) - end - end) -end - -return Promise diff --git a/lua/fzfx/commons/promise/test/helper.lua b/lua/fzfx/commons/promise/test/helper.lua deleted file mode 100644 index 7e426a68a..000000000 --- a/lua/fzfx/commons/promise/test/helper.lua +++ /dev/null @@ -1,39 +0,0 @@ -local helper = require("vusted.helper") -local plugin_name = helper.get_module_root(...) - -helper.root = helper.find_plugin_root(plugin_name) - -function helper.before_each() end - -function helper.after_each() - helper.cleanup_loaded_modules(plugin_name) - vim.cmd([[lua collectgarbage("collect")]]) -end - -function helper.on_finished() - local finished = false - return setmetatable({ - wait = function() - local ok = vim.wait(1000, function() - return finished - end, 10, false) - if not ok then - error("wait timeout") - end - end, - }, { - __call = function() - finished = true - end, - }) -end - -function helper.wait(promise) - local on_finished = helper.on_finished() - promise:finally(function() - on_finished() - end) - on_finished:wait() -end - -return helper diff --git a/lua/fzfx/commons/version.txt b/lua/fzfx/commons/version.txt index 7c626aa69..946789e61 100644 --- a/lua/fzfx/commons/version.txt +++ b/lua/fzfx/commons/version.txt @@ -1 +1 @@ -15.0.2 +16.0.0 diff --git a/lua/fzfx/detail/fzf_helpers.lua b/lua/fzfx/detail/fzf_helpers.lua index 624b516e4..a49a42e18 100644 --- a/lua/fzfx/detail/fzf_helpers.lua +++ b/lua/fzfx/detail/fzf_helpers.lua @@ -318,9 +318,9 @@ end --- @return string? local function nvim_exec() local exe_list = {} + table.insert(exe_list, "nvim") table.insert(exe_list, vim.v.argv[1]) table.insert(exe_list, vim.env.VIM) - table.insert(exe_list, "nvim") for _, e in ipairs(exe_list) do if e ~= nil and vim.fn.executable(e) > 0 then return e @@ -333,10 +333,10 @@ end --- @return string? local function fzf_exec() local exe_list = {} + table.insert(exe_list, "fzf") if vim.fn.exists("*fzf#exec") > 0 then table.insert(exe_list, vim.fn["fzf#exec"]()) end - table.insert(exe_list, "fzf") for _, e in ipairs(exe_list) do if e ~= nil and vim.fn.executable(e) > 0 then return e diff --git a/lua/fzfx/detail/popup/buffer_popup_window.lua b/lua/fzfx/detail/popup/buffer_popup_window.lua index ae1da4c82..55786dcc8 100644 --- a/lua/fzfx/detail/popup/buffer_popup_window.lua +++ b/lua/fzfx/detail/popup/buffer_popup_window.lua @@ -514,11 +514,38 @@ function BufferPopupWindow:preview_file(job_id, previewer_result, previewer_labe return end + -- log.debug( + -- string.format( + -- "|BufferPopupWindow:preview_file - asyncreadfile| contents:%s", + -- vim.inspect(contents) + -- ) + -- ) + -- log.debug( + -- string.format( + -- "|BufferPopupWindow:preview_file - asyncreadfile| contents length:%s", + -- vim.inspect(string.len(contents)) + -- ) + -- ) local lines = {} if str.not_empty(contents) then contents = contents:gsub("\r\n", "\n") lines = str.split(contents, "\n") + if str.endswith(contents, "\n") and #lines > 0 and lines[#lines] == "" then + table.remove(lines, #lines) + end end + -- log.debug( + -- string.format( + -- "|BufferPopupWindow:preview_file - asyncreadfile| lines:%s", + -- vim.inspect(lines) + -- ) + -- ) + -- log.debug( + -- string.format( + -- "|BufferPopupWindow:preview_file - asyncreadfile| lines count:%s", + -- vim.inspect(#lines) + -- ) + -- ) table.insert(self.preview_file_contents_queue, { contents = lines, job_id = last_job.job_id, @@ -664,7 +691,7 @@ function BufferPopupWindow:render_file_contents(file_content, content_view, on_c local LAST_LINE = LINES_COUNT local line_index = FIRST_LINE if line_step == nil then - line_step = LARGE_FILE and math.max(math.ceil(math.sqrt(LINES_COUNT)), 5) or 5 + line_step = LARGE_FILE and math.max(math.ceil(math.sqrt(LINES_COUNT)), 10) or 10 end -- log.debug( -- string.format( @@ -713,14 +740,19 @@ function BufferPopupWindow:render_file_contents(file_content, content_view, on_c end local set_start = line_index - 1 - local set_end = math.min(line_index + line_step, LAST_LINE) - 1 + local set_end = math.min(line_index + line_step - 1, LAST_LINE) -- log.debug( - -- "|BufferPopupWindow:render_file_contents - set_buf_lines| line_index:%s, set start:%s, end:%s, TOP_LINE/BOTTOM_LINE:%s/%s", - -- vim.inspect(line_index), - -- vim.inspect(set_start), - -- vim.inspect(set_end), - -- vim.inspect(TOP_LINE), - -- vim.inspect(BOTTOM_LINE) + -- string.format( + -- "|BufferPopupWindow:render_file_contents - set_buf_lines| previewer_label_result:%s line_index:%s, line_step:%s set start:%s, set_end:%s, FIRST_LINE/LAST_LINE:%s/%s, LINES_COUNT:%s", + -- vim.inspect(file_content.previewer_label_result), + -- vim.inspect(line_index), + -- vim.inspect(line_step), + -- vim.inspect(set_start), + -- vim.inspect(set_end), + -- vim.inspect(FIRST_LINE), + -- vim.inspect(LAST_LINE), + -- vim.inspect(LINES_COUNT) + -- ) -- ) vim.api.nvim_buf_set_lines(self.previewer_bufnr, set_start, set_end, false, buf_lines) if hi_line then