source: mainline/uspace/app/edit/edit.c@ 1df977c

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

Save As feature.

  • Property mode set to 100644
File size: 17.2 KB
Line 
1/*
2 * Copyright (c) 2009 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup edit
30 * @brief Text editor.
31 * @{
32 */
33/**
34 * @file
35 */
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <sys/types.h>
40#include <vfs/vfs.h>
41#include <io/console.h>
42#include <io/color.h>
43#include <io/keycode.h>
44#include <errno.h>
45#include <align.h>
46#include <macros.h>
47#include <bool.h>
48
49#include "sheet.h"
50
51enum redraw_flags {
52 REDRAW_TEXT = (1 << 0),
53 REDRAW_ROW = (1 << 1),
54 REDRAW_STATUS = (1 << 2),
55 REDRAW_CARET = (1 << 3)
56};
57
58/** Pane
59 *
60 * A rectangular area of the screen used to edit a document. Different
61 * panes can be possibly used to edit the same document.
62 */
63typedef struct {
64 /* Pane dimensions */
65 int rows, columns;
66
67 /* Position of the visible area */
68 int sh_row, sh_column;
69
70 /** Bitmask of components that need redrawing */
71 enum redraw_flags rflags;
72
73 /** Current position of the caret */
74 tag_t caret_pos;
75
76 /**
77 * Ideal column where the caret should try to get. This is used
78 * for maintaining the same column during vertical movement.
79 */
80 int ideal_column;
81} pane_t;
82
83/** Document
84 *
85 * Associates a sheet with a file where it can be saved to.
86 */
87typedef struct {
88 char *file_name;
89 sheet_t sh;
90} doc_t;
91
92static int con;
93static doc_t doc;
94static bool done;
95static pane_t pane;
96
97static int scr_rows, scr_columns;
98
99#define ROW_BUF_SIZE 4096
100#define BUF_SIZE 64
101#define TAB_WIDTH 8
102#define ED_INFTY 65536
103
104static void key_handle_unmod(console_event_t const *ev);
105static void key_handle_ctrl(console_event_t const *ev);
106static int file_save(char const *fname);
107static void file_save_as(void);
108static int file_insert(char *fname);
109static int file_save_range(char const *fname, spt_t const *spos,
110 spt_t const *epos);
111static char *filename_prompt(char const *prompt, char const *init_value);
112static void pane_text_display(void);
113static void pane_row_display(void);
114static void pane_row_range_display(int r0, int r1);
115static void pane_status_display(void);
116static void pane_caret_display(void);
117static void insert_char(wchar_t c);
118static void delete_char_before(void);
119static void delete_char_after(void);
120static void caret_update(void);
121static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
122static void pt_get_sof(spt_t *pt);
123static void pt_get_eof(spt_t *pt);
124static void status_display(char const *str);
125
126
127int main(int argc, char *argv[])
128{
129 console_event_t ev;
130 coord_t coord;
131 bool new_file;
132
133 spt_t pt;
134
135 con = fphone(stdout);
136 console_clear(con);
137
138 console_get_size(con, &scr_columns, &scr_rows);
139
140 pane.rows = scr_rows - 1;
141 pane.columns = scr_columns;
142 pane.sh_row = 1;
143 pane.sh_column = 1;
144
145 /* Start with an empty sheet. */
146 sheet_init(&doc.sh);
147
148 /* Place caret at the beginning of file. */
149 coord.row = coord.column = 1;
150 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
151 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
152 pane.ideal_column = coord.column;
153
154 if (argc == 2) {
155 doc.file_name = str_dup(argv[1]);
156 } else if (argc > 1) {
157 printf("Invalid arguments.\n");
158 return -2;
159 } else {
160 doc.file_name = NULL;
161 }
162
163 new_file = false;
164
165 if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
166 new_file = true;
167
168 /* Move to beginning of file. */
169 caret_move(-ED_INFTY, -ED_INFTY, dir_before);
170
171 /* Initial display */
172 console_clear(con);
173 pane_text_display();
174 pane_status_display();
175 if (new_file && doc.file_name != NULL)
176 status_display("File not found. Starting empty file.");
177 pane_caret_display();
178
179
180 done = false;
181
182 while (!done) {
183 console_get_event(con, &ev);
184 pane.rflags = 0;
185
186 if (ev.type == KEY_PRESS) {
187 /* Handle key press. */
188 if (((ev.mods & KM_ALT) == 0) &&
189 (ev.mods & KM_CTRL) != 0) {
190 key_handle_ctrl(&ev);
191 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
192 key_handle_unmod(&ev);
193 }
194 }
195
196 /* Redraw as necessary. */
197
198 if (pane.rflags & REDRAW_TEXT)
199 pane_text_display();
200 if (pane.rflags & REDRAW_ROW)
201 pane_row_display();
202 if (pane.rflags & REDRAW_STATUS)
203 pane_status_display();
204 if (pane.rflags & REDRAW_CARET)
205 pane_caret_display();
206
207 }
208
209 console_clear(con);
210
211 return 0;
212}
213
214/** Handle key without modifier. */
215static void key_handle_unmod(console_event_t const *ev)
216{
217 switch (ev->key) {
218 case KC_ENTER:
219 insert_char('\n');
220 caret_update();
221 break;
222 case KC_LEFT:
223 caret_move(0, -1, dir_before);
224 break;
225 case KC_RIGHT:
226 caret_move(0, 0, dir_after);
227 break;
228 case KC_UP:
229 caret_move(-1, 0, dir_before);
230 break;
231 case KC_DOWN:
232 caret_move(+1, 0, dir_before);
233 break;
234 case KC_HOME:
235 caret_move(0, -ED_INFTY, dir_before);
236 break;
237 case KC_END:
238 caret_move(0, +ED_INFTY, dir_before);
239 break;
240 case KC_PAGE_UP:
241 caret_move(-pane.rows, 0, dir_before);
242 break;
243 case KC_PAGE_DOWN:
244 caret_move(+pane.rows, 0, dir_before);
245 break;
246 case KC_BACKSPACE:
247 delete_char_before();
248 caret_update();
249 break;
250 case KC_DELETE:
251 delete_char_after();
252 caret_update();
253 break;
254 default:
255 if (ev->c >= 32 || ev->c == '\t') {
256 insert_char(ev->c);
257 caret_update();
258 }
259 break;
260 }
261}
262
263/** Handle Ctrl-key combination. */
264static void key_handle_ctrl(console_event_t const *ev)
265{
266 switch (ev->key) {
267 case KC_Q:
268 done = true;
269 break;
270 case KC_S:
271 if (doc.file_name != NULL)
272 file_save(doc.file_name);
273 else
274 file_save_as();
275 break;
276 case KC_E:
277 file_save_as();
278 break;
279 default:
280 break;
281 }
282}
283
284/** Save the document. */
285static int file_save(char const *fname)
286{
287 spt_t sp, ep;
288 int rc;
289
290 status_display("Saving...");
291 pt_get_sof(&sp);
292 pt_get_eof(&ep);
293
294 rc = file_save_range(fname, &sp, &ep);
295
296 switch (rc) {
297 case EINVAL:
298 status_display("Error opening file!");
299 break;
300 case EIO:
301 status_display("Error writing data!");
302 break;
303 default:
304 status_display("File saved.");
305 break;
306 }
307
308 return rc;
309}
310
311/** Change document name and save. */
312static void file_save_as(void)
313{
314 char *old_fname, *fname;
315 int rc;
316
317 old_fname = (doc.file_name != NULL) ? doc.file_name : "";
318 fname = filename_prompt("Save As", old_fname);
319 if (fname == NULL) {
320 status_display("Save cancelled.");
321 return;
322 }
323
324 rc = file_save(fname);
325 if (rc != EOK)
326 return;
327
328 if (doc.file_name != NULL)
329 free(doc.file_name);
330 doc.file_name = fname;
331}
332
333#define INPUT_MAX_LEN 64
334
335/** Ask for a file name. */
336static char *filename_prompt(char const *prompt, char const *init_value)
337{
338 console_event_t ev;
339 char *str;
340 wchar_t buffer[INPUT_MAX_LEN + 1];
341 int nc;
342 bool done;
343
344 asprintf(&str, "%s: %s", prompt, init_value);
345 status_display(str);
346 console_goto(con, 1 + str_length(str), scr_rows - 1);
347 free(str);
348
349 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
350
351 str_to_wstr(buffer, INPUT_MAX_LEN + 1, init_value);
352 nc = wstr_length(buffer);
353 done = false;
354
355 while (!done) {
356 console_get_event(con, &ev);
357
358 if (ev.type == KEY_PRESS) {
359 /* Handle key press. */
360 if (((ev.mods & KM_ALT) == 0) &&
361 (ev.mods & KM_CTRL) != 0) {
362 ;
363 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
364 switch (ev.key) {
365 case KC_ESCAPE:
366 return NULL;
367 case KC_BACKSPACE:
368 if (nc > 0) {
369 putchar('\b');
370 fflush(stdout);
371 --nc;
372 }
373 break;
374 case KC_ENTER:
375 done = true;
376 break;
377 default:
378 if (ev.c >= 32 && nc < INPUT_MAX_LEN) {
379 putchar(ev.c);
380 fflush(stdout);
381 buffer[nc++] = ev.c;
382 }
383 break;
384 }
385 }
386 }
387 }
388
389 buffer[nc] = '\0';
390
391 str = malloc(STR_BOUNDS(wstr_length(buffer)) + 1);
392 if (str == NULL)
393 return NULL;
394
395 wstr_nstr(str, buffer, STR_BOUNDS(wstr_length(buffer)) + 1);
396
397 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
398
399 return str;
400}
401
402/** Insert file at caret position.
403 *
404 * Reads in the contents of a file and inserts them at the current position
405 * of the caret.
406 */
407static int file_insert(char *fname)
408{
409 FILE *f;
410 wchar_t c;
411 char buf[BUF_SIZE];
412 int bcnt;
413 int n_read;
414 size_t off;
415
416 f = fopen(fname, "rt");
417 if (f == NULL)
418 return EINVAL;
419
420 bcnt = 0;
421
422 while (true) {
423 if (bcnt < STR_BOUNDS(1)) {
424 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
425 bcnt += n_read;
426 }
427
428 off = 0;
429 c = str_decode(buf, &off, bcnt);
430 if (c == '\0')
431 break;
432
433 bcnt -= off;
434 memcpy(buf, buf + off, bcnt);
435
436 insert_char(c);
437 }
438
439 fclose(f);
440
441 return EOK;
442}
443
444/** Save a range of text into a file. */
445static int file_save_range(char const *fname, spt_t const *spos,
446 spt_t const *epos)
447{
448 FILE *f;
449 char buf[BUF_SIZE];
450 spt_t sp, bep;
451 size_t bytes, n_written;
452
453 f = fopen(fname, "wt");
454 if (f == NULL)
455 return EINVAL;
456
457 sp = *spos;
458
459 do {
460 sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
461 bytes = str_size(buf);
462
463 n_written = fwrite(buf, 1, bytes, f);
464 if (n_written != bytes) {
465 return EIO;
466 }
467
468 sp = bep;
469 } while (!spt_equal(&bep, epos));
470
471 if (fclose(f) != EOK)
472 return EIO;
473
474 return EOK;
475}
476
477static void pane_text_display(void)
478{
479 int sh_rows, rows;
480 int i, j;
481
482 sheet_get_num_rows(&doc.sh, &sh_rows);
483 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
484
485 /* Draw rows from the sheet. */
486
487 console_goto(con, 0, 0);
488 pane_row_range_display(0, rows);
489
490 /* Clear the remaining rows if file is short. */
491
492 for (i = rows; i < pane.rows; ++i) {
493 console_goto(con, 0, i);
494 for (j = 0; j < scr_columns; ++j)
495 putchar(' ');
496 fflush(stdout);
497 }
498
499 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
500 pane.rflags &= ~REDRAW_ROW;
501}
502
503/** Display just the row where the caret is. */
504static void pane_row_display(void)
505{
506 spt_t caret_pt;
507 coord_t coord;
508 int ridx;
509
510 tag_get_pt(&pane.caret_pos, &caret_pt);
511 spt_get_coord(&caret_pt, &coord);
512
513 ridx = coord.row - pane.sh_row;
514 pane_row_range_display(ridx, ridx + 1);
515 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
516}
517
518static void pane_row_range_display(int r0, int r1)
519{
520 int i, j, fill;
521 spt_t rb, re, dep;
522 coord_t rbc, rec;
523 char row_buf[ROW_BUF_SIZE];
524 wchar_t c;
525 size_t pos, size;
526 unsigned s_column;
527
528 /* Draw rows from the sheet. */
529
530 console_goto(con, 0, 0);
531 for (i = r0; i < r1; ++i) {
532 /* Starting point for row display */
533 rbc.row = pane.sh_row + i;
534 rbc.column = pane.sh_column;
535 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
536
537 /* Ending point for row display */
538 rec.row = pane.sh_row + i;
539 rec.column = pane.sh_column + pane.columns;
540 sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
541
542 /* Copy the text of the row to the buffer. */
543 sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
544
545 /* Display text from the buffer. */
546
547 console_goto(con, 0, i);
548 size = str_size(row_buf);
549 pos = 0;
550 s_column = 1;
551 while (pos < size) {
552 c = str_decode(row_buf, &pos, size);
553 if (c != '\t') {
554 printf("%lc", c);
555 s_column += 1;
556 } else {
557 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
558 - s_column;
559
560 for (j = 0; j < fill; ++j)
561 putchar(' ');
562 s_column += fill;
563 }
564 }
565
566 /* Fill until the end of display area. */
567
568 if (str_length(row_buf) < (unsigned) scr_columns)
569 fill = scr_columns - str_length(row_buf);
570 else
571 fill = 0;
572
573 for (j = 0; j < fill; ++j)
574 putchar(' ');
575 fflush(stdout);
576 }
577
578 pane.rflags |= REDRAW_CARET;
579}
580
581/** Display pane status in the status line. */
582static void pane_status_display(void)
583{
584 spt_t caret_pt;
585 coord_t coord;
586 char *fname;
587 int n;
588
589 tag_get_pt(&pane.caret_pos, &caret_pt);
590 spt_get_coord(&caret_pt, &coord);
591
592 fname = (doc.file_name != NULL) ? doc.file_name : "<unnamed>";
593
594 console_goto(con, 0, scr_rows - 1);
595 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
596 n = printf(" %d, %d: File '%s'. Ctrl-Q Quit Ctrl-S Save "
597 "Ctrl-E Save As", coord.row, coord.column, fname);
598 printf("%*s", scr_columns - 1 - n, "");
599 fflush(stdout);
600 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
601
602 pane.rflags |= REDRAW_CARET;
603}
604
605/** Set cursor to reflect position of the caret. */
606static void pane_caret_display(void)
607{
608 spt_t caret_pt;
609 coord_t coord;
610
611 tag_get_pt(&pane.caret_pos, &caret_pt);
612
613 spt_get_coord(&caret_pt, &coord);
614 console_goto(con, coord.column - pane.sh_column,
615 coord.row - pane.sh_row);
616}
617
618/** Insert a character at caret position. */
619static void insert_char(wchar_t c)
620{
621 spt_t pt;
622 char cbuf[STR_BOUNDS(1) + 1];
623 size_t offs;
624
625 tag_get_pt(&pane.caret_pos, &pt);
626
627 offs = 0;
628 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
629 cbuf[offs] = '\0';
630
631 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
632
633 pane.rflags |= REDRAW_ROW;
634 if (c == '\n')
635 pane.rflags |= REDRAW_TEXT;
636}
637
638/** Delete the character before the caret. */
639static void delete_char_before(void)
640{
641 spt_t sp, ep;
642 coord_t coord;
643
644 tag_get_pt(&pane.caret_pos, &ep);
645 spt_get_coord(&ep, &coord);
646
647 coord.column -= 1;
648 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
649
650 (void) sheet_delete(&doc.sh, &sp, &ep);
651
652 pane.rflags |= REDRAW_ROW;
653 if (coord.column < 1)
654 pane.rflags |= REDRAW_TEXT;
655}
656
657/** Delete the character after the caret. */
658static void delete_char_after(void)
659{
660 spt_t sp, ep;
661 coord_t sc, ec;
662
663 tag_get_pt(&pane.caret_pos, &sp);
664 spt_get_coord(&sp, &sc);
665
666 sheet_get_cell_pt(&doc.sh, &sc, dir_after, &ep);
667 spt_get_coord(&ep, &ec);
668
669 (void) sheet_delete(&doc.sh, &sp, &ep);
670
671 pane.rflags |= REDRAW_ROW;
672 if (ec.row != sc.row)
673 pane.rflags |= REDRAW_TEXT;
674}
675
676/** Scroll pane after caret has moved.
677 *
678 * After modifying the position of the caret, this is called to scroll
679 * the pane to ensure that the caret is in the visible area.
680 */
681static void caret_update(void)
682{
683 spt_t pt;
684 coord_t coord;
685
686 tag_get_pt(&pane.caret_pos, &pt);
687 spt_get_coord(&pt, &coord);
688
689 /* Scroll pane vertically. */
690
691 if (coord.row < pane.sh_row) {
692 pane.sh_row = coord.row;
693 pane.rflags |= REDRAW_TEXT;
694 }
695
696 if (coord.row > pane.sh_row + pane.rows - 1) {
697 pane.sh_row = coord.row - pane.rows + 1;
698 pane.rflags |= REDRAW_TEXT;
699 }
700
701 /* Scroll pane horizontally. */
702
703 if (coord.column < pane.sh_column) {
704 pane.sh_column = coord.column;
705 pane.rflags |= REDRAW_TEXT;
706 }
707
708 if (coord.column > pane.sh_column + pane.columns - 1) {
709 pane.sh_column = coord.column - pane.columns + 1;
710 pane.rflags |= REDRAW_TEXT;
711 }
712
713 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
714}
715
716/** Change the caret position.
717 *
718 * Moves caret relatively to the current position. Looking at the first
719 * character cell after the caret and moving by @a drow and @a dcolumn, we get
720 * to a new character cell, and thus a new character. Then we either go to the
721 * point before the the character or after it, depending on @a align_dir.
722 */
723static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
724{
725 spt_t pt;
726 coord_t coord;
727 int num_rows;
728 bool pure_vertical;
729
730 tag_get_pt(&pane.caret_pos, &pt);
731 spt_get_coord(&pt, &coord);
732 coord.row += drow; coord.column += dcolumn;
733
734 /* Clamp coordinates. */
735 if (drow < 0 && coord.row < 1) coord.row = 1;
736 if (dcolumn < 0 && coord.column < 1) coord.column = 1;
737 if (drow > 0) {
738 sheet_get_num_rows(&doc.sh, &num_rows);
739 if (coord.row > num_rows) coord.row = num_rows;
740 }
741
742 /* For purely vertical movement try attaining @c ideal_column. */
743 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
744 if (pure_vertical)
745 coord.column = pane.ideal_column;
746
747 /*
748 * Select the point before or after the character at the designated
749 * coordinates. The character can be wider than one cell (e.g. tab).
750 */
751 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
752 sheet_remove_tag(&doc.sh, &pane.caret_pos);
753 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
754
755 /* For non-vertical movement set the new value for @c ideal_column. */
756 if (!pure_vertical) {
757 spt_get_coord(&pt, &coord);
758 pane.ideal_column = coord.column;
759 }
760
761 caret_update();
762}
763
764
765/** Get start-of-file s-point. */
766static void pt_get_sof(spt_t *pt)
767{
768 coord_t coord;
769
770 coord.row = coord.column = 1;
771 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
772}
773
774/** Get end-of-file s-point. */
775static void pt_get_eof(spt_t *pt)
776{
777 coord_t coord;
778 int num_rows;
779
780 sheet_get_num_rows(&doc.sh, &num_rows);
781 coord.row = num_rows + 1;
782 coord.column = 1;
783
784 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
785}
786
787/** Display text in the status line. */
788static void status_display(char const *str)
789{
790 console_goto(con, 0, scr_rows - 1);
791 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
792 printf(" %*s ", -(scr_columns - 3), str);
793 fflush(stdout);
794 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
795
796 pane.rflags |= REDRAW_CARET;
797}
798
799/** @}
800 */
Note: See TracBrowser for help on using the repository browser.