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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since b8b742e was b8b742e, checked in by Martin Sucha <sucha14@…>, 13 years ago

Add Go To Line functionality for edit (use Ctrl+L).

  • Property mode set to 100644
File size: 30.5 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>
[1352fc1]38#include <stdlib.h>
[3052ff4]39#include <sys/types.h>
40#include <vfs/vfs.h>
41#include <io/console.h>
[9f1362d4]42#include <io/style.h>
[3052ff4]43#include <io/keycode.h>
44#include <errno.h>
45#include <align.h>
46#include <macros.h>
[0902edfe]47#include <clipboard.h>
[3052ff4]48#include <bool.h>
49
50#include "sheet.h"
51
52enum redraw_flags {
53 REDRAW_TEXT = (1 << 0),
54 REDRAW_ROW = (1 << 1),
55 REDRAW_STATUS = (1 << 2),
56 REDRAW_CARET = (1 << 3)
57};
58
59/** Pane
60 *
61 * A rectangular area of the screen used to edit a document. Different
62 * panes can be possibly used to edit the same document.
63 */
64typedef struct {
65 /* Pane dimensions */
66 int rows, columns;
67
68 /* Position of the visible area */
69 int sh_row, sh_column;
70
71 /** Bitmask of components that need redrawing */
72 enum redraw_flags rflags;
73
74 /** Current position of the caret */
75 tag_t caret_pos;
[743e17b]76
[0f24c57]77 /** Start of selection */
78 tag_t sel_start;
79
[743e17b]80 /**
81 * Ideal column where the caret should try to get. This is used
82 * for maintaining the same column during vertical movement.
83 */
84 int ideal_column;
[3052ff4]85} pane_t;
86
87/** Document
88 *
89 * Associates a sheet with a file where it can be saved to.
90 */
91typedef struct {
92 char *file_name;
93 sheet_t sh;
94} doc_t;
95
[79ae36dd]96static console_ctrl_t *con;
[3052ff4]97static doc_t doc;
98static bool done;
99static pane_t pane;
[8190e63]100static bool cursor_visible;
[3052ff4]101
[96b02eb9]102static sysarg_t scr_rows;
103static sysarg_t scr_columns;
[3052ff4]104
105#define ROW_BUF_SIZE 4096
106#define BUF_SIZE 64
107#define TAB_WIDTH 8
108#define ED_INFTY 65536
109
[ba26129]110/** Maximum filename length that can be entered. */
111#define INFNAME_MAX_LEN 128
112
[8190e63]113static void cursor_show(void);
114static void cursor_hide(void);
115static void cursor_setvis(bool visible);
116
[79ae36dd]117static void key_handle_unmod(kbd_event_t const *ev);
118static void key_handle_ctrl(kbd_event_t const *ev);
119static void key_handle_shift(kbd_event_t const *ev);
[8f6bffdd]120static void key_handle_shift_ctrl(kbd_event_t const *ev);
[0f24c57]121static void key_handle_movement(unsigned int key, bool shift);
122
[3052ff4]123static int file_save(char const *fname);
[1352fc1]124static void file_save_as(void);
[3052ff4]125static int file_insert(char *fname);
126static int file_save_range(char const *fname, spt_t const *spos,
127 spt_t const *epos);
[0902edfe]128static char *range_get_str(spt_t const *spos, spt_t const *epos);
[0f24c57]129
[b8b742e]130static char *prompt(char const *prompt, char const *init_value);
131
[3052ff4]132static void pane_text_display(void);
133static void pane_row_display(void);
134static void pane_row_range_display(int r0, int r1);
135static void pane_status_display(void);
136static void pane_caret_display(void);
[0f24c57]137
[3052ff4]138static void insert_char(wchar_t c);
139static void delete_char_before(void);
140static void delete_char_after(void);
141static void caret_update(void);
142static void caret_move(int drow, int dcolumn, enum dir_spec align_dir);
[8f6bffdd]143static void caret_move_word_left(void);
144static void caret_move_word_right(void);
[b8b742e]145static void caret_move_to_line(int row);
146static void caret_go_to_line_ask(void);
[0f24c57]147
148static bool selection_active(void);
[cedd33b]149static void selection_sel_all(void);
[8f6bffdd]150static void selection_sel_range(spt_t pa, spt_t pb);
151static void selection_sel_prev_word(void);
152static void selection_sel_next_word(void);
[0902edfe]153static void selection_get_points(spt_t *pa, spt_t *pb);
[0f24c57]154static void selection_delete(void);
[0902edfe]155static void selection_copy(void);
156static void insert_clipboard_data(void);
[0f24c57]157
[3052ff4]158static void pt_get_sof(spt_t *pt);
159static void pt_get_eof(spt_t *pt);
[8f6bffdd]160static void pt_get_sol(spt_t *cpt, spt_t *spt);
161static void pt_get_eol(spt_t *cpt, spt_t *ept);
162static bool pt_is_word_beginning(spt_t *pt);
163static bool pt_is_delimiter(spt_t *pt);
164static bool pt_is_punctuation(spt_t *pt);
[0f24c57]165static int tag_cmp(tag_t const *a, tag_t const *b);
166static int spt_cmp(spt_t const *a, spt_t const *b);
167static int coord_cmp(coord_t const *a, coord_t const *b);
168
[3052ff4]169static void status_display(char const *str);
170
171
172int main(int argc, char *argv[])
173{
[79ae36dd]174 kbd_event_t ev;
[3052ff4]175 coord_t coord;
176 bool new_file;
177
178 spt_t pt;
179
[79ae36dd]180 con = console_init(stdin, stdout);
[3052ff4]181 console_clear(con);
182
183 console_get_size(con, &scr_columns, &scr_rows);
184
185 pane.rows = scr_rows - 1;
[99e5526]186 pane.columns = scr_columns;
[3052ff4]187 pane.sh_row = 1;
[99e5526]188 pane.sh_column = 1;
[3052ff4]189
190 /* Start with an empty sheet. */
191 sheet_init(&doc.sh);
192
193 /* Place caret at the beginning of file. */
194 coord.row = coord.column = 1;
195 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
196 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
[743e17b]197 pane.ideal_column = coord.column;
[3052ff4]198
199 if (argc == 2) {
[1352fc1]200 doc.file_name = str_dup(argv[1]);
[3052ff4]201 } else if (argc > 1) {
202 printf("Invalid arguments.\n");
203 return -2;
204 } else {
[1352fc1]205 doc.file_name = NULL;
[3052ff4]206 }
207
208 new_file = false;
209
[1352fc1]210 if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
[3052ff4]211 new_file = true;
212
213 /* Move to beginning of file. */
214 caret_move(-ED_INFTY, -ED_INFTY, dir_before);
215
[0f24c57]216 /* Place selection start tag. */
217 tag_get_pt(&pane.caret_pos, &pt);
218 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
219
[3052ff4]220 /* Initial display */
[8190e63]221 cursor_visible = true;
222
223 cursor_hide();
[3052ff4]224 console_clear(con);
225 pane_text_display();
226 pane_status_display();
[1352fc1]227 if (new_file && doc.file_name != NULL)
228 status_display("File not found. Starting empty file.");
[3052ff4]229 pane_caret_display();
[8190e63]230 cursor_show();
[3052ff4]231
232 done = false;
233
234 while (!done) {
[79ae36dd]235 console_get_kbd_event(con, &ev);
[3052ff4]236 pane.rflags = 0;
237
238 if (ev.type == KEY_PRESS) {
239 /* Handle key press. */
240 if (((ev.mods & KM_ALT) == 0) &&
[0f24c57]241 ((ev.mods & KM_SHIFT) == 0) &&
[3052ff4]242 (ev.mods & KM_CTRL) != 0) {
243 key_handle_ctrl(&ev);
[0f24c57]244 } else if (((ev.mods & KM_ALT) == 0) &&
245 ((ev.mods & KM_CTRL) == 0) &&
246 (ev.mods & KM_SHIFT) != 0) {
247 key_handle_shift(&ev);
[8f6bffdd]248 } else if (((ev.mods & KM_ALT) == 0) &&
249 ((ev.mods & KM_CTRL) != 0) &&
250 (ev.mods & KM_SHIFT) != 0) {
251 key_handle_shift_ctrl(&ev);
[0f24c57]252 } else if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
[3052ff4]253 key_handle_unmod(&ev);
254 }
255 }
256
257 /* Redraw as necessary. */
258
[8190e63]259 cursor_hide();
260
[3052ff4]261 if (pane.rflags & REDRAW_TEXT)
262 pane_text_display();
263 if (pane.rflags & REDRAW_ROW)
264 pane_row_display();
265 if (pane.rflags & REDRAW_STATUS)
266 pane_status_display();
267 if (pane.rflags & REDRAW_CARET)
268 pane_caret_display();
[8190e63]269
270 cursor_show();
[3052ff4]271 }
272
273 console_clear(con);
274
275 return 0;
276}
277
[8190e63]278static void cursor_show(void)
279{
280 cursor_setvis(true);
281}
282
283static void cursor_hide(void)
284{
285 cursor_setvis(false);
286}
287
288static void cursor_setvis(bool visible)
289{
290 if (cursor_visible != visible) {
291 console_cursor_visibility(con, visible);
292 cursor_visible = visible;
293 }
294}
295
[3052ff4]296/** Handle key without modifier. */
[79ae36dd]297static void key_handle_unmod(kbd_event_t const *ev)
[3052ff4]298{
299 switch (ev->key) {
300 case KC_ENTER:
[0f24c57]301 selection_delete();
[3052ff4]302 insert_char('\n');
303 caret_update();
304 break;
305 case KC_LEFT:
306 case KC_RIGHT:
307 case KC_UP:
308 case KC_DOWN:
309 case KC_HOME:
310 case KC_END:
311 case KC_PAGE_UP:
312 case KC_PAGE_DOWN:
[0f24c57]313 key_handle_movement(ev->key, false);
[3052ff4]314 break;
315 case KC_BACKSPACE:
[0f24c57]316 if (selection_active())
317 selection_delete();
318 else
319 delete_char_before();
[3052ff4]320 caret_update();
321 break;
322 case KC_DELETE:
[0f24c57]323 if (selection_active())
324 selection_delete();
325 else
326 delete_char_after();
[3052ff4]327 caret_update();
328 break;
329 default:
330 if (ev->c >= 32 || ev->c == '\t') {
[0f24c57]331 selection_delete();
332 insert_char(ev->c);
333 caret_update();
334 }
335 break;
336 }
337}
338
339/** Handle Shift-key combination. */
[79ae36dd]340static void key_handle_shift(kbd_event_t const *ev)
[0f24c57]341{
342 switch (ev->key) {
343 case KC_LEFT:
344 case KC_RIGHT:
345 case KC_UP:
346 case KC_DOWN:
347 case KC_HOME:
348 case KC_END:
349 case KC_PAGE_UP:
350 case KC_PAGE_DOWN:
351 key_handle_movement(ev->key, true);
352 break;
353 default:
354 if (ev->c >= 32 || ev->c == '\t') {
355 selection_delete();
[3052ff4]356 insert_char(ev->c);
357 caret_update();
358 }
359 break;
360 }
361}
362
363/** Handle Ctrl-key combination. */
[79ae36dd]364static void key_handle_ctrl(kbd_event_t const *ev)
[3052ff4]365{
366 switch (ev->key) {
367 case KC_Q:
368 done = true;
369 break;
370 case KC_S:
[1352fc1]371 if (doc.file_name != NULL)
372 file_save(doc.file_name);
373 else
374 file_save_as();
375 break;
376 case KC_E:
377 file_save_as();
[3052ff4]378 break;
[0902edfe]379 case KC_C:
380 selection_copy();
381 break;
382 case KC_V:
383 selection_delete();
384 insert_clipboard_data();
385 pane.rflags |= REDRAW_TEXT;
386 caret_update();
387 break;
[cedd33b]388 case KC_X:
389 selection_copy();
390 selection_delete();
391 pane.rflags |= REDRAW_TEXT;
392 caret_update();
393 break;
394 case KC_A:
395 selection_sel_all();
396 break;
[8f6bffdd]397 case KC_W:
398 if (selection_active())
399 break;
400 selection_sel_prev_word();
401 selection_delete();
402 break;
403 case KC_RIGHT:
404 caret_move_word_right();
405 break;
406 case KC_LEFT:
407 caret_move_word_left();
408 break;
[b8b742e]409 case KC_L:
410 caret_go_to_line_ask();
411 break;
[8f6bffdd]412 default:
413 break;
414 }
415}
416
417static void key_handle_shift_ctrl(kbd_event_t const *ev)
418{
419 switch(ev->key) {
420 case KC_LEFT:
421 selection_sel_prev_word();
422 break;
423 case KC_RIGHT:
424 selection_sel_next_word();
425 break;
[3052ff4]426 default:
427 break;
428 }
429}
430
[0f24c57]431static void key_handle_movement(unsigned int key, bool select)
432{
433 spt_t pt;
434 spt_t caret_pt;
435 coord_t c_old, c_new;
436 bool had_sel;
437
438 /* Check if we had selection before. */
439 tag_get_pt(&pane.caret_pos, &caret_pt);
440 tag_get_pt(&pane.sel_start, &pt);
441 had_sel = !spt_equal(&caret_pt, &pt);
442
443 switch (key) {
444 case KC_LEFT:
445 caret_move(0, -1, dir_before);
446 break;
447 case KC_RIGHT:
448 caret_move(0, 0, dir_after);
449 break;
450 case KC_UP:
451 caret_move(-1, 0, dir_before);
452 break;
453 case KC_DOWN:
454 caret_move(+1, 0, dir_before);
455 break;
456 case KC_HOME:
457 caret_move(0, -ED_INFTY, dir_before);
458 break;
459 case KC_END:
460 caret_move(0, +ED_INFTY, dir_before);
461 break;
462 case KC_PAGE_UP:
463 caret_move(-pane.rows, 0, dir_before);
464 break;
465 case KC_PAGE_DOWN:
466 caret_move(+pane.rows, 0, dir_before);
467 break;
468 default:
469 break;
470 }
471
472 if (select == false) {
473 /* Move sel_start to the same point as caret. */
474 sheet_remove_tag(&doc.sh, &pane.sel_start);
475 tag_get_pt(&pane.caret_pos, &pt);
476 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
477 }
478
479 if (select) {
480 tag_get_pt(&pane.caret_pos, &pt);
481 spt_get_coord(&caret_pt, &c_old);
482 spt_get_coord(&pt, &c_new);
483
484 if (c_old.row == c_new.row)
485 pane.rflags |= REDRAW_ROW;
486 else
487 pane.rflags |= REDRAW_TEXT;
488
489 } else if (had_sel == true) {
490 /* Redraw because text was unselected. */
491 pane.rflags |= REDRAW_TEXT;
492 }
493}
494
[3052ff4]495/** Save the document. */
496static int file_save(char const *fname)
497{
498 spt_t sp, ep;
499 int rc;
500
501 status_display("Saving...");
502 pt_get_sof(&sp);
503 pt_get_eof(&ep);
504
505 rc = file_save_range(fname, &sp, &ep);
[1352fc1]506
507 switch (rc) {
508 case EINVAL:
509 status_display("Error opening file!");
510 break;
511 case EIO:
512 status_display("Error writing data!");
513 break;
514 default:
515 status_display("File saved.");
516 break;
517 }
[3052ff4]518
519 return rc;
520}
521
[1352fc1]522/** Change document name and save. */
523static void file_save_as(void)
524{
[a000878c]525 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
526 char *fname;
527
[b8b742e]528 fname = prompt("Save As", old_fname);
[1352fc1]529 if (fname == NULL) {
530 status_display("Save cancelled.");
531 return;
532 }
533
[a000878c]534 int rc = file_save(fname);
[1352fc1]535 if (rc != EOK)
536 return;
537
538 if (doc.file_name != NULL)
539 free(doc.file_name);
540 doc.file_name = fname;
541}
542
[b8b742e]543/** Ask for a string. */
544static char *prompt(char const *prompt, char const *init_value)
[1352fc1]545{
[79ae36dd]546 kbd_event_t ev;
[1352fc1]547 char *str;
[ba26129]548 wchar_t buffer[INFNAME_MAX_LEN + 1];
549 int max_len;
[1352fc1]550 int nc;
551 bool done;
552
553 asprintf(&str, "%s: %s", prompt, init_value);
554 status_display(str);
[9f1362d4]555 console_set_pos(con, 1 + str_length(str), scr_rows - 1);
[1352fc1]556 free(str);
557
[9f1362d4]558 console_set_style(con, STYLE_INVERTED);
[1352fc1]559
[ba26129]560 max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
561 str_to_wstr(buffer, max_len + 1, init_value);
[1352fc1]562 nc = wstr_length(buffer);
563 done = false;
564
565 while (!done) {
[79ae36dd]566 console_get_kbd_event(con, &ev);
[1352fc1]567
568 if (ev.type == KEY_PRESS) {
569 /* Handle key press. */
570 if (((ev.mods & KM_ALT) == 0) &&
571 (ev.mods & KM_CTRL) != 0) {
572 ;
573 } else if ((ev.mods & (KM_CTRL | KM_ALT)) == 0) {
574 switch (ev.key) {
575 case KC_ESCAPE:
576 return NULL;
577 case KC_BACKSPACE:
578 if (nc > 0) {
579 putchar('\b');
[79ae36dd]580 console_flush(con);
[1352fc1]581 --nc;
582 }
583 break;
584 case KC_ENTER:
585 done = true;
586 break;
587 default:
[ba26129]588 if (ev.c >= 32 && nc < max_len) {
[1352fc1]589 putchar(ev.c);
[79ae36dd]590 console_flush(con);
[1352fc1]591 buffer[nc++] = ev.c;
592 }
593 break;
594 }
595 }
596 }
597 }
598
599 buffer[nc] = '\0';
[b67c7d64]600 str = wstr_to_astr(buffer);
[1352fc1]601
[9f1362d4]602 console_set_style(con, STYLE_NORMAL);
[1352fc1]603
604 return str;
605}
606
[3052ff4]607/** Insert file at caret position.
608 *
609 * Reads in the contents of a file and inserts them at the current position
610 * of the caret.
611 */
612static int file_insert(char *fname)
613{
614 FILE *f;
615 wchar_t c;
616 char buf[BUF_SIZE];
617 int bcnt;
618 int n_read;
619 size_t off;
620
621 f = fopen(fname, "rt");
622 if (f == NULL)
623 return EINVAL;
624
625 bcnt = 0;
626
627 while (true) {
628 if (bcnt < STR_BOUNDS(1)) {
629 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
630 bcnt += n_read;
631 }
632
633 off = 0;
634 c = str_decode(buf, &off, bcnt);
635 if (c == '\0')
636 break;
637
638 bcnt -= off;
639 memcpy(buf, buf + off, bcnt);
640
641 insert_char(c);
642 }
643
644 fclose(f);
645
646 return EOK;
647}
648
649/** Save a range of text into a file. */
650static int file_save_range(char const *fname, spt_t const *spos,
651 spt_t const *epos)
652{
653 FILE *f;
654 char buf[BUF_SIZE];
655 spt_t sp, bep;
656 size_t bytes, n_written;
657
658 f = fopen(fname, "wt");
659 if (f == NULL)
660 return EINVAL;
661
662 sp = *spos;
663
664 do {
665 sheet_copy_out(&doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
666 bytes = str_size(buf);
667
668 n_written = fwrite(buf, 1, bytes, f);
669 if (n_written != bytes) {
670 return EIO;
671 }
672
673 sp = bep;
674 } while (!spt_equal(&bep, epos));
675
[1352fc1]676 if (fclose(f) != EOK)
677 return EIO;
[3052ff4]678
679 return EOK;
680}
681
[0902edfe]682/** Return contents of range as a new string. */
683static char *range_get_str(spt_t const *spos, spt_t const *epos)
684{
685 char *buf;
686 spt_t sp, bep;
687 size_t bytes;
688 size_t buf_size, bpos;
689
690 buf_size = 1;
691
692 buf = malloc(buf_size);
693 if (buf == NULL)
694 return NULL;
695
696 bpos = 0;
697 sp = *spos;
698
699 while (true) {
700 sheet_copy_out(&doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
701 &bep);
702 bytes = str_size(&buf[bpos]);
703 bpos += bytes;
704 sp = bep;
705
706 if (spt_equal(&bep, epos))
707 break;
708
709 buf_size *= 2;
710 buf = realloc(buf, buf_size);
711 if (buf == NULL)
712 return NULL;
713 }
714
715 return buf;
716}
717
[3052ff4]718static void pane_text_display(void)
719{
720 int sh_rows, rows;
721
722 sheet_get_num_rows(&doc.sh, &sh_rows);
723 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
724
725 /* Draw rows from the sheet. */
726
[9f1362d4]727 console_set_pos(con, 0, 0);
[3052ff4]728 pane_row_range_display(0, rows);
729
730 /* Clear the remaining rows if file is short. */
[9f1362d4]731
732 int i;
[96b02eb9]733 sysarg_t j;
[3052ff4]734 for (i = rows; i < pane.rows; ++i) {
[9f1362d4]735 console_set_pos(con, 0, i);
[3052ff4]736 for (j = 0; j < scr_columns; ++j)
737 putchar(' ');
[79ae36dd]738 console_flush(con);
[3052ff4]739 }
740
741 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
742 pane.rflags &= ~REDRAW_ROW;
743}
744
745/** Display just the row where the caret is. */
746static void pane_row_display(void)
747{
748 spt_t caret_pt;
749 coord_t coord;
750 int ridx;
751
752 tag_get_pt(&pane.caret_pos, &caret_pt);
753 spt_get_coord(&caret_pt, &coord);
754
755 ridx = coord.row - pane.sh_row;
756 pane_row_range_display(ridx, ridx + 1);
757 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
758}
759
760static void pane_row_range_display(int r0, int r1)
761{
762 int i, j, fill;
[0f24c57]763 spt_t rb, re, dep, pt;
[3052ff4]764 coord_t rbc, rec;
765 char row_buf[ROW_BUF_SIZE];
766 wchar_t c;
767 size_t pos, size;
[36e9cd1]768 int s_column;
[0f24c57]769 coord_t csel_start, csel_end, ctmp;
770
771 /* Determine selection start and end. */
772
773 tag_get_pt(&pane.sel_start, &pt);
774 spt_get_coord(&pt, &csel_start);
775
776 tag_get_pt(&pane.caret_pos, &pt);
777 spt_get_coord(&pt, &csel_end);
778
779 if (coord_cmp(&csel_start, &csel_end) > 0) {
780 ctmp = csel_start;
781 csel_start = csel_end;
782 csel_end = ctmp;
783 }
[3052ff4]784
785 /* Draw rows from the sheet. */
786
[9f1362d4]787 console_set_pos(con, 0, 0);
[3052ff4]788 for (i = r0; i < r1; ++i) {
[99e5526]789 /* Starting point for row display */
790 rbc.row = pane.sh_row + i;
791 rbc.column = pane.sh_column;
[3052ff4]792 sheet_get_cell_pt(&doc.sh, &rbc, dir_before, &rb);
793
[99e5526]794 /* Ending point for row display */
795 rec.row = pane.sh_row + i;
796 rec.column = pane.sh_column + pane.columns;
[3052ff4]797 sheet_get_cell_pt(&doc.sh, &rec, dir_before, &re);
798
799 /* Copy the text of the row to the buffer. */
800 sheet_copy_out(&doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
801
802 /* Display text from the buffer. */
803
[0f24c57]804 if (coord_cmp(&csel_start, &rbc) <= 0 &&
805 coord_cmp(&rbc, &csel_end) < 0) {
[79ae36dd]806 console_flush(con);
[9f1362d4]807 console_set_style(con, STYLE_SELECTED);
[79ae36dd]808 console_flush(con);
[0f24c57]809 }
810
[9f1362d4]811 console_set_pos(con, 0, i);
[3052ff4]812 size = str_size(row_buf);
813 pos = 0;
[cd82bb1]814 s_column = pane.sh_column;
[3052ff4]815 while (pos < size) {
[36e9cd1]816 if ((csel_start.row == rbc.row) && (csel_start.column == s_column)) {
[79ae36dd]817 console_flush(con);
[9f1362d4]818 console_set_style(con, STYLE_SELECTED);
[79ae36dd]819 console_flush(con);
[0f24c57]820 }
821
[36e9cd1]822 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
[79ae36dd]823 console_flush(con);
[9f1362d4]824 console_set_style(con, STYLE_NORMAL);
[79ae36dd]825 console_flush(con);
[0f24c57]826 }
827
[3052ff4]828 c = str_decode(row_buf, &pos, size);
829 if (c != '\t') {
[7e752b2]830 printf("%lc", (wint_t) c);
[3052ff4]831 s_column += 1;
832 } else {
833 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH)
834 - s_column;
835
836 for (j = 0; j < fill; ++j)
837 putchar(' ');
838 s_column += fill;
839 }
840 }
841
[36e9cd1]842 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
[79ae36dd]843 console_flush(con);
[9f1362d4]844 console_set_style(con, STYLE_NORMAL);
[79ae36dd]845 console_flush(con);
[0f24c57]846 }
847
[3052ff4]848 /* Fill until the end of display area. */
849
[c5a6076]850 if ((unsigned)s_column - 1 < scr_columns)
851 fill = scr_columns - (s_column - 1);
[3052ff4]852 else
853 fill = 0;
854
855 for (j = 0; j < fill; ++j)
856 putchar(' ');
[79ae36dd]857 console_flush(con);
[9f1362d4]858 console_set_style(con, STYLE_NORMAL);
[3052ff4]859 }
860
861 pane.rflags |= REDRAW_CARET;
862}
863
864/** Display pane status in the status line. */
865static void pane_status_display(void)
866{
867 spt_t caret_pt;
868 coord_t coord;
869
870 tag_get_pt(&pane.caret_pos, &caret_pt);
871 spt_get_coord(&caret_pt, &coord);
872
[a000878c]873 const char *fname = (doc.file_name != NULL) ? doc.file_name : "<unnamed>";
[1352fc1]874
[9f1362d4]875 console_set_pos(con, 0, scr_rows - 1);
876 console_set_style(con, STYLE_INVERTED);
[a000878c]877 int n = printf(" %d, %d: File '%s'. Ctrl-Q Quit Ctrl-S Save "
[1352fc1]878 "Ctrl-E Save As", coord.row, coord.column, fname);
[7e752b2]879
880 int pos = scr_columns - 1 - n;
881 printf("%*s", pos, "");
[79ae36dd]882 console_flush(con);
[9f1362d4]883 console_set_style(con, STYLE_NORMAL);
[3052ff4]884
885 pane.rflags |= REDRAW_CARET;
886}
887
888/** Set cursor to reflect position of the caret. */
889static void pane_caret_display(void)
890{
891 spt_t caret_pt;
892 coord_t coord;
893
894 tag_get_pt(&pane.caret_pos, &caret_pt);
895
896 spt_get_coord(&caret_pt, &coord);
[9f1362d4]897 console_set_pos(con, coord.column - pane.sh_column,
[99e5526]898 coord.row - pane.sh_row);
[3052ff4]899}
900
901/** Insert a character at caret position. */
902static void insert_char(wchar_t c)
903{
904 spt_t pt;
905 char cbuf[STR_BOUNDS(1) + 1];
906 size_t offs;
907
908 tag_get_pt(&pane.caret_pos, &pt);
909
910 offs = 0;
911 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
912 cbuf[offs] = '\0';
913
914 (void) sheet_insert(&doc.sh, &pt, dir_before, cbuf);
[884b461]915
916 pane.rflags |= REDRAW_ROW;
917 if (c == '\n')
918 pane.rflags |= REDRAW_TEXT;
[3052ff4]919}
920
921/** Delete the character before the caret. */
922static void delete_char_before(void)
923{
924 spt_t sp, ep;
925 coord_t coord;
926
927 tag_get_pt(&pane.caret_pos, &ep);
928 spt_get_coord(&ep, &coord);
929
930 coord.column -= 1;
931 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &sp);
932
933 (void) sheet_delete(&doc.sh, &sp, &ep);
[884b461]934
935 pane.rflags |= REDRAW_ROW;
936 if (coord.column < 1)
937 pane.rflags |= REDRAW_TEXT;
[3052ff4]938}
939
940/** Delete the character after the caret. */
941static void delete_char_after(void)
942{
943 spt_t sp, ep;
[884b461]944 coord_t sc, ec;
[3052ff4]945
946 tag_get_pt(&pane.caret_pos, &sp);
[884b461]947 spt_get_coord(&sp, &sc);
[3052ff4]948
[884b461]949 sheet_get_cell_pt(&doc.sh, &sc, dir_after, &ep);
950 spt_get_coord(&ep, &ec);
[3052ff4]951
952 (void) sheet_delete(&doc.sh, &sp, &ep);
[884b461]953
954 pane.rflags |= REDRAW_ROW;
955 if (ec.row != sc.row)
956 pane.rflags |= REDRAW_TEXT;
[3052ff4]957}
958
959/** Scroll pane after caret has moved.
960 *
961 * After modifying the position of the caret, this is called to scroll
962 * the pane to ensure that the caret is in the visible area.
963 */
964static void caret_update(void)
965{
966 spt_t pt;
967 coord_t coord;
968
969 tag_get_pt(&pane.caret_pos, &pt);
970 spt_get_coord(&pt, &coord);
971
[99e5526]972 /* Scroll pane vertically. */
[3052ff4]973
974 if (coord.row < pane.sh_row) {
975 pane.sh_row = coord.row;
976 pane.rflags |= REDRAW_TEXT;
977 }
[99e5526]978
[3052ff4]979 if (coord.row > pane.sh_row + pane.rows - 1) {
980 pane.sh_row = coord.row - pane.rows + 1;
981 pane.rflags |= REDRAW_TEXT;
982 }
983
[99e5526]984 /* Scroll pane horizontally. */
985
986 if (coord.column < pane.sh_column) {
987 pane.sh_column = coord.column;
988 pane.rflags |= REDRAW_TEXT;
989 }
[3052ff4]990
[99e5526]991 if (coord.column > pane.sh_column + pane.columns - 1) {
992 pane.sh_column = coord.column - pane.columns + 1;
993 pane.rflags |= REDRAW_TEXT;
994 }
995
996 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
[3052ff4]997}
998
999/** Change the caret position.
1000 *
1001 * Moves caret relatively to the current position. Looking at the first
1002 * character cell after the caret and moving by @a drow and @a dcolumn, we get
1003 * to a new character cell, and thus a new character. Then we either go to the
1004 * point before the the character or after it, depending on @a align_dir.
1005 */
1006static void caret_move(int drow, int dcolumn, enum dir_spec align_dir)
1007{
1008 spt_t pt;
1009 coord_t coord;
1010 int num_rows;
[743e17b]1011 bool pure_vertical;
[3052ff4]1012
1013 tag_get_pt(&pane.caret_pos, &pt);
1014 spt_get_coord(&pt, &coord);
1015 coord.row += drow; coord.column += dcolumn;
1016
1017 /* Clamp coordinates. */
1018 if (drow < 0 && coord.row < 1) coord.row = 1;
[8f6bffdd]1019 if (dcolumn < 0 && coord.column < 1) {
1020 if (coord.row < 2)
1021 coord.column = 1;
1022 else {
1023 coord.row--;
1024 sheet_get_row_width(&doc.sh, coord.row, &coord.column);
1025 }
1026 }
[3052ff4]1027 if (drow > 0) {
1028 sheet_get_num_rows(&doc.sh, &num_rows);
1029 if (coord.row > num_rows) coord.row = num_rows;
1030 }
1031
[743e17b]1032 /* For purely vertical movement try attaining @c ideal_column. */
1033 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
1034 if (pure_vertical)
1035 coord.column = pane.ideal_column;
1036
[3052ff4]1037 /*
1038 * Select the point before or after the character at the designated
1039 * coordinates. The character can be wider than one cell (e.g. tab).
1040 */
1041 sheet_get_cell_pt(&doc.sh, &coord, align_dir, &pt);
1042 sheet_remove_tag(&doc.sh, &pane.caret_pos);
1043 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
1044
[743e17b]1045 /* For non-vertical movement set the new value for @c ideal_column. */
1046 if (!pure_vertical) {
1047 spt_get_coord(&pt, &coord);
1048 pane.ideal_column = coord.column;
1049 }
1050
[3052ff4]1051 caret_update();
1052}
1053
[8f6bffdd]1054static void caret_move_word_left(void)
1055{
1056 spt_t pt;
1057
1058 do {
1059 caret_move(0, -1, dir_before);
1060
1061 tag_get_pt(&pane.caret_pos, &pt);
1062
1063 sheet_remove_tag(&doc.sh, &pane.sel_start);
1064 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
1065 } while (!pt_is_word_beginning(&pt));
1066
1067 pane.rflags |= REDRAW_TEXT;
1068}
1069
1070static void caret_move_word_right(void)
1071{
1072 spt_t pt;
1073
1074 do {
1075 caret_move(0, 0, dir_after);
1076
1077 tag_get_pt(&pane.caret_pos, &pt);
1078
1079 sheet_remove_tag(&doc.sh, &pane.sel_start);
1080 sheet_place_tag(&doc.sh, &pt, &pane.sel_start);
1081 } while (!pt_is_word_beginning(&pt));
1082
1083 pane.rflags |= REDRAW_TEXT;
1084}
1085
[b8b742e]1086/** Change the caret position to a beginning of a given line
1087 */
1088static void caret_move_to_line(int row)
1089{
1090 spt_t pt;
1091 coord_t coord;
1092 int num_rows;
1093
1094 tag_get_pt(&pane.caret_pos, &pt);
1095 spt_get_coord(&pt, &coord);
1096 coord.row = row;
1097 coord.column = 1;
1098
1099 /* Clamp coordinates. */
1100 if (coord.row < 1) coord.row = 1;
1101 sheet_get_num_rows(&doc.sh, &num_rows);
1102 if (coord.row > num_rows) coord.row = num_rows;
1103
1104 /*
1105 * Select the point before the character at the designated
1106 * coordinates. The character can be wider than one cell (e.g. tab).
1107 */
1108 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &pt);
1109 sheet_remove_tag(&doc.sh, &pane.caret_pos);
1110 sheet_place_tag(&doc.sh, &pt, &pane.caret_pos);
1111
1112 /* Set the new value for @c ideal_column. */
1113 spt_get_coord(&pt, &coord);
1114 pane.ideal_column = coord.column;
1115
1116 caret_update();
1117}
1118
1119/** Ask for line and go to it. */
1120static void caret_go_to_line_ask(void)
1121{
1122 char *sline;
1123
1124 sline = prompt("Go to line", "");
1125 if (sline == NULL) {
1126 status_display("Go to line cancelled.");
1127 return;
1128 }
1129
1130 char *endptr;
1131 int line = strtol(sline, &endptr, 10);
1132 if (*endptr != '\0') {
1133 status_display("Invalid number entered.");
1134 return;
1135 }
1136
1137 caret_move_to_line(line);
1138}
1139
1140
[0f24c57]1141/** Check for non-empty selection. */
1142static bool selection_active(void)
1143{
1144 return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
1145}
1146
[0902edfe]1147static void selection_get_points(spt_t *pa, spt_t *pb)
1148{
1149 spt_t pt;
1150
1151 tag_get_pt(&pane.sel_start, pa);
1152 tag_get_pt(&pane.caret_pos, pb);
1153
1154 if (spt_cmp(pa, pb) > 0) {
1155 pt = *pa;
1156 *pa = *pb;
1157 *pb = pt;
1158 }
1159}
1160
[0f24c57]1161/** Delete selected text. */
1162static void selection_delete(void)
1163{
1164 spt_t pa, pb;
1165 coord_t ca, cb;
1166 int rel;
1167
1168 tag_get_pt(&pane.sel_start, &pa);
1169 tag_get_pt(&pane.caret_pos, &pb);
1170 spt_get_coord(&pa, &ca);
1171 spt_get_coord(&pb, &cb);
1172 rel = coord_cmp(&ca, &cb);
1173
1174 if (rel == 0)
1175 return;
1176
1177 if (rel < 0)
1178 sheet_delete(&doc.sh, &pa, &pb);
1179 else
1180 sheet_delete(&doc.sh, &pb, &pa);
1181
1182 if (ca.row == cb.row)
1183 pane.rflags |= REDRAW_ROW;
1184 else
1185 pane.rflags |= REDRAW_TEXT;
1186}
[3052ff4]1187
[8f6bffdd]1188/** Select all text in the editor */
[cedd33b]1189static void selection_sel_all(void)
1190{
1191 spt_t spt, ept;
1192
1193 pt_get_sof(&spt);
1194 pt_get_eof(&ept);
[8f6bffdd]1195
1196 selection_sel_range(spt, ept);
1197}
1198
1199/** Select select all text in a given range with the given direction */
1200static void selection_sel_range(spt_t pa, spt_t pb)
1201{
[cedd33b]1202 sheet_remove_tag(&doc.sh, &pane.sel_start);
[8f6bffdd]1203 sheet_place_tag(&doc.sh, &pa, &pane.sel_start);
[cedd33b]1204 sheet_remove_tag(&doc.sh, &pane.caret_pos);
[8f6bffdd]1205 sheet_place_tag(&doc.sh, &pb, &pane.caret_pos);
[cedd33b]1206
1207 pane.rflags |= REDRAW_TEXT;
[cd82bb1]1208 caret_update();
[cedd33b]1209}
1210
[8f6bffdd]1211/** Add the previous word to the selection */
1212static void selection_sel_prev_word(void)
1213{
1214 spt_t cpt, wpt, spt, ept;
1215
1216 selection_get_points(&spt, &ept);
1217
1218 tag_get_pt(&pane.caret_pos, &cpt);
1219 caret_move_word_left();
1220 tag_get_pt(&pane.caret_pos, &wpt);
1221
1222 if (spt_cmp(&spt, &cpt) == 0)
1223 selection_sel_range(ept, wpt);
1224 else
1225 selection_sel_range(spt, wpt);
1226}
1227
1228/** Add the next word to the selection */
1229static void selection_sel_next_word(void)
1230{
1231 spt_t cpt, wpt, spt, ept;
1232
1233 selection_get_points(&spt, &ept);
1234
1235 tag_get_pt(&pane.caret_pos, &cpt);
1236 caret_move_word_right();
1237 tag_get_pt(&pane.caret_pos, &wpt);
1238
1239 if (spt_cmp(&ept, &cpt) == 0)
1240 selection_sel_range(spt, wpt);
1241 else
1242 selection_sel_range(ept, wpt);
1243}
1244
[0902edfe]1245static void selection_copy(void)
1246{
1247 spt_t pa, pb;
1248 char *str;
1249
1250 selection_get_points(&pa, &pb);
1251 str = range_get_str(&pa, &pb);
1252 if (str == NULL || clipboard_put_str(str) != EOK) {
1253 status_display("Copying to clipboard failed!");
1254 }
1255 free(str);
1256}
1257
1258static void insert_clipboard_data(void)
1259{
1260 char *str;
1261 size_t off;
1262 wchar_t c;
1263 int rc;
1264
1265 rc = clipboard_get_str(&str);
1266 if (rc != EOK || str == NULL)
1267 return;
1268
1269 off = 0;
1270
1271 while (true) {
1272 c = str_decode(str, &off, STR_NO_LIMIT);
1273 if (c == '\0')
1274 break;
1275
1276 insert_char(c);
1277 }
1278
1279 free(str);
1280}
1281
[3052ff4]1282/** Get start-of-file s-point. */
1283static void pt_get_sof(spt_t *pt)
1284{
1285 coord_t coord;
1286
1287 coord.row = coord.column = 1;
1288 sheet_get_cell_pt(&doc.sh, &coord, dir_before, pt);
1289}
1290
1291/** Get end-of-file s-point. */
1292static void pt_get_eof(spt_t *pt)
1293{
1294 coord_t coord;
1295 int num_rows;
1296
1297 sheet_get_num_rows(&doc.sh, &num_rows);
[00413c5c]1298 coord.row = num_rows + 1;
[3052ff4]1299 coord.column = 1;
1300
1301 sheet_get_cell_pt(&doc.sh, &coord, dir_after, pt);
1302}
1303
[8f6bffdd]1304/** Get start-of-line s-point for given s-point cpt */
1305static void pt_get_sol(spt_t *cpt, spt_t *spt)
1306{
1307 coord_t coord;
1308
1309 spt_get_coord(cpt, &coord);
1310 coord.column = 1;
1311
1312 sheet_get_cell_pt(&doc.sh, &coord, dir_before, spt);
1313}
1314
1315/** Get end-of-line s-point for given s-point cpt */
1316static void pt_get_eol(spt_t *cpt, spt_t *ept)
1317{
1318 coord_t coord;
1319 int row_width;
1320
1321 spt_get_coord(cpt, &coord);
1322 sheet_get_row_width(&doc.sh, coord.row, &row_width);
1323 coord.column = row_width - 1;
1324
1325 sheet_get_cell_pt(&doc.sh, &coord, dir_after, ept);
1326}
1327
1328/** Check whether the spt is at a beginning of a word */
1329static bool pt_is_word_beginning(spt_t *pt)
1330{
1331 spt_t lp, sfp, efp, slp, elp;
1332 coord_t coord;
1333
1334 pt_get_sof(&sfp);
1335 pt_get_eof(&efp);
1336 pt_get_sol(pt, &slp);
1337 pt_get_eol(pt, &elp);
1338
1339 /* the spt is at the beginning or end of the file or line */
1340 if ((spt_cmp(&sfp, pt) == 0) || (spt_cmp(&efp, pt) == 0)
1341 || (spt_cmp(&slp, pt) == 0) || (spt_cmp(&elp, pt) == 0))
1342 return true;
1343
1344 /* the spt is a delimiter */
1345 if (pt_is_delimiter(pt))
1346 return false;
1347
1348 spt_get_coord(pt, &coord);
1349
1350 coord.column -= 1;
1351 sheet_get_cell_pt(&doc.sh, &coord, dir_before, &lp);
1352
1353 return pt_is_delimiter(&lp)
1354 || (pt_is_punctuation(pt) && !pt_is_punctuation(&lp))
1355 || (pt_is_punctuation(&lp) && !pt_is_punctuation(pt));
1356}
1357
1358static wchar_t get_first_wchar(const char *str)
1359{
1360 size_t offset = 0;
1361 return str_decode(str, &offset, str_size(str));
1362}
1363
1364static bool pt_is_delimiter(spt_t *pt)
1365{
1366 spt_t rp;
1367 coord_t coord;
1368 char *ch = NULL;
1369
1370 spt_get_coord(pt, &coord);
1371
1372 coord.column += 1;
1373 sheet_get_cell_pt(&doc.sh, &coord, dir_after, &rp);
1374
1375 ch = range_get_str(pt, &rp);
1376 if (ch == NULL)
1377 return false;
1378
1379 wchar_t first_char = get_first_wchar(ch);
1380 switch(first_char) {
1381 case ' ':
1382 case '\t':
1383 case '\n':
1384 return true;
1385 default:
1386 return false;
1387 }
1388}
1389
1390static bool pt_is_punctuation(spt_t *pt)
1391{
1392 spt_t rp;
1393 coord_t coord;
1394 char *ch = NULL;
1395
1396 spt_get_coord(pt, &coord);
1397
1398 coord.column += 1;
1399 sheet_get_cell_pt(&doc.sh, &coord, dir_after, &rp);
1400
1401 ch = range_get_str(pt, &rp);
1402 if (ch == NULL)
1403 return false;
1404
1405 wchar_t first_char = get_first_wchar(ch);
1406 switch(first_char) {
1407 case ',':
1408 case '.':
1409 case ';':
1410 case ':':
1411 case '/':
1412 case '?':
1413 case '\\':
1414 case '|':
1415 case '_':
1416 case '+':
1417 case '-':
1418 case '*':
1419 case '=':
1420 case '<':
1421 case '>':
1422 return true;
1423 default:
1424 return false;
1425 }
1426}
1427
[0f24c57]1428/** Compare tags. */
1429static int tag_cmp(tag_t const *a, tag_t const *b)
1430{
1431 spt_t pa, pb;
1432
1433 tag_get_pt(a, &pa);
1434 tag_get_pt(b, &pb);
1435
1436 return spt_cmp(&pa, &pb);
1437}
1438
1439/** Compare s-points. */
1440static int spt_cmp(spt_t const *a, spt_t const *b)
1441{
1442 coord_t ca, cb;
1443
1444 spt_get_coord(a, &ca);
1445 spt_get_coord(b, &cb);
1446
1447 return coord_cmp(&ca, &cb);
1448}
1449
1450/** Compare coordinats. */
1451static int coord_cmp(coord_t const *a, coord_t const *b)
1452{
1453 if (a->row - b->row != 0)
1454 return a->row - b->row;
1455
1456 return a->column - b->column;
1457}
1458
[3052ff4]1459/** Display text in the status line. */
1460static void status_display(char const *str)
1461{
[9f1362d4]1462 console_set_pos(con, 0, scr_rows - 1);
1463 console_set_style(con, STYLE_INVERTED);
[7e752b2]1464
1465 int pos = -(scr_columns - 3);
1466 printf(" %*s ", pos, str);
[79ae36dd]1467 console_flush(con);
[9f1362d4]1468 console_set_style(con, STYLE_NORMAL);
[3052ff4]1469
1470 pane.rflags |= REDRAW_CARET;
1471}
1472
1473/** @}
1474 */
Note: See TracBrowser for help on using the repository browser.