Skip to content
Draft
Show file tree
Hide file tree
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
17 changes: 16 additions & 1 deletion src/viewer/EditorPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,14 +693,29 @@ function footerRows(state: EditorState, columns: number): string[] {
const actions = renderActionRow(dialogControls(dialog), {columns: width, keys: true, color: colorLevel() !== 'none'}).rows;
const stale = dialog.kind === 'recover' && dialog.offer && draftIsStale(state, dialog.offer) ? 'The file changed on disk since this draft was saved. ' : '';
const detail = dialog.detail || stale ? [`${foreground(UI_COLORS.subtle)}${truncateText(inert(`${stale}${dialog.detail ?? ''}`.trim()), width)}${RESET}`] : [];
return [`${accent}${truncateText(message, width)}${RESET}`, ...detail, ...actions];
return [`${accent}${fitFileMessage(message, state.file.title, width)}${RESET}`, ...detail, ...actions];
}
if (state.input) return renderControlRows([['Enter', state.input.kind === 'saveas' ? 'save here' : 'find'], ['Esc', 'cancel']], width);
return renderControlRows([['Ctrl+S', 'save'], ['Esc', 'close'], ['Ctrl+Z', 'undo'], ['Ctrl+Y', 'redo'], ['Ctrl+F', 'find'], ['Ctrl+C', 'copy'], ['Ctrl+X', 'cut'],
...(isMarkdownFile(state) ? [['F1', 'Markdown']] as Array<[string, string]> : paletteFormat(state) ? [['F1', 'Snippets']] as Array<[string, string]> : []),
...(state.workspace ? [['F2', 'split']] as Array<[string, string]> : [])], width);
}

/**
* A dialog message that starts with the file's path keeps what it says when the path is long: the path gives way from
* the left, keeping its file name, so "has unsaved changes." or "changed on disk" is never the part that is cut.
*/
function fitFileMessage(message: string, title: string, width: number): string {
if (displayWidth(message) <= width || !title || !message.startsWith(title)) return truncateText(message, width);
const rest = message.slice(title.length);
const room = width - displayWidth(rest);
if (room < 8) return truncateText(message, width);
const parts = graphemes(title);
let kept = '';
for (let index = parts.length - 1; index >= 0 && displayWidth(`…${parts[index]}${kept}`) <= room; index -= 1) kept = parts[index] + kept;
return `…${kept}${rest}`;
}

export function renderEditor(state: EditorState, columns: number, height: number): string[] {
if (state.review) return renderReview(state, columns, height);
if (state.revealPending) { state.revealPending = false; reveal(state, columns, height); }
Expand Down
25 changes: 25 additions & 0 deletions tests/textEditor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,31 @@ test('panel: Esc with unsaved changes asks; Discard, Save and Keep editing do wh
} finally { rmSync(root, {recursive: true, force: true}); }
});

test('panel: a long file path gives way from the left, so the unsaved and on-disk questions stay readable', async () => {
const root = scratch();
try {
// Longer than an 80-column panel on its own, whatever the temp folder's length.
const folder = join(root, 'a-deliberately-long-folder-name-that-fills-the-panel-by-itself-alone');
mkdirSync(folder);
writeFileSync(join(folder, 'notes.txt'), 'b\n');
const state = createEditor(await opened(join(folder, 'notes.txt')));
assert.ok(displayWidth(state.file.title) > 80);
press(state, 'x');
press(state, {kind: 'escape'});
assert.equal(state.dialog?.kind, 'unsaved');
for (const columns of [80, 40]) {
const rows = renderEditor(state, columns, 24).map(row => stripAnsi(row));
assert.ok(rows.every(row => displayWidth(row) <= columns), `fits ${columns} columns`);
assert.ok(rows.some(row => /…[^\s]*\/notes\.txt has unsaved changes\.$/u.test(row.trimEnd())), `the question is whole at ${columns} columns`);
}
state.dialog = {kind: 'conflict', focus: 0, conflict: 'changed', reason: `${state.file.title} changed on disk since you opened it.`};
const rows = renderEditor(state, 80, 24).map(row => stripAnsi(row));
assert.ok(rows.some(row => /…[^\s]*\/notes\.txt changed on disk since you opened it\.$/u.test(row.trimEnd())));
state.dialog = {kind: 'conflict', focus: 0, conflict: 'changed'};
assert.ok(renderEditor(state, 80, 24).map(row => stripAnsi(row)).some(row => /…[^\s]*\/notes\.txt changed on disk\.$/u.test(row.trimEnd())));
} finally { rmSync(root, {recursive: true, force: true}); }
});

test('panel: hostile file content is drawn inert, the caret and selection are visible without color, and it fits narrow panels', async () => {
const root = scratch();
try {
Expand Down
Loading