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

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

Add wstr_to_astr() for easy conversion from wide string to string.

  • 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
104/** Maximum filename length that can be entered. */
105#define INFNAME_MAX_LEN 128
106
107static void key_handle_unmod(console_event_t const *ev);
108static void key_handle_ctrl(console_event_t const *ev);
109static int file_save(char const *fname);
110static void file_save_as(void);
111static int file_insert(char *fname);
112static int file_save_range(char const *fname, spt_t const *spos,
113 spt_t const *epos);
114static char *filename_prompt(char const *prompt, char const *init_value);
115static void pane_text_display(void);
116static void pane_row_display(void);
117static void pane_row_range_display(int r0, int r1);
118static void pane_status_display(void);
119static void pane_caret_display(void);
120static void insert_char(wchar_t c);
121static void delete_char_before(void);
122static void delete_char_after(void);
123static void caret_update(void);
124static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
125static void pt_get_sof(spt_t *pt);
126static void pt_get_eof(spt_t *pt);
127static void status_display(char const *str);
128
129
130int main(int argc, char *argv[])
131{
132 console_event_t ev;
133 coord_t coord;
134 bool new_file;
135
136 spt_t pt;
137
138 con = fphone(stdout);
139 console_clear(con);
140
141 console_get_size(con, &scr_columns, &scr_rows);
142
143 pane.rows = scr_rows - 1;
144 pane.columns = scr_columns;
145 pane.sh_row = 1;
146 pane.sh_column = 1;
147
148 /* Start with an empty sheet. */
149 sheet_init(&doc.sh);
150
151 /* Place caret at the beginning of file. */
152 coord.row = coord.column = 1;
153 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
154 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
155 pane.ideal_column = coord.column;
156
157 if (argc == 2) {
158 doc.file_name = str_dup(argv[1]);
159 } else if (argc > 1) {
160 printf("Invalid arguments.\n");
161 return -2;
162 } else {
163 doc.file_name = NULL;
164 }
165
166 new_file = false;
167
168 if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
169 new_file = true;
170
171 /* Move to beginning of file. */
172 caret_move(-ED_INFTY, -ED_INFTY, dir_before);
173
174 /* Initial display */
175 console_clear(con);
176 pane_text_display();
177 pane_status_display();
178 if (new_file && doc.file_name != NULL)
179 status_display("File not found. Starting empty file.");
180 pane_caret_display();
181
182
183 done = false;
184
185 while (!done) {
186 console_get_event(con, &ev);
187 pane.rflags = 0;
188
189 if (ev.type == KEY_PRESS) {
190 /* Handle key press. */
191 if (((ev.mods & KM_ALT) == 0) &&
192 (ev.mods & KM_CTRL) != 0) {
193 key_handle_ctrl(&ev);
194 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
195 key_handle_unmod(&ev);
196 }
197 }
198
199 /* Redraw as necessary. */
200
201 if (pane.rflags & REDRAW_TEXT)
202 pane_text_display();
203 if (pane.rflags & REDRAW_ROW)
204 pane_row_display();
205 if (pane.rflags & REDRAW_STATUS)
206 pane_status_display();
207 if (pane.rflags & REDRAW_CARET)
208 pane_caret_display();
209
210 }
211
212 console_clear(con);
213
214 return 0;
215}
216
217/** Handle key without modifier. */
218static void key_handle_unmod(console_event_t const *ev)
219{
220 switch (ev->key) {
221 case KC_ENTER:
222 insert_char('\n');
223 caret_update();
224 break;
225 case KC_LEFT:
226 caret_move(0, -1, dir_before);
227 break;
228 case KC_RIGHT:
229 caret_move(0, 0, dir_after);
230 break;
231 case KC_UP:
232 caret_move(-1, 0, dir_before);
233 break;
234 case KC_DOWN:
235 caret_move(+1, 0, dir_before);
236 break;
237 case KC_HOME:
238 caret_move(0, -ED_INFTY, dir_before);
239 break;
240 case KC_END:
241 caret_move(0, +ED_INFTY, dir_before);
242 break;
243 case KC_PAGE_UP:
244 caret_move(-pane.rows, 0, dir_before);
245 break;
246 case KC_PAGE_DOWN:
247 caret_move(+pane.rows, 0, dir_before);
248 break;
249 case KC_BACKSPACE:
250 delete_char_before();
251 caret_update();
252 break;
253 case KC_DELETE:
254 delete_char_after();
255 caret_update();
256 break;
257 default:
258 if (ev->c >= 32 || ev->c == '\t') {
259 insert_char(ev->c);
260 caret_update();
261 }
262 break;
263 }
264}
265
266/** Handle Ctrl-key combination. */
267static void key_handle_ctrl(console_event_t const *ev)
268{
269 switch (ev->key) {
270 case KC_Q:
271 done = true;
272 break;
273 case KC_S:
274 if (doc.file_name != NULL)
275 file_save(doc.file_name);
276 else
277 file_save_as();
278 break;
279 case KC_E:
280 file_save_as();
281 break;
282 default:
283 break;
284 }
285}
286
287/** Save the document. */
288static int file_save(char const *fname)
289{
290 spt_t sp, ep;
291 int rc;
292
293 status_display("Saving...");
294 pt_get_sof(&sp);
295 pt_get_eof(&ep);
296
297 rc = file_save_range(fname, &sp, &ep);
298
299 switch (rc) {
300 case EINVAL:
301 status_display("Error opening file!");
302 break;
303 case EIO:
304 status_display("Error writing data!");
305 break;
306 default:
307 status_display("File saved.");
308 break;
309 }
310
311 return rc;
312}
313
314/** Change document name and save. */
315static void file_save_as(void)
316{
317 char *old_fname, *fname;
318 int rc;
319
320 old_fname = (doc.file_name != NULL) ? doc.file_name : "";
321 fname = filename_prompt("Save As", old_fname);
322 if (fname == NULL) {
323 status_display("Save cancelled.");
324 return;
325 }
326
327 rc = file_save(fname);
328 if (rc != EOK)
329 return;
330
331 if (doc.file_name != NULL)
332 free(doc.file_name);
333 doc.file_name = fname;
334}
335
336/** Ask for a file name. */
337static char *filename_prompt(char const *prompt, char const *init_value)
338{
339 console_event_t ev;
340 char *str;
341 wchar_t buffer[INFNAME_MAX_LEN + 1];
342 int max_len;
343 int nc;
344 bool done;
345
346 asprintf(&str, "%s: %s", prompt, init_value);
347 status_display(str);
348 console_goto(con, 1 + str_length(str), scr_rows - 1);
349 free(str);
350
351 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
352
353 max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
354 str_to_wstr(buffer, max_len + 1, init_value);
355 nc = wstr_length(buffer);
356 done = false;
357
358 while (!done) {
359 console_get_event(con, &ev);
360
361 if (ev.type == KEY_PRESS) {
362 /* Handle key press. */
363 if (((ev.mods & KM_ALT) == 0) &&
364 (ev.mods & KM_CTRL) != 0) {
365 ;
366 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
367 switch (ev.key) {
368 case KC_ESCAPE:
369 return NULL;
370 case KC_BACKSPACE:
371 if (nc > 0) {
372 putchar('\b');
373 fflush(stdout);
374 --nc;
375 }
376 break;
377 case KC_ENTER:
378 done = true;
379 break;
380 default:
381 if (ev.c >= 32 && nc < max_len) {
382 putchar(ev.c);
383 fflush(stdout);
384 buffer[nc++] = ev.c;
385 }
386 break;
387 }
388 }
389 }
390 }
391
392 buffer[nc] = '\0';
393 str = wstr_to_astr(buffer);
394
395 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
396
397 return str;
398}
399
400/** Insert file at caret position.
401 *
402 * Reads in the contents of a file and inserts them at the current position
403 * of the caret.
404 */
405static int file_insert(char *fname)
406{
407 FILE *f;
408 wchar_t c;
409 char buf[BUF_SIZE];
410 int bcnt;
411 int n_read;
412 size_t off;
413
414 f = fopen(fname, "rt");
415 if (f == NULL)
416 return EINVAL;
417
418 bcnt = 0;
419
420 while (true) {
421 if (bcnt < STR_BOUNDS(1)) {
422 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
423 bcnt += n_read;
424 }
425
426 off = 0;
427 c = str_decode(buf, &off, bcnt);
428 if (c == '\0')
429 break;
430
431 bcnt -= off;
432 memcpy(buf, buf + off, bcnt);
433
434 insert_char(c);
435 }
436
437 fclose(f);
438
439 return EOK;
440}
441
442/** Save a range of text into a file. */
443static int file_save_range(char const *fname, spt_t const *spos,
444 spt_t const *epos)
445{
446 FILE *f;
447 char buf[BUF_SIZE];
448 spt_t sp, bep;
449 size_t bytes, n_written;
450
451 f = fopen(fname, "wt");
452 if (f == NULL)
453 return EINVAL;
454
455 sp = *spos;
456
457 do {
458 sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
459 bytes = str_size(buf);
460
461 n_written = fwrite(buf, 1, bytes, f);
462 if (n_written != bytes) {
463 return EIO;
464 }
465
466 sp = bep;
467 } while (!spt_equal(&bep, epos));
468
469 if (fclose(f) != EOK)
470 return EIO;
471
472 return EOK;
473}
474
475static void pane_text_display(void)
476{
477 int sh_rows, rows;
478 int i, j;
479
480 sheet_get_num_rows(&doc.sh, &sh_rows);
481 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
482
483 /* Draw rows from the sheet. */
484
485 console_goto(con, 0, 0);
486 pane_row_range_display(0, rows);
487
488 /* Clear the remaining rows if file is short. */
489
490 for (i = rows; i < pane.rows; ++i) {
491 console_goto(con, 0, i);
492 for (j = 0; j < scr_columns; ++j)
493 putchar(' ');
494 fflush(stdout);
495 }
496
497 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
498 pane.rflags &= ~REDRAW_ROW;
499}
500
501/** Display just the row where the caret is. */
502static void pane_row_display(void)
503{
504 spt_t caret_pt;
505 coord_t coord;
506 int ridx;
507
508 tag_get_pt(&pane.caret_pos, &caret_pt);
509 spt_get_coord(&caret_pt, &coord);
510
511 ridx = coord.row - pane.sh_row;
512 pane_row_range_display(ridx, ridx + 1);
513 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
514}
515
516static void pane_row_range_display(int r0, int r1)
517{
518 int i, j, fill;
519 spt_t rb, re, dep;
520 coord_t rbc, rec;
521 char row_buf[ROW_BUF_SIZE];
522 wchar_t c;
523 size_t pos, size;
524 unsigned s_column;
525
526 /* Draw rows from the sheet. */
527
528 console_goto(con, 0, 0);
529 for (i = r0; i < r1; ++i) {
530 /* Starting point for row display */
531 rbc.row = pane.sh_row + i;
532 rbc.column = pane.sh_column;
533 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
534
535 /* Ending point for row display */
536 rec.row = pane.sh_row + i;
537 rec.column = pane.sh_column + pane.columns;
538 sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
539
540 /* Copy the text of the row to the buffer. */
541 sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
542
543 /* Display text from the buffer. */
544
545 console_goto(con, 0, i);
546 size = str_size(row_buf);
547 pos = 0;
548 s_column = 1;
549 while (pos < size) {
550 c = str_decode(row_buf, &pos, size);
551 if (c != '\t') {
552 printf("%lc", c);
553 s_column += 1;
554 } else {
555 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
556 - s_column;
557
558 for (j = 0; j < fill; ++j)
559 putchar(' ');
560 s_column += fill;
561 }
562 }
563
564 /* Fill until the end of display area. */
565
566 if (str_length(row_buf) < (unsigned) scr_columns)
567 fill = scr_columns - str_length(row_buf);
568 else
569 fill = 0;
570
571 for (j = 0; j < fill; ++j)
572 putchar(' ');
573 fflush(stdout);
574 }
575
576 pane.rflags |= REDRAW_CARET;
577}
578
579/** Display pane status in the status line. */
580static void pane_status_display(void)
581{
582 spt_t caret_pt;
583 coord_t coord;
584 char *fname;
585 int n;
586
587 tag_get_pt(&pane.caret_pos, &caret_pt);
588 spt_get_coord(&caret_pt, &coord);
589
590 fname = (doc.file_name != NULL) ? doc.file_name : "<unnamed>";
591
592 console_goto(con, 0, scr_rows - 1);
593 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
594 n = printf(" %d, %d: File '%s'. Ctrl-Q Quit Ctrl-S Save "
595 "Ctrl-E Save As", coord.row, coord.column, fname);
596 printf("%*s", scr_columns - 1 - n, "");
597 fflush(stdout);
598 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
599
600 pane.rflags |= REDRAW_CARET;
601}
602
603/** Set cursor to reflect position of the caret. */
604static void pane_caret_display(void)
605{
606 spt_t caret_pt;
607 coord_t coord;
608
609 tag_get_pt(&pane.caret_pos, &caret_pt);
610
611 spt_get_coord(&caret_pt, &coord);
612 console_goto(con, coord.column - pane.sh_column,
613 coord.row - pane.sh_row);
614}
615
616/** Insert a character at caret position. */
617static void insert_char(wchar_t c)
618{
619 spt_t pt;
620 char cbuf[STR_BOUNDS(1) + 1];
621 size_t offs;
622
623 tag_get_pt(&pane.caret_pos, &pt);
624
625 offs = 0;
626 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
627 cbuf[offs] = '\0';
628
629 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
630
631 pane.rflags |= REDRAW_ROW;
632 if (c == '\n')
633 pane.rflags |= REDRAW_TEXT;
634}
635
636/** Delete the character before the caret. */
637static void delete_char_before(void)
638{
639 spt_t sp, ep;
640 coord_t coord;
641
642 tag_get_pt(&pane.caret_pos, &ep);
643 spt_get_coord(&ep, &coord);
644
645 coord.column -= 1;
646 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
647
648 (void) sheet_delete(&doc.sh, &sp, &ep);
649
650 pane.rflags |= REDRAW_ROW;
651 if (coord.column < 1)
652 pane.rflags |= REDRAW_TEXT;
653}
654
655/** Delete the character after the caret. */
656static void delete_char_after(void)
657{
658 spt_t sp, ep;
659 coord_t sc, ec;
660
661 tag_get_pt(&pane.caret_pos, &sp);
662 spt_get_coord(&sp, &sc);
663
664 sheet_get_cell_pt(&doc.sh, &sc, dir_after, &ep);
665 spt_get_coord(&ep, &ec);
666
667 (void) sheet_delete(&doc.sh, &sp, &ep);
668
669 pane.rflags |= REDRAW_ROW;
670 if (ec.row != sc.row)
671 pane.rflags |= REDRAW_TEXT;
672}
673
674/** Scroll pane after caret has moved.
675 *
676 * After modifying the position of the caret, this is called to scroll
677 * the pane to ensure that the caret is in the visible area.
678 */
679static void caret_update(void)
680{
681 spt_t pt;
682 coord_t coord;
683
684 tag_get_pt(&pane.caret_pos, &pt);
685 spt_get_coord(&pt, &coord);
686
687 /* Scroll pane vertically. */
688
689 if (coord.row < pane.sh_row) {
690 pane.sh_row = coord.row;
691 pane.rflags |= REDRAW_TEXT;
692 }
693
694 if (coord.row > pane.sh_row + pane.rows - 1) {
695 pane.sh_row = coord.row - pane.rows + 1;
696 pane.rflags |= REDRAW_TEXT;
697 }
698
699 /* Scroll pane horizontally. */
700
701 if (coord.column < pane.sh_column) {
702 pane.sh_column = coord.column;
703 pane.rflags |= REDRAW_TEXT;
704 }
705
706 if (coord.column > pane.sh_column + pane.columns - 1) {
707 pane.sh_column = coord.column - pane.columns + 1;
708 pane.rflags |= REDRAW_TEXT;
709 }
710
711 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
712}
713
714/** Change the caret position.
715 *
716 * Moves caret relatively to the current position. Looking at the first
717 * character cell after the caret and moving by @a drow and @a dcolumn, we get
718 * to a new character cell, and thus a new character. Then we either go to the
719 * point before the the character or after it, depending on @a align_dir.
720 */
721static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
722{
723 spt_t pt;
724 coord_t coord;
725 int num_rows;
726 bool pure_vertical;
727
728 tag_get_pt(&pane.caret_pos, &pt);
729 spt_get_coord(&pt, &coord);
730 coord.row += drow; coord.column += dcolumn;
731
732 /* Clamp coordinates. */
733 if (drow < 0 && coord.row < 1) coord.row = 1;
734 if (dcolumn < 0 && coord.column < 1) coord.column = 1;
735 if (drow > 0) {
736 sheet_get_num_rows(&doc.sh, &num_rows);
737 if (coord.row > num_rows) coord.row = num_rows;
738 }
739
740 /* For purely vertical movement try attaining @c ideal_column. */
741 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
742 if (pure_vertical)
743 coord.column = pane.ideal_column;
744
745 /*
746 * Select the point before or after the character at the designated
747 * coordinates. The character can be wider than one cell (e.g. tab).
748 */
749 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
750 sheet_remove_tag(&doc.sh, &pane.caret_pos);
751 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
752
753 /* For non-vertical movement set the new value for @c ideal_column. */
754 if (!pure_vertical) {
755 spt_get_coord(&pt, &coord);
756 pane.ideal_column = coord.column;
757 }
758
759 caret_update();
760}
761
762
763/** Get start-of-file s-point. */
764static void pt_get_sof(spt_t *pt)
765{
766 coord_t coord;
767
768 coord.row = coord.column = 1;
769 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
770}
771
772/** Get end-of-file s-point. */
773static void pt_get_eof(spt_t *pt)
774{
775 coord_t coord;
776 int num_rows;
777
778 sheet_get_num_rows(&doc.sh, &num_rows);
779 coord.row = num_rows + 1;
780 coord.column = 1;
781
782 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
783}
784
785/** Display text in the status line. */
786static void status_display(char const *str)
787{
788 console_goto(con, 0, scr_rows - 1);
789 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
790 printf(" %*s ", -(scr_columns - 3), str);
791 fflush(stdout);
792 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
793
794 pane.rflags |= REDRAW_CARET;
795}
796
797/** @}
798 */
Note: See TracBrowser for help on using the repository browser.