diff --git a/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts b/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts index b4295876bf244..e7837a89c56c5 100644 --- a/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts +++ b/packages/plugin-ext-vscode/src/browser/plugin-vscode-commands-contribution.ts @@ -31,7 +31,7 @@ import TheiaURI from '@theia/core/lib/common/uri'; import { EditorManager } from '@theia/editor/lib/browser'; import { TextDocumentShowOptions } from '@theia/plugin-ext/lib/common/plugin-api-rpc-model'; import { DocumentsMainImpl } from '@theia/plugin-ext/lib/main/browser/documents-main'; -import { createUntitledResource } from '@theia/plugin-ext/lib/main/browser/editor/untitled-resource'; +import { createUntitledURI } from '@theia/plugin-ext/lib/main/browser/editor/untitled-resource'; import { fromViewColumn, toDocumentSymbol } from '@theia/plugin-ext/lib/plugin/type-converters'; import { ViewColumn } from '@theia/plugin-ext/lib/plugin/types-impl'; import { WorkspaceCommands } from '@theia/workspace/lib/browser'; @@ -140,7 +140,7 @@ export class PluginVscodeCommandsContribution implements CommandContribution { * and apply actions only to them */ commands.registerCommand({ id: 'workbench.action.files.newUntitledFile' }, { - execute: () => open(this.openerService, createUntitledResource().uri) + execute: () => open(this.openerService, createUntitledURI()) }); commands.registerCommand({ id: 'workbench.action.files.openFile' }, { execute: () => commands.executeCommand(WorkspaceCommands.OPEN_FILE.id) diff --git a/packages/plugin-ext/src/main/browser/documents-main.ts b/packages/plugin-ext/src/main/browser/documents-main.ts index 5d1218ae1908a..0601c55d5cc73 100644 --- a/packages/plugin-ext/src/main/browser/documents-main.ts +++ b/packages/plugin-ext/src/main/browser/documents-main.ts @@ -20,7 +20,7 @@ import { DisposableCollection, Disposable } from '@theia/core'; import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model'; import { RPCProtocol } from '../../common/rpc-protocol'; import { EditorModelService } from './text-editor-model-service'; -import { createUntitledResource } from './editor/untitled-resource'; +import { UntitledResourceResolver } from './editor/untitled-resource'; import { EditorManager, EditorOpenerOptions } from '@theia/editor/lib/browser'; import URI from '@theia/core/lib/common/uri'; import CodeURI from 'vscode-uri'; @@ -94,7 +94,8 @@ export class DocumentsMainImpl implements DocumentsMain, Disposable { rpc: RPCProtocol, private editorManager: EditorManager, private openerService: OpenerService, - private shell: ApplicationShell + private shell: ApplicationShell, + private untitledResourceResolver: UntitledResourceResolver ) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.DOCUMENTS_EXT); @@ -169,7 +170,7 @@ export class DocumentsMainImpl implements DocumentsMain, Disposable { async $tryCreateDocument(options?: { language?: string; content?: string; }): Promise { const language = options && options.language; const content = options && options.content; - const resource = createUntitledResource(content, language); + const resource = await this.untitledResourceResolver.createUntitledResource(content, language); return monaco.Uri.parse(resource.uri.toString()); } diff --git a/packages/plugin-ext/src/main/browser/editor/untitled-resource.ts b/packages/plugin-ext/src/main/browser/editor/untitled-resource.ts index d68b1067925c9..da17771cb2566 100644 --- a/packages/plugin-ext/src/main/browser/editor/untitled-resource.ts +++ b/packages/plugin-ext/src/main/browser/editor/untitled-resource.ts @@ -14,39 +14,96 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ResourceResolver, Resource } from '@theia/core'; +import { injectable, inject } from 'inversify'; +import { TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol'; +import { Resource, ResourceResolver } from '@theia/core'; import URI from '@theia/core/lib/common/uri'; -import { injectable } from 'inversify'; import { Schemes } from '../../../common/uri-components'; +import { FileResource, FileResourceResolver } from '@theia/filesystem/lib/browser'; -const resources = new Map(); let index = 0; + @injectable() export class UntitledResourceResolver implements ResourceResolver { - resolve(uri: URI): Resource | Promise { - if (uri.scheme === Schemes.UNTITLED) { - return resources.get(uri.toString())!; + + @inject(FileResourceResolver) + protected readonly fileResourceResolver: FileResourceResolver; + + resources = new Map(); + + async resolve(uri: URI): Promise { + if (uri.scheme !== Schemes.UNTITLED) { + throw new Error('The given uri is not untitled file uri: ' + uri); + } else { + const untitledResource = this.resources.get(uri.toString()); + if (!untitledResource) { + return this.createUntitledResource('', '', uri, this.fileResourceResolver); + } else { + return untitledResource; + } + } + } + + async createUntitledResource(content?: string, language?: string, uri?: URI, fileResourceResolver?: FileResourceResolver): Promise { + let extension; + if (language) { + for (const lang of monaco.languages.getLanguages()) { + if (lang.id === language) { + if (lang.extensions) { + extension = lang.extensions[0]; + break; + } + } + } } - throw new Error(`scheme ${uri.scheme} is not '${Schemes.UNTITLED}'`); + return new UntitledResource(this.resources, uri ? uri : new URI().withScheme(Schemes.UNTITLED).withPath(`/Untitled-${index++}${extension ? extension : ''}`), + content, fileResourceResolver); } } export class UntitledResource implements Resource { + private fileResource?: FileResource; - constructor(public uri: URI, private content?: string) { - resources.set(this.uri.toString(), this); + constructor(private resources: Map, public uri: URI, private content?: string, private fileResourceResolver?: FileResourceResolver) { + this.resources.set(this.uri.toString(), this); } - readContents(options?: { encoding?: string | undefined; } | undefined): Promise { - return Promise.resolve(this.content ? this.content : ''); + dispose(): void { + this.resources.delete(this.uri.toString()); } - dispose(): void { - resources.delete(this.uri.toString()); + async readContents(options?: { encoding?: string | undefined; } | undefined): Promise { + if (this.fileResource) { + return this.fileResource.readContents(options); + } else if (this.content) { + return Promise.resolve(this.content); + } else { + return ''; + } + } + + async saveContents(content: string, options?: { encoding?: string, overwriteEncoding?: string }): Promise { + const that = this; + // The path of URI's with Untitled scheme has a prefix of: 'file:///'. + // When the user chosse to save the file we need to change the scheme from 'untitled' to 'file'. + // Need to find better way to remove the 'file:///' prefix from a URI with untitled scheme + const fileUri = new URI(this.uri.path.toString().replace('file:///', '')); + if (this.fileResourceResolver) { + this.fileResourceResolver.resolve(fileUri).then(resolvedFileResource => { + that.fileResource = resolvedFileResource; + that.fileResource.saveContents(content, options); + }); + } + } + + async saveContentChanges(changes: TextDocumentContentChangeEvent[], options?: { encoding?: string, overwriteEncoding?: string }): Promise { + if (this.fileResource) { + this.fileResource.saveContentChanges(changes, options); + } } } -export function createUntitledResource(content?: string, language?: string): UntitledResource { +export function createUntitledURI(language?: string): URI { let extension; if (language) { for (const lang of monaco.languages.getLanguages()) { @@ -58,5 +115,5 @@ export function createUntitledResource(content?: string, language?: string): Unt } } } - return new UntitledResource(new URI().withScheme(Schemes.UNTITLED).withPath(`/Untitled-${index++}${extension ? extension : ''}`), content); + return new URI().withScheme(Schemes.UNTITLED).withPath(`/Untitled-${index++}${extension ? extension : ''}`); } diff --git a/packages/plugin-ext/src/main/browser/main-context.ts b/packages/plugin-ext/src/main/browser/main-context.ts index 2392961b80dcb..bd530d76fcfe2 100644 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ b/packages/plugin-ext/src/main/browser/main-context.ts @@ -47,6 +47,7 @@ import { OpenerService } from '@theia/core/lib/browser/opener-service'; import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; import { MonacoBulkEditService } from '@theia/monaco/lib/browser/monaco-bulk-edit-service'; import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service'; +import { UntitledResourceResolver } from './editor/untitled-resource'; export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void { const commandRegistryMain = new CommandRegistryMainImpl(rpc, container); @@ -73,7 +74,8 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container const editorManager = container.get(EditorManager); const openerService = container.get(OpenerService); const shell = container.get(ApplicationShell); - const documentsMain = new DocumentsMainImpl(editorsAndDocuments, modelService, rpc, editorManager, openerService, shell); + const untitledResourceResolver = container.get(UntitledResourceResolver); + const documentsMain = new DocumentsMainImpl(editorsAndDocuments, modelService, rpc, editorManager, openerService, shell, untitledResourceResolver); rpc.set(PLUGIN_RPC_CONTEXT.DOCUMENTS_MAIN, documentsMain); const bulkEditService = container.get(MonacoBulkEditService); diff --git a/packages/plugin-ext/src/plugin/documents.ts b/packages/plugin-ext/src/plugin/documents.ts index 7eb590cfa12c9..3aa40e440af30 100644 --- a/packages/plugin-ext/src/plugin/documents.ts +++ b/packages/plugin-ext/src/plugin/documents.ts @@ -124,11 +124,11 @@ export class DocumentsExtImpl implements DocumentsExt { protected async fireTextDocumentWillSaveEvent({ document, reason, fireEvent, accept }: { - document: theia.TextDocument, - reason: theia.TextDocumentSaveReason, - fireEvent: (e: theia.TextDocumentWillSaveEvent) => any, - accept: (operation: SingleEditOperation) => void - }): Promise { + document: theia.TextDocument, + reason: theia.TextDocumentSaveReason, + fireEvent: (e: theia.TextDocumentWillSaveEvent) => any, + accept: (operation: SingleEditOperation) => void + }): Promise { const promises: PromiseLike[] = []; fireEvent(Object.freeze({