diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 864592e7..d8f92ac8 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -26,6 +26,7 @@ module.exports = { 'plugin:react/recommended', 'plugin:react-hooks/recommended', // 'plugin:@react-three/recommended', + 'plugin:valtio/recommended', 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. // This will display prettier errors as ESLint errors. diff --git a/.nvmrc b/.nvmrc index 0828ab79..9a2a0e21 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 \ No newline at end of file +v20 diff --git a/package.json b/package.json index b851532d..fe8dd677 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", + "eslint-plugin-valtio": "^0.6.3", "fast-json-patch": "^3.1.1", "is-hotkey": "^0.2.0", "jest": "^29.7.0", diff --git a/yarn.lock b/yarn.lock index d7e537e3..39a09aa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2736,6 +2736,11 @@ eslint-plugin-react@^7.34.1: semver "^6.3.1" string.prototype.matchall "^4.0.10" +eslint-plugin-valtio@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/eslint-plugin-valtio/-/eslint-plugin-valtio-0.6.3.tgz#23b2855d682d225fc614fde4f619c23a47839f5e" + integrity sha512-s1Ttxcb70pzW5NU28DQ+bUy84ohhQApzuP3ZZeZF7y3nQJlCjh3KRceXvxhf9p+1fTBXcl3psLjNVchk8NetAw== + eslint-scope@^7.2.2: version "7.2.2" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" diff --git a/zss/gadget/components/tape.tsx b/zss/gadget/components/tape.tsx index 74249e8e..9acf50cd 100644 --- a/zss/gadget/components/tape.tsx +++ b/zss/gadget/components/tape.tsx @@ -114,14 +114,23 @@ export function TapeConsole() { // input & selection const visiblerange = width - 3 const inputindex = (height - 1) * width + 1 - const hasselection = ispresent(selection) - const ii1 = hasselection ? Math.min(selection, cursor) : cursor - const ii2 = hasselection ? Math.max(selection, cursor) : cursor - const iic = ii2 - ii1 - const inputstate = inputbuffer[inputbufferindex] + + let ii1 = cursor + let ii2 = cursor + let hasselection = false + if (ispresent(selection)) { + ii1 = Math.min(cursor, selection) + ii2 = Math.max(cursor, selection) + if (cursor !== selection) { + --ii2 + hasselection = true + } + } + + const iic = ii2 - ii1 + 1 const inputstateselected = hasselection - ? inputstate.substring(ii1, ii2 + 1) + ? inputstate.substring(ii1, ii2) : inputstate // draw input line @@ -130,7 +139,9 @@ export function TapeConsole() { // draw selection if (hasselection) { - applycolortoindexes(inputindex + ii1, inputindex + ii2, 15, 8, context) + const p1 = inputindex + ii1 + const p2 = inputindex + ii2 + applycolortoindexes(p1, p2, 15, 8, context) } // draw cursor @@ -146,6 +157,7 @@ export function TapeConsole() { count, insert, ) + return inputbuffer[inputbufferindex] } function trackselection(index: number | undefined) { @@ -161,7 +173,7 @@ export function TapeConsole() { function deleteselection() { setcursor(ii1) setselection(undefined) - inputstatesetsplice(ii1, iic) + return inputstatesetsplice(ii1, iic) } return ( @@ -176,14 +188,21 @@ export function TapeConsole() { tapesetmode(mods.shift ? -1 : 1)} MOVE_UP={() => { - setinputbufferindex( - clamp(inputbufferindex + 1, 0, inputbuffer.length - 1), - ) + const ir = inputbuffer.length - 1 + const index = clamp(inputbufferindex + 1, 0, ir) + setselection(undefined) + setinputbufferindex(index) + setcursor((inputbuffer[index] ?? '').length) }} MOVE_DOWN={() => { - setinputbufferindex( - clamp(inputbufferindex - 1, 0, inputbuffer.length - 1), - ) + // TODO: we should actually be copying history items into an active buffer on edit ? + // so instead of overwriting the historyical entry + // we copy to our mutable entry before continuting + const ir = inputbuffer.length - 1 + const index = clamp(inputbufferindex - 1, 0, ir) + setselection(undefined) + setinputbufferindex(index) + setcursor((inputbuffer[index] ?? '').length) }} MOVE_LEFT={(mods) => { trackselection(mods.shift ? cursor : undefined) @@ -195,12 +214,17 @@ export function TapeConsole() { }} OK_BUTTON={() => { const invoke = hasselection ? inputstateselected : inputstate - setinputbuffer([invoke, ...inputbuffer]) - setinputbufferindex(0) - setcursor(0) - setselection(undefined) - setinputbuffer(['', ...inputbuffer]) - vm_cli('tape', invoke, gadgetstategetplayer()) + if (invoke.length) { + setinputbuffer([ + '', + invoke, + ...inputbuffer.slice(1).filter((item) => item !== invoke), + ]) + setinputbufferindex(0) + setcursor(0) + setselection(undefined) + vm_cli('tape', invoke, gadgetstategetplayer()) + } }} keydown={(event) => { const { key } = event @@ -249,7 +273,7 @@ export function TapeConsole() { if (hasselection) { inputstatesetsplice(ii1, iic, text) setselection(undefined) - setcursor(ii2) + setcursor(ii1 + text.length) } else { inputstatesetsplice(cursor, 0, text) setcursor(cursor + text.length) @@ -269,18 +293,18 @@ export function TapeConsole() { } } else if (mods.alt) { // no-op ?? - could this shove text around when you have selection ?? - // or jump by 10 ? + // or jump by 10 or by word ?? } else if ( key.length === 1 && inputstate.length < visiblerange ) { if (hasselection) { - setcursor(ii2) + inputstatesetsplice(ii1, iic, key) setselection(undefined) - inputstatesetsplice(ii1, ii2, key) + setcursor(ii1 + 1) } else { + inputstatesetsplice(cursor, 0, key) setcursor(cursor + 1) - inputstatesetsplice(cursor, cursor, key) } } break diff --git a/zss/lang/ast.ts b/zss/lang/ast.ts index ca6601d7..93aa43bb 100644 --- a/zss/lang/ast.ts +++ b/zss/lang/ast.ts @@ -15,7 +15,7 @@ function addRange(node: CodeNode | undefined): OffsetRange | undefined { } const offsets: (OffsetRange | undefined)[] = [ - { start: node.startOffset, end: node.endOffset || 0 }, + { start: node.startOffset, end: node.endOffset ?? 0 }, ] Object.keys(node).forEach((name) => { @@ -33,10 +33,10 @@ function addRange(node: CodeNode | undefined): OffsetRange | undefined { node.range = { start: Math.min( - ...offsets.filter((item) => item).map((item) => item?.start || 0), + ...offsets.filter((item) => item).map((item) => item?.start ?? 0), ), end: Math.max( - ...offsets.filter((item) => item).map((item) => item?.end || 0), + ...offsets.filter((item) => item).map((item) => item?.end ?? 0), ), } diff --git a/zss/lang/transformer.ts b/zss/lang/transformer.ts index 31aff140..16974d67 100644 --- a/zss/lang/transformer.ts +++ b/zss/lang/transformer.ts @@ -26,8 +26,8 @@ export function write( chunks: (string | SourceNode)[] | SourceNode | string, ) { return new SourceNode( - ast.startLine || 1, - ast.startColumn || 1, + ast.startLine ?? 1, + ast.startColumn ?? 1, GENERATED_FILENAME, chunks, ) diff --git a/zss/lang/visitor.ts b/zss/lang/visitor.ts index fbc81d7f..66b9ab05 100644 --- a/zss/lang/visitor.ts +++ b/zss/lang/visitor.ts @@ -77,7 +77,7 @@ function asIToken(thing: CstNode | CstElement): IToken { function asList(thing: ScriptVisitor, node: CstNode[] | undefined): CodeNode[] { return ( - node?.map((item) => thing.visit(item)).filter((item) => item) || [] + node?.map((item) => thing.visit(item)).filter((item) => item) ?? [] ).flat() } @@ -257,12 +257,12 @@ function getLocation(obj: CstChildrenDictionary): CstNodeLocation { }) return { - startLine: Math.min(...locations.map((item) => item.startLine || 1)), - startColumn: Math.min(...locations.map((item) => item.startColumn || 1)), - startOffset: Math.min(...locations.map((item) => item.startOffset || 1)), - endLine: Math.max(...locations.map((item) => item.endLine || 1)), - endColumn: Math.max(...locations.map((item) => item.endColumn || 1)), - endOffset: Math.max(...locations.map((item) => item.endOffset || 1)), + startLine: Math.min(...locations.map((item) => item.startLine ?? 1)), + startColumn: Math.min(...locations.map((item) => item.startColumn ?? 1)), + startOffset: Math.min(...locations.map((item) => item.startOffset ?? 1)), + endLine: Math.max(...locations.map((item) => item.endLine ?? 1)), + endColumn: Math.max(...locations.map((item) => item.endColumn ?? 1)), + endOffset: Math.max(...locations.map((item) => item.endOffset ?? 1)), } } diff --git a/zss/memory/atomics.ts b/zss/memory/atomics.ts index d892cdfe..06d640ac 100644 --- a/zss/memory/atomics.ts +++ b/zss/memory/atomics.ts @@ -70,10 +70,12 @@ export function listelementsbykind( // console.info('no match on name', name) return false } + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if (ispresent(color) && boardelementcolor(element) !== color) { // console.info('no match on color', color) return false } + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if (ispresent(bg) && boardelementbg(element) !== bg) { // console.info('no match on bg', bg) return false diff --git a/zss/terminal/crt.tsx b/zss/terminal/crt.tsx index a5db6f1a..cfc0150d 100644 --- a/zss/terminal/crt.tsx +++ b/zss/terminal/crt.tsx @@ -90,7 +90,7 @@ void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) float phase = time + cos(uv.x + uv.y); float slowband = (cos(uv.x + uv.y - phase) + 1.0) / 2.0; float fastband = (cos(uv.y + time * 0.37) + 1.0) / 2.0; - float blankdmix = 0.5 - + float blankdmix = 0.3 - pow(slowband, viewheight * 0.01) * 0.05 - pow(fastband, viewheight * 64.0) * 0.2 - pow(fastband, viewheight * 128.0) * 0.1;