Skip to content

Commit f4c74bd

Browse files
committed
Add locks to the edit stacks while undoing / redoing
1 parent e6b16e8 commit f4c74bd

1 file changed

Lines changed: 97 additions & 19 deletions

File tree

src/vs/platform/undoRedo/common/undoRedoService.ts

Lines changed: 97 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,13 @@ class ResourceEditStack {
154154
public resource: URI;
155155
public past: StackElement[];
156156
public future: StackElement[];
157+
public locked: boolean;
157158

158159
constructor(resource: URI) {
159160
this.resource = resource;
160161
this.past = [];
161162
this.future = [];
163+
this.locked = false;
162164
}
163165
}
164166

@@ -363,16 +365,52 @@ export class UndoRedoService implements IUndoRedoService {
363365
this._notificationService.error(err);
364366
}
365367

366-
private _safeInvoke(element: StackElement, invoke: () => Promise<void> | void): Promise<void> | void {
368+
private _acquireLocks(affectedEditStacks: ResourceEditStack[]): () => void {
369+
// first, check if all locks can be acquired
370+
for (const editStack of affectedEditStacks) {
371+
if (editStack.locked) {
372+
throw new Error('Cannot acquire edit stack lock');
373+
}
374+
}
375+
376+
// can acquire all locks
377+
for (const editStack of affectedEditStacks) {
378+
editStack.locked = true;
379+
}
380+
381+
return () => {
382+
// release all locks
383+
for (const editStack of affectedEditStacks) {
384+
editStack.locked = false;
385+
}
386+
};
387+
}
388+
389+
private _safeInvokeWithLocks(element: StackElement, invoke: () => Promise<void> | void, affectedEditStacks: ResourceEditStack[]): Promise<void> | void {
390+
const releaseLocks = this._acquireLocks(affectedEditStacks);
391+
367392
let result: Promise<void> | void;
368393
try {
369394
result = invoke();
370395
} catch (err) {
396+
releaseLocks();
371397
return this._onError(err, element);
372398
}
373399

374400
if (result) {
375-
return result.then(undefined, (err) => this._onError(err, element));
401+
// result is Promise<void>
402+
return result.then(
403+
() => {
404+
releaseLocks();
405+
},
406+
(err) => {
407+
releaseLocks();
408+
return this._onError(err, element);
409+
}
410+
);
411+
} else {
412+
// result is void
413+
releaseLocks();
376414
}
377415
}
378416

@@ -405,7 +443,6 @@ export class UndoRedoService implements IUndoRedoService {
405443
cannotUndoDueToResources.push(editStack.resource);
406444
}
407445
}
408-
409446
if (cannotUndoDueToResources.length > 0) {
410447
this._splitPastWorkspaceElement(element, null);
411448
const paths = cannotUndoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path);
@@ -414,6 +451,20 @@ export class UndoRedoService implements IUndoRedoService {
414451
return new WorkspaceVerificationError(this.undo(resource));
415452
}
416453

454+
const cannotLockDueToResources: URI[] = [];
455+
for (const editStack of affectedEditStacks) {
456+
if (editStack.locked) {
457+
cannotLockDueToResources.push(editStack.resource);
458+
}
459+
}
460+
if (cannotLockDueToResources.length > 0) {
461+
this._splitPastWorkspaceElement(element, null);
462+
const paths = cannotLockDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path);
463+
const message = nls.localize('cannotWorkspaceUndoDueToInProgressUndoRedo', "Could not undo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, paths.join(', '));
464+
this._notificationService.info(message);
465+
return new WorkspaceVerificationError(this.undo(resource));
466+
}
467+
417468
return null;
418469
}
419470

@@ -445,21 +496,25 @@ export class UndoRedoService implements IUndoRedoService {
445496
return;
446497
}
447498

448-
if (result.choice === 0) {
449-
// At this point, it is possible that the element has been made invalid in the meantime (due to the confirmation await)
450-
const verificationError = this._checkWorkspaceUndo(resource, element, affectedEditStacks);
451-
if (verificationError) {
452-
return verificationError.returnValue;
453-
}
454-
for (const editStack of affectedEditStacks) {
455-
editStack.past.pop();
456-
editStack.future.push(element);
457-
}
458-
return this._safeInvoke(element, () => element.actual.undo());
459-
} else {
499+
if (result.choice === 1) {
500+
// undo this file
460501
this._splitPastWorkspaceElement(element, null);
461502
return this.undo(resource);
462503
}
504+
505+
// undo in all files
506+
// At this point, it is possible that the element has been made invalid in the meantime (due to the confirmation await)
507+
const verificationError = this._checkWorkspaceUndo(resource, element, affectedEditStacks);
508+
if (verificationError) {
509+
return verificationError.returnValue;
510+
}
511+
512+
for (const editStack of affectedEditStacks) {
513+
editStack.past.pop();
514+
editStack.future.push(element);
515+
}
516+
517+
return this._safeInvokeWithLocks(element, () => element.actual.undo(), affectedEditStacks);
463518
}
464519

465520
private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
@@ -469,9 +524,14 @@ export class UndoRedoService implements IUndoRedoService {
469524
editStack.future = [];
470525
return;
471526
}
527+
if (editStack.locked) {
528+
const message = nls.localize('cannotResourceUndoDueToInProgressUndoRedo', "Could not undo '{0}' because there is already an undo or redo operation running.", element.label);
529+
this._notificationService.info(message);
530+
return;
531+
}
472532
editStack.past.pop();
473533
editStack.future.push(element);
474-
return this._safeInvoke(element, () => element.actual.undo());
534+
return this._safeInvokeWithLocks(element, () => element.actual.undo(), [editStack]);
475535
}
476536

477537
public undo(resource: URI): Promise<void> | void {
@@ -523,7 +583,6 @@ export class UndoRedoService implements IUndoRedoService {
523583
cannotRedoDueToResources.push(editStack.resource);
524584
}
525585
}
526-
527586
if (cannotRedoDueToResources.length > 0) {
528587
this._splitFutureWorkspaceElement(element, null);
529588
const paths = cannotRedoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path);
@@ -532,6 +591,20 @@ export class UndoRedoService implements IUndoRedoService {
532591
return new WorkspaceVerificationError(this.redo(resource));
533592
}
534593

594+
const cannotLockDueToResources: URI[] = [];
595+
for (const editStack of affectedEditStacks) {
596+
if (editStack.locked) {
597+
cannotLockDueToResources.push(editStack.resource);
598+
}
599+
}
600+
if (cannotLockDueToResources.length > 0) {
601+
this._splitFutureWorkspaceElement(element, null);
602+
const paths = cannotLockDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path);
603+
const message = nls.localize('cannotWorkspaceRedoDueToInProgressUndoRedo', "Could not redo '{0}' across all files because there is already an undo or redo operation running on {1}", element.label, paths.join(', '));
604+
this._notificationService.info(message);
605+
return new WorkspaceVerificationError(this.redo(resource));
606+
}
607+
535608
return null;
536609
}
537610

@@ -546,7 +619,7 @@ export class UndoRedoService implements IUndoRedoService {
546619
editStack.future.pop();
547620
editStack.past.push(element);
548621
}
549-
return this._safeInvoke(element, () => element.actual.redo());
622+
return this._safeInvokeWithLocks(element, () => element.actual.redo(), affectedEditStacks);
550623
}
551624

552625
private _resourceRedo(editStack: ResourceEditStack, element: ResourceStackElement): Promise<void> | void {
@@ -556,9 +629,14 @@ export class UndoRedoService implements IUndoRedoService {
556629
editStack.future = [];
557630
return;
558631
}
632+
if (editStack.locked) {
633+
const message = nls.localize('cannotResourceRedoDueToInProgressUndoRedo', "Could not redo '{0}' because there is already an undo or redo operation running.", element.label);
634+
this._notificationService.info(message);
635+
return;
636+
}
559637
editStack.future.pop();
560638
editStack.past.push(element);
561-
return this._safeInvoke(element, () => element.actual.redo());
639+
return this._safeInvokeWithLocks(element, () => element.actual.redo(), [editStack]);
562640
}
563641

564642
public redo(resource: URI): Promise<void> | void {

0 commit comments

Comments
 (0)