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

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

fputc, putchar vs. fputwc, putwchar.

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