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

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

Go to Line needs to properly update sel_start tag. It should also stay in the current column.

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