source: mainline/uspace/app/edit/edit.c@ 09ab0a9a

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 09ab0a9a was 09ab0a9a, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Fix vertical spacing with new Ccheck revision.

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