Comparing version 2.3.0-beta.2 to 2.3.0-beta.3
{ | ||
"name": "run-pty", | ||
"version": "2.3.0-beta.2", | ||
"version": "2.3.0-beta.3", | ||
"author": "Simon Lydell", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
232
run-pty.js
@@ -74,2 +74,3 @@ #!/usr/bin/env node | ||
const RESET_COLOR = "\x1B[m"; | ||
const RESET_COLOR_REGEX = /(\x1B\[0?m)/; | ||
const CLEAR = IS_WINDOWS ? "\x1B[2J\x1B[0f" : "\x1B[2J\x1B[3J\x1B[H"; | ||
@@ -132,4 +133,9 @@ const CLEAR_RIGHT = "\x1B[K"; | ||
*/ | ||
const invert = (string) => | ||
NO_COLOR ? string : `\x1B[7m${string}${RESET_COLOR}`; | ||
const invert = (string) => { | ||
const inverted = string | ||
.split(RESET_COLOR_REGEX) | ||
.map((part, index) => (index % 2 === 0 ? `\x1B[7m${part}` : part)) | ||
.join(""); | ||
return NO_COLOR ? string : `${inverted}${RESET_COLOR}`; | ||
}; | ||
@@ -140,6 +146,6 @@ /** | ||
*/ | ||
const shortcut = (string, { pad = true, highlight = false } = {}) => | ||
dim(NO_COLOR && highlight ? "<" : "[") + | ||
bold(highlight ? invert(string) : string) + | ||
dim(NO_COLOR && highlight ? ">" : "]") + | ||
const shortcut = (string, { pad = true } = {}) => | ||
dim("[") + | ||
bold(string) + | ||
dim("]") + | ||
(pad ? " ".repeat(Math.max(0, KEYS.kill.length - string.length)) : ""); | ||
@@ -226,15 +232,14 @@ | ||
const drawDashboardCommandLines = (commands, width, cursorIndex) => { | ||
const lines = commands.map((command, index) => { | ||
const lines = commands.map((command) => { | ||
const [icon, status] = statusText(command.status, command.statusFromRules); | ||
return { | ||
label: shortcut(command.label || " ", { | ||
pad: false, | ||
highlight: index === cursorIndex, | ||
}), | ||
label: shortcut(command.label || " ", { pad: false }), | ||
icon, | ||
status, | ||
title: command.title, | ||
title: command.titleWithGraphicRenditions, | ||
}; | ||
}); | ||
const separator = " "; | ||
const widestStatus = Math.max( | ||
@@ -245,4 +250,3 @@ 0, | ||
return lines.map(({ label, icon, status, title }) => { | ||
const separator = " "; | ||
return lines.map(({ label, icon, status, title }, index) => { | ||
const start = truncate(`${label}${separator}${icon}`, width); | ||
@@ -260,6 +264,12 @@ const startLength = | ||
removeGraphicRenditions(truncatedEnd).length; | ||
const finalEnd = | ||
index === cursorIndex | ||
? NO_COLOR | ||
? `${separator.slice(0, -1)}→${truncatedEnd}` | ||
: `${separator}${invert(truncatedEnd)}` | ||
: `${separator}${truncatedEnd}`; | ||
return { | ||
line: `${start}${RESET_COLOR}${cursorHorizontalAbsolute( | ||
startLength + 1 | ||
)}${CLEAR_RIGHT}${separator}${truncatedEnd}${RESET_COLOR}`, | ||
)}${CLEAR_RIGHT}${finalEnd}${RESET_COLOR}`, | ||
length, | ||
@@ -316,4 +326,3 @@ }; | ||
const cwdText = (command) => | ||
path.resolve(command.cwd) === process.cwd() || | ||
command.cwd === removeGraphicRenditions(command.title) | ||
path.resolve(command.cwd) === process.cwd() || command.cwd === command.title | ||
? "" | ||
@@ -397,4 +406,7 @@ : `${folder}${EMOJI_WIDTH_FIX} ${dim(command.cwd)}\n`; | ||
// eslint-disable-next-line no-control-regex | ||
const GRAPHIC_RENDITIONS = /(\x1B\[(?:\d+(?:;\d+)*)?m)/g; | ||
const WINDOWS_HACK = IS_WINDOWS ? "\\0" : ""; | ||
const EMPTY_LAST_LINE = RegExp( | ||
`(?:^|[${WINDOWS_HACK}\\r\\n])(?:[^\\S\\r\\n]|${GRAPHIC_RENDITIONS.source})*$` | ||
); | ||
@@ -769,2 +781,3 @@ /** | ||
label: string, | ||
focusOnlyCommand: boolean, | ||
commandDescription: CommandDescription, | ||
@@ -777,2 +790,3 @@ onData: (data: string, statusFromRulesChanged: boolean) => undefined, | ||
label, | ||
focusOnlyCommand, | ||
commandDescription: { | ||
@@ -793,6 +807,9 @@ title, | ||
this.cwd = cwd; | ||
this.title = title; | ||
this.title = removeGraphicRenditions(title); | ||
this.titleWithGraphicRenditions = title; | ||
this.formattedCommandWithTitle = | ||
title === formattedCommand | ||
? formattedCommand | ||
: NO_COLOR | ||
? `${removeGraphicRenditions(title)}: ${formattedCommand}` | ||
: `${bold(`${title}${RESET_COLOR}:`)} ${formattedCommand}`; | ||
@@ -811,9 +828,10 @@ this.onData = onData; | ||
this.statusRules = statusRules; | ||
this.start(); | ||
this.start({ isFocused: focusOnlyCommand }); | ||
} | ||
/** | ||
* @param {{ isFocused: boolean }} options | ||
* @returns {void} | ||
*/ | ||
start() { | ||
start({ isFocused }) { | ||
if (this.status.tag !== "Exit") { | ||
@@ -836,2 +854,11 @@ throw new Error( | ||
"/c", | ||
...(!isFocused | ||
? [ | ||
"node", | ||
"-e", | ||
cmdEscapeArg("process.stdout.write('\\0')"), | ||
// "echo:", | ||
"&&", | ||
] | ||
: []), | ||
cmdEscapeMetaChars(this.file), | ||
@@ -850,5 +877,5 @@ ...this.args.map(cmdEscapeArg), | ||
if (IS_WINDOWS) { | ||
// Needed when using `conptyInheritCursor`. Otherwise the spawned | ||
// terminals hang and will not run their command. | ||
if (IS_WINDOWS && !isFocused) { | ||
// Needed when using `conptyInheritCursor`. Otherwise terminals spawned in | ||
// the background hang and will not run their command until focused. | ||
terminal.write("\x1B[1;1R"); | ||
@@ -1005,14 +1032,9 @@ } | ||
const maybeNewline = /[\r\n][^\S\r\n]*$/.test(command.history) ? "" : "\n"; | ||
const maybeNewline = EMPTY_LAST_LINE.test(command.history) ? "" : "\n"; | ||
switch (command.status.tag) { | ||
case "Running": | ||
if ( | ||
command.history.endsWith("\n") || | ||
command.history.endsWith(`\n${RESET_COLOR}`) | ||
) { | ||
if (command.history.endsWith("\n")) { | ||
process.stdout.write( | ||
RESET_COLOR + | ||
maybeNewline + | ||
runningText(command.status.terminal.pid) | ||
RESET_COLOR + runningText(command.status.terminal.pid) | ||
); | ||
@@ -1068,5 +1090,8 @@ } | ||
*/ | ||
const switchToCommand = (index) => { | ||
const switchToCommand = (index, { viaMouse = false } = {}) => { | ||
const command = commands[index]; | ||
current = { tag: "Command", index }; | ||
if (viaMouse) { | ||
cursorIndex = undefined; | ||
} | ||
printHistoryAndExtraText(command); | ||
@@ -1076,30 +1101,7 @@ }; | ||
/** | ||
* @param {number | undefined} index | ||
* @returns {void} | ||
*/ | ||
const switchToCommandAtCursor = () => { | ||
if (cursorIndex !== undefined) { | ||
const command = commands[cursorIndex]; | ||
current = { tag: "Command", index: cursorIndex }; | ||
printHistoryAndExtraText(command); | ||
} | ||
}; | ||
/** | ||
* @param {number} delta | ||
* @returns {void} | ||
*/ | ||
const moveCursor = (delta) => { | ||
if (cursorIndex === undefined) { | ||
cursorIndex = | ||
delta === 0 | ||
? undefined | ||
: delta > 0 | ||
? delta - 1 | ||
: commands.length + delta; | ||
} else { | ||
cursorIndex = (cursorIndex + delta) % commands.length; | ||
if (cursorIndex < 0) { | ||
cursorIndex = commands.length + cursorIndex; | ||
} | ||
} | ||
const setCursor = (index) => { | ||
cursorIndex = index; | ||
// Redraw dashboard. | ||
@@ -1129,2 +1131,4 @@ switchToDashboard(); | ||
const focusOnlyCommand = commandDescriptions.length === 1; | ||
/** @type {Array<Command>} */ | ||
@@ -1135,2 +1139,3 @@ const commands = commandDescriptions.map( | ||
label: ALL_LABELS[index] || "", | ||
focusOnlyCommand, | ||
commandDescription, | ||
@@ -1219,6 +1224,6 @@ onData: (data, statusFromRulesChanged) => { | ||
commands, | ||
cursorIndex, | ||
switchToDashboard, | ||
switchToCommand, | ||
switchToCommandAtCursor, | ||
moveCursor, | ||
setCursor, | ||
killAll, | ||
@@ -1257,3 +1262,3 @@ printHistoryAndExtraText | ||
if (commands.length === 1) { | ||
if (focusOnlyCommand) { | ||
switchToCommand(0); | ||
@@ -1269,6 +1274,6 @@ } else { | ||
* @param {Array<Command>} commands | ||
* @param {number | undefined} cursorIndex | ||
* @param {() => void} switchToDashboard | ||
* @param {(index: number) => void} switchToCommand | ||
* @param {() => void} switchToCommandAtCursor | ||
* @param {(delta: number) => void} moveCursor | ||
* @param {(index: number, options?: { viaMouse?: boolean }) => void} switchToCommand | ||
* @param {(index: number | undefined) => void} setCursor | ||
* @param {() => void} killAll | ||
@@ -1282,6 +1287,6 @@ * @param {(command: Command) => void} printHistoryAndExtraText | ||
commands, | ||
cursorIndex, | ||
switchToDashboard, | ||
switchToCommand, | ||
switchToCommandAtCursor, | ||
moveCursor, | ||
setCursor, | ||
killAll, | ||
@@ -1321,3 +1326,3 @@ printHistoryAndExtraText | ||
case KEY_CODES.restart: | ||
command.start(); | ||
command.start({ isFocused: true }); | ||
printHistoryAndExtraText(command); | ||
@@ -1340,3 +1345,5 @@ return undefined; | ||
case KEY_CODES.enterVim: | ||
switchToCommandAtCursor(); | ||
if (cursorIndex !== undefined) { | ||
switchToCommand(cursorIndex); | ||
} | ||
return undefined; | ||
@@ -1347,3 +1354,7 @@ | ||
case KEY_CODES.upVim: | ||
moveCursor(-1); | ||
setCursor( | ||
cursorIndex === undefined || cursorIndex === 0 | ||
? commands.length - 1 | ||
: cursorIndex - 1 | ||
); | ||
return undefined; | ||
@@ -1354,3 +1365,7 @@ | ||
case KEY_CODES.downVim: | ||
moveCursor(1); | ||
setCursor( | ||
cursorIndex === undefined || cursorIndex === commands.length - 1 | ||
? 0 | ||
: cursorIndex + 1 | ||
); | ||
return undefined; | ||
@@ -1367,21 +1382,28 @@ | ||
const mouseupPosition = parseMouseup(data); | ||
if (mouseupPosition !== undefined) { | ||
const { x, y } = mouseupPosition; | ||
const lines = drawDashboardCommandLines( | ||
commands, | ||
process.stdout.columns, | ||
undefined | ||
); | ||
if (y >= 0 && y < lines.length) { | ||
const max = Math.max( | ||
...lines.map((otherLine) => otherLine.length) | ||
); | ||
if (x >= 0 && x < max) { | ||
switchToCommand(y); | ||
const mouseupPosition = parseMouse(data); | ||
if (mouseupPosition === undefined) { | ||
return undefined; | ||
} | ||
const index = getCommandIndexFromMousePosition( | ||
commands, | ||
mouseupPosition | ||
); | ||
switch (mouseupPosition.type) { | ||
case "mousedown": | ||
if (index !== undefined) { | ||
setCursor(index); | ||
} | ||
return undefined; | ||
case "mouseup": { | ||
if (index !== undefined && index === cursorIndex) { | ||
switchToCommand(index, { viaMouse: true }); | ||
} else if (cursorIndex !== undefined) { | ||
setCursor(undefined); | ||
} | ||
return undefined; | ||
} | ||
} | ||
return undefined; | ||
} | ||
@@ -1392,10 +1414,9 @@ } | ||
// eslint-disable-next-line no-control-regex | ||
const MOUSEUP_REGEX = /\x1B\[<0;(\d+);(\d+)m/; | ||
const MOUSEUP_REGEX = /\x1B\[<0;(\d+);(\d+)([Mm])/; | ||
/** | ||
* @param {string} string | ||
* @returns {{ x: number, y: number } | undefined} | ||
* @returns {{ type: "mousedown" | "mouseup", x: number, y: number } | undefined} | ||
*/ | ||
const parseMouseup = (string) => { | ||
const parseMouse = (string) => { | ||
const match = MOUSEUP_REGEX.exec(string); | ||
@@ -1405,7 +1426,32 @@ if (match === null) { | ||
} | ||
const [, x, y] = match; | ||
return { x: Number(x) - 1, y: Number(y) - 1 }; | ||
const [, x, y, type] = match; | ||
return { | ||
type: type === "M" ? "mousedown" : "mouseup", | ||
x: Number(x) - 1, | ||
y: Number(y) - 1, | ||
}; | ||
}; | ||
/** | ||
* @param {Array<Command>} commands | ||
* @param {{ x: number, y: number }} mousePosition | ||
*/ | ||
const getCommandIndexFromMousePosition = (commands, { x, y }) => { | ||
const lines = drawDashboardCommandLines( | ||
commands, | ||
process.stdout.columns, | ||
undefined | ||
); | ||
if (y >= 0 && y < lines.length) { | ||
const line = lines[y]; | ||
if (x >= 0 && x < line.length) { | ||
return y; | ||
} | ||
} | ||
return undefined; | ||
}; | ||
/** | ||
* @returns {undefined} | ||
@@ -1412,0 +1458,0 @@ */ |
50854
1303