This commit is contained in:
@@ -123,3 +123,38 @@ describe('terminal state reconciliation', () => {
|
||||
expect(useTerminalStore.getState().buffers.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default terminal tab labels', () => {
|
||||
afterEach(() => useTerminalStore.getState().clearAll());
|
||||
|
||||
const labels = () =>
|
||||
useTerminalStore.getState().getDirectoryState('/repo')!.tabs.map((tab) => tab.label);
|
||||
|
||||
// Regression for https://github.com/openchamber/openchamber/issues/2718
|
||||
test('does not reuse the number of a closed tab', () => {
|
||||
const first = setup();
|
||||
useTerminalStore.getState().createTab('/repo');
|
||||
expect(labels()).toEqual(['Terminal', 'Terminal 2']);
|
||||
|
||||
useTerminalStore.getState().closeTab('/repo', first);
|
||||
useTerminalStore.getState().createTab('/repo');
|
||||
|
||||
expect(labels()).toEqual(['Terminal 2', 'Terminal 3']);
|
||||
});
|
||||
|
||||
test('numbers past a user-renamed "Terminal N" label instead of duplicating it', () => {
|
||||
const first = setup();
|
||||
useTerminalStore.getState().setTabLabel('/repo', first, 'Terminal 5');
|
||||
useTerminalStore.getState().createTab('/repo');
|
||||
|
||||
expect(labels()).toEqual(['Terminal 5', 'Terminal 6']);
|
||||
});
|
||||
|
||||
test('ignores custom labels and starts over at "Terminal" when no default-labeled tabs remain', () => {
|
||||
const first = setup();
|
||||
useTerminalStore.getState().setTabLabel('/repo', first, 'build');
|
||||
useTerminalStore.getState().createTab('/repo');
|
||||
|
||||
expect(labels()).toEqual(['build', 'Terminal']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -157,6 +157,27 @@ function normalizeDirectory(dir: string): string {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const DEFAULT_TAB_LABEL_PATTERN = /^Terminal(?: (\d+))?$/;
|
||||
|
||||
/**
|
||||
* Default labels must stay unique among the directory's open tabs even after
|
||||
* closes (#2718), so number from the highest existing "Terminal N" suffix
|
||||
* instead of the live tab count. Labels are persisted with the tabs, so the
|
||||
* derivation also survives reloads without a dedicated counter. User-renamed
|
||||
* labels only participate when they match the default pattern; they are never
|
||||
* rewritten.
|
||||
*/
|
||||
const nextDefaultTabLabel = (tabs: readonly TerminalTab[]): string => {
|
||||
let highest = 0;
|
||||
for (const tab of tabs) {
|
||||
const match = DEFAULT_TAB_LABEL_PATTERN.exec(tab.label);
|
||||
if (!match) continue;
|
||||
const value = match[1] ? Number.parseInt(match[1], 10) : 1;
|
||||
if (Number.isSafeInteger(value)) highest = Math.max(highest, value);
|
||||
}
|
||||
return highest === 0 ? 'Terminal' : `Terminal ${highest + 1}`;
|
||||
};
|
||||
|
||||
const createEmptyTab = (id: string, label: string): TerminalTab => ({
|
||||
id,
|
||||
terminalSessionId: null,
|
||||
@@ -295,8 +316,7 @@ export const useTerminalStore = create<TerminalStore>()(
|
||||
const existing = newSessions.get(key);
|
||||
|
||||
const nextTabId = state.nextTabId + 1;
|
||||
const labelIndex = (existing?.tabs.length ?? 0) + 1;
|
||||
const label = `Terminal ${labelIndex}`;
|
||||
const label = nextDefaultTabLabel(existing?.tabs ?? []);
|
||||
const tab = createEmptyTab(tabId, label);
|
||||
|
||||
if (!existing) {
|
||||
|
||||
Reference in New Issue
Block a user