mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
terminal: track pwd reported via OSC 7
This commit is contained in:
@ -64,6 +64,9 @@ cols: usize,
|
|||||||
/// The current scrolling region.
|
/// The current scrolling region.
|
||||||
scrolling_region: ScrollingRegion,
|
scrolling_region: ScrollingRegion,
|
||||||
|
|
||||||
|
/// The last reported pwd, if any.
|
||||||
|
pwd: std.ArrayList(u8),
|
||||||
|
|
||||||
/// The charset state
|
/// The charset state
|
||||||
charset: CharsetState = .{},
|
charset: CharsetState = .{},
|
||||||
|
|
||||||
@ -169,6 +172,7 @@ pub fn init(alloc: Allocator, cols: usize, rows: usize) !Terminal {
|
|||||||
.top = 0,
|
.top = 0,
|
||||||
.bottom = rows - 1,
|
.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.tabstops.deinit(alloc);
|
||||||
self.screen.deinit();
|
self.screen.deinit();
|
||||||
self.secondary_screen.deinit();
|
self.secondary_screen.deinit();
|
||||||
|
self.pwd.deinit();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1501,6 +1506,19 @@ pub fn cursorIsAtPrompt(self: *Terminal) bool {
|
|||||||
return false;
|
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
|
/// Full reset
|
||||||
pub fn fullReset(self: *Terminal) void {
|
pub fn fullReset(self: *Terminal) void {
|
||||||
self.primaryScreen(.{ .clear_on_exit = true, .cursor_save = true });
|
self.primaryScreen(.{ .clear_on_exit = true, .cursor_save = true });
|
||||||
@ -1514,6 +1532,7 @@ pub fn fullReset(self: *Terminal) void {
|
|||||||
self.screen.selection = null;
|
self.screen.selection = null;
|
||||||
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
self.scrolling_region = .{ .top = 0, .bottom = self.rows - 1 };
|
||||||
self.previous_char = null;
|
self.previous_char = null;
|
||||||
|
self.pwd.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Terminal: input with no control characters" {
|
test "Terminal: input with no control characters" {
|
||||||
|
@ -242,7 +242,7 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
.@"7" => switch (c) {
|
.@"7" => switch (c) {
|
||||||
';' => {
|
';' => {
|
||||||
self.command = .{ .report_pwd = .{ .value = undefined } };
|
self.command = .{ .report_pwd = .{ .value = "" } };
|
||||||
|
|
||||||
self.state = .string;
|
self.state = .string;
|
||||||
self.temp_state = .{ .str = &self.command.report_pwd.value };
|
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));
|
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" {
|
test "OSC: longer than buffer" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
@ -495,6 +495,12 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
} 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"))
|
else => if (@hasDecl(T, "oscUnimplemented"))
|
||||||
try self.handler.oscUnimplemented(cmd)
|
try self.handler.oscUnimplemented(cmd)
|
||||||
else
|
else
|
||||||
|
@ -1249,4 +1249,46 @@ const StreamHandler = struct {
|
|||||||
pub fn endOfInput(self: *StreamHandler) !void {
|
pub fn endOfInput(self: *StreamHandler) !void {
|
||||||
self.terminal.markSemanticPrompt(.command);
|
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