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

Contents of /lbbs/src/editor.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.59 - (show annotations)
Mon Nov 10 11:54:30 2025 UTC (4 months ago) by sysadm
Branch: MAIN
Changes since 1.58: +0 -1 lines
Content type: text/x-csrc
Convert \t to no more than TAB_SIZE spaces based on current line width

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

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