Created
December 14, 2022 03:59
-
-
Save amw-zero/0996d24ac5f9ccf4d89bffcb28e51dba to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { assert } from "https://deno.land/std@0.149.0/testing/asserts.ts"; | |
import fc from 'https://cdn.skypack.dev/fast-check'; | |
type BufferPosition = { | |
x: number; | |
y: number; | |
} | |
type ContentIndex = { | |
x: number; | |
y: number; | |
} | |
type TextContent = string[][]; | |
type TextBuffer = string[][]; | |
function contentToBuffer(content: TextContent, lineWidth: number): TextBuffer { | |
const buffer: string[][] = []; | |
content.forEach(line => { | |
const numBufferLines = Math.ceil(line.length / lineWidth); | |
for(let i = 0; i < numBufferLines; i++) { | |
const lineOffset = i * lineWidth; | |
buffer.push(line.slice(lineOffset, lineOffset + lineWidth)); | |
} | |
}); | |
return buffer; | |
} | |
function positionToContentIdx(pos: BufferPosition, content: TextContent, lineWidth: number): ContentIndex { | |
let bufferX = 0; | |
let bufferY = 0; | |
let contentX = 0; | |
let contentY = 0; | |
let contentIdx: ContentIndex | null = null; | |
content.forEach(line => { | |
line.forEach(_ => { | |
if (bufferX === pos.x && bufferY === pos.y) { | |
contentIdx = { | |
x: contentX, | |
y: contentY, | |
} | |
return | |
} else if (bufferX === lineWidth - 1) { | |
bufferX = 0; | |
bufferY += 1; | |
contentX += 1; | |
} else { | |
bufferX += 1; | |
contentX += 1; | |
} | |
}); | |
if (contentIdx !== null) { | |
return | |
} | |
bufferX = 0; | |
bufferY += 1; | |
contentX = 0; | |
contentY += 1; | |
}); | |
if (contentIdx !== null) { | |
return contentIdx; | |
} | |
console.error("Unable to find content index for position!"); | |
return { | |
x: 0, | |
y: 0 | |
} | |
} | |
class TextEdit { | |
lineWidth; | |
content: TextContent; | |
cursor: BufferPosition; | |
constructor(lineWidth: number = 80, content: TextContent = [[]], cursor: BufferPosition = { x: 0, y: 0 }) { | |
this.lineWidth = lineWidth, this.content = content, this.cursor = cursor; | |
} | |
Type(char: string) { | |
if (char === "\n") { | |
this.content.push([]); | |
this.cursor.x = 0; | |
this.cursor.y += 1; | |
return; | |
} | |
this.content[this.content.length - 1].push(char); | |
if (this.cursor.x === this.lineWidth) { | |
this.cursor.x = 0; | |
this.cursor.y += 1; | |
} else { | |
this.cursor.x += 1; | |
} | |
} | |
Click(pos: BufferPosition) { | |
this.cursor = pos; | |
} | |
Delete() { | |
const contentIdx = positionToContentIdx(this.cursor, this.content, this.lineWidth); | |
const line = this.content[contentIdx.y]; | |
line.splice(contentIdx.x, 1); | |
} | |
Render() { | |
return contentToBuffer(this.content, this.lineWidth); | |
} | |
} | |
Deno.test("Line widths are always preserved", (_) => { | |
const contentGen = fc.array(fc.array(fc.char())); | |
const lineWidthGen = fc.integer({ min: 1, max: 160 }); | |
return fc.assert( | |
fc.property(contentGen, lineWidthGen, (content: TextContent, lineWidth: number) => { | |
const textEdit = new TextEdit(lineWidth, content); | |
const buffer = textEdit.Render(); | |
assert(buffer.every(line => line.length <= lineWidth)); | |
}) | |
); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment