source: mainline/uspace/app/edit/edit.c@ 03b2b2c

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 03b2b2c was 8f6bffdd, checked in by Vojtech Horky <vojtechhorky@…>, 13 years ago

Jumping over words in edit with ctrl+arrow (thx Tobias Börtitz)

This patch implements

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