Skip to content

Commit

Permalink
feat: diff mode with -d flag (#63)
Browse files Browse the repository at this point in the history
* refactor: allow smart open to be used as utility fn
* refactor: use dfs / stack instead of queue in smart open
  • Loading branch information
willothy committed Aug 10, 2023
1 parent 09f6cc7 commit 667989a
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 42 deletions.
37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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!

Expand Down Expand Up @@ -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.
Expand All @@ -93,6 +96,20 @@ nvim +<cmd>
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
Expand All @@ -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,
Expand All @@ -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)
Expand Down
99 changes: 70 additions & 29 deletions lua/flatten/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 })
Expand Down
2 changes: 1 addition & 1 deletion lua/flatten/guest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
11 changes: 8 additions & 3 deletions lua/flatten/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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,
Expand All @@ -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",
},
Expand Down

0 comments on commit 667989a

Please sign in to comment.