feat: working filesystem
This commit is contained in:
8
.zed/settings.json
Normal file
8
.zed/settings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
// Folder-specific settings
|
||||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||
{
|
||||
"tab_size": 4,
|
||||
"format_on_save": "off",
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export const SetCurrentTerminal = (terminal: Terminal) => CurrentTerminal = term
|
||||
export const GetCurrentTerminal = (): Terminal => CurrentTerminal
|
||||
|
||||
// Initializes the app
|
||||
const init = () => {
|
||||
const init = async () => {
|
||||
const localBroadcaster = new EventBroadcaster()
|
||||
|
||||
// creates keyboard listeners for the local event broadcaster
|
||||
@@ -51,5 +51,5 @@ const init = () => {
|
||||
)
|
||||
|
||||
const terminal = new Terminal()
|
||||
terminal.LoadShell(new Wush(localBroadcaster, terminal))
|
||||
await terminal.LoadShell(new Wush(localBroadcaster, terminal))
|
||||
}
|
||||
|
||||
266
src/fs/Item.ts
266
src/fs/Item.ts
@@ -1,131 +1,211 @@
|
||||
import { GetWebfsDatabase } from "../app"
|
||||
|
||||
interface ItemPayload {
|
||||
isDirectory: boolean
|
||||
executable: boolean
|
||||
data: string | null
|
||||
children: string[]
|
||||
}
|
||||
|
||||
export class Item {
|
||||
private path: string
|
||||
private isDirectory: boolean = false
|
||||
private executable: boolean = false
|
||||
private data: string | null = null
|
||||
|
||||
private children: string[] = []
|
||||
|
||||
constructor(path: string, data?: string) {
|
||||
private constructor(path: string) {
|
||||
this.path = this.normalizePath(path)
|
||||
}
|
||||
|
||||
if (this.Exists()) {
|
||||
this.load()
|
||||
// -------------------------------------------------------------------------
|
||||
// Static factories
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Opens an existing file or prepares a new one in memory.
|
||||
* Call Create() afterwards if the item does not yet exist.
|
||||
* Use openDir() to prepare a directory instead.
|
||||
* Throws if the path exists but is a directory.
|
||||
* Note: initialData is ignored (with a warning) if the file already exists.
|
||||
*/
|
||||
static async open(path: string, initialData: string | null = null): Promise<Item> {
|
||||
const item = new Item(path)
|
||||
if (await item.Exists()) {
|
||||
await item.load()
|
||||
if (item.isDirectory) {
|
||||
throw new Error(`Path is a directory, not a file: ${path}`)
|
||||
}
|
||||
if (initialData !== null) {
|
||||
console.warn(`Item.open(): initialData ignored because file already exists: ${path}`)
|
||||
}
|
||||
} else {
|
||||
this.data = data !== undefined ? data : null
|
||||
this.isDirectory = data === undefined
|
||||
item.data = initialData
|
||||
item.isDirectory = false
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
static get Root(): Item {
|
||||
/**
|
||||
* Opens an existing directory or prepares a new one in memory.
|
||||
* Call Create() afterwards if the item does not yet exist.
|
||||
* Throws if the path exists but is a file.
|
||||
*/
|
||||
static async openDir(path: string): Promise<Item> {
|
||||
const item = new Item(path)
|
||||
if (await item.Exists()) {
|
||||
await item.load()
|
||||
if (!item.isDirectory) {
|
||||
throw new Error(`Path is a file, not a directory: ${path}`)
|
||||
}
|
||||
} else {
|
||||
item.data = null
|
||||
item.isDirectory = true
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root directory, creating it if it doesn't exist yet.
|
||||
*/
|
||||
static async Root(): Promise<Item> {
|
||||
const root = new Item('/')
|
||||
if (!root.Exists()) {
|
||||
root.Create()
|
||||
if (!(await root.Exists())) {
|
||||
root.isDirectory = true
|
||||
await root.Create()
|
||||
} else {
|
||||
await root.load()
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
Create(): void {
|
||||
if (this.Exists()) throw new Error("File already exists")
|
||||
// -------------------------------------------------------------------------
|
||||
// Public API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
this.save()
|
||||
async Create(): Promise<void> {
|
||||
if (await this.Exists()) throw new Error(`File already exists: ${this.path}`)
|
||||
|
||||
await this.save()
|
||||
|
||||
if (this.path !== '/') {
|
||||
const parentPath = this.getParentPath()
|
||||
const parent = new Item(parentPath)
|
||||
const parent = await Item.openDir(parentPath)
|
||||
|
||||
// recursively creates parent directories
|
||||
if (!parent.Exists()) {
|
||||
parent.Create()
|
||||
if (!(await parent.Exists())) {
|
||||
await parent.Create()
|
||||
// Reload the parent after recursive Create() to get the
|
||||
// up-to-date children list before we append to it, so we
|
||||
// don't overwrite any children that Create() already added.
|
||||
await parent.load()
|
||||
}
|
||||
|
||||
// adds this item to the parent's children list and saves the parent
|
||||
if (!parent.children.includes(this.path)) {
|
||||
parent.children.push(this.path)
|
||||
parent.save()
|
||||
await parent.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Exists(): boolean {
|
||||
// localStorage.getItem(this.storageKey) !== null
|
||||
const store = GetWebfsDatabase()!
|
||||
async Exists(): Promise<boolean> {
|
||||
const db = GetWebfsDatabase()
|
||||
if (!db) throw new Error("WebFS database is not initialized")
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = db
|
||||
.transaction("webfs", "readonly")
|
||||
.objectStore("webfs")
|
||||
.getKey(this.storageKey)
|
||||
|
||||
return Boolean(store.getKey(this.storageKey))
|
||||
request.onsuccess = () => resolve(request.result !== undefined)
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
IsDirectory(): boolean {
|
||||
return this.isDirectory
|
||||
}
|
||||
|
||||
Append(data: string): void {
|
||||
if (this.isDirectory) {
|
||||
throw new Error("Cannot append data to a directory")
|
||||
}
|
||||
this.data = (this.data || '') + data
|
||||
this.save()
|
||||
async Append(data: string): Promise<void> {
|
||||
if (!(await this.Exists())) throw new Error(`Cannot append to a file that has not been created: ${this.path}`)
|
||||
if (this.isDirectory) throw new Error("Cannot append data to a directory")
|
||||
this.data = (this.data ?? '') + data
|
||||
await this.save()
|
||||
}
|
||||
|
||||
Write(data: string): void {
|
||||
if (this.isDirectory)
|
||||
throw new Error("Cannot write data to a directory")
|
||||
|
||||
async Write(data: string): Promise<void> {
|
||||
if (!(await this.Exists())) throw new Error(`Cannot write to a file that has not been created: ${this.path}`)
|
||||
if (this.isDirectory) throw new Error("Cannot write data to a directory")
|
||||
this.data = data
|
||||
this.save()
|
||||
await this.save()
|
||||
}
|
||||
|
||||
SetExecutable(executable: boolean): void {
|
||||
/**
|
||||
* Sets the executable flag. Only takes effect if the item has already
|
||||
* been created in the filesystem (i.e. Create() has been called).
|
||||
*/
|
||||
async SetExecutable(executable: boolean): Promise<void> {
|
||||
this.executable = executable
|
||||
|
||||
// update the item if it already exists in the filesystem
|
||||
if (this.Exists())
|
||||
this.save()
|
||||
if (await this.Exists()) await this.save()
|
||||
}
|
||||
|
||||
GetPath(): string {
|
||||
return this.path
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns just the filename/directory name, not the full path.
|
||||
*/
|
||||
GetName(): string {
|
||||
return this.path
|
||||
return this.path === '/' ? '/' : this.path.substring(this.path.lastIndexOf('/') + 1)
|
||||
}
|
||||
|
||||
ReadData(): string | null {
|
||||
return this.data
|
||||
}
|
||||
|
||||
List(): Item[] {
|
||||
if (!this.isDirectory)
|
||||
throw new Error(`Not a directory: ${this.path}`)
|
||||
async List(): Promise<Item[]> {
|
||||
if (!this.isDirectory) throw new Error(`Not a directory: ${this.path}`)
|
||||
|
||||
// grab all the children
|
||||
return this.children.map(childPath => new Item(childPath))
|
||||
// Always reload from store to ensure children list is current.
|
||||
await this.load()
|
||||
|
||||
const items = await Promise.all(
|
||||
this.children.map(async childPath => {
|
||||
const child = new Item(childPath)
|
||||
if (!(await child.Exists())) return null
|
||||
await child.load()
|
||||
return child
|
||||
})
|
||||
)
|
||||
return items.filter((item): item is Item => item !== null)
|
||||
}
|
||||
|
||||
Delete(): void {
|
||||
if (!this.Exists()) return
|
||||
async Delete(): Promise<void> {
|
||||
if (!(await this.Exists())) return
|
||||
|
||||
// Reload from store to ensure we have the current state,
|
||||
// particularly the up-to-date children list for directories.
|
||||
await this.load()
|
||||
|
||||
// delete children recursively if this item is a directory
|
||||
if (this.isDirectory) {
|
||||
this.List().forEach(child => child.Delete())
|
||||
const children = await this.List()
|
||||
await Promise.all(children.map(child => child.Delete()))
|
||||
}
|
||||
|
||||
// clear all traces
|
||||
localStorage.removeItem(this.storageKey)
|
||||
await this.remove()
|
||||
|
||||
if (this.path !== '/') {
|
||||
const parent = new Item(this.getParentPath())
|
||||
if (parent.Exists()) {
|
||||
parent.children = parent.children.filter(path => path !== this.path)
|
||||
parent.save()
|
||||
}
|
||||
const parent = await Item.openDir(this.getParentPath())
|
||||
parent.children = parent.children.filter(p => p !== this.path)
|
||||
await parent.save()
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private get storageKey(): string {
|
||||
return this.path
|
||||
}
|
||||
@@ -144,32 +224,90 @@ export class Item {
|
||||
: this.path.substring(0, lastSlashIndex)
|
||||
}
|
||||
|
||||
private db(): IDBDatabase {
|
||||
const db = GetWebfsDatabase()
|
||||
if (!db) throw new Error("WebFS database is not initialized")
|
||||
return db
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the item to the filesystem
|
||||
* Persists this item to IndexedDB.
|
||||
* Enforces that the isDirectory flag cannot be changed after creation.
|
||||
*/
|
||||
private save(): void {
|
||||
const payload = {
|
||||
private async save(): Promise<void> {
|
||||
// Guard against flipping isDirectory on an already-created item.
|
||||
if (await this.Exists()) {
|
||||
const request = this.db()
|
||||
.transaction("webfs", "readonly")
|
||||
.objectStore("webfs")
|
||||
.get(this.storageKey)
|
||||
|
||||
const stored = await new Promise<ItemPayload | undefined>((resolve, reject) => {
|
||||
request.onsuccess = () => resolve(request.result as ItemPayload | undefined)
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
|
||||
if (stored && stored.isDirectory !== this.isDirectory) {
|
||||
throw new Error(
|
||||
`Cannot change isDirectory flag after creation: ${this.path}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const payload: ItemPayload = {
|
||||
isDirectory: this.isDirectory,
|
||||
executable: this.executable,
|
||||
data: this.data,
|
||||
children: this.children,
|
||||
}
|
||||
|
||||
window.localStorage.setItem(this.storageKey, JSON.stringify(payload))
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = this.db()
|
||||
.transaction("webfs", "readwrite")
|
||||
.objectStore("webfs")
|
||||
.put(payload, this.storageKey)
|
||||
|
||||
request.onsuccess = () => resolve()
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data associated with the item from the filesystem
|
||||
* Loads this item's data from IndexedDB into memory.
|
||||
*/
|
||||
private load(): void {
|
||||
const raw = localStorage.getItem(this.storageKey)
|
||||
private load(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = this.db()
|
||||
.transaction("webfs", "readonly")
|
||||
.objectStore("webfs")
|
||||
.get(this.storageKey)
|
||||
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw)
|
||||
request.onsuccess = () => {
|
||||
const parsed = request.result as ItemPayload | undefined
|
||||
if (parsed) {
|
||||
this.isDirectory = parsed.isDirectory
|
||||
this.executable = parsed.executable
|
||||
this.data = parsed.data
|
||||
this.children = parsed.children
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this item's record from IndexedDB.
|
||||
*/
|
||||
private remove(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = this.db()
|
||||
.transaction("webfs", "readwrite")
|
||||
.objectStore("webfs")
|
||||
.delete(this.storageKey)
|
||||
|
||||
request.onsuccess = () => resolve()
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
28
src/program/Cat.ts
Normal file
28
src/program/Cat.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Item } from '../fs/Item'
|
||||
import type { SimpleStream } from '../utils/SimpleStream'
|
||||
import { Program } from './Program'
|
||||
|
||||
export class Cat extends Program {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, __: Item, args: string[]): Promise<number> {
|
||||
let item: Item = await Item.open(args[1])
|
||||
|
||||
if (!(await item.Exists())) {
|
||||
stdout.emit(`cat: error: item ${item.GetPath()} doesn't exist.\n`)
|
||||
return 1
|
||||
}
|
||||
|
||||
if (item.IsDirectory()) {
|
||||
stdout.emit(`cat: error: can't read data from a directory; item ${item.GetPath()} is a directory.\n`)
|
||||
return 2
|
||||
}
|
||||
|
||||
stdout.emit(`${item.ReadData()}`)
|
||||
stdout.emit('[ EOF ]\n')
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
27
src/program/Echo.ts
Normal file
27
src/program/Echo.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Item } from '../fs/Item'
|
||||
import type { SimpleStream } from '../utils/SimpleStream'
|
||||
import { Program } from './Program'
|
||||
|
||||
export class Echo extends Program {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, ___: Item, args: string[]): Promise<number> {
|
||||
let item: Item = await Item.open(args[1])
|
||||
|
||||
if (!(await item.Exists())) {
|
||||
stdout.emit(`echo: error: item ${item.GetPath()} doesn't exist.\n`)
|
||||
return 1
|
||||
}
|
||||
|
||||
if (item.IsDirectory()) {
|
||||
stdout.emit(`echo: error: can't write data to a directory; item ${item.GetPath()} is a directory.\n`)
|
||||
return 2
|
||||
}
|
||||
|
||||
await item.Write(args.slice(2).join(' '))
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,24 @@ export class Ls extends Program {
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||
if (!(new Item(args[1]).IsDirectory())) {
|
||||
stdout.emit("ls: error: the provided path is not a directory")
|
||||
let item: Item = args[1] ? await Item.openDir(args[1]) : workdir
|
||||
if (args[1] && !item.IsDirectory()) {
|
||||
stdout.emit("ls: error: the provided path is not a directory\n")
|
||||
return 1
|
||||
}
|
||||
|
||||
stdout.emit(`item: '${workdir.GetPath()}'\n`)
|
||||
stdout.emit(`index attr name\n`)
|
||||
workdir.List().forEach((entry, i) => {
|
||||
stdout.emit(`${i.toString().padEnd(8, ' ')} ${''.padEnd(4, '-').padEnd(8, ' ')} '${entry.GetName()}'\n`)
|
||||
if (!(await item.Exists())) {
|
||||
stdout.emit(`ls: error: path ${item.GetPath()} doesn't exist\n`)
|
||||
return 2
|
||||
}
|
||||
|
||||
console.log(item)
|
||||
|
||||
stdout.emit(`-> Listing contents of item: '${item.GetPath()}'\n`)
|
||||
stdout.emit(` [ attr name ]\n`)
|
||||
const items = await item.List()
|
||||
items.forEach((entry, i) => {
|
||||
stdout.emit(` | ${''.padEnd(4, '-').padEnd(8, ' ')} ${entry.GetName()}\n`)
|
||||
})
|
||||
|
||||
return 0
|
||||
|
||||
@@ -12,8 +12,12 @@ export class Lsprg extends Program {
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, __: Item, ___: string[]): Promise<number> {
|
||||
const programs = this.wush.GetPrograms()
|
||||
|
||||
stdout.emit(`-> ${Object.keys(programs).length} loaded programs:\n`)
|
||||
|
||||
for (const program in this.wush.GetPrograms())
|
||||
stdout.emit(`${program}\n`)
|
||||
stdout.emit(` | - ${program}\n`)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
23
src/program/Mkdir.ts
Normal file
23
src/program/Mkdir.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Item } from '../fs/Item'
|
||||
import type { SimpleStream } from '../utils/SimpleStream'
|
||||
import { Program } from './Program'
|
||||
|
||||
export class Mkdir extends Program {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||
const item = await Item.openDir(args[1])
|
||||
stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`)
|
||||
|
||||
if (await item.Exists()) {
|
||||
stdout.emit(`mkdir: directory ${item.GetPath()} already exists.\n`)
|
||||
return 1
|
||||
}
|
||||
|
||||
item.Create()
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
44
src/program/ResetIndexedDb.ts
Normal file
44
src/program/ResetIndexedDb.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { GetWebfsDatabase } from '../app'
|
||||
import { Item } from '../fs/Item'
|
||||
import type { SimpleStream } from '../utils/SimpleStream'
|
||||
import { Program } from './Program'
|
||||
|
||||
export class ResetIndexedDb extends Program {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, ___: Item, ____: string[]): Promise<number> {
|
||||
const db = GetWebfsDatabase()
|
||||
|
||||
if (!db) {
|
||||
stdout.emit("rsindb: error: GetWebfsDatabase returned null")
|
||||
return 1
|
||||
}
|
||||
|
||||
return new Promise<number>(resolve => {
|
||||
const request = indexedDB.deleteDatabase(db.name)
|
||||
|
||||
request.onerror = () => {
|
||||
stdout.emit("rsindb: error: IndexedDB deletion request has failed\n")
|
||||
resolve(2)
|
||||
}
|
||||
|
||||
request.onblocked = () => {
|
||||
stdout.emit("rsindb: debug: database open, closing connection\n")
|
||||
db.close()
|
||||
}
|
||||
|
||||
request.onupgradeneeded = () =>
|
||||
stdout.emit("rsindb: debug: request upgrade needed\n")
|
||||
|
||||
request.onsuccess = () => {
|
||||
stdout.emit("success\n")
|
||||
|
||||
resolve(0)
|
||||
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
15
src/program/Rl.ts
Normal file
15
src/program/Rl.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Item } from '../fs/Item'
|
||||
import type { SimpleStream } from '../utils/SimpleStream'
|
||||
import { Program } from './Program'
|
||||
|
||||
export class Rl extends Program {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, __: SimpleStream<string>, ___: Item, ____: string[]): Promise<number> {
|
||||
location.reload()
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Item } from '../fs/Item'
|
||||
import type { SimpleStream } from '../utils/SimpleStream'
|
||||
import { Program } from './Program'
|
||||
|
||||
export class Rm extends Program {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||
if (!args[1]) {
|
||||
stdout.emit("rm: error: missing first argument\n")
|
||||
return 1
|
||||
}
|
||||
|
||||
let item: Item
|
||||
|
||||
try {
|
||||
item = await Item.openDir(args[1])
|
||||
} catch {
|
||||
item = await Item.open(args[1])
|
||||
}
|
||||
|
||||
if (!(await item.Exists())) {
|
||||
stdout.emit(`rm: error: item ${item.GetPath()} doesn't exist.\n`)
|
||||
return 1
|
||||
}
|
||||
|
||||
stdout.emit(`removing: '${item.GetPath()}'\n`)
|
||||
|
||||
await item.Delete()
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ export class Touch extends Program {
|
||||
async Exec(_: SimpleStream<string>, stdout: SimpleStream<string>, workdir: Item, args: string[]): Promise<number> {
|
||||
stdout.emit(`touching children, please wait...\n`)
|
||||
|
||||
const file = new Item(args[1])
|
||||
stdout.emit(`does ${args[1]} exist? ${file.Exists()}\n`)
|
||||
const item = await Item.open(args[1])
|
||||
stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`)
|
||||
|
||||
if (file.Exists()) {
|
||||
if (await item.Exists()) {
|
||||
stdout.emit("touch: the file already exists.\n")
|
||||
return 1
|
||||
}
|
||||
|
||||
file.Create()
|
||||
item.Create()
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@ export abstract class Shell {
|
||||
|
||||
abstract LoadProgram(program: Program, name: string): void
|
||||
abstract ExecuteProgram(name: string, args: string[]): void
|
||||
abstract Init(): void
|
||||
abstract Init(): Promise<void>
|
||||
abstract HandleKeyInput(key: string, isCharacter: boolean): void
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ import { Ls } from '../program/Ls'
|
||||
import { Item } from '../fs/Item'
|
||||
import { Touch } from '../program/Touch'
|
||||
import { Sl } from '../program/Sl'
|
||||
import { Rm } from '../program/Rm'
|
||||
import { Rl } from '../program/Rl'
|
||||
import { ResetIndexedDb } from '../program/ResetIndexedDb'
|
||||
import { Cat } from '../program/Cat'
|
||||
import { Echo } from '../program/Echo'
|
||||
import { Mkdir } from '../program/Mkdir'
|
||||
|
||||
export class Wush extends Shell {
|
||||
public readonly Version = "0.1.0"
|
||||
@@ -33,12 +39,15 @@ export class Wush extends Shell {
|
||||
*/
|
||||
private execExitCode: number = 0
|
||||
|
||||
// workers
|
||||
private workersAllowed: boolean = false
|
||||
|
||||
// streams
|
||||
readonly stdin: SimpleStream<string>
|
||||
readonly stdout: SimpleStream<string>
|
||||
|
||||
private programs: { [name: string]: Program } = {}
|
||||
private workingDirectory: Item = Item.Root
|
||||
private workingDirectory: Item = null as unknown as Item // workdir is initialized in the Init so this should be safe
|
||||
|
||||
constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
|
||||
super(broadcaster, terminal)
|
||||
@@ -48,11 +57,17 @@ export class Wush extends Shell {
|
||||
this.stdout = new SimpleStream<string>()
|
||||
}
|
||||
|
||||
Init() {
|
||||
async Init() {
|
||||
this.broadcaster.on('keydown', (key: string, isCharacter: boolean) =>
|
||||
this.HandleKeyInput(key, isCharacter),
|
||||
)
|
||||
|
||||
// load workdir
|
||||
this.workingDirectory = await Item.Root()
|
||||
|
||||
if (!this.workingDirectory.Exists())
|
||||
this.workingDirectory.Create()
|
||||
|
||||
// load core programs
|
||||
this.programs['clear'] = new Clear()
|
||||
this.programs['eval'] = new Eval()
|
||||
@@ -62,6 +77,12 @@ export class Wush extends Shell {
|
||||
this.programs['ls'] = new Ls()
|
||||
this.programs['touch'] = new Touch()
|
||||
this.programs['sl'] = new Sl()
|
||||
this.programs['rm'] = new Rm()
|
||||
this.programs['rl'] = new Rl()
|
||||
this.programs['rsindb'] = new ResetIndexedDb()
|
||||
this.programs['cat'] = new Cat()
|
||||
this.programs['echo'] = new Echo()
|
||||
this.programs['mkdir'] = new Mkdir()
|
||||
|
||||
this.stdout.on(data => this.WriteEscapedString(data))
|
||||
this.Prompt()
|
||||
@@ -85,8 +106,8 @@ export class Wush extends Shell {
|
||||
this.execExitCode = code != -1 ? code : -2
|
||||
})
|
||||
.catch((e) => {
|
||||
this.WriteEscapedString("lol")
|
||||
this.WriteEscapedString(`\n${String(e)}\n`)
|
||||
this.WriteEscapedString(`wush: command ${name} exited with the following exception\n`)
|
||||
this.WriteEscapedString(` | ${String(e)}\n`)
|
||||
})
|
||||
.finally(() => {
|
||||
// check if the exec actually exited with an exit code
|
||||
@@ -176,11 +197,13 @@ export class Wush extends Shell {
|
||||
}
|
||||
}
|
||||
|
||||
// todo: actual processing
|
||||
ProcessAdvancedControlCode(complex: string): boolean {
|
||||
if (!complex.match(/\\(.*;)/gm))
|
||||
return false
|
||||
|
||||
const code = matches[]
|
||||
return true
|
||||
// const code = matches[]
|
||||
}
|
||||
|
||||
HandleKeyInput(key: string, isCharacter: boolean) {
|
||||
|
||||
@@ -30,9 +30,9 @@ export class Terminal {
|
||||
this.NewPage()
|
||||
}
|
||||
|
||||
LoadShell(shell: Shell) {
|
||||
async LoadShell(shell: Shell) {
|
||||
this.shell = shell
|
||||
this.shell.Init()
|
||||
await this.shell.Init()
|
||||
}
|
||||
|
||||
GetShell(): Shell | undefined {
|
||||
|
||||
Reference in New Issue
Block a user