Merge 16bdb58c92 into 5758e14931
commit
4408de2f68
|
|
@ -853,9 +853,20 @@ typedef struct {
|
|||
|
||||
// apprt.action.CommandFinished.C
|
||||
typedef struct {
|
||||
// -1 if no exit code was reported, otherwise 0-255
|
||||
// The command line or len==0 if the command line
|
||||
// is unknown.
|
||||
struct {
|
||||
const char *ptr;
|
||||
uint64_t len;
|
||||
} command_line;
|
||||
// The exit code of the command. The exit code will be a number between 0
|
||||
// and 255 or -1 if no exit code was provided. 0 indicates that the command
|
||||
// was successful. Any number from 1 to 255 indicates an application specific
|
||||
// error code.
|
||||
int16_t exit_code;
|
||||
// number of nanoseconds that command was running for
|
||||
// How long the command took in nanoseconds. Despite the duration being
|
||||
// reported in nanoseconds the accuracy is probably only within a few
|
||||
// milliseconds.
|
||||
uint64_t duration;
|
||||
} ghostty_action_command_finished_s;
|
||||
|
||||
|
|
|
|||
|
|
@ -163,13 +163,19 @@ selection_scroll_active: bool = false,
|
|||
/// always enabled in this state.
|
||||
readonly: bool = false,
|
||||
|
||||
/// Used to send notifications that long running commands have finished.
|
||||
/// Requires that shell integration be active. Should represent a nanosecond
|
||||
/// precision timestamp. It does not necessarily need to correspond to the
|
||||
/// Timestamp used to send notifications that long running commands have
|
||||
/// finished. Requires that the shell reports to Ghostty when a command stops
|
||||
/// and start. The timestamp does not necessarily need to correspond to the
|
||||
/// actual time, but we must be able to compare two subsequent timestamps to get
|
||||
/// the wall clock time that has elapsed between timestamps.
|
||||
command_timer: ?std.time.Instant = null,
|
||||
|
||||
/// The command that is being executed, as reported by by the shell. If shell
|
||||
/// does not report the command line this will always be null. This will never
|
||||
/// be the `command` or `initial-command` that was used to start the shell.
|
||||
/// Ghostty's shell integration does not supply the command line being executed.
|
||||
command_line: ?[]const u8 = null,
|
||||
|
||||
/// Search state
|
||||
search: ?Search = null,
|
||||
|
||||
|
|
@ -821,6 +827,14 @@ pub fn deinit(self: *Surface) void {
|
|||
self.alloc.destroy(v);
|
||||
}
|
||||
|
||||
// If we're still storing a command line, deallocate it. This could happen
|
||||
// if a shell sends a report that a command started containing a command
|
||||
// line but doesn't send a report that the command finished before the shell
|
||||
// exits.
|
||||
if (self.command_line) |command_line| {
|
||||
self.alloc.free(command_line);
|
||||
}
|
||||
|
||||
// Clean up our keyboard state
|
||||
for (self.keyboard.sequence_queued.items) |req| req.deinit();
|
||||
self.keyboard.sequence_queued.deinit(self.alloc);
|
||||
|
|
@ -1125,10 +1139,32 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||
try self.selectionScrollTick();
|
||||
},
|
||||
|
||||
.start_command => {
|
||||
// A command has started executing.
|
||||
.start_command => |start_command| {
|
||||
defer start_command.deinit();
|
||||
|
||||
self.command_timer = try .now();
|
||||
if (start_command.command_line) |new_command_line| {
|
||||
|
||||
// Deallocate old command line if we are setting a new one. We
|
||||
// don't deallocate the command line unconditionally because
|
||||
// there are situations where the shell sends a bare command
|
||||
// started report _after_ it has already sent a command started
|
||||
// report with the command line but before it sends a command
|
||||
// finished report.
|
||||
if (self.command_line) |old_command_line| {
|
||||
self.alloc.free(old_command_line);
|
||||
self.command_line = null;
|
||||
}
|
||||
|
||||
// Create our own copy of the command line, the copy that
|
||||
// comes in the message could be deallocated once we are done
|
||||
// processing the message.
|
||||
self.command_line = self.alloc.dupe(u8, new_command_line.slice()) catch null;
|
||||
}
|
||||
},
|
||||
|
||||
// A command has finished executing.
|
||||
.stop_command => |v| timer: {
|
||||
const end: std.time.Instant = try .now();
|
||||
const start = self.command_timer orelse break :timer;
|
||||
|
|
@ -1141,12 +1177,19 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||
.{ .surface = self },
|
||||
.command_finished,
|
||||
.{
|
||||
.command_line = self.command_line,
|
||||
.exit_code = v,
|
||||
.duration = duration,
|
||||
},
|
||||
) catch |err| {
|
||||
log.warn("apprt failed to notify command finish={}", .{err});
|
||||
};
|
||||
|
||||
// Free up memory as the command line should never be used again.
|
||||
if (self.command_line) |old_command_line| {
|
||||
self.alloc.free(old_command_line);
|
||||
self.command_line = null;
|
||||
}
|
||||
},
|
||||
|
||||
.search_total => |v| {
|
||||
|
|
|
|||
|
|
@ -455,8 +455,8 @@ pub const Action = union(Key) {
|
|||
// At the time of writing, we don't promise ABI compatibility
|
||||
// so we can change this but I want to be aware of it.
|
||||
assert(@sizeOf(CValue) == switch (@sizeOf(usize)) {
|
||||
4 => 16,
|
||||
8 => 24,
|
||||
4 => 20,
|
||||
8 => 32,
|
||||
else => unreachable,
|
||||
});
|
||||
}
|
||||
|
|
@ -940,17 +940,38 @@ pub const CloseTabMode = enum(c_int) {
|
|||
};
|
||||
|
||||
pub const CommandFinished = struct {
|
||||
/// The command line, as reported by the shell, or null if the command line
|
||||
/// is unknown.
|
||||
command_line: ?[]const u8,
|
||||
/// The exit code, as reported by the shell. The exit code will be a number
|
||||
/// between 0 and 255 or null if no exit code was provided. 0 indicates
|
||||
/// that the command was successful. Any number from 1 to 255 indicates an
|
||||
/// application specific error code.
|
||||
exit_code: ?u8,
|
||||
/// How long the command took in nanoseconds. Despite the duration being
|
||||
/// reported in nanoseconds the accuracy is probably only within a few
|
||||
/// milliseconds.
|
||||
duration: configpkg.Config.Duration,
|
||||
|
||||
/// sync with ghostty_action_command_finished_s in ghostty.h
|
||||
pub const C = extern struct {
|
||||
/// The command line, as reported by the shell, or null if the command
|
||||
/// line is unknown.
|
||||
command_line: lib.String,
|
||||
/// The exit code, as reported by the shell. The exit code will be a
|
||||
/// number between 0 and 255 or null if no exit code was provided. 0
|
||||
/// indicates that the command was successful. Any number from 1 to 255
|
||||
/// indicates an application specific error code.
|
||||
exit_code: i16,
|
||||
// How long the command took in nanoseconds. Despite the duration being
|
||||
// reported in nanoseconds the accuracy is probably only within a few
|
||||
// milliseconds.
|
||||
duration: u64,
|
||||
};
|
||||
|
||||
pub fn cval(self: CommandFinished) C {
|
||||
return .{
|
||||
.command_line = .init(self.command_line orelse ""),
|
||||
.exit_code = self.exit_code orelse -1,
|
||||
.duration = self.duration.duration,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1165,26 +1165,45 @@ pub const Surface = extern struct {
|
|||
if (action.bell) self.setBellRinging(true);
|
||||
|
||||
if (action.notify) notify: {
|
||||
const title_ = title: {
|
||||
const title = std.mem.span(title: {
|
||||
const exit_code = value.exit_code orelse break :title i18n._("Command Finished");
|
||||
if (exit_code == 0) break :title i18n._("Command Succeeded");
|
||||
break :title i18n._("Command Failed");
|
||||
};
|
||||
const title = std.mem.span(title_);
|
||||
const body = body: {
|
||||
const exit_code = value.exit_code orelse break :body std.fmt.allocPrintSentinel(
|
||||
alloc,
|
||||
"Command took {f}.",
|
||||
.{value.duration.round(std.time.ns_per_ms)},
|
||||
0,
|
||||
) catch break :notify;
|
||||
break :body std.fmt.allocPrintSentinel(
|
||||
alloc,
|
||||
"Command took {f} and exited with code {d}.",
|
||||
.{ value.duration.round(std.time.ns_per_ms), exit_code },
|
||||
0,
|
||||
) catch break :notify;
|
||||
};
|
||||
});
|
||||
|
||||
var buf: std.Io.Writer.Allocating = .init(alloc);
|
||||
defer buf.deinit();
|
||||
const writer = &buf.writer;
|
||||
|
||||
writer.writeAll("Command ") catch break :notify;
|
||||
|
||||
if (value.command_line) |command_line| command_line: {
|
||||
// Don't bother with a zero length command line.
|
||||
if (command_line.len == 0) break :command_line;
|
||||
// The defacto standard for "hiding" a command line from being
|
||||
// saved in history or other places is to prefix it with a
|
||||
// space. Honor that here.
|
||||
if (command_line[0] == ' ') {
|
||||
writer.writeAll("«hidden» ") catch break :notify;
|
||||
break :command_line;
|
||||
}
|
||||
writer.writeAll("“") catch break :notify;
|
||||
writer.writeAll(command_line) catch break :notify;
|
||||
writer.writeAll("” ") catch break :notify;
|
||||
}
|
||||
|
||||
writer.print(
|
||||
"took {f}",
|
||||
.{value.duration.round(std.time.ns_per_ms)},
|
||||
) catch break :notify;
|
||||
|
||||
if (value.exit_code) |exit_code| {
|
||||
writer.print(" and exited with code {d}", .{exit_code}) catch break :notify;
|
||||
}
|
||||
|
||||
writer.writeByte('.') catch break :notify;
|
||||
|
||||
const body = buf.toOwnedSliceSentinel(0) catch break :notify;
|
||||
defer alloc.free(body);
|
||||
|
||||
self.sendDesktopNotification(title, body);
|
||||
|
|
|
|||
|
|
@ -91,12 +91,13 @@ pub const Message = union(enum) {
|
|||
/// Report the progress of an action using a GUI element
|
||||
progress_report: terminal.osc.Command.ProgressReport,
|
||||
|
||||
/// A command has started in the shell, start a timer.
|
||||
start_command,
|
||||
/// A command has started in the shell. Start a timer and store the command
|
||||
/// line (if provided) for later display.
|
||||
start_command: StartCommand,
|
||||
|
||||
/// A command has finished in the shell, stop the timer and send out
|
||||
/// notifications as appropriate. The optional u8 is the exit code
|
||||
/// of the command.
|
||||
/// A command has finished in the shell. Stop the timer and send out
|
||||
/// notifications as appropriate. The optional u8 is the exit code of the
|
||||
/// command.
|
||||
stop_command: ?u8,
|
||||
|
||||
/// The scrollbar state changed for the surface.
|
||||
|
|
@ -129,6 +130,25 @@ pub const Message = union(enum) {
|
|||
.none => void,
|
||||
};
|
||||
};
|
||||
|
||||
pub const StartCommand = struct {
|
||||
command_line: ?WriteReq,
|
||||
|
||||
/// Return the command line, or null if no command line was supplied.
|
||||
pub fn init(alloc: std.mem.Allocator, command_line: []const u8) StartCommand {
|
||||
if (command_line.len == 0) return .{
|
||||
.command_line = null,
|
||||
};
|
||||
|
||||
return .{
|
||||
.command_line = WriteReq.init(alloc, command_line) catch null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const StartCommand) void {
|
||||
if (self.command_line) |command_line| command_line.deinit();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// A surface mailbox.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub const String = extern struct {
|
|||
.ptr = zig.ptr,
|
||||
.len = zig.len,
|
||||
},
|
||||
else => |v| @compileLog(v),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const assert = @import("../quirks.zig").inlineAssert;
|
|||
const Allocator = std.mem.Allocator;
|
||||
const xev = @import("../global.zig").xev;
|
||||
const apprt = @import("../apprt.zig");
|
||||
const Message = apprt.surface.Message;
|
||||
const build_config = @import("../build_config.zig");
|
||||
const configpkg = @import("../config.zig");
|
||||
const internal_os = @import("../os/main.zig");
|
||||
|
|
@ -124,7 +125,7 @@ pub const StreamHandler = struct {
|
|||
|
||||
inline fn surfaceMessageWriter(
|
||||
self: *StreamHandler,
|
||||
msg: apprt.surface.Message,
|
||||
msg: Message,
|
||||
) void {
|
||||
// See messageWriter which has similar logic and explains why
|
||||
// we may have to do this.
|
||||
|
|
@ -1079,7 +1080,18 @@ pub const StreamHandler = struct {
|
|||
) !void {
|
||||
switch (cmd.action) {
|
||||
.end_input_start_output => {
|
||||
self.surfaceMessageWriter(.start_command);
|
||||
const message: Message = .{
|
||||
.start_command = c: {
|
||||
var w: std.Io.Writer.Allocating = .init(self.alloc);
|
||||
defer w.deinit();
|
||||
|
||||
cmd.writeCommandLine(&w.writer) catch break :c .{ .command_line = null };
|
||||
|
||||
break :c Message.StartCommand.init(self.alloc, w.written());
|
||||
},
|
||||
};
|
||||
|
||||
self.surfaceMessageWriter(message);
|
||||
},
|
||||
|
||||
.end_command => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue