Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ $(BIN_DIR)/user/SHELL.ELF: src/user/shell.c src/user/shellabi.h src/user/runtime
$(BIN_DIR)/user/INIT.ELF: src/user/init.c src/user/runtime.h src/user/user.ld $(BIN_DIR)/user/runtime.o $(BIN_DIR)/user/start.o
$(CC) $(CFLAGS) -c $< -o $(BIN_DIR)/user/INIT.o
$(LD) -m elf_i386 -T src/user/user.ld $(BIN_DIR)/user/start.o $(BIN_DIR)/user/runtime.o $(BIN_DIR)/user/INIT.o -o $@
TUI_PROGRAMS := KUNED VIK KBSM KMEMMAN KC KPROCM KDISKMAN KTUI KTTE PAGER JOURNAL
TUI_PROGRAMS := KUNED VIK KBSM KMEMMAN KC KPROCM KDISKMAN KTUI KTTE PAGER JOURNAL HIERO
UTIL_PROGRAMS := TREE FIND GREP TAIL WC SORT KFSCK PROC
USER_ELFS += $(addprefix $(BIN_DIR)/user/,$(addsuffix .ELF,$(UTIL_PROGRAMS)))
USER_ELFS += $(addprefix $(BIN_DIR)/user/,$(addsuffix .ELF,$(TUI_PROGRAMS)))
Expand All @@ -61,6 +61,8 @@ $(eval $(call TUI_RULE,KC,kc))
$(eval $(call TUI_RULE,KTTE,ktte))
$(eval $(call TUI_RULE,PAGER,pager))
$(eval $(call TUI_RULE,JOURNAL,journal))
$(eval $(call TUI_RULE,HIERO,hieroglyph))
$(BIN_DIR)/user/ktuilib.o: src/user/tuidoc.c
define DIAG_RULE
$(BIN_DIR)/user/$(1).ELF: src/user/diagnostics.c src/user/sysabi.h $(BIN_DIR)/user/ktuilib.o $(BIN_DIR)/user/runtime.o $(BIN_DIR)/user/start.o
$$(CC) $$(CFLAGS) -DAPP=$(2) -c $$< -o $$(BIN_DIR)/user/$(1).o
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# KintsugiOS

**Hieroglyph** — TUI для чтения и написания Markdown-документов:
`hieroglyph /HOME/NOTE.MD`, переключение режимов F3.
[Управление и формат](docs/HIEROGLYPH_RU.md).

<div align="center">
<h3>Минималистичная операционная 32 разрядная система с монолитным ядром и собственным загрузчиком на (N)ASM и GCC 15</h3>
<br>
Expand Down
83 changes: 83 additions & 0 deletions docs/HIEROGLYPH_RU.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Hieroglyph — текстовые документы

Запуск: `hieroglyph /HOME/NOTE.MD` или `hiero /HOME/NOTE.MD`.
Без пути создаётся новый документ. Отсутствующий файл открывается как новый;
существующий — в режиме чтения. В KTUI: пункт 9.

Исполняемый файл `/BIN/HIERO.ELF`: полное имя не помещается в FAT 8.3,
поэтому `hieroglyph` — явный псевдоним shell.

## Управление

| Клавиша | Действие |
|---|---|
| F1 | Справка по разметке |
| F2 | Сохранить |
| F3 | Чтение / написание |
| F4 | Сохранить как |
| F10 / Esc | Выход; при изменениях — подтверждение отказа от них |
| Ctrl+T | Общая тема |
| Ctrl+Z / Ctrl+R | Undo / redo в режиме написания |

В чтении: стрелки, PgUp/PgDn, Space, Home/End. Справа scrollbar,
внизу номер экранной страницы. Это не печатная пагинация.
В написании: стрелки, Home/End, PgUp/PgDn, Backspace/Delete, Enter и Tab
(четыре пробела). Длинные строки визуально переносятся без изменения файла.
Вертикальные движения редактора идут по исходным строкам, не экранным переносам.

F3 показывает текущий буфер без необходимости сохранять его. После сохранения
путь обновляется; при ошибке буфер и отметка изменений остаются. Save as
спрашивает подтверждение замены существующего файла.

## Разметка

````text
# Заголовок
## Подзаголовок

Абзац с **выделением**, *акцентом* и `кодом`.

- Первый пункт
- Второй пункт

1. Нумерованный пункт

> Цитата

---

```c
int main(void) { return 0; }
```
````

В текущей ОС текст отображается печатным ASCII: примеры на русском иллюстрируют
синтаксис, но для корректного отображения содержимого используйте ASCII.
Заголовки и inline-выделение обозначаются цветами общих ролей темы; настоящего
курсива/изменения размера шрифта в VGA text mode нет. Цитаты имеют боковой
маркер, списки — маркеры, разделители и code fences — горизонтальные линии.
Внутри code fences inline-разметка не интерпретируется. Нумерованные списки
сохраняют явно записанную нумерацию. Пустые строки разделяют абзацы;
исходные переводы строк сохраняются при отображении.

Это небольшой Markdown-поднабор, не CommonMark: нет вложенных списков,
таблиц, ссылок, изображений, HTML и встроенного выполнения команд.

## Общие виджеты ktuilib

`tui_editor` — владеющий историей, но не текстовым буфером многострочный виджет.
Caller предоставляет `text[capacity+1]`, длину и завершающий NUL. Состояние
нужно обнулить перед использованием. До восьми undo/redo-снимков; на каждое
изменение создаётся один снимок. Ошибка выделения памяти не меняет текст.
`tui_editor_destroy` освобождает историю; буфер освобождает приложение.

`tui_document_key` — ограниченная прокрутка документа, используется также pager.
`tui_markdown` — общий проход layout/render: при height=0 только считает строки,
при ненулевой высоте рисует окно документа. Все этапы используют один перенос.
Реализация находится в `src/user/tuidoc.c`, включается в ktuilib и отдельно
не компилируется, чтобы сохранить простой механизм линковки TUI-программ.

Документ ограничен 64 КиБ. Чтение принимает CRLF и нормализует его в LF;
сохранение использует LF. Бинарные NUL отвергаются. История не сохраняется
между запусками; undo пока посимвольный. Отметка изменений консервативная:
возврат через undo к сохранённому тексту не снимает её автоматически.
19 changes: 11 additions & 8 deletions src/user/diagnostics.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ int main(int argc, char** argv) {
tui_window_draw(&applications);
tui_text(11, 7, "1 Editor 2 Commander 3 Resource monitor", 0x1f);
tui_text(11, 10, "4 Processes 5 Disks 6 Vik 7 Themes 8 Memory", 0x1f);
tui_text(11, 14, "Shared panels, modal dialogs, settings and gauges", 0x1e);
tui_text(11, 14, "9 Hieroglyph documents | H widget demo", 0x1e);
tui_bar(23, "Choose application by number | Enter opens application menu");
#endif
tui_bar(
Expand Down Expand Up @@ -153,20 +153,23 @@ int main(int argc, char** argv) {
: "FAT12 allocation and directory structure are consistent");
}
#elif APP == 3
const char* programs[] = { "KUNED", "KC", "KBSM", "KPROCM", "KDISKMAN", "VIK", "KTTE", "KMEMMAN" };
const char* paths[] = { "/BIN/KUNED.ELF", "/BIN/KC.ELF", "/BIN/KBSM.ELF", "/BIN/KPROCM.ELF",
"/BIN/KDISKMAN.ELF", "/BIN/VIK.ELF", "/BIN/KTTE.ELF", "/BIN/KMEMMAN.ELF" };
const char* programs[] = { "KUNED", "KC", "KBSM", "KPROCM", "KDISKMAN",
"VIK", "KTTE", "KMEMMAN", "HIERO" };
const char* paths[] = { "/BIN/KUNED.ELF", "/BIN/KC.ELF", "/BIN/KBSM.ELF",
"/BIN/KPROCM.ELF", "/BIN/KDISKMAN.ELF", "/BIN/VIK.ELF",
"/BIN/KTTE.ELF", "/BIN/KMEMMAN.ELF", "/BIN/HIERO.ELF" };
if (key == '\n') {
const char* items[] = { "Editor", "Commander", "Resource monitor", "Processes",
"Disks", "Vik", "Theme editor", "Memory manager" };
int choice = tui_menu("Launch application", items, 8, selected);
const char* items[] = { "Editor", "Commander", "Resource monitor",
"Processes", "Disks", "Vik",
"Theme editor", "Memory manager", "Hieroglyph documents" };
int choice = tui_menu("Launch application", items, 9, selected);
if (choice >= 0) {
selected = choice;
key = '1' + choice;
}
}
(void)top;
if (key >= '1' && key <= '8') {
if (key >= '1' && key <= '9') {
char* args[] = { (char*)programs[key - '1'], 0 };
int pid = spawn(paths[key - '1'], 1, args);
if (pid < 0 || wait(pid)) {
Expand Down
158 changes: 158 additions & 0 deletions src/user/hieroglyph.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#include "ktuilib.h"
#define LIMIT 65536u
#define ROWS 20

static int save(tui_editor* editor, char path[256], int save_as) {
char destination[256];
tui_copy(destination, path, 256);
if (save_as || !*path) {
if (!tui_prompt("Save document as (8.3 path, e.g. /HOME/NOTE.MD):", destination, 256)) {
return 0;
}
tui_stat stat;
if (!tui_stat_path(destination, &stat) && !tui_confirm("Replace existing document?")) {
return 0;
}
}
int result = syscall(10, (u32)destination, (u32)editor->text, editor->length, 0);
if (result) {
tui_message("Save failed", kos_error(result));
return 0;
}
tui_copy(path, destination, 256);
return 1;
}

int main(int argc, char** argv) {
if (argc > 2) {
write_all(2, "Usage: hieroglyph [FILE.MD]\n", 27);
return 2;
}
tui_editor editor = { 0 };
editor.capacity = LIMIT;
editor.text = malloc(LIMIT + 1);
if (!editor.text) {
return 1;
}
char path[256] = "";
int writing = argc < 2, dirty = 0;
if (argc == 2) {
tui_copy(path, argv[1], 256);
tui_stat stat;
int result = tui_stat_path(path, &stat);
if (result == -1) {
writing = 1;
} else if (result || stat.directory || stat.size > LIMIT) {
tui_clear(0x07);
tui_message(
"Cannot open document",
result ? kos_error(result) : "Not a regular document or exceeds 64 KiB");
free(editor.text);
return 1;
} else {
int fd = open(path, KOS_O_READ);
if (fd < 0) {
free(editor.text);
return 1;
}
while (editor.length < stat.size) {
u32 chunk = stat.size - editor.length;
if (chunk > 4096) {
chunk = 4096;
}
int n = read(fd, editor.text + editor.length, chunk);
if (n <= 0) {
close(fd);
free(editor.text);
return 1;
}
editor.length += n;
}
close(fd);
/* Normalize CRLF; reject binary NUL rather than silently hiding data. */
u32 out = 0;
for (u32 i = 0; i < editor.length; ++i) {
if (!editor.text[i]) {
free(editor.text);
return 1;
}
if (editor.text[i] == '\r' && i + 1 < editor.length && editor.text[i + 1] == '\n') {
continue;
}
editor.text[out++] = editor.text[i];
}
editor.length = out;
}
}
editor.text[editor.length] = 0;
u32 top = 0;
const char* status = "Markdown document | F3 switches source / formatted view";
for (;;) {
tui_clear(TUI_DESKTOP);
tui_bar(0, "Hieroglyph - documents");
tui_textn(29, 0, *path ? path : "[New document]", 47, 0x70);
if (dirty) {
tui_text(78, 0, "*", 0x70);
}
tui_window paper = { 1, 1, 78,
22, 1, writing ? "WRITE - Markdown source" : "READ - formatted document" };
tui_window_draw(&paper);
u32 total = 1;
if (writing) {
tui_editor_draw(&editor, 3, 2, 73, ROWS);
} else {
total = tui_markdown(editor.text, editor.length, 3, 2, 73, 0, 0);
tui_document_key(0, &top, total, ROWS);
tui_markdown(editor.text, editor.length, 3, 2, 73, ROWS, top);
tui_scrollbar(76, 2, ROWS, top, ROWS, total);
}
tui_bar(23, status);
if (!writing) {
tui_text(57, 23, "Page", 0x70);
tui_number(62, 23, (top + ROWS - 1) / ROWS + 1, 0x70);
tui_text(67, 23, "/", 0x70);
tui_number(69, 23, (total + ROWS - 1) / ROWS, 0x70);
}
tui_bar(24, "F1 Help F2 Save F3 Read/Write F4 Save as F10 Quit | ^Z Undo ^R Redo ^T Theme");
tui_present(79, 24);
int key = tui_key(1);
if (key == TUI_F10 || key == 27) {
if (!dirty || tui_confirm("Discard unsaved document changes?")) {
break;
}
} else if (key == TUI_F1) {
const char* help[] = { "# Heading (levels 1-6)",
"**strong** *emphasis* `inline code`",
"- List item; numbered lists: 1. Item",
"> Quoted paragraph",
"--- Horizontal separator",
"``` starts / ends a literal code block",
"READ: arrows, PgUp/PgDn, Space, Home/End",
"WRITE: arrows, Home/End, Tab, ^Z undo, ^R redo",
"F3 previews the current buffer without saving",
"ASCII text, 64 KiB; LF output; no HTML or images" };
tui_menu("Hieroglyph - Markdown help", help, 10, 0);
} else if (key == TUI_F3) {
writing = !writing;
} else if (key == TUI_F2 || key == TUI_F4) {
if (save(&editor, path, key == TUI_F4)) {
dirty = 0;
status = "Document saved";
}
} else if (writing) {
int result = tui_editor_key(&editor, key);
if (result > 0) {
dirty = 1;
status = "Modified | F3 preview";
}
if (result < 0) {
status = "Cannot edit: document full or insufficient memory";
}
} else {
tui_document_key(key, &top, total, ROWS);
}
}
tui_editor_destroy(&editor);
free(editor.text);
return 0;
}
2 changes: 2 additions & 0 deletions src/user/ktuilib.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "ktuilib.h"

#include "tuidoc.c"
static ktui_cell screen[25][80];
#define MODALS 4
static ktui_cell backgrounds[MODALS][25][80];
Expand Down
18 changes: 18 additions & 0 deletions src/user/ktuilib.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ enum {
typedef kos_stat tui_stat;
typedef kos_dirent tui_entry;

/* Bounded multiline editor. Capacity excludes the required trailing NUL byte.
* Caller owns text[capacity+1]; widget owns up to eight undo/redo snapshots. */
typedef struct {
char* text;
u32 length, capacity, cursor, top;
char* history[8];
u32 lengths[8], cursors[8], history_count, history_position;
} tui_editor;

void tui_editor_destroy(tui_editor* editor);
/* 1 changed, 0 navigation/unhandled, -1 full/allocation failure. */
int tui_editor_key(tui_editor* editor, int key);
void tui_editor_draw(tui_editor* editor, int x, int y, int width, int height);
int tui_document_key(int key, u32* top, u32 total, u32 visible);
/* Same layout pass measures and draws: no separate wrapping implementation.
* Returns total rendered rows; height=0 measures without drawing. */
u32 tui_markdown(const char* text, u32 length, int x, int y, int width, int height, u32 top);

/* Dedicated popup role; older themes inherit their selection color. */
#define TUI_POPUP 0x71
#define TUI_DESKTOP 0x72
Expand Down
10 changes: 2 additions & 8 deletions src/user/pager.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ int main(int argc, char** argv) {
column = 0;
}
}
u32 top = 0, selected = 0;
u32 top = 0;
for (;;) {
tui_clear(0x07);
tui_bar(0, argc > 1 ? argv[1] : "PAGER - standard input");
Expand All @@ -56,13 +56,7 @@ int main(int argc, char** argv) {
if (key == 'q' || key == 27) {
break;
}
u32 maximum = lines > 23 ? lines - 23 : 0;
if (key == ' ') {
key = TUI_PAGE_DOWN;
}
selected = top;
tui_list_key(key, &selected, &top, maximum + 1, 23);
top = selected;
tui_document_key(key, &top, lines, 23);
}
free(data);
return 0;
Expand Down
4 changes: 4 additions & 0 deletions src/user/shell.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ static int parse(char* text, char** argv, int capacity) {
}

static void path(char* out, const char* name) {
if (equal(name, "hieroglyph")) {
copy(out, "/BIN/HIERO.ELF");
return;
}
if (equal(name, "sh")) {
copy(out, "/BIN/SHELL.ELF");
return;
Expand Down
Loading
Loading