terminal: track pwd reported via OSC 7

This commit is contained in:
Mitchell Hashimoto
2023-05-31 18:54:24 -07:00
parent b538072972
commit e59b2f7fca
4 changed files with 79 additions and 1 deletions

View File

@ -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" {

View File

@ -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;

View File

@ -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

View File

@ -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);
}
};