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

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

Fix computation of eof s-point.

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