// edit.c - tiny native text editor for nt-lite // Controls: // arrows/home/end/pageup/pagedown move // enter/backspace/delete/tab edit // ctrl+s save // ctrl+q quit (press twice if dirty) #include #include #include #include #include #define GLYPH_W 8 #define GLYPH_H 16 #define MAX_LINES 512 #define MAX_LINE 256 #define PATH_MAX2 260 #define MSG_MAX 160 #define COL_BG 0x000000u #define COL_FG 0xC8C8C8u #define COL_DIM 0x808080u #define COL_STATUS 0x303030u #define COL_STATUSF 0xFFFFFFu #define COL_CURSOR 0xE0E0E0u #define COL_CURSORF 0x000000u #define MOD_LSHIFT (1u << 0) #define MOD_RSHIFT (1u << 1) #define MOD_LCTRL (1u << 2) #define MOD_RCTRL (1u << 3) #define MOD_CAPS (1u << 6) #define MOD_SHIFT (MOD_LSHIFT | MOD_RSHIFT) #define MOD_CTRL (MOD_LCTRL | MOD_RCTRL) #define SC_ESC 0x01u #define SC_BACKSPACE 0x0Eu #define SC_TAB 0x0Fu #define SC_Q 0x10u #define SC_S 0x1Fu #define SC_ENTER 0x1Cu #define SC_HOME 0x47u #define SC_UP 0x48u #define SC_PGUP 0x49u #define SC_LEFT 0x4Bu #define SC_RIGHT 0x4Du #define SC_END 0x4Fu #define SC_DOWN 0x50u #define SC_PGDN 0x51u #define SC_DEL 0x53u typedef struct LINE { int len; char text[MAX_LINE]; } LINE; static LINE gLines[MAX_LINES]; static int gLineCount = 1; static int gCx = 0; static int gCy = 0; static int gTop = 0; static int gLeft = 0; static int gCols = 80; static int gRows = 25; static bool gDirty = false; static bool gQuitArmed = false; static char gFilename[PATH_MAX2] = "untitled.txt"; static char gMessage[MSG_MAX] = "ready"; static const char gUnshifted[128] = { 0, 0, '1','2','3','4','5','6','7','8','9','0','-','=','\b','\t', 'q','w','e','r','t','y','u','i','o','p','[',']','\n', 0, 'a','s', 'd','f','g','h','j','k','l',';','\'','`', 0, '\\','z','x','c','v', 'b','n','m',',','.','/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7','8','9','-', '4','5','6','+', '1','2','3','0','.' }; static const char gShifted[128] = { 0, 0, '!','@','#','$','%','^','&','*','(',')','_','+','\b','\t', 'Q','W','E','R','T','Y','U','I','O','P','{','}','\n', 0, 'A','S', 'D','F','G','H','J','K','L',':','"','~', 0, '|','Z','X','C','V', 'B','N','M','<','>','?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7','8','9','-', '4','5','6','+', '1','2','3','0','.' }; static void put_cell(int col, int row, char c, uint32_t fg, uint32_t bg) { SysScreenPutChar(c ? c : ' ', col * GLYPH_W, row * GLYPH_H, fg, bg); } static void clear_row(int row, uint32_t bg) { for (int x = 0; x < gCols; x++) put_cell(x, row, ' ', COL_FG, bg); } static void draw_text(int col, int row, const char* s, uint32_t fg, uint32_t bg) { while (*s && col < gCols) put_cell(col++, row, *s++, fg, bg); } static void msg_set(const char* s) { size_t n = strlen(s); if (n >= sizeof(gMessage)) n = sizeof(gMessage) - 1; memcpy(gMessage, s, n); gMessage[n] = 0; } static void append_char(char* b, size_t cap, size_t* p, char c) { if (*p + 1 < cap) b[(*p)++] = c; b[*p < cap ? *p : cap - 1] = 0; } static void append_str(char* b, size_t cap, size_t* p, const char* s) { while (*s) append_char(b, cap, p, *s++); } static void append_u32(char* b, size_t cap, size_t* p, uint32_t v) { char tmp[12]; int n = 0; if (v == 0) { append_char(b, cap, p, '0'); return; } while (v && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (v % 10u)); v /= 10u; } while (n > 0) append_char(b, cap, p, tmp[--n]); } static char key_to_char(uint8_t sc, uint32_t mods) { if (sc >= 128) return 0; bool shifted = (mods & MOD_SHIFT) != 0; if (mods & MOD_CAPS) { char base = gUnshifted[sc]; if (base >= 'a' && base <= 'z') shifted = !shifted; } return shifted ? gShifted[sc] : gUnshifted[sc]; } static void clamp_cursor(void) { if (gLineCount < 1) gLineCount = 1; if (gCy < 0) gCy = 0; if (gCy >= gLineCount) gCy = gLineCount - 1; if (gCx < 0) gCx = 0; if (gCx > gLines[gCy].len) gCx = gLines[gCy].len; } static void ensure_visible(void) { int textRows = gRows - 2; if (textRows < 1) textRows = 1; if (gCy < gTop) gTop = gCy; if (gCy >= gTop + textRows) gTop = gCy - textRows + 1; if (gTop < 0) gTop = 0; if (gCx < gLeft) gLeft = gCx; if (gCx >= gLeft + gCols) gLeft = gCx - gCols + 1; if (gLeft < 0) gLeft = 0; } static void render(void) { int pxW = 0, pxH = 0; if (SysScreenGetSize(&pxW, &pxH) == 0 && pxW > 0 && pxH > 0) { gCols = pxW / GLYPH_W; gRows = pxH / GLYPH_H; if (gCols < 20) gCols = 20; if (gRows < 5) gRows = 5; } clamp_cursor(); ensure_visible(); char status[256]; size_t p = 0; append_str(status, sizeof(status), &p, " edit "); append_str(status, sizeof(status), &p, gFilename); append_str(status, sizeof(status), &p, gDirty ? " * " : " "); append_str(status, sizeof(status), &p, "Ctrl+S save Ctrl+Q quit Ln "); append_u32(status, sizeof(status), &p, (uint32_t)(gCy + 1)); append_str(status, sizeof(status), &p, ", Col "); append_u32(status, sizeof(status), &p, (uint32_t)(gCx + 1)); clear_row(0, COL_STATUS); draw_text(0, 0, status, COL_STATUSF, COL_STATUS); int textRows = gRows - 2; for (int sy = 0; sy < textRows; sy++) { int row = sy + 1; int li = gTop + sy; clear_row(row, COL_BG); if (li >= gLineCount) continue; LINE* ln = &gLines[li]; for (int sx = 0; sx < gCols; sx++) { int ci = gLeft + sx; char c = (ci < ln->len) ? ln->text[ci] : ' '; bool atCursor = (li == gCy && ci == gCx); put_cell(sx, row, c, atCursor ? COL_CURSORF : COL_FG, atCursor ? COL_CURSOR : COL_BG); } } clear_row(gRows - 1, COL_STATUS); draw_text(0, gRows - 1, gMessage, COL_STATUSF, COL_STATUS); } static void insert_char_at_cursor(char c) { LINE* ln = &gLines[gCy]; if (ln->len >= MAX_LINE - 1) { msg_set("line too long"); return; } memmove(&ln->text[gCx + 1], &ln->text[gCx], (size_t)(ln->len - gCx + 1)); ln->text[gCx] = c; ln->len++; gCx++; gDirty = true; } static void newline_at_cursor(void) { if (gLineCount >= MAX_LINES) { msg_set("too many lines"); return; } for (int i = gLineCount; i > gCy + 1; i--) gLines[i] = gLines[i - 1]; LINE* cur = &gLines[gCy]; LINE* next = &gLines[gCy + 1]; int tail = cur->len - gCx; if (tail > 0) memcpy(next->text, &cur->text[gCx], (size_t)tail); next->len = tail; next->text[next->len] = 0; cur->len = gCx; cur->text[cur->len] = 0; gLineCount++; gCy++; gCx = 0; gDirty = true; } static void backspace_at_cursor(void) { if (gCx > 0) { LINE* ln = &gLines[gCy]; memmove(&ln->text[gCx - 1], &ln->text[gCx], (size_t)(ln->len - gCx + 1)); ln->len--; gCx--; gDirty = true; return; } if (gCy <= 0) return; LINE* prev = &gLines[gCy - 1]; LINE* cur = &gLines[gCy]; if (prev->len + cur->len >= MAX_LINE) { msg_set("join would make line too long"); return; } int oldLen = prev->len; memcpy(&prev->text[prev->len], cur->text, (size_t)cur->len + 1); prev->len += cur->len; for (int i = gCy; i < gLineCount - 1; i++) gLines[i] = gLines[i + 1]; gLineCount--; gCy--; gCx = oldLen; gDirty = true; } static void delete_at_cursor(void) { LINE* ln = &gLines[gCy]; if (gCx < ln->len) { memmove(&ln->text[gCx], &ln->text[gCx + 1], (size_t)(ln->len - gCx)); ln->len--; gDirty = true; return; } if (gCy + 1 >= gLineCount) return; LINE* next = &gLines[gCy + 1]; if (ln->len + next->len >= MAX_LINE) { msg_set("join would make line too long"); return; } memcpy(&ln->text[ln->len], next->text, (size_t)next->len + 1); ln->len += next->len; for (int i = gCy + 1; i < gLineCount - 1; i++) gLines[i] = gLines[i + 1]; gLineCount--; gDirty = true; } static void move_left(void) { if (gCx > 0) gCx--; else if (gCy > 0) { gCy--; gCx = gLines[gCy].len; } } static void move_right(void) { if (gCx < gLines[gCy].len) gCx++; else if (gCy + 1 < gLineCount) { gCy++; gCx = 0; } } static void move_up(void) { if (gCy > 0) gCy--; if (gCx > gLines[gCy].len) gCx = gLines[gCy].len; } static void move_down(void) { if (gCy + 1 < gLineCount) gCy++; if (gCx > gLines[gCy].len) gCx = gLines[gCy].len; } static void save_file(void) { uint32_t h = 0; uint32_t st = SysCreateFile(gFilename, &h); if (st != 0) st = SysOpenFile(gFilename, &h); if (st != 0) { msg_set("save failed: cannot open file"); return; } SysTruncateFile(h, 0); SysSeekFile(h, 0); for (int i = 0; i < gLineCount; i++) { size_t written = 0; if (gLines[i].len) { st = SysWriteFile(h, gLines[i].text, (size_t)gLines[i].len, &written); if (st != 0) { SysCloseHandle(h); msg_set("save failed: write error"); return; } } if (i + 1 < gLineCount) { const char nl = '\n'; st = SysWriteFile(h, &nl, 1, &written); if (st != 0) { SysCloseHandle(h); msg_set("save failed: write error"); return; } } } SysCloseHandle(h); gDirty = false; msg_set("saved"); } static void reset_buffer(void) { gLineCount = 1; gLines[0].len = 0; gLines[0].text[0] = 0; gCx = gCy = gTop = gLeft = 0; gDirty = false; } static void load_file(void) { reset_buffer(); uint32_t h = 0; if (SysOpenFile(gFilename, &h) != 0) { msg_set("new file"); return; } char buf[512]; size_t got = 0; bool truncated = false; while (SysReadFile(h, buf, sizeof(buf), &got) == 0 && got > 0) { for (size_t i = 0; i < got; i++) { char c = buf[i]; if (c == '\r') continue; if (c == '\n') { if (gLineCount < MAX_LINES) { gLineCount++; gLines[gLineCount - 1].len = 0; gLines[gLineCount - 1].text[0] = 0; } else truncated = true; continue; } if (c < 0x20 && c != '\t') continue; LINE* ln = &gLines[gLineCount - 1]; if (ln->len + 1 < MAX_LINE) { ln->text[ln->len++] = (c == '\t') ? ' ' : c; ln->text[ln->len] = 0; } else truncated = true; } } SysCloseHandle(h); msg_set(truncated ? "loaded, truncated long file" : "loaded"); } static const char* skip_spaces(const char* s) { while (s && (*s == ' ' || *s == '\t')) s++; return s; } static const char* skip_token(const char* s) { s = skip_spaces(s); if (!s || !*s) return s; if (*s == '"') { s++; while (*s && *s != '"') s++; if (*s == '"') s++; return s; } while (*s && *s != ' ' && *s != '\t') s++; return s; } static bool read_token(const char* s, char* out, size_t cap) { s = skip_spaces(s); if (!s || !*s || cap == 0) return false; size_t n = 0; if (*s == '"') { s++; while (*s && *s != '"') { if (n + 1 < cap) out[n++] = *s; s++; } } else { while (*s && *s != ' ' && *s != '\t') { if (n + 1 < cap) out[n++] = *s; s++; } } out[n] = 0; return n != 0; } static void parse_filename_from_cmdline(void) { SYS_PEB* peb = SysGetPeb(); const char* cmd = peb ? peb->CommandLine : NULL; const char* arg = skip_token(cmd); if (read_token(arg, gFilename, sizeof(gFilename))) return; strcpy(gFilename, "untitled.txt"); } static void editor_loop(void) { while (true) { render(); uint8_t sc = SysKbReadScancode(); uint32_t mods = SysKbGetModifiers(); bool ctrl = (mods & MOD_CTRL) != 0; if (ctrl && sc == SC_Q) { if (gDirty && !gQuitArmed) { gQuitArmed = true; msg_set("unsaved changes: press Ctrl+Q again to quit"); continue; } break; } gQuitArmed = false; if (ctrl && sc == SC_S) { save_file(); continue; } if (sc == SC_ESC) { msg_set("Ctrl+Q quits, Ctrl+S saves"); continue; } switch (sc) { case SC_LEFT: move_left(); break; case SC_RIGHT: move_right(); break; case SC_UP: move_up(); break; case SC_DOWN: move_down(); break; case SC_HOME: gCx = 0; break; case SC_END: gCx = gLines[gCy].len; break; case SC_PGUP: { int n = gRows - 2; if (n < 1) n = 1; gCy -= n; clamp_cursor(); break; } case SC_PGDN: { int n = gRows - 2; if (n < 1) n = 1; gCy += n; clamp_cursor(); break; } case SC_BACKSPACE: backspace_at_cursor(); break; case SC_DEL: delete_at_cursor(); break; case SC_ENTER: newline_at_cursor(); break; case SC_TAB: for (int i = 0; i < 4; i++) insert_char_at_cursor(' '); break; default: { char c = key_to_char(sc, mods); if (!ctrl && c >= 0x20 && c <= 0x7E) insert_char_at_cursor(c); break; } } } } static void print(const char* s) { SysWriteConsole(s, strlen(s)); } int main(void) { parse_filename_from_cmdline(); load_file(); SysScreenClear(COL_BG); editor_loop(); SysScreenClear(COL_BG); print("edit: goodbye\n"); SysExitProcess(0); return 0; }