-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathterminal-notifications.mjs
More file actions
97 lines (89 loc) · 2.65 KB
/
Copy pathterminal-notifications.mjs
File metadata and controls
97 lines (89 loc) · 2.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// SPDX-License-Identifier: MIT
export function selectTerminalTarget(context = process) {
const { platform, env, stdout, stderr } = context;
if (
platform !== "darwin" ||
env.TERM === "dumb" ||
env.TMUX ||
env.STY ||
/^(?:screen|tmux)(?:[.-]|$)/i.test(env.TERM ?? "") ||
env.SSH_CONNECTION ||
env.SSH_CLIENT ||
env.SSH_TTY ||
env.HERDR_ENV ||
env.HERDR_PANE_ID ||
env.HERDR_SOCKET_PATH ||
["tmux", "herdr"].includes(env.COPILOT_MULTIPLEXER)
) {
return undefined;
}
const terminal = (env.TERM_PROGRAM ?? "").toLowerCase();
let protocol;
if (terminal === "ghostty" || (!terminal && env.TERM === "xterm-ghostty")) {
protocol = "osc777";
} else if (terminal === "iterm.app") {
protocol = "osc9";
} else {
return undefined;
}
const stream = [stdout, stderr].find(
(candidate) =>
candidate?.isTTY === true &&
candidate.writable !== false &&
!candidate.writableEnded &&
!candidate.destroyed,
);
return stream ? { protocol, stream } : undefined;
}
export function encodeTerminalNotification(payload, protocol) {
const sanitize = (value) => {
if (typeof value !== "string") {
throw new TypeError("Notification text must be a string");
}
return value
.replace(/[\x00-\x1f\x7f-\x9f;]/g, " ")
.replace(/\s+/gu, " ")
.trim();
};
const title = sanitize(payload.summary);
if (!title) {
throw new TypeError("Notification summary must not be empty");
}
const details = [payload.subtitle, payload.body]
.filter((value) => value !== undefined)
.map(sanitize)
.filter(Boolean)
.join(" - ");
if (protocol === "osc777") {
return `\x1b]777;notify;${title};${details}\x07`;
}
if (protocol === "osc9") {
return `\x1b]9;${[title, details].filter(Boolean).join(": ")}\x07`;
}
throw new RangeError(`Unsupported notification protocol: ${protocol}`);
}
export async function showTerminalNotification(payload, context = process) {
if (context.env.COPILOT_DISABLE_DESKTOP_NOTIFICATIONS === "1") {
return "disabled";
}
const target = selectTerminalTarget(context);
if (!target) {
return "unsupported";
}
const sequence = encodeTerminalNotification(payload, target.protocol);
let onError;
try {
await new Promise((resolve, reject) => {
onError = reject;
target.stream.once("error", onError);
// A false write result is backpressure, not a failed notification.
target.stream.write(sequence, (error) => {
if (error) reject(error);
else resolve();
});
});
} finally {
if (onError) target.stream.removeListener("error", onError);
}
return "sent";
}