Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,24 @@ export function createHistory(options: HistoryOptions = {}): History {
}
};

const undoEntry = (): Entry | undefined => {
if (cursor === 0) return undefined;
// The cursor is kept in [0, length], so cursor - 1 names an undoable entry.
return entries[cursor - 1]!;
};

const redoEntry = (): Entry | undefined => {
if (cursor === entries.length) return undefined;
// The cursor is kept in [0, length], so cursor names a redoable entry.
return entries[cursor]!;
};

const buildMeta = (): HistoryMeta =>
Object.freeze({
canUndo: cursor > 0,
canRedo: cursor < entries.length,
undoLabel: cursor > 0 ? entries[cursor - 1].label : undefined,
redoLabel: cursor < entries.length ? entries[cursor].label : undefined,
undoLabel: undoEntry()?.label,
redoLabel: redoEntry()?.label,
position: cursor,
length: entries.length,
});
Expand Down Expand Up @@ -88,11 +100,13 @@ export function createHistory(options: HistoryOptions = {}): History {
while (remaining > 0) {
if (dir < 0) {
if (cursor === 0) break;
runGuarded(entries[cursor - 1].undo);
const entry = undoEntry()!;
runGuarded(entry.undo);
cursor -= 1;
} else {
if (cursor === entries.length) break;
runGuarded(entries[cursor].do);
const entry = redoEntry()!;
runGuarded(entry.do);
cursor += 1;
}
remaining -= 1;
Expand Down Expand Up @@ -154,7 +168,7 @@ export function createHistory(options: HistoryOptions = {}): History {

const checkpoint = (name: string): void => {
assertIdle();
checkpoints.set(name, cursor === 0 ? START : entries[cursor - 1]);
checkpoints.set(name, cursor === 0 ? START : undoEntry()!);
};

const hasCheckpoint = (name: string): boolean => checkpoints.has(name);
Expand Down Expand Up @@ -218,10 +232,10 @@ export function createHistory(options: HistoryOptions = {}): History {
return cursor < entries.length;
},
get undoLabel() {
return cursor > 0 ? entries[cursor - 1].label : undefined;
return undoEntry()?.label;
},
get redoLabel() {
return cursor < entries.length ? entries[cursor].label : undefined;
return redoEntry()?.label;
},
get position() {
return cursor;
Expand Down
8 changes: 6 additions & 2 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ export function runTransaction(
closed = true;
try {
for (let i = ops.length - 1; i >= 0; i -= 1) {
engine.runGuarded(ops[i].undo);
// The reverse walk starts at the last recorded op and stops before -1.
const op = ops[i]!;
engine.runGuarded(op.undo);
}
} finally {
engine.endMutation();
Expand All @@ -106,7 +108,9 @@ export function runTransaction(
},
undo() {
for (let i = ops.length - 1; i >= 0; i -= 1) {
engine.runGuarded(ops[i].undo);
// The reverse walk starts at the last recorded op and stops before -1.
const op = ops[i]!;
engine.runGuarded(op.undo);
}
},
});
Expand Down
17 changes: 10 additions & 7 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["es2023"],
"target": "es2024",
"lib": ["es2024"],
"moduleDetection": "force",
"module": "nodenext",
"moduleResolution": "nodenext",
"resolveJsonModule": true,
"types": ["node"],
"types": [],
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"declaration": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noEmit": true,
"allowImportingTsExtensions": true,
"esModuleInterop": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
}
},
"include": ["src"]
}
9 changes: 9 additions & 0 deletions tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
// Keeps editor/raw tsc coverage for Node-run files while tsconfig.json
// remains the shipped-source contract with no Node ambient globals.
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["tests", "vite.config.ts"]
}