source: mainline/uspace/app/edit/edit.c@ 07b7c48

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

Extend console library API to support different event types.

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