source: mainline/uspace/app/edit/edit.c@ 7feb86e6

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

Improve edit.

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