git-coco 0.62.2 → 0.62.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.mjs +111 -27
- package/dist/index.js +111 -27
- package/package.json +1 -1
package/dist/index.esm.mjs
CHANGED
|
@@ -61,7 +61,7 @@ import { pathToFileURL } from 'url';
|
|
|
61
61
|
/**
|
|
62
62
|
* Current build version from package.json
|
|
63
63
|
*/
|
|
64
|
-
const BUILD_VERSION = "0.62.
|
|
64
|
+
const BUILD_VERSION = "0.62.3";
|
|
65
65
|
|
|
66
66
|
const isInteractive = (config) => {
|
|
67
67
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -34351,7 +34351,13 @@ function sidebarTabLabel(tab) {
|
|
|
34351
34351
|
* Sliding window keeps the cursor in view as the user navigates a long
|
|
34352
34352
|
* list; truncation hints surface the count of hidden rows.
|
|
34353
34353
|
*/
|
|
34354
|
-
function renderSelectableSidebarRows(h, Text, items, selectedIndex, focused, width, theme, toRowText, keyPrefix, visibleCount
|
|
34354
|
+
function renderSelectableSidebarRows(h, Text, items, selectedIndex, focused, width, theme, toRowText, keyPrefix, visibleCount,
|
|
34355
|
+
// Optional per-row foreground colour (e.g. the current branch in
|
|
34356
|
+
// green). Applied only when the row is NOT the active selection —
|
|
34357
|
+
// the selection's inverse/background styling owns the row's colour in
|
|
34358
|
+
// that case, and layering a foreground under `inverse` would swap it
|
|
34359
|
+
// into an unexpected background tint.
|
|
34360
|
+
rowColor) {
|
|
34355
34361
|
if (items.length === 0)
|
|
34356
34362
|
return [];
|
|
34357
34363
|
const window = getSidebarVisibleWindow(items.length, selectedIndex, visibleCount);
|
|
@@ -34368,10 +34374,12 @@ function renderSelectableSidebarRows(h, Text, items, selectedIndex, focused, wid
|
|
|
34368
34374
|
break;
|
|
34369
34375
|
const isSelected = focused && index === selectedIndex;
|
|
34370
34376
|
const text = toRowText(items[index], index);
|
|
34377
|
+
const color = isSelected ? undefined : rowColor?.(items[index], index);
|
|
34371
34378
|
elements.push(h(Text, {
|
|
34372
34379
|
key: `${keyPrefix}-row-${index}`,
|
|
34373
34380
|
backgroundColor: isSelected && !theme.noColor ? theme.colors.selection : undefined,
|
|
34374
34381
|
inverse: isSelected,
|
|
34382
|
+
color,
|
|
34375
34383
|
}, truncateCells(` ${text}`, width - 4)));
|
|
34376
34384
|
}
|
|
34377
34385
|
if (window.truncatedBelow > 0) {
|
|
@@ -34481,7 +34489,11 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34481
34489
|
? spin
|
|
34482
34490
|
: branchRowMarker(branch, { ascii: theme.ascii }).glyph;
|
|
34483
34491
|
return `${glyph} ${branch.shortName}`;
|
|
34484
|
-
}, 'tab-branches', visibleListCount
|
|
34492
|
+
}, 'tab-branches', visibleListCount,
|
|
34493
|
+
// Paint the checked-out branch green so "where am I?" reads at a
|
|
34494
|
+
// glance, matching the green HEAD marker the branches surface
|
|
34495
|
+
// already uses. NO_COLOR themes fall back to the `*` glyph alone.
|
|
34496
|
+
(branch) => (branch.current && !theme.noColor ? theme.colors.success : undefined)),
|
|
34485
34497
|
];
|
|
34486
34498
|
}
|
|
34487
34499
|
if (tab === 'tags') {
|
|
@@ -34982,11 +34994,16 @@ function renderBranchesSurface(ctx, spinnerFrame = 0) {
|
|
|
34982
34994
|
const truncated = truncateCells(fullText, Math.max(20, width - 4));
|
|
34983
34995
|
// If truncation chopped into the timestamp/divergence portion,
|
|
34984
34996
|
// fall back to a single Text to keep the visible width honest.
|
|
34997
|
+
// The checked-out branch is painted green (matching its green
|
|
34998
|
+
// HEAD marker) so "where am I?" reads at a glance. NO_COLOR
|
|
34999
|
+
// themes fall back to the `*` glyph alone.
|
|
35000
|
+
const currentColor = branch.current && !theme.noColor ? theme.colors.success : undefined;
|
|
34985
35001
|
if (truncated !== fullText) {
|
|
34986
35002
|
return h(Text, {
|
|
34987
35003
|
key: `branch-${index}`,
|
|
34988
35004
|
bold: isSelected,
|
|
34989
35005
|
dimColor: lineDim,
|
|
35006
|
+
color: currentColor,
|
|
34990
35007
|
}, truncated);
|
|
34991
35008
|
}
|
|
34992
35009
|
return h(Text, {
|
|
@@ -35001,7 +35018,12 @@ function renderBranchesSurface(ctx, spinnerFrame = 0) {
|
|
|
35001
35018
|
// no-upstream kinds return undefined from
|
|
35002
35019
|
// `getBranchRowMarkerColor`, so those markers inherit the
|
|
35003
35020
|
// row's dim and read as quiet chrome.
|
|
35004
|
-
h(Text, { color: glyphColor, dimColor: glyphColor ? false : undefined }, glyph),
|
|
35021
|
+
h(Text, { color: glyphColor, dimColor: glyphColor ? false : undefined }, glyph),
|
|
35022
|
+
// Name span: green for the current branch (dimColor:false keeps
|
|
35023
|
+
// it bright), otherwise it inherits the row's normal styling.
|
|
35024
|
+
currentColor
|
|
35025
|
+
? h(Text, { color: currentColor, dimColor: false }, trailingName)
|
|
35026
|
+
: trailingName, h(Text, { dimColor: true }, timestampPadded), trailingDivergence);
|
|
35005
35027
|
});
|
|
35006
35028
|
// Scroll indicators — same "N more above/below" pattern as the
|
|
35007
35029
|
// sidebar and help overlay so the user knows the list continues.
|
|
@@ -38800,6 +38822,15 @@ function renderReflogSurface(ctx) {
|
|
|
38800
38822
|
* Extracted from `src/commands/log/inkRuntime.ts` as part of phase 5a.1
|
|
38801
38823
|
* of #890. No behavior change.
|
|
38802
38824
|
*/
|
|
38825
|
+
const GAP = 2; // cells between columns
|
|
38826
|
+
/** Truncate to `w` cells, then pad to `w` (left = padEnd, right = padStart). */
|
|
38827
|
+
function cell(value, w, align = 'left') {
|
|
38828
|
+
const t = truncateCells(value, w);
|
|
38829
|
+
// padStart/padEnd count code units; refs / ages / counts / branch
|
|
38830
|
+
// names are ASCII in practice, matching the branches surface's
|
|
38831
|
+
// padEnd-based column alignment.
|
|
38832
|
+
return align === 'right' ? t.padStart(w) : t.padEnd(w);
|
|
38833
|
+
}
|
|
38803
38834
|
function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
38804
38835
|
const { h, components, state, context, contextStatus, bodyRows, width, theme } = ctx;
|
|
38805
38836
|
const { Box, Text } = components;
|
|
@@ -38810,7 +38841,9 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38810
38841
|
? allStashes.filter((stash) => matchesPromotedFilter([stash.ref, stash.message], state.filter))
|
|
38811
38842
|
: allStashes;
|
|
38812
38843
|
const selected = Math.max(0, Math.min(state.selectedStashIndex, Math.max(0, stashes.length - 1)));
|
|
38813
|
-
|
|
38844
|
+
// One extra row reserved (vs the other surfaces' `- 4`) for the column
|
|
38845
|
+
// header row below.
|
|
38846
|
+
const listRows = Math.max(4, bodyRows - 5);
|
|
38814
38847
|
const startIndex = Math.max(0, selected - Math.floor(listRows / 2));
|
|
38815
38848
|
const visible = stashes.slice(startIndex, startIndex + listRows);
|
|
38816
38849
|
const filterLabel = state.filter ? ` | filter: ${state.filter}` : '';
|
|
@@ -38820,10 +38853,67 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38820
38853
|
const emptyLabel = formatLogInkStashEmpty({ filter: state.filter });
|
|
38821
38854
|
const loadingLabel = formatLogInkLoading({ resource: 'stashes' });
|
|
38822
38855
|
const now = getRenderNow();
|
|
38823
|
-
//
|
|
38824
|
-
//
|
|
38825
|
-
//
|
|
38826
|
-
|
|
38856
|
+
// Usable interior width: panel width minus the border (2) and the
|
|
38857
|
+
// paddingX:1 (2). The previous `width - 2` under-counted the border
|
|
38858
|
+
// and made every near-full row overflow by 2 cells and wrap — the
|
|
38859
|
+
// single biggest readability hit in the old list.
|
|
38860
|
+
const contentWidth = Math.max(20, width - 4);
|
|
38861
|
+
const ageOf = (s) => formatCompactRelativeDate(s.date, now);
|
|
38862
|
+
// Column widths derived from the visible window (#833 pattern) so rows
|
|
38863
|
+
// align without re-measuring the whole list. Each column is at least
|
|
38864
|
+
// as wide as its header label so the header never truncates ("age",
|
|
38865
|
+
// "branch", "files"); age tops out at 5 ("today" is the longest value
|
|
38866
|
+
// formatCompactRelativeDate emits).
|
|
38867
|
+
const refCol = visible.length
|
|
38868
|
+
? Math.min(11, Math.max(3, ...visible.map((s) => cellWidth(s.ref))))
|
|
38869
|
+
: 9;
|
|
38870
|
+
const ageCol = visible.length
|
|
38871
|
+
? Math.min(5, Math.max(3, ...visible.map((s) => cellWidth(ageOf(s)))))
|
|
38872
|
+
: 3;
|
|
38873
|
+
const branchColMax = visible.length
|
|
38874
|
+
? Math.max(0, ...visible.map((s) => cellWidth(s.branch || '')))
|
|
38875
|
+
: 0;
|
|
38876
|
+
const filesCol = visible.length
|
|
38877
|
+
? Math.max(5, ...visible.map((s) => String(s.files.length).length))
|
|
38878
|
+
: 5;
|
|
38879
|
+
// Responsive degradation. Keep ref + message always; when the message
|
|
38880
|
+
// floor is threatened, shed columns the preview pane already shows —
|
|
38881
|
+
// branch first, then age, then the file count — before squeezing the
|
|
38882
|
+
// message.
|
|
38883
|
+
// Widen to fit the "branch" header when any stash carries a branch;
|
|
38884
|
+
// stays 0 (column dropped) when none do.
|
|
38885
|
+
let branchCol = branchColMax > 0 ? Math.min(18, Math.max(6, branchColMax)) : 0;
|
|
38886
|
+
let showAge = true;
|
|
38887
|
+
let showFiles = true;
|
|
38888
|
+
const fixedWidth = () => 2 + refCol +
|
|
38889
|
+
(showAge ? GAP + ageCol : 0) +
|
|
38890
|
+
(branchCol > 0 ? GAP + branchCol : 0) +
|
|
38891
|
+
(showFiles ? GAP + filesCol : 0) +
|
|
38892
|
+
GAP; // gap before the message column
|
|
38893
|
+
let messageWidth = contentWidth - fixedWidth();
|
|
38894
|
+
if (messageWidth < 24 && branchCol > 0) {
|
|
38895
|
+
branchCol = 0;
|
|
38896
|
+
messageWidth = contentWidth - fixedWidth();
|
|
38897
|
+
}
|
|
38898
|
+
if (messageWidth < 16 && showAge) {
|
|
38899
|
+
showAge = false;
|
|
38900
|
+
messageWidth = contentWidth - fixedWidth();
|
|
38901
|
+
}
|
|
38902
|
+
if (messageWidth < 12 && showFiles) {
|
|
38903
|
+
showFiles = false;
|
|
38904
|
+
messageWidth = contentWidth - fixedWidth();
|
|
38905
|
+
}
|
|
38906
|
+
messageWidth = Math.max(8, messageWidth);
|
|
38907
|
+
// Column header. Right-aligned labels over the right-aligned numeric
|
|
38908
|
+
// columns (age, files) so header and data share an edge.
|
|
38909
|
+
let headerText = ` ${cell('ref', refCol)}`;
|
|
38910
|
+
if (showAge)
|
|
38911
|
+
headerText += `${' '.repeat(GAP)}${cell('age', ageCol, 'right')}`;
|
|
38912
|
+
if (branchCol > 0)
|
|
38913
|
+
headerText += `${' '.repeat(GAP)}${cell('branch', branchCol)}`;
|
|
38914
|
+
if (showFiles)
|
|
38915
|
+
headerText += `${' '.repeat(GAP)}${cell('files', filesCol, 'right')}`;
|
|
38916
|
+
headerText += `${' '.repeat(GAP)}message`;
|
|
38827
38917
|
const lines = loading
|
|
38828
38918
|
? [h(Text, { key: 'stash-loading', dimColor: true }, loadingLabel)]
|
|
38829
38919
|
: stashes.length === 0
|
|
@@ -38832,32 +38922,22 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38832
38922
|
const index = startIndex + offset;
|
|
38833
38923
|
const isSelected = index === selected;
|
|
38834
38924
|
const cursor = isSelected ? '>' : ' ';
|
|
38835
|
-
|
|
38836
|
-
// branch, file count, and relative age — between the ref and the
|
|
38837
|
-
// message, so the list answers "which stash is this?" without an
|
|
38838
|
-
// Enter→diff round trip.
|
|
38839
|
-
const age = formatCompactRelativeDate(stash.date, now);
|
|
38840
|
-
const fileCount = stash.files.length;
|
|
38841
|
-
const meta = [
|
|
38842
|
-
stash.branch ? `on ${stash.branch}` : '',
|
|
38843
|
-
fileCount > 0 ? `${fileCount} file${fileCount === 1 ? '' : 's'}` : '',
|
|
38844
|
-
age,
|
|
38845
|
-
].filter(Boolean).join(' · ');
|
|
38846
|
-
const rowText = meta
|
|
38847
|
-
? `${cursor} ${stash.ref.padEnd(11)} ${meta} ${stash.message}`
|
|
38848
|
-
: `${cursor} ${stash.ref.padEnd(11)} ${stash.message}`;
|
|
38925
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'stash', stash.ref);
|
|
38849
38926
|
// The `stash@{N}` ref is an identifier, not a status icon, so a
|
|
38850
38927
|
// delete-in-flight appends an accent spinner at the row's end
|
|
38851
|
-
// (2 cells reserved from the
|
|
38852
|
-
const deleting = isPendingItemAction(state.pendingItemAction, 'stash', stash.ref);
|
|
38928
|
+
// (2 cells reserved from the message budget).
|
|
38853
38929
|
const spinnerSpan = deleting
|
|
38854
38930
|
? h(Text, { color: theme.noColor ? undefined : theme.colors.accent, dimColor: false }, ` ${inlineSpinnerGlyph(spinnerFrame, theme.ascii)}`)
|
|
38855
38931
|
: null;
|
|
38932
|
+
const message = truncateCells(stash.message, messageWidth - (deleting ? 2 : 0));
|
|
38933
|
+
// ref + message read as primary; age / branch / file-count are
|
|
38934
|
+
// dim metadata (kept dim even on the bold selected row so the
|
|
38935
|
+
// message stays the focal point).
|
|
38856
38936
|
return h(Text, {
|
|
38857
38937
|
key: `stash-${index}`,
|
|
38858
38938
|
bold: isSelected,
|
|
38859
38939
|
dimColor: !isSelected,
|
|
38860
|
-
},
|
|
38940
|
+
}, `${cursor} `, cell(stash.ref, refCol), showAge ? h(Text, { dimColor: true }, `${' '.repeat(GAP)}${cell(ageOf(stash), ageCol, 'right')}`) : null, branchCol > 0 ? h(Text, { dimColor: true }, `${' '.repeat(GAP)}${cell(stash.branch || '', branchCol)}`) : null, showFiles ? h(Text, { dimColor: true }, `${' '.repeat(GAP)}${cell(String(stash.files.length), filesCol, 'right')}`) : null, `${' '.repeat(GAP)}${message}`, spinnerSpan);
|
|
38861
38941
|
});
|
|
38862
38942
|
const stashHasMoreAbove = startIndex > 0 && stashes.length > 0;
|
|
38863
38943
|
const stashHasMoreBelow = startIndex + listRows < stashes.length;
|
|
@@ -38868,7 +38948,11 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38868
38948
|
flexShrink: 0,
|
|
38869
38949
|
paddingX: 1,
|
|
38870
38950
|
width,
|
|
38871
|
-
}, h(Box, { justifyContent: 'space-between' }, h(Text, { bold: true }, panelTitle('Stash', focused)), h(Text, { dimColor: true }, headerRight)), ...renderPromotedFilterAffordance(h, Text, state, theme),
|
|
38951
|
+
}, h(Box, { justifyContent: 'space-between' }, h(Text, { bold: true }, panelTitle('Stash', focused)), h(Text, { dimColor: true }, headerRight)), ...renderPromotedFilterAffordance(h, Text, state, theme),
|
|
38952
|
+
// Column header — only when there are rows to label.
|
|
38953
|
+
...(!loading && stashes.length > 0
|
|
38954
|
+
? [h(Text, { key: 'stash-col-header', dimColor: true }, truncateCells(headerText, contentWidth))]
|
|
38955
|
+
: []), ...(stashHasMoreAbove
|
|
38872
38956
|
? [h(Text, { key: 'stash-more-above', dimColor: true }, ` ↑ ${startIndex} more above`)]
|
|
38873
38957
|
: []), ...lines, ...(stashHasMoreBelow
|
|
38874
38958
|
? [h(Text, { key: 'stash-more-below', dimColor: true }, ` ↓ ${stashes.length - (startIndex + listRows)} more below`)]
|
package/dist/index.js
CHANGED
|
@@ -78,7 +78,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
|
78
78
|
/**
|
|
79
79
|
* Current build version from package.json
|
|
80
80
|
*/
|
|
81
|
-
const BUILD_VERSION = "0.62.
|
|
81
|
+
const BUILD_VERSION = "0.62.3";
|
|
82
82
|
|
|
83
83
|
const isInteractive = (config) => {
|
|
84
84
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -34368,7 +34368,13 @@ function sidebarTabLabel(tab) {
|
|
|
34368
34368
|
* Sliding window keeps the cursor in view as the user navigates a long
|
|
34369
34369
|
* list; truncation hints surface the count of hidden rows.
|
|
34370
34370
|
*/
|
|
34371
|
-
function renderSelectableSidebarRows(h, Text, items, selectedIndex, focused, width, theme, toRowText, keyPrefix, visibleCount
|
|
34371
|
+
function renderSelectableSidebarRows(h, Text, items, selectedIndex, focused, width, theme, toRowText, keyPrefix, visibleCount,
|
|
34372
|
+
// Optional per-row foreground colour (e.g. the current branch in
|
|
34373
|
+
// green). Applied only when the row is NOT the active selection —
|
|
34374
|
+
// the selection's inverse/background styling owns the row's colour in
|
|
34375
|
+
// that case, and layering a foreground under `inverse` would swap it
|
|
34376
|
+
// into an unexpected background tint.
|
|
34377
|
+
rowColor) {
|
|
34372
34378
|
if (items.length === 0)
|
|
34373
34379
|
return [];
|
|
34374
34380
|
const window = getSidebarVisibleWindow(items.length, selectedIndex, visibleCount);
|
|
@@ -34385,10 +34391,12 @@ function renderSelectableSidebarRows(h, Text, items, selectedIndex, focused, wid
|
|
|
34385
34391
|
break;
|
|
34386
34392
|
const isSelected = focused && index === selectedIndex;
|
|
34387
34393
|
const text = toRowText(items[index], index);
|
|
34394
|
+
const color = isSelected ? undefined : rowColor?.(items[index], index);
|
|
34388
34395
|
elements.push(h(Text, {
|
|
34389
34396
|
key: `${keyPrefix}-row-${index}`,
|
|
34390
34397
|
backgroundColor: isSelected && !theme.noColor ? theme.colors.selection : undefined,
|
|
34391
34398
|
inverse: isSelected,
|
|
34399
|
+
color,
|
|
34392
34400
|
}, truncateCells(` ${text}`, width - 4)));
|
|
34393
34401
|
}
|
|
34394
34402
|
if (window.truncatedBelow > 0) {
|
|
@@ -34498,7 +34506,11 @@ function renderActiveSidebarContent(h, Text, tab, state, context, contextStatus,
|
|
|
34498
34506
|
? spin
|
|
34499
34507
|
: branchRowMarker(branch, { ascii: theme.ascii }).glyph;
|
|
34500
34508
|
return `${glyph} ${branch.shortName}`;
|
|
34501
|
-
}, 'tab-branches', visibleListCount
|
|
34509
|
+
}, 'tab-branches', visibleListCount,
|
|
34510
|
+
// Paint the checked-out branch green so "where am I?" reads at a
|
|
34511
|
+
// glance, matching the green HEAD marker the branches surface
|
|
34512
|
+
// already uses. NO_COLOR themes fall back to the `*` glyph alone.
|
|
34513
|
+
(branch) => (branch.current && !theme.noColor ? theme.colors.success : undefined)),
|
|
34502
34514
|
];
|
|
34503
34515
|
}
|
|
34504
34516
|
if (tab === 'tags') {
|
|
@@ -34999,11 +35011,16 @@ function renderBranchesSurface(ctx, spinnerFrame = 0) {
|
|
|
34999
35011
|
const truncated = truncateCells(fullText, Math.max(20, width - 4));
|
|
35000
35012
|
// If truncation chopped into the timestamp/divergence portion,
|
|
35001
35013
|
// fall back to a single Text to keep the visible width honest.
|
|
35014
|
+
// The checked-out branch is painted green (matching its green
|
|
35015
|
+
// HEAD marker) so "where am I?" reads at a glance. NO_COLOR
|
|
35016
|
+
// themes fall back to the `*` glyph alone.
|
|
35017
|
+
const currentColor = branch.current && !theme.noColor ? theme.colors.success : undefined;
|
|
35002
35018
|
if (truncated !== fullText) {
|
|
35003
35019
|
return h(Text, {
|
|
35004
35020
|
key: `branch-${index}`,
|
|
35005
35021
|
bold: isSelected,
|
|
35006
35022
|
dimColor: lineDim,
|
|
35023
|
+
color: currentColor,
|
|
35007
35024
|
}, truncated);
|
|
35008
35025
|
}
|
|
35009
35026
|
return h(Text, {
|
|
@@ -35018,7 +35035,12 @@ function renderBranchesSurface(ctx, spinnerFrame = 0) {
|
|
|
35018
35035
|
// no-upstream kinds return undefined from
|
|
35019
35036
|
// `getBranchRowMarkerColor`, so those markers inherit the
|
|
35020
35037
|
// row's dim and read as quiet chrome.
|
|
35021
|
-
h(Text, { color: glyphColor, dimColor: glyphColor ? false : undefined }, glyph),
|
|
35038
|
+
h(Text, { color: glyphColor, dimColor: glyphColor ? false : undefined }, glyph),
|
|
35039
|
+
// Name span: green for the current branch (dimColor:false keeps
|
|
35040
|
+
// it bright), otherwise it inherits the row's normal styling.
|
|
35041
|
+
currentColor
|
|
35042
|
+
? h(Text, { color: currentColor, dimColor: false }, trailingName)
|
|
35043
|
+
: trailingName, h(Text, { dimColor: true }, timestampPadded), trailingDivergence);
|
|
35022
35044
|
});
|
|
35023
35045
|
// Scroll indicators — same "N more above/below" pattern as the
|
|
35024
35046
|
// sidebar and help overlay so the user knows the list continues.
|
|
@@ -38817,6 +38839,15 @@ function renderReflogSurface(ctx) {
|
|
|
38817
38839
|
* Extracted from `src/commands/log/inkRuntime.ts` as part of phase 5a.1
|
|
38818
38840
|
* of #890. No behavior change.
|
|
38819
38841
|
*/
|
|
38842
|
+
const GAP = 2; // cells between columns
|
|
38843
|
+
/** Truncate to `w` cells, then pad to `w` (left = padEnd, right = padStart). */
|
|
38844
|
+
function cell(value, w, align = 'left') {
|
|
38845
|
+
const t = truncateCells(value, w);
|
|
38846
|
+
// padStart/padEnd count code units; refs / ages / counts / branch
|
|
38847
|
+
// names are ASCII in practice, matching the branches surface's
|
|
38848
|
+
// padEnd-based column alignment.
|
|
38849
|
+
return align === 'right' ? t.padStart(w) : t.padEnd(w);
|
|
38850
|
+
}
|
|
38820
38851
|
function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
38821
38852
|
const { h, components, state, context, contextStatus, bodyRows, width, theme } = ctx;
|
|
38822
38853
|
const { Box, Text } = components;
|
|
@@ -38827,7 +38858,9 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38827
38858
|
? allStashes.filter((stash) => matchesPromotedFilter([stash.ref, stash.message], state.filter))
|
|
38828
38859
|
: allStashes;
|
|
38829
38860
|
const selected = Math.max(0, Math.min(state.selectedStashIndex, Math.max(0, stashes.length - 1)));
|
|
38830
|
-
|
|
38861
|
+
// One extra row reserved (vs the other surfaces' `- 4`) for the column
|
|
38862
|
+
// header row below.
|
|
38863
|
+
const listRows = Math.max(4, bodyRows - 5);
|
|
38831
38864
|
const startIndex = Math.max(0, selected - Math.floor(listRows / 2));
|
|
38832
38865
|
const visible = stashes.slice(startIndex, startIndex + listRows);
|
|
38833
38866
|
const filterLabel = state.filter ? ` | filter: ${state.filter}` : '';
|
|
@@ -38837,10 +38870,67 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38837
38870
|
const emptyLabel = formatLogInkStashEmpty({ filter: state.filter });
|
|
38838
38871
|
const loadingLabel = formatLogInkLoading({ resource: 'stashes' });
|
|
38839
38872
|
const now = getRenderNow();
|
|
38840
|
-
//
|
|
38841
|
-
//
|
|
38842
|
-
//
|
|
38843
|
-
|
|
38873
|
+
// Usable interior width: panel width minus the border (2) and the
|
|
38874
|
+
// paddingX:1 (2). The previous `width - 2` under-counted the border
|
|
38875
|
+
// and made every near-full row overflow by 2 cells and wrap — the
|
|
38876
|
+
// single biggest readability hit in the old list.
|
|
38877
|
+
const contentWidth = Math.max(20, width - 4);
|
|
38878
|
+
const ageOf = (s) => formatCompactRelativeDate(s.date, now);
|
|
38879
|
+
// Column widths derived from the visible window (#833 pattern) so rows
|
|
38880
|
+
// align without re-measuring the whole list. Each column is at least
|
|
38881
|
+
// as wide as its header label so the header never truncates ("age",
|
|
38882
|
+
// "branch", "files"); age tops out at 5 ("today" is the longest value
|
|
38883
|
+
// formatCompactRelativeDate emits).
|
|
38884
|
+
const refCol = visible.length
|
|
38885
|
+
? Math.min(11, Math.max(3, ...visible.map((s) => cellWidth(s.ref))))
|
|
38886
|
+
: 9;
|
|
38887
|
+
const ageCol = visible.length
|
|
38888
|
+
? Math.min(5, Math.max(3, ...visible.map((s) => cellWidth(ageOf(s)))))
|
|
38889
|
+
: 3;
|
|
38890
|
+
const branchColMax = visible.length
|
|
38891
|
+
? Math.max(0, ...visible.map((s) => cellWidth(s.branch || '')))
|
|
38892
|
+
: 0;
|
|
38893
|
+
const filesCol = visible.length
|
|
38894
|
+
? Math.max(5, ...visible.map((s) => String(s.files.length).length))
|
|
38895
|
+
: 5;
|
|
38896
|
+
// Responsive degradation. Keep ref + message always; when the message
|
|
38897
|
+
// floor is threatened, shed columns the preview pane already shows —
|
|
38898
|
+
// branch first, then age, then the file count — before squeezing the
|
|
38899
|
+
// message.
|
|
38900
|
+
// Widen to fit the "branch" header when any stash carries a branch;
|
|
38901
|
+
// stays 0 (column dropped) when none do.
|
|
38902
|
+
let branchCol = branchColMax > 0 ? Math.min(18, Math.max(6, branchColMax)) : 0;
|
|
38903
|
+
let showAge = true;
|
|
38904
|
+
let showFiles = true;
|
|
38905
|
+
const fixedWidth = () => 2 + refCol +
|
|
38906
|
+
(showAge ? GAP + ageCol : 0) +
|
|
38907
|
+
(branchCol > 0 ? GAP + branchCol : 0) +
|
|
38908
|
+
(showFiles ? GAP + filesCol : 0) +
|
|
38909
|
+
GAP; // gap before the message column
|
|
38910
|
+
let messageWidth = contentWidth - fixedWidth();
|
|
38911
|
+
if (messageWidth < 24 && branchCol > 0) {
|
|
38912
|
+
branchCol = 0;
|
|
38913
|
+
messageWidth = contentWidth - fixedWidth();
|
|
38914
|
+
}
|
|
38915
|
+
if (messageWidth < 16 && showAge) {
|
|
38916
|
+
showAge = false;
|
|
38917
|
+
messageWidth = contentWidth - fixedWidth();
|
|
38918
|
+
}
|
|
38919
|
+
if (messageWidth < 12 && showFiles) {
|
|
38920
|
+
showFiles = false;
|
|
38921
|
+
messageWidth = contentWidth - fixedWidth();
|
|
38922
|
+
}
|
|
38923
|
+
messageWidth = Math.max(8, messageWidth);
|
|
38924
|
+
// Column header. Right-aligned labels over the right-aligned numeric
|
|
38925
|
+
// columns (age, files) so header and data share an edge.
|
|
38926
|
+
let headerText = ` ${cell('ref', refCol)}`;
|
|
38927
|
+
if (showAge)
|
|
38928
|
+
headerText += `${' '.repeat(GAP)}${cell('age', ageCol, 'right')}`;
|
|
38929
|
+
if (branchCol > 0)
|
|
38930
|
+
headerText += `${' '.repeat(GAP)}${cell('branch', branchCol)}`;
|
|
38931
|
+
if (showFiles)
|
|
38932
|
+
headerText += `${' '.repeat(GAP)}${cell('files', filesCol, 'right')}`;
|
|
38933
|
+
headerText += `${' '.repeat(GAP)}message`;
|
|
38844
38934
|
const lines = loading
|
|
38845
38935
|
? [h(Text, { key: 'stash-loading', dimColor: true }, loadingLabel)]
|
|
38846
38936
|
: stashes.length === 0
|
|
@@ -38849,32 +38939,22 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38849
38939
|
const index = startIndex + offset;
|
|
38850
38940
|
const isSelected = index === selected;
|
|
38851
38941
|
const cursor = isSelected ? '>' : ' ';
|
|
38852
|
-
|
|
38853
|
-
// branch, file count, and relative age — between the ref and the
|
|
38854
|
-
// message, so the list answers "which stash is this?" without an
|
|
38855
|
-
// Enter→diff round trip.
|
|
38856
|
-
const age = formatCompactRelativeDate(stash.date, now);
|
|
38857
|
-
const fileCount = stash.files.length;
|
|
38858
|
-
const meta = [
|
|
38859
|
-
stash.branch ? `on ${stash.branch}` : '',
|
|
38860
|
-
fileCount > 0 ? `${fileCount} file${fileCount === 1 ? '' : 's'}` : '',
|
|
38861
|
-
age,
|
|
38862
|
-
].filter(Boolean).join(' · ');
|
|
38863
|
-
const rowText = meta
|
|
38864
|
-
? `${cursor} ${stash.ref.padEnd(11)} ${meta} ${stash.message}`
|
|
38865
|
-
: `${cursor} ${stash.ref.padEnd(11)} ${stash.message}`;
|
|
38942
|
+
const deleting = isPendingItemAction(state.pendingItemAction, 'stash', stash.ref);
|
|
38866
38943
|
// The `stash@{N}` ref is an identifier, not a status icon, so a
|
|
38867
38944
|
// delete-in-flight appends an accent spinner at the row's end
|
|
38868
|
-
// (2 cells reserved from the
|
|
38869
|
-
const deleting = isPendingItemAction(state.pendingItemAction, 'stash', stash.ref);
|
|
38945
|
+
// (2 cells reserved from the message budget).
|
|
38870
38946
|
const spinnerSpan = deleting
|
|
38871
38947
|
? h(Text, { color: theme.noColor ? undefined : theme.colors.accent, dimColor: false }, ` ${inlineSpinnerGlyph(spinnerFrame, theme.ascii)}`)
|
|
38872
38948
|
: null;
|
|
38949
|
+
const message = truncateCells(stash.message, messageWidth - (deleting ? 2 : 0));
|
|
38950
|
+
// ref + message read as primary; age / branch / file-count are
|
|
38951
|
+
// dim metadata (kept dim even on the bold selected row so the
|
|
38952
|
+
// message stays the focal point).
|
|
38873
38953
|
return h(Text, {
|
|
38874
38954
|
key: `stash-${index}`,
|
|
38875
38955
|
bold: isSelected,
|
|
38876
38956
|
dimColor: !isSelected,
|
|
38877
|
-
},
|
|
38957
|
+
}, `${cursor} `, cell(stash.ref, refCol), showAge ? h(Text, { dimColor: true }, `${' '.repeat(GAP)}${cell(ageOf(stash), ageCol, 'right')}`) : null, branchCol > 0 ? h(Text, { dimColor: true }, `${' '.repeat(GAP)}${cell(stash.branch || '', branchCol)}`) : null, showFiles ? h(Text, { dimColor: true }, `${' '.repeat(GAP)}${cell(String(stash.files.length), filesCol, 'right')}`) : null, `${' '.repeat(GAP)}${message}`, spinnerSpan);
|
|
38878
38958
|
});
|
|
38879
38959
|
const stashHasMoreAbove = startIndex > 0 && stashes.length > 0;
|
|
38880
38960
|
const stashHasMoreBelow = startIndex + listRows < stashes.length;
|
|
@@ -38885,7 +38965,11 @@ function renderStashSurface(ctx, spinnerFrame = 0) {
|
|
|
38885
38965
|
flexShrink: 0,
|
|
38886
38966
|
paddingX: 1,
|
|
38887
38967
|
width,
|
|
38888
|
-
}, h(Box, { justifyContent: 'space-between' }, h(Text, { bold: true }, panelTitle('Stash', focused)), h(Text, { dimColor: true }, headerRight)), ...renderPromotedFilterAffordance(h, Text, state, theme),
|
|
38968
|
+
}, h(Box, { justifyContent: 'space-between' }, h(Text, { bold: true }, panelTitle('Stash', focused)), h(Text, { dimColor: true }, headerRight)), ...renderPromotedFilterAffordance(h, Text, state, theme),
|
|
38969
|
+
// Column header — only when there are rows to label.
|
|
38970
|
+
...(!loading && stashes.length > 0
|
|
38971
|
+
? [h(Text, { key: 'stash-col-header', dimColor: true }, truncateCells(headerText, contentWidth))]
|
|
38972
|
+
: []), ...(stashHasMoreAbove
|
|
38889
38973
|
? [h(Text, { key: 'stash-more-above', dimColor: true }, ` ↑ ${startIndex} more above`)]
|
|
38890
38974
|
: []), ...lines, ...(stashHasMoreBelow
|
|
38891
38975
|
? [h(Text, { key: 'stash-more-below', dimColor: true }, ` ↓ ${stashes.length - (startIndex + listRows)} more below`)]
|