Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/common/editor.ts
3291 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 { localize } from '../../nls.js';
7
import { Event } from '../../base/common/event.js';
8
import { DeepRequiredNonNullable, assertReturnsDefined } from '../../base/common/types.js';
9
import { URI } from '../../base/common/uri.js';
10
import { Disposable, IDisposable, toDisposable } from '../../base/common/lifecycle.js';
11
import { ICodeEditorViewState, IDiffEditor, IDiffEditorViewState, IEditor, IEditorViewState } from '../../editor/common/editorCommon.js';
12
import { IEditorOptions, IResourceEditorInput, ITextResourceEditorInput, IBaseTextResourceEditorInput, IBaseUntypedEditorInput, ITextEditorOptions } from '../../platform/editor/common/editor.js';
13
import type { EditorInput } from './editor/editorInput.js';
14
import { IInstantiationService, IConstructorSignature, ServicesAccessor, BrandedService } from '../../platform/instantiation/common/instantiation.js';
15
import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js';
16
import { Registry } from '../../platform/registry/common/platform.js';
17
import { IEncodingSupport, ILanguageSupport } from '../services/textfile/common/textfiles.js';
18
import { IEditorGroup } from '../services/editor/common/editorGroupsService.js';
19
import { ICompositeControl, IComposite } from './composite.js';
20
import { FileType, IFileReadLimits, IFileService } from '../../platform/files/common/files.js';
21
import { IPathData } from '../../platform/window/common/window.js';
22
import { IExtUri } from '../../base/common/resources.js';
23
import { Schemas } from '../../base/common/network.js';
24
import { IEditorService } from '../services/editor/common/editorService.js';
25
import { ILogService } from '../../platform/log/common/log.js';
26
import { IErrorWithActions, createErrorWithActions, isErrorWithActions } from '../../base/common/errorMessage.js';
27
import { IAction, toAction } from '../../base/common/actions.js';
28
import Severity from '../../base/common/severity.js';
29
import { IPreferencesService } from '../services/preferences/common/preferences.js';
30
import { IReadonlyEditorGroupModel } from './editor/editorGroupModel.js';
31
32
// Static values for editor contributions
33
export const EditorExtensions = {
34
EditorPane: 'workbench.contributions.editors',
35
EditorFactory: 'workbench.contributions.editor.inputFactories'
36
};
37
38
// Static information regarding the text editor
39
export const DEFAULT_EDITOR_ASSOCIATION = {
40
id: 'default',
41
displayName: localize('promptOpenWith.defaultEditor.displayName', "Text Editor"),
42
providerDisplayName: localize('builtinProviderDisplayName', "Built-in")
43
};
44
45
/**
46
* Side by side editor id.
47
*/
48
export const SIDE_BY_SIDE_EDITOR_ID = 'workbench.editor.sidebysideEditor';
49
50
/**
51
* Text diff editor id.
52
*/
53
export const TEXT_DIFF_EDITOR_ID = 'workbench.editors.textDiffEditor';
54
55
/**
56
* Binary diff editor id.
57
*/
58
export const BINARY_DIFF_EDITOR_ID = 'workbench.editors.binaryResourceDiffEditor';
59
60
export interface IEditorDescriptor<T extends IEditorPane> {
61
62
/**
63
* The unique type identifier of the editor. All instances
64
* of the same `IEditorPane` should have the same type
65
* identifier.
66
*/
67
readonly typeId: string;
68
69
/**
70
* The display name of the editor.
71
*/
72
readonly name: string;
73
74
/**
75
* Instantiates the editor pane using the provided services.
76
*/
77
instantiate(instantiationService: IInstantiationService, group: IEditorGroup): T;
78
79
/**
80
* Whether the descriptor is for the provided editor pane.
81
*/
82
describes(editorPane: T): boolean;
83
}
84
85
/**
86
* The editor pane is the container for workbench editors.
87
*/
88
export interface IEditorPane extends IComposite {
89
90
/**
91
* An event to notify when the `IEditorControl` in this
92
* editor pane changes.
93
*
94
* This can be used for editor panes that are a compound
95
* of multiple editor controls to signal that the active
96
* editor control has changed when the user clicks around.
97
*/
98
readonly onDidChangeControl: Event<void>;
99
100
/**
101
* An optional event to notify when the selection inside the editor
102
* pane changed in case the editor has a selection concept.
103
*
104
* For example, in a text editor pane, the selection changes whenever
105
* the cursor is set to a new location.
106
*/
107
readonly onDidChangeSelection?: Event<IEditorPaneSelectionChangeEvent>;
108
109
/**
110
* An optional event to notify when the editor inside the pane scrolled
111
*/
112
readonly onDidChangeScroll?: Event<void>;
113
114
/**
115
* The assigned input of this editor.
116
*/
117
readonly input: EditorInput | undefined;
118
119
/**
120
* The assigned options of the editor.
121
*/
122
readonly options: IEditorOptions | undefined;
123
124
/**
125
* The assigned group this editor is showing in.
126
*/
127
readonly group: IEditorGroup;
128
129
/**
130
* The minimum width of this editor.
131
*/
132
readonly minimumWidth: number;
133
134
/**
135
* The maximum width of this editor.
136
*/
137
readonly maximumWidth: number;
138
139
/**
140
* The minimum height of this editor.
141
*/
142
readonly minimumHeight: number;
143
144
/**
145
* The maximum height of this editor.
146
*/
147
readonly maximumHeight: number;
148
149
/**
150
* An event to notify whenever minimum/maximum width/height changes.
151
*/
152
readonly onDidChangeSizeConstraints: Event<{ width: number; height: number } | undefined>;
153
154
/**
155
* The context key service for this editor. Should be overridden by
156
* editors that have their own ScopedContextKeyService
157
*/
158
readonly scopedContextKeyService: IContextKeyService | undefined;
159
160
/**
161
* Returns the underlying control of this editor. Callers need to cast
162
* the control to a specific instance as needed, e.g. by using the
163
* `isCodeEditor` helper method to access the text code editor.
164
*
165
* Use the `onDidChangeControl` event to track whenever the control
166
* changes.
167
*/
168
getControl(): IEditorControl | undefined;
169
170
/**
171
* Returns the current view state of the editor if any.
172
*
173
* This method is optional to override for the editor pane
174
* and should only be overridden when the pane can deal with
175
* `IEditorOptions.viewState` to be applied when opening.
176
*/
177
getViewState(): object | undefined;
178
179
/**
180
* An optional method to return the current selection in
181
* the editor pane in case the editor pane has a selection
182
* concept.
183
*
184
* Clients of this method will typically react to the
185
* `onDidChangeSelection` event to receive the current
186
* selection as needed.
187
*/
188
getSelection?(): IEditorPaneSelection | undefined;
189
190
/**
191
* An optional method to return the current scroll position
192
* of an editor inside the pane.
193
*
194
* Clients of this method will typically react to the
195
* `onDidChangeScroll` event to receive the current
196
* scroll position as needed.
197
*/
198
getScrollPosition?(): IEditorPaneScrollPosition;
199
200
/**
201
* An optional method to set the current scroll position
202
* of an editor inside the pane.
203
*/
204
setScrollPosition?(scrollPosition: IEditorPaneScrollPosition): void;
205
206
/**
207
* Finds out if this editor is visible or not.
208
*/
209
isVisible(): boolean;
210
}
211
212
export interface IEditorPaneSelectionChangeEvent {
213
214
/**
215
* More details for how the selection was made.
216
*/
217
reason: EditorPaneSelectionChangeReason;
218
}
219
220
export const enum EditorPaneSelectionChangeReason {
221
222
/**
223
* The selection was changed as a result of a programmatic
224
* method invocation.
225
*
226
* For a text editor pane, this for example can be a selection
227
* being restored from previous view state automatically.
228
*/
229
PROGRAMMATIC = 1,
230
231
/**
232
* The selection was changed by the user.
233
*
234
* This typically means the user changed the selection
235
* with mouse or keyboard.
236
*/
237
USER,
238
239
/**
240
* The selection was changed as a result of editing in
241
* the editor pane.
242
*
243
* For a text editor pane, this for example can be typing
244
* in the text of the editor pane.
245
*/
246
EDIT,
247
248
/**
249
* The selection was changed as a result of a navigation
250
* action.
251
*
252
* For a text editor pane, this for example can be a result
253
* of selecting an entry from a text outline view.
254
*/
255
NAVIGATION,
256
257
/**
258
* The selection was changed as a result of a jump action
259
* from within the editor pane.
260
*
261
* For a text editor pane, this for example can be a result
262
* of invoking "Go to definition" from a symbol.
263
*/
264
JUMP
265
}
266
267
export interface IEditorPaneSelection {
268
269
/**
270
* Asks to compare this selection to another selection.
271
*/
272
compare(otherSelection: IEditorPaneSelection): EditorPaneSelectionCompareResult;
273
274
/**
275
* Asks to massage the provided `options` in a way
276
* that the selection can be restored when the editor
277
* is opened again.
278
*
279
* For a text editor this means to apply the selected
280
* line and column as text editor options.
281
*/
282
restore(options: IEditorOptions): IEditorOptions;
283
284
/**
285
* Only used for logging to print more info about the selection.
286
*/
287
log?(): string;
288
}
289
290
export const enum EditorPaneSelectionCompareResult {
291
292
/**
293
* The selections are identical.
294
*/
295
IDENTICAL = 1,
296
297
/**
298
* The selections are similar.
299
*
300
* For a text editor this can mean that the one
301
* selection is in close proximity to the other
302
* selection.
303
*
304
* Upstream clients may decide in this case to
305
* not treat the selection different from the
306
* previous one because it is not distinct enough.
307
*/
308
SIMILAR = 2,
309
310
/**
311
* The selections are entirely different.
312
*/
313
DIFFERENT = 3
314
}
315
316
export interface IEditorPaneWithSelection extends IEditorPane {
317
318
readonly onDidChangeSelection: Event<IEditorPaneSelectionChangeEvent>;
319
320
getSelection(): IEditorPaneSelection | undefined;
321
}
322
323
export function isEditorPaneWithSelection(editorPane: IEditorPane | undefined): editorPane is IEditorPaneWithSelection {
324
const candidate = editorPane as IEditorPaneWithSelection | undefined;
325
326
return !!candidate && typeof candidate.getSelection === 'function' && !!candidate.onDidChangeSelection;
327
}
328
329
export interface IEditorPaneWithScrolling extends IEditorPane {
330
331
readonly onDidChangeScroll: Event<void>;
332
333
getScrollPosition(): IEditorPaneScrollPosition;
334
335
setScrollPosition(position: IEditorPaneScrollPosition): void;
336
}
337
338
export function isEditorPaneWithScrolling(editorPane: IEditorPane | undefined): editorPane is IEditorPaneWithScrolling {
339
const candidate = editorPane as IEditorPaneWithScrolling | undefined;
340
341
return !!candidate && typeof candidate.getScrollPosition === 'function' && typeof candidate.setScrollPosition === 'function' && !!candidate.onDidChangeScroll;
342
}
343
344
/**
345
* Scroll position of a pane
346
*/
347
export interface IEditorPaneScrollPosition {
348
readonly scrollTop: number;
349
readonly scrollLeft?: number;
350
}
351
352
/**
353
* Try to retrieve the view state for the editor pane that
354
* has the provided editor input opened, if at all.
355
*
356
* This method will return `undefined` if the editor input
357
* is not visible in any of the opened editor panes.
358
*/
359
export function findViewStateForEditor(input: EditorInput, group: GroupIdentifier, editorService: IEditorService): object | undefined {
360
for (const editorPane of editorService.visibleEditorPanes) {
361
if (editorPane.group.id === group && input.matches(editorPane.input)) {
362
return editorPane.getViewState();
363
}
364
}
365
366
return undefined;
367
}
368
369
/**
370
* Overrides `IEditorPane` where `input` and `group` are known to be set.
371
*/
372
export interface IVisibleEditorPane extends IEditorPane {
373
readonly input: EditorInput;
374
}
375
376
/**
377
* The text editor pane is the container for workbench text editors.
378
*/
379
export interface ITextEditorPane extends IEditorPane {
380
381
/**
382
* Returns the underlying text editor widget of this editor.
383
*/
384
getControl(): IEditor | undefined;
385
}
386
387
/**
388
* The text editor pane is the container for workbench text diff editors.
389
*/
390
export interface ITextDiffEditorPane extends IEditorPane {
391
392
/**
393
* Returns the underlying text diff editor widget of this editor.
394
*/
395
getControl(): IDiffEditor | undefined;
396
}
397
398
/**
399
* Marker interface for the control inside an editor pane. Callers
400
* have to cast the control to work with it, e.g. via methods
401
* such as `isCodeEditor(control)`.
402
*/
403
export interface IEditorControl extends ICompositeControl { }
404
405
export interface IFileEditorFactory {
406
407
/**
408
* The type identifier of the file editor.
409
*/
410
typeId: string;
411
412
/**
413
* Creates new editor capable of showing files.
414
*/
415
createFileEditor(resource: URI, preferredResource: URI | undefined, preferredName: string | undefined, preferredDescription: string | undefined, preferredEncoding: string | undefined, preferredLanguageId: string | undefined, preferredContents: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
416
417
/**
418
* Check if the provided object is a file editor.
419
*/
420
isFileEditor(obj: unknown): obj is IFileEditorInput;
421
}
422
423
export interface IEditorFactoryRegistry {
424
425
/**
426
* Registers the file editor factory to use for file editors.
427
*/
428
registerFileEditorFactory(factory: IFileEditorFactory): void;
429
430
/**
431
* Returns the file editor factory to use for file editors.
432
*/
433
getFileEditorFactory(): IFileEditorFactory;
434
435
/**
436
* Registers a editor serializer for the given editor to the registry.
437
* An editor serializer is capable of serializing and deserializing editor
438
* from string data.
439
*
440
* @param editorTypeId the type identifier of the editor
441
* @param serializer the editor serializer for serialization/deserialization
442
*/
443
registerEditorSerializer<Services extends BrandedService[]>(editorTypeId: string, ctor: { new(...Services: Services): IEditorSerializer }): IDisposable;
444
445
/**
446
* Returns the editor serializer for the given editor.
447
*/
448
getEditorSerializer(editor: EditorInput): IEditorSerializer | undefined;
449
getEditorSerializer(editorTypeId: string): IEditorSerializer | undefined;
450
451
/**
452
* Starts the registry by providing the required services.
453
*/
454
start(accessor: ServicesAccessor): void;
455
}
456
457
export interface IEditorSerializer {
458
459
/**
460
* Determines whether the given editor can be serialized by the serializer.
461
*/
462
canSerialize(editor: EditorInput): boolean;
463
464
/**
465
* Returns a string representation of the provided editor that contains enough information
466
* to deserialize back to the original editor from the deserialize() method.
467
*/
468
serialize(editor: EditorInput): string | undefined;
469
470
/**
471
* Returns an editor from the provided serialized form of the editor. This form matches
472
* the value returned from the serialize() method.
473
*/
474
deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined;
475
}
476
477
export interface IUntitledTextResourceEditorInput extends IBaseTextResourceEditorInput {
478
479
/**
480
* Optional resource for the untitled editor. Depending on the value, the editor:
481
* - should get a unique name if `undefined` (for example `Untitled-1`)
482
* - should use the resource directly if the scheme is `untitled:`
483
* - should change the scheme to `untitled:` otherwise and assume an associated path
484
*
485
* Untitled editors with associated path behave slightly different from other untitled
486
* editors:
487
* - they are dirty right when opening
488
* - they will not ask for a file path when saving but use the associated path
489
*/
490
readonly resource: URI | undefined;
491
}
492
493
/**
494
* A resource side by side editor input shows 2 editors side by side but
495
* without highlighting any differences.
496
*
497
* Note: both sides will be resolved as editor individually. As such, it is
498
* possible to show 2 different editors side by side.
499
*
500
* @see {@link IResourceDiffEditorInput} for a variant that compares 2 editors.
501
*/
502
export interface IResourceSideBySideEditorInput extends IBaseUntypedEditorInput {
503
504
/**
505
* The right hand side editor to open inside a side-by-side editor.
506
*/
507
readonly primary: Omit<IResourceEditorInput, 'options'> | Omit<ITextResourceEditorInput, 'options'> | Omit<IUntitledTextResourceEditorInput, 'options'>;
508
509
/**
510
* The left hand side editor to open inside a side-by-side editor.
511
*/
512
readonly secondary: Omit<IResourceEditorInput, 'options'> | Omit<ITextResourceEditorInput, 'options'> | Omit<IUntitledTextResourceEditorInput, 'options'>;
513
}
514
515
/**
516
* A resource diff editor input compares 2 editors side by side
517
* highlighting the differences.
518
*
519
* Note: both sides must be resolvable to the same editor, or
520
* a text based presentation will be used as fallback.
521
*/
522
export interface IResourceDiffEditorInput extends IBaseUntypedEditorInput {
523
524
/**
525
* The left hand side editor to open inside a diff editor.
526
*/
527
readonly original: Omit<IResourceEditorInput, 'options'> | Omit<ITextResourceEditorInput, 'options'> | Omit<IUntitledTextResourceEditorInput, 'options'>;
528
529
/**
530
* The right hand side editor to open inside a diff editor.
531
*/
532
readonly modified: Omit<IResourceEditorInput, 'options'> | Omit<ITextResourceEditorInput, 'options'> | Omit<IUntitledTextResourceEditorInput, 'options'>;
533
}
534
535
export interface ITextResourceDiffEditorInput extends IBaseTextResourceEditorInput {
536
537
/**
538
* The left hand side text editor to open inside a diff editor.
539
*/
540
readonly original: Omit<ITextResourceEditorInput, 'options'> | Omit<IUntitledTextResourceEditorInput, 'options'>;
541
542
/**
543
* The right hand side text editor to open inside a diff editor.
544
*/
545
readonly modified: Omit<ITextResourceEditorInput, 'options'> | Omit<IUntitledTextResourceEditorInput, 'options'>;
546
}
547
548
/**
549
* A resource list diff editor input compares multiple resources side by side
550
* highlighting the differences.
551
*/
552
export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput {
553
/**
554
* A unique identifier of this multi diff editor input.
555
* If a second multi diff editor with the same uri is opened, the existing one is revealed instead (even if the resources list is different!).
556
*/
557
readonly multiDiffSource?: URI;
558
559
/**
560
* The list of resources to compare.
561
* If not set, the resources are dynamically derived from the {@link multiDiffSource}.
562
*/
563
readonly resources?: IMultiDiffEditorResource[];
564
565
/**
566
* Whether the editor should be serialized and stored for subsequent sessions.
567
*/
568
readonly isTransient?: boolean;
569
}
570
571
export interface IMultiDiffEditorResource extends IResourceDiffEditorInput {
572
readonly goToFileResource?: URI;
573
}
574
export type IResourceMergeEditorInputSide = (Omit<IResourceEditorInput, 'options'> | Omit<ITextResourceEditorInput, 'options'>) & { detail?: string };
575
576
/**
577
* A resource merge editor input compares multiple editors
578
* highlighting the differences for merging.
579
*
580
* Note: all sides must be resolvable to the same editor, or
581
* a text based presentation will be used as fallback.
582
*/
583
export interface IResourceMergeEditorInput extends IBaseUntypedEditorInput {
584
585
/**
586
* The one changed version of the file.
587
*/
588
readonly input1: IResourceMergeEditorInputSide;
589
590
/**
591
* The second changed version of the file.
592
*/
593
readonly input2: IResourceMergeEditorInputSide;
594
595
/**
596
* The base common ancestor of the file to merge.
597
*/
598
readonly base: Omit<IResourceEditorInput, 'options'> | Omit<ITextResourceEditorInput, 'options'>;
599
600
/**
601
* The resulting output of the merge.
602
*/
603
readonly result: Omit<IResourceEditorInput, 'options'> | Omit<ITextResourceEditorInput, 'options'>;
604
}
605
606
export function isResourceEditorInput(editor: unknown): editor is IResourceEditorInput {
607
if (isEditorInput(editor)) {
608
return false; // make sure to not accidentally match on typed editor inputs
609
}
610
611
const candidate = editor as IResourceEditorInput | undefined;
612
613
return URI.isUri(candidate?.resource);
614
}
615
616
export function isResourceDiffEditorInput(editor: unknown): editor is IResourceDiffEditorInput {
617
if (isEditorInput(editor)) {
618
return false; // make sure to not accidentally match on typed editor inputs
619
}
620
621
const candidate = editor as IResourceDiffEditorInput | undefined;
622
623
return candidate?.original !== undefined && candidate.modified !== undefined;
624
}
625
626
export function isResourceMultiDiffEditorInput(editor: unknown): editor is IResourceMultiDiffEditorInput {
627
if (isEditorInput(editor)) {
628
return false; // make sure to not accidentally match on typed editor inputs
629
}
630
631
const candidate = editor as IResourceMultiDiffEditorInput | undefined;
632
if (!candidate) {
633
return false;
634
}
635
if (candidate.resources && !Array.isArray(candidate.resources)) {
636
return false;
637
}
638
639
return !!candidate.resources || !!candidate.multiDiffSource;
640
}
641
642
export function isResourceSideBySideEditorInput(editor: unknown): editor is IResourceSideBySideEditorInput {
643
if (isEditorInput(editor)) {
644
return false; // make sure to not accidentally match on typed editor inputs
645
}
646
647
if (isResourceDiffEditorInput(editor)) {
648
return false; // make sure to not accidentally match on diff editors
649
}
650
651
const candidate = editor as IResourceSideBySideEditorInput | undefined;
652
653
return candidate?.primary !== undefined && candidate.secondary !== undefined;
654
}
655
656
export function isUntitledResourceEditorInput(editor: unknown): editor is IUntitledTextResourceEditorInput {
657
if (isEditorInput(editor)) {
658
return false; // make sure to not accidentally match on typed editor inputs
659
}
660
661
const candidate = editor as IUntitledTextResourceEditorInput | undefined;
662
if (!candidate) {
663
return false;
664
}
665
666
return candidate.resource === undefined || candidate.resource.scheme === Schemas.untitled || candidate.forceUntitled === true;
667
}
668
669
export function isResourceMergeEditorInput(editor: unknown): editor is IResourceMergeEditorInput {
670
if (isEditorInput(editor)) {
671
return false; // make sure to not accidentally match on typed editor inputs
672
}
673
674
const candidate = editor as IResourceMergeEditorInput | undefined;
675
676
return URI.isUri(candidate?.base?.resource) && URI.isUri(candidate?.input1?.resource) && URI.isUri(candidate?.input2?.resource) && URI.isUri(candidate?.result?.resource);
677
}
678
679
export const enum Verbosity {
680
SHORT,
681
MEDIUM,
682
LONG
683
}
684
685
export const enum SaveReason {
686
687
/**
688
* Explicit user gesture.
689
*/
690
EXPLICIT = 1,
691
692
/**
693
* Auto save after a timeout.
694
*/
695
AUTO = 2,
696
697
/**
698
* Auto save after editor focus change.
699
*/
700
FOCUS_CHANGE = 3,
701
702
/**
703
* Auto save after window change.
704
*/
705
WINDOW_CHANGE = 4
706
}
707
708
export type SaveSource = string;
709
710
interface ISaveSourceDescriptor {
711
source: SaveSource;
712
label: string;
713
}
714
715
class SaveSourceFactory {
716
717
private readonly mapIdToSaveSource = new Map<SaveSource, ISaveSourceDescriptor>();
718
719
/**
720
* Registers a `SaveSource` with an identifier and label
721
* to the registry so that it can be used in save operations.
722
*/
723
registerSource(id: string, label: string): SaveSource {
724
let sourceDescriptor = this.mapIdToSaveSource.get(id);
725
if (!sourceDescriptor) {
726
sourceDescriptor = { source: id, label };
727
this.mapIdToSaveSource.set(id, sourceDescriptor);
728
}
729
730
return sourceDescriptor.source;
731
}
732
733
getSourceLabel(source: SaveSource): string {
734
return this.mapIdToSaveSource.get(source)?.label ?? source;
735
}
736
}
737
738
export const SaveSourceRegistry = new SaveSourceFactory();
739
740
export interface ISaveOptions {
741
742
/**
743
* An indicator how the save operation was triggered.
744
*/
745
reason?: SaveReason;
746
747
/**
748
* An indicator about the source of the save operation.
749
*
750
* Must use `SaveSourceRegistry.registerSource()` to obtain.
751
*/
752
readonly source?: SaveSource;
753
754
/**
755
* Forces to save the contents of the working copy
756
* again even if the working copy is not dirty.
757
*/
758
readonly force?: boolean;
759
760
/**
761
* Instructs the save operation to skip any save participants.
762
*/
763
readonly skipSaveParticipants?: boolean;
764
765
/**
766
* A hint as to which file systems should be available for saving.
767
*/
768
readonly availableFileSystems?: string[];
769
}
770
771
export interface IRevertOptions {
772
773
/**
774
* Forces to load the contents of the working copy
775
* again even if the working copy is not dirty.
776
*/
777
readonly force?: boolean;
778
779
/**
780
* A soft revert will clear dirty state of a working copy
781
* but will not attempt to load it from its persisted state.
782
*
783
* This option may be used in scenarios where an editor is
784
* closed and where we do not require to load the contents.
785
*/
786
readonly soft?: boolean;
787
}
788
789
export interface IMoveResult {
790
editor: EditorInput | IUntypedEditorInput;
791
options?: IEditorOptions;
792
}
793
794
export const enum EditorInputCapabilities {
795
796
/**
797
* Signals no specific capability for the input.
798
*/
799
None = 0,
800
801
/**
802
* Signals that the input is readonly.
803
*/
804
Readonly = 1 << 1,
805
806
/**
807
* Signals that the input is untitled.
808
*/
809
Untitled = 1 << 2,
810
811
/**
812
* Signals that the input can only be shown in one group
813
* and not be split into multiple groups.
814
*/
815
Singleton = 1 << 3,
816
817
/**
818
* Signals that the input requires workspace trust.
819
*/
820
RequiresTrust = 1 << 4,
821
822
/**
823
* Signals that the editor can split into 2 in the same
824
* editor group.
825
*/
826
CanSplitInGroup = 1 << 5,
827
828
/**
829
* Signals that the editor wants its description to be
830
* visible when presented to the user. By default, a UI
831
* component may decide to hide the description portion
832
* for brevity.
833
*/
834
ForceDescription = 1 << 6,
835
836
/**
837
* Signals that the editor supports dropping into the
838
* editor by holding shift.
839
*/
840
CanDropIntoEditor = 1 << 7,
841
842
/**
843
* Signals that the editor is composed of multiple editors
844
* within.
845
*/
846
MultipleEditors = 1 << 8,
847
848
/**
849
* Signals that the editor cannot be in a dirty state
850
* and may still have unsaved changes
851
*/
852
Scratchpad = 1 << 9
853
}
854
855
export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceMultiDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput;
856
857
export abstract class AbstractEditorInput extends Disposable {
858
// Marker class for implementing `isEditorInput`
859
}
860
861
export function isEditorInput(editor: unknown): editor is EditorInput {
862
return editor instanceof AbstractEditorInput;
863
}
864
865
export interface EditorInputWithPreferredResource {
866
867
/**
868
* An editor may provide an additional preferred resource alongside
869
* the `resource` property. While the `resource` property serves as
870
* unique identifier of the editor that should be used whenever we
871
* compare to other editors, the `preferredResource` should be used
872
* in places where e.g. the resource is shown to the user.
873
*
874
* For example: on Windows and macOS, the same URI with different
875
* casing may point to the same file. The editor may chose to
876
* "normalize" the URIs so that only one editor opens for different
877
* URIs. But when displaying the editor label to the user, the
878
* preferred URI should be used.
879
*
880
* Not all editors have a `preferredResource`. The `EditorResourceAccessor`
881
* utility can be used to always get the right resource without having
882
* to do instanceof checks.
883
*/
884
readonly preferredResource: URI;
885
}
886
887
function isEditorInputWithPreferredResource(editor: unknown): editor is EditorInputWithPreferredResource {
888
const candidate = editor as EditorInputWithPreferredResource | undefined;
889
890
return URI.isUri(candidate?.preferredResource);
891
}
892
893
export interface ISideBySideEditorInput extends EditorInput {
894
895
/**
896
* The primary editor input is shown on the right hand side.
897
*/
898
primary: EditorInput;
899
900
/**
901
* The secondary editor input is shown on the left hand side.
902
*/
903
secondary: EditorInput;
904
}
905
906
export function isSideBySideEditorInput(editor: unknown): editor is ISideBySideEditorInput {
907
const candidate = editor as ISideBySideEditorInput | undefined;
908
909
return isEditorInput(candidate?.primary) && isEditorInput(candidate?.secondary);
910
}
911
912
export interface IDiffEditorInput extends EditorInput {
913
914
/**
915
* The modified (primary) editor input is shown on the right hand side.
916
*/
917
modified: EditorInput;
918
919
/**
920
* The original (secondary) editor input is shown on the left hand side.
921
*/
922
original: EditorInput;
923
}
924
925
export function isDiffEditorInput(editor: unknown): editor is IDiffEditorInput {
926
const candidate = editor as IDiffEditorInput | undefined;
927
928
return isEditorInput(candidate?.modified) && isEditorInput(candidate?.original);
929
}
930
931
export interface IUntypedFileEditorInput extends ITextResourceEditorInput {
932
933
/**
934
* A marker to create a `IFileEditorInput` from this untyped input.
935
*/
936
forceFile: true;
937
}
938
939
/**
940
* This is a tagging interface to declare an editor input being capable of dealing with files. It is only used in the editor registry
941
* to register this kind of input to the platform.
942
*/
943
export interface IFileEditorInput extends EditorInput, IEncodingSupport, ILanguageSupport, EditorInputWithPreferredResource {
944
945
/**
946
* Gets the resource this file input is about. This will always be the
947
* canonical form of the resource, so it may differ from the original
948
* resource that was provided to create the input. Use `preferredResource`
949
* for the form as it was created.
950
*/
951
readonly resource: URI;
952
953
/**
954
* Sets the preferred resource to use for this file input.
955
*/
956
setPreferredResource(preferredResource: URI): void;
957
958
/**
959
* Sets the preferred name to use for this file input.
960
*
961
* Note: for certain file schemes the input may decide to ignore this
962
* name and use our standard naming. Specifically for schemes we own,
963
* we do not let others override the name.
964
*/
965
setPreferredName(name: string): void;
966
967
/**
968
* Sets the preferred description to use for this file input.
969
*
970
* Note: for certain file schemes the input may decide to ignore this
971
* description and use our standard naming. Specifically for schemes we own,
972
* we do not let others override the description.
973
*/
974
setPreferredDescription(description: string): void;
975
976
/**
977
* Sets the preferred encoding to use for this file input.
978
*/
979
setPreferredEncoding(encoding: string): void;
980
981
/**
982
* Sets the preferred language id to use for this file input.
983
*/
984
setPreferredLanguageId(languageId: string): void;
985
986
/**
987
* Sets the preferred contents to use for this file input.
988
*/
989
setPreferredContents(contents: string): void;
990
991
/**
992
* Forces this file input to open as binary instead of text.
993
*/
994
setForceOpenAsBinary(): void;
995
996
/**
997
* Figure out if the file input has been resolved or not.
998
*/
999
isResolved(): boolean;
1000
}
1001
1002
export interface IFileLimitedEditorInputOptions extends IEditorOptions {
1003
1004
/**
1005
* If provided, the size of the file will be checked against the limits
1006
* and an error will be thrown if any limit is exceeded.
1007
*/
1008
readonly limits?: IFileReadLimits;
1009
}
1010
1011
export interface IFileEditorInputOptions extends ITextEditorOptions, IFileLimitedEditorInputOptions { }
1012
1013
export function createTooLargeFileError(group: IEditorGroup, input: EditorInput, options: IEditorOptions | undefined, message: string, preferencesService: IPreferencesService): Error {
1014
return createEditorOpenError(message, [
1015
toAction({
1016
id: 'workbench.action.openLargeFile', label: localize('openLargeFile', "Open Anyway"), run: () => {
1017
const fileEditorOptions: IFileEditorInputOptions = {
1018
...options,
1019
limits: {
1020
size: Number.MAX_VALUE
1021
}
1022
};
1023
1024
group.openEditor(input, fileEditorOptions);
1025
}
1026
}),
1027
toAction({
1028
id: 'workbench.action.configureEditorLargeFileConfirmation', label: localize('configureEditorLargeFileConfirmation', "Configure Limit"), run: () => {
1029
return preferencesService.openUserSettings({ query: 'workbench.editorLargeFileConfirmation' });
1030
}
1031
}),
1032
], {
1033
forceMessage: true,
1034
forceSeverity: Severity.Warning
1035
});
1036
}
1037
1038
export interface EditorInputWithOptions {
1039
editor: EditorInput;
1040
options?: IEditorOptions;
1041
}
1042
1043
export interface EditorInputWithOptionsAndGroup extends EditorInputWithOptions {
1044
group: IEditorGroup;
1045
}
1046
1047
export function isEditorInputWithOptions(editor: unknown): editor is EditorInputWithOptions {
1048
const candidate = editor as EditorInputWithOptions | undefined;
1049
1050
return isEditorInput(candidate?.editor);
1051
}
1052
1053
export function isEditorInputWithOptionsAndGroup(editor: unknown): editor is EditorInputWithOptionsAndGroup {
1054
const candidate = editor as EditorInputWithOptionsAndGroup | undefined;
1055
1056
return isEditorInputWithOptions(editor) && candidate?.group !== undefined;
1057
}
1058
1059
/**
1060
* Context passed into `EditorPane#setInput` to give additional
1061
* context information around why the editor was opened.
1062
*/
1063
export interface IEditorOpenContext {
1064
1065
/**
1066
* An indicator if the editor input is new for the group the editor is in.
1067
* An editor is new for a group if it was not part of the group before and
1068
* otherwise was already opened in the group and just became the active editor.
1069
*
1070
* This hint can e.g. be used to decide whether to restore view state or not.
1071
*/
1072
newInGroup?: boolean;
1073
}
1074
1075
export interface IEditorIdentifier {
1076
groupId: GroupIdentifier;
1077
editor: EditorInput;
1078
}
1079
1080
export function isEditorIdentifier(identifier: unknown): identifier is IEditorIdentifier {
1081
const candidate = identifier as IEditorIdentifier | undefined;
1082
1083
return typeof candidate?.groupId === 'number' && isEditorInput(candidate.editor);
1084
}
1085
1086
/**
1087
* The editor commands context is used for editor commands (e.g. in the editor title)
1088
* and we must ensure that the context is serializable because it potentially travels
1089
* to the extension host!
1090
*/
1091
export interface IEditorCommandsContext {
1092
groupId: GroupIdentifier;
1093
editorIndex?: number;
1094
1095
preserveFocus?: boolean;
1096
}
1097
1098
export function isEditorCommandsContext(context: unknown): context is IEditorCommandsContext {
1099
const candidate = context as IEditorCommandsContext | undefined;
1100
1101
return typeof candidate?.groupId === 'number';
1102
}
1103
1104
/**
1105
* More information around why an editor was closed in the model.
1106
*/
1107
export enum EditorCloseContext {
1108
1109
/**
1110
* No specific context for closing (e.g. explicit user gesture).
1111
*/
1112
UNKNOWN,
1113
1114
/**
1115
* The editor closed because it was replaced with another editor.
1116
* This can either happen via explicit replace call or when an
1117
* editor is in preview mode and another editor opens.
1118
*/
1119
REPLACE,
1120
1121
/**
1122
* The editor closed as a result of moving it to another group.
1123
*/
1124
MOVE,
1125
1126
/**
1127
* The editor closed because another editor turned into preview
1128
* and this used to be the preview editor before.
1129
*/
1130
UNPIN
1131
}
1132
1133
export interface IEditorCloseEvent extends IEditorIdentifier {
1134
1135
/**
1136
* More information around why the editor was closed.
1137
*/
1138
readonly context: EditorCloseContext;
1139
1140
/**
1141
* The index of the editor before closing.
1142
*/
1143
readonly index: number;
1144
1145
/**
1146
* Whether the editor was sticky or not.
1147
*/
1148
readonly sticky: boolean;
1149
}
1150
1151
export interface IActiveEditorChangeEvent {
1152
1153
/**
1154
* The new active editor or `undefined` if the group is empty.
1155
*/
1156
editor: EditorInput | undefined;
1157
}
1158
1159
export interface IEditorWillMoveEvent extends IEditorIdentifier {
1160
1161
/**
1162
* The target group of the move operation.
1163
*/
1164
readonly target: GroupIdentifier;
1165
}
1166
1167
export interface IEditorWillOpenEvent extends IEditorIdentifier { }
1168
1169
export interface IWillInstantiateEditorPaneEvent {
1170
1171
/**
1172
* @see {@link IEditorDescriptor.typeId}
1173
*/
1174
readonly typeId: string;
1175
}
1176
1177
export type GroupIdentifier = number;
1178
1179
export const enum GroupModelChangeKind {
1180
1181
/* Group Changes */
1182
GROUP_ACTIVE,
1183
GROUP_INDEX,
1184
GROUP_LABEL,
1185
GROUP_LOCKED,
1186
1187
/* Editors Change */
1188
EDITORS_SELECTION,
1189
1190
/* Editor Changes */
1191
EDITOR_OPEN,
1192
EDITOR_CLOSE,
1193
EDITOR_MOVE,
1194
EDITOR_ACTIVE,
1195
EDITOR_LABEL,
1196
EDITOR_CAPABILITIES,
1197
EDITOR_PIN,
1198
EDITOR_TRANSIENT,
1199
EDITOR_STICKY,
1200
EDITOR_DIRTY,
1201
EDITOR_WILL_DISPOSE
1202
}
1203
1204
export interface IWorkbenchEditorConfiguration {
1205
workbench?: {
1206
editor?: IEditorPartConfiguration;
1207
iconTheme?: string;
1208
};
1209
}
1210
1211
interface IEditorPartLimitConfiguration {
1212
enabled?: boolean;
1213
excludeDirty?: boolean;
1214
value?: number;
1215
perEditorGroup?: boolean;
1216
}
1217
1218
export interface IEditorPartLimitOptions extends Required<IEditorPartLimitConfiguration> { }
1219
1220
interface IEditorPartDecorationsConfiguration {
1221
badges?: boolean;
1222
colors?: boolean;
1223
}
1224
1225
export interface IEditorPartDecorationOptions extends Required<IEditorPartDecorationsConfiguration> { }
1226
1227
interface IEditorPartConfiguration {
1228
showTabs?: 'multiple' | 'single' | 'none';
1229
wrapTabs?: boolean;
1230
scrollToSwitchTabs?: boolean;
1231
highlightModifiedTabs?: boolean;
1232
tabActionLocation?: 'left' | 'right';
1233
tabActionCloseVisibility?: boolean;
1234
tabActionUnpinVisibility?: boolean;
1235
showTabIndex?: boolean;
1236
alwaysShowEditorActions?: boolean;
1237
tabSizing?: 'fit' | 'shrink' | 'fixed';
1238
tabSizingFixedMinWidth?: number;
1239
tabSizingFixedMaxWidth?: number;
1240
pinnedTabSizing?: 'normal' | 'compact' | 'shrink';
1241
pinnedTabsOnSeparateRow?: boolean;
1242
tabHeight?: 'default' | 'compact';
1243
preventPinnedEditorClose?: PreventPinnedEditorClose;
1244
titleScrollbarSizing?: 'default' | 'large';
1245
titleScrollbarVisibility?: 'auto' | 'visible' | 'hidden';
1246
focusRecentEditorAfterClose?: boolean;
1247
showIcons?: boolean;
1248
enablePreview?: boolean;
1249
enablePreviewFromQuickOpen?: boolean;
1250
enablePreviewFromCodeNavigation?: boolean;
1251
closeOnFileDelete?: boolean;
1252
openPositioning?: 'left' | 'right' | 'first' | 'last';
1253
openSideBySideDirection?: 'right' | 'down';
1254
closeEmptyGroups?: boolean;
1255
autoLockGroups?: Set<string>;
1256
revealIfOpen?: boolean;
1257
mouseBackForwardToNavigate?: boolean;
1258
labelFormat?: 'default' | 'short' | 'medium' | 'long';
1259
restoreViewState?: boolean;
1260
splitInGroupLayout?: 'vertical' | 'horizontal';
1261
splitSizing?: 'auto' | 'split' | 'distribute';
1262
splitOnDragAndDrop?: boolean;
1263
dragToOpenWindow?: boolean;
1264
centeredLayoutFixedWidth?: boolean;
1265
doubleClickTabToToggleEditorGroupSizes?: 'maximize' | 'expand' | 'off';
1266
editorActionsLocation?: 'default' | 'titleBar' | 'hidden';
1267
limit?: IEditorPartLimitConfiguration;
1268
decorations?: IEditorPartDecorationsConfiguration;
1269
}
1270
1271
export interface IEditorPartOptions extends DeepRequiredNonNullable<IEditorPartConfiguration> {
1272
hasIcons: boolean;
1273
}
1274
1275
export interface IEditorPartOptionsChangeEvent {
1276
oldPartOptions: IEditorPartOptions;
1277
newPartOptions: IEditorPartOptions;
1278
}
1279
1280
export enum SideBySideEditor {
1281
PRIMARY = 1,
1282
SECONDARY = 2,
1283
BOTH = 3,
1284
ANY = 4
1285
}
1286
1287
export interface IFindEditorOptions {
1288
1289
/**
1290
* Whether to consider any or both side by side editor as matching.
1291
* By default, side by side editors will not be considered
1292
* as matching, even if the editor is opened in one of the sides.
1293
*/
1294
supportSideBySide?: SideBySideEditor.PRIMARY | SideBySideEditor.SECONDARY | SideBySideEditor.ANY;
1295
1296
/**
1297
* The order in which to consider editors for finding.
1298
*/
1299
order?: EditorsOrder;
1300
}
1301
1302
export interface IMatchEditorOptions {
1303
1304
/**
1305
* Whether to consider a side by side editor as matching.
1306
* By default, side by side editors will not be considered
1307
* as matching, even if the editor is opened in one of the sides.
1308
*/
1309
supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH;
1310
1311
/**
1312
* Only consider an editor to match when the
1313
* `candidate === editor` but not when
1314
* `candidate.matches(editor)`.
1315
*/
1316
strictEquals?: boolean;
1317
}
1318
1319
export interface IEditorResourceAccessorOptions {
1320
1321
/**
1322
* Allows to access the `resource(s)` of side by side editors. If not
1323
* specified, a `resource` for a side by side editor will always be
1324
* `undefined`.
1325
*/
1326
supportSideBySide?: SideBySideEditor;
1327
1328
/**
1329
* Allows to filter the scheme to consider. A resource scheme that does
1330
* not match a filter will not be considered.
1331
*/
1332
filterByScheme?: string | string[];
1333
}
1334
1335
class EditorResourceAccessorImpl {
1336
1337
/**
1338
* The original URI of an editor is the URI that was used originally to open
1339
* the editor and should be used whenever the URI is presented to the user,
1340
* e.g. as a label together with utility methods such as `ResourceLabel` or
1341
* `ILabelService` that can turn this original URI into the best form for
1342
* presenting.
1343
*
1344
* In contrast, the canonical URI (#getCanonicalUri) may be different and should
1345
* be used whenever the URI is used to e.g. compare with other editors or when
1346
* caching certain data based on the URI.
1347
*
1348
* For example: on Windows and macOS, the same file URI with different casing may
1349
* point to the same file. The editor may chose to "normalize" the URI into a canonical
1350
* form so that only one editor opens for same file URIs with different casing. As
1351
* such, the original URI and the canonical URI can be different.
1352
*/
1353
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null): URI | undefined;
1354
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide?: SideBySideEditor.PRIMARY | SideBySideEditor.SECONDARY | SideBySideEditor.ANY }): URI | undefined;
1355
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { primary?: URI; secondary?: URI } | undefined;
1356
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined;
1357
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined {
1358
if (!editor) {
1359
return undefined;
1360
}
1361
1362
// Merge editors are handled with `merged` result editor
1363
if (isResourceMergeEditorInput(editor)) {
1364
return EditorResourceAccessor.getOriginalUri(editor.result, options);
1365
}
1366
1367
// Optionally support side-by-side editors
1368
if (options?.supportSideBySide) {
1369
const { primary, secondary } = this.getSideEditors(editor);
1370
if (primary && secondary) {
1371
if (options?.supportSideBySide === SideBySideEditor.BOTH) {
1372
return {
1373
primary: this.getOriginalUri(primary, { filterByScheme: options.filterByScheme }),
1374
secondary: this.getOriginalUri(secondary, { filterByScheme: options.filterByScheme })
1375
};
1376
} else if (options?.supportSideBySide === SideBySideEditor.ANY) {
1377
return this.getOriginalUri(primary, { filterByScheme: options.filterByScheme }) ?? this.getOriginalUri(secondary, { filterByScheme: options.filterByScheme });
1378
}
1379
1380
editor = options.supportSideBySide === SideBySideEditor.PRIMARY ? primary : secondary;
1381
}
1382
}
1383
1384
if (isResourceDiffEditorInput(editor) || isResourceMultiDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
1385
return undefined;
1386
}
1387
1388
// Original URI is the `preferredResource` of an editor if any
1389
const originalResource = isEditorInputWithPreferredResource(editor) ? editor.preferredResource : editor.resource;
1390
if (!originalResource || !options || !options.filterByScheme) {
1391
return originalResource;
1392
}
1393
1394
return this.filterUri(originalResource, options.filterByScheme);
1395
}
1396
1397
private getSideEditors(editor: EditorInput | IUntypedEditorInput): { primary: EditorInput | IUntypedEditorInput | undefined; secondary: EditorInput | IUntypedEditorInput | undefined } {
1398
if (isSideBySideEditorInput(editor) || isResourceSideBySideEditorInput(editor)) {
1399
return { primary: editor.primary, secondary: editor.secondary };
1400
}
1401
1402
if (isDiffEditorInput(editor) || isResourceDiffEditorInput(editor)) {
1403
return { primary: editor.modified, secondary: editor.original };
1404
}
1405
1406
return { primary: undefined, secondary: undefined };
1407
}
1408
1409
/**
1410
* The canonical URI of an editor is the true unique identifier of the editor
1411
* and should be used whenever the URI is used e.g. to compare with other
1412
* editors or when caching certain data based on the URI.
1413
*
1414
* In contrast, the original URI (#getOriginalUri) may be different and should
1415
* be used whenever the URI is presented to the user, e.g. as a label.
1416
*
1417
* For example: on Windows and macOS, the same file URI with different casing may
1418
* point to the same file. The editor may chose to "normalize" the URI into a canonical
1419
* form so that only one editor opens for same file URIs with different casing. As
1420
* such, the original URI and the canonical URI can be different.
1421
*/
1422
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null): URI | undefined;
1423
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide?: SideBySideEditor.PRIMARY | SideBySideEditor.SECONDARY | SideBySideEditor.ANY }): URI | undefined;
1424
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { primary?: URI; secondary?: URI } | undefined;
1425
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined;
1426
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined {
1427
if (!editor) {
1428
return undefined;
1429
}
1430
1431
// Merge editors are handled with `merged` result editor
1432
if (isResourceMergeEditorInput(editor)) {
1433
return EditorResourceAccessor.getCanonicalUri(editor.result, options);
1434
}
1435
1436
// Optionally support side-by-side editors
1437
if (options?.supportSideBySide) {
1438
const { primary, secondary } = this.getSideEditors(editor);
1439
if (primary && secondary) {
1440
if (options?.supportSideBySide === SideBySideEditor.BOTH) {
1441
return {
1442
primary: this.getCanonicalUri(primary, { filterByScheme: options.filterByScheme }),
1443
secondary: this.getCanonicalUri(secondary, { filterByScheme: options.filterByScheme })
1444
};
1445
} else if (options?.supportSideBySide === SideBySideEditor.ANY) {
1446
return this.getCanonicalUri(primary, { filterByScheme: options.filterByScheme }) ?? this.getCanonicalUri(secondary, { filterByScheme: options.filterByScheme });
1447
}
1448
1449
editor = options.supportSideBySide === SideBySideEditor.PRIMARY ? primary : secondary;
1450
}
1451
}
1452
1453
if (isResourceDiffEditorInput(editor) || isResourceMultiDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
1454
return undefined;
1455
}
1456
1457
// Canonical URI is the `resource` of an editor
1458
const canonicalResource = editor.resource;
1459
if (!canonicalResource || !options || !options.filterByScheme) {
1460
return canonicalResource;
1461
}
1462
1463
return this.filterUri(canonicalResource, options.filterByScheme);
1464
}
1465
1466
private filterUri(resource: URI, filter: string | string[]): URI | undefined {
1467
1468
// Multiple scheme filter
1469
if (Array.isArray(filter)) {
1470
if (filter.some(scheme => resource.scheme === scheme)) {
1471
return resource;
1472
}
1473
}
1474
1475
// Single scheme filter
1476
else {
1477
if (filter === resource.scheme) {
1478
return resource;
1479
}
1480
}
1481
1482
return undefined;
1483
}
1484
}
1485
1486
export type PreventPinnedEditorClose = 'keyboardAndMouse' | 'keyboard' | 'mouse' | 'never' | undefined;
1487
1488
export enum EditorCloseMethod {
1489
UNKNOWN,
1490
KEYBOARD,
1491
MOUSE
1492
}
1493
1494
export function preventEditorClose(group: IEditorGroup | IReadonlyEditorGroupModel, editor: EditorInput, method: EditorCloseMethod, configuration: IEditorPartConfiguration): boolean {
1495
if (!group.isSticky(editor)) {
1496
return false; // only interested in sticky editors
1497
}
1498
1499
switch (configuration.preventPinnedEditorClose) {
1500
case 'keyboardAndMouse': return method === EditorCloseMethod.MOUSE || method === EditorCloseMethod.KEYBOARD;
1501
case 'mouse': return method === EditorCloseMethod.MOUSE;
1502
case 'keyboard': return method === EditorCloseMethod.KEYBOARD;
1503
}
1504
1505
return false;
1506
}
1507
1508
export const EditorResourceAccessor = new EditorResourceAccessorImpl();
1509
1510
export const enum CloseDirection {
1511
LEFT,
1512
RIGHT
1513
}
1514
1515
export interface IEditorMemento<T> {
1516
1517
saveEditorState(group: IEditorGroup, resource: URI, state: T): void;
1518
saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void;
1519
1520
loadEditorState(group: IEditorGroup, resource: URI): T | undefined;
1521
loadEditorState(group: IEditorGroup, editor: EditorInput): T | undefined;
1522
1523
clearEditorState(resource: URI, group?: IEditorGroup): void;
1524
clearEditorState(editor: EditorInput, group?: IEditorGroup): void;
1525
1526
clearEditorStateOnDispose(resource: URI, editor: EditorInput): void;
1527
1528
moveEditorState(source: URI, target: URI, comparer: IExtUri): void;
1529
}
1530
1531
class EditorFactoryRegistry implements IEditorFactoryRegistry {
1532
private instantiationService: IInstantiationService | undefined;
1533
1534
private fileEditorFactory: IFileEditorFactory | undefined;
1535
1536
private readonly editorSerializerConstructors = new Map<string /* Type ID */, IConstructorSignature<IEditorSerializer>>();
1537
private readonly editorSerializerInstances = new Map<string /* Type ID */, IEditorSerializer>();
1538
1539
start(accessor: ServicesAccessor): void {
1540
const instantiationService = this.instantiationService = accessor.get(IInstantiationService);
1541
1542
for (const [key, ctor] of this.editorSerializerConstructors) {
1543
this.createEditorSerializer(key, ctor, instantiationService);
1544
}
1545
1546
this.editorSerializerConstructors.clear();
1547
}
1548
1549
private createEditorSerializer(editorTypeId: string, ctor: IConstructorSignature<IEditorSerializer>, instantiationService: IInstantiationService): void {
1550
const instance = instantiationService.createInstance(ctor);
1551
this.editorSerializerInstances.set(editorTypeId, instance);
1552
}
1553
1554
registerFileEditorFactory(factory: IFileEditorFactory): void {
1555
if (this.fileEditorFactory) {
1556
throw new Error('Can only register one file editor factory.');
1557
}
1558
1559
this.fileEditorFactory = factory;
1560
}
1561
1562
getFileEditorFactory(): IFileEditorFactory {
1563
return assertReturnsDefined(this.fileEditorFactory);
1564
}
1565
1566
registerEditorSerializer(editorTypeId: string, ctor: IConstructorSignature<IEditorSerializer>): IDisposable {
1567
if (this.editorSerializerConstructors.has(editorTypeId) || this.editorSerializerInstances.has(editorTypeId)) {
1568
throw new Error(`A editor serializer with type ID '${editorTypeId}' was already registered.`);
1569
}
1570
1571
if (!this.instantiationService) {
1572
this.editorSerializerConstructors.set(editorTypeId, ctor);
1573
} else {
1574
this.createEditorSerializer(editorTypeId, ctor, this.instantiationService);
1575
}
1576
1577
return toDisposable(() => {
1578
this.editorSerializerConstructors.delete(editorTypeId);
1579
this.editorSerializerInstances.delete(editorTypeId);
1580
});
1581
}
1582
1583
getEditorSerializer(editor: EditorInput): IEditorSerializer | undefined;
1584
getEditorSerializer(editorTypeId: string): IEditorSerializer | undefined;
1585
getEditorSerializer(arg1: string | EditorInput): IEditorSerializer | undefined {
1586
return this.editorSerializerInstances.get(typeof arg1 === 'string' ? arg1 : arg1.typeId);
1587
}
1588
}
1589
1590
Registry.add(EditorExtensions.EditorFactory, new EditorFactoryRegistry());
1591
1592
export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService, logService: ILogService): Promise<ReadonlyArray<IResourceEditorInput | IUntitledTextResourceEditorInput | undefined>> {
1593
if (!paths || !paths.length) {
1594
return [];
1595
}
1596
1597
return await Promise.all(paths.map(async path => {
1598
const resource = URI.revive(path.fileUri);
1599
if (!resource) {
1600
logService.info('Cannot resolve the path because it is not valid.', path);
1601
return undefined;
1602
}
1603
1604
const canHandleResource = await fileService.canHandleResource(resource);
1605
if (!canHandleResource) {
1606
logService.info('Cannot resolve the path because it cannot be handled', path);
1607
return undefined;
1608
}
1609
1610
let exists = path.exists;
1611
let type = path.type;
1612
if (typeof exists !== 'boolean' || typeof type !== 'number') {
1613
try {
1614
type = (await fileService.stat(resource)).isDirectory ? FileType.Directory : FileType.Unknown;
1615
exists = true;
1616
} catch (error) {
1617
logService.error(error);
1618
exists = false;
1619
}
1620
}
1621
1622
if (!exists && path.openOnlyIfExists) {
1623
logService.info('Cannot resolve the path because it does not exist', path);
1624
return undefined;
1625
}
1626
1627
if (type === FileType.Directory) {
1628
logService.info('Cannot resolve the path because it is a directory', path);
1629
return undefined;
1630
}
1631
1632
const options: IEditorOptions = {
1633
...path.options,
1634
pinned: true
1635
};
1636
1637
if (!exists) {
1638
return { resource, options, forceUntitled: true };
1639
}
1640
1641
return { resource, options };
1642
}));
1643
}
1644
1645
export const enum EditorsOrder {
1646
1647
/**
1648
* Editors sorted by most recent activity (most recent active first)
1649
*/
1650
MOST_RECENTLY_ACTIVE,
1651
1652
/**
1653
* Editors sorted by sequential order
1654
*/
1655
SEQUENTIAL
1656
}
1657
1658
export function isTextEditorViewState(candidate: unknown): candidate is IEditorViewState {
1659
const viewState = candidate as IEditorViewState | undefined;
1660
if (!viewState) {
1661
return false;
1662
}
1663
1664
const diffEditorViewState = viewState as IDiffEditorViewState;
1665
if (diffEditorViewState.modified) {
1666
return isTextEditorViewState(diffEditorViewState.modified);
1667
}
1668
1669
const codeEditorViewState = viewState as ICodeEditorViewState;
1670
1671
return !!(codeEditorViewState.contributionsState && codeEditorViewState.viewState && Array.isArray(codeEditorViewState.cursorState));
1672
}
1673
1674
export interface IEditorOpenErrorOptions {
1675
1676
/**
1677
* If set to true, the message will be taken
1678
* from the error message entirely and not be
1679
* composed with more text.
1680
*/
1681
forceMessage?: boolean;
1682
1683
/**
1684
* If set, will override the severity of the error.
1685
*/
1686
forceSeverity?: Severity;
1687
1688
/**
1689
* If set to true, the error may be shown in a dialog
1690
* to the user if the editor opening was triggered by
1691
* user action. Otherwise and by default, the error will
1692
* be shown as place holder in the editor area.
1693
*/
1694
allowDialog?: boolean;
1695
}
1696
1697
export interface IEditorOpenError extends IErrorWithActions, IEditorOpenErrorOptions { }
1698
1699
export function isEditorOpenError(obj: unknown): obj is IEditorOpenError {
1700
return isErrorWithActions(obj);
1701
}
1702
1703
export function createEditorOpenError(messageOrError: string | Error, actions: IAction[], options?: IEditorOpenErrorOptions): IEditorOpenError {
1704
const error: IEditorOpenError = createErrorWithActions(messageOrError, actions);
1705
1706
error.forceMessage = options?.forceMessage;
1707
error.forceSeverity = options?.forceSeverity;
1708
error.allowDialog = options?.allowDialog;
1709
1710
return error;
1711
}
1712
1713
export interface IToolbarActions {
1714
readonly primary: IAction[];
1715
readonly secondary: IAction[];
1716
}
1717
1718