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

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

Merge mainline changes.

  • Property mode set to 100644
File size: 13.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} 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 int 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, j;
365
366 sheet_get_num_rows(&doc.sh, &sh_rows);
367 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
368
369 /* Draw rows from the sheet. */
370
371 console_goto(con, 0, 0);
372 pane_row_range_display(0, rows);
373
374 /* Clear the remaining rows if file is short. */
375
376 for (i = rows; i < pane.rows; ++i) {
377 console_goto(con, 0, i);
378 for (j = 0; j < scr_columns; ++j)
379 putchar(' ');
380 fflush(stdout);
381 }
382
383 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
384 pane.rflags &= ~REDRAW_ROW;
385}
386
387/** Display just the row where the caret is. */
388static void pane_row_display(void)
389{
390 spt_t caret_pt;
391 coord_t coord;
392 int ridx;
393
394 tag_get_pt(&pane.caret_pos, &caret_pt);
395 spt_get_coord(&caret_pt, &coord);
396
397 ridx = coord.row - pane.sh_row;
398 pane_row_range_display(ridx, ridx + 1);
399 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
400}
401
402static void pane_row_range_display(int r0, int r1)
403{
404 int width;
405 int i, j, fill;
406 spt_t rb, re, dep;
407 coord_t rbc, rec;
408 char row_buf[ROW_BUF_SIZE];
409 wchar_t c;
410 size_t pos, size;
411 unsigned s_column;
412
413 /* Draw rows from the sheet. */
414
415 console_goto(con, 0, 0);
416 for (i = r0; i < r1; ++i) {
417 sheet_get_row_width(&doc.sh, pane.sh_row + i, &width);
418
419 /* Determine row starting point. */
420 rbc.row = pane.sh_row + i; rbc.column = 1;
421 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
422
423 /* Determine row ending point. */
424 rec.row = pane.sh_row + i; rec.column = width + 1;
425 sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
426
427 /* Copy the text of the row to the buffer. */
428 sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
429
430 /* Display text from the buffer. */
431
432 console_goto(con, 0, i);
433 size = str_size(row_buf);
434 pos = 0;
435 s_column = 1;
436 while (pos < size) {
437 c = str_decode(row_buf, &pos, size);
438 if (c != '\t') {
439 printf("%lc", c);
440 s_column += 1;
441 } else {
442 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
443 - s_column;
444
445 for (j = 0; j < fill; ++j)
446 putchar(' ');
447 s_column += fill;
448 }
449 }
450
451 /* Fill until the end of display area. */
452
453 if (str_length(row_buf) < (unsigned) scr_columns)
454 fill = scr_columns - str_length(row_buf);
455 else
456 fill = 0;
457
458 for (j = 0; j < fill; ++j)
459 putchar(' ');
460 fflush(stdout);
461 }
462
463 pane.rflags |= REDRAW_CARET;
464}
465
466/** Display pane status in the status line. */
467static void pane_status_display(void)
468{
469 spt_t caret_pt;
470 coord_t coord;
471 int n;
472
473 tag_get_pt(&pane.caret_pos, &caret_pt);
474 spt_get_coord(&caret_pt, &coord);
475
476 console_goto(con, 0, scr_rows - 1);
477 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
478 n = printf(" %d, %d: File '%s'. Ctrl-S Save Ctrl-Q Quit",
479 coord.row, coord.column, doc.file_name);
480 printf("%*s", scr_columns - 1 - n, "");
481 fflush(stdout);
482 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
483
484 pane.rflags |= REDRAW_CARET;
485}
486
487/** Set cursor to reflect position of the caret. */
488static void pane_caret_display(void)
489{
490 spt_t caret_pt;
491 coord_t coord;
492
493 tag_get_pt(&pane.caret_pos, &caret_pt);
494
495 spt_get_coord(&caret_pt, &coord);
496 console_goto(con, coord.column - 1, coord.row - pane.sh_row);
497}
498
499/** Insert a character at caret position. */
500static void insert_char(wchar_t c)
501{
502 spt_t pt;
503 char cbuf[STR_BOUNDS(1) + 1];
504 size_t offs;
505
506 tag_get_pt(&pane.caret_pos, &pt);
507
508 offs = 0;
509 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
510 cbuf[offs] = '\0';
511
512 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
513}
514
515/** Delete the character before the caret. */
516static void delete_char_before(void)
517{
518 spt_t sp, ep;
519 coord_t coord;
520
521 tag_get_pt(&pane.caret_pos, &ep);
522 spt_get_coord(&ep, &coord);
523
524 coord.column -= 1;
525 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
526
527 (void) sheet_delete(&doc.sh, &sp, &ep);
528}
529
530/** Delete the character after the caret. */
531static void delete_char_after(void)
532{
533 spt_t sp, ep;
534 coord_t coord;
535
536 tag_get_pt(&pane.caret_pos, &sp);
537 spt_get_coord(&sp, &coord);
538
539 sheet_get_cell_pt(&doc.sh, &coord, dir_after, &ep);
540
541 (void) sheet_delete(&doc.sh, &sp, &ep);
542}
543
544/** Scroll pane after caret has moved.
545 *
546 * After modifying the position of the caret, this is called to scroll
547 * the pane to ensure that the caret is in the visible area.
548 */
549static void caret_update(void)
550{
551 spt_t pt;
552 coord_t coord;
553
554 tag_get_pt(&pane.caret_pos, &pt);
555 spt_get_coord(&pt, &coord);
556
557 /* Scroll pane as necessary. */
558
559 if (coord.row < pane.sh_row) {
560 pane.sh_row = coord.row;
561 pane.rflags |= REDRAW_TEXT;
562 }
563 if (coord.row > pane.sh_row + pane.rows - 1) {
564 pane.sh_row = coord.row - pane.rows + 1;
565 pane.rflags |= REDRAW_TEXT;
566 }
567
568 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
569
570}
571
572/** Change the caret position.
573 *
574 * Moves caret relatively to the current position. Looking at the first
575 * character cell after the caret and moving by @a drow and @a dcolumn, we get
576 * to a new character cell, and thus a new character. Then we either go to the
577 * point before the the character or after it, depending on @a align_dir.
578 */
579static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
580{
581 spt_t pt;
582 coord_t coord;
583 int num_rows;
584
585 tag_get_pt(&pane.caret_pos, &pt);
586 spt_get_coord(&pt, &coord);
587 coord.row += drow; coord.column += dcolumn;
588
589 /* Clamp coordinates. */
590 if (drow < 0 && coord.row < 1) coord.row = 1;
591 if (dcolumn < 0 && coord.column < 1) coord.column = 1;
592 if (drow > 0) {
593 sheet_get_num_rows(&doc.sh, &num_rows);
594 if (coord.row > num_rows) coord.row = num_rows;
595 }
596
597 /*
598 * Select the point before or after the character at the designated
599 * coordinates. The character can be wider than one cell (e.g. tab).
600 */
601 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
602 sheet_remove_tag(&doc.sh, &pane.caret_pos);
603 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
604
605 caret_update();
606}
607
608
609/** Get start-of-file s-point. */
610static void pt_get_sof(spt_t *pt)
611{
612 coord_t coord;
613
614 coord.row = coord.column = 1;
615 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
616}
617
618/** Get end-of-file s-point. */
619static void pt_get_eof(spt_t *pt)
620{
621 coord_t coord;
622 int num_rows;
623
624 sheet_get_num_rows(&doc.sh, &num_rows);
625 coord.row = num_rows;
626 coord.column = 1;
627
628 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
629}
630
631/** Display text in the status line. */
632static void status_display(char const *str)
633{
634 console_goto(con, 0, scr_rows - 1);
635 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
636 printf(" %*s ", -(scr_columns - 3), str);
637 fflush(stdout);
638 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
639
640 pane.rflags |= REDRAW_CARET;
641}
642
643/** @}
644 */
Note: See TracBrowser for help on using the repository browser.