mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
terminal: track pwd reported via OSC 7
This commit is contained in:
@ -64,6 +64,9 @@ cols: usize,
|
||||
/// The current scrolling region.
|
||||
scrolling_region: ScrollingRegion,
|
||||
|
||||
/// The last reported pwd, if any.
|
||||
pwd: std.ArrayList(u8),
|
||||
|
||||
/// The charset state
|
||||
charset: CharsetState = .{},
|
||||
|
||||
@ -169,6 +172,7 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal {
|
||||
.top = 0,
|
||||
.bottom = rows - 1,
|
||||
},
|
||||
.pwd = std.ArrayList(u8).init(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
@ -176,6 +180,7 @@ pub fn deinit(self: *Terminal, alloc: Allocator) void {
|
||||
self.tabstops.deinit(alloc);
|
||||
self.screen.deinit();
|
||||
self.secondary_screen.deinit();
|
||||
self.pwd.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
@ -1501,6 +1506,19 @@ pub fn cursorIsAtPrompt(self: *Terminal) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Set the pwd for the terminal.
|
||||
pub fn setPwd(self: *Terminal, pwd: []const u8) !void {
|
||||
self.pwd.clearRetainingCapacity();
|
||||
try self.pwd.appendSlice(pwd);
|
||||
}
|
||||
|
||||
/// Returns the pwd for the terminal, if any. The memory is owned by the
|
||||
/// Terminal and is not copied. It is safe until a reset or setPwd.
|
||||
pub fn getPwd(self: *Terminal) ?[]const u8 {
|
||||
if (self.pwd.items.len == 0) return null;
|
||||
return self.pwd.items;
|
||||
}
|
||||
|
||||
/// Full reset
|
||||
pub fn fullReset(self: *Terminal) void {
|
||||
self.primaryScreen(.{ .clear_on_exit = true, .cursor_save = true });
|
||||
@ -1514,6 +1532,7 @@ pub fn fullReset(self: *Terminal) void {
|
||||
self.screen.selection = null;
|
||||
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
||||
self.previous_char = null;
|
||||
self.pwd.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
test "Terminal: input with no control characters" {
|
||||
|
@ -242,7 +242,7 @@ pub const Parser = struct {
|
||||
|
||||
.@"7" => switch (c) {
|
||||
';' => {
|
||||
self.command = .{ .report_pwd = .{ .value = undefined } };
|
||||
self.command = .{ .report_pwd = .{ .value = "" } };
|
||||
|
||||
self.state = .string;
|
||||
self.temp_state = .{ .str = &self.command.report_pwd.value };
|
||||
@ -593,6 +593,17 @@ test "OSC: report pwd" {
|
||||
try testing.expect(std.mem.eql(u8, "file:///tmp/example", cmd.report_pwd.value));
|
||||
}
|
||||
|
||||
test "OSC: report pwd empty" {
|
||||
const testing = std.testing;
|
||||
|
||||
var p: Parser = .{};
|
||||
|
||||
const input = "7;";
|
||||
for (input) |ch| p.next(ch);
|
||||
|
||||
try testing.expect(p.end() == null);
|
||||
}
|
||||
|
||||
test "OSC: longer than buffer" {
|
||||
const testing = std.testing;
|
||||
|
||||
|
@ -495,6 +495,12 @@ pub fn Stream(comptime Handler: type) type {
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
||||
.report_pwd => |v| {
|
||||
if (@hasDecl(T, "reportPwd")) {
|
||||
try self.handler.reportPwd(v.value);
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
||||
else => if (@hasDecl(T, "oscUnimplemented"))
|
||||
try self.handler.oscUnimplemented(cmd)
|
||||
else
|
||||
|
@ -1249,4 +1249,46 @@ const StreamHandler = struct {
|
||||
pub fn endOfInput(self: *StreamHandler) !void {
|
||||
self.terminal.markSemanticPrompt(.command);
|
||||
}
|
||||
|
||||
pub fn reportPwd(self: *StreamHandler, url: []const u8) !void {
|
||||
const uri = std.Uri.parse(url) catch |e| {
|
||||
log.warn("invalid url in OSC 7: {}", .{e});
|
||||
return;
|
||||
};
|
||||
|
||||
if (!std.mem.eql(u8, "file", uri.scheme) and
|
||||
!std.mem.eql(u8, "kitty-shell-cwd", uri.scheme))
|
||||
{
|
||||
log.warn("OSC 7 scheme must be file, got: {s}", .{uri.scheme});
|
||||
return;
|
||||
}
|
||||
|
||||
// OSC 7 is a little sketchy because anyone can send any value from
|
||||
// any host (such an SSH session). The best practice terminals follow
|
||||
// is to valid the hostname to be local.
|
||||
const host_valid = host_valid: {
|
||||
const host = uri.host orelse break :host_valid false;
|
||||
|
||||
// Empty or localhost is always good
|
||||
if (host.len == 0 or std.mem.eql(u8, "localhost", host)) {
|
||||
break :host_valid true;
|
||||
}
|
||||
|
||||
// Otherwise, it must match our hostname.
|
||||
var buf: [std.os.HOST_NAME_MAX]u8 = undefined;
|
||||
const hostname = std.os.gethostname(&buf) catch |err| {
|
||||
log.warn("failed to get hostname for OSC 7 validation: {}", .{err});
|
||||
break :host_valid false;
|
||||
};
|
||||
|
||||
break :host_valid std.mem.eql(u8, host, hostname);
|
||||
};
|
||||
if (!host_valid) {
|
||||
log.warn("OSC 7 host must be local", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("terminal pwd: {s}", .{uri.path});
|
||||
try self.terminal.setPwd(uri.path);
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user