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

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

Set color for selected text

  • Property mode set to 100644
File size: 43.6 KB
Line 
1/*
2 * Copyright (c) 2021 Jiri Svoboda
3 * Copyright (c) 2012 Martin Sucha
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/** @addtogroup edit
31 * @brief Text editor.
32 * @{
33 */
34/**
35 * @file
36 */
37
38#include <align.h>
39#include <clipboard.h>
40#include <errno.h>
41#include <gfx/color.h>
42#include <gfx/cursor.h>
43#include <gfx/font.h>
44#include <gfx/render.h>
45#include <gfx/text.h>
46#include <io/kbd_event.h>
47#include <io/keycode.h>
48#include <io/pos_event.h>
49#include <io/style.h>
50#include <macros.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <stddef.h>
54#include <stdbool.h>
55#include <types/common.h>
56#include <ui/control.h>
57#include <ui/fixed.h>
58#include <ui/menu.h>
59#include <ui/menubar.h>
60#include <ui/menuentry.h>
61#include <ui/resource.h>
62#include <ui/ui.h>
63#include <ui/window.h>
64#include <vfs/vfs.h>
65
66#include "sheet.h"
67#include "search.h"
68
69enum redraw_flags {
70 REDRAW_TEXT = (1 << 0),
71 REDRAW_ROW = (1 << 1),
72 REDRAW_STATUS = (1 << 2),
73 REDRAW_CARET = (1 << 3)
74};
75
76/** Pane
77 *
78 * A rectangular area of the screen used to edit a document. Different
79 * panes can be possibly used to edit the same document. This is a custom
80 * UI control.
81 */
82typedef struct {
83 /** Base control object */
84 struct ui_control *control;
85
86 /** Containing window */
87 ui_window_t *window;
88
89 /** UI resource */
90 struct ui_resource *res;
91
92 /** Pane rectangle */
93 gfx_rect_t rect;
94
95 /** Pane color */
96 gfx_color_t *color;
97
98 /** Selection color */
99 gfx_color_t *sel_color;
100
101 /* Pane dimensions */
102 int rows, columns;
103
104 /* Position of the visible area */
105 int sh_row, sh_column;
106
107 /** Bitmask of components that need redrawing */
108 enum redraw_flags rflags;
109
110 /** Current position of the caret */
111 tag_t caret_pos;
112
113 /** Start of selection */
114 tag_t sel_start;
115
116 /** Active keyboard modifiers */
117 keymod_t keymod;
118
119 /**
120 * Ideal column where the caret should try to get. This is used
121 * for maintaining the same column during vertical movement.
122 */
123 int ideal_column;
124
125 char *previous_search;
126 bool previous_search_reverse;
127} pane_t;
128
129/** Text editor */
130typedef struct {
131 /** User interface */
132 ui_t *ui;
133 /** Editor window */
134 ui_window_t *window;
135 /** UI resource */
136 ui_resource_t *ui_res;
137 /** Menu bar */
138 ui_menu_bar_t *menubar;
139} edit_t;
140
141/** Document
142 *
143 * Associates a sheet with a file where it can be saved to.
144 */
145typedef struct {
146 char *file_name;
147 sheet_t *sh;
148} doc_t;
149
150static edit_t edit;
151static doc_t doc;
152static bool done;
153static pane_t pane;
154
155static sysarg_t scr_rows;
156static sysarg_t scr_columns;
157
158#define ROW_BUF_SIZE 4096
159#define BUF_SIZE 64
160#define TAB_WIDTH 8
161
162/** Maximum filename length that can be entered. */
163#define INFNAME_MAX_LEN 128
164
165static void cursor_setvis(bool visible);
166
167static void key_handle_press(kbd_event_t *ev);
168static void key_handle_unmod(kbd_event_t const *ev);
169static void key_handle_ctrl(kbd_event_t const *ev);
170static void key_handle_shift(kbd_event_t const *ev);
171static void key_handle_shift_ctrl(kbd_event_t const *ev);
172static void key_handle_movement(unsigned int key, bool shift);
173
174static void pos_handle(pos_event_t *ev);
175
176static errno_t file_save(char const *fname);
177static void file_save_as(void);
178static errno_t file_insert(char *fname);
179static errno_t file_save_range(char const *fname, spt_t const *spos,
180 spt_t const *epos);
181static char *range_get_str(spt_t const *spos, spt_t const *epos);
182
183static char *prompt(char const *prompt, char const *init_value);
184
185static errno_t pane_init(ui_window_t *, pane_t *);
186static void pane_fini(pane_t *);
187static ui_control_t *pane_ctl(pane_t *);
188static errno_t pane_update(pane_t *);
189static errno_t pane_text_display(pane_t *);
190static void pane_row_display(void);
191static errno_t pane_row_range_display(pane_t *, int r0, int r1);
192static void pane_status_display(void);
193static void pane_caret_display(pane_t *);
194
195static void insert_char(char32_t c);
196static void delete_char_before(void);
197static void delete_char_after(void);
198static void caret_update(void);
199static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir, bool select);
200static void caret_move_absolute(int row, int column, enum dir_spec align_dir, bool select);
201static void caret_move(spt_t spt, bool select, bool update_ideal_column);
202static void caret_move_word_left(bool select);
203static void caret_move_word_right(bool select);
204static void caret_go_to_line_ask(void);
205
206static bool selection_active(void);
207static void selection_sel_all(void);
208static void selection_sel_range(spt_t pa, spt_t pb);
209static void selection_get_points(spt_t *pa, spt_t *pb);
210static void selection_delete(void);
211static void selection_copy(void);
212static void insert_clipboard_data(void);
213
214static void search(char *pattern, bool reverse);
215static void search_prompt(bool reverse);
216static void search_repeat(void);
217
218static void pt_get_sof(spt_t *pt);
219static void pt_get_eof(spt_t *pt);
220static void pt_get_sol(spt_t *cpt, spt_t *spt);
221static void pt_get_eol(spt_t *cpt, spt_t *ept);
222static bool pt_is_word_beginning(spt_t *pt);
223static bool pt_is_delimiter(spt_t *pt);
224static bool pt_is_punctuation(spt_t *pt);
225static spt_t pt_find_word_left(spt_t spt);
226static spt_t pt_find_word_left(spt_t spt);
227
228static int tag_cmp(tag_t const *a, tag_t const *b);
229static int spt_cmp(spt_t const *a, spt_t const *b);
230static int coord_cmp(coord_t const *a, coord_t const *b);
231
232static void status_display(char const *str);
233static errno_t edit_ui_create(edit_t *);
234static void edit_ui_destroy(edit_t *);
235
236static void edit_wnd_close(ui_window_t *, void *);
237static void edit_wnd_kbd_event(ui_window_t *, void *, kbd_event_t *);
238
239static ui_window_cb_t edit_window_cb = {
240 .close = edit_wnd_close,
241 .kbd = edit_wnd_kbd_event
242};
243
244static void edit_file_exit(ui_menu_entry_t *, void *);
245static void edit_edit_copy(ui_menu_entry_t *, void *);
246static void edit_edit_paste(ui_menu_entry_t *, void *);
247
248static void pane_ctl_destroy(void *);
249static errno_t pane_ctl_paint(void *);
250static ui_evclaim_t pane_ctl_pos_event(void *, pos_event_t *);
251
252/** Pabe control ops */
253ui_control_ops_t pane_ctl_ops = {
254 .destroy = pane_ctl_destroy,
255 .paint = pane_ctl_paint,
256 .pos_event = pane_ctl_pos_event
257};
258
259
260int main(int argc, char *argv[])
261{
262 bool new_file;
263 errno_t rc;
264
265 (void) pos_handle;
266 (void) pane_row_display;
267
268// console_get_size(con, &scr_columns, &scr_rows);
269 scr_columns = 80;
270 scr_rows = 25;
271
272 pane.rows = scr_rows - 1;
273 pane.columns = scr_columns;
274 pane.sh_row = 1;
275 pane.sh_column = 1;
276
277 /* Start with an empty sheet. */
278 rc = sheet_create(&doc.sh);
279 if (rc != EOK) {
280 printf("Out of memory.\n");
281 return -1;
282 }
283
284 /* Place caret at the beginning of file. */
285 spt_t sof;
286 pt_get_sof(&sof);
287 sheet_place_tag(doc.sh, &sof, &pane.caret_pos);
288 pane.ideal_column = 1;
289
290 if (argc == 2) {
291 doc.file_name = str_dup(argv[1]);
292 } else if (argc > 1) {
293 printf("Invalid arguments.\n");
294 return -2;
295 } else {
296 doc.file_name = NULL;
297 }
298
299 new_file = false;
300
301 if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
302 new_file = true;
303
304 /* Place selection start tag. */
305 sheet_place_tag(doc.sh, &sof, &pane.sel_start);
306
307 /* Move to beginning of file. */
308 pt_get_sof(&sof);
309 caret_move(sof, true, true);
310
311 /* Create UI */
312 rc = edit_ui_create(&edit);
313 if (rc != EOK)
314 return 1;
315
316 /* Initial display */
317 rc = ui_window_paint(edit.window);
318 if (rc != EOK) {
319 printf("Error painting window.\n");
320 return rc;
321 }
322
323 pane_status_display();
324 if (new_file && doc.file_name != NULL)
325 status_display("File not found. Starting empty file.");
326 pane_caret_display(&pane);
327 cursor_setvis(true);
328
329 ui_run(edit.ui);
330
331 edit_ui_destroy(&edit);
332 return 0;
333}
334
335/** Create text editor UI.
336 *
337 * @param edit Editor
338 * @return EOK on success or an error code
339 */
340static errno_t edit_ui_create(edit_t *edit)
341{
342 errno_t rc;
343 ui_wnd_params_t params;
344 ui_fixed_t *fixed = NULL;
345 ui_menu_t *mfile = NULL;
346 ui_menu_t *medit = NULL;
347 ui_menu_entry_t *mexit = NULL;
348 ui_menu_entry_t *mcopy = NULL;
349 ui_menu_entry_t *mpaste = NULL;
350 gfx_rect_t arect;
351 gfx_rect_t rect;
352
353 rc = ui_create(UI_CONSOLE_DEFAULT, &edit->ui);
354 if (rc != EOK) {
355 printf("Error creating UI on display %s.\n",
356 UI_CONSOLE_DEFAULT);
357 goto error;
358 }
359
360 ui_wnd_params_init(&params);
361 params.caption = "Text Editor";
362 params.rect.p0.x = 0;
363 params.rect.p0.y = 0;
364 params.rect.p1.x = 80;
365 params.rect.p1.y = 25;
366
367 rc = ui_window_create(edit->ui, &params, &edit->window);
368 if (rc != EOK) {
369 printf("Error creating window.\n");
370 goto error;
371 }
372
373 ui_window_set_cb(edit->window, &edit_window_cb, (void *) edit);
374
375 edit->ui_res = ui_window_get_res(edit->window);
376
377 rc = ui_fixed_create(&fixed);
378 if (rc != EOK) {
379 printf("Error creating fixed layout.\n");
380 return rc;
381 }
382
383 rc = ui_menu_bar_create(edit->ui, edit->window, &edit->menubar);
384 if (rc != EOK) {
385 printf("Error creating menu bar.\n");
386 return rc;
387 }
388
389 rc = ui_menu_create(edit->menubar, "File", &mfile);
390 if (rc != EOK) {
391 printf("Error creating menu.\n");
392 return rc;
393 }
394
395 rc = ui_menu_entry_create(mfile, "Exit", "Alt-F4", &mexit);
396 if (rc != EOK) {
397 printf("Error creating menu.\n");
398 return rc;
399 }
400
401 ui_menu_entry_set_cb(mexit, edit_file_exit, (void *) edit);
402
403 rc = ui_menu_create(edit->menubar, "Edit", &medit);
404 if (rc != EOK) {
405 printf("Error creating menu.\n");
406 return rc;
407 }
408
409 rc = ui_menu_entry_create(medit, "Copy", "Ctrl-C", &mcopy);
410 if (rc != EOK) {
411 printf("Error creating menu.\n");
412 return rc;
413 }
414
415 ui_menu_entry_set_cb(mcopy, edit_edit_copy, (void *) edit);
416
417 rc = ui_menu_entry_create(medit, "Paste", "Ctrl-V", &mpaste);
418 if (rc != EOK) {
419 printf("Error creating menu.\n");
420 return rc;
421 }
422
423 ui_menu_entry_set_cb(mpaste, edit_edit_paste, (void *) edit);
424
425 ui_window_get_app_rect(edit->window, &arect);
426
427 rect.p0 = arect.p0;
428 rect.p1.x = arect.p1.x;
429 rect.p1.y = arect.p0.y + 1;
430 ui_menu_bar_set_rect(edit->menubar, &rect);
431
432 rc = ui_fixed_add(fixed, ui_menu_bar_ctl(edit->menubar));
433 if (rc != EOK) {
434 printf("Error adding control to layout.\n");
435 return rc;
436 }
437
438 rc = pane_init(edit->window, &pane);
439 if (rc != EOK) {
440 printf("Error initializing pane.\n");
441 return rc;
442 }
443
444 rc = ui_fixed_add(fixed, pane_ctl(&pane));
445 if (rc != EOK) {
446 printf("Error adding control to layout.\n");
447 return rc;
448 }
449
450 ui_window_add(edit->window, ui_fixed_ctl(fixed));
451 return EOK;
452error:
453 if (edit->window != NULL)
454 ui_window_destroy(edit->window);
455 if (edit->ui != NULL)
456 ui_destroy(edit->ui);
457 return rc;
458}
459
460/** Destroy text editor UI.
461 *
462 * @param edit Editor
463 */
464static void edit_ui_destroy(edit_t *edit)
465{
466 ui_window_destroy(edit->window);
467 ui_destroy(edit->ui);
468}
469
470/* Handle key press. */
471static void key_handle_press(kbd_event_t *ev)
472{
473 if (((ev->mods & KM_ALT) == 0) &&
474 ((ev->mods & KM_SHIFT) == 0) &&
475 (ev->mods & KM_CTRL) != 0) {
476 key_handle_ctrl(ev);
477 } else if (((ev->mods & KM_ALT) == 0) &&
478 ((ev->mods & KM_CTRL) == 0) &&
479 (ev->mods & KM_SHIFT) != 0) {
480 key_handle_shift(ev);
481 } else if (((ev->mods & KM_ALT) == 0) &&
482 ((ev->mods & KM_CTRL) != 0) &&
483 (ev->mods & KM_SHIFT) != 0) {
484 key_handle_shift_ctrl(ev);
485 } else if ((ev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
486 key_handle_unmod(ev);
487 }
488}
489
490static void cursor_setvis(bool visible)
491{
492 gfx_context_t *gc = ui_window_get_gc(edit.window);
493
494 (void) gfx_cursor_set_visible(gc, visible);
495}
496
497/** Handle key without modifier. */
498static void key_handle_unmod(kbd_event_t const *ev)
499{
500 switch (ev->key) {
501 case KC_ENTER:
502 selection_delete();
503 insert_char('\n');
504 caret_update();
505 break;
506 case KC_LEFT:
507 case KC_RIGHT:
508 case KC_UP:
509 case KC_DOWN:
510 case KC_HOME:
511 case KC_END:
512 case KC_PAGE_UP:
513 case KC_PAGE_DOWN:
514 key_handle_movement(ev->key, false);
515 break;
516 case KC_BACKSPACE:
517 if (selection_active())
518 selection_delete();
519 else
520 delete_char_before();
521 caret_update();
522 break;
523 case KC_DELETE:
524 if (selection_active())
525 selection_delete();
526 else
527 delete_char_after();
528 caret_update();
529 break;
530 default:
531 if (ev->c >= 32 || ev->c == '\t') {
532 selection_delete();
533 insert_char(ev->c);
534 caret_update();
535 }
536 break;
537 }
538}
539
540/** Handle Shift-key combination. */
541static void key_handle_shift(kbd_event_t const *ev)
542{
543 switch (ev->key) {
544 case KC_LEFT:
545 case KC_RIGHT:
546 case KC_UP:
547 case KC_DOWN:
548 case KC_HOME:
549 case KC_END:
550 case KC_PAGE_UP:
551 case KC_PAGE_DOWN:
552 key_handle_movement(ev->key, true);
553 break;
554 default:
555 if (ev->c >= 32 || ev->c == '\t') {
556 selection_delete();
557 insert_char(ev->c);
558 caret_update();
559 }
560 break;
561 }
562}
563
564/** Handle Ctrl-key combination. */
565static void key_handle_ctrl(kbd_event_t const *ev)
566{
567 spt_t pt;
568 switch (ev->key) {
569 case KC_Q:
570 done = true;
571 break;
572 case KC_S:
573 if (doc.file_name != NULL)
574 file_save(doc.file_name);
575 else
576 file_save_as();
577 break;
578 case KC_E:
579 file_save_as();
580 break;
581 case KC_C:
582 selection_copy();
583 break;
584 case KC_V:
585 selection_delete();
586 insert_clipboard_data();
587 pane.rflags |= REDRAW_TEXT;
588 caret_update();
589 break;
590 case KC_X:
591 selection_copy();
592 selection_delete();
593 pane.rflags |= REDRAW_TEXT;
594 caret_update();
595 break;
596 case KC_A:
597 selection_sel_all();
598 break;
599 case KC_RIGHT:
600 caret_move_word_right(false);
601 break;
602 case KC_LEFT:
603 caret_move_word_left(false);
604 break;
605 case KC_L:
606 caret_go_to_line_ask();
607 break;
608 case KC_F:
609 search_prompt(false);
610 break;
611 case KC_N:
612 search_repeat();
613 break;
614 case KC_HOME:
615 pt_get_sof(&pt);
616 caret_move(pt, false, true);
617 break;
618 case KC_END:
619 pt_get_eof(&pt);
620 caret_move(pt, false, true);
621 break;
622 default:
623 break;
624 }
625}
626
627static void key_handle_shift_ctrl(kbd_event_t const *ev)
628{
629 spt_t pt;
630 switch (ev->key) {
631 case KC_LEFT:
632 caret_move_word_left(true);
633 break;
634 case KC_RIGHT:
635 caret_move_word_right(true);
636 break;
637 case KC_F:
638 search_prompt(true);
639 break;
640 case KC_HOME:
641 pt_get_sof(&pt);
642 caret_move(pt, true, true);
643 break;
644 case KC_END:
645 pt_get_eof(&pt);
646 caret_move(pt, true, true);
647 break;
648 default:
649 break;
650 }
651}
652
653static void pos_handle(pos_event_t *ev)
654{
655 coord_t bc;
656 spt_t pt;
657 bool select;
658
659 if (ev->type == POS_PRESS && ev->vpos < (unsigned)pane.rows) {
660 bc.row = pane.sh_row + ev->vpos;
661 bc.column = pane.sh_column + ev->hpos;
662 sheet_get_cell_pt(doc.sh, &bc, dir_before, &pt);
663
664 select = (pane.keymod & KM_SHIFT) != 0;
665
666 caret_move(pt, select, true);
667 }
668}
669
670/** Move caret while preserving or resetting selection. */
671static void caret_move(spt_t new_caret_pt, bool select, bool update_ideal_column)
672{
673 spt_t old_caret_pt, old_sel_pt;
674 coord_t c_old, c_new;
675 bool had_sel;
676
677 /* Check if we had selection before. */
678 tag_get_pt(&pane.caret_pos, &old_caret_pt);
679 tag_get_pt(&pane.sel_start, &old_sel_pt);
680 had_sel = !spt_equal(&old_caret_pt, &old_sel_pt);
681
682 /* Place tag of the caret */
683 sheet_remove_tag(doc.sh, &pane.caret_pos);
684 sheet_place_tag(doc.sh, &new_caret_pt, &pane.caret_pos);
685
686 if (select == false) {
687 /* Move sel_start to the same point as caret. */
688 sheet_remove_tag(doc.sh, &pane.sel_start);
689 sheet_place_tag(doc.sh, &new_caret_pt, &pane.sel_start);
690 }
691
692 spt_get_coord(&new_caret_pt, &c_new);
693 if (select) {
694 spt_get_coord(&old_caret_pt, &c_old);
695
696 if (c_old.row == c_new.row)
697 pane.rflags |= REDRAW_ROW;
698 else
699 pane.rflags |= REDRAW_TEXT;
700
701 } else if (had_sel == true) {
702 /* Redraw because text was unselected. */
703 pane.rflags |= REDRAW_TEXT;
704 }
705
706 if (update_ideal_column)
707 pane.ideal_column = c_new.column;
708
709 caret_update();
710}
711
712static void key_handle_movement(unsigned int key, bool select)
713{
714 spt_t pt;
715 switch (key) {
716 case KC_LEFT:
717 caret_move_relative(0, -1, dir_before, select);
718 break;
719 case KC_RIGHT:
720 caret_move_relative(0, 0, dir_after, select);
721 break;
722 case KC_UP:
723 caret_move_relative(-1, 0, dir_before, select);
724 break;
725 case KC_DOWN:
726 caret_move_relative(+1, 0, dir_before, select);
727 break;
728 case KC_HOME:
729 tag_get_pt(&pane.caret_pos, &pt);
730 pt_get_sol(&pt, &pt);
731 caret_move(pt, select, true);
732 break;
733 case KC_END:
734 tag_get_pt(&pane.caret_pos, &pt);
735 pt_get_eol(&pt, &pt);
736 caret_move(pt, select, true);
737 break;
738 case KC_PAGE_UP:
739 caret_move_relative(-pane.rows, 0, dir_before, select);
740 break;
741 case KC_PAGE_DOWN:
742 caret_move_relative(+pane.rows, 0, dir_before, select);
743 break;
744 default:
745 break;
746 }
747}
748
749/** Save the document. */
750static errno_t file_save(char const *fname)
751{
752 spt_t sp, ep;
753 errno_t rc;
754
755 status_display("Saving...");
756 pt_get_sof(&sp);
757 pt_get_eof(&ep);
758
759 rc = file_save_range(fname, &sp, &ep);
760
761 switch (rc) {
762 case EINVAL:
763 status_display("Error opening file!");
764 break;
765 case EIO:
766 status_display("Error writing data!");
767 break;
768 default:
769 status_display("File saved.");
770 break;
771 }
772
773 return rc;
774}
775
776/** Change document name and save. */
777static void file_save_as(void)
778{
779 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
780 char *fname;
781
782 fname = prompt("Save As", old_fname);
783 if (fname == NULL) {
784 status_display("Save cancelled.");
785 return;
786 }
787
788 errno_t rc = file_save(fname);
789 if (rc != EOK)
790 return;
791
792 if (doc.file_name != NULL)
793 free(doc.file_name);
794 doc.file_name = fname;
795}
796
797/** Ask for a string. */
798static char *prompt(char const *prompt, char const *init_value)
799{
800 return str_dup("42");
801}
802
803/** Insert file at caret position.
804 *
805 * Reads in the contents of a file and inserts them at the current position
806 * of the caret.
807 */
808static errno_t file_insert(char *fname)
809{
810 FILE *f;
811 char32_t c;
812 char buf[BUF_SIZE];
813 int bcnt;
814 int n_read;
815 size_t off;
816
817 f = fopen(fname, "rt");
818 if (f == NULL)
819 return EINVAL;
820
821 bcnt = 0;
822
823 while (true) {
824 if (bcnt < STR_BOUNDS(1)) {
825 n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
826 bcnt += n_read;
827 }
828
829 off = 0;
830 c = str_decode(buf, &off, bcnt);
831 if (c == '\0')
832 break;
833
834 bcnt -= off;
835 memcpy(buf, buf + off, bcnt);
836
837 insert_char(c);
838 }
839
840 fclose(f);
841
842 return EOK;
843}
844
845/** Save a range of text into a file. */
846static errno_t file_save_range(char const *fname, spt_t const *spos,
847 spt_t const *epos)
848{
849 FILE *f;
850 char buf[BUF_SIZE];
851 spt_t sp, bep;
852 size_t bytes, n_written;
853
854 f = fopen(fname, "wt");
855 if (f == NULL)
856 return EINVAL;
857
858 sp = *spos;
859
860 do {
861 sheet_copy_out(doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
862 bytes = str_size(buf);
863
864 n_written = fwrite(buf, 1, bytes, f);
865 if (n_written != bytes) {
866 return EIO;
867 }
868
869 sp = bep;
870 } while (!spt_equal(&bep, epos));
871
872 if (fclose(f) < 0)
873 return EIO;
874
875 return EOK;
876}
877
878/** Return contents of range as a new string. */
879static char *range_get_str(spt_t const *spos, spt_t const *epos)
880{
881 char *buf;
882 spt_t sp, bep;
883 size_t bytes;
884 size_t buf_size, bpos;
885
886 buf_size = 1;
887
888 buf = malloc(buf_size);
889 if (buf == NULL)
890 return NULL;
891
892 bpos = 0;
893 sp = *spos;
894
895 while (true) {
896 sheet_copy_out(doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
897 &bep);
898 bytes = str_size(&buf[bpos]);
899 bpos += bytes;
900 sp = bep;
901
902 if (spt_equal(&bep, epos))
903 break;
904
905 buf_size *= 2;
906 char *tmp = realloc(buf, buf_size);
907 if (tmp == NULL) {
908 free(buf);
909 return NULL;
910 }
911 buf = tmp;
912 }
913
914 return buf;
915}
916
917/** Initialize pane.
918 *
919 * TODO: Replace with pane_create() that allocates the pane.
920 *
921 * @param window Editor window
922 * @param pane Pane
923 * @return EOK on success or an error code
924 */
925static errno_t pane_init(ui_window_t *window, pane_t *pane)
926{
927 errno_t rc;
928 gfx_rect_t arect;
929
930 pane->control = NULL;
931 pane->color = NULL;
932 pane->sel_color = NULL;
933
934 rc = ui_control_new(&pane_ctl_ops, (void *) pane, &pane->control);
935 if (rc != EOK)
936 goto error;
937
938 rc = gfx_color_new_ega(0x07, &pane->color);
939 if (rc != EOK)
940 goto error;
941
942 rc = gfx_color_new_ega(0x1e, &pane->sel_color);
943 if (rc != EOK)
944 goto error;
945
946 pane->res = ui_window_get_res(window);
947 pane->window = window;
948
949 ui_window_get_app_rect(window, &arect);
950 pane->rect.p0.x = arect.p0.x;
951 pane->rect.p0.y = arect.p0.y + 1;
952 pane->rect.p1.x = arect.p1.x;
953 pane->rect.p1.y = arect.p1.y - 1;
954
955 pane->columns = pane->rect.p1.x - pane->rect.p0.x;
956 pane->rows = pane->rect.p1.y - pane->rect.p0.y;
957
958 return EOK;
959error:
960 if (pane->control != NULL) {
961 ui_control_delete(pane->control);
962 pane->control = NULL;
963 }
964
965 if (pane->color != NULL) {
966 gfx_color_delete(pane->color);
967 pane->color = NULL;
968 }
969
970 return rc;
971}
972
973/** Finalize pane.
974 *
975 * TODO: Replace with pane_destroy() that deallocates the pane.
976 *
977 * @param pane Pane
978 */
979static void pane_fini(pane_t *pane)
980{
981 gfx_color_delete(pane->color);
982 pane->color = NULL;
983 gfx_color_delete(pane->sel_color);
984 pane->sel_color = NULL;
985 ui_control_delete(pane->control);
986 pane->control = NULL;
987}
988
989/** Return base control object for a pane.
990 *
991 * @param pane Pane
992 * @return Base UI cntrol
993 */
994static ui_control_t *pane_ctl(pane_t *pane)
995{
996 return pane->control;
997}
998
999/** Repaint parts of pane that need updating.
1000 *
1001 * @param pane Pane
1002 * @return EOK on succes or an error code
1003 */
1004static errno_t pane_update(pane_t *pane)
1005{
1006 errno_t rc;
1007
1008 if (pane->rflags & REDRAW_TEXT) {
1009 rc = pane_text_display(pane);
1010 if (rc != EOK)
1011 return rc;
1012 }
1013
1014 if (pane->rflags & REDRAW_ROW)
1015 pane_row_display();
1016
1017 if (pane->rflags & REDRAW_STATUS)
1018 pane_status_display();
1019
1020 if (pane->rflags & REDRAW_CARET)
1021 pane_caret_display(pane);
1022
1023 return EOK;
1024}
1025
1026
1027/** Display pane text.
1028 *
1029 * @param pane Pane
1030 * @return EOK on success or an error code
1031 */
1032static errno_t pane_text_display(pane_t *pane)
1033{
1034 gfx_rect_t rect;
1035 gfx_context_t *gc;
1036 errno_t rc;
1037 int sh_rows, rows;
1038
1039 sheet_get_num_rows(doc.sh, &sh_rows);
1040 rows = min(sh_rows - pane->sh_row + 1, pane->rows);
1041
1042 /* Draw rows from the sheet. */
1043
1044 rc = pane_row_range_display(pane, 0, rows);
1045 if (rc != EOK)
1046 return rc;
1047
1048 /* Clear the remaining rows if file is short. */
1049
1050 gc = ui_window_get_gc(pane->window);
1051
1052 rc = gfx_set_color(gc, pane->color);
1053 if (rc != EOK)
1054 goto error;
1055
1056 rect.p0.x = pane->rect.p0.x;
1057 rect.p0.y = pane->rect.p0.y + rows;
1058 rect.p1.x = pane->rect.p1.x;
1059 rect.p1.y = pane->rect.p1.y;
1060
1061 rc = gfx_fill_rect(gc, &rect);
1062 if (rc != EOK)
1063 goto error;
1064
1065 pane->rflags &= ~REDRAW_ROW;
1066 return EOK;
1067error:
1068 return rc;
1069}
1070
1071/** Display just the row where the caret is. */
1072static void pane_row_display(void)
1073{
1074 spt_t caret_pt;
1075 coord_t coord;
1076 int ridx;
1077
1078 tag_get_pt(&pane.caret_pos, &caret_pt);
1079 spt_get_coord(&caret_pt, &coord);
1080
1081 ridx = coord.row - pane.sh_row;
1082 (void) pane_row_range_display(&pane, ridx, ridx + 1);
1083 pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
1084}
1085
1086/** Display a range of rows of text.
1087 *
1088 * @param r0 Start row (inclusive)
1089 * @param r1 End row (exclusive)
1090 * @return EOk on success or an error code
1091 */
1092static errno_t pane_row_range_display(pane_t *pane, int r0, int r1)
1093{
1094 int i, fill;
1095 spt_t rb, re, dep, pt;
1096 coord_t rbc, rec;
1097 char row_buf[ROW_BUF_SIZE];
1098 char cbuf[STR_BOUNDS(1) + 1];
1099 char32_t c;
1100 size_t pos, size;
1101 size_t cpos;
1102 int s_column;
1103 coord_t csel_start, csel_end, ctmp;
1104 gfx_font_t *font;
1105 gfx_context_t *gc;
1106 gfx_text_fmt_t fmt;
1107 gfx_coord2_t tpos;
1108 gfx_rect_t rect;
1109 errno_t rc;
1110
1111 font = ui_resource_get_font(edit.ui_res);
1112 gc = ui_window_get_gc(edit.window);
1113
1114 gfx_text_fmt_init(&fmt);
1115 fmt.color = pane->color;
1116
1117 /* Determine selection start and end. */
1118
1119 tag_get_pt(&pane->sel_start, &pt);
1120 spt_get_coord(&pt, &csel_start);
1121
1122 tag_get_pt(&pane->caret_pos, &pt);
1123 spt_get_coord(&pt, &csel_end);
1124
1125 if (coord_cmp(&csel_start, &csel_end) > 0) {
1126 ctmp = csel_start;
1127 csel_start = csel_end;
1128 csel_end = ctmp;
1129 }
1130
1131 /* Draw rows from the sheet. */
1132
1133 for (i = r0; i < r1; ++i) {
1134 tpos.x = pane->rect.p0.x;
1135 tpos.y = pane->rect.p0.y + i;
1136
1137 /* Starting point for row display */
1138 rbc.row = pane->sh_row + i;
1139 rbc.column = pane->sh_column;
1140 sheet_get_cell_pt(doc.sh, &rbc, dir_before, &rb);
1141
1142 /* Ending point for row display */
1143 rec.row = pane->sh_row + i;
1144 rec.column = pane->sh_column + pane->columns;
1145 sheet_get_cell_pt(doc.sh, &rec, dir_before, &re);
1146
1147 /* Copy the text of the row to the buffer. */
1148 sheet_copy_out(doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
1149
1150 /* Display text from the buffer. */
1151
1152 if (coord_cmp(&csel_start, &rbc) <= 0 &&
1153 coord_cmp(&rbc, &csel_end) < 0) {
1154 fmt.color = pane->sel_color;
1155 }
1156
1157 size = str_size(row_buf);
1158 pos = 0;
1159 s_column = pane->sh_column;
1160 while (pos < size) {
1161 if ((csel_start.row == rbc.row) && (csel_start.column == s_column))
1162 fmt.color = pane->sel_color;
1163
1164 if ((csel_end.row == rbc.row) && (csel_end.column == s_column))
1165 fmt.color = pane->color;
1166
1167 c = str_decode(row_buf, &pos, size);
1168 if (c != '\t') {
1169 cpos = 0;
1170 rc = chr_encode(c, cbuf, &cpos, sizeof(cbuf));
1171 if (rc != EOK)
1172 return rc;
1173
1174 rc = gfx_puttext(font, &tpos, &fmt, cbuf);
1175 if (rc != EOK)
1176 return rc;
1177
1178 s_column += 1;
1179 tpos.x++;
1180 } else {
1181 fill = 1 + ALIGN_UP(s_column, TAB_WIDTH) -
1182 s_column;
1183
1184 rc = gfx_set_color(gc, fmt.color);
1185 if (rc != EOK)
1186 return rc;
1187
1188 rect.p0.x = tpos.x;
1189 rect.p0.y = tpos.y;
1190 rect.p1.x = tpos.x + fill;
1191 rect.p1.y = tpos.y + 1;
1192
1193 rc = gfx_fill_rect(gc, &rect);
1194 if (rc != EOK)
1195 return rc;
1196
1197 s_column += fill;
1198 tpos.x += fill;
1199 }
1200 }
1201
1202 if ((csel_end.row == rbc.row) && (csel_end.column == s_column))
1203 fmt.color = pane->color;
1204
1205 /* Fill until the end of display area. */
1206
1207 rc = gfx_set_color(gc, fmt.color);
1208 if (rc != EOK)
1209 return rc;
1210
1211 rect.p0.x = tpos.x;
1212 rect.p0.y = tpos.y;
1213 rect.p1.x = pane->rect.p1.x;
1214 rect.p1.y = tpos.y + 1;
1215
1216 rc = gfx_fill_rect(gc, &rect);
1217 if (rc != EOK)
1218 return rc;
1219 }
1220
1221 return EOK;
1222}
1223
1224/** Display pane status in the status line. */
1225static void pane_status_display(void)
1226{
1227 spt_t caret_pt;
1228 coord_t coord;
1229 int last_row;
1230 char *fname;
1231 char *p;
1232 char *text;
1233 size_t n;
1234 int pos;
1235 size_t nextra;
1236 size_t fnw;
1237
1238 tag_get_pt(&pane.caret_pos, &caret_pt);
1239 spt_get_coord(&caret_pt, &coord);
1240
1241 sheet_get_num_rows(doc.sh, &last_row);
1242
1243 if (doc.file_name != NULL) {
1244 /* Remove directory component */
1245 p = str_rchr(doc.file_name, '/');
1246 if (p != NULL)
1247 fname = str_dup(p + 1);
1248 else
1249 fname = str_dup(doc.file_name);
1250 } else {
1251 fname = str_dup("<unnamed>");
1252 }
1253
1254 if (fname == NULL)
1255 return;
1256
1257// console_set_pos(con, 0, scr_rows - 1);
1258// console_set_style(con, STYLE_INVERTED);
1259
1260 /*
1261 * Make sure the status fits on the screen. This loop should
1262 * be executed at most twice.
1263 */
1264 while (true) {
1265 int rc = asprintf(&text, " %d, %d (%d): File '%s'. Ctrl-Q Quit Ctrl-S Save "
1266 "Ctrl-E Save As", coord.row, coord.column, last_row, fname);
1267 if (rc < 0) {
1268 n = 0;
1269 goto finish;
1270 }
1271
1272 /* If it already fits, we're done */
1273 n = str_width(text);
1274 if (n <= scr_columns - 2)
1275 break;
1276
1277 /* Compute number of excess characters */
1278 nextra = n - (scr_columns - 2);
1279 /** With of the file name part */
1280 fnw = str_width(fname);
1281
1282 /*
1283 * If reducing file name to two characters '..' won't help,
1284 * just give up and print a blank status.
1285 */
1286 if (nextra > fnw - 2)
1287 goto finish;
1288
1289 /* Compute position where we overwrite with '..\0' */
1290 if (fnw >= nextra + 2) {
1291 p = fname + str_lsize(fname, fnw - nextra - 2);
1292 } else {
1293 p = fname;
1294 }
1295
1296 /* Shorten the string */
1297 p[0] = p[1] = '.';
1298 p[2] = '\0';
1299
1300 /* Need to format the string once more. */
1301 free(text);
1302 }
1303
1304 printf("%s", text);
1305 free(text);
1306 free(fname);
1307finish:
1308 /* Fill the rest of the line */
1309 pos = scr_columns - 1 - n;
1310 printf("%*s", pos, "");
1311// console_flush(con);
1312// console_set_style(con, STYLE_NORMAL);
1313
1314 pane.rflags |= REDRAW_CARET;
1315}
1316
1317/** Set cursor to reflect position of the caret.
1318 *
1319 * @param pane Pane
1320 */
1321static void pane_caret_display(pane_t *pane)
1322{
1323 spt_t caret_pt;
1324 coord_t coord;
1325 gfx_coord2_t pos;
1326 gfx_context_t *gc;
1327
1328 tag_get_pt(&pane->caret_pos, &caret_pt);
1329
1330 spt_get_coord(&caret_pt, &coord);
1331
1332 gc = ui_window_get_gc(edit.window);
1333 pos.x = pane->rect.p0.x + coord.column - pane->sh_column;
1334 pos.y = pane->rect.p0.y + coord.row - pane->sh_row;
1335
1336 (void) gfx_cursor_set_pos(gc, &pos);
1337}
1338
1339/** Destroy pane control.
1340 *
1341 * @param arg Argument (pane_t *)
1342 */
1343static void pane_ctl_destroy(void *arg)
1344{
1345 pane_t *pane = (pane_t *)arg;
1346
1347 pane_fini(pane);
1348}
1349
1350/** Paint pane control.
1351 *
1352 * @param arg Argument (pane_t *)
1353 */
1354static errno_t pane_ctl_paint(void *arg)
1355{
1356 pane_t *pane = (pane_t *)arg;
1357 gfx_context_t *gc;
1358 errno_t rc;
1359
1360 gc = ui_window_get_gc(pane->window);
1361
1362 rc = pane_text_display(pane);
1363 if (rc != EOK)
1364 goto error;
1365
1366 rc = gfx_update(gc);
1367 if (rc != EOK)
1368 goto error;
1369
1370error:
1371 return rc;
1372}
1373
1374/** Handle pane control position event.
1375 *
1376 * @param arg Argument (pane_t *)
1377 * @param event Position event
1378 */
1379static ui_evclaim_t pane_ctl_pos_event(void *arg, pos_event_t *event)
1380{
1381 return ui_unclaimed;
1382}
1383
1384/** Insert a character at caret position. */
1385static void insert_char(char32_t c)
1386{
1387 spt_t pt;
1388 char cbuf[STR_BOUNDS(1) + 1];
1389 size_t offs;
1390
1391 tag_get_pt(&pane.caret_pos, &pt);
1392
1393 offs = 0;
1394 chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
1395 cbuf[offs] = '\0';
1396
1397 (void) sheet_insert(doc.sh, &pt, dir_before, cbuf);
1398
1399 pane.rflags |= REDRAW_ROW;
1400 if (c == '\n')
1401 pane.rflags |= REDRAW_TEXT;
1402}
1403
1404/** Delete the character before the caret. */
1405static void delete_char_before(void)
1406{
1407 spt_t sp, ep;
1408 coord_t coord;
1409
1410 tag_get_pt(&pane.caret_pos, &ep);
1411 spt_get_coord(&ep, &coord);
1412
1413 coord.column -= 1;
1414 sheet_get_cell_pt(doc.sh, &coord, dir_before, &sp);
1415
1416 (void) sheet_delete(doc.sh, &sp, &ep);
1417
1418 pane.rflags |= REDRAW_ROW;
1419 if (coord.column < 1)
1420 pane.rflags |= REDRAW_TEXT;
1421}
1422
1423/** Delete the character after the caret. */
1424static void delete_char_after(void)
1425{
1426 spt_t sp, ep;
1427 coord_t sc, ec;
1428
1429 tag_get_pt(&pane.caret_pos, &sp);
1430 spt_get_coord(&sp, &sc);
1431
1432 sheet_get_cell_pt(doc.sh, &sc, dir_after, &ep);
1433 spt_get_coord(&ep, &ec);
1434
1435 (void) sheet_delete(doc.sh, &sp, &ep);
1436
1437 pane.rflags |= REDRAW_ROW;
1438 if (ec.row != sc.row)
1439 pane.rflags |= REDRAW_TEXT;
1440}
1441
1442/** Scroll pane after caret has moved.
1443 *
1444 * After modifying the position of the caret, this is called to scroll
1445 * the pane to ensure that the caret is in the visible area.
1446 */
1447static void caret_update(void)
1448{
1449 spt_t pt;
1450 coord_t coord;
1451
1452 tag_get_pt(&pane.caret_pos, &pt);
1453 spt_get_coord(&pt, &coord);
1454
1455 /* Scroll pane vertically. */
1456
1457 if (coord.row < pane.sh_row) {
1458 pane.sh_row = coord.row;
1459 pane.rflags |= REDRAW_TEXT;
1460 }
1461
1462 if (coord.row > pane.sh_row + pane.rows - 1) {
1463 pane.sh_row = coord.row - pane.rows + 1;
1464 pane.rflags |= REDRAW_TEXT;
1465 }
1466
1467 /* Scroll pane horizontally. */
1468
1469 if (coord.column < pane.sh_column) {
1470 pane.sh_column = coord.column;
1471 pane.rflags |= REDRAW_TEXT;
1472 }
1473
1474 if (coord.column > pane.sh_column + pane.columns - 1) {
1475 pane.sh_column = coord.column - pane.columns + 1;
1476 pane.rflags |= REDRAW_TEXT;
1477 }
1478
1479 pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
1480}
1481
1482/** Relatively move caret position.
1483 *
1484 * Moves caret relatively to the current position. Looking at the first
1485 * character cell after the caret and moving by @a drow and @a dcolumn, we get
1486 * to a new character cell, and thus a new character. Then we either go to the
1487 * point before the the character or after it, depending on @a align_dir.
1488 *
1489 * @param select true if the selection tag should stay where it is
1490 */
1491static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir,
1492 bool select)
1493{
1494 spt_t pt;
1495 coord_t coord;
1496 int num_rows;
1497 bool pure_vertical;
1498
1499 tag_get_pt(&pane.caret_pos, &pt);
1500 spt_get_coord(&pt, &coord);
1501 coord.row += drow;
1502 coord.column += dcolumn;
1503
1504 /* Clamp coordinates. */
1505 if (drow < 0 && coord.row < 1)
1506 coord.row = 1;
1507 if (dcolumn < 0 && coord.column < 1) {
1508 if (coord.row < 2)
1509 coord.column = 1;
1510 else {
1511 coord.row--;
1512 sheet_get_row_width(doc.sh, coord.row, &coord.column);
1513 }
1514 }
1515 if (drow > 0) {
1516 sheet_get_num_rows(doc.sh, &num_rows);
1517 if (coord.row > num_rows)
1518 coord.row = num_rows;
1519 }
1520
1521 /* For purely vertical movement try attaining @c ideal_column. */
1522 pure_vertical = (dcolumn == 0 && align_dir == dir_before);
1523 if (pure_vertical)
1524 coord.column = pane.ideal_column;
1525
1526 /*
1527 * Select the point before or after the character at the designated
1528 * coordinates. The character can be wider than one cell (e.g. tab).
1529 */
1530 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1531
1532 /* For non-vertical movement set the new value for @c ideal_column. */
1533 caret_move(pt, select, !pure_vertical);
1534}
1535
1536/** Absolutely move caret position.
1537 *
1538 * Moves caret to a specified position. We get to a new character cell, and
1539 * thus a new character. Then we either go to the point before the the character
1540 * or after it, depending on @a align_dir.
1541 *
1542 * @param select true if the selection tag should stay where it is
1543 */
1544static void caret_move_absolute(int row, int column, enum dir_spec align_dir,
1545 bool select)
1546{
1547 coord_t coord;
1548 coord.row = row;
1549 coord.column = column;
1550
1551 spt_t pt;
1552 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1553
1554 caret_move(pt, select, true);
1555}
1556
1557/** Find beginning of a word to the left of spt */
1558static spt_t pt_find_word_left(spt_t spt)
1559{
1560 do {
1561 spt_prev_char(spt, &spt);
1562 } while (!pt_is_word_beginning(&spt));
1563 return spt;
1564}
1565
1566/** Find beginning of a word to the right of spt */
1567static spt_t pt_find_word_right(spt_t spt)
1568{
1569 do {
1570 spt_next_char(spt, &spt);
1571 } while (!pt_is_word_beginning(&spt));
1572 return spt;
1573}
1574
1575static void caret_move_word_left(bool select)
1576{
1577 spt_t pt;
1578 tag_get_pt(&pane.caret_pos, &pt);
1579 spt_t word_left = pt_find_word_left(pt);
1580 caret_move(word_left, select, true);
1581}
1582
1583static void caret_move_word_right(bool select)
1584{
1585 spt_t pt;
1586 tag_get_pt(&pane.caret_pos, &pt);
1587 spt_t word_right = pt_find_word_right(pt);
1588 caret_move(word_right, select, true);
1589}
1590
1591/** Ask for line and go to it. */
1592static void caret_go_to_line_ask(void)
1593{
1594 char *sline;
1595
1596 sline = prompt("Go to line", "");
1597 if (sline == NULL) {
1598 status_display("Go to line cancelled.");
1599 return;
1600 }
1601
1602 char *endptr;
1603 int line = strtol(sline, &endptr, 10);
1604 if (*endptr != '\0') {
1605 free(sline);
1606 status_display("Invalid number entered.");
1607 return;
1608 }
1609 free(sline);
1610
1611 caret_move_absolute(line, pane.ideal_column, dir_before, false);
1612}
1613
1614/* Search operations */
1615static errno_t search_spt_producer(void *data, char32_t *ret)
1616{
1617 assert(data != NULL);
1618 assert(ret != NULL);
1619 spt_t *spt = data;
1620 *ret = spt_next_char(*spt, spt);
1621 return EOK;
1622}
1623
1624static errno_t search_spt_reverse_producer(void *data, char32_t *ret)
1625{
1626 assert(data != NULL);
1627 assert(ret != NULL);
1628 spt_t *spt = data;
1629 *ret = spt_prev_char(*spt, spt);
1630 return EOK;
1631}
1632
1633static errno_t search_spt_mark(void *data, void **mark)
1634{
1635 assert(data != NULL);
1636 assert(mark != NULL);
1637 spt_t *spt = data;
1638 spt_t *new = calloc(1, sizeof(spt_t));
1639 *mark = new;
1640 if (new == NULL)
1641 return ENOMEM;
1642 *new = *spt;
1643 return EOK;
1644}
1645
1646static void search_spt_mark_free(void *data)
1647{
1648 free(data);
1649}
1650
1651static search_ops_t search_spt_ops = {
1652 .equals = char_exact_equals,
1653 .producer = search_spt_producer,
1654 .mark = search_spt_mark,
1655 .mark_free = search_spt_mark_free,
1656};
1657
1658static search_ops_t search_spt_reverse_ops = {
1659 .equals = char_exact_equals,
1660 .producer = search_spt_reverse_producer,
1661 .mark = search_spt_mark,
1662 .mark_free = search_spt_mark_free,
1663};
1664
1665/** Ask for line and go to it. */
1666static void search_prompt(bool reverse)
1667{
1668 char *pattern;
1669
1670 const char *prompt_text = "Find next";
1671 if (reverse)
1672 prompt_text = "Find previous";
1673
1674 const char *default_value = "";
1675 if (pane.previous_search)
1676 default_value = pane.previous_search;
1677
1678 pattern = prompt(prompt_text, default_value);
1679 if (pattern == NULL) {
1680 status_display("Search cancelled.");
1681 return;
1682 }
1683
1684 if (pane.previous_search)
1685 free(pane.previous_search);
1686 pane.previous_search = pattern;
1687 pane.previous_search_reverse = reverse;
1688
1689 search(pattern, reverse);
1690}
1691
1692static void search_repeat(void)
1693{
1694 if (pane.previous_search == NULL) {
1695 status_display("No previous search to repeat.");
1696 return;
1697 }
1698
1699 search(pane.previous_search, pane.previous_search_reverse);
1700}
1701
1702static void search(char *pattern, bool reverse)
1703{
1704 status_display("Searching...");
1705
1706 spt_t sp, producer_pos;
1707 tag_get_pt(&pane.caret_pos, &sp);
1708
1709 /* Start searching on the position before/after caret */
1710 if (!reverse) {
1711 spt_next_char(sp, &sp);
1712 } else {
1713 spt_prev_char(sp, &sp);
1714 }
1715 producer_pos = sp;
1716
1717 search_ops_t ops = search_spt_ops;
1718 if (reverse)
1719 ops = search_spt_reverse_ops;
1720
1721 search_t *search = search_init(pattern, &producer_pos, ops, reverse);
1722 if (search == NULL) {
1723 status_display("Failed initializing search.");
1724 return;
1725 }
1726
1727 match_t match;
1728 errno_t rc = search_next_match(search, &match);
1729 if (rc != EOK) {
1730 status_display("Failed searching.");
1731 search_fini(search);
1732 }
1733
1734 if (match.end) {
1735 status_display("Match found.");
1736 assert(match.end != NULL);
1737 spt_t *end = match.end;
1738 caret_move(*end, false, true);
1739 while (match.length > 0) {
1740 match.length--;
1741 if (reverse) {
1742 spt_next_char(*end, end);
1743 } else {
1744 spt_prev_char(*end, end);
1745 }
1746 }
1747 caret_move(*end, true, true);
1748 free(end);
1749 } else {
1750 status_display("Not found.");
1751 }
1752
1753 search_fini(search);
1754}
1755
1756/** Check for non-empty selection. */
1757static bool selection_active(void)
1758{
1759 return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
1760}
1761
1762static void selection_get_points(spt_t *pa, spt_t *pb)
1763{
1764 spt_t pt;
1765
1766 tag_get_pt(&pane.sel_start, pa);
1767 tag_get_pt(&pane.caret_pos, pb);
1768
1769 if (spt_cmp(pa, pb) > 0) {
1770 pt = *pa;
1771 *pa = *pb;
1772 *pb = pt;
1773 }
1774}
1775
1776/** Delete selected text. */
1777static void selection_delete(void)
1778{
1779 spt_t pa, pb;
1780 coord_t ca, cb;
1781 int rel;
1782
1783 tag_get_pt(&pane.sel_start, &pa);
1784 tag_get_pt(&pane.caret_pos, &pb);
1785 spt_get_coord(&pa, &ca);
1786 spt_get_coord(&pb, &cb);
1787 rel = coord_cmp(&ca, &cb);
1788
1789 if (rel == 0)
1790 return;
1791
1792 if (rel < 0)
1793 sheet_delete(doc.sh, &pa, &pb);
1794 else
1795 sheet_delete(doc.sh, &pb, &pa);
1796
1797 if (ca.row == cb.row)
1798 pane.rflags |= REDRAW_ROW;
1799 else
1800 pane.rflags |= REDRAW_TEXT;
1801}
1802
1803/** Select all text in the editor */
1804static void selection_sel_all(void)
1805{
1806 spt_t spt, ept;
1807
1808 pt_get_sof(&spt);
1809 pt_get_eof(&ept);
1810
1811 selection_sel_range(spt, ept);
1812}
1813
1814/** Select select all text in a given range with the given direction */
1815static void selection_sel_range(spt_t pa, spt_t pb)
1816{
1817 sheet_remove_tag(doc.sh, &pane.sel_start);
1818 sheet_place_tag(doc.sh, &pa, &pane.sel_start);
1819 sheet_remove_tag(doc.sh, &pane.caret_pos);
1820 sheet_place_tag(doc.sh, &pb, &pane.caret_pos);
1821
1822 pane.rflags |= REDRAW_TEXT;
1823 caret_update();
1824}
1825
1826static void selection_copy(void)
1827{
1828 spt_t pa, pb;
1829 char *str;
1830
1831 selection_get_points(&pa, &pb);
1832 str = range_get_str(&pa, &pb);
1833 if (str == NULL || clipboard_put_str(str) != EOK) {
1834 status_display("Copying to clipboard failed!");
1835 }
1836 free(str);
1837}
1838
1839static void insert_clipboard_data(void)
1840{
1841 char *str;
1842 size_t off;
1843 char32_t c;
1844 errno_t rc;
1845
1846 rc = clipboard_get_str(&str);
1847 if (rc != EOK || str == NULL)
1848 return;
1849
1850 off = 0;
1851
1852 while (true) {
1853 c = str_decode(str, &off, STR_NO_LIMIT);
1854 if (c == '\0')
1855 break;
1856
1857 insert_char(c);
1858 }
1859
1860 free(str);
1861}
1862
1863/** Get start-of-file s-point. */
1864static void pt_get_sof(spt_t *pt)
1865{
1866 coord_t coord;
1867
1868 coord.row = coord.column = 1;
1869 sheet_get_cell_pt(doc.sh, &coord, dir_before, pt);
1870}
1871
1872/** Get end-of-file s-point. */
1873static void pt_get_eof(spt_t *pt)
1874{
1875 coord_t coord;
1876 int num_rows;
1877
1878 sheet_get_num_rows(doc.sh, &num_rows);
1879 coord.row = num_rows + 1;
1880 coord.column = 1;
1881
1882 sheet_get_cell_pt(doc.sh, &coord, dir_after, pt);
1883}
1884
1885/** Get start-of-line s-point for given s-point cpt */
1886static void pt_get_sol(spt_t *cpt, spt_t *spt)
1887{
1888 coord_t coord;
1889
1890 spt_get_coord(cpt, &coord);
1891 coord.column = 1;
1892
1893 sheet_get_cell_pt(doc.sh, &coord, dir_before, spt);
1894}
1895
1896/** Get end-of-line s-point for given s-point cpt */
1897static void pt_get_eol(spt_t *cpt, spt_t *ept)
1898{
1899 coord_t coord;
1900 int row_width;
1901
1902 spt_get_coord(cpt, &coord);
1903 sheet_get_row_width(doc.sh, coord.row, &row_width);
1904 coord.column = row_width - 1;
1905
1906 sheet_get_cell_pt(doc.sh, &coord, dir_after, ept);
1907}
1908
1909/** Check whether the spt is at a beginning of a word */
1910static bool pt_is_word_beginning(spt_t *pt)
1911{
1912 spt_t lp, sfp, efp, slp, elp;
1913 coord_t coord;
1914
1915 pt_get_sof(&sfp);
1916 pt_get_eof(&efp);
1917 pt_get_sol(pt, &slp);
1918 pt_get_eol(pt, &elp);
1919
1920 /* the spt is at the beginning or end of the file or line */
1921 if ((spt_cmp(&sfp, pt) == 0) || (spt_cmp(&efp, pt) == 0) ||
1922 (spt_cmp(&slp, pt) == 0) || (spt_cmp(&elp, pt) == 0))
1923 return true;
1924
1925 /* the spt is a delimiter */
1926 if (pt_is_delimiter(pt))
1927 return false;
1928
1929 spt_get_coord(pt, &coord);
1930
1931 coord.column -= 1;
1932 sheet_get_cell_pt(doc.sh, &coord, dir_before, &lp);
1933
1934 return pt_is_delimiter(&lp) ||
1935 (pt_is_punctuation(pt) && !pt_is_punctuation(&lp)) ||
1936 (pt_is_punctuation(&lp) && !pt_is_punctuation(pt));
1937}
1938
1939static char32_t get_first_wchar(const char *str)
1940{
1941 size_t offset = 0;
1942 return str_decode(str, &offset, str_size(str));
1943}
1944
1945static bool pt_is_delimiter(spt_t *pt)
1946{
1947 spt_t rp;
1948 coord_t coord;
1949 char *ch = NULL;
1950
1951 spt_get_coord(pt, &coord);
1952
1953 coord.column += 1;
1954 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
1955
1956 ch = range_get_str(pt, &rp);
1957 if (ch == NULL)
1958 return false;
1959
1960 char32_t first_char = get_first_wchar(ch);
1961 switch (first_char) {
1962 case ' ':
1963 case '\t':
1964 case '\n':
1965 return true;
1966 default:
1967 return false;
1968 }
1969}
1970
1971static bool pt_is_punctuation(spt_t *pt)
1972{
1973 spt_t rp;
1974 coord_t coord;
1975 char *ch = NULL;
1976
1977 spt_get_coord(pt, &coord);
1978
1979 coord.column += 1;
1980 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
1981
1982 ch = range_get_str(pt, &rp);
1983 if (ch == NULL)
1984 return false;
1985
1986 char32_t first_char = get_first_wchar(ch);
1987 switch (first_char) {
1988 case ',':
1989 case '.':
1990 case ';':
1991 case ':':
1992 case '/':
1993 case '?':
1994 case '\\':
1995 case '|':
1996 case '_':
1997 case '+':
1998 case '-':
1999 case '*':
2000 case '=':
2001 case '<':
2002 case '>':
2003 return true;
2004 default:
2005 return false;
2006 }
2007}
2008
2009/** Compare tags. */
2010static int tag_cmp(tag_t const *a, tag_t const *b)
2011{
2012 spt_t pa, pb;
2013
2014 tag_get_pt(a, &pa);
2015 tag_get_pt(b, &pb);
2016
2017 return spt_cmp(&pa, &pb);
2018}
2019
2020/** Compare s-points. */
2021static int spt_cmp(spt_t const *a, spt_t const *b)
2022{
2023 coord_t ca, cb;
2024
2025 spt_get_coord(a, &ca);
2026 spt_get_coord(b, &cb);
2027
2028 return coord_cmp(&ca, &cb);
2029}
2030
2031/** Compare coordinats. */
2032static int coord_cmp(coord_t const *a, coord_t const *b)
2033{
2034 if (a->row - b->row != 0)
2035 return a->row - b->row;
2036
2037 return a->column - b->column;
2038}
2039
2040/** Display text in the status line. */
2041static void status_display(char const *str)
2042{
2043// console_set_pos(con, 0, scr_rows - 1);
2044// console_set_style(con, STYLE_INVERTED);
2045
2046 int pos = -(scr_columns - 3);
2047 printf(" %*s ", pos, str);
2048// console_flush(con);
2049// console_set_style(con, STYLE_NORMAL);
2050
2051 pane.rflags |= REDRAW_CARET;
2052}
2053
2054/** Window close request
2055 *
2056 * @param window Window
2057 * @param arg Argument (edit_t *)
2058 */
2059static void edit_wnd_close(ui_window_t *window, void *arg)
2060{
2061 edit_t *edit = (edit_t *) arg;
2062
2063 ui_quit(edit->ui);
2064}
2065
2066/** Window keyboard event
2067 *
2068 * @param window Window
2069 * @param arg Argument (edit_t *)
2070 * @param event Keyboard event
2071 */
2072static void edit_wnd_kbd_event(ui_window_t *window, void *arg,
2073 kbd_event_t *event)
2074{
2075 if (event->type == KEY_PRESS) {
2076 key_handle_press(event);
2077 (void) pane_update(&pane);
2078 (void) gfx_update(ui_window_get_gc(window));
2079 }
2080}
2081
2082/** File / Exit menu entry selected.
2083 *
2084 * @param mentry Menu entry
2085 * @param arg Argument (edit_t *)
2086 */
2087static void edit_file_exit(ui_menu_entry_t *mentry, void *arg)
2088{
2089 edit_t *edit = (edit_t *) arg;
2090
2091 ui_quit(edit->ui);
2092}
2093
2094/** Edit / Copy menu entry selected.
2095 *
2096 * @param mentry Menu entry
2097 * @param arg Argument (edit_t *)
2098 */
2099static void edit_edit_copy(ui_menu_entry_t *mentry, void *arg)
2100{
2101 (void) arg;
2102}
2103
2104/** Edit / Paste menu entry selected.
2105 *
2106 * @param mentry Menu entry
2107 * @param arg Argument (edit_t *)
2108 */
2109static void edit_edit_paste(ui_menu_entry_t *mentry, void *arg)
2110{
2111 (void) arg;
2112}
2113
2114/** @}
2115 */
Note: See TracBrowser for help on using the repository browser.