提交 dac56c18 编写于 作者: B Benjamin Pasero

allow to provide a language mode when opening text editors

上级 8deb7562
......@@ -230,10 +230,11 @@ export function isUnspecific(mime: string[] | string): boolean {
* 2. Otherwise, if there are other extensions, suggest the first one.
* 3. Otherwise, suggest the prefix.
*/
export function suggestFilename(langId: string | null, prefix: string): string {
export function suggestFilename(mode: string | undefined, prefix: string): string {
const extensions = registeredAssociations
.filter(assoc => !assoc.userConfigured && assoc.extension && assoc.id === langId)
.filter(assoc => !assoc.userConfigured && assoc.extension && assoc.id === mode)
.map(assoc => assoc.extension);
const extensionsWithDotFirst = coalesce(extensions)
.filter(assoc => startsWith(assoc, '.'));
......
......@@ -63,7 +63,7 @@ export interface IBaseResourceInput {
export interface IResourceInput extends IBaseResourceInput {
/**
* The resource URL of the resource to open.
* The resource URI of the resource to open.
*/
resource: URI;
......@@ -71,6 +71,12 @@ export interface IResourceInput extends IBaseResourceInput {
* The encoding of the text input if known.
*/
readonly encoding?: string;
/**
* The identifier of the language mode of the text input
* if known to use when displaying the contents.
*/
readonly mode?: string;
}
export interface IEditorOptions {
......
......@@ -228,10 +228,10 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
});
}
private _doCreateUntitled(resource?: URI, modeId?: string, initialValue?: string): Promise<URI> {
private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise<URI> {
return this._untitledEditorService.loadOrCreate({
resource,
modeId,
mode,
initialValue,
useResourcePath: Boolean(resource && resource.path)
}).then(model => {
......
......@@ -104,7 +104,7 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
interface ISerializedUntitledEditorInput {
resource: string;
resourceJSON: object;
modeId: string | null;
modeId: string | undefined;
encoding: string;
}
......@@ -131,7 +131,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
const serialized: ISerializedUntitledEditorInput = {
resource: resource.toString(), // Keep for backwards compatibility
resourceJSON: resource.toJSON(),
modeId: untitledEditorInput.getModeId(),
modeId: untitledEditorInput.getMode(),
encoding: untitledEditorInput.getEncoding()
};
......@@ -142,10 +142,10 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
return instantiationService.invokeFunction<UntitledEditorInput>(accessor => {
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
const resource = !!deserialized.resourceJSON ? URI.revive(<UriComponents>deserialized.resourceJSON) : URI.parse(deserialized.resource);
const language = deserialized.modeId;
const mode = deserialized.modeId;
const encoding = deserialized.encoding;
return accessor.get(IEditorService).createInput({ resource, language, encoding, forceUntitled: true }) as UntitledEditorInput;
return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledEditorInput;
});
}
}
......
......@@ -14,11 +14,11 @@ import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { Action } from 'vs/base/common/actions';
import { Language } from 'vs/base/common/platform';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor';
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor';
import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
import { EndOfLineSequence } from 'vs/editor/common/model';
import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/linesOperations';
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation';
......@@ -59,7 +59,15 @@ class SideBySideEditorEncodingSupport implements IEncodingSupport {
}
setEncoding(encoding: string, mode: EncodingMode): void {
[this.master, this.details].forEach(s => s.setEncoding(encoding, mode));
[this.master, this.details].forEach(editor => editor.setEncoding(encoding, mode));
}
}
class SideBySideEditorModeSupport implements IModeSupport {
constructor(private master: IModeSupport, private details: IModeSupport) { }
setMode(mode: string): void {
[this.master, this.details].forEach(editor => editor.setMode(mode));
}
}
......@@ -83,7 +91,7 @@ function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | nu
}
// File or Resource Editor
let encodingSupport = input as IFileEditorInput;
const encodingSupport = input as IFileEditorInput;
if (areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) {
return encodingSupport;
}
......@@ -92,14 +100,41 @@ function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | nu
return null;
}
function toEditorWithModeSupport(input: IEditorInput): IModeSupport | null {
// Untitled Editor
if (input instanceof UntitledEditorInput) {
return input;
}
// Side by Side (diff) Editor
if (input instanceof SideBySideEditorInput) {
const masterModeSupport = toEditorWithModeSupport(input.master);
const detailsModeSupport = toEditorWithModeSupport(input.details);
if (masterModeSupport && detailsModeSupport) {
return new SideBySideEditorModeSupport(masterModeSupport, detailsModeSupport);
}
return masterModeSupport;
}
// File or Resource Editor
const modeSupport = input as IFileEditorInput;
if (typeof modeSupport.setMode === 'function') {
return modeSupport;
}
// Unsupported for any other editor
return null;
}
interface IEditorSelectionStatus {
selections?: Selection[];
charactersSelected?: number;
}
class StateChange {
_stateChangeBrand: void;
indentation: boolean = false;
selectionStatus: boolean = false;
mode: boolean = false;
......@@ -120,7 +155,7 @@ class StateChange {
this.metadata = this.metadata || other.metadata;
}
public hasChanges(): boolean {
hasChanges(): boolean {
return this.indentation
|| this.selectionStatus
|| this.mode
......@@ -179,42 +214,49 @@ class State {
change.selectionStatus = true;
}
}
if ('indentation' in update) {
if (this._indentation !== update.indentation) {
this._indentation = update.indentation;
change.indentation = true;
}
}
if ('mode' in update) {
if (this._mode !== update.mode) {
this._mode = update.mode;
change.mode = true;
}
}
if ('encoding' in update) {
if (this._encoding !== update.encoding) {
this._encoding = update.encoding;
change.encoding = true;
}
}
if ('EOL' in update) {
if (this._EOL !== update.EOL) {
this._EOL = update.EOL;
change.EOL = true;
}
}
if ('tabFocusMode' in update) {
if (this._tabFocusMode !== update.tabFocusMode) {
this._tabFocusMode = update.tabFocusMode;
change.tabFocusMode = true;
}
}
if ('screenReaderMode' in update) {
if (this._screenReaderMode !== update.screenReaderMode) {
this._screenReaderMode = update.screenReaderMode;
change.screenReaderMode = true;
}
}
if ('metadata' in update) {
if (this._metadata !== update.metadata) {
this._metadata = update.metadata;
......@@ -236,7 +278,6 @@ const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized");
const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\".");
class StatusBarItem {
private _showing = true;
......@@ -248,15 +289,15 @@ class StatusBarItem {
this.element.title = title;
}
public set textContent(value: string) {
set textContent(value: string) {
this.element.textContent = value;
}
public set onclick(value: () => void) {
set onclick(value: () => void) {
this.element.onclick = value;
}
public setVisible(shouldShow: boolean): void {
setVisible(shouldShow: boolean): void {
if (shouldShow !== this._showing) {
this._showing = shouldShow;
this.element.style.display = shouldShow ? '' : 'none';
......@@ -264,7 +305,6 @@ class StatusBarItem {
}
}
export class EditorStatus implements IStatusbarItem {
private state: State;
private element: HTMLElement;
......@@ -661,7 +701,7 @@ export class EditorStatus implements IStatusbarItem {
this.updateState(update);
}
private _promptedScreenReader: boolean = false;
private promptedScreenReader: boolean = false;
private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void {
let screenReaderMode = false;
......@@ -673,8 +713,8 @@ export class EditorStatus implements IStatusbarItem {
const screenReaderConfiguration = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
if (screenReaderConfiguration === 'auto') {
// show explanation
if (!this._promptedScreenReader) {
this._promptedScreenReader = true;
if (!this.promptedScreenReader) {
this.promptedScreenReader = true;
setTimeout(() => {
this.onScreenReaderModeClick();
}, 100);
......@@ -948,43 +988,28 @@ export class ChangeModeAction extends Action {
// Change mode for active editor
const activeEditor = this.editorService.activeEditor;
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
const models: ITextModel[] = [];
if (isCodeEditor(activeTextEditorWidget)) {
const codeEditorModel = activeTextEditorWidget.getModel();
if (codeEditorModel) {
models.push(codeEditorModel);
}
} else if (isDiffEditor(activeTextEditorWidget)) {
const diffEditorModel = activeTextEditorWidget.getModel();
if (diffEditorModel) {
if (diffEditorModel.original) {
models.push(diffEditorModel.original);
}
if (diffEditorModel.modified) {
models.push(diffEditorModel.modified);
if (activeEditor) {
const modeSupport = toEditorWithModeSupport(activeEditor);
if (modeSupport) {
// Find mode
let languageSelection: ILanguageSelection | undefined;
if (pick === autoDetectMode) {
if (textModel) {
const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
if (resource) {
languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1));
}
}
} else {
languageSelection = this.modeService.createByLanguageName(pick.label);
}
}
}
// Find mode
let languageSelection: ILanguageSelection | undefined;
if (pick === autoDetectMode) {
if (textModel) {
const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER });
if (resource) {
languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1));
// Change mode
if (typeof languageSelection !== 'undefined') {
modeSupport.setMode(languageSelection.languageIdentifier.language);
}
}
} else {
languageSelection = this.modeService.createByLanguageName(pick.label);
}
// Change mode
if (typeof languageSelection !== 'undefined') {
for (const textModel of models) {
this.modelService.setMode(textModel, languageSelection);
}
}
});
}
......@@ -1159,6 +1184,7 @@ export class ChangeEncodingAction extends Action {
if (!activeControl) {
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
}
const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input);
if (!encodingSupport) {
return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
......@@ -1249,10 +1275,12 @@ export class ChangeEncodingAction extends Action {
if (!encoding) {
return;
}
const activeControl = this.editorService.activeControl;
if (!activeControl) {
return;
}
const encodingSupport = toEditorWithEncodingSupport(activeControl.input);
if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
......
......@@ -144,7 +144,7 @@ export interface IEditorControl extends ICompositeControl { }
export interface IFileInputFactory {
createFileInput(resource: URI, encoding: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
isFileInput(obj: any): obj is IFileEditorInput;
}
......@@ -209,7 +209,7 @@ export interface IUntitledResourceInput extends IBaseResourceInput {
/**
* Optional language of the untitled resource.
*/
language?: string;
mode?: string;
/**
* Optional contents of the untitled resource.
......@@ -505,19 +505,35 @@ export interface IEncodingSupport {
setEncoding(encoding: string, mode: EncodingMode): void;
}
export interface IModeSupport {
/**
* Sets the language mode of the input.
*/
setMode(mode: string): void;
}
/**
* This is a tagging interface to declare an editor input being capable of dealing with files. It is only used in the editor registry
* to register this kind of input to the platform.
*/
export interface IFileEditorInput extends IEditorInput, IEncodingSupport {
export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeSupport {
/**
* Gets the resource this editor is about.
*/
getResource(): URI;
/**
* Sets the preferred encodingt to use for this input.
* Sets the preferred encoding to use for this input.
*/
setPreferredEncoding(encoding: string): void;
/**
* Sets the preferred language mode to use for this input.
*/
setPreferredMode(mode: string): void;
/**
* Forces this file input to open as binary instead of text.
*/
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorInput, ITextEditorModel } from 'vs/workbench/common/editor';
import { EditorInput, ITextEditorModel, IModeSupport } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { IReference } from 'vs/base/common/lifecycle';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
......@@ -13,16 +13,18 @@ import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorMo
* A read-only text editor input whos contents are made of the provided resource that points to an existing
* code editor model.
*/
export class ResourceEditorInput extends EditorInput {
export class ResourceEditorInput extends EditorInput implements IModeSupport {
static readonly ID: string = 'workbench.editors.resourceEditorInput';
private cachedModel: ResourceEditorModel | null;
private modelReference: Promise<IReference<ITextEditorModel>> | null;
constructor(
private name: string,
private description: string | null,
private readonly resource: URI,
private preferredMode: string | undefined,
@ITextModelService private readonly textModelResolverService: ITextModelService
) {
super();
......@@ -62,6 +64,18 @@ export class ResourceEditorInput extends EditorInput {
}
}
setMode(mode: string): void {
this.setPreferredMode(mode);
if (this.cachedModel) {
this.cachedModel.setMode(mode);
}
}
setPreferredMode(mode: string): void {
this.preferredMode = mode;
}
resolve(): Promise<ITextEditorModel> {
if (!this.modelReference) {
this.modelReference = this.textModelResolverService.createModelReference(this.resource);
......@@ -70,6 +84,7 @@ export class ResourceEditorInput extends EditorInput {
return this.modelReference.then(ref => {
const model = ref.object;
// Ensure the resolved model is of expected type
if (!(model instanceof ResourceEditorModel)) {
ref.dispose();
this.modelReference = null;
......@@ -77,6 +92,13 @@ export class ResourceEditorInput extends EditorInput {
return Promise.reject(new Error(`Unexpected model for ResourceInput: ${this.resource}`));
}
this.cachedModel = model;
// Set mode if we have a preferred mode configured
if (this.preferredMode) {
model.setMode(this.preferredMode);
}
return model;
});
}
......@@ -87,7 +109,7 @@ export class ResourceEditorInput extends EditorInput {
}
if (otherInput instanceof ResourceEditorInput) {
let otherResourceEditorInput = <ResourceEditorInput>otherInput;
const otherResourceEditorInput = <ResourceEditorInput>otherInput;
// Compare by properties
return otherResourceEditorInput.resource.toString() === this.resource.toString();
......@@ -102,6 +124,8 @@ export class ResourceEditorInput extends EditorInput {
this.modelReference = null;
}
this.cachedModel = null;
super.dispose();
}
}
......@@ -4,17 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import { ITextModel, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model';
import { EditorModel } from 'vs/workbench/common/editor';
import { EditorModel, IModeSupport } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
/**
* The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated.
*/
export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel {
export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport {
protected createdEditorModel: boolean;
......@@ -64,12 +65,25 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
abstract isReadonly(): boolean;
setMode(mode: string): void {
if (!this.isResolved()) {
return;
}
if (!mode || mode === this.textEditorModel.getModeId()) {
return;
}
this.modelService.setMode(this.textEditorModel, this.modeService.create(mode));
}
/**
* Creates the text editor model with the provided value, modeId (can be comma separated for multiple values) and optional resource URL.
* Creates the text editor model with the provided value, optional preferred mode
* (can be comma separated for multiple values) and optional resource URL.
*/
protected createTextEditorModel(value: ITextBufferFactory, resource: URI | undefined, modeId?: string): EditorModel {
protected createTextEditorModel(value: ITextBufferFactory, resource: URI | undefined, preferredMode?: string): EditorModel {
const firstLineText = this.getFirstLineText(value);
const languageSelection = this.getOrCreateMode(this.modeService, modeId, firstLineText);
const languageSelection = this.getOrCreateMode(resource, this.modeService, preferredMode, firstLineText);
return this.doCreateTextEditorModel(value, languageSelection, resource);
}
......@@ -83,8 +97,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
// Make sure we clean up when this model gets disposed
this.registerModelDisposeListener(model);
} else {
this.modelService.updateModel(model, value);
this.modelService.setMode(model, languageSelection);
this.updateTextEditorModel(value, languageSelection.languageIdentifier.language);
}
this.textEditorModelHandle = model.uri;
......@@ -110,19 +123,32 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
*
* @param firstLineText optional first line of the text buffer to set the mode on. This can be used to guess a mode from content.
*/
protected getOrCreateMode(modeService: IModeService, modeId: string | undefined, firstLineText?: string): ILanguageSelection {
return modeService.create(modeId);
protected getOrCreateMode(resource: URI | undefined, modeService: IModeService, preferredMode: string | undefined, firstLineText?: string): ILanguageSelection {
// lookup mode via resource path if the provided mode is unspecific
if (!preferredMode || preferredMode === PLAINTEXT_MODE_ID) {
return modeService.createByFilepathOrFirstLine(resource ? resource.fsPath : null, firstLineText);
}
// otherwise take the preferred mode for granted
return modeService.create(preferredMode);
}
/**
* Updates the text editor model with the provided value. If the value is the same as the model has, this is a no-op.
*/
protected updateTextEditorModel(newValue: ITextBufferFactory): void {
if (!this.textEditorModel) {
protected updateTextEditorModel(newValue: ITextBufferFactory, preferredMode?: string): void {
if (!this.isResolved()) {
return;
}
// contents
this.modelService.updateModel(this.textEditorModel, newValue);
// mode (only if specific and changed)
if (preferredMode && preferredMode !== PLAINTEXT_MODE_ID && this.textEditorModel.getModeId() !== preferredMode) {
this.modelService.setMode(this.textEditorModel, this.modeService.create(preferredMode));
}
}
createSnapshot(this: IResolvedTextEditorModel): ITextSnapshot;
......
......@@ -9,7 +9,7 @@ import { memoize } from 'vs/base/common/decorators';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { basename } from 'vs/base/common/path';
import { basenameOrAuthority, dirname } from 'vs/base/common/resources';
import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity } from 'vs/workbench/common/editor';
import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity, IModeSupport } from 'vs/workbench/common/editor';
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
......@@ -20,12 +20,12 @@ import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverServ
/**
* An editor input to be used for untitled text buffers.
*/
export class UntitledEditorInput extends EditorInput implements IEncodingSupport {
export class UntitledEditorInput extends EditorInput implements IEncodingSupport, IModeSupport {
static readonly ID: string = 'workbench.editors.untitledEditorInput';
private cachedModel: UntitledEditorModel;
private modelResolve?: Promise<UntitledEditorModel & IResolvedTextEditorModel>;
private cachedModel: UntitledEditorModel | null;
private modelResolve: Promise<UntitledEditorModel & IResolvedTextEditorModel> | null;
private readonly _onDidModelChangeContent: Emitter<void> = this._register(new Emitter<void>());
get onDidModelChangeContent(): Event<void> { return this._onDidModelChangeContent.event; }
......@@ -36,7 +36,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport
constructor(
private readonly resource: URI,
private readonly _hasAssociatedFilePath: boolean,
private readonly modeId: string,
private preferredMode: string,
private readonly initialValue: string,
private preferredEncoding: string,
@IInstantiationService private readonly instantiationService: IInstantiationService,
......@@ -58,14 +58,6 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport
return this.resource;
}
getModeId(): string | null {
if (this.cachedModel) {
return this.cachedModel.getModeId();
}
return this.modeId;
}
getName(): string {
return this.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path;
}
......@@ -168,9 +160,9 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport
suggestFileName(): string {
if (!this.hasAssociatedFilePath) {
if (this.cachedModel) {
const modeId = this.cachedModel.getModeId();
if (modeId !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text
return suggestFilename(modeId, this.getName());
const mode = this.cachedModel.getMode();
if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text
return suggestFilename(mode, this.getName());
}
}
}
......@@ -194,6 +186,22 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport
}
}
setMode(mode: string): void {
this.preferredMode = mode;
if (this.cachedModel) {
this.cachedModel.setMode(mode);
}
}
getMode(): string | undefined {
if (this.cachedModel) {
return this.cachedModel.getMode();
}
return this.preferredMode;
}
resolve(): Promise<UntitledEditorModel & IResolvedTextEditorModel> {
// Join a model resolve if we have had one before
......@@ -209,7 +217,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport
}
private createModel(): UntitledEditorModel {
const model = this._register(this.instantiationService.createInstance(UntitledEditorModel, this.modeId, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding));
const model = this._register(this.instantiationService.createInstance(UntitledEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding));
// re-emit some events from the model
this._register(model.onDidChangeContent(() => this._onDidModelChangeContent.fire()));
......@@ -235,7 +243,8 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport
}
dispose(): void {
this.modelResolve = undefined;
this.cachedModel = null;
this.modelResolve = null;
super.dispose();
}
......
......@@ -6,9 +6,8 @@
import { IEncodingSupport } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { URI } from 'vs/base/common/uri';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files';
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Event, Emitter } from 'vs/base/common/event';
import { RunOnceScheduler } from 'vs/base/common/async';
......@@ -37,7 +36,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
private configuredEncoding: string;
constructor(
private readonly modeId: string,
private readonly preferredMode: string,
private readonly resource: URI,
private _hasAssociatedFilePath: boolean,
private readonly initialValue: string,
......@@ -58,14 +57,6 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
return this._hasAssociatedFilePath;
}
protected getOrCreateMode(modeService: IModeService, modeId: string, firstLineText?: string): ILanguageSelection {
if (!modeId || modeId === PLAINTEXT_MODE_ID) {
return modeService.createByFilepathOrFirstLine(this.resource.fsPath, firstLineText); // lookup mode via resource path if the provided modeId is unspecific
}
return super.getOrCreateMode(modeService, modeId, firstLineText);
}
private registerListeners(): void {
// Config Changes
......@@ -88,12 +79,12 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
return this.versionId;
}
getModeId(): string | null {
getMode(): string | undefined {
if (this.textEditorModel) {
return this.textEditorModel.getLanguageIdentifier().language;
}
return this.modeId;
return this.preferredMode;
}
getEncoding(): string {
......@@ -166,12 +157,12 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin
// Create text editor model if not yet done
if (!this.textEditorModel) {
this.createTextEditorModel(untitledContents, this.resource, this.modeId);
this.createTextEditorModel(untitledContents, this.resource, this.preferredMode);
}
// Otherwise update
else {
this.updateTextEditorModel(untitledContents);
this.updateTextEditorModel(untitledContents, this.preferredMode);
}
// Encoding
......
......@@ -42,8 +42,8 @@ import { Schemas } from 'vs/base/common/network';
// Viewlet Action
export class OpenExplorerViewletAction extends ShowViewletAction {
public static readonly ID = VIEWLET_ID;
public static readonly LABEL = nls.localize('showExplorerViewlet', "Show Explorer");
static readonly ID = VIEWLET_ID;
static readonly LABEL = nls.localize('showExplorerViewlet', "Show Explorer");
constructor(
id: string,
......@@ -124,8 +124,8 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
// Register default file input factory
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerFileInputFactory({
createFileInput: (resource, encoding, instantiationService): IFileEditorInput => {
return instantiationService.createInstance(FileEditorInput, resource, encoding);
createFileInput: (resource, encoding, mode, instantiationService): IFileEditorInput => {
return instantiationService.createInstance(FileEditorInput, resource, encoding, mode);
},
isFileInput: (obj): obj is IFileEditorInput => {
......@@ -137,6 +137,7 @@ interface ISerializedFileInput {
resource: string;
resourceJSON: object;
encoding?: string;
modeId?: string;
}
// Register Editor Input Factory
......@@ -144,25 +145,27 @@ class FileEditorInputFactory implements IEditorInputFactory {
constructor() { }
public serialize(editorInput: EditorInput): string {
serialize(editorInput: EditorInput): string {
const fileEditorInput = <FileEditorInput>editorInput;
const resource = fileEditorInput.getResource();
const fileInput: ISerializedFileInput = {
resource: resource.toString(), // Keep for backwards compatibility
resourceJSON: resource.toJSON(),
encoding: fileEditorInput.getEncoding()
encoding: fileEditorInput.getEncoding(),
modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data
};
return JSON.stringify(fileInput);
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput {
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput {
return instantiationService.invokeFunction<FileEditorInput>(accessor => {
const fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput);
const resource = !!fileInput.resourceJSON ? URI.revive(<UriComponents>fileInput.resourceJSON) : URI.parse(fileInput.resource);
const encoding = fileInput.encoding;
const mode = fileInput.modeId;
return accessor.get(IEditorService).createInput({ resource, encoding, forceFile: true }) as FileEditorInput;
return accessor.get(IEditorService).createInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput;
});
}
}
......
......@@ -24,8 +24,11 @@ import { ILabelService } from 'vs/platform/label/common/label';
*/
export class FileEditorInput extends EditorInput implements IFileEditorInput {
private preferredEncoding: string;
private preferredMode: string;
private forceOpenAsBinary: boolean;
private forceOpenAsText: boolean;
private textModelReference: Promise<IReference<ITextEditorModel>> | null;
private name: string;
......@@ -35,6 +38,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
constructor(
private resource: URI,
preferredEncoding: string | undefined,
preferredMode: string | undefined,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ITextFileService private readonly textFileService: ITextFileService,
@ITextModelService private readonly textModelResolverService: ITextModelService,
......@@ -46,6 +50,10 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
this.setPreferredEncoding(preferredEncoding);
}
if (preferredMode) {
this.setPreferredMode(preferredMode);
}
this.registerListeners();
}
......@@ -89,7 +97,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
}
setEncoding(encoding: string, mode: EncodingMode): void {
this.preferredEncoding = encoding;
this.setPreferredEncoding(encoding);
const textModel = this.textFileService.models.get(this.resource);
if (textModel) {
......@@ -102,6 +110,24 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
this.forceOpenAsText = true; // encoding is a good hint to open the file as text
}
getPreferredMode(): string | undefined {
return this.preferredMode;
}
setMode(mode: string): void {
this.setPreferredMode(mode);
const textModel = this.textFileService.models.get(this.resource);
if (textModel) {
textModel.setMode(mode);
}
}
setPreferredMode(mode: string): void {
this.preferredMode = mode;
this.forceOpenAsText = true; // mode is a good hint to open the file as text
}
setForceOpenAsText(): void {
this.forceOpenAsText = true;
this.forceOpenAsBinary = false;
......@@ -251,6 +277,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
// Resolve as text
return this.textFileService.models.loadOrCreate(this.resource, {
mode: this.preferredMode,
encoding: this.preferredEncoding,
reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model
allowBinary: this.forceOpenAsText,
......
......@@ -16,6 +16,7 @@ import { FileOperationResult, FileOperationError } from 'vs/platform/files/commo
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { timeout } from 'vs/base/common/async';
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
class ServiceAccessor {
constructor(
......@@ -36,10 +37,10 @@ suite('Files - FileEditorInput', () => {
accessor = instantiationService.createInstance(ServiceAccessor);
});
test('Basics', function () {
let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined);
const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined);
const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined);
test('Basics', async function () {
let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined);
const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined, undefined);
const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined, undefined);
assert(input.matches(input));
assert(input.matches(otherInputSame));
......@@ -54,52 +55,65 @@ suite('Files - FileEditorInput', () => {
assert.strictEqual(toResource.call(this, '/foo/bar/file.js').fsPath, input.getResource().fsPath);
assert(input.getResource() instanceof URI);
input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined);
input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined, undefined);
const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined);
const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined);
const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined);
const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined);
return inputToResolve.resolve().then(resolved => {
assert.ok(inputToResolve.isResolved());
let resolved = await inputToResolve.resolve();
assert.ok(inputToResolve.isResolved());
const resolvedModelA = resolved;
return inputToResolve.resolve().then(resolved => {
assert(resolvedModelA === resolved); // OK: Resolved Model cached globally per input
const resolvedModelA = resolved;
resolved = await inputToResolve.resolve();
assert(resolvedModelA === resolved); // OK: Resolved Model cached globally per input
return sameOtherInput.resolve().then(otherResolved => {
assert(otherResolved === resolvedModelA); // OK: Resolved Model cached globally per input
const otherResolved = await sameOtherInput.resolve();
assert(otherResolved === resolvedModelA); // OK: Resolved Model cached globally per input
inputToResolve.dispose();
inputToResolve.dispose();
resolved = await inputToResolve.resolve();
assert(resolvedModelA === resolved); // Model is still the same because we had 2 clients
inputToResolve.dispose();
sameOtherInput.dispose();
resolvedModelA.dispose();
return inputToResolve.resolve().then(resolved => {
assert(resolvedModelA === resolved); // Model is still the same because we had 2 clients
resolved = await inputToResolve.resolve();
assert(resolvedModelA !== resolved); // Different instance, because input got disposed
inputToResolve.dispose();
sameOtherInput.dispose();
const stat = (resolved as TextFileEditorModel).getStat();
resolved = await inputToResolve.resolve();
await timeout(0);
assert(stat !== (resolved as TextFileEditorModel).getStat()); // Different stat, because resolve always goes to the server for refresh
});
test('preferred mode', async function () {
const mode = 'file-input-test';
ModesRegistry.registerLanguage({
id: mode,
});
resolvedModelA.dispose();
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, mode);
assert.equal(input.getPreferredMode(), mode);
return inputToResolve.resolve().then(resolved => {
assert(resolvedModelA !== resolved); // Different instance, because input got disposed
const model = await input.resolve() as TextFileEditorModel;
assert.equal(model.textEditorModel!.getModeId(), mode);
let stat = (resolved as TextFileEditorModel).getStat();
return inputToResolve.resolve().then(resolved => {
return timeout(0).then(() => { // due to file editor input using `reload: { async: true }`
assert(stat !== (resolved as TextFileEditorModel).getStat()); // Different stat, because resolve always goes to the server for refresh
});
});
});
});
});
});
});
input.setMode('text');
assert.equal(input.getPreferredMode(), 'text');
assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID);
const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined);
input2.setPreferredMode(mode);
const model2 = await input2.resolve() as TextFileEditorModel;
assert.equal(model2.textEditorModel!.getModeId(), mode);
});
test('matches', function () {
const input1 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
const input3 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/other.js'), undefined);
const input2Upper = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined);
const input1 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined);
const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined);
const input3 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/other.js'), undefined, undefined);
const input2Upper = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined, undefined);
assert.strictEqual(input1.matches(null), false);
assert.strictEqual(input1.matches(input1), true);
......@@ -109,70 +123,58 @@ suite('Files - FileEditorInput', () => {
assert.strictEqual(input1.matches(input2Upper), false);
});
test('getEncoding/setEncoding', function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
test('getEncoding/setEncoding', async function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined);
input.setEncoding('utf16', EncodingMode.Encode);
assert.equal(input.getEncoding(), 'utf16');
return input.resolve().then((resolved: TextFileEditorModel) => {
assert.equal(input.getEncoding(), resolved.getEncoding());
resolved.dispose();
});
const resolved = await input.resolve() as TextFileEditorModel;
assert.equal(input.getEncoding(), resolved.getEncoding());
resolved.dispose();
});
test('save', function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
test('save', async function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined);
return input.resolve().then((resolved: TextFileEditorModel) => {
resolved.textEditorModel!.setValue('changed');
assert.ok(input.isDirty());
const resolved = await input.resolve() as TextFileEditorModel;
resolved.textEditorModel!.setValue('changed');
assert.ok(input.isDirty());
return input.save().then(() => {
assert.ok(!input.isDirty());
resolved.dispose();
});
});
await input.save();
assert.ok(!input.isDirty());
resolved.dispose();
});
test('revert', function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
test('revert', async function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined);
return input.resolve().then((resolved: TextFileEditorModel) => {
resolved.textEditorModel!.setValue('changed');
assert.ok(input.isDirty());
const resolved = await input.resolve() as TextFileEditorModel;
resolved.textEditorModel!.setValue('changed');
assert.ok(input.isDirty());
return input.revert().then(() => {
assert.ok(!input.isDirty());
resolved.dispose();
});
});
await input.revert();
assert.ok(!input.isDirty());
resolved.dispose();
});
test('resolve handles binary files', function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
test('resolve handles binary files', async function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined);
accessor.textFileService.setResolveTextContentErrorOnce(new TextFileOperationError('error', TextFileOperationResult.FILE_IS_BINARY));
return input.resolve().then(resolved => {
assert.ok(resolved);
resolved.dispose();
});
const resolved = await input.resolve();
assert.ok(resolved);
resolved.dispose();
});
test('resolve handles too large files', function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined);
test('resolve handles too large files', async function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined);
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_TOO_LARGE));
return input.resolve().then(resolved => {
assert.ok(resolved);
resolved.dispose();
});
const resolved = await input.resolve();
assert.ok(resolved);
resolved.dispose();
});
});
......@@ -28,7 +28,7 @@ export class LogViewerInput extends ResourceEditorInput {
constructor(private outputChannelDescriptor: IFileOutputChannelDescriptor,
@ITextModelService textModelResolverService: ITextModelService
) {
super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), textModelResolverService);
super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService);
}
public getTypeId(): string {
......
......@@ -244,7 +244,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo
private createInput(channel: IOutputChannel): ResourceEditorInput {
const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id });
return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource);
return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource, undefined);
}
private saveState(): void {
......
......@@ -52,6 +52,7 @@ export class PerfviewInput extends ResourceEditorInput {
localize('name', "Startup Performance"),
null,
PerfviewInput.Uri,
undefined,
textModelResolverService
);
}
......
......@@ -236,7 +236,7 @@ class GenerateColorThemeAction extends Action {
}, null, '\t');
contents = contents.replace(/\"__/g, '//"');
return this.editorService.openEditor({ contents, language: 'jsonc' });
return this.editorService.openEditor({ contents, mode: 'jsonc' });
}
}
......
......@@ -528,7 +528,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Untitled file support
const untitledInput = <IUntitledResourceInput>input;
if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
return this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.language, untitledInput.contents, untitledInput.encoding);
return this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding);
}
// Resource Editor Support
......@@ -539,13 +539,13 @@ export class EditorService extends Disposable implements EditorServiceImpl {
label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs)
}
return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.forceFile) as EditorInput;
return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput;
}
throw new Error('Unknown input type');
}
private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, forceFile: boolean | undefined): ICachedEditorInput {
private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, mode: string | undefined, forceFile: boolean | undefined): ICachedEditorInput {
if (EditorService.CACHE.has(resource)) {
const input = EditorService.CACHE.get(resource)!;
if (input instanceof ResourceEditorInput) {
......@@ -556,10 +556,18 @@ export class EditorService extends Disposable implements EditorServiceImpl {
if (description) {
input.setDescription(description);
}
if (mode) {
input.setPreferredMode(mode);
}
} else if (!(input instanceof DataUriEditorInput)) {
if (encoding) {
input.setPreferredEncoding(encoding);
}
if (mode) {
input.setPreferredMode(mode);
}
}
return input;
......@@ -569,7 +577,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// File
if (forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resource)) {
input = this.fileInputFactory.createFileInput(resource, encoding, instantiationService);
input = this.fileInputFactory.createFileInput(resource, encoding, mode, instantiationService);
}
// Data URI
......@@ -579,13 +587,12 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Resource
else {
input = instantiationService.createInstance(ResourceEditorInput, label, description, resource);
input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode);
}
// Add to cache and remove when input gets disposed
EditorService.CACHE.set(resource, input);
Event.once(input.onDispose)(() => {
EditorService.CACHE.delete(resource);
});
Event.once(input.onDispose)(() => EditorService.CACHE.delete(resource));
return input;
}
......
......@@ -24,10 +24,10 @@ export class TestEditorControl extends BaseEditor {
constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyFileEditorForEditorGroupService', NullTelemetryService, new TestThemeService(), new TestStorageService()); }
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
async setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
super.setInput(input, options, token);
return input.resolve().then(() => undefined);
await input.resolve();
}
getId(): string { return 'MyFileEditorForEditorGroupService'; }
......@@ -45,11 +45,13 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput {
setEncoding(encoding: string) { }
getEncoding(): string { return null!; }
setPreferredEncoding(encoding: string) { }
setMode(mode: string) { }
setPreferredMode(mode: string) { }
getResource(): URI { return this.resource; }
setForceOpenAsBinary(): void { }
}
suite('Editor groups service', () => {
suite('EditorGroupsService', () => {
function registerTestEditorInput(): void {
......@@ -291,7 +293,7 @@ suite('Editor groups service', () => {
part.dispose();
});
test('copy/merge groups', function () {
test('copy/merge groups', async () => {
const part = createPart();
let groupAddedCounter = 0;
......@@ -312,40 +314,32 @@ suite('Editor groups service', () => {
const input = new TestEditorInput(URI.file('foo/bar'));
return rootGroup.openEditor(input, EditorOptions.create({ pinned: true })).then(() => {
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT, { activate: true });
const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN);
assert.equal(groupAddedCounter, 2);
assert.equal(downGroup.count, 1);
assert.ok(downGroup.activeEditor instanceof TestEditorInput);
part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.COPY_EDITORS });
assert.equal(rightGroup.count, 1);
assert.ok(rightGroup.activeEditor instanceof TestEditorInput);
part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.MOVE_EDITORS });
assert.equal(rootGroup.count, 0);
part.mergeGroup(rootGroup, downGroup);
assert.equal(groupRemovedCounter, 1);
assert.equal(rootGroupDisposed, true);
groupAddedListener.dispose();
groupRemovedListener.dispose();
disposeListener.dispose();
part.dispose();
});
await rootGroup.openEditor(input, EditorOptions.create({ pinned: true }));
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT, { activate: true });
const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN);
assert.equal(groupAddedCounter, 2);
assert.equal(downGroup.count, 1);
assert.ok(downGroup.activeEditor instanceof TestEditorInput);
part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.COPY_EDITORS });
assert.equal(rightGroup.count, 1);
assert.ok(rightGroup.activeEditor instanceof TestEditorInput);
part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.MOVE_EDITORS });
assert.equal(rootGroup.count, 0);
part.mergeGroup(rootGroup, downGroup);
assert.equal(groupRemovedCounter, 1);
assert.equal(rootGroupDisposed, true);
groupAddedListener.dispose();
groupRemovedListener.dispose();
disposeListener.dispose();
part.dispose();
});
test('whenRestored', () => {
test('whenRestored', async () => {
const part = createPart();
return part.whenRestored.then(() => {
assert.ok(true);
part.dispose();
});
await part.whenRestored;
assert.ok(true);
part.dispose();
});
test('options', () => {
......@@ -467,7 +461,7 @@ suite('Editor groups service', () => {
part.dispose();
});
test('openEditors / closeEditors', function () {
test('openEditors / closeEditors', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -475,20 +469,17 @@ suite('Editor groups service', () => {
const input = new TestEditorInput(URI.file('foo/bar'));
const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive'));
return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => {
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
return group.closeEditors([input, inputInactive]).then(() => {
assert.equal(group.isEmpty(), true);
await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]);
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
part.dispose();
});
});
await group.closeEditors([input, inputInactive]);
assert.equal(group.isEmpty(), true);
part.dispose();
});
test('closeEditors (except one)', function () {
test('closeEditors (except one)', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -497,22 +488,19 @@ suite('Editor groups service', () => {
const input2 = new TestEditorInput(URI.file('foo/bar2'));
const input3 = new TestEditorInput(URI.file('foo/bar3'));
return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => {
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]);
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
return group.closeEditors({ except: input2 }).then(() => {
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), input2);
part.dispose();
});
});
await group.closeEditors({ except: input2 });
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), input2);
part.dispose();
});
test('closeEditors (saved only)', function () {
test('closeEditors (saved only)', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -521,21 +509,18 @@ suite('Editor groups service', () => {
const input2 = new TestEditorInput(URI.file('foo/bar2'));
const input3 = new TestEditorInput(URI.file('foo/bar3'));
return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => {
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
return group.closeEditors({ savedOnly: true }).then(() => {
assert.equal(group.count, 0);
await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]);
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
part.dispose();
});
});
await group.closeEditors({ savedOnly: true });
assert.equal(group.count, 0);
part.dispose();
});
test('closeEditors (direction: right)', function () {
test('closeEditors (direction: right)', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -544,23 +529,20 @@ suite('Editor groups service', () => {
const input2 = new TestEditorInput(URI.file('foo/bar2'));
const input3 = new TestEditorInput(URI.file('foo/bar3'));
return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => {
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
return group.closeEditors({ direction: CloseDirection.RIGHT, except: input2 }).then(() => {
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]);
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
part.dispose();
});
});
await group.closeEditors({ direction: CloseDirection.RIGHT, except: input2 });
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
part.dispose();
});
test('closeEditors (direction: left)', function () {
test('closeEditors (direction: left)', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -569,23 +551,20 @@ suite('Editor groups service', () => {
const input2 = new TestEditorInput(URI.file('foo/bar2'));
const input3 = new TestEditorInput(URI.file('foo/bar3'));
return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => {
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]);
assert.equal(group.count, 3);
assert.equal(group.getEditor(0), input1);
assert.equal(group.getEditor(1), input2);
assert.equal(group.getEditor(2), input3);
return group.closeEditors({ direction: CloseDirection.LEFT, except: input2 }).then(() => {
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input2);
assert.equal(group.getEditor(1), input3);
part.dispose();
});
});
await group.closeEditors({ direction: CloseDirection.LEFT, except: input2 });
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input2);
assert.equal(group.getEditor(1), input3);
part.dispose();
});
test('closeAllEditors', () => {
test('closeAllEditors', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -593,20 +572,17 @@ suite('Editor groups service', () => {
const input = new TestEditorInput(URI.file('foo/bar'));
const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive'));
return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => {
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
return group.closeAllEditors().then(() => {
assert.equal(group.isEmpty(), true);
await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]);
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
part.dispose();
});
});
await group.closeAllEditors();
assert.equal(group.isEmpty(), true);
part.dispose();
});
test('moveEditor (same group)', function () {
test('moveEditor (same group)', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -622,22 +598,19 @@ suite('Editor groups service', () => {
}
});
return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => {
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
group.moveEditor(inputInactive, group, { index: 0 });
assert.equal(editorMoveCounter, 1);
assert.equal(group.getEditor(0), inputInactive);
assert.equal(group.getEditor(1), input);
editorGroupChangeListener.dispose();
part.dispose();
});
await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]);
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
group.moveEditor(inputInactive, group, { index: 0 });
assert.equal(editorMoveCounter, 1);
assert.equal(group.getEditor(0), inputInactive);
assert.equal(group.getEditor(1), input);
editorGroupChangeListener.dispose();
part.dispose();
});
test('moveEditor (across groups)', function () {
test('moveEditor (across groups)', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -647,23 +620,19 @@ suite('Editor groups service', () => {
const input = new TestEditorInput(URI.file('foo/bar'));
const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive'));
return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => {
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
group.moveEditor(inputInactive, rightGroup, { index: 0 });
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), input);
assert.equal(rightGroup.count, 1);
assert.equal(rightGroup.getEditor(0), inputInactive);
part.dispose();
});
await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]);
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
group.moveEditor(inputInactive, rightGroup, { index: 0 });
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), input);
assert.equal(rightGroup.count, 1);
assert.equal(rightGroup.getEditor(0), inputInactive);
part.dispose();
});
test('copyEditor (across groups)', function () {
test('copyEditor (across groups)', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -673,24 +642,20 @@ suite('Editor groups service', () => {
const input = new TestEditorInput(URI.file('foo/bar'));
const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive'));
return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => {
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
group.copyEditor(inputInactive, rightGroup, { index: 0 });
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
assert.equal(rightGroup.count, 1);
assert.equal(rightGroup.getEditor(0), inputInactive);
part.dispose();
});
await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]);
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
group.copyEditor(inputInactive, rightGroup, { index: 0 });
assert.equal(group.count, 2);
assert.equal(group.getEditor(0), input);
assert.equal(group.getEditor(1), inputInactive);
assert.equal(rightGroup.count, 1);
assert.equal(rightGroup.getEditor(0), inputInactive);
part.dispose();
});
test('replaceEditors', () => {
test('replaceEditors', async () => {
const part = createPart();
const group = part.activeGroup;
assert.equal(group.isEmpty(), true);
......@@ -698,17 +663,14 @@ suite('Editor groups service', () => {
const input = new TestEditorInput(URI.file('foo/bar'));
const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive'));
return group.openEditor(input).then(() => {
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), input);
return group.replaceEditors([{ editor: input, replacement: inputInactive }]).then(() => {
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), inputInactive);
await group.openEditor(input);
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), input);
part.dispose();
});
});
await group.replaceEditors([{ editor: input, replacement: inputInactive }]);
assert.equal(group.count, 1);
assert.equal(group.getEditor(0), inputInactive);
part.dispose();
});
test('find neighbour group (left/right)', function () {
......
......@@ -29,15 +29,17 @@ import { timeout } from 'vs/base/common/async';
import { toResource } from 'vs/base/test/common/utils';
import { IFileService } from 'vs/platform/files/common/files';
import { Disposable } from 'vs/base/common/lifecycle';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
export class TestEditorControl extends BaseEditor {
constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyTestEditorForEditorService', NullTelemetryService, new TestThemeService(), new TestStorageService()); }
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
async setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
super.setInput(input, options, token);
return input.resolve().then(() => undefined);
await input.resolve();
}
getId(): string { return 'MyTestEditorForEditorService'; }
......@@ -56,6 +58,8 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput {
setEncoding(encoding: string) { }
getEncoding(): string { return null!; }
setPreferredEncoding(encoding: string) { }
setMode(mode: string) { }
setPreferredMode(mode: string) { }
getResource(): URI { return this.resource; }
setForceOpenAsBinary(): void { }
setFailToOpen(): void {
......@@ -75,7 +79,7 @@ class FileServiceProvider extends Disposable {
}
}
suite('Editor service', () => {
suite('EditorService', () => {
function registerTestEditorInput(): void {
Registry.as<IEditorRegistry>(Extensions.Editors).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), new SyncDescriptor(TestEditorInput));
......@@ -83,7 +87,7 @@ suite('Editor service', () => {
registerTestEditorInput();
test('basics', function () {
test('basics', async () => {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart);
......@@ -112,51 +116,49 @@ suite('Editor service', () => {
didCloseEditorListenerCounter++;
});
return part.whenRestored.then(() => {
// Open input
return service.openEditor(input, { pinned: true }).then(editor => {
assert.ok(editor instanceof TestEditorControl);
assert.equal(editor, service.activeControl);
assert.equal(input, service.activeEditor);
assert.equal(service.visibleControls.length, 1);
assert.equal(service.visibleControls[0], editor);
assert.ok(!service.activeTextEditorWidget);
assert.equal(service.visibleTextEditorWidgets.length, 0);
assert.equal(service.isOpen(input), true);
assert.equal(service.getOpened({ resource: input.getResource() }), input);
assert.equal(service.isOpen(input, part.activeGroup), true);
assert.equal(activeEditorChangeEventCounter, 1);
assert.equal(visibleEditorChangeEventCounter, 1);
// Close input
return editor!.group!.closeEditor(input).then(() => {
assert.equal(didCloseEditorListenerCounter, 1);
assert.equal(activeEditorChangeEventCounter, 2);
assert.equal(visibleEditorChangeEventCounter, 2);
assert.ok(input.gotDisposed);
// Open again 2 inputs
return service.openEditor(input, { pinned: true }).then(editor => {
return service.openEditor(otherInput, { pinned: true }).then(editor => {
assert.equal(service.visibleControls.length, 1);
assert.equal(service.isOpen(input), true);
assert.equal(service.isOpen(otherInput), true);
assert.equal(activeEditorChangeEventCounter, 4);
assert.equal(visibleEditorChangeEventCounter, 4);
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
didCloseEditorListener.dispose();
});
});
});
});
});
await part.whenRestored;
// Open input
let editor = await service.openEditor(input, { pinned: true });
assert.ok(editor instanceof TestEditorControl);
assert.equal(editor, service.activeControl);
assert.equal(input, service.activeEditor);
assert.equal(service.visibleControls.length, 1);
assert.equal(service.visibleControls[0], editor);
assert.ok(!service.activeTextEditorWidget);
assert.equal(service.visibleTextEditorWidgets.length, 0);
assert.equal(service.isOpen(input), true);
assert.equal(service.getOpened({ resource: input.getResource() }), input);
assert.equal(service.isOpen(input, part.activeGroup), true);
assert.equal(activeEditorChangeEventCounter, 1);
assert.equal(visibleEditorChangeEventCounter, 1);
// Close input
await editor!.group!.closeEditor(input);
assert.equal(didCloseEditorListenerCounter, 1);
assert.equal(activeEditorChangeEventCounter, 2);
assert.equal(visibleEditorChangeEventCounter, 2);
assert.ok(input.gotDisposed);
// Open again 2 inputs
await service.openEditor(input, { pinned: true });
editor = await service.openEditor(otherInput, { pinned: true });
assert.equal(service.visibleControls.length, 1);
assert.equal(service.isOpen(input), true);
assert.equal(service.isOpen(otherInput), true);
assert.equal(activeEditorChangeEventCounter, 4);
assert.equal(visibleEditorChangeEventCounter, 4);
activeEditorChangeListener.dispose();
visibleEditorChangeListener.dispose();
didCloseEditorListener.dispose();
});
test('openEditors() / replaceEditors()', function () {
test('openEditors() / replaceEditors()', async () => {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart);
......@@ -171,18 +173,16 @@ suite('Editor service', () => {
const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openEditors'));
const replaceInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-openEditors'));
return part.whenRestored.then(() => {
await part.whenRestored;
// Open editors
return service.openEditors([{ editor: input }, { editor: otherInput }]).then(() => {
assert.equal(part.activeGroup.count, 2);
// Open editors
await service.openEditors([{ editor: input }, { editor: otherInput }]);
assert.equal(part.activeGroup.count, 2);
return service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup).then(() => {
assert.equal(part.activeGroup.count, 2);
assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0);
});
});
});
// Replace editors
await service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup);
assert.equal(part.activeGroup.count, 2);
assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0);
});
test('caching', function () {
......@@ -234,10 +234,15 @@ suite('Editor service', () => {
assert.ok(!input1AgainAndAgain!.isDisposed());
});
test('createInput', function () {
test('createInput', async function () {
const instantiationService = workbenchInstantiationService();
const service: EditorService = <any>instantiationService.createInstance(EditorService);
const mode = 'create-input-test';
ModesRegistry.registerLanguage({
id: mode,
});
// Untyped Input (file)
let input = service.createInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof FileEditorInput);
......@@ -250,6 +255,18 @@ suite('Editor service', () => {
contentInput = <FileEditorInput>input;
assert.equal(contentInput.getPreferredEncoding(), 'utf16le');
// Untyped Input (file, mode)
input = service.createInput({ resource: toResource.call(this, '/index.html'), mode });
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
assert.equal(contentInput.getPreferredMode(), mode);
// Untyped Input (file, different mode)
input = service.createInput({ resource: toResource.call(this, '/index.html'), mode: 'text' });
assert(input instanceof FileEditorInput);
contentInput = <FileEditorInput>input;
assert.equal(contentInput.getPreferredMode(), 'text');
// Untyped Input (untitled)
input = service.createInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledEditorInput);
......@@ -257,6 +274,14 @@ suite('Editor service', () => {
// Untyped Input (untitled with contents)
input = service.createInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledEditorInput);
let model = await input.resolve() as UntitledEditorModel;
assert.equal(model.textEditorModel!.getValue(), 'Hello Untitled');
// Untyped Input (untitled with mode)
input = service.createInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } });
assert(input instanceof UntitledEditorInput);
model = await input.resolve() as UntitledEditorModel;
assert.equal(model.getMode(), mode);
// Untyped Input (untitled with file path)
input = service.createInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } });
......@@ -276,6 +301,10 @@ suite('Editor service', () => {
assert.ok((input as UntitledEditorInput).hasAssociatedFilePath);
provider.dispose();
// Untyped Input (resource)
input = service.createInput({ resource: URI.parse('custom:resource') });
assert(input instanceof ResourceEditorInput);
});
test('delegate', function (done) {
......@@ -298,7 +327,7 @@ suite('Editor service', () => {
const ed = instantiationService.createInstance(MyEditor, 'my.editor');
const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'));
const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'), undefined);
const delegate = instantiationService.createInstance(DelegatingEditorService);
delegate.setEditorOpenHandler((group: IEditorGroup, input: IEditorInput, options?: EditorOptions) => {
assert.strictEqual(input, inp);
......@@ -311,7 +340,7 @@ suite('Editor service', () => {
delegate.openEditor(inp);
});
test('close editor does not dispose when editor opened in other group', function () {
test('close editor does not dispose when editor opened in other group', async () => {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart);
......@@ -327,30 +356,26 @@ suite('Editor service', () => {
const rootGroup = part.activeGroup;
const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT);
return part.whenRestored.then(() => {
// Open input
return service.openEditor(input, { pinned: true }).then(editor => {
return service.openEditor(input, { pinned: true }, rightGroup).then(editor => {
const editors = service.editors;
assert.equal(editors.length, 2);
assert.equal(editors[0], input);
assert.equal(editors[1], input);
// Close input
return rootGroup.closeEditor(input).then(() => {
assert.equal(input.isDisposed(), false);
return rightGroup.closeEditor(input).then(() => {
assert.equal(input.isDisposed(), true);
});
});
});
});
});
await part.whenRestored;
// Open input
await service.openEditor(input, { pinned: true });
await service.openEditor(input, { pinned: true }, rightGroup);
const editors = service.editors;
assert.equal(editors.length, 2);
assert.equal(editors[0], input);
assert.equal(editors[1], input);
// Close input
await rootGroup.closeEditor(input);
assert.equal(input.isDisposed(), false);
await rightGroup.closeEditor(input);
assert.equal(input.isDisposed(), true);
});
test('open to the side', function () {
test('open to the side', async () => {
const partInstantiator = workbenchInstantiationService();
const part = partInstantiator.createInstance(EditorPart);
......@@ -366,22 +391,20 @@ suite('Editor service', () => {
const rootGroup = part.activeGroup;
return part.whenRestored.then(() => {
return service.openEditor(input1, { pinned: true }, rootGroup).then(editor => {
return service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => {
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor!.group, part.groups[1]);
// Open to the side uses existing neighbour group if any
return service.openEditor(input2, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => {
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor!.group, part.groups[1]);
});
});
});
});
await part.whenRestored;
await service.openEditor(input1, { pinned: true }, rootGroup);
let editor = await service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP);
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor!.group, part.groups[1]);
// Open to the side uses existing neighbour group if any
editor = await service.openEditor(input2, { pinned: true, preserveFocus: true }, SIDE_GROUP);
assert.equal(part.activeGroup, rootGroup);
assert.equal(part.count, 2);
assert.equal(editor!.group, part.groups[1]);
});
test('active editor change / visible editor change events', async function () {
......
......@@ -31,7 +31,7 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput {
constructor(defaultSettingsResource: URI,
@ITextModelService textModelResolverService: ITextModelService
) {
super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService);
super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService);
}
getTypeId(): string {
......
......@@ -18,7 +18,7 @@ import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RunOnceScheduler, timeout } from 'vs/base/common/async';
......@@ -63,8 +63,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
private resource: URI;
private contentEncoding: string; // encoding as reported from disk
private preferredEncoding: string; // encoding as chosen by the user
private contentEncoding: string; // encoding as reported from disk
private preferredEncoding: string; // encoding as chosen by the user
private preferredMode: string; // mode as chosen by the user
private versionId: number;
private bufferSavedVersionId: number;
......@@ -92,6 +94,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
constructor(
resource: URI,
preferredEncoding: string,
preferredMode: string,
@INotificationService private readonly notificationService: INotificationService,
@IModeService modeService: IModeService,
@IModelService modelService: IModelService,
......@@ -108,6 +111,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.resource = resource;
this.preferredEncoding = preferredEncoding;
this.preferredMode = preferredMode;
this.inOrphanMode = false;
this.dirty = false;
this.versionId = 0;
......@@ -208,11 +212,17 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
const firstLineText = this.getFirstLineText(this.textEditorModel);
const languageSelection = this.getOrCreateMode(this.modeService, undefined, firstLineText);
const languageSelection = this.getOrCreateMode(this.resource, this.modeService, this.preferredMode, firstLineText);
this.modelService.setMode(this.textEditorModel, languageSelection);
}
setMode(mode: string): void {
super.setMode(mode);
this.preferredMode = mode;
}
async backup(target = this.resource): Promise<void> {
if (this.isResolved()) {
......@@ -451,7 +461,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.logService.trace('load() - created text editor model', this.resource);
// Create model
this.createTextEditorModel(value, resource);
this.createTextEditorModel(value, resource, this.preferredMode);
// We restored a backup so we have to set the model as being dirty
// We also want to trigger auto save if it is enabled to simulate the exact same behaviour
......@@ -481,7 +491,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Update model value in a block that ignores model content change events
this.blockModelContentChange = true;
try {
this.updateTextEditorModel(value);
this.updateTextEditorModel(value, this.preferredMode);
} finally {
this.blockModelContentChange = false;
}
......@@ -502,10 +512,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
}
protected getOrCreateMode(modeService: IModeService, preferredModeIds: string | undefined, firstLineText?: string): ILanguageSelection {
return modeService.createByFilepathOrFirstLine(this.resource.fsPath, firstLineText);
}
private onModelContentChanged(): void {
this.logService.trace(`onModelContentChanged() - enter`, this.resource);
......
......@@ -153,7 +153,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Model does not exist
else {
const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined);
const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined);
modelPromise = model.load(options);
// Install state change listener
......@@ -204,6 +204,11 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Remove from pending loads
this.mapResourceToPendingModelLoaders.delete(resource);
// Apply mode if provided
if (options && options.mode) {
resolvedModel.setMode(options.mode);
}
return resolvedModel;
} catch (error) {
......
......@@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor';
import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
......@@ -367,6 +367,11 @@ export interface IModelLoadOrCreateOptions {
*/
reason?: LoadReason;
/**
* The language mode to use for the model text content.
*/
mode?: string;
/**
* The encoding to use when resolving the model text content.
*/
......@@ -443,7 +448,7 @@ export interface ILoadOptions {
reason?: LoadReason;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport {
readonly onDidContentChange: Event<StateChange>;
readonly onDidStateChange: Event<StateChange>;
......
......@@ -14,6 +14,7 @@ import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/commo
import { FileOperationResult, FileOperationError, IFileService } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/modelService';
import { timeout } from 'vs/base/common/async';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
class ServiceAccessor {
constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) {
......@@ -45,7 +46,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('save', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
......@@ -70,7 +71,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('save - touching also emits saved event', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
......@@ -90,7 +91,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('setEncoding - encode', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.setEncoding('utf8', EncodingMode.Encode); // no-op
assert.equal(getLastModifiedTime(model), -1);
......@@ -103,7 +104,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('setEncoding - decode', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.setEncoding('utf16', EncodingMode.Decode);
......@@ -112,8 +113,24 @@ suite('Files - TextFileEditorModel', () => {
model.dispose();
});
test('create with mode', async function () {
const mode = 'text-file-model-test';
ModesRegistry.registerLanguage({
id: mode,
});
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', mode);
await model.load();
assert.equal(model.textEditorModel!.getModeId(), mode);
model.dispose();
assert.ok(!accessor.modelService.getModel(model.getResource()));
});
test('disposes when underlying model is destroyed', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
......@@ -122,7 +139,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('Load does not trigger save', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8');
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined);
assert.ok(model.hasState(ModelState.SAVED));
model.onDidStateChange(e => {
......@@ -136,7 +153,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('Load returns dirty model as long as model is dirty', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
model.textEditorModel!.setValue('foo');
......@@ -151,7 +168,7 @@ suite('Files - TextFileEditorModel', () => {
test('Revert', async function () {
let eventCounter = 0;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.onDidStateChange(e => {
if (e === StateChange.REVERTED) {
......@@ -173,7 +190,7 @@ suite('Files - TextFileEditorModel', () => {
test('Revert (soft)', async function () {
let eventCounter = 0;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.onDidStateChange(e => {
if (e === StateChange.REVERTED) {
......@@ -193,7 +210,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('Load and undo turns model dirty', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
accessor.fileService.setContent('Hello Change');
......@@ -203,7 +220,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('File not modified error is handled gracefully', async function () {
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
......@@ -218,7 +235,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('Load error is handled gracefully if model already exists', async function () {
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND));
......@@ -264,7 +281,7 @@ suite('Files - TextFileEditorModel', () => {
test('Save Participant', async function () {
let eventCounter = 0;
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
model.onDidStateChange(e => {
if (e === StateChange.SAVED) {
......@@ -294,7 +311,7 @@ suite('Files - TextFileEditorModel', () => {
test('Save Participant, async participant', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
TextFileEditorModel.setSaveParticipant({
participate: (model) => {
......@@ -312,7 +329,7 @@ suite('Files - TextFileEditorModel', () => {
});
test('Save Participant, bad participant', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
TextFileEditorModel.setSaveParticipant({
participate: (model) => {
......
......@@ -13,6 +13,7 @@ import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/file
import { IModelService } from 'vs/editor/common/services/modelService';
import { timeout } from 'vs/base/common/async';
import { toResource } from 'vs/base/test/common/utils';
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
export class TestTextFileEditorModelManager extends TextFileEditorModelManager {
......@@ -42,9 +43,9 @@ suite('Files - TextFileEditorModelManager', () => {
test('add, remove, clear, get, getAll', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8');
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8');
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8');
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined);
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined);
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined);
manager.add(URI.file('/test.html'), model1);
manager.add(URI.file('/some/other.html'), model2);
......@@ -117,9 +118,9 @@ suite('Files - TextFileEditorModelManager', () => {
test('removed from cache when model disposed', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8');
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8');
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8');
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined);
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined);
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined);
manager.add(URI.file('/test.html'), model1);
manager.add(URI.file('/some/other.html'), model2);
......@@ -290,4 +291,24 @@ suite('Files - TextFileEditorModelManager', () => {
assert.ok(model.isDisposed());
manager.dispose();
});
test('mode', async function () {
const mode = 'text-file-model-manager-test';
ModesRegistry.registerLanguage({
id: mode,
});
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource = toResource.call(this, '/path/index_something.txt');
let model = await manager.loadOrCreate(resource, { mode });
assert.equal(model.textEditorModel!.getModeId(), mode);
model = await manager.loadOrCreate(resource, { mode: 'text' });
assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID);
manager.disposeModel((model as TextFileEditorModel));
manager.dispose();
});
});
\ No newline at end of file
......@@ -66,7 +66,7 @@ suite('Files - TextFileService', () => {
});
test('confirm onWillShutdown - no veto', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const event = new BeforeShutdownEventImpl();
......@@ -81,7 +81,7 @@ suite('Files - TextFileService', () => {
});
test('confirm onWillShutdown - veto if user cancels', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -97,7 +97,7 @@ suite('Files - TextFileService', () => {
});
test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -123,7 +123,7 @@ suite('Files - TextFileService', () => {
});
test('confirm onWillShutdown - save (hot.exit: off)', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -142,7 +142,7 @@ suite('Files - TextFileService', () => {
});
test('isDirty/getDirty - files and untitled', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -169,7 +169,7 @@ suite('Files - TextFileService', () => {
});
test('save - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -185,11 +185,11 @@ suite('Files - TextFileService', () => {
test('save - UNC path', async function () {
const untitledUncUri = URI.from({ scheme: 'untitled', authority: 'server', path: '/share/path/file.txt' });
model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const mockedFileUri = untitledUncUri.with({ scheme: Schemas.file });
const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8');
const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8', undefined);
const loadOrCreateStub = sinon.stub(accessor.textFileService.models, 'loadOrCreate', () => Promise.resolve(mockedEditorInput));
sinon.stub(accessor.untitledEditorService, 'exists', () => true);
......@@ -209,7 +209,7 @@ suite('Files - TextFileService', () => {
});
test('saveAll - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -226,7 +226,7 @@ suite('Files - TextFileService', () => {
});
test('saveAs - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -242,7 +242,7 @@ suite('Files - TextFileService', () => {
});
test('revert - file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -258,7 +258,7 @@ suite('Files - TextFileService', () => {
});
test('delete - dirty file', async function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......@@ -272,8 +272,8 @@ suite('Files - TextFileService', () => {
});
test('move - dirty file', async function () {
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8');
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(sourceModel.getResource(), sourceModel);
(<TextFileEditorModelManager>accessor.textFileService.models).add(targetModel.getResource(), targetModel);
......@@ -393,7 +393,7 @@ suite('Files - TextFileService', () => {
});
async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: true, shouldVeto: boolean): Promise<void> {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
......
......@@ -53,7 +53,7 @@ suite('Workbench - TextModelResolverService', () => {
accessor.untitledEditorService.revertAll();
});
test('resolve resource', function () {
test('resolve resource', async () => {
const dispose = accessor.textModelResolverService.registerTextModelContentProvider('test', {
provideTextContent: function (resource: URI): Promise<ITextModel> {
if (resource.scheme === 'test') {
......@@ -67,67 +67,60 @@ suite('Workbench - TextModelResolverService', () => {
});
let resource = URI.from({ scheme: 'test', authority: null!, path: 'thePath' });
let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource);
return input.resolve().then(async model => {
assert.ok(model);
assert.equal(snapshotToString((model as ResourceEditorModel).createSnapshot()!), 'Hello Test');
let disposed = false;
let disposedPromise = new Promise(resolve => {
Event.once(model.onDispose)(() => {
disposed = true;
resolve();
});
let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource, undefined);
const model = await input.resolve();
assert.ok(model);
assert.equal(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'Hello Test');
let disposed = false;
let disposedPromise = new Promise(resolve => {
Event.once(model.onDispose)(() => {
disposed = true;
resolve();
});
input.dispose();
await disposedPromise;
assert.equal(disposed, true);
dispose.dispose();
});
input.dispose();
await disposedPromise;
assert.equal(disposed, true);
dispose.dispose();
});
test('resolve file', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
test('resolve file', async function () {
const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined);
(<TextFileEditorModelManager>accessor.textFileService.models).add(textModel.getResource(), textModel);
return model.load().then(() => {
return accessor.textModelResolverService.createModelReference(model.getResource()).then(ref => {
const model = ref.object;
const editorModel = model.textEditorModel;
await textModel.load();
assert.ok(editorModel);
assert.equal(editorModel.getValue(), 'Hello Html');
const ref = await accessor.textModelResolverService.createModelReference(textModel.getResource());
let disposed = false;
Event.once(model.onDispose)(() => {
disposed = true;
});
const model = ref.object;
const editorModel = model.textEditorModel;
ref.dispose();
return timeout(0).then(() => { // due to the reference resolving the model first which is async
assert.equal(disposed, true);
});
});
assert.ok(editorModel);
assert.equal(editorModel.getValue(), 'Hello Html');
let disposed = false;
Event.once(model.onDispose)(() => {
disposed = true;
});
ref.dispose();
await timeout(0); // due to the reference resolving the model first which is async
assert.equal(disposed, true);
});
test('resolve untitled', function () {
test('resolve untitled', async () => {
const service = accessor.untitledEditorService;
const input = service.createOrGet();
return input.resolve().then(() => {
return accessor.textModelResolverService.createModelReference(input.getResource()).then(ref => {
const model = ref.object;
const editorModel = model.textEditorModel;
assert.ok(editorModel);
ref.dispose();
input.dispose();
});
});
await input.resolve();
const ref = await accessor.textModelResolverService.createModelReference(input.getResource());
const model = ref.object;
const editorModel = model.textEditorModel;
assert.ok(editorModel);
ref.dispose();
input.dispose();
});
test('even loading documents should be refcounted', async () => {
......@@ -135,12 +128,12 @@ suite('Workbench - TextModelResolverService', () => {
let waitForIt = new Promise(c => resolveModel = c);
const disposable = accessor.textModelResolverService.registerTextModelContentProvider('test', {
provideTextContent: (resource: URI): Promise<ITextModel> => {
return waitForIt.then(_ => {
let modelContent = 'Hello Test';
let languageSelection = accessor.modeService.create('json');
return accessor.modelService.createModel(modelContent, languageSelection, resource);
});
provideTextContent: async (resource: URI): Promise<ITextModel> => {
await waitForIt;
let modelContent = 'Hello Test';
let languageSelection = accessor.modeService.create('json');
return accessor.modelService.createModel(modelContent, languageSelection, resource);
}
});
......
......@@ -21,7 +21,7 @@ export const IUntitledEditorService = createDecorator<IUntitledEditorService>('u
export interface IModelLoadOrCreateOptions {
resource?: URI;
modeId?: string;
mode?: string;
initialValue?: string;
encoding?: string;
useResourcePath?: boolean;
......@@ -29,7 +29,7 @@ export interface IModelLoadOrCreateOptions {
export interface IUntitledEditorService {
_serviceBrand: any;
_serviceBrand: ServiceIdentifier<IUntitledEditorService>;
/**
* Events for when untitled editors content changes (e.g. any keystroke).
......@@ -78,7 +78,7 @@ export interface IUntitledEditorService {
* It is valid to pass in a file resource. In that case the path will be used as identifier.
* The use case is to be able to create a new file with a specific path with VSCode.
*/
createOrGet(resource?: URI, modeId?: string, initialValue?: string, encoding?: string): UntitledEditorInput;
createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput;
/**
* Creates a new untitled model with the optional resource URI or returns an existing one
......@@ -184,10 +184,10 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor
}
loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise<UntitledEditorModel> {
return this.createOrGet(options.resource, options.modeId, options.initialValue, options.encoding, options.useResourcePath).resolve();
return this.createOrGet(options.resource, options.mode, options.initialValue, options.encoding, options.useResourcePath).resolve();
}
createOrGet(resource?: URI, modeId?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledEditorInput {
createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledEditorInput {
if (resource) {
// Massage resource if it comes with known file based resource
......@@ -207,10 +207,10 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor
}
// Create new otherwise
return this.doCreate(resource, hasAssociatedFilePath, modeId, initialValue, encoding);
return this.doCreate(resource, hasAssociatedFilePath, mode, initialValue, encoding);
}
private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, modeId?: string, initialValue?: string, encoding?: string): UntitledEditorInput {
private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput {
if (!resource) {
// Create new taking a resource URI that is not already taken
......@@ -222,14 +222,14 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor
}
// Look up default language from settings if any
if (!modeId && !hasAssociatedFilePath) {
if (!mode && !hasAssociatedFilePath) {
const configuration = this.configurationService.getValue<IFilesConfiguration>();
if (configuration.files && configuration.files.defaultLanguage) {
modeId = configuration.files.defaultLanguage;
mode = configuration.files.defaultLanguage;
}
}
const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId, initialValue, encoding);
const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, mode, initialValue, encoding);
const contentListener = input.onDidModelChangeContent(() => {
this._onDidChangeContent.fire(resource!);
......
......@@ -86,7 +86,7 @@ class MyResourceInput extends ResourceEditorInput { }
suite('Workbench base editor', () => {
test('BaseEditor API', function () {
test('BaseEditor API', async () => {
let e = new MyEditor(NullTelemetryService);
let input = new MyOtherInput();
let options = new EditorOptions();
......@@ -94,25 +94,24 @@ suite('Workbench base editor', () => {
assert(!e.isVisible());
assert(!e.input);
assert(!e.options);
return e.setInput(input, options, CancellationToken.None).then(() => {
assert.strictEqual(input, e.input);
assert.strictEqual(options, e.options);
const group = new TestEditorGroup(1);
e.setVisible(true, group);
assert(e.isVisible());
assert.equal(e.group, group);
input.onDispose(() => {
assert(false);
});
e.dispose();
e.clearInput();
e.setVisible(false, group);
assert(!e.isVisible());
assert(!e.input);
assert(!e.options);
assert(!e.getControl());
await e.setInput(input, options, CancellationToken.None);
assert.strictEqual(input, e.input);
assert.strictEqual(options, e.options);
const group = new TestEditorGroup(1);
e.setVisible(true, group);
assert(e.isVisible());
assert.equal(e.group, group);
input.onDispose(() => {
assert(false);
});
e.dispose();
e.clearInput();
e.setVisible(false, group);
assert(!e.isVisible());
assert(!e.input);
assert(!e.options);
assert(!e.getControl());
});
test('EditorDescriptor', () => {
......@@ -154,10 +153,10 @@ suite('Workbench base editor', () => {
let inst = new TestInstantiationService();
const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake')))!.instantiate(inst);
const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst);
assert.strictEqual(editor.getId(), 'myEditor');
const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake')))!.instantiate(inst);
const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst);
assert.strictEqual(otherEditor.getId(), 'myOtherEditor');
(<any>EditorRegistry).setEditors(oldEditors);
......@@ -173,7 +172,7 @@ suite('Workbench base editor', () => {
let inst = new TestInstantiationService();
const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake')))!.instantiate(inst);
const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst);
assert.strictEqual('myOtherEditor', editor.getId());
(<any>EditorRegistry).setEditors(oldEditors);
......
......@@ -35,7 +35,7 @@ suite('Workbench editor model', () => {
accessor = instantiationService.createInstance(ServiceAccessor);
});
test('TextDiffEditorModel', () => {
test('TextDiffEditorModel', async () => {
const dispose = accessor.textModelResolverService.registerTextModelContentProvider('test', {
provideTextContent: function (resource: URI): Promise<ITextModel> {
if (resource.scheme === 'test') {
......@@ -48,27 +48,26 @@ suite('Workbench editor model', () => {
}
});
let input = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' }));
let otherInput = instantiationService.createInstance(ResourceEditorInput, 'name2', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' }));
let input = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' }), undefined);
let otherInput = instantiationService.createInstance(ResourceEditorInput, 'name2', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' }), undefined);
let diffInput = new DiffEditorInput('name', 'description', input, otherInput);
return diffInput.resolve().then((model: any) => {
assert(model);
assert(model instanceof TextDiffEditorModel);
let model = await diffInput.resolve() as TextDiffEditorModel;
let diffEditorModel = model.textDiffEditorModel;
assert(diffEditorModel.original);
assert(diffEditorModel.modified);
assert(model);
assert(model instanceof TextDiffEditorModel);
return diffInput.resolve().then((model: any) => {
assert(model.isResolved());
let diffEditorModel = model.textDiffEditorModel!;
assert(diffEditorModel.original);
assert(diffEditorModel.modified);
assert(diffEditorModel !== model.textDiffEditorModel);
diffInput.dispose();
assert(!model.textDiffEditorModel);
model = await diffInput.resolve() as TextDiffEditorModel;
assert(model.isResolved());
dispose.dispose();
});
});
assert(diffEditorModel !== model.textDiffEditorModel);
diffInput.dispose();
assert(!model.textDiffEditorModel);
dispose.dispose();
});
});
......@@ -111,27 +111,17 @@ class TestFileEditorInput extends EditorInput implements IFileEditorInput {
}
getTypeId() { return 'testFileEditorInputForGroups'; }
resolve(): Promise<IEditorModel> { return Promise.resolve(null!); }
setEncoding(encoding: string) { }
getEncoding(): string { return null!; }
setPreferredEncoding(encoding: string) { }
getResource(): URI { return this.resource; }
setForceOpenAsBinary(): void { }
setMode(mode: string) { }
setPreferredMode(mode: string) { }
matches(other: TestFileEditorInput): boolean {
return other && this.id === other.id && other instanceof TestFileEditorInput;
}
setEncoding(encoding: string) {
}
getEncoding(): string {
return null!;
}
setPreferredEncoding(encoding: string) {
}
getResource(): URI {
return this.resource;
}
setForceOpenAsBinary(): void {
}
}
function input(id = String(index++), nonSerializable?: boolean, resource?: URI): EditorInput {
......
......@@ -21,8 +21,8 @@ import { TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTe
class MyEditorModel extends EditorModel { }
class MyTextEditorModel extends BaseTextEditorModel {
public createTextEditorModel(value: ITextBufferFactory, resource?: URI, modeId?: string) {
return super.createTextEditorModel(value, resource, modeId);
public createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) {
return super.createTextEditorModel(value, resource, preferredMode);
}
isReadonly(): boolean {
......@@ -40,7 +40,7 @@ suite('Workbench editor model', () => {
modeService = instantiationService.stub(IModeService, ModeServiceImpl);
});
test('EditorModel', () => {
test('EditorModel', async () => {
let counter = 0;
let m = new MyEditorModel();
......@@ -50,25 +50,23 @@ suite('Workbench editor model', () => {
counter++;
});
return m.load().then(model => {
assert(model === m);
assert.strictEqual(m.isResolved(), true);
m.dispose();
assert.equal(counter, 1);
});
const model = await m.load();
assert(model === m);
assert.strictEqual(m.isResolved(), true);
m.dispose();
assert.equal(counter, 1);
});
test('BaseTextEditorModel', () => {
test('BaseTextEditorModel', async () => {
let modelService = stubModelService(instantiationService);
let m = new MyTextEditorModel(modelService, modeService);
return m.load().then((model: MyTextEditorModel) => {
assert(model === m);
model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain');
assert.strictEqual(m.isResolved(), true);
}).then(() => {
m.dispose();
});
const model = await m.load() as MyTextEditorModel;
assert(model === m);
model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain');
assert.strictEqual(m.isResolved(), true);
m.dispose();
});
function stubModelService(instantiationService: TestInstantiationService): IModelService {
......
......@@ -12,17 +12,16 @@ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestSe
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
class ServiceAccessor {
constructor(
@IModelService public modelService: IModelService,
@IModeService public modeService: IModeService
) {
}
) { }
}
suite('Workbench resource editor input', () => {
let instantiationService: IInstantiationService;
let accessor: ServiceAccessor;
......@@ -31,14 +30,33 @@ suite('Workbench resource editor input', () => {
accessor = instantiationService.createInstance(ServiceAccessor);
});
test('simple', () => {
let resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' });
test('basics', async () => {
const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' });
accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource);
let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource);
return input.resolve().then(model => {
assert.ok(model);
assert.equal(snapshotToString((model as ResourceEditorModel).createSnapshot()!), 'function test() {}');
const input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource, undefined);
const model = await input.resolve();
assert.ok(model);
assert.equal(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'function test() {}');
});
test('custom mode', async () => {
ModesRegistry.registerLanguage({
id: 'resource-input-test',
});
const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' });
accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource);
const input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource, 'resource-input-test');
const model = await input.resolve();
assert.ok(model);
assert.equal(model.textEditorModel.getModeId(), 'resource-input-test');
input.setMode('text');
assert.equal(model.textEditorModel.getModeId(), PLAINTEXT_MODE_ID);
});
});
\ No newline at end of file
......@@ -16,6 +16,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { timeout } from 'vs/base/common/async';
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
export class TestUntitledEditorService extends UntitledEditorService {
get(resource: URI) { return super.get(resource); }
......@@ -45,7 +46,7 @@ suite('Workbench untitled editors', () => {
accessor.untitledEditorService.dispose();
});
test('Untitled Editor Service', function (done) {
test('Untitled Editor Service', async (done) => {
const service = accessor.untitledEditorService;
assert.equal(service.getAll().length, 0);
......@@ -68,36 +69,35 @@ suite('Workbench untitled editors', () => {
assert.equal(service.getAll().length, 1);
// dirty
input2.resolve().then(model => {
assert.ok(!service.isDirty(input2.getResource()));
const model = await input2.resolve();
const listener = service.onDidChangeDirty(resource => {
listener.dispose();
assert.ok(!service.isDirty(input2.getResource()));
assert.equal(resource.toString(), input2.getResource().toString());
const listener = service.onDidChangeDirty(resource => {
listener.dispose();
assert.ok(service.isDirty(input2.getResource()));
assert.equal(service.getDirty()[0].toString(), input2.getResource().toString());
assert.equal(service.getDirty([input2.getResource()])[0].toString(), input2.getResource().toString());
assert.equal(service.getDirty([input1.getResource()]).length, 0);
assert.equal(resource.toString(), input2.getResource().toString());
service.revertAll();
assert.equal(service.getAll().length, 0);
assert.ok(!input2.isDirty());
assert.ok(!model.isDirty());
assert.ok(service.isDirty(input2.getResource()));
assert.equal(service.getDirty()[0].toString(), input2.getResource().toString());
assert.equal(service.getDirty([input2.getResource()])[0].toString(), input2.getResource().toString());
assert.equal(service.getDirty([input1.getResource()]).length, 0);
input2.dispose();
service.revertAll();
assert.equal(service.getAll().length, 0);
assert.ok(!input2.isDirty());
assert.ok(!model.isDirty());
assert.ok(!service.exists(input2.getResource()));
input2.dispose();
done();
});
assert.ok(!service.exists(input2.getResource()));
done();
});
model.textEditorModel.setValue('foo bar');
}, err => done(err));
model.textEditorModel.setValue('foo bar');
});
test('Untitled with associated resource', function () {
test('Untitled with associated resource', () => {
const service = accessor.untitledEditorService;
const file = URI.file(join('C:\\', '/foo/file.txt'));
const untitled = service.createOrGet(file);
......@@ -107,53 +107,49 @@ suite('Workbench untitled editors', () => {
untitled.dispose();
});
test('Untitled no longer dirty when content gets empty', function () {
test('Untitled no longer dirty when content gets empty', async () => {
const service = accessor.untitledEditorService;
const input = service.createOrGet();
// dirty
return input.resolve().then(model => {
model.textEditorModel.setValue('foo bar');
assert.ok(model.isDirty());
model.textEditorModel.setValue('');
assert.ok(!model.isDirty());
input.dispose();
});
const model = await input.resolve();
model.textEditorModel.setValue('foo bar');
assert.ok(model.isDirty());
model.textEditorModel.setValue('');
assert.ok(!model.isDirty());
input.dispose();
});
test('Untitled via loadOrCreate', function () {
test('Untitled via loadOrCreate', async () => {
const service = accessor.untitledEditorService;
service.loadOrCreate().then(model1 => {
model1.textEditorModel!.setValue('foo bar');
assert.ok(model1.isDirty());
model1.textEditorModel!.setValue('');
assert.ok(!model1.isDirty());
return service.loadOrCreate({ initialValue: 'Hello World' }).then(model2 => {
assert.equal(snapshotToString(model2.createSnapshot()!), 'Hello World');
const input = service.createOrGet();
return service.loadOrCreate({ resource: input.getResource() }).then(model3 => {
assert.equal(model3.getResource().toString(), input.getResource().toString());
const file = URI.file(join('C:\\', '/foo/file44.txt'));
return service.loadOrCreate({ resource: file }).then(model4 => {
assert.ok(service.hasAssociatedFilePath(model4.getResource()));
assert.ok(model4.isDirty());
model1.dispose();
model2.dispose();
model3.dispose();
model4.dispose();
input.dispose();
});
});
});
});
const model1 = await service.loadOrCreate();
model1.textEditorModel!.setValue('foo bar');
assert.ok(model1.isDirty());
model1.textEditorModel!.setValue('');
assert.ok(!model1.isDirty());
const model2 = await service.loadOrCreate({ initialValue: 'Hello World' });
assert.equal(snapshotToString(model2.createSnapshot()!), 'Hello World');
const input = service.createOrGet();
const model3 = await service.loadOrCreate({ resource: input.getResource() });
assert.equal(model3.getResource().toString(), input.getResource().toString());
const file = URI.file(join('C:\\', '/foo/file44.txt'));
const model4 = await service.loadOrCreate({ resource: file });
assert.ok(service.hasAssociatedFilePath(model4.getResource()));
assert.ok(model4.isDirty());
model1.dispose();
model2.dispose();
model3.dispose();
model4.dispose();
input.dispose();
});
test('Untitled suggest name', function () {
......@@ -163,24 +159,21 @@ suite('Workbench untitled editors', () => {
assert.ok(service.suggestFileName(input.getResource()));
});
test('Untitled with associated path remains dirty when content gets empty', function () {
test('Untitled with associated path remains dirty when content gets empty', async () => {
const service = accessor.untitledEditorService;
const file = URI.file(join('C:\\', '/foo/file.txt'));
const input = service.createOrGet(file);
// dirty
return input.resolve().then(model => {
model.textEditorModel.setValue('foo bar');
assert.ok(model.isDirty());
model.textEditorModel.setValue('');
assert.ok(model.isDirty());
input.dispose();
});
const model = await input.resolve();
model.textEditorModel.setValue('foo bar');
assert.ok(model.isDirty());
model.textEditorModel.setValue('');
assert.ok(model.isDirty());
input.dispose();
});
test('Untitled created with files.defaultLanguage setting', function () {
test('Untitled created with files.defaultLanguage setting', () => {
const defaultLanguage = 'javascript';
const config = accessor.testConfigurationService;
config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage });
......@@ -188,30 +181,52 @@ suite('Workbench untitled editors', () => {
const service = accessor.untitledEditorService;
const input = service.createOrGet();
assert.equal(input.getModeId(), defaultLanguage);
assert.equal(input.getMode(), defaultLanguage);
config.setUserConfiguration('files', { 'defaultLanguage': undefined });
input.dispose();
});
test('Untitled created with modeId overrides files.defaultLanguage setting', function () {
const modeId = 'typescript';
test('Untitled created with mode overrides files.defaultLanguage setting', () => {
const mode = 'typescript';
const defaultLanguage = 'javascript';
const config = accessor.testConfigurationService;
config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage });
const service = accessor.untitledEditorService;
const input = service.createOrGet(null!, modeId);
const input = service.createOrGet(null!, mode);
assert.equal(input.getModeId(), modeId);
assert.equal(input.getMode(), mode);
config.setUserConfiguration('files', { 'defaultLanguage': undefined });
input.dispose();
});
test('encoding change event', function () {
test('Untitled can change mode afterwards', async () => {
const mode = 'untitled-input-test';
ModesRegistry.registerLanguage({
id: mode,
});
const service = accessor.untitledEditorService;
const input = service.createOrGet(null!, mode);
assert.equal(input.getMode(), mode);
const model = await input.resolve();
assert.equal(model.getMode(), mode);
input.setMode('text');
assert.equal(input.getMode(), PLAINTEXT_MODE_ID);
input.dispose();
});
test('encoding change event', async () => {
const service = accessor.untitledEditorService;
const input = service.createOrGet();
......@@ -223,16 +238,13 @@ suite('Workbench untitled editors', () => {
});
// dirty
return input.resolve().then(model => {
model.setEncoding('utf16');
assert.equal(counter, 1);
input.dispose();
});
const model = await input.resolve();
model.setEncoding('utf16');
assert.equal(counter, 1);
input.dispose();
});
test('onDidChangeContent event', () => {
test('onDidChangeContent event', async () => {
const service = accessor.untitledEditorService;
const input = service.createOrGet();
......@@ -245,39 +257,32 @@ suite('Workbench untitled editors', () => {
assert.equal(r.toString(), input.getResource().toString());
});
return input.resolve().then(model => {
model.textEditorModel.setValue('foo');
assert.equal(counter, 0, 'Dirty model should not trigger event immediately');
const model = await input.resolve();
model.textEditorModel.setValue('foo');
assert.equal(counter, 0, 'Dirty model should not trigger event immediately');
return timeout(3).then(() => {
assert.equal(counter, 1, 'Dirty model should trigger event');
await timeout(3);
assert.equal(counter, 1, 'Dirty model should trigger event');
model.textEditorModel.setValue('bar');
model.textEditorModel.setValue('bar');
return timeout(3).then(() => {
assert.equal(counter, 2, 'Content change when dirty should trigger event');
await timeout(3);
assert.equal(counter, 2, 'Content change when dirty should trigger event');
model.textEditorModel.setValue('');
model.textEditorModel.setValue('');
return timeout(3).then(() => {
assert.equal(counter, 3, 'Manual revert should trigger event');
await timeout(3);
assert.equal(counter, 3, 'Manual revert should trigger event');
model.textEditorModel.setValue('foo');
model.textEditorModel.setValue('foo');
return timeout(3).then(() => {
assert.equal(counter, 4, 'Dirty model should trigger event');
await timeout(3);
assert.equal(counter, 4, 'Dirty model should trigger event');
model.revert();
model.revert();
return timeout(3).then(() => {
assert.equal(counter, 5, 'Revert should trigger event');
input.dispose();
});
});
});
});
});
});
await timeout(3);
assert.equal(counter, 5, 'Revert should trigger event');
input.dispose();
});
test('onDidDisposeModel event', () => {
test('onDidDisposeModel event', async () => {
const service = accessor.untitledEditorService;
const input = service.createOrGet();
......@@ -288,10 +293,9 @@ suite('Workbench untitled editors', () => {
assert.equal(r.toString(), input.getResource().toString());
});
return input.resolve().then(model => {
assert.equal(counter, 0);
input.dispose();
assert.equal(counter, 1);
});
await input.resolve();
assert.equal(counter, 0);
input.dispose();
assert.equal(counter, 1);
});
});
\ No newline at end of file
......@@ -37,7 +37,7 @@ suite('MainThreadSaveParticipant', function () {
});
test('insert final new line', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
const configService = new TestConfigurationService();
......@@ -70,7 +70,7 @@ suite('MainThreadSaveParticipant', function () {
});
test('trim final new lines', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
const configService = new TestConfigurationService();
......@@ -105,7 +105,7 @@ suite('MainThreadSaveParticipant', function () {
});
test('trim final new lines bug#39750', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
const configService = new TestConfigurationService();
......@@ -132,7 +132,7 @@ suite('MainThreadSaveParticipant', function () {
});
test('trim final new lines bug#46075', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
const configService = new TestConfigurationService();
......
......@@ -85,7 +85,7 @@ import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
return instantiationService.createInstance(FileEditorInput, resource, undefined);
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined);
}
export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv) as IWindowConfiguration, process.execPath);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册