source: mainline/uspace/app/edit/edit.c@ 3052ff4

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

Add simple text editor.

  • Property mode set to 100644
File size: 13.9 KB
RevLine 
[3052ff4]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 <sys/types.h>
39#include <vfs/vfs.h>
40#include <io/console.h>
41#include <io/color.h>
42#include <io/keycode.h>
43#include <errno.h>
44#include <align.h>
45#include <macros.h>
46#include <bool.h>
47
48#include "sheet.h"
49
50enum redraw_flags {
51 REDRAW_TEXT = (1 << 0),
52 REDRAW_ROW = (1 << 1),
53 REDRAW_STATUS = (1 << 2),
54 REDRAW_CARET = (1 << 3)
55};
56
57/** Pane
58 *
59 * A rectangular area of the screen used to edit a document. Different
60 * panes can be possibly used to edit the same document.
61 */
62typedef struct {
63 /* Pane dimensions */
64 int rows, columns;
65
66 /* Position of the visible area */
67 int sh_row, sh_column;
68
69 /** Bitmask of components that need redrawing */
70 enum redraw_flags rflags;
71
72 /** Current position of the caret */
73 tag_t caret_pos;
74} pane_t;
75
76/** Document
77 *
78 * Associates a sheet with a file where it can be saved to.
79 */
80typedef struct {
81 char *file_name;
82 sheet_t sh;
83} doc_t;
84
85static int con;
86static doc_t doc;
87static bool done;
88static pane_t pane;
89
90static ipcarg_t scr_rows, scr_columns;
91
92#define ROW_BUF_SIZE 4096
93#define BUF_SIZE 64
94#define TAB_WIDTH 8
95#define ED_INFTY 65536
96
97static void key_handle_unmod(console_event_t const *ev);
98static void key_handle_ctrl(console_event_t const *ev);
99static int file_save(char const *fname);
100static int file_insert(char *fname);
101static int file_save_range(char const *fname, spt_t const *spos,
102 spt_t const *epos);
103static void pane_text_display(void);
104static void pane_row_display(void);
105static void pane_row_range_display(int r0, int r1);
106static void pane_status_display(void);
107static void pane_caret_display(void);
108static void insert_char(wchar_t c);
109static void delete_char_before(void);
110static void delete_char_after(void);
111static void caret_update(void);
112static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
113static void pt_get_sof(spt_t *pt);
114static void pt_get_eof(spt_t *pt);
115static void status_display(char const *str);
116
117
118int main(int argc, char *argv[])
119{
120 console_event_t ev;
121 coord_t coord;
122 bool new_file;
123
124 spt_t pt;
125
126 con = fphone(stdout);
127 console_clear(con);
128
129 console_get_size(con, &scr_columns, &scr_rows);
130
131 pane.rows = scr_rows - 1;
132 pane.sh_row = 1;
133
134 /* Start with an empty sheet. */
135 sheet_init(&doc.sh);
136
137 /* Place caret at the beginning of file. */
138 coord.row = coord.column = 1;
139 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
140 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
141
142 if (argc == 2) {
143 doc.file_name = argv[1];
144 } else if (argc > 1) {
145 printf("Invalid arguments.\n");
146 return -2;
147 } else {
148 doc.file_name = "/edit.txt";
149 }
150
151 new_file = false;
152
153 if (file_insert(doc.file_name) != EOK)
154 new_file = true;
155
156 /* Move to beginning of file. */
157 caret_move(-ED_INFTY, -ED_INFTY, dir_before);
158
159 /* Initial display */
160 console_clear(con);
161 pane_text_display();
162 pane_status_display();
163 if (new_file)
164 status_display("File not found. Created empty file.");
165 pane_caret_display();
166
167
168 done = false;
169
170 while (!done) {
171 console_get_event(con, &ev);
172 pane.rflags = 0;
173
174 if (ev.type == KEY_PRESS) {
175 /* Handle key press. */
176 if (((ev.mods & KM_ALT) == 0) &&
177 (ev.mods & KM_CTRL) != 0) {
178 key_handle_ctrl(&ev);
179 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
180 key_handle_unmod(&ev);
181 }
182 }
183
184 /* Redraw as necessary. */
185
186 if (pane.rflags & REDRAW_TEXT)
187 pane_text_display();
188 if (pane.rflags & REDRAW_ROW)
189 pane_row_display();
190 if (pane.rflags & REDRAW_STATUS)
191 pane_status_display();
192 if (pane.rflags & REDRAW_CARET)
193 pane_caret_display();
194
195 }
196
197 console_clear(con);
198
199 return 0;
200}
201
202/** Handle key without modifier. */
203static void key_handle_unmod(console_event_t const *ev)
204{
205 switch (ev->key) {
206 case KC_ENTER:
207 insert_char('\n');
208 pane.rflags |= REDRAW_TEXT;
209 caret_update();
210 break;
211 case KC_LEFT:
212 caret_move(0, -1, dir_before);
213 break;
214 case KC_RIGHT:
215 caret_move(0, 0, dir_after);
216 break;
217 case KC_UP:
218 caret_move(-1, 0, dir_before);
219 break;
220 case KC_DOWN:
221 caret_move(+1, 0, dir_before);
222 break;
223 case KC_HOME:
224 caret_move(0, -ED_INFTY, dir_before);
225 break;
226 case KC_END:
227 caret_move(0, +ED_INFTY, dir_before);
228 break;
229 case KC_PAGE_UP:
230 caret_move(-pane.rows, 0, dir_before);
231 break;
232 case KC_PAGE_DOWN:
233 caret_move(+pane.rows, 0, dir_before);
234 break;
235 case KC_BACKSPACE:
236 delete_char_before();
237 pane.rflags |= REDRAW_TEXT;
238 caret_update();
239 break;
240 case KC_DELETE:
241 delete_char_after();
242 pane.rflags |= REDRAW_TEXT;
243 caret_update();
244 break;
245 default:
246 if (ev->c >= 32 || ev->c == '\t') {
247 insert_char(ev->c);
248 pane.rflags |= REDRAW_ROW;
249 caret_update();
250 }
251 break;
252 }
253}
254
255/** Handle Ctrl-key combination. */
256static void key_handle_ctrl(console_event_t const *ev)
257{
258 switch (ev->key) {
259 case KC_Q:
260 done = true;
261 break;
262 case KC_S:
263 (void) file_save(doc.file_name);
264 break;
265 default:
266 break;
267 }
268}
269
270
271/** Save the document. */
272static int file_save(char const *fname)
273{
274 spt_t sp, ep;
275 int rc;
276
277 status_display("Saving...");
278 pt_get_sof(&sp);
279 pt_get_eof(&ep);
280
281 rc = file_save_range(fname, &sp, &ep);
282 status_display("File saved.");
283
284 return rc;
285}
286
287/** Insert file at caret position.
288 *
289 * Reads in the contents of a file and inserts them at the current position
290 * of the caret.
291 */
292static int file_insert(char *fname)
293{
294 FILE *f;
295 wchar_t c;
296 char buf[BUF_SIZE];
297 int bcnt;
298 int n_read;
299 size_t off;
300
301 f = fopen(fname, "rt");
302 if (f == NULL)
303 return EINVAL;
304
305 bcnt = 0;
306
307 while (true) {
308 if (bcnt < STR_BOUNDS(1)) {
309 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
310 bcnt += n_read;
311 }
312
313 off = 0;
314 c = str_decode(buf, &off, bcnt);
315 if (c == '\0')
316 break;
317
318 bcnt -= off;
319 memcpy(buf, buf + off, bcnt);
320
321 insert_char(c);
322 }
323
324 fclose(f);
325
326 return EOK;
327}
328
329/** Save a range of text into a file. */
330static int file_save_range(char const *fname, spt_t const *spos,
331 spt_t const *epos)
332{
333 FILE *f;
334 char buf[BUF_SIZE];
335 spt_t sp, bep;
336 size_t bytes, n_written;
337
338 f = fopen(fname, "wt");
339 if (f == NULL)
340 return EINVAL;
341
342 sp = *spos;
343
344 do {
345 sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
346 bytes = str_size(buf);
347
348 n_written = fwrite(buf, 1, bytes, f);
349 if (n_written != bytes) {
350 return EIO;
351 }
352
353 sp = bep;
354 } while (!spt_equal(&bep, epos));
355
356 fclose(f);
357
358 return EOK;
359}
360
361static void pane_text_display(void)
362{
363 int sh_rows, rows;
364 int i;
365 unsigned j;
366
367 sheet_get_num_rows(&doc.sh, &sh_rows);
368 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
369
370 /* Draw rows from the sheet. */
371
372 console_goto(con, 0, 0);
373 pane_row_range_display(0, rows);
374
375 /* Clear the remaining rows if file is short. */
376
377 for (i = rows; i < pane.rows; ++i) {
378 console_goto(con, 0, i);
379 for (j = 0; j < scr_columns; ++j)
380 putchar(' ');
381 fflush(stdout);
382 }
383
384 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
385 pane.rflags &= ~REDRAW_ROW;
386}
387
388/** Display just the row where the caret is. */
389static void pane_row_display(void)
390{
391 spt_t caret_pt;
392 coord_t coord;
393 int ridx;
394
395 tag_get_pt(&pane.caret_pos, &caret_pt);
396 spt_get_coord(&caret_pt, &coord);
397
398 ridx = coord.row - pane.sh_row;
399 pane_row_range_display(ridx, ridx + 1);
400 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
401}
402
403static void pane_row_range_display(int r0, int r1)
404{
405 int width;
406 int i, j, fill;
407 spt_t rb, re, dep;
408 coord_t rbc, rec;
409 char row_buf[ROW_BUF_SIZE];
410 wchar_t c;
411 size_t pos, size;
412 unsigned s_column;
413
414 /* Draw rows from the sheet. */
415
416 console_goto(con, 0, 0);
417 for (i = r0; i < r1; ++i) {
418 sheet_get_row_width(&doc.sh, pane.sh_row + i, &width);
419
420 /* Determine row starting point. */
421 rbc.row = pane.sh_row + i; rbc.column = 1;
422 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
423
424 /* Determine row ending point. */
425 rec.row = pane.sh_row + i; rec.column = width + 1;
426 sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
427
428 /* Copy the text of the row to the buffer. */
429 sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
430
431 /* Display text from the buffer. */
432
433 console_goto(con, 0, i);
434 size = str_size(row_buf);
435 pos = 0;
436 s_column = 1;
437 while (pos < size) {
438 c = str_decode(row_buf, &pos, size);
439 if (c != '\t') {
440 printf("%lc", c);
441 s_column += 1;
442 } else {
443 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
444 - s_column;
445
446 for (j = 0; j < fill; ++j)
447 putchar(' ');
448 s_column += fill;
449 }
450 }
451
452 /* Fill until the end of display area. */
453
454 if (str_length(row_buf) < scr_columns)
455 fill = scr_columns - str_length(row_buf);
456 else
457 fill = 0;
458
459 for (j = 0; j < fill; ++j)
460 putchar(' ');
461 fflush(stdout);
462 }
463
464 pane.rflags |= REDRAW_CARET;
465}
466
467/** Display pane status in the status line. */
468static void pane_status_display(void)
469{
470 spt_t caret_pt;
471 coord_t coord;
472 int n;
473
474 tag_get_pt(&pane.caret_pos, &caret_pt);
475 spt_get_coord(&caret_pt, &coord);
476
477 console_goto(con, 0, scr_rows - 1);
478 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
479 n = printf(" %d, %d: File '%s'. Ctrl-S Save Ctrl-Q Quit",
480 coord.row, coord.column, doc.file_name);
481 printf("%*s", scr_columns - 1 - n, "");
482 fflush(stdout);
483 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
484
485 pane.rflags |= REDRAW_CARET;
486}
487
488/** Set cursor to reflect position of the caret. */
489static void pane_caret_display(void)
490{
491 spt_t caret_pt;
492 coord_t coord;
493
494 tag_get_pt(&pane.caret_pos, &caret_pt);
495
496 spt_get_coord(&caret_pt, &coord);
497 console_goto(con, coord.column - 1, coord.row - pane.sh_row);
498}
499
500/** Insert a character at caret position. */
501static void insert_char(wchar_t c)
502{
503 spt_t pt;
504 char cbuf[STR_BOUNDS(1) + 1];
505 size_t offs;
506
507 tag_get_pt(&pane.caret_pos, &pt);
508
509 offs = 0;
510 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
511 cbuf[offs] = '\0';
512
513 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
514}
515
516/** Delete the character before the caret. */
517static void delete_char_before(void)
518{
519 spt_t sp, ep;
520 coord_t coord;
521
522 tag_get_pt(&pane.caret_pos, &ep);
523 spt_get_coord(&ep, &coord);
524
525 coord.column -= 1;
526 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
527
528 (void) sheet_delete(&doc.sh, &sp, &ep);
529}
530
531/** Delete the character after the caret. */
532static void delete_char_after(void)
533{
534 spt_t sp, ep;
535 coord_t coord;
536
537 tag_get_pt(&pane.caret_pos, &sp);
538 spt_get_coord(&sp, &coord);
539
540 sheet_get_cell_pt(&doc.sh, &coord, dir_after, &ep);
541
542 (void) sheet_delete(&doc.sh, &sp, &ep);
543}
544
545/** Scroll pane after caret has moved.
546 *
547 * After modifying the position of the caret, this is called to scroll
548 * the pane to ensure that the caret is in the visible area.
549 */
550static void caret_update(void)
551{
552 spt_t pt;
553 coord_t coord;
554
555 tag_get_pt(&pane.caret_pos, &pt);
556 spt_get_coord(&pt, &coord);
557
558 /* Scroll pane as necessary. */
559
560 if (coord.row < pane.sh_row) {
561 pane.sh_row = coord.row;
562 pane.rflags |= REDRAW_TEXT;
563 }
564 if (coord.row > pane.sh_row + pane.rows - 1) {
565 pane.sh_row = coord.row - pane.rows + 1;
566 pane.rflags |= REDRAW_TEXT;
567 }
568
569 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
570
571}
572
573/** Change the caret position.
574 *
575 * Moves caret relatively to the current position. Looking at the first
576 * character cell after the caret and moving by @a drow and @a dcolumn, we get
577 * to a new character cell, and thus a new character. Then we either go to the
578 * point before the the character or after it, depending on @a align_dir.
579 */
580static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
581{
582 spt_t pt;
583 coord_t coord;
584 int num_rows;
585
586 tag_get_pt(&pane.caret_pos, &pt);
587 spt_get_coord(&pt, &coord);
588 coord.row += drow; coord.column += dcolumn;
589
590 /* Clamp coordinates. */
591 if (drow < 0 && coord.row < 1) coord.row = 1;
592 if (dcolumn < 0 && coord.column < 1) coord.column = 1;
593 if (drow > 0) {
594 sheet_get_num_rows(&doc.sh, &num_rows);
595 if (coord.row > num_rows) coord.row = num_rows;
596 }
597
598 /*
599 * Select the point before or after the character at the designated
600 * coordinates. The character can be wider than one cell (e.g. tab).
601 */
602 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
603 sheet_remove_tag(&doc.sh, &pane.caret_pos);
604 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
605
606 caret_update();
607}
608
609
610/** Get start-of-file s-point. */
611static void pt_get_sof(spt_t *pt)
612{
613 coord_t coord;
614
615 coord.row = coord.column = 1;
616 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
617}
618
619/** Get end-of-file s-point. */
620static void pt_get_eof(spt_t *pt)
621{
622 coord_t coord;
623 int num_rows;
624
625 sheet_get_num_rows(&doc.sh, &num_rows);
626 coord.row = num_rows;
627 coord.column = 1;
628
629 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
630}
631
632/** Display text in the status line. */
633static void status_display(char const *str)
634{
635 console_goto(con, 0, scr_rows - 1);
636 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
637 printf(" %*s ", -(scr_columns - 3), str);
638 fflush(stdout);
639 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
640
641 pane.rflags |= REDRAW_CARET;
642}
643
644/** @}
645 */
Note: See TracBrowser for help on using the repository browser.