Skip to content

Commit 1126e98

Browse files
add curl command, change js commands to be parented to js
1 parent 86f132b commit 1126e98

File tree

4 files changed

+213
-6
lines changed

4 files changed

+213
-6
lines changed

src/assets/help.txt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,25 @@ VFS STORAGE:
2626
storage - Show storage usage stats
2727

2828
JAVASCRIPT COMMANDS:
29-
execute - Run JavaScript file (execute script.js)
29+
js execute - Run JavaScript file (js execute script.js)
30+
js open - Open HTML file in new window (js open page.html)
31+
32+
CURL COMMANDS:
33+
curl - Make HTTP requests (see curl help for options)
34+
35+
INPUT CONTROLS:
36+
Ctrl+Z - Undo last input change
37+
Ctrl+Y - Redo last undone change
38+
Up/Down - Navigate command history
39+
Left/Right - Move cursor in current line
40+
Ctrl+V - Paste clipboard content
3041

3142
TIPS:
3243
• Commands are case-insensitive
3344
• Directories shown with trailing "/"
3445
• VFS persists in localStorage (512 KB limit)
3546
• Use quotes for spaces: write "my file" "content"
47+
• Undo/redo works for typing, backspace, and paste operations
48+
• Command history persists during session
3649

3750
Repository: https://github.com/daniel4-scratch/miaoshell

src/commands/curl.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// curl.js
2+
import * as vfs from './vfs.js';
3+
4+
const curlCommands = {
5+
help: (term) => {
6+
term.writeln('\r\nCURL - Command-line tool for transferring data');
7+
term.writeln('Usage: curl <command> [options]');
8+
term.writeln('');
9+
term.writeln('Commands:');
10+
term.writeln(' help Show this help message');
11+
term.writeln(' <url> Fetch content from URL');
12+
term.writeln(' <url> > <file> Fetch content and save to file');
13+
term.writeln('');
14+
term.writeln('Examples:');
15+
term.writeln(' curl https://api.github.com');
16+
term.writeln(' curl https://example.com > page.html');
17+
term.writeln(' curl help');
18+
},
19+
fetch: async (term, args) => {
20+
if (args.length === 0) {
21+
term.writeln('\r\nUsage: curl <url> [> filename]');
22+
return;
23+
}
24+
// Detect > operator for output redirection
25+
let url = args[0];
26+
let saveToFile = false;
27+
let filename = '';
28+
const gtIndex = args.indexOf('>');
29+
if (gtIndex !== -1 && args.length > gtIndex + 1) {
30+
saveToFile = true;
31+
filename = args[gtIndex + 1];
32+
url = args.slice(0, gtIndex).join(' ');
33+
}
34+
try {
35+
const response = await fetch(url);
36+
if (response.ok) {
37+
const text = await response.text();
38+
if (saveToFile) {
39+
// Save to file in VFS
40+
const currentDir = vfs.cwd();
41+
currentDir[filename] = text;
42+
term.writeln(`\r\nSaved output to ${filename}`);
43+
} else {
44+
term.writeln('\r\n' + text);
45+
}
46+
} else {
47+
throw new Error('HTTP ' + response.status);
48+
}
49+
} catch (error) {
50+
term.writeln(`\r\nError fetching ${url}: ${error.message}`);
51+
}
52+
}
53+
};
54+
55+
export const commands = {
56+
curl: async (term, args) => {
57+
if (args.length === 0) {
58+
curlCommands.help(term);
59+
return;
60+
}
61+
62+
const firstArg = args[0].toLowerCase();
63+
64+
// Check if it's a help command
65+
if (firstArg === 'help') {
66+
curlCommands.help(term);
67+
return;
68+
}
69+
70+
// Otherwise, treat it as a fetch command
71+
await curlCommands.fetch(term, args);
72+
}
73+
};

src/commands/js.js

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
//javascript in miao shell
22
import * as vfs from './vfs.js';
33

4-
export const commands = {
4+
const jsCommands = {
55
execute: (term, args) => {
66
if (args.length === 0){
77
term.writeln('\r\nUsage: js execute <file.js>');
88
}
99
else {
1010
const fileName = args[0];
11-
const file = vfs.cwd()[fileName];
12-
if (!file) {
11+
const currentDir = vfs.cwd();
12+
const file = currentDir[fileName];
13+
14+
if (file === undefined) {
1315
term.writeln(`\r\nNo such file: ${fileName}`);
1416
return;
1517
}
@@ -25,5 +27,51 @@ export const commands = {
2527
term.writeln(`\r\nError executing ${fileName}: ${error.message}`);
2628
}
2729
}
30+
},
31+
open: (term, args) => {
32+
if (args.length === 0) {
33+
term.writeln('\r\nUsage: js open <file.html>');
34+
return;
35+
}
36+
const fileName = args[0];
37+
const currentDir = vfs.cwd();
38+
const file = currentDir[fileName];
39+
40+
if (file === undefined) {
41+
term.writeln(`\r\nNo such file: ${fileName}`);
42+
return;
43+
}
44+
if (typeof file !== 'string') {
45+
term.writeln(`\r\n${fileName} does not contain valid HTML content.`);
46+
return;
47+
}
48+
if (!fileName.endsWith('.html')) {
49+
term.writeln(`\r\n${fileName} is not a valid HTML file name.`);
50+
return;
51+
}
52+
// Open the HTML file in a new window using Blob and Object URL
53+
const blob = new Blob([file], { type: 'text/html' });
54+
const url = URL.createObjectURL(blob);
55+
window.open(url, '_blank');
56+
// Optionally, revoke the object URL after a short delay
57+
setTimeout(() => URL.revokeObjectURL(url), 10000);
58+
}
59+
};
60+
61+
export const commands = {
62+
js: (term, args) => {
63+
if (args.length === 0) {
64+
term.writeln('\r\nAvailable js commands: execute, open');
65+
term.writeln('Usage: js <command> [arguments]');
66+
return;
67+
}
68+
const subCommand = args[0].toLowerCase();
69+
const subArgs = args.slice(1);
70+
if (jsCommands[subCommand]) {
71+
jsCommands[subCommand](term, subArgs);
72+
} else {
73+
term.writeln(`\r\nUnknown js command: ${subCommand}`);
74+
term.writeln('Available js commands: execute, open');
75+
}
2876
}
2977
};

src/script.js

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//script.js
1+
//script.js - main logic for miao shell
22
import { Terminal } from '@xterm/xterm';
33
import { FitAddon } from '@xterm/addon-fit';
44
import { WebLinksAddon } from 'xterm-addon-web-links';
@@ -7,6 +7,7 @@ import '@xterm/xterm/css/xterm.css';
77

88
import * as vfs from './commands/vfs.js'; // Import all VFS commands
99
import * as jsC from './commands/js.js'; // Import JavaScript commands
10+
import * as curl from './commands/curl.js'; // Import curl commands
1011

1112
// ANSI color codes for terminal formatting
1213
const colors = {
@@ -66,6 +67,8 @@ let currentLine = '';
6667
let cursorPosition = 0;
6768
let commandHistory = [];
6869
let historyIndex = -1;
70+
let undoStack = [];
71+
let redoStack = [];
6972

7073
// Load VFS on startup
7174
vfs.loadVFS();
@@ -104,7 +107,7 @@ const commands = {
104107
term.writeln('Version: 1.0.0');
105108
term.writeln('https://github.com/daniel4-scratch/miaoshell');
106109
},
107-
...vfs.commands, ...jsC.commands
110+
...vfs.commands, ...jsC.commands, ...curl.commands
108111
};
109112

110113
function executeCommand(input) {
@@ -153,12 +156,24 @@ function prompt() {
153156
// Handle terminal input
154157
term.onKey(e => {
155158
const { key, domEvent } = e;
159+
160+
// Save state for undo before any input-changing action
161+
function saveUndoState() {
162+
undoStack.push({ input: currentInput, cursor: cursorPosition });
163+
// Clear redo stack on new input
164+
redoStack = [];
165+
// Limit undo stack size
166+
if (undoStack.length > 100) undoStack.shift();
167+
}
168+
156169
if (domEvent.key === 'Enter') {
170+
saveUndoState();
157171
executeCommand(currentInput);
158172
currentInput = '';
159173
cursorPosition = 0; // Reset cursor position
160174
} else if (domEvent.key === 'Backspace') {
161175
if (currentInput.length > 0 && cursorPosition > 0) {
176+
saveUndoState();
162177
// Remove character at cursor position
163178
currentInput = currentInput.slice(0, cursorPosition - 1) + currentInput.slice(cursorPosition);
164179
cursorPosition--;
@@ -223,7 +238,42 @@ term.onKey(e => {
223238
cursorPosition = 0; // Reset cursor position
224239
}
225240
}
241+
} else if (domEvent.ctrlKey && domEvent.key === 'z') {
242+
// Undo (Ctrl+Z)
243+
if (undoStack.length > 0) {
244+
redoStack.push({ input: currentInput, cursor: cursorPosition });
245+
const prev = undoStack.pop();
246+
currentInput = prev.input;
247+
cursorPosition = prev.cursor;
248+
// Clear line and redraw
249+
term.write('\x1b[2K');
250+
term.write('\r');
251+
term.write('\x1b[1A');
252+
prompt();
253+
term.write(currentInput);
254+
// Move cursor to correct position
255+
if (cursorPosition < currentInput.length) {
256+
term.write(`\x1b[${currentInput.length - cursorPosition}D`);
257+
}
258+
}
259+
} else if (domEvent.ctrlKey && domEvent.key === 'y') {
260+
// Redo (Ctrl+Y)
261+
if (redoStack.length > 0) {
262+
undoStack.push({ input: currentInput, cursor: cursorPosition });
263+
const next = redoStack.pop();
264+
currentInput = next.input;
265+
cursorPosition = next.cursor;
266+
term.write('\x1b[2K');
267+
term.write('\r');
268+
term.write('\x1b[1A');
269+
prompt();
270+
term.write(currentInput);
271+
if (cursorPosition < currentInput.length) {
272+
term.write(`\x1b[${currentInput.length - cursorPosition}D`);
273+
}
274+
}
226275
} else if (typeof key === 'string' && key.length === 1) {
276+
saveUndoState();
227277
// Insert character at cursor position
228278
currentInput = currentInput.slice(0, cursorPosition) + key + currentInput.slice(cursorPosition);
229279

@@ -233,6 +283,29 @@ term.onKey(e => {
233283
cursorPosition++; // Update cursor position
234284
}
235285
});
286+
// Paste support: handle paste event and insert clipboard text at cursor position
287+
if (term.textarea) {
288+
term.textarea.addEventListener('paste', async (event) => {
289+
event.preventDefault();
290+
let pasteText = '';
291+
if (event.clipboardData) {
292+
pasteText = event.clipboardData.getData('text');
293+
} else if (window.clipboardData) {
294+
pasteText = window.clipboardData.getData('Text');
295+
}
296+
if (pasteText) {
297+
// Save undo state before paste
298+
undoStack.push({ input: currentInput, cursor: cursorPosition });
299+
redoStack = [];
300+
if (undoStack.length > 100) undoStack.shift();
301+
302+
// Insert pasted text at cursor position
303+
currentInput = currentInput.slice(0, cursorPosition) + pasteText + currentInput.slice(cursorPosition);
304+
term.write(pasteText);
305+
cursorPosition += pasteText.length;
306+
}
307+
});
308+
}
236309

237310
async function init() {
238311
// Initialize terminal

0 commit comments

Comments
 (0)