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

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

Basic rendering of pane text

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