/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as fse from "fs-extra"; import * as path from "path"; import * as vscode from "vscode"; import { ext } from '../extensionVariables'; import { createTemporaryFile } from "./createTemporaryFile"; import { DialogResponses } from "./DialogResponses"; // tslint:disable-next-line:no-unsafe-any export abstract class BaseEditor implements vscode.Disposable { private fileMap: { [key: string]: [vscode.TextDocument, ContextT] } = {}; private ignoreSave: boolean = false; constructor(private readonly showSavePromptKey: string) {} public abstract getData(context: ContextT): Promise; public abstract updateData(context: ContextT, data: string): Promise; public abstract getFilename(context: ContextT): Promise; public abstract getResourceName(context: ContextT): Promise; public abstract getSaveConfirmationText(context: ContextT): Promise; public abstract getSize(context: ContextT): Promise; public async showEditor(context: ContextT): Promise { const fileName: string = await this.getFilename(context); const resourceName: string = await this.getResourceName(context); this.appendLineToOutput(`Opening ${resourceName}...`, { resourceName }); const localFilePath: string = await createTemporaryFile(fileName); const document: vscode.TextDocument = await vscode.workspace.openTextDocument(localFilePath); this.fileMap[localFilePath] = [document, context]; const data: string = await this.getData(context); const textEditor: vscode.TextEditor = await vscode.window.showTextDocument(document); await this.updateEditor(data, textEditor); } public async updateMatchingContext(doc: vscode.Uri): Promise { const filePath: string | undefined = Object.keys(this.fileMap).find((fsPath: string) => path.relative(doc.fsPath, fsPath) === ""); if (filePath) { const [textDocument, context]: [vscode.TextDocument, ContextT] = this.fileMap[filePath]; await this.updateRemote(context, textDocument); } } public async dispose(): Promise { Object.keys(this.fileMap).forEach(async (key: string) => await fse.remove(path.dirname(key))); } public async onDidSaveTextDocument(globalState: vscode.Memento, doc: vscode.TextDocument): Promise { const filePath: string | undefined = Object.keys(this.fileMap).find( (fsPath: string) => path.relative(doc.uri.fsPath, fsPath) === "" ); if (!this.ignoreSave && filePath) { const context: ContextT = this.fileMap[filePath][1]; const showSaveWarning: boolean | undefined = vscode.workspace.getConfiguration().get(this.showSavePromptKey); if (showSaveWarning) { const message: string = await this.getSaveConfirmationText(context); const result: vscode.MessageItem | undefined = await vscode.window.showWarningMessage( message, DialogResponses.upload, DialogResponses.alwaysUpload, DialogResponses.dontUpload ); if (result === DialogResponses.alwaysUpload) { await vscode.workspace.getConfiguration().update(this.showSavePromptKey, false, vscode.ConfigurationTarget.Global); await globalState.update(this.showSavePromptKey, true); } } await this.updateRemote(context, doc); } } protected appendLineToOutput(value: string, options?: { resourceName?: string; date?: Date }): void { ext.outputChannel?.appendLog(value, options); ext.outputChannel?.show(true); } private async updateRemote(context: ContextT, doc: vscode.TextDocument): Promise { const filename: string = await this.getFilename(context); const resourceName: string = await this.getResourceName(context); this.appendLineToOutput(`Updating "${filename}" ...', filename)`, { resourceName }); const updatedData: string = await this.updateData(context, doc.getText()); this.appendLineToOutput(`Updated "${filename}".`, { resourceName }); if (doc.isClosed !== true) { const visibleDocument: vscode.TextEditor | undefined = vscode.window.visibleTextEditors.find((ed) => ed.document === doc); if (visibleDocument) { await this.updateEditor(updatedData, visibleDocument); } } } private async updateEditor(data: string, textEditor?: vscode.TextEditor): Promise { if (textEditor) { await BaseEditor.writeToEditor(textEditor, data); this.ignoreSave = true; try { await textEditor.document.save(); } finally { this.ignoreSave = false; } } } // tslint:disable-next-line:member-ordering private static async writeToEditor(editor: vscode.TextEditor, data: string): Promise { await editor.edit((editBuilder: vscode.TextEditorEdit) => { if (editor.document.lineCount > 0) { const lastLine: vscode.TextLine = editor.document.lineAt(editor.document.lineCount - 1); editBuilder.delete( new vscode.Range( new vscode.Position(0, 0), new vscode.Position(lastLine.range.start.line, lastLine.range.end.character) ) ); } editBuilder.insert(new vscode.Position(0, 0), data); }); } }