source: mainline/uspace/app/edit/edit.c@ 68632b4

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

Port Text Editor to libui (WIP 1)

Remove io/console.h calls, create UI window and run UI.

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