From 667989afb7a07e54d5ce11cb43197679ea0dbefa Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Wed, 9 Aug 2023 20:18:04 -0700 Subject: [PATCH] feat: diff mode with -d flag (#63) * refactor: allow smart open to be used as utility fn * refactor: use dfs / stack instead of queue in smart open --- README.md | 37 ++++++++++++---- lua/flatten/core.lua | 99 ++++++++++++++++++++++++++++++------------- lua/flatten/guest.lua | 2 +- lua/flatten/init.lua | 11 +++-- 4 files changed, 107 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 08913be..c25f542 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Flatten allows you to open files from a neovim terminal buffer in your current n - [x] Pipe from terminal into a new Neovim buffer ([demo](https://user-images.githubusercontent.com/38540736/225779817-ed7efea8-9108-4f28-983f-1a889d32826f.mp4)) - [x] Setting to force blocking from the commandline, regardless of filetype - [x] Command passthrough from guest to host +- [x] Flatten instances from wezterm and kitty tabs/panes based on working directory ## Plans and Ideas @@ -20,7 +21,6 @@ Ideas: - [ ] Multi-screen support - [ ] Move buffers between Neovim instances in separate windows - [ ] Single cursor between Neovim instances in separate windows -- [ ] Flatten instances based on working directory If you have an idea or feature request, open an issue with the `enhancement` tag! @@ -71,6 +71,9 @@ nvim file1 file2 # force blocking for a file nvim --cmd 'let g:flatten_wait=1' file1 +# open files in diff mode +nvim -d file1 file2 + # enable blocking for $VISUAL # allows edit-exec # in your .bashrc, .zshrc, etc. @@ -93,6 +96,20 @@ nvim + Flatten comes with the following defaults: ```lua +---Types: +-- +-- Passed to callbacks that handle opening files +---@alias BufInfo { fname: string, bufnr: buffer } +-- +-- The first argument is a list of BufInfo tables representing the newly opened files. +-- The third argument is a single BufInfo table, only provided when a buffer is created from stdin. +-- +-- IMPORTANT: For `block_for` to work, you need to return a buffer number OR a buffer number and a window number. +-- The `winnr` return value is not required, `vim.fn.bufwinid(bufnr)` is used if it is not provided. +-- The `filetype` of this buffer will determine whether block should happen or not. +-- +---@alias OpenHandler fun(files: BufInfo[], argv: string[], stdin_buf: BufInfo, guest_cwd: string):window, buffer +-- { callbacks = { ---@param argv table a list of all the arguments in the nested session @@ -103,7 +120,7 @@ Flatten comes with the following defaults: pre_open = function() end, -- Called after a file is opened -- Passed the buf id, win id, and filetype of the new window - post_open = function(bufnr, winnr, filetype) end, + post_open = function(bufnr, winnr, filetype, is_blocking, is_diff) end, -- Called when a file is open in blocking mode, after it's done blocking -- (after bufdelete, bufunload, or quitpre for the blocking buffer) block_end = function() end, @@ -125,14 +142,16 @@ Flatten comes with the following defaults: -- split -> open in split -- vsplit -> open in vsplit -- smart -> smart open (avoids special buffers) - -- function(new_file_names, argv, stdin_buf_id, guest_cwd) -> bufnr, winnr? - -- Only open the files, allowing you to handle window opening yourself. - -- The first argument is an array of file names representing the newly opened files. - -- The third argument is only provided when a buffer is created from stdin. - -- IMPORTANT: For `block_for` to work, you need to return a buffer number OR a buffer number and a window number. - -- The `winnr` return value is not required, `vim.fn.bufwinid(bufnr)` is used if it is not provided. - -- The `filetype` of this buffer will determine whether block should happen or not. + -- OpenHandler -> allows you to handle file opening yourself (see Types) + -- open = "current", + -- Options: + -- vsplit -> opens files in diff vsplits + -- split -> opens files in diff splits + -- tab_vsplit -> creates a new tabpage, and opens diff vsplits + -- tab_split -> creates a new tabpage, and opens diff splits + -- OpenHandler -> allows you to handle file opening yourself (see Types) + diff = "tab_vsplit", -- Affects which file gets focused when opening multiple at once -- Options: -- "first" -> open first file of new files (default) diff --git a/lua/flatten/core.lua b/lua/flatten/core.lua index 704a633..dd6f326 100644 --- a/lua/flatten/core.lua +++ b/lua/flatten/core.lua @@ -26,53 +26,52 @@ end ---@param focus Flatten.BufInfo function M.smart_open(focus) - local bufnr = focus.bufnr - local curwin = vim.api.nvim_get_current_win() - local available_wins = vim - .iter(vim.api.nvim_list_wins()) - :filter(function(win) - if win == curwin then - return false - end - if vim.api.nvim_win_get_config(win).zindex ~= nil then - return false - end - local winbuf = vim.api.nvim_win_get_buf(win) - return vim.bo[winbuf].buftype == "" and vim.bo[winbuf].buflisted - end) - :fold({}, function(set, win) - set[win] = true - return set - end) + -- set of valid target windows + local valid_targets = {} + for _, win in ipairs(vim.api.nvim_list_wins()) do + local win_buf = vim.api.nvim_win_get_buf(win) + if + win ~= curwin + and vim.api.nvim_win_get_config(win).zindex == nil + and vim.bo[win_buf].buftype == "" + then + valid_targets[win] = true + end + end local layout = vim.fn.winlayout() -- traverse the window tree to find the first available window - local queue = { layout } + local stack = { layout } local win - while #queue > 0 do - local node = table.remove(queue, 1) + while #stack > 0 do + local node = table.remove(stack) if node[1] == "leaf" then - if available_wins[node[2]] then + if valid_targets[node[2]] then win = node[2] break end else - for _, child in ipairs(node[2]) do - table.insert(queue, child) + for i = #node[2], 1, -1 do + table.insert(stack, node[2][i]) end end end + -- allows using this function as a utility to get a window to open something in + if not focus then + return win + end + if win then - vim.api.nvim_win_set_buf(win, bufnr) + vim.api.nvim_win_set_buf(win, focus.bufnr) vim.api.nvim_set_current_win(win) else vim.cmd("split") - vim.api.nvim_win_set_buf(0, bufnr) + vim.api.nvim_win_set_buf(0, focus.bufnr) end end @@ -175,8 +174,50 @@ M.edit_files = function(opts) local winnr local bufnr - -- Open window - if type(open) == "function" then + local is_diff = vim.tbl_contains(argv, "-d") + + if is_diff then + local diff_open = config.window.diff + if type(diff_open) == "function" then + winnr, bufnr = config.window.diff(files, argv, stdin_buf, guest_cwd) + else + winnr = M.smart_open() + vim.api.nvim_set_current_win(winnr) + + if stdin_buf then + files = vim.list_extend({ stdin_buf }, files) + end + local tab = false + local vert = false + + if diff_open == "tab_split" or diff_open == "tab_vsplit" then + tab = true + end + if diff_open == "vsplit" or diff_open == "tab_vsplit" then + vert = true + end + + for i, file in ipairs(files) do + if i == 1 then + if tab then + vim.cmd.tabedit(file.fname) + else + vim.api.nvim_set_current_buf(file.bufnr) + end + else + if vert then + vim.cmd.vsplit(file.fname) + else + vim.cmd.split(file.fname) + end + end + vim.cmd.diffthis() + end + end + + winnr = winnr or vim.api.nvim_get_current_win() + bufnr = bufnr or vim.api.nvim_get_current_buf() + elseif type(open) == "function" then bufnr, winnr = open(files, argv, stdin_buf, guest_cwd) if winnr == nil and bufnr ~= nil then winnr = vim.fn.bufwinid(bufnr) @@ -225,7 +266,7 @@ M.edit_files = function(opts) end end - callbacks.post_open(bufnr, winnr, ft, block) + callbacks.post_open(bufnr, winnr, ft, block, is_diff) if block then M.augroup = vim.api.nvim_create_augroup("flatten_notify", { clear = true }) diff --git a/lua/flatten/guest.lua b/lua/flatten/guest.lua index 0d67dab..0b76e48 100644 --- a/lua/flatten/guest.lua +++ b/lua/flatten/guest.lua @@ -108,7 +108,7 @@ M.init = function(host_pipe) return result end files = filter_map(vim.api.nvim_list_bufs(), function(buffer) - if not vim.api.nvim_buf_is_loaded(buffer) then + if not vim.api.nvim_buf_is_valid(buffer) then return end local buftype = vim.api.nvim_buf_get_option(buffer, "buftype") diff --git a/lua/flatten/init.lua b/lua/flatten/init.lua index 31d6e2a..0c2d90e 100644 --- a/lua/flatten/init.lua +++ b/lua/flatten/init.lua @@ -81,6 +81,9 @@ function M.is_guest() return is_guest end +---@alias Flatten.BufInfo { fname: string, bufnr: buffer } +---@alias Flatten.OpenHandler fun(files: Flatten.BufInfo[], argv: string[], stdin_buf: Flatten.BufInfo, guest_cwd: string):window, buffer + -- selene: allow(unused_variable) M.config = { callbacks = { @@ -100,7 +103,8 @@ M.config = { ---@param winnr window ---@param filetype string ---@param is_blocking boolean - post_open = function(bufnr, winnr, filetype, is_blocking) end, + ---@param is_diff boolean + post_open = function(bufnr, winnr, filetype, is_blocking, is_diff) end, ---Called when a nested session is done waiting for the host. ---@param filetype string block_end = function(filetype) end, @@ -111,9 +115,10 @@ M.config = { gitrebase = true, }, window = { - ---@alias Flatten.BufInfo { fname: string, bufnr: buffer } - ---@type "current" | "alternate" | "split" | "vsplit" | "tab" | "smart" | fun(files: Flatten.BufInfo[], arv: string[], stdin_buf: Flatten.BufInfo, guest_cwd: string):window, buffer + ---@type "current" | "alternate" | "split" | "vsplit" | "tab" | "smart" | Flatten.OpenHandler open = "current", + ---@type "split" | "vsplit" | "tab_split" | "tab_vsplit" | Flatten.OpenHandler + diff = "tab_vsplit", ---@type "first" | "last" focus = "first", },