mirror of
https://github.com/Microsoft/vscode
synced 2024-10-12 06:17:18 +00:00
first implementation of registerTextDocumentContentProvider api-idea
This commit is contained in:
parent
f2bba12f8b
commit
26fab247a9
|
@ -119,6 +119,63 @@ suite('workspace-namespace', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('registerTextDocumentContentProvider, simple', function() {
|
||||
|
||||
let registration = workspace.registerTextDocumentContentProvider('foo', {
|
||||
open(uri) {
|
||||
return uri.toString();
|
||||
},
|
||||
close() {
|
||||
// nothing
|
||||
}
|
||||
});
|
||||
|
||||
const uri = Uri.parse('foo://testing/virtual.js');
|
||||
return workspace.openTextDocument(uri).then(doc => {
|
||||
assert.equal(doc.getText(), uri.toString());
|
||||
assert.equal(doc.isDirty, false);
|
||||
assert.equal(doc.uri.toString(), uri.toString());
|
||||
registration.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('registerTextDocumentContentProvider, constrains', function() {
|
||||
|
||||
// built-in
|
||||
assert.throws(function() {
|
||||
workspace.registerTextDocumentContentProvider('untitled', { open() { return null; }, close() { } });
|
||||
});
|
||||
// built-in
|
||||
assert.throws(function() {
|
||||
workspace.registerTextDocumentContentProvider('file', { open() { return null; }, close() { } });
|
||||
});
|
||||
|
||||
// duplicate registration
|
||||
let registration = workspace.registerTextDocumentContentProvider('foo', {
|
||||
open(uri) {
|
||||
return uri.toString();
|
||||
},
|
||||
close() {
|
||||
// nothing
|
||||
}
|
||||
});
|
||||
assert.throws(function() {
|
||||
workspace.registerTextDocumentContentProvider('foo', { open() { return null; }, close() { } });
|
||||
});
|
||||
|
||||
// unregister & register
|
||||
registration.dispose();
|
||||
registration = workspace.registerTextDocumentContentProvider('foo', { open() { return null; }, close() { } });
|
||||
registration.dispose();
|
||||
|
||||
// missing scheme
|
||||
return workspace.openTextDocument(Uri.parse('notThere://foo/far/boo/bar')).then(() => {
|
||||
assert.ok(false, 'expected failure')
|
||||
}, err => {
|
||||
// expected
|
||||
})
|
||||
})
|
||||
|
||||
test('findFiles', () => {
|
||||
return workspace.findFiles('*.js', null).then((res) => {
|
||||
assert.equal(res.length, 1);
|
||||
|
|
15
src/vs/vscode.d.ts
vendored
15
src/vs/vscode.d.ts
vendored
|
@ -1019,11 +1019,18 @@ declare namespace vscode {
|
|||
|
||||
export interface TextDocumentContentProvider {
|
||||
|
||||
open(uri: Uri): string | Thenable<string>;
|
||||
|
||||
close(uri: Uri): any | Thenable<any>;
|
||||
|
||||
/**
|
||||
* An event to signal a resource has changed.
|
||||
*/
|
||||
onDidChange?: Event<Uri>;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
open(uri: Uri, token: CancellationToken): string | Thenable<string>;
|
||||
|
||||
// todo@joh make this optional?
|
||||
close(uri: Uri, token: CancellationToken): any | Thenable<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,7 @@ import URI from 'vs/base/common/uri';
|
|||
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
|
||||
import {Range, Position, Disposable} from 'vs/workbench/api/common/extHostTypes';
|
||||
import {IEventService} from 'vs/platform/event/common/event';
|
||||
import {IEditorService} from 'vs/platform/editor/common/editor';
|
||||
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
|
||||
import {EventType as FileEventType, LocalFileChangeEvent, ITextFileService} from 'vs/workbench/parts/files/common/files';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
|
@ -23,6 +23,12 @@ import * as vscode from 'vscode';
|
|||
import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers';
|
||||
import {IFileService} from 'vs/platform/files/common/files';
|
||||
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import {asWinJsPromise} from 'vs/base/common/async';
|
||||
import {EditorModel, EditorInput} from 'vs/workbench/common/editor';
|
||||
import {IEditorInput, IResourceInput} from 'vs/platform/editor/common/editor';
|
||||
import {BaseTextEditorModel} from 'vs/workbench/browser/parts/editor/textEditorModel';
|
||||
import {IMode} from 'vs/editor/common/modes';
|
||||
import {IModeService} from 'vs/editor/common/services/modeService';
|
||||
|
||||
export interface IModelAddedData {
|
||||
url: URI;
|
||||
|
@ -59,7 +65,8 @@ export class ExtHostModelService {
|
|||
private _onDidSaveDocumentEventEmitter: Emitter<BaseTextDocument>;
|
||||
public onDidSaveDocument: Event<BaseTextDocument>;
|
||||
|
||||
private _documents: {[modelUri:string]:ExtHostDocument;};
|
||||
private _documents: { [modelUri: string]: ExtHostDocument; };
|
||||
private _documentContentProviders: { [scheme: string]: vscode.TextDocumentContentProvider };
|
||||
|
||||
private _proxy: MainThreadDocuments;
|
||||
|
||||
|
@ -79,6 +86,7 @@ export class ExtHostModelService {
|
|||
this.onDidSaveDocument = this._onDidSaveDocumentEventEmitter.event;
|
||||
|
||||
this._documents = Object.create(null);
|
||||
this._documentContentProviders = Object.create(null);
|
||||
}
|
||||
|
||||
public getDocuments(): BaseTextDocument[] {
|
||||
|
@ -117,7 +125,28 @@ export class ExtHostModelService {
|
|||
}
|
||||
|
||||
registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider): vscode.Disposable {
|
||||
return new Disposable(() => { });
|
||||
if (scheme === 'file' || scheme === 'untitled' || this._documentContentProviders[scheme]) {
|
||||
throw new Error(`scheme '${scheme}' already registered`);
|
||||
}
|
||||
this._documentContentProviders[scheme] = provider;
|
||||
return new Disposable(() => delete this._documentContentProviders[scheme]);
|
||||
}
|
||||
|
||||
$openTextDocumentContent(uri: URI): TPromise<string> {
|
||||
const provider = this._documentContentProviders[uri.scheme];
|
||||
if (!provider) {
|
||||
return TPromise.wrapError<string>(`unsupported uri-scheme: ${uri.scheme}`);
|
||||
}
|
||||
// todo@joh protected for !string results, slow provider etc
|
||||
return asWinJsPromise(token => provider.open(uri, token));
|
||||
}
|
||||
|
||||
$closeTextDocumentContent(uri: URI): TPromise<any> {
|
||||
const provider = this._documentContentProviders[uri.scheme];
|
||||
if (!provider) {
|
||||
return TPromise.wrapError<string>(`unsupported uri-scheme: ${uri.scheme}`);
|
||||
}
|
||||
return asWinJsPromise(token => provider.close(uri, token));
|
||||
}
|
||||
|
||||
public _acceptModelAdd(data:IModelAddedData): void {
|
||||
|
@ -529,8 +558,10 @@ export class ExtHostDocument extends BaseTextDocument {
|
|||
|
||||
@Remotable.MainContext('MainThreadDocuments')
|
||||
export class MainThreadDocuments {
|
||||
private _modelService: IModelService;
|
||||
private _modeService: IModeService;
|
||||
private _textFileService: ITextFileService;
|
||||
private _editorService: IEditorService;
|
||||
private _editorService: IWorkbenchEditorService;
|
||||
private _fileService: IFileService;
|
||||
private _untitledEditorService: IUntitledEditorService;
|
||||
private _toDispose: IDisposable[];
|
||||
|
@ -540,13 +571,16 @@ export class MainThreadDocuments {
|
|||
|
||||
constructor(
|
||||
@IThreadService threadService: IThreadService,
|
||||
@IModelService modelService:IModelService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IEventService eventService:IEventService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IUntitledEditorService untitledEditorService: IUntitledEditorService
|
||||
) {
|
||||
this._modelService = modelService;
|
||||
this._modeService = modeService;
|
||||
this._textFileService = textFileService;
|
||||
this._editorService = editorService;
|
||||
this._fileService = fileService;
|
||||
|
@ -633,6 +667,39 @@ export class MainThreadDocuments {
|
|||
}
|
||||
}
|
||||
|
||||
// --- editor input
|
||||
|
||||
getEditorInput(uri: URI): TPromise<IEditorInput> {
|
||||
switch (uri.scheme) {
|
||||
case 'file':
|
||||
// file-scheme is support by the workbench
|
||||
return this._editorService.inputToType({ resource: uri });
|
||||
|
||||
case 'untitled':
|
||||
// some very special dance for unititled resources
|
||||
const asFileUri = URI.file(uri.fsPath);
|
||||
return this._fileService.resolveFile(asFileUri).then(stats => {
|
||||
// don't create a new file ontop of an existing file
|
||||
return TPromise.wrapError<any>('file already exists on disk');
|
||||
}, err => {
|
||||
const input = this._untitledEditorService.createOrGet(asFileUri); // using file-uri makes it show in 'Working Files' section
|
||||
return input.resolve(true).then(model => {
|
||||
if (input.getResource().toString() !== uri.toString()) {
|
||||
throw new Error(`expected URI ${uri.toString()} BUT GOT ${input.getResource().toString()}`);
|
||||
}
|
||||
return this._proxy._acceptModelDirty(uri); // mark as dirty
|
||||
}).then(() => {
|
||||
return input;
|
||||
});
|
||||
});
|
||||
|
||||
default:
|
||||
// create an input that talks back to the extension host
|
||||
return TPromise.as(new MainThreadExtensionEditorInput(uri, this._proxy, this._modelService,
|
||||
this._modeService));
|
||||
}
|
||||
}
|
||||
|
||||
// --- from plugin host process
|
||||
|
||||
_trySaveDocument(uri: URI): TPromise<boolean> {
|
||||
|
@ -645,49 +712,68 @@ export class MainThreadDocuments {
|
|||
return TPromise.wrapError('Uri must have scheme and path. One or both are missing in: ' + uri.toString());
|
||||
}
|
||||
|
||||
let promise: TPromise<boolean>;
|
||||
switch (uri.scheme) {
|
||||
case 'file':
|
||||
promise = this._handleFileScheme(uri);
|
||||
break;
|
||||
case 'untitled':
|
||||
promise = this._handleUnititledScheme(uri);
|
||||
break;
|
||||
default:
|
||||
promise = TPromise.wrapError<boolean>('unsupported URI-scheme: ' + uri.scheme);
|
||||
break;
|
||||
}
|
||||
|
||||
return promise.then(success => {
|
||||
if (!success) {
|
||||
return TPromise.wrapError('cannot open ' + uri.toString());
|
||||
}
|
||||
return this.getEditorInput(uri).then(input => {
|
||||
return this._editorService.resolveEditorModel(input).then(model => {
|
||||
return true;
|
||||
});
|
||||
}, err => {
|
||||
return TPromise.wrapError('cannot open ' + uri.toString() + '. Detail: ' + toErrorMessage(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleFileScheme(uri: URI): TPromise<boolean> {
|
||||
return this._editorService.resolveEditorModel({ resource: uri }).then(model => {
|
||||
return !!model;
|
||||
export class MainThreadExtensionEditorInput extends EditorInput {
|
||||
|
||||
private _model: MainThreadEditorModel;
|
||||
|
||||
constructor(resource: URI, documents: ExtHostModelService, modelService:IModelService, modeService:IModeService) {
|
||||
super();
|
||||
this._model = new MainThreadEditorModel(resource, documents, modelService, modeService)
|
||||
// todo@joh name, description
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return 'MainThreadExtensionEditorInput'
|
||||
}
|
||||
|
||||
resolve(refresh?: boolean): TPromise<EditorModel> {
|
||||
// todo@joh proper refresh
|
||||
return this._model.load(refresh);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
console.log('MainThreadExtensionEditorInput DISPOSE');
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class MainThreadEditorModel extends BaseTextEditorModel {
|
||||
|
||||
private _resource: URI;
|
||||
private _documents: ExtHostModelService;
|
||||
|
||||
constructor(resource: URI, documents: ExtHostModelService, @IModelService modelService: IModelService,
|
||||
@IModeService modeService: IModeService) {
|
||||
|
||||
super(modelService, modeService);
|
||||
this._documents = documents;
|
||||
this._resource = resource;
|
||||
}
|
||||
|
||||
load(refresh?: boolean): TPromise<EditorModel> {
|
||||
return this._documents.$openTextDocumentContent(this._resource).then(value => {
|
||||
return this.createTextEditorModel(value, this._resource)
|
||||
}).then(() => {
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
private _handleUnititledScheme(uri: URI): TPromise<boolean> {
|
||||
let asFileUri = URI.file(uri.fsPath);
|
||||
return this._fileService.resolveFile(asFileUri).then(stats => {
|
||||
// don't create a new file ontop of an existing file
|
||||
return TPromise.wrapError<boolean>('file already exists on disk');
|
||||
}, err => {
|
||||
let input = this._untitledEditorService.createOrGet(asFileUri); // using file-uri makes it show in 'Working Files' section
|
||||
return input.resolve(true).then(model => {
|
||||
if (input.getResource().toString() !== uri.toString()) {
|
||||
throw new Error(`expected URI ${uri.toString() } BUT GOT ${input.getResource().toString() }`);
|
||||
}
|
||||
return this._proxy._acceptModelDirty(uri); // mark as dirty
|
||||
}).then(() => {
|
||||
return true;
|
||||
});
|
||||
});
|
||||
protected getOrCreateMode(modeService: IModeService, mime: string, firstLineText?: string): TPromise<IMode> {
|
||||
return modeService.getOrCreateModeByFilenameOrFirstLine(this._resource.fsPath, firstLineText);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
console.log('MainThreadEditorModel DISPOSE');
|
||||
super.dispose();
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import Event, {Emitter} from 'vs/base/common/event';
|
|||
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
|
||||
import {TPromise} from 'vs/base/common/winjs.base';
|
||||
import {Remotable, IThreadService} from 'vs/platform/thread/common/thread';
|
||||
import {ExtHostModelService} from 'vs/workbench/api/common/extHostDocuments';
|
||||
import {ExtHostModelService, MainThreadDocuments} from 'vs/workbench/api/common/extHostDocuments';
|
||||
import {Selection, Range, Position, EditorOptions} from './extHostTypes';
|
||||
import {ISingleEditOperation, ISelection, IRange, IInternalIndentationOptions, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IRangeWithMessage} from 'vs/editor/common/editorCommon';
|
||||
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
|
||||
|
@ -20,6 +20,7 @@ import {MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, IT
|
|||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import {TextDocument, TextEditorSelectionChangeEvent, TextEditorOptionsChangeEvent, TextEditorOptions, ViewColumn} from 'vscode';
|
||||
import {EventType} from 'vs/workbench/common/events';
|
||||
import {EditorOptions as WorkbenchEditorOptions} from 'vs/workbench/common/editor';
|
||||
import {IEventService} from 'vs/platform/event/common/event';
|
||||
import {equals as arrayEquals} from 'vs/base/common/arrays';
|
||||
|
||||
|
@ -414,6 +415,7 @@ class ExtHostTextEditor implements vscode.TextEditor {
|
|||
export class MainThreadEditors {
|
||||
|
||||
private _proxy: ExtHostEditors;
|
||||
private _documents: MainThreadDocuments;
|
||||
private _workbenchEditorService: IWorkbenchEditorService;
|
||||
private _editorTracker: MainThreadEditorsTracker;
|
||||
private _toDispose: IDisposable[];
|
||||
|
@ -430,6 +432,7 @@ export class MainThreadEditors {
|
|||
@IModelService modelService:IModelService
|
||||
) {
|
||||
this._proxy = threadService.getRemotable(ExtHostEditors);
|
||||
this._documents = threadService.getRemotable(MainThreadDocuments);
|
||||
this._workbenchEditorService = workbenchEditorService;
|
||||
this._toDispose = [];
|
||||
this._textEditorsListenersMap = Object.create(null);
|
||||
|
@ -527,13 +530,12 @@ export class MainThreadEditors {
|
|||
|
||||
_tryShowTextDocument(resource: URI, position: EditorPosition, preserveFocus: boolean): TPromise<string> {
|
||||
|
||||
// the input we want to open
|
||||
let input = {
|
||||
resource,
|
||||
options: { preserveFocus }
|
||||
};
|
||||
return this._documents.getEditorInput(resource).then(input => {
|
||||
// open editor first
|
||||
return this._workbenchEditorService.openEditor(input, WorkbenchEditorOptions.create({ preserveFocus }),
|
||||
position);
|
||||
|
||||
return this._workbenchEditorService.openEditor(input, position).then(editor => {
|
||||
}).then(editor => {
|
||||
|
||||
return new TPromise<void>(c => {
|
||||
// not very nice but the way it is: changes to the editor state aren't
|
||||
|
|
Loading…
Reference in a new issue