source: mainline/uspace/app/edit/edit.c@ 3e6a98c5

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

Standards-compliant boolean type.

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