Skip to content

Commit

Permalink
feat: auto-handle hiding/showing the images on tmux window switch
Browse files Browse the repository at this point in the history
  • Loading branch information
3rd committed Oct 12, 2023
1 parent 0408305 commit 3fbe47d
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 32 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ require("image").setup({
})
```

## Tmux

- You must set: `set -gq allow-passthrough on`
- If you want the images to be automatically hidden/shown when you switch windows, set: `set -g visual-activity off`

### Try it out with a minimal setup

Download [minimal-setup.lua](./minimal-setup.lua) from the root of this repository and run the demo with:
Expand Down
37 changes: 21 additions & 16 deletions lua/image/backends/kitty/helpers.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
local utils = require("image/utils")
local codes = require("image/backends/kitty/codes")

local stdout = vim.loop.new_tty(1, false)
if not stdout then error("failed to open stdout") end
local is_tmux = vim.env.TMUX ~= nil

-- https://github.com/edluffy/hologram.nvim/blob/main/lua/hologram/terminal.lua#L77
local get_chunked = function(str)
local chunks = {}
Expand All @@ -15,16 +11,25 @@ local get_chunked = function(str)
return chunks
end

local encode = function(data)
if is_tmux then return "\x1bPtmux;" .. data:gsub("\x1b", "\x1b\x1b") .. "\x1b\\" end
return data
end

local write = function(data)
---@param data string
---@param tty? string
---@param escape? boolean
local write = function(data, tty, escape)
if data == "" then return end
-- utils.debug("write:", vim.inspect(data))
stdout:write(data)
-- vim.fn.chansend(vim.v.stderr, data)
vim.fn.chansend(vim.v.stderr, data)

local payload = data
if escape and utils.tmux.is_tmux then payload = utils.tmux.escape(data) end
-- utils.debug("write:", vim.inspect(payload), tty)

if tty then
local handle = io.open(tty, "w")
if not handle then error("failed to open tty") end
handle:write(payload)
handle:close()
else
vim.fn.chansend(vim.v.stderr, payload)
end
end

local move_cursor = function(x, y, save)
Expand Down Expand Up @@ -56,7 +61,7 @@ local write_graphics = function(config, data)
for k, v in pairs(config) do
if v ~= nil then
local key = codes.control.keys[k]
control_payload = control_payload .. key .. "=" .. v .. ","
if key then control_payload = control_payload .. key .. "=" .. v .. "," end
end
end
control_payload = control_payload:sub(0, -2)
Expand All @@ -65,7 +70,7 @@ local write_graphics = function(config, data)
if config.transmit_medium ~= codes.control.transmit_medium.direct then data = utils.base64.encode(data) end
local chunks = get_chunked(data)
for i = 1, #chunks do
write(encode("\x1b_G" .. control_payload .. ";" .. chunks[i] .. "\x1b\\"))
write("\x1b_G" .. control_payload .. ";" .. chunks[i] .. "\x1b\\", config.tty, true)
if i == #chunks - 1 then
control_payload = "m=0"
else
Expand All @@ -74,7 +79,7 @@ local write_graphics = function(config, data)
end
else
-- utils.debug("kitty control payload:", control_payload)
write(encode("\x1b_G" .. control_payload .. "\x1b\\"))
write("\x1b_G" .. control_payload .. "\x1b\\", config.tty, true)
end
end

Expand Down
22 changes: 14 additions & 8 deletions lua/image/backends/kitty/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ local utils = require("image/utils")
local codes = require("image/backends/kitty/codes")
local helpers = require("image/backends/kitty/helpers")

local is_tmux = vim.env.TMUX ~= nil
local tmux_has_passthrough = false

if is_tmux then
local ok, result = pcall(vim.fn.system, "tmux show -Apv allow-passthrough")
if ok and result == "on\n" then tmux_has_passthrough = true end
end
local editor_tty = utils.term.get_tty()

---@type Backend
---@diagnostic disable-next-line: missing-fields
Expand All @@ -24,7 +18,7 @@ local backend = {
local transmitted_images = {}
backend.setup = function(state)
backend.state = state
if is_tmux and not tmux_has_passthrough then
if utils.tmux.is_tmux and not utils.tmux.has_passthrough then
utils.throw("tmux does not have allow-passthrough enabled")
return
end
Expand Down Expand Up @@ -159,16 +153,26 @@ backend.render = function(image, x, y, width, height)
-- utils.debug("path:", image.cropped_path, display_payload)
end

local get_clear_tty_override = function()
if not utils.tmux.is_tmux then return nil end
local current_tmux_tty = utils.tmux.get_pane_tty()
if current_tmux_tty == editor_tty then return nil end
return current_tmux_tty
end

backend.clear = function(image_id, shallow)
-- one
if image_id then
local image = backend.state.images[image_id]
if not image then return end
if not image.is_rendered then return end

helpers.write_graphics({
action = codes.control.action.delete,
display_delete = "i",
image_id = image.internal_id,
quiet = 2,
tty = get_clear_tty_override(),
})
image.is_rendered = false
if not shallow then
Expand All @@ -184,7 +188,9 @@ backend.clear = function(image_id, shallow)
action = codes.control.action.delete,
display_delete = "a",
quiet = 2,
tty = get_clear_tty_override(),
})

-- utils.debug("[kitty] cleared all")
for id, image in pairs(backend.state.images) do
image.is_rendered = false
Expand Down
28 changes: 20 additions & 8 deletions lua/image/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -214,31 +214,43 @@ api.setup = function(options)
})

-- auto-toggle on editor focus change
if state.options.editor_only_render_when_focused then
if state.options.editor_only_render_when_focused or utils.tmux.is_tmux then
local images_to_restore_on_focus = {}
local initial_tmux_window_id = utils.is_tmux and utils.tmux.get_window_id() or nil

vim.api.nvim_create_autocmd("FocusLost", {
group = group,
callback = function() -- auto-clear images when windows and buffers change
vim.schedule(function()
local images = api.get_images()
for _, current_image in ipairs(images) do
if current_image.is_rendered then
table.insert(images_to_restore_on_focus, current_image)
current_image:clear(true)
-- utils.debug("FocusLost")

if
state.options.editor_only_render_when_focused
or (utils.tmux.is_tmux and utils.tmux.get_window_id() ~= initial_tmux_window_id)
then
local images = api.get_images()
for _, current_image in ipairs(images) do
if current_image.is_rendered then
table.insert(images_to_restore_on_focus, current_image)
current_image:clear(true)
end
end
end
end)
end,
})

vim.api.nvim_create_autocmd("FocusGained", {
group = group,
callback = function() -- auto-clear images when windows and buffers change
vim.schedule(function()
-- utils.debug("FocusGained")

vim.schedule_wrap(function()
for _, current_image in ipairs(images_to_restore_on_focus) do
current_image:render()
end
images_to_restore_on_focus = {}
end)
end)()
end,
})
end
Expand Down
2 changes: 2 additions & 0 deletions lua/image/utils/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local window = require("image/utils/window")
local term = require("image/utils/term")
local math = require("image/utils/math")
local offsets = require("image/utils/offsets")
local tmux = require("image/utils/tmux")

local log = logger.create_logger({
prefix = "[image.nvim]",
Expand Down Expand Up @@ -37,4 +38,5 @@ return {
term = term,
math = math,
offsets = offsets,
tmux = tmux,
}
11 changes: 11 additions & 0 deletions lua/image/utils/term.lua
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,19 @@ vim.api.nvim_create_autocmd("VimResized", {
callback = update_size,
})

local get_tty = function()
local handle = io.popen("tty 2>/dev/null")
if not handle then return nil end
local result = handle:read("*a")
handle:close()
result = vim.fn.trim(result)
if result == "" then return nil end
return result
end

return {
get_size = function()
return cached_size
end,
get_tty = get_tty,
}
31 changes: 31 additions & 0 deletions lua/image/utils/tmux.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
local is_tmux = vim.env.TMUX ~= nil

local has_passthrough = false
if is_tmux then
local ok, result = pcall(vim.fn.system, "tmux show -Apv allow-passthrough")
if ok and result == "on\n" then has_passthrough = true end
end

local create_dm_getter = function(name)
return function()
local result = vim.fn.system("tmux display-message -p '#{" .. name .. "}'")
return vim.fn.trim(result)
end
end

local escape = function(sequence)
return "\x1bPtmux;" .. sequence:gsub("\x1b", "\x1b\x1b") .. "\x1b\\"
end

return {
is_tmux = is_tmux,
has_passthrough = has_passthrough,
get_pid = create_dm_getter("pid"),
get_socket_path = create_dm_getter("socket_path"),
get_window_id = create_dm_getter("window_id"),
get_window_name = create_dm_getter("window_name"),
get_pane_id = create_dm_getter("pane_id"),
get_pane_pid = create_dm_getter("pane_pid"),
get_pane_tty = create_dm_getter("pane_tty"),
escape = escape,
}
1 change: 1 addition & 0 deletions lua/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,4 @@
---@field display_virtual_placeholder? 0|1
---@field display_zindex? number
---@field display_delete? "a"|"A"|"i"|"I"|"p"
---@field tty? string

0 comments on commit 3fbe47d

Please sign in to comment.