Files
nt-lite/code/user/apps/edit/edit.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

581 lines
17 KiB
C

// User application entry points for edit.
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/input_service.h>
#include <ntlite/console_service.h>
#include <ntlite/path.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 MAX_SCREEN_COLS 256
#define MAX_SCREEN_ROWS 128
#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 EDIT_KEY_ESC INPUT_KEY_ESCAPE
#define EDIT_KEY_BACKSPACE INPUT_KEY_BACKSPACE
#define EDIT_KEY_TAB INPUT_KEY_TAB
#define EDIT_KEY_Q INPUT_KEY_Q
#define EDIT_KEY_S INPUT_KEY_S
#define EDIT_KEY_ENTER INPUT_KEY_ENTER
#define EDIT_KEY_HOME INPUT_KEY_HOME
#define EDIT_KEY_UP INPUT_KEY_UP
#define EDIT_KEY_PGUP INPUT_KEY_PAGE_UP
#define EDIT_KEY_LEFT INPUT_KEY_LEFT
#define EDIT_KEY_RIGHT INPUT_KEY_RIGHT
#define EDIT_KEY_END INPUT_KEY_END
#define EDIT_KEY_DOWN INPUT_KEY_DOWN
#define EDIT_KEY_PGDN INPUT_KEY_PAGE_DOWN
#define EDIT_KEY_DEL INPUT_KEY_DELETE
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";
typedef struct SCREEN_CELL {
char c;
uint32_t fg;
uint32_t bg;
} SCREEN_CELL;
static SCREEN_CELL gScreenCache[MAX_SCREEN_COLS * MAX_SCREEN_ROWS];
static bool gScreenCacheValid = false;
// Implements the invalidate screen cache operation.
static void invalidate_screen_cache(void) {
gScreenCacheValid = false;
}
// Updates screen size.
static void update_screen_size(void) {
int oldCols = gCols;
int oldRows = gRows;
NTL_CONSOLE_INFO info;
if (NtlConsoleQueryInfo(SYS_HANDLE_INVALID, &info) == NTLSTATUS_SUCCESS &&
info.Columns > 0 && info.Rows > 0) {
gCols = (int)info.Columns;
gRows = (int)info.Rows;
if (gCols < 20) gCols = 20;
if (gRows < 5) gRows = 5;
if (gCols > MAX_SCREEN_COLS) gCols = MAX_SCREEN_COLS;
if (gRows > MAX_SCREEN_ROWS) gRows = MAX_SCREEN_ROWS;
}
if (gCols != oldCols || gRows != oldRows)
invalidate_screen_cache();
}
// Updates cell.
static void put_cell(int col, int row, char c, uint32_t fg, uint32_t bg) {
if (col < 0 || row < 0 || col >= gCols || row >= gRows)
return;
if (!c) c = ' ';
if (col < MAX_SCREEN_COLS && row < MAX_SCREEN_ROWS) {
SCREEN_CELL* cell = &gScreenCache[row * MAX_SCREEN_COLS + col];
if (gScreenCacheValid && cell->c == c && cell->fg == fg && cell->bg == bg)
return;
cell->c = c;
cell->fg = fg;
cell->bg = bg;
}
(void)NtlConsolePutCharacter(SYS_HANDLE_INVALID, c,
col * GLYPH_W, row * GLYPH_H, fg, bg);
}
// Clears row.
static void clear_row(int row, uint32_t bg) {
for (int x = 0; x < gCols; x++) put_cell(x, row, ' ', COL_FG, bg);
}
// Draws text.
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);
}
// Implements the msg set operation.
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;
}
// Appends char.
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;
}
// Appends str.
static void append_str(char* b, size_t cap, size_t* p, const char* s) {
while (*s) append_char(b, cap, p, *s++);
}
// Appends u32.
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]);
}
// Implements the key to char operation.
static char key_to_char(INPUT_KEY key, uint32_t mods) {
if (key >= INPUT_KEY_A && key <= INPUT_KEY_Z) {
bool shifted = (mods & INPUT_MOD_SHIFT) != 0;
bool caps = (mods & INPUT_MOD_CAPS_LOCK) != 0;
char c = (char)('a' + (key - INPUT_KEY_A));
return (shifted ^ caps) ? (char)(c - 'a' + 'A') : c;
}
typedef struct { INPUT_KEY key; char normal; char shifted; } PAIR;
static const PAIR map[] = {
{ INPUT_KEY_1, '1', '!' }, { INPUT_KEY_2, '2', '@' },
{ INPUT_KEY_3, '3', '#' }, { INPUT_KEY_4, '4', '$' },
{ INPUT_KEY_5, '5', '%' }, { INPUT_KEY_6, '6', '^' },
{ INPUT_KEY_7, '7', '&' }, { INPUT_KEY_8, '8', '*' },
{ INPUT_KEY_9, '9', '(' }, { INPUT_KEY_0, '0', ')' },
{ INPUT_KEY_MINUS, '-', '_' }, { INPUT_KEY_EQUAL, '=', '+' },
{ INPUT_KEY_LEFT_BRACKET, '[', '{' }, { INPUT_KEY_RIGHT_BRACKET, ']', '}' },
{ INPUT_KEY_BACKSLASH, '\\', '|' }, { INPUT_KEY_SEMICOLON, ';', ':' },
{ INPUT_KEY_APOSTROPHE, '\'', '"' }, { INPUT_KEY_GRAVE, '`', '~' },
{ INPUT_KEY_COMMA, ',', '<' }, { INPUT_KEY_PERIOD, '.', '>' },
{ INPUT_KEY_SLASH, '/', '?' }, { INPUT_KEY_SPACE, ' ', ' ' },
};
bool shifted = (mods & INPUT_MOD_SHIFT) != 0;
for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) {
if (map[i].key == key) return shifted ? map[i].shifted : map[i].normal;
}
return 0;
}
// Implements the clamp cursor operation.
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;
}
// Implements the ensure visible operation.
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;
}
// Implements the render operation.
static void render(void) {
update_screen_size();
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));
size_t statusLen = p;
for (int sx = 0; sx < gCols; sx++) {
char c = ((size_t)sx < statusLen) ? status[sx] : ' ';
put_cell(sx, 0, c, COL_STATUSF, COL_STATUS);
}
int textRows = gRows - 2;
for (int sy = 0; sy < textRows; sy++) {
int row = sy + 1;
int li = gTop + sy;
for (int sx = 0; sx < gCols; sx++) {
int ci = gLeft + sx;
char c = ' ';
bool atCursor = false;
if (li < gLineCount) {
LINE* ln = &gLines[li];
c = (ci < ln->len) ? ln->text[ci] : ' ';
atCursor = (li == gCy && ci == gCx);
}
put_cell(sx, row, c, atCursor ? COL_CURSORF : COL_FG,
atCursor ? COL_CURSOR : COL_BG);
}
}
size_t messageLen = strlen(gMessage);
for (int sx = 0; sx < gCols; sx++) {
char c = ((size_t)sx < messageLen) ? gMessage[sx] : ' ';
put_cell(sx, gRows - 1, c, COL_STATUSF, COL_STATUS);
}
gScreenCacheValid = true;
}
// Inserts char at cursor.
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;
}
// Implements the newline at cursor operation.
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;
}
// Implements the backspace at cursor operation.
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;
}
// Releases at cursor.
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;
}
// Moves left.
static void move_left(void) {
if (gCx > 0) gCx--;
else if (gCy > 0) { gCy--; gCx = gLines[gCy].len; }
}
// Moves right.
static void move_right(void) {
if (gCx < gLines[gCy].len) gCx++;
else if (gCy + 1 < gLineCount) { gCy++; gCx = 0; }
}
// Moves up.
static void move_up(void) {
if (gCy > 0) gCy--;
if (gCx > gLines[gCy].len) gCx = gLines[gCy].len;
}
// Moves down.
static void move_down(void) {
if (gCy + 1 < gLineCount) gCy++;
if (gCx > gLines[gCy].len) gCx = gLines[gCy].len;
}
// Updates file.
static void save_file(void) {
uint32_t h = 0;
uint32_t st = NtlOpenFile(SysSvFromCstr(gFilename),
SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
SYS_FILE_CREATE_ALWAYS, SYS_FILE_FLAG_NONE, &h);
if (st != 0) { msg_set("save failed: cannot open file"); return; }
SysSeekFile(h, 0);
for (int i = 0; i < gLineCount; i++) {
size_t written = 0;
if (gLines[i].len) {
SYS_FILE_IO_REQUEST writeReq = SysMakeFileIoRequest(h, gLines[i].text, (size_t)gLines[i].len, &written);
st = SysWriteFile(&writeReq);
if (st != 0) { SysCloseHandle(h); msg_set("save failed: write error"); return; }
}
if (i + 1 < gLineCount) {
const char nl = '\n';
SYS_FILE_IO_REQUEST newlineReq = SysMakeFileIoRequest(h, &nl, 1, &written);
st = SysWriteFile(&newlineReq);
if (st != 0) { SysCloseHandle(h); msg_set("save failed: write error"); return; }
}
}
SysCloseHandle(h);
gDirty = false;
msg_set("saved");
}
// Implements the reset buffer operation.
static void reset_buffer(void) {
gLineCount = 1;
gLines[0].len = 0;
gLines[0].text[0] = 0;
gCx = gCy = gTop = gLeft = 0;
gDirty = false;
}
// Loads file.
static void load_file(void) {
reset_buffer();
uint32_t h = 0;
if (NtlOpenFile(SysSvFromCstr(gFilename), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &h) != 0) {
msg_set("new file");
return;
}
char buf[512];
size_t got = 0;
bool truncated = false;
while (true) {
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(h, buf, sizeof(buf), &got);
if (SysReadFile(&readReq) != 0 || got == 0) break;
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");
}
// Implements the skip spaces operation.
static const char* skip_spaces(const char* s) {
while (s && (*s == ' ' || *s == '\t')) s++;
return s;
}
// Implements the skip token operation.
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;
}
// Returns token.
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;
}
// Parses filename from cmdline.
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");
}
// Implements the editor loop operation.
static void editor_loop(void) {
while (true) {
render();
INPUT_KEY key = INPUT_KEY_NONE;
if (NtlInputReadKey(&key) != NTLSTATUS_SUCCESS) continue;
uint32_t mods = NtlInputGetKeyModifiers();
bool ctrl = (mods & INPUT_MOD_CTRL) != 0;
if (ctrl && key == EDIT_KEY_Q) {
if (gDirty && !gQuitArmed) {
gQuitArmed = true;
msg_set("unsaved changes: press Ctrl+Q again to quit");
continue;
}
break;
}
gQuitArmed = false;
if (ctrl && key == EDIT_KEY_S) { save_file(); continue; }
if (key == EDIT_KEY_ESC) { msg_set("Ctrl+Q quits, Ctrl+S saves"); continue; }
switch (key) {
case EDIT_KEY_LEFT: move_left(); break;
case EDIT_KEY_RIGHT: move_right(); break;
case EDIT_KEY_UP: move_up(); break;
case EDIT_KEY_DOWN: move_down(); break;
case EDIT_KEY_HOME: gCx = 0; break;
case EDIT_KEY_END: gCx = gLines[gCy].len; break;
case EDIT_KEY_PGUP: {
int n = gRows - 2;
if (n < 1) n = 1;
gCy -= n;
clamp_cursor();
break;
}
case EDIT_KEY_PGDN: {
int n = gRows - 2;
if (n < 1) n = 1;
gCy += n;
clamp_cursor();
break;
}
case EDIT_KEY_BACKSPACE: backspace_at_cursor(); break;
case EDIT_KEY_DEL: delete_at_cursor(); break;
case EDIT_KEY_ENTER: newline_at_cursor(); break;
case EDIT_KEY_TAB:
for (int i = 0; i < 4; i++) insert_char_at_cursor(' ');
break;
default: {
char c = key_to_char(key, mods);
if (!ctrl && c >= 0x20 && c <= 0x7E) insert_char_at_cursor(c);
break;
}
}
}
}
// Prints the requested operation.
static void print(const char* s) {
(void)NtlConsoleWrite(SYS_HANDLE_INVALID, s, strlen(s), 0);
}
// Runs the requested operation.
int main(void) {
parse_filename_from_cmdline();
load_file();
NtlConsoleClear(SYS_HANDLE_INVALID, COL_BG);
invalidate_screen_cache();
editor_loop();
NtlConsoleClear(SYS_HANDLE_INVALID, 0x012456);
invalidate_screen_cache();
print("edit: goodbye\n");
SysExitProcess(0);
return 0;
}