diff --git a/index.html b/index.html
index 774f101..0efc117 100644
--- a/index.html
+++ b/index.html
@@ -4,11 +4,6 @@
-
-
-
diff --git a/src/app.ts b/src/app.ts
index 4251edf..0c76dcf 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -22,7 +22,7 @@ request.onerror = _ => {
"webfs error",
"Failed to initialize the webfs database using IndexedDB. Make sure webshell does not have any IndexedDB related permissions disabled and try again.",
'critical',
- ).Show(-1)
+ ).Show()
}
request.onsuccess = event => {
diff --git a/src/fs/Item.ts b/src/fs/Item.ts
index 4e83c25..629494c 100644
--- a/src/fs/Item.ts
+++ b/src/fs/Item.ts
@@ -212,8 +212,23 @@ export class Item {
private normalizePath(path: string): string {
if (!path.startsWith('/')) path = '/' + path
- if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1)
- return path
+
+ // Resolve . and .. segments.
+ // Split on '/' and walk each part: skip empty strings (from leading
+ // slash or consecutive slashes) and '.', pop on '..', push otherwise.
+ const parts = path.split('/')
+ const resolved: string[] = []
+ for (const part of parts) {
+ if (part === '' || part === '.') {
+ continue
+ } else if (part === '..') {
+ if (resolved.length > 0) resolved.pop() // never climbs above root
+ } else {
+ resolved.push(part)
+ }
+ }
+
+ return '/' + resolved.join('/')
}
private getParentPath(): string {
diff --git a/src/init.ts b/src/init.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/program/Cat.ts b/src/program/Cat.ts
index cc2f74f..e94f27b 100644
--- a/src/program/Cat.ts
+++ b/src/program/Cat.ts
@@ -3,10 +3,6 @@ import type { SimpleStream } from '../utils/SimpleStream'
import { Program } from './Program'
export class Cat extends Program {
- constructor() {
- super()
- }
-
async Exec(_: SimpleStream, stdout: SimpleStream, __: Item, args: string[]): Promise {
let item: Item = await Item.open(args[1])
diff --git a/src/program/Echo.ts b/src/program/Echo.ts
index 40a19d6..57df71e 100644
--- a/src/program/Echo.ts
+++ b/src/program/Echo.ts
@@ -7,17 +7,24 @@ export class Echo extends Program {
super()
}
- async Exec(_: SimpleStream, stdout: SimpleStream, ___: Item, args: string[]): Promise {
- let item: Item = await Item.open(args[1])
+ async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise {
+ if (args.length < 2) {
+ stdout.emit("echo: error: missing path argument\n")
+ return 1
+ }
+
+ const item = await Item.open(args[1].startsWith('/')
+ ? args[1]
+ : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
if (!(await item.Exists())) {
stdout.emit(`echo: error: item ${item.GetPath()} doesn't exist.\n`)
- return 1
+ return 2
}
if (item.IsDirectory()) {
stdout.emit(`echo: error: can't write data to a directory; item ${item.GetPath()} is a directory.\n`)
- return 2
+ return 3
}
await item.Write(args.slice(2).join(' '))
diff --git a/src/program/Ls.ts b/src/program/Ls.ts
index e5a63f0..f85c317 100644
--- a/src/program/Ls.ts
+++ b/src/program/Ls.ts
@@ -8,7 +8,12 @@ export class Ls extends Program {
}
async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise {
- let item: Item = args[1] ? await Item.openDir(args[1]) : workdir
+ const item = args[1]
+ ? await Item.openDir(args[1].startsWith('/')
+ ? args[1]
+ : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
+ : workdir
+
if (args[1] && !item.IsDirectory()) {
stdout.emit("ls: error: the provided path is not a directory\n")
return 1
@@ -22,10 +27,11 @@ export class Ls extends Program {
console.log(item)
stdout.emit(`-> Listing contents of item: '${item.GetPath()}'\n`)
- stdout.emit(` [ attr name ]\n`)
+ stdout.emit(` [ drwx name ]\n`)
const items = await item.List()
- items.forEach((entry, i) => {
- stdout.emit(` | ${''.padEnd(4, '-').padEnd(8, ' ')} ${entry.GetName()}\n`)
+ items.forEach(entry => {
+ stdout.emit(item.IsDirectory().toString())
+ stdout.emit(` | ${(item.IsDirectory() ? 'd' : '').padEnd(4, '-').padEnd(8, ' ')} ${entry.GetName()}\n`)
})
return 0
diff --git a/src/program/Mkdir.ts b/src/program/Mkdir.ts
index 10b387a..49be8fd 100644
--- a/src/program/Mkdir.ts
+++ b/src/program/Mkdir.ts
@@ -8,12 +8,20 @@ export class Mkdir extends Program {
}
async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise {
- const item = await Item.openDir(args[1])
+ if (args.length < 2) {
+ stdout.emit("mkdir: error: missing path argument\n")
+ return 1
+ }
+
+ const item = await Item.openDir(args[1].startsWith('/')
+ ? args[1]
+ : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${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
+ return 2
}
item.Create()
diff --git a/src/program/Rm.ts b/src/program/Rm.ts
index 9c98347..f6607ed 100644
--- a/src/program/Rm.ts
+++ b/src/program/Rm.ts
@@ -16,9 +16,13 @@ export class Rm extends Program {
let item: Item
try {
- item = await Item.openDir(args[1])
+ item = await Item.openDir(args[1].startsWith('/')
+ ? args[1]
+ : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
} catch {
- item = await Item.open(args[1])
+ item = await Item.open(args[1].startsWith('/')
+ ? args[1]
+ : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${args[1]}`)
}
if (!(await item.Exists())) {
diff --git a/src/program/Touch.ts b/src/program/Touch.ts
index 43f50d5..9b655e4 100644
--- a/src/program/Touch.ts
+++ b/src/program/Touch.ts
@@ -10,8 +10,9 @@ export class Touch extends Program {
async Exec(_: SimpleStream, stdout: SimpleStream, workdir: Item, args: string[]): Promise {
stdout.emit(`touching children, please wait...\n`)
- const item = await Item.open(args[1])
- stdout.emit(`does ${args[1]} exist? ${await item.Exists()}\n`)
+ const path = args.slice(1).join()
+ const item = await Item.open(path.includes('/') ? path : `${workdir.GetPath()}${workdir.GetPath().endsWith('/') ? '' : '/'}${path}`)
+ stdout.emit(`does ${item.GetPath()} exist? ${await item.Exists()}\n`)
if (await item.Exists()) {
stdout.emit("touch: the file already exists.\n")
diff --git a/src/shell/Shell.ts b/src/shell/Shell.ts
index 0360faa..c89a34e 100644
--- a/src/shell/Shell.ts
+++ b/src/shell/Shell.ts
@@ -1,3 +1,4 @@
+import type { Item } from '../fs/Item'
import type { Program } from '../program/Program'
import type { Terminal } from '../terminal/Terminal'
import type { EventBroadcaster } from '../utils/EventBroadcaster'
@@ -17,4 +18,5 @@ export abstract class Shell {
abstract ExecuteProgram(name: string, args: string[]): void
abstract Init(): Promise
abstract HandleKeyInput(key: string, isCharacter: boolean): void
+ abstract SetWorkingDirectory(directory: Item): Promise
}
diff --git a/src/shell/Wush.ts b/src/shell/Wush.ts
index d95ef61..9c427f9 100644
--- a/src/shell/Wush.ts
+++ b/src/shell/Wush.ts
@@ -21,6 +21,8 @@ import { ResetIndexedDb } from '../program/ResetIndexedDb'
import { Cat } from '../program/Cat'
import { Echo } from '../program/Echo'
import { Mkdir } from '../program/Mkdir'
+import { Pwd } from '../program/Pwd'
+import { Cd } from '../program/Cd'
export class Wush extends Shell {
public readonly Version = "0.1.0"
@@ -47,7 +49,7 @@ export class Wush extends Shell {
readonly stdout: SimpleStream
private programs: { [name: string]: Program } = {}
- private workingDirectory: Item = null as unknown as Item // workdir is initialized in the Init so this should be safe
+ private workingDirectory: Item = null as unknown as Item // workdir is initialized in Init so this should be safe
constructor(broadcaster: EventBroadcaster, terminal: Terminal) {
super(broadcaster, terminal)
@@ -65,9 +67,6 @@ export class Wush extends Shell {
// 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()
@@ -83,6 +82,8 @@ export class Wush extends Shell {
this.programs['cat'] = new Cat()
this.programs['echo'] = new Echo()
this.programs['mkdir'] = new Mkdir()
+ this.programs['pwd'] = new Pwd()
+ this.programs['cd'] = new Cd(this)
this.stdout.on(data => this.WriteEscapedString(data))
this.Prompt()
@@ -121,7 +122,19 @@ export class Wush extends Shell {
}
Prompt() {
- this.terminal.Write(`hi [${this.execExitCode}] -> `)
+ this.terminal.Write(`boykisser in ${this.workingDirectory.GetPath()} -> `)
+ }
+
+ /**
+ * Changes the working directory
+ * @param directory the directory to enter into
+ * @throws an error if the directory cannot be opened
+ */
+ async SetWorkingDirectory(directory: Item) {
+ if (!(await directory.Exists()) || !directory.IsDirectory())
+ throw new Error(`Directory ${directory.GetPath()} doesn't exist`)
+
+ this.workingDirectory = directory
}
WriteStdin(data: string) {
diff --git a/src/styles/app.scss b/src/styles/app.scss
index 980872a..6ac355a 100644
--- a/src/styles/app.scss
+++ b/src/styles/app.scss
@@ -2,7 +2,7 @@
@use "./colors.scss" as colors;
body {
- margin: 0;
+ margin: 10px;
background: colors.$terminal-background;
}
diff --git a/src/terminal/Terminal.ts b/src/terminal/Terminal.ts
index c90c27f..00a27fe 100644
--- a/src/terminal/Terminal.ts
+++ b/src/terminal/Terminal.ts
@@ -155,8 +155,8 @@ export class Terminal {
UpdateCursor() {
this.SetCursorStyle(this.cursorStyle)
- this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth}px`
- this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight}px`
+ this.cursor.style.left = `${this.cursorPosition.col * this.cellWidth + this.terminal.offsetLeft}px `
+ this.cursor.style.top = `${this.cursorPosition.row * this.cellHeight + this.terminal.offsetTop}px`
}
GetCursorPosition(): CursorPosition {