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

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

Squash more bugs.

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