Merge remote-tracking branch 'upstream/main' into grapheme-width-changes

This commit is contained in:
Jacob Sandlund
2026-01-28 09:45:51 -05:00
8 changed files with 73 additions and 59 deletions

View File

@@ -120,14 +120,16 @@ struct TerminalCommandPaletteView: View {
/// Custom commands from the command-palette-entry configuration.
private var terminalOptions: [CommandOption] {
guard let appDelegate = NSApp.delegate as? AppDelegate else { return [] }
return appDelegate.ghostty.config.commandPaletteEntries.map { c in
CommandOption(
title: c.title,
description: c.description
) {
onAction(c.action)
return appDelegate.ghostty.config.commandPaletteEntries
.filter(\.isSupported)
.map { c in
CommandOption(
title: c.title,
description: c.description
) {
onAction(c.action)
}
}
}
}
/// Commands for jumping to other terminal surfaces.

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24506"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="3008" height="1661"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="949"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24506"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,7 +17,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="0.0" y="0.0" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="3008" height="1661"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="949"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@@ -269,16 +269,10 @@ extension Ghostty {
// Builds up the "input.ScrollMods" bitmask
var mods: Int32 = 0
var x = event.scrollingDeltaX
var y = event.scrollingDeltaY
let x = event.scrollingDeltaX
let y = event.scrollingDeltaY
if event.hasPreciseScrollingDeltas {
mods = 1
// We do a 2x speed multiplier. This is subjective, it "feels" better to me.
x *= 2;
y *= 2;
// TODO(mitchellh): do we have to scale the x/y here by window scale factor?
}
// Determine our momentum value

View File

@@ -1133,15 +1133,18 @@ pub const Inspector = struct {
yoff: f64,
mods: input.ScrollMods,
) void {
_ = mods;
self.queueRender();
cimgui.c.ImGui_SetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// For precision scrolling (trackpads), the values are in pixels which
// scroll way too fast. Scale them down to approximate discrete wheel
// notches. imgui expects 1.0 to scroll ~5 lines of text.
const scale: f64 = if (mods.precision) 0.1 else 1.0;
cimgui.c.ImGuiIO_AddMouseWheelEvent(
io,
@floatCast(xoff),
@floatCast(yoff),
@floatCast(xoff * scale),
@floatCast(yoff * scale),
);
}
@@ -1202,10 +1205,11 @@ pub const Inspector = struct {
// Determine our delta time
const now = try std.time.Instant.now();
io.DeltaTime = if (self.instant) |prev| delta: {
const since_ns = now.since(prev);
const since_s: f32 = @floatFromInt(since_ns / std.time.ns_per_s);
const since_ns: f64 = @floatFromInt(now.since(prev));
const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s);
const since_s: f32 = @floatCast(since_ns / ns_per_s);
break :delta @max(0.00001, since_s);
} else (1 / 60);
} else (1.0 / 60.0);
self.instant = now;
}
};

View File

@@ -131,21 +131,17 @@ pub const ImguiWidget = extern struct {
/// Initialize the frame. Expects that the context is already current.
fn newFrame(self: *Self) void {
// If we can't determine the time since the last frame we default to
// 1/60th of a second.
const default_delta_time = 1 / 60;
const priv = self.private();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Determine our delta time
const now = std.time.Instant.now() catch unreachable;
io.DeltaTime = if (priv.instant) |prev| delta: {
const since_ns = now.since(prev);
const since_s: f32 = @floatFromInt(since_ns / std.time.ns_per_s);
const since_ns: f64 = @floatFromInt(now.since(prev));
const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s);
const since_s: f32 = @floatCast(since_ns / ns_per_s);
break :delta @max(0.00001, since_s);
} else default_delta_time;
} else (1.0 / 60.0);
priv.instant = now;
}

View File

@@ -583,10 +583,12 @@ fn renderModesWindow(self: *Inspector) void {
const tag: terminal.modes.ModeTag = @bitCast(@as(terminal.modes.ModeTag.Backing, field.value));
cimgui.c.ImGui_TableNextRow();
cimgui.c.ImGui_PushIDInt(@intCast(field.value));
defer cimgui.c.ImGui_PopID();
{
_ = cimgui.c.ImGui_TableSetColumnIndex(0);
var value: bool = t.modes.get(@field(terminal.Mode, field.name));
_ = cimgui.c.ImGui_Checkbox("", &value);
_ = cimgui.c.ImGui_Checkbox("##checkbox", &value);
}
{
_ = cimgui.c.ImGui_TableSetColumnIndex(1);

View File

@@ -254,7 +254,7 @@ fn threadMain_(self: *Thread) !void {
);
// Start the draw timer
self.startDrawTimer();
self.syncDrawTimer();
// Run
log.debug("starting renderer thread", .{});
@@ -292,11 +292,33 @@ fn setQosClass(self: *const Thread) void {
}
}
fn startDrawTimer(self: *Thread) void {
// If our renderer doesn't support animations then we never run this.
if (!@hasDecl(rendererpkg.Renderer, "hasAnimations")) return;
if (!self.renderer.hasAnimations()) return;
if (self.config.custom_shader_animation == .false) return;
fn syncDrawTimer(self: *Thread) void {
skip: {
// If we have an inspector, we always run the draw timer.
if (self.flags.has_inspector) break :skip;
// If our renderer supports animations and has them, then we
// always have a draw timer.
if (@hasDecl(rendererpkg.Renderer, "hasAnimations") and
self.renderer.hasAnimations())
{
break :skip;
}
// If our config says to always animate, we do so.
switch (self.config.custom_shader_animation) {
// Always animate
.always => break :skip,
// Only when focused
.true => if (self.flags.focused) break :skip,
// Never animate
.false => {},
}
// We're skipping the draw timer. Stop it on the next iteration.
self.draw_active = false;
return;
}
// Set our active state so it knows we're running. We set this before
// even checking the active state in case we have a pending shutdown.
@@ -316,11 +338,6 @@ fn startDrawTimer(self: *Thread) void {
);
}
fn stopDrawTimer(self: *Thread) void {
// This will stop the draw on the next iteration.
self.draw_active = false;
}
/// Drain the mailbox.
fn drainMailbox(self: *Thread) !void {
// There's probably a more elegant way to do this...
@@ -377,12 +394,10 @@ fn drainMailbox(self: *Thread) !void {
// Set it on the renderer
try self.renderer.setFocus(v);
if (!v) {
if (self.config.custom_shader_animation != .always) {
// Stop the draw timer
self.stopDrawTimer();
}
// We always resync our draw timer (may disable it)
self.syncDrawTimer();
if (!v) {
// If we're not focused, then we stop the cursor blink
if (self.cursor_c.state() == .active and
self.cursor_c_cancel.state() == .dead)
@@ -397,9 +412,6 @@ fn drainMailbox(self: *Thread) !void {
);
}
} else {
// Start the draw timer
self.startDrawTimer();
// If we're focused, we immediately show the cursor again
// and then restart the timer.
if (self.cursor_c.state() != .active) {
@@ -446,8 +458,7 @@ fn drainMailbox(self: *Thread) !void {
// Stop and start the draw timer to capture the new
// hasAnimations value.
self.stopDrawTimer();
self.startDrawTimer();
self.syncDrawTimer();
},
.search_viewport_matches => |v| {
@@ -466,7 +477,12 @@ fn drainMailbox(self: *Thread) !void {
self.renderer.search_matches_dirty = true;
},
.inspector => |v| self.flags.has_inspector = v,
.inspector => |v| {
self.flags.has_inspector = v;
// Reset our draw timer state, which might change due
// to the inspector change.
self.syncDrawTimer();
},
.macos_display_id => |v| {
if (@hasDecl(rendererpkg.Renderer, "setMacOSDisplayID")) {