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

Contents of /lbbs/src/editor.c

Parent Directory Parent Directory | Revision Log Revision Log


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

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