@ecmaos/coreutils 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +35 -0
- package/dist/commands/git.d.ts +4 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +708 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/history.d.ts +4 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +157 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/ls.js +1 -1
- package/dist/commands/tty.d.ts +4 -0
- package/dist/commands/tty.d.ts.map +1 -0
- package/dist/commands/tty.js +60 -0
- package/dist/commands/tty.js.map +1 -0
- package/dist/commands/vim.d.ts +4 -0
- package/dist/commands/vim.d.ts.map +1 -0
- package/dist/commands/vim.js +272 -0
- package/dist/commands/vim.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/commands/git.ts +843 -0
- package/src/commands/history.ts +172 -0
- package/src/commands/ls.ts +1 -1
- package/src/commands/tty.ts +68 -0
- package/src/commands/vim.ts +307 -0
- package/src/index.ts +13 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: history [OPTION]... [N]
|
|
8
|
+
Display or manipulate the command history.
|
|
9
|
+
|
|
10
|
+
N display the last N entries
|
|
11
|
+
-c clear the history list
|
|
12
|
+
-d N delete the history entry at position N
|
|
13
|
+
-r reload the history file (useful after manual edits)
|
|
14
|
+
--help display this help and exit
|
|
15
|
+
|
|
16
|
+
If N is provided without options, display the last N entries.
|
|
17
|
+
If no arguments are provided, display all history entries.
|
|
18
|
+
|
|
19
|
+
Note: History is automatically saved on each command execution.`
|
|
20
|
+
writelnStderr(process, terminal, usage)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readHistoryFile(shell: Shell, kernel: Kernel): Promise<string[]> {
|
|
24
|
+
const home = shell.env.get('HOME') || '/root'
|
|
25
|
+
const historyPath = path.join(home, '.history')
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (!kernel.filesystem?.fs) {
|
|
29
|
+
return []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const exists = await kernel.filesystem.fs.exists(historyPath)
|
|
33
|
+
if (!exists) {
|
|
34
|
+
return []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const content = await kernel.filesystem.fs.readFile(historyPath, 'utf-8')
|
|
38
|
+
const lines = content.split('\n').filter(line => line.length > 0)
|
|
39
|
+
return lines
|
|
40
|
+
} catch {
|
|
41
|
+
return []
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function writeHistoryFile(shell: Shell, kernel: Kernel, lines: string[]): Promise<void> {
|
|
46
|
+
const home = shell.env.get('HOME') || '/root'
|
|
47
|
+
const historyPath = path.join(home, '.history')
|
|
48
|
+
|
|
49
|
+
if (!kernel.filesystem?.fs) {
|
|
50
|
+
throw new Error('Filesystem not available')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const content = lines.join('\n')
|
|
54
|
+
await kernel.filesystem.fs.writeFile(historyPath, content, 'utf-8')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
58
|
+
return new TerminalCommand({
|
|
59
|
+
command: 'history',
|
|
60
|
+
description: 'Display or manipulate the command history',
|
|
61
|
+
kernel,
|
|
62
|
+
shell,
|
|
63
|
+
terminal,
|
|
64
|
+
run: async (pid: number, argv: string[]) => {
|
|
65
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
66
|
+
|
|
67
|
+
if (!process) return 1
|
|
68
|
+
|
|
69
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
70
|
+
printUsage(process, terminal)
|
|
71
|
+
return 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const writer = process.stdout.getWriter()
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
let clearHistory = false
|
|
78
|
+
let deleteIndex: number | null = null
|
|
79
|
+
let readHistory = false
|
|
80
|
+
let numEntries: number | null = null
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < argv.length; i++) {
|
|
83
|
+
const arg = argv[i]
|
|
84
|
+
if (!arg) continue
|
|
85
|
+
|
|
86
|
+
if (arg === '-c') {
|
|
87
|
+
clearHistory = true
|
|
88
|
+
} else if (arg === '-r') {
|
|
89
|
+
readHistory = true
|
|
90
|
+
} else if (arg === '-d') {
|
|
91
|
+
if (i + 1 < argv.length) {
|
|
92
|
+
i++
|
|
93
|
+
const nextArg = argv[i]
|
|
94
|
+
if (nextArg !== undefined) {
|
|
95
|
+
const index = parseInt(nextArg, 10)
|
|
96
|
+
if (!isNaN(index)) {
|
|
97
|
+
deleteIndex = index
|
|
98
|
+
} else {
|
|
99
|
+
await writelnStderr(process, terminal, `history: invalid history number '${nextArg}'`)
|
|
100
|
+
return 1
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
await writelnStderr(process, terminal, 'history: -d requires a history number')
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
} else if (!arg.startsWith('-')) {
|
|
108
|
+
const num = parseInt(arg, 10)
|
|
109
|
+
if (!isNaN(num)) {
|
|
110
|
+
numEntries = num
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (clearHistory) {
|
|
116
|
+
const uid = shell.credentials.uid
|
|
117
|
+
await terminal.clearHistory(uid)
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (deleteIndex !== null) {
|
|
122
|
+
const lines = await readHistoryFile(shell, kernel)
|
|
123
|
+
if (deleteIndex < 1 || deleteIndex > lines.length) {
|
|
124
|
+
await writelnStderr(process, terminal, `history: history number '${deleteIndex}' out of range`)
|
|
125
|
+
return 1
|
|
126
|
+
}
|
|
127
|
+
const newLines = lines.filter((_, index) => index !== deleteIndex - 1)
|
|
128
|
+
await writeHistoryFile(shell, kernel, newLines)
|
|
129
|
+
|
|
130
|
+
const uid = shell.credentials.uid
|
|
131
|
+
await terminal.reloadHistory(uid).catch(() => {})
|
|
132
|
+
return 0
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (readHistory) {
|
|
136
|
+
const uid = shell.credentials.uid
|
|
137
|
+
await terminal.reloadHistory(uid).catch(() => {})
|
|
138
|
+
return 0
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const lines = await readHistoryFile(shell, kernel)
|
|
142
|
+
|
|
143
|
+
if (lines.length === 0) {
|
|
144
|
+
return 0
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const displayLines = numEntries !== null
|
|
148
|
+
? lines.slice(-numEntries)
|
|
149
|
+
: lines
|
|
150
|
+
|
|
151
|
+
const startIndex = numEntries !== null
|
|
152
|
+
? lines.length - numEntries + 1
|
|
153
|
+
: 1
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
156
|
+
const lineNumber = startIndex + i
|
|
157
|
+
const line = displayLines[i]
|
|
158
|
+
const output = ` ${lineNumber} ${line}\n`
|
|
159
|
+
await writer.write(new TextEncoder().encode(output))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return 0
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
165
|
+
await writelnStderr(process, terminal, `history: ${errorMessage}`)
|
|
166
|
+
return 1
|
|
167
|
+
} finally {
|
|
168
|
+
writer.releaseLock()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
}
|
package/src/commands/ls.ts
CHANGED
|
@@ -14,7 +14,7 @@ List information about the FILEs (the current directory by default).
|
|
|
14
14
|
writelnStderr(process, terminal, usage)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function truncateInfo(text: string, maxWidth: number =
|
|
17
|
+
function truncateInfo(text: string, maxWidth: number = 35): string {
|
|
18
18
|
if (!text) return text
|
|
19
19
|
|
|
20
20
|
// Strip ANSI codes to get visible length
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
+
import { writelnStderr, writelnStdout } from '../shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
|
+
const usage = `Usage: tty [TTY_NUMBER]
|
|
7
|
+
Print the current TTY number or switch to a different TTY.
|
|
8
|
+
|
|
9
|
+
TTY_NUMBER switch to the specified TTY (0-9)
|
|
10
|
+
--help display this help and exit
|
|
11
|
+
|
|
12
|
+
If no TTY_NUMBER is provided, prints the current TTY number.
|
|
13
|
+
If TTY_NUMBER is provided, switches to that TTY.`
|
|
14
|
+
writelnStderr(process, terminal, usage)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
18
|
+
return new TerminalCommand({
|
|
19
|
+
command: 'tty',
|
|
20
|
+
description: 'Print the current TTY number or switch to a different TTY',
|
|
21
|
+
kernel,
|
|
22
|
+
shell,
|
|
23
|
+
terminal,
|
|
24
|
+
run: async (pid: number, argv: string[]) => {
|
|
25
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
26
|
+
|
|
27
|
+
if (!process) return 1
|
|
28
|
+
|
|
29
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
30
|
+
printUsage(process, terminal)
|
|
31
|
+
return 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (argv.length === 0) {
|
|
35
|
+
await writelnStdout(process, terminal, kernel.activeTty.toString())
|
|
36
|
+
return 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (argv.length > 1) {
|
|
40
|
+
await writelnStderr(process, terminal, 'tty: too many arguments')
|
|
41
|
+
await writelnStderr(process, terminal, "Try 'tty --help' for more information.")
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ttyNumber = parseInt(argv[0] ?? '0', 10)
|
|
46
|
+
|
|
47
|
+
if (isNaN(ttyNumber)) {
|
|
48
|
+
await writelnStderr(process, terminal, `tty: invalid TTY number '${argv[0]}'`)
|
|
49
|
+
await writelnStderr(process, terminal, "Try 'tty --help' for more information.")
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (ttyNumber < 0 || ttyNumber > 9) {
|
|
54
|
+
await writelnStderr(process, terminal, `tty: TTY number must be between 0 and 9`)
|
|
55
|
+
await writelnStderr(process, terminal, "Try 'tty --help' for more information.")
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await kernel.switchTty(ttyNumber)
|
|
61
|
+
return 0
|
|
62
|
+
} catch (error) {
|
|
63
|
+
await writelnStderr(process, terminal, `tty: failed to switch to TTY ${ttyNumber}: ${error instanceof Error ? error.message : String(error)}`)
|
|
64
|
+
return 1
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: vim [OPTION]... [FILE]...
|
|
8
|
+
Vi IMproved - a text editor.
|
|
9
|
+
|
|
10
|
+
FILE file(s) to edit
|
|
11
|
+
--help, -h display this help and exit
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
vim file.txt edit file.txt
|
|
15
|
+
vim file1.txt file2.txt edit multiple files`
|
|
16
|
+
writelnStderr(process, terminal, usage)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
20
|
+
return new TerminalCommand({
|
|
21
|
+
command: 'vim',
|
|
22
|
+
description: 'Vi IMproved - a text editor',
|
|
23
|
+
kernel,
|
|
24
|
+
shell,
|
|
25
|
+
terminal,
|
|
26
|
+
run: async (pid: number, argv: string[]) => {
|
|
27
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
28
|
+
|
|
29
|
+
if (!process) return 1
|
|
30
|
+
|
|
31
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
32
|
+
printUsage(process, terminal)
|
|
33
|
+
return 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const { VimWasm, checkBrowserCompatibility } = await import('vim-wasm/vimwasm.js')
|
|
38
|
+
|
|
39
|
+
const compatibilityError = checkBrowserCompatibility()
|
|
40
|
+
if (compatibilityError !== undefined) {
|
|
41
|
+
await writelnStderr(process, terminal, `vim: ${compatibilityError}`)
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const files: string[] = []
|
|
46
|
+
for (const arg of argv) {
|
|
47
|
+
if (arg && !arg.startsWith('-')) {
|
|
48
|
+
files.push(arg)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (files.length === 0) {
|
|
53
|
+
await writelnStderr(process, terminal, 'vim: no file specified')
|
|
54
|
+
await writelnStderr(process, terminal, "Try 'vim --help' for more information.")
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fileContents: Record<string, string> = {}
|
|
59
|
+
const dirs = new Set<string>()
|
|
60
|
+
const cmdArgs: string[] = []
|
|
61
|
+
|
|
62
|
+
const cwd = shell.cwd
|
|
63
|
+
let currentCwd = cwd
|
|
64
|
+
while (currentCwd !== '/' && currentCwd !== '') {
|
|
65
|
+
dirs.add(currentCwd)
|
|
66
|
+
currentCwd = path.dirname(currentCwd)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const homeDir = shell.expandTilde('~')
|
|
70
|
+
if (homeDir && homeDir !== '~') {
|
|
71
|
+
let currentDir = homeDir
|
|
72
|
+
while (currentDir !== '/' && currentDir !== '') {
|
|
73
|
+
dirs.add(currentDir)
|
|
74
|
+
currentDir = path.dirname(currentDir)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const vimrcPath = path.join(homeDir, '.vim', 'vimrc')
|
|
78
|
+
const vimrcExists = await shell.context.fs.promises.exists(vimrcPath)
|
|
79
|
+
if (vimrcExists) {
|
|
80
|
+
const vimrcContent = await shell.context.fs.promises.readFile(vimrcPath, 'utf-8')
|
|
81
|
+
const vimrcVirtualPath = '/home/web_user/.vim/vimrc'
|
|
82
|
+
fileContents[vimrcVirtualPath] = vimrcContent
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
const expandedPath = shell.expandTilde(file)
|
|
88
|
+
const fullPath = path.resolve(shell.cwd, expandedPath)
|
|
89
|
+
|
|
90
|
+
const exists = await shell.context.fs.promises.exists(fullPath)
|
|
91
|
+
|
|
92
|
+
if (exists) {
|
|
93
|
+
const stats = await shell.context.fs.promises.stat(fullPath)
|
|
94
|
+
if (stats.isDirectory()) {
|
|
95
|
+
await writelnStderr(process, terminal, `vim: ${file}: Is a directory`)
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const content = await shell.context.fs.promises.readFile(fullPath, 'utf-8')
|
|
100
|
+
fileContents[fullPath] = content
|
|
101
|
+
} else {
|
|
102
|
+
fileContents[fullPath] = ''
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const dir = path.dirname(fullPath)
|
|
106
|
+
let currentDir = dir
|
|
107
|
+
while (currentDir !== '/' && currentDir !== '') {
|
|
108
|
+
dirs.add(currentDir)
|
|
109
|
+
currentDir = path.dirname(currentDir)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
cmdArgs.push(fullPath)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const emscriptenDefaultDirs = new Set(['/', '/tmp', '/home', '/home/web_user', '/home/web_user/.vim', '/dev'])
|
|
116
|
+
const dirsArray = Array.from(dirs)
|
|
117
|
+
.filter(d => !emscriptenDefaultDirs.has(d))
|
|
118
|
+
.sort((a, b) => a.length - b.length)
|
|
119
|
+
|
|
120
|
+
const container = document.createElement('div')
|
|
121
|
+
container.style.width = '100%'
|
|
122
|
+
container.style.height = '100%'
|
|
123
|
+
container.style.display = 'flex'
|
|
124
|
+
container.style.flexDirection = 'column'
|
|
125
|
+
container.style.background = '#1e1e1e'
|
|
126
|
+
container.style.overflow = 'hidden'
|
|
127
|
+
|
|
128
|
+
const canvas = document.createElement('canvas')
|
|
129
|
+
canvas.id = 'vim-canvas'
|
|
130
|
+
canvas.style.width = '100%'
|
|
131
|
+
canvas.style.height = '100%'
|
|
132
|
+
canvas.style.flex = '1'
|
|
133
|
+
|
|
134
|
+
const input = document.createElement('input')
|
|
135
|
+
input.id = 'vim-input'
|
|
136
|
+
input.type = 'text'
|
|
137
|
+
input.autocomplete = 'off'
|
|
138
|
+
input.autofocus = true
|
|
139
|
+
input.style.position = 'absolute'
|
|
140
|
+
input.style.left = '-9999px'
|
|
141
|
+
input.style.width = '1px'
|
|
142
|
+
input.style.height = '1px'
|
|
143
|
+
input.style.opacity = '0'
|
|
144
|
+
|
|
145
|
+
container.appendChild(canvas)
|
|
146
|
+
container.appendChild(input)
|
|
147
|
+
|
|
148
|
+
const firstFile = files.length > 0 ? files[0] : undefined
|
|
149
|
+
const windowTitle = firstFile ? path.basename(firstFile) : 'vim'
|
|
150
|
+
const win = kernel.windows.create({
|
|
151
|
+
title: windowTitle,
|
|
152
|
+
width: 900,
|
|
153
|
+
height: 700,
|
|
154
|
+
max: false
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
win.mount(container)
|
|
158
|
+
|
|
159
|
+
let workerScriptPath: string
|
|
160
|
+
try {
|
|
161
|
+
workerScriptPath = new URL('vim-wasm/vim.js', import.meta.url).href
|
|
162
|
+
} catch {
|
|
163
|
+
await writelnStderr(process, terminal, 'vim: failed to resolve worker script path. Please ensure vim-wasm is properly installed.')
|
|
164
|
+
win.close()
|
|
165
|
+
return 1
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const vim = new VimWasm({
|
|
169
|
+
canvas,
|
|
170
|
+
input,
|
|
171
|
+
workerScriptPath
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
let exitCode = 0
|
|
175
|
+
let vimExited = false
|
|
176
|
+
let resizeObserver: ResizeObserver | null = null
|
|
177
|
+
|
|
178
|
+
vim.onFileExport = async (fullpath: string, contents: ArrayBuffer) => {
|
|
179
|
+
try {
|
|
180
|
+
const text = new TextDecoder().decode(contents)
|
|
181
|
+
await shell.context.fs.promises.writeFile(fullpath, text, 'utf-8')
|
|
182
|
+
} catch (error) {
|
|
183
|
+
await writelnStderr(process, terminal, `vim: error writing file ${fullpath}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
vim.onVimExit = (status: number) => {
|
|
188
|
+
vimExited = true
|
|
189
|
+
exitCode = status === 0 ? 0 : 1
|
|
190
|
+
if (resizeObserver) {
|
|
191
|
+
resizeObserver.disconnect()
|
|
192
|
+
resizeObserver = null
|
|
193
|
+
}
|
|
194
|
+
win.close()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
vim.onError = async (err: Error) => {
|
|
198
|
+
console.error('[vim] Error callback triggered:', err)
|
|
199
|
+
console.error('[vim] Error message:', err.message)
|
|
200
|
+
console.error('[vim] Error stack:', err.stack)
|
|
201
|
+
await writelnStderr(process, terminal, `vim: error: ${err.message}`)
|
|
202
|
+
if (!vimExited) {
|
|
203
|
+
exitCode = 1
|
|
204
|
+
if (resizeObserver) {
|
|
205
|
+
resizeObserver.disconnect()
|
|
206
|
+
resizeObserver = null
|
|
207
|
+
}
|
|
208
|
+
win.close()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
vim.onTitleUpdate = (title: string) => {
|
|
213
|
+
win.setTitle(title || windowTitle)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
vim.onVimInit = async () => {
|
|
217
|
+
try {
|
|
218
|
+
await vim.cmdline('autocmd BufWritePost * :export')
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('[vim] Failed to set up auto-export:', error)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const handleResize = () => {
|
|
224
|
+
if (vimExited || !vim.isRunning()) return
|
|
225
|
+
|
|
226
|
+
const rect = container.getBoundingClientRect()
|
|
227
|
+
const width = Math.floor(rect.width)
|
|
228
|
+
const height = Math.floor(rect.height)
|
|
229
|
+
|
|
230
|
+
if (width > 0 && height > 0) {
|
|
231
|
+
const dpr = window.devicePixelRatio || 1
|
|
232
|
+
canvas.width = width * dpr
|
|
233
|
+
canvas.height = height * dpr
|
|
234
|
+
|
|
235
|
+
vim.resize(width, height)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
resizeObserver = new ResizeObserver(handleResize)
|
|
240
|
+
resizeObserver.observe(container)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const filePath of Object.keys(fileContents)) {
|
|
244
|
+
const parentDir = path.dirname(filePath)
|
|
245
|
+
if (!dirsArray.includes(parentDir)) {
|
|
246
|
+
console.warn(`[vim] WARNING: Parent directory ${parentDir} of file ${filePath} is not in dirs array!`)
|
|
247
|
+
}
|
|
248
|
+
if (dirsArray.includes(filePath)) {
|
|
249
|
+
console.error(`[vim] ERROR: File path ${filePath} is also in dirs array! This will cause ENOTDIR error.`)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const dirPath of dirsArray) {
|
|
254
|
+
if (fileContents[dirPath] !== undefined) {
|
|
255
|
+
console.error(`[vim] ERROR: Directory path ${dirPath} is also in files object! This will cause ENOTDIR error.`)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < dirsArray.length; i++) {
|
|
260
|
+
const dir = dirsArray[i]
|
|
261
|
+
if (dir) {
|
|
262
|
+
const parent = path.dirname(dir)
|
|
263
|
+
if (parent !== dir && !dirsArray.slice(0, i).includes(parent)) {
|
|
264
|
+
console.warn(`[vim] WARNING: Directory ${dir} has parent ${parent} that comes after it in the array!`)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const startOptions = {
|
|
271
|
+
files: fileContents,
|
|
272
|
+
dirs: dirsArray,
|
|
273
|
+
cmdArgs,
|
|
274
|
+
debug: false
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
vim.start(startOptions)
|
|
278
|
+
} catch (startError) {
|
|
279
|
+
console.error('[vim] Error calling vim.start():', startError)
|
|
280
|
+
await writelnStderr(process, terminal, `vim: failed to start: ${startError instanceof Error ? startError.message : 'Unknown error'}`)
|
|
281
|
+
if (startError instanceof Error && startError.stack) {
|
|
282
|
+
console.error('[vim] Start error stack:', startError.stack)
|
|
283
|
+
}
|
|
284
|
+
win.close()
|
|
285
|
+
return 1
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return new Promise<number>((resolve) => {
|
|
289
|
+
const checkExit = () => {
|
|
290
|
+
if (vimExited) {
|
|
291
|
+
resolve(exitCode)
|
|
292
|
+
} else {
|
|
293
|
+
setTimeout(checkExit, 100)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
checkExit()
|
|
297
|
+
})
|
|
298
|
+
} catch (error) {
|
|
299
|
+
await writelnStderr(process, terminal, `vim: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
300
|
+
if (error instanceof Error && error.stack) {
|
|
301
|
+
console.error(error.stack)
|
|
302
|
+
}
|
|
303
|
+
return 1
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { createCommand as createGrep } from './commands/grep.js'
|
|
|
21
21
|
import { createCommand as createGroups } from './commands/groups.js'
|
|
22
22
|
import { createCommand as createHash } from './commands/hash.js'
|
|
23
23
|
import { createCommand as createHead } from './commands/head.js'
|
|
24
|
+
import { createCommand as createHistory } from './commands/history.js'
|
|
24
25
|
import { createCommand as createHostname } from './commands/hostname.js'
|
|
25
26
|
import { createCommand as createLn } from './commands/ln.js'
|
|
26
27
|
import { createCommand as createLs } from './commands/ls.js'
|
|
@@ -85,6 +86,7 @@ import { createCommand as createTest } from './commands/test.js'
|
|
|
85
86
|
import { createCommand as createTime } from './commands/time.js'
|
|
86
87
|
import { createCommand as createTr } from './commands/tr.js'
|
|
87
88
|
import { createCommand as createTrue } from './commands/true.js'
|
|
89
|
+
import { createCommand as createTty } from './commands/tty.js'
|
|
88
90
|
import { createCommand as createUname } from './commands/uname.js'
|
|
89
91
|
import { createCommand as createUmount } from './commands/umount.js'
|
|
90
92
|
import { createCommand as createUnexpand } from './commands/unexpand.js'
|
|
@@ -99,6 +101,8 @@ import { createCommand as createZip } from './commands/zip.js'
|
|
|
99
101
|
import { createCommand as createUnzip } from './commands/unzip.js'
|
|
100
102
|
import { createCommand as createVideo } from './commands/video.js'
|
|
101
103
|
import { createCommand as createView } from './commands/view.js'
|
|
104
|
+
import { createCommand as createVim } from './commands/vim.js'
|
|
105
|
+
import { createCommand as createGit } from './commands/git.js'
|
|
102
106
|
|
|
103
107
|
// Export individual command factories
|
|
104
108
|
export { createCommand as createCat } from './commands/cat.js'
|
|
@@ -113,6 +117,7 @@ export { createCommand as createFetch } from './commands/fetch.js'
|
|
|
113
117
|
export { createCommand as createGrep } from './commands/grep.js'
|
|
114
118
|
export { createCommand as createGroups } from './commands/groups.js'
|
|
115
119
|
export { createCommand as createHash } from './commands/hash.js'
|
|
120
|
+
export { createCommand as createHistory } from './commands/history.js'
|
|
116
121
|
export { createCommand as createLn } from './commands/ln.js'
|
|
117
122
|
export { createCommand as createLs } from './commands/ls.js'
|
|
118
123
|
export { createCommand as createMkdir } from './commands/mkdir.js'
|
|
@@ -161,6 +166,7 @@ export { createCommand as createSleep } from './commands/sleep.js'
|
|
|
161
166
|
export { createCommand as createSort } from './commands/sort.js'
|
|
162
167
|
export { createCommand as createTest } from './commands/test.js'
|
|
163
168
|
export { createCommand as createTr } from './commands/tr.js'
|
|
169
|
+
export { createCommand as createTty } from './commands/tty.js'
|
|
164
170
|
export { createCommand as createUname } from './commands/uname.js'
|
|
165
171
|
export { createCommand as createUmount } from './commands/umount.js'
|
|
166
172
|
export { createCommand as createUptime } from './commands/uptime.js'
|
|
@@ -174,6 +180,8 @@ export { createCommand as createZip } from './commands/zip.js'
|
|
|
174
180
|
export { createCommand as createUnzip } from './commands/unzip.js'
|
|
175
181
|
export { createCommand as createVideo } from './commands/video.js'
|
|
176
182
|
export { createCommand as createView } from './commands/view.js'
|
|
183
|
+
export { createCommand as createVim } from './commands/vim.js'
|
|
184
|
+
export { createCommand as createGit } from './commands/git.js'
|
|
177
185
|
|
|
178
186
|
/**
|
|
179
187
|
* Creates all coreutils commands.
|
|
@@ -195,6 +203,7 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
|
|
|
195
203
|
groups: createGroups(kernel, shell, terminal),
|
|
196
204
|
hash: createHash(kernel, shell, terminal),
|
|
197
205
|
head: createHead(kernel, shell, terminal),
|
|
206
|
+
history: createHistory(kernel, shell, terminal),
|
|
198
207
|
hostname: createHostname(kernel, shell, terminal),
|
|
199
208
|
ln: createLn(kernel, shell, terminal),
|
|
200
209
|
ls: createLs(kernel, shell, terminal),
|
|
@@ -259,6 +268,7 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
|
|
|
259
268
|
time: createTime(kernel, shell, terminal),
|
|
260
269
|
tr: createTr(kernel, shell, terminal),
|
|
261
270
|
true: createTrue(kernel, shell, terminal),
|
|
271
|
+
tty: createTty(kernel, shell, terminal),
|
|
262
272
|
uname: createUname(kernel, shell, terminal),
|
|
263
273
|
umount: createUmount(kernel, shell, terminal),
|
|
264
274
|
unexpand: createUnexpand(kernel, shell, terminal),
|
|
@@ -272,7 +282,9 @@ export function createAllCommands(kernel: Kernel, shell: Shell, terminal: Termin
|
|
|
272
282
|
zip: createZip(kernel, shell, terminal),
|
|
273
283
|
unzip: createUnzip(kernel, shell, terminal),
|
|
274
284
|
video: createVideo(kernel, shell, terminal),
|
|
275
|
-
view: createView(kernel, shell, terminal)
|
|
285
|
+
view: createView(kernel, shell, terminal),
|
|
286
|
+
vim: createVim(kernel, shell, terminal),
|
|
287
|
+
git: createGit(kernel, shell, terminal)
|
|
276
288
|
}
|
|
277
289
|
}
|
|
278
290
|
|