major restructuring, prepare for smp
This commit is contained in:
1 parent
b3569a2b90
commit
6bff37746d
229 files changed
+1617
-1489
No files matched your search
@@ -0,0 +1,494 @@
|
||||
// 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 <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#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;
|
||||
SYS_PATH_HANDLE_REQUEST pathReq = { SysSvFromCstr(gFilename), &h };
|
||||
uint32_t st = SysCreateFile(&pathReq);
|
||||
if (st != 0) st = SysOpenFile(&pathReq);
|
||||
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;
|
||||
SYS_PATH_HANDLE_REQUEST pathReq = { SysSvFromCstr(gFilename), &h };
|
||||
if (SysOpenFile(&pathReq) != 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.Data) ? peb->CommandLine.Data : 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) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user