source: mainline/uspace/app/edit/edit.c@ a35b458

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a35b458 was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

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