From e225eb9eff5dfa7138ebe3a0778dd5e89fc0ca94 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 13 Nov 2024 13:35:51 -0800 Subject: [PATCH] terminal: OSC7 with empty URL resets the pwd to nil When an empty string is given to OSC7, the pwd is reset to nil (as if the terminal never received a pwd report to begin with). This is analogous to how OSC0/2 reset the title to nil when given an empty string. This is practically useful for macOS because it allows our proxy icon to also be reset instead of being stuck on the last known path. This breaks from any known terminal behavior. As far as I can find, this is totally unspecified so we're somewhat free to do what we want. I don't think any terminal programs depend on this behavior, so I think it's safe to change it. --- src/terminal/osc.zig | 25 +++++++++++++------------ src/termio/stream_handler.zig | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 7e93b48d7..34bc46745 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -641,7 +641,7 @@ pub const Parser = struct { .@"7" => switch (c) { ';' => { self.command = .{ .report_pwd = .{ .value = "" } }; - + self.complete = true; self.state = .string; self.temp_state = .{ .str = &self.command.report_pwd.value }; self.buf_start = self.buf_idx; @@ -1382,6 +1382,18 @@ 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); + const cmd = p.end(null).?; + try testing.expect(cmd == .report_pwd); + try testing.expect(std.mem.eql(u8, "", cmd.report_pwd.value)); +} + test "OSC: pointer cursor" { const testing = std.testing; @@ -1395,17 +1407,6 @@ test "OSC: pointer cursor" { try testing.expect(std.mem.eql(u8, "pointer", cmd.mouse_shape.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) == null); -} - test "OSC: longer than buffer" { const testing = std.testing; diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 66c8359bd..dd7763334 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const xev = @import("xev"); const apprt = @import("../apprt.zig"); @@ -1048,6 +1049,28 @@ pub const StreamHandler = struct { } pub fn reportPwd(self: *StreamHandler, url: []const u8) !void { + // Special handling for the empty URL. We treat the empty URL + // as resetting the pwd as if we never saw a pwd. I can't find any + // other terminal that does this but it seems like a reasonable + // behavior that enables some useful features. For example, the macOS + // proxy icon can be hidden when a program reports it doesn't know + // the pwd rather than showing a stale pwd. + if (url.len == 0) { + // Blank value can never fail because no allocs happen. + self.terminal.setPwd("") catch unreachable; + + // If we haven't seen a title, we're using the pwd as our title. + // Set it to blank which will reset our title behavior. + if (!self.seen_title) { + try self.changeWindowTitle(""); + assert(!self.seen_title); + } + + // Report the change. + self.surfaceMessageWriter(.{ .pwd_change = .{ .stable = "" } }); + return; + } + if (builtin.os.tag == .windows) { log.warn("reportPwd unimplemented on windows", .{}); return;