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

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

Shift-click to select in text editor.

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