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

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

Add Go To Line functionality for edit (use Ctrl+L).

  • Property mode set to 100644
File size: 30.5 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
431static void key_handle_movement(unsigned int key, bool select)
432{
433 spt_t pt;
434 spt_t caret_pt;
435 coord_t c_old, c_new;
436 bool had_sel;
437
438 /* Check if we had selection before. */
439 tag_get_pt(&pane.caret_pos, &caret_pt);
440 tag_get_pt(&pane.sel_start, &pt);
441 had_sel = !spt_equal(&caret_pt, &pt);
442
443 switch (key) {
444 case KC_LEFT:
445 caret_move(0, -1, dir_before);
446 break;
447 case KC_RIGHT:
448 caret_move(0, 0, dir_after);
449 break;
450 case KC_UP:
451 caret_move(-1, 0, dir_before);
452 break;
453 case KC_DOWN:
454 caret_move(+1, 0, dir_before);
455 break;
456 case KC_HOME:
457 caret_move(0, -ED_INFTY, dir_before);
458 break;
459 case KC_END:
460 caret_move(0, +ED_INFTY, dir_before);
461 break;
462 case KC_PAGE_UP:
463 caret_move(-pane.rows, 0, dir_before);
464 break;
465 case KC_PAGE_DOWN:
466 caret_move(+pane.rows, 0, dir_before);
467 break;
468 default:
469 break;
470 }
471
472 if (select == false) {
473 /* Move sel_start to the same point as caret. */
474 sheet_remove_tag(&doc.sh, &pane.sel_start);
475 tag_get_pt(&pane.caret_pos, &pt);
476 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
477 }
478
479 if (select) {
480 tag_get_pt(&pane.caret_pos, &pt);
481 spt_get_coord(&caret_pt, &c_old);
482 spt_get_coord(&pt, &c_new);
483
484 if (c_old.row == c_new.row)
485 pane.rflags |= REDRAW_ROW;
486 else
487 pane.rflags |= REDRAW_TEXT;
488
489 } else if (had_sel == true) {
490 /* Redraw because text was unselected. */
491 pane.rflags |= REDRAW_TEXT;
492 }
493}
494
495/** Save the document. */
496static int file_save(char const *fname)
497{
498 spt_t sp, ep;
499 int rc;
500
501 status_display("Saving...");
502 pt_get_sof(&sp);
503 pt_get_eof(&ep);
504
505 rc = file_save_range(fname, &sp, &ep);
506
507 switch (rc) {
508 case EINVAL:
509 status_display("Error opening file!");
510 break;
511 case EIO:
512 status_display("Error writing data!");
513 break;
514 default:
515 status_display("File saved.");
516 break;
517 }
518
519 return rc;
520}
521
522/** Change document name and save. */
523static void file_save_as(void)
524{
525 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
526 char *fname;
527
528 fname = prompt("Save As", old_fname);
529 if (fname == NULL) {
530 status_display("Save cancelled.");
531 return;
532 }
533
534 int rc = file_save(fname);
535 if (rc != EOK)
536 return;
537
538 if (doc.file_name != NULL)
539 free(doc.file_name);
540 doc.file_name = fname;
541}
542
543/** Ask for a string. */
544static char *prompt(char const *prompt, char const *init_value)
545{
546 kbd_event_t ev;
547 char *str;
548 wchar_t buffer[INFNAME_MAX_LEN + 1];
549 int max_len;
550 int nc;
551 bool done;
552
553 asprintf(&str, "%s: %s", prompt, init_value);
554 status_display(str);
555 console_set_pos(con, 1 + str_length(str), scr_rows - 1);
556 free(str);
557
558 console_set_style(con, STYLE_INVERTED);
559
560 max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
561 str_to_wstr(buffer, max_len + 1, init_value);
562 nc = wstr_length(buffer);
563 done = false;
564
565 while (!done) {
566 console_get_kbd_event(con, &ev);
567
568 if (ev.type == KEY_PRESS) {
569 /* Handle key press. */
570 if (((ev.mods & KM_ALT) == 0) &&
571 (ev.mods & KM_CTRL) != 0) {
572 ;
573 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
574 switch (ev.key) {
575 case KC_ESCAPE:
576 return NULL;
577 case KC_BACKSPACE:
578 if (nc > 0) {
579 putchar('\b');
580 console_flush(con);
581 --nc;
582 }
583 break;
584 case KC_ENTER:
585 done = true;
586 break;
587 default:
588 if (ev.c >= 32 && nc < max_len) {
589 putchar(ev.c);
590 console_flush(con);
591 buffer[nc++] = ev.c;
592 }
593 break;
594 }
595 }
596 }
597 }
598
599 buffer[nc] = '\0';
600 str = wstr_to_astr(buffer);
601
602 console_set_style(con, STYLE_NORMAL);
603
604 return str;
605}
606
607/** Insert file at caret position.
608 *
609 * Reads in the contents of a file and inserts them at the current position
610 * of the caret.
611 */
612static int file_insert(char *fname)
613{
614 FILE *f;
615 wchar_t c;
616 char buf[BUF_SIZE];
617 int bcnt;
618 int n_read;
619 size_t off;
620
621 f = fopen(fname, "rt");
622 if (f == NULL)
623 return EINVAL;
624
625 bcnt = 0;
626
627 while (true) {
628 if (bcnt < STR_BOUNDS(1)) {
629 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
630 bcnt += n_read;
631 }
632
633 off = 0;
634 c = str_decode(buf, &off, bcnt);
635 if (c == '\0')
636 break;
637
638 bcnt -= off;
639 memcpy(buf, buf + off, bcnt);
640
641 insert_char(c);
642 }
643
644 fclose(f);
645
646 return EOK;
647}
648
649/** Save a range of text into a file. */
650static int file_save_range(char const *fname, spt_t const *spos,
651 spt_t const *epos)
652{
653 FILE *f;
654 char buf[BUF_SIZE];
655 spt_t sp, bep;
656 size_t bytes, n_written;
657
658 f = fopen(fname, "wt");
659 if (f == NULL)
660 return EINVAL;
661
662 sp = *spos;
663
664 do {
665 sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
666 bytes = str_size(buf);
667
668 n_written = fwrite(buf, 1, bytes, f);
669 if (n_written != bytes) {
670 return EIO;
671 }
672
673 sp = bep;
674 } while (!spt_equal(&bep, epos));
675
676 if (fclose(f) != EOK)
677 return EIO;
678
679 return EOK;
680}
681
682/** Return contents of range as a new string. */
683static char *range_get_str(spt_t const *spos, spt_t const *epos)
684{
685 char *buf;
686 spt_t sp, bep;
687 size_t bytes;
688 size_t buf_size, bpos;
689
690 buf_size = 1;
691
692 buf = malloc(buf_size);
693 if (buf == NULL)
694 return NULL;
695
696 bpos = 0;
697 sp = *spos;
698
699 while (true) {
700 sheet_copy_out(&doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
701 &bep);
702 bytes = str_size(&buf[bpos]);
703 bpos += bytes;
704 sp = bep;
705
706 if (spt_equal(&bep, epos))
707 break;
708
709 buf_size *= 2;
710 buf = realloc(buf, buf_size);
711 if (buf == NULL)
712 return NULL;
713 }
714
715 return buf;
716}
717
718static void pane_text_display(void)
719{
720 int sh_rows, rows;
721
722 sheet_get_num_rows(&doc.sh, &sh_rows);
723 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
724
725 /* Draw rows from the sheet. */
726
727 console_set_pos(con, 0, 0);
728 pane_row_range_display(0, rows);
729
730 /* Clear the remaining rows if file is short. */
731
732 int i;
733 sysarg_t j;
734 for (i = rows; i < pane.rows; ++i) {
735 console_set_pos(con, 0, i);
736 for (j = 0; j < scr_columns; ++j)
737 putchar(' ');
738 console_flush(con);
739 }
740
741 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
742 pane.rflags &= ~REDRAW_ROW;
743}
744
745/** Display just the row where the caret is. */
746static void pane_row_display(void)
747{
748 spt_t caret_pt;
749 coord_t coord;
750 int ridx;
751
752 tag_get_pt(&pane.caret_pos, &caret_pt);
753 spt_get_coord(&caret_pt, &coord);
754
755 ridx = coord.row - pane.sh_row;
756 pane_row_range_display(ridx, ridx + 1);
757 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
758}
759
760static void pane_row_range_display(int r0, int r1)
761{
762 int i, j, fill;
763 spt_t rb, re, dep, pt;
764 coord_t rbc, rec;
765 char row_buf[ROW_BUF_SIZE];
766 wchar_t c;
767 size_t pos, size;
768 int s_column;
769 coord_t csel_start, csel_end, ctmp;
770
771 /* Determine selection start and end. */
772
773 tag_get_pt(&pane.sel_start, &pt);
774 spt_get_coord(&pt, &csel_start);
775
776 tag_get_pt(&pane.caret_pos, &pt);
777 spt_get_coord(&pt, &csel_end);
778
779 if (coord_cmp(&csel_start, &csel_end) > 0) {
780 ctmp = csel_start;
781 csel_start = csel_end;
782 csel_end = ctmp;
783 }
784
785 /* Draw rows from the sheet. */
786
787 console_set_pos(con, 0, 0);
788 for (i = r0; i < r1; ++i) {
789 /* Starting point for row display */
790 rbc.row = pane.sh_row + i;
791 rbc.column = pane.sh_column;
792 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
793
794 /* Ending point for row display */
795 rec.row = pane.sh_row + i;
796 rec.column = pane.sh_column + pane.columns;
797 sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
798
799 /* Copy the text of the row to the buffer. */
800 sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
801
802 /* Display text from the buffer. */
803
804 if (coord_cmp(&csel_start, &rbc) <= 0 &&
805 coord_cmp(&rbc, &csel_end) < 0) {
806 console_flush(con);
807 console_set_style(con, STYLE_SELECTED);
808 console_flush(con);
809 }
810
811 console_set_pos(con, 0, i);
812 size = str_size(row_buf);
813 pos = 0;
814 s_column = pane.sh_column;
815 while (pos < size) {
816 if ((csel_start.row == rbc.row) && (csel_start.column == s_column)) {
817 console_flush(con);
818 console_set_style(con, STYLE_SELECTED);
819 console_flush(con);
820 }
821
822 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
823 console_flush(con);
824 console_set_style(con, STYLE_NORMAL);
825 console_flush(con);
826 }
827
828 c = str_decode(row_buf, &pos, size);
829 if (c != '\t') {
830 printf("%lc", (wint_t) c);
831 s_column += 1;
832 } else {
833 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
834 - s_column;
835
836 for (j = 0; j < fill; ++j)
837 putchar(' ');
838 s_column += fill;
839 }
840 }
841
842 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
843 console_flush(con);
844 console_set_style(con, STYLE_NORMAL);
845 console_flush(con);
846 }
847
848 /* Fill until the end of display area. */
849
850 if ((unsigned)s_column - 1 < scr_columns)
851 fill = scr_columns - (s_column - 1);
852 else
853 fill = 0;
854
855 for (j = 0; j < fill; ++j)
856 putchar(' ');
857 console_flush(con);
858 console_set_style(con, STYLE_NORMAL);
859 }
860
861 pane.rflags |= REDRAW_CARET;
862}
863
864/** Display pane status in the status line. */
865static void pane_status_display(void)
866{
867 spt_t caret_pt;
868 coord_t coord;
869
870 tag_get_pt(&pane.caret_pos, &caret_pt);
871 spt_get_coord(&caret_pt, &coord);
872
873 const char *fname = (doc.file_name != NULL) ? doc.file_name : "<unnamed>";
874
875 console_set_pos(con, 0, scr_rows - 1);
876 console_set_style(con, STYLE_INVERTED);
877 int n = printf(" %d, %d: File '%s'. Ctrl-Q Quit Ctrl-S Save "
878 "Ctrl-E Save As", coord.row, coord.column, fname);
879
880 int pos = scr_columns - 1 - n;
881 printf("%*s", pos, "");
882 console_flush(con);
883 console_set_style(con, STYLE_NORMAL);
884
885 pane.rflags |= REDRAW_CARET;
886}
887
888/** Set cursor to reflect position of the caret. */
889static void pane_caret_display(void)
890{
891 spt_t caret_pt;
892 coord_t coord;
893
894 tag_get_pt(&pane.caret_pos, &caret_pt);
895
896 spt_get_coord(&caret_pt, &coord);
897 console_set_pos(con, coord.column - pane.sh_column,
898 coord.row - pane.sh_row);
899}
900
901/** Insert a character at caret position. */
902static void insert_char(wchar_t c)
903{
904 spt_t pt;
905 char cbuf[STR_BOUNDS(1) + 1];
906 size_t offs;
907
908 tag_get_pt(&pane.caret_pos, &pt);
909
910 offs = 0;
911 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
912 cbuf[offs] = '\0';
913
914 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
915
916 pane.rflags |= REDRAW_ROW;
917 if (c == '\n')
918 pane.rflags |= REDRAW_TEXT;
919}
920
921/** Delete the character before the caret. */
922static void delete_char_before(void)
923{
924 spt_t sp, ep;
925 coord_t coord;
926
927 tag_get_pt(&pane.caret_pos, &ep);
928 spt_get_coord(&ep, &coord);
929
930 coord.column -= 1;
931 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
932
933 (void) sheet_delete(&doc.sh, &sp, &ep);
934
935 pane.rflags |= REDRAW_ROW;
936 if (coord.column < 1)
937 pane.rflags |= REDRAW_TEXT;
938}
939
940/** Delete the character after the caret. */
941static void delete_char_after(void)
942{
943 spt_t sp, ep;
944 coord_t sc, ec;
945
946 tag_get_pt(&pane.caret_pos, &sp);
947 spt_get_coord(&sp, &sc);
948
949 sheet_get_cell_pt(&doc.sh, &sc, dir_after, &ep);
950 spt_get_coord(&ep, &ec);
951
952 (void) sheet_delete(&doc.sh, &sp, &ep);
953
954 pane.rflags |= REDRAW_ROW;
955 if (ec.row != sc.row)
956 pane.rflags |= REDRAW_TEXT;
957}
958
959/** Scroll pane after caret has moved.
960 *
961 * After modifying the position of the caret, this is called to scroll
962 * the pane to ensure that the caret is in the visible area.
963 */
964static void caret_update(void)
965{
966 spt_t pt;
967 coord_t coord;
968
969 tag_get_pt(&pane.caret_pos, &pt);
970 spt_get_coord(&pt, &coord);
971
972 /* Scroll pane vertically. */
973
974 if (coord.row < pane.sh_row) {
975 pane.sh_row = coord.row;
976 pane.rflags |= REDRAW_TEXT;
977 }
978
979 if (coord.row > pane.sh_row + pane.rows - 1) {
980 pane.sh_row = coord.row - pane.rows + 1;
981 pane.rflags |= REDRAW_TEXT;
982 }
983
984 /* Scroll pane horizontally. */
985
986 if (coord.column < pane.sh_column) {
987 pane.sh_column = coord.column;
988 pane.rflags |= REDRAW_TEXT;
989 }
990
991 if (coord.column > pane.sh_column + pane.columns - 1) {
992 pane.sh_column = coord.column - pane.columns + 1;
993 pane.rflags |= REDRAW_TEXT;
994 }
995
996 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
997}
998
999/** Change the caret position.
1000 *
1001 * Moves caret relatively to the current position. Looking at the first
1002 * character cell after the caret and moving by @a drow and @a dcolumn, we get
1003 * to a new character cell, and thus a new character. Then we either go to the
1004 * point before the the character or after it, depending on @a align_dir.
1005 */
1006static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
1007{
1008 spt_t pt;
1009 coord_t coord;
1010 int num_rows;
1011 bool pure_vertical;
1012
1013 tag_get_pt(&pane.caret_pos, &pt);
1014 spt_get_coord(&pt, &coord);
1015 coord.row += drow; coord.column += dcolumn;
1016
1017 /* Clamp coordinates. */
1018 if (drow < 0 && coord.row < 1) coord.row = 1;
1019 if (dcolumn < 0 && coord.column < 1) {
1020 if (coord.row < 2)
1021 coord.column = 1;
1022 else {
1023 coord.row--;
1024 sheet_get_row_width(&doc.sh, coord.row, &coord.column);
1025 }
1026 }
1027 if (drow > 0) {
1028 sheet_get_num_rows(&doc.sh, &num_rows);
1029 if (coord.row > num_rows) coord.row = num_rows;
1030 }
1031
1032 /* For purely vertical movement try attaining @c ideal_column. */
1033 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
1034 if (pure_vertical)
1035 coord.column = pane.ideal_column;
1036
1037 /*
1038 * Select the point before or after the character at the designated
1039 * coordinates. The character can be wider than one cell (e.g. tab).
1040 */
1041 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
1042 sheet_remove_tag(&doc.sh, &pane.caret_pos);
1043 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
1044
1045 /* For non-vertical movement set the new value for @c ideal_column. */
1046 if (!pure_vertical) {
1047 spt_get_coord(&pt, &coord);
1048 pane.ideal_column = coord.column;
1049 }
1050
1051 caret_update();
1052}
1053
1054static void caret_move_word_left(void)
1055{
1056 spt_t pt;
1057
1058 do {
1059 caret_move(0, -1, dir_before);
1060
1061 tag_get_pt(&pane.caret_pos, &pt);
1062
1063 sheet_remove_tag(&doc.sh, &pane.sel_start);
1064 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
1065 } while (!pt_is_word_beginning(&pt));
1066
1067 pane.rflags |= REDRAW_TEXT;
1068}
1069
1070static void caret_move_word_right(void)
1071{
1072 spt_t pt;
1073
1074 do {
1075 caret_move(0, 0, dir_after);
1076
1077 tag_get_pt(&pane.caret_pos, &pt);
1078
1079 sheet_remove_tag(&doc.sh, &pane.sel_start);
1080 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
1081 } while (!pt_is_word_beginning(&pt));
1082
1083 pane.rflags |= REDRAW_TEXT;
1084}
1085
1086/** Change the caret position to a beginning of a given line
1087 */
1088static void caret_move_to_line(int row)
1089{
1090 spt_t pt;
1091 coord_t coord;
1092 int num_rows;
1093
1094 tag_get_pt(&pane.caret_pos, &pt);
1095 spt_get_coord(&pt, &coord);
1096 coord.row = row;
1097 coord.column = 1;
1098
1099 /* Clamp coordinates. */
1100 if (coord.row < 1) coord.row = 1;
1101 sheet_get_num_rows(&doc.sh, &num_rows);
1102 if (coord.row > num_rows) coord.row = num_rows;
1103
1104 /*
1105 * Select the point before the character at the designated
1106 * coordinates. The character can be wider than one cell (e.g. tab).
1107 */
1108 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
1109 sheet_remove_tag(&doc.sh, &pane.caret_pos);
1110 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
1111
1112 /* Set the new value for @c ideal_column. */
1113 spt_get_coord(&pt, &coord);
1114 pane.ideal_column = coord.column;
1115
1116 caret_update();
1117}
1118
1119/** Ask for line and go to it. */
1120static void caret_go_to_line_ask(void)
1121{
1122 char *sline;
1123
1124 sline = prompt("Go to line", "");
1125 if (sline == NULL) {
1126 status_display("Go to line cancelled.");
1127 return;
1128 }
1129
1130 char *endptr;
1131 int line = strtol(sline, &endptr, 10);
1132 if (*endptr != '\0') {
1133 status_display("Invalid number entered.");
1134 return;
1135 }
1136
1137 caret_move_to_line(line);
1138}
1139
1140
1141/** Check for non-empty selection. */
1142static bool selection_active(void)
1143{
1144 return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
1145}
1146
1147static void selection_get_points(spt_t *pa, spt_t *pb)
1148{
1149 spt_t pt;
1150
1151 tag_get_pt(&pane.sel_start, pa);
1152 tag_get_pt(&pane.caret_pos, pb);
1153
1154 if (spt_cmp(pa, pb) > 0) {
1155 pt = *pa;
1156 *pa = *pb;
1157 *pb = pt;
1158 }
1159}
1160
1161/** Delete selected text. */
1162static void selection_delete(void)
1163{
1164 spt_t pa, pb;
1165 coord_t ca, cb;
1166 int rel;
1167
1168 tag_get_pt(&pane.sel_start, &pa);
1169 tag_get_pt(&pane.caret_pos, &pb);
1170 spt_get_coord(&pa, &ca);
1171 spt_get_coord(&pb, &cb);
1172 rel = coord_cmp(&ca, &cb);
1173
1174 if (rel == 0)
1175 return;
1176
1177 if (rel < 0)
1178 sheet_delete(&doc.sh, &pa, &pb);
1179 else
1180 sheet_delete(&doc.sh, &pb, &pa);
1181
1182 if (ca.row == cb.row)
1183 pane.rflags |= REDRAW_ROW;
1184 else
1185 pane.rflags |= REDRAW_TEXT;
1186}
1187
1188/** Select all text in the editor */
1189static void selection_sel_all(void)
1190{
1191 spt_t spt, ept;
1192
1193 pt_get_sof(&spt);
1194 pt_get_eof(&ept);
1195
1196 selection_sel_range(spt, ept);
1197}
1198
1199/** Select select all text in a given range with the given direction */
1200static void selection_sel_range(spt_t pa, spt_t pb)
1201{
1202 sheet_remove_tag(&doc.sh, &pane.sel_start);
1203 sheet_place_tag(&doc.sh, &pa, &pane.sel_start);
1204 sheet_remove_tag(&doc.sh, &pane.caret_pos);
1205 sheet_place_tag(&doc.sh, &pb, &pane.caret_pos);
1206
1207 pane.rflags |= REDRAW_TEXT;
1208 caret_update();
1209}
1210
1211/** Add the previous word to the selection */
1212static void selection_sel_prev_word(void)
1213{
1214 spt_t cpt, wpt, spt, ept;
1215
1216 selection_get_points(&spt, &ept);
1217
1218 tag_get_pt(&pane.caret_pos, &cpt);
1219 caret_move_word_left();
1220 tag_get_pt(&pane.caret_pos, &wpt);
1221
1222 if (spt_cmp(&spt, &cpt) == 0)
1223 selection_sel_range(ept, wpt);
1224 else
1225 selection_sel_range(spt, wpt);
1226}
1227
1228/** Add the next word to the selection */
1229static void selection_sel_next_word(void)
1230{
1231 spt_t cpt, wpt, spt, ept;
1232
1233 selection_get_points(&spt, &ept);
1234
1235 tag_get_pt(&pane.caret_pos, &cpt);
1236 caret_move_word_right();
1237 tag_get_pt(&pane.caret_pos, &wpt);
1238
1239 if (spt_cmp(&ept, &cpt) == 0)
1240 selection_sel_range(spt, wpt);
1241 else
1242 selection_sel_range(ept, wpt);
1243}
1244
1245static void selection_copy(void)
1246{
1247 spt_t pa, pb;
1248 char *str;
1249
1250 selection_get_points(&pa, &pb);
1251 str = range_get_str(&pa, &pb);
1252 if (str == NULL || clipboard_put_str(str) != EOK) {
1253 status_display("Copying to clipboard failed!");
1254 }
1255 free(str);
1256}
1257
1258static void insert_clipboard_data(void)
1259{
1260 char *str;
1261 size_t off;
1262 wchar_t c;
1263 int rc;
1264
1265 rc = clipboard_get_str(&str);
1266 if (rc != EOK || str == NULL)
1267 return;
1268
1269 off = 0;
1270
1271 while (true) {
1272 c = str_decode(str, &off, STR_NO_LIMIT);
1273 if (c == '\0')
1274 break;
1275
1276 insert_char(c);
1277 }
1278
1279 free(str);
1280}
1281
1282/** Get start-of-file s-point. */
1283static void pt_get_sof(spt_t *pt)
1284{
1285 coord_t coord;
1286
1287 coord.row = coord.column = 1;
1288 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
1289}
1290
1291/** Get end-of-file s-point. */
1292static void pt_get_eof(spt_t *pt)
1293{
1294 coord_t coord;
1295 int num_rows;
1296
1297 sheet_get_num_rows(&doc.sh, &num_rows);
1298 coord.row = num_rows + 1;
1299 coord.column = 1;
1300
1301 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
1302}
1303
1304/** Get start-of-line s-point for given s-point cpt */
1305static void pt_get_sol(spt_t *cpt, spt_t *spt)
1306{
1307 coord_t coord;
1308
1309 spt_get_coord(cpt, &coord);
1310 coord.column = 1;
1311
1312 sheet_get_cell_pt(&doc.sh, &coord, dir_before, spt);
1313}
1314
1315/** Get end-of-line s-point for given s-point cpt */
1316static void pt_get_eol(spt_t *cpt, spt_t *ept)
1317{
1318 coord_t coord;
1319 int row_width;
1320
1321 spt_get_coord(cpt, &coord);
1322 sheet_get_row_width(&doc.sh, coord.row, &row_width);
1323 coord.column = row_width - 1;
1324
1325 sheet_get_cell_pt(&doc.sh, &coord, dir_after, ept);
1326}
1327
1328/** Check whether the spt is at a beginning of a word */
1329static bool pt_is_word_beginning(spt_t *pt)
1330{
1331 spt_t lp, sfp, efp, slp, elp;
1332 coord_t coord;
1333
1334 pt_get_sof(&sfp);
1335 pt_get_eof(&efp);
1336 pt_get_sol(pt, &slp);
1337 pt_get_eol(pt, &elp);
1338
1339 /* the spt is at the beginning or end of the file or line */
1340 if ((spt_cmp(&sfp, pt) == 0) || (spt_cmp(&efp, pt) == 0)
1341 || (spt_cmp(&slp, pt) == 0) || (spt_cmp(&elp, pt) == 0))
1342 return true;
1343
1344 /* the spt is a delimiter */
1345 if (pt_is_delimiter(pt))
1346 return false;
1347
1348 spt_get_coord(pt, &coord);
1349
1350 coord.column -= 1;
1351 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &lp);
1352
1353 return pt_is_delimiter(&lp)
1354 || (pt_is_punctuation(pt) && !pt_is_punctuation(&lp))
1355 || (pt_is_punctuation(&lp) && !pt_is_punctuation(pt));
1356}
1357
1358static wchar_t get_first_wchar(const char *str)
1359{
1360 size_t offset = 0;
1361 return str_decode(str, &offset, str_size(str));
1362}
1363
1364static bool pt_is_delimiter(spt_t *pt)
1365{
1366 spt_t rp;
1367 coord_t coord;
1368 char *ch = NULL;
1369
1370 spt_get_coord(pt, &coord);
1371
1372 coord.column += 1;
1373 sheet_get_cell_pt(&doc.sh, &coord, dir_after, &rp);
1374
1375 ch = range_get_str(pt, &rp);
1376 if (ch == NULL)
1377 return false;
1378
1379 wchar_t first_char = get_first_wchar(ch);
1380 switch(first_char) {
1381 case ' ':
1382 case '\t':
1383 case '\n':
1384 return true;
1385 default:
1386 return false;
1387 }
1388}
1389
1390static bool pt_is_punctuation(spt_t *pt)
1391{
1392 spt_t rp;
1393 coord_t coord;
1394 char *ch = NULL;
1395
1396 spt_get_coord(pt, &coord);
1397
1398 coord.column += 1;
1399 sheet_get_cell_pt(&doc.sh, &coord, dir_after, &rp);
1400
1401 ch = range_get_str(pt, &rp);
1402 if (ch == NULL)
1403 return false;
1404
1405 wchar_t first_char = get_first_wchar(ch);
1406 switch(first_char) {
1407 case ',':
1408 case '.':
1409 case ';':
1410 case ':':
1411 case '/':
1412 case '?':
1413 case '\\':
1414 case '|':
1415 case '_':
1416 case '+':
1417 case '-':
1418 case '*':
1419 case '=':
1420 case '<':
1421 case '>':
1422 return true;
1423 default:
1424 return false;
1425 }
1426}
1427
1428/** Compare tags. */
1429static int tag_cmp(tag_t const *a, tag_t const *b)
1430{
1431 spt_t pa, pb;
1432
1433 tag_get_pt(a, &pa);
1434 tag_get_pt(b, &pb);
1435
1436 return spt_cmp(&pa, &pb);
1437}
1438
1439/** Compare s-points. */
1440static int spt_cmp(spt_t const *a, spt_t const *b)
1441{
1442 coord_t ca, cb;
1443
1444 spt_get_coord(a, &ca);
1445 spt_get_coord(b, &cb);
1446
1447 return coord_cmp(&ca, &cb);
1448}
1449
1450/** Compare coordinats. */
1451static int coord_cmp(coord_t const *a, coord_t const *b)
1452{
1453 if (a->row - b->row != 0)
1454 return a->row - b->row;
1455
1456 return a->column - b->column;
1457}
1458
1459/** Display text in the status line. */
1460static void status_display(char const *str)
1461{
1462 console_set_pos(con, 0, scr_rows - 1);
1463 console_set_style(con, STYLE_INVERTED);
1464
1465 int pos = -(scr_columns - 3);
1466 printf(" %*s ", pos, str);
1467 console_flush(con);
1468 console_set_style(con, STYLE_NORMAL);
1469
1470 pane.rflags |= REDRAW_CARET;
1471}
1472
1473/** @}
1474 */
Note: See TracBrowser for help on using the repository browser.