Add undo-redo

This commit is contained in:
dylan 2023-05-06 11:35:02 -07:00
parent 9685568f90
commit 3ad23f3a91
4 changed files with 86 additions and 17 deletions

View File

@ -5,10 +5,10 @@ import {
} from "./window.ts"; } from "./window.ts";
import { font } from "./font.ts"; import { font } from "./font.ts";
// import { keyDown, keyPressed, keyReleased } from "./keyboard.ts"; // import { keyDown, keyPressed, keyReleased } from "./keyboard.ts";
import { addToContext } from "./runcode.ts"; import { addToContext, runCode } from "./runcode.ts";
import { resetRepl } from "./repl.ts"; import { resetRepl } from "./repl.ts";
import { COLOR } from "./colors.ts"; import { COLOR } from "./colors.ts";
import { getSheet } from "./sheet.ts"; import { getSheet, getCodeSheet } from "./sheet.ts";
export const drawSprite = (x: number, y: number, spr: number) => { export const drawSprite = (x: number, y: number, spr: number) => {
const {sheet_type, value: sprites} = getSheet(2); const {sheet_type, value: sprites} = getSheet(2);
@ -43,6 +43,9 @@ const faux = {
// key_down: keyDown, // key_down: keyDown,
// key_pressed: keyPressed, // key_pressed: keyPressed,
// key_released: keyReleased, // key_released: keyReleased,
code: (n: number) => {
return runCode(getCodeSheet(n));
},
log: console.log, log: console.log,
JSON: JSON, JSON: JSON,
}; };

View File

@ -2,11 +2,49 @@ import { clearScreen, fillRect } from "./window.ts";
import { fontWidth, fontHeight } from "./font.ts"; import { fontWidth, fontHeight } from "./font.ts";
import { drawText } from "./builtins.ts"; import { drawText } from "./builtins.ts";
import { COLOR } from "./colors.ts"; import { COLOR } from "./colors.ts";
import {getSheet, setSheet} from "./sheet.ts"; import {getCodeSheet, setSheet} from "./sheet.ts";
import { K, ctrlKeyDown, getKeyboardString, keyPressed, shiftKeyDown } from "./keyboard.ts"; import { K, ctrlKeyDown, getKeyboardString, keyPressed, shiftKeyDown } from "./keyboard.ts";
import { clipboard, tokenize } from "./deps.ts"; import { clipboard, tokenize } from "./deps.ts";
const historyDebounceFrames = 20;
const state = { const state = {
history: [{code: getCodeSheet(0), anchor: 0, focus: 0}],
historyDebounce: 0,
historyIndex: 0,
undo() {
if (this.historyIndex === this.history.length && this.historyDebounce > 0) {
this.snapshot();
}
if (this.historyIndex > 0) {
this.historyIndex -= 1;
const snap = this.history[this.historyIndex];
this.code = snap.code;
this.setSelection(snap.anchor, snap.focus);
}
},
redo() {
if (this.historyIndex < this.history.length-1) {
this.historyIndex += 1;
const snap = this.history[this.historyIndex];
this.code = snap.code;
this.setSelection(snap.anchor, snap.focus);
}
},
snapshot() {
this.history.push({
code: this.code,
anchor: this.anchor,
focus: this.focus,
});
},
startSnapping() {
this.historyIndex += 1;
if (this.history.length > this.historyIndex) {
this.history.length = this.historyIndex;
}
this.historyDebounce = historyDebounceFrames;
},
scrollX: 0, scrollX: 0,
scrollY: 0, scrollY: 0,
anchor: 0, anchor: 0,
@ -42,6 +80,7 @@ const state = {
const {code, anchor, focus} = this; const {code, anchor, focus} = this;
this.code = code.slice(0, Math.min(anchor, focus)) + text + code.slice(Math.max(anchor, focus)); this.code = code.slice(0, Math.min(anchor, focus)) + text + code.slice(Math.max(anchor, focus));
this.setSelection(Math.min(anchor, focus) + text.length); this.setSelection(Math.min(anchor, focus) + text.length);
this.startSnapping();
}, },
indent(indentString: string) { indent(indentString: string) {
const lines = this.code.split("\n"); const lines = this.code.split("\n");
@ -55,6 +94,7 @@ const state = {
}); });
this.code = newLines.join("\n"); this.code = newLines.join("\n");
this.setSelection({x: anchorX+1, y: anchorY}, {x: focusX+1, y: focusY}); this.setSelection({x: anchorX+1, y: anchorY}, {x: focusX+1, y: focusY});
this.startSnapping();
}, },
outdent(outdentRegex: RegExp) { outdent(outdentRegex: RegExp) {
const lines = this.code.split("\n"); const lines = this.code.split("\n");
@ -69,6 +109,7 @@ const state = {
}); });
this.code = newLines.join("\n"); this.code = newLines.join("\n");
this.setSelection({x: Math.max(0,anchorX-1), y: anchorY}, {x: Math.max(0,focusX-1), y: focusY}); this.setSelection({x: Math.max(0,anchorX-1), y: anchorY}, {x: Math.max(0,focusX-1), y: focusY});
this.startSnapping();
}, },
backspace() { backspace() {
const {code, focus} = this; const {code, focus} = this;
@ -76,6 +117,7 @@ const state = {
if (focus > 0) { if (focus > 0) {
this.code = code.slice(0, focus-1) + code.slice(focus); this.code = code.slice(0, focus-1) + code.slice(focus);
this.setSelection(focus-1); this.setSelection(focus-1);
this.startSnapping();
} }
} else { } else {
this.insertText(""); this.insertText("");
@ -86,6 +128,7 @@ const state = {
if (this.isCollapsed()) { if (this.isCollapsed()) {
if (focus < code.length) { if (focus < code.length) {
this.code = code.slice(0, focus) + code.slice(1+focus); this.code = code.slice(0, focus) + code.slice(1+focus);
this.startSnapping();
} }
} else { } else {
this.insertText(""); this.insertText("");
@ -101,7 +144,6 @@ const state = {
this.insertText(""); this.insertText("");
}, },
async paste() { async paste() {
this.insertText(await clipboard.readText()); this.insertText(await clipboard.readText());
}, },
scrollToCursor() { scrollToCursor() {
@ -122,11 +164,7 @@ const state = {
} }
}, },
get code() { get code() {
const {sheet_type, value} = getSheet(0); return getCodeSheet(0);
if (sheet_type !== "code") {
throw "Trying to run a non-code sheet as code."
}
return value;
}, },
set code(val) { set code(val) {
setSheet(0, "code", val); setSheet(0, "code", val);
@ -347,12 +385,18 @@ const drawCodeField = (code: string, x: number, y: number, w: number, h: number)
const update = async () => { const update = async () => {
const { focus, focusX, focusY} = state; const { focus, focusX, focusY} = state;
if (state.historyDebounce > 0) {
state.historyDebounce -= 1;
if (state.historyDebounce <= 0) {
state.snapshot();
}
}
const keyboardString = getKeyboardString(); const keyboardString = getKeyboardString();
if (keyboardString) { if (keyboardString) {
state.insertText(keyboardString); state.insertText(keyboardString);
state.scrollToCursor(); state.scrollToCursor();
} }
// TODO: Handle ctrl-Z
// TODO: Make ctrl-/ do commenting out (take inspiration from tab) // TODO: Make ctrl-/ do commenting out (take inspiration from tab)
if (keyPressed(K.ENTER)) { if (keyPressed(K.ENTER)) {
@ -414,12 +458,25 @@ const update = async () => {
} }
if (keyPressed("C") && ctrlKeyDown()) { if (keyPressed("C") && ctrlKeyDown()) {
await state.copy(); await state.copy();
state.scrollToCursor();
} }
if (keyPressed("X") && ctrlKeyDown()) { if (keyPressed("X") && ctrlKeyDown()) {
await state.cut(); await state.cut();
state.scrollToCursor();
} }
if (keyPressed("V") && ctrlKeyDown()) { if (keyPressed("V") && ctrlKeyDown()) {
await state.paste(); await state.paste();
state.scrollToCursor();
}
if (keyPressed("Z") && ctrlKeyDown()) {
if (shiftKeyDown()) {
state.redo();
} else {
state.undo();
}
}
if (keyPressed("Y") && ctrlKeyDown()) {
state.redo();
} }
} }

View File

@ -3,7 +3,8 @@ import {
frame, frame,
clearScreen, clearScreen,
} from "./window.ts"; } from "./window.ts";
import { codeSheet } from "./sheet.ts"; import { runCode } from "./runcode.ts";
import { getCodeSheet } from "./sheet.ts";
import { refreshKeyboard, keyPressed, K } from "./keyboard.ts"; import { refreshKeyboard, keyPressed, K } from "./keyboard.ts";
import { repl, resetRepl } from "./repl.ts"; import { repl, resetRepl } from "./repl.ts";
import { addToContext } from "./runcode.ts"; import { addToContext } from "./runcode.ts";
@ -17,7 +18,7 @@ let mode: "play" | "edit" | "repl" = "repl";
addToContext("play", () => { addToContext("play", () => {
mode = "play"; mode = "play";
game = codeSheet(0); game = runCode(getCodeSheet(0));
game.init(); game.init();
}); });

View File

@ -1,5 +1,5 @@
import { getCart } from "./cart.ts"; import { getCart } from "./cart.ts";
import { runCode, addToContext } from "./runcode.ts"; // import { runCode, addToContext } from "./runcode.ts";
// "code" | "spritesheet" | "map" | "sfx" | "patterns" | "fonts" // "code" | "spritesheet" | "map" | "sfx" | "patterns" | "fonts"
export type Sheet = { export type Sheet = {
@ -20,12 +20,20 @@ export const setSheet = (n: number, type: SheetType, value: any) => {
return getCart()[n] = {sheet_type: type, value}; return getCart()[n] = {sheet_type: type, value};
} }
export const codeSheet = (sheet: number) => { export const getCodeSheet = (sheet: number) => {
const {sheet_type, value} = getSheet(sheet); const {sheet_type, value} = getSheet(sheet);
if (sheet_type !== "code") { if (sheet_type !== "code") {
throw "Trying to run a non-code sheet as code." throw "Trying to use a non-code sheet as code."
} }
return runCode(value); return value;
} }
addToContext("code_sheet", codeSheet); export const getSpriteSheet = (sheet: number) => {
const {sheet_type, value} = getSheet(sheet);
if (sheet_type !== "spritesheet") {
throw "Trying to use a non-sprite sheet as a spritesheet."
}
return value;
}
// addToContext("code", codeSheet);