mdview2: полировка статус-бара/справки/детекции + libc min/max

- status-bar: dirty-tracking (числа/процент/кодировка перерисовываются
  только при изменении), поле кодировки сдвинуто к DIV1_X-10 (8 симв.)
- md_key: HOME/END не перерисовывают экран, если позиция не меняется
- help: версия v1.0(a3), добавлены F2/F3 (RAW/Wrap), компактные секции
- enc: детекция по 5 частотным буквам и сэмплу 1КБ; ENC_UNSUPPORTED (UTF16/32)
- libc: добавлены min()/max() (naked, <stdlib.h>) + сборка в lib/Makefile

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:43:04 +03:00
parent 1b78dda125
commit 733746572c
9 changed files with 260 additions and 104 deletions
+107 -50
View File
@@ -28,6 +28,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <conio.h> #include <conio.h>
#include <bios/text.h> #include <bios/text.h>
@@ -269,6 +270,7 @@ static void doc_switch(uint8_t target); /* forward (нужна в progress_tic
void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr) void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr)
{ {
uint8_t len = (x < SCREEN_W) ? (uint8_t)(SCREEN_W - x) : 0; uint8_t len = (x < SCREEN_W) ? (uint8_t)(SCREEN_W - x) : 0;
len = min(len, strlen(s));
if (len == 0) return; if (len == 0) return;
bios_set_place(y, x); bios_set_place(y, x);
bios_writeattr_until(s, len, attr, 0); bios_writeattr_until(s, len, attr, 0);
@@ -490,7 +492,7 @@ static int alloc_set_storage(void)
{ {
uint8_t want; uint8_t want;
want = (uint8_t)(file_pages >> 1 + 1u); want = (uint8_t)((file_pages >> 1) + 1u);
if (want > MAX_INDEX_PAGES) want = MAX_INDEX_PAGES; if (want > MAX_INDEX_PAGES) want = MAX_INDEX_PAGES;
index_blk = mem_alloc_pages(want); index_blk = mem_alloc_pages(want);
if (index_blk == 0) return -5; if (index_blk == 0) return -5;
@@ -499,7 +501,7 @@ static int alloc_set_storage(void)
index_phys[i] = mem_get_page(index_blk, i); index_phys[i] = mem_get_page(index_blk, i);
max_lines = (uint16_t)index_pages * INDEX_RECS_PER_PAGE; max_lines = (uint16_t)index_pages * INDEX_RECS_PER_PAGE;
want = (uint8_t)(file_pages >> 1 + 1u); want = (uint8_t)((file_pages >> 1) + 1u);
if (want > MAX_CACHE_DIR_PAGES) want = MAX_CACHE_DIR_PAGES; if (want > MAX_CACHE_DIR_PAGES) want = MAX_CACHE_DIR_PAGES;
cache_dir_blk = mem_alloc_pages(want); cache_dir_blk = mem_alloc_pages(want);
if (cache_dir_blk == 0) return -6; if (cache_dir_blk == 0) return -6;
@@ -616,32 +618,67 @@ static void load_key(uint16_t k)
uint16_t maxtop = (dr > VIEW_H) ? (uint16_t)(dr - VIEW_H) : 0; uint16_t maxtop = (dr > VIEW_H) ? (uint16_t)(dr - VIEW_H) : 0;
uint16_t nt = top_line; uint16_t nt = top_line;
if (ascii == 0x1B || scan == KEY_F10) { g_abort = 1; return; } /* Esc/F10 → выход */ if (ascii == 0x1B || scan == KEY_F10) {
if (ascii) return; /* прочие ascii игнорируем */ g_abort = 1;
return;
} /* Esc/F10 → выход */
if (ascii)
return; /* прочие ascii игнорируем */
switch (scan) { switch (scan) {
case KEY_UP: nt = (top_line >= 1) ? (uint16_t)(top_line - 1) : 0; break; case KEY_UP:
case KEY_DOWN: nt = top_line + 1; break; nt = (top_line >= 1) ? (uint16_t)(top_line - 1) : 0;
case KEY_PGUP: nt = (top_line >= VIEW_H) ? (uint16_t)(top_line - VIEW_H) : 0; break; break;
case KEY_PGDN: nt = top_line + VIEW_H; break; case KEY_DOWN:
case KEY_HOME: top_line = 0; viewport_x = 0; draw_viewport_from_cache(); render_md_status_numbers(); return; nt = top_line + 1;
case KEY_END: top_line = maxtop; viewport_x = 0; draw_viewport_from_cache(); render_md_status_numbers(); return; break;
case KEY_LEFT: md_scroll_horizon(-(int8_t)HPAN_STEP); render_md_status_numbers(); return; case KEY_PGUP:
case KEY_RIGHT: md_scroll_horizon(+(int8_t)HPAN_STEP); render_md_status_numbers(); return; nt = (top_line >= VIEW_H) ? (uint16_t)(top_line - VIEW_H) : 0;
case KEY_F1: show_help(); return; break;
case KEY_F8: /* во время сборки крутим ТОЛЬКО 8-бит и только если набор 8-битный case KEY_PGDN:
* (ремап на отрисовке; индексация не зависит от кодировки) */ nt = top_line + VIEW_H;
if (g_f8_enabled && g_active_doc == DOC_8BIT) { break;
uint8_t nx = (g_encoding == ENC_CP866) ? ENC_CP1251 : case KEY_HOME:
(g_encoding == ENC_CP1251) ? ENC_KOI8R : ENC_CP866; top_line = 0;
set_encoding(nx); g_doc[DOC_8BIT].enc = nx; viewport_x = 0;
draw_viewport_from_cache(); draw_viewport_from_cache();
render_full_status(); render_md_status_numbers();
} return;
return; case KEY_END:
default: return; top_line = maxtop;
viewport_x = 0;
draw_viewport_from_cache();
render_md_status_numbers();
return;
case KEY_LEFT:
md_scroll_horizon(-(int8_t)HPAN_STEP);
render_md_status_numbers();
return;
case KEY_RIGHT:
md_scroll_horizon(+(int8_t)HPAN_STEP);
render_md_status_numbers();
return;
case KEY_F1:
show_help();
return;
case KEY_F8:
/* во время сборки крутим ТОЛЬКО 8-бит и только если набор 8-битный
* (ремап на отрисовке; индексация не зависит от кодировки) */
if (g_f8_enabled && g_active_doc == DOC_8BIT) {
uint8_t nx = (g_encoding == ENC_CP866) ? ENC_CP1251 :
(g_encoding == ENC_CP1251) ? ENC_KOI8R : ENC_CP866;
set_encoding(nx);
g_doc[DOC_8BIT].enc = nx;
draw_viewport_from_cache();
render_full_status();
}
return;
default:
return;
} }
if (nt > maxtop) nt = maxtop;
if (nt > maxtop)
nt = maxtop;
if (nt != top_line) { if (nt != top_line) {
top_line = nt; top_line = nt;
draw_viewport_from_cache(); draw_viewport_from_cache();
@@ -676,10 +713,11 @@ void progress_tick(void)
g_first_drawn = 1; g_first_drawn = 1;
touched = 1; touched = 1;
} }
if (n_lines >= (uint16_t)(g_last_prog + 20)) { if (n_lines >= (uint16_t)(g_last_prog + 10)) {
g_last_prog = n_lines; g_last_prog = n_lines;
spinner_tick(); spinner_tick();
if (g_first_drawn) render_md_status_numbers(); if (g_first_drawn)
render_md_status_numbers();
touched = 1; touched = 1;
} }
if (g_first_drawn && kbhit()) { if (g_first_drawn && kbhit()) {
@@ -688,7 +726,8 @@ void progress_tick(void)
} }
/* Отрисовка/WINREST/BIOS могли сбить маппинг W3, на который опирается fb() /* Отрисовка/WINREST/BIOS могли сбить маппинг W3, на который опирается fb()
* при индексации → форсируем ре-маппинг страницы файла на следующем fb(). */ * при индексации → форсируем ре-маппинг страницы файла на следующем fb(). */
if (touched) cur_page = 0xFF; if (touched)
cur_page = 0xFF;
} }
/* ================================================================== /* ==================================================================
@@ -710,10 +749,12 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
{ {
if (slot == DOC_UTF8) { if (slot == DOC_UTF8) {
if (!utf_avail) { /* первый вход: выделить страницы */ if (!utf_avail) { /* первый вход: выделить страницы */
if (!utf_alloc()) return -7; /* выделяет страницы + сбрасывает конвертер */ if (!utf_alloc())
return -7; /* выделяет страницы + сбрасывает конвертер */
utf_avail = 1; utf_avail = 1;
} }
file_blk = utf_blk; file_pages = utf_pages; 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) { if (g_utf_building) {
file_size = 0; file_size = 0;
@@ -723,18 +764,25 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
file_size = utf_size; 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);
} }
cur_page = 0xFF; cur_page = 0xFF;
int rc = alloc_set_storage(); int rc = alloc_set_storage();
if (rc < 0) return rc; if (rc < 0)
return rc;
set_encoding(enc); set_encoding(enc);
top_line = 0; viewport_x = 0; top_line = 0;
viewport_x = 0;
g_active_doc = slot; g_active_doc = slot;
if (visible) { g_disp_doc = slot; g_first_drawn = 0; } if (visible) {
g_disp_doc = slot;
g_first_drawn = 0;
}
g_loading = 1; g_loading = 1;
g_last_prog = 0; g_last_prog = 0;
@@ -743,7 +791,10 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
/* Зафиксировать конвертацию (на случай обрыва индекса по max_lines до конца /* Зафиксировать конвертацию (на случай обрыва индекса по max_lines до конца
* исходника) — иначе g_utf_building остался бы 1 и сломал бы следующую сборку. */ * исходника) — иначе g_utf_building остался бы 1 и сломал бы следующую сборку. */
if (g_utf_building) { utf_size = file_size; g_utf_building = 0; } 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);
@@ -762,31 +813,37 @@ 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;
if (target == g_active_doc) { /* тот же набор — только ремап */ if (target == g_active_doc) { /* тот же набор — только ремап */
set_encoding(next); g_doc[target].enc = next; set_encoding(next);
} else if (g_doc[target].built) { /* набор готов — мгновенный свап */ g_doc[target].enc = next;
doc_switch(target); g_disp_doc = target; } else if (g_doc[target].built) { /* набор готов — мгновенный свап */
set_encoding(next); g_doc[target].enc = next; doc_switch(target);
} else { /* строим набор лениво */ g_disp_doc = target;
doc_save_live(g_active_doc); /* сохранить текущий (позиция и т.п.) */ set_encoding(next);
g_doc[target].enc = next;
} else { /* строим набор лениво */
doc_save_live(g_active_doc); /* сохранить текущий (позиция и т.п.) */
#if WITH_RAW #if WITH_RAW
g_ready = 0; render_menu(); /* идёт индексация — прячем F2 */ g_ready = 0;
render_menu(); /* идёт индексация — прячем F2 */
#endif #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) */ * (progress_tick рисует по текущему виду — MD/RAW) */
spinner_show(0); spinner_show(0);
#if WITH_RAW #if WITH_RAW
g_ready = 1; render_menu(); g_ready = 1;
render_menu();
#endif #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);
return; return;
} }
g_disp_doc = target; /* build_doc уже выставил active/enc */ g_disp_doc = target; /* build_doc уже выставил active/enc */
} }
clamp_top(); /* привести top_line к новому набору (для MD) */ clamp_top(); /* привести top_line к новому набору (для MD) */
} }
/* ================================================================== /* ==================================================================
+5 -4
View File
@@ -72,10 +72,11 @@
/* ---- Кодировки --------------------------------------------------- */ /* ---- Кодировки --------------------------------------------------- */
/* 8-битные (CP866/CP1251/KOI8R) различаются только ремапом глифов [128-255] /* 8-битные (CP866/CP1251/KOI8R) различаются только ремапом глифов [128-255]
* на отрисовке; UTF-8 — отдельный декодированный набор. */ * на отрисовке; UTF-8 — отдельный декодированный набор. */
#define ENC_CP866 0 #define ENC_CP866 0
#define ENC_CP1251 1 #define ENC_CP1251 1
#define ENC_KOI8R 2 #define ENC_KOI8R 2
#define ENC_UTF8 3 #define ENC_UTF8 3
#define ENC_UNSUPPORTED -1 /* UTF16 / UTF32 */
#define CONV_MARGIN 4096u /* на сколько байт держать UTF-конвертацию впереди индексатора */ #define CONV_MARGIN 4096u /* на сколько байт держать UTF-конвертацию впереди индексатора */
/* ---- Флаги сегмента индекса (IF_*) — общие для индексатора/кэша/вью ---- */ /* ---- Флаги сегмента индекса (IF_*) — общие для индексатора/кэша/вью ---- */
+32 -12
View File
@@ -70,12 +70,22 @@ void set_encoding(uint8_t enc)
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты /* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
* различают 8-битные кодировки по частоте. */ * различают 8-битные кодировки по частоте. */
static const uint8_t common866 [10] = {0xAE,0xA5,0xA0,0xA8,0xAD,0xE2,0xE1,0xE0,0xA2,0xAB}; // static const uint8_t common866 [10] = {0xAE,0xA5,0xA0,0xA8,0xAD,0xE2,0xE1,0xE0,0xA2,0xAB};
static const uint8_t common1251[10] = {0xEE,0xE5,0xE0,0xE8,0xED,0xF2,0xF1,0xF0,0xE2,0xEB}; // static const uint8_t common1251[10] = {0xEE,0xE5,0xE0,0xE8,0xED,0xF2,0xF1,0xF0,0xE2,0xEB};
static const uint8_t commonkoi [10] = {0xCF,0xC5,0xC1,0xC9,0xCE,0xD4,0xD3,0xD2,0xD7,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;
// }
/* Проверяем только пять самых популярных символов (о е а и н) */
static const uint8_t common866 [5] = {0xAE,0xA5,0xA0,0xA8,0xAD};
static const uint8_t common1251[5] = {0xEE,0xE5,0xE0,0xE8,0xED};
static const uint8_t commonkoi [5] = {0xCF,0xC5,0xC1,0xC9,0xCE};
static uint8_t in_set10(const uint8_t *s, uint8_t b) 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; for (uint8_t i = 0; i < 5; i++) if (s[i] == b) return 1;
return 0; return 0;
} }
@@ -88,7 +98,7 @@ uint8_t detect_encoding(void)
return ENC_UTF8; return ENC_UTF8;
uint32_t n = file_size; uint32_t n = file_size;
if (n > 4096u) n = 4096u; /* сэмпл: первые 4 КБ — детекции хватает */ if (n > 1024u) n = 1024u; /* сэмпл: первый 1 КБ — детекции хватает */
uint8_t utf_ok = 1, has_mb = 0, has_high = 0, cont = 0; uint8_t utf_ok = 1, has_mb = 0, has_high = 0, cont = 0;
uint16_t s866 = 0, s1251 = 0, skoi = 0; uint16_t s866 = 0, s1251 = 0, skoi = 0;
@@ -102,10 +112,11 @@ uint8_t detect_encoding(void)
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; }
} else if (b >= 0xC2 && b <= 0xDF) { cont = 1; has_mb = 1; } }
else if (b >= 0xE0 && b <= 0xEF) { cont = 2; has_mb = 1; } else if (b >= 0xC2 && b <= 0xDF) { cont = 1; has_mb = 1; }
else if (b >= 0xF0 && b <= 0xF4) { cont = 3; has_mb = 1; } else if (b >= 0xE0 && b <= 0xEF) { cont = 2; has_mb = 1; }
else utf_ok = 0; /* битый лид/одиночный континюэйшн */ else if (b >= 0xF0 && b <= 0xF4) { cont = 3; has_mb = 1; }
else utf_ok = 0; /* битый лид/одиночный континюэйшн */
} }
/* Незавершённая multibyte-последовательность на КОНЦЕ — нарушение только /* Незавершённая multibyte-последовательность на КОНЦЕ — нарушение только
* если это настоящий EOF; на границе сэмпла (n<file_size) это просто * если это настоящий EOF; на границе сэмпла (n<file_size) это просто
@@ -138,7 +149,10 @@ static uint8_t attr_is_content(uint8_t a)
* g_remap (CP1251/KOI8). Без активной таблицы — прямой win_rest. */ * g_remap (CP1251/KOI8). Без активной таблицы — прямой win_rest. */
void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off) void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off)
{ {
if (!g_remap || w == 0) { win_rest(row, 0, 1, w, page, off); return; } if (!g_remap || w == 0) {
win_rest(row, 0, 1, w, page, off);
return;
}
uint8_t buf[SCREEN_W * 2]; uint8_t buf[SCREEN_W * 2];
bank_read(page, off, buf, (uint16_t)w * 2u); bank_read(page, off, buf, (uint16_t)w * 2u);
for (uint8_t i = 0; i < w; i++) { for (uint8_t i = 0; i < w; i++) {
@@ -225,7 +239,10 @@ static void conv_emit_cp(uint32_t cp)
if (cp == 0x2026) { conv_put('.'); conv_put('.'); conv_put('.'); return; } /* … → "..." */ if (cp == 0x2026) { conv_put('.'); conv_put('.'); conv_put('.'); return; } /* … → "..." */
if (cp == 0xFEFF) return; /* BOM/ZWNBSP — выкинуть */ if (cp == 0xFEFF) return; /* BOM/ZWNBSP — выкинуть */
for (uint8_t i = 0; i < UTF_SYM_N; i++) for (uint8_t i = 0; i < UTF_SYM_N; i++)
if (utf_sym[i].utf8 == (uint16_t)cp) { conv_put(utf_sym[i].cp866); return; } if (utf_sym[i].utf8 == (uint16_t)cp) {
conv_put(utf_sym[i].cp866);
return;
}
conv_put('?'); conv_put('?');
} }
@@ -251,7 +268,10 @@ void utf_convert_more(uint32_t target)
while (utf_src < n && while (utf_src < n &&
(uint32_t)((uint32_t)conv_page * PAGE_SIZE + conv_off) < target) { (uint32_t)((uint32_t)conv_page * PAGE_SIZE + conv_off) < target) {
uint8_t b = (uint8_t)cv_read(utf_src++); 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;
uint8_t need; uint8_t need;
+16 -12
View File
@@ -17,7 +17,7 @@
/* Геометрия диалога справки (в символьных координатах 80×32). */ /* Геометрия диалога справки (в символьных координатах 80×32). */
#define HELP_X 8u /* левая граница рамки */ #define HELP_X 8u /* левая граница рамки */
#define HELP_Y 4u /* верхняя граница рамки */ #define HELP_Y 3u /* верхняя граница рамки */
#define HELP_W 64u /* ширина рамки (включая │) */ #define HELP_W 64u /* ширина рамки (включая │) */
#define HELP_H 26u /* высота рамки (включая ─) */ #define HELP_H 26u /* высота рамки (включая ─) */
@@ -28,10 +28,13 @@ static void help_line(uint8_t r, const char *s, uint8_t attr)
uint8_t x = HELP_X + 1u; uint8_t x = HELP_X + 1u;
uint8_t y = HELP_Y + 1u + r; uint8_t y = HELP_Y + 1u + r;
uint8_t len = (uint8_t)strlen(s); uint8_t len = (uint8_t)strlen(s);
if (len > HELP_W - 2u) len = HELP_W - 2u;
if (len > HELP_W - 2u)
len = HELP_W - 2u;
bios_set_place(y, x); bios_set_place(y, x);
if (len > 0) bios_writeattr(s, len, attr); if (len > 0)
bios_writeattr(s, len, attr);
/* place уже продвинут bios_writeattr на len колонок (verified). */ /* place уже продвинут bios_writeattr на len колонок (verified). */
if (len < HELP_W - 2u) { if (len < HELP_W - 2u) {
bios_fillcharattr(' ', ATTR_HELP_BG, (uint8_t)(HELP_W - 2u - len)); bios_fillcharattr(' ', ATTR_HELP_BG, (uint8_t)(HELP_W - 2u - len));
@@ -70,24 +73,25 @@ void show_help(void)
/* Содержимое (20 внутренних строк) */ /* Содержимое (20 внутренних строк) */
uint8_t r = 0; uint8_t r = 0;
help_line(r++, "", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG);
help_line(r++, " MDView v0.2 -- Markdown Viewer for Sprinter", ATTR_HELP_HDR); help_line(r++, " MDView v1.0 (a3) -- Markdown Viewer for Sprinter", ATTR_HELP_HDR);
help_line(r++, " (c) 2026 Petrov A.G.", ATTR_HELP_BG); help_line(r++, " (c) 2026 \x8F\xA5\xE2\xE0\xAE\xA2 \x80\x2E\x83\x2E",
ATTR_HELP_BG);
help_line(r++, "", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG);
help_line(r++, " Navigation:", ATTR_HELP_HDR); help_line(r++, " Navigation:", ATTR_HELP_HDR);
help_line(r++, " \x18 \x19 Scroll one line up / down", ATTR_HELP_BG); help_line(r++, " \x18 \x19 Scroll one line up / down", ATTR_HELP_BG);
help_line(r++, " PgUp PgDn Scroll one page up / down", ATTR_HELP_BG); help_line(r++, " PgUp PgDn Scroll one page up / down", ATTR_HELP_BG);
help_line(r++, " Home End Jump to beginning / end of document", ATTR_HELP_BG); help_line(r++, " Home End Jump to beginning / end of document", ATTR_HELP_BG);
help_line(r++, " \x1B \x1A Horizontal pan (code blocks/tables)", ATTR_HELP_BG); help_line(r++, " \x1B \x1A Horizontal pan (code blocks/tables/unwrap)", ATTR_HELP_BG);
help_line(r++, " Esc F10 Exit", ATTR_HELP_BG); help_line(r++, " Esc F10 Exit", ATTR_HELP_BG);
help_line(r++, "", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG);
help_line(r++, " Markdown elements:", ATTR_HELP_HDR); help_line(r++, " Markdown elements:", ATTR_HELP_HDR);
help_line(r++, " # ## ### Headings H1-H6", ATTR_HELP_BG); help_line(r++, " # ## ### Headings H1-H6, **bold**, *italic*,", ATTR_HELP_BG);
help_line(r++, " **bold** *italic* `code` ~~strike~~", ATTR_HELP_BG); help_line(r++, " `code`, ``` code ```, ~~strike~~, > quote", ATTR_HELP_BG);
help_line(r++, " > quote ``` ... ``` Fenced code block", ATTR_HELP_BG); help_line(r++, " - * + 1. 2. Ordered list, |----|----| Tables", ATTR_HELP_BG);
help_line(r++, " - * + item 1. 2. Ordered list", ATTR_HELP_BG);
help_line(r++, " |----|----| Tables", ATTR_HELP_BG);
help_line(r++, "", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG);
help_line(r++, " Encoding:", ATTR_HELP_HDR); help_line(r++, " Encoding and View modes:", ATTR_HELP_HDR);
help_line(r++, " F2 RAW Mode", ATTR_HELP_BG);
help_line(r++, " F3 Wrap/Unwrap Mode for RAW View", ATTR_HELP_BG);
help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG); help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG);
help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG); help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG);
help_line(r++, "", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG);
+11 -6
View File
@@ -187,14 +187,19 @@ uint8_t md_key(uint8_t scan)
md_scroll_down(VIEW_H); md_scroll_down(VIEW_H);
break; break;
case KEY_HOME: case KEY_HOME:
top_line = 0; if(top_line != 0 || viewport_x != 0 ) {
viewport_x = 0; top_line = 0;
draw_viewport_from_cache(); viewport_x = 0;
draw_viewport_from_cache();
}
break; break;
case KEY_END: case KEY_END:
top_line = (n_lines > VIEW_H) ? (uint16_t)(n_lines - VIEW_H) : 0; uint16_t new_top_line = (n_lines > VIEW_H) ? (uint16_t)(n_lines - VIEW_H) : 0;
viewport_x = 0; if(top_line != new_top_line || viewport_x != 0 ) {
draw_viewport_from_cache(); top_line = new_top_line;
viewport_x = 0;
draw_viewport_from_cache();
}
break; break;
default: default:
+34 -20
View File
@@ -21,6 +21,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdio.h> /* dec8/dec16, cputs */ #include <stdio.h> /* dec8/dec16, cputs */
#include <conio.h> /* textattr/gotoxy/wrchar/COLOR */ #include <conio.h> /* textattr/gotoxy/wrchar/COLOR */
#include <limits.h>
#include <bios/text.h> /* bios_fillcharattr/bios_write_until */ #include <bios/text.h> /* bios_fillcharattr/bios_write_until */
#include "mdview2.h" #include "mdview2.h"
@@ -32,7 +33,8 @@ static uint8_t spinner_active = 0;
/* Продвигает спиннер на один кадр (если включён). */ /* Продвигает спиннер на один кадр (если включён). */
void spinner_tick(void) void spinner_tick(void)
{ {
if (!spinner_active) return; if (!spinner_active)
return;
wrchar(SPINNER_COL, 0, spinner_chars[spinner_phase & 3], ATTR_BAR_SPINNER); wrchar(SPINNER_COL, 0, spinner_chars[spinner_phase & 3], ATTR_BAR_SPINNER);
spinner_phase++; spinner_phase++;
} }
@@ -73,8 +75,8 @@ void prerender_status(void)
void status_encoding(void) void status_encoding(void)
{ {
textattr(ATTR_BAR); textattr(ATTR_BAR);
gotoxy(37, 0); gotoxy(DIV1_X - 10, 0);
bios_write_until(enc_name(g_encoding), 7, 0); bios_write_until(enc_name(g_encoding), 8, 0);
} }
/* Числовая часть (MD): "L a-b / total" между разделителями + "pct%" справа. /* Числовая часть (MD): "L a-b / total" между разделителями + "pct%" справа.
@@ -82,22 +84,29 @@ void status_encoding(void)
* хром не трогает. */ * хром не трогает. */
void render_md_status_numbers(void) void render_md_status_numbers(void)
{ {
static uint16_t local_total = UINT_MAX;
static uint16_t local_last = UINT_MAX;
static uint8_t local_loading = UCHAR_MAX;
uint16_t total = drawable_lines(); uint16_t total = drawable_lines();
uint16_t last = top_line + VIEW_H; uint16_t last = top_line + VIEW_H;
if (last > total) if (last > total)
last = total; last = total;
textattr(ATTR_BAR); if(local_total != total || local_last != last || local_loading != g_loading) {
gotoxy(DIV1_X + 1, 0); local_total = total; local_last = last; local_loading = g_loading;
bios_fillcharattr(' ', ATTR_BAR, DIV2_X - DIV1_X - 1); /* очистить [DIV1_X+1 .. DIV2_X-1] */ textattr(ATTR_BAR);
gotoxy(DIV1_X + 2, 0); gotoxy(DIV1_X + 2, 0);
cputs("L "); bios_fillcharattr(' ', ATTR_BAR, DIV2_X - DIV1_X - 2); /* очистить [DIV1_X+1 .. DIV2_X-1] */
dec16(top_line + 1); gotoxy(DIV1_X + 2, 0);
cputs("-"); cputs("L ");
dec16(last); dec16(top_line + 1);
cputs(" / "); cputs("-");
dec16(total); dec16(last);
if (g_loading) cputs("..."); /* ещё грузится */ cputs(" / ");
dec16(total);
if (g_loading) cputs("..."); /* ещё грузится */
}
} }
@@ -109,11 +118,15 @@ void render_full_status(void)
} }
void render_percent_progress(uint8_t pct) { void render_percent_progress(uint8_t pct) {
gotoxy(DIV2_X + 1, 0); static uint8_t local_pct = UCHAR_MAX;
bios_fillcharattr(' ', ATTR_BAR, SCREEN_W - DIV2_X - 1); /* очистить [DIV2_X+1 .. конец] */ if (local_pct != pct) {
gotoxy(DIV2_X + 2, 0); local_pct = pct;
dec8(pct); gotoxy(DIV2_X + 2, 0);
cputs("%"); bios_fillcharattr(' ', ATTR_BAR, SCREEN_W - DIV2_X - 2); /* очистить [DIV2_X+1 .. конец] */
gotoxy(DIV2_X + 2, 0);
dec8(pct);
cputs("%");
}
} }
#if WITH_RAW #if WITH_RAW
@@ -141,7 +154,8 @@ void render_menu(void)
* только у задействованных и доступных сейчас. */ * только у задействованных и доступных сейчас. */
fill_row(MENU_ROW, ATTR_MENU_T); fill_row(MENU_ROW, ATTR_MENU_T);
char num[3]; num[2] = 0; char num[3];
num[2] = 0;
for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */ for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */
num[0] = ' '; num[1] = (char)('1' + i); num[0] = ' '; num[1] = (char)('1' + i);
put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K); put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K);
+1
View File
@@ -60,6 +60,7 @@ LIBC_C := \
libc/stdio/getchar.c \ libc/stdio/getchar.c \
libc/stdio/putchar.c \ libc/stdio/putchar.c \
libc/stdio/puts.c \ libc/stdio/puts.c \
libc/stdlib/minmax.c \
libc/file/file.c \ libc/file/file.c \
libc/stdio/hex_print.c \ libc/stdio/hex_print.c \
libc/stdio/dec_print.c \ libc/stdio/dec_print.c \
+12
View File
@@ -0,0 +1,12 @@
#ifndef STDLIB_H
#define STDLIB_H
#include <stddef.h> /* size_t */
#include <stdint.h>
int16_t min(int16_t a, int16_t b);
int16_t max(int16_t a, int16_t b);
#endif
+42
View File
@@ -0,0 +1,42 @@
#include <stdlib.h>
int16_t min(int16_t a, int16_t b) __naked
{
(void)a; (void)b;
__asm
ld a, h
cp d
jr c, min_less
jr nz, min_greater
ld a, l
cp e
jr c, min_less
min_greater:
ret /* DE уже содержит b */
min_less:
ex de, hl
ret
__endasm;
}
int16_t max(int16_t a, int16_t b) __naked
{
(void)a; (void)b;
__asm
ld a, h
cp d
jr c, max_less
jr nz, max_greater
ld a, l
cp e
jr c, max_less
ret /* a >= b, return a in HL */
max_less:
/* a < b, return b in DE */
ret /* DE already contains b */
max_greater:
/* a > b, return a in HL */
ex de, hl
ret
__endasm;
}