#include #include #include #include #include #include #include #define NANO_MAX_LINES 512 #define NANO_LINE_LEN 128 #define NANO_SCREEN_ROWS 21 static char nano_lines[NANO_MAX_LINES][NANO_LINE_LEN]; static uint32_t nano_line_count = 1; static int nano_cursor_row = 0; static int nano_cursor_col = 0; static int nano_top_row = 0; static bool nano_modified = false; static char nano_filename[256]; static char nano_status[128]; static char nano_cut_buffer[NANO_LINE_LEN]; static bool nano_has_cut = false; static void nano_render(void) { // 1. Title bar (Row 0) tty_set_cursor(0, 0); char title[128]; snprintf(title, sizeof(title), " GNU nano 2.0.0 File: %-32s %s", nano_filename[0] ? nano_filename : "New Buffer", nano_modified ? "[Modified]" : " "); printk(ANSI_INVERT "%-80s" ANSI_RESET, title); // 2. Viewport Lines (Rows 1 to 21) for (int r = 0; r < NANO_SCREEN_ROWS; r++) { tty_set_cursor(r + 1, 0); int line_idx = nano_top_row + r; if (line_idx < (int)nano_line_count) { printk("\033[K%s", nano_lines[line_idx]); } else { printk("\033[K" ANSI_BRIGHT_BLACK "~" ANSI_RESET); } } // 3. Status Message Bar (Row 22) tty_set_cursor(22, 0); printk(ANSI_BRIGHT_CYAN "\033[K%-78s" ANSI_RESET, nano_status); // 4. Shortcut Cheatsheet Footer (Row 23) tty_set_cursor(23, 0); printk(ANSI_INVERT "%-80s" ANSI_RESET, "^G Help ^O WriteOut ^W Where Is ^K Cut Line ^U Paste ^X Exit"); // 5. Position Hardware Cursor int scr_row = (nano_cursor_row - nano_top_row) + 1; int scr_col = nano_cursor_col; if (scr_row >= 1 && scr_row <= NANO_SCREEN_ROWS) { tty_set_cursor(scr_row, scr_col); } } static void nano_save(void) { if (!nano_filename[0]) { strcpy(nano_status, "[ Error: No filename specified ]"); return; } int fd = vfs_open(nano_filename, O_CREAT | O_WRONLY | O_TRUNC); if (fd < 0) { strcpy(nano_status, "[ Error: Cannot open file for writing ]"); return; } size_t total_bytes = 0; for (uint32_t i = 0; i < nano_line_count; i++) { size_t len = strlen(nano_lines[i]); if (len > 0) { vfs_write(fd, nano_lines[i], len); total_bytes += len; } if (i + 1 < nano_line_count || len > 0) { vfs_write(fd, "\n", 1); total_bytes += 1; } } vfs_close(fd); nano_modified = false; snprintf(nano_status, sizeof(nano_status), "[ Wrote %llu bytes to %s ]", (uint64_t)total_bytes, nano_filename); } static void nano_load(const char* path) { memset(nano_lines, 0, sizeof(nano_lines)); nano_line_count = 0; nano_cursor_row = 0; nano_cursor_col = 0; nano_top_row = 0; nano_modified = false; nano_status[0] = '\0'; if (!path || path[0] == '\0') { nano_filename[0] = '\0'; nano_line_count = 1; nano_lines[0][0] = '\0'; strcpy(nano_status, "[ New Buffer - Use ^O to save ]"); return; } vfs_resolve_path(path, nano_filename, sizeof(nano_filename)); int fd = vfs_open(nano_filename, O_RDONLY); if (fd < 0) { nano_line_count = 1; nano_lines[0][0] = '\0'; snprintf(nano_status, sizeof(nano_status), "[ New File: %s ]", nano_filename); return; } char buf[512]; ssize_t bytes; int cur_line = 0; int cur_col = 0; while ((bytes = vfs_read(fd, buf, sizeof(buf))) > 0) { for (ssize_t i = 0; i < bytes; i++) { char c = buf[i]; if (c == '\r') continue; if (c == '\n') { nano_lines[cur_line][cur_col] = '\0'; cur_line++; cur_col = 0; if (cur_line >= NANO_MAX_LINES) break; } else { if (cur_col < NANO_LINE_LEN - 1) { nano_lines[cur_line][cur_col++] = c; } } } if (cur_line >= NANO_MAX_LINES) break; } vfs_close(fd); if (cur_col > 0 || cur_line == 0) { nano_lines[cur_line][cur_col] = '\0'; cur_line++; } nano_line_count = cur_line > 0 ? cur_line : 1; snprintf(nano_status, sizeof(nano_status), "[ Read %llu line(s) from %s ]", (uint64_t)nano_line_count, nano_filename); } int nano_main(int argc, char** argv) { tty_clear(); if (argc < 2) { nano_load(""); } else { nano_load(argv[1]); } bool running = true; while (running) { // Adjust viewport scrolling if (nano_cursor_row < nano_top_row) { nano_top_row = nano_cursor_row; } else if (nano_cursor_row >= nano_top_row + NANO_SCREEN_ROWS) { nano_top_row = nano_cursor_row - NANO_SCREEN_ROWS + 1; } // Clamp cursor column int line_len = (int)strlen(nano_lines[nano_cursor_row]); if (nano_cursor_col > line_len) { nano_cursor_col = line_len; } if (nano_cursor_col < 0) nano_cursor_col = 0; nano_render(); uint16_t key = keyboard_get_key(); switch (key) { case 0x18: // Ctrl+X case 0x11: // Ctrl+Q case 27: // ESC case KEY_F10: running = false; break; case 0x0F: // Ctrl+O case 0x13: // Ctrl+S case KEY_F2: nano_save(); break; case 0x0B: // Ctrl+K -> Cut line if (nano_line_count > 0) { strcpy(nano_cut_buffer, nano_lines[nano_cursor_row]); nano_has_cut = true; for (uint32_t i = nano_cursor_row; i + 1 < nano_line_count; i++) { strcpy(nano_lines[i], nano_lines[i + 1]); } if (nano_line_count > 1) { nano_line_count--; } else { nano_lines[0][0] = '\0'; } nano_modified = true; strcpy(nano_status, "[ Cut 1 line ]"); } break; case 0x15: // Ctrl+U -> Uncut / Paste if (nano_has_cut && nano_line_count < NANO_MAX_LINES) { for (int i = (int)nano_line_count; i > nano_cursor_row; i--) { strcpy(nano_lines[i], nano_lines[i - 1]); } strcpy(nano_lines[nano_cursor_row], nano_cut_buffer); nano_line_count++; nano_modified = true; strcpy(nano_status, "[ Pasted 1 line ]"); } break; case 0x07: // Ctrl+G -> Help case KEY_F1: strcpy(nano_status, "[ Help: ^O=Save, ^X=Exit, ^K=Cut, ^U=Paste, Arrows=Move ]"); break; case KEY_UP: if (nano_cursor_row > 0) { nano_cursor_row--; } break; case KEY_DOWN: if (nano_cursor_row + 1 < (int)nano_line_count) { nano_cursor_row++; } break; case KEY_LEFT: if (nano_cursor_col > 0) { nano_cursor_col--; } else if (nano_cursor_row > 0) { nano_cursor_row--; nano_cursor_col = strlen(nano_lines[nano_cursor_row]); } break; case KEY_RIGHT: if (nano_cursor_col < line_len) { nano_cursor_col++; } else if (nano_cursor_row + 1 < (int)nano_line_count) { nano_cursor_row++; nano_cursor_col = 0; } break; case KEY_HOME: nano_cursor_col = 0; break; case KEY_END: nano_cursor_col = line_len; break; case KEY_PAGE_UP: nano_cursor_row = nano_cursor_row >= NANO_SCREEN_ROWS ? nano_cursor_row - NANO_SCREEN_ROWS : 0; break; case KEY_PAGE_DOWN: nano_cursor_row = (nano_cursor_row + NANO_SCREEN_ROWS < (int)nano_line_count) ? nano_cursor_row + NANO_SCREEN_ROWS : (int)nano_line_count - 1; break; case '\n': case '\r': // Enter -> Split line if (nano_line_count < NANO_MAX_LINES) { for (int i = (int)nano_line_count; i > nano_cursor_row + 1; i--) { strcpy(nano_lines[i], nano_lines[i - 1]); } strcpy(nano_lines[nano_cursor_row + 1], &nano_lines[nano_cursor_row][nano_cursor_col]); nano_lines[nano_cursor_row][nano_cursor_col] = '\0'; nano_line_count++; nano_cursor_row++; nano_cursor_col = 0; nano_modified = true; } break; case '\b': // Backspace (0x08) case 0x7F: // DEL if (nano_cursor_col > 0) { memmove(&nano_lines[nano_cursor_row][nano_cursor_col - 1], &nano_lines[nano_cursor_row][nano_cursor_col], strlen(&nano_lines[nano_cursor_row][nano_cursor_col]) + 1); nano_cursor_col--; nano_modified = true; } else if (nano_cursor_row > 0) { int prev_len = (int)strlen(nano_lines[nano_cursor_row - 1]); if (prev_len + line_len < NANO_LINE_LEN - 1) { strcat(nano_lines[nano_cursor_row - 1], nano_lines[nano_cursor_row]); for (uint32_t i = nano_cursor_row; i + 1 < nano_line_count; i++) { strcpy(nano_lines[i], nano_lines[i + 1]); } nano_line_count--; nano_cursor_row--; nano_cursor_col = prev_len; nano_modified = true; } } break; case KEY_DELETE: if (nano_cursor_col < line_len) { memmove(&nano_lines[nano_cursor_row][nano_cursor_col], &nano_lines[nano_cursor_row][nano_cursor_col + 1], strlen(&nano_lines[nano_cursor_row][nano_cursor_col + 1]) + 1); nano_modified = true; } break; default: if (key >= 32 && key <= 126) { char c = (char)key; if (line_len < NANO_LINE_LEN - 2) { memmove(&nano_lines[nano_cursor_row][nano_cursor_col + 1], &nano_lines[nano_cursor_row][nano_cursor_col], strlen(&nano_lines[nano_cursor_row][nano_cursor_col]) + 1); nano_lines[nano_cursor_row][nano_cursor_col] = c; nano_cursor_col++; nano_modified = true; } } break; } } tty_clear(); return 0; }