Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions packages/store/src/tab/tab-persistence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { relative } from "pathe"
import type { WorkspaceSettings } from "../workspace/workspace-settings"
import { buildPersistedLastOpenedFilePaths } from "./tab-state"
import type { Tab, TabHistoryEntry } from "./tab-types"

const MAX_PERSISTED_LAST_OPENED_FILE_PATHS = 5

type PersistedLastOpenedFileState = {
workspacePath: string | null
tabs: Tab[]
activeTabId: number | null
history: TabHistoryEntry[]
}

type SaveSettings = (
workspacePath: string,
settings: Partial<WorkspaceSettings>,
) => Promise<void>

export const createLastOpenedFileHistoryPersistence = ({
getState,
saveSettings,
onError,
}: {
getState: () => PersistedLastOpenedFileState
saveSettings: SaveSettings
onError: (error: unknown) => void
}) => {
let persistQueue: Promise<void> = Promise.resolve()

const buildPersistInput = (): {
workspacePath: string
lastOpenedFilePaths: string[]
} | null => {
const state = getState()
if (!state.workspacePath) {
return null
}
const workspacePath = state.workspacePath

return {
workspacePath,
lastOpenedFilePaths: buildPersistedLastOpenedFilePaths(
{
tabs: state.tabs,
activeTabId: state.activeTabId,
history: state.history,
},
MAX_PERSISTED_LAST_OPENED_FILE_PATHS,
).map((path) => relative(workspacePath, path)),
}
}

const enqueue = (): Promise<void> => {
const persistInput = buildPersistInput()
if (!persistInput) {
return Promise.resolve()
}

const persistTask = persistQueue
.catch(() => {})
.then(() =>
saveSettings(persistInput.workspacePath, {
lastOpenedFilePaths: persistInput.lastOpenedFilePaths,
}),
)
Comment thread
hhhjin marked this conversation as resolved.

persistQueue = persistTask
return persistTask
}

return {
enqueue,
enqueueSafely: () => {
void enqueue().catch(onError)
},
}
}
105 changes: 105 additions & 0 deletions packages/store/src/tab/tab-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type {
PendingHistorySelectionRestoreResult,
TabHistoryEntry,
TabHistorySelection,
} from "./tab-types"
import {
areHistorySelectionsEqual,
cloneHistorySelection,
} from "./utils/history-selection-utils"

type TabHistoryState = {
history: TabHistoryEntry[]
historyIndex: number
}

export const updateHistorySelection = (
state: TabHistoryState,
selection: TabHistorySelection,
): TabHistoryEntry[] | null => {
if (
state.historyIndex < 0 ||
state.historyIndex >= state.history.length ||
areHistorySelectionsEqual(
state.history[state.historyIndex].selection,
selection,
)
) {
return null
}

const nextHistory = [...state.history]
nextHistory[state.historyIndex] = {
...nextHistory[state.historyIndex],
selection,
}
return nextHistory
}

export const createTabHistorySession = () => {
let selectionProvider: (() => TabHistorySelection) | null = null
let pendingRestore: {
path: string
selection: TabHistorySelection
} | null = null

return {
setSelectionProvider: (provider: (() => TabHistorySelection) | null) => {
selectionProvider = provider
},
readCurrentSelection: (): TabHistorySelection => {
if (!selectionProvider) {
return null
}

try {
return cloneHistorySelection(selectionProvider())
} catch {
return null
}
},
queuePendingRestore: (path: string, selection: TabHistorySelection) => {
pendingRestore = {
path,
selection: cloneHistorySelection(selection),
}
},
clearPendingRestore: () => {
pendingRestore = null
},
consumePendingRestore: (
path: string,
): PendingHistorySelectionRestoreResult => {
if (!pendingRestore || pendingRestore.path !== path) {
return { found: false }
}

const selection = cloneHistorySelection(pendingRestore.selection)
pendingRestore = null
return { found: true, selection }
},
}
}

export const createExternalReloadSaveSkipTracker = () => {
const pendingTabIds = new Set<number>()

return {
add: (tabId: number) => {
pendingTabIds.add(tabId)
},
remove: (tabId: number) => {
pendingTabIds.delete(tabId)
},
clear: () => {
pendingTabIds.clear()
},
consume: (tabId: number): boolean => {
const shouldSkip = pendingTabIds.has(tabId)
if (shouldSkip) {
pendingTabIds.delete(tabId)
}
return shouldSkip
},
}
}
Loading
Loading