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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a1a101d was ad78054, checked in by Martin Sucha <sucha14@…>, 13 years ago

Implement Ctrl+HOME/Ctrl+END in edit.

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