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

Contents of /lbbs/src/editor.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.60 - (show annotations)
Tue Nov 11 00:28:05 2025 UTC (4 months ago) by sysadm
Branch: MAIN
Changes since 1.59: +4 -0 lines
Content type: text/x-csrc
Use config.h

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

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