Skip to content

Commit 1d86226

Browse files
committed
Rename Timeline to Checkpoints, implement checkpoint management actions, and introduce a tabbed view for tasks and checkpoints in the home panel
- rename 'timeline' to 'checkpoints' - fix 'timeline' translation (this is how we called checkpoints before. - remove 'scroll to checkpoints' feature - user can now collapse tasks/checkpoints. Remove this functionality and set collapsed on UI elements to be false (hard-coded) - 'List headers' should be replaced by a new but similar component. Name it 'Tabs'. Should display two tabs: 'Tasks' and 'Checkpoints' on the left side, and dependent on active tab actions on the right side. - move the column with connector to the left - following the `apps/editor/src/commands/checkpoints-command/checkpoints-command.ts` behavior, in Tabs component, show action 'Revert last restored checkpoint'. Make sure timing of this action's appearance is right (showing when reverting is possible) - once checkpoint is restored, 'revert' action doesn't appear. It's there upon application restart, though. - The checkpoint-related logic in the panel must not depend on the command in any way. Code duplication is allowed. - move `apps/editor/src/views/panel/backend/checkpoints` to `apps/editor/src/views/panel/backend/message-handlers/checkpoints` - move checkpoint related handlers to `apps/editor/src/views/panel/backend/message-handlers/checkpoints/handlers` - some checkpoints related handlers are inline in `apps/editor/src/views/panel/backend/panel-provider.ts`, but should have separate files in `apps/editor/src/views/panel/backend/message-handlers/checkpoints/handlers` - create index.ts file in `apps/editor/src/views/panel/backend/message-handlers/checkpoints/handlers`
1 parent fb01034 commit 1d86226

47 files changed

Lines changed: 2423 additions & 423 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/editor/src/constants/state-keys.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const INSTRUCTIONS_FIND_RELEVANT_FILES_STATE_KEY =
4646
'instructions-find-relevant-files'
4747

4848
export const ARE_TASKS_COLLAPSED_STATE_KEY = 'are-tasks-collapsed'
49-
export const IS_TIMELINE_COLLAPSED_STATE_KEY = 'is-timeline-collapsed'
49+
export const ARE_CHECKPOINTS_COLLAPSED_STATE_KEY = 'are-checkpoints-collapsed'
5050

5151
export const HISTORY_ASK_STATE_KEY = 'history-ask'
5252
export const HISTORY_EDIT_STATE_KEY = 'history-edit'
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import * as vscode from 'vscode'
2+
import {
3+
CHECKPOINTS_STATE_KEY,
4+
TEMPORARY_CHECKPOINT_STATE_KEY
5+
} from '@/constants/state-keys'
6+
import { get_checkpoint_path } from '../utils'
7+
import { get_checkpoints } from './get-checkpoints'
8+
import { Logger } from '@shared/utils/logger'
9+
import { PanelProvider } from '../../../panel-provider'
10+
import type { Checkpoint } from '../types'
11+
12+
export const clear_all_checkpoints = async (
13+
context: vscode.ExtensionContext,
14+
panel_provider: PanelProvider
15+
) => {
16+
const clear_task = async () => {
17+
const checkpoints = await get_checkpoints(context)
18+
for (const checkpoint of checkpoints) {
19+
try {
20+
const checkpoint_path = get_checkpoint_path(checkpoint.timestamp)
21+
await vscode.workspace.fs.delete(vscode.Uri.file(checkpoint_path), {
22+
recursive: true
23+
})
24+
} catch (error) {
25+
Logger.warn({
26+
function_name: 'clear_all_checkpoints',
27+
message: 'Could not delete checkpoint file',
28+
data: error
29+
})
30+
}
31+
}
32+
const temp_checkpoint = context.workspaceState.get<Checkpoint>(
33+
TEMPORARY_CHECKPOINT_STATE_KEY
34+
)
35+
if (temp_checkpoint) {
36+
try {
37+
const checkpoint_path = get_checkpoint_path(temp_checkpoint.timestamp)
38+
await vscode.workspace.fs.delete(vscode.Uri.file(checkpoint_path), {
39+
recursive: true
40+
})
41+
} catch (error) {
42+
Logger.warn({
43+
function_name: 'clear_all_checkpoints',
44+
message: 'Could not delete temporary checkpoint file',
45+
data: error
46+
})
47+
}
48+
}
49+
await context.workspaceState.update(
50+
TEMPORARY_CHECKPOINT_STATE_KEY,
51+
undefined
52+
)
53+
await context.workspaceState.update(CHECKPOINTS_STATE_KEY, [])
54+
await panel_provider.send_checkpoints()
55+
}
56+
await vscode.window.withProgress(
57+
{
58+
location: vscode.ProgressLocation.Notification,
59+
title: 'Clearing all checkpoints...',
60+
cancellable: false
61+
},
62+
clear_task
63+
)
64+
}
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import * as vscode from 'vscode'
2+
import {
3+
CHECKPOINTS_STATE_KEY,
4+
TEMPORARY_CHECKPOINT_STATE_KEY,
5+
CHECKPOINT_OPERATION_IN_PROGRESS_STATE_KEY
6+
} from '@/constants/state-keys'
7+
import { WorkspaceProvider } from '@/context/providers/workspace/workspace-provider'
8+
import type { Checkpoint, CheckpointTab, CheckpointTrigger } from '../types'
9+
import { get_checkpoints, remove_old_checkpoints } from './get-checkpoints'
10+
import {
11+
copy_optimised_recursively,
12+
get_checkpoint_path,
13+
get_incremented_description
14+
} from '../utils'
15+
import {
16+
get_git_diff,
17+
get_git_info,
18+
is_git_repository
19+
} from '../utils/git-utils'
20+
import * as path from 'path'
21+
import { Logger } from '@shared/utils/logger'
22+
import { PanelProvider } from '../../../panel-provider'
23+
import { dictionary } from '@shared/constants/dictionary'
24+
import { response_preview_promise_resolve } from '@/commands/apply-chat-response-command/utils/preview'
25+
26+
export const create_checkpoint = async (params: {
27+
workspace_provider: WorkspaceProvider
28+
context: vscode.ExtensionContext
29+
panel_provider: PanelProvider
30+
trigger?: CheckpointTrigger
31+
description?: string
32+
silent?: boolean
33+
}): Promise<Checkpoint | undefined> => {
34+
try {
35+
const trigger = params.trigger ?? 'manual'
36+
const are_automatic_checkpoints_disabled = vscode.workspace
37+
.getConfiguration('codeWebChat')
38+
.get<boolean>('areAutomaticCheckpointsDisabled', false)
39+
const is_automatic_checkpoint = trigger != 'manual'
40+
41+
if (are_automatic_checkpoints_disabled && is_automatic_checkpoint) {
42+
Logger.info({
43+
function_name: 'create_checkpoint',
44+
message: 'Automatic checkpoint creation is disabled. Skipping.',
45+
data: { trigger }
46+
})
47+
return undefined
48+
}
49+
50+
const operation_in_progress = params.context.workspaceState.get<number>(
51+
CHECKPOINT_OPERATION_IN_PROGRESS_STATE_KEY
52+
)
53+
if (
54+
operation_in_progress &&
55+
Date.now() - operation_in_progress < 60 * 1000
56+
) {
57+
vscode.window.showWarningMessage(
58+
dictionary.warning_message.CHECKPOINT_OPERATION_IN_PROGRESS
59+
)
60+
return undefined
61+
}
62+
63+
const old_temp_checkpoint = params.context.workspaceState.get<Checkpoint>(
64+
TEMPORARY_CHECKPOINT_STATE_KEY
65+
)
66+
if (old_temp_checkpoint) {
67+
try {
68+
const checkpoint_path = get_checkpoint_path(
69+
old_temp_checkpoint.timestamp
70+
)
71+
await vscode.workspace.fs.delete(vscode.Uri.file(checkpoint_path), {
72+
recursive: true
73+
})
74+
} catch (error) {
75+
Logger.warn({
76+
function_name: 'create_checkpoint',
77+
message: 'Could not delete old temporary checkpoint file',
78+
data: error
79+
})
80+
}
81+
await params.context.workspaceState.update(
82+
TEMPORARY_CHECKPOINT_STATE_KEY,
83+
undefined
84+
)
85+
}
86+
const timestamp = Date.now()
87+
const workspace_folders = vscode.workspace.workspaceFolders!
88+
89+
const folder_git_statuses = await Promise.all(
90+
workspace_folders.map((folder) => is_git_repository(folder))
91+
)
92+
let new_checkpoint: Checkpoint | undefined
93+
94+
const create_checkpoint_task = async () => {
95+
await params.context.workspaceState.update(
96+
CHECKPOINT_OPERATION_IN_PROGRESS_STATE_KEY,
97+
Date.now()
98+
)
99+
await vscode.workspace.saveAll()
100+
const checkpoint_dir_path = get_checkpoint_path(timestamp)
101+
const checkpoint_dir_uri = vscode.Uri.file(checkpoint_dir_path)
102+
await vscode.workspace.fs.createDirectory(checkpoint_dir_uri)
103+
104+
const git_data: Record<string, any> = {}
105+
let uses_git = false
106+
107+
for (const [index, folder] of workspace_folders.entries()) {
108+
const has_git = folder_git_statuses[index]
109+
110+
if (has_git) {
111+
uses_git = true
112+
const git_info = await get_git_info(folder)
113+
const diff = await get_git_diff(folder)
114+
115+
if (git_info && diff !== null) {
116+
const folder_name = folder.name
117+
git_data[folder_name] = {
118+
branch: git_info.branch,
119+
commit_hash: git_info.commit_hash,
120+
folder_name
121+
}
122+
123+
const diff_file_path = path.join(
124+
checkpoint_dir_path,
125+
`${folder_name}.diff`
126+
)
127+
await vscode.workspace.fs.writeFile(
128+
vscode.Uri.file(diff_file_path),
129+
Buffer.from(diff, 'utf8')
130+
)
131+
} else {
132+
Logger.warn({
133+
function_name: 'create_checkpoint',
134+
message: `Failed to get git information for repository in ${folder.name}. Falling back to file copy.`
135+
})
136+
const dest_folder_path =
137+
workspace_folders.length > 1
138+
? path.join(checkpoint_dir_path, folder.name)
139+
: checkpoint_dir_path
140+
const dest_folder_uri = vscode.Uri.file(dest_folder_path)
141+
if (workspace_folders.length > 1) {
142+
await vscode.workspace.fs.createDirectory(dest_folder_uri)
143+
}
144+
145+
const entries = await vscode.workspace.fs.readDirectory(folder.uri)
146+
for (const [name] of entries) {
147+
await copy_optimised_recursively(
148+
vscode.Uri.joinPath(folder.uri, name),
149+
vscode.Uri.joinPath(dest_folder_uri, name),
150+
folder.uri.fsPath,
151+
params.workspace_provider
152+
)
153+
}
154+
}
155+
} else {
156+
const dest_folder_path =
157+
workspace_folders.length > 1
158+
? path.join(checkpoint_dir_path, folder.name)
159+
: checkpoint_dir_path
160+
const dest_folder_uri = vscode.Uri.file(dest_folder_path)
161+
if (workspace_folders.length > 1) {
162+
await vscode.workspace.fs.createDirectory(dest_folder_uri)
163+
}
164+
165+
const entries = await vscode.workspace.fs.readDirectory(folder.uri)
166+
for (const [name] of entries) {
167+
await copy_optimised_recursively(
168+
vscode.Uri.joinPath(folder.uri, name),
169+
vscode.Uri.joinPath(dest_folder_uri, name),
170+
folder.uri.fsPath,
171+
params.workspace_provider
172+
)
173+
}
174+
}
175+
}
176+
177+
let checkpoints = await get_checkpoints(params.context)
178+
179+
checkpoints = await remove_old_checkpoints(checkpoints)
180+
181+
const final_description = get_incremented_description(
182+
trigger,
183+
params.description,
184+
checkpoints
185+
)
186+
187+
const active_tabs: CheckpointTab[] = []
188+
const tab_groups = vscode.window.tabGroups.all
189+
for (const group of tab_groups) {
190+
for (const tab of group.tabs) {
191+
if (tab.input instanceof vscode.TabInputText) {
192+
active_tabs.push({
193+
uri: (tab.input as vscode.TabInputText).uri.toString(),
194+
view_column: group.viewColumn,
195+
is_active: tab.isActive,
196+
is_group_active: group.isActive
197+
})
198+
}
199+
}
200+
}
201+
202+
const checkpoint_object: Checkpoint = {
203+
trigger,
204+
timestamp,
205+
description: final_description,
206+
uses_git,
207+
git_data: Object.keys(git_data).length > 0 ? git_data : undefined,
208+
checked_files: params.workspace_provider.get_all_checked_paths(),
209+
active_tabs
210+
}
211+
212+
if (trigger == 'manual' && !response_preview_promise_resolve) {
213+
checkpoint_object.response_history = [
214+
...params.panel_provider.response_history
215+
]
216+
}
217+
218+
checkpoints.unshift(checkpoint_object)
219+
await params.context.workspaceState.update(
220+
CHECKPOINTS_STATE_KEY,
221+
checkpoints
222+
)
223+
await params.panel_provider.send_checkpoints()
224+
await params.context.workspaceState.update(
225+
CHECKPOINT_OPERATION_IN_PROGRESS_STATE_KEY,
226+
undefined
227+
)
228+
new_checkpoint = checkpoint_object
229+
}
230+
231+
let timer: ReturnType<typeof setTimeout> | undefined
232+
let did_show_modal = false
233+
234+
try {
235+
if (!params.silent) {
236+
timer = setTimeout(() => {
237+
did_show_modal = true
238+
params.panel_provider.send_message({
239+
command: 'SHOW_PROGRESS',
240+
title: 'Creating checkpoint...'
241+
})
242+
}, 1000)
243+
}
244+
245+
await create_checkpoint_task()
246+
} finally {
247+
if (timer) clearTimeout(timer)
248+
if (did_show_modal) {
249+
params.panel_provider.send_message({
250+
command: 'HIDE_PROGRESS'
251+
})
252+
}
253+
}
254+
return new_checkpoint
255+
} catch (err: any) {
256+
vscode.window.showErrorMessage(
257+
`Failed to create checkpoint: ${err.message}`
258+
)
259+
await params.context.workspaceState.update(
260+
CHECKPOINT_OPERATION_IN_PROGRESS_STATE_KEY,
261+
undefined
262+
)
263+
return undefined
264+
}
265+
}

0 commit comments

Comments
 (0)