@opensumi/ide-preferences 2.21.13 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/lib/browser/abstract-resource-preference-provider.d.ts +1 -0
  2. package/lib/browser/abstract-resource-preference-provider.d.ts.map +1 -1
  3. package/lib/browser/abstract-resource-preference-provider.js +22 -3
  4. package/lib/browser/abstract-resource-preference-provider.js.map +1 -1
  5. package/lib/browser/folder-file-preference-provider.d.ts +24 -0
  6. package/lib/browser/folder-file-preference-provider.d.ts.map +1 -0
  7. package/lib/browser/{folder-preference-provider.js → folder-file-preference-provider.js} +12 -14
  8. package/lib/browser/folder-file-preference-provider.js.map +1 -0
  9. package/lib/browser/folders-preferences-provider.d.ts +8 -8
  10. package/lib/browser/folders-preferences-provider.d.ts.map +1 -1
  11. package/lib/browser/folders-preferences-provider.js +7 -7
  12. package/lib/browser/folders-preferences-provider.js.map +1 -1
  13. package/lib/browser/index.d.ts.map +1 -1
  14. package/lib/browser/index.js +8 -8
  15. package/lib/browser/index.js.map +1 -1
  16. package/lib/browser/preference-contribution.d.ts.map +1 -1
  17. package/lib/browser/preference-contribution.js +3 -3
  18. package/lib/browser/preference-contribution.js.map +1 -1
  19. package/lib/browser/preference-settings.service.d.ts +22 -10
  20. package/lib/browser/preference-settings.service.d.ts.map +1 -1
  21. package/lib/browser/preference-settings.service.js +284 -101
  22. package/lib/browser/preference-settings.service.js.map +1 -1
  23. package/lib/browser/preference-widgets.js.map +1 -1
  24. package/lib/browser/preferenceItem.view.d.ts +4 -3
  25. package/lib/browser/preferenceItem.view.d.ts.map +1 -1
  26. package/lib/browser/preferenceItem.view.js +125 -62
  27. package/lib/browser/preferenceItem.view.js.map +1 -1
  28. package/lib/browser/preferences.module.less +100 -60
  29. package/lib/browser/preferences.view.d.ts +3 -17
  30. package/lib/browser/preferences.view.d.ts.map +1 -1
  31. package/lib/browser/preferences.view.js +194 -112
  32. package/lib/browser/preferences.view.js.map +1 -1
  33. package/lib/browser/user-preference-provider.js.map +1 -1
  34. package/lib/browser/userstorage/user-storage.contribution.js.map +1 -1
  35. package/lib/browser/userstorage/user-storage.service.js +7 -7
  36. package/lib/browser/userstorage/user-storage.service.js.map +1 -1
  37. package/lib/browser/workspace-file-preference-provider.d.ts +1 -1
  38. package/lib/browser/workspace-file-preference-provider.d.ts.map +1 -1
  39. package/lib/browser/workspace-file-preference-provider.js.map +1 -1
  40. package/lib/browser/workspace-preference-provider.js.map +1 -1
  41. package/lib/common/preference-id.d.ts +3 -0
  42. package/lib/common/preference-id.d.ts.map +1 -1
  43. package/lib/common/preference-id.js +4 -1
  44. package/lib/common/preference-id.js.map +1 -1
  45. package/lib/common/preference.d.ts +1 -0
  46. package/lib/common/preference.d.ts.map +1 -1
  47. package/lib/common/preference.js +26 -4
  48. package/lib/common/preference.js.map +1 -1
  49. package/lib/common/types.d.ts +18 -3
  50. package/lib/common/types.d.ts.map +1 -1
  51. package/lib/common/user-storage.d.ts +1 -1
  52. package/lib/common/user-storage.d.ts.map +1 -1
  53. package/package.json +12 -11
  54. package/src/browser/abstract-resource-preference-provider.ts +357 -0
  55. package/src/browser/folder-file-preference-provider.ts +56 -0
  56. package/src/browser/folders-preferences-provider.ts +262 -0
  57. package/src/browser/index.ts +125 -0
  58. package/src/browser/preference-contribution.ts +432 -0
  59. package/src/browser/preference-settings.service.ts +810 -0
  60. package/src/browser/preference-widgets.ts +368 -0
  61. package/src/browser/preferenceItem.view.tsx +787 -0
  62. package/src/browser/preferences.module.less +345 -0
  63. package/src/browser/preferences.view.tsx +388 -0
  64. package/src/browser/user-preference-provider.ts +18 -0
  65. package/src/browser/userstorage/index.ts +2 -0
  66. package/src/browser/userstorage/user-storage.contribution.ts +19 -0
  67. package/src/browser/userstorage/user-storage.service.ts +192 -0
  68. package/src/browser/workspace-file-preference-provider.ts +50 -0
  69. package/src/browser/workspace-preference-provider.ts +129 -0
  70. package/src/common/commands.ts +5 -0
  71. package/src/common/index.ts +4 -0
  72. package/src/common/preference-id.ts +11 -0
  73. package/src/common/preference.ts +51 -0
  74. package/src/common/types.ts +63 -0
  75. package/src/common/user-storage.ts +6 -0
  76. package/src/index.ts +1 -0
  77. package/lib/browser/folder-preference-provider.d.ts +0 -26
  78. package/lib/browser/folder-preference-provider.d.ts.map +0 -1
  79. package/lib/browser/folder-preference-provider.js.map +0 -1
  80. package/lib/browser/index.less +0 -36
@@ -0,0 +1,388 @@
1
+ import debounce from 'lodash/debounce';
2
+ import { observer } from 'mobx-react-lite';
3
+ import React, { useState } from 'react';
4
+ import AutoSizer from 'react-virtualized-auto-sizer';
5
+
6
+ import {
7
+ Input,
8
+ ComponentContextProvider,
9
+ Tabs,
10
+ IIconResourceOptions,
11
+ BasicRecycleTree,
12
+ IBasicTreeData,
13
+ } from '@opensumi/ide-components';
14
+ import { VirtualList } from '@opensumi/ide-components/lib/virtual-list';
15
+ import { IVirtualListRange } from '@opensumi/ide-components/lib/virtual-list/types';
16
+ import {
17
+ useInjectable,
18
+ localize,
19
+ formatLocalize,
20
+ ISettingGroup,
21
+ IPreferenceSettingsService,
22
+ ISettingSection,
23
+ getIcon,
24
+ URI,
25
+ LabelService,
26
+ IResolvedSettingSection,
27
+ } from '@opensumi/ide-core-browser';
28
+ import { SplitPanel } from '@opensumi/ide-core-browser/lib/components/layout/split-panel';
29
+ import useThrottleFn from '@opensumi/ide-core-browser/lib/react-hooks/useThrottleFn';
30
+ import { ReactEditorComponent } from '@opensumi/ide-editor/lib/browser';
31
+
32
+ import { ESectionItemKind, ISectionItemData, toNormalCase } from '../common';
33
+
34
+ import { PreferenceSettingsService } from './preference-settings.service';
35
+ import { NextPreferenceItem } from './preferenceItem.view';
36
+ import styles from './preferences.module.less';
37
+
38
+ interface IPreferenceTreeData extends IBasicTreeData {
39
+ section?: string;
40
+ groupId?: string;
41
+ order?: number;
42
+ }
43
+
44
+ const TREE_NAME = 'preferenceViewIndexTree';
45
+
46
+ export const PreferenceView: ReactEditorComponent<null> = observer(() => {
47
+ const preferenceService: PreferenceSettingsService = useInjectable(IPreferenceSettingsService);
48
+ const labelService = useInjectable<LabelService>(LabelService);
49
+ const getResourceIcon = React.useCallback(
50
+ (uri: string, options: IIconResourceOptions) => labelService.getIcon(URI.parse(uri), options),
51
+ [],
52
+ );
53
+
54
+ const inputRef = React.useRef<HTMLInputElement | null>(null);
55
+ const [focusItem, setFocusItem] = useState<string | undefined>(undefined);
56
+
57
+ const debouncedSearch = debounce(
58
+ (value: string) => {
59
+ preferenceService.search(value);
60
+ },
61
+ 100,
62
+ { maxWait: 300 },
63
+ );
64
+
65
+ React.useEffect(() => {
66
+ const focusDispose = preferenceService.onFocus(() => {
67
+ if (inputRef && inputRef.current) {
68
+ inputRef.current.focus();
69
+ }
70
+ });
71
+ return () => {
72
+ focusDispose.dispose();
73
+ };
74
+ }, []);
75
+
76
+ React.useEffect(() => {
77
+ if (focusItem && preferenceService.treeHandler?.focusItem) {
78
+ preferenceService.treeHandler.focusItem(focusItem);
79
+ }
80
+ }, [preferenceService.tabIndex, preferenceService.treeHandler, focusItem]);
81
+
82
+ const treeData = React.useMemo(() => {
83
+ if (!preferenceService.groups) {
84
+ return [];
85
+ }
86
+
87
+ const parseTreeData = (id: string, section: ISettingSection, i: number) => {
88
+ let innerTreeData: IPreferenceTreeData | undefined;
89
+ if (section.title) {
90
+ innerTreeData = {
91
+ label: section.title,
92
+ section: section.title,
93
+ groupId: id,
94
+ order: i,
95
+ } as IPreferenceTreeData;
96
+ }
97
+ const subTreeData = [] as IPreferenceTreeData[];
98
+ if (section.subSections) {
99
+ section.subSections.forEach((v, _i) => {
100
+ const _treeData = parseTreeData(id, v, _i);
101
+ _treeData && subTreeData.push(_treeData);
102
+ });
103
+ }
104
+ if (innerTreeData && subTreeData && subTreeData.length > 0) {
105
+ innerTreeData.children = subTreeData;
106
+ innerTreeData.expandable = true;
107
+ }
108
+ return innerTreeData;
109
+ };
110
+
111
+ const basicTreeData = [] as IPreferenceTreeData[];
112
+ for (let index = 0; index < preferenceService.groups.length; index++) {
113
+ const { id, title, iconClass } = preferenceService.groups[index];
114
+ const data = {
115
+ label: toNormalCase(title),
116
+ iconClassName: iconClass,
117
+ groupId: id,
118
+ order: index,
119
+ } as IPreferenceTreeData;
120
+ const children = [] as IPreferenceTreeData[];
121
+ const sections = preferenceService.getResolvedSections(
122
+ id,
123
+ preferenceService.currentScope,
124
+ preferenceService.currentSearch,
125
+ );
126
+ sections.forEach((sec, i) => {
127
+ const _treeData = parseTreeData(id, sec, i);
128
+ if (_treeData) {
129
+ children.push(_treeData);
130
+ }
131
+ });
132
+ // 要传这个,让 BasicTree 认为这是文件夹以保持排列顺序
133
+ data.children = children;
134
+ if (children.length > 0) {
135
+ data.expandable = true;
136
+ }
137
+ basicTreeData.push(data);
138
+ }
139
+
140
+ return basicTreeData;
141
+ }, [preferenceService.groups, preferenceService.getResolvedSections]);
142
+
143
+ const items = React.useMemo(() => {
144
+ // 如果是搜索模式,是只展示用户左侧选择的组的内容
145
+ const result: ISectionItemData[] = [];
146
+ preferenceService.groups.forEach((v) => {
147
+ result.push(...collectGroup(v));
148
+ });
149
+ return result;
150
+
151
+ function collectGroup(group: ISettingGroup) {
152
+ const groupItems = [] as ISectionItemData[];
153
+ const sections = preferenceService.getResolvedSections(
154
+ group.id,
155
+ preferenceService.currentScope,
156
+ preferenceService.currentSearch,
157
+ );
158
+
159
+ const collectItem = (section: IResolvedSettingSection, prefix = '') => {
160
+ let currentItemPath = prefix;
161
+ if (section.title) {
162
+ currentItemPath = prefix + '/' + section.title;
163
+ }
164
+
165
+ const innerItems = [] as ISectionItemData[];
166
+
167
+ if (section.component) {
168
+ innerItems.push({
169
+ component: section.component,
170
+ scope: preferenceService.currentScope,
171
+ });
172
+ } else if (section.preferences) {
173
+ innerItems.push(
174
+ ...section.preferences.map((pre) => ({
175
+ id: ESectionItemKind.Preference + pre.id,
176
+ preference: pre,
177
+ scope: preferenceService.currentScope,
178
+ _path: currentItemPath,
179
+ })),
180
+ );
181
+ } else if (section.subSections) {
182
+ section.subSections.forEach((v) => {
183
+ const _items = collectItem(v, currentItemPath);
184
+ innerItems.push(..._items);
185
+ });
186
+ }
187
+
188
+ // 如果该 section 有选项,填入一个 title
189
+ if (innerItems.length > 0 && section.title) {
190
+ innerItems.unshift({
191
+ id: ESectionItemKind.Section + section.title,
192
+ title: section.title,
193
+ scope: preferenceService.currentScope,
194
+ _path: currentItemPath,
195
+ });
196
+ }
197
+
198
+ return innerItems;
199
+ };
200
+
201
+ for (const section of sections) {
202
+ const _items = collectItem(section, group.title);
203
+ groupItems.push(..._items);
204
+ }
205
+
206
+ // 如果该 group 有选项,填入一个 title
207
+ if (groupItems.length > 0 && group.title) {
208
+ groupItems.unshift({
209
+ title: group.title,
210
+ id: ESectionItemKind.Group + group.id,
211
+ scope: preferenceService.currentScope,
212
+ _path: group.title,
213
+ });
214
+ }
215
+ return groupItems;
216
+ }
217
+ }, [preferenceService.groups, preferenceService.currentScope, preferenceService.currentSearch]);
218
+
219
+ const navigateTo = (id: string) => {
220
+ if (id) {
221
+ const index = items.findIndex((item) => item.id === id);
222
+ if (index >= 0) {
223
+ preferenceService.listHandler?.scrollToIndex({
224
+ index,
225
+ behavior: 'auto',
226
+ align: 'start',
227
+ });
228
+ }
229
+ }
230
+ };
231
+
232
+ const onRangeChanged = useThrottleFn(
233
+ (range: IVirtualListRange) => {
234
+ // 我们通过第一个 item 来变更左侧文件树的选择状态
235
+ // 当我们点击左侧的 section 的时候,我们的设计是让每一个 section 的 title 滚到顶部
236
+ // 此时仍然会触发该事件,但有时可能因为计算取整等原因,它上报的 startIndex 是 title 的上一个 index。
237
+ // 我们在这里 +1 就是防止因为计算错误而取到上一个章节的 _path 的情况。
238
+ const item = items[range.startIndex + 1];
239
+ if (item && item._path) {
240
+ setFocusItem(item._path);
241
+ }
242
+ },
243
+ 16 * 3,
244
+ {
245
+ leading: true,
246
+ trailing: true,
247
+ },
248
+ );
249
+
250
+ React.useEffect(() => {
251
+ if (preferenceService.currentSelectId) {
252
+ navigateTo(preferenceService.currentSelectId);
253
+ }
254
+ }, [items, preferenceService.currentSelectId]);
255
+
256
+ return (
257
+ <ComponentContextProvider value={{ getIcon, localize, getResourceIcon }}>
258
+ <div className={styles.preferences}>
259
+ <div className={styles.preferences_header}>
260
+ <Tabs
261
+ className={styles.tabs}
262
+ value={preferenceService.tabIndex}
263
+ onChange={(index: number) => {
264
+ preferenceService.tabIndex = index;
265
+ }}
266
+ tabs={preferenceService.tabList.map((n) => localize(n.label))}
267
+ />
268
+ <div className={styles.search_pref}>
269
+ <Input
270
+ autoFocus
271
+ value={preferenceService.currentSearch}
272
+ placeholder={localize('preference.searchPlaceholder')}
273
+ onValueChange={debouncedSearch}
274
+ ref={inputRef}
275
+ hasClear
276
+ />
277
+ </div>
278
+ </div>
279
+ {preferenceService.groups.length > 0 ? (
280
+ <SplitPanel
281
+ id='preference-panel'
282
+ resizeHandleClassName={styles.devider}
283
+ className={styles.preferences_body}
284
+ direction='left-to-right'
285
+ >
286
+ <AutoSizer className={styles.preferences_indexes} data-sp-defaultSize={180} data-sp-minSize={150}>
287
+ {({ width, height }) => (
288
+ <BasicRecycleTree
289
+ treeName={TREE_NAME}
290
+ sortComparator={(a: IPreferenceTreeData, b: IPreferenceTreeData) => {
291
+ if (typeof a.order !== 'undefined' && typeof b.order !== 'undefined') {
292
+ return a.order > b.order ? 1 : a.order < b.order ? -1 : 0;
293
+ }
294
+ return undefined;
295
+ }}
296
+ height={height}
297
+ width={width}
298
+ itemHeight={26}
299
+ getItemClassName={(item) => {
300
+ if (item?.depth === 1) {
301
+ return styles.group_item;
302
+ }
303
+ return styles.index_item;
304
+ }}
305
+ baseIndent={8}
306
+ treeData={treeData}
307
+ onClick={(_e, node) => {
308
+ const treeData = node && ((node as any)._raw as IPreferenceTreeData);
309
+ if (treeData) {
310
+ if (treeData.section) {
311
+ preferenceService.scrollToSection(treeData.section);
312
+ } else if (treeData.groupId) {
313
+ preferenceService.scrollToGroup(treeData.groupId);
314
+ }
315
+ }
316
+ }}
317
+ onReady={(handler) => {
318
+ preferenceService.handleTreeHandler(handler);
319
+ }}
320
+ />
321
+ )}
322
+ </AutoSizer>
323
+ <div className={styles.preferences_items} data-sp-flex={1}>
324
+ <PreferenceBody
325
+ items={items}
326
+ onReady={preferenceService.handleListHandler}
327
+ onRangeChanged={onRangeChanged.run}
328
+ />
329
+ </div>
330
+ </SplitPanel>
331
+ ) : (
332
+ <div className={styles.preference_noResults}>
333
+ {preferenceService.currentSearch
334
+ ? formatLocalize('preference.noResults', preferenceService.currentSearch)
335
+ : formatLocalize('preference.empty')}
336
+ </div>
337
+ )}
338
+ </div>
339
+ </ComponentContextProvider>
340
+ );
341
+ });
342
+
343
+ export const PreferenceItem = ({ data, index }: { data: ISectionItemData; index: number }) => {
344
+ if (data.title) {
345
+ if (data.id?.startsWith(ESectionItemKind.Group)) {
346
+ return (
347
+ <div className={styles.group_title} id={data.id}>
348
+ {data.title}
349
+ </div>
350
+ );
351
+ }
352
+ return (
353
+ <div className={styles.section_title} id={data.id}>
354
+ {data.title}
355
+ </div>
356
+ );
357
+ } else if (data.component) {
358
+ return <data.component scope={data.scope} />;
359
+ } else if (data.preference) {
360
+ return (
361
+ <NextPreferenceItem
362
+ key={`${index} - ${data.preference.id} - ${data.scope}`}
363
+ preference={data.preference}
364
+ preferenceId={data.preference.id}
365
+ localizedName={data.preference.label}
366
+ scope={data.scope}
367
+ />
368
+ );
369
+ }
370
+ };
371
+
372
+ export const PreferenceBody = ({
373
+ items,
374
+ onReady,
375
+ onRangeChanged,
376
+ }: {
377
+ items: ISectionItemData[];
378
+ onReady: (handler: any) => void;
379
+ onRangeChanged: (props: IVirtualListRange) => any;
380
+ }) => (
381
+ <VirtualList
382
+ data={items}
383
+ template={PreferenceItem as React.FunctionComponent<{ data: ISectionItemData; index: number }>}
384
+ className={styles.preference_section}
385
+ refSetter={onReady}
386
+ onRangeChanged={onRangeChanged}
387
+ />
388
+ );
@@ -0,0 +1,18 @@
1
+ import { Injectable } from '@opensumi/di';
2
+ import { Schemes, URI } from '@opensumi/ide-core-browser';
3
+ import { PreferenceScope } from '@opensumi/ide-core-browser/lib/preferences';
4
+
5
+ import { AbstractResourcePreferenceProvider } from './abstract-resource-preference-provider';
6
+
7
+ export const USER_PREFERENCE_URI = new URI().withScheme(Schemes.userStorage).withPath('settings.json');
8
+
9
+ @Injectable()
10
+ export class UserPreferenceProvider extends AbstractResourcePreferenceProvider {
11
+ protected getUri(): URI {
12
+ return USER_PREFERENCE_URI;
13
+ }
14
+
15
+ protected getScope() {
16
+ return PreferenceScope.User;
17
+ }
18
+ }
@@ -0,0 +1,2 @@
1
+ export * from './user-storage.service';
2
+ export * from './user-storage.contribution';
@@ -0,0 +1,19 @@
1
+ import { Autowired } from '@opensumi/di';
2
+ import { Domain, ClientAppContribution, Schemes } from '@opensumi/ide-core-browser';
3
+ import { IFileServiceClient } from '@opensumi/ide-file-service';
4
+ import { FileServiceClient } from '@opensumi/ide-file-service/lib/browser/file-service-client';
5
+
6
+ import { IUserStorageService } from '../../common';
7
+
8
+ @Domain(ClientAppContribution)
9
+ export class UserStorageContribution implements ClientAppContribution {
10
+ @Autowired(IUserStorageService)
11
+ private readonly userStorageService: IUserStorageService;
12
+
13
+ @Autowired(IFileServiceClient)
14
+ protected readonly fileSystem: FileServiceClient;
15
+
16
+ initialize() {
17
+ this.fileSystem.registerProvider(Schemes.userStorage, this.userStorageService);
18
+ }
19
+ }
@@ -0,0 +1,192 @@
1
+ import merge from 'lodash/merge';
2
+
3
+ import { Injectable, Autowired } from '@opensumi/di';
4
+ import {
5
+ DisposableCollection,
6
+ ILogger,
7
+ Emitter,
8
+ URI,
9
+ AppConfig,
10
+ Uri,
11
+ FileType,
12
+ FileChangeEvent,
13
+ } from '@opensumi/ide-core-browser';
14
+ import { Event, FileSystemProviderCapabilities, Schemes } from '@opensumi/ide-core-common';
15
+ import { FileSetContentOptions } from '@opensumi/ide-file-service/lib/common';
16
+ import { IFileServiceClient } from '@opensumi/ide-file-service/lib/common';
17
+
18
+ import { IUserStorageService } from '../../common';
19
+
20
+ export const DEFAULT_USER_STORAGE_FOLDER = '.sumi';
21
+
22
+ const DEFAULT_WATCH_OPTIONS = { recursive: false, excludes: ['**/logs/**'] };
23
+
24
+ @Injectable()
25
+ export class UserStorageServiceImpl implements IUserStorageService {
26
+ capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite;
27
+ onDidChangeCapabilities = Event.None;
28
+ /**
29
+ * 基于用户存储路径创建文件路径
30
+ * @param userStorageFolderUri 存储目录路径,如 file://{home}/
31
+ * @param fsPath 文件系统路径
32
+ */
33
+ public static toUserStorageUri(userStorageFolderUri: URI, rawUri: URI): URI {
34
+ const userStorageRelativePath = this.getRelativeUserStoragePath(userStorageFolderUri, rawUri);
35
+ return new URI('')
36
+ .withScheme(Schemes.userStorage)
37
+ .withPath(userStorageRelativePath)
38
+ .withFragment(rawUri.fragment)
39
+ .withQuery(rawUri.query);
40
+ }
41
+
42
+ /**
43
+ * 返回相对存储路径
44
+ * 如传入 'file://{path_to_userhome}/.sumi', 'file://{path_to_userhome}/.sumi/settings.json',
45
+ * 则返回 'settings.json'
46
+ * @param userStorageFolderUri 存储目录路径,如 file://{path_to_userhome}/
47
+ * @param fileUri 文件路径
48
+ */
49
+ private static getRelativeUserStoragePath(userStorageFolderUri: URI, fileUri: URI): string {
50
+ // 返回虚拟用户协议下的路径,去掉头部的/,如返回settings.json而不是/settings.json
51
+ return fileUri.toString().slice(userStorageFolderUri.toString().length + 1);
52
+ }
53
+
54
+ /**
55
+ * 返回用户路径下的文件绝对路径
56
+ * @param userStorageFolderUri 存储目录路径,如 file://{home}/.sumi
57
+ * @param userStorageUri 存储文件路径,如 file://{home}/.sumi/settings.json
58
+ */
59
+ public static toFilesystemURI(userStorageFolderUri: URI, userStorageUri: URI): URI {
60
+ return userStorageFolderUri.withPath(userStorageFolderUri.path.join(userStorageUri.path.toString()));
61
+ }
62
+
63
+ protected readonly toDispose = new DisposableCollection();
64
+ protected readonly onDidChangeFileEmitter = new Emitter<FileChangeEvent>();
65
+ private _whenReady: Promise<void>;
66
+ private userStorageFolder: URI;
67
+
68
+ @Autowired(IFileServiceClient)
69
+ protected readonly fileServiceClient: IFileServiceClient;
70
+ @Autowired(ILogger)
71
+ protected readonly logger: ILogger;
72
+ @Autowired(AppConfig)
73
+ protected readonly appConfig: AppConfig;
74
+
75
+ constructor() {
76
+ this._whenReady = this.init();
77
+ }
78
+
79
+ get whenReady() {
80
+ return this._whenReady;
81
+ }
82
+
83
+ get onDidChangeFile() {
84
+ return this.onDidChangeFileEmitter.event;
85
+ }
86
+
87
+ async init() {
88
+ // 请求用户路径并存储
89
+ const home = await this.fileServiceClient.getCurrentUserHome();
90
+ if (home) {
91
+ const userStorageFolderUri = new URI(home.uri).resolve(
92
+ this.appConfig.userPreferenceDirName || this.appConfig.preferenceDirName || DEFAULT_USER_STORAGE_FOLDER,
93
+ );
94
+ if (!(await this.fileServiceClient.access(userStorageFolderUri.toString()))) {
95
+ await this.fileServiceClient.createFolder(userStorageFolderUri.toString());
96
+ }
97
+ this.userStorageFolder = userStorageFolderUri;
98
+ }
99
+ this.toDispose.push(this.onDidChangeFileEmitter);
100
+ }
101
+
102
+ readDirectory(uri: Uri): [string, FileType][] | Promise<[string, FileType][]> {
103
+ throw new Error('Method not implemented.');
104
+ }
105
+
106
+ createDirectory(uri: Uri) {
107
+ throw new Error('Method not implemented.');
108
+ }
109
+
110
+ async watch(uri: Uri, options: { recursive: boolean; excludes: string[] }) {
111
+ await this.whenReady;
112
+ const target = UserStorageServiceImpl.toFilesystemURI(this.userStorageFolder, URI.from(uri));
113
+ const watchOptions = merge(DEFAULT_WATCH_OPTIONS, options);
114
+ const watcher = await this.fileServiceClient.watchFileChanges(target.parent, watchOptions.excludes);
115
+ this.toDispose.push(watcher);
116
+ this.toDispose.push(
117
+ watcher.onFilesChanged((changes) => {
118
+ const effectedChanges: FileChangeEvent = [];
119
+ for (const change of changes) {
120
+ // 在UserStorage的监听模式下,只会存在一个独立的 Change 事件
121
+ // 故在获取到变更事件时直接推出遍历
122
+ if (change.uri === target.toString()) {
123
+ effectedChanges.push(change);
124
+ }
125
+ }
126
+ if (effectedChanges.length > 0) {
127
+ this.onDidChangeFileEmitter.fire(
128
+ effectedChanges.map((change) => ({
129
+ uri: UserStorageServiceImpl.toUserStorageUri(this.userStorageFolder, new URI(change.uri)).toString(),
130
+ type: change.type,
131
+ })),
132
+ );
133
+ }
134
+ }),
135
+ );
136
+ return watcher.watchId;
137
+ }
138
+
139
+ async readFile(uri: Uri) {
140
+ await this.whenReady;
141
+ const target = UserStorageServiceImpl.toFilesystemURI(this.userStorageFolder, URI.from(uri));
142
+ try {
143
+ const { content } = await this.fileServiceClient.readFile(target.toString());
144
+ return content.buffer;
145
+ } catch (e) {
146
+ throw new Error(e);
147
+ }
148
+ }
149
+
150
+ async writeFile(uri: Uri, content: Uint8Array, options?: FileSetContentOptions) {
151
+ await this.whenReady;
152
+ const target = UserStorageServiceImpl.toFilesystemURI(this.userStorageFolder, URI.from(uri));
153
+ try {
154
+ let fileStat = await this.fileServiceClient.getFileStat(target.toString());
155
+ if (fileStat) {
156
+ await this.fileServiceClient.setContent(fileStat, content, options);
157
+ } else {
158
+ fileStat = await this.fileServiceClient.createFile(target.toString());
159
+ await this.fileServiceClient.setContent(fileStat, content, options);
160
+ }
161
+ } catch (e) {
162
+ throw new Error(e);
163
+ }
164
+ }
165
+
166
+ delete(uri: Uri, options: { recursive: boolean; moveToTrash?: boolean }) {
167
+ throw new Error('Method not implemented.');
168
+ }
169
+
170
+ rename(oldUri: Uri, newUri: Uri, options: { overwrite: boolean }) {
171
+ throw new Error('Method not implemented.');
172
+ }
173
+
174
+ copy(source: Uri, destination: Uri, options: { overwrite: boolean }) {
175
+ throw new Error('Method not implemented.');
176
+ }
177
+
178
+ async stat(uri: Uri) {
179
+ await this.whenReady;
180
+ const target = UserStorageServiceImpl.toFilesystemURI(this.userStorageFolder, URI.from(uri));
181
+ const stat = await this.fileServiceClient.getFileStat(target.toString());
182
+ if (stat) {
183
+ return stat;
184
+ }
185
+ }
186
+
187
+ async access(uri: Uri, mode: number) {
188
+ await this.whenReady;
189
+ const target = UserStorageServiceImpl.toFilesystemURI(this.userStorageFolder, URI.from(uri));
190
+ return this.fileServiceClient.access(target.toString(), mode);
191
+ }
192
+ }
@@ -0,0 +1,50 @@
1
+ import { Autowired, Injectable } from '@opensumi/di';
2
+ import { URI } from '@opensumi/ide-core-browser';
3
+ import { PreferenceScope } from '@opensumi/ide-core-browser/lib/preferences';
4
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
5
+ import { WorkspaceData } from '@opensumi/ide-workspace/lib/browser/workspace-data';
6
+
7
+ import { AbstractResourcePreferenceProvider } from './abstract-resource-preference-provider';
8
+
9
+ @Injectable()
10
+ export class WorkspaceFilePreferenceProviderOptions {
11
+ workspaceUri: URI;
12
+ }
13
+
14
+ export const WorkspaceFilePreferenceProviderFactory = Symbol('WorkspaceFilePreferenceProviderFactory');
15
+ export type WorkspaceFilePreferenceProviderFactory = (
16
+ options: WorkspaceFilePreferenceProviderOptions,
17
+ ) => WorkspaceFilePreferenceProvider;
18
+
19
+ @Injectable()
20
+ export class WorkspaceFilePreferenceProvider extends AbstractResourcePreferenceProvider {
21
+ @Autowired(IWorkspaceService)
22
+ protected readonly workspaceService: IWorkspaceService;
23
+
24
+ @Autowired(WorkspaceFilePreferenceProviderOptions)
25
+ protected readonly options: WorkspaceFilePreferenceProviderOptions;
26
+
27
+ protected getUri(): URI {
28
+ return this.options.workspaceUri;
29
+ }
30
+
31
+ protected parse(content: string): any {
32
+ const data = super.parse(content);
33
+ if (WorkspaceData.is(data)) {
34
+ return data.settings || {};
35
+ }
36
+ return {};
37
+ }
38
+
39
+ protected getPath(preferenceName: string): string[] {
40
+ return ['settings', preferenceName];
41
+ }
42
+
43
+ protected getScope(): PreferenceScope {
44
+ return PreferenceScope.Workspace;
45
+ }
46
+
47
+ getDomain(): string[] {
48
+ return this.workspaceService.tryGetRoots().map((r) => r.uri);
49
+ }
50
+ }