source: mainline/uspace/app/edit/edit.c@ 07b7c48

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

Extend console library API to support different event types.

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