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