aethelos-source/aethelos.iso
aethelos-source/aethelos.iso
This is a binary file and will not be displayed.
+23
-3
aethelos-source/heartwood/src/attunement/keyboard.rs
+23
-3
aethelos-source/heartwood/src/attunement/keyboard.rs
···
96
96
0x32 => Some('m'),
97
97
0x39 => Some(' '), // Space
98
98
0x1C => Some('\n'), // Enter
99
+
0x0E => Some('\x08'), // Backspace
100
+
0x48 => Some('\x01'), // Up arrow (special control char)
101
+
0x50 => Some('\x02'), // Down arrow (special control char)
99
102
_ => None, // Ignore other keys
100
103
};
101
104
102
-
// If we got a valid character, send it to both display and shell buffer
105
+
// Handle the character
103
106
if let Some(character) = ch {
104
-
crate::print!("{}", character);
105
-
crate::eldarin::handle_char(character);
107
+
match character {
108
+
'\x08' => {
109
+
// Backspace: erase character visually and update buffer
110
+
crate::eldarin::handle_backspace();
111
+
}
112
+
'\x01' => {
113
+
// Up arrow: navigate to previous command
114
+
crate::eldarin::handle_arrow_up();
115
+
}
116
+
'\x02' => {
117
+
// Down arrow: navigate to next command
118
+
crate::eldarin::handle_arrow_down();
119
+
}
120
+
_ => {
121
+
// Regular character: display and buffer it
122
+
crate::print!("{}", character);
123
+
crate::eldarin::handle_char(character);
124
+
}
125
+
}
106
126
}
107
127
}
108
128
}
+200
-1
aethelos-source/heartwood/src/eldarin.rs
+200
-1
aethelos-source/heartwood/src/eldarin.rs
···
16
16
/// Maximum command buffer size
17
17
const BUFFER_SIZE: usize = 256;
18
18
19
+
/// Maximum number of commands to store in history
20
+
const HISTORY_SIZE: usize = 32;
21
+
19
22
/// The Scroll Buffer - stores the command being typed
20
23
pub struct CommandBuffer {
21
24
buffer: [u8; BUFFER_SIZE],
22
25
position: usize,
26
+
}
27
+
28
+
/// Command History - stores previous commands for recall
29
+
pub struct CommandHistory {
30
+
/// Ring buffer of previous commands
31
+
commands: [[u8; BUFFER_SIZE]; HISTORY_SIZE],
32
+
/// Length of each command in the ring buffer
33
+
lengths: [usize; HISTORY_SIZE],
34
+
/// Current write position in ring buffer
35
+
write_pos: usize,
36
+
/// Current read position when navigating history
37
+
read_pos: usize,
38
+
/// Total number of commands in history (up to HISTORY_SIZE)
39
+
count: usize,
40
+
/// True if currently navigating history
41
+
navigating: bool,
23
42
}
24
43
25
44
impl CommandBuffer {
···
67
86
pub fn is_empty(&self) -> bool {
68
87
self.position == 0
69
88
}
89
+
90
+
/// Set buffer contents from a byte slice
91
+
pub fn set_from_bytes(&mut self, bytes: &[u8]) {
92
+
self.clear();
93
+
let len = bytes.len().min(BUFFER_SIZE);
94
+
self.buffer[..len].copy_from_slice(&bytes[..len]);
95
+
self.position = len;
96
+
}
97
+
}
98
+
99
+
impl CommandHistory {
100
+
pub const fn new() -> Self {
101
+
CommandHistory {
102
+
commands: [[0; BUFFER_SIZE]; HISTORY_SIZE],
103
+
lengths: [0; HISTORY_SIZE],
104
+
write_pos: 0,
105
+
read_pos: 0,
106
+
count: 0,
107
+
navigating: false,
108
+
}
109
+
}
110
+
111
+
/// Add a command to history
112
+
pub fn add(&mut self, cmd: &str) {
113
+
if cmd.is_empty() {
114
+
return;
115
+
}
116
+
117
+
let bytes = cmd.as_bytes();
118
+
let len = bytes.len().min(BUFFER_SIZE);
119
+
120
+
self.commands[self.write_pos][..len].copy_from_slice(&bytes[..len]);
121
+
self.lengths[self.write_pos] = len;
122
+
123
+
self.write_pos = (self.write_pos + 1) % HISTORY_SIZE;
124
+
if self.count < HISTORY_SIZE {
125
+
self.count += 1;
126
+
}
127
+
128
+
// Reset navigation state
129
+
self.navigating = false;
130
+
self.read_pos = self.write_pos;
131
+
}
132
+
133
+
/// Navigate to previous command (up arrow)
134
+
/// Returns Some(command) if available, None if at the beginning
135
+
pub fn previous(&mut self) -> Option<&[u8]> {
136
+
if self.count == 0 {
137
+
return None;
138
+
}
139
+
140
+
if !self.navigating {
141
+
// First time navigating, start from most recent
142
+
self.navigating = true;
143
+
self.read_pos = if self.write_pos == 0 {
144
+
self.count - 1
145
+
} else {
146
+
self.write_pos - 1
147
+
};
148
+
} else {
149
+
// Already navigating, go back one more
150
+
if self.read_pos == 0 {
151
+
self.read_pos = self.count - 1;
152
+
} else {
153
+
self.read_pos -= 1;
154
+
}
155
+
}
156
+
157
+
let len = self.lengths[self.read_pos];
158
+
Some(&self.commands[self.read_pos][..len])
159
+
}
160
+
161
+
/// Navigate to next command (down arrow)
162
+
/// Returns Some(command) if available, None if at the end
163
+
pub fn next(&mut self) -> Option<&[u8]> {
164
+
if !self.navigating {
165
+
return None;
166
+
}
167
+
168
+
self.read_pos = (self.read_pos + 1) % self.count;
169
+
170
+
// If we're back at the write position, stop navigating (return to empty)
171
+
if self.read_pos == self.write_pos {
172
+
self.navigating = false;
173
+
return None;
174
+
}
175
+
176
+
let len = self.lengths[self.read_pos];
177
+
Some(&self.commands[self.read_pos][..len])
178
+
}
70
179
}
71
180
72
181
/// Global command buffer (interrupt-safe for keyboard input)
73
182
static mut COMMAND_BUFFER: MaybeUninit<InterruptSafeLock<CommandBuffer>> = MaybeUninit::uninit();
74
183
static mut BUFFER_INITIALIZED: bool = false;
75
184
185
+
/// Global command history
186
+
static mut COMMAND_HISTORY: MaybeUninit<InterruptSafeLock<CommandHistory>> = MaybeUninit::uninit();
187
+
static mut HISTORY_INITIALIZED: bool = false;
188
+
76
189
/// Initialize the shell
77
190
pub fn init() {
78
191
unsafe {
···
80
193
let lock = InterruptSafeLock::new(buffer);
81
194
core::ptr::write(COMMAND_BUFFER.as_mut_ptr(), lock);
82
195
BUFFER_INITIALIZED = true;
196
+
197
+
let history = CommandHistory::new();
198
+
let history_lock = InterruptSafeLock::new(history);
199
+
core::ptr::write(COMMAND_HISTORY.as_mut_ptr(), history_lock);
200
+
HISTORY_INITIALIZED = true;
83
201
}
84
202
}
85
203
86
204
/// Get reference to command buffer
87
205
unsafe fn get_buffer() -> &'static InterruptSafeLock<CommandBuffer> {
88
206
COMMAND_BUFFER.assume_init_ref()
207
+
}
208
+
209
+
/// Get reference to command history
210
+
unsafe fn get_history() -> &'static InterruptSafeLock<CommandHistory> {
211
+
COMMAND_HISTORY.assume_init_ref()
89
212
}
90
213
91
214
/// Handle a character from keyboard input
···
122
245
}
123
246
}
124
247
248
+
/// Handle backspace key - erase character visually and from buffer
249
+
pub fn handle_backspace() {
250
+
unsafe {
251
+
if !BUFFER_INITIALIZED {
252
+
return;
253
+
}
254
+
255
+
let mut buffer = get_buffer().lock();
256
+
if buffer.pop() {
257
+
// Erase character visually (VGA driver handles the erasure)
258
+
crate::print!("\x08");
259
+
}
260
+
}
261
+
}
262
+
263
+
/// Handle up arrow - navigate to previous command in history
264
+
pub fn handle_arrow_up() {
265
+
unsafe {
266
+
if !BUFFER_INITIALIZED || !HISTORY_INITIALIZED {
267
+
return;
268
+
}
269
+
270
+
let mut history = get_history().lock();
271
+
if let Some(cmd_bytes) = history.previous() {
272
+
let mut buffer = get_buffer().lock();
273
+
let current_len = buffer.as_str().len();
274
+
275
+
// Erase current line (VGA driver erases each character as we backspace)
276
+
for _ in 0..current_len {
277
+
crate::print!("\x08");
278
+
}
279
+
280
+
// Set buffer to historical command and display it
281
+
buffer.set_from_bytes(cmd_bytes);
282
+
if let Ok(cmd_str) = core::str::from_utf8(cmd_bytes) {
283
+
crate::print!("{}", cmd_str);
284
+
}
285
+
}
286
+
}
287
+
}
288
+
289
+
/// Handle down arrow - navigate to next command in history
290
+
pub fn handle_arrow_down() {
291
+
unsafe {
292
+
if !BUFFER_INITIALIZED || !HISTORY_INITIALIZED {
293
+
return;
294
+
}
295
+
296
+
let mut history = get_history().lock();
297
+
let mut buffer = get_buffer().lock();
298
+
let current_len = buffer.as_str().len();
299
+
300
+
// Erase current line (VGA driver erases each character as we backspace)
301
+
for _ in 0..current_len {
302
+
crate::print!("\x08");
303
+
}
304
+
305
+
if let Some(cmd_bytes) = history.next() {
306
+
// Set buffer to next historical command and display it
307
+
buffer.set_from_bytes(cmd_bytes);
308
+
if let Ok(cmd_str) = core::str::from_utf8(cmd_bytes) {
309
+
crate::print!("{}", cmd_str);
310
+
}
311
+
} else {
312
+
// At the end of history, clear buffer
313
+
buffer.clear();
314
+
}
315
+
}
316
+
}
317
+
125
318
/// Check for pending commands and execute them (call from shell thread)
126
319
pub fn poll() {
127
320
unsafe {
···
167
360
buffer.clear();
168
361
}
169
362
170
-
// Execute command
363
+
// Execute command and save to history
171
364
if cmd_len > 0 {
172
365
if let Ok(cmd_str) = core::str::from_utf8(&cmd_copy[..cmd_len]) {
366
+
// Save to history before executing
367
+
if HISTORY_INITIALIZED {
368
+
let mut history = get_history().lock();
369
+
history.add(cmd_str);
370
+
}
371
+
173
372
execute_command(cmd_str);
174
373
}
175
374
}
+22
-1
aethelos-source/heartwood/src/vga_buffer.rs
+22
-1
aethelos-source/heartwood/src/vga_buffer.rs
···
102
102
pub fn write_byte(&mut self, byte: u8) {
103
103
match byte {
104
104
b'\n' => self.new_line(),
105
+
b'\x08' => {
106
+
// Backspace: move cursor back and erase character
107
+
if self.column_position > 0 {
108
+
self.column_position -= 1;
109
+
let row = self.row_position;
110
+
let col = self.column_position;
111
+
112
+
// Write a space to erase the character
113
+
unsafe {
114
+
core::ptr::write_volatile(
115
+
&mut self.buffer.chars[row][col] as *mut ScreenChar,
116
+
ScreenChar {
117
+
ascii_character: b' ',
118
+
color_code: self.color_code,
119
+
}
120
+
);
121
+
}
122
+
123
+
move_cursor(self.row_position, self.column_position);
124
+
}
125
+
}
105
126
byte => {
106
127
if self.column_position >= BUFFER_WIDTH {
107
128
self.new_line();
···
135
156
}
136
157
for byte in s.bytes() {
137
158
match byte {
138
-
0x20..=0x7e | b'\n' => self.write_byte(byte),
159
+
0x20..=0x7e | b'\n' | b'\x08' => self.write_byte(byte),
139
160
_ => self.write_byte(0xfe), // ■ for unprintable
140
161
}
141
162
}
aethelos-source/isodir/boot/aethelos/heartwood.bin
aethelos-source/isodir/boot/aethelos/heartwood.bin
This is a binary file and will not be displayed.