pi-goosedump 0.7.0 → 0.7.1
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/index.ts +92 -9
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ interface GoosedumpListing {
|
|
|
49
49
|
provider: string;
|
|
50
50
|
nativeId: string;
|
|
51
51
|
cwd: string;
|
|
52
|
+
parent: string | null;
|
|
52
53
|
label: string | null;
|
|
53
54
|
path: string | null;
|
|
54
55
|
}
|
|
@@ -273,6 +274,7 @@ function goosedumpList(provider = 'pi'): GoosedumpListing[] {
|
|
|
273
274
|
provider: entry.provider,
|
|
274
275
|
nativeId: entry.native_id,
|
|
275
276
|
cwd: entry.cwd,
|
|
277
|
+
parent: entry.parent,
|
|
276
278
|
label: entry.label ?? null,
|
|
277
279
|
// Only pi sessions map back to a local file we can resume.
|
|
278
280
|
path: provider === 'pi' ? piSessionFilePath(entry.native_id) : null,
|
|
@@ -662,8 +664,46 @@ async function runSessionBrowser(
|
|
|
662
664
|
|
|
663
665
|
const OVERLAY_WIDTH = 72;
|
|
664
666
|
|
|
667
|
+
// Build tree from flat listings using parent relationships.
|
|
668
|
+
const listingIds = new Set(listings.map((l) => l.id));
|
|
669
|
+
const roots = listings.filter((l) => l.parent === null || !listingIds.has(l.parent));
|
|
670
|
+
const childrenMap = new Map<string, GoosedumpListing[]>();
|
|
671
|
+
for (const l of listings) {
|
|
672
|
+
if (l.parent !== null) {
|
|
673
|
+
const siblings = childrenMap.get(l.parent) ?? [];
|
|
674
|
+
siblings.push(l);
|
|
675
|
+
childrenMap.set(l.parent, siblings);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function truncateCwd(cwd: string, maxLen: number): string {
|
|
680
|
+
if (cwd.length <= maxLen) return cwd;
|
|
681
|
+
const head = Math.floor(maxLen / 2) + 2;
|
|
682
|
+
const tail = maxLen - head - 3;
|
|
683
|
+
if (tail < 4) return cwd.slice(0, maxLen - 3) + '...';
|
|
684
|
+
return cwd.slice(0, head) + '...' + cwd.slice(cwd.length - tail);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function flattenTree(
|
|
688
|
+
nodes: GoosedumpListing[],
|
|
689
|
+
expanded: Set<string>,
|
|
690
|
+
depth: number,
|
|
691
|
+
): { listing: GoosedumpListing; depth: number; hasChildren: boolean }[] {
|
|
692
|
+
const result: { listing: GoosedumpListing; depth: number; hasChildren: boolean }[] = [];
|
|
693
|
+
for (const node of nodes) {
|
|
694
|
+
const children = childrenMap.get(node.id) ?? [];
|
|
695
|
+
result.push({ listing: node, depth, hasChildren: children.length > 0 });
|
|
696
|
+
if (children.length > 0 && expanded.has(node.id)) {
|
|
697
|
+
result.push(...flattenTree(children, expanded, depth + 1));
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return result;
|
|
701
|
+
}
|
|
702
|
+
|
|
665
703
|
await ctx.ui.custom<void>(
|
|
666
704
|
(tui, theme, _kb, done) => {
|
|
705
|
+
let expandedNodes = new Set<string>();
|
|
706
|
+
let displayList = flattenTree(roots, expandedNodes, 0);
|
|
667
707
|
let selectedIndex = 0;
|
|
668
708
|
let listScroll = 0;
|
|
669
709
|
let mode: 'list' | 'compact' = 'list';
|
|
@@ -675,6 +715,10 @@ async function runSessionBrowser(
|
|
|
675
715
|
const LIST_VIEWPORT = 20;
|
|
676
716
|
const wrapWidth = OVERLAY_WIDTH - 4;
|
|
677
717
|
|
|
718
|
+
function rebuildDisplayList(): void {
|
|
719
|
+
displayList = flattenTree(roots, expandedNodes, 0);
|
|
720
|
+
}
|
|
721
|
+
|
|
678
722
|
return {
|
|
679
723
|
render(width: number): string[] {
|
|
680
724
|
const innerW = width - 4;
|
|
@@ -723,15 +767,29 @@ async function runSessionBrowser(
|
|
|
723
767
|
lines.push(makeRow('', innerW, border));
|
|
724
768
|
|
|
725
769
|
// Session list
|
|
726
|
-
const total =
|
|
727
|
-
const window =
|
|
770
|
+
const total = displayList.length;
|
|
771
|
+
const window = displayList.slice(listScroll, listScroll + LIST_VIEWPORT);
|
|
728
772
|
for (let i = 0; i < window.length; i++) {
|
|
729
773
|
const absolute = listScroll + i;
|
|
730
|
-
const
|
|
774
|
+
const entry = window[i];
|
|
731
775
|
const isSelected = absolute === selectedIndex;
|
|
776
|
+
|
|
732
777
|
const cursor = isSelected ? accent('▶') : ' ';
|
|
733
|
-
const
|
|
734
|
-
const
|
|
778
|
+
const indent = ' '.repeat(entry.depth * 2);
|
|
779
|
+
const toggle = entry.hasChildren
|
|
780
|
+
? expandedNodes.has(entry.listing.id)
|
|
781
|
+
? muted('−')
|
|
782
|
+
: muted('+')
|
|
783
|
+
: ' ';
|
|
784
|
+
const idText = isSelected ? accent(entry.listing.id) : text(entry.listing.id);
|
|
785
|
+
|
|
786
|
+
// Prefix: " ▶" (3) + indent + toggle + " " + id (8) + " "
|
|
787
|
+
const prefixLen = 3 + entry.depth * 2 + 1 + 1 + 8 + 1;
|
|
788
|
+
const cwdMax = Math.max(8, innerW - prefixLen);
|
|
789
|
+
const cwdTruncated = truncateCwd(entry.listing.cwd, cwdMax);
|
|
790
|
+
const cwdText = isSelected ? accent(cwdTruncated) : muted(cwdTruncated);
|
|
791
|
+
|
|
792
|
+
const line = ` ${cursor}${indent}${toggle} ${idText} ${cwdText}`;
|
|
735
793
|
lines.push(makeRow(line, innerW, border));
|
|
736
794
|
}
|
|
737
795
|
|
|
@@ -744,7 +802,7 @@ async function runSessionBrowser(
|
|
|
744
802
|
lines.push(makeRow('', innerW, border));
|
|
745
803
|
lines.push(
|
|
746
804
|
makeRow(
|
|
747
|
-
dim('↑↓ navigate enter compact shift+enter resume esc close'),
|
|
805
|
+
dim('↑↓ navigate space/+/− fold enter compact shift+enter resume esc close'),
|
|
748
806
|
innerW,
|
|
749
807
|
border,
|
|
750
808
|
),
|
|
@@ -800,7 +858,7 @@ async function runSessionBrowser(
|
|
|
800
858
|
}
|
|
801
859
|
|
|
802
860
|
if (matchesKey(data, Key.down)) {
|
|
803
|
-
selectedIndex = Math.min(
|
|
861
|
+
selectedIndex = Math.min(displayList.length - 1, selectedIndex + 1);
|
|
804
862
|
if (selectedIndex >= listScroll + LIST_VIEWPORT) {
|
|
805
863
|
listScroll = selectedIndex - LIST_VIEWPORT + 1;
|
|
806
864
|
}
|
|
@@ -808,14 +866,39 @@ async function runSessionBrowser(
|
|
|
808
866
|
return;
|
|
809
867
|
}
|
|
810
868
|
|
|
869
|
+
// Expand/collapse: space, +, or - key
|
|
870
|
+
const entry = displayList[selectedIndex];
|
|
871
|
+
if (entry && entry.hasChildren && (data === ' ' || data === '+' || data === '-')) {
|
|
872
|
+
const id = entry.listing.id;
|
|
873
|
+
if (data === '+') {
|
|
874
|
+
expandedNodes.add(id);
|
|
875
|
+
} else if (data === '-') {
|
|
876
|
+
expandedNodes.delete(id);
|
|
877
|
+
} else {
|
|
878
|
+
// space = toggle
|
|
879
|
+
if (expandedNodes.has(id)) {
|
|
880
|
+
expandedNodes.delete(id);
|
|
881
|
+
} else {
|
|
882
|
+
expandedNodes.add(id);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
rebuildDisplayList();
|
|
886
|
+
// Keep selection on the same entry
|
|
887
|
+
const newIndex = displayList.findIndex((d) => d.listing.id === id);
|
|
888
|
+
if (newIndex >= 0) selectedIndex = newIndex;
|
|
889
|
+
if (selectedIndex < listScroll) listScroll = selectedIndex;
|
|
890
|
+
tui.requestRender();
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
811
894
|
if (matchesKey(data, Key.shift(Key.enter))) {
|
|
812
|
-
resumePath =
|
|
895
|
+
resumePath = displayList[selectedIndex]?.listing.path;
|
|
813
896
|
done(undefined);
|
|
814
897
|
return;
|
|
815
898
|
}
|
|
816
899
|
|
|
817
900
|
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
818
|
-
const listing =
|
|
901
|
+
const listing = displayList[selectedIndex]?.listing;
|
|
819
902
|
if (!listing) return;
|
|
820
903
|
compactTitle = listing.id;
|
|
821
904
|
compactScroll = 0;
|