source: mainline/uspace/app/edit/edit.c@ 99e5526

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

Handle long lines properly.

  • Property mode set to 100644
File size: 14.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 <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.columns = scr_columns;
133 pane.sh_row = 1;
134 pane.sh_column = 1;
135
136 /* Start with an empty sheet. */
137 sheet_init(&doc.sh);
138
139 /* Place caret at the beginning of file. */
140 coord.row = coord.column = 1;
141 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
142 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
143
144 if (argc == 2) {
145 doc.file_name = argv[1];
146 } else if (argc > 1) {
147 printf("Invalid arguments.\n");
148 return -2;
149 } else {
150 doc.file_name = "/edit.txt";
151 }
152
153 new_file = false;
154
155 if (file_insert(doc.file_name) != EOK)
156 new_file = true;
157
158 /* Move to beginning of file. */
159 caret_move(-ED_INFTY, -ED_INFTY, dir_before);
160
161 /* Initial display */
162 console_clear(con);
163 pane_text_display();
164 pane_status_display();
165 if (new_file)
166 status_display("File not found. Created empty file.");
167 pane_caret_display();
168
169
170 done = false;
171
172 while (!done) {
173 console_get_event(con, &ev);
174 pane.rflags = 0;
175
176 if (ev.type == KEY_PRESS) {
177 /* Handle key press. */
178 if (((ev.mods & KM_ALT) == 0) &&
179 (ev.mods & KM_CTRL) != 0) {
180 key_handle_ctrl(&ev);
181 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
182 key_handle_unmod(&ev);
183 }
184 }
185
186 /* Redraw as necessary. */
187
188 if (pane.rflags & REDRAW_TEXT)
189 pane_text_display();
190 if (pane.rflags & REDRAW_ROW)
191 pane_row_display();
192 if (pane.rflags & REDRAW_STATUS)
193 pane_status_display();
194 if (pane.rflags & REDRAW_CARET)
195 pane_caret_display();
196
197 }
198
199 console_clear(con);
200
201 return 0;
202}
203
204/** Handle key without modifier. */
205static void key_handle_unmod(console_event_t const *ev)
206{
207 switch (ev->key) {
208 case KC_ENTER:
209 insert_char('\n');
210 pane.rflags |= REDRAW_TEXT;
211 caret_update();
212 break;
213 case KC_LEFT:
214 caret_move(0, -1, dir_before);
215 break;
216 case KC_RIGHT:
217 caret_move(0, 0, dir_after);
218 break;
219 case KC_UP:
220 caret_move(-1, 0, dir_before);
221 break;
222 case KC_DOWN:
223 caret_move(+1, 0, dir_before);
224 break;
225 case KC_HOME:
226 caret_move(0, -ED_INFTY, dir_before);
227 break;
228 case KC_END:
229 caret_move(0, +ED_INFTY, dir_before);
230 break;
231 case KC_PAGE_UP:
232 caret_move(-pane.rows, 0, dir_before);
233 break;
234 case KC_PAGE_DOWN:
235 caret_move(+pane.rows, 0, dir_before);
236 break;
237 case KC_BACKSPACE:
238 delete_char_before();
239 pane.rflags |= REDRAW_TEXT;
240 caret_update();
241 break;
242 case KC_DELETE:
243 delete_char_after();
244 pane.rflags |= REDRAW_TEXT;
245 caret_update();
246 break;
247 default:
248 if (ev->c >= 32 || ev->c == '\t') {
249 insert_char(ev->c);
250 pane.rflags |= REDRAW_ROW;
251 caret_update();
252 }
253 break;
254 }
255}
256
257/** Handle Ctrl-key combination. */
258static void key_handle_ctrl(console_event_t const *ev)
259{
260 switch (ev->key) {
261 case KC_Q:
262 done = true;
263 break;
264 case KC_S:
265 (void) file_save(doc.file_name);
266 break;
267 default:
268 break;
269 }
270}
271
272
273/** Save the document. */
274static int file_save(char const *fname)
275{
276 spt_t sp, ep;
277 int rc;
278
279 status_display("Saving...");
280 pt_get_sof(&sp);
281 pt_get_eof(&ep);
282
283 rc = file_save_range(fname, &sp, &ep);
284 status_display("File saved.");
285
286 return rc;
287}
288
289/** Insert file at caret position.
290 *
291 * Reads in the contents of a file and inserts them at the current position
292 * of the caret.
293 */
294static int file_insert(char *fname)
295{
296 FILE *f;
297 wchar_t c;
298 char buf[BUF_SIZE];
299 int bcnt;
300 int n_read;
301 size_t off;
302
303 f = fopen(fname, "rt");
304 if (f == NULL)
305 return EINVAL;
306
307 bcnt = 0;
308
309 while (true) {
310 if (bcnt < STR_BOUNDS(1)) {
311 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
312 bcnt += n_read;
313 }
314
315 off = 0;
316 c = str_decode(buf, &off, bcnt);
317 if (c == '\0')
318 break;
319
320 bcnt -= off;
321 memcpy(buf, buf + off, bcnt);
322
323 insert_char(c);
324 }
325
326 fclose(f);
327
328 return EOK;
329}
330
331/** Save a range of text into a file. */
332static int file_save_range(char const *fname, spt_t const *spos,
333 spt_t const *epos)
334{
335 FILE *f;
336 char buf[BUF_SIZE];
337 spt_t sp, bep;
338 size_t bytes, n_written;
339
340 f = fopen(fname, "wt");
341 if (f == NULL)
342 return EINVAL;
343
344 sp = *spos;
345
346 do {
347 sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
348 bytes = str_size(buf);
349
350 n_written = fwrite(buf, 1, bytes, f);
351 if (n_written != bytes) {
352 return EIO;
353 }
354
355 sp = bep;
356 } while (!spt_equal(&bep, epos));
357
358 fclose(f);
359
360 return EOK;
361}
362
363static void pane_text_display(void)
364{
365 int sh_rows, rows;
366 int i, j;
367
368 sheet_get_num_rows(&doc.sh, &sh_rows);
369 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
370
371 /* Draw rows from the sheet. */
372
373 console_goto(con, 0, 0);
374 pane_row_range_display(0, rows);
375
376 /* Clear the remaining rows if file is short. */
377
378 for (i = rows; i < pane.rows; ++i) {
379 console_goto(con, 0, i);
380 for (j = 0; j < scr_columns; ++j)
381 putchar(' ');
382 fflush(stdout);
383 }
384
385 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
386 pane.rflags &= ~REDRAW_ROW;
387}
388
389/** Display just the row where the caret is. */
390static void pane_row_display(void)
391{
392 spt_t caret_pt;
393 coord_t coord;
394 int ridx;
395
396 tag_get_pt(&pane.caret_pos, &caret_pt);
397 spt_get_coord(&caret_pt, &coord);
398
399 ridx = coord.row - pane.sh_row;
400 pane_row_range_display(ridx, ridx + 1);
401 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
402}
403
404static void pane_row_range_display(int r0, int r1)
405{
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 /* Starting point for row display */
419 rbc.row = pane.sh_row + i;
420 rbc.column = pane.sh_column;
421 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
422
423 /* Ending point for row display */
424 rec.row = pane.sh_row + i;
425 rec.column = pane.sh_column + pane.columns;
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) < (unsigned) 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 - pane.sh_column,
498 coord.row - pane.sh_row);
499}
500
501/** Insert a character at caret position. */
502static void insert_char(wchar_t c)
503{
504 spt_t pt;
505 char cbuf[STR_BOUNDS(1) + 1];
506 size_t offs;
507
508 tag_get_pt(&pane.caret_pos, &pt);
509
510 offs = 0;
511 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
512 cbuf[offs] = '\0';
513
514 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
515}
516
517/** Delete the character before the caret. */
518static void delete_char_before(void)
519{
520 spt_t sp, ep;
521 coord_t coord;
522
523 tag_get_pt(&pane.caret_pos, &ep);
524 spt_get_coord(&ep, &coord);
525
526 coord.column -= 1;
527 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
528
529 (void) sheet_delete(&doc.sh, &sp, &ep);
530}
531
532/** Delete the character after the caret. */
533static void delete_char_after(void)
534{
535 spt_t sp, ep;
536 coord_t coord;
537
538 tag_get_pt(&pane.caret_pos, &sp);
539 spt_get_coord(&sp, &coord);
540
541 sheet_get_cell_pt(&doc.sh, &coord, dir_after, &ep);
542
543 (void) sheet_delete(&doc.sh, &sp, &ep);
544}
545
546/** Scroll pane after caret has moved.
547 *
548 * After modifying the position of the caret, this is called to scroll
549 * the pane to ensure that the caret is in the visible area.
550 */
551static void caret_update(void)
552{
553 spt_t pt;
554 coord_t coord;
555
556 tag_get_pt(&pane.caret_pos, &pt);
557 spt_get_coord(&pt, &coord);
558
559 /* Scroll pane vertically. */
560
561 if (coord.row < pane.sh_row) {
562 pane.sh_row = coord.row;
563 pane.rflags |= REDRAW_TEXT;
564 }
565
566 if (coord.row > pane.sh_row + pane.rows - 1) {
567 pane.sh_row = coord.row - pane.rows + 1;
568 pane.rflags |= REDRAW_TEXT;
569 }
570
571 /* Scroll pane horizontally. */
572
573 if (coord.column < pane.sh_column) {
574 pane.sh_column = coord.column;
575 pane.rflags |= REDRAW_TEXT;
576 }
577
578 if (coord.column > pane.sh_column + pane.columns - 1) {
579 pane.sh_column = coord.column - pane.columns + 1;
580 pane.rflags |= REDRAW_TEXT;
581 }
582
583 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
584}
585
586/** Change the caret position.
587 *
588 * Moves caret relatively to the current position. Looking at the first
589 * character cell after the caret and moving by @a drow and @a dcolumn, we get
590 * to a new character cell, and thus a new character. Then we either go to the
591 * point before the the character or after it, depending on @a align_dir.
592 */
593static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
594{
595 spt_t pt;
596 coord_t coord;
597 int num_rows;
598
599 tag_get_pt(&pane.caret_pos, &pt);
600 spt_get_coord(&pt, &coord);
601 coord.row += drow; coord.column += dcolumn;
602
603 /* Clamp coordinates. */
604 if (drow < 0 && coord.row < 1) coord.row = 1;
605 if (dcolumn < 0 && coord.column < 1) coord.column = 1;
606 if (drow > 0) {
607 sheet_get_num_rows(&doc.sh, &num_rows);
608 if (coord.row > num_rows) coord.row = num_rows;
609 }
610
611 /*
612 * Select the point before or after the character at the designated
613 * coordinates. The character can be wider than one cell (e.g. tab).
614 */
615 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
616 sheet_remove_tag(&doc.sh, &pane.caret_pos);
617 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
618
619 caret_update();
620}
621
622
623/** Get start-of-file s-point. */
624static void pt_get_sof(spt_t *pt)
625{
626 coord_t coord;
627
628 coord.row = coord.column = 1;
629 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
630}
631
632/** Get end-of-file s-point. */
633static void pt_get_eof(spt_t *pt)
634{
635 coord_t coord;
636 int num_rows;
637
638 sheet_get_num_rows(&doc.sh, &num_rows);
639 coord.row = num_rows;
640 coord.column = 1;
641
642 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
643}
644
645/** Display text in the status line. */
646static void status_display(char const *str)
647{
648 console_goto(con, 0, scr_rows - 1);
649 console_set_color(con, COLOR_WHITE, COLOR_BLACK, 0);
650 printf(" %*s ", -(scr_columns - 3), str);
651 fflush(stdout);
652 console_set_color(con, COLOR_BLACK, COLOR_WHITE, 0);
653
654 pane.rflags |= REDRAW_CARET;
655}
656
657/** @}
658 */
Note: See TracBrowser for help on using the repository browser.