Skip to content

Commit 7e3ba4d

Browse files
committed
Migrate saved contexts from workspaceState to global file storage for workspace persistence
1 parent 915b0f8 commit 7e3ba4d

8 files changed

Lines changed: 227 additions & 23 deletions

File tree

‎packages/vscode/src/commands/apply-context-command/apply-context-command.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as fs from 'fs'
33
import * as path from 'path'
44
import { WorkspaceProvider } from '../../context/providers/workspace/workspace-provider'
55
import {
6-
SAVED_CONTEXTS_STATE_KEY,
76
LAST_APPLY_CONTEXT_OPTION_STATE_KEY,
87
QUICK_SAVES_STATE_KEY
98
} from '../../constants/state-keys'
@@ -15,7 +14,11 @@ import {
1514
handle_workspace_state_source,
1615
handle_commit_files_source
1716
} from './sources'
18-
import { load_all_contexts, get_contexts_file_path } from './helpers/saving'
17+
import {
18+
load_all_contexts,
19+
get_contexts_file_path,
20+
load_contexts_for_workspace
21+
} from './helpers/saving'
1922
import { handle_quick_save } from './helpers/saving/handle-quick-save'
2023

2124
export const apply_context_command = (
@@ -49,8 +52,10 @@ export const apply_context_command = (
4952
return
5053
}
5154

52-
const internal_contexts: SavedContext[] =
53-
extension_context.workspaceState.get(SAVED_CONTEXTS_STATE_KEY, [])
55+
const internal_contexts: SavedContext[] = load_contexts_for_workspace(
56+
extension_context,
57+
workspace_root
58+
)
5459

5560
const file_contexts_map = await load_all_contexts()
5661
let file_contexts_count = 0
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as vscode from 'vscode'
2+
import * as fs from 'fs'
3+
import * as path from 'path'
4+
import { SavedContext } from '@/types/context'
5+
6+
const GLOBAL_CONTEXTS_FILENAME = 'saved-contexts.json'
7+
8+
type GlobalContextsData = Record<string, SavedContext[]>
9+
10+
const get_global_contexts_file_path = (
11+
context: vscode.ExtensionContext
12+
): string => {
13+
return path.join(context.globalStorageUri.fsPath, GLOBAL_CONTEXTS_FILENAME)
14+
}
15+
16+
const load_all_global_contexts = (
17+
context: vscode.ExtensionContext
18+
): GlobalContextsData => {
19+
const file_path = get_global_contexts_file_path(context)
20+
try {
21+
if (fs.existsSync(file_path)) {
22+
const content = fs.readFileSync(file_path, 'utf8')
23+
return JSON.parse(content)
24+
}
25+
} catch (error) {
26+
console.error('Error loading global contexts:', error)
27+
}
28+
return {}
29+
}
30+
31+
export const load_contexts_for_workspace = (
32+
context: vscode.ExtensionContext,
33+
workspace_root: string
34+
): SavedContext[] => {
35+
const all_data = load_all_global_contexts(context)
36+
return all_data[workspace_root] || []
37+
}
38+
39+
export const save_contexts_for_workspace = (
40+
context: vscode.ExtensionContext,
41+
workspace_root: string,
42+
contexts: SavedContext[]
43+
): void => {
44+
const file_path = get_global_contexts_file_path(context)
45+
const dir_path = path.dirname(file_path)
46+
47+
if (!fs.existsSync(dir_path)) {
48+
fs.mkdirSync(dir_path, { recursive: true })
49+
}
50+
51+
const all_data = load_all_global_contexts(context)
52+
all_data[workspace_root] = contexts
53+
54+
try {
55+
fs.writeFileSync(file_path, JSON.stringify(all_data, null, 2), 'utf8')
56+
} catch (error) {
57+
console.error('Error saving global contexts:', error)
58+
throw new Error('Failed to save contexts to global storage.')
59+
}
60+
}

‎packages/vscode/src/commands/apply-context-command/helpers/saving/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './ask-for-new-context-name'
22
export * from './context-file-utils'
33
export * from './path-utils'
44
export * from './handle-quick-save'
5+
export * from './global-storage-utils'

‎packages/vscode/src/commands/apply-context-command/sources/workspace-state-source.ts‎

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as vscode from 'vscode'
22
import { WorkspaceProvider } from '../../../context/providers/workspace/workspace-provider'
3-
import { SAVED_CONTEXTS_STATE_KEY } from '../../../constants/state-keys'
43
import { SavedContext } from '@/types/context'
54
import { Logger } from '@shared/utils/logger'
65
import { dictionary } from '@shared/constants/dictionary'
@@ -10,7 +9,9 @@ import {
109
group_files_by_workspace,
1110
condense_paths,
1211
add_workspace_prefix,
13-
resolve_unique_context_name
12+
resolve_unique_context_name,
13+
load_contexts_for_workspace,
14+
save_contexts_for_workspace
1415
} from '../helpers/saving'
1516

1617
const LABEL_NEW_ENTRY = '$(add) New entry...'
@@ -23,8 +24,16 @@ export const handle_workspace_state_source = async (
2324
on_context_selected: () => void
2425
): Promise<'back' | void> => {
2526
try {
26-
let internal_contexts: SavedContext[] =
27-
extension_context.workspaceState.get(SAVED_CONTEXTS_STATE_KEY, [])
27+
const workspace_root = workspace_provider.getWorkspaceRoot()
28+
if (!workspace_root) {
29+
vscode.window.showErrorMessage(dictionary.error_message.NO_WORKSPACE_ROOT)
30+
return
31+
}
32+
33+
let internal_contexts: SavedContext[] = load_contexts_for_workspace(
34+
extension_context,
35+
workspace_root
36+
)
2837

2938
const edit_button = {
3039
iconPath: new vscode.ThemeIcon('edit'),
@@ -155,8 +164,9 @@ export const handle_workspace_state_source = async (
155164

156165
const updated_contexts = [new_context, ...internal_contexts]
157166

158-
await extension_context.workspaceState.update(
159-
SAVED_CONTEXTS_STATE_KEY,
167+
save_contexts_for_workspace(
168+
extension_context,
169+
workspace_root,
160170
updated_contexts
161171
)
162172
internal_contexts = updated_contexts
@@ -258,8 +268,9 @@ export const handle_workspace_state_source = async (
258268
c.name == item.context.name ? { ...c, name: trimmed_name } : c
259269
)
260270

261-
await extension_context.workspaceState.update(
262-
SAVED_CONTEXTS_STATE_KEY,
271+
save_contexts_for_workspace(
272+
extension_context,
273+
workspace_root,
263274
updated_contexts
264275
)
265276
internal_contexts = updated_contexts
@@ -289,8 +300,9 @@ export const handle_workspace_state_source = async (
289300
const updated_contexts = internal_contexts.filter(
290301
(c) => c.name != deleted_context_name
291302
)
292-
await extension_context.workspaceState.update(
293-
SAVED_CONTEXTS_STATE_KEY,
303+
save_contexts_for_workspace(
304+
extension_context,
305+
workspace_root,
294306
updated_contexts
295307
)
296308
internal_contexts = updated_contexts
@@ -316,8 +328,9 @@ export const handle_workspace_state_source = async (
316328

317329
if (choice == 'Undo') {
318330
internal_contexts.splice(deleted_index, 0, deleted_context)
319-
await extension_context.workspaceState.update(
320-
SAVED_CONTEXTS_STATE_KEY,
331+
save_contexts_for_workspace(
332+
extension_context,
333+
workspace_root,
321334
internal_contexts
322335
)
323336
vscode.window.showInformationMessage(
@@ -372,16 +385,15 @@ export const handle_workspace_state_source = async (
372385
(c) => c.name !== context_to_apply.name
373386
)
374387
updated_contexts.unshift(context_to_apply)
375-
await extension_context.workspaceState.update(
376-
SAVED_CONTEXTS_STATE_KEY,
388+
save_contexts_for_workspace(
389+
extension_context,
390+
workspace_root,
377391
updated_contexts
378392
)
379393

380-
const primary_workspace_root = workspace_provider.getWorkspaceRoot()!
381-
382394
await apply_saved_context(
383395
context_to_apply,
384-
primary_workspace_root,
396+
workspace_root,
385397
workspace_provider,
386398
extension_context
387399
)

‎packages/vscode/src/constants/state-keys.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export const LAST_APPLIED_CHANGES_EDITOR_STATE_STATE_KEY =
66
'last-applied-changes-editor-state'
77
export const LAST_APPLIED_CLIPBOARD_CONTENT_STATE_KEY =
88
'last-applied-clipboard-content'
9-
export const SAVED_CONTEXTS_STATE_KEY = 'savedContexts'
109
export const QUICK_SAVES_STATE_KEY = 'quick-saves'
1110
export const LAST_CONTEXT_SAVE_LOCATION_STATE_KEY = 'lastSaveLocation'
1211
export const RANGES_STATE_KEY = 'ranges'

‎packages/vscode/src/extension.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { ApiManager } from './services/api-manager'
66
import {
77
migrate_preset_is_default_to_is_selected,
88
migrate_api_providers_to_model_providers,
9-
migrate_token_count_cache
9+
migrate_token_count_cache,
10+
migrate_saved_contexts_to_global_storage
1011
} from './migrations'
1112
import {
1213
apply_chat_response_command,
@@ -53,6 +54,8 @@ export async function activate(context: vscode.ExtensionContext) {
5354
await migrate_api_providers_to_model_providers(context)
5455
// 4 January 2026
5556
await migrate_token_count_cache(context)
57+
// 5 January 2026
58+
await migrate_saved_contexts_to_global_storage(context)
5659
}
5760

5861
await migrations()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './migrate-preset-is-default-to-is-selected'
22
export * from './migrate-api-providers-to-model-providers'
33
export * from './migrate-token-count-cache'
4+
export * from './migrate-saved-contexts-to-global-storage'
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import * as vscode from 'vscode'
2+
import * as fs from 'fs'
3+
import * as path from 'path'
4+
import { Logger } from '@shared/utils/logger'
5+
import { SavedContext } from '@/types/context'
6+
7+
const MIGRATION_ID = 'saved-contexts-to-global-storage-migration-202601051'
8+
// NOTE: This key is a guess, as the original value of SAVED_CONTEXTS_STATE_KEY was not provided.
9+
const OLD_KEY = 'savedContexts'
10+
const GLOBAL_CONTEXTS_FILENAME = 'saved-contexts.json'
11+
12+
type GlobalContextsData = Record<string, SavedContext[]>
13+
14+
const get_global_contexts_file_path = (
15+
context: vscode.ExtensionContext
16+
): string => {
17+
return path.join(context.globalStorageUri.fsPath, GLOBAL_CONTEXTS_FILENAME)
18+
}
19+
20+
const load_all_global_contexts = (
21+
context: vscode.ExtensionContext
22+
): GlobalContextsData => {
23+
const file_path = get_global_contexts_file_path(context)
24+
try {
25+
if (fs.existsSync(file_path)) {
26+
const content = fs.readFileSync(file_path, 'utf8')
27+
return JSON.parse(content)
28+
}
29+
} catch (error) {
30+
Logger.warn({
31+
function_name: 'load_all_global_contexts',
32+
message: 'Error loading global contexts during migration',
33+
data: error
34+
})
35+
}
36+
return {}
37+
}
38+
39+
const save_all_global_contexts = (
40+
context: vscode.ExtensionContext,
41+
data: GlobalContextsData
42+
): void => {
43+
const file_path = get_global_contexts_file_path(context)
44+
const dir_path = path.dirname(file_path)
45+
46+
if (!fs.existsSync(dir_path)) {
47+
fs.mkdirSync(dir_path, { recursive: true })
48+
}
49+
50+
try {
51+
fs.writeFileSync(file_path, JSON.stringify(data, null, 2), 'utf8')
52+
} catch (error) {
53+
Logger.error({
54+
function_name: 'save_all_global_contexts',
55+
message: 'Error saving global contexts during migration',
56+
data: error
57+
})
58+
throw new Error('Failed to save contexts to global storage.')
59+
}
60+
}
61+
62+
/**
63+
* Migration to move saved contexts from workspaceState to a file-based global storage.
64+
* This makes the saved contexts persistent per workspace folder across different VS Code windows.
65+
*/
66+
export async function migrate_saved_contexts_to_global_storage(
67+
context: vscode.ExtensionContext
68+
): Promise<void> {
69+
try {
70+
if (context.globalState.get(MIGRATION_ID)) {
71+
return
72+
}
73+
74+
const old_contexts = context.workspaceState.get<SavedContext[]>(OLD_KEY)
75+
76+
if (
77+
old_contexts &&
78+
Array.isArray(old_contexts) &&
79+
old_contexts.length > 0
80+
) {
81+
const workspace_folders = vscode.workspace.workspaceFolders
82+
if (workspace_folders && workspace_folders.length > 0) {
83+
const primary_workspace_root = workspace_folders[0].uri.fsPath
84+
85+
const all_global_contexts = load_all_global_contexts(context)
86+
87+
const existing_contexts =
88+
all_global_contexts[primary_workspace_root] || []
89+
const existing_names = new Set(existing_contexts.map((c) => c.name))
90+
const contexts_to_migrate = old_contexts.filter(
91+
(c) => c.name && !existing_names.has(c.name)
92+
)
93+
94+
if (contexts_to_migrate.length > 0) {
95+
all_global_contexts[primary_workspace_root] = [
96+
...contexts_to_migrate,
97+
...existing_contexts
98+
]
99+
save_all_global_contexts(context, all_global_contexts)
100+
101+
Logger.info({
102+
function_name: 'migrate_saved_contexts_to_global_storage',
103+
message: `Successfully migrated ${contexts_to_migrate.length} saved contexts for workspace: ${primary_workspace_root}`
104+
})
105+
}
106+
}
107+
108+
await context.workspaceState.update(OLD_KEY, undefined)
109+
Logger.info({
110+
function_name: 'migrate_saved_contexts_to_global_storage',
111+
message: 'Removed old saved contexts from workspaceState.'
112+
})
113+
}
114+
115+
await context.globalState.update(MIGRATION_ID, true)
116+
} catch (error) {
117+
Logger.error({
118+
function_name: 'migrate_saved_contexts_to_global_storage',
119+
message: 'Error migrating saved contexts to global storage',
120+
data: error instanceof Error ? error.message : String(error)
121+
})
122+
}
123+
}

0 commit comments

Comments
 (0)