diff --git a/README.md b/README.md index 96ae33e..3195af1 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ run the whole block in a single request — this matters for scripts like statement in one submission runs on the same database connection/session. Type `occ` to switch back to the normal occ-command mode. +Long values in result tables are cut to fit the terminal. Add `--full` to the +query (it is an SQL comment, so the database ignores it) to see them in full, +e.g. `SELECT configvalue FROM oc_appconfig WHERE configkey = 'x'; --full`. + Every statement that changes data or the schema (`DELETE`, `UPDATE`, `INSERT`, `DROP`, a data-modifying `WITH`, ...) asks for confirmation before the batch runs. There is still no undo — double check what you are about to diff --git a/js/index.js b/js/index.js index 236d619..8483e60 100644 --- a/js/index.js +++ b/js/index.js @@ -203,7 +203,10 @@ } // Tabular rendering of SELECT results, psql-style. - function renderTable(term, rows) { + // Unless full is set, the widest columns are narrowed until the table + // fits the terminal, so one long value doesn't wrap the whole table. + // Returns true when a value was cut. + function renderTable(term, rows, full) { if (!rows || !rows.length) { return; } @@ -234,9 +237,34 @@ }, col.length); }); + var MIN_WIDTH = 10; + var cut = false; + if (!full) { + var available = term.cols() - 1; + var total = function () { + return widths.reduce(function (sum, w) { return sum + w + 3; }, -1); + }; + while (total() > available) { + var widest = widths.indexOf(Math.max.apply(null, widths)); + if (widths[widest] <= MIN_WIDTH) { + break; + } + widths[widest] = Math.max(MIN_WIDTH, widths[widest] - (total() - available)); + } + } + + function fit(str, width) { + str = String(str); + if (str.length <= width) { + return str; + } + cut = true; + return str.slice(0, width - 1) + '\u2026'; + } + function formatRow(cells) { return cells.map(function (cell, i) { - return ' ' + pad(cell, widths[i]) + ' '; + return ' ' + pad(fit(cell, widths[i]), widths[i]) + ' '; }).join('|'); } @@ -248,9 +276,10 @@ }); term.echo($.terminal.escape_formatting(lines.join('\n'))); + return cut; } - function renderSqlResponse(term, response) { + function renderSqlResponse(term, response, full) { if (!response) { term.echo('[[;#ff5555;]Empty response from server]'); return; @@ -270,8 +299,8 @@ term.echo('[[;#ff5555;] Error: ]' + $.terminal.escape_formatting(r.error || 'unknown error')); } else if (r.type === 'select') { term.echo('[[;gray;] ' + r.count + ' row(s)]'); - if (r.count > 0) { - renderTable(term, r.data); + if (r.count > 0 && renderTable(term, r.data, full)) { + term.echo('[[;gray;] Long values are cut to fit the terminal. Add --full to the query to see them in full.]'); } if (r.truncated) { term.echo('[[;yellow;] Result truncated — showing only the first ' + r.count + ' rows, add LIMIT to see more precisely.]'); @@ -327,7 +356,8 @@ askDeleteConfirmation(term, sql, response.error); return; } - renderSqlResponse(term, response); + // "--full" is a plain SQL comment, so the database ignores it + renderSqlResponse(term, response, /(^|\s)-- ?full\b/.test(sql)); term.resume(); }).fail(function (xhr, status) { if (status === 'timeout') {