source: mainline/uspace/dist/src/c/demos/edit/edit.c@ 08e103d4

Last change on this file since 08e103d4 was 08e103d4, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

Use clearer naming for string length functions

This and the following commit change the names of functions, as well as
their documentation, to use unambiguous terms "bytes" and "code points"
instead of ambiguous terms "size", "length", and "characters".

  • 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_code_points(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_code_points(prompt));
645 str_to_wstr(buffer, max_len + 1, init_value);
646 nc = wstr_code_points(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_bytes(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_bytes(&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_bytes(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_lbytes(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_bytes(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.