diff --git a/index.html b/index.html
index c3072e6..d0a9fcd 100644
--- a/index.html
+++ b/index.html
@@ -17,7 +17,11 @@
diff --git a/src/app.ts b/src/app.ts
index 0c08052..b0722b7 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,4 +1,5 @@
import { CreateKeyboardListeners } from './input/keyboard'
+import { Wush } from './shell/Wush'
import { Terminal } from './terminal/Terminal'
import { EventBroadcaster } from './utils/EventBroadcaster'
@@ -8,11 +9,12 @@ const init = () => {
// creates keyboard listeners for the local event broadcaster
CreateKeyboardListeners(
- (key: string) => localBroadcaster.emit('keydown', key),
- (key: string) => localBroadcaster.emit('keyup', key),
+ (key: string, isCharacter: boolean) => localBroadcaster.emit('keydown', key, isCharacter),
+ (key: string, isCharacter: boolean) => localBroadcaster.emit('keyup', key, isCharacter),
)
- const terminal = new Terminal()
+ const terminal = new Terminal(localBroadcaster)
+ terminal.LoadShell(new Wush(localBroadcaster, terminal))
}
init()
diff --git a/src/init.ts b/src/init.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/input/keyboard.ts b/src/input/keyboard.ts
index a87369e..68c116b 100644
--- a/src/input/keyboard.ts
+++ b/src/input/keyboard.ts
@@ -1,5 +1,15 @@
// creates keyboard listeners
-export const CreateKeyboardListeners = (onKeyDown: (key: string) => void, onKeyUp: (key: string) => void) => {
- document.addEventListener('keydown', event => onKeyDown(event.key))
- document.addEventListener('keyup', event => onKeyUp(event.key))
+export const CreateKeyboardListeners = (
+ onKeyDown: (key: string, isCharacter: boolean) => void,
+ onKeyUp: (key: string, isCharacter: boolean) => void,
+) => {
+ document.addEventListener('keydown', event => {
+ onKeyDown(event.key, event.key.length === 1)
+ event.preventDefault()
+ })
+
+ document.addEventListener('keyup', event => {
+ onKeyUp(event.key, event.key.length === 1)
+ event.preventDefault()
+ })
}
diff --git a/src/shell/Shell.ts b/src/shell/Shell.ts
new file mode 100644
index 0000000..ebbd99d
--- /dev/null
+++ b/src/shell/Shell.ts
@@ -0,0 +1,15 @@
+import type { Terminal } from '../terminal/Terminal'
+import type { EventBroadcaster } from '../utils/EventBroadcaster'
+
+export abstract class Shell {
+ broadcaster: EventBroadcaster
+ terminal: Terminal
+
+ constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
+ this.broadcaster = broadcaster
+ this.terminal = terminal
+ }
+
+ abstract Init(): void
+ abstract HandleKeyInput(key: string, isCharacter: boolean): void
+}
diff --git a/src/shell/Wush.ts b/src/shell/Wush.ts
new file mode 100644
index 0000000..ba35f1f
--- /dev/null
+++ b/src/shell/Wush.ts
@@ -0,0 +1,42 @@
+// Web-Uno Shell
+// the best name I could come up with lmao
+
+import { Terminal } from '../terminal/Terminal'
+import type { EventBroadcaster } from '../utils/EventBroadcaster'
+import { Shell } from './Shell'
+
+export class Wush extends Shell {
+ constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
+ super(broadcaster, terminal)
+ }
+
+ Init() {
+ this.broadcaster.on('keydown', (key: string, isCharacter: boolean) =>
+ this.HandleKeyInput(key, isCharacter),
+ )
+ this.Prompt()
+ }
+
+ Prompt() {
+ this.terminal.Write('hi -> ')
+ }
+
+ HandleKeyInput(key: string, isCharacter: boolean) {
+ console.log(key)
+ console.log(isCharacter)
+
+ if (!isCharacter)
+ switch (key) {
+ case 'Backspace':
+ this.terminal.RemoveCells(1)
+ break
+ case 'Enter':
+ this.Prompt()
+ break
+ }
+ else {
+ this.terminal.Write(key)
+ this.terminal.AdjustCursorPosition(1, 0)
+ }
+ }
+}
diff --git a/src/styles/cursor.scss b/src/styles/cursor.scss
new file mode 100644
index 0000000..db44c03
--- /dev/null
+++ b/src/styles/cursor.scss
@@ -0,0 +1,10 @@
+@use "colors.scss" as colors;
+
+#cursor {
+ top: 0;
+ left: 0;
+ width: 1px;
+ height: 20px;
+ position: absolute;
+ background: colors.$terminal-white;
+}
diff --git a/src/styles/terminal.scss b/src/styles/terminal.scss
index 80cef7a..7e53580 100644
--- a/src/styles/terminal.scss
+++ b/src/styles/terminal.scss
@@ -1,5 +1,7 @@
@use "colors.scss" as colors;
+@forward "cursor.scss";
+
#terminal {
background: colors.$terminal-background;
color: colors.$terminal-white;
@@ -13,9 +15,16 @@
p {
margin: 0;
height: fit-content;
+ word-wrap: break-word;
+ text-wrap: nowrap;
span {
display: inline-block;
}
}
+
+ .line {
+ background: red;
+ width: fit-content
+ }
}
diff --git a/src/terminal/CursorProperties.ts b/src/terminal/CursorProperties.ts
new file mode 100644
index 0000000..f1de570
--- /dev/null
+++ b/src/terminal/CursorProperties.ts
@@ -0,0 +1,6 @@
+export type CursorPosition = {
+ col: number,
+ row: number,
+}
+
+export type CursorStyle = 'bar' | 'underline' | 'cell' | 'cell_hollow'
diff --git a/src/terminal/Terminal.ts b/src/terminal/Terminal.ts
index 2dd98ef..f7b2874 100644
--- a/src/terminal/Terminal.ts
+++ b/src/terminal/Terminal.ts
@@ -1,49 +1,122 @@
+import type { Shell } from '../shell/Shell'
+import type { EventBroadcaster } from '../utils/EventBroadcaster'
import sqs from '../utils/sqs'
+import type { CursorPosition, CursorStyle } from './CursorProperties'
export class Terminal {
private terminal: HTMLElement
+ private cursor: HTMLElement
private cellHeight = 16
private cellWidth = 8
- constructor() {
+ private cursorPosition: CursorPosition = {
+ col: 0,
+ row: 0,
+ }
+
+ private shell?: Shell
+
+ constructor(broadcaster: EventBroadcaster) {
this.terminal = sqs('#terminal')
+ this.cursor = sqs('#cursor')
+
+ this.SetCursorStyle('bar')
this.ResetCellSize()
-
this.NewPage()
- this.AppendLine('idk./home/idk -> ')
+ }
+
+ LoadShell(shell: Shell) {
+ this.shell = shell
+ this.shell.Init()
}
NewPage() {
this.terminal.innerHTML = ''
+ this.SetCursorPosition(0, 0)
}
- AppendLine(line: string) {
+ AppendLine(content: string) {
+ this.AdjustCursorPosition(0, 1)
+
const paragraph = document.createElement('p')
paragraph.style.height = `${this.cellHeight}px`
paragraph.style.lineHeight = `${this.cellHeight}px`
-
- line.split('').forEach((char) => {
- const span = document.createElement('span')
- span.textContent = char.replace(' ', '\u00A0')
- span.style.width = `${this.cellWidth}px`
- span.style.height = `${this.cellHeight}px`
- span.style.lineHeight = `${this.cellHeight}px`
- paragraph.appendChild(span)
- })
+ paragraph.id = `line-${this.cursorPosition.row}`
+ paragraph.className = 'line'
this.terminal.appendChild(paragraph)
+ this.AppendCells(content)
+ }
+
+ Write(text: string) {
+ let adjustment = 0
+
+ text.split('').forEach(char => {
+ console.log(this.cursorPosition)
+
+ this.AdjustCursorPosition(adjustment, 0)
+ adjustment = 1
+
+ this.SetCell(char)
+ })
+ }
+
+ SetCell(char: string) {
+ const id = `#cell-${this.cursorPosition.row}_${this.cursorPosition.col}`
+
+ try {
+ sqs(id)
+ } catch (_) {
+ const cell = document.createElement('span')
+ cell.id = id
+ cell.style.width = `${this.cellWidth}px`
+ cell.style.height = `${this.cellHeight}px`
+ cell.style.lineHeight = `${this.cellHeight}px`
+
+ const lineId = `#line-${this.cursorPosition.row}`
+
+ try {
+ sqs(lineId)
+ } catch (_) {
+ // todo: create as many lines as we need
+ for (let i = sqs('#terminal'); ;) {}
+ }
+ } finally {
+ sqs(id).innerText = char[0]
+ }
+ }
+
+ AppendCells(content: string) {
+ const paragraph = sqs(`#line-${this.cursorPosition.row}`)
+
+ content.split('').forEach(char => {
+ paragraph.textContent += char.replace(' ', '\u00A0')
+ this.AdjustCursorPosition(1, 0)
+ })
+ }
+
+ RemoveCells(amount: number) {
+ const paragraph = sqs(`#line-${this.cursorPosition.row}`)
+ paragraph.innerText = paragraph.innerText.slice(
+ 0,
+ Math.max(paragraph.innerText.length - amount, 0),
+ )
+
+ this.AdjustCursorPosition(-amount, 0)
}
ResetCellSize() {
// dynamically determine cell size using dom
const cell = document.createElement('span')
- cell.textContent = '\\'
+ cell.textContent = 'A'
this.terminal.appendChild(cell)
this.cellWidth = cell.scrollWidth
this.cellHeight = cell.scrollHeight
+
+ cell.remove()
}
GetHeightCells(): number {
@@ -53,4 +126,39 @@ export class Terminal {
GetWidthCells(): number {
return this.terminal.clientWidth / this.cellWidth
}
+
+ UpdateCursor() {
+ this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth}px`
+ this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight}px`
+ }
+
+ GetCursorPosition(): CursorPosition {
+ return this.cursorPosition
+ }
+
+ SetCursorPosition(col: number, row: number) {
+ this.cursorPosition.col = Math.min(
+ this.GetWidthCells(),
+ Math.max(col, 0),
+ )
+
+ this.cursorPosition.row = Math.min(
+ this.GetHeightCells(),
+ Math.max(row, 0),
+ )
+
+ this.UpdateCursor()
+ }
+
+ AdjustCursorPosition(col: number, row: number) {
+ this.SetCursorPosition(this.cursorPosition.col + col, this.cursorPosition.row + row)
+ }
+
+ SetCursorStyle(style: CursorStyle) {
+ switch (style) {
+ default:
+ this.cursor.style.width = '1px'
+ this.cursor.style.height = `${this.cellHeight}px`
+ }
+ }
}