feat: complete shell revamp
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user