Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68d5be6e47 | |||
| 17639ed62d | |||
| 6d515e4b9a |
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
EXAMPLE := mdview2
|
EXAMPLE := mdview2
|
||||||
|
EXTRA_SRCS := mdview2_raw.c
|
||||||
MEMORY := small
|
MEMORY := small
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(PROJ_ROOT)/app.mk
|
||||||
|
|||||||
+341
-353
@@ -30,25 +30,7 @@
|
|||||||
#include <sprinter.h>
|
#include <sprinter.h>
|
||||||
#include <sprinter_mem.h>
|
#include <sprinter_mem.h>
|
||||||
#include <palette.h>
|
#include <palette.h>
|
||||||
|
#include "mdview2.h" /* геометрия, атрибуты, extern для модуля RAW */
|
||||||
/* ---- Геометрия экрана -------------------------------------------- */
|
|
||||||
|
|
||||||
#define SCREEN_W 80
|
|
||||||
#define SCREEN_H 32
|
|
||||||
#define VIEW_TOP_ROW 1
|
|
||||||
#define VIEW_H 30 /* видимая область: строки 1..30 включительно */
|
|
||||||
#define MENU_ROW 31
|
|
||||||
#define TAB_STOP 4
|
|
||||||
|
|
||||||
/* ---- Параметры файла и памяти ------------------------------------ */
|
|
||||||
|
|
||||||
#define PAGE_BITS 14u
|
|
||||||
#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */
|
|
||||||
#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u))
|
|
||||||
#define MAX_PAGES 8 /* 8 страниц × 16 КБ = 128 КБ */
|
|
||||||
#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE) /* максимальный размер файла: 131072 байт */
|
|
||||||
// #define MAX_LINES 2048
|
|
||||||
#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */
|
|
||||||
|
|
||||||
/* ---- Палитра атрибутов ------------------------------------------- */
|
/* ---- Палитра атрибутов ------------------------------------------- */
|
||||||
|
|
||||||
@@ -71,33 +53,7 @@ static const uint8_t md_pallete[16 * 4] = {
|
|||||||
0xFF, 0xFF, 0xFF, 0xFF
|
0xFF, 0xFF, 0xFF, 0xFF
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ATTR_RESET COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
/* Атрибуты экрана/справки и геометрия — в mdview2.h (общие с модулем RAW). */
|
||||||
#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_TITLE4 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_BOLD COLOR(COLOR_LIGHTRED, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE)
|
|
||||||
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
|
||||||
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
|
||||||
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
|
||||||
#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE)
|
|
||||||
#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE) /* рамка таблиц; отдельный (не контентный) attr — чтобы ремап кодировок не трогал box-глифы */
|
|
||||||
#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE)
|
|
||||||
#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
|
||||||
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
|
|
||||||
#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
|
||||||
#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK)
|
|
||||||
|
|
||||||
/* ---- Атрибуты диалога справки ------------------------------------ */
|
|
||||||
|
|
||||||
#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
|
||||||
#define ATTR_HELP_BDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
|
||||||
#define ATTR_HELP_TIT COLOR(COLOR_YELLOW, COLOR_BLACK)
|
|
||||||
#define ATTR_HELP_HDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
|
||||||
#define ATTR_HELP_HINT COLOR(COLOR_YELLOW, COLOR_BLACK)
|
#define ATTR_HELP_HINT COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||||
|
|
||||||
/* Геометрия диалога справки (в символьных координатах 80×32). */
|
/* Геометрия диалога справки (в символьных координатах 80×32). */
|
||||||
@@ -269,7 +225,7 @@ static uint8_t emph_to_attr(uint8_t emph, uint8_t base_attr);
|
|||||||
static uint16_t n_lines = 0;
|
static uint16_t n_lines = 0;
|
||||||
static uint16_t max_lines = 0; /* ёмкость индекса: index_pages * 2048 */
|
static uint16_t max_lines = 0; /* ёмкость индекса: index_pages * 2048 */
|
||||||
static uint16_t top_line = 0;
|
static uint16_t top_line = 0;
|
||||||
static uint32_t file_size = 0;
|
uint32_t file_size = 0;
|
||||||
static uint8_t file_blk = 0;
|
static uint8_t file_blk = 0;
|
||||||
static uint8_t file_pages = 0;
|
static uint8_t file_pages = 0;
|
||||||
static uint8_t file_phys[MAX_PAGES] = {0};
|
static uint8_t file_phys[MAX_PAGES] = {0};
|
||||||
@@ -285,6 +241,11 @@ static char filename[64] = {0};
|
|||||||
|
|
||||||
/* Прогрессивная загрузка: рисуем первый экран и пускаем скролл, пока
|
/* Прогрессивная загрузка: рисуем первый экран и пускаем скролл, пока
|
||||||
* index_lines() ещё досканирует файл (кооперативно, без прерываний). */
|
* index_lines() ещё досканирует файл (кооперативно, без прерываний). */
|
||||||
|
#if WITH_RAW
|
||||||
|
uint8_t g_view = VIEW_MD; /* режим просмотра (общий с mdview2_raw.c) */
|
||||||
|
static uint8_t g_ready = 0; /* 1 когда первичный документ построен — F2 (RAW) рабочий */
|
||||||
|
static uint8_t g_raw_last = VIEW_RAW_WRAP; /* последний под-режим RAW (возврат по F2) */
|
||||||
|
#endif
|
||||||
static uint8_t g_loading = 0; /* 1 во время index_lines() */
|
static uint8_t g_loading = 0; /* 1 во время index_lines() */
|
||||||
static uint8_t g_first_drawn = 0; /* первый экран уже нарисован */
|
static uint8_t g_first_drawn = 0; /* первый экран уже нарисован */
|
||||||
static uint8_t g_f8_enabled = 0; /* можно ли сейчас переключать кодировку (метка F8 в меню + F8 в load_key) */
|
static uint8_t g_f8_enabled = 0; /* можно ли сейчас переключать кодировку (метка F8 в меню + F8 в load_key) */
|
||||||
@@ -300,8 +261,8 @@ static void progress_tick(void); /* определена ниже (нужн
|
|||||||
#define ENC_KOI8R 2
|
#define ENC_KOI8R 2
|
||||||
#define ENC_UTF8 3
|
#define ENC_UTF8 3
|
||||||
static uint8_t g_encoding = ENC_CP866; /* активная кодировка */
|
static uint8_t g_encoding = ENC_CP866; /* активная кодировка */
|
||||||
static const uint8_t *g_remap = 0; /* активная таблица ремапа (CP1251/KOI8), 0 = без ремапа */
|
const uint8_t *g_remap = 0; /* активная таблица ремапа (CP1251/KOI8), 0 = без ремапа */
|
||||||
static uint8_t g_scratch_phys = 0; /* EMM-страница под ремап-буфер строки для win_rest */
|
uint8_t g_scratch_phys = 0; /* EMM-страница под ремап-буфер строки для win_rest */
|
||||||
|
|
||||||
/* UTF-8 набор (Фаза 2): src декодируется в отдельный блок EMM-страниц как
|
/* UTF-8 набор (Фаза 2): src декодируется в отдельный блок EMM-страниц как
|
||||||
* чистый CP866. После конвертации активный буфер файла (file_phys/file_size/
|
* чистый CP866. После конвертации активный буфер файла (file_phys/file_size/
|
||||||
@@ -312,7 +273,16 @@ static uint8_t utf_blk = 0;
|
|||||||
static uint8_t utf_pages = 0;
|
static uint8_t utf_pages = 0;
|
||||||
static uint8_t utf_phys[MAX_PAGES] = {0};
|
static uint8_t utf_phys[MAX_PAGES] = {0};
|
||||||
static uint32_t utf_size = 0;
|
static uint32_t utf_size = 0;
|
||||||
static uint8_t utf_avail = 0; /* 1 = UTF-8 набор удалось сконвертировать/построить */
|
static uint8_t utf_avail = 0; /* 1 = страницы UTF-8 выделены */
|
||||||
|
|
||||||
|
/* Инкрементальная конвертация UTF-8→CP866: идёт чанками, чередуясь с
|
||||||
|
* индексацией (index_lines докручивает конвертацию впереди позиции чтения),
|
||||||
|
* чтобы первый экран появлялся быстро, а не после полного прохода. */
|
||||||
|
static uint8_t g_utf_building = 0; /* 1 пока конвертация не дошла до конца исходника */
|
||||||
|
static uint32_t utf_src = 0; /* позиция чтения ОРИГИНАЛА (utf-8) конвертером */
|
||||||
|
static uint8_t cv_page = 0xFF;/* orig-страница в W3 для cv_read (своя от cur_page) */
|
||||||
|
#define CONV_MARGIN 4096u /* на сколько байт держать конвертацию впереди индексатора */
|
||||||
|
static void utf_convert_more(uint32_t target); /* forward (нужна в index_lines) */
|
||||||
|
|
||||||
/* Снимок исходного (8-битного) файлового буфера — набор DOC_8BIT читает его
|
/* Снимок исходного (8-битного) файлового буфера — набор DOC_8BIT читает его
|
||||||
* как есть, набор DOC_UTF8 читает utf_phys. */
|
* как есть, набор DOC_UTF8 читает utf_phys. */
|
||||||
@@ -386,10 +356,11 @@ static void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr)
|
|||||||
|
|
||||||
|
|
||||||
/* Полностью очищает указанную строку экрана символами пробела с attr. */
|
/* Полностью очищает указанную строку экрана символами пробела с attr. */
|
||||||
static void fill_row(uint8_t y, uint8_t attr)
|
void fill_row(uint8_t y, uint8_t attr)
|
||||||
{
|
{
|
||||||
bios_set_place(y, 0);
|
// bios_set_place(y, 0);
|
||||||
bios_fillcharattr(' ', attr, SCREEN_W);
|
// bios_fillcharattr(' ', attr, SCREEN_W);
|
||||||
|
bios_clearwin(y, 0, 1, SCREEN_W, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Продвигает спиннер на один кадр.
|
/* Продвигает спиннер на один кадр.
|
||||||
@@ -439,7 +410,7 @@ static inline void map_page(uint8_t page)
|
|||||||
* bank_read читает через IN — общий аппаратный регистр, без теневой RAM.
|
* bank_read читает через IN — общий аппаратный регистр, без теневой RAM.
|
||||||
* NB: __naked обязателен (внутри собственный ret); IX/IY не трогаем
|
* NB: __naked обязателен (внутри собственный ret); IX/IY не трогаем
|
||||||
* (callee-saved), BC/DE/HL/AF — caller-saved, можно клобберить. */
|
* (callee-saved), BC/DE/HL/AF — caller-saved, можно клобберить. */
|
||||||
static char fb(uint32_t p) __naked
|
char fb(uint32_t p) __naked
|
||||||
{
|
{
|
||||||
(void)p; /* p = HLDE; доступ только из asm */
|
(void)p; /* p = HLDE; доступ только из asm */
|
||||||
__asm
|
__asm
|
||||||
@@ -497,7 +468,7 @@ static char fb(uint32_t p) __naked
|
|||||||
* Когда Фаза 2 начала пакетировать много строк на одной странице с
|
* Когда Фаза 2 начала пакетировать много строк на одной странице с
|
||||||
* РАЗНЫМИ off, всё стало читать с начала страницы (т.е. контент первой
|
* РАЗНЫМИ off, всё стало читать с начала страницы (т.е. контент первой
|
||||||
* записи) независимо от реальной строки — добавлен off, IX = 0xC000+off. */
|
* записи) независимо от реальной строки — добавлен off, IX = 0xC000+off. */
|
||||||
static void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off) __naked
|
void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off) __naked
|
||||||
{
|
{
|
||||||
(void)row; (void)col; (void)h; (void)w; (void)page; (void)off;
|
(void)row; (void)col; (void)h; (void)w; (void)page; (void)off;
|
||||||
__asm
|
__asm
|
||||||
@@ -586,7 +557,7 @@ static void idx_put(uint16_t idx, const idx_rec_t *r)
|
|||||||
bank_write(index_phys[page], off, r, INDEX_REC_SIZE);
|
bank_write(index_phys[page], off, r, INDEX_REC_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t seg_off(uint16_t idx)
|
uint32_t seg_off(uint16_t idx)
|
||||||
{
|
{
|
||||||
idx_rec_t r;
|
idx_rec_t r;
|
||||||
if (idx >= n_lines) return file_size;
|
if (idx >= n_lines) return file_size;
|
||||||
@@ -1011,6 +982,70 @@ static uint8_t is_table_raw(uint32_t p)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Разбор одного inline-МАРКЕРА в позиции *pq (< bound): \X, [x], `, **, ~~,
|
||||||
|
* *, _, \t. Общий код для inline_scan() и scan_join_stream(). Возврат: 1 —
|
||||||
|
* маркер обработан (вызывающий делает continue), 0 — символ обычный (пробел/
|
||||||
|
* текст), его кладёт сам вызывающий (и проверяет перенос). Меняет
|
||||||
|
* *pq/*pls/*pseg/*pprev. attr берётся как emph_to_attr(line_style, base) —
|
||||||
|
* для join base=ATTR_TEXT, что тождественно styles_map[line_style]. */
|
||||||
|
static uint8_t inline_marker(uint32_t *pq, uint32_t bound, uint8_t *pls,
|
||||||
|
uint8_t *pseg, char *pprev, uint8_t base)
|
||||||
|
{
|
||||||
|
uint32_t q = *pq;
|
||||||
|
uint8_t ls = *pls;
|
||||||
|
char ch = fb(q);
|
||||||
|
|
||||||
|
if (ls != INIT_STYLE_CODE && ch == '\\' && (q + 1) < bound && is_escapable(fb(q + 1))) {
|
||||||
|
char nb = fb(q + 1);
|
||||||
|
gc_put(nb, emph_to_attr(ls, base));
|
||||||
|
*pseg += 1; *pq = q + 2; *pprev = nb; return 1;
|
||||||
|
}
|
||||||
|
if (ls != INIT_STYLE_CODE && ch == '[' && (q + 2) < bound && fb(q + 2) == ']') {
|
||||||
|
gc_put('[', ATTR_TEXT_BOLD); gc_put(fb(q + 1), ATTR_TEXT_BOLD); gc_put(']', ATTR_TEXT_BOLD);
|
||||||
|
*pseg += 3; *pq = q + 3; *pprev = ']'; return 1;
|
||||||
|
}
|
||||||
|
if (ch == '`') {
|
||||||
|
if (ls == INIT_STYLE_PLAIN) { *pls = INIT_STYLE_CODE; *pq = q + 1; return 1; }
|
||||||
|
if (ls == INIT_STYLE_CODE) { *pls = INIT_STYLE_PLAIN; *pq = q + 1; return 1; }
|
||||||
|
gc_put('`', emph_to_attr(ls, base)); *pseg += 1; *pq = q + 1; *pprev = '`'; return 1;
|
||||||
|
}
|
||||||
|
if (ch == '*' && (q + 1) < bound && fb(q + 1) == '*') {
|
||||||
|
char nx = ((q + 2) < bound) ? fb(q + 2) : '\n';
|
||||||
|
if (is_emph_flanked(*pprev, nx)) {
|
||||||
|
if (ls == INIT_STYLE_PLAIN && ws_or_eol(*pprev)) { *pls = INIT_STYLE_BOLD; *pq = q + 2; return 1; }
|
||||||
|
if (ls == INIT_STYLE_BOLD && ws_or_eol_or_delim(nx)) { *pls = INIT_STYLE_PLAIN; *pq = q + 2; return 1; }
|
||||||
|
}
|
||||||
|
gc_put('*', emph_to_attr(ls, base)); gc_put('*', emph_to_attr(ls, base));
|
||||||
|
*pseg += 2; *pq = q + 2; *pprev = '*'; return 1;
|
||||||
|
}
|
||||||
|
if (ch == '~' && (q + 1) < bound && fb(q + 1) == '~') {
|
||||||
|
char nx = ((q + 2) < bound) ? fb(q + 2) : '\n';
|
||||||
|
if (is_emph_flanked(*pprev, nx)) {
|
||||||
|
if (ls == INIT_STYLE_PLAIN && ws_or_eol(*pprev)) { *pls = INIT_STYLE_STRIKE; *pq = q + 2; return 1; }
|
||||||
|
if (ls == INIT_STYLE_STRIKE && ws_or_eol_or_delim(nx)) { *pls = INIT_STYLE_PLAIN; *pq = q + 2; return 1; }
|
||||||
|
}
|
||||||
|
gc_put('~', emph_to_attr(ls, base)); gc_put('~', emph_to_attr(ls, base));
|
||||||
|
*pseg += 2; *pq = q + 2; *pprev = '~'; return 1;
|
||||||
|
}
|
||||||
|
if (ch == '*' || ch == '_') {
|
||||||
|
char nx = ((q + 1) < bound) ? fb(q + 1) : '\n';
|
||||||
|
if (is_emph_flanked(*pprev, nx)) {
|
||||||
|
uint8_t ns = (ch == '*') ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER;
|
||||||
|
if (ls == INIT_STYLE_PLAIN && ws_or_eol(*pprev)) { *pls = ns; *pq = q + 1; return 1; }
|
||||||
|
if (ls != INIT_STYLE_PLAIN && ws_or_eol_or_delim(nx)) { *pls = INIT_STYLE_PLAIN; *pq = q + 1; return 1; }
|
||||||
|
}
|
||||||
|
gc_put(ch, emph_to_attr(ls, base)); *pseg += 1; *pq = q + 1; *pprev = ch; return 1;
|
||||||
|
}
|
||||||
|
if (ch == '\t') {
|
||||||
|
uint8_t old_sc = *pseg;
|
||||||
|
uint8_t tgt_full = (uint8_t)((old_sc & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP);
|
||||||
|
uint8_t tgt = tgt_full; if (tgt > SCREEN_W) tgt = SCREEN_W;
|
||||||
|
gc_fill(' ', emph_to_attr(ls, base), (uint8_t)(tgt_full - old_sc));
|
||||||
|
*pseg = tgt; *pq = q + 1; *pprev = ' '; return 1;
|
||||||
|
}
|
||||||
|
return 0; /* пробел или обычный символ — обрабатывает вызывающий */
|
||||||
|
}
|
||||||
|
|
||||||
/* Общий сканер inline-форматирования и переносов.
|
/* Общий сканер inline-форматирования и переносов.
|
||||||
* Идёт от q до q_end, учитывает стартовую колонку col и при необходимости
|
* Идёт от q до q_end, учитывает стартовую колонку col и при необходимости
|
||||||
* эмитит continuation-сегменты. Возвращает итоговый line_style. */
|
* эмитит continuation-сегменты. Возвращает итоговый line_style. */
|
||||||
@@ -1039,73 +1074,9 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Экранированная пунктуация: \* \_ \` … → литерал (не внутри кода). */
|
/* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор. */
|
||||||
if (line_style != INIT_STYLE_CODE && ch == '\\' && (q + 1) < q_end && is_escapable(fb(q + 1))) {
|
if (inline_marker(&q, q_end, &line_style, &seg_col, &prev_ch, base)) continue;
|
||||||
char nb = fb(q + 1);
|
|
||||||
gc_put(nb, emph_to_attr(line_style, base));
|
|
||||||
seg_col++; q += 2; prev_ch = nb; continue;
|
|
||||||
}
|
|
||||||
/* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */
|
|
||||||
if (line_style != INIT_STYLE_CODE && ch == '[' && (q + 2) < q_end && fb(q + 2) == ']') {
|
|
||||||
char mid = fb(q + 1);
|
|
||||||
gc_put('[', ATTR_TEXT_BOLD);
|
|
||||||
gc_put(mid, ATTR_TEXT_BOLD);
|
|
||||||
gc_put(']', ATTR_TEXT_BOLD);
|
|
||||||
seg_col += 3; q += 3; prev_ch = ']'; continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == '`') {
|
|
||||||
if (line_style == INIT_STYLE_PLAIN) { line_style = INIT_STYLE_CODE; q++; continue; }
|
|
||||||
if (line_style == INIT_STYLE_CODE) { line_style = INIT_STYLE_PLAIN; q++; continue; }
|
|
||||||
gc_put('`', emph_to_attr(line_style, base));
|
|
||||||
seg_col++; q++; prev_ch = '`'; continue;
|
|
||||||
}
|
|
||||||
if (ch == '*' && (q + 1) < q_end && fb(q + 1) == '*') {
|
|
||||||
char next_ch = ((q + 2) < q_end) ? fb(q + 2) : '\n';
|
|
||||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
|
||||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
|
||||||
line_style = INIT_STYLE_BOLD; q += 2; continue;
|
|
||||||
} else if (line_style == INIT_STYLE_BOLD && ws_or_eol_or_delim(next_ch)) {
|
|
||||||
line_style = INIT_STYLE_PLAIN; q += 2; continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ gc_put('*', emph_to_attr(line_style, base)); gc_put('*', emph_to_attr(line_style, base)); }
|
|
||||||
seg_col += 2; q += 2; prev_ch = '*'; continue;
|
|
||||||
}
|
|
||||||
if (ch == '~' && (q + 1) < q_end && fb(q + 1) == '~') {
|
|
||||||
char next_ch = ((q + 2) < q_end) ? fb(q + 2) : '\n';
|
|
||||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
|
||||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
|
||||||
line_style = INIT_STYLE_STRIKE; q += 2; continue;
|
|
||||||
} else if (line_style == INIT_STYLE_STRIKE && ws_or_eol_or_delim(next_ch)) {
|
|
||||||
line_style = INIT_STYLE_PLAIN; q += 2; continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ gc_put('~', emph_to_attr(line_style, base)); gc_put('~', emph_to_attr(line_style, base)); }
|
|
||||||
seg_col += 2; q += 2; prev_ch = '~'; continue;
|
|
||||||
}
|
|
||||||
if (ch == '*' || ch == '_') {
|
|
||||||
char next_ch = ((q + 1) < q_end) ? fb(q + 1) : '\n';
|
|
||||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
|
||||||
uint8_t new_style = (ch == '*') ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER;
|
|
||||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
|
||||||
line_style = new_style; q++; continue;
|
|
||||||
} else if (line_style != INIT_STYLE_PLAIN && ws_or_eol_or_delim(next_ch)) {
|
|
||||||
/* как render: закрывает любой активный эмфазис */
|
|
||||||
line_style = INIT_STYLE_PLAIN; q++; continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gc_put(ch, emph_to_attr(line_style, base));
|
|
||||||
seg_col++; q++; prev_ch = ch; continue;
|
|
||||||
}
|
|
||||||
if (ch == '\t') {
|
|
||||||
uint8_t old_sc = seg_col;
|
|
||||||
uint8_t tgt_full = (uint8_t)((seg_col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP);
|
|
||||||
uint8_t tgt = tgt_full;
|
|
||||||
if (tgt > SCREEN_W) tgt = SCREEN_W;
|
|
||||||
gc_fill(' ', emph_to_attr(line_style, base), (uint8_t)(tgt_full - old_sc));
|
|
||||||
seg_col = tgt; q++; prev_ch = ' '; continue;
|
|
||||||
}
|
|
||||||
if (ch == ' ') {
|
if (ch == ' ') {
|
||||||
gc_put(' ', emph_to_attr(line_style, base));
|
gc_put(' ', emph_to_attr(line_style, base));
|
||||||
last_space = q + 1;
|
last_space = q + 1;
|
||||||
@@ -1261,112 +1232,12 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Экранированная пунктуация: \* \_ \` … → литерал (не внутри кода). */
|
/* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор; при
|
||||||
if (line_style != INIT_STYLE_CODE && ch == '\\' && (q + 1) < file_size && is_escapable(fb(q + 1))) {
|
* soft_break ch==' ' (синтетический пробел склейки) — маркеры
|
||||||
char nb = fb(q + 1);
|
* пропускаем, его кладёт ветка пробела ниже. */
|
||||||
gc_put(nb, styles_map[line_style]);
|
if (!soft_break && inline_marker(&q, file_size, &line_style, &seg_col, &prev_ch, ATTR_TEXT))
|
||||||
seg_col++; q += 2; prev_ch = nb; continue;
|
continue;
|
||||||
}
|
|
||||||
/* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */
|
|
||||||
if (line_style != INIT_STYLE_CODE && ch == '[' && (q + 2) < file_size && fb(q + 2) == ']') {
|
|
||||||
char mid = fb(q + 1);
|
|
||||||
gc_put('[', ATTR_TEXT_BOLD);
|
|
||||||
gc_put(mid, ATTR_TEXT_BOLD);
|
|
||||||
gc_put(']', ATTR_TEXT_BOLD);
|
|
||||||
seg_col += 3; q += 3; prev_ch = ']'; continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == '`') {
|
|
||||||
/* Как handle_inline_marker в render: backtick переключает code-режим
|
|
||||||
* ТОЛЬКО из PLAIN или CODE; если активен эмфазис (bold/italic/...),
|
|
||||||
* backtick — обычный литеральный символ (нет вложенного кода). */
|
|
||||||
if (line_style == INIT_STYLE_PLAIN) {
|
|
||||||
line_style = INIT_STYLE_CODE;
|
|
||||||
if (!soft_break) q++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (line_style == INIT_STYLE_CODE) {
|
|
||||||
line_style = INIT_STYLE_PLAIN;
|
|
||||||
if (!soft_break) q++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
gc_put('`', styles_map[line_style]);
|
|
||||||
seg_col++;
|
|
||||||
if (!soft_break) q++; else q++;
|
|
||||||
prev_ch = '`';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ch == '*' && (q + 1) < file_size && fb(q + 1) == '*') {
|
|
||||||
char next_ch = ((q + 2) < file_size) ? fb(q + 2) : '\n';
|
|
||||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
|
||||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
|
||||||
line_style = INIT_STYLE_BOLD;
|
|
||||||
if (!soft_break) q += 2; else q++;
|
|
||||||
continue;
|
|
||||||
} else if (line_style == INIT_STYLE_BOLD && ws_or_eol_or_delim(next_ch)) {
|
|
||||||
line_style = INIT_STYLE_PLAIN;
|
|
||||||
if (!soft_break) q += 2; else q++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ gc_put('*', styles_map[line_style]); gc_put('*', styles_map[line_style]); }
|
|
||||||
seg_col += 2;
|
|
||||||
if (!soft_break) q += 2; else q++;
|
|
||||||
prev_ch = '*';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ch == '~' && (q + 1) < file_size && fb(q + 1) == '~') {
|
|
||||||
char next_ch = ((q + 2) < file_size) ? fb(q + 2) : '\n';
|
|
||||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
|
||||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
|
||||||
line_style = INIT_STYLE_STRIKE;
|
|
||||||
if (!soft_break) q += 2; else q++;
|
|
||||||
continue;
|
|
||||||
} else if (line_style == INIT_STYLE_STRIKE && ws_or_eol_or_delim(next_ch)) {
|
|
||||||
line_style = INIT_STYLE_PLAIN;
|
|
||||||
if (!soft_break) q += 2; else q++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{ gc_put('~', styles_map[line_style]); gc_put('~', styles_map[line_style]); }
|
|
||||||
seg_col += 2;
|
|
||||||
if (!soft_break) q += 2; else q++;
|
|
||||||
prev_ch = '~';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ch == '*' || ch == '_') {
|
|
||||||
char next_ch = ((q + 1) < file_size) ? fb(q + 1) : '\n';
|
|
||||||
if (is_emph_flanked(prev_ch, next_ch)) {
|
|
||||||
uint8_t new_style = (ch == '*') ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER;
|
|
||||||
if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) {
|
|
||||||
line_style = new_style;
|
|
||||||
if (!soft_break) q++; else q++;
|
|
||||||
continue;
|
|
||||||
} else if (line_style != INIT_STYLE_PLAIN && ws_or_eol_or_delim(next_ch)) {
|
|
||||||
/* как render: одиночный маркер закрывает ЛЮБОЙ активный эмфазис */
|
|
||||||
line_style = INIT_STYLE_PLAIN;
|
|
||||||
if (!soft_break) q++; else q++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gc_put(ch, styles_map[line_style]);
|
|
||||||
seg_col++;
|
|
||||||
if (!soft_break) q++; else q++;
|
|
||||||
prev_ch = ch;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ch == '\t') {
|
|
||||||
uint8_t old_sc = seg_col;
|
|
||||||
uint8_t tgt_full = (uint8_t)((seg_col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP);
|
|
||||||
uint8_t tgt = tgt_full;
|
|
||||||
if (tgt > SCREEN_W) tgt = SCREEN_W;
|
|
||||||
/* render заполняет до НЕкапнутого tgt_full (по cc); для plain cc==seg_col */
|
|
||||||
gc_fill(' ', styles_map[line_style], (uint8_t)(tgt_full - old_sc));
|
|
||||||
seg_col = tgt;
|
|
||||||
if (!soft_break) q++; else q++;
|
|
||||||
prev_ch = ' ';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ch == ' ') {
|
if (ch == ' ') {
|
||||||
gc_put(' ', styles_map[line_style]);
|
gc_put(' ', styles_map[line_style]);
|
||||||
if (soft_break) {
|
if (soft_break) {
|
||||||
@@ -1384,7 +1255,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
|
|||||||
} else {
|
} else {
|
||||||
gc_put(ch, styles_map[line_style]);
|
gc_put(ch, styles_map[line_style]);
|
||||||
seg_col++;
|
seg_col++;
|
||||||
if (!soft_break) q++; else q++;
|
q++; /* soft_break здесь всегда 0 (ch!=' ') */
|
||||||
prev_ch = ch;
|
prev_ch = ch;
|
||||||
}
|
}
|
||||||
if (seg_col >= SCREEN_W) {
|
if (seg_col >= SCREEN_W) {
|
||||||
@@ -1533,6 +1404,12 @@ static void index_lines(void)
|
|||||||
uint32_t p = 0;
|
uint32_t p = 0;
|
||||||
while (p < file_size && n_lines < max_lines && !g_abort) {
|
while (p < file_size && n_lines < max_lines && !g_abort) {
|
||||||
|
|
||||||
|
/* UTF-8: держим конвертацию впереди позиции чтения (включая look-ahead
|
||||||
|
* склейки/классификации ~ один абзац). file_size при этом = доступная
|
||||||
|
* сконвертированная часть и растёт по ходу. */
|
||||||
|
if (g_utf_building && (uint32_t)(p + CONV_MARGIN) > file_size)
|
||||||
|
utf_convert_more((uint32_t)(p + CONV_MARGIN));
|
||||||
|
|
||||||
if (g_loading)
|
if (g_loading)
|
||||||
progress_tick(); /* первый экран + статус + скролл по готовым данным */
|
progress_tick(); /* первый экран + статус + скролл по готовым данным */
|
||||||
else if ((n_lines & 15) == 0)
|
else if ((n_lines & 15) == 0)
|
||||||
@@ -1813,43 +1690,29 @@ static uint8_t emph_to_attr(uint8_t emph, uint8_t base_attr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Таблицы ремапа байтов [128-255] в CP866 (ASCII identity). Сгенерированы
|
/* Таблицы ремапа СТАРШИХ байтов [128-255] в CP866. Индексируются (ch-0x80):
|
||||||
* Python codecs. Кириллица + ходовая пунктуация; неизвестное → '?'. */
|
* младшие 128 (ASCII) — identity, в ремапе не участвуют (win_rest_remap
|
||||||
static const uint8_t cp1251_to_866[256] = {
|
* трогает только ch>=0x80), поэтому в таблицах их нет. Кириллица + ходовая
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
* пунктуация; неизвестное → '?' (0x3F). Сгенерированы Python codecs. */
|
||||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
static const uint8_t cp1251_to_866[128] = {
|
||||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
/* 80 */ 0x3F, 0x3F, 0x27, 0x3F, 0x22, 0x2E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
/* 90 */ 0x3F, 0x27, 0x27, 0x22, 0x22, 0x07, 0x2D, 0x2D, 0x3F, 0x3F, 0x3F, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
/* A0 */ 0xFF, 0xF6, 0xF7, 0x3F, 0xFD, 0x3F, 0x3F, 0x3F, 0xF0, 0x63, 0xF2, 0x3C, 0x3F, 0x3F, 0x72, 0xF4,
|
||||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
/* B0 */ 0xF8, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFA, 0xF1, 0xFC, 0xF3, 0x3E, 0x3F, 0x3F, 0x3F, 0xF5,
|
||||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
/* C0 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
/* D0 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||||
0x3F, 0x3F, 0x27, 0x3F, 0x22, 0x2E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3F, 0x3F, 0x3F, 0x3F,
|
/* E0 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||||
0x3F, 0x27, 0x27, 0x22, 0x22, 0x07, 0x2D, 0x2D, 0x3F, 0x3F, 0x3F, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F,
|
/* F0 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||||
0xFF, 0xF6, 0xF7, 0x3F, 0xFD, 0x3F, 0x3F, 0x3F, 0xF0, 0x63, 0xF2, 0x3C, 0x3F, 0x3F, 0x72, 0xF4,
|
|
||||||
0xF8, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFA, 0xF1, 0xFC, 0xF3, 0x3E, 0x3F, 0x3F, 0x3F, 0xF5,
|
|
||||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|
||||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
|
||||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
|
||||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
|
||||||
};
|
};
|
||||||
static const uint8_t koi8r_to_866[256] = {
|
static const uint8_t koi8r_to_866[128] = {
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
/* 80 */ 0xC4, 0xB3, 0xDA, 0xBF, 0xC0, 0xD9, 0xC3, 0xB4, 0xC2, 0xC1, 0xC5, 0xDF, 0xDC, 0xDB, 0xDD, 0xDE,
|
||||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
/* 90 */ 0xB0, 0xB1, 0xB2, 0x3F, 0xFE, 0xF9, 0xFB, 0x3F, 0x3F, 0x3F, 0xFF, 0x3F, 0xF8, 0x3F, 0xFA, 0x3F,
|
||||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
/* A0 */ 0xCD, 0xBA, 0xD5, 0xF1, 0xD6, 0xC9, 0xB8, 0xB7, 0xBB, 0xD4, 0xD3, 0xC8, 0xBE, 0xBD, 0xBC, 0xC6,
|
||||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
/* B0 */ 0xC7, 0xCC, 0xB5, 0xF0, 0xB6, 0xB9, 0xD1, 0xD2, 0xCB, 0xCF, 0xD0, 0xCA, 0xD8, 0xD7, 0xCE, 0x63,
|
||||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
/* C0 */ 0xEE, 0xA0, 0xA1, 0xE6, 0xA4, 0xA5, 0xE4, 0xA3, 0xE5, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE,
|
||||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
/* D0 */ 0xAF, 0xEF, 0xE0, 0xE1, 0xE2, 0xE3, 0xA6, 0xA2, 0xEC, 0xEB, 0xA7, 0xE8, 0xED, 0xE9, 0xE7, 0xEA,
|
||||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
/* E0 */ 0x9E, 0x80, 0x81, 0x96, 0x84, 0x85, 0x94, 0x83, 0x95, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
/* F0 */ 0x8F, 0x9F, 0x90, 0x91, 0x92, 0x93, 0x86, 0x82, 0x9C, 0x9B, 0x87, 0x98, 0x9D, 0x99, 0x97, 0x9A,
|
||||||
0xC4, 0xB3, 0xDA, 0xBF, 0xC0, 0xD9, 0xC3, 0xB4, 0xC2, 0xC1, 0xC5, 0xDF, 0xDC, 0xDB, 0xDD, 0xDE,
|
|
||||||
0xB0, 0xB1, 0xB2, 0x3F, 0xFE, 0xF9, 0xFB, 0x3F, 0x3F, 0x3F, 0xFF, 0x3F, 0xF8, 0x3F, 0xFA, 0x3F,
|
|
||||||
0xCD, 0xBA, 0xD5, 0xF1, 0xD6, 0xC9, 0xB8, 0xB7, 0xBB, 0xD4, 0xD3, 0xC8, 0xBE, 0xBD, 0xBC, 0xC6,
|
|
||||||
0xC7, 0xCC, 0xB5, 0xF0, 0xB6, 0xB9, 0xD1, 0xD2, 0xCB, 0xCF, 0xD0, 0xCA, 0xD8, 0xD7, 0xCE, 0x63,
|
|
||||||
0xEE, 0xA0, 0xA1, 0xE6, 0xA4, 0xA5, 0xE4, 0xA3, 0xE5, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE,
|
|
||||||
0xAF, 0xEF, 0xE0, 0xE1, 0xE2, 0xE3, 0xA6, 0xA2, 0xEC, 0xEB, 0xA7, 0xE8, 0xED, 0xE9, 0xE7, 0xEA,
|
|
||||||
0x9E, 0x80, 0x81, 0x96, 0x84, 0x85, 0x94, 0x83, 0x95, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
|
||||||
0x8F, 0x9F, 0x90, 0x91, 0x92, 0x93, 0x86, 0x82, 0x9C, 0x9B, 0x87, 0x98, 0x9D, 0x99, 0x97, 0x9A,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Устанавливает активную кодировку и таблицу ремапа (для 8-битных). */
|
/* Устанавливает активную кодировку и таблицу ремапа (для 8-битных). */
|
||||||
@@ -1910,9 +1773,14 @@ static void doc_switch(uint8_t target)
|
|||||||
|
|
||||||
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
|
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
|
||||||
* различают 8-битные кодировки по частоте. */
|
* различают 8-битные кодировки по частоте. */
|
||||||
static uint8_t common_cp866 (uint8_t b){ return b==0xAE||b==0xA5||b==0xA0||b==0xA8||b==0xAD||b==0xE2||b==0xE1||b==0xE0||b==0xA2||b==0xAB; }
|
static const uint8_t common866 [10] = {0xAE,0xA5,0xA0,0xA8,0xAD,0xE2,0xE1,0xE0,0xA2,0xAB};
|
||||||
static uint8_t common_cp1251(uint8_t b){ return b==0xEE||b==0xE5||b==0xE0||b==0xE8||b==0xED||b==0xF2||b==0xF1||b==0xF0||b==0xE2||b==0xEB; }
|
static const uint8_t common1251[10] = {0xEE,0xE5,0xE0,0xE8,0xED,0xF2,0xF1,0xF0,0xE2,0xEB};
|
||||||
static uint8_t common_koi8r (uint8_t b){ return b==0xCF||b==0xC5||b==0xC1||b==0xC9||b==0xCE||b==0xD4||b==0xD3||b==0xD2||b==0xD7||b==0xCC; }
|
static const uint8_t commonkoi [10] = {0xCF,0xC5,0xC1,0xC9,0xCE,0xD4,0xD3,0xD2,0xD7,0xCC};
|
||||||
|
static uint8_t in_set10(const uint8_t *s, uint8_t b)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < 10; i++) if (s[i] == b) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
|
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
|
||||||
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
|
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
|
||||||
@@ -1931,9 +1799,9 @@ static uint8_t detect_encoding(void)
|
|||||||
uint8_t b = (uint8_t)fb(p);
|
uint8_t b = (uint8_t)fb(p);
|
||||||
if (b < 0x80) { if (cont) { utf_ok = 0; cont = 0; } continue; }
|
if (b < 0x80) { if (cont) { utf_ok = 0; cont = 0; } continue; }
|
||||||
has_high = 1;
|
has_high = 1;
|
||||||
if (common_cp866(b)) s866++;
|
if (in_set10(common866, b)) s866++;
|
||||||
if (common_cp1251(b)) s1251++;
|
if (in_set10(common1251, b)) s1251++;
|
||||||
if (common_koi8r(b)) skoi++;
|
if (in_set10(commonkoi, b)) skoi++;
|
||||||
if (cont) {
|
if (cont) {
|
||||||
if ((b & 0xC0) == 0x80) cont--;
|
if ((b & 0xC0) == 0x80) cont--;
|
||||||
else { utf_ok = 0; cont = 0; }
|
else { utf_ok = 0; cont = 0; }
|
||||||
@@ -1979,7 +1847,7 @@ static void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off)
|
|||||||
for (uint8_t i = 0; i < w; i++) {
|
for (uint8_t i = 0; i < w; i++) {
|
||||||
uint8_t ch = buf[(uint16_t)i * 2u];
|
uint8_t ch = buf[(uint16_t)i * 2u];
|
||||||
if (ch >= 0x80 && attr_is_content(buf[(uint16_t)i * 2u + 1u]))
|
if (ch >= 0x80 && attr_is_content(buf[(uint16_t)i * 2u + 1u]))
|
||||||
buf[(uint16_t)i * 2u] = g_remap[ch];
|
buf[(uint16_t)i * 2u] = g_remap[ch - 0x80]; /* таблицы хранят только старшие 128 */
|
||||||
}
|
}
|
||||||
bank_write(g_scratch_phys, 0, buf, (uint16_t)w * 2u);
|
bank_write(g_scratch_phys, 0, buf, (uint16_t)w * 2u);
|
||||||
win_rest(row, 0, 1, w, g_scratch_phys, 0);
|
win_rest(row, 0, 1, w, g_scratch_phys, 0);
|
||||||
@@ -2027,64 +1895,67 @@ static void conv_put(uint8_t b)
|
|||||||
if (conv_n == (uint16_t)sizeof(conv_buf)) conv_flush();
|
if (conv_n == (uint16_t)sizeof(conv_buf)) conv_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Кодпойнт → CP866-глиф(ы). Кириллица по таблице; ходовые символы (тире,
|
/* Подстановки одиночных не-кириллических символов UTF-8 → один CP866-байт.
|
||||||
* кавычки, буллет, стрелки, галочки, box-drawing) — подстановкой; BOM
|
* Таблица вместо switch (экономия кода); линейный поиск дёшев — вызывается
|
||||||
* проглатывается; прочее → '?'. */
|
* лишь при конвертации на редких символьных кодпойнтах. Чтобы добавить новый
|
||||||
|
* символ — достаточно дописать одну строку {кодпойнт, байт-CP866}.
|
||||||
|
* (Символы с НЕ-1:1 заменой, напр. … → "...", обрабатываются отдельно ниже.) */
|
||||||
|
typedef struct { uint16_t utf8; uint8_t cp866; } utf_sym_t;
|
||||||
|
static const utf_sym_t utf_sym[] = {
|
||||||
|
{0x00A0, 0x20}, {0x00AB, 0x3C}, {0x00BB, 0x3E}, /* nbsp « » → space < > */
|
||||||
|
{0x00B0, 0xF8}, {0x00B7, 0xFA}, /* ° · */
|
||||||
|
{0x2013, 0x2D}, {0x2014, 0x2D}, /* – — → '-' */
|
||||||
|
{0x2018, 0x27}, {0x2019, 0x27}, /* ‘ ’ → '\'' */
|
||||||
|
{0x201C, 0x22}, {0x201D, 0x22}, /* “ ” → '"' */
|
||||||
|
{0x2022, 0xF9}, {0x2116, 0xFC}, /* • → ∙ № */
|
||||||
|
{0x2190, 0x1B}, {0x2191, 0x18}, {0x2192, 0x1A}, {0x2193, 0x19}, /* ← ↑ → ↓ */
|
||||||
|
{0x2500, 0xC4}, {0x2502, 0xB3}, {0x250C, 0xDA}, {0x2510, 0xBF}, /* ─ │ ┌ ┐ */
|
||||||
|
{0x2514, 0xC0}, {0x2518, 0xD9}, {0x251C, 0xC3}, {0x2524, 0xB4}, /* └ ┘ ├ ┤ */
|
||||||
|
{0x252C, 0xC2}, {0x2534, 0xC1}, {0x253C, 0xC5}, /* ┬ ┴ ┼ */
|
||||||
|
{0x2580, 0xDF}, {0x2584, 0xDC}, {0x2588, 0xDB}, /* ▀ ▄ █ */
|
||||||
|
{0x2591, 0xB0}, {0x2592, 0xB1}, {0x2593, 0xB2}, {0x25A0, 0xFE}, /* ░ ▒ ▓ ■ */
|
||||||
|
{0x2713, 0xFB}, {0x2714, 0xFB}, /* ✓ ✔ → галка */
|
||||||
|
{0x2715, 0x78}, {0x2717, 0x78}, {0x2718, 0x78}, /* ✕ ✗ ✘ → 'x' */
|
||||||
|
};
|
||||||
|
#define UTF_SYM_N (uint8_t)(sizeof(utf_sym) / sizeof(utf_sym[0]))
|
||||||
|
|
||||||
|
/* Кодпойнт → CP866-глиф(ы). Кириллица по таблице; спецслучаи (…, BOM) явно;
|
||||||
|
* остальные ходовые символы по таблице utf_sym[]; прочее → '?'. */
|
||||||
static void conv_emit_cp(uint32_t cp)
|
static void conv_emit_cp(uint32_t cp)
|
||||||
{
|
{
|
||||||
if (cp < 0x80) { conv_put((uint8_t)cp); return; }
|
if (cp < 0x80) { conv_put((uint8_t)cp); return; }
|
||||||
if (cp >= 0x0400 && cp <= 0x045F) { conv_put(utf_cyr_to_866[cp - 0x0400]); return; }
|
if (cp >= 0x0400 && cp <= 0x045F) { conv_put(utf_cyr_to_866[cp - 0x0400]); return; }
|
||||||
switch (cp) {
|
if (cp == 0x2026) { conv_put('.'); conv_put('.'); conv_put('.'); return; } /* … → "..." */
|
||||||
case 0x00A0: conv_put(' '); break; /* nbsp */
|
if (cp == 0xFEFF) return; /* BOM/ZWNBSP — выкинуть */
|
||||||
case 0x00AB: conv_put('<'); break; /* « (в CP866 гильеметов нет — как в cp1251_to_866) */
|
for (uint8_t i = 0; i < UTF_SYM_N; i++)
|
||||||
case 0x00BB: conv_put('>'); break; /* » */
|
if (utf_sym[i].utf8 == (uint16_t)cp) { conv_put(utf_sym[i].cp866); return; }
|
||||||
case 0x00B0: conv_put(0xF8); break; /* ° */
|
conv_put('?');
|
||||||
case 0x00B7: conv_put(0xFA); break; /* · */
|
|
||||||
case 0x2013: case 0x2014: conv_put('-'); break; /* – — */
|
|
||||||
case 0x2018: case 0x2019: conv_put('\''); break; /* ‘ ’ */
|
|
||||||
case 0x201C: case 0x201D: conv_put('"'); break; /* “ ” */
|
|
||||||
case 0x2022: conv_put(0xF9); break; /* • → ∙ (пункт списка) */
|
|
||||||
case 0x2026: conv_put('.'); conv_put('.'); conv_put('.'); break; /* … */
|
|
||||||
case 0x2190: conv_put(0x1B); break; /* ← */
|
|
||||||
case 0x2192: conv_put(0x1A); break; /* → */
|
|
||||||
case 0x2191: conv_put(0x18); break; /* ↑ */
|
|
||||||
case 0x2193: conv_put(0x19); break; /* ↓ */
|
|
||||||
case 0x2713: case 0x2714: conv_put(0xFB); break; /* ✓ ✔ → галка */
|
|
||||||
case 0x2715: case 0x2717: case 0x2718: conv_put('x'); break; /* ✕ ✗ ✘ */
|
|
||||||
case 0x2500: conv_put(0xC4); break; /* ─ */
|
|
||||||
case 0x2502: conv_put(0xB3); break; /* │ */
|
|
||||||
case 0x250C: conv_put(0xDA); break; /* ┌ */
|
|
||||||
case 0x2510: conv_put(0xBF); break; /* ┐ */
|
|
||||||
case 0x2514: conv_put(0xC0); break; /* └ */
|
|
||||||
case 0x2518: conv_put(0xD9); break; /* ┘ */
|
|
||||||
case 0x251C: conv_put(0xC3); break; /* ├ */
|
|
||||||
case 0x2524: conv_put(0xB4); break; /* ┤ */
|
|
||||||
case 0x252C: conv_put(0xC2); break; /* ┬ */
|
|
||||||
case 0x2534: conv_put(0xC1); break; /* ┴ */
|
|
||||||
case 0x253C: conv_put(0xC5); break; /* ┼ */
|
|
||||||
case 0x2580: conv_put(0xDF); break; /* ▀ */
|
|
||||||
case 0x2584: conv_put(0xDC); break; /* ▄ */
|
|
||||||
case 0x2588: conv_put(0xDB); break; /* █ */
|
|
||||||
case 0x2591: conv_put(0xB0); break; /* ░ */
|
|
||||||
case 0x2592: conv_put(0xB1); break; /* ▒ */
|
|
||||||
case 0x2593: conv_put(0xB2); break; /* ▓ */
|
|
||||||
case 0x25A0: conv_put(0xFE); break; /* ■ */
|
|
||||||
case 0x2116: conv_put(0xFC); break; /* № */
|
|
||||||
case 0xFEFF: break; /* BOM / ZWNBSP — выкинуть */
|
|
||||||
default: conv_put('?'); break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Декодирует активный (исходный UTF-8) буфер в utf_phys как CP866.
|
/* Декодирует активный (исходный UTF-8) буфер в utf_phys как CP866.
|
||||||
* Битые/обрезанные последовательности → '?'; 4-байтные (эмодзи и т.п.) → '?'. */
|
* Битые/обрезанные последовательности → '?'; 4-байтные (эмодзи и т.п.) → '?'. */
|
||||||
static void utf8_convert(void)
|
/* Последовательное чтение байта ОРИГИНАЛА (utf-8) конвертером. Оригинал лежит
|
||||||
|
* в orig_file_phys[]; мапим его страницу в W3 напрямую, со своей кэш-переменной
|
||||||
|
* cv_page (отдельной от cur_page индексатора, т.к. оба используют W3 и
|
||||||
|
* чередуются). На границе с fb()-чтением utf-буфера кэши взаимно сбрасываются. */
|
||||||
|
static uint8_t cv_read(uint32_t s)
|
||||||
{
|
{
|
||||||
conv_n = 0; conv_page = 0; conv_off = 0;
|
uint8_t pg = (uint8_t)(s >> PAGE_BITS);
|
||||||
uint32_t n = file_size;
|
if (pg != cv_page) { sprinter_page_w3(orig_file_phys[pg]); cv_page = pg; }
|
||||||
uint32_t p = 0;
|
return *((volatile uint8_t *)(0xC000u + (uint16_t)(s & PAGE_MASK)));
|
||||||
while (p < n) {
|
}
|
||||||
if ((uint16_t)((uint16_t)p & 0x0FFFu) == 0) spinner_tick();
|
|
||||||
uint8_t b = (uint8_t)fb(p++);
|
/* Возобновляемая конвертация: дописывает utf_phys из оригинала, пока
|
||||||
|
* сконвертированных (flushed) байт меньше target и не достигнут конец
|
||||||
|
* исходника. По выходу file_size = доступная (сконвертированная) часть; при
|
||||||
|
* достижении конца фиксирует utf_size и снимает g_utf_building. */
|
||||||
|
static void utf_convert_more(uint32_t target)
|
||||||
|
{
|
||||||
|
cv_page = 0xFF; /* W3 был на utf (fb индексатора) — пере-смаппим orig */
|
||||||
|
uint32_t n = orig_file_size;
|
||||||
|
while (utf_src < n &&
|
||||||
|
(uint32_t)((uint32_t)conv_page * PAGE_SIZE + conv_off) < target) {
|
||||||
|
uint8_t b = (uint8_t)cv_read(utf_src++);
|
||||||
if (b < 0x80) { conv_emit_cp(b); continue; }
|
if (b < 0x80) { conv_emit_cp(b); continue; }
|
||||||
|
|
||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
@@ -2092,21 +1963,23 @@ static void utf8_convert(void)
|
|||||||
if ((b & 0xE0) == 0xC0) { cp = (uint32_t)(b & 0x1F); need = 1; }
|
if ((b & 0xE0) == 0xC0) { cp = (uint32_t)(b & 0x1F); need = 1; }
|
||||||
else if ((b & 0xF0) == 0xE0) { cp = (uint32_t)(b & 0x0F); need = 2; }
|
else if ((b & 0xF0) == 0xE0) { cp = (uint32_t)(b & 0x0F); need = 2; }
|
||||||
else if ((b & 0xF8) == 0xF0) { cp = (uint32_t)(b & 0x07); need = 3; }
|
else if ((b & 0xF8) == 0xF0) { cp = (uint32_t)(b & 0x07); need = 3; }
|
||||||
else { conv_put('?'); continue; } /* одиночный континюэйшн/битый лид */
|
else { conv_put('?'); continue; }
|
||||||
|
|
||||||
uint8_t ok = 1;
|
uint8_t ok = 1;
|
||||||
for (uint8_t k = 0; k < need; k++) {
|
for (uint8_t k = 0; k < need; k++) {
|
||||||
if (p >= n) { ok = 0; break; }
|
if (utf_src >= n) { ok = 0; break; }
|
||||||
uint8_t cb = (uint8_t)fb(p);
|
uint8_t cb = (uint8_t)cv_read(utf_src);
|
||||||
if ((cb & 0xC0) != 0x80) { ok = 0; break; }
|
if ((cb & 0xC0) != 0x80) { ok = 0; break; }
|
||||||
cp = (cp << 6) | (uint32_t)(cb & 0x3F);
|
cp = (cp << 6) | (uint32_t)(cb & 0x3F);
|
||||||
p++;
|
utf_src++;
|
||||||
}
|
}
|
||||||
if (!ok || need == 3) { conv_put('?'); continue; } /* битое / 4-байтное */
|
if (!ok || need == 3) { conv_put('?'); continue; }
|
||||||
conv_emit_cp(cp);
|
conv_emit_cp(cp);
|
||||||
}
|
}
|
||||||
conv_flush();
|
conv_flush();
|
||||||
utf_size = (uint32_t)conv_page * PAGE_SIZE + conv_off;
|
file_size = (uint32_t)conv_page * PAGE_SIZE + conv_off; /* доступно индексатору */
|
||||||
|
if (utf_src >= n) { utf_size = file_size; g_utf_building = 0; }
|
||||||
|
cur_page = 0xFF; /* W3 был на orig — fb индексатора пере-смаппит utf */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Выделяет страницы под UTF-8 набор и конвертирует туда исходник (читает
|
/* Выделяет страницы под UTF-8 набор и конвертирует туда исходник (читает
|
||||||
@@ -2114,15 +1987,16 @@ static void utf8_convert(void)
|
|||||||
* активный буфер: это делает build_doc() при сборке набора DOC_UTF8.
|
* активный буфер: это делает build_doc() при сборке набора DOC_UTF8.
|
||||||
* Конвертированный поток ≤ исходного (кириллица 2 байта → 1), хватает того же
|
* Конвертированный поток ≤ исходного (кириллица 2 байта → 1), хватает того же
|
||||||
* числа страниц. Возврат: 1 — успех, 0 — нет EMM. */
|
* числа страниц. Возврат: 1 — успех, 0 — нет EMM. */
|
||||||
static uint8_t alloc_and_convert_utf8(void)
|
/* Выделяет страницы под UTF-8 набор (конвертированный ≤ оригинала по размеру).
|
||||||
|
* Сама конвертация — инкрементальная, через utf_convert_more(). Возврат: 1 —
|
||||||
|
* успех, 0 — нет EMM. */
|
||||||
|
static uint8_t utf_alloc(void)
|
||||||
{
|
{
|
||||||
utf_pages = file_pages;
|
utf_pages = orig_file_pages;
|
||||||
utf_blk = mem_alloc_pages(utf_pages);
|
utf_blk = mem_alloc_pages(utf_pages);
|
||||||
if (utf_blk == 0) return 0;
|
if (utf_blk == 0) return 0;
|
||||||
for (uint8_t i = 0; i < utf_pages; i++)
|
for (uint8_t i = 0; i < utf_pages; i++)
|
||||||
utf_phys[i] = mem_get_page(utf_blk, i);
|
utf_phys[i] = mem_get_page(utf_blk, i);
|
||||||
|
|
||||||
utf8_convert(); /* читает оригинал (active) → utf_phys */
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2241,18 +2115,30 @@ static void render_updated_status(void)
|
|||||||
/* Отрисовывает нижнюю строку меню. */
|
/* Отрисовывает нижнюю строку меню. */
|
||||||
static void render_menu(void)
|
static void render_menu(void)
|
||||||
{
|
{
|
||||||
/* Строка меню разбита на 10 блоков по 8 колонок: метку Fn кладём в блок
|
/* Блок i = колонка i*8 (8 шириной): 2 поз. НОМЕР клавиши без 'F' (стиль
|
||||||
* (n-1)*8, чтобы позиции соответствовали номерам клавиш (F1→0, F8→7,
|
* ATTR_MENU_K, ведущий «чёрный» пробел кроме F10) + 6 поз. ТЕКСТ-функция
|
||||||
* F10→9). Пока заполнены только F1/F8/F10. */
|
* (ATTR_MENU_T) сразу за номером. Номера рисуем для ВСЕХ 10 клавиш; текст —
|
||||||
|
* только у задействованных и доступных сейчас. */
|
||||||
fill_row(MENU_ROW, ATTR_MENU_T);
|
fill_row(MENU_ROW, ATTR_MENU_T);
|
||||||
put_str_attr(0 * 8 + 1, MENU_ROW, "F1", ATTR_MENU_K);
|
|
||||||
put_str_attr(0 * 8 + 4, MENU_ROW, "Help", ATTR_MENU_T);
|
char num[3]; num[2] = 0;
|
||||||
if (g_f8_enabled) { /* F8 показываем только когда переключение кодировки возможно */
|
for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */
|
||||||
put_str_attr(7 * 8 + 1, MENU_ROW, "F8", ATTR_MENU_K);
|
num[0] = ' '; num[1] = (char)('1' + i);
|
||||||
put_str_attr(7 * 8 + 4, MENU_ROW, "Codepage", ATTR_MENU_T);
|
put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K);
|
||||||
}
|
}
|
||||||
put_str_attr(9 * 8 + 0, MENU_ROW, "F10", ATTR_MENU_K);
|
put_str_attr(9 * 8, MENU_ROW, "10", ATTR_MENU_K); /* F10 */
|
||||||
put_str_attr(9 * 8 + 4, MENU_ROW, "Exit", ATTR_MENU_T);
|
|
||||||
|
put_str_attr(0 * 8 + 2, MENU_ROW, "Help", ATTR_MENU_T);
|
||||||
|
#if WITH_RAW
|
||||||
|
if (g_ready) /* RAW доступен после постройки документа */
|
||||||
|
put_str_attr(1 * 8 + 2, MENU_ROW, (g_view == VIEW_MD) ? "RAW" : "MD", ATTR_MENU_T);
|
||||||
|
if (g_view != VIEW_MD) /* Wrap/Unwrap — только в RAW */
|
||||||
|
put_str_attr(2 * 8 + 2, MENU_ROW,
|
||||||
|
(g_view == VIEW_RAW_WRAP) ? "UnWrap" : "Wrap", ATTR_MENU_T);
|
||||||
|
#endif
|
||||||
|
if (g_f8_enabled) /* смена кодировки сейчас возможна */
|
||||||
|
put_str_attr(7 * 8 + 2, MENU_ROW, "CodePg", ATTR_MENU_T);
|
||||||
|
put_str_attr(9 * 8 + 2, MENU_ROW, "Exit", ATTR_MENU_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================================================================
|
/* ==================================================================
|
||||||
@@ -2490,6 +2376,22 @@ static void load_key(uint16_t k)
|
|||||||
/* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */
|
/* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */
|
||||||
static void progress_tick(void)
|
static void progress_tick(void)
|
||||||
{
|
{
|
||||||
|
#if WITH_RAW
|
||||||
|
/* В RAW-режиме буфер документа готов сразу после конвертации (до индекса),
|
||||||
|
* поэтому первый экран RAW можно показать немедленно — рисуем raw_draw, а
|
||||||
|
* не MD-вид (иначе мелькал бы MD). Индекс при этом строится дальше (нужен
|
||||||
|
* для MD-вида). */
|
||||||
|
if (g_view != VIEW_MD) {
|
||||||
|
if (!g_first_drawn) {
|
||||||
|
raw_draw();
|
||||||
|
raw_status_numbers();
|
||||||
|
g_first_drawn = 1;
|
||||||
|
cur_page = 0xFF; /* raw_draw сбил W3-маппинг, на который опирается fb() */
|
||||||
|
}
|
||||||
|
spinner_tick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
/* Прогрессивная отрисовка строящегося (всегда показываемого) набора. */
|
/* Прогрессивная отрисовка строящегося (всегда показываемого) набора. */
|
||||||
uint8_t touched = 0;
|
uint8_t touched = 0;
|
||||||
if (!g_first_drawn && drawable_lines() >= VIEW_H) {
|
if (!g_first_drawn && drawable_lines() >= VIEW_H) {
|
||||||
@@ -2523,20 +2425,30 @@ static void progress_tick(void)
|
|||||||
* живых глобалах, а progress_tick через свап показывает/листает уже готовый
|
* живых глобалах, а progress_tick через свап показывает/листает уже готовый
|
||||||
* g_disp_doc. Возврат: 0 — успех, <0 — нехватка EMM (-5/-6/-7).
|
* g_disp_doc. Возврат: 0 — успех, <0 — нехватка EMM (-5/-6/-7).
|
||||||
*
|
*
|
||||||
* UTF-8 конвертация делается ЛЕНИВО здесь, а не до первого экрана: при входе
|
* UTF-8 набор конвертируется ИНКРЕМЕНТАЛЬНО: выделяем страницы, переключаем
|
||||||
* (для DOC_UTF8 ещё не сконвертированного) живой буфер обязан быть = оригинал
|
* живой буфер на utf и запускаем конвертацию первого чанка; дальше index_lines
|
||||||
* (8-битные байты) — utf8_convert читает его через fb(). Это так и в случае
|
* докручивает её впереди позиции чтения (g_utf_building). Конвертер читает
|
||||||
* первичного UTF-8 (сразу после load_file), и вторичного (после сборки
|
* оригинал из orig_file_phys[] через cv_read (не через fb, который теперь
|
||||||
* первичного 8-битного, чей буфер = оригинал). */
|
* указывает на utf). */
|
||||||
static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
|
static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
|
||||||
{
|
{
|
||||||
if (slot == DOC_UTF8) {
|
if (slot == DOC_UTF8) {
|
||||||
if (!utf_avail) { /* конвертируем сейчас (live = оригинал) */
|
if (!utf_avail) { /* первый вход: выделить страницы */
|
||||||
if (!alloc_and_convert_utf8()) return -7;
|
if (!utf_alloc()) return -7;
|
||||||
utf_avail = 1;
|
utf_avail = 1;
|
||||||
|
conv_n = 0; conv_page = 0; conv_off = 0;
|
||||||
|
utf_src = 0;
|
||||||
|
g_utf_building = 1;
|
||||||
}
|
}
|
||||||
file_blk = utf_blk; file_pages = utf_pages; file_size = utf_size;
|
file_blk = utf_blk; file_pages = utf_pages;
|
||||||
memcpy(file_phys, utf_phys, MAX_PAGES);
|
memcpy(file_phys, utf_phys, MAX_PAGES);
|
||||||
|
if (g_utf_building) {
|
||||||
|
file_size = 0;
|
||||||
|
cur_page = 0xFF;
|
||||||
|
utf_convert_more(CONV_MARGIN); /* первый чанк — для первого экрана */
|
||||||
|
} else {
|
||||||
|
file_size = utf_size;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
file_blk = orig_file_blk; file_pages = orig_file_pages; file_size = orig_file_size;
|
file_blk = orig_file_blk; file_pages = orig_file_pages; file_size = orig_file_size;
|
||||||
memcpy(file_phys, orig_file_phys, MAX_PAGES);
|
memcpy(file_phys, orig_file_phys, MAX_PAGES);
|
||||||
@@ -2556,6 +2468,10 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
|
|||||||
index_lines();
|
index_lines();
|
||||||
g_loading = 0;
|
g_loading = 0;
|
||||||
|
|
||||||
|
/* Зафиксировать конвертацию (на случай обрыва индекса по max_lines до конца
|
||||||
|
* исходника) — иначе g_utf_building остался бы 1 и сломал бы следующую сборку. */
|
||||||
|
if (g_utf_building) { utf_size = file_size; g_utf_building = 0; }
|
||||||
|
|
||||||
g_doc[slot].built = 1;
|
g_doc[slot].built = 1;
|
||||||
doc_save_live(slot);
|
doc_save_live(slot);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -2565,6 +2481,10 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
|
|||||||
* 8-бит↔8-бит внутри одного набора — мгновенный ремап; переход в другой набор
|
* 8-бит↔8-бит внутри одного набора — мгновенный ремап; переход в другой набор
|
||||||
* — мгновенный doc_switch, если он построен, иначе ЛЕНИВАЯ сборка по месту
|
* — мгновенный doc_switch, если он построен, иначе ЛЕНИВАЯ сборка по месту
|
||||||
* (со спиннером), результат кэшируется. */
|
* (со спиннером), результат кэшируется. */
|
||||||
|
/* Меняет кодировку: тот же набор — ремап; другой готовый — doc_switch;
|
||||||
|
* непостроенный — ЛЕНИВАЯ ТИХАЯ сборка (спиннер, без отрисовки документа).
|
||||||
|
* НЕ перерисовывает документ/статус — это делает вызывающий в своём режиме
|
||||||
|
* (MD: draw_viewport_from_cache; RAW: raw_draw), чтобы не мелькал чужой вид. */
|
||||||
static void switch_encoding(uint8_t next)
|
static void switch_encoding(uint8_t next)
|
||||||
{
|
{
|
||||||
uint8_t target = (next == ENC_UTF8) ? DOC_UTF8 : DOC_8BIT;
|
uint8_t target = (next == ENC_UTF8) ? DOC_UTF8 : DOC_8BIT;
|
||||||
@@ -2576,20 +2496,24 @@ static void switch_encoding(uint8_t next)
|
|||||||
set_encoding(next); g_doc[target].enc = next;
|
set_encoding(next); g_doc[target].enc = next;
|
||||||
} else { /* строим набор лениво */
|
} else { /* строим набор лениво */
|
||||||
doc_save_live(g_active_doc); /* сохранить текущий (позиция и т.п.) */
|
doc_save_live(g_active_doc); /* сохранить текущий (позиция и т.п.) */
|
||||||
|
#if WITH_RAW
|
||||||
|
g_ready = 0; render_menu(); /* идёт индексация — прячем F2 */
|
||||||
|
#endif
|
||||||
spinner_show(1);
|
spinner_show(1);
|
||||||
int rc = build_doc(target, next, 1); /* visible: рисует прогрессивно */
|
int rc = build_doc(target, next, 1); /* visible: прогрессивный показ первого экрана
|
||||||
|
* (progress_tick рисует по текущему виду — MD/RAW) */
|
||||||
spinner_show(0);
|
spinner_show(0);
|
||||||
|
#if WITH_RAW
|
||||||
|
g_ready = 1; render_menu();
|
||||||
|
#endif
|
||||||
if (rc < 0) { /* нет EMM — откат на текущий набор */
|
if (rc < 0) { /* нет EMM — откат на текущий набор */
|
||||||
if (target == DOC_UTF8) g_utf_failed = 1;
|
if (target == DOC_UTF8) g_utf_failed = 1;
|
||||||
doc_load_live(g_active_doc);
|
doc_load_live(g_active_doc);
|
||||||
clamp_top(); draw_viewport_from_cache(); render_full_status();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
g_disp_doc = target; /* build_doc уже выставил active/enc */
|
g_disp_doc = target; /* build_doc уже выставил active/enc */
|
||||||
}
|
}
|
||||||
clamp_top();
|
clamp_top(); /* привести top_line к новому набору (для MD) */
|
||||||
draw_viewport_from_cache();
|
|
||||||
render_full_status();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
@@ -2701,6 +2625,9 @@ int main(int argc, char **argv)
|
|||||||
* счётчиком строк. */
|
* счётчиком строк. */
|
||||||
spinner_show(0);
|
spinner_show(0);
|
||||||
g_f8_enabled = 1;
|
g_f8_enabled = 1;
|
||||||
|
#if WITH_RAW
|
||||||
|
g_ready = 1; /* документ построен — теперь доступен F2 (RAW) */
|
||||||
|
#endif
|
||||||
render_menu();
|
render_menu();
|
||||||
clamp_top();
|
clamp_top();
|
||||||
render_full_status();
|
render_full_status();
|
||||||
@@ -2715,6 +2642,65 @@ int main(int argc, char **argv)
|
|||||||
if (ascii == 0x1B) break; /* клавиша Esc */
|
if (ascii == 0x1B) break; /* клавиша Esc */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if WITH_RAW
|
||||||
|
/* F2 — тумблер RAW ↔ MD (рабочий только после постройки документа). */
|
||||||
|
if (scan == KEY_F2) {
|
||||||
|
if (!g_ready) continue;
|
||||||
|
if (g_view == VIEW_MD) {
|
||||||
|
g_view = g_raw_last; /* → RAW (последний под-режим) */
|
||||||
|
raw_seed_from(top_line); /* позиция из MD */
|
||||||
|
raw_screen_init();
|
||||||
|
render_full_status(); render_menu();
|
||||||
|
raw_status_numbers(); raw_draw();
|
||||||
|
} else {
|
||||||
|
g_raw_last = g_view; /* запомнить под-режим RAW */
|
||||||
|
g_view = VIEW_MD;
|
||||||
|
clamp_top();
|
||||||
|
render_full_status(); render_menu();
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RAW-режим: F3 Wrap/Unwrap, своя навигация; F8 — кодировка; F1/F10. */
|
||||||
|
if (g_view != VIEW_MD) {
|
||||||
|
if (scan == KEY_F10) goto exit_loop;
|
||||||
|
if (scan == KEY_F1) {
|
||||||
|
show_help();
|
||||||
|
raw_screen_init();
|
||||||
|
render_full_status(); render_menu();
|
||||||
|
raw_status_numbers(); raw_draw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (scan == KEY_F3) { /* Wrap ↔ Unwrap */
|
||||||
|
g_view = (g_view == VIEW_RAW_WRAP) ? VIEW_RAW_HSCROLL : VIEW_RAW_WRAP;
|
||||||
|
g_raw_last = g_view;
|
||||||
|
raw_renorm();
|
||||||
|
raw_screen_init();
|
||||||
|
render_menu();
|
||||||
|
raw_status_numbers(); raw_draw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (scan == KEY_F8) {
|
||||||
|
uint8_t next = (g_encoding == ENC_CP866) ? ENC_CP1251 :
|
||||||
|
(g_encoding == ENC_CP1251) ? ENC_KOI8R :
|
||||||
|
(g_encoding == ENC_KOI8R) ? ENC_UTF8 : ENC_CP866;
|
||||||
|
if (next == ENC_UTF8 && g_utf_failed) next = ENC_CP866;
|
||||||
|
uint8_t target = (next == ENC_UTF8) ? DOC_UTF8 : DOC_8BIT;
|
||||||
|
/* другой буфер (в/из UTF-8) → в начало ДО сборки, чтобы прогрессивный
|
||||||
|
* raw_draw в progress_tick показывал верх нового набора. */
|
||||||
|
if (target != g_active_doc) raw_home();
|
||||||
|
switch_encoding(next); /* меняет набор/ремап */
|
||||||
|
if (g_abort) goto exit_loop;
|
||||||
|
render_full_status(); raw_status_numbers(); raw_draw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
raw_key(scan);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif /* WITH_RAW */
|
||||||
|
|
||||||
switch (scan) {
|
switch (scan) {
|
||||||
case KEY_F1: show_help(); break;
|
case KEY_F1: show_help(); break;
|
||||||
case KEY_F8: { /* цикл CP866→CP1251→KOI8R→UTF8→CP866; нужный набор дособерётся лениво */
|
case KEY_F8: { /* цикл CP866→CP1251→KOI8R→UTF8→CP866; нужный набор дособерётся лениво */
|
||||||
@@ -2724,6 +2710,8 @@ int main(int argc, char **argv)
|
|||||||
if (next == ENC_UTF8 && g_utf_failed) next = ENC_CP866; /* UTF-8 недоступен */
|
if (next == ENC_UTF8 && g_utf_failed) next = ENC_CP866; /* UTF-8 недоступен */
|
||||||
switch_encoding(next);
|
switch_encoding(next);
|
||||||
if (g_abort) goto exit_loop; /* Esc/F10 во время ленивой досборки */
|
if (g_abort) goto exit_loop; /* Esc/F10 во время ленивой досборки */
|
||||||
|
render_full_status(); /* перерисовка — в MD-режиме */
|
||||||
|
draw_viewport_from_cache();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case KEY_F10: goto exit_loop;
|
case KEY_F10: goto exit_loop;
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* mdview2.h — общие определения и интерфейс между модулями mdview2.
|
||||||
|
*
|
||||||
|
* Монолит mdview2.c расщепляется инкрементально; первый отделённый модуль —
|
||||||
|
* RAW-просмотр (mdview2_raw.c). Здесь живут разделяемые константы экрана/
|
||||||
|
* атрибутов, extern-объявления нужных RAW символов из mdview2.c и API RAW.
|
||||||
|
*/
|
||||||
|
#ifndef MDVIEW2_H
|
||||||
|
#define MDVIEW2_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <palette.h> /* COLOR(), COLOR_* */
|
||||||
|
#include "mdview2_conf.h" /* WITH_RAW / WITH_HEX — выбор опциональных модулей */
|
||||||
|
|
||||||
|
/* ---- Геометрия экрана -------------------------------------------- */
|
||||||
|
#define SCREEN_W 80
|
||||||
|
#define SCREEN_H 32
|
||||||
|
#define VIEW_TOP_ROW 1
|
||||||
|
#define VIEW_H 30 /* видимая область: строки 1..30 включительно */
|
||||||
|
#define MENU_ROW 31
|
||||||
|
#define TAB_STOP 4
|
||||||
|
|
||||||
|
/* ---- Параметры файла и памяти ------------------------------------ */
|
||||||
|
#define PAGE_BITS 14u
|
||||||
|
#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */
|
||||||
|
#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u))
|
||||||
|
#define MAX_PAGES 8 /* 8 страниц × 16 КБ = 128 КБ */
|
||||||
|
#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE)
|
||||||
|
#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */
|
||||||
|
|
||||||
|
/* ---- Палитра атрибутов ------------------------------------------- */
|
||||||
|
#define ATTR_RESET COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||||
|
#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE4 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_BOLD COLOR(COLOR_LIGHTRED, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
||||||
|
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||||
|
#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||||
|
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
|
||||||
|
#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||||
|
#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||||
|
|
||||||
|
/* ---- Атрибуты диалога справки ------------------------------------ */
|
||||||
|
#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||||
|
#define ATTR_HELP_BDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||||
|
#define ATTR_HELP_TIT COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||||
|
#define ATTR_HELP_HDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||||
|
|
||||||
|
/* ---- Режимы просмотра -------------------------------------------- */
|
||||||
|
#define VIEW_MD 0 /* markdown с форматированием (по умолчанию) */
|
||||||
|
#define VIEW_RAW_WRAP 1 /* RAW: длинные строки переносятся кратно 80 */
|
||||||
|
#define VIEW_RAW_HSCROLL 2 /* RAW: длинные строки в одну, гориз. скролл */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Разделяемые символы mdview2.c, используемые модулем RAW.
|
||||||
|
* ================================================================== */
|
||||||
|
extern uint8_t g_view; /* активный режим просмотра (VIEW_*) */
|
||||||
|
extern uint32_t file_size; /* размер активного буфера документа */
|
||||||
|
extern const uint8_t *g_remap; /* таблица ремапа [128-255] (0 = нет) */
|
||||||
|
extern uint8_t g_scratch_phys; /* scratch EMM-страница для win_rest */
|
||||||
|
|
||||||
|
char fb(uint32_t p); /* байт активного буфера (W3-маппинг) */
|
||||||
|
void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off);
|
||||||
|
void fill_row(uint8_t y, uint8_t attr);
|
||||||
|
uint32_t seg_off(uint16_t idx); /* offset исходника для логической строки idx */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* API модуля RAW (mdview2_raw.c).
|
||||||
|
* ================================================================== */
|
||||||
|
void raw_seed_from(uint16_t md_top_line); /* вход в RAW: позиция по top_line */
|
||||||
|
void raw_home(void); /* сброс в начало (смена буфера при F8 в/из UTF-8) */
|
||||||
|
void raw_draw(void); /* перерисовать область документа в RAW */
|
||||||
|
void raw_status_numbers(void); /* числовая зона статус-бара для RAW */
|
||||||
|
uint8_t raw_key(uint8_t scan); /* навигация RAW; 1 = обработано */
|
||||||
|
void raw_screen_init(void); /* очистка экрана с нужным аттрибутом */
|
||||||
|
void raw_renorm(void); /* выровнять позицию при смене под-режима (F3) */
|
||||||
|
|
||||||
|
#endif /* MDVIEW2_H */
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_conf.h — конфигурация сборки mdview2: какие опциональные модули
|
||||||
|
* просмотра включать. 1 = включить (код входит в бинарь), 0 = исключить
|
||||||
|
* полностью (нулевой расход кода/памяти).
|
||||||
|
*
|
||||||
|
* Модули обёрнуты в #if WITH_xxx целиком, поэтому файлы можно держать в
|
||||||
|
* сборке всегда (при 0 они компилируются в пустой объектник) — менять
|
||||||
|
* нужно только эти define.
|
||||||
|
*/
|
||||||
|
#ifndef MDVIEW2_CONF_H
|
||||||
|
#define MDVIEW2_CONF_H
|
||||||
|
|
||||||
|
#define WITH_RAW 1 /* RAW-просмотр исходника (F2): mdview2_raw.c */
|
||||||
|
#define WITH_HEX 0 /* HEX-просмотр (зарезервировано): mdview2_hex.c */
|
||||||
|
|
||||||
|
#endif /* MDVIEW2_CONF_H */
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_raw.c — RAW-просмотр исходного текста (без markdown-форматирования).
|
||||||
|
*
|
||||||
|
* Работает по АКТИВНОМУ буферу документа через fb() (8-бит = исходные байты,
|
||||||
|
* UTF-8 = декодированный в CP866 буфер), ремап ≥0x80 (CP1251/KOI8) — на
|
||||||
|
* отрисовке. Индекс/кэш markdown не используются: позиция — байт-offset,
|
||||||
|
* 1 байт = 1 ячейка, \t показываем пробелом. Два режима:
|
||||||
|
* VIEW_RAW_WRAP — длинные строки переносятся кратно 80 (рвём слова);
|
||||||
|
* VIEW_RAW_HSCROLL — строка в один ряд, горизонтальный скролл.
|
||||||
|
*
|
||||||
|
* Память: 0 доп. EMM; near-буфер строки + переиспользование g_scratch_phys.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <conio.h>
|
||||||
|
#include <bios/text.h>
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
#if WITH_RAW /* весь модуль — опционален (mdview_conf.h); при 0 пустой объектник */
|
||||||
|
|
||||||
|
static uint32_t raw_top = 0; /* байт-offset верхней строки экрана */
|
||||||
|
static uint32_t raw_bot = 0; /* offset ряда сразу ПОД экраном (для прокрутки) */
|
||||||
|
static uint16_t raw_hpan = 0; /* гориз. сдвиг (только HSCROLL) */
|
||||||
|
|
||||||
|
#define HPAN_STEP 8u
|
||||||
|
|
||||||
|
/* ---- примитивы навигации по физическим строкам / переносам -------- */
|
||||||
|
|
||||||
|
static uint32_t raw_line_start(uint32_t off)
|
||||||
|
{
|
||||||
|
while (off && fb(off - 1) != '\n') off--;
|
||||||
|
return off;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WRAP: начало следующего ряда от off (\n завершает строку; иначе +80). */
|
||||||
|
static uint32_t raw_next_wrap(uint32_t off)
|
||||||
|
{
|
||||||
|
uint32_t e = off, lim = off + SCREEN_W;
|
||||||
|
while (e < file_size) {
|
||||||
|
if (fb(e) == '\n') return e + 1;
|
||||||
|
e++;
|
||||||
|
if (e >= lim) { /* прошли 80 без \n */
|
||||||
|
if (e < file_size && fb(e) == '\n') return e + 1; /* ровно 80 + \n */
|
||||||
|
return e; /* длинная строка → продолжение */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e; /* EOF */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WRAP: начало предыдущего ряда. */
|
||||||
|
// static uint32_t raw_prev_wrap(uint32_t off)
|
||||||
|
// {
|
||||||
|
// if (off == 0) return 0;
|
||||||
|
// uint32_t ls = raw_line_start(off);
|
||||||
|
// if (off > ls) /* тот же абзац: на 80 назад */
|
||||||
|
// return off - SCREEN_W;
|
||||||
|
// /* off == начало строки: последний ряд предыдущей физической строки.
|
||||||
|
// * Последнюю 80-границу ищем циклом (без 32-битного деления). */
|
||||||
|
// uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */
|
||||||
|
// uint32_t last = pls;
|
||||||
|
// while (last + SCREEN_W < (off - 1)) last += SCREEN_W;
|
||||||
|
// return last;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static uint32_t raw_prev_wrap(uint32_t off)
|
||||||
|
{
|
||||||
|
if (off == 0) return 0;
|
||||||
|
uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */
|
||||||
|
while (pls + SCREEN_W < (off - 1)) pls += SCREEN_W;
|
||||||
|
return pls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HSCROLL: начало следующей/предыдущей физической строки. */
|
||||||
|
static uint32_t raw_next_line(uint32_t off)
|
||||||
|
{
|
||||||
|
while (off < file_size && fb(off) != '\n') off++;
|
||||||
|
return (off < file_size) ? off + 1 : off;
|
||||||
|
}
|
||||||
|
// static uint32_t raw_prev_line(uint32_t off)
|
||||||
|
// {
|
||||||
|
// if (off == 0) return 0;
|
||||||
|
// uint32_t p = off - 1; /* \n, завершивший пред. строку */
|
||||||
|
// if (p == 0) return 0;
|
||||||
|
// p--;
|
||||||
|
// while (p != 0 && fb(p) != '\n') p--;
|
||||||
|
// return (fb(p) == '\n') ? p + 1 : 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static uint32_t raw_prev_line(uint32_t off)
|
||||||
|
{
|
||||||
|
if (off == 0) return 0;
|
||||||
|
return raw_line_start(off - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- отрисовка ряда (общий near-буфер → g_scratch_phys → win_rest) - */
|
||||||
|
|
||||||
|
static uint8_t raw_buf[SCREEN_W + 1]; /* только символы (атрибут единый, задаётся при выводе) */
|
||||||
|
|
||||||
|
/* В RAW атрибут единый (ATTR_TEXT): буфер — только 80 символов, без пар
|
||||||
|
* (char,attr). Кладём один байт-глиф (\t→пробел, ремап ≥0x80). */
|
||||||
|
static void raw_cell(uint8_t i, uint8_t ch)
|
||||||
|
{
|
||||||
|
if (ch >= 0x80 && g_remap) ch = g_remap[ch - 0x80];
|
||||||
|
raw_buf[i] = ch;
|
||||||
|
}
|
||||||
|
/* Вывод готового 80-символьного буфера в строку экрана одним BIOS-вызовом
|
||||||
|
* (char-буфер + единый attr — без WINREST/scratch-страницы). */
|
||||||
|
static void raw_flush(uint8_t row)
|
||||||
|
{
|
||||||
|
bios_set_place(row, 0);
|
||||||
|
// bios_writeattr((const char *)raw_buf, SCREEN_W, ATTR_TEXT);
|
||||||
|
// bios_writeattr_until((const char *)raw_buf, SCREEN_W, ATTR_TEXT, 0);
|
||||||
|
bios_write_until((const char *)raw_buf, SCREEN_W, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t raw_draw_wrap_row(uint32_t off, uint8_t row)
|
||||||
|
{
|
||||||
|
uint8_t i = 0;
|
||||||
|
uint32_t e = off, lim = off + SCREEN_W;
|
||||||
|
while (i < SCREEN_W && e < file_size) {
|
||||||
|
char c = fb(e);
|
||||||
|
if (c == '\n') {
|
||||||
|
raw_cell(i, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||||
|
i++; e++;
|
||||||
|
}
|
||||||
|
raw_flush(row);
|
||||||
|
return raw_next_wrap(off);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static uint32_t raw_draw_hscroll_row(uint32_t off, uint8_t row)
|
||||||
|
// {
|
||||||
|
// uint32_t e = off;
|
||||||
|
// while (e < file_size && fb(e) != '\n') e++; /* e = конец строки */
|
||||||
|
// uint16_t len = e - off;
|
||||||
|
|
||||||
|
// uint8_t i = 0;
|
||||||
|
// uint32_t p = off + raw_hpan;
|
||||||
|
// while (i < SCREEN_W && p < e) {
|
||||||
|
// char c = fb(p);
|
||||||
|
// raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||||
|
// i++; p++;
|
||||||
|
// }
|
||||||
|
// for (; i < SCREEN_W; i++) raw_buf[i] = ' ';
|
||||||
|
|
||||||
|
// if (raw_hpan > 0) raw_buf[0] = '<';
|
||||||
|
// if (len > (uint16_t)raw_hpan + SCREEN_W) raw_buf[SCREEN_W - 1] = '>';
|
||||||
|
// raw_flush(row);
|
||||||
|
// return (e < file_size) ? e + 1 : e;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static uint32_t raw_draw_hscroll_row(uint32_t off, uint8_t row)
|
||||||
|
{
|
||||||
|
uint32_t e = off;
|
||||||
|
while (e < file_size && fb(e) != '\n') e++; /* e = конец строки */
|
||||||
|
uint16_t len = e - off;
|
||||||
|
|
||||||
|
uint8_t i = 0;
|
||||||
|
uint32_t p = off + raw_hpan;
|
||||||
|
while (i < SCREEN_W && p < e) {
|
||||||
|
char c = fb(p);
|
||||||
|
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||||
|
i++; p++;
|
||||||
|
}
|
||||||
|
// raw_buf[i] = 0;
|
||||||
|
|
||||||
|
if (raw_hpan > 0)
|
||||||
|
raw_buf[0] = '<';
|
||||||
|
if (len > (uint16_t)raw_hpan + SCREEN_W)
|
||||||
|
raw_buf[SCREEN_W - 1] = '>';
|
||||||
|
if (i < SCREEN_W)
|
||||||
|
raw_buf[i] = 0;
|
||||||
|
raw_flush(row);
|
||||||
|
return (e < file_size) ? e + 1 : e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Рисует один ряд от off (по режиму) и возвращает offset следующего ряда. */
|
||||||
|
static uint32_t raw_draw_one(uint32_t off, uint8_t row)
|
||||||
|
{
|
||||||
|
if (off >= file_size) { fill_row(row, ATTR_TEXT); return off; }
|
||||||
|
return (g_view == VIEW_RAW_WRAP) ? raw_draw_wrap_row(off, row)
|
||||||
|
: raw_draw_hscroll_row(off, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_draw(void)
|
||||||
|
{
|
||||||
|
uint32_t off = raw_top;
|
||||||
|
for (uint8_t r = 0; r < VIEW_H; r++)
|
||||||
|
off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + r));
|
||||||
|
raw_bot = off; /* offset первого ряда НИЖЕ экрана */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Прокрутка на одну строку: аппаратный scroll окна документа + отрисовка
|
||||||
|
* ОДНОЙ новой строки (как в MD-режиме). raw_bot — offset ряда сразу под
|
||||||
|
* экраном, ведётся инкрементально. */
|
||||||
|
static uint32_t raw_next(uint32_t off)
|
||||||
|
{
|
||||||
|
return (g_view == VIEW_RAW_WRAP) ? raw_next_wrap(off) : raw_next_line(off);
|
||||||
|
}
|
||||||
|
static uint32_t raw_prev(uint32_t off)
|
||||||
|
{
|
||||||
|
return (g_view == VIEW_RAW_WRAP) ? raw_prev_wrap(off) : raw_prev_line(off);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void raw_scroll_down1(void)
|
||||||
|
{
|
||||||
|
if (raw_bot >= file_size) return; /* уже внизу */
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */
|
||||||
|
raw_bot = raw_draw_one(raw_bot, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||||
|
raw_top = raw_next(raw_top);
|
||||||
|
}
|
||||||
|
static void raw_scroll_up1(void)
|
||||||
|
{
|
||||||
|
if (raw_top == 0) return;
|
||||||
|
uint32_t nt = raw_prev(raw_top);
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */
|
||||||
|
(void)raw_draw_one(nt, VIEW_TOP_ROW);
|
||||||
|
raw_bot = raw_prev(raw_bot);
|
||||||
|
raw_top = nt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- статус-бар (без подсчёта строк: процент по байтам + режим) ---- */
|
||||||
|
|
||||||
|
/* Процент по байтам без 32-битного деления: масштабируем offset'ы в единицы
|
||||||
|
* по 1 КБ (>>10), дальше 16-битная арифметика (a*100 ≤ 12800). */
|
||||||
|
static uint8_t raw_pct(void)
|
||||||
|
{
|
||||||
|
uint16_t a = (uint16_t)(raw_top >> 10);
|
||||||
|
uint16_t b = (uint16_t)(file_size >> 10);
|
||||||
|
if (b == 0) return 0; /* файл < 1 КБ */
|
||||||
|
return (uint8_t)(a * 100u / b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_status_numbers(void)
|
||||||
|
{
|
||||||
|
uint8_t pct = raw_pct();
|
||||||
|
textattr(ATTR_BAR);
|
||||||
|
gotoxy(47, 0);
|
||||||
|
bios_writeattr_until(g_view == VIEW_RAW_WRAP ? "RAW wrap" : "RAW pan",
|
||||||
|
20, ATTR_BAR, 0);
|
||||||
|
gotoxy(73, 0);
|
||||||
|
dec8(pct);
|
||||||
|
cputs("% ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- посев позиции / пересев при смене активного буфера ----------- */
|
||||||
|
|
||||||
|
void raw_seed_from(uint16_t md_top_line)
|
||||||
|
{
|
||||||
|
uint32_t off = seg_off(md_top_line);
|
||||||
|
if (off > file_size) off = 0;
|
||||||
|
raw_top = raw_line_start(off);
|
||||||
|
raw_hpan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Сброс в начало — при смене активного буфера (F8 в/из UTF-8: размеры/offset'ы
|
||||||
|
* разные, старый raw_top невалиден). Без percent-математики. */
|
||||||
|
void raw_home(void)
|
||||||
|
{
|
||||||
|
raw_top = 0;
|
||||||
|
raw_hpan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_screen_init(void) {
|
||||||
|
bios_clearwin(1, 0, 30, 80, ATTR_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Нормализация позиции при смене под-режима RAW (F3 Wrap/Unwrap): выровнять
|
||||||
|
* raw_top на начало физической строки и снять горизонтальный сдвиг. */
|
||||||
|
void raw_renorm(void)
|
||||||
|
{
|
||||||
|
raw_top = raw_line_start(raw_top);
|
||||||
|
raw_hpan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- навигация (вызывается из главного цикла при g_view != VIEW_MD) - */
|
||||||
|
|
||||||
|
uint8_t raw_key(uint8_t scan)
|
||||||
|
{
|
||||||
|
uint32_t nt;
|
||||||
|
|
||||||
|
switch (scan) {
|
||||||
|
case KEY_DOWN: raw_scroll_down1(); raw_status_numbers(); return 1;
|
||||||
|
case KEY_UP: raw_scroll_up1(); raw_status_numbers(); return 1;
|
||||||
|
|
||||||
|
case KEY_PGDN:
|
||||||
|
nt = raw_top;
|
||||||
|
for (uint8_t k = 0; k < VIEW_H; k++) {
|
||||||
|
uint32_t n2 = raw_next(nt);
|
||||||
|
if (n2 >= file_size) break;
|
||||||
|
nt = n2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_PGUP:
|
||||||
|
nt = raw_top;
|
||||||
|
for (uint8_t k = 0; k < VIEW_H; k++) nt = raw_prev(nt);
|
||||||
|
break;
|
||||||
|
case KEY_HOME:
|
||||||
|
nt = 0; raw_hpan = 0;
|
||||||
|
break;
|
||||||
|
case KEY_END:
|
||||||
|
nt = file_size; /* отмотать VIEW_H рядов назад */
|
||||||
|
for (uint8_t k = 0; k < VIEW_H; k++) nt = raw_prev(nt);
|
||||||
|
raw_hpan = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KEY_LEFT:
|
||||||
|
if (g_view == VIEW_RAW_HSCROLL && raw_hpan) {
|
||||||
|
raw_hpan = (raw_hpan >= HPAN_STEP) ? (uint16_t)(raw_hpan - HPAN_STEP) : 0;
|
||||||
|
raw_draw();
|
||||||
|
raw_status_numbers();
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
case KEY_RIGHT:
|
||||||
|
if (g_view == VIEW_RAW_HSCROLL) {
|
||||||
|
if (raw_hpan < 248u) raw_hpan = (uint16_t)(raw_hpan + HPAN_STEP);
|
||||||
|
raw_draw();
|
||||||
|
raw_status_numbers();
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return 0; /* не наша клавиша */
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_top = nt;
|
||||||
|
raw_draw(); /* PgUp/PgDn/Home/End — полный экран */
|
||||||
|
raw_status_numbers();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* WITH_RAW */
|
||||||
Reference in New Issue
Block a user