/[LeafOK_CVS]/lbbs/src/editor.c
ViewVC logotype

Contents of /lbbs/src/editor.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.65 - (show annotations)
Sat Jan 3 10:27:14 2026 UTC (2 months, 1 week ago) by sysadm
Branch: MAIN
CVS Tags: HEAD
Changes since 1.64: +1 -1 lines
Content type: text/x-csrc
Update copyright info

1 /* SPDX-License-Identifier: GPL-3.0-or-later */
2 /*
3 * editor
4 * - user interactive full-screen text editor
5 *
6 * Copyright (C) 2004-2026 Leaflet <leaflet@leafok.com>
7 */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include "bbs.h"
14 #include "common.h"
15 #include "editor.h"
16 #include "io.h"
17 #include "log.h"
18 #include "login.h"
19 #include "memory_pool.h"
20 #include "str_process.h"
21 #include <stdlib.h>
22 #include <string.h>
23 #include <wchar.h>
24 #include <sys/param.h>
25
26 enum _editor_constant_t
27 {
28 EDITOR_MEM_POOL_LINE_PER_CHUNK = 1000,
29 EDITOR_MEM_POOL_CHUNK_LIMIT = (MAX_EDITOR_DATA_LINES / EDITOR_MEM_POOL_LINE_PER_CHUNK + 1),
30 };
31
32 static const char EDITOR_ESC_DISPLAY_STR[] = "\033[32m*\033[m";
33
34 static MEMORY_POOL *p_mp_data_line;
35 static MEMORY_POOL *p_mp_editor_data;
36
37 int editor_memory_pool_init(void)
38 {
39 if (p_mp_data_line != NULL || p_mp_editor_data != NULL)
40 {
41 log_error("Editor mem pool already initialized");
42 return -1;
43 }
44
45 p_mp_data_line = memory_pool_init(MAX_EDITOR_DATA_LINE_LENGTH, EDITOR_MEM_POOL_LINE_PER_CHUNK, EDITOR_MEM_POOL_CHUNK_LIMIT);
46 if (p_mp_data_line == NULL)
47 {
48 log_error("Memory pool init error");
49 return -2;
50 }
51
52 p_mp_editor_data = memory_pool_init(sizeof(EDITOR_DATA), 1, 1);
53 if (p_mp_editor_data == NULL)
54 {
55 log_error("Memory pool init error");
56 return -3;
57 }
58
59 return 0;
60 }
61
62 void editor_memory_pool_cleanup(void)
63 {
64 if (p_mp_data_line != NULL)
65 {
66 memory_pool_cleanup(p_mp_data_line);
67 p_mp_data_line = NULL;
68 }
69
70 if (p_mp_editor_data != NULL)
71 {
72 memory_pool_cleanup(p_mp_editor_data);
73 p_mp_editor_data = NULL;
74 }
75 }
76
77 EDITOR_DATA *editor_data_load(const char *p_data)
78 {
79 EDITOR_DATA *p_editor_data;
80 char *p_data_line = NULL;
81 long line_offsets[MAX_EDITOR_DATA_LINES + 1];
82 long current_data_line_length = 0;
83 long i;
84 int j;
85
86 if (p_data == NULL)
87 {
88 log_error("NULL pointer error");
89 return NULL;
90 }
91
92 p_editor_data = memory_pool_alloc(p_mp_editor_data);
93 if (p_editor_data == NULL)
94 {
95 log_error("memory_pool_alloc() error");
96 return NULL;
97 }
98
99 p_editor_data->display_line_total = split_data_lines(p_data, SCREEN_COLS, line_offsets, MAX_EDITOR_DATA_LINES + 1,
100 0, p_editor_data->display_line_widths);
101
102 for (i = 0; i < p_editor_data->display_line_total; i++)
103 {
104 p_editor_data->display_line_lengths[i] = line_offsets[i + 1] - line_offsets[i];
105
106 if (i == 0 ||
107 current_data_line_length + p_editor_data->display_line_lengths[i] + 1 > MAX_EDITOR_DATA_LINE_LENGTH ||
108 (p_editor_data->display_line_lengths[i - 1] > 0 && p_data[line_offsets[i - 1] + p_editor_data->display_line_lengths[i - 1] - 1] == '\n'))
109 {
110 // Allocate new data line
111 p_data_line = memory_pool_alloc(p_mp_data_line);
112 if (p_data_line == NULL)
113 {
114 log_error("memory_pool_alloc() error: i = %d", i);
115 // Cleanup
116 editor_data_cleanup(p_editor_data);
117 return NULL;
118 }
119
120 p_editor_data->p_display_lines[i] = p_data_line;
121 current_data_line_length = 0;
122 }
123 else
124 {
125 p_editor_data->p_display_lines[i] = p_editor_data->p_display_lines[i - 1] + p_editor_data->display_line_lengths[i - 1];
126 }
127
128 memcpy(p_editor_data->p_display_lines[i], p_data + line_offsets[i], (size_t)p_editor_data->display_line_lengths[i]);
129 current_data_line_length += p_editor_data->display_line_lengths[i];
130
131 // Convert \t to single space
132 for (j = 0; j < p_editor_data->display_line_lengths[i]; j++)
133 {
134 if (p_editor_data->p_display_lines[i][j] == '\t')
135 {
136 p_editor_data->p_display_lines[i][j] = ' ';
137 }
138 }
139
140 // Trim \n from last line
141 if (i + 1 == p_editor_data->display_line_total &&
142 p_editor_data->display_line_lengths[i] > 0 &&
143 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n')
144 {
145 p_editor_data->display_line_lengths[i]--;
146 p_editor_data->display_line_widths[i]--;
147 current_data_line_length--;
148 }
149 p_data_line[current_data_line_length] = '\0';
150 }
151
152 memset(p_editor_data->p_display_lines + p_editor_data->display_line_total, 0, MAX_EDITOR_DATA_LINES - (size_t)p_editor_data->display_line_total);
153
154 return p_editor_data;
155 }
156
157 long editor_data_save(const EDITOR_DATA *p_editor_data, char *p_data, size_t buf_len)
158 {
159 long current_pos = 0;
160 long i;
161
162 if (p_editor_data == NULL || p_data == NULL)
163 {
164 log_error("NULL pointer error");
165 return -1;
166 }
167
168 for (i = 0; i < p_editor_data->display_line_total; i++)
169 {
170 if (current_pos + p_editor_data->display_line_lengths[i] + 1 > buf_len)
171 {
172 log_error("Data buffer not longer enough %d > %d", current_pos + p_editor_data->display_line_lengths[i] + 1, buf_len);
173 p_data[current_pos] = '\0';
174 return -2;
175 }
176
177 memcpy(p_data + current_pos, p_editor_data->p_display_lines[i], (size_t)p_editor_data->display_line_lengths[i]);
178 current_pos += p_editor_data->display_line_lengths[i];
179 }
180
181 p_data[current_pos] = '\0';
182
183 return current_pos;
184 }
185
186 void editor_data_cleanup(EDITOR_DATA *p_editor_data)
187 {
188 char *p_data_line = NULL;
189 long i;
190
191 if (p_editor_data == NULL)
192 {
193 return;
194 }
195
196 for (i = 0; i < p_editor_data->display_line_total; i++)
197 {
198 if (p_data_line == NULL)
199 {
200 p_data_line = p_editor_data->p_display_lines[i];
201 }
202
203 if (p_editor_data->display_line_lengths[i] > 0 &&
204 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n')
205 {
206 memory_pool_free(p_mp_data_line, p_data_line);
207 p_data_line = NULL;
208 }
209 }
210
211 if (p_data_line != NULL)
212 {
213 memory_pool_free(p_mp_data_line, p_data_line);
214 }
215
216 memory_pool_free(p_mp_editor_data, p_editor_data);
217 }
218
219 int editor_data_insert(EDITOR_DATA *p_editor_data, long *p_display_line, long *p_offset,
220 const char *str, int str_len, long *p_last_updated_line)
221 {
222 long display_line = *p_display_line;
223 long offset = *p_offset;
224 char *p_data_line = NULL;
225 long len_data_line;
226 long offset_data_line;
227 long last_display_line; // of data line
228 long line_offsets[MAX_EDITOR_DATA_LINE_LENGTH + 1];
229 int line_widths[MAX_EDITOR_DATA_LINE_LENGTH + 1];
230 long split_line_total;
231 long i;
232 int len;
233 int eol;
234 int display_len;
235
236 if (p_editor_data == NULL || p_last_updated_line == NULL)
237 {
238 log_error("NULL pointer error");
239 return -1;
240 }
241
242 // Get length of current data line
243 len_data_line = 0;
244 p_data_line = p_editor_data->p_display_lines[display_line];
245 for (i = display_line - 1; i >= 0; i--)
246 {
247 if (p_editor_data->display_line_lengths[i] > 0 &&
248 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n') // reach end of prior data line
249 {
250 break;
251 }
252
253 len_data_line += p_editor_data->display_line_lengths[i];
254 p_data_line = p_editor_data->p_display_lines[i];
255 }
256 offset_data_line = len_data_line + offset;
257 last_display_line = p_editor_data->display_line_total - 1;
258 for (i = display_line; i < p_editor_data->display_line_total; i++)
259 {
260 len_data_line += p_editor_data->display_line_lengths[i];
261
262 if (p_editor_data->display_line_lengths[i] > 0 &&
263 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n') // reach end of current data line
264 {
265 last_display_line = i;
266 break;
267 }
268 }
269
270 // Split current data line if over-length
271 if (len_data_line + str_len + 2 > MAX_EDITOR_DATA_LINE_LENGTH || str[0] == CR)
272 {
273 if (p_editor_data->display_line_total >= MAX_EDITOR_DATA_LINES)
274 {
275 log_debug("Split line error, display_line_total(%ld) reach limit(%d)",
276 p_editor_data->display_line_total, MAX_EDITOR_DATA_LINES);
277
278 return -2;
279 }
280
281 // Allocate new data line
282 p_data_line = memory_pool_alloc(p_mp_data_line);
283 if (p_data_line == NULL)
284 {
285 log_error("memory_pool_alloc() error");
286 return -2;
287 }
288
289 if (offset_data_line + str_len + 2 >= MAX_EDITOR_DATA_LINE_LENGTH || str[0] == CR)
290 {
291 if (str[0] == CR)
292 {
293 str_len = 0;
294 }
295
296 // Copy str to new data line
297 memcpy(p_data_line, str, (size_t)str_len);
298
299 // Copy rest part of current data line to new data line
300 memcpy(p_data_line + str_len,
301 p_editor_data->p_display_lines[display_line] + offset,
302 (size_t)(len_data_line - offset_data_line));
303
304 p_data_line[str_len + len_data_line - offset_data_line] = '\0';
305
306 // Add line ending to current display line (data line)
307 p_editor_data->p_display_lines[display_line][offset] = '\n';
308 p_editor_data->p_display_lines[display_line][offset + 1] = '\0';
309 p_editor_data->display_line_lengths[display_line] = offset + 1;
310
311 *p_display_line = display_line + 1;
312 *p_offset = str_len;
313 }
314 else
315 {
316 // Copy rest part of current data line to new data line
317 memcpy(p_data_line,
318 p_editor_data->p_display_lines[display_line] + offset,
319 (size_t)(len_data_line - offset_data_line));
320
321 p_data_line[len_data_line - offset_data_line] = '\0';
322
323 // Append str to current display line
324 memcpy(p_editor_data->p_display_lines[display_line] + offset, str, (size_t)str_len);
325
326 // Add line ending to current display line (data line)
327 p_editor_data->p_display_lines[display_line][offset + str_len] = '\n';
328 p_editor_data->p_display_lines[display_line][offset + str_len + 1] = '\0';
329 p_editor_data->display_line_lengths[display_line] = offset + str_len + 1;
330
331 *p_display_line = display_line;
332 *p_offset = offset + str_len;
333 }
334
335 // Update display width of current display line
336 len = split_line(p_editor_data->p_display_lines[display_line], SCREEN_COLS, &eol, &display_len, 0);
337 p_editor_data->display_line_widths[display_line] = display_len;
338
339 split_line_total = last_display_line - display_line + 3;
340
341 // Set start display_line for spliting new data line
342 display_line++;
343
344 *p_last_updated_line = p_editor_data->display_line_total;
345 }
346 else // insert str into current data line at offset_data_line
347 {
348 memmove(p_data_line + offset_data_line + str_len, p_data_line + offset_data_line, (size_t)(len_data_line - offset_data_line));
349 memcpy(p_data_line + offset_data_line, str, (size_t)str_len);
350 p_data_line[len_data_line + str_len] = '\0';
351
352 // Set p_data_line to head of current display line
353 p_data_line = p_editor_data->p_display_lines[display_line];
354 split_line_total = last_display_line - display_line + 3;
355
356 *p_display_line = display_line;
357 *p_offset = offset + str_len;
358 }
359
360 // Split current data line since beginning of current display line
361 split_line_total = split_data_lines(p_data_line, SCREEN_COLS, line_offsets, split_line_total, 0, line_widths);
362
363 for (i = 0; i < split_line_total; i++)
364 {
365 if (display_line + i > last_display_line)
366 {
367 // Insert blank display line after last_display_line
368 if (p_editor_data->display_line_total >= MAX_EDITOR_DATA_LINES)
369 {
370 log_debug("display_line_total over limit %d >= %d", p_editor_data->display_line_total, MAX_EDITOR_DATA_LINES);
371
372 // Terminate prior display line with \n, to avoid error on cleanup
373 if (display_line + i - 1 >= 0 && p_editor_data->display_line_lengths[display_line + i - 1] > 0)
374 {
375 len = split_line(p_editor_data->p_display_lines[display_line + i - 1], SCREEN_COLS - 1, &eol, &display_len, 0);
376 p_editor_data->p_display_lines[display_line + i - 1][len] = '\n';
377 p_editor_data->p_display_lines[display_line + i - 1][len + 1] = '\0';
378 p_editor_data->display_line_lengths[display_line + i - 1] = len + 1;
379 p_editor_data->display_line_widths[display_line + i - 1] = display_len;
380 }
381 if (*p_offset >= p_editor_data->display_line_lengths[*p_display_line])
382 {
383 *p_offset = p_editor_data->display_line_lengths[*p_display_line] - 1;
384 }
385 break;
386 }
387
388 // for (j = p_editor_data->display_line_total; j > last_display_line + 1; j--)
389 // {
390 // p_editor_data->p_display_lines[j] = p_editor_data->p_display_lines[j - 1];
391 // p_editor_data->display_line_lengths[j] = p_editor_data->display_line_lengths[j - 1];
392 // p_editor_data->display_line_widths[j] = p_editor_data->display_line_widths[j - 1];
393 // }
394 memmove(p_editor_data->p_display_lines + last_display_line + 2,
395 p_editor_data->p_display_lines + last_display_line + 1,
396 (size_t)(p_editor_data->display_line_total - last_display_line - 1) *
397 sizeof(p_editor_data->p_display_lines[last_display_line + 1]));
398 memmove(p_editor_data->display_line_lengths + last_display_line + 2,
399 p_editor_data->display_line_lengths + last_display_line + 1,
400 (size_t)(p_editor_data->display_line_total - last_display_line - 1) *
401 sizeof(p_editor_data->display_line_lengths[last_display_line + 1]));
402 memmove(p_editor_data->display_line_widths + last_display_line + 2,
403 p_editor_data->display_line_widths + last_display_line + 1,
404 (size_t)(p_editor_data->display_line_total - last_display_line - 1) *
405 sizeof(p_editor_data->display_line_widths[last_display_line + 1]));
406
407 last_display_line++;
408 *p_last_updated_line = p_editor_data->display_line_total;
409 (p_editor_data->display_line_total)++;
410 }
411
412 p_editor_data->display_line_lengths[display_line + i] = line_offsets[i + 1] - line_offsets[i];
413 p_editor_data->display_line_widths[display_line + i] = line_widths[i];
414 p_editor_data->p_display_lines[display_line + i] =
415 (i == 0
416 ? p_data_line
417 : (p_editor_data->p_display_lines[display_line + i - 1] + p_editor_data->display_line_lengths[display_line + i - 1]));
418
419 if (p_editor_data->display_line_lengths[display_line + i] > 0 &&
420 p_editor_data->p_display_lines[display_line + i][p_editor_data->display_line_lengths[display_line + i] - 1] == '\n')
421 {
422 break;
423 }
424 }
425
426 *p_last_updated_line = MAX(display_line + MIN(i, split_line_total - 1), *p_last_updated_line);
427
428 if (*p_offset >= p_editor_data->display_line_lengths[*p_display_line])
429 {
430 if (*p_display_line + 1 < p_editor_data->display_line_total)
431 {
432 *p_offset -= p_editor_data->display_line_lengths[*p_display_line];
433 (*p_display_line)++;
434 }
435 }
436
437 // Prevent the last display line from being over-length
438 if (p_editor_data->display_line_total == MAX_EDITOR_DATA_LINES)
439 {
440 len = split_line(p_editor_data->p_display_lines[p_editor_data->display_line_total - 1], SCREEN_COLS - 1, &eol, &display_len, 0);
441 p_editor_data->p_display_lines[p_editor_data->display_line_total - 1][len] = '\0';
442 p_editor_data->display_line_lengths[p_editor_data->display_line_total - 1] = len;
443 p_editor_data->display_line_widths[p_editor_data->display_line_total - 1] = display_len;
444 if (*p_display_line + 1 >= p_editor_data->display_line_total)
445 {
446 *p_offset = MIN(*p_offset, len);
447 *p_display_line = p_editor_data->display_line_total - 1;
448 }
449 }
450
451 return 0;
452 }
453
454 int editor_data_delete(EDITOR_DATA *p_editor_data, long *p_display_line, long *p_offset,
455 long *p_last_updated_line, int del_line)
456 {
457 long display_line = *p_display_line;
458 long offset = *p_offset;
459 char *p_data_line = NULL;
460 long len_data_line;
461 long offset_data_line;
462 long last_display_line; // of data line
463 long line_offsets[MAX_EDITOR_DATA_LINE_LENGTH + 1];
464 int line_widths[MAX_EDITOR_DATA_LINE_LENGTH + 1];
465 long split_line_total;
466 long i, j;
467 int str_len = 0;
468 char c;
469
470 if (p_editor_data == NULL || p_last_updated_line == NULL)
471 {
472 log_error("NULL pointer error");
473 return -1;
474 }
475
476 // Get length of current data line
477 len_data_line = 0;
478 p_data_line = p_editor_data->p_display_lines[display_line];
479 for (i = display_line - 1; i >= 0; i--)
480 {
481 if (p_editor_data->display_line_lengths[i] > 0 &&
482 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n') // reach end of prior data line
483 {
484 break;
485 }
486
487 len_data_line += p_editor_data->display_line_lengths[i];
488 p_data_line = p_editor_data->p_display_lines[i];
489 }
490 offset_data_line = len_data_line + offset;
491 last_display_line = p_editor_data->display_line_total - 1;
492 for (i = display_line; i < p_editor_data->display_line_total; i++)
493 {
494 len_data_line += p_editor_data->display_line_lengths[i];
495
496 if (p_editor_data->display_line_lengths[i] > 0 &&
497 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n') // reach end of current data line
498 {
499 last_display_line = i;
500 break;
501 }
502 }
503
504 if (offset_data_line >= len_data_line) // end-of-line
505 {
506 return 0;
507 }
508
509 // Check str to be deleted
510 if (del_line)
511 {
512 str_len = (int)(p_editor_data->display_line_lengths[display_line] - offset);
513 }
514 else if (p_data_line[offset_data_line] > 0 && p_data_line[offset_data_line] < 127)
515 {
516 str_len = 1;
517 }
518 else if (p_data_line[offset_data_line] & 0x80) // head of multi-byte character
519 {
520 str_len = 1;
521 c = (p_data_line[offset_data_line] & 0x70) << 1;
522 while (c & 0x80)
523 {
524 str_len++;
525 c = (c & 0x7f) << 1;
526 }
527 }
528 else
529 {
530 log_error("Some strange character at display_line %ld, offset %ld: %d %d",
531 display_line, offset, p_data_line[offset_data_line], p_data_line[offset_data_line + 1]);
532 str_len = 1;
533 }
534
535 // Current display line is (almost) empty
536 if (offset_data_line + str_len > len_data_line ||
537 (offset_data_line + str_len == len_data_line &&
538 p_data_line[del_line ? len_data_line - 1 : offset_data_line] == '\n'))
539 {
540 if (display_line + 1 >= p_editor_data->display_line_total) // No additional display line (data line)
541 {
542 return 0;
543 }
544
545 len_data_line = 0; // Next data line
546 last_display_line = p_editor_data->display_line_total - 1;
547 for (i = display_line + 1; i < p_editor_data->display_line_total; i++)
548 {
549 len_data_line += p_editor_data->display_line_lengths[i];
550
551 if (p_editor_data->display_line_lengths[i] > 0 &&
552 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n') // reach end of current data line
553 {
554 last_display_line = i;
555 break;
556 }
557 }
558
559 if (offset_data_line + len_data_line + 1 > MAX_EDITOR_DATA_LINE_LENGTH) // No enough buffer to merge current data line with next data line
560 {
561 return 0;
562 }
563
564 // Append next data line to current one
565 memcpy(p_data_line + offset_data_line, p_editor_data->p_display_lines[display_line + 1], (size_t)len_data_line);
566 p_data_line[offset_data_line + len_data_line] = '\0';
567
568 // Recycle next data line
569 memory_pool_free(p_mp_data_line, p_editor_data->p_display_lines[display_line + 1]);
570 }
571 else
572 {
573 memmove(p_data_line + offset_data_line, p_data_line + offset_data_line + str_len, (size_t)(len_data_line - offset_data_line - str_len));
574 p_data_line[len_data_line - str_len] = '\0';
575 len_data_line -= str_len;
576 }
577
578 // Set p_data_line to head of current display line
579 p_data_line = p_editor_data->p_display_lines[display_line];
580 split_line_total = last_display_line - display_line + 2;
581
582 // Split current data line since beginning of current display line
583 split_line_total = split_data_lines(p_data_line, SCREEN_COLS, line_offsets, split_line_total, 0, line_widths);
584
585 for (i = 0; i < split_line_total; i++)
586 {
587 p_editor_data->display_line_lengths[display_line + i] = line_offsets[i + 1] - line_offsets[i];
588 p_editor_data->display_line_widths[display_line + i] = line_widths[i];
589 p_editor_data->p_display_lines[display_line + i] =
590 (i == 0
591 ? p_data_line
592 : (p_editor_data->p_display_lines[display_line + i - 1] + p_editor_data->display_line_lengths[display_line + i - 1]));
593
594 if (p_editor_data->display_line_lengths[display_line + i] > 0 &&
595 p_editor_data->p_display_lines[display_line + i][p_editor_data->display_line_lengths[display_line + i] - 1] == '\n')
596 {
597 break;
598 }
599 }
600
601 *p_last_updated_line = display_line + MIN(i, split_line_total - 1);
602
603 if (*p_last_updated_line < last_display_line)
604 {
605 // Remove redundant display line after last_display_line
606 // for (j = last_display_line + 1; j < p_editor_data->display_line_total; j++)
607 // {
608 // p_editor_data->p_display_lines[j - (last_display_line - *p_last_updated_line)] = p_editor_data->p_display_lines[j];
609 // p_editor_data->display_line_lengths[j - (last_display_line - *p_last_updated_line)] = p_editor_data->display_line_lengths[j];
610 // p_editor_data->display_line_widths[j - (last_display_line - *p_last_updated_line)] = p_editor_data->display_line_widths[j];
611 // }
612 memmove(p_editor_data->p_display_lines + *p_last_updated_line + 1,
613 p_editor_data->p_display_lines + last_display_line + 1,
614 (size_t)(p_editor_data->display_line_total - last_display_line - 1) *
615 sizeof(p_editor_data->p_display_lines[last_display_line + 1]));
616 memmove(p_editor_data->display_line_lengths + *p_last_updated_line + 1,
617 p_editor_data->display_line_lengths + last_display_line + 1,
618 (size_t)(p_editor_data->display_line_total - last_display_line - 1) *
619 sizeof(p_editor_data->display_line_lengths[last_display_line + 1]));
620 memmove(p_editor_data->display_line_widths + *p_last_updated_line + 1,
621 p_editor_data->display_line_widths + last_display_line + 1,
622 (size_t)(p_editor_data->display_line_total - last_display_line - 1) *
623 sizeof(p_editor_data->display_line_widths[last_display_line + 1]));
624
625 j = p_editor_data->display_line_total;
626 (p_editor_data->display_line_total) -= (last_display_line - *p_last_updated_line);
627 *p_last_updated_line = MAX(j - 1, *p_last_updated_line);
628 }
629
630 // Return real offset
631 *p_offset = offset;
632
633 return str_len;
634 }
635
636 static int editor_display_key_handler(int *p_key, EDITOR_CTX *p_ctx)
637 {
638 switch (*p_key)
639 {
640 case 0: // Set msg
641 snprintf(p_ctx->msg, sizeof(p_ctx->msg),
642 "| 退出[\033[32mCtrl-W\033[33m] | [\033[32m%s\033[33m]",
643 (UTF8_fixed_width ? "定宽" : "变宽"));
644 break;
645 case KEY_CSI:
646 *p_key = KEY_ESC;
647 break;
648 }
649
650 return 0;
651 }
652
653 int editor_display(EDITOR_DATA *p_editor_data)
654 {
655 static int show_help = 1;
656 char buffer[MAX_EDITOR_DATA_LINE_LENGTH];
657 EDITOR_CTX ctx;
658 int ch = 0;
659 char input_str[5];
660 int str_len = 0;
661 wchar_t wcs[2];
662 int wc_len;
663 char c;
664 int input_ok;
665 const int screen_begin_row = 1;
666 const int screen_row_total = SCREEN_ROWS - screen_begin_row;
667 int output_current_row = screen_begin_row;
668 int output_end_row = SCREEN_ROWS - 1;
669 long int line_current = 0;
670 long int len;
671 int loop;
672 int eol, display_len;
673 long row_pos = 1, col_pos = 1;
674 long display_line_in, offset_in;
675 long display_line_out, offset_out;
676 int scroll_rows;
677 long last_updated_line = 0;
678 int key_insert = 1;
679 int i, j;
680 char *p_str;
681 int del_line;
682 int tab_width = 0;
683
684 clrline(output_current_row, SCREEN_ROWS);
685
686 // update msg_ext with extended key handler
687 if (editor_display_key_handler(&ch, &ctx) != 0)
688 {
689 return ch;
690 }
691
692 for (loop = 1; !SYS_server_exit && loop;)
693 {
694 if (line_current >= p_editor_data->display_line_total || output_current_row > output_end_row)
695 {
696 ctx.line_cursor = line_current - output_current_row + row_pos + 1;
697
698 snprintf(buffer, sizeof(buffer),
699 "\033[1;44;33m[\033[32m%ld\033[33m;\033[32m%ld\033[33m] "
700 "第\033[32m%ld\033[33m/\033[32m%ld\033[33m行 [\033[32m%s\033[33m] "
701 "%s",
702 row_pos, col_pos,
703 ctx.line_cursor, p_editor_data->display_line_total,
704 key_insert ? "插入" : "替换",
705 ctx.msg);
706
707 len = split_line(buffer, SCREEN_COLS, &eol, &display_len, 1);
708 for (; display_len < SCREEN_COLS; display_len++)
709 {
710 buffer[len++] = ' ';
711 }
712 buffer[len] = '\0';
713 strncat(buffer, "\033[m", sizeof(buffer) - 1 - strnlen(buffer, sizeof(buffer)));
714
715 moveto(SCREEN_ROWS, 0);
716 prints("%s", buffer);
717
718 moveto((int)row_pos, (int)col_pos);
719 iflush();
720
721 tab_width = 0;
722 str_len = 0;
723 ch = igetch_t(BBS_max_user_idle_time);
724 while (!SYS_server_exit)
725 {
726 if (ch != KEY_NULL && ch != KEY_TIMEOUT)
727 {
728 BBS_last_access_tm = time(NULL);
729 }
730
731 // extended key handler
732 if (editor_display_key_handler(&ch, &ctx) != 0)
733 {
734 goto cleanup;
735 }
736
737 if (ch == '\t')
738 {
739 ch = ' ';
740 tab_width = TAB_SIZE - ((int)(col_pos - 1) % TAB_SIZE) - 1;
741 }
742
743 if (ch < 256 && (ch & 0x80)) // head of multi-byte character
744 {
745 str_len = 0;
746 c = (char)(ch & 0xf0);
747 while (c & 0x80)
748 {
749 input_str[str_len] = (char)(ch - 256);
750 str_len++;
751 c = (c & 0x7f) << 1;
752
753 if ((c & 0x80) == 0) // Input completed
754 {
755 break;
756 }
757
758 // Expect additional bytes of input
759 ch = igetch(100); // 0.1 second
760 if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Ignore received bytes if no futher input
761 {
762 log_debug("Ignore %d bytes of incomplete UTF8 character", str_len);
763 str_len = 0;
764 break;
765 }
766 }
767 input_str[str_len] = '\0';
768 }
769
770 if ((ch >= 32 && ch < 127) || str_len >= 2 || // Printable character or multi-byte character
771 ch == CR || ch == KEY_ESC) // Special character
772 {
773 // Refresh current action while user input
774 if (user_online_update(NULL) < 0)
775 {
776 log_error("user_online_update(NULL) error");
777 }
778
779 if (str_len == 0) // ch >= 32 && ch < 127
780 {
781 input_str[0] = (char)ch;
782 str_len = 1;
783 }
784
785 display_line_in = line_current - output_current_row + row_pos;
786 offset_in = split_line(p_editor_data->p_display_lines[display_line_in], (int)col_pos - 1, &eol, &display_len, 0);
787 display_line_out = display_line_in;
788 offset_out = offset_in;
789
790 last_updated_line = display_line_in;
791
792 if (!key_insert) // overwrite
793 {
794 if (editor_data_delete(p_editor_data, &display_line_out, &offset_out,
795 &last_updated_line, 0) < 0)
796 {
797 log_error("editor_data_delete() error");
798 }
799 }
800
801 if (editor_data_insert(p_editor_data, &display_line_out, &offset_out,
802 input_str, str_len, &last_updated_line) < 0)
803 {
804 log_error("editor_data_insert(str_len=%d) error", str_len);
805 }
806 else
807 {
808 output_end_row = MIN(SCREEN_ROWS - 1, output_current_row + (int)(last_updated_line - line_current));
809 line_current -= (output_current_row - row_pos);
810 output_current_row = (int)row_pos;
811
812 scroll_rows = MAX(0, (int)(display_line_out - display_line_in) - (output_end_row - output_current_row));
813
814 if (scroll_rows > 0)
815 {
816 moveto(SCREEN_ROWS, 0);
817 clrtoeol();
818 for (i = 0; i < scroll_rows; i++)
819 {
820 // prints("\033[S"); // Scroll up 1 line
821 prints("\n"); // Legacy Cterm only works with this line
822 }
823
824 output_current_row -= scroll_rows;
825 if (output_current_row < screen_begin_row)
826 {
827 line_current += (screen_begin_row - output_current_row);
828 output_current_row = screen_begin_row;
829 }
830 row_pos = output_end_row;
831 }
832 else // if (scroll_lines == 0)
833 {
834 row_pos += (display_line_out - display_line_in);
835 }
836
837 if (offset_out != offset_in)
838 {
839 if (display_line_out != display_line_in)
840 {
841 col_pos = 1;
842 }
843 if (offset_out > 0)
844 {
845 if (mbstowcs(wcs, input_str, 1) == (size_t)-1)
846 {
847 log_error("mbstowcs() error");
848 }
849 col_pos += (str_len == 1 ? 1 : (UTF8_fixed_width ? 2 : wcwidth(wcs[0])));
850 }
851 }
852 }
853
854 if (display_line_out != display_line_in) // Output on line change
855 {
856 break;
857 }
858
859 if (ch == ' ' && tab_width > 0)
860 {
861 tab_width--;
862 }
863 else
864 {
865 ch = igetch(0);
866 if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Output if no futher input
867 {
868 break;
869 }
870 }
871
872 str_len = 0;
873 continue;
874 }
875 else if (ch == KEY_DEL || ch == BACKSPACE || ch == Ctrl('K') || ch == Ctrl('Y')) // Del
876 {
877 // Refresh current action while user input
878 if (user_online_update(NULL) < 0)
879 {
880 log_error("user_online_update(NULL) error");
881 }
882
883 del_line = 0;
884
885 if (ch == BACKSPACE)
886 {
887 if (line_current - output_current_row + row_pos <= 0 && col_pos <= 1) // Forbidden
888 {
889 break; // force output prior operation result if any
890 }
891
892 offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos],
893 (int)col_pos - 1, &eol, &display_len, 0);
894 col_pos = display_len;
895 if (col_pos < 1 && line_current - output_current_row + row_pos >= 0)
896 {
897 row_pos--;
898 col_pos = MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]);
899 }
900 }
901 else if (ch == Ctrl('K'))
902 {
903 del_line = 1;
904 }
905 else if (ch == Ctrl('Y'))
906 {
907 col_pos = 1;
908 del_line = 1;
909 }
910
911 display_line_in = line_current - output_current_row + row_pos;
912 offset_in = split_line(p_editor_data->p_display_lines[display_line_in], (int)col_pos - 1, &eol, &display_len, 0);
913 display_line_out = display_line_in;
914 offset_out = offset_in;
915
916 if ((str_len = editor_data_delete(p_editor_data, &display_line_out, &offset_out,
917 &last_updated_line, del_line)) < 0)
918 {
919 log_error("editor_data_delete() error: %d", str_len);
920 }
921 else
922 {
923 col_pos = display_len + 1; // Set col_pos to accurate pos
924
925 output_end_row = MIN(SCREEN_ROWS - 1, output_current_row + (int)(last_updated_line - line_current));
926 line_current -= (output_current_row - row_pos);
927 output_current_row = (int)row_pos;
928
929 if (output_current_row < screen_begin_row) // row_pos <= 0
930 {
931 output_current_row = screen_begin_row;
932 row_pos = screen_begin_row;
933 output_end_row = SCREEN_ROWS - 1;
934 }
935
936 // Exceed end
937 if (line_current + (screen_row_total - output_current_row) >= p_editor_data->display_line_total &&
938 p_editor_data->display_line_total > screen_row_total)
939 {
940 scroll_rows = (int)((line_current - (output_current_row - screen_begin_row)) -
941 (p_editor_data->display_line_total - screen_row_total));
942
943 line_current = p_editor_data->display_line_total - screen_row_total;
944 row_pos += scroll_rows;
945 output_current_row = screen_begin_row;
946 output_end_row = SCREEN_ROWS - 1;
947 }
948
949 clrline(output_current_row, output_end_row);
950 }
951
952 if (display_line_out != display_line_in) // Output on line change
953 {
954 break;
955 }
956
957 ch = igetch(0);
958 if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Output if no futher input
959 {
960 break;
961 }
962
963 str_len = 0;
964 continue;
965 }
966
967 input_ok = 1;
968 switch (ch)
969 {
970 case KEY_NULL:
971 log_debug("KEY_NULL");
972 goto cleanup;
973 case KEY_TIMEOUT:
974 log_debug("User input timeout");
975 goto cleanup;
976 case Ctrl('W'):
977 case Ctrl('X'):
978 loop = 0;
979 break;
980 case Ctrl('S'): // Start of line
981 case KEY_CTRL_LEFT:
982 col_pos = 1;
983 break;
984 case Ctrl('E'): // End of line
985 case KEY_CTRL_RIGHT:
986 if (line_current - output_current_row + row_pos == p_editor_data->display_line_total - 1) // row_pos at end line
987 {
988 // last display line does NOT have \n in the end
989 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
990 break;
991 }
992 col_pos = MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]);
993 break;
994 case Ctrl('T'): // Top of screen
995 case KEY_CTRL_UP:
996 row_pos = screen_begin_row;
997 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
998 break;
999 case Ctrl('B'): // Bottom of screen
1000 case KEY_CTRL_DOWN:
1001 if (p_editor_data->display_line_total < screen_row_total)
1002 {
1003 row_pos = p_editor_data->display_line_total;
1004 }
1005 else
1006 {
1007 row_pos = SCREEN_ROWS - 1;
1008 }
1009 if (line_current + (screen_row_total - (output_current_row - screen_begin_row)) >= p_editor_data->display_line_total) // Reach end
1010 {
1011 // last display line does NOT have \n in the end
1012 col_pos = MIN(col_pos, p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1);
1013 }
1014 else
1015 {
1016 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1017 }
1018 break;
1019 case KEY_INS:
1020 key_insert = !key_insert;
1021 break;
1022 case KEY_HOME:
1023 row_pos = 1;
1024 col_pos = 1;
1025 if (line_current - output_current_row < 0) // Reach begin
1026 {
1027 break;
1028 }
1029 line_current = 0;
1030 output_current_row = screen_begin_row;
1031 output_end_row = SCREEN_ROWS - 1;
1032 clrline(output_current_row, SCREEN_ROWS);
1033 break;
1034 case KEY_END:
1035 if (p_editor_data->display_line_total < screen_row_total)
1036 {
1037 row_pos = p_editor_data->display_line_total;
1038 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
1039 break;
1040 }
1041 line_current = p_editor_data->display_line_total - screen_row_total;
1042 output_current_row = screen_begin_row;
1043 output_end_row = SCREEN_ROWS - 1;
1044 row_pos = SCREEN_ROWS - 1;
1045 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
1046 clrline(output_current_row, SCREEN_ROWS);
1047 break;
1048 case KEY_LEFT:
1049 offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos],
1050 (int)col_pos - 1, &eol, &display_len, 0);
1051 col_pos = display_len;
1052 if (offset_in > 0)
1053 {
1054 str_len = 1;
1055 offset_in--;
1056 if (p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] < 0 ||
1057 p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] > 127) // UTF8
1058 {
1059 while (offset_in > 0 &&
1060 (p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0xc0) != 0xc0)
1061 {
1062 str_len++;
1063 offset_in--;
1064 }
1065
1066 if (str_len > 4)
1067 {
1068 log_error("Invalid UTF-8 data detected: str_len > 4");
1069 }
1070
1071 if (mbstowcs(wcs, p_editor_data->p_display_lines[line_current - output_current_row + row_pos] + offset_in, 1) ==
1072 (size_t)-1)
1073 {
1074 log_error("mbstowcs() error");
1075 }
1076 wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0]));
1077
1078 if (wc_len == 2)
1079 {
1080 col_pos--;
1081 }
1082 }
1083 }
1084 if (col_pos >= 1)
1085 {
1086 break;
1087 }
1088 col_pos = SCREEN_COLS; // continue to KEY_UP
1089 case KEY_UP:
1090 if (row_pos > screen_begin_row)
1091 {
1092 row_pos--;
1093 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1094 break;
1095 }
1096 if (line_current - output_current_row < 0) // Reach begin
1097 {
1098 col_pos = 1;
1099 break;
1100 }
1101 line_current -= output_current_row;
1102 output_current_row = screen_begin_row;
1103 // screen_end_line = begin_line;
1104 // prints("\033[T"); // Scroll down 1 line
1105 output_end_row = SCREEN_ROWS - 1; // Legacy Fterm only works with this line
1106 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1107 break;
1108 case KEY_RIGHT:
1109 offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos],
1110 (int)col_pos - 1, &eol, &display_len, 0);
1111 col_pos = display_len + 2;
1112 if (offset_in < p_editor_data->display_line_lengths[line_current - output_current_row + row_pos])
1113 {
1114 str_len = 0;
1115 if ((p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0x80) ==
1116 0x80) // head of multi-byte character
1117 {
1118 c = (char)(p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0xf0);
1119 while (c & 0x80)
1120 {
1121 str_len++;
1122 c = (c & 0x7f) << 1;
1123 }
1124
1125 if (str_len > 4)
1126 {
1127 log_error("Invalid UTF-8 data detected: str_len > 4");
1128 }
1129
1130 if (mbstowcs(wcs, p_editor_data->p_display_lines[line_current - output_current_row + row_pos] + offset_in, 1) ==
1131 (size_t)-1)
1132 {
1133 log_error("mbstowcs() error");
1134 }
1135 wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0]));
1136
1137 if (wc_len == 2)
1138 {
1139 col_pos++;
1140 }
1141 }
1142 else
1143 {
1144 str_len = 1;
1145 }
1146 offset_in += str_len;
1147 }
1148 if (col_pos <= p_editor_data->display_line_widths[line_current - output_current_row + row_pos])
1149 {
1150 break;
1151 }
1152 col_pos = 1; // continue to KEY_DOWN
1153 case KEY_DOWN:
1154 if (row_pos < MIN(screen_row_total, p_editor_data->display_line_total))
1155 {
1156 row_pos++;
1157 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1158 break;
1159 }
1160 if (line_current - output_current_row + row_pos == p_editor_data->display_line_total - 1) // row_pos at end line
1161 {
1162 // last display line does NOT have \n in the end
1163 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
1164 break;
1165 }
1166 line_current += (screen_row_total - (output_current_row - screen_begin_row));
1167 output_current_row = screen_row_total;
1168 output_end_row = SCREEN_ROWS - 1;
1169 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1170 moveto(SCREEN_ROWS, 0);
1171 clrtoeol();
1172 // prints("\033[S"); // Scroll up 1 line
1173 prints("\n"); // Legacy Cterm only works with this line
1174 break;
1175 case KEY_PGUP:
1176 if (line_current - output_current_row < 0) // Reach begin
1177 {
1178 break;
1179 }
1180 line_current -= ((screen_row_total - 1) + (output_current_row - screen_begin_row));
1181 if (line_current < 0)
1182 {
1183 line_current = 0;
1184 }
1185 output_current_row = screen_begin_row;
1186 output_end_row = SCREEN_ROWS - 1;
1187 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1188 clrline(output_current_row, SCREEN_ROWS);
1189 break;
1190 case KEY_PGDN:
1191 if (line_current + screen_row_total - (output_current_row - screen_begin_row) >= p_editor_data->display_line_total) // Reach end
1192 {
1193 break;
1194 }
1195 line_current += (screen_row_total - 1) - (output_current_row - screen_begin_row);
1196 if (line_current + screen_row_total > p_editor_data->display_line_total) // No enough lines to display
1197 {
1198 line_current = p_editor_data->display_line_total - screen_row_total;
1199 }
1200 output_current_row = screen_begin_row;
1201 output_end_row = SCREEN_ROWS - 1;
1202 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1203 clrline(output_current_row, SCREEN_ROWS);
1204 break;
1205 case Ctrl('Q'):
1206 case KEY_F1:
1207 if (!show_help) // Not re-entrant
1208 {
1209 break;
1210 }
1211 // Display help information
1212 show_help = 0;
1213 display_file(DATA_EDITOR_HELP, 1);
1214 show_help = 1;
1215 case KEY_F5:
1216 // Refresh after display help information
1217 line_current -= (output_current_row - screen_begin_row);
1218 output_current_row = screen_begin_row;
1219 output_end_row = SCREEN_ROWS - 1;
1220 clrline(output_current_row, SCREEN_ROWS);
1221 break;
1222 case 0: // Refresh bottom line
1223 break;
1224 default:
1225 input_ok = 0;
1226 break;
1227 }
1228
1229 // Refresh current action while user input
1230 if (user_online_update(NULL) < 0)
1231 {
1232 log_error("user_online_update(NULL) error");
1233 }
1234
1235 if (input_ok)
1236 {
1237 break;
1238 }
1239
1240 ch = igetch_t(BBS_max_user_idle_time);
1241 }
1242
1243 continue;
1244 }
1245
1246 len = p_editor_data->display_line_lengths[line_current];
1247 if (len >= sizeof(buffer))
1248 {
1249 log_error("Buffer overflow: len=%ld line=%ld ", len, line_current);
1250 len = sizeof(buffer) - 1;
1251 }
1252 else if (len < 0)
1253 {
1254 log_error("Incorrect line offsets: len=%ld line=%ld ", len, line_current);
1255 len = 0;
1256 }
1257
1258 // memcpy(buffer, p_editor_data->p_display_lines[line_current], (size_t)len);
1259 // Replace '\033' with '*'
1260 p_str = p_editor_data->p_display_lines[line_current];
1261 for (i = 0, j = 0; i < len; i++)
1262 {
1263 if (p_str[i] == '\033')
1264 {
1265 memcpy(buffer + j, EDITOR_ESC_DISPLAY_STR, sizeof(EDITOR_ESC_DISPLAY_STR) - 1);
1266 j += (int)(sizeof(EDITOR_ESC_DISPLAY_STR) - 1);
1267 }
1268 else
1269 {
1270 buffer[j] = p_str[i];
1271 j++;
1272 }
1273 }
1274 buffer[j] = '\0';
1275
1276 moveto(output_current_row, 0);
1277 clrtoeol();
1278 prints("%s", buffer);
1279 line_current++;
1280 output_current_row++;
1281 }
1282
1283 cleanup:
1284 return ch;
1285 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1