1use alloc::{format, string::String, vec, vec::Vec};
9
10use spin::RwLock;
11
12use crate::{
13 desktop::window_manager::{with_window_manager, InputEvent, WindowId},
14 error::KernelError,
15 fs::{get_vfs, OpenFlags},
16 sync::once_lock::GlobalState,
17};
18
19type Line = Vec<char>;
21
22pub struct TextEditor {
24 window_id: WindowId,
26
27 surface_id: u32,
29 pool_id: u32,
31 pool_buf_id: u32,
33
34 file_path: Option<String>,
36
37 buffer: Vec<Line>,
39
40 cursor_line: usize,
42 cursor_col: usize,
43
44 scroll_line: usize,
46
47 modified: bool,
49
50 width: u32,
52 height: u32,
53
54 #[allow(dead_code)] visible_rows: usize,
57
58 #[allow(dead_code)] visible_cols: usize,
61}
62
63impl TextEditor {
64 pub fn new(file_path: Option<String>) -> Result<Self, KernelError> {
66 let width = 800;
67 let height = 600;
68
69 let title_bar_h = 28u32;
71 let window_id =
72 with_window_manager(|wm| wm.create_window(150, 80, width, height + title_bar_h, 0))
73 .ok_or(KernelError::InvalidState {
74 expected: "initialized",
75 actual: "uninitialized",
76 })??;
77
78 let (surface_id, pool_id, pool_buf_id) =
80 super::renderer::create_app_surface(150, 80, width, height + title_bar_h);
81
82 let char_width = 8;
84 let char_height = 16;
85 let visible_cols = (width as usize) / char_width;
86 let visible_rows = ((height as usize) - 24) / char_height; let mut editor = Self {
89 window_id,
90 surface_id,
91 pool_id,
92 pool_buf_id,
93 file_path: file_path.clone(),
94 buffer: vec![Vec::new()], cursor_line: 0,
96 cursor_col: 0,
97 scroll_line: 0,
98 modified: false,
99 width,
100 height,
101 visible_rows,
102 visible_cols,
103 };
104
105 if let Some(ref path) = file_path {
107 editor.load_file(path)?;
108 }
109
110 println!("[TEXT-EDITOR] Created editor window {}", window_id);
111
112 Ok(editor)
113 }
114
115 pub fn load_file(&mut self, path: &str) -> Result<(), KernelError> {
117 println!("[TEXT-EDITOR] Loading file: {}", path);
118
119 let vfs = get_vfs();
120
121 match vfs.read().open(path, OpenFlags::read_only()) {
123 Ok(node) => {
124 let metadata = node.metadata().map_err(|_| KernelError::InvalidArgument {
126 name: "file_metadata",
127 value: "failed_to_read",
128 })?;
129 let mut file_buffer = vec![0u8; metadata.size];
130
131 match node.read(0, &mut file_buffer) {
132 Ok(_bytes_read) => {
133 self.buffer.clear();
135 let content = core::str::from_utf8(&file_buffer).map_err(|_| {
136 KernelError::InvalidArgument {
137 name: "file_content",
138 value: "invalid_utf8",
139 }
140 })?;
141
142 for line in content.lines() {
143 self.buffer.push(line.chars().collect());
144 }
145
146 if self.buffer.is_empty() {
147 self.buffer.push(Vec::new());
148 }
149
150 self.modified = false;
151 println!("[TEXT-EDITOR] Loaded {} lines", self.buffer.len());
152 }
153 Err(_e) => {
154 println!("[TEXT-EDITOR] Failed to read file");
155 return Err(KernelError::InvalidArgument {
156 name: "file_read",
157 value: "failed",
158 });
159 }
160 }
161 }
162 Err(_e) => {
163 println!("[TEXT-EDITOR] Failed to open file");
164 return Err(KernelError::InvalidArgument {
165 name: "file_open",
166 value: "failed",
167 });
168 }
169 }
170
171 Ok(())
172 }
173
174 pub fn save_file(&mut self) -> Result<(), KernelError> {
176 let path = self
177 .file_path
178 .as_ref()
179 .ok_or(KernelError::InvalidArgument {
180 name: "file_path",
181 value: "no_path_specified",
182 })?;
183
184 println!("[TEXT-EDITOR] Saving file: {}", path);
185
186 let mut content = String::new();
188 for line in &self.buffer {
189 for &ch in line {
190 content.push(ch);
191 }
192 content.push('\n');
193 }
194
195 let bytes = content.as_bytes();
196
197 let vfs = get_vfs();
199
200 match vfs.read().open(path, OpenFlags::read_only()) {
202 Ok(node) => {
203 node.write(0, bytes)
205 .map_err(|_| KernelError::InvalidArgument {
206 name: "file_write",
207 value: "failed",
208 })?;
209 self.modified = false;
210 println!("[TEXT-EDITOR] File saved ({} bytes)", bytes.len());
211 Ok(())
212 }
213 Err(_) => {
214 println!("[TEXT-EDITOR] Failed to save file: file does not exist");
217 Err(KernelError::InvalidArgument {
218 name: "file_save",
219 value: "file_not_found",
220 })
221 }
222 }
223 }
224
225 pub fn process_input(&mut self, event: InputEvent) -> Result<(), KernelError> {
227 if let InputEvent::KeyPress {
228 character,
229 scancode,
230 } = event
231 {
232 match character {
233 '\n' | '\r' => {
234 self.insert_newline();
236 }
237 '\x08' => {
238 self.delete_char();
240 }
241 '\x13' => {
242 if let Err(e) = self.save_file() {
244 crate::println!("[EDITOR] Save failed: {:?}", e);
245 }
246 }
247 '\x0F' => {
248 crate::println!("[EDITOR] Open file: use file manager to open files");
250 }
251 '\x0E' => {
252 self.buffer.clear();
254 self.buffer.push(alloc::vec::Vec::new());
255 self.cursor_line = 0;
256 self.cursor_col = 0;
257 self.scroll_line = 0;
258 self.file_path = None;
259 self.modified = false;
260 }
261 '\t' => {
262 for _ in 0..4 {
264 self.insert_char(' ');
265 }
266 }
267 ch if (' '..='~').contains(&ch) => {
268 self.insert_char(ch);
270 }
271 _ => {
272 match scancode {
274 72 => self.move_cursor_up(), 80 => self.move_cursor_down(), 75 => self.move_cursor_left(), 77 => self.move_cursor_right(), _ => {}
279 }
280 }
281 }
282 }
283
284 Ok(())
285 }
286
287 fn insert_char(&mut self, ch: char) {
289 if self.cursor_line < self.buffer.len() {
290 self.buffer[self.cursor_line].insert(self.cursor_col, ch);
291 self.cursor_col += 1;
292 self.modified = true;
293 }
294 }
295
296 fn delete_char(&mut self) {
298 if self.cursor_col > 0 {
299 self.buffer[self.cursor_line].remove(self.cursor_col - 1);
300 self.cursor_col -= 1;
301 self.modified = true;
302 } else if self.cursor_line > 0 {
303 let current_line = self.buffer.remove(self.cursor_line);
305 self.cursor_line -= 1;
306 self.cursor_col = self.buffer[self.cursor_line].len();
307 self.buffer[self.cursor_line].extend(current_line);
308 self.modified = true;
309 }
310 }
311
312 fn insert_newline(&mut self) {
314 if self.cursor_line < self.buffer.len() {
315 let rest = self.buffer[self.cursor_line].split_off(self.cursor_col);
316 self.cursor_line += 1;
317 self.buffer.insert(self.cursor_line, rest);
318 self.cursor_col = 0;
319 self.modified = true;
320 }
321 }
322
323 fn move_cursor_up(&mut self) {
325 if self.cursor_line > 0 {
326 self.cursor_line -= 1;
327 self.cursor_col = self.cursor_col.min(self.buffer[self.cursor_line].len());
328 }
329 }
330
331 fn move_cursor_down(&mut self) {
333 if self.cursor_line < self.buffer.len() - 1 {
334 self.cursor_line += 1;
335 self.cursor_col = self.cursor_col.min(self.buffer[self.cursor_line].len());
336 }
337 }
338
339 fn move_cursor_left(&mut self) {
341 if self.cursor_col > 0 {
342 self.cursor_col -= 1;
343 } else if self.cursor_line > 0 {
344 self.cursor_line -= 1;
345 self.cursor_col = self.buffer[self.cursor_line].len();
346 }
347 }
348
349 fn move_cursor_right(&mut self) {
351 if self.cursor_col < self.buffer[self.cursor_line].len() {
352 self.cursor_col += 1;
353 } else if self.cursor_line < self.buffer.len() - 1 {
354 self.cursor_line += 1;
355 self.cursor_col = 0;
356 }
357 }
358
359 pub fn render(&self, buf: &mut [u8], width: usize, height: usize) -> Result<(), KernelError> {
363 use super::renderer::{draw_char_into_buffer, draw_string_into_buffer};
364
365 let char_h = 16;
366
367 for chunk in buf.chunks_exact_mut(4) {
369 chunk[0] = 0x1E; chunk[1] = 0x1E; chunk[2] = 0x1E; chunk[3] = 0xFF; }
374
375 for x in 0..width {
377 for dy in 0..20 {
378 let offset = (dy * width + x) * 4;
379 if offset + 3 < buf.len() {
380 buf[offset] = 0x40; buf[offset + 1] = 0x30; buf[offset + 2] = 0x25; buf[offset + 3] = 0xFF;
384 }
385 }
386 }
387
388 let status = if let Some(ref path) = self.file_path {
390 if self.modified {
391 format!(
392 "{}* L{} C{}",
393 path,
394 self.cursor_line + 1,
395 self.cursor_col + 1
396 )
397 } else {
398 format!(
399 "{} L{} C{}",
400 path,
401 self.cursor_line + 1,
402 self.cursor_col + 1
403 )
404 }
405 } else {
406 format!(
407 "[New File] L{} C{}",
408 self.cursor_line + 1,
409 self.cursor_col + 1
410 )
411 };
412 draw_string_into_buffer(buf, width, status.as_bytes(), 6, 2, 0xCCCCCC);
413
414 let text_y_start = 24;
416 let max_visible = (height - text_y_start) / char_h;
417
418 for (i, line) in self
419 .buffer
420 .iter()
421 .enumerate()
422 .skip(self.scroll_line)
423 .take(max_visible)
424 {
425 let row = i - self.scroll_line;
426 let y = text_y_start + row * char_h;
427
428 let line_num = i + 1;
430 let num_str = format!("{:>4} ", line_num);
431 draw_string_into_buffer(buf, width, num_str.as_bytes(), 0, y, 0x606060);
432
433 let text_x = 5 * 8; for (j, &ch) in line.iter().enumerate() {
436 if ch as u32 >= 0x20 && (ch as u32) <= 0x7E {
437 draw_char_into_buffer(buf, width, ch as u8, text_x + j * 8, y, 0xD4D4D4);
438 }
439 }
440
441 if i == self.cursor_line {
443 let cursor_px = text_x + self.cursor_col * 8;
444 for dy in 0..char_h {
445 for dx in 0..2 {
446 let offset = ((y + dy) * width + cursor_px + dx) * 4;
447 if offset + 3 < buf.len() {
448 buf[offset] = 0xFF; buf[offset + 1] = 0xFF; buf[offset + 2] = 0xFF; buf[offset + 3] = 0xFF;
452 }
453 }
454 }
455 }
456 }
457
458 let status_y = height.saturating_sub(20);
460 for x in 0..width {
461 for dy in 0..20 {
462 let offset = ((status_y + dy) * width + x) * 4;
463 if offset + 3 < buf.len() {
464 buf[offset] = 0x50; buf[offset + 1] = 0x40; buf[offset + 2] = 0x30; buf[offset + 3] = 0xFF;
468 }
469 }
470 }
471 let mod_indicator = if self.modified { "*" } else { "" };
473 let file_name = self.file_path.as_deref().unwrap_or("[New File]");
474 let bottom_status = format!(
475 " {}{} | Ln {}, Col {} | Ctrl+S Save Ctrl+N New",
476 file_name,
477 mod_indicator,
478 self.cursor_line + 1,
479 self.cursor_col + 1,
480 );
481 draw_string_into_buffer(
482 buf,
483 width,
484 bottom_status.as_bytes(),
485 0,
486 status_y + 2,
487 0xCCCCCC,
488 );
489
490 Ok(())
491 }
492
493 pub fn window_id(&self) -> WindowId {
495 self.window_id
496 }
497
498 pub fn surface_id(&self) -> u32 {
500 self.surface_id
501 }
502
503 pub fn render_to_surface(&self) {
507 let w = self.width as usize;
508 let content_h = self.height as usize;
509 let title_bar_h: usize = 28;
510 let total_h = content_h + title_bar_h;
511 let mut pixels = vec![0u8; w * total_h * 4];
512
513 let mut content = vec![0u8; w * content_h * 4];
514 let _ = self.render(&mut content, w, content_h);
515 for y in 0..content_h {
516 let src_off = y * w * 4;
517 let dst_off = (y + title_bar_h) * w * 4;
518 pixels[dst_off..dst_off + w * 4].copy_from_slice(&content[src_off..src_off + w * 4]);
519 }
520
521 super::renderer::draw_title_bar_into_surface(&mut pixels, w, total_h, self.window_id);
522
523 super::renderer::update_surface_pixels(
524 self.surface_id,
525 self.pool_id,
526 self.pool_buf_id,
527 &pixels,
528 );
529 }
530}
531
532static TEXT_EDITOR: GlobalState<RwLock<TextEditor>> = GlobalState::new();
534
535pub fn init() -> Result<(), KernelError> {
537 println!("[TEXT-EDITOR] Text editor initialized");
538 Ok(())
539}
540
541pub fn create_text_editor(file_path: Option<String>) -> Result<(), KernelError> {
543 let editor = TextEditor::new(file_path)?;
544 TEXT_EDITOR
545 .init(RwLock::new(editor))
546 .map_err(|_| KernelError::InvalidState {
547 expected: "uninitialized",
548 actual: "initialized",
549 })?;
550 Ok(())
551}
552
553pub fn with_text_editor<R, F: FnOnce(&RwLock<TextEditor>) -> R>(f: F) -> Option<R> {
555 TEXT_EDITOR.with(f)
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561
562 #[test]
563 fn test_char_insertion() {
564 }
566
567 #[test]
568 fn test_newline_insertion() {
569 }
571}