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

Contents of /lbbs/src/editor.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.57 - (show annotations)
Sun Nov 9 11:18:07 2025 UTC (4 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.56: +10 -0 lines
Content type: text/x-csrc
Replace \t into single space while loading data into editor

1 /* SPDX-License-Identifier: GPL-3.0-or-later */
2 /*
3 * editor
4 * - user interactive full-screen text editor
5 *
6 * Copyright (C) 2004-2025 Leaflet <leaflet@leafok.com>
7 */
8
9 #include "bbs.h"
10 #include "common.h"
11 #include "editor.h"
12 #include "io.h"
13 #include "log.h"
14 #include "login.h"
15 #include "memory_pool.h"
16 #include "str_process.h"
17 #include <stdlib.h>
18 #include <string.h>
19 #include <wchar.h>
20 #include <sys/param.h>
21
22 enum _editor_constant_t
23 {
24 EDITOR_MEM_POOL_LINE_PER_CHUNK = 1000,
25 EDITOR_MEM_POOL_CHUNK_LIMIT = (MAX_EDITOR_DATA_LINES / EDITOR_MEM_POOL_LINE_PER_CHUNK + 1),
26 };
27
28 static const char EDITOR_ESC_DISPLAY_STR[] = "\033[32m*\033[m";
29
30 static MEMORY_POOL *p_mp_data_line;
31 static MEMORY_POOL *p_mp_editor_data;
32
33 int editor_memory_pool_init(void)
34 {
35 if (p_mp_data_line != NULL || p_mp_editor_data != NULL)
36 {
37 log_error("Editor mem pool already initialized\n");
38 return -1;
39 }
40
41 p_mp_data_line = memory_pool_init(MAX_EDITOR_DATA_LINE_LENGTH, EDITOR_MEM_POOL_LINE_PER_CHUNK, EDITOR_MEM_POOL_CHUNK_LIMIT);
42 if (p_mp_data_line == NULL)
43 {
44 log_error("Memory pool init error\n");
45 return -2;
46 }
47
48 p_mp_editor_data = memory_pool_init(sizeof(EDITOR_DATA), 1, 1);
49 if (p_mp_editor_data == NULL)
50 {
51 log_error("Memory pool init error\n");
52 return -3;
53 }
54
55 return 0;
56 }
57
58 void editor_memory_pool_cleanup(void)
59 {
60 if (p_mp_data_line != NULL)
61 {
62 memory_pool_cleanup(p_mp_data_line);
63 p_mp_data_line = NULL;
64 }
65
66 if (p_mp_editor_data != NULL)
67 {
68 memory_pool_cleanup(p_mp_editor_data);
69 p_mp_editor_data = NULL;
70 }
71 }
72
73 EDITOR_DATA *editor_data_load(const char *p_data)
74 {
75 EDITOR_DATA *p_editor_data;
76 char *p_data_line = NULL;
77 long line_offsets[MAX_EDITOR_DATA_LINES + 1];
78 long current_data_line_length = 0;
79 long i;
80 int j;
81
82 if (p_data == NULL)
83 {
84 log_error("NULL pointer error\n");
85 return NULL;
86 }
87
88 p_editor_data = memory_pool_alloc(p_mp_editor_data);
89 if (p_editor_data == NULL)
90 {
91 log_error("memory_pool_alloc() error\n");
92 return NULL;
93 }
94
95 p_editor_data->display_line_total = split_data_lines(p_data, SCREEN_COLS, line_offsets, MAX_EDITOR_DATA_LINES + 1,
96 0, p_editor_data->display_line_widths);
97
98 for (i = 0; i < p_editor_data->display_line_total; i++)
99 {
100 p_editor_data->display_line_lengths[i] = line_offsets[i + 1] - line_offsets[i];
101
102 if (i == 0 ||
103 current_data_line_length + p_editor_data->display_line_lengths[i] + 1 > MAX_EDITOR_DATA_LINE_LENGTH ||
104 (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'))
105 {
106 // Allocate new data line
107 p_data_line = memory_pool_alloc(p_mp_data_line);
108 if (p_data_line == NULL)
109 {
110 log_error("memory_pool_alloc() error: i = %d\n", i);
111 // Cleanup
112 editor_data_cleanup(p_editor_data);
113 return NULL;
114 }
115
116 p_editor_data->p_display_lines[i] = p_data_line;
117 current_data_line_length = 0;
118 }
119 else
120 {
121 p_editor_data->p_display_lines[i] = p_editor_data->p_display_lines[i - 1] + p_editor_data->display_line_lengths[i - 1];
122 }
123
124 memcpy(p_editor_data->p_display_lines[i], p_data + line_offsets[i], (size_t)p_editor_data->display_line_lengths[i]);
125 current_data_line_length += p_editor_data->display_line_lengths[i];
126
127 // Convert \t to single space
128 for (j = 0; j < p_editor_data->display_line_lengths[i]; j++)
129 {
130 if (p_editor_data->p_display_lines[i][j] == '\t')
131 {
132 p_editor_data->p_display_lines[i][j] = ' ';
133 }
134 }
135
136 // Trim \n from last line
137 if (i + 1 == p_editor_data->display_line_total &&
138 p_editor_data->display_line_lengths[i] > 0 &&
139 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n')
140 {
141 p_editor_data->display_line_lengths[i]--;
142 p_editor_data->display_line_widths[i]--;
143 current_data_line_length--;
144 }
145 p_data_line[current_data_line_length] = '\0';
146 }
147
148 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);
149
150 return p_editor_data;
151 }
152
153 long editor_data_save(const EDITOR_DATA *p_editor_data, char *p_data, size_t buf_len)
154 {
155 long current_pos = 0;
156 long i;
157
158 if (p_editor_data == NULL || p_data == NULL)
159 {
160 log_error("NULL pointer error\n");
161 return -1;
162 }
163
164 for (i = 0; i < p_editor_data->display_line_total; i++)
165 {
166 if (current_pos + p_editor_data->display_line_lengths[i] + 1 > buf_len)
167 {
168 log_error("Data buffer not longer enough %d > %d\n", current_pos + p_editor_data->display_line_lengths[i] + 1, buf_len);
169 p_data[current_pos] = '\0';
170 return -2;
171 }
172
173 memcpy(p_data + current_pos, p_editor_data->p_display_lines[i], (size_t)p_editor_data->display_line_lengths[i]);
174 current_pos += p_editor_data->display_line_lengths[i];
175 }
176
177 p_data[current_pos] = '\0';
178
179 return current_pos;
180 }
181
182 void editor_data_cleanup(EDITOR_DATA *p_editor_data)
183 {
184 char *p_data_line = NULL;
185 long i;
186
187 if (p_editor_data == NULL)
188 {
189 return;
190 }
191
192 for (i = 0; i < p_editor_data->display_line_total; i++)
193 {
194 if (p_data_line == NULL)
195 {
196 p_data_line = p_editor_data->p_display_lines[i];
197 }
198
199 if (p_editor_data->display_line_lengths[i] > 0 &&
200 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n')
201 {
202 memory_pool_free(p_mp_data_line, p_data_line);
203 p_data_line = NULL;
204 }
205 }
206
207 if (p_data_line != NULL)
208 {
209 memory_pool_free(p_mp_data_line, p_data_line);
210 }
211
212 memory_pool_free(p_mp_editor_data, p_editor_data);
213 }
214
215 int editor_data_insert(EDITOR_DATA *p_editor_data, long *p_display_line, long *p_offset,
216 const char *str, int str_len, long *p_last_updated_line)
217 {
218 long display_line = *p_display_line;
219 long offset = *p_offset;
220 char *p_data_line = NULL;
221 long len_data_line;
222 long offset_data_line;
223 long last_display_line; // of data line
224 long line_offsets[MAX_EDITOR_DATA_LINE_LENGTH + 1];
225 int line_widths[MAX_EDITOR_DATA_LINE_LENGTH + 1];
226 long split_line_total;
227 long i;
228 int len;
229 int eol;
230 int display_len;
231
232 if (p_editor_data == NULL || p_last_updated_line == NULL)
233 {
234 log_error("NULL pointer error\n");
235 return -1;
236 }
237
238 // Get length of current data line
239 len_data_line = 0;
240 p_data_line = p_editor_data->p_display_lines[display_line];
241 for (i = display_line - 1; i >= 0; i--)
242 {
243 if (p_editor_data->display_line_lengths[i] > 0 &&
244 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n') // reach end of prior data line
245 {
246 break;
247 }
248
249 len_data_line += p_editor_data->display_line_lengths[i];
250 p_data_line = p_editor_data->p_display_lines[i];
251 }
252 offset_data_line = len_data_line + offset;
253 last_display_line = p_editor_data->display_line_total - 1;
254 for (i = display_line; i < p_editor_data->display_line_total; i++)
255 {
256 len_data_line += p_editor_data->display_line_lengths[i];
257
258 if (p_editor_data->display_line_lengths[i] > 0 &&
259 p_editor_data->p_display_lines[i][p_editor_data->display_line_lengths[i] - 1] == '\n') // reach end of current data line
260 {
261 last_display_line = i;
262 break;
263 }
264 }
265
266 // Split current data line if over-length
267 if (len_data_line + str_len + 2 > MAX_EDITOR_DATA_LINE_LENGTH || str[0] == CR)
268 {
269 if (p_editor_data->display_line_total >= MAX_EDITOR_DATA_LINES)
270 {
271 #ifdef _DEBUG
272 log_error("Split line error, display_line_total(%ld) reach limit(%d)\n",
273 p_editor_data->display_line_total, MAX_EDITOR_DATA_LINES);
274 #endif
275
276 return -2;
277 }
278
279 // Allocate new data line
280 p_data_line = memory_pool_alloc(p_mp_data_line);
281 if (p_data_line == NULL)
282 {
283 log_error("memory_pool_alloc() error\n");
284 return -2;
285 }
286
287 if (offset_data_line + str_len + 2 >= MAX_EDITOR_DATA_LINE_LENGTH || str[0] == CR)
288 {
289 if (str[0] == CR)
290 {
291 str_len = 0;
292 }
293
294 // Copy str to new data line
295 memcpy(p_data_line, str, (size_t)str_len);
296
297 // Copy rest part of current data line to new data line
298 memcpy(p_data_line + str_len,
299 p_editor_data->p_display_lines[display_line] + offset,
300 (size_t)(len_data_line - offset_data_line));
301
302 p_data_line[str_len + len_data_line - offset_data_line] = '\0';
303
304 // Add line ending to current display line (data line)
305 p_editor_data->p_display_lines[display_line][offset] = '\n';
306 p_editor_data->p_display_lines[display_line][offset + 1] = '\0';
307 p_editor_data->display_line_lengths[display_line] = offset + 1;
308
309 *p_display_line = display_line + 1;
310 *p_offset = str_len;
311 }
312 else
313 {
314 // Copy rest part of current data line to new data line
315 memcpy(p_data_line,
316 p_editor_data->p_display_lines[display_line] + offset,
317 (size_t)(len_data_line - offset_data_line));
318
319 p_data_line[len_data_line - offset_data_line] = '\0';
320
321 // Append str to current display line
322 memcpy(p_editor_data->p_display_lines[display_line] + offset, str, (size_t)str_len);
323
324 // Add line ending to current display line (data line)
325 p_editor_data->p_display_lines[display_line][offset + str_len] = '\n';
326 p_editor_data->p_display_lines[display_line][offset + str_len + 1] = '\0';
327 p_editor_data->display_line_lengths[display_line] = offset + str_len + 1;
328
329 *p_display_line = display_line;
330 *p_offset = offset + str_len;
331 }
332
333 // Update display width of current display line
334 len = split_line(p_editor_data->p_display_lines[display_line], SCREEN_COLS, &eol, &display_len, 0);
335 p_editor_data->display_line_widths[display_line] = display_len;
336
337 split_line_total = last_display_line - display_line + 3;
338
339 // Set start display_line for spliting new data line
340 display_line++;
341
342 *p_last_updated_line = p_editor_data->display_line_total;
343 }
344 else // insert str into current data line at offset_data_line
345 {
346 memmove(p_data_line + offset_data_line + str_len, p_data_line + offset_data_line, (size_t)(len_data_line - offset_data_line));
347 memcpy(p_data_line + offset_data_line, str, (size_t)str_len);
348 p_data_line[len_data_line + str_len] = '\0';
349
350 // Set p_data_line to head of current display line
351 p_data_line = p_editor_data->p_display_lines[display_line];
352 split_line_total = last_display_line - display_line + 3;
353
354 *p_display_line = display_line;
355 *p_offset = offset + str_len;
356 }
357
358 // Split current data line since beginning of current display line
359 split_line_total = split_data_lines(p_data_line, SCREEN_COLS, line_offsets, split_line_total, 0, line_widths);
360
361 for (i = 0; i < split_line_total; i++)
362 {
363 if (display_line + i > last_display_line)
364 {
365 // Insert blank display line after last_display_line
366 if (p_editor_data->display_line_total >= MAX_EDITOR_DATA_LINES)
367 {
368 #ifdef _DEBUG
369 log_error("display_line_total over limit %d >= %d\n", p_editor_data->display_line_total, MAX_EDITOR_DATA_LINES);
370 #endif
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\n");
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\n",
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
683 clrline(output_current_row, SCREEN_ROWS);
684
685 // update msg_ext with extended key handler
686 if (editor_display_key_handler(&ch, &ctx) != 0)
687 {
688 return ch;
689 }
690
691 for (loop = 1; !SYS_server_exit && loop;)
692 {
693 if (line_current >= p_editor_data->display_line_total || output_current_row > output_end_row)
694 {
695 ctx.line_cursor = line_current - output_current_row + row_pos + 1;
696
697 snprintf(buffer, sizeof(buffer),
698 "\033[1;44;33m[\033[32m%ld\033[33m;\033[32m%ld\033[33m] "
699 "第\033[32m%ld\033[33m/\033[32m%ld\033[33m行 [\033[32m%s\033[33m] "
700 "%s",
701 row_pos, col_pos,
702 ctx.line_cursor, p_editor_data->display_line_total,
703 key_insert ? "插入" : "替换",
704 ctx.msg);
705
706 len = split_line(buffer, SCREEN_COLS, &eol, &display_len, 1);
707 for (; display_len < SCREEN_COLS; display_len++)
708 {
709 buffer[len++] = ' ';
710 }
711 buffer[len] = '\0';
712 strncat(buffer, "\033[m", sizeof(buffer) - 1 - strnlen(buffer, sizeof(buffer)));
713
714 moveto(SCREEN_ROWS, 0);
715 prints("%s", buffer);
716
717 moveto((int)row_pos, (int)col_pos);
718 iflush();
719
720 str_len = 0;
721 ch = igetch_t(BBS_max_user_idle_time);
722 while (!SYS_server_exit)
723 {
724 if (ch != KEY_NULL && ch != KEY_TIMEOUT)
725 {
726 BBS_last_access_tm = time(NULL);
727 }
728
729 // extended key handler
730 if (editor_display_key_handler(&ch, &ctx) != 0)
731 {
732 goto cleanup;
733 }
734
735 if (ch < 256 && (ch & 0x80)) // head of multi-byte character
736 {
737 str_len = 0;
738 c = (char)(ch & 0xf0);
739 while (c & 0x80)
740 {
741 input_str[str_len] = (char)(ch - 256);
742 str_len++;
743 c = (c & 0x7f) << 1;
744
745 if ((c & 0x80) == 0) // Input completed
746 {
747 break;
748 }
749
750 // Expect additional bytes of input
751 ch = igetch(100); // 0.1 second
752 if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Ignore received bytes if no futher input
753 {
754 #ifdef _DEBUG
755 log_error("Ignore %d bytes of incomplete UTF8 character\n", str_len);
756 #endif
757 str_len = 0;
758 break;
759 }
760 }
761 input_str[str_len] = '\0';
762 }
763
764 if ((ch >= 32 && ch < 127) || str_len >= 2 || // Printable character or multi-byte character
765 ch == CR || ch == KEY_ESC) // Special character
766 {
767 // Refresh current action while user input
768 if (user_online_update(NULL) < 0)
769 {
770 log_error("user_online_update(NULL) error\n");
771 }
772
773 if (str_len == 0) // ch >= 32 && ch < 127
774 {
775 input_str[0] = (char)ch;
776 str_len = 1;
777 }
778
779 display_line_in = line_current - output_current_row + row_pos;
780 offset_in = split_line(p_editor_data->p_display_lines[display_line_in], (int)col_pos - 1, &eol, &display_len, 0);
781 display_line_out = display_line_in;
782 offset_out = offset_in;
783
784 last_updated_line = display_line_in;
785
786 if (!key_insert) // overwrite
787 {
788 if (editor_data_delete(p_editor_data, &display_line_out, &offset_out,
789 &last_updated_line, 0) < 0)
790 {
791 log_error("editor_data_delete() error\n");
792 }
793 }
794
795 if (editor_data_insert(p_editor_data, &display_line_out, &offset_out,
796 input_str, str_len, &last_updated_line) < 0)
797 {
798 log_error("editor_data_insert(str_len=%d) error\n", str_len);
799 }
800 else
801 {
802 output_end_row = MIN(SCREEN_ROWS - 1, output_current_row + (int)(last_updated_line - line_current));
803 line_current -= (output_current_row - row_pos);
804 output_current_row = (int)row_pos;
805
806 scroll_rows = MAX(0, (int)(display_line_out - display_line_in) - (output_end_row - output_current_row));
807
808 if (scroll_rows > 0)
809 {
810 moveto(SCREEN_ROWS, 0);
811 clrtoeol();
812 for (i = 0; i < scroll_rows; i++)
813 {
814 // prints("\033[S"); // Scroll up 1 line
815 prints("\n"); // Legacy Cterm only works with this line
816 }
817
818 output_current_row -= scroll_rows;
819 if (output_current_row < screen_begin_row)
820 {
821 line_current += (screen_begin_row - output_current_row);
822 output_current_row = screen_begin_row;
823 }
824 row_pos = output_end_row;
825 }
826 else // if (scroll_lines == 0)
827 {
828 row_pos += (display_line_out - display_line_in);
829 }
830
831 if (offset_out != offset_in)
832 {
833 if (display_line_out != display_line_in)
834 {
835 col_pos = 1;
836 }
837 if (offset_out > 0)
838 {
839 if (mbstowcs(wcs, input_str, 1) == (size_t)-1)
840 {
841 log_error("mbstowcs() error\n");
842 }
843 col_pos += (str_len == 1 ? 1 : (UTF8_fixed_width ? 2 : wcwidth(wcs[0])));
844 }
845 }
846 }
847
848 if (display_line_out != display_line_in) // Output on line change
849 {
850 break;
851 }
852
853 ch = igetch(0);
854 if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Output if no futher input
855 {
856 break;
857 }
858
859 str_len = 0;
860 continue;
861 }
862 else if (ch == KEY_DEL || ch == BACKSPACE || ch == Ctrl('K') || ch == Ctrl('Y')) // Del
863 {
864 // Refresh current action while user input
865 if (user_online_update(NULL) < 0)
866 {
867 log_error("user_online_update(NULL) error\n");
868 }
869
870 del_line = 0;
871
872 if (ch == BACKSPACE)
873 {
874 if (line_current - output_current_row + row_pos <= 0 && col_pos <= 1) // Forbidden
875 {
876 break; // force output prior operation result if any
877 }
878
879 offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos],
880 (int)col_pos - 1, &eol, &display_len, 0);
881 col_pos = display_len;
882 if (col_pos < 1 && line_current - output_current_row + row_pos >= 0)
883 {
884 row_pos--;
885 col_pos = MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]);
886 }
887 }
888 else if (ch == Ctrl('K'))
889 {
890 del_line = 1;
891 }
892 else if (ch == Ctrl('Y'))
893 {
894 col_pos = 1;
895 del_line = 1;
896 }
897
898 display_line_in = line_current - output_current_row + row_pos;
899 offset_in = split_line(p_editor_data->p_display_lines[display_line_in], (int)col_pos - 1, &eol, &display_len, 0);
900 display_line_out = display_line_in;
901 offset_out = offset_in;
902
903 if ((str_len = editor_data_delete(p_editor_data, &display_line_out, &offset_out,
904 &last_updated_line, del_line)) < 0)
905 {
906 log_error("editor_data_delete() error: %d\n", str_len);
907 }
908 else
909 {
910 col_pos = display_len + 1; // Set col_pos to accurate pos
911
912 output_end_row = MIN(SCREEN_ROWS - 1, output_current_row + (int)(last_updated_line - line_current));
913 line_current -= (output_current_row - row_pos);
914 output_current_row = (int)row_pos;
915
916 if (output_current_row < screen_begin_row) // row_pos <= 0
917 {
918 output_current_row = screen_begin_row;
919 row_pos = screen_begin_row;
920 output_end_row = SCREEN_ROWS - 1;
921 }
922
923 // Exceed end
924 if (line_current + (screen_row_total - output_current_row) >= p_editor_data->display_line_total &&
925 p_editor_data->display_line_total > screen_row_total)
926 {
927 scroll_rows = (int)((line_current - (output_current_row - screen_begin_row)) -
928 (p_editor_data->display_line_total - screen_row_total));
929
930 line_current = p_editor_data->display_line_total - screen_row_total;
931 row_pos += scroll_rows;
932 output_current_row = screen_begin_row;
933 output_end_row = SCREEN_ROWS - 1;
934 }
935
936 clrline(output_current_row, output_end_row);
937 }
938
939 if (display_line_out != display_line_in) // Output on line change
940 {
941 break;
942 }
943
944 ch = igetch(0);
945 if (ch == KEY_NULL || ch == KEY_TIMEOUT) // Output if no futher input
946 {
947 break;
948 }
949
950 str_len = 0;
951 continue;
952 }
953
954 input_ok = 1;
955 switch (ch)
956 {
957 case KEY_NULL:
958 log_error("KEY_NULL\n");
959 goto cleanup;
960 case KEY_TIMEOUT:
961 log_error("User input timeout\n");
962 goto cleanup;
963 case Ctrl('W'):
964 case Ctrl('X'):
965 loop = 0;
966 break;
967 case Ctrl('S'): // Start of line
968 case KEY_CTRL_LEFT:
969 col_pos = 1;
970 break;
971 case Ctrl('E'): // End of line
972 case KEY_CTRL_RIGHT:
973 if (line_current - output_current_row + row_pos == p_editor_data->display_line_total - 1) // row_pos at end line
974 {
975 // last display line does NOT have \n in the end
976 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
977 break;
978 }
979 col_pos = MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]);
980 break;
981 case Ctrl('T'): // Top of screen
982 case KEY_CTRL_UP:
983 row_pos = screen_begin_row;
984 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
985 break;
986 case Ctrl('B'): // Bottom of screen
987 case KEY_CTRL_DOWN:
988 if (p_editor_data->display_line_total < screen_row_total)
989 {
990 row_pos = p_editor_data->display_line_total;
991 }
992 else
993 {
994 row_pos = SCREEN_ROWS - 1;
995 }
996 if (line_current + (screen_row_total - (output_current_row - screen_begin_row)) >= p_editor_data->display_line_total) // Reach end
997 {
998 // last display line does NOT have \n in the end
999 col_pos = MIN(col_pos, p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1);
1000 }
1001 else
1002 {
1003 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1004 }
1005 break;
1006 case KEY_INS:
1007 key_insert = !key_insert;
1008 break;
1009 case KEY_HOME:
1010 row_pos = 1;
1011 col_pos = 1;
1012 if (line_current - output_current_row < 0) // Reach begin
1013 {
1014 break;
1015 }
1016 line_current = 0;
1017 output_current_row = screen_begin_row;
1018 output_end_row = SCREEN_ROWS - 1;
1019 clrline(output_current_row, SCREEN_ROWS);
1020 break;
1021 case KEY_END:
1022 if (p_editor_data->display_line_total < screen_row_total)
1023 {
1024 row_pos = p_editor_data->display_line_total;
1025 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
1026 break;
1027 }
1028 line_current = p_editor_data->display_line_total - screen_row_total;
1029 output_current_row = screen_begin_row;
1030 output_end_row = SCREEN_ROWS - 1;
1031 row_pos = SCREEN_ROWS - 1;
1032 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
1033 clrline(output_current_row, SCREEN_ROWS);
1034 break;
1035 case KEY_LEFT:
1036 offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos],
1037 (int)col_pos - 1, &eol, &display_len, 0);
1038 col_pos = display_len;
1039 if (offset_in > 0)
1040 {
1041 str_len = 1;
1042 offset_in--;
1043 if (p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] < 0 ||
1044 p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] > 127) // UTF8
1045 {
1046 while (offset_in > 0 &&
1047 (p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0xc0) != 0xc0)
1048 {
1049 str_len++;
1050 offset_in--;
1051 }
1052
1053 if (str_len > 4)
1054 {
1055 log_error("Invalid UTF-8 data detected: str_len > 4\n");
1056 }
1057
1058 if (mbstowcs(wcs, p_editor_data->p_display_lines[line_current - output_current_row + row_pos] + offset_in, 1) ==
1059 (size_t)-1)
1060 {
1061 log_error("mbstowcs() error\n");
1062 }
1063 wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0]));
1064
1065 if (wc_len == 2)
1066 {
1067 col_pos--;
1068 }
1069 }
1070 }
1071 if (col_pos >= 1)
1072 {
1073 break;
1074 }
1075 col_pos = SCREEN_COLS; // continue to KEY_UP
1076 case KEY_UP:
1077 if (row_pos > screen_begin_row)
1078 {
1079 row_pos--;
1080 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1081 break;
1082 }
1083 if (line_current - output_current_row < 0) // Reach begin
1084 {
1085 col_pos = 1;
1086 break;
1087 }
1088 line_current -= output_current_row;
1089 output_current_row = screen_begin_row;
1090 // screen_end_line = begin_line;
1091 // prints("\033[T"); // Scroll down 1 line
1092 output_end_row = SCREEN_ROWS - 1; // Legacy Fterm only works with this line
1093 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1094 break;
1095 case KEY_RIGHT:
1096 offset_in = split_line(p_editor_data->p_display_lines[line_current - output_current_row + row_pos],
1097 (int)col_pos - 1, &eol, &display_len, 0);
1098 col_pos = display_len + 2;
1099 if (offset_in < p_editor_data->display_line_lengths[line_current - output_current_row + row_pos])
1100 {
1101 str_len = 0;
1102 if ((p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0x80) ==
1103 0x80) // head of multi-byte character
1104 {
1105 c = (char)(p_editor_data->p_display_lines[line_current - output_current_row + row_pos][offset_in] & 0xf0);
1106 while (c & 0x80)
1107 {
1108 str_len++;
1109 c = (c & 0x7f) << 1;
1110 }
1111
1112 if (str_len > 4)
1113 {
1114 log_error("Invalid UTF-8 data detected: str_len > 4\n");
1115 }
1116
1117 if (mbstowcs(wcs, p_editor_data->p_display_lines[line_current - output_current_row + row_pos] + offset_in, 1) ==
1118 (size_t)-1)
1119 {
1120 log_error("mbstowcs() error\n");
1121 }
1122 wc_len = (UTF8_fixed_width ? 2 : wcwidth(wcs[0]));
1123
1124 if (wc_len == 2)
1125 {
1126 col_pos++;
1127 }
1128 }
1129 else
1130 {
1131 str_len = 1;
1132 }
1133 offset_in += str_len;
1134 }
1135 if (col_pos <= p_editor_data->display_line_widths[line_current - output_current_row + row_pos])
1136 {
1137 break;
1138 }
1139 col_pos = 1; // continue to KEY_DOWN
1140 case KEY_DOWN:
1141 if (row_pos < MIN(screen_row_total, p_editor_data->display_line_total))
1142 {
1143 row_pos++;
1144 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1145 break;
1146 }
1147 if (line_current - output_current_row + row_pos == p_editor_data->display_line_total - 1) // row_pos at end line
1148 {
1149 // last display line does NOT have \n in the end
1150 col_pos = p_editor_data->display_line_widths[line_current - output_current_row + row_pos] + 1;
1151 break;
1152 }
1153 line_current += (screen_row_total - (output_current_row - screen_begin_row));
1154 output_current_row = screen_row_total;
1155 output_end_row = SCREEN_ROWS - 1;
1156 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1157 moveto(SCREEN_ROWS, 0);
1158 clrtoeol();
1159 // prints("\033[S"); // Scroll up 1 line
1160 prints("\n"); // Legacy Cterm only works with this line
1161 break;
1162 case KEY_PGUP:
1163 if (line_current - output_current_row < 0) // Reach begin
1164 {
1165 break;
1166 }
1167 line_current -= ((screen_row_total - 1) + (output_current_row - screen_begin_row));
1168 if (line_current < 0)
1169 {
1170 line_current = 0;
1171 }
1172 output_current_row = screen_begin_row;
1173 output_end_row = SCREEN_ROWS - 1;
1174 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1175 clrline(output_current_row, SCREEN_ROWS);
1176 break;
1177 case KEY_PGDN:
1178 if (line_current + screen_row_total - (output_current_row - screen_begin_row) >= p_editor_data->display_line_total) // Reach end
1179 {
1180 break;
1181 }
1182 line_current += (screen_row_total - 1) - (output_current_row - screen_begin_row);
1183 if (line_current + screen_row_total > p_editor_data->display_line_total) // No enough lines to display
1184 {
1185 line_current = p_editor_data->display_line_total - screen_row_total;
1186 }
1187 output_current_row = screen_begin_row;
1188 output_end_row = SCREEN_ROWS - 1;
1189 col_pos = MIN(col_pos, MAX(1, p_editor_data->display_line_widths[line_current - output_current_row + row_pos]));
1190 clrline(output_current_row, SCREEN_ROWS);
1191 break;
1192 case Ctrl('Q'):
1193 case KEY_F1:
1194 if (!show_help) // Not re-entrant
1195 {
1196 break;
1197 }
1198 // Display help information
1199 show_help = 0;
1200 display_file(DATA_EDITOR_HELP, 1);
1201 show_help = 1;
1202 case KEY_F5:
1203 // Refresh after display help information
1204 line_current -= (output_current_row - screen_begin_row);
1205 output_current_row = screen_begin_row;
1206 output_end_row = SCREEN_ROWS - 1;
1207 clrline(output_current_row, SCREEN_ROWS);
1208 break;
1209 case 0: // Refresh bottom line
1210 break;
1211 default:
1212 input_ok = 0;
1213 break;
1214 }
1215
1216 // Refresh current action while user input
1217 if (user_online_update(NULL) < 0)
1218 {
1219 log_error("user_online_update(NULL) error\n");
1220 }
1221
1222 if (input_ok)
1223 {
1224 break;
1225 }
1226
1227 ch = igetch_t(BBS_max_user_idle_time);
1228 }
1229
1230 continue;
1231 }
1232
1233 len = p_editor_data->display_line_lengths[line_current];
1234 if (len >= sizeof(buffer))
1235 {
1236 log_error("Buffer overflow: len=%ld line=%ld \n", len, line_current);
1237 len = sizeof(buffer) - 1;
1238 }
1239 else if (len < 0)
1240 {
1241 log_error("Incorrect line offsets: len=%ld line=%ld \n", len, line_current);
1242 len = 0;
1243 }
1244
1245 // memcpy(buffer, p_editor_data->p_display_lines[line_current], (size_t)len);
1246 // Replace '\033' with '*'
1247 p_str = p_editor_data->p_display_lines[line_current];
1248 for (i = 0, j = 0; i < len; i++)
1249 {
1250 if (p_str[i] == '\033')
1251 {
1252 memcpy(buffer + j, EDITOR_ESC_DISPLAY_STR, sizeof(EDITOR_ESC_DISPLAY_STR) - 1);
1253 j += (int)(sizeof(EDITOR_ESC_DISPLAY_STR) - 1);
1254 }
1255 else
1256 {
1257 buffer[j] = p_str[i];
1258 j++;
1259 }
1260 }
1261 buffer[j] = '\0';
1262
1263 moveto(output_current_row, 0);
1264 clrtoeol();
1265 prints("%s", buffer);
1266 line_current++;
1267 output_current_row++;
1268 }
1269
1270 cleanup:
1271 return ch;
1272 }

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