customEditor.ts 6.3 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

M
Matt Bierner 已提交
6
import { distinct, mergeSort } from 'vs/base/common/arrays';
7
import { Event } from 'vs/base/common/event';
8
import * as glob from 'vs/base/common/glob';
9
import { IDisposable, IReference } from 'vs/base/common/lifecycle';
10
import { posix } from 'vs/base/common/path';
11
import { basename } from 'vs/base/common/resources';
12
import { URI } from 'vs/base/common/uri';
13
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
14 15
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
16
import { GroupIdentifier, IEditorInput, IEditorPane, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
17 18 19 20
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';

export const ICustomEditorService = createDecorator<ICustomEditorService>('customEditorService');

21
export const CONTEXT_CUSTOM_EDITORS = new RawContextKey<string>('customEditors', '');
22 23
export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey<boolean>('focusedCustomEditorIsEditable', false);

24
export interface CustomEditorCapabilities {
25
	readonly supportsMultipleEditorsPerDocument?: boolean;
26 27
}

28 29 30
export interface ICustomEditorService {
	_serviceBrand: any;

31 32
	readonly models: ICustomEditorModelManager;

33
	getCustomEditor(viewType: string): CustomEditorInfo | undefined;
34
	getAllCustomEditors(resource: URI): CustomEditorInfoCollection;
35 36
	getContributedCustomEditors(resource: URI): CustomEditorInfoCollection;
	getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection;
37

38
	createInput(resource: URI, viewType: string, group: GroupIdentifier | undefined, options?: { readonly customClasses: string }): IEditorInput;
39

40 41
	openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined>;
	promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise<IEditorPane | undefined>;
42 43

	registerCustomEditorCapabilities(viewType: string, options: CustomEditorCapabilities): IDisposable;
44 45
}

46
export interface ICustomEditorModelManager {
47
	get(resource: URI, viewType: string): Promise<ICustomEditorModel | undefined>;
48

49
	tryRetain(resource: URI, viewType: string): Promise<IReference<ICustomEditorModel>> | undefined;
50

51
	add(resource: URI, viewType: string, model: Promise<ICustomEditorModel>): Promise<IReference<ICustomEditorModel>>;
52 53

	disposeAllModelsForView(viewType: string): void;
54 55
}

56
export interface ICustomEditorModel extends IDisposable {
57
	readonly viewType: string;
58
	readonly resource: URI;
59

60 61
	isReadonly(): boolean;

62 63
	isDirty(): boolean;
	readonly onDidChangeDirty: Event<void>;
M
Matt Bierner 已提交
64

65
	revert(options?: IRevertOptions): Promise<void>;
66

67 68
	saveCustomEditor(options?: ISaveOptions): Promise<URI | undefined>;
	saveCustomEditorAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise<boolean>;
69 70
}

71
export const enum CustomEditorPriority {
72
	default = 'default',
73
	builtin = 'builtin',
74 75 76 77 78 79 80
	option = 'option',
}

export interface CustomEditorSelector {
	readonly filenamePattern?: string;
}

81 82 83 84
export class CustomEditorInfo {

	public readonly id: string;
	public readonly displayName: string;
85
	public readonly providerDisplayName: string;
86 87 88 89 90 91
	public readonly priority: CustomEditorPriority;
	public readonly selector: readonly CustomEditorSelector[];

	constructor(descriptor: {
		readonly id: string;
		readonly displayName: string;
92
		readonly providerDisplayName: string;
93 94 95 96 97
		readonly priority: CustomEditorPriority;
		readonly selector: readonly CustomEditorSelector[];
	}) {
		this.id = descriptor.id;
		this.displayName = descriptor.displayName;
98
		this.providerDisplayName = descriptor.providerDisplayName;
99 100 101 102 103 104 105 106 107 108
		this.priority = descriptor.priority;
		this.selector = descriptor.selector;
	}

	matches(resource: URI): boolean {
		return this.selector.some(selector => CustomEditorInfo.selectorMatches(selector, resource));
	}

	static selectorMatches(selector: CustomEditorSelector, resource: URI): boolean {
		if (selector.filenamePattern) {
109 110 111
			const matchOnPath = selector.filenamePattern.indexOf(posix.sep) >= 0;
			const target = matchOnPath ? resource.path : basename(resource);
			if (glob.match(selector.filenamePattern.toLowerCase(), target.toLowerCase())) {
112 113 114 115 116
				return true;
			}
		}
		return false;
	}
117
}
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

export class CustomEditorInfoCollection {

	public readonly allEditors: readonly CustomEditorInfo[];

	constructor(
		editors: readonly CustomEditorInfo[],
	) {
		this.allEditors = distinct(editors, editor => editor.id);
	}

	public get length(): number { return this.allEditors.length; }

	/**
	 * Find the single default editor to use (if any) by looking at the editor's priority and the
	 * other contributed editors.
	 */
	public get defaultEditor(): CustomEditorInfo | undefined {
M
Matt Bierner 已提交
136
		return this.allEditors.find(editor => {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
			switch (editor.priority) {
				case CustomEditorPriority.default:
				case CustomEditorPriority.builtin:
					// A default editor must have higher priority than all other contributed editors.
					return this.allEditors.every(otherEditor =>
						otherEditor === editor || isLowerPriority(otherEditor, editor));

				default:
					return false;
			}
		});
	}

	/**
	 * Find the best available editor to use.
	 *
	 * Unlike the `defaultEditor`, a bestAvailableEditor can exist even if there are other editors with
	 * the same priority.
	 */
	public get bestAvailableEditor(): CustomEditorInfo | undefined {
		const editors = mergeSort(Array.from(this.allEditors), (a, b) => {
			return priorityToRank(a.priority) - priorityToRank(b.priority);
		});
		return editors[0];
	}
}

function isLowerPriority(otherEditor: CustomEditorInfo, editor: CustomEditorInfo): unknown {
	return priorityToRank(otherEditor.priority) < priorityToRank(editor.priority);
}

function priorityToRank(priority: CustomEditorPriority): number {
	switch (priority) {
		case CustomEditorPriority.default: return 3;
		case CustomEditorPriority.builtin: return 2;
		case CustomEditorPriority.option: return 1;
	}
}