source: mainline/uspace/app/edit/edit.c@ 69cf3a4

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

Make sheet_t opaque.

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