/ src / browser / tabs.ts
tabs.ts
 1  /**
 2   * Browser tab management helpers: extract, diff, and cleanup tab state.
 3   */
 4  
 5  export function extractTabEntries(raw: unknown): Array<{ index: number; identity: string }> {
 6    if (Array.isArray(raw)) {
 7      return raw.map((tab: Record<string, unknown>, index: number) => ({
 8        index,
 9        identity: [
10          tab?.id ?? '',
11          tab?.url ?? '',
12          tab?.title ?? '',
13          tab?.name ?? '',
14        ].join('|'),
15      }));
16    }
17  
18    if (typeof raw === 'string') {
19      return raw
20        .split('\n')
21        .map(line => line.trim())
22        .filter(Boolean)
23        .map(line => {
24          // Match tab list format: "- 0: (current) [title](url)"  or  "- 1: [title](url)"
25          const tabMatch = line.match(/^-\s+(\d+):\s*(.*)$/);
26          if (tabMatch) {
27            return {
28              index: parseInt(tabMatch[1], 10),
29              identity: tabMatch[2].trim() || `tab-${tabMatch[1]}`,
30            };
31          }
32          // Legacy format: "Tab 0 ..."
33          const legacyMatch = line.match(/Tab\s+(\d+)\s*(.*)$/);
34          if (legacyMatch) {
35            return {
36              index: parseInt(legacyMatch[1], 10),
37              identity: legacyMatch[2].trim() || `tab-${legacyMatch[1]}`,
38            };
39          }
40          return null;
41        })
42        .filter((entry): entry is { index: number; identity: string } => entry !== null);
43    }
44  
45    return [];
46  }
47  
48  export function extractTabIdentities(raw: unknown): string[] {
49    return extractTabEntries(raw).map(tab => tab.identity);
50  }
51  
52  export function diffTabIndexes(initialIdentities: string[], currentTabs: Array<{ index: number; identity: string }>): number[] {
53    if (initialIdentities.length === 0 || currentTabs.length === 0) return [];
54    const remaining = new Map<string, number>();
55    for (const identity of initialIdentities) {
56      remaining.set(identity, (remaining.get(identity) ?? 0) + 1);
57    }
58  
59    const tabsToClose: number[] = [];
60    for (const tab of currentTabs) {
61      const count = remaining.get(tab.identity) ?? 0;
62      if (count > 0) {
63        remaining.set(tab.identity, count - 1);
64        continue;
65      }
66      tabsToClose.push(tab.index);
67    }
68  
69    return tabsToClose.sort((a, b) => b - a);
70  }
71  
72  export function appendLimited(current: string, chunk: string, limit: number): string {
73    const next = current + chunk;
74    if (next.length <= limit) return next;
75    return next.slice(-limit);
76  }