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

Contents of /lbbs/src/editor.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.54 - (show annotations)
Wed Nov 5 04:19:21 2025 UTC (4 months, 1 week ago) by sysadm
Branch: MAIN
Changes since 1.53: +7 -3 lines
Content type: text/x-csrc
Use enum / const int instead of macro define constant integers
Use const char * instead of macro define for constant strings

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

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