--- lbbs/src/editor.c 2025/07/20 02:04:21 1.40 +++ lbbs/src/editor.c 2025/12/18 10:43:48 1.63 @@ -1,33 +1,35 @@ -/*************************************************************************** - editor.h - description - ------------------- - copyright : (C) 2004-2025 by Leaflet - email : leaflet@leafok.com - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 3 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * editor + * - user interactive full-screen text editor + * + * Copyright (C) 2004-2025 Leaflet + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif #include "bbs.h" #include "common.h" #include "editor.h" #include "io.h" #include "log.h" +#include "login.h" #include "memory_pool.h" #include "str_process.h" #include #include +#include #include -#define EDITOR_ESC_DISPLAY_STR "\033[32m*\033[m" -#define EDITOR_MEM_POOL_LINE_PER_CHUNK 1000 -#define EDITOR_MEM_POOL_CHUNK_LIMIT (MAX_EDITOR_DATA_LINES / EDITOR_MEM_POOL_LINE_PER_CHUNK + 1) +enum _editor_constant_t +{ + EDITOR_MEM_POOL_LINE_PER_CHUNK = 1000, + EDITOR_MEM_POOL_CHUNK_LIMIT = (MAX_EDITOR_DATA_LINES / EDITOR_MEM_POOL_LINE_PER_CHUNK + 1), +}; + +static const char EDITOR_ESC_DISPLAY_STR[] = "\033[32m*\033[m"; static MEMORY_POOL *p_mp_data_line; static MEMORY_POOL *p_mp_editor_data; @@ -48,7 +50,7 @@ int editor_memory_pool_init(void) } p_mp_editor_data = memory_pool_init(sizeof(EDITOR_DATA), 1, 1); - if (p_mp_data_line == NULL) + if (p_mp_editor_data == NULL) { log_error("Memory pool init error\n"); return -3; @@ -79,10 +81,11 @@ EDITOR_DATA *editor_data_load(const char long line_offsets[MAX_EDITOR_DATA_LINES + 1]; long current_data_line_length = 0; long i; + int j; if (p_data == NULL) { - log_error("editor_data_load() error: NULL pointer\n"); + log_error("NULL pointer error\n"); return NULL; } @@ -125,6 +128,15 @@ EDITOR_DATA *editor_data_load(const char memcpy(p_editor_data->p_display_lines[i], p_data + line_offsets[i], (size_t)p_editor_data->display_line_lengths[i]); current_data_line_length += p_editor_data->display_line_lengths[i]; + // Convert \t to single space + for (j = 0; j < p_editor_data->display_line_lengths[i]; j++) + { + if (p_editor_data->p_display_lines[i][j] == '\t') + { + p_editor_data->p_display_lines[i][j] = ' '; + } + } + // Trim \n from last line if (i + 1 == p_editor_data->display_line_total && p_editor_data->display_line_lengths[i] > 0 && @@ -149,7 +161,7 @@ long editor_data_save(const EDITOR_DATA if (p_editor_data == NULL || p_data == NULL) { - log_error("editor_data_save() error: NULL pointer\n"); + log_error("NULL pointer error\n"); return -1; } @@ -223,7 +235,7 @@ int editor_data_insert(EDITOR_DATA *p_ed if (p_editor_data == NULL || p_last_updated_line == NULL) { - log_error("editor_data_op() error: NULL pointer\n"); + log_error("NULL pointer error\n"); return -1; } @@ -260,10 +272,8 @@ int editor_data_insert(EDITOR_DATA *p_ed { if (p_editor_data->display_line_total >= MAX_EDITOR_DATA_LINES) { -#ifdef _DEBUG - log_error("Split line error, display_line_total(%ld) reach limit(%d)\n", + log_debug("Split line error, display_line_total(%ld) reach limit(%d)\n", p_editor_data->display_line_total, MAX_EDITOR_DATA_LINES); -#endif return -2; } @@ -357,9 +367,7 @@ int editor_data_insert(EDITOR_DATA *p_ed // Insert blank display line after last_display_line if (p_editor_data->display_line_total >= MAX_EDITOR_DATA_LINES) { -#ifdef _DEBUG - log_error("display_line_total over limit %d >= %d\n", p_editor_data->display_line_total, MAX_EDITOR_DATA_LINES); -#endif + log_debug("display_line_total over limit %d >= %d\n", p_editor_data->display_line_total, MAX_EDITOR_DATA_LINES); // Terminate prior display line with \n, to avoid error on cleanup if (display_line + i - 1 >= 0 && p_editor_data->display_line_lengths[display_line + i - 1] > 0) @@ -444,7 +452,7 @@ int editor_data_insert(EDITOR_DATA *p_ed } int editor_data_delete(EDITOR_DATA *p_editor_data, long *p_display_line, long *p_offset, - long *p_last_updated_line) + long *p_last_updated_line, int del_line) { long display_line = *p_display_line; long offset = *p_offset; @@ -461,7 +469,7 @@ int editor_data_delete(EDITOR_DATA *p_ed if (p_editor_data == NULL || p_last_updated_line == NULL) { - log_error("editor_data_op() error: NULL pointer\n"); + log_error("NULL pointer error\n"); return -1; } @@ -499,18 +507,22 @@ int editor_data_delete(EDITOR_DATA *p_ed } // Check str to be deleted - if (p_data_line[offset_data_line] > 0 && p_data_line[offset_data_line] < 127) + if (del_line) + { + str_len = (int)(p_editor_data->display_line_lengths[display_line] - offset); + } + else if (p_data_line[offset_data_line] > 0 && p_data_line[offset_data_line] < 127) { str_len = 1; } - else if (p_data_line[offset_data_line] & 0b10000000) // head of multi-byte character + else if (p_data_line[offset_data_line] & 0x80) // head of multi-byte character { str_len = 1; - c = (p_data_line[offset_data_line] & 0b01110000) << 1; - while (c & 0b10000000) + c = (p_data_line[offset_data_line] & 0x70) << 1; + while (c & 0x80) { str_len++; - c = (c & 0b01111111) << 1; + c = (c & 0x7f) << 1; } } else @@ -522,7 +534,8 @@ int editor_data_delete(EDITOR_DATA *p_ed // Current display line is (almost) empty if (offset_data_line + str_len > len_data_line || - (offset_data_line + str_len == len_data_line && p_data_line[offset_data_line] == '\n')) + (offset_data_line + str_len == len_data_line && + p_data_line[del_line ? len_data_line - 1 : offset_data_line] == '\n')) { if (display_line + 1 >= p_editor_data->display_line_total) // No additional display line (data line) { @@ -626,7 +639,8 @@ static int editor_display_key_handler(in { case 0: // Set msg snprintf(p_ctx->msg, sizeof(p_ctx->msg), - "| 退出[\033[32mCtrl-W\033[33m] |"); + "| 退出[\033[32mCtrl-W\033[33m] | [\033[32m%s\033[33m]", + (UTF8_fixed_width ? "定宽" : "变宽")); break; case KEY_CSI: *p_key = KEY_ESC; @@ -642,9 +656,11 @@ int editor_display(EDITOR_DATA *p_editor char buffer[MAX_EDITOR_DATA_LINE_LENGTH]; EDITOR_CTX ctx; int ch = 0; - char input_str[4]; - char c; + char input_str[5]; int str_len = 0; + wchar_t wcs[2]; + int wc_len; + char c; int input_ok; const int screen_begin_row = 1; const int screen_row_total = SCREEN_ROWS - screen_begin_row; @@ -662,6 +678,8 @@ int editor_display(EDITOR_DATA *p_editor int key_insert = 1; int i, j; char *p_str; + int del_line; + int tab_width = 0; clrline(output_current_row, SCREEN_ROWS); @@ -700,27 +718,39 @@ int editor_display(EDITOR_DATA *p_editor moveto((int)row_pos, (int)col_pos); iflush(); + tab_width = 0; str_len = 0; - ch = igetch_t(MAX_DELAY_TIME); + ch = igetch_t(BBS_max_user_idle_time); while (!SYS_server_exit) { + if (ch != KEY_NULL && ch != KEY_TIMEOUT) + { + BBS_last_access_tm = time(NULL); + } + // extended key handler if (editor_display_key_handler(&ch, &ctx) != 0) { goto cleanup; } - if (ch < 256 && (ch & 0b10000000)) // head of multi-byte character + if (ch == '\t') + { + ch = ' '; + tab_width = TAB_SIZE - ((int)(col_pos - 1) % TAB_SIZE) - 1; + } + + if (ch < 256 && (ch & 0x80)) // head of multi-byte character { str_len = 0; - c = (char)(ch & 0b11110000); - while (c & 0b10000000) + c = (char)(ch & 0xf0); + while (c & 0x80) { input_str[str_len] = (char)(ch - 256); str_len++; - c = (c & 0b01111111) << 1; + c = (c & 0x7f) << 1; - if ((c & 0b10000000) == 0) // Input completed + if ((c & 0x80) == 0) // Input completed { break; } @@ -729,19 +759,22 @@ int editor_display(EDITOR_DATA *p_editor ch = igetch(100); // 0.1 second if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Ignore received bytes if no futher input { -#ifdef _DEBUG - log_error("Ignore %d bytes of incomplete UTF8 character\n", str_len); -#endif + log_debug("Ignore %d bytes of incomplete UTF8 character\n", str_len); str_len = 0; break; } } + input_str[str_len] = '\0'; } if ((ch >= 32 && ch < 127) || str_len >= 2 || // Printable character or multi-byte character ch == CR || ch == KEY_ESC) // Special character { - BBS_last_access_tm = time(NULL); + // Refresh current action while user input + if (user_online_update(NULL) < 0) + { + log_error("user_online_update(NULL) error\n"); + } if (str_len == 0) // ch >= 32 && ch < 127 { @@ -759,7 +792,7 @@ int editor_display(EDITOR_DATA *p_editor if (!key_insert) // overwrite { if (editor_data_delete(p_editor_data, &display_line_out, &offset_out, - &last_updated_line) < 0) + &last_updated_line, 0) < 0) { log_error("editor_data_delete() error\n"); } @@ -807,9 +840,13 @@ int editor_display(EDITOR_DATA *p_editor { col_pos = 1; } - if (ch != CR) + if (offset_out > 0) { - col_pos += (str_len == 1 ? 1 : 2); + if (mbstowcs(wcs, input_str, 1) == (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + col_pos += (str_len == 1 ? 1 : (UTF8_fixed_width ? 2 : wcwidth(wcs[0]))); } } } @@ -819,18 +856,31 @@ int editor_display(EDITOR_DATA *p_editor break; } - ch = igetch(0); - if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Output if no futher input + if (ch == ' ' && tab_width > 0) { - break; + tab_width--; + } + else + { + ch = igetch(0); + if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Output if no futher input + { + break; + } } str_len = 0; continue; } - else if (ch == KEY_DEL || ch == BACKSPACE) // Del + else if (ch == KEY_DEL || ch == BACKSPACE || ch == Ctrl('K') || ch == Ctrl('Y')) // Del { - BBS_last_access_tm = time(NULL); + // Refresh current action while user input + if (user_online_update(NULL) < 0) + { + log_error("user_online_update(NULL) error\n"); + } + + del_line = 0; if (ch == BACKSPACE) { @@ -841,21 +891,22 @@ int editor_display(EDITOR_DATA *p_editor offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos], (int)col_pos - 1, &eol, &display_len, 0); - if (offset_in >= 1 && p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in - 1] < 0) // UTF8 - { - col_pos = display_len - 1; - } - else - { - col_pos = display_len; - } - + col_pos = display_len; if (col_pos < 1 && line_current - output_current_row + row_pos >= 0) { row_pos--; col_pos = MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]); } } + else if (ch == Ctrl('K')) + { + del_line = 1; + } + else if (ch == Ctrl('Y')) + { + col_pos = 1; + del_line = 1; + } display_line_in = line_current - output_current_row + row_pos; offset_in = split_line(p_editor_data->p_display_lines[display_line_in], (int)col_pos - 1, &eol, &display_len, 0); @@ -863,9 +914,9 @@ int editor_display(EDITOR_DATA *p_editor offset_out = offset_in; if ((str_len = editor_data_delete(p_editor_data, &display_line_out, &offset_out, - &last_updated_line)) < 0) + &last_updated_line, del_line)) < 0) { - log_error("editor_data_delete() error\n"); + log_error("editor_data_delete() error: %d\n", str_len); } else { @@ -917,9 +968,13 @@ int editor_display(EDITOR_DATA *p_editor switch (ch) { case KEY_NULL: + log_debug("KEY_NULL\n"); + goto cleanup; case KEY_TIMEOUT: + log_debug("User input timeout\n"); goto cleanup; case Ctrl('W'): + case Ctrl('X'): loop = 0; break; case Ctrl('S'): // Start of line @@ -993,13 +1048,38 @@ int editor_display(EDITOR_DATA *p_editor case KEY_LEFT: offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos], (int)col_pos - 1, &eol, &display_len, 0); - if (offset_in >= 1 && p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in - 1] < 0) // UTF8 + col_pos = display_len; + if (offset_in > 0) { - col_pos = display_len - 1; - } - else - { - col_pos = display_len; + str_len = 1; + offset_in--; + if (p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] < 0 || + p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] > 127) // UTF8 + { + while (offset_in > 0 && + (p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0xc0) != 0xc0) + { + str_len++; + offset_in--; + } + + if (str_len > 4) + { + log_error("Invalid UTF-8 data detected: str_len > 4\n"); + } + + if (mbstowcs(wcs, p_editor_data->p_display_lines[line_current - output_current_row + row_pos] + offset_in, 1) == + (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0])); + + if (wc_len == 2) + { + col_pos--; + } + } } if (col_pos >= 1) { @@ -1025,19 +1105,45 @@ int editor_display(EDITOR_DATA *p_editor output_end_row = SCREEN_ROWS - 1; // Legacy Fterm only works with this line col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos])); break; - case KEY_SPACE: - break; case KEY_RIGHT: offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos], (int)col_pos - 1, &eol, &display_len, 0); - if (offset_in < p_editor_data->display_line_lengths[line_current - output_current_row + row_pos] && - p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] < 0) // UTF8 - { - col_pos = display_len + 3; - } - else + col_pos = display_len + 2; + if (offset_in < p_editor_data->display_line_lengths[line_current - output_current_row + row_pos]) { - col_pos = display_len + 2; + str_len = 0; + if ((p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0x80) == + 0x80) // head of multi-byte character + { + c = (char)(p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0xf0); + while (c & 0x80) + { + str_len++; + c = (c & 0x7f) << 1; + } + + if (str_len > 4) + { + log_error("Invalid UTF-8 data detected: str_len > 4\n"); + } + + if (mbstowcs(wcs, p_editor_data->p_display_lines[line_current - output_current_row + row_pos] + offset_in, 1) == + (size_t)-1) + { + log_error("mbstowcs() error\n"); + } + wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0])); + + if (wc_len == 2) + { + col_pos++; + } + } + else + { + str_len = 1; + } + offset_in += str_len; } if (col_pos <= p_editor_data->display_line_widths[line_current - output_current_row + row_pos]) { @@ -1096,14 +1202,15 @@ int editor_display(EDITOR_DATA *p_editor col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos])); clrline(output_current_row, SCREEN_ROWS); break; + case Ctrl('Q'): case KEY_F1: - if (!show_help) // Not reentrant + if (!show_help) // Not re-entrant { break; } // Display help information show_help = 0; - display_file(DATA_READ_HELP, 1); + display_file(DATA_EDITOR_HELP, 1); show_help = 1; case KEY_F5: // Refresh after display help information @@ -1119,14 +1226,18 @@ int editor_display(EDITOR_DATA *p_editor break; } - BBS_last_access_tm = time(NULL); + // Refresh current action while user input + if (user_online_update(NULL) < 0) + { + log_error("user_online_update(NULL) error\n"); + } if (input_ok) { break; } - ch = igetch_t(MAX_DELAY_TIME); + ch = igetch_t(BBS_max_user_idle_time); } continue;