提交 6c685145 编写于 作者: R rebornix

Fix #100329. Content providers can contribute to undo/redo stack of a notebook document.

上级 ba6892d6
......@@ -568,6 +568,70 @@ suite('notebook undo redo', () => {
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
});
test('execute and then undo redo', async function () {
const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb'));
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const cellsChangeEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
const cellChangeEventRet = await cellsChangeEvent;
assert.equal(cellChangeEventRet.document, vscode.notebook.activeNotebookEditor?.document);
assert.equal(cellChangeEventRet.changes.length, 1);
assert.deepEqual(cellChangeEventRet.changes[0], {
start: 1,
deletedCount: 0,
deletedItems: [],
items: [
vscode.notebook.activeNotebookEditor!.document.cells[1]
]
});
const secondCell = vscode.notebook.activeNotebookEditor!.document.cells[1];
const moveCellEvent = getEventOncePromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
await vscode.commands.executeCommand('notebook.cell.moveUp');
const moveCellEventRet = await moveCellEvent;
assert.deepEqual(moveCellEventRet, {
document: vscode.notebook.activeNotebookEditor!.document,
changes: [
{
start: 1,
deletedCount: 1,
deletedItems: [secondCell],
items: []
},
{
start: 0,
deletedCount: 0,
deletedItems: [],
items: [vscode.notebook.activeNotebookEditor?.document.cells[0]]
}
]
});
const cellOutputChange = getEventOncePromise<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs);
await vscode.commands.executeCommand('notebook.cell.execute');
const cellOutputsAddedRet = await cellOutputChange;
assert.deepEqual(cellOutputsAddedRet, {
document: vscode.notebook.activeNotebookEditor!.document,
cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]]
});
assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1);
const cellOutputClear = getEventOncePromise<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs);
await vscode.commands.executeCommand('notebook.undo');
const cellOutputsCleardRet = await cellOutputClear;
assert.deepEqual(cellOutputsCleardRet, {
document: vscode.notebook.activeNotebookEditor!.document,
cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]]
});
assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0);
await vscode.commands.executeCommand('workbench.action.files.save');
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
});
});
suite('notebook working copy', () => {
......
......@@ -10,8 +10,10 @@ import { smokeTestActivate } from './notebookSmokeTestMain';
export function activate(context: vscode.ExtensionContext): any {
smokeTestActivate(context);
const _onDidChangeNotebook = new vscode.EventEmitter<vscode.NotebookDocumentEditEvent | vscode.NotebookDocumentContentChangeEvent>();
context.subscriptions.push(_onDidChangeNotebook);
context.subscriptions.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', {
onDidChangeNotebook: new vscode.EventEmitter<vscode.NotebookDocumentEditEvent>().event,
onDidChangeNotebook: _onDidChangeNotebook.event,
openNotebook: async (_resource: vscode.Uri) => {
if (_resource.path.endsWith('empty.vsctestnb')) {
return {
......@@ -71,13 +73,13 @@ export function activate(context: vscode.ExtensionContext): any {
}];
return;
},
executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined, _token: vscode.CancellationToken) => {
if (!_cell) {
_cell = _document.cells[0];
executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined, _token: vscode.CancellationToken) => {
if (!cell) {
cell = document.cells[0];
}
if (_document.uri.path.endsWith('customRenderer.vsctestnb')) {
_cell.outputs = [{
if (document.uri.path.endsWith('customRenderer.vsctestnb')) {
cell.outputs = [{
outputKind: vscode.CellOutputKind.Rich,
data: {
'text/custom': 'test'
......@@ -87,13 +89,29 @@ export function activate(context: vscode.ExtensionContext): any {
return;
}
_cell.outputs = [{
const previousOutputs = cell.outputs;
const newOutputs: vscode.CellOutput[] = [{
outputKind: vscode.CellOutputKind.Rich,
data: {
'text/plain': ['my output']
}
}];
cell.outputs = newOutputs;
_onDidChangeNotebook.fire({
document: document,
undo: () => {
if (cell) {
cell.outputs = previousOutputs;
}
},
redo: () => {
if (cell) {
cell.outputs = newOutputs;
}
}
});
return;
}
}));
......
......@@ -1604,12 +1604,45 @@ declare module 'vscode' {
readonly metadata: NotebookDocumentMetadata;
}
interface NotebookDocumentContentChangeEvent {
/**
* The document that the edit is for.
*/
readonly document: NotebookDocument;
}
interface NotebookDocumentEditEvent {
/**
* The document that the edit is for.
*/
readonly document: NotebookDocument;
/**
* Undo the edit operation.
*
* This is invoked by VS Code when the user undoes this edit. To implement `undo`, your
* extension should restore the document and editor to the state they were in just before this
* edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`.
*/
undo(): Thenable<void> | void;
/**
* Redo the edit operation.
*
* This is invoked by VS Code when the user redoes this edit. To implement `redo`, your
* extension should restore the document and editor to the state they were in just after this
* edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`.
*/
redo(): Thenable<void> | void;
/**
* Display name describing the edit.
*
* This will be shown to users in the UI for undo/redo operations.
*/
readonly label?: string;
}
interface NotebookDocumentBackup {
......@@ -1660,7 +1693,7 @@ declare module 'vscode' {
}): Promise<void>;
saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
readonly onDidChangeNotebook: Event<NotebookDocumentEditEvent>;
readonly onDidChangeNotebook: Event<NotebookDocumentContentChangeEvent>;
backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise<NotebookDocumentBackup>;
kernel?: NotebookKernel;
......
......@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IExtHostContext, ExtHostNotebookShape, ExtHostContext, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData } from '../common/extHost.protocol';
import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
......@@ -17,6 +18,8 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IRelativePattern } from 'vs/base/common/glob';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
export class MainThreadNotebookDocument extends Disposable {
private _textModel: NotebookTextModel;
......@@ -31,9 +34,12 @@ export class MainThreadNotebookDocument extends Disposable {
public viewType: string,
public supportBackup: boolean,
public uri: URI,
readonly notebookService: INotebookService
@INotebookService readonly notebookService: INotebookService,
@IUndoRedoService readonly undoRedoService: IUndoRedoService
) {
super();
this._textModel = new NotebookTextModel(handle, viewType, supportBackup, uri);
this._register(this._textModel.onDidModelChangeProxy(e => {
this._proxy.$acceptModelChanged(this.uri, e);
......@@ -54,6 +60,22 @@ export class MainThreadNotebookDocument extends Disposable {
await this.notebookService.transformSpliceOutputs(this.textModel, splices);
this._textModel.$spliceNotebookCellOutputs(cellHandle, splices);
}
handleEdit(editId: number, label: string | undefined): void {
this.undoRedoService.pushElement({
type: UndoRedoElementType.Resource,
resource: this._textModel.uri,
label: label ?? nls.localize('defaultEditLabel', "Edit"),
undo: async () => {
await this._proxy.$undoNotebook(this._textModel.viewType, this._textModel.uri, editId, this._textModel.isDirty);
},
redo: async () => {
await this._proxy.$redoNotebook(this._textModel.viewType, this._textModel.uri, editId, this._textModel.isDirty);
},
});
this._textModel.setDirty(true);
}
dispose() {
this._textModel.dispose();
super.dispose();
......@@ -179,7 +201,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
@INotebookService private _notebookService: INotebookService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
......@@ -388,7 +411,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
}
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, kernel: INotebookKernelInfoDto | undefined): Promise<void> {
let controller = new MainThreadNotebookController(this._proxy, this, viewType, supportBackup, kernel, this._notebookService);
let controller = new MainThreadNotebookController(this._proxy, this, viewType, supportBackup, kernel, this._notebookService, this._instantiationService);
this._notebookProviders.set(viewType, controller);
this._notebookService.registerNotebookController(viewType, extension, controller);
return;
......@@ -467,6 +490,16 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
return false;
}
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void {
let controller = this._notebookProviders.get(viewType);
controller?.handleEdit(resource, editId, label);
}
$onContentChange(resource: UriComponents, viewType: string): void {
let controller = this._notebookProviders.get(viewType);
controller?.handleNotebookChange(resource);
}
}
export class MainThreadNotebookController implements IMainNotebookController {
......@@ -480,6 +513,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
private _supportBackup: boolean,
readonly kernel: INotebookKernelInfoDto | undefined,
readonly notebookService: INotebookService,
readonly _instantiationService: IInstantiationService
) {
}
......@@ -504,7 +538,7 @@ export class MainThreadNotebookController implements IMainNotebookController {
return mainthreadNotebook.textModel;
}
let document = new MainThreadNotebookDocument(this._proxy, MainThreadNotebookController.documentHandle++, viewType, this._supportBackup, uri, this.notebookService);
let document = this._instantiationService.createInstance(MainThreadNotebookDocument, this._proxy, MainThreadNotebookController.documentHandle++, viewType, this._supportBackup, uri);
this._mapping.set(document.uri.toString(), document);
if (backup) {
......@@ -635,6 +669,11 @@ export class MainThreadNotebookController implements IMainNotebookController {
document?.textModel.handleUnknownChange();
}
handleEdit(resource: UriComponents, editId: number, label: string | undefined): void {
let document = this._mapping.get(URI.from(resource).toString());
document?.handleEdit(editId, label);
}
updateLanguages(resource: UriComponents, languages: string[]) {
let document = this._mapping.get(URI.from(resource).toString());
document?.textModel.updateLanguages(languages);
......
......@@ -710,6 +710,9 @@ export interface MainThreadNotebookShape extends IDisposable {
$updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise<void>;
$spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise<void>;
$postMessage(handle: number, value: any): Promise<boolean>;
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
$onContentChange(resource: UriComponents, viewType: string): void;
}
export interface MainThreadUrlsShape extends IDisposable {
......@@ -1596,6 +1599,9 @@ export interface ExtHostNotebookShape {
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void;
$acceptEditorPropertiesChanged(uriComponents: UriComponents, data: INotebookEditorPropertiesChangeData): void;
$acceptDocumentAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): Promise<void>;
$undoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
$redoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void>;
}
export interface ExtHostStorageShape {
......
......@@ -25,6 +25,7 @@ import { joinPath } from 'vs/base/common/resources';
import { Schemas } from 'vs/base/common/network';
import { hash } from 'vs/base/common/hash';
import { generateUuid } from 'vs/base/common/uuid';
import { Cache } from './cache';
interface IObservable<T> {
proxy: T;
......@@ -250,6 +251,43 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
private _backup?: vscode.NotebookDocumentBackup;
private readonly _edits = new Cache<vscode.NotebookDocumentEditEvent>('notebook documents');
addEdit(item: vscode.NotebookDocumentEditEvent): number {
return this._edits.add([item]);
}
async undo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).undo();
// if (!isDirty) {
// this.disposeBackup();
// }
}
async redo(editId: number, isDirty: boolean): Promise<void> {
await this.getEdit(editId).redo();
// if (!isDirty) {
// this.disposeBackup();
// }
}
private getEdit(editId: number): vscode.NotebookDocumentEditEvent {
const edit = this._edits.get(editId, 0);
if (!edit) {
throw new Error('No edit found');
}
return edit;
}
disposeEdits(editIds: number[]): void {
for (const id of editIds) {
this._edits.delete(id);
}
}
private _disposed = false;
constructor(
......@@ -906,8 +944,25 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
const supportBackup = !!provider.backupNotebook;
this._proxy.$registerNotebookProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, supportBackup, provider.kernel ? { id: viewType, label: provider.kernel.label, extensionLocation: extension.extensionLocation, preloads: provider.kernel.preloads } : undefined);
const contentChangeListener = provider.onDidChangeNotebook(e => {
const document = this._documents.get(URI.revive(e.document.uri).toString());
if (!document) {
throw new Error(`Notebook document ${e.document.uri.toString()} not found`);
}
if (isEditEvent(e)) {
const editId = document.addEdit(e);
this._proxy.$onDidEdit(e.document.uri, viewType, editId, e.label);
} else {
this._proxy.$onContentChange(e.document.uri, viewType);
}
});
return new extHostTypes.Disposable(() => {
listener.dispose();
contentChangeListener.dispose();
this._notebookContentProviders.delete(viewType);
this._proxy.$unregisterNotebookProvider(viewType);
});
......@@ -1080,6 +1135,26 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return false;
}
async $undoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void> {
const document = this._documents.get(URI.revive(uri).toString());
if (!document) {
return;
}
document.undo(editId, isDirty);
}
async $redoNotebook(viewType: string, uri: UriComponents, editId: number, isDirty: boolean): Promise<void> {
const document = this._documents.get(URI.revive(uri).toString());
if (!document) {
return;
}
document.redo(editId, isDirty);
}
async $backup(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string | undefined> {
const document = this._documents.get(URI.revive(uri).toString());
const provider = this._notebookContentProviders.get(viewType);
......@@ -1353,3 +1428,8 @@ function hashPath(resource: URI): string {
const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString();
return hash(str) + '';
}
function isEditEvent(e: vscode.NotebookDocumentEditEvent | vscode.NotebookDocumentContentChangeEvent): e is vscode.NotebookDocumentEditEvent {
return typeof (e as vscode.NotebookDocumentEditEvent).undo === 'function'
&& typeof (e as vscode.NotebookDocumentEditEvent).redo === 'function';
}
......@@ -78,8 +78,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
onDidChangeContent: Event<void> = this._onDidChangeContent.event;
private _onDidChangeMetadata = new Emitter<NotebookDocumentMetadata>();
onDidChangeMetadata: Event<NotebookDocumentMetadata> = this._onDidChangeMetadata.event;
private readonly _onDidChangeUnknown = new Emitter<void>();
readonly onDidChangeUnknown: Event<void> = this._onDidChangeUnknown.event;
private _mapping: Map<number, NotebookCellTextModel> = new Map();
private _cellListeners: Map<number, IDisposable> = new Map();
cells: NotebookCellTextModel[];
......@@ -104,6 +102,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._onDidSelectionChangeProxy.fire(this._selections);
}
private _dirty = false;
protected readonly _onDidChangeDirty = this._register(new Emitter<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
constructor(
public handle: number,
public viewType: string,
......@@ -114,6 +116,17 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this.cells = [];
}
get isDirty() {
return this._dirty;
}
setDirty(newState: boolean) {
if (this._dirty !== newState) {
this._dirty = newState;
this._onDidChangeDirty.fire();
}
}
createCellTextModel(
source: string | string[],
language: string,
......@@ -135,7 +148,21 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
const cellUri = CellUri.generate(this.uri, cellHandle);
return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata);
});
this.insertNewCell(0, mainCells, false);
this._isUntitled = false;
for (let i = 0; i < mainCells.length; i++) {
this._mapping.set(mainCells[i].handle, mainCells[i]);
let dirtyStateListener = mainCells[i].onDidChangeContent(() => {
this.setDirty(true);
this._onDidChangeContent.fire();
});
this._cellListeners.set(mainCells[i].handle, dirtyStateListener);
}
this.cells.splice(0, 0, ...mainCells);
this._increaseVersionId();
}
$applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], emitToExtHost: boolean = true): boolean {
......@@ -228,7 +255,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
handleUnknownChange() {
this._onDidChangeUnknown.fire();
this.setDirty(true);
}
updateLanguages(languages: string[]) {
......@@ -270,10 +297,12 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
let dirtyStateListener = cell.onDidChangeContent(() => {
this._isUntitled = false;
this.setDirty(true);
this._onDidChangeContent.fire();
});
this._cellListeners.set(cell.handle, dirtyStateListener);
this.setDirty(true);
this._onDidChangeContent.fire();
this._onDidModelChangeProxy.fire({
......@@ -303,6 +332,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
for (let i = 0; i < cells.length; i++) {
this._mapping.set(cells[i].handle, cells[i]);
let dirtyStateListener = cells[i].onDidChangeContent(() => {
this.setDirty(true);
this._onDidChangeContent.fire();
});
......@@ -310,7 +340,9 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
this.cells.splice(index, 0, ...cells);
this.setDirty(true);
this._onDidChangeContent.fire();
this._increaseVersionId();
if (emitToExtHost) {
......@@ -345,6 +377,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._cellListeners.delete(cell.handle);
}
this.cells.splice(index, count);
this.setDirty(true);
this._onDidChangeContent.fire();
this._increaseVersionId();
......@@ -359,6 +392,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
const cells = this.cells.splice(index, 1);
this.cells.splice(newIdx, 0, ...cells);
this.setDirty(true);
this._onDidChangeContent.fire();
this._increaseVersionId();
......
......@@ -271,7 +271,6 @@ export interface INotebookTextModel {
renderers: Set<string>;
onDidChangeCells?: Event<NotebookCellTextModelSplice[]>;
onDidChangeContent: Event<void>;
onDidChangeUnknown: Event<void>;
onWillDispose(listener: () => void): IDisposable;
}
......
......@@ -35,7 +35,6 @@ export interface INotebookLoadOptions {
export class NotebookEditorModel extends EditorModel implements IWorkingCopy, INotebookEditorModel {
private _dirty = false;
protected readonly _onDidChangeDirty = this._register(new Emitter<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
private readonly _onDidChangeContent = this._register(new Emitter<void>());
......@@ -113,7 +112,7 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
await this.load({ forceReadFromDisk: true });
this._dirty = false;
this._notebook.setDirty(false);
this._onDidChangeDirty.fire();
}
......@@ -153,15 +152,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
this._name = basename(this._notebook!.uri);
this._register(this._notebook.onDidChangeContent(() => {
this.setDirty(true);
this._onDidChangeContent.fire();
}));
this._register(this._notebook.onDidChangeUnknown(() => {
this.setDirty(true);
this._register(this._notebook.onDidChangeDirty(() => {
this._onDidChangeDirty.fire();
}));
await this._backupFileService.discardBackup(this._workingCopyResource);
this.setDirty(true);
this._notebook.setDirty(true);
return this;
}
......@@ -173,16 +171,15 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
this._name = basename(this._notebook!.uri);
this._register(this._notebook.onDidChangeContent(() => {
this.setDirty(true);
this._onDidChangeContent.fire();
}));
this._register(this._notebook.onDidChangeUnknown(() => {
this.setDirty(true);
this._register(this._notebook.onDidChangeDirty(() => {
this._onDidChangeDirty.fire();
}));
if (backupId) {
await this._backupFileService.discardBackup(this._workingCopyResource);
this.setDirty(true);
this._notebook.setDirty(true);
}
return this;
......@@ -192,15 +189,8 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
return !!this._notebook;
}
setDirty(newState: boolean) {
if (this._dirty !== newState) {
this._dirty = newState;
this._onDidChangeDirty.fire();
}
}
isDirty() {
return this._dirty;
return this._notebook?.isDirty;
}
isUntitled() {
......@@ -210,16 +200,14 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
async save(): Promise<boolean> {
const tokenSource = new CancellationTokenSource();
await this._notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token);
this._dirty = false;
this._onDidChangeDirty.fire();
this._notebook.setDirty(false);
return true;
}
async saveAs(targetResource: URI): Promise<boolean> {
const tokenSource = new CancellationTokenSource();
await this._notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token);
this._dirty = false;
this._onDidChangeDirty.fire();
this._notebook.setDirty(false);
return true;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册