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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since caad59a was a000878c, checked in by Martin Decky <martin@…>, 15 years ago

make sure that all statically allocated strings are declared as "const char *"
and are treated as read-only

  • Property mode set to 100644
File size: 24.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/color.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 int con;
97static doc_t doc;
98static bool done;
99static pane_t pane;
100static bool cursor_visible;
101
102static int scr_rows, scr_columns;
103
104#define ROW_BUF_SIZE 4096
105#define BUF_SIZE 64
106#define TAB_WIDTH 8
107#define ED_INFTY 65536
108
109/** Maximum filename length that can be entered. */
110#define INFNAME_MAX_LEN 128
111
112static void cursor_show(void);
113static void cursor_hide(void);
114static void cursor_setvis(bool visible);
115
116static void key_handle_unmod(console_event_t const *ev);
117static void key_handle_ctrl(console_event_t const *ev);
118static void key_handle_shift(console_event_t const *ev);
119static void key_handle_movement(unsigned int key, bool shift);
120
121static int file_save(char const *fname);
122static void file_save_as(void);
123static int file_insert(char *fname);
124static int file_save_range(char const *fname, spt_t const *spos,
125 spt_t const *epos);
126static char *filename_prompt(char const *prompt, char const *init_value);
127static char *range_get_str(spt_t const *spos, spt_t const *epos);
128
129static void pane_text_display(void);
130static void pane_row_display(void);
131static void pane_row_range_display(int r0, int r1);
132static void pane_status_display(void);
133static void pane_caret_display(void);
134
135static void insert_char(wchar_t c);
136static void delete_char_before(void);
137static void delete_char_after(void);
138static void caret_update(void);
139static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
140
141static bool selection_active(void);
142static void selection_sel_all(void);
143static void selection_get_points(spt_t *pa, spt_t *pb);
144static void selection_delete(void);
145static void selection_copy(void);
146static void insert_clipboard_data(void);
147
148static void pt_get_sof(spt_t *pt);
149static void pt_get_eof(spt_t *pt);
150static int tag_cmp(tag_t const *a, tag_t const *b);
151static int spt_cmp(spt_t const *a, spt_t const *b);
152static int coord_cmp(coord_t const *a, coord_t const *b);
153
154static void status_display(char const *str);
155
156
157int main(int argc, char *argv[])
158{
159 console_event_t ev;
160 coord_t coord;
161 bool new_file;
162
163 spt_t pt;
164
165 con = fphone(stdout);
166 console_clear(con);
167
168 console_get_size(con, &scr_columns, &scr_rows);
169
170 pane.rows = scr_rows - 1;
171 pane.columns = scr_columns;
172 pane.sh_row = 1;
173 pane.sh_column = 1;
174
175 /* Start with an empty sheet. */
176 sheet_init(&doc.sh);
177
178 /* Place caret at the beginning of file. */
179 coord.row = coord.column = 1;
180 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
181 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
182 pane.ideal_column = coord.column;
183
184 if (argc == 2) {
185 doc.file_name = str_dup(argv[1]);
186 } else if (argc > 1) {
187 printf("Invalid arguments.\n");
188 return -2;
189 } else {
190 doc.file_name = NULL;
191 }
192
193 new_file = false;
194
195 if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
196 new_file = true;
197
198 /* Move to beginning of file. */
199 caret_move(-ED_INFTY, -ED_INFTY, dir_before);
200
201 /* Place selection start tag. */
202 tag_get_pt(&pane.caret_pos, &pt);
203 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
204
205 /* Initial display */
206 cursor_visible = true;
207
208 cursor_hide();
209 console_clear(con);
210 pane_text_display();
211 pane_status_display();
212 if (new_file && doc.file_name != NULL)
213 status_display("File not found. Starting empty file.");
214 pane_caret_display();
215 cursor_show();
216
217 done = false;
218
219 while (!done) {
220 console_get_event(con, &ev);
221 pane.rflags = 0;
222
223 if (ev.type == KEY_PRESS) {
224 /* Handle key press. */
225 if (((ev.mods & KM_ALT) == 0) &&
226 ((ev.mods & KM_SHIFT) == 0) &&
227 (ev.mods & KM_CTRL) != 0) {
228 key_handle_ctrl(&ev);
229 } else if (((ev.mods & KM_ALT) == 0) &&
230 ((ev.mods & KM_CTRL) == 0) &&
231 (ev.mods & KM_SHIFT) != 0) {
232 key_handle_shift(&ev);
233 } else if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
234 key_handle_unmod(&ev);
235 }
236 }
237
238 /* Redraw as necessary. */
239
240 cursor_hide();
241
242 if (pane.rflags & REDRAW_TEXT)
243 pane_text_display();
244 if (pane.rflags & REDRAW_ROW)
245 pane_row_display();
246 if (pane.rflags & REDRAW_STATUS)
247 pane_status_display();
248 if (pane.rflags & REDRAW_CARET)
249 pane_caret_display();
250
251 cursor_show();
252 }
253
254 console_clear(con);
255
256 return 0;
257}
258
259static void cursor_show(void)
260{
261 cursor_setvis(true);
262}
263
264static void cursor_hide(void)
265{
266 cursor_setvis(false);
267}
268
269static void cursor_setvis(bool visible)
270{
271 if (cursor_visible != visible) {
272 console_cursor_visibility(con, visible);
273 cursor_visible = visible;
274 }
275}
276
277/** Handle key without modifier. */
278static void key_handle_unmod(console_event_t const *ev)
279{
280 switch (ev->key) {
281 case KC_ENTER:
282 selection_delete();
283 insert_char('\n');
284 caret_update();
285 break;
286 case KC_LEFT:
287 case KC_RIGHT:
288 case KC_UP:
289 case KC_DOWN:
290 case KC_HOME:
291 case KC_END:
292 case KC_PAGE_UP:
293 case KC_PAGE_DOWN:
294 key_handle_movement(ev->key, false);
295 break;
296 case KC_BACKSPACE:
297 if (selection_active())
298 selection_delete();
299 else
300 delete_char_before();
301 caret_update();
302 break;
303 case KC_DELETE:
304 if (selection_active())
305 selection_delete();
306 else
307 delete_char_after();
308 caret_update();
309 break;
310 default:
311 if (ev->c >= 32 || ev->c == '\t') {
312 selection_delete();
313 insert_char(ev->c);
314 caret_update();
315 }
316 break;
317 }
318}
319
320/** Handle Shift-key combination. */
321static void key_handle_shift(console_event_t const *ev)
322{
323 switch (ev->key) {
324 case KC_LEFT:
325 case KC_RIGHT:
326 case KC_UP:
327 case KC_DOWN:
328 case KC_HOME:
329 case KC_END:
330 case KC_PAGE_UP:
331 case KC_PAGE_DOWN:
332 key_handle_movement(ev->key, true);
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 Ctrl-key combination. */
345static void key_handle_ctrl(console_event_t const *ev)
346{
347 switch (ev->key) {
348 case KC_Q:
349 done = true;
350 break;
351 case KC_S:
352 if (doc.file_name != NULL)
353 file_save(doc.file_name);
354 else
355 file_save_as();
356 break;
357 case KC_E:
358 file_save_as();
359 break;
360 case KC_C:
361 selection_copy();
362 break;
363 case KC_V:
364 selection_delete();
365 insert_clipboard_data();
366 pane.rflags |= REDRAW_TEXT;
367 caret_update();
368 break;
369 case KC_X:
370 selection_copy();
371 selection_delete();
372 pane.rflags |= REDRAW_TEXT;
373 caret_update();
374 break;
375 case KC_A:
376 selection_sel_all();
377 break;
378 default:
379 break;
380 }
381}
382
383static void key_handle_movement(unsigned int key, bool select)
384{
385 spt_t pt;
386 spt_t caret_pt;
387 coord_t c_old, c_new;
388 bool had_sel;
389
390 /* Check if we had selection before. */
391 tag_get_pt(&pane.caret_pos, &caret_pt);
392 tag_get_pt(&pane.sel_start, &pt);
393 had_sel = !spt_equal(&caret_pt, &pt);
394
395 switch (key) {
396 case KC_LEFT:
397 caret_move(0, -1, dir_before);
398 break;
399 case KC_RIGHT:
400 caret_move(0, 0, dir_after);
401 break;
402 case KC_UP:
403 caret_move(-1, 0, dir_before);
404 break;
405 case KC_DOWN:
406 caret_move(+1, 0, dir_before);
407 break;
408 case KC_HOME:
409 caret_move(0, -ED_INFTY, dir_before);
410 break;
411 case KC_END:
412 caret_move(0, +ED_INFTY, dir_before);
413 break;
414 case KC_PAGE_UP:
415 caret_move(-pane.rows, 0, dir_before);
416 break;
417 case KC_PAGE_DOWN:
418 caret_move(+pane.rows, 0, dir_before);
419 break;
420 default:
421 break;
422 }
423
424 if (select == false) {
425 /* Move sel_start to the same point as caret. */
426 sheet_remove_tag(&doc.sh, &pane.sel_start);
427 tag_get_pt(&pane.caret_pos, &pt);
428 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
429 }
430
431 if (select) {
432 tag_get_pt(&pane.caret_pos, &pt);
433 spt_get_coord(&caret_pt, &c_old);
434 spt_get_coord(&pt, &c_new);
435
436 if (c_old.row == c_new.row)
437 pane.rflags |= REDRAW_ROW;
438 else
439 pane.rflags |= REDRAW_TEXT;
440
441 } else if (had_sel == true) {
442 /* Redraw because text was unselected. */
443 pane.rflags |= REDRAW_TEXT;
444 }
445}
446
447/** Save the document. */
448static int file_save(char const *fname)
449{
450 spt_t sp, ep;
451 int rc;
452
453 status_display("Saving...");
454 pt_get_sof(&sp);
455 pt_get_eof(&ep);
456
457 rc = file_save_range(fname, &sp, &ep);
458
459 switch (rc) {
460 case EINVAL:
461 status_display("Error opening file!");
462 break;
463 case EIO:
464 status_display("Error writing data!");
465 break;
466 default:
467 status_display("File saved.");
468 break;
469 }
470
471 return rc;
472}
473
474/** Change document name and save. */
475static void file_save_as(void)
476{
477 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
478 char *fname;
479
480 fname = filename_prompt("Save As", old_fname);
481 if (fname == NULL) {
482 status_display("Save cancelled.");
483 return;
484 }
485
486 int rc = file_save(fname);
487 if (rc != EOK)
488 return;
489
490 if (doc.file_name != NULL)
491 free(doc.file_name);
492 doc.file_name = fname;
493}
494
495/** Ask for a file name. */
496static char *filename_prompt(char const *prompt, char const *init_value)
497{
498 console_event_t ev;
499 char *str;
500 wchar_t buffer[INFNAME_MAX_LEN + 1];
501 int max_len;
502 int nc;
503 bool done;
504
505 asprintf(&str, "%s: %s", prompt, init_value);
506 status_display(str);
507 console_goto(con, 1 + str_length(str), scr_rows - 1);
508 free(str);
509
510 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
511
512 max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
513 str_to_wstr(buffer, max_len + 1, init_value);
514 nc = wstr_length(buffer);
515 done = false;
516
517 while (!done) {
518 console_get_event(con, &ev);
519
520 if (ev.type == KEY_PRESS) {
521 /* Handle key press. */
522 if (((ev.mods & KM_ALT) == 0) &&
523 (ev.mods & KM_CTRL) != 0) {
524 ;
525 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
526 switch (ev.key) {
527 case KC_ESCAPE:
528 return NULL;
529 case KC_BACKSPACE:
530 if (nc > 0) {
531 putchar('\b');
532 fflush(stdout);
533 --nc;
534 }
535 break;
536 case KC_ENTER:
537 done = true;
538 break;
539 default:
540 if (ev.c >= 32 && nc < max_len) {
541 putchar(ev.c);
542 fflush(stdout);
543 buffer[nc++] = ev.c;
544 }
545 break;
546 }
547 }
548 }
549 }
550
551 buffer[nc] = '\0';
552 str = wstr_to_astr(buffer);
553
554 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
555
556 return str;
557}
558
559/** Insert file at caret position.
560 *
561 * Reads in the contents of a file and inserts them at the current position
562 * of the caret.
563 */
564static int file_insert(char *fname)
565{
566 FILE *f;
567 wchar_t c;
568 char buf[BUF_SIZE];
569 int bcnt;
570 int n_read;
571 size_t off;
572
573 f = fopen(fname, "rt");
574 if (f == NULL)
575 return EINVAL;
576
577 bcnt = 0;
578
579 while (true) {
580 if (bcnt < STR_BOUNDS(1)) {
581 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
582 bcnt += n_read;
583 }
584
585 off = 0;
586 c = str_decode(buf, &off, bcnt);
587 if (c == '\0')
588 break;
589
590 bcnt -= off;
591 memcpy(buf, buf + off, bcnt);
592
593 insert_char(c);
594 }
595
596 fclose(f);
597
598 return EOK;
599}
600
601/** Save a range of text into a file. */
602static int file_save_range(char const *fname, spt_t const *spos,
603 spt_t const *epos)
604{
605 FILE *f;
606 char buf[BUF_SIZE];
607 spt_t sp, bep;
608 size_t bytes, n_written;
609
610 f = fopen(fname, "wt");
611 if (f == NULL)
612 return EINVAL;
613
614 sp = *spos;
615
616 do {
617 sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
618 bytes = str_size(buf);
619
620 n_written = fwrite(buf, 1, bytes, f);
621 if (n_written != bytes) {
622 return EIO;
623 }
624
625 sp = bep;
626 } while (!spt_equal(&bep, epos));
627
628 if (fclose(f) != EOK)
629 return EIO;
630
631 return EOK;
632}
633
634/** Return contents of range as a new string. */
635static char *range_get_str(spt_t const *spos, spt_t const *epos)
636{
637 char *buf;
638 spt_t sp, bep;
639 size_t bytes;
640 size_t buf_size, bpos;
641
642 buf_size = 1;
643
644 buf = malloc(buf_size);
645 if (buf == NULL)
646 return NULL;
647
648 bpos = 0;
649 sp = *spos;
650
651 while (true) {
652 sheet_copy_out(&doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
653 &bep);
654 bytes = str_size(&buf[bpos]);
655 bpos += bytes;
656 sp = bep;
657
658 if (spt_equal(&bep, epos))
659 break;
660
661 buf_size *= 2;
662 buf = realloc(buf, buf_size);
663 if (buf == NULL)
664 return NULL;
665 }
666
667 return buf;
668}
669
670static void pane_text_display(void)
671{
672 int sh_rows, rows;
673 int i, j;
674
675 sheet_get_num_rows(&doc.sh, &sh_rows);
676 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
677
678 /* Draw rows from the sheet. */
679
680 console_goto(con, 0, 0);
681 pane_row_range_display(0, rows);
682
683 /* Clear the remaining rows if file is short. */
684
685 for (i = rows; i < pane.rows; ++i) {
686 console_goto(con, 0, i);
687 for (j = 0; j < scr_columns; ++j)
688 putchar(' ');
689 fflush(stdout);
690 }
691
692 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
693 pane.rflags &= ~REDRAW_ROW;
694}
695
696/** Display just the row where the caret is. */
697static void pane_row_display(void)
698{
699 spt_t caret_pt;
700 coord_t coord;
701 int ridx;
702
703 tag_get_pt(&pane.caret_pos, &caret_pt);
704 spt_get_coord(&caret_pt, &coord);
705
706 ridx = coord.row - pane.sh_row;
707 pane_row_range_display(ridx, ridx + 1);
708 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
709}
710
711static void pane_row_range_display(int r0, int r1)
712{
713 int i, j, fill;
714 spt_t rb, re, dep, pt;
715 coord_t rbc, rec;
716 char row_buf[ROW_BUF_SIZE];
717 wchar_t c;
718 size_t pos, size;
719 int s_column;
720 coord_t csel_start, csel_end, ctmp;
721
722 /* Determine selection start and end. */
723
724 tag_get_pt(&pane.sel_start, &pt);
725 spt_get_coord(&pt, &csel_start);
726
727 tag_get_pt(&pane.caret_pos, &pt);
728 spt_get_coord(&pt, &csel_end);
729
730 if (coord_cmp(&csel_start, &csel_end) > 0) {
731 ctmp = csel_start;
732 csel_start = csel_end;
733 csel_end = ctmp;
734 }
735
736 /* Draw rows from the sheet. */
737
738 console_goto(con, 0, 0);
739 for (i = r0; i < r1; ++i) {
740 /* Starting point for row display */
741 rbc.row = pane.sh_row + i;
742 rbc.column = pane.sh_column;
743 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
744
745 /* Ending point for row display */
746 rec.row = pane.sh_row + i;
747 rec.column = pane.sh_column + pane.columns;
748 sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
749
750 /* Copy the text of the row to the buffer. */
751 sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
752
753 /* Display text from the buffer. */
754
755 if (coord_cmp(&csel_start, &rbc) <= 0 &&
756 coord_cmp(&rbc, &csel_end) < 0) {
757 fflush(stdout);
758 console_set_color(con, COLOR_BLACK, COLOR_RED, 0);
759 fflush(stdout);
760 }
761
762 console_goto(con, 0, i);
763 size = str_size(row_buf);
764 pos = 0;
765 s_column = pane.sh_column;
766 while (pos < size) {
767 if ((csel_start.row == rbc.row) && (csel_start.column == s_column)) {
768 fflush(stdout);
769 console_set_color(con, COLOR_BLACK, COLOR_RED, 0);
770 fflush(stdout);
771 }
772
773 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
774 fflush(stdout);
775 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
776 fflush(stdout);
777 }
778
779 c = str_decode(row_buf, &pos, size);
780 if (c != '\t') {
781 printf("%lc", c);
782 s_column += 1;
783 } else {
784 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
785 - s_column;
786
787 for (j = 0; j < fill; ++j)
788 putchar(' ');
789 s_column += fill;
790 }
791 }
792
793 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
794 fflush(stdout);
795 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
796 fflush(stdout);
797 }
798
799 /* Fill until the end of display area. */
800
801 if (str_length(row_buf) < (unsigned) scr_columns)
802 fill = scr_columns - str_length(row_buf);
803 else
804 fill = 0;
805
806 for (j = 0; j < fill; ++j)
807 putchar(' ');
808 fflush(stdout);
809 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
810 }
811
812 pane.rflags |= REDRAW_CARET;
813}
814
815/** Display pane status in the status line. */
816static void pane_status_display(void)
817{
818 spt_t caret_pt;
819 coord_t coord;
820
821 tag_get_pt(&pane.caret_pos, &caret_pt);
822 spt_get_coord(&caret_pt, &coord);
823
824 const char *fname = (doc.file_name != NULL) ? doc.file_name : "<unnamed>";
825
826 console_goto(con, 0, scr_rows - 1);
827 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
828 int n = printf(" %d, %d: File '%s'. Ctrl-Q Quit Ctrl-S Save "
829 "Ctrl-E Save As", coord.row, coord.column, fname);
830 printf("%*s", scr_columns - 1 - n, "");
831 fflush(stdout);
832 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
833
834 pane.rflags |= REDRAW_CARET;
835}
836
837/** Set cursor to reflect position of the caret. */
838static void pane_caret_display(void)
839{
840 spt_t caret_pt;
841 coord_t coord;
842
843 tag_get_pt(&pane.caret_pos, &caret_pt);
844
845 spt_get_coord(&caret_pt, &coord);
846 console_goto(con, coord.column - pane.sh_column,
847 coord.row - pane.sh_row);
848}
849
850/** Insert a character at caret position. */
851static void insert_char(wchar_t c)
852{
853 spt_t pt;
854 char cbuf[STR_BOUNDS(1) + 1];
855 size_t offs;
856
857 tag_get_pt(&pane.caret_pos, &pt);
858
859 offs = 0;
860 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
861 cbuf[offs] = '\0';
862
863 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
864
865 pane.rflags |= REDRAW_ROW;
866 if (c == '\n')
867 pane.rflags |= REDRAW_TEXT;
868}
869
870/** Delete the character before the caret. */
871static void delete_char_before(void)
872{
873 spt_t sp, ep;
874 coord_t coord;
875
876 tag_get_pt(&pane.caret_pos, &ep);
877 spt_get_coord(&ep, &coord);
878
879 coord.column -= 1;
880 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
881
882 (void) sheet_delete(&doc.sh, &sp, &ep);
883
884 pane.rflags |= REDRAW_ROW;
885 if (coord.column < 1)
886 pane.rflags |= REDRAW_TEXT;
887}
888
889/** Delete the character after the caret. */
890static void delete_char_after(void)
891{
892 spt_t sp, ep;
893 coord_t sc, ec;
894
895 tag_get_pt(&pane.caret_pos, &sp);
896 spt_get_coord(&sp, &sc);
897
898 sheet_get_cell_pt(&doc.sh, &sc, dir_after, &ep);
899 spt_get_coord(&ep, &ec);
900
901 (void) sheet_delete(&doc.sh, &sp, &ep);
902
903 pane.rflags |= REDRAW_ROW;
904 if (ec.row != sc.row)
905 pane.rflags |= REDRAW_TEXT;
906}
907
908/** Scroll pane after caret has moved.
909 *
910 * After modifying the position of the caret, this is called to scroll
911 * the pane to ensure that the caret is in the visible area.
912 */
913static void caret_update(void)
914{
915 spt_t pt;
916 coord_t coord;
917
918 tag_get_pt(&pane.caret_pos, &pt);
919 spt_get_coord(&pt, &coord);
920
921 /* Scroll pane vertically. */
922
923 if (coord.row < pane.sh_row) {
924 pane.sh_row = coord.row;
925 pane.rflags |= REDRAW_TEXT;
926 }
927
928 if (coord.row > pane.sh_row + pane.rows - 1) {
929 pane.sh_row = coord.row - pane.rows + 1;
930 pane.rflags |= REDRAW_TEXT;
931 }
932
933 /* Scroll pane horizontally. */
934
935 if (coord.column < pane.sh_column) {
936 pane.sh_column = coord.column;
937 pane.rflags |= REDRAW_TEXT;
938 }
939
940 if (coord.column > pane.sh_column + pane.columns - 1) {
941 pane.sh_column = coord.column - pane.columns + 1;
942 pane.rflags |= REDRAW_TEXT;
943 }
944
945 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
946}
947
948/** Change the caret position.
949 *
950 * Moves caret relatively to the current position. Looking at the first
951 * character cell after the caret and moving by @a drow and @a dcolumn, we get
952 * to a new character cell, and thus a new character. Then we either go to the
953 * point before the the character or after it, depending on @a align_dir.
954 */
955static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
956{
957 spt_t pt;
958 coord_t coord;
959 int num_rows;
960 bool pure_vertical;
961
962 tag_get_pt(&pane.caret_pos, &pt);
963 spt_get_coord(&pt, &coord);
964 coord.row += drow; coord.column += dcolumn;
965
966 /* Clamp coordinates. */
967 if (drow < 0 && coord.row < 1) coord.row = 1;
968 if (dcolumn < 0 && coord.column < 1) coord.column = 1;
969 if (drow > 0) {
970 sheet_get_num_rows(&doc.sh, &num_rows);
971 if (coord.row > num_rows) coord.row = num_rows;
972 }
973
974 /* For purely vertical movement try attaining @c ideal_column. */
975 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
976 if (pure_vertical)
977 coord.column = pane.ideal_column;
978
979 /*
980 * Select the point before or after the character at the designated
981 * coordinates. The character can be wider than one cell (e.g. tab).
982 */
983 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
984 sheet_remove_tag(&doc.sh, &pane.caret_pos);
985 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
986
987 /* For non-vertical movement set the new value for @c ideal_column. */
988 if (!pure_vertical) {
989 spt_get_coord(&pt, &coord);
990 pane.ideal_column = coord.column;
991 }
992
993 caret_update();
994}
995
996/** Check for non-empty selection. */
997static bool selection_active(void)
998{
999 return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
1000}
1001
1002static void selection_get_points(spt_t *pa, spt_t *pb)
1003{
1004 spt_t pt;
1005
1006 tag_get_pt(&pane.sel_start, pa);
1007 tag_get_pt(&pane.caret_pos, pb);
1008
1009 if (spt_cmp(pa, pb) > 0) {
1010 pt = *pa;
1011 *pa = *pb;
1012 *pb = pt;
1013 }
1014}
1015
1016/** Delete selected text. */
1017static void selection_delete(void)
1018{
1019 spt_t pa, pb;
1020 coord_t ca, cb;
1021 int rel;
1022
1023 tag_get_pt(&pane.sel_start, &pa);
1024 tag_get_pt(&pane.caret_pos, &pb);
1025 spt_get_coord(&pa, &ca);
1026 spt_get_coord(&pb, &cb);
1027 rel = coord_cmp(&ca, &cb);
1028
1029 if (rel == 0)
1030 return;
1031
1032 if (rel < 0)
1033 sheet_delete(&doc.sh, &pa, &pb);
1034 else
1035 sheet_delete(&doc.sh, &pb, &pa);
1036
1037 if (ca.row == cb.row)
1038 pane.rflags |= REDRAW_ROW;
1039 else
1040 pane.rflags |= REDRAW_TEXT;
1041}
1042
1043static void selection_sel_all(void)
1044{
1045 spt_t spt, ept;
1046
1047 pt_get_sof(&spt);
1048 pt_get_eof(&ept);
1049 sheet_remove_tag(&doc.sh, &pane.sel_start);
1050 sheet_place_tag(&doc.sh, &spt, &pane.sel_start);
1051 sheet_remove_tag(&doc.sh, &pane.caret_pos);
1052 sheet_place_tag(&doc.sh, &ept, &pane.caret_pos);
1053
1054 pane.rflags |= REDRAW_TEXT;
1055 caret_update();
1056}
1057
1058static void selection_copy(void)
1059{
1060 spt_t pa, pb;
1061 char *str;
1062
1063 selection_get_points(&pa, &pb);
1064 str = range_get_str(&pa, &pb);
1065 if (str == NULL || clipboard_put_str(str) != EOK) {
1066 status_display("Copying to clipboard failed!");
1067 }
1068 free(str);
1069}
1070
1071static void insert_clipboard_data(void)
1072{
1073 char *str;
1074 size_t off;
1075 wchar_t c;
1076 int rc;
1077
1078 rc = clipboard_get_str(&str);
1079 if (rc != EOK || str == NULL)
1080 return;
1081
1082 off = 0;
1083
1084 while (true) {
1085 c = str_decode(str, &off, STR_NO_LIMIT);
1086 if (c == '\0')
1087 break;
1088
1089 insert_char(c);
1090 }
1091
1092 free(str);
1093}
1094
1095/** Get start-of-file s-point. */
1096static void pt_get_sof(spt_t *pt)
1097{
1098 coord_t coord;
1099
1100 coord.row = coord.column = 1;
1101 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
1102}
1103
1104/** Get end-of-file s-point. */
1105static void pt_get_eof(spt_t *pt)
1106{
1107 coord_t coord;
1108 int num_rows;
1109
1110 sheet_get_num_rows(&doc.sh, &num_rows);
1111 coord.row = num_rows + 1;
1112 coord.column = 1;
1113
1114 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
1115}
1116
1117/** Compare tags. */
1118static int tag_cmp(tag_t const *a, tag_t const *b)
1119{
1120 spt_t pa, pb;
1121
1122 tag_get_pt(a, &pa);
1123 tag_get_pt(b, &pb);
1124
1125 return spt_cmp(&pa, &pb);
1126}
1127
1128/** Compare s-points. */
1129static int spt_cmp(spt_t const *a, spt_t const *b)
1130{
1131 coord_t ca, cb;
1132
1133 spt_get_coord(a, &ca);
1134 spt_get_coord(b, &cb);
1135
1136 return coord_cmp(&ca, &cb);
1137}
1138
1139/** Compare coordinats. */
1140static int coord_cmp(coord_t const *a, coord_t const *b)
1141{
1142 if (a->row - b->row != 0)
1143 return a->row - b->row;
1144
1145 return a->column - b->column;
1146}
1147
1148/** Display text in the status line. */
1149static void status_display(char const *str)
1150{
1151 console_goto(con, 0, scr_rows - 1);
1152 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
1153 printf(" %*s ", -(scr_columns - 3), str);
1154 fflush(stdout);
1155 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
1156
1157 pane.rflags |= REDRAW_CARET;
1158}
1159
1160/** @}
1161 */
Note: See TracBrowser for help on using the repository browser.