feat: complete shell revamp

This commit is contained in:
binekrasik
2026-05-19 21:42:14 +02:00
parent 4a484dd546
commit 1d00cf6deb
6 changed files with 1132 additions and 217 deletions

View File

@@ -8,6 +8,7 @@ import { Lsprg } from '../../program/Lsprg'
import { Info } from '../../program/Info'
import { Program } from '../../program/Program'
import { Terminal } from '../../terminal/Terminal'
import type { CursorPosition } from '../../terminal/CursorProperties'
import { EventBroadcaster } from '../../utils/EventBroadcaster'
import { SimpleStream } from '../../utils/SimpleStream'
import { Shell } from '../Shell'
@@ -34,9 +35,11 @@ export class Wush extends Shell {
// buffer
private buffer: string[] = []
private bufferPos: number = 0
private promptStart: CursorPosition = { col: 0, row: 0 }
private history: string[] = []
private historyPos: number = 0
private historyIndex: number | null = null
private historyDraft: string = ''
// exec stuff
/**
@@ -131,6 +134,11 @@ export class Wush extends Shell {
Prompt() {
this.terminal.Write(`user in ${this.workingDirectory.GetPath()} -> `)
this.promptStart = this.terminal.GetCursorPosition()
this.buffer = []
this.bufferPos = 0
this.historyIndex = null
this.historyDraft = ''
}
/**
@@ -150,11 +158,41 @@ export class Wush extends Shell {
}
WriteEscapedString(data: string) {
let buf = data.split('')
buf.forEach((char) => {
if (!this.ProcessSimpleControlCode(char))
this.terminal.Write(char)
})
let i = 0
let buffer = ''
const flush = () => {
if (buffer.length === 0)
return
this.terminal.Write(buffer)
buffer = ''
}
while (i < data.length) {
const char = data[i]
if (char === '\x1b') {
flush()
const nextIndex = this.ProcessEscapeSequence(data, i)
if (nextIndex !== null) {
i = nextIndex
continue
}
}
if (char === '\f' || char === '\n') {
flush()
this.ProcessSimpleControlCode(char)
i++
continue
}
buffer += char
i++
}
flush()
}
PushToBuffer(text: string) {
@@ -458,13 +496,50 @@ export class Wush extends Shell {
}
}
// todo: actual processing
ProcessAdvancedControlCode(complex: string): boolean {
if (!complex.match(/\\(.*;)/gm))
return false
ProcessEscapeSequence(data: string, index: number): number | null {
if (data[index] !== '\x1b' || data[index + 1] !== '[')
return null
return true
// const code = matches[]
let i = index + 2
let params = ''
while (i < data.length) {
const char = data[i]
if ((char >= '0' && char <= '9') || char === ';') {
params += char
i++
continue
}
this.ProcessCsiControlCode(char, params)
return i + 1
}
return data.length
}
ProcessCsiControlCode(command: string, params: string): boolean {
const values = params.length === 0
? []
: params.split(';').map(value => Number.parseInt(value, 10))
switch (command) {
case 'H':
case 'f': {
const row = Number.isFinite(values[0]) ? values[0] : 1
const col = Number.isFinite(values[1]) ? values[1] : 1
this.terminal.SetCursorPosition(Math.max(col - 1, 0), Math.max(row - 1, 0))
return true
}
case 'J': {
this.terminal.NewPage()
return true
}
default:
return false
}
}
HandleKeyInput(key: string, isCharacter: boolean) {
@@ -473,57 +548,43 @@ export class Wush extends Shell {
switch (key) {
case 'ArrowLeft':
if (this.bufferPos > 0) {
this.terminal.MoveCursor(-1, 0)
this.MoveBufferPos(-1)
this.bufferPos -= 1
this.SyncCursorToBuffer()
}
break
case 'ArrowRight':
if (this.bufferPos < this.buffer.length) {
this.terminal.MoveCursor(1, 0)
this.MoveBufferPos(1)
this.bufferPos += 1
this.SyncCursorToBuffer()
}
break
case 'ArrowUp':
if (this.historyPos < this.history.length) {
if (this.historyPos === 0)
this.history.splice(0, 0, this.buffer.join(''))
this.historyPos++
console.log(this.historyPos)
console.log(this.history[this.historyPos])
}
this.NavigateHistory(-1)
break
case 'ArrowDown':
if (this.historyPos > 0) {
this.historyPos--
if (this.historyPos === 0)
this.history.splice(0, 1)
console.log(this.historyPos)
console.log(this.history[this.historyPos])
}
this.NavigateHistory(1)
break
case 'Backspace':
// don't erase anything if there's nothing left in the buffer
if (this.bufferPos === 0)
break
this.ExitHistoryNavigation()
this.terminal.RemoveCell()
this.RemoveCharFromBuffer(1, this.bufferPos)
// this.buffer.splice(this.bufferPos, 1)
this.terminal.MoveCursor(-1, 0)
this.SyncCursorToBuffer()
break
case 'Delete':
if (this.bufferPos >= this.buffer.length || this.buffer.length <= 0)
break
this.bufferPos++
this.terminal.MoveCursor(1, 0)
this.ExitHistoryNavigation()
this.bufferPos += 1
this.SyncCursorToBuffer()
this.terminal.RemoveCell()
this.RemoveCharFromBuffer(1, this.bufferPos)
this.terminal.MoveCursor(-1, 0)
this.SyncCursorToBuffer()
break
case 'Enter':
// send the buffer to stdin if an exec is running
@@ -533,7 +594,12 @@ export class Wush extends Shell {
} else {
// "execute" the buffer
this.terminal.MoveCursor(0, 1, { x: true, y: false })
this.history.splice(0, 0, this.buffer.join(''))
const command = this.buffer.join('')
if (command.length > 0)
this.history.push(command)
this.historyIndex = null
this.historyDraft = ''
this.ExecuteBuffer()
}
@@ -543,12 +609,99 @@ export class Wush extends Shell {
break
}
} else {
this.historyPos = 0
this.ExitHistoryNavigation()
// push the character into the buffer
this.terminal.InsertCell(key)
this.PushToBuffer(key)
this.terminal.MoveCursor(1, 0)
this.InsertText(key)
}
}
private GetWrapWidth(): number {
const width = Math.floor(this.terminal.GetWidthCells())
if (!Number.isFinite(width) || width <= 0)
return 1
return Math.max(1, width)
}
private GetCursorPositionForBufferPos(pos: number): CursorPosition {
const width = this.GetWrapWidth()
const absoluteIndex = this.promptStart.col + Math.max(pos, 0)
const rowOffset = Math.floor(absoluteIndex / width)
const col = absoluteIndex % width
return { row: this.promptStart.row + rowOffset, col }
}
private SyncCursorToBuffer() {
const position = this.GetCursorPositionForBufferPos(this.bufferPos)
this.terminal.SetCursorPosition(position.col, position.row)
}
private ClearBuffer() {
if (this.buffer.length === 0)
return
if (this.bufferPos < this.buffer.length) {
this.bufferPos = this.buffer.length
this.SyncCursorToBuffer()
}
while (this.bufferPos > 0) {
this.terminal.RemoveCell()
this.RemoveCharFromBuffer(1, this.bufferPos)
this.SyncCursorToBuffer()
}
}
private InsertText(text: string) {
if (text.length === 0)
return
for (const char of text) {
this.terminal.InsertCell(char)
this.PushToBuffer(char)
this.SyncCursorToBuffer()
}
}
private SetBuffer(text: string) {
this.ClearBuffer()
this.InsertText(text)
}
private ExitHistoryNavigation() {
this.historyIndex = null
this.historyDraft = ''
}
private NavigateHistory(direction: -1 | 1) {
if (this.history.length === 0)
return
if (direction === -1) {
if (this.historyIndex === null) {
this.historyDraft = this.buffer.join('')
this.historyIndex = this.history.length - 1
} else if (this.historyIndex > 0) {
this.historyIndex -= 1
} else {
return
}
this.SetBuffer(this.history[this.historyIndex])
return
}
if (this.historyIndex === null)
return
if (this.historyIndex < this.history.length - 1) {
this.historyIndex += 1
this.SetBuffer(this.history[this.historyIndex])
return
}
this.historyIndex = null
this.SetBuffer(this.historyDraft)
}
}