+type UndoStackEntry = {
+ content: string;
+ selection: MarkdownEditorInputSelection;
+}
+
+class UndoStack {
+ protected onChangeDebounced: (callback: () => UndoStackEntry) => void;
+
+ protected stack: UndoStackEntry[] = [];
+ protected pointer: number = -1;
+ protected lastActionTime: number = 0;
+
+ constructor() {
+ this.onChangeDebounced = debounce(this.onChange, 1000, false);
+ }
+
+ undo(): UndoStackEntry|null {
+ if (this.pointer < 1) {
+ return null;
+ }
+
+ this.lastActionTime = Date.now();
+ this.pointer -= 1;
+ return this.stack[this.pointer];
+ }
+
+ redo(): UndoStackEntry|null {
+ const atEnd = this.pointer === this.stack.length - 1;
+ if (atEnd) {
+ return null;
+ }
+
+ this.lastActionTime = Date.now();
+ this.pointer++;
+ return this.stack[this.pointer];
+ }
+
+ push(getValueCallback: () => UndoStackEntry): void {
+ // Ignore changes made via undo/redo actions
+ if (Date.now() - this.lastActionTime < 100) {
+ return;
+ }
+
+ this.onChangeDebounced(getValueCallback);
+ }
+
+ protected onChange(getValueCallback: () => UndoStackEntry) {
+ // Trim the end of the stack from the pointer since we're branching away
+ if (this.pointer !== this.stack.length - 1) {
+ this.stack = this.stack.slice(0, this.pointer)
+ }
+
+ this.stack.push(getValueCallback());
+
+ // Limit stack size
+ if (this.stack.length > 50) {
+ this.stack = this.stack.slice(this.stack.length - 50);
+ }
+
+ this.pointer = this.stack.length - 1;
+ }
+}