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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since dd13349 was dd13349, checked in by Martin Sucha <sucha14@…>, 13 years ago

edit: Implement HOME/END keys using move to sol/eol spt instead of inifinite relative movement.

This fixes HOME key in the first line of document.

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