source: mainline/uspace/app/edit/edit.c@ 79ae36dd

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

new async framework with integrated exchange tracking

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