Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/command_dispatcher.zig
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ fn execWorkspaceRename(self: *App, request: command_mod.Request) command_mod.Res
}

fn execTabNew(self: *App, request: command_mod.Request) command_mod.Response {
mux_ops.newTab(self, request.cwd, request.domain, request.cmd, LUA_NOREF);
mux_ops.newTab(self, request.cwd, request.domain, request.cmd, LUA_NOREF, false);
return okNull();
}

Expand Down Expand Up @@ -951,7 +951,7 @@ fn execConfigTheme(self: *App, request: command_mod.Request) command_mod.Respons
}

fn execRun(self: *App, request: command_mod.Request) command_mod.Response {
mux_ops.newTab(self, request.cwd, request.domain, request.cmd, LUA_NOREF);
mux_ops.newTab(self, request.cwd, request.domain, request.cmd, LUA_NOREF, false);
return okNull();
}

Expand Down
3 changes: 2 additions & 1 deletion src/app/input.zig
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub const PendingInputEvent = union(enum) {
domain_name: ?[]const u8,
command: ?[]const u8,
callback_ref: c_int,
insert_at_end: bool = false,
},
close_tab,
close_pane,
Expand Down Expand Up @@ -303,7 +304,7 @@ pub fn processInputQueue(self: *App) void {
mux_ops.closeTabAt(self, idx);
},
.new_tab => |payload| {
mux_ops.newTab(self, payload.cwd, payload.domain_name, payload.command, payload.callback_ref);
mux_ops.newTab(self, payload.cwd, payload.domain_name, payload.command, payload.callback_ref, payload.insert_at_end);
},
.close_tab => {
mux_ops.closeTab(
Expand Down
4 changes: 2 additions & 2 deletions src/app/lua_callbacks.zig
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ pub fn luaGetPaneForegroundProcessCallback(app_ptr: *anyopaque, pane_id: usize,
return app.getPaneForegroundProcess(pane_id, out_buf);
}

pub fn luaNewTabCallback(app_ptr: *anyopaque, cwd: ?[]const u8, domain_name: ?[]const u8, command: ?[]const u8, callback_ref: c_int) bool {
pub fn luaNewTabCallback(app_ptr: *anyopaque, cwd: ?[]const u8, domain_name: ?[]const u8, command: ?[]const u8, callback_ref: c_int, insert_at_end: bool) bool {
const app: *App = @ptrCast(@alignCast(app_ptr));
const owned_cwd = if (cwd) |value| app.allocator.dupe(u8, value) catch null else null;
const owned_domain = if (domain_name) |name| app.allocator.dupe(u8, name) catch null else null;
const owned_command = if (command) |value| app.allocator.dupe(u8, value) catch null else null;
var event: input.PendingInputEvent = .{ .new_tab = .{ .cwd = owned_cwd, .domain_name = owned_domain, .command = owned_command, .callback_ref = callback_ref } };
var event: input.PendingInputEvent = .{ .new_tab = .{ .cwd = owned_cwd, .domain_name = owned_domain, .command = owned_command, .callback_ref = callback_ref, .insert_at_end = insert_at_end } };
const queued = app.enqueueMouse(event);
if (!queued) input.deinitPendingInputEvent(app.allocator, &event);
return queued;
Expand Down
4 changes: 2 additions & 2 deletions src/app/session_controller.zig
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ pub fn scrollActiveViewportBottom(self: *App) void {
// Tab operations
// ============================================================

pub fn newTab(self: *App, cwd: ?[]const u8, domain_name: ?[]const u8, command: ?[]const u8, callback_ref: c_int) void {
pub fn newTab(self: *App, cwd: ?[]const u8, domain_name: ?[]const u8, command: ?[]const u8, callback_ref: c_int, insert_at_end: bool) void {
const start_ms = io.milliTimestamp();
var mux = if (self.mux) |*value| value else return;
const runtime = if (self.ghostty) |*value| value else return;
Expand All @@ -172,7 +172,7 @@ pub fn newTab(self: *App, cwd: ?[]const u8, domain_name: ?[]const u8, command: ?
.{ .command = value }
else
null;
mux.newTab(runtime, cbs, self.config, self.cell_width_px, self.cell_height_px, self.config.window_width, self.config.window_height, cwd, domain_name, launch_command) catch |err| {
mux.newTab(runtime, cbs, self.config, self.cell_width_px, self.cell_height_px, self.config.window_width, self.config.window_height, cwd, domain_name, launch_command, insert_at_end) catch |err| {
std.log.err("app: newTab failed: {s}", .{@errorName(err)});
if (self.lua) |*lua| lua.invokeOperationCallback(callback_ref, false, .none);
return;
Expand Down
8 changes: 4 additions & 4 deletions src/lua/hollow/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ function M.setup(hollow, host_api)
-- ── Tab Actions ──────────────────────────────────

register(hollow, "new_tab", {
run = function()
host_api.new_tab({})
run = function(opts)
host_api.new_tab(opts or {})
end,
desc = "Create new tab",
category = "tab",
Expand Down Expand Up @@ -738,9 +738,9 @@ function M.setup(hollow, host_api)
})

register(hollow, "new_tab_in_domain", {
run = function()
run = function(opts)
pick_domain_and_run(function(item)
host_api.new_tab({ domain = item.domain_name })
host_api.new_tab({ domain = item.domain_name, insert_at_end = opts and opts.insert_at_end })
end)
end,
desc = "Create new tab with a domain",
Expand Down
12 changes: 12 additions & 0 deletions src/lua/hollow/events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ function M.setup(hollow, state, term_helpers)
}
end

local function adapt_bar_node(payload)
return {
id = payload.id,
mods = hollow.keymap.format_mods(payload.mods),
shifted = payload.shifted == true,
}
end

local adapters = {
["term:tab_activated"] = adapt_tab_activated,
["workspace:new"] = adapt_workspace_new,
Expand All @@ -127,6 +135,10 @@ function M.setup(hollow, state, term_helpers)
["copy_mode:changed"] = adapt_copy_mode_changed,
["key:unhandled"] = adapt_key_unhandled,
["window:files_dropped"] = adapt_files_dropped,
["topbar:hover"] = adapt_bar_node,
["topbar:click"] = adapt_bar_node,
["bottombar:hover"] = adapt_bar_node,
["bottombar:click"] = adapt_bar_node,
}

local function adapt_builtin_payload(name, payload)
Expand Down
2 changes: 2 additions & 0 deletions src/lua/hollow/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@ function M.new(host_api)
topbar_cache_state = nil,
topbar_cache_layout = nil,
topbar_hovered_id = nil,
topbar_hovered_shifted = false,
topbar_handlers = {},
mounted_bottombar = nil,
bottombar_cache_dirty = true,
bottombar_cache_expires_at = nil,
bottombar_cache_state = nil,
bottombar_cache_layout = nil,
bottombar_hovered_id = nil,
bottombar_hovered_shifted = false,
bottombar_handlers = {},
mounted_sidebar = nil,
sidebar_visible = false,
Expand Down
1 change: 1 addition & 0 deletions src/lua/hollow/ui/primitives.lua
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ function bar.custom(opts)
return {
_type = "bar_custom",
id = opts.id,
style = opts.style,
render = opts.render,
cache_ttl_ms = opts.cache_ttl_ms,
on_click = opts.on_click,
Expand Down
39 changes: 31 additions & 8 deletions src/lua/hollow/ui/widgets/bars/bar_events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ function M.hovered_key(surface)
return nil
end

---@param surface string
---@return string|nil
function M.hovered_shifted_key(surface)
if surface == "topbar" then
return "topbar_hovered_shifted"
end
if surface == "bottombar" then
return "bottombar_hovered_shifted"
end
return nil
end

---@param surface string|nil
---@param style any
---@return boolean
Expand Down Expand Up @@ -165,11 +177,15 @@ function M.install(ui, active_widget, invalidate)
return
end
local key = M.hovered_key(surface)
local shifted_key = M.hovered_shifted_key(surface)
if kind == surface .. ":leave" then
local id = state.ui[key]
if id then
call(surface, id, "on_mouse_leave", { id = id })
state.ui[key] = nil
end
state.ui[key] = nil
state.ui[shifted_key] = false
if id then
invalidate(surface)
end
return
Expand All @@ -178,14 +194,21 @@ function M.install(ui, active_widget, invalidate)
if not id then
return
end
if kind == surface .. ":hover" and state.ui[key] ~= id then
local old = state.ui[key]
if old then
call(surface, old, "on_mouse_leave", { id = old })
if kind == surface .. ":hover" then
local shifted = payload.shifted == true
if state.ui[key] ~= id then
local old = state.ui[key]
if old then
call(surface, old, "on_mouse_leave", { id = old })
end
state.ui[key] = id
state.ui[shifted_key] = shifted
call(surface, id, "on_mouse_enter", payload)
invalidate(surface)
elseif state.ui[shifted_key] ~= shifted then
state.ui[shifted_key] = shifted
invalidate(surface)
end
state.ui[key] = id
call(surface, id, "on_mouse_enter", payload)
invalidate(surface)
elseif kind == surface .. ":click" then
call(surface, id, "on_click", payload)
end
Expand Down
38 changes: 36 additions & 2 deletions src/lua/hollow/ui/widgets/bars/bar_serialize.lua
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,45 @@ local function clamp_tab_segment_width(segment, width)
return segment
end

local text = truncate_text_end(segment.text or "", width)
if text == segment.text then
if util.utf8_len(segment.text or "") <= width then
return segment
end

local segments = segment.segments
if type(segments) == "table" and #segments > 1 then
local suffix_index
for index = #segments, 1, -1 do
if type(segments[index].id) == "string" and segments[index].id ~= "" then
suffix_index = index
break
end
end

if suffix_index ~= nil then
local suffix_width = 0
for index = suffix_index, #segments do
suffix_width = suffix_width + util.utf8_len(segments[index].text or "")
end

if suffix_width <= width then
local prefix = {}
for index = 1, suffix_index - 1 do
prefix[#prefix + 1] = segments[index]
end

local clamped = util.clone_value(segment)
clamped.segments = truncate_segments_end(prefix, width - suffix_width) or {}
for index = suffix_index, #segments do
clamped.segments[#clamped.segments + 1] = util.clone_value(segments[index])
end
clamped.text = shared.segments_plain_text(clamped.segments)
return clamped
end
end
end

local text = truncate_text_end(segment.text or "", width)

local clamped = util.clone_value(segment)
clamped.text = text
clamped.segments = truncate_segments_end(segment.segments, width)
Expand Down
51 changes: 48 additions & 3 deletions src/lua/hollow/ui/widgets/bars/topbar.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
local attention = require("hollow.ui.widgets.attention")
local color = require("hollow.color")
local shared = require("hollow.ui.shared")
local hollow = _G.hollow
local state = require("hollow.state").get()
local ui = hollow.ui
local tbl = hollow.tbl
local util = hollow.util
local M = {}
local BAR_CACHE_NO_EXPIRY = false
local DEFAULT_TOPBAR_HEIGHT = 22
local DEFAULT_NEW_TAB_TEXT = "+"
local DEFAULT_SHIFTED_NEW_TAB_TEXT = ""
local DEFAULT_TOPBAR_LAYOUT = {
padding = { left = 1, right = 1, top = 1, bottom = 1 },
}
Expand Down Expand Up @@ -107,6 +106,49 @@ local function configured_topbar_bar_opts(value)
return type(value) == "table" and value or {}
end

local function configured_topbar_new_tab(value)
if value == false then
return nil
end

local options = type(value) == "table" and value or {}
local theme = shared.resolve_theme().ui
local id = options.id or "new-tab-button"
local style = M.merge_tables({
bg = theme.tab_bar.inactive_tab.bg,
fg = theme.tab_bar.inactive_tab.fg,
radius = 4,
padding = { left = 5, right = 5, top = 1, bottom = 2 },
margin = { left = 1 },
hover = {
bg = theme.tab_bar.hover_tab.bg,
fg = theme.tab_bar.hover_tab.fg,
},
}, options.style)
style.id = id

return ui.bar.custom({
id = id,
style = style,
render = function()
local shifted = state.ui.topbar_hovered_id == id and state.ui.topbar_hovered_shifted
if shifted then
return ui.span(options.shifted_text or DEFAULT_SHIFTED_NEW_TAB_TEXT, {
padding = { left = 2, right = 7 },
})
end
return options.text or DEFAULT_NEW_TAB_TEXT
end,
on_click = function(event)
if event and event.shifted then
hollow.action.new_tab_in_domain({ insert_at_end = true })
else
hollow.action.new_tab({ insert_at_end = true })
end
end,
})
end

local function configured_topbar_time(value)
if value == false then
return false
Expand Down Expand Up @@ -137,13 +179,16 @@ function M.widget()
render = function(ctx)
local workspace = configured_topbar_bar_opts(opts.workspace)
local tabs = configured_topbar_bar_opts(opts.tabs)
local new_tab = configured_topbar_new_tab(opts.new_tab)
local separator = configured_topbar_separator(opts.separator)
local cwd = configured_topbar_cwd(ctx, opts.cwd)
local key_legend = configured_topbar_bar_opts(opts.key_legend)
local items = tbl({
workspace ~= false and ui.bar.workspace(workspace),
separator ~= nil and workspace ~= false and tabs ~= false and separator,
tabs ~= false and tabs.fit ~= "content" and new_tab or false,
tabs ~= false and ui.bar.tabs(tabs),
(tabs == false or tabs.fit == "content") and new_tab or false,
})
:filter(function(item)
return item ~= false
Expand Down
Loading
Loading