Files
SUB-OS/userland/lazybox/nano.c
T

340 lines
11 KiB
C

#include <userland/nano.h>
#include <fs/vfs.h>
#include <drivers/keyboard.h>
#include <drivers/tty.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <kernel/printk.h>
#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
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;
}