@opensumi/ide-editor 2.22.10 → 2.23.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/breadcrumb/default.d.ts.map +1 -1
  2. package/lib/browser/breadcrumb/default.js +2 -0
  3. package/lib/browser/breadcrumb/default.js.map +1 -1
  4. package/lib/browser/diff/index.d.ts +5 -0
  5. package/lib/browser/diff/index.d.ts.map +1 -1
  6. package/lib/browser/diff/index.js +36 -3
  7. package/lib/browser/diff/index.js.map +1 -1
  8. package/lib/browser/doc-model/editor-document-model.d.ts.map +1 -1
  9. package/lib/browser/doc-model/editor-document-model.js +1 -0
  10. package/lib/browser/doc-model/editor-document-model.js.map +1 -1
  11. package/lib/browser/doc-model/saveParticipants.d.ts +14 -0
  12. package/lib/browser/doc-model/saveParticipants.d.ts.map +1 -1
  13. package/lib/browser/doc-model/saveParticipants.js +109 -1
  14. package/lib/browser/doc-model/saveParticipants.js.map +1 -1
  15. package/lib/browser/doc-model/types.d.ts +1 -0
  16. package/lib/browser/doc-model/types.d.ts.map +1 -1
  17. package/lib/browser/doc-model/types.js.map +1 -1
  18. package/lib/browser/editor-collection.service.d.ts +3 -2
  19. package/lib/browser/editor-collection.service.d.ts.map +1 -1
  20. package/lib/browser/editor-collection.service.js +30 -3
  21. package/lib/browser/editor-collection.service.js.map +1 -1
  22. package/lib/browser/editor.contribution.d.ts.map +1 -1
  23. package/lib/browser/editor.contribution.js +18 -0
  24. package/lib/browser/editor.contribution.js.map +1 -1
  25. package/lib/browser/editor.module.less +3 -0
  26. package/lib/browser/fs-resource/fs-resource.d.ts +3 -0
  27. package/lib/browser/fs-resource/fs-resource.d.ts.map +1 -1
  28. package/lib/browser/fs-resource/fs-resource.js +27 -1
  29. package/lib/browser/fs-resource/fs-resource.js.map +1 -1
  30. package/lib/browser/menu/breadcrumbs.menus.d.ts +8 -0
  31. package/lib/browser/menu/breadcrumbs.menus.d.ts.map +1 -0
  32. package/lib/browser/menu/breadcrumbs.menus.js +34 -0
  33. package/lib/browser/menu/breadcrumbs.menus.js.map +1 -0
  34. package/lib/browser/monaco-contrib/callHierarchy/callHierarchy.service.d.ts +1 -0
  35. package/lib/browser/monaco-contrib/callHierarchy/callHierarchy.service.d.ts.map +1 -1
  36. package/lib/browser/navigation.view.d.ts +7 -4
  37. package/lib/browser/navigation.view.d.ts.map +1 -1
  38. package/lib/browser/navigation.view.js +27 -11
  39. package/lib/browser/navigation.view.js.map +1 -1
  40. package/lib/browser/preference/contribution.d.ts.map +1 -1
  41. package/lib/browser/preference/contribution.js +1 -1
  42. package/lib/browser/preference/contribution.js.map +1 -1
  43. package/lib/browser/quick-open/workspace-symbol-quickopen.d.ts +1 -0
  44. package/lib/browser/quick-open/workspace-symbol-quickopen.d.ts.map +1 -1
  45. package/lib/browser/resource.service.js +1 -0
  46. package/lib/browser/resource.service.js.map +1 -1
  47. package/lib/browser/tab.view.d.ts.map +1 -1
  48. package/lib/browser/tab.view.js +2 -1
  49. package/lib/browser/tab.view.js.map +1 -1
  50. package/lib/browser/types.d.ts +10 -1
  51. package/lib/browser/types.d.ts.map +1 -1
  52. package/lib/browser/types.js +6 -0
  53. package/lib/browser/types.js.map +1 -1
  54. package/lib/common/language.d.ts +1 -0
  55. package/lib/common/language.d.ts.map +1 -1
  56. package/lib/common/mocks/workbench-editor.service.d.ts +1 -0
  57. package/lib/common/mocks/workbench-editor.service.d.ts.map +1 -1
  58. package/lib/common/mocks/workbench-editor.service.js +3 -0
  59. package/lib/common/mocks/workbench-editor.service.js.map +1 -1
  60. package/lib/common/resource.d.ts +3 -1
  61. package/lib/common/resource.d.ts.map +1 -1
  62. package/lib/common/resource.js.map +1 -1
  63. package/package.json +13 -13
  64. package/src/browser/breadcrumb/default.ts +2 -0
  65. package/src/browser/diff/index.ts +45 -10
  66. package/src/browser/doc-model/editor-document-model.ts +1 -0
  67. package/src/browser/doc-model/saveParticipants.ts +119 -1
  68. package/src/browser/doc-model/types.ts +1 -0
  69. package/src/browser/editor-collection.service.ts +42 -3
  70. package/src/browser/editor.contribution.ts +19 -0
  71. package/src/browser/editor.module.less +3 -0
  72. package/src/browser/fs-resource/fs-resource.ts +30 -1
  73. package/src/browser/menu/breadcrumbs.menus.ts +34 -0
  74. package/src/browser/navigation.view.tsx +30 -10
  75. package/src/browser/preference/contribution.ts +2 -2
  76. package/src/browser/resource.service.ts +1 -0
  77. package/src/browser/tab.view.tsx +5 -0
  78. package/src/browser/types.ts +15 -4
  79. package/src/common/mocks/workbench-editor.service.ts +4 -0
  80. package/src/common/resource.ts +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensumi/ide-editor",
3
- "version": "2.22.10",
3
+ "version": "2.23.0",
4
4
  "files": [
5
5
  "lib",
6
6
  "src"
@@ -17,21 +17,21 @@
17
17
  "url": "git@github.com:opensumi/core.git"
18
18
  },
19
19
  "dependencies": {
20
- "@opensumi/ide-core-common": "2.22.10",
21
- "@opensumi/ide-core-node": "2.22.10",
22
- "@opensumi/ide-file-service": "2.22.10",
23
- "@opensumi/ide-monaco": "2.22.10",
24
- "@opensumi/ide-utils": "2.22.10",
20
+ "@opensumi/ide-core-common": "2.23.0",
21
+ "@opensumi/ide-core-node": "2.23.0",
22
+ "@opensumi/ide-file-service": "2.23.0",
23
+ "@opensumi/ide-monaco": "2.23.0",
24
+ "@opensumi/ide-utils": "2.23.0",
25
25
  "vscode-oniguruma": "1.5.1"
26
26
  },
27
27
  "devDependencies": {
28
- "@opensumi/ide-components": "2.22.10",
29
- "@opensumi/ide-core-browser": "2.22.10",
28
+ "@opensumi/ide-components": "2.23.0",
29
+ "@opensumi/ide-core-browser": "2.23.0",
30
30
  "@opensumi/ide-dev-tool": "1.3.1",
31
- "@opensumi/ide-overlay": "2.22.10",
32
- "@opensumi/ide-quick-open": "2.22.10",
33
- "@opensumi/ide-theme": "2.22.10",
34
- "@opensumi/ide-workspace": "2.22.10"
31
+ "@opensumi/ide-overlay": "2.23.0",
32
+ "@opensumi/ide-quick-open": "2.23.0",
33
+ "@opensumi/ide-theme": "2.23.0",
34
+ "@opensumi/ide-workspace": "2.23.0"
35
35
  },
36
- "gitHead": "93351a388ce5e0f469ed1c3964e728784eb3955f"
36
+ "gitHead": "5d8e4fcf717531446c838d142f7601699fd50d0f"
37
37
  }
@@ -98,6 +98,7 @@ export class DefaultBreadCrumbProvider extends WithEventBus implements IBreadCru
98
98
 
99
99
  const res: IBreadCrumbPart = {
100
100
  name: uri.path.base,
101
+ uri,
101
102
  icon: this.labelService.getIcon(uri, { isDirectory }),
102
103
  getSiblings: async () => {
103
104
  const parentDir = URI.from({
@@ -173,6 +174,7 @@ export class DefaultBreadCrumbProvider extends WithEventBus implements IBreadCru
173
174
  const res: IBreadCrumbPart = {
174
175
  name: symbol.name,
175
176
  icon: getSymbolIcon(symbol.kind),
177
+ isSymbol: true,
176
178
  onClick: () => {
177
179
  editor.setSelection({
178
180
  startColumn: symbol.range.startColumn,
@@ -1,6 +1,7 @@
1
1
  import { Injectable, Autowired } from '@opensumi/di';
2
2
  import { URI, Domain, WithEventBus, OnEvent } from '@opensumi/ide-core-browser';
3
3
  import { LabelService } from '@opensumi/ide-core-browser/lib/services';
4
+ import { IFileServiceClient } from '@opensumi/ide-file-service';
4
5
 
5
6
  import { IResourceProvider, IDiffResource, ResourceService, ResourceDecorationChangeEvent } from '../../common';
6
7
  import { BrowserEditorContribution, EditorComponentRegistry, EditorOpenType } from '../types';
@@ -18,10 +19,15 @@ export class DiffResourceProvider extends WithEventBus implements IResourceProvi
18
19
  @Autowired(ResourceService)
19
20
  resourceService: ResourceService;
20
21
 
22
+ @Autowired(IFileServiceClient)
23
+ protected fileServiceClient: IFileServiceClient;
24
+
21
25
  scheme = 'diff';
22
26
 
23
27
  private modifiedToResource = new Map<string, URI>();
24
28
 
29
+ private userhomePath: URI | null;
30
+
25
31
  @OnEvent(ResourceDecorationChangeEvent)
26
32
  onResourceDecorationChangeEvent(e: ResourceDecorationChangeEvent) {
27
33
  if (e.payload.uri && this.modifiedToResource.has(e.payload.uri.toString())) {
@@ -34,21 +40,50 @@ export class DiffResourceProvider extends WithEventBus implements IResourceProvi
34
40
  }
35
41
  }
36
42
 
43
+ private async getCurrentUserHome() {
44
+ if (!this.userhomePath) {
45
+ try {
46
+ const userhome = await this.fileServiceClient.getCurrentUserHome();
47
+ if (userhome) {
48
+ this.userhomePath = new URI(userhome.uri);
49
+ }
50
+ } catch (err) {}
51
+ }
52
+ return this.userhomePath;
53
+ }
54
+
55
+ private async getReadableTooltip(path: URI) {
56
+ const pathStr = path.toString();
57
+ const userhomePath = await this.getCurrentUserHome();
58
+ if (!userhomePath) {
59
+ return decodeURIComponent(path.withScheme('').toString());
60
+ }
61
+ if (userhomePath.isEqualOrParent(path)) {
62
+ const userhomePathStr = userhomePath && userhomePath.toString();
63
+ return decodeURIComponent(pathStr.replace(userhomePathStr, '~'));
64
+ }
65
+ return decodeURIComponent(path.withScheme('').toString());
66
+ }
67
+
37
68
  async provideResource(uri: URI): Promise<IDiffResource> {
38
69
  const { original, modified, name } = uri.getParsedQuery();
39
70
  const originalUri = new URI(original);
40
71
  const modifiedUri = new URI(modified);
41
- const icon = await this.labelService.getIcon(originalUri);
42
72
  this.modifiedToResource.set(modifiedUri.toString(), uri);
43
- return {
44
- name,
45
- icon,
46
- uri,
47
- metadata: {
48
- original: originalUri,
49
- modified: modifiedUri,
50
- },
51
- };
73
+ return Promise.all([
74
+ this.labelService.getIcon(originalUri),
75
+ // 默认显示 modified 文件路径
76
+ this.getReadableTooltip(modifiedUri),
77
+ ]).then(([icon, title]) => ({
78
+ name,
79
+ icon,
80
+ uri,
81
+ metadata: {
82
+ original: originalUri,
83
+ modified: modifiedUri,
84
+ },
85
+ title,
86
+ }));
52
87
  }
53
88
 
54
89
  async shouldCloseResource(resource, openedResources): Promise<boolean> {
@@ -630,6 +630,7 @@ export class EditorDocumentModel extends Disposable implements IEditorDocumentMo
630
630
  new EditorDocumentModelContentChangedEvent({
631
631
  uri: this.uri,
632
632
  dirty: this.dirty,
633
+ readonly: this.readonly,
633
634
  changes,
634
635
  eol: this.eol,
635
636
  isRedoing,
@@ -1,4 +1,4 @@
1
- import { Injectable, Autowired } from '@opensumi/di';
1
+ import { Injectable, Autowired, Injector, INJECTOR_TOKEN } from '@opensumi/di';
2
2
  import {
3
3
  ClientAppContribution,
4
4
  WithEventBus,
@@ -18,6 +18,11 @@ import { IProgressService } from '@opensumi/ide-core-browser/lib/progress';
18
18
  import { ResourceEdit } from '@opensumi/ide-monaco/lib/browser/monaco-api';
19
19
  import { languageFeaturesService } from '@opensumi/ide-monaco/lib/browser/monaco-api/languages';
20
20
  import { ITextModel } from '@opensumi/ide-monaco/lib/browser/monaco-api/types';
21
+ import { Selection } from '@opensumi/monaco-editor-core';
22
+ import { IActiveCodeEditor } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
23
+ import { ICodeEditorService } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/services/codeEditorService';
24
+ import { EditOperation } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/editOperation';
25
+ import { Range } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/range';
21
26
  import * as languages from '@opensumi/monaco-editor-core/esm/vs/editor/common/languages';
22
27
  import { CodeActionProvider } from '@opensumi/monaco-editor-core/esm/vs/editor/common/languages';
23
28
  import {
@@ -30,10 +35,29 @@ import {
30
35
  } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/codeAction/browser/types';
31
36
  import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';
32
37
 
38
+ import { MonacoCodeService } from '../editor.override';
33
39
  import { SaveReason } from '../types';
34
40
 
35
41
  import { EditorDocumentModelWillSaveEvent, IEditorDocumentModelService } from './types';
36
42
 
43
+ function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null {
44
+ let candidate: IActiveCodeEditor | null = null;
45
+
46
+ if (model.isAttachedToEditor()) {
47
+ for (const editor of codeEditorService.listCodeEditors()) {
48
+ if (editor.hasModel() && editor.getModel() === model) {
49
+ if (editor.hasTextFocus()) {
50
+ return editor; // favour focused editor if there are multiple
51
+ }
52
+
53
+ candidate = editor;
54
+ }
55
+ }
56
+ }
57
+
58
+ return candidate;
59
+ }
60
+
37
61
  @Injectable()
38
62
  export class CodeActionOnSaveParticipant extends WithEventBus {
39
63
  @Autowired(PreferenceService)
@@ -216,12 +240,106 @@ export class CodeActionOnSaveParticipant extends WithEventBus {
216
240
  }
217
241
  }
218
242
 
243
+ @Injectable()
244
+ export class TrimFinalNewLinesParticipant extends WithEventBus {
245
+ @Autowired(PreferenceService)
246
+ private readonly preferenceService: PreferenceService;
247
+
248
+ @Autowired(IEditorDocumentModelService)
249
+ private readonly docService: IEditorDocumentModelService;
250
+
251
+ @Autowired(ILogger)
252
+ private readonly logger: ILogger;
253
+
254
+ @Autowired(INJECTOR_TOKEN)
255
+ private readonly injector: Injector;
256
+
257
+ activate() {
258
+ // noop
259
+ }
260
+
261
+ @OnEvent(EditorDocumentModelWillSaveEvent)
262
+ async onEditorDocumentModelWillSave(e: EditorDocumentModelWillSaveEvent) {
263
+ const isTrimFinalNewlines = this.preferenceService.get('files.trimFinalNewlines');
264
+
265
+ if (isTrimFinalNewlines) {
266
+ const modelRef = this.docService.getModelReference(e.payload.uri, 'trimFinalNewlines');
267
+
268
+ if (!modelRef) {
269
+ return;
270
+ }
271
+
272
+ const model = modelRef.instance.getMonacoModel();
273
+ this.doTrimFinalNewLines(model, e.payload.reason !== SaveReason.Manual);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * returns 0 if the entire file is empty
279
+ */
280
+ private findLastNonEmptyLine(model: ITextModel): number {
281
+ for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) {
282
+ const lineContent = model.getLineContent(lineNumber);
283
+ if (lineContent.length > 0) {
284
+ // this line has content
285
+ return lineNumber;
286
+ }
287
+ }
288
+ // no line has content
289
+ return 0;
290
+ }
291
+
292
+ private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void {
293
+ const lineCount = model.getLineCount();
294
+
295
+ // Do not insert new line if file does not end with new line
296
+ if (lineCount === 1) {
297
+ return;
298
+ }
299
+
300
+ let prevSelection: Selection[] = [];
301
+ let cannotTouchLineNumber = 0;
302
+
303
+ const codeEditorService = this.injector.get(MonacoCodeService);
304
+ const editor = findEditor(model, codeEditorService);
305
+ if (editor) {
306
+ prevSelection = editor.getSelections();
307
+ if (isAutoSaved) {
308
+ for (let i = 0, len = prevSelection.length; i < len; i++) {
309
+ const positionLineNumber = prevSelection[i].positionLineNumber;
310
+ if (positionLineNumber > cannotTouchLineNumber) {
311
+ cannotTouchLineNumber = positionLineNumber;
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ const lastNonEmptyLine = this.findLastNonEmptyLine(model);
318
+ const deleteFromLineNumber = Math.max(lastNonEmptyLine + 1, cannotTouchLineNumber + 1);
319
+ const deletionRange = model.validateRange(
320
+ new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount)),
321
+ );
322
+
323
+ if (deletionRange.isEmpty()) {
324
+ return;
325
+ }
326
+
327
+ model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], () => prevSelection);
328
+
329
+ editor?.setSelections(prevSelection);
330
+ }
331
+ }
332
+
219
333
  @Domain(ClientAppContribution)
220
334
  export class SaveParticipantsContribution implements ClientAppContribution {
221
335
  @Autowired()
222
336
  codeActionOnSaveParticipant: CodeActionOnSaveParticipant;
223
337
 
338
+ @Autowired()
339
+ trimFinalNewLinesParticipant: TrimFinalNewLinesParticipant;
340
+
224
341
  onStart() {
225
342
  this.codeActionOnSaveParticipant.activate();
343
+ this.trimFinalNewLinesParticipant.activate();
226
344
  }
227
345
  }
@@ -270,6 +270,7 @@ export class EditorDocumentModelContentChangedEvent extends BasicEvent<IEditorDo
270
270
  export interface IEditorDocumentModelContentChangedEventPayload {
271
271
  uri: URI;
272
272
  dirty: boolean;
273
+ readonly: boolean;
273
274
  changes: IEditorDocumentModelContentChange[];
274
275
  eol: string;
275
276
  versionId: number;
@@ -185,6 +185,7 @@ export class EditorCollectionServiceImpl extends WithEventBus implements EditorC
185
185
  uri: e.payload.uri,
186
186
  decoration: {
187
187
  dirty: !!e.payload.dirty,
188
+ readOnly: !!e.payload.readonly,
188
189
  },
189
190
  }),
190
191
  );
@@ -221,7 +222,7 @@ function updateOptionsWithMonacoEditor(
221
222
  }
222
223
 
223
224
  @Injectable({ multiple: true })
224
- export abstract class BaseMonacoEditorWrapper extends Disposable implements IEditor {
225
+ export abstract class BaseMonacoEditorWrapper extends WithEventBus implements IEditor {
225
226
  public abstract readonly currentDocumentModel: IEditorDocumentModel | null;
226
227
 
227
228
  public get currentUri(): URI | null {
@@ -517,10 +518,29 @@ export class BrowserCodeEditor extends BaseMonacoEditorWrapper implements ICodeE
517
518
  position: this.monacoEditor.getPosition(),
518
519
  selectionLength: 0,
519
520
  });
521
+
522
+ const { instance } = documentModelRef;
523
+
524
+ /**
525
+ * 由于通过 monaco model 并不能得到该文档是否 readonly
526
+ * 所以这里需要对 readonly 单独设置一下
527
+ */
528
+ this.monacoEditor.updateOptions({
529
+ readOnly: instance.readonly,
530
+ });
531
+
532
+ this.eventBus.fire(
533
+ new ResourceDecorationNeedChangeEvent({
534
+ uri: instance.uri,
535
+ decoration: {
536
+ readOnly: instance.readonly,
537
+ },
538
+ }),
539
+ );
520
540
  }
521
541
  }
522
542
 
523
- export class BrowserDiffEditor extends Disposable implements IDiffEditor {
543
+ export class BrowserDiffEditor extends WithEventBus implements IDiffEditor {
524
544
  @Autowired(EditorCollectionService)
525
545
  private collectionService: EditorCollectionServiceImpl;
526
546
 
@@ -676,13 +696,32 @@ export class BrowserDiffEditor extends Disposable implements IDiffEditor {
676
696
  await this.doUpdateDiffOptions();
677
697
  }
678
698
 
699
+ isReadonly(): boolean {
700
+ return !!this.modifiedDocModel?.readonly;
701
+ }
702
+
679
703
  private async doUpdateDiffOptions() {
680
704
  const uriStr = this.modifiedEditor.currentUri ? this.modifiedEditor.currentUri.toString() : undefined;
681
705
  const languageId = this.modifiedEditor.currentDocumentModel
682
706
  ? this.modifiedEditor.currentDocumentModel.languageId
683
707
  : undefined;
684
708
  const options = getConvertedMonacoOptions(this.configurationService, uriStr, languageId);
685
- this.monacoDiffEditor.updateOptions({ ...options.diffOptions, ...this.specialOptions });
709
+ this.monacoDiffEditor.updateOptions({
710
+ ...options.diffOptions,
711
+ ...this.specialOptions,
712
+ readOnly: this.isReadonly(),
713
+ });
714
+
715
+ if (this.currentUri) {
716
+ this.eventBus.fire(
717
+ new ResourceDecorationNeedChangeEvent({
718
+ uri: this.currentUri,
719
+ decoration: {
720
+ readOnly: this.isReadonly(),
721
+ },
722
+ }),
723
+ );
724
+ }
686
725
  }
687
726
 
688
727
  updateDiffOptions(options: Partial<monaco.editor.IDiffEditorOptions>) {
@@ -1202,6 +1202,25 @@ export class EditorContribution
1202
1202
  order: 2,
1203
1203
  });
1204
1204
 
1205
+ menus.registerMenuItem(MenuId.BreadcrumbsTitleContext, {
1206
+ command: EDITOR_COMMANDS.COPY_PATH.id,
1207
+ group: '0_path',
1208
+ order: 1,
1209
+ });
1210
+ menus.registerMenuItem(MenuId.BreadcrumbsTitleContext, {
1211
+ command: EDITOR_COMMANDS.COPY_RELATIVE_PATH.id,
1212
+ group: '0_path',
1213
+ order: 2,
1214
+ });
1215
+ menus.registerMenuItem(MenuId.BreadcrumbsTitleContext, {
1216
+ command: {
1217
+ id: FILE_COMMANDS.REVEAL_IN_EXPLORER.id,
1218
+ label: localize('file.revealInExplorer'),
1219
+ },
1220
+ group: '1_file',
1221
+ order: 3,
1222
+ });
1223
+
1205
1224
  menus.registerMenuItem(MenuId.EditorTitleContext, {
1206
1225
  command: EDITOR_COMMANDS.SPLIT_TO_LEFT.id,
1207
1226
  group: '9_split',
@@ -241,6 +241,9 @@
241
241
  background-position: 2px;
242
242
  }
243
243
  }
244
+ .editor_readonly_icon {
245
+ margin-left: 8px;
246
+ }
244
247
  .kt_editor_close_icon {
245
248
  margin-right: 0 !important;
246
249
  display: flex;
@@ -48,6 +48,8 @@ export class FileSystemResourceProvider extends WithEventBus implements IResourc
48
48
 
49
49
  private ready: Promise<void>;
50
50
 
51
+ private userhomePath: URI | null;
52
+
51
53
  constructor() {
52
54
  super();
53
55
  this.ready = this.init();
@@ -103,6 +105,31 @@ export class FileSystemResourceProvider extends WithEventBus implements IResourc
103
105
  return this.cachedFileStat.get(uri);
104
106
  }
105
107
 
108
+ private async getCurrentUserHome() {
109
+ if (!this.userhomePath) {
110
+ try {
111
+ const userhome = await this.fileServiceClient.getCurrentUserHome();
112
+ if (userhome) {
113
+ this.userhomePath = new URI(userhome.uri);
114
+ }
115
+ } catch (err) {}
116
+ }
117
+ return this.userhomePath;
118
+ }
119
+
120
+ private async getReadableTooltip(path: URI) {
121
+ const pathStr = path.toString();
122
+ const userhomePath = await this.getCurrentUserHome();
123
+ if (!userhomePath) {
124
+ return decodeURIComponent(path.withScheme('').toString());
125
+ }
126
+ if (userhomePath.isEqualOrParent(path)) {
127
+ const userhomePathStr = userhomePath && userhomePath.toString();
128
+ return decodeURIComponent(pathStr.replace(userhomePathStr, '~'));
129
+ }
130
+ return decodeURIComponent(path.withScheme('').toString());
131
+ }
132
+
106
133
  async provideResource(uri: URI): Promise<IResource<any>> {
107
134
  // 获取文件类型 getFileType: (path: string) => string
108
135
  await this.ready;
@@ -111,13 +138,15 @@ export class FileSystemResourceProvider extends WithEventBus implements IResourc
111
138
  this.getFileStat(uri.toString()),
112
139
  this.labelService.getName(uri),
113
140
  this.labelService.getIcon(uri),
114
- ] as const).then(([stat, name, icon]) => ({
141
+ this.getReadableTooltip(uri),
142
+ ] as const).then(([stat, name, icon, title]) => ({
115
143
  name: stat ? name : name + localize('file.resource-deleted', '(已删除)'),
116
144
  icon,
117
145
  uri,
118
146
  metadata: null,
119
147
  deleted: !stat,
120
148
  supportsRevive: true,
149
+ title,
121
150
  }));
122
151
  }
123
152
 
@@ -0,0 +1,34 @@
1
+ import { Injectable, Autowired } from '@opensumi/di';
2
+ import {
3
+ AbstractContextMenuService,
4
+ ICtxMenuRenderer,
5
+ MenuId,
6
+ IContextMenu,
7
+ } from '@opensumi/ide-core-browser/lib/menu/next';
8
+ import { URI } from '@opensumi/ide-core-common';
9
+
10
+ import { EditorGroup } from '../workbench-editor.service';
11
+
12
+ @Injectable()
13
+ export class BreadCrumbsMenuService {
14
+ @Autowired(AbstractContextMenuService)
15
+ private readonly ctxMenuService: AbstractContextMenuService;
16
+
17
+ @Autowired(ICtxMenuRenderer)
18
+ private readonly ctxMenuRenderer: ICtxMenuRenderer;
19
+
20
+ show(x: number, y: number, group: EditorGroup, uri: URI) {
21
+ const titleContext = group.contextKeyService;
22
+ const menus = this.ctxMenuService.createMenu({
23
+ id: MenuId.BreadcrumbsTitleContext,
24
+ contextKeyService: titleContext,
25
+ });
26
+ const menuNodes = menus.getMergedMenuNodes();
27
+
28
+ this.ctxMenuRenderer.show({
29
+ anchor: { x, y },
30
+ menuNodes,
31
+ args: [{ uri, group }],
32
+ });
33
+ }
34
+ }
@@ -9,6 +9,7 @@ import { Scrollbars } from '@opensumi/ide-components';
9
9
  import { useInjectable, DomListener, Disposable, useUpdateOnEvent } from '@opensumi/ide-core-browser';
10
10
  import { getIcon } from '@opensumi/ide-core-browser';
11
11
 
12
+ import { BreadCrumbsMenuService } from './menu/breadcrumbs.menus';
12
13
  import styles from './navigation.module.less';
13
14
  import { IBreadCrumbService, IBreadCrumbPart } from './types';
14
15
  import { useUpdateOnGroupTabChange } from './view/react-hook';
@@ -47,18 +48,24 @@ export const NavigationBar = ({ editorGroup }: { editorGroup: EditorGroup }) =>
47
48
  return null;
48
49
  }
49
50
  return parts.length === 0 ? null : (
50
- <div className={styles.navigation_container}>
51
+ <div
52
+ className={styles.navigation_container}
53
+ onContextMenu={(event) => {
54
+ event.preventDefault();
55
+ }}
56
+ >
51
57
  {parts.map((p, i) => (
52
58
  <React.Fragment key={i + '-crumb:' + p.name}>
53
59
  {i > 0 && <Icon icon={'right'} size='small' className={styles.navigation_icon} />}
54
- <NavigationItem part={p} />
60
+ <NavigationItem part={p} editorGroup={editorGroup} />
55
61
  </React.Fragment>
56
62
  ))}
57
63
  </div>
58
64
  );
59
65
  };
60
- export const NavigationItem = memo(({ part }: { part: IBreadCrumbPart }) => {
66
+ export const NavigationItem = memo(({ part, editorGroup }: { part: IBreadCrumbPart; editorGroup: EditorGroup }) => {
61
67
  const viewService = useInjectable(NavigationBarViewService) as NavigationBarViewService;
68
+ const breadcrumbsMenuService = useInjectable(BreadCrumbsMenuService) as BreadCrumbsMenuService;
62
69
  const itemRef = useRef<HTMLSpanElement>();
63
70
 
64
71
  const onClick = useCallback(async () => {
@@ -70,12 +77,22 @@ export const NavigationItem = memo(({ part }: { part: IBreadCrumbPart }) => {
70
77
  // 放左边
71
78
  leftPos = window.innerWidth - 200 - 5;
72
79
  }
73
- viewService.showMenu(siblings.parts, leftPos, top + height + 5, siblings.currentIndex);
80
+ viewService.showMenu(siblings.parts, leftPos, top + height + 5, siblings.currentIndex, part.uri, editorGroup);
74
81
  }
75
82
  }, [itemRef.current, part]);
76
83
 
77
84
  return (
78
- <span onClick={onClick} className={styles['navigation-part']} ref={itemRef as any}>
85
+ <span
86
+ onClick={onClick}
87
+ onContextMenu={(event) => {
88
+ if (!part.isSymbol && part.uri) {
89
+ breadcrumbsMenuService.show(event.nativeEvent.x, event.nativeEvent.y, editorGroup, part.uri);
90
+ }
91
+ event.preventDefault();
92
+ }}
93
+ className={styles['navigation-part']}
94
+ ref={itemRef as any}
95
+ >
79
96
  {part.icon && <span className={part.icon}></span>}
80
97
  <span>{part.name}</span>
81
98
  </span>
@@ -136,7 +153,7 @@ export const NavigationMenu = observer(({ model }: { model: NavigationMenuModel
136
153
  // 放左边
137
154
  nextLeft = left - width - 5;
138
155
  }
139
- model.showSubMenu(await p.getChildren!(), nextLeft, top);
156
+ model.showSubMenu(await p.getChildren!(), nextLeft, top, model);
140
157
  }
141
158
  }
142
159
  : undefined;
@@ -218,9 +235,11 @@ export const NavigationMenuContainer = observer(() => {
218
235
  @Injectable()
219
236
  export class NavigationBarViewService {
220
237
  @observable.ref current: NavigationMenuModel | undefined;
238
+ @observable.ref editorGroup: EditorGroup;
221
239
 
222
- showMenu(parts: IBreadCrumbPart[], x, y, currentIndex) {
223
- this.current = new NavigationMenuModel(parts, x, y, currentIndex);
240
+ showMenu(parts: IBreadCrumbPart[], x, y, currentIndex, uri, editorGroup) {
241
+ this.current = new NavigationMenuModel(parts, x, y, currentIndex, uri);
242
+ this.editorGroup = editorGroup;
224
243
  }
225
244
 
226
245
  dispose() {
@@ -239,10 +258,11 @@ export class NavigationMenuModel {
239
258
  public readonly x,
240
259
  public readonly y,
241
260
  public readonly initialIndex: number = -1,
261
+ public readonly uri,
242
262
  ) {}
243
263
 
244
- showSubMenu(parts: IBreadCrumbPart[], x, y) {
245
- this.subMenu = new NavigationMenuModel(parts, x, y);
264
+ showSubMenu(parts: IBreadCrumbPart[], x, y, uri) {
265
+ this.subMenu = new NavigationMenuModel(parts, x, y, -1, uri);
246
266
  }
247
267
 
248
268
  dispose() {
@@ -1,8 +1,8 @@
1
- import { PreferenceContribution, Domain, ClientAppContribution } from '@opensumi/ide-core-browser';
1
+ import { PreferenceContribution, Domain } from '@opensumi/ide-core-browser';
2
2
 
3
3
  import { editorPreferenceSchema } from './schema';
4
4
 
5
- @Domain(PreferenceContribution, ClientAppContribution)
5
+ @Domain(PreferenceContribution)
6
6
  export class EditorPreferenceContribution implements PreferenceContribution {
7
7
  schema = editorPreferenceSchema;
8
8
  }
@@ -255,6 +255,7 @@ export class ResourceServiceImpl extends WithEventBus implements ResourceService
255
255
 
256
256
  const DefaultResourceDecoration: IResourceDecoration = {
257
257
  dirty: false,
258
+ readOnly: false,
258
259
  };
259
260
 
260
261
  const GhostResourceProvider: IResourceProvider = {
@@ -27,6 +27,7 @@ import {
27
27
  PreferenceService,
28
28
  DisposableCollection,
29
29
  Event,
30
+ getExternalIcon,
30
31
  } from '@opensumi/ide-core-browser';
31
32
  import { InlineMenuBar } from '@opensumi/ide-core-browser/lib/components/actions';
32
33
  import { LAYOUT_VIEW_SIZE } from '@opensumi/ide-core-browser/lib/layout/constants';
@@ -322,6 +323,7 @@ export const Tabs = ({ group }: ITabsProps) => {
322
323
  return (
323
324
  <div
324
325
  draggable={true}
326
+ title={resource.title}
325
327
  className={classnames({
326
328
  [styles.kt_editor_tab]: true,
327
329
  [styles.last_in_row]: tabMap.get(i),
@@ -385,6 +387,9 @@ export const Tabs = ({ group }: ITabsProps) => {
385
387
  </div>
386
388
  <div>{resource.name}</div>
387
389
  {subname ? <div className={styles.subname}>{subname}</div> : null}
390
+ {decoration.readOnly ? (
391
+ <span className={classnames(getExternalIcon('lock'), styles.editor_readonly_icon)}></span>
392
+ ) : null}
388
393
  <div className={styles.tab_right}>
389
394
  <div
390
395
  className={classnames({