source: mainline/uspace/app/edit/edit.c@ 45004f3

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 45004f3 was 87822ce, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Avoid infinite loop when console communication is broken

Need to make sure callers of console_get_event_timeout() can distinguish
between timeout and I/O error. Fix all callers of console_get_event()
and console_get_event_timeout() not to enter infinite loop when console
connection is broken. Also avoid setting of errno variable.

  • Property mode set to 100644
File size: 35.8 KB
Line 
1/*
2 * Copyright (c) 2009 Jiri Svoboda
3 * Copyright (c) 2012 Martin Sucha
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup edit
31 * @brief Text editor.
32 * @{
33 */
34/**
35 * @file
36 */
37
38#include <stdio.h>
39#include <stdlib.h>
40#include <stddef.h>
41#include <stdbool.h>
42#include <vfs/vfs.h>
43#include <io/console.h>
44#include <io/style.h>
45#include <io/keycode.h>
46#include <errno.h>
47#include <align.h>
48#include <macros.h>
49#include <clipboard.h>
50#include <types/common.h>
51
52#include "sheet.h"
53#include "search.h"
54
55enum redraw_flags {
56 REDRAW_TEXT = (1 << 0),
57 REDRAW_ROW = (1 << 1),
58 REDRAW_STATUS = (1 << 2),
59 REDRAW_CARET = (1 << 3)
60};
61
62/** Pane
63 *
64 * A rectangular area of the screen used to edit a document. Different
65 * panes can be possibly used to edit the same document.
66 */
67typedef struct {
68 /* Pane dimensions */
69 int rows, columns;
70
71 /* Position of the visible area */
72 int sh_row, sh_column;
73
74 /** Bitmask of components that need redrawing */
75 enum redraw_flags rflags;
76
77 /** Current position of the caret */
78 tag_t caret_pos;
79
80 /** Start of selection */
81 tag_t sel_start;
82
83 /** Active keyboard modifiers */
84 keymod_t keymod;
85
86 /**
87 * Ideal column where the caret should try to get. This is used
88 * for maintaining the same column during vertical movement.
89 */
90 int ideal_column;
91
92 char *previous_search;
93 bool previous_search_reverse;
94} pane_t;
95
96/** Document
97 *
98 * Associates a sheet with a file where it can be saved to.
99 */
100typedef struct {
101 char *file_name;
102 sheet_t *sh;
103} doc_t;
104
105static console_ctrl_t *con;
106static doc_t doc;
107static bool done;
108static pane_t pane;
109static bool cursor_visible;
110
111static sysarg_t scr_rows;
112static sysarg_t scr_columns;
113
114#define ROW_BUF_SIZE 4096
115#define BUF_SIZE 64
116#define TAB_WIDTH 8
117
118/** Maximum filename length that can be entered. */
119#define INFNAME_MAX_LEN 128
120
121static void cursor_show(void);
122static void cursor_hide(void);
123static void cursor_setvis(bool visible);
124
125static void key_handle_press(kbd_event_t *ev);
126static void key_handle_unmod(kbd_event_t const *ev);
127static void key_handle_ctrl(kbd_event_t const *ev);
128static void key_handle_shift(kbd_event_t const *ev);
129static void key_handle_shift_ctrl(kbd_event_t const *ev);
130static void key_handle_movement(unsigned int key, bool shift);
131
132static void pos_handle(pos_event_t *ev);
133
134static errno_t file_save(char const *fname);
135static void file_save_as(void);
136static errno_t file_insert(char *fname);
137static errno_t file_save_range(char const *fname, spt_t const *spos,
138 spt_t const *epos);
139static char *range_get_str(spt_t const *spos, spt_t const *epos);
140
141static char *prompt(char const *prompt, char const *init_value);
142
143static void pane_text_display(void);
144static void pane_row_display(void);
145static void pane_row_range_display(int r0, int r1);
146static void pane_status_display(void);
147static void pane_caret_display(void);
148
149static void insert_char(char32_t c);
150static void delete_char_before(void);
151static void delete_char_after(void);
152static void caret_update(void);
153static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir, bool select);
154static void caret_move_absolute(int row, int column, enum dir_spec align_dir, bool select);
155static void caret_move(spt_t spt, bool select, bool update_ideal_column);
156static void caret_move_word_left(bool select);
157static void caret_move_word_right(bool select);
158static void caret_go_to_line_ask(void);
159
160static bool selection_active(void);
161static void selection_sel_all(void);
162static void selection_sel_range(spt_t pa, spt_t pb);
163static void selection_get_points(spt_t *pa, spt_t *pb);
164static void selection_delete(void);
165static void selection_copy(void);
166static void insert_clipboard_data(void);
167
168static void search(char *pattern, bool reverse);
169static void search_prompt(bool reverse);
170static void search_repeat(void);
171
172static void pt_get_sof(spt_t *pt);
173static void pt_get_eof(spt_t *pt);
174static void pt_get_sol(spt_t *cpt, spt_t *spt);
175static void pt_get_eol(spt_t *cpt, spt_t *ept);
176static bool pt_is_word_beginning(spt_t *pt);
177static bool pt_is_delimiter(spt_t *pt);
178static bool pt_is_punctuation(spt_t *pt);
179static spt_t pt_find_word_left(spt_t spt);
180static spt_t pt_find_word_left(spt_t spt);
181
182static int tag_cmp(tag_t const *a, tag_t const *b);
183static int spt_cmp(spt_t const *a, spt_t const *b);
184static int coord_cmp(coord_t const *a, coord_t const *b);
185
186static void status_display(char const *str);
187
188int main(int argc, char *argv[])
189{
190 cons_event_t ev;
191 bool new_file;
192 errno_t rc;
193
194 con = console_init(stdin, stdout);
195 console_clear(con);
196
197 console_get_size(con, &scr_columns, &scr_rows);
198
199 pane.rows = scr_rows - 1;
200 pane.columns = scr_columns;
201 pane.sh_row = 1;
202 pane.sh_column = 1;
203
204 /* Start with an empty sheet. */
205 rc = sheet_create(&doc.sh);
206 if (rc != EOK) {
207 printf("Out of memory.\n");
208 return -1;
209 }
210
211 /* Place caret at the beginning of file. */
212 spt_t sof;
213 pt_get_sof(&sof);
214 sheet_place_tag(doc.sh, &sof, &pane.caret_pos);
215 pane.ideal_column = 1;
216
217 if (argc == 2) {
218 doc.file_name = str_dup(argv[1]);
219 } else if (argc > 1) {
220 printf("Invalid arguments.\n");
221 return -2;
222 } else {
223 doc.file_name = NULL;
224 }
225
226 new_file = false;
227
228 if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
229 new_file = true;
230
231 /* Place selection start tag. */
232 sheet_place_tag(doc.sh, &sof, &pane.sel_start);
233
234 /* Move to beginning of file. */
235 pt_get_sof(&sof);
236 caret_move(sof, true, true);
237
238 /* Initial display */
239 cursor_visible = true;
240
241 cursor_hide();
242 console_clear(con);
243 pane_text_display();
244 pane_status_display();
245 if (new_file && doc.file_name != NULL)
246 status_display("File not found. Starting empty file.");
247 pane_caret_display();
248 cursor_show();
249
250 done = false;
251
252 while (!done) {
253 rc = console_get_event(con, &ev);
254 if (rc != EOK)
255 break;
256
257 pane.rflags = 0;
258
259 switch (ev.type) {
260 case CEV_KEY:
261 pane.keymod = ev.ev.key.mods;
262 if (ev.ev.key.type == KEY_PRESS)
263 key_handle_press(&ev.ev.key);
264 break;
265 case CEV_POS:
266 pos_handle(&ev.ev.pos);
267 break;
268 }
269
270 /* Redraw as necessary. */
271
272 cursor_hide();
273
274 if (pane.rflags & REDRAW_TEXT)
275 pane_text_display();
276 if (pane.rflags & REDRAW_ROW)
277 pane_row_display();
278 if (pane.rflags & REDRAW_STATUS)
279 pane_status_display();
280 if (pane.rflags & REDRAW_CARET)
281 pane_caret_display();
282
283 cursor_show();
284 }
285
286 console_clear(con);
287
288 return 0;
289}
290
291/* Handle key press. */
292static void key_handle_press(kbd_event_t *ev)
293{
294 if (((ev->mods & KM_ALT) == 0) &&
295 ((ev->mods & KM_SHIFT) == 0) &&
296 (ev->mods & KM_CTRL) != 0) {
297 key_handle_ctrl(ev);
298 } else if (((ev->mods & KM_ALT) == 0) &&
299 ((ev->mods & KM_CTRL) == 0) &&
300 (ev->mods & KM_SHIFT) != 0) {
301 key_handle_shift(ev);
302 } else if (((ev->mods & KM_ALT) == 0) &&
303 ((ev->mods & KM_CTRL) != 0) &&
304 (ev->mods & KM_SHIFT) != 0) {
305 key_handle_shift_ctrl(ev);
306 } else if ((ev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
307 key_handle_unmod(ev);
308 }
309}
310
311static void cursor_show(void)
312{
313 cursor_setvis(true);
314}
315
316static void cursor_hide(void)
317{
318 cursor_setvis(false);
319}
320
321static void cursor_setvis(bool visible)
322{
323 if (cursor_visible != visible) {
324 console_cursor_visibility(con, visible);
325 cursor_visible = visible;
326 }
327}
328
329/** Handle key without modifier. */
330static void key_handle_unmod(kbd_event_t const *ev)
331{
332 switch (ev->key) {
333 case KC_ENTER:
334 selection_delete();
335 insert_char('\n');
336 caret_update();
337 break;
338 case KC_LEFT:
339 case KC_RIGHT:
340 case KC_UP:
341 case KC_DOWN:
342 case KC_HOME:
343 case KC_END:
344 case KC_PAGE_UP:
345 case KC_PAGE_DOWN:
346 key_handle_movement(ev->key, false);
347 break;
348 case KC_BACKSPACE:
349 if (selection_active())
350 selection_delete();
351 else
352 delete_char_before();
353 caret_update();
354 break;
355 case KC_DELETE:
356 if (selection_active())
357 selection_delete();
358 else
359 delete_char_after();
360 caret_update();
361 break;
362 default:
363 if (ev->c >= 32 || ev->c == '\t') {
364 selection_delete();
365 insert_char(ev->c);
366 caret_update();
367 }
368 break;
369 }
370}
371
372/** Handle Shift-key combination. */
373static void key_handle_shift(kbd_event_t const *ev)
374{
375 switch (ev->key) {
376 case KC_LEFT:
377 case KC_RIGHT:
378 case KC_UP:
379 case KC_DOWN:
380 case KC_HOME:
381 case KC_END:
382 case KC_PAGE_UP:
383 case KC_PAGE_DOWN:
384 key_handle_movement(ev->key, true);
385 break;
386 default:
387 if (ev->c >= 32 || ev->c == '\t') {
388 selection_delete();
389 insert_char(ev->c);
390 caret_update();
391 }
392 break;
393 }
394}
395
396/** Handle Ctrl-key combination. */
397static void key_handle_ctrl(kbd_event_t const *ev)
398{
399 spt_t pt;
400 switch (ev->key) {
401 case KC_Q:
402 done = true;
403 break;
404 case KC_S:
405 if (doc.file_name != NULL)
406 file_save(doc.file_name);
407 else
408 file_save_as();
409 break;
410 case KC_E:
411 file_save_as();
412 break;
413 case KC_C:
414 selection_copy();
415 break;
416 case KC_V:
417 selection_delete();
418 insert_clipboard_data();
419 pane.rflags |= REDRAW_TEXT;
420 caret_update();
421 break;
422 case KC_X:
423 selection_copy();
424 selection_delete();
425 pane.rflags |= REDRAW_TEXT;
426 caret_update();
427 break;
428 case KC_A:
429 selection_sel_all();
430 break;
431 case KC_RIGHT:
432 caret_move_word_right(false);
433 break;
434 case KC_LEFT:
435 caret_move_word_left(false);
436 break;
437 case KC_L:
438 caret_go_to_line_ask();
439 break;
440 case KC_F:
441 search_prompt(false);
442 break;
443 case KC_N:
444 search_repeat();
445 break;
446 case KC_HOME:
447 pt_get_sof(&pt);
448 caret_move(pt, false, true);
449 break;
450 case KC_END:
451 pt_get_eof(&pt);
452 caret_move(pt, false, true);
453 break;
454 default:
455 break;
456 }
457}
458
459static void key_handle_shift_ctrl(kbd_event_t const *ev)
460{
461 spt_t pt;
462 switch (ev->key) {
463 case KC_LEFT:
464 caret_move_word_left(true);
465 break;
466 case KC_RIGHT:
467 caret_move_word_right(true);
468 break;
469 case KC_F:
470 search_prompt(true);
471 break;
472 case KC_HOME:
473 pt_get_sof(&pt);
474 caret_move(pt, true, true);
475 break;
476 case KC_END:
477 pt_get_eof(&pt);
478 caret_move(pt, true, true);
479 break;
480 default:
481 break;
482 }
483}
484
485static void pos_handle(pos_event_t *ev)
486{
487 coord_t bc;
488 spt_t pt;
489 bool select;
490
491 if (ev->type == POS_PRESS && ev->vpos < (unsigned)pane.rows) {
492 bc.row = pane.sh_row + ev->vpos;
493 bc.column = pane.sh_column + ev->hpos;
494 sheet_get_cell_pt(doc.sh, &bc, dir_before, &pt);
495
496 select = (pane.keymod & KM_SHIFT) != 0;
497
498 caret_move(pt, select, true);
499 }
500}
501
502/** Move caret while preserving or resetting selection. */
503static void caret_move(spt_t new_caret_pt, bool select, bool update_ideal_column)
504{
505 spt_t old_caret_pt, old_sel_pt;
506 coord_t c_old, c_new;
507 bool had_sel;
508
509 /* Check if we had selection before. */
510 tag_get_pt(&pane.caret_pos, &old_caret_pt);
511 tag_get_pt(&pane.sel_start, &old_sel_pt);
512 had_sel = !spt_equal(&old_caret_pt, &old_sel_pt);
513
514 /* Place tag of the caret */
515 sheet_remove_tag(doc.sh, &pane.caret_pos);
516 sheet_place_tag(doc.sh, &new_caret_pt, &pane.caret_pos);
517
518 if (select == false) {
519 /* Move sel_start to the same point as caret. */
520 sheet_remove_tag(doc.sh, &pane.sel_start);
521 sheet_place_tag(doc.sh, &new_caret_pt, &pane.sel_start);
522 }
523
524 spt_get_coord(&new_caret_pt, &c_new);
525 if (select) {
526 spt_get_coord(&old_caret_pt, &c_old);
527
528 if (c_old.row == c_new.row)
529 pane.rflags |= REDRAW_ROW;
530 else
531 pane.rflags |= REDRAW_TEXT;
532
533 } else if (had_sel == true) {
534 /* Redraw because text was unselected. */
535 pane.rflags |= REDRAW_TEXT;
536 }
537
538 if (update_ideal_column)
539 pane.ideal_column = c_new.column;
540
541 caret_update();
542}
543
544static void key_handle_movement(unsigned int key, bool select)
545{
546 spt_t pt;
547 switch (key) {
548 case KC_LEFT:
549 caret_move_relative(0, -1, dir_before, select);
550 break;
551 case KC_RIGHT:
552 caret_move_relative(0, 0, dir_after, select);
553 break;
554 case KC_UP:
555 caret_move_relative(-1, 0, dir_before, select);
556 break;
557 case KC_DOWN:
558 caret_move_relative(+1, 0, dir_before, select);
559 break;
560 case KC_HOME:
561 tag_get_pt(&pane.caret_pos, &pt);
562 pt_get_sol(&pt, &pt);
563 caret_move(pt, select, true);
564 break;
565 case KC_END:
566 tag_get_pt(&pane.caret_pos, &pt);
567 pt_get_eol(&pt, &pt);
568 caret_move(pt, select, true);
569 break;
570 case KC_PAGE_UP:
571 caret_move_relative(-pane.rows, 0, dir_before, select);
572 break;
573 case KC_PAGE_DOWN:
574 caret_move_relative(+pane.rows, 0, dir_before, select);
575 break;
576 default:
577 break;
578 }
579}
580
581/** Save the document. */
582static errno_t file_save(char const *fname)
583{
584 spt_t sp, ep;
585 errno_t rc;
586
587 status_display("Saving...");
588 pt_get_sof(&sp);
589 pt_get_eof(&ep);
590
591 rc = file_save_range(fname, &sp, &ep);
592
593 switch (rc) {
594 case EINVAL:
595 status_display("Error opening file!");
596 break;
597 case EIO:
598 status_display("Error writing data!");
599 break;
600 default:
601 status_display("File saved.");
602 break;
603 }
604
605 return rc;
606}
607
608/** Change document name and save. */
609static void file_save_as(void)
610{
611 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
612 char *fname;
613
614 fname = prompt("Save As", old_fname);
615 if (fname == NULL) {
616 status_display("Save cancelled.");
617 return;
618 }
619
620 errno_t rc = file_save(fname);
621 if (rc != EOK)
622 return;
623
624 if (doc.file_name != NULL)
625 free(doc.file_name);
626 doc.file_name = fname;
627}
628
629/** Ask for a string. */
630static char *prompt(char const *prompt, char const *init_value)
631{
632 cons_event_t ev;
633 kbd_event_t *kev;
634 char *str;
635 char32_t buffer[INFNAME_MAX_LEN + 1];
636 int max_len;
637 int nc;
638 bool done;
639 errno_t rc;
640
641 asprintf(&str, "%s: %s", prompt, init_value);
642 status_display(str);
643 console_set_pos(con, 1 + str_length(str), scr_rows - 1);
644 free(str);
645
646 console_set_style(con, STYLE_INVERTED);
647
648 max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
649 str_to_wstr(buffer, max_len + 1, init_value);
650 nc = wstr_length(buffer);
651 done = false;
652
653 while (!done) {
654 rc = console_get_event(con, &ev);
655 if (rc != EOK)
656 return NULL;
657
658 if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS) {
659 kev = &ev.ev.key;
660
661 /* Handle key press. */
662 if ((kev->mods & (KM_CTRL | KM_ALT)) == 0) {
663 switch (kev->key) {
664 case KC_ESCAPE:
665 return NULL;
666 case KC_BACKSPACE:
667 if (nc > 0) {
668 putchar('\b');
669 console_flush(con);
670 --nc;
671 }
672 break;
673 case KC_ENTER:
674 done = true;
675 break;
676 default:
677 if (kev->c >= 32 && nc < max_len) {
678 putuchar(kev->c);
679 console_flush(con);
680 buffer[nc++] = kev->c;
681 }
682 break;
683 }
684 }
685 }
686 }
687
688 buffer[nc] = '\0';
689 str = wstr_to_astr(buffer);
690
691 console_set_style(con, STYLE_NORMAL);
692
693 return str;
694}
695
696/** Insert file at caret position.
697 *
698 * Reads in the contents of a file and inserts them at the current position
699 * of the caret.
700 */
701static errno_t file_insert(char *fname)
702{
703 FILE *f;
704 char32_t c;
705 char buf[BUF_SIZE];
706 int bcnt;
707 int n_read;
708 size_t off;
709
710 f = fopen(fname, "rt");
711 if (f == NULL)
712 return EINVAL;
713
714 bcnt = 0;
715
716 while (true) {
717 if (bcnt < STR_BOUNDS(1)) {
718 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
719 bcnt += n_read;
720 }
721
722 off = 0;
723 c = str_decode(buf, &off, bcnt);
724 if (c == '\0')
725 break;
726
727 bcnt -= off;
728 memcpy(buf, buf + off, bcnt);
729
730 insert_char(c);
731 }
732
733 fclose(f);
734
735 return EOK;
736}
737
738/** Save a range of text into a file. */
739static errno_t file_save_range(char const *fname, spt_t const *spos,
740 spt_t const *epos)
741{
742 FILE *f;
743 char buf[BUF_SIZE];
744 spt_t sp, bep;
745 size_t bytes, n_written;
746
747 f = fopen(fname, "wt");
748 if (f == NULL)
749 return EINVAL;
750
751 sp = *spos;
752
753 do {
754 sheet_copy_out(doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
755 bytes = str_size(buf);
756
757 n_written = fwrite(buf, 1, bytes, f);
758 if (n_written != bytes) {
759 return EIO;
760 }
761
762 sp = bep;
763 } while (!spt_equal(&bep, epos));
764
765 if (fclose(f) < 0)
766 return EIO;
767
768 return EOK;
769}
770
771/** Return contents of range as a new string. */
772static char *range_get_str(spt_t const *spos, spt_t const *epos)
773{
774 char *buf;
775 spt_t sp, bep;
776 size_t bytes;
777 size_t buf_size, bpos;
778
779 buf_size = 1;
780
781 buf = malloc(buf_size);
782 if (buf == NULL)
783 return NULL;
784
785 bpos = 0;
786 sp = *spos;
787
788 while (true) {
789 sheet_copy_out(doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
790 &bep);
791 bytes = str_size(&buf[bpos]);
792 bpos += bytes;
793 sp = bep;
794
795 if (spt_equal(&bep, epos))
796 break;
797
798 buf_size *= 2;
799 char *tmp = realloc(buf, buf_size);
800 if (tmp == NULL) {
801 free(buf);
802 return NULL;
803 }
804 buf = tmp;
805 }
806
807 return buf;
808}
809
810static void pane_text_display(void)
811{
812 int sh_rows, rows;
813
814 sheet_get_num_rows(doc.sh, &sh_rows);
815 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
816
817 /* Draw rows from the sheet. */
818
819 console_set_pos(con, 0, 0);
820 pane_row_range_display(0, rows);
821
822 /* Clear the remaining rows if file is short. */
823
824 int i;
825 sysarg_t j;
826 for (i = rows; i < pane.rows; ++i) {
827 console_set_pos(con, 0, i);
828 for (j = 0; j < scr_columns; ++j)
829 putchar(' ');
830 console_flush(con);
831 }
832
833 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
834 pane.rflags &= ~REDRAW_ROW;
835}
836
837/** Display just the row where the caret is. */
838static void pane_row_display(void)
839{
840 spt_t caret_pt;
841 coord_t coord;
842 int ridx;
843
844 tag_get_pt(&pane.caret_pos, &caret_pt);
845 spt_get_coord(&caret_pt, &coord);
846
847 ridx = coord.row - pane.sh_row;
848 pane_row_range_display(ridx, ridx + 1);
849 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
850}
851
852static void pane_row_range_display(int r0, int r1)
853{
854 int i, j, fill;
855 spt_t rb, re, dep, pt;
856 coord_t rbc, rec;
857 char row_buf[ROW_BUF_SIZE];
858 char32_t c;
859 size_t pos, size;
860 int s_column;
861 coord_t csel_start, csel_end, ctmp;
862
863 /* Determine selection start and end. */
864
865 tag_get_pt(&pane.sel_start, &pt);
866 spt_get_coord(&pt, &csel_start);
867
868 tag_get_pt(&pane.caret_pos, &pt);
869 spt_get_coord(&pt, &csel_end);
870
871 if (coord_cmp(&csel_start, &csel_end) > 0) {
872 ctmp = csel_start;
873 csel_start = csel_end;
874 csel_end = ctmp;
875 }
876
877 /* Draw rows from the sheet. */
878
879 console_set_pos(con, 0, 0);
880 for (i = r0; i < r1; ++i) {
881 /* Starting point for row display */
882 rbc.row = pane.sh_row + i;
883 rbc.column = pane.sh_column;
884 sheet_get_cell_pt(doc.sh, &rbc, dir_before, &rb);
885
886 /* Ending point for row display */
887 rec.row = pane.sh_row + i;
888 rec.column = pane.sh_column + pane.columns;
889 sheet_get_cell_pt(doc.sh, &rec, dir_before, &re);
890
891 /* Copy the text of the row to the buffer. */
892 sheet_copy_out(doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
893
894 /* Display text from the buffer. */
895
896 if (coord_cmp(&csel_start, &rbc) <= 0 &&
897 coord_cmp(&rbc, &csel_end) < 0) {
898 console_flush(con);
899 console_set_style(con, STYLE_SELECTED);
900 console_flush(con);
901 }
902
903 console_set_pos(con, 0, i);
904 size = str_size(row_buf);
905 pos = 0;
906 s_column = pane.sh_column;
907 while (pos < size) {
908 if ((csel_start.row == rbc.row) && (csel_start.column == s_column)) {
909 console_flush(con);
910 console_set_style(con, STYLE_SELECTED);
911 console_flush(con);
912 }
913
914 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
915 console_flush(con);
916 console_set_style(con, STYLE_NORMAL);
917 console_flush(con);
918 }
919
920 c = str_decode(row_buf, &pos, size);
921 if (c != '\t') {
922 printf("%lc", (wint_t) c);
923 s_column += 1;
924 } else {
925 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH) -
926 s_column;
927
928 for (j = 0; j < fill; ++j)
929 putchar(' ');
930 s_column += fill;
931 }
932 }
933
934 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
935 console_flush(con);
936 console_set_style(con, STYLE_NORMAL);
937 console_flush(con);
938 }
939
940 /* Fill until the end of display area. */
941
942 if ((unsigned)s_column - 1 < scr_columns)
943 fill = scr_columns - (s_column - 1);
944 else
945 fill = 0;
946
947 for (j = 0; j < fill; ++j)
948 putchar(' ');
949 console_flush(con);
950 console_set_style(con, STYLE_NORMAL);
951 }
952
953 pane.rflags |= REDRAW_CARET;
954}
955
956/** Display pane status in the status line. */
957static void pane_status_display(void)
958{
959 spt_t caret_pt;
960 coord_t coord;
961 int last_row;
962 char *fname;
963 char *p;
964 char *text;
965 size_t n;
966 int pos;
967 size_t nextra;
968 size_t fnw;
969
970 tag_get_pt(&pane.caret_pos, &caret_pt);
971 spt_get_coord(&caret_pt, &coord);
972
973 sheet_get_num_rows(doc.sh, &last_row);
974
975 if (doc.file_name != NULL) {
976 /* Remove directory component */
977 p = str_rchr(doc.file_name, '/');
978 if (p != NULL)
979 fname = str_dup(p + 1);
980 else
981 fname = str_dup(doc.file_name);
982 } else {
983 fname = str_dup("<unnamed>");
984 }
985
986 if (fname == NULL)
987 return;
988
989 console_set_pos(con, 0, scr_rows - 1);
990 console_set_style(con, STYLE_INVERTED);
991
992 /*
993 * Make sure the status fits on the screen. This loop should
994 * be executed at most twice.
995 */
996 while (true) {
997 int rc = asprintf(&text, " %d, %d (%d): File '%s'. Ctrl-Q Quit Ctrl-S Save "
998 "Ctrl-E Save As", coord.row, coord.column, last_row, fname);
999 if (rc < 0) {
1000 n = 0;
1001 goto finish;
1002 }
1003
1004 /* If it already fits, we're done */
1005 n = str_width(text);
1006 if (n <= scr_columns - 2)
1007 break;
1008
1009 /* Compute number of excess characters */
1010 nextra = n - (scr_columns - 2);
1011 /** With of the file name part */
1012 fnw = str_width(fname);
1013
1014 /*
1015 * If reducing file name to two characters '..' won't help,
1016 * just give up and print a blank status.
1017 */
1018 if (nextra > fnw - 2)
1019 goto finish;
1020
1021 /* Compute position where we overwrite with '..\0' */
1022 if (fnw >= nextra + 2) {
1023 p = fname + str_lsize(fname, fnw - nextra - 2);
1024 } else {
1025 p = fname;
1026 }
1027
1028 /* Shorten the string */
1029 p[0] = p[1] = '.';
1030 p[2] = '\0';
1031
1032 /* Need to format the string once more. */
1033 free(text);
1034 }
1035
1036 printf("%s", text);
1037 free(text);
1038 free(fname);
1039finish:
1040 /* Fill the rest of the line */
1041 pos = scr_columns - 1 - n;
1042 printf("%*s", pos, "");
1043 console_flush(con);
1044 console_set_style(con, STYLE_NORMAL);
1045
1046 pane.rflags |= REDRAW_CARET;
1047}
1048
1049/** Set cursor to reflect position of the caret. */
1050static void pane_caret_display(void)
1051{
1052 spt_t caret_pt;
1053 coord_t coord;
1054
1055 tag_get_pt(&pane.caret_pos, &caret_pt);
1056
1057 spt_get_coord(&caret_pt, &coord);
1058 console_set_pos(con, coord.column - pane.sh_column,
1059 coord.row - pane.sh_row);
1060}
1061
1062/** Insert a character at caret position. */
1063static void insert_char(char32_t c)
1064{
1065 spt_t pt;
1066 char cbuf[STR_BOUNDS(1) + 1];
1067 size_t offs;
1068
1069 tag_get_pt(&pane.caret_pos, &pt);
1070
1071 offs = 0;
1072 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
1073 cbuf[offs] = '\0';
1074
1075 (void) sheet_insert(doc.sh, &pt, dir_before, cbuf);
1076
1077 pane.rflags |= REDRAW_ROW;
1078 if (c == '\n')
1079 pane.rflags |= REDRAW_TEXT;
1080}
1081
1082/** Delete the character before the caret. */
1083static void delete_char_before(void)
1084{
1085 spt_t sp, ep;
1086 coord_t coord;
1087
1088 tag_get_pt(&pane.caret_pos, &ep);
1089 spt_get_coord(&ep, &coord);
1090
1091 coord.column -= 1;
1092 sheet_get_cell_pt(doc.sh, &coord, dir_before, &sp);
1093
1094 (void) sheet_delete(doc.sh, &sp, &ep);
1095
1096 pane.rflags |= REDRAW_ROW;
1097 if (coord.column < 1)
1098 pane.rflags |= REDRAW_TEXT;
1099}
1100
1101/** Delete the character after the caret. */
1102static void delete_char_after(void)
1103{
1104 spt_t sp, ep;
1105 coord_t sc, ec;
1106
1107 tag_get_pt(&pane.caret_pos, &sp);
1108 spt_get_coord(&sp, &sc);
1109
1110 sheet_get_cell_pt(doc.sh, &sc, dir_after, &ep);
1111 spt_get_coord(&ep, &ec);
1112
1113 (void) sheet_delete(doc.sh, &sp, &ep);
1114
1115 pane.rflags |= REDRAW_ROW;
1116 if (ec.row != sc.row)
1117 pane.rflags |= REDRAW_TEXT;
1118}
1119
1120/** Scroll pane after caret has moved.
1121 *
1122 * After modifying the position of the caret, this is called to scroll
1123 * the pane to ensure that the caret is in the visible area.
1124 */
1125static void caret_update(void)
1126{
1127 spt_t pt;
1128 coord_t coord;
1129
1130 tag_get_pt(&pane.caret_pos, &pt);
1131 spt_get_coord(&pt, &coord);
1132
1133 /* Scroll pane vertically. */
1134
1135 if (coord.row < pane.sh_row) {
1136 pane.sh_row = coord.row;
1137 pane.rflags |= REDRAW_TEXT;
1138 }
1139
1140 if (coord.row > pane.sh_row + pane.rows - 1) {
1141 pane.sh_row = coord.row - pane.rows + 1;
1142 pane.rflags |= REDRAW_TEXT;
1143 }
1144
1145 /* Scroll pane horizontally. */
1146
1147 if (coord.column < pane.sh_column) {
1148 pane.sh_column = coord.column;
1149 pane.rflags |= REDRAW_TEXT;
1150 }
1151
1152 if (coord.column > pane.sh_column + pane.columns - 1) {
1153 pane.sh_column = coord.column - pane.columns + 1;
1154 pane.rflags |= REDRAW_TEXT;
1155 }
1156
1157 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
1158}
1159
1160/** Relatively move caret position.
1161 *
1162 * Moves caret relatively to the current position. Looking at the first
1163 * character cell after the caret and moving by @a drow and @a dcolumn, we get
1164 * to a new character cell, and thus a new character. Then we either go to the
1165 * point before the the character or after it, depending on @a align_dir.
1166 *
1167 * @param select true if the selection tag should stay where it is
1168 */
1169static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir,
1170 bool select)
1171{
1172 spt_t pt;
1173 coord_t coord;
1174 int num_rows;
1175 bool pure_vertical;
1176
1177 tag_get_pt(&pane.caret_pos, &pt);
1178 spt_get_coord(&pt, &coord);
1179 coord.row += drow;
1180 coord.column += dcolumn;
1181
1182 /* Clamp coordinates. */
1183 if (drow < 0 && coord.row < 1)
1184 coord.row = 1;
1185 if (dcolumn < 0 && coord.column < 1) {
1186 if (coord.row < 2)
1187 coord.column = 1;
1188 else {
1189 coord.row--;
1190 sheet_get_row_width(doc.sh, coord.row, &coord.column);
1191 }
1192 }
1193 if (drow > 0) {
1194 sheet_get_num_rows(doc.sh, &num_rows);
1195 if (coord.row > num_rows)
1196 coord.row = num_rows;
1197 }
1198
1199 /* For purely vertical movement try attaining @c ideal_column. */
1200 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
1201 if (pure_vertical)
1202 coord.column = pane.ideal_column;
1203
1204 /*
1205 * Select the point before or after the character at the designated
1206 * coordinates. The character can be wider than one cell (e.g. tab).
1207 */
1208 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1209
1210 /* For non-vertical movement set the new value for @c ideal_column. */
1211 caret_move(pt, select, !pure_vertical);
1212}
1213
1214/** Absolutely move caret position.
1215 *
1216 * Moves caret to a specified position. We get to a new character cell, and
1217 * thus a new character. Then we either go to the point before the the character
1218 * or after it, depending on @a align_dir.
1219 *
1220 * @param select true if the selection tag should stay where it is
1221 */
1222static void caret_move_absolute(int row, int column, enum dir_spec align_dir,
1223 bool select)
1224{
1225 coord_t coord;
1226 coord.row = row;
1227 coord.column = column;
1228
1229 spt_t pt;
1230 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1231
1232 caret_move(pt, select, true);
1233}
1234
1235/** Find beginning of a word to the left of spt */
1236static spt_t pt_find_word_left(spt_t spt)
1237{
1238 do {
1239 spt_prev_char(spt, &spt);
1240 } while (!pt_is_word_beginning(&spt));
1241 return spt;
1242}
1243
1244/** Find beginning of a word to the right of spt */
1245static spt_t pt_find_word_right(spt_t spt)
1246{
1247 do {
1248 spt_next_char(spt, &spt);
1249 } while (!pt_is_word_beginning(&spt));
1250 return spt;
1251}
1252
1253static void caret_move_word_left(bool select)
1254{
1255 spt_t pt;
1256 tag_get_pt(&pane.caret_pos, &pt);
1257 spt_t word_left = pt_find_word_left(pt);
1258 caret_move(word_left, select, true);
1259}
1260
1261static void caret_move_word_right(bool select)
1262{
1263 spt_t pt;
1264 tag_get_pt(&pane.caret_pos, &pt);
1265 spt_t word_right = pt_find_word_right(pt);
1266 caret_move(word_right, select, true);
1267}
1268
1269/** Ask for line and go to it. */
1270static void caret_go_to_line_ask(void)
1271{
1272 char *sline;
1273
1274 sline = prompt("Go to line", "");
1275 if (sline == NULL) {
1276 status_display("Go to line cancelled.");
1277 return;
1278 }
1279
1280 char *endptr;
1281 int line = strtol(sline, &endptr, 10);
1282 if (*endptr != '\0') {
1283 free(sline);
1284 status_display("Invalid number entered.");
1285 return;
1286 }
1287 free(sline);
1288
1289 caret_move_absolute(line, pane.ideal_column, dir_before, false);
1290}
1291
1292/* Search operations */
1293static errno_t search_spt_producer(void *data, char32_t *ret)
1294{
1295 assert(data != NULL);
1296 assert(ret != NULL);
1297 spt_t *spt = data;
1298 *ret = spt_next_char(*spt, spt);
1299 return EOK;
1300}
1301
1302static errno_t search_spt_reverse_producer(void *data, char32_t *ret)
1303{
1304 assert(data != NULL);
1305 assert(ret != NULL);
1306 spt_t *spt = data;
1307 *ret = spt_prev_char(*spt, spt);
1308 return EOK;
1309}
1310
1311static errno_t search_spt_mark(void *data, void **mark)
1312{
1313 assert(data != NULL);
1314 assert(mark != NULL);
1315 spt_t *spt = data;
1316 spt_t *new = calloc(1, sizeof(spt_t));
1317 *mark = new;
1318 if (new == NULL)
1319 return ENOMEM;
1320 *new = *spt;
1321 return EOK;
1322}
1323
1324static void search_spt_mark_free(void *data)
1325{
1326 free(data);
1327}
1328
1329static search_ops_t search_spt_ops = {
1330 .equals = char_exact_equals,
1331 .producer = search_spt_producer,
1332 .mark = search_spt_mark,
1333 .mark_free = search_spt_mark_free,
1334};
1335
1336static search_ops_t search_spt_reverse_ops = {
1337 .equals = char_exact_equals,
1338 .producer = search_spt_reverse_producer,
1339 .mark = search_spt_mark,
1340 .mark_free = search_spt_mark_free,
1341};
1342
1343/** Ask for line and go to it. */
1344static void search_prompt(bool reverse)
1345{
1346 char *pattern;
1347
1348 const char *prompt_text = "Find next";
1349 if (reverse)
1350 prompt_text = "Find previous";
1351
1352 const char *default_value = "";
1353 if (pane.previous_search)
1354 default_value = pane.previous_search;
1355
1356 pattern = prompt(prompt_text, default_value);
1357 if (pattern == NULL) {
1358 status_display("Search cancelled.");
1359 return;
1360 }
1361
1362 if (pane.previous_search)
1363 free(pane.previous_search);
1364 pane.previous_search = pattern;
1365 pane.previous_search_reverse = reverse;
1366
1367 search(pattern, reverse);
1368}
1369
1370static void search_repeat(void)
1371{
1372 if (pane.previous_search == NULL) {
1373 status_display("No previous search to repeat.");
1374 return;
1375 }
1376
1377 search(pane.previous_search, pane.previous_search_reverse);
1378}
1379
1380static void search(char *pattern, bool reverse)
1381{
1382 status_display("Searching...");
1383
1384 spt_t sp, producer_pos;
1385 tag_get_pt(&pane.caret_pos, &sp);
1386
1387 /* Start searching on the position before/after caret */
1388 if (!reverse) {
1389 spt_next_char(sp, &sp);
1390 } else {
1391 spt_prev_char(sp, &sp);
1392 }
1393 producer_pos = sp;
1394
1395 search_ops_t ops = search_spt_ops;
1396 if (reverse)
1397 ops = search_spt_reverse_ops;
1398
1399 search_t *search = search_init(pattern, &producer_pos, ops, reverse);
1400 if (search == NULL) {
1401 status_display("Failed initializing search.");
1402 return;
1403 }
1404
1405 match_t match;
1406 errno_t rc = search_next_match(search, &match);
1407 if (rc != EOK) {
1408 status_display("Failed searching.");
1409 search_fini(search);
1410 }
1411
1412 if (match.end) {
1413 status_display("Match found.");
1414 assert(match.end != NULL);
1415 spt_t *end = match.end;
1416 caret_move(*end, false, true);
1417 while (match.length > 0) {
1418 match.length--;
1419 if (reverse) {
1420 spt_next_char(*end, end);
1421 } else {
1422 spt_prev_char(*end, end);
1423 }
1424 }
1425 caret_move(*end, true, true);
1426 free(end);
1427 } else {
1428 status_display("Not found.");
1429 }
1430
1431 search_fini(search);
1432}
1433
1434/** Check for non-empty selection. */
1435static bool selection_active(void)
1436{
1437 return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
1438}
1439
1440static void selection_get_points(spt_t *pa, spt_t *pb)
1441{
1442 spt_t pt;
1443
1444 tag_get_pt(&pane.sel_start, pa);
1445 tag_get_pt(&pane.caret_pos, pb);
1446
1447 if (spt_cmp(pa, pb) > 0) {
1448 pt = *pa;
1449 *pa = *pb;
1450 *pb = pt;
1451 }
1452}
1453
1454/** Delete selected text. */
1455static void selection_delete(void)
1456{
1457 spt_t pa, pb;
1458 coord_t ca, cb;
1459 int rel;
1460
1461 tag_get_pt(&pane.sel_start, &pa);
1462 tag_get_pt(&pane.caret_pos, &pb);
1463 spt_get_coord(&pa, &ca);
1464 spt_get_coord(&pb, &cb);
1465 rel = coord_cmp(&ca, &cb);
1466
1467 if (rel == 0)
1468 return;
1469
1470 if (rel < 0)
1471 sheet_delete(doc.sh, &pa, &pb);
1472 else
1473 sheet_delete(doc.sh, &pb, &pa);
1474
1475 if (ca.row == cb.row)
1476 pane.rflags |= REDRAW_ROW;
1477 else
1478 pane.rflags |= REDRAW_TEXT;
1479}
1480
1481/** Select all text in the editor */
1482static void selection_sel_all(void)
1483{
1484 spt_t spt, ept;
1485
1486 pt_get_sof(&spt);
1487 pt_get_eof(&ept);
1488
1489 selection_sel_range(spt, ept);
1490}
1491
1492/** Select select all text in a given range with the given direction */
1493static void selection_sel_range(spt_t pa, spt_t pb)
1494{
1495 sheet_remove_tag(doc.sh, &pane.sel_start);
1496 sheet_place_tag(doc.sh, &pa, &pane.sel_start);
1497 sheet_remove_tag(doc.sh, &pane.caret_pos);
1498 sheet_place_tag(doc.sh, &pb, &pane.caret_pos);
1499
1500 pane.rflags |= REDRAW_TEXT;
1501 caret_update();
1502}
1503
1504static void selection_copy(void)
1505{
1506 spt_t pa, pb;
1507 char *str;
1508
1509 selection_get_points(&pa, &pb);
1510 str = range_get_str(&pa, &pb);
1511 if (str == NULL || clipboard_put_str(str) != EOK) {
1512 status_display("Copying to clipboard failed!");
1513 }
1514 free(str);
1515}
1516
1517static void insert_clipboard_data(void)
1518{
1519 char *str;
1520 size_t off;
1521 char32_t c;
1522 errno_t rc;
1523
1524 rc = clipboard_get_str(&str);
1525 if (rc != EOK || str == NULL)
1526 return;
1527
1528 off = 0;
1529
1530 while (true) {
1531 c = str_decode(str, &off, STR_NO_LIMIT);
1532 if (c == '\0')
1533 break;
1534
1535 insert_char(c);
1536 }
1537
1538 free(str);
1539}
1540
1541/** Get start-of-file s-point. */
1542static void pt_get_sof(spt_t *pt)
1543{
1544 coord_t coord;
1545
1546 coord.row = coord.column = 1;
1547 sheet_get_cell_pt(doc.sh, &coord, dir_before, pt);
1548}
1549
1550/** Get end-of-file s-point. */
1551static void pt_get_eof(spt_t *pt)
1552{
1553 coord_t coord;
1554 int num_rows;
1555
1556 sheet_get_num_rows(doc.sh, &num_rows);
1557 coord.row = num_rows + 1;
1558 coord.column = 1;
1559
1560 sheet_get_cell_pt(doc.sh, &coord, dir_after, pt);
1561}
1562
1563/** Get start-of-line s-point for given s-point cpt */
1564static void pt_get_sol(spt_t *cpt, spt_t *spt)
1565{
1566 coord_t coord;
1567
1568 spt_get_coord(cpt, &coord);
1569 coord.column = 1;
1570
1571 sheet_get_cell_pt(doc.sh, &coord, dir_before, spt);
1572}
1573
1574/** Get end-of-line s-point for given s-point cpt */
1575static void pt_get_eol(spt_t *cpt, spt_t *ept)
1576{
1577 coord_t coord;
1578 int row_width;
1579
1580 spt_get_coord(cpt, &coord);
1581 sheet_get_row_width(doc.sh, coord.row, &row_width);
1582 coord.column = row_width - 1;
1583
1584 sheet_get_cell_pt(doc.sh, &coord, dir_after, ept);
1585}
1586
1587/** Check whether the spt is at a beginning of a word */
1588static bool pt_is_word_beginning(spt_t *pt)
1589{
1590 spt_t lp, sfp, efp, slp, elp;
1591 coord_t coord;
1592
1593 pt_get_sof(&sfp);
1594 pt_get_eof(&efp);
1595 pt_get_sol(pt, &slp);
1596 pt_get_eol(pt, &elp);
1597
1598 /* the spt is at the beginning or end of the file or line */
1599 if ((spt_cmp(&sfp, pt) == 0) || (spt_cmp(&efp, pt) == 0) ||
1600 (spt_cmp(&slp, pt) == 0) || (spt_cmp(&elp, pt) == 0))
1601 return true;
1602
1603 /* the spt is a delimiter */
1604 if (pt_is_delimiter(pt))
1605 return false;
1606
1607 spt_get_coord(pt, &coord);
1608
1609 coord.column -= 1;
1610 sheet_get_cell_pt(doc.sh, &coord, dir_before, &lp);
1611
1612 return pt_is_delimiter(&lp) ||
1613 (pt_is_punctuation(pt) && !pt_is_punctuation(&lp)) ||
1614 (pt_is_punctuation(&lp) && !pt_is_punctuation(pt));
1615}
1616
1617static char32_t get_first_wchar(const char *str)
1618{
1619 size_t offset = 0;
1620 return str_decode(str, &offset, str_size(str));
1621}
1622
1623static bool pt_is_delimiter(spt_t *pt)
1624{
1625 spt_t rp;
1626 coord_t coord;
1627 char *ch = NULL;
1628
1629 spt_get_coord(pt, &coord);
1630
1631 coord.column += 1;
1632 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
1633
1634 ch = range_get_str(pt, &rp);
1635 if (ch == NULL)
1636 return false;
1637
1638 char32_t first_char = get_first_wchar(ch);
1639 switch (first_char) {
1640 case ' ':
1641 case '\t':
1642 case '\n':
1643 return true;
1644 default:
1645 return false;
1646 }
1647}
1648
1649static bool pt_is_punctuation(spt_t *pt)
1650{
1651 spt_t rp;
1652 coord_t coord;
1653 char *ch = NULL;
1654
1655 spt_get_coord(pt, &coord);
1656
1657 coord.column += 1;
1658 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
1659
1660 ch = range_get_str(pt, &rp);
1661 if (ch == NULL)
1662 return false;
1663
1664 char32_t first_char = get_first_wchar(ch);
1665 switch (first_char) {
1666 case ',':
1667 case '.':
1668 case ';':
1669 case ':':
1670 case '/':
1671 case '?':
1672 case '\\':
1673 case '|':
1674 case '_':
1675 case '+':
1676 case '-':
1677 case '*':
1678 case '=':
1679 case '<':
1680 case '>':
1681 return true;
1682 default:
1683 return false;
1684 }
1685}
1686
1687/** Compare tags. */
1688static int tag_cmp(tag_t const *a, tag_t const *b)
1689{
1690 spt_t pa, pb;
1691
1692 tag_get_pt(a, &pa);
1693 tag_get_pt(b, &pb);
1694
1695 return spt_cmp(&pa, &pb);
1696}
1697
1698/** Compare s-points. */
1699static int spt_cmp(spt_t const *a, spt_t const *b)
1700{
1701 coord_t ca, cb;
1702
1703 spt_get_coord(a, &ca);
1704 spt_get_coord(b, &cb);
1705
1706 return coord_cmp(&ca, &cb);
1707}
1708
1709/** Compare coordinats. */
1710static int coord_cmp(coord_t const *a, coord_t const *b)
1711{
1712 if (a->row - b->row != 0)
1713 return a->row - b->row;
1714
1715 return a->column - b->column;
1716}
1717
1718/** Display text in the status line. */
1719static void status_display(char const *str)
1720{
1721 console_set_pos(con, 0, scr_rows - 1);
1722 console_set_style(con, STYLE_INVERTED);
1723
1724 int pos = -(scr_columns - 3);
1725 printf(" %*s ", pos, str);
1726 console_flush(con);
1727 console_set_style(con, STYLE_NORMAL);
1728
1729 pane.rflags |= REDRAW_CARET;
1730}
1731
1732/** @}
1733 */
Note: See TracBrowser for help on using the repository browser.