mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
fix(vscode): address local tab review feedback
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Open multiple same-repository sessions as tabs from Kilo sidebar and editor-tab chats.
|
||||
|
||||
@@ -13,6 +13,10 @@ import {
|
||||
} from "../../webview-ui/src/utils/local-tabs"
|
||||
|
||||
const pending = (id = "sidebar-pending:1") => id
|
||||
const makePending =
|
||||
(id = "sidebar-pending:1") =>
|
||||
() =>
|
||||
id
|
||||
|
||||
function state(ids: string[], active?: string): LocalTabState {
|
||||
return { ids, active }
|
||||
@@ -36,11 +40,11 @@ const inventory = (local: string[], external: string[] = []) => ({ local, extern
|
||||
|
||||
describe("local session tabs", () => {
|
||||
it("restores a fresh pending tab when no sessions were persisted", () => {
|
||||
expect(restoreTabs(undefined, undefined, pending())).toEqual({ ids: [pending()], active: pending() })
|
||||
expect(restoreTabs(undefined, undefined, makePending())).toEqual({ ids: [pending()], active: pending() })
|
||||
})
|
||||
|
||||
it("restores persisted local sessions and their active tab", () => {
|
||||
expect(restoreTabs(["s1", "s2"], "s2", pending())).toEqual({ ids: ["s1", "s2"], active: "s2" })
|
||||
expect(restoreTabs(["s1", "s2"], "s2", makePending())).toEqual({ ids: ["s1", "s2"], active: "s2" })
|
||||
})
|
||||
|
||||
it("promotes a pending tab into the created session without moving it", () => {
|
||||
@@ -61,18 +65,18 @@ describe("local session tabs", () => {
|
||||
})
|
||||
|
||||
it("selects the neighboring tab after closing the active one", () => {
|
||||
expect(closeTab(state(["s1", "s2", "s3"], "s2"), "s2", pending())).toEqual({
|
||||
expect(closeTab(state(["s1", "s2", "s3"], "s2"), "s2", makePending())).toEqual({
|
||||
ids: ["s1", "s3"],
|
||||
active: "s3",
|
||||
})
|
||||
})
|
||||
|
||||
it("keeps an empty chat available after closing the final tab", () => {
|
||||
expect(closeTab(state(["s1"], "s1"), "s1", pending())).toEqual({ ids: [pending()], active: pending() })
|
||||
expect(closeTab(state(["s1"], "s1"), "s1", makePending())).toEqual({ ids: [pending()], active: pending() })
|
||||
})
|
||||
|
||||
it("drops missing persisted sessions while preserving pending work", () => {
|
||||
expect(reconcileTabs(state(["s1", pending(), "gone"], "gone"), ["s1"], "sidebar-pending:2")).toEqual({
|
||||
expect(reconcileTabs(state(["s1", pending(), "gone"], "gone"), ["s1"], makePending("sidebar-pending:2"))).toEqual({
|
||||
ids: ["s1", pending()],
|
||||
active: "s1",
|
||||
})
|
||||
|
||||
@@ -50,7 +50,7 @@ export const LocalTabsProvider: ParentComponent = (props) => {
|
||||
const saved = vscode.getState<LocalTabsState>()
|
||||
let count = 0
|
||||
const pending = () => `${PENDING_TAB_PREFIX}${++count}`
|
||||
const init = restoreTabs(saved?.sidebarSessionTabIDs, saved?.sidebarActiveSessionTabID, pending())
|
||||
const init = restoreTabs(saved?.sidebarSessionTabIDs, saved?.sidebarActiveSessionTabID, pending)
|
||||
const [ids, setIds] = createSignal(init.ids)
|
||||
const [active, setActive] = createSignal(init.active)
|
||||
const fresh = new Set<string>()
|
||||
@@ -92,7 +92,7 @@ export const LocalTabsProvider: ParentComponent = (props) => {
|
||||
|
||||
const close = (id: string) => {
|
||||
const before = active()
|
||||
const next = closeTab(current(), id, pending())
|
||||
const next = closeTab(current(), id, pending)
|
||||
apply(next)
|
||||
if (before === id || before !== next.active) focus(next.active)
|
||||
}
|
||||
@@ -144,7 +144,7 @@ export const LocalTabsProvider: ParentComponent = (props) => {
|
||||
const before = active()
|
||||
const listed = message.sessions.map((item) => item.id)
|
||||
for (const id of listed) fresh.delete(id)
|
||||
const next = reconcileTabs(current(), [...listed, ...(message.preserveSessionIds ?? []), ...fresh], pending())
|
||||
const next = reconcileTabs(current(), [...listed, ...(message.preserveSessionIds ?? []), ...fresh], pending)
|
||||
apply(next)
|
||||
if (before !== next.active) focus(next.active)
|
||||
return
|
||||
@@ -152,7 +152,7 @@ export const LocalTabsProvider: ParentComponent = (props) => {
|
||||
if (message.type === "sessionDeleted") {
|
||||
fresh.delete(message.sessionID)
|
||||
const before = active()
|
||||
const next = closeTab(current(), message.sessionID, pending())
|
||||
const next = closeTab(current(), message.sessionID, pending)
|
||||
apply(next)
|
||||
if (before !== next.active) focus(next.active)
|
||||
}
|
||||
|
||||
@@ -22,16 +22,21 @@ export const isPendingTab = (id: string) => id.startsWith(PENDING_TAB_PREFIX)
|
||||
|
||||
const unique = (ids: string[]) => [...new Set(ids.filter(Boolean))]
|
||||
|
||||
function normalize(ids: string[], active: string | undefined, pending: string): LocalTabState {
|
||||
type PendingTabFactory = () => string
|
||||
|
||||
function normalize(ids: string[], active: string | undefined, pending: PendingTabFactory): LocalTabState {
|
||||
const tabs = unique(ids)
|
||||
if (tabs.length === 0) return { ids: [pending], active: pending }
|
||||
if (tabs.length === 0) {
|
||||
const id = pending()
|
||||
return { ids: [id], active: id }
|
||||
}
|
||||
return { ids: tabs, active: active && tabs.includes(active) ? active : tabs[0] }
|
||||
}
|
||||
|
||||
export function restoreTabs(
|
||||
ids: string[] | undefined,
|
||||
active: string | undefined,
|
||||
pending: string,
|
||||
pending: PendingTabFactory,
|
||||
check: PendingTabCheck = isPendingTab,
|
||||
): LocalTabState {
|
||||
const tabs = ids?.filter((id) => !check(id)) ?? []
|
||||
@@ -39,10 +44,12 @@ export function restoreTabs(
|
||||
return normalize(tabs, tab, pending)
|
||||
}
|
||||
|
||||
// New composers become active immediately even before the backend creates a session.
|
||||
export function addPendingTab(state: LocalTabState, id: string): LocalTabState {
|
||||
return { ids: unique([...state.ids, id]), active: id }
|
||||
}
|
||||
|
||||
// Existing sessions use the same state shape, but callers keep their open-or-focus intent explicit.
|
||||
export function openSessionTab(state: LocalTabState, id: string): LocalTabState {
|
||||
return { ids: unique([...state.ids, id]), active: id }
|
||||
}
|
||||
@@ -61,7 +68,7 @@ export function nextTabAfterClose(ids: readonly string[], id: string): string |
|
||||
return tabs[Math.min(index, tabs.length - 1)]
|
||||
}
|
||||
|
||||
export function closeTab(state: LocalTabState, id: string, pending: string): LocalTabState {
|
||||
export function closeTab(state: LocalTabState, id: string, pending: PendingTabFactory): LocalTabState {
|
||||
if (!state.ids.includes(id)) return state
|
||||
const ids = state.ids.filter((tab) => tab !== id)
|
||||
if (state.active !== id) return normalize(ids, state.active, pending)
|
||||
@@ -71,7 +78,7 @@ export function closeTab(state: LocalTabState, id: string, pending: string): Loc
|
||||
export function reconcileTabs(
|
||||
state: LocalTabState,
|
||||
loaded: string[],
|
||||
pending: string,
|
||||
pending: PendingTabFactory,
|
||||
check: PendingTabCheck = isPendingTab,
|
||||
): LocalTabState {
|
||||
const seen = new Set(loaded)
|
||||
|
||||
Reference in New Issue
Block a user