Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions static/js/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ async function executeCommands(dbid, pre, input, commands, animate) {
try {
const { replies } = await execute(commands, dbid);
for (const [i, command] of commands.entries()) {
const { error, value } = replies[i];
const { error, value, status } = replies[i];
try {
await writeLines(pre, input, command, error ? `(error) ${value}` : formatReply(value), animate, false);
await writeLines(pre, input, command, error ? `(error) ${value}` : formatReply(value, '', status), animate, false);
} catch (err) {
console.error(err);
await writeLines(pre, input, command, `(fatal error) ${err.message}`, animate);
Expand Down Expand Up @@ -239,14 +239,54 @@ async function execute(commands, dbid = '') {
return reply;
}

function formatReply(reply, indent = '') {
// Quote and escape a bulk string exactly like redis-cli's sdscatrepr: operate
// on the raw UTF-8 bytes, use the named escapes \\ " \n \r \t \a \b, leave
// printable ASCII (0x20-0x7e) literal, and emit \xHH (lowercase) for every
// other byte. This mirrors the native CLI byte-for-byte, e.g. a NUL renders as
// "\x00" (not JSON's "\u0000") and "é" as "\xc3\xa9".
function reprString(str) {
const bytes = new TextEncoder().encode(str);
let out = '"';
for (const b of bytes) {
switch (b) {
case 0x5c: out += '\\\\'; break;
case 0x22: out += '\\"'; break;
case 0x0a: out += '\\n'; break;
case 0x0d: out += '\\r'; break;
case 0x09: out += '\\t'; break;
case 0x07: out += '\\a'; break;
case 0x08: out += '\\b'; break;
default:
out += (b >= 0x20 && b <= 0x7e)
? String.fromCharCode(b)
: `\\x${b.toString(16).padStart(2, '0')}`;
}
}
return out + '"';
}

function formatReply(reply, indent = '', status = false) {
if (reply === null) {
return '(nil)';
}

// Out-of-range 64-bit integers are tagged by the backend (safe_json_integers)
// as {$int: "<decimal>"} so they survive JSON without precision loss and stay
// distinct from numeric bulk strings. Render them as a plain integer reply.
if (typeof reply === 'object' && !Array.isArray(reply)
&& typeof reply.$int === 'string') {
return `(integer) ${reply.$int}`;
}

const type = typeof reply;
if (type === 'string') {
return `"${reply}"`;
// RESP simple string / status reply (e.g. PONG, OK): rendered without quotes
if (status) {
return reply;
}
// Bulk string: quote and escape it byte-for-byte the way redis-cli does
// (e.g. a JSON.GET payload renders as "{\"a\":1}" and a NUL byte as "\x00").
return reprString(reply);
} else if (type === 'number') {
return `(integer) ${reply}`;
} else if (Array.isArray(reply)) {
Expand Down Expand Up @@ -343,4 +383,4 @@ document.addEventListener('DOMContentLoaded', () => {
for (const cli of document.querySelectorAll('form.redis-cli')) {
createCli(cli);
}
});
});
Loading