source: mainline/uspace/app/edit/edit.c@ 45b7d4d

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

Clear dirty flags when updating pane

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