@@ -30,7 +30,6 @@
# include <sprinter.h>
# include <sprinter_mem.h>
# include <palette.h>
# include <time.h> /* DEBUG: время обработки в статус-баре прогресса */
/* ---- Геометрия экрана -------------------------------------------- */
@@ -85,7 +84,8 @@ static const uint8_t md_pallete[16 * 4] = {
# 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_LIGHTGRAY, 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)
@@ -104,7 +104,7 @@ static const uint8_t md_pallete[16 * 4] = {
# define HELP_X 8u /* левая граница рамки */
# define HELP_Y 4u /* верхняя граница рамки */
# define HELP_W 64u /* ширина рамки (включая │) */
# define HELP_H 22 u /* высота рамки (включая ─) */
# define HELP_H 26 u /* высота рамки (включая ─) */
/* ---- Глобальное состояние ---------------------------------------- */
@@ -287,11 +287,75 @@ static char filename[64] = {0};
* index_lines() ещё досканирует файл (кооперативно, без прерываний). */
static uint8_t g_loading = 0 ; /* 1 во время index_lines() */
static uint8_t g_first_drawn = 0 ; /* первый экран уже нарисован */
static uint8_t g_f8_enabled = 0 ; /* можно ли сейчас переключать кодировку (метка F8 в меню + F8 в load_key) */
static uint8_t g_utf_failed = 0 ; /* UTF-8 набор не удалось построить (нет EMM) — пропускаем UTF-8 в цикле F8 */
static uint8_t g_abort = 0 ; /* F10/Esc во время загрузки → прервать индексацию */
static uint16_t g_last_prog = 0 ; /* n_lines на последнем обновлении статуса */
static datetime_t g_load_t0 ; /* DEBUG: старт обработки (для времени в статусе) */
static void progress_tick ( void ) ; /* определена ниже (нужна в index_lines) */
/* Кодировки. 8-битные (CP866/CP1251/KOI8R) — общая структура, различаются
* только ремапом глифов [128-255] на этапе отрисовки; UTF-8 — отдельный набор. */
# define ENC_CP866 0
# define ENC_CP1251 1
# define ENC_KOI8R 2
# define ENC_UTF8 3
static uint8_t g_encoding = ENC_CP866 ; /* активная кодировка */
static const uint8_t * g_remap = 0 ; /* активная таблица ремапа (CP1251/KOI8), 0 = без ремапа */
static uint8_t g_scratch_phys = 0 ; /* EMM-страница под ремап-буфер строки для win_rest */
/* UTF-8 набор (Фаза 2): src декодируется в отдельный блок EMM-страниц как
* чистый CP866. После конвертации активный буфер файла (file_phys/file_size/
* file_pages, который читает fb()) перенаправляется на эти страницы, и весь
* существующий конвейер индексации/кэша работает по ним без изменений —
* конвертированный поток это уже CP866, ремап на отрисовке не нужен. */
static uint8_t utf_blk = 0 ;
static uint8_t utf_pages = 0 ;
static uint8_t utf_phys [ MAX_PAGES ] = { 0 } ;
static uint32_t utf_size = 0 ;
static uint8_t utf_avail = 0 ; /* 1 = UTF-8 набор удалось сконвертировать/построить */
/* Снимок исходного (8-битного) файлового буфера — набор DOC_8BIT читает его
* как есть, набор DOC_UTF8 читает utf_phys. */
static uint8_t orig_file_blk = 0 ;
static uint8_t orig_file_pages = 0 ;
static uint32_t orig_file_size = 0 ;
static uint8_t orig_file_phys [ MAX_PAGES ] = { 0 } ;
/* ---------- Два сосуществующих набора индекс/кэш (Фазы 4-5) ----------
* DOC_8BIT — исходные байты (CP866/CP1251/KOI8R, различаются ремапом на
* отрисовке); DOC_UTF8 — UTF-8, декодированный в CP866. Оба строятся eager
* при старте (кооперативно), поэтому F8 переключает активный набор мгновенно.
* fb()/idx/cache-аксессоры работают по «живым» глобалам; doc_switch() свапает
* их с сохранённой копией нужного набора. */
typedef struct {
uint8_t built ;
uint8_t enc ; /* кодировка отображения этого набора */
uint8_t file_blk , file_pages ;
uint32_t file_size ;
uint8_t file_phys [ MAX_PAGES ] ;
uint8_t index_blk , index_pages , index_truncated ;
uint16_t n_lines , max_lines ;
uint8_t index_phys [ MAX_INDEX_PAGES ] ;
uint8_t cache_dir_blk , cache_dir_pages ;
uint8_t cache_dir_phys [ MAX_CACHE_DIR_PAGES ] ;
uint8_t cache_content_n_pages , cache_cur_page_idx ;
uint16_t cache_cur_off ;
uint8_t cache_content_blk [ MAX_CACHE_CONTENT_PAGES ] ;
uint8_t cache_content_phys [ MAX_CACHE_CONTENT_PAGES ] ;
uint16_t top_line ;
uint8_t viewport_x ;
} docset_t ;
# define DOC_8BIT 0
# define DOC_UTF8 1
static docset_t g_doc [ 2 ] = { { 0 } , { 0 } } ; /* ОБЯЗАТЕЛЬНО инициализировать: built=0 для обоих
* (SDCC z80 не обнуляет неинициализированные статики
* надёжно — см. memory/sdcc_static_storage_gotcha) */
static uint8_t g_active_doc = DOC_8BIT ; /* какой набор сейчас в живых глобалах */
static uint8_t g_disp_doc = DOC_8BIT ; /* какой набор показан пользователю */
static void set_encoding ( uint8_t enc ) ; /* forward (нужна в doc_load_live) */
static void doc_switch ( uint8_t target ) ; /* forward (нужна в progress_tick) */
# define HPAN_STEP 8u
/* Спиннер в статус-баре (col=8,row=0).
@@ -560,7 +624,8 @@ static void set_pallete(void) {
* Загрузка/выгрузка файла
* ================================================================== */
/* Загружает файл в EMM-страницы и подготавливает EMM-индекс.
/* Загружает исходные байты файла в EMM-страницы (без индекса/кэша — те
* выделяются отдельно на КАЖДЫЙ набор через alloc_set_storage()).
* Возврат: 0 — успех, отрицательное значение — код ошибки. */
static int load_file ( const char * path )
{
@@ -604,10 +669,16 @@ static int load_file(const char *path)
remaining - = chunk ;
}
close ( fd ) ;
return 0 ;
}
/* Выделяем блок индекса в EMM (8 байт на сегмент, 2048 записей/страница).
* Размер берем с запасом относительно файла; если памяти мало —
* уменьшаем количество страниц индекса до доступного значения. */
/* Выделяет в живые глобалы блок индекса + директорию рендер-кэша под текущий
* набор. file_pages должно быть уже выставлено (размер набора). Сбрасывает
* пул контента в пустое состояние (страницы доращиваются лениво при рендере).
* Возврат: 0 — успех, -5/-6 — нехватка EMM. */
static int alloc_set_storage ( void )
{
/* Блок индекса (8 байт на сегмент, 2048 записей/страница). */
{
uint8_t want = ( uint8_t ) ( file_pages + 1u ) ;
if ( want > MAX_INDEX_PAGES ) want = MAX_INDEX_PAGES ;
@@ -624,8 +695,7 @@ static int load_file(const char *path)
max_lines = ( uint16_t ) index_pages * INDEX_RECS_PER_PAGE ;
}
/* Директория рендер-кэша (Фаза 1) — та же ёмкость, что у индекса:
* по одной cache_rec_t на каждую логическую строку. */
/* Директория рендер-кэша — та же ёмкость, что у индекса. */
{
uint8_t want = ( uint8_t ) ( file_pages + 1u ) ;
if ( want > MAX_CACHE_DIR_PAGES ) want = MAX_CACHE_DIR_PAGES ;
@@ -640,20 +710,32 @@ static int load_file(const char *path)
for ( uint8_t i = 0 ; i < cache_dir_pages ; i + + )
cache_dir_phys [ i ] = mem_get_page ( cache_dir_blk , i ) ;
}
/* Свежий пул контента под этот набор. */
cache_content_n_pages = 0 ;
cache_cur_page_idx = 0 ;
cache_cur_off = 0 ;
return 0 ;
}
/* Освобождает выделенные EMM-блоки файла и индекса. */
static void doc_save_live ( uint8_t i ) ; /* forward */
/* Освобождает все EMM-блоки обоих наборов (индекс/директория/пул контента)
* плюс два файловых буфера (исходный 8-битный и UTF-8). */
static void unload_file ( void )
{
if ( file_blk ) {
mem_free_block ( file_blk ) ;
file_blk = 0 ;
}
if ( index_blk ) {
mem_free_block ( index_blk ) ;
index_blk = 0 ;
doc_save_live ( g_active_doc ) ; /* зафиксировать живой набор в g_doc */
for ( uint8_t s = 0 ; s < 2 ; s + + ) {
docset_t * d = & g_doc [ s ] ;
if ( d - > index_blk ) { mem_free_block ( d - > index_blk ) ; d - > index_blk = 0 ; }
if ( d - > cache_dir_blk ) { mem_free_block ( d - > cache_dir_blk ) ; d - > cache_dir_blk = 0 ; }
for ( uint8_t i = 0 ; i < d - > cache_content_n_pages ; i + + )
if ( d - > cache_content_blk [ i ] ) mem_free_block ( d - > cache_content_blk [ i ] ) ;
d - > cache_content_n_pages = 0 ;
}
if ( orig_file_blk ) { mem_free_block ( orig_file_blk ) ; orig_file_blk = 0 ; }
if ( utf_blk ) { mem_free_block ( utf_blk ) ; utf_blk = 0 ; }
file_blk = 0 ;
}
/* ==================================================================
@@ -1345,7 +1427,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
/* ====================== Таблицы: выровненная отрисовка ==================== */
# define TBL_MAX_COLS 16
# define TBL_ATTR ATTR_TEXT
# define TBL_ATTR ATTR_BOX
/* CP866 light box-drawing. */
# define TBL_H 0xC4 /* ─ */
# define TBL_V 0xB3 /* │ */
@@ -1731,6 +1813,319 @@ static uint8_t emph_to_attr(uint8_t emph, uint8_t base_attr)
}
/* Таблицы ремапа байтов [128-255] в CP866 (ASCII identity). Сгенерированы
* Python codecs. Кириллица + ходовая пунктуация; неизвестное → '?'. */
static const uint8_t cp1251_to_866 [ 256 ] = {
0x00 , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 , 0x09 , 0x0A , 0x0B , 0x0C , 0x0D , 0x0E , 0x0F ,
0x10 , 0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 , 0x19 , 0x1A , 0x1B , 0x1C , 0x1D , 0x1E , 0x1F ,
0x20 , 0x21 , 0x22 , 0x23 , 0x24 , 0x25 , 0x26 , 0x27 , 0x28 , 0x29 , 0x2A , 0x2B , 0x2C , 0x2D , 0x2E , 0x2F ,
0x30 , 0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x36 , 0x37 , 0x38 , 0x39 , 0x3A , 0x3B , 0x3C , 0x3D , 0x3E , 0x3F ,
0x40 , 0x41 , 0x42 , 0x43 , 0x44 , 0x45 , 0x46 , 0x47 , 0x48 , 0x49 , 0x4A , 0x4B , 0x4C , 0x4D , 0x4E , 0x4F ,
0x50 , 0x51 , 0x52 , 0x53 , 0x54 , 0x55 , 0x56 , 0x57 , 0x58 , 0x59 , 0x5A , 0x5B , 0x5C , 0x5D , 0x5E , 0x5F ,
0x60 , 0x61 , 0x62 , 0x63 , 0x64 , 0x65 , 0x66 , 0x67 , 0x68 , 0x69 , 0x6A , 0x6B , 0x6C , 0x6D , 0x6E , 0x6F ,
0x70 , 0x71 , 0x72 , 0x73 , 0x74 , 0x75 , 0x76 , 0x77 , 0x78 , 0x79 , 0x7A , 0x7B , 0x7C , 0x7D , 0x7E , 0x7F ,
0x3F , 0x3F , 0x27 , 0x3F , 0x22 , 0x2E , 0x3F , 0x3F , 0x3F , 0x3F , 0x3F , 0x3C , 0x3F , 0x3F , 0x3F , 0x3F ,
0x3F , 0x27 , 0x27 , 0x22 , 0x22 , 0x07 , 0x2D , 0x2D , 0x3F , 0x3F , 0x3F , 0x3E , 0x3F , 0x3F , 0x3F , 0x3F ,
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 ] = {
0x00 , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 , 0x09 , 0x0A , 0x0B , 0x0C , 0x0D , 0x0E , 0x0F ,
0x10 , 0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 , 0x19 , 0x1A , 0x1B , 0x1C , 0x1D , 0x1E , 0x1F ,
0x20 , 0x21 , 0x22 , 0x23 , 0x24 , 0x25 , 0x26 , 0x27 , 0x28 , 0x29 , 0x2A , 0x2B , 0x2C , 0x2D , 0x2E , 0x2F ,
0x30 , 0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x36 , 0x37 , 0x38 , 0x39 , 0x3A , 0x3B , 0x3C , 0x3D , 0x3E , 0x3F ,
0x40 , 0x41 , 0x42 , 0x43 , 0x44 , 0x45 , 0x46 , 0x47 , 0x48 , 0x49 , 0x4A , 0x4B , 0x4C , 0x4D , 0x4E , 0x4F ,
0x50 , 0x51 , 0x52 , 0x53 , 0x54 , 0x55 , 0x56 , 0x57 , 0x58 , 0x59 , 0x5A , 0x5B , 0x5C , 0x5D , 0x5E , 0x5F ,
0x60 , 0x61 , 0x62 , 0x63 , 0x64 , 0x65 , 0x66 , 0x67 , 0x68 , 0x69 , 0x6A , 0x6B , 0x6C , 0x6D , 0x6E , 0x6F ,
0x70 , 0x71 , 0x72 , 0x73 , 0x74 , 0x75 , 0x76 , 0x77 , 0x78 , 0x79 , 0x7A , 0x7B , 0x7C , 0x7D , 0x7E , 0x7F ,
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-битных). */
static void set_encoding ( uint8_t enc )
{
g_encoding = enc ;
g_remap = ( enc = = ENC_CP1251 ) ? cp1251_to_866 :
( enc = = ENC_KOI8R ) ? koi8r_to_866 : ( const uint8_t * ) 0 ;
}
/* ---------- Сохранение/восстановление/переключение наборов ----------
* doc_save_live: живые глобалы → g_doc[i]. doc_load_live: g_doc[i] → живые
* глобалы (+ инвалидация маппинга fb() и активной кодировки). */
static void doc_save_live ( uint8_t i )
{
docset_t * d = & g_doc [ i ] ;
d - > enc = g_encoding ;
d - > file_blk = file_blk ; d - > file_pages = file_pages ; d - > file_size = file_size ;
memcpy ( d - > file_phys , file_phys , MAX_PAGES ) ;
d - > index_blk = index_blk ; d - > index_pages = index_pages ; d - > index_truncated = index_truncated ;
d - > n_lines = n_lines ; d - > max_lines = max_lines ;
memcpy ( d - > index_phys , index_phys , MAX_INDEX_PAGES ) ;
d - > cache_dir_blk = cache_dir_blk ; d - > cache_dir_pages = cache_dir_pages ;
memcpy ( d - > cache_dir_phys , cache_dir_phys , MAX_CACHE_DIR_PAGES ) ;
d - > cache_content_n_pages = cache_content_n_pages ;
d - > cache_cur_page_idx = cache_cur_page_idx ; d - > cache_cur_off = cache_cur_off ;
memcpy ( d - > cache_content_blk , cache_content_blk , MAX_CACHE_CONTENT_PAGES ) ;
memcpy ( d - > cache_content_phys , cache_content_phys , MAX_CACHE_CONTENT_PAGES ) ;
d - > top_line = top_line ; d - > viewport_x = viewport_x ;
}
static void doc_load_live ( uint8_t i )
{
docset_t * d = & g_doc [ i ] ;
file_blk = d - > file_blk ; file_pages = d - > file_pages ; file_size = d - > file_size ;
memcpy ( file_phys , d - > file_phys , MAX_PAGES ) ;
index_blk = d - > index_blk ; index_pages = d - > index_pages ; index_truncated = d - > index_truncated ;
n_lines = d - > n_lines ; max_lines = d - > max_lines ;
memcpy ( index_phys , d - > index_phys , MAX_INDEX_PAGES ) ;
cache_dir_blk = d - > cache_dir_blk ; cache_dir_pages = d - > cache_dir_pages ;
memcpy ( cache_dir_phys , d - > cache_dir_phys , MAX_CACHE_DIR_PAGES ) ;
cache_content_n_pages = d - > cache_content_n_pages ;
cache_cur_page_idx = d - > cache_cur_page_idx ; cache_cur_off = d - > cache_cur_off ;
memcpy ( cache_content_blk , d - > cache_content_blk , MAX_CACHE_CONTENT_PAGES ) ;
memcpy ( cache_content_phys , d - > cache_content_phys , MAX_CACHE_CONTENT_PAGES ) ;
top_line = d - > top_line ; viewport_x = d - > viewport_x ;
cur_page = 0xFF ; /* следующий fb() пере-смаппит страницу */
set_encoding ( d - > enc ) ;
}
static void doc_switch ( uint8_t target )
{
if ( target = = g_active_doc ) return ;
doc_save_live ( g_active_doc ) ;
doc_load_live ( target ) ;
g_active_doc = target ;
}
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
* различают 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 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 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 ; }
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
* частоте ходовых букв; фолбэк CP866. */
static uint8_t detect_encoding ( void )
{
if ( file_size > = 3 & & ( uint8_t ) fb ( 0 ) = = 0xEF & & ( uint8_t ) fb ( 1 ) = = 0xBB & & ( uint8_t ) fb ( 2 ) = = 0xBF )
return ENC_UTF8 ;
uint32_t n = file_size ;
if ( n > 4096u ) n = 4096u ; /* сэмпл: первые 4 КБ — детекции хватает */
uint8_t utf_ok = 1 , has_mb = 0 , has_high = 0 , cont = 0 ;
uint16_t s866 = 0 , s1251 = 0 , skoi = 0 ;
for ( uint32_t p = 0 ; p < n ; p + + ) {
uint8_t b = ( uint8_t ) fb ( p ) ;
if ( b < 0x80 ) { if ( cont ) { utf_ok = 0 ; cont = 0 ; } continue ; }
has_high = 1 ;
if ( common_cp866 ( b ) ) s866 + + ;
if ( common_cp1251 ( b ) ) s1251 + + ;
if ( common_koi8r ( b ) ) skoi + + ;
if ( cont ) {
if ( ( b & 0xC0 ) = = 0x80 ) cont - - ;
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 > = 0xF0 & & b < = 0xF4 ) { cont = 3 ; has_mb = 1 ; }
else utf_ok = 0 ; /* битый лид/одиночный континюэйшн */
}
/* Незавершённая multibyte-последовательность на КОНЦЕ — нарушение только
* если это настоящий EOF; на границе сэмпла (n<file_size) это просто
* обрезка, не считаем за ошибку. */
if ( cont & & n = = file_size ) utf_ok = 0 ;
if ( ! has_high ) return ENC_CP866 ; /* чистый ASCII */
if ( utf_ok & & has_mb ) return ENC_UTF8 ;
if ( s1251 > = s866 & & s1251 > = skoi ) return ENC_CP1251 ;
if ( skoi > = s866 ) return ENC_KOI8R ;
return ENC_CP866 ;
}
static const char * enc_name ( uint8_t enc )
{
return ( enc = = ENC_CP1251 ) ? " CP1251 " :
( enc = = ENC_KOI8R ) ? " KOI8-R " :
( enc = = ENC_UTF8 ) ? " UTF-8 " : " CP866 " ;
}
/* Контентный глиф (ремапим при смене кодировки) vs структурный (рамка/HR/
* маркеры — уже CP866, не трогаем). Различаем по attr. */
static uint8_t attr_is_content ( uint8_t a )
{
return ( uint8_t ) ( a ! = ATTR_BOX & & a ! = ATTR_HR & &
a ! = ATTR_LIST_MARKER & & a ! = ATTR_QUOTE_MARKER ) ;
}
/* Вывод среза строки кэша с ремапом контентных глифов [128-255] через
* g_remap (CP1251/KOI8). Без активной таблицы — прямой win_rest. */
static 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 ; }
uint8_t buf [ SCREEN_W * 2 ] ;
bank_read ( page , off , buf , ( uint16_t ) w * 2u ) ;
for ( uint8_t i = 0 ; i < w ; i + + ) {
uint8_t ch = buf [ ( uint16_t ) i * 2u ] ;
if ( ch > = 0x80 & & attr_is_content ( buf [ ( uint16_t ) i * 2u + 1u ] ) )
buf [ ( uint16_t ) i * 2u ] = g_remap [ ch ] ;
}
bank_write ( g_scratch_phys , 0 , buf , ( uint16_t ) w * 2u ) ;
win_rest ( row , 0 , 1 , w , g_scratch_phys , 0 ) ;
}
/* ========================= UTF-8 → CP866 (Фаза 2) ======================== */
/* Кириллица U+0400..U+045F → CP866. Русский набор + Ё/ё и часть украинских
* (Є є Ї ї Ў ў), которые есть в CP866; пропуски → '?' (0x3F). */
static const uint8_t utf_cyr_to_866 [ 96 ] = {
/* 0400 */ 0x3F , 0xF0 , 0x3F , 0x3F , 0xF2 , 0x3F , 0x3F , 0xF4 , 0x3F , 0x3F , 0x3F , 0x3F , 0x3F , 0x3F , 0xF6 , 0x3F ,
/* 0410 */ 0x80 , 0x81 , 0x82 , 0x83 , 0x84 , 0x85 , 0x86 , 0x87 , 0x88 , 0x89 , 0x8A , 0x8B , 0x8C , 0x8D , 0x8E , 0x8F ,
/* 0420 */ 0x90 , 0x91 , 0x92 , 0x93 , 0x94 , 0x95 , 0x96 , 0x97 , 0x98 , 0x99 , 0x9A , 0x9B , 0x9C , 0x9D , 0x9E , 0x9F ,
/* 0430 */ 0xA0 , 0xA1 , 0xA2 , 0xA3 , 0xA4 , 0xA5 , 0xA6 , 0xA7 , 0xA8 , 0xA9 , 0xAA , 0xAB , 0xAC , 0xAD , 0xAE , 0xAF ,
/* 0440 */ 0xE0 , 0xE1 , 0xE2 , 0xE3 , 0xE4 , 0xE5 , 0xE6 , 0xE7 , 0xE8 , 0xE9 , 0xEA , 0xEB , 0xEC , 0xED , 0xEE , 0xEF ,
/* 0450 */ 0x3F , 0xF1 , 0x3F , 0x3F , 0xF3 , 0x3F , 0x3F , 0xF5 , 0x3F , 0x3F , 0x3F , 0x3F , 0x3F , 0x3F , 0xF7 , 0x3F ,
} ;
/* Поток вывода конвертера: пишем в utf_phys чанками по странице, буфер в
* near-памяти, флаш через один bank_write (не побайтовый swap W3). */
static uint8_t conv_buf [ 256 ] ;
static uint16_t conv_n = 0 ; /* байт в буфере, ещё не сброшено */
static uint8_t conv_page = 0 ; /* индекс текущей страницы в utf_phys */
static uint16_t conv_off = 0 ; /* уже сброшенное смещение в этой странице */
static void conv_flush ( void )
{
if ( conv_n ) {
bank_write ( utf_phys [ conv_page ] , conv_off , conv_buf , conv_n ) ;
conv_off = ( uint16_t ) ( conv_off + conv_n ) ;
conv_n = 0 ;
}
}
/* Добавляет один CP866-байт в выходной поток. Запись никогда не пересекает
* границу страницы (256 делит 16384 нацело). */
static void conv_put ( uint8_t b )
{
if ( ( uint16_t ) ( conv_off + conv_n ) = = PAGE_SIZE ) { /* страница заполнена */
conv_flush ( ) ;
conv_page + + ;
conv_off = 0 ;
}
conv_buf [ conv_n + + ] = b ;
if ( conv_n = = ( uint16_t ) sizeof ( conv_buf ) ) conv_flush ( ) ;
}
/* Кодпойнт → CP866-глиф(ы). Кириллица по таблице; ходовые символы (тире,
* кавычки, буллет, стрелки, галочки, box-drawing) — подстановкой; BOM
* проглатывается; прочее → '?'. */
static void conv_emit_cp ( uint32_t cp )
{
if ( cp < 0x80 ) { conv_put ( ( uint8_t ) cp ) ; return ; }
if ( cp > = 0x0400 & & cp < = 0x045F ) { conv_put ( utf_cyr_to_866 [ cp - 0x0400 ] ) ; return ; }
switch ( cp ) {
case 0x00A0 : conv_put ( ' ' ) ; break ; /* nbsp */
case 0x00AB : conv_put ( ' < ' ) ; break ; /* « (в CP866 гильеметов нет — как в cp1251_to_866) */
case 0x00BB : conv_put ( ' > ' ) ; break ; /* » */
case 0x00B0 : conv_put ( 0xF8 ) ; break ; /* ° */
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.
* Битые/обрезанные последовательности → '?'; 4-байтные (эмодзи и т.п.) → '?'. */
static void utf8_convert ( void )
{
conv_n = 0 ; conv_page = 0 ; conv_off = 0 ;
uint32_t n = file_size ;
uint32_t p = 0 ;
while ( p < n ) {
if ( ( uint16_t ) ( ( uint16_t ) p & 0x0FFFu ) = = 0 ) spinner_tick ( ) ;
uint8_t b = ( uint8_t ) fb ( p + + ) ;
if ( b < 0x80 ) { conv_emit_cp ( b ) ; continue ; }
uint32_t cp ;
uint8_t need ;
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 & 0xF8 ) = = 0xF0 ) { cp = ( uint32_t ) ( b & 0x07 ) ; need = 3 ; }
else { conv_put ( ' ? ' ) ; continue ; } /* одиночный континюэйшн/битый лид */
uint8_t ok = 1 ;
for ( uint8_t k = 0 ; k < need ; k + + ) {
if ( p > = n ) { ok = 0 ; break ; }
uint8_t cb = ( uint8_t ) fb ( p ) ;
if ( ( cb & 0xC0 ) ! = 0x80 ) { ok = 0 ; break ; }
cp = ( cp < < 6 ) | ( uint32_t ) ( cb & 0x3F ) ;
p + + ;
}
if ( ! ok | | need = = 3 ) { conv_put ( ' ? ' ) ; continue ; } /* битое / 4-байтное */
conv_emit_cp ( cp ) ;
}
conv_flush ( ) ;
utf_size = ( uint32_t ) conv_page * PAGE_SIZE + conv_off ;
}
/* Выделяет страницы под UTF-8 набор и конвертирует туда исходник (читает
* оригинал — вызывать, когда живой буфер = исходные байты). НЕ перенаправляет
* активный буфер: это делает build_doc() при сборке набора DOC_UTF8.
* Конвертированный поток ≤ исходного (кириллица 2 байта → 1), хватает того же
* числа страниц. Возврат: 1 — успех, 0 — нет EMM. */
static uint8_t alloc_and_convert_utf8 ( void )
{
utf_pages = file_pages ;
utf_blk = mem_alloc_pages ( utf_pages ) ;
if ( utf_blk = = 0 ) return 0 ;
for ( uint8_t i = 0 ; i < utf_pages ; i + + )
utf_phys [ i ] = mem_get_page ( utf_blk , i ) ;
utf8_convert ( ) ; /* читает оригинал (active) → utf_phys */
return 1 ;
}
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
@@ -1757,7 +2152,7 @@ static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
uint16_t remaining = ( uint16_t ) ( rec . len - vx ) ;
uint8_t w = ( remaining > SCREEN_W ) ? SCREEN_W : ( uint8_t ) remaining ;
uint16_t off = ( uint16_t ) ( rec . off + ( uint16_t ) vx * 2u ) ;
win_rest ( row , 0 , 1 , w , rec . page , off ) ;
win_rest_remap ( row , w , rec . page , off ) ;
if ( remaining > SCREEN_W ) {
wrchar ( SCREEN_W - 1 , row , ' > ' , ATTR_TRUNC ) ;
}
@@ -1823,18 +2218,6 @@ static void render_status_numbers(void)
gotoxy ( 73 , 0 ) ;
dec8 ( pct ) ;
cputs ( " % " ) ;
if ( g_loading ) { /* DEBUG: время обработки (зона имени файла) */
datetime_t now ;
getdatetime ( & now ) ;
int16_t el = ( int16_t ) now . minute * 60 + now . second
- ( ( int16_t ) g_load_t0 . minute * 60 + g_load_t0 . second ) ;
if ( el < 0 ) el + = 3600 ;
gotoxy ( 31 , 0 ) ;
cputs ( " t= " ) ;
dec16 ( ( uint16_t ) el ) ;
cputs ( " s " ) ; /* трейлинг-пробелы затирают прошлую ширину */
}
}
/* Полностью перерисовывает статус-бар (заголовок + числа). */
@@ -1845,6 +2228,7 @@ static void render_full_status(void)
/* Размещение в шапке: "MDVIEW", затем слот спиннера и имя файла. */
put_str_attr ( 1 , 0 , " MDVIEW " , ATTR_BAR ) ;
put_str_attr ( 10 , 0 , filename , ATTR_BAR ) ;
put_str_attr ( 37 , 0 , enc_name ( g_encoding ) , ATTR_BAR ) ; /* текущая кодировка */
render_status_numbers ( ) ;
}
@@ -1857,12 +2241,18 @@ static void render_updated_status(void)
/* Отрисовывает нижнюю строку меню. */
static void render_menu ( void )
{
/* Строка меню разбита на 10 блоков по 8 колонок: метку Fn кладём в блок
* (n-1)*8, чтобы позиции соответствовали номерам клавиш (F1→0, F8→7,
* F10→9). Пока заполнены только F1/F8/F10. */
fill_row ( MENU_ROW , ATTR_MENU_T ) ;
put_str_attr ( 1 , MENU_ROW , " F1 " , ATTR_MENU_K ) ;
put_str_attr ( 4 , MENU_ROW , " Help " , ATTR_MENU_T ) ;
/* F2 зарезервирован: режимов wrap/unwrap больше нет. */
put_str_attr ( SCREEN_W - 10 , MENU_ROW , " F10 " , ATTR_MENU_K ) ;
put_str_attr ( SCREEN_W - 5 , MENU_ROW , " Exit " , 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 ) ;
if ( g_f8_enabled ) { /* F8 показываем только когда переключение кодировки возможно */
put_str_attr ( 7 * 8 + 1 , MENU_ROW , " F8 " , ATTR_MENU_K ) ;
put_str_attr ( 7 * 8 + 4 , MENU_ROW , " Codepage " , ATTR_MENU_T ) ;
}
put_str_attr ( 9 * 8 + 0 , MENU_ROW , " F10 " , ATTR_MENU_K ) ;
put_str_attr ( 9 * 8 + 4 , MENU_ROW , " Exit " , ATTR_MENU_T ) ;
}
/* ==================================================================
@@ -2039,6 +2429,10 @@ static void show_help(void)
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 + + , " Encoding: " , ATTR_HELP_HDR ) ;
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 + + , " " , ATTR_HELP_BG ) ;
help_line ( r + + , " File size: up to 128 KB (EMM). Lines: up to 16384. " , ATTR_HELP_BG ) ;
help_line ( r + + , " " , ATTR_HELP_BG ) ;
@@ -2073,6 +2467,16 @@ static void load_key(uint16_t k)
case KEY_LEFT : scroll_h ( - ( int8_t ) HPAN_STEP ) ; render_status_numbers ( ) ; return ;
case KEY_RIGHT : scroll_h ( + ( int8_t ) HPAN_STEP ) ; render_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 ;
@@ -2086,8 +2490,8 @@ static void load_key(uint16_t k)
/* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */
static void progress_tick ( void )
{
/* Прогрессивная отрисовка строящегося (всегда показываемого) набора. */
uint8_t touched = 0 ;
if ( ! g_first_drawn & & drawable_lines ( ) > = VIEW_H ) {
draw_viewport_from_cache ( ) ;
render_status_numbers ( ) ;
@@ -2113,6 +2517,81 @@ static void progress_tick(void)
* Точка входа
* ================================================================== */
/* Строит набор `slot` кооперативно (index_lines + progress_tick).
* visible=1 — первичный (показываемый): прогрессивная отрисовка первого экрана
* и скролл по готовым строкам. visible=0 — вторичный: index_lines строит его в
* живых глобалах, а progress_tick через свап показывает/листает уже готовый
* g_disp_doc. Возврат: 0 — успех, <0 — нехватка EMM (-5/-6/-7).
*
* UTF-8 конвертация делается ЛЕНИВО здесь, а не до первого экрана: при входе
* (для DOC_UTF8 ещё не сконвертированного) живой буфер обязан быть = оригинал
* (8-битные байты) — utf8_convert читает его через fb(). Это так и в случае
* первичного UTF-8 (сразу после load_file), и вторичного (после сборки
* первичного 8-битного, чей буфер = оригинал). */
static int build_doc ( uint8_t slot , uint8_t enc , uint8_t visible )
{
if ( slot = = DOC_UTF8 ) {
if ( ! utf_avail ) { /* конвертируем сейчас (live = оригинал) */
if ( ! alloc_and_convert_utf8 ( ) ) return - 7 ;
utf_avail = 1 ;
}
file_blk = utf_blk ; file_pages = utf_pages ; file_size = utf_size ;
memcpy ( file_phys , utf_phys , MAX_PAGES ) ;
} else {
file_blk = orig_file_blk ; file_pages = orig_file_pages ; file_size = orig_file_size ;
memcpy ( file_phys , orig_file_phys , MAX_PAGES ) ;
}
cur_page = 0xFF ;
int rc = alloc_set_storage ( ) ;
if ( rc < 0 ) return rc ;
set_encoding ( enc ) ;
top_line = 0 ; viewport_x = 0 ;
g_active_doc = slot ;
if ( visible ) { g_disp_doc = slot ; g_first_drawn = 0 ; }
g_loading = 1 ;
g_last_prog = 0 ;
index_lines ( ) ;
g_loading = 0 ;
g_doc [ slot ] . built = 1 ;
doc_save_live ( slot ) ;
return 0 ;
}
/* Переключает кодировку на `next` (вызывается из главного цикла по F8).
* 8-бит↔8-бит внутри одного набора — мгновенный ремап; переход в другой набор
* — мгновенный doc_switch, если он построен, иначе ЛЕНИВАЯ сборка по месту
* (со спиннером), результат кэшируется. */
static void switch_encoding ( uint8_t next )
{
uint8_t target = ( next = = ENC_UTF8 ) ? DOC_UTF8 : DOC_8BIT ;
if ( target = = g_active_doc ) { /* тот же набор — только ремап */
set_encoding ( next ) ; g_doc [ target ] . enc = next ;
} else if ( g_doc [ target ] . built ) { /* набор готов — мгновенный свап */
doc_switch ( target ) ; g_disp_doc = target ;
set_encoding ( next ) ; g_doc [ target ] . enc = next ;
} else { /* строим набор лениво */
doc_save_live ( g_active_doc ) ; /* сохранить текущий (позиция и т.п.) */
spinner_show ( 1 ) ;
int rc = build_doc ( target , next , 1 ) ; /* visible: рисует прогрессивно */
spinner_show ( 0 ) ;
if ( rc < 0 ) { /* нет EMM — откат на текущий набор */
if ( target = = DOC_UTF8 ) g_utf_failed = 1 ;
doc_load_live ( g_active_doc ) ;
clamp_top ( ) ; draw_viewport_from_cache ( ) ; render_full_status ( ) ;
return ;
}
g_disp_doc = target ; /* build_doc уже выставил active/enc */
}
clamp_top ( ) ;
draw_viewport_from_cache ( ) ;
render_full_status ( ) ;
}
int main ( int argc , char * * argv )
{
const char * path ;
@@ -2172,15 +2651,44 @@ int main(int argc, char **argv)
cputs ( " \n \r \n \r " ) ;
return 1 ;
}
/* Прогрессивная загрузка: index_lines() сама рисует первый экран и пускает
* скролл по готовым данным через progress_tick(). */
g_loading = 1 ;
g_first_drawn = 0 ;
g_last_prog = 0 ;
getdatetime ( & g_load_t0 ) ; /* DEBUG: старт обработки */
index_lines ( ) ;
g_loading = 0 ;
spinner_show ( 0 ) ;
/* Scratch-страница под ремап-буфер строки (win_rest_remap). */
g_scratch_phys = mem_get_page ( mem_alloc_pages ( 1 ) , 0 ) ;
/* Оба набора пока не построены (страховка к инициализатору g_doc). */
g_doc [ DOC_8BIT ] . built = 0 ;
g_doc [ DOC_UTF8 ] . built = 0 ;
/* Снимок исходного 8-битного буфера (его читает набор DOC_8BIT). */
orig_file_blk = file_blk ; orig_file_pages = file_pages ; orig_file_size = file_size ;
memcpy ( orig_file_phys , file_phys , MAX_PAGES ) ;
/* Детекция кодировки по оригиналу. UTF-8 конвертацию НЕ делаем здесь —
* она дорогая (полный проход) и нужна только для UTF-8 набора, который
* строится через build_doc лениво. Так первый экран рисуется по индексации
* первичного набора без лишнего прохода. */
uint8_t det = detect_encoding ( ) ;
/* Первичный (показываемый) набор — по детекции; второй строится ЛЕНИВО по
* первому F8 (switch_encoding). Во время сборки 8-битного первичного F8
* разрешён (ремап 8-бит, см. load_key) → показываем метку F8; для UTF-8
* первичного переключаться пока некуда → метку прячем. */
uint8_t prim_enc = det ;
uint8_t prim_doc = ( prim_enc = = ENC_UTF8 ) ? DOC_UTF8 : DOC_8BIT ;
g_f8_enabled = ( prim_doc = = DOC_8BIT ) ;
render_menu ( ) ;
rc = build_doc ( prim_doc , prim_enc , 1 ) ; /* первичный — видимый */
if ( rc < 0 & & prim_doc = = DOC_UTF8 ) { /* UTF-8 не вышел (нет EMM) → 8-бит */
prim_doc = DOC_8BIT ; prim_enc = ENC_CP866 ;
rc = build_doc ( DOC_8BIT , ENC_CP866 , 1 ) ;
}
if ( rc < 0 ) {
spinner_show ( 0 ) ;
clrscr_attr ( ATTR_RESET ) ;
textattr ( ATTR_RESET ) ;
cputs ( " MDView: index/cache alloc failed \n \r \n \r " ) ;
return 1 ;
}
if ( g_abort ) goto exit_loop ; /* F10/Esc во время загрузки — корректный выход */
if ( n_lines = = 0 ) {
die ( " mdview: empty file " ) ;
@@ -2188,7 +2696,12 @@ int main(int argc, char **argv)
return 1 ;
}
/* Финальная отрисовка: все строки готовы, top_line мог уехать при скролле. */
/* Первичный готов — гасим спиннер, включаем F8 (второй набор дособерётся
* лениво по первому переключению) и рисуем финальный статус с ПОЛНЫМ
* счётчиком строк. */
spinner_show ( 0 ) ;
g_f8_enabled = 1 ;
render_menu ( ) ;
clamp_top ( ) ;
render_full_status ( ) ;
draw_viewport_from_cache ( ) ;
@@ -2204,6 +2717,15 @@ int main(int argc, char **argv)
}
switch ( scan ) {
case KEY_F1 : show_help ( ) ; break ;
case KEY_F8 : { /* цикл CP866→CP1251→KOI8R→UTF8→CP866; нужный набор дособерётся лениво */
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 ; /* UTF-8 недоступен */
switch_encoding ( next ) ;
if ( g_abort ) goto exit_loop ; /* Esc/F10 во время ленивой досборки */
continue ;
}
case KEY_F10 : goto exit_loop ;
case KEY_UP : scroll_up ( 1 ) ; break ;
case KEY_DOWN : scroll_down ( 1 ) ; break ;