Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts
5272 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Disposable } from '../../../../base/common/lifecycle.js';
7
import { localize } from '../../../../nls.js';
8
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
9
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
10
import { Registry } from '../../../../platform/registry/common/platform.js';
11
import { TreeView, TreeViewPane } from '../../../browser/parts/views/treeView.js';
12
import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewsRegistry, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer } from '../../../common/views.js';
13
import { ChangeType, EDIT_SESSIONS_DATA_VIEW_ID, EDIT_SESSIONS_SCHEME, EDIT_SESSIONS_SHOW_VIEW, EDIT_SESSIONS_TITLE, EditSession, IEditSessionsStorageService } from '../common/editSessions.js';
14
import { URI } from '../../../../base/common/uri.js';
15
import { fromNow } from '../../../../base/common/date.js';
16
import { Codicon } from '../../../../base/common/codicons.js';
17
import { API_OPEN_EDITOR_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js';
18
import { registerAction2, Action2, MenuId } from '../../../../platform/actions/common/actions.js';
19
import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
20
import { ICommandService } from '../../../../platform/commands/common/commands.js';
21
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
22
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
23
import { joinPath } from '../../../../base/common/resources.js';
24
import { IFileService } from '../../../../platform/files/common/files.js';
25
import { basename } from '../../../../base/common/path.js';
26
import { createCommandUri } from '../../../../base/common/htmlContent.js';
27
28
const EDIT_SESSIONS_COUNT_KEY = 'editSessionsCount';
29
const EDIT_SESSIONS_COUNT_CONTEXT_KEY = new RawContextKey<number>(EDIT_SESSIONS_COUNT_KEY, 0);
30
31
export class EditSessionsDataViews extends Disposable {
32
constructor(
33
container: ViewContainer,
34
@IInstantiationService private readonly instantiationService: IInstantiationService,
35
) {
36
super();
37
this.registerViews(container);
38
}
39
40
private registerViews(container: ViewContainer): void {
41
const viewId = EDIT_SESSIONS_DATA_VIEW_ID;
42
const treeView = this.instantiationService.createInstance(TreeView, viewId, EDIT_SESSIONS_TITLE.value);
43
treeView.showCollapseAllAction = true;
44
treeView.showRefreshAction = true;
45
treeView.dataProvider = this.instantiationService.createInstance(EditSessionDataViewDataProvider);
46
47
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
48
// eslint-disable-next-line local/code-no-dangerous-type-assertions
49
viewsRegistry.registerViews([<ITreeViewDescriptor>{
50
id: viewId,
51
name: EDIT_SESSIONS_TITLE,
52
ctorDescriptor: new SyncDescriptor(TreeViewPane),
53
canToggleVisibility: true,
54
canMoveView: false,
55
treeView,
56
collapsed: false,
57
when: ContextKeyExpr.and(EDIT_SESSIONS_SHOW_VIEW),
58
order: 100,
59
hideByDefault: true,
60
}], container);
61
62
viewsRegistry.registerViewWelcomeContent(viewId, {
63
content: localize(
64
'noStoredChanges',
65
'You have no stored changes in the cloud to display.\n{0}',
66
`[${localize('storeWorkingChangesTitle', 'Store Working Changes')}](${createCommandUri('workbench.editSessions.actions.store')})`,
67
),
68
when: ContextKeyExpr.equals(EDIT_SESSIONS_COUNT_KEY, 0),
69
order: 1
70
});
71
72
this._register(registerAction2(class extends Action2 {
73
constructor() {
74
super({
75
id: 'workbench.editSessions.actions.resume',
76
title: localize('workbench.editSessions.actions.resume.v2', "Resume Working Changes"),
77
icon: Codicon.desktopDownload,
78
menu: {
79
id: MenuId.ViewItemContext,
80
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewId), ContextKeyExpr.regex('viewItem', /edit-session/i)),
81
group: 'inline'
82
}
83
});
84
}
85
86
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
87
const editSessionId = URI.parse(handle.$treeItemHandle).path.substring(1);
88
const commandService = accessor.get(ICommandService);
89
await commandService.executeCommand('workbench.editSessions.actions.resumeLatest', editSessionId, true);
90
await treeView.refresh();
91
}
92
}));
93
94
this._register(registerAction2(class extends Action2 {
95
constructor() {
96
super({
97
id: 'workbench.editSessions.actions.store',
98
title: localize('workbench.editSessions.actions.store.v2', "Store Working Changes"),
99
icon: Codicon.cloudUpload,
100
});
101
}
102
103
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
104
const commandService = accessor.get(ICommandService);
105
await commandService.executeCommand('workbench.editSessions.actions.storeCurrent');
106
await treeView.refresh();
107
}
108
}));
109
110
this._register(registerAction2(class extends Action2 {
111
constructor() {
112
super({
113
id: 'workbench.editSessions.actions.delete',
114
title: localize('workbench.editSessions.actions.delete.v2', "Delete Working Changes"),
115
icon: Codicon.trash,
116
menu: {
117
id: MenuId.ViewItemContext,
118
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewId), ContextKeyExpr.regex('viewItem', /edit-session/i)),
119
group: 'inline'
120
}
121
});
122
}
123
124
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
125
const editSessionId = URI.parse(handle.$treeItemHandle).path.substring(1);
126
const dialogService = accessor.get(IDialogService);
127
const editSessionStorageService = accessor.get(IEditSessionsStorageService);
128
const result = await dialogService.confirm({
129
message: localize('confirm delete.v2', 'Are you sure you want to permanently delete your working changes with ref {0}?', editSessionId),
130
detail: localize('confirm delete detail.v2', ' You cannot undo this action.'),
131
type: 'warning',
132
title: EDIT_SESSIONS_TITLE.value
133
});
134
if (result.confirmed) {
135
await editSessionStorageService.delete('editSessions', editSessionId);
136
await treeView.refresh();
137
}
138
}
139
}));
140
141
this._register(registerAction2(class extends Action2 {
142
constructor() {
143
super({
144
id: 'workbench.editSessions.actions.deleteAll',
145
title: localize('workbench.editSessions.actions.deleteAll', "Delete All Working Changes from Cloud"),
146
icon: Codicon.trash,
147
menu: {
148
id: MenuId.ViewTitle,
149
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewId), ContextKeyExpr.greater(EDIT_SESSIONS_COUNT_KEY, 0)),
150
}
151
});
152
}
153
154
async run(accessor: ServicesAccessor): Promise<void> {
155
const dialogService = accessor.get(IDialogService);
156
const editSessionStorageService = accessor.get(IEditSessionsStorageService);
157
const result = await dialogService.confirm({
158
message: localize('confirm delete all', 'Are you sure you want to permanently delete all stored changes from the cloud?'),
159
detail: localize('confirm delete all detail', ' You cannot undo this action.'),
160
type: 'warning',
161
title: EDIT_SESSIONS_TITLE.value
162
});
163
if (result.confirmed) {
164
await editSessionStorageService.delete('editSessions', null);
165
await treeView.refresh();
166
}
167
}
168
}));
169
}
170
}
171
172
class EditSessionDataViewDataProvider implements ITreeViewDataProvider {
173
174
private editSessionsCount;
175
176
constructor(
177
@IEditSessionsStorageService private readonly editSessionsStorageService: IEditSessionsStorageService,
178
@IContextKeyService private readonly contextKeyService: IContextKeyService,
179
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
180
@IFileService private readonly fileService: IFileService,
181
) {
182
this.editSessionsCount = EDIT_SESSIONS_COUNT_CONTEXT_KEY.bindTo(this.contextKeyService);
183
}
184
185
async getChildren(element?: ITreeItem): Promise<ITreeItem[]> {
186
if (!element) {
187
return this.getAllEditSessions();
188
}
189
190
const [ref, folderName, filePath] = URI.parse(element.handle).path.substring(1).split('/');
191
192
if (ref && !folderName) {
193
return this.getEditSession(ref);
194
} else if (ref && folderName && !filePath) {
195
return this.getEditSessionFolderContents(ref, folderName);
196
}
197
198
return [];
199
}
200
201
private async getAllEditSessions(): Promise<ITreeItem[]> {
202
const allEditSessions = await this.editSessionsStorageService.list('editSessions');
203
this.editSessionsCount.set(allEditSessions.length);
204
const editSessions = [];
205
206
for (const session of allEditSessions) {
207
const resource = URI.from({ scheme: EDIT_SESSIONS_SCHEME, authority: 'remote-session-content', path: `/${session.ref}` });
208
const sessionData = await this.editSessionsStorageService.read('editSessions', session.ref);
209
if (!sessionData) {
210
continue;
211
}
212
const content: EditSession = JSON.parse(sessionData.content);
213
const label = content.folders.map((folder) => folder.name).join(', ') ?? session.ref;
214
const machineId = content.machine;
215
const machineName = machineId ? await this.editSessionsStorageService.getMachineById(machineId) : undefined;
216
const description = machineName === undefined ? fromNow(session.created, true) : `${fromNow(session.created, true)}\u00a0\u00a0\u2022\u00a0\u00a0${machineName}`;
217
218
editSessions.push({
219
handle: resource.toString(),
220
collapsibleState: TreeItemCollapsibleState.Collapsed,
221
label: { label },
222
description: description,
223
themeIcon: Codicon.repo,
224
contextValue: `edit-session`
225
});
226
}
227
228
return editSessions;
229
}
230
231
private async getEditSession(ref: string): Promise<ITreeItem[]> {
232
const data = await this.editSessionsStorageService.read('editSessions', ref);
233
234
if (!data) {
235
return [];
236
}
237
const content: EditSession = JSON.parse(data.content);
238
239
if (content.folders.length === 1) {
240
const folder = content.folders[0];
241
return this.getEditSessionFolderContents(ref, folder.name);
242
}
243
244
return content.folders.map((folder) => {
245
const resource = URI.from({ scheme: EDIT_SESSIONS_SCHEME, authority: 'remote-session-content', path: `/${data.ref}/${folder.name}` });
246
return {
247
handle: resource.toString(),
248
collapsibleState: TreeItemCollapsibleState.Collapsed,
249
label: { label: folder.name },
250
themeIcon: Codicon.folder
251
};
252
});
253
}
254
255
private async getEditSessionFolderContents(ref: string, folderName: string): Promise<ITreeItem[]> {
256
const data = await this.editSessionsStorageService.read('editSessions', ref);
257
258
if (!data) {
259
return [];
260
}
261
const content: EditSession = JSON.parse(data.content);
262
263
const currentWorkspaceFolder = this.workspaceContextService.getWorkspace().folders.find((folder) => folder.name === folderName);
264
const editSessionFolder = content.folders.find((folder) => folder.name === folderName);
265
266
if (!editSessionFolder) {
267
return [];
268
}
269
270
return Promise.all(editSessionFolder.workingChanges.map(async (change) => {
271
const cloudChangeUri = URI.from({ scheme: EDIT_SESSIONS_SCHEME, authority: 'remote-session-content', path: `/${data.ref}/${folderName}/${change.relativeFilePath}` });
272
273
if (currentWorkspaceFolder?.uri) {
274
// find the corresponding file in the workspace
275
const localCopy = joinPath(currentWorkspaceFolder.uri, change.relativeFilePath);
276
if (change.type === ChangeType.Addition && await this.fileService.exists(localCopy)) {
277
return {
278
handle: cloudChangeUri.toString(),
279
resourceUri: cloudChangeUri,
280
collapsibleState: TreeItemCollapsibleState.None,
281
label: { label: change.relativeFilePath },
282
themeIcon: Codicon.file,
283
command: {
284
id: 'vscode.diff',
285
title: localize('compare changes', 'Compare Changes'),
286
arguments: [
287
localCopy,
288
cloudChangeUri,
289
`${basename(change.relativeFilePath)} (${localize('local copy', 'Local Copy')} \u2194 ${localize('cloud changes', 'Cloud Changes')})`,
290
undefined
291
]
292
}
293
};
294
}
295
}
296
297
return {
298
handle: cloudChangeUri.toString(),
299
resourceUri: cloudChangeUri,
300
collapsibleState: TreeItemCollapsibleState.None,
301
label: { label: change.relativeFilePath },
302
themeIcon: Codicon.file,
303
command: {
304
id: API_OPEN_EDITOR_COMMAND_ID,
305
title: localize('open file', 'Open File'),
306
arguments: [cloudChangeUri, undefined, undefined]
307
}
308
};
309
}));
310
}
311
}
312
313