From 7b3771f0d2a5a4334a72b8ab965ecb0c3b5e6dbb Mon Sep 17 00:00:00 2001 From: Shimon Ben Yair Date: Mon, 24 Feb 2020 11:39:28 +0200 Subject: [PATCH] Fix 'window.showTextDocument' to open resources with 'untitled' schema, Fixes eclipse-theia#6565 Signed-off-by: Shimon Ben Yair --- .../plugin-vscode-commands-contribution.ts | 4 +- .../src/main/browser/documents-main.ts | 9 +- .../main/browser/editor/untitled-resource.ts | 110 +++++++++++++++--- .../src/main/browser/main-context.ts | 6 +- 4 files changed, 108 insertions(+), 21 deletions(-) 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 7e717ec9b0575..c379d8391c349 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 @@ -32,7 +32,7 @@ import { EditorManager } from '@theia/editor/lib/browser'; import { CodeEditorWidget } from '@theia/plugin-ext/lib/main/browser/menus/menus-contribution-handler'; 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 { 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'; @@ -172,7 +172,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 1753f7e4f4921..6269243cf320f 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'; @@ -30,6 +30,7 @@ import { Range } from 'vscode-languageserver-types'; import { OpenerService } from '@theia/core/lib/browser/opener-service'; import { Reference } from '@theia/core/lib/common/reference'; import { dispose } from '../../common/disposable-util'; +import { FileResourceResolver } from '@theia/filesystem/lib/browser'; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. @@ -92,7 +93,9 @@ export class DocumentsMainImpl implements DocumentsMain, Disposable { rpc: RPCProtocol, private editorManager: EditorManager, private openerService: OpenerService, - private shell: ApplicationShell + private shell: ApplicationShell, + private untitledResourceResolver: UntitledResourceResolver, + private fileResourceResolver: FileResourceResolver ) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.DOCUMENTS_EXT); @@ -178,7 +181,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(this.fileResourceResolver, 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..eefc929797bb2 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,119 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { ResourceResolver, Resource } from '@theia/core'; +import { Emitter, Event } from '@theia/core/lib/common/event'; +import { injectable, inject } from 'inversify'; +import { TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol'; +import { Resource, ResourceResolver, ResourceVersion } 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; + + protected readonly 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(this.fileResourceResolver, '', '', uri); + } else { + return untitledResource; + } + } + } + + async createUntitledResource(fileResourceResolver: FileResourceResolver, content?: string, language?: string, uri?: URI): 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 : ''}`), + fileResourceResolver, content); } } export class UntitledResource implements Resource { - constructor(public uri: URI, private content?: string) { - resources.set(this.uri.toString(), this); - } + private fileResource?: FileResource; + + protected readonly onDidChangeContentsEmitter = new Emitter(); + readonly onDidChangeContents: Event = this.onDidChangeContentsEmitter.event; - readContents(options?: { encoding?: string | undefined; } | undefined): Promise { - return Promise.resolve(this.content ? this.content : ''); + constructor(private resources: Map, public uri: URI, private fileResourceResolver: FileResourceResolver, private content?: string) { + this.resources.set(this.uri.toString(), this); } dispose(): void { - resources.delete(this.uri.toString()); + this.resources.delete(this.uri.toString()); + this.onDidChangeContentsEmitter.dispose(); + if (this.fileResource) { + this.fileResource.dispose(); + } + } + + async readContents(options?: { encoding?: string | undefined; } | undefined): Promise { + if (this.fileResource) { + return this.fileResource.readContents(options); + } else if (this.content) { + return this.content; + } else { + return ''; + } + } + + async saveContents(content: string, options?: { encoding?: string, overwriteEncoding?: string }): Promise { + if (!this.fileResource) { + this.fileResource = await this.fileResourceResolver.resolve(new URI(this.uri.path.toString())); + if (this.fileResource.onDidChangeContents) { + this.fileResource.onDidChangeContents(() => this.fireDidChangeContents()); + } + } + await this.fileResource.saveContents(content, options); + } + + async saveContentChanges(changes: TextDocumentContentChangeEvent[], options?: { encoding?: string, overwriteEncoding?: string }): Promise { + if (!this.fileResource) { + throw new Error('FileResource is not available for: ' + this.uri.path.toString()); + } + await this.fileResource.saveContentChanges(changes, options); + } + + async guessEncoding(): Promise { + if (this.fileResource) { + return this.fileResource.guessEncoding(); + } + } + + protected fireDidChangeContents(): void { + this.onDidChangeContentsEmitter.fire(undefined); + } + + get version(): ResourceVersion | undefined { + if (this.fileResource) { + return this.fileResource.version; + } + return undefined; } } -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 +138,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..e778cacf5b22e 100644 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ b/packages/plugin-ext/src/main/browser/main-context.ts @@ -47,6 +47,8 @@ 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'; +import { FileResourceResolver } from '@theia/filesystem/lib/browser'; export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void { const commandRegistryMain = new CommandRegistryMainImpl(rpc, container); @@ -73,7 +75,9 @@ 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 fileResourceResolver = container.get(FileResourceResolver); + const documentsMain = new DocumentsMainImpl(editorsAndDocuments, modelService, rpc, editorManager, openerService, shell, untitledResourceResolver, fileResourceResolver); rpc.set(PLUGIN_RPC_CONTEXT.DOCUMENTS_MAIN, documentsMain); const bulkEditService = container.get(MonacoBulkEditService);