2 * Copyright (c) 2009 Jiri Svoboda
3 * Copyright (c) 2012 Martin Sucha
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
40 #include <sys/types.h>
42 #include <io/console.h>
44 #include <io/keycode.h>
48 #include <clipboard.h>
55 REDRAW_TEXT
= (1 << 0),
56 REDRAW_ROW
= (1 << 1),
57 REDRAW_STATUS
= (1 << 2),
58 REDRAW_CARET
= (1 << 3)
63 * A rectangular area of the screen used to edit a document. Different
64 * panes can be possibly used to edit the same document.
70 /* Position of the visible area */
71 int sh_row
, sh_column
;
73 /** Bitmask of components that need redrawing */
74 enum redraw_flags rflags
;
76 /** Current position of the caret */
79 /** Start of selection */
83 * Ideal column where the caret should try to get. This is used
84 * for maintaining the same column during vertical movement.
88 char *previous_search
;
89 bool previous_search_reverse
;
94 * Associates a sheet with a file where it can be saved to.
101 static console_ctrl_t
*con
;
105 static bool cursor_visible
;
107 static sysarg_t scr_rows
;
108 static sysarg_t scr_columns
;
110 #define ROW_BUF_SIZE 4096
113 #define ED_INFTY 65536
115 /** Maximum filename length that can be entered. */
116 #define INFNAME_MAX_LEN 128
118 static void cursor_show(void);
119 static void cursor_hide(void);
120 static void cursor_setvis(bool visible
);
122 static void key_handle_unmod(kbd_event_t
const *ev
);
123 static void key_handle_ctrl(kbd_event_t
const *ev
);
124 static void key_handle_shift(kbd_event_t
const *ev
);
125 static void key_handle_shift_ctrl(kbd_event_t
const *ev
);
126 static void key_handle_movement(unsigned int key
, bool shift
);
128 static int file_save(char const *fname
);
129 static void file_save_as(void);
130 static int file_insert(char *fname
);
131 static int file_save_range(char const *fname
, spt_t
const *spos
,
133 static char *range_get_str(spt_t
const *spos
, spt_t
const *epos
);
135 static char *prompt(char const *prompt
, char const *init_value
);
137 static void pane_text_display(void);
138 static void pane_row_display(void);
139 static void pane_row_range_display(int r0
, int r1
);
140 static void pane_status_display(void);
141 static void pane_caret_display(void);
143 static void insert_char(wchar_t c
);
144 static void delete_char_before(void);
145 static void delete_char_after(void);
146 static void caret_update(void);
147 static void caret_move_relative(int drow
, int dcolumn
, enum dir_spec align_dir
, bool select
);
148 static void caret_move_absolute(int row
, int column
, enum dir_spec align_dir
, bool select
);
149 static void caret_move(spt_t spt
, bool select
, bool update_ideal_column
);
150 static void caret_move_word_left(bool select
);
151 static void caret_move_word_right(bool select
);
152 static void caret_go_to_line_ask(void);
154 static bool selection_active(void);
155 static void selection_sel_all(void);
156 static void selection_sel_range(spt_t pa
, spt_t pb
);
157 static void selection_get_points(spt_t
*pa
, spt_t
*pb
);
158 static void selection_delete(void);
159 static void selection_copy(void);
160 static void insert_clipboard_data(void);
162 static void search(char *pattern
, bool reverse
);
163 static void search_prompt(bool reverse
);
164 static void search_repeat(void);
166 static void pt_get_sof(spt_t
*pt
);
167 static void pt_get_eof(spt_t
*pt
);
168 static void pt_get_sol(spt_t
*cpt
, spt_t
*spt
);
169 static void pt_get_eol(spt_t
*cpt
, spt_t
*ept
);
170 static bool pt_is_word_beginning(spt_t
*pt
);
171 static bool pt_is_delimiter(spt_t
*pt
);
172 static bool pt_is_punctuation(spt_t
*pt
);
173 static spt_t
pt_find_word_left(spt_t spt
);
174 static spt_t
pt_find_word_left(spt_t spt
);
176 static int tag_cmp(tag_t
const *a
, tag_t
const *b
);
177 static int spt_cmp(spt_t
const *a
, spt_t
const *b
);
178 static int coord_cmp(coord_t
const *a
, coord_t
const *b
);
180 static void status_display(char const *str
);
183 int main(int argc
, char *argv
[])
189 con
= console_init(stdin
, stdout
);
192 console_get_size(con
, &scr_columns
, &scr_rows
);
194 pane
.rows
= scr_rows
- 1;
195 pane
.columns
= scr_columns
;
199 /* Start with an empty sheet. */
200 rc
= sheet_create(&doc
.sh
);
202 printf("Out of memory.\n");
206 /* Place caret at the beginning of file. */
209 sheet_place_tag(doc
.sh
, &sof
, &pane
.caret_pos
);
210 pane
.ideal_column
= 1;
213 doc
.file_name
= str_dup(argv
[1]);
214 } else if (argc
> 1) {
215 printf("Invalid arguments.\n");
218 doc
.file_name
= NULL
;
223 if (doc
.file_name
== NULL
|| file_insert(doc
.file_name
) != EOK
)
226 /* Place selection start tag. */
227 sheet_place_tag(doc
.sh
, &sof
, &pane
.sel_start
);
229 /* Move to beginning of file. */
231 caret_move(sof
, true, true);
233 /* Initial display */
234 cursor_visible
= true;
239 pane_status_display();
240 if (new_file
&& doc
.file_name
!= NULL
)
241 status_display("File not found. Starting empty file.");
242 pane_caret_display();
248 console_get_kbd_event(con
, &ev
);
251 if (ev
.type
== KEY_PRESS
) {
252 /* Handle key press. */
253 if (((ev
.mods
& KM_ALT
) == 0) &&
254 ((ev
.mods
& KM_SHIFT
) == 0) &&
255 (ev
.mods
& KM_CTRL
) != 0) {
256 key_handle_ctrl(&ev
);
257 } else if (((ev
.mods
& KM_ALT
) == 0) &&
258 ((ev
.mods
& KM_CTRL
) == 0) &&
259 (ev
.mods
& KM_SHIFT
) != 0) {
260 key_handle_shift(&ev
);
261 } else if (((ev
.mods
& KM_ALT
) == 0) &&
262 ((ev
.mods
& KM_CTRL
) != 0) &&
263 (ev
.mods
& KM_SHIFT
) != 0) {
264 key_handle_shift_ctrl(&ev
);
265 } else if ((ev
.mods
& (KM_CTRL
| KM_ALT
| KM_SHIFT
)) == 0) {
266 key_handle_unmod(&ev
);
270 /* Redraw as necessary. */
274 if (pane
.rflags
& REDRAW_TEXT
)
276 if (pane
.rflags
& REDRAW_ROW
)
278 if (pane
.rflags
& REDRAW_STATUS
)
279 pane_status_display();
280 if (pane
.rflags
& REDRAW_CARET
)
281 pane_caret_display();
291 static void cursor_show(void)
296 static void cursor_hide(void)
298 cursor_setvis(false);
301 static void cursor_setvis(bool visible
)
303 if (cursor_visible
!= visible
) {
304 console_cursor_visibility(con
, visible
);
305 cursor_visible
= visible
;
309 /** Handle key without modifier. */
310 static void key_handle_unmod(kbd_event_t
const *ev
)
326 key_handle_movement(ev
->key
, false);
329 if (selection_active())
332 delete_char_before();
336 if (selection_active())
343 if (ev
->c
>= 32 || ev
->c
== '\t') {
352 /** Handle Shift-key combination. */
353 static void key_handle_shift(kbd_event_t
const *ev
)
364 key_handle_movement(ev
->key
, true);
367 if (ev
->c
>= 32 || ev
->c
== '\t') {
376 /** Handle Ctrl-key combination. */
377 static void key_handle_ctrl(kbd_event_t
const *ev
)
385 if (doc
.file_name
!= NULL
)
386 file_save(doc
.file_name
);
398 insert_clipboard_data();
399 pane
.rflags
|= REDRAW_TEXT
;
405 pane
.rflags
|= REDRAW_TEXT
;
412 caret_move_word_right(false);
415 caret_move_word_left(false);
418 caret_go_to_line_ask();
421 search_prompt(false);
428 caret_move(pt
, false, true);
432 caret_move(pt
, false, true);
439 static void key_handle_shift_ctrl(kbd_event_t
const *ev
)
444 caret_move_word_left(true);
447 caret_move_word_right(true);
454 caret_move(pt
, true, true);
458 caret_move(pt
, true, true);
465 /** Move caret while preserving or resetting selection. */
466 static void caret_move(spt_t new_caret_pt
, bool select
, bool update_ideal_column
)
468 spt_t old_caret_pt
, old_sel_pt
;
469 coord_t c_old
, c_new
;
472 /* Check if we had selection before. */
473 tag_get_pt(&pane
.caret_pos
, &old_caret_pt
);
474 tag_get_pt(&pane
.sel_start
, &old_sel_pt
);
475 had_sel
= !spt_equal(&old_caret_pt
, &old_sel_pt
);
477 /* Place tag of the caret */
478 sheet_remove_tag(doc
.sh
, &pane
.caret_pos
);
479 sheet_place_tag(doc
.sh
, &new_caret_pt
, &pane
.caret_pos
);
481 if (select
== false) {
482 /* Move sel_start to the same point as caret. */
483 sheet_remove_tag(doc
.sh
, &pane
.sel_start
);
484 sheet_place_tag(doc
.sh
, &new_caret_pt
, &pane
.sel_start
);
487 spt_get_coord(&new_caret_pt
, &c_new
);
489 spt_get_coord(&old_caret_pt
, &c_old
);
491 if (c_old
.row
== c_new
.row
)
492 pane
.rflags
|= REDRAW_ROW
;
494 pane
.rflags
|= REDRAW_TEXT
;
496 } else if (had_sel
== true) {
497 /* Redraw because text was unselected. */
498 pane
.rflags
|= REDRAW_TEXT
;
501 if (update_ideal_column
)
502 pane
.ideal_column
= c_new
.column
;
507 static void key_handle_movement(unsigned int key
, bool select
)
511 caret_move_relative(0, -1, dir_before
, select
);
514 caret_move_relative(0, 0, dir_after
, select
);
517 caret_move_relative(-1, 0, dir_before
, select
);
520 caret_move_relative(+1, 0, dir_before
, select
);
523 caret_move_relative(0, -ED_INFTY
, dir_after
, select
);
526 caret_move_relative(0, +ED_INFTY
, dir_before
, select
);
529 caret_move_relative(-pane
.rows
, 0, dir_before
, select
);
532 caret_move_relative(+pane
.rows
, 0, dir_before
, select
);
539 /** Save the document. */
540 static int file_save(char const *fname
)
545 status_display("Saving...");
549 rc
= file_save_range(fname
, &sp
, &ep
);
553 status_display("Error opening file!");
556 status_display("Error writing data!");
559 status_display("File saved.");
566 /** Change document name and save. */
567 static void file_save_as(void)
569 const char *old_fname
= (doc
.file_name
!= NULL
) ? doc
.file_name
: "";
572 fname
= prompt("Save As", old_fname
);
574 status_display("Save cancelled.");
578 int rc
= file_save(fname
);
582 if (doc
.file_name
!= NULL
)
584 doc
.file_name
= fname
;
587 /** Ask for a string. */
588 static char *prompt(char const *prompt
, char const *init_value
)
592 wchar_t buffer
[INFNAME_MAX_LEN
+ 1];
597 asprintf(&str
, "%s: %s", prompt
, init_value
);
599 console_set_pos(con
, 1 + str_length(str
), scr_rows
- 1);
602 console_set_style(con
, STYLE_INVERTED
);
604 max_len
= min(INFNAME_MAX_LEN
, scr_columns
- 4 - str_length(prompt
));
605 str_to_wstr(buffer
, max_len
+ 1, init_value
);
606 nc
= wstr_length(buffer
);
610 console_get_kbd_event(con
, &ev
);
612 if (ev
.type
== KEY_PRESS
) {
613 /* Handle key press. */
614 if (((ev
.mods
& KM_ALT
) == 0) &&
615 (ev
.mods
& KM_CTRL
) != 0) {
617 } else if ((ev
.mods
& (KM_CTRL
| KM_ALT
)) == 0) {
632 if (ev
.c
>= 32 && nc
< max_len
) {
644 str
= wstr_to_astr(buffer
);
646 console_set_style(con
, STYLE_NORMAL
);
651 /** Insert file at caret position.
653 * Reads in the contents of a file and inserts them at the current position
656 static int file_insert(char *fname
)
665 f
= fopen(fname
, "rt");
672 if (bcnt
< STR_BOUNDS(1)) {
673 n_read
= fread(buf
+ bcnt
, 1, BUF_SIZE
- bcnt
, f
);
678 c
= str_decode(buf
, &off
, bcnt
);
683 memcpy(buf
, buf
+ off
, bcnt
);
693 /** Save a range of text into a file. */
694 static int file_save_range(char const *fname
, spt_t
const *spos
,
700 size_t bytes
, n_written
;
702 f
= fopen(fname
, "wt");
709 sheet_copy_out(doc
.sh
, &sp
, epos
, buf
, BUF_SIZE
, &bep
);
710 bytes
= str_size(buf
);
712 n_written
= fwrite(buf
, 1, bytes
, f
);
713 if (n_written
!= bytes
) {
718 } while (!spt_equal(&bep
, epos
));
720 if (fclose(f
) != EOK
)
726 /** Return contents of range as a new string. */
727 static char *range_get_str(spt_t
const *spos
, spt_t
const *epos
)
732 size_t buf_size
, bpos
;
736 buf
= malloc(buf_size
);
744 sheet_copy_out(doc
.sh
, &sp
, epos
, &buf
[bpos
], buf_size
- bpos
,
746 bytes
= str_size(&buf
[bpos
]);
750 if (spt_equal(&bep
, epos
))
754 buf
= realloc(buf
, buf_size
);
762 static void pane_text_display(void)
766 sheet_get_num_rows(doc
.sh
, &sh_rows
);
767 rows
= min(sh_rows
- pane
.sh_row
+ 1, pane
.rows
);
769 /* Draw rows from the sheet. */
771 console_set_pos(con
, 0, 0);
772 pane_row_range_display(0, rows
);
774 /* Clear the remaining rows if file is short. */
778 for (i
= rows
; i
< pane
.rows
; ++i
) {
779 console_set_pos(con
, 0, i
);
780 for (j
= 0; j
< scr_columns
; ++j
)
785 pane
.rflags
|= (REDRAW_STATUS
| REDRAW_CARET
);
786 pane
.rflags
&= ~REDRAW_ROW
;
789 /** Display just the row where the caret is. */
790 static void pane_row_display(void)
796 tag_get_pt(&pane
.caret_pos
, &caret_pt
);
797 spt_get_coord(&caret_pt
, &coord
);
799 ridx
= coord
.row
- pane
.sh_row
;
800 pane_row_range_display(ridx
, ridx
+ 1);
801 pane
.rflags
|= (REDRAW_STATUS
| REDRAW_CARET
);
804 static void pane_row_range_display(int r0
, int r1
)
807 spt_t rb
, re
, dep
, pt
;
809 char row_buf
[ROW_BUF_SIZE
];
813 coord_t csel_start
, csel_end
, ctmp
;
815 /* Determine selection start and end. */
817 tag_get_pt(&pane
.sel_start
, &pt
);
818 spt_get_coord(&pt
, &csel_start
);
820 tag_get_pt(&pane
.caret_pos
, &pt
);
821 spt_get_coord(&pt
, &csel_end
);
823 if (coord_cmp(&csel_start
, &csel_end
) > 0) {
825 csel_start
= csel_end
;
829 /* Draw rows from the sheet. */
831 console_set_pos(con
, 0, 0);
832 for (i
= r0
; i
< r1
; ++i
) {
833 /* Starting point for row display */
834 rbc
.row
= pane
.sh_row
+ i
;
835 rbc
.column
= pane
.sh_column
;
836 sheet_get_cell_pt(doc
.sh
, &rbc
, dir_before
, &rb
);
838 /* Ending point for row display */
839 rec
.row
= pane
.sh_row
+ i
;
840 rec
.column
= pane
.sh_column
+ pane
.columns
;
841 sheet_get_cell_pt(doc
.sh
, &rec
, dir_before
, &re
);
843 /* Copy the text of the row to the buffer. */
844 sheet_copy_out(doc
.sh
, &rb
, &re
, row_buf
, ROW_BUF_SIZE
, &dep
);
846 /* Display text from the buffer. */
848 if (coord_cmp(&csel_start
, &rbc
) <= 0 &&
849 coord_cmp(&rbc
, &csel_end
) < 0) {
851 console_set_style(con
, STYLE_SELECTED
);
855 console_set_pos(con
, 0, i
);
856 size
= str_size(row_buf
);
858 s_column
= pane
.sh_column
;
860 if ((csel_start
.row
== rbc
.row
) && (csel_start
.column
== s_column
)) {
862 console_set_style(con
, STYLE_SELECTED
);
866 if ((csel_end
.row
== rbc
.row
) && (csel_end
.column
== s_column
)) {
868 console_set_style(con
, STYLE_NORMAL
);
872 c
= str_decode(row_buf
, &pos
, size
);
874 printf("%lc", (wint_t) c
);
877 fill
= 1 + ALIGN_UP(s_column
, TAB_WIDTH
)
880 for (j
= 0; j
< fill
; ++j
)
886 if ((csel_end
.row
== rbc
.row
) && (csel_end
.column
== s_column
)) {
888 console_set_style(con
, STYLE_NORMAL
);
892 /* Fill until the end of display area. */
894 if ((unsigned)s_column
- 1 < scr_columns
)
895 fill
= scr_columns
- (s_column
- 1);
899 for (j
= 0; j
< fill
; ++j
)
902 console_set_style(con
, STYLE_NORMAL
);
905 pane
.rflags
|= REDRAW_CARET
;
908 /** Display pane status in the status line. */
909 static void pane_status_display(void)
915 tag_get_pt(&pane
.caret_pos
, &caret_pt
);
916 spt_get_coord(&caret_pt
, &coord
);
918 sheet_get_num_rows(doc
.sh
, &last_row
);
920 const char *fname
= (doc
.file_name
!= NULL
) ? doc
.file_name
: "<unnamed>";
922 console_set_pos(con
, 0, scr_rows
- 1);
923 console_set_style(con
, STYLE_INVERTED
);
924 int n
= printf(" %d, %d (%d): File '%s'. Ctrl-Q Quit Ctrl-S Save "
925 "Ctrl-E Save As", coord
.row
, coord
.column
, last_row
, fname
);
927 int pos
= scr_columns
- 1 - n
;
928 printf("%*s", pos
, "");
930 console_set_style(con
, STYLE_NORMAL
);
932 pane
.rflags
|= REDRAW_CARET
;
935 /** Set cursor to reflect position of the caret. */
936 static void pane_caret_display(void)
941 tag_get_pt(&pane
.caret_pos
, &caret_pt
);
943 spt_get_coord(&caret_pt
, &coord
);
944 console_set_pos(con
, coord
.column
- pane
.sh_column
,
945 coord
.row
- pane
.sh_row
);
948 /** Insert a character at caret position. */
949 static void insert_char(wchar_t c
)
952 char cbuf
[STR_BOUNDS(1) + 1];
955 tag_get_pt(&pane
.caret_pos
, &pt
);
958 chr_encode(c
, cbuf
, &offs
, STR_BOUNDS(1) + 1);
961 (void) sheet_insert(doc
.sh
, &pt
, dir_before
, cbuf
);
963 pane
.rflags
|= REDRAW_ROW
;
965 pane
.rflags
|= REDRAW_TEXT
;
968 /** Delete the character before the caret. */
969 static void delete_char_before(void)
974 tag_get_pt(&pane
.caret_pos
, &ep
);
975 spt_get_coord(&ep
, &coord
);
978 sheet_get_cell_pt(doc
.sh
, &coord
, dir_before
, &sp
);
980 (void) sheet_delete(doc
.sh
, &sp
, &ep
);
982 pane
.rflags
|= REDRAW_ROW
;
983 if (coord
.column
< 1)
984 pane
.rflags
|= REDRAW_TEXT
;
987 /** Delete the character after the caret. */
988 static void delete_char_after(void)
993 tag_get_pt(&pane
.caret_pos
, &sp
);
994 spt_get_coord(&sp
, &sc
);
996 sheet_get_cell_pt(doc
.sh
, &sc
, dir_after
, &ep
);
997 spt_get_coord(&ep
, &ec
);
999 (void) sheet_delete(doc
.sh
, &sp
, &ep
);
1001 pane
.rflags
|= REDRAW_ROW
;
1002 if (ec
.row
!= sc
.row
)
1003 pane
.rflags
|= REDRAW_TEXT
;
1006 /** Scroll pane after caret has moved.
1008 * After modifying the position of the caret, this is called to scroll
1009 * the pane to ensure that the caret is in the visible area.
1011 static void caret_update(void)
1016 tag_get_pt(&pane
.caret_pos
, &pt
);
1017 spt_get_coord(&pt
, &coord
);
1019 /* Scroll pane vertically. */
1021 if (coord
.row
< pane
.sh_row
) {
1022 pane
.sh_row
= coord
.row
;
1023 pane
.rflags
|= REDRAW_TEXT
;
1026 if (coord
.row
> pane
.sh_row
+ pane
.rows
- 1) {
1027 pane
.sh_row
= coord
.row
- pane
.rows
+ 1;
1028 pane
.rflags
|= REDRAW_TEXT
;
1031 /* Scroll pane horizontally. */
1033 if (coord
.column
< pane
.sh_column
) {
1034 pane
.sh_column
= coord
.column
;
1035 pane
.rflags
|= REDRAW_TEXT
;
1038 if (coord
.column
> pane
.sh_column
+ pane
.columns
- 1) {
1039 pane
.sh_column
= coord
.column
- pane
.columns
+ 1;
1040 pane
.rflags
|= REDRAW_TEXT
;
1043 pane
.rflags
|= (REDRAW_CARET
| REDRAW_STATUS
);
1046 /** Relatively move caret position.
1048 * Moves caret relatively to the current position. Looking at the first
1049 * character cell after the caret and moving by @a drow and @a dcolumn, we get
1050 * to a new character cell, and thus a new character. Then we either go to the
1051 * point before the the character or after it, depending on @a align_dir.
1053 * @param select true if the selection tag should stay where it is
1055 static void caret_move_relative(int drow
, int dcolumn
, enum dir_spec align_dir
,
1063 tag_get_pt(&pane
.caret_pos
, &pt
);
1064 spt_get_coord(&pt
, &coord
);
1065 coord
.row
+= drow
; coord
.column
+= dcolumn
;
1067 /* Clamp coordinates. */
1068 if (drow
< 0 && coord
.row
< 1) coord
.row
= 1;
1069 if (dcolumn
< 0 && coord
.column
< 1) {
1074 sheet_get_row_width(doc
.sh
, coord
.row
, &coord
.column
);
1078 sheet_get_num_rows(doc
.sh
, &num_rows
);
1079 if (coord
.row
> num_rows
) coord
.row
= num_rows
;
1082 /* For purely vertical movement try attaining @c ideal_column. */
1083 pure_vertical
= (dcolumn
== 0 && align_dir
== dir_before
);
1085 coord
.column
= pane
.ideal_column
;
1088 * Select the point before or after the character at the designated
1089 * coordinates. The character can be wider than one cell (e.g. tab).
1091 sheet_get_cell_pt(doc
.sh
, &coord
, align_dir
, &pt
);
1093 /* For non-vertical movement set the new value for @c ideal_column. */
1094 caret_move(pt
, select
, !pure_vertical
);
1097 /** Absolutely move caret position.
1099 * Moves caret to a specified position. We get to a new character cell, and
1100 * thus a new character. Then we either go to the point before the the character
1101 * or after it, depending on @a align_dir.
1103 * @param select true if the selection tag should stay where it is
1105 static void caret_move_absolute(int row
, int column
, enum dir_spec align_dir
,
1110 coord
.column
= column
;
1113 sheet_get_cell_pt(doc
.sh
, &coord
, align_dir
, &pt
);
1115 caret_move(pt
, select
, true);
1118 /** Find beginning of a word to the left of spt */
1119 static spt_t
pt_find_word_left(spt_t spt
)
1122 spt_prev_char(spt
, &spt
);
1123 } while (!pt_is_word_beginning(&spt
));
1127 /** Find beginning of a word to the right of spt */
1128 static spt_t
pt_find_word_right(spt_t spt
)
1131 spt_next_char(spt
, &spt
);
1132 } while (!pt_is_word_beginning(&spt
));
1136 static void caret_move_word_left(bool select
)
1139 tag_get_pt(&pane
.caret_pos
, &pt
);
1140 spt_t word_left
= pt_find_word_left(pt
);
1141 caret_move(word_left
, select
, true);
1144 static void caret_move_word_right(bool select
)
1147 tag_get_pt(&pane
.caret_pos
, &pt
);
1148 spt_t word_right
= pt_find_word_right(pt
);
1149 caret_move(word_right
, select
, true);
1152 /** Ask for line and go to it. */
1153 static void caret_go_to_line_ask(void)
1157 sline
= prompt("Go to line", "");
1158 if (sline
== NULL
) {
1159 status_display("Go to line cancelled.");
1164 int line
= strtol(sline
, &endptr
, 10);
1165 if (*endptr
!= '\0') {
1167 status_display("Invalid number entered.");
1172 caret_move_absolute(line
, pane
.ideal_column
, dir_before
, false);
1175 /* Search operations */
1176 static int search_spt_producer(void *data
, wchar_t *ret
)
1178 assert(data
!= NULL
);
1179 assert(ret
!= NULL
);
1181 *ret
= spt_next_char(*spt
, spt
);
1185 static int search_spt_reverse_producer(void *data
, wchar_t *ret
)
1187 assert(data
!= NULL
);
1188 assert(ret
!= NULL
);
1190 *ret
= spt_prev_char(*spt
, spt
);
1194 static int search_spt_mark(void *data
, void **mark
)
1196 assert(data
!= NULL
);
1197 assert(mark
!= NULL
);
1199 spt_t
*new = calloc(1, sizeof(spt_t
));
1207 static void search_spt_mark_free(void *data
)
1212 static search_ops_t search_spt_ops
= {
1213 .equals
= char_exact_equals
,
1214 .producer
= search_spt_producer
,
1215 .mark
= search_spt_mark
,
1216 .mark_free
= search_spt_mark_free
,
1219 static search_ops_t search_spt_reverse_ops
= {
1220 .equals
= char_exact_equals
,
1221 .producer
= search_spt_reverse_producer
,
1222 .mark
= search_spt_mark
,
1223 .mark_free
= search_spt_mark_free
,
1226 /** Ask for line and go to it. */
1227 static void search_prompt(bool reverse
)
1231 const char *prompt_text
= "Find next";
1233 prompt_text
= "Find previous";
1235 const char *default_value
= "";
1236 if (pane
.previous_search
)
1237 default_value
= pane
.previous_search
;
1239 pattern
= prompt(prompt_text
, default_value
);
1240 if (pattern
== NULL
) {
1241 status_display("Search cancelled.");
1245 if (pane
.previous_search
)
1246 free(pane
.previous_search
);
1247 pane
.previous_search
= pattern
;
1248 pane
.previous_search_reverse
= reverse
;
1250 search(pattern
, reverse
);
1253 static void search_repeat(void)
1255 if (pane
.previous_search
== NULL
) {
1256 status_display("No previous search to repeat.");
1260 search(pane
.previous_search
, pane
.previous_search_reverse
);
1263 static void search(char *pattern
, bool reverse
)
1265 status_display("Searching...");
1267 spt_t sp
, producer_pos
;
1268 tag_get_pt(&pane
.caret_pos
, &sp
);
1270 /* Start searching on the position before/after caret */
1272 spt_next_char(sp
, &sp
);
1275 spt_prev_char(sp
, &sp
);
1279 search_ops_t ops
= search_spt_ops
;
1281 ops
= search_spt_reverse_ops
;
1283 search_t
*search
= search_init(pattern
, &producer_pos
, ops
, reverse
);
1284 if (search
== NULL
) {
1285 status_display("Failed initializing search.");
1290 int rc
= search_next_match(search
, &match
);
1292 status_display("Failed searching.");
1293 search_fini(search
);
1297 status_display("Match found.");
1298 assert(match
.end
!= NULL
);
1299 spt_t
*end
= match
.end
;
1300 caret_move(*end
, false, true);
1301 while (match
.length
> 0) {
1304 spt_next_char(*end
, end
);
1307 spt_prev_char(*end
, end
);
1310 caret_move(*end
, true, true);
1314 status_display("Not found.");
1317 search_fini(search
);
1320 /** Check for non-empty selection. */
1321 static bool selection_active(void)
1323 return (tag_cmp(&pane
.caret_pos
, &pane
.sel_start
) != 0);
1326 static void selection_get_points(spt_t
*pa
, spt_t
*pb
)
1330 tag_get_pt(&pane
.sel_start
, pa
);
1331 tag_get_pt(&pane
.caret_pos
, pb
);
1333 if (spt_cmp(pa
, pb
) > 0) {
1340 /** Delete selected text. */
1341 static void selection_delete(void)
1347 tag_get_pt(&pane
.sel_start
, &pa
);
1348 tag_get_pt(&pane
.caret_pos
, &pb
);
1349 spt_get_coord(&pa
, &ca
);
1350 spt_get_coord(&pb
, &cb
);
1351 rel
= coord_cmp(&ca
, &cb
);
1357 sheet_delete(doc
.sh
, &pa
, &pb
);
1359 sheet_delete(doc
.sh
, &pb
, &pa
);
1361 if (ca
.row
== cb
.row
)
1362 pane
.rflags
|= REDRAW_ROW
;
1364 pane
.rflags
|= REDRAW_TEXT
;
1367 /** Select all text in the editor */
1368 static void selection_sel_all(void)
1375 selection_sel_range(spt
, ept
);
1378 /** Select select all text in a given range with the given direction */
1379 static void selection_sel_range(spt_t pa
, spt_t pb
)
1381 sheet_remove_tag(doc
.sh
, &pane
.sel_start
);
1382 sheet_place_tag(doc
.sh
, &pa
, &pane
.sel_start
);
1383 sheet_remove_tag(doc
.sh
, &pane
.caret_pos
);
1384 sheet_place_tag(doc
.sh
, &pb
, &pane
.caret_pos
);
1386 pane
.rflags
|= REDRAW_TEXT
;
1390 static void selection_copy(void)
1395 selection_get_points(&pa
, &pb
);
1396 str
= range_get_str(&pa
, &pb
);
1397 if (str
== NULL
|| clipboard_put_str(str
) != EOK
) {
1398 status_display("Copying to clipboard failed!");
1403 static void insert_clipboard_data(void)
1410 rc
= clipboard_get_str(&str
);
1411 if (rc
!= EOK
|| str
== NULL
)
1417 c
= str_decode(str
, &off
, STR_NO_LIMIT
);
1427 /** Get start-of-file s-point. */
1428 static void pt_get_sof(spt_t
*pt
)
1432 coord
.row
= coord
.column
= 1;
1433 sheet_get_cell_pt(doc
.sh
, &coord
, dir_before
, pt
);
1436 /** Get end-of-file s-point. */
1437 static void pt_get_eof(spt_t
*pt
)
1442 sheet_get_num_rows(doc
.sh
, &num_rows
);
1443 coord
.row
= num_rows
+ 1;
1446 sheet_get_cell_pt(doc
.sh
, &coord
, dir_after
, pt
);
1449 /** Get start-of-line s-point for given s-point cpt */
1450 static void pt_get_sol(spt_t
*cpt
, spt_t
*spt
)
1454 spt_get_coord(cpt
, &coord
);
1457 sheet_get_cell_pt(doc
.sh
, &coord
, dir_before
, spt
);
1460 /** Get end-of-line s-point for given s-point cpt */
1461 static void pt_get_eol(spt_t
*cpt
, spt_t
*ept
)
1466 spt_get_coord(cpt
, &coord
);
1467 sheet_get_row_width(doc
.sh
, coord
.row
, &row_width
);
1468 coord
.column
= row_width
- 1;
1470 sheet_get_cell_pt(doc
.sh
, &coord
, dir_after
, ept
);
1473 /** Check whether the spt is at a beginning of a word */
1474 static bool pt_is_word_beginning(spt_t
*pt
)
1476 spt_t lp
, sfp
, efp
, slp
, elp
;
1481 pt_get_sol(pt
, &slp
);
1482 pt_get_eol(pt
, &elp
);
1484 /* the spt is at the beginning or end of the file or line */
1485 if ((spt_cmp(&sfp
, pt
) == 0) || (spt_cmp(&efp
, pt
) == 0)
1486 || (spt_cmp(&slp
, pt
) == 0) || (spt_cmp(&elp
, pt
) == 0))
1489 /* the spt is a delimiter */
1490 if (pt_is_delimiter(pt
))
1493 spt_get_coord(pt
, &coord
);
1496 sheet_get_cell_pt(doc
.sh
, &coord
, dir_before
, &lp
);
1498 return pt_is_delimiter(&lp
)
1499 || (pt_is_punctuation(pt
) && !pt_is_punctuation(&lp
))
1500 || (pt_is_punctuation(&lp
) && !pt_is_punctuation(pt
));
1503 static wchar_t get_first_wchar(const char *str
)
1506 return str_decode(str
, &offset
, str_size(str
));
1509 static bool pt_is_delimiter(spt_t
*pt
)
1515 spt_get_coord(pt
, &coord
);
1518 sheet_get_cell_pt(doc
.sh
, &coord
, dir_after
, &rp
);
1520 ch
= range_get_str(pt
, &rp
);
1524 wchar_t first_char
= get_first_wchar(ch
);
1525 switch(first_char
) {
1535 static bool pt_is_punctuation(spt_t
*pt
)
1541 spt_get_coord(pt
, &coord
);
1544 sheet_get_cell_pt(doc
.sh
, &coord
, dir_after
, &rp
);
1546 ch
= range_get_str(pt
, &rp
);
1550 wchar_t first_char
= get_first_wchar(ch
);
1551 switch(first_char
) {
1573 /** Compare tags. */
1574 static int tag_cmp(tag_t
const *a
, tag_t
const *b
)
1581 return spt_cmp(&pa
, &pb
);
1584 /** Compare s-points. */
1585 static int spt_cmp(spt_t
const *a
, spt_t
const *b
)
1589 spt_get_coord(a
, &ca
);
1590 spt_get_coord(b
, &cb
);
1592 return coord_cmp(&ca
, &cb
);
1595 /** Compare coordinats. */
1596 static int coord_cmp(coord_t
const *a
, coord_t
const *b
)
1598 if (a
->row
- b
->row
!= 0)
1599 return a
->row
- b
->row
;
1601 return a
->column
- b
->column
;
1604 /** Display text in the status line. */
1605 static void status_display(char const *str
)
1607 console_set_pos(con
, 0, scr_rows
- 1);
1608 console_set_style(con
, STYLE_INVERTED
);
1610 int pos
= -(scr_columns
- 3);
1611 printf(" %*s ", pos
, str
);
1613 console_set_style(con
, STYLE_NORMAL
);
1615 pane
.rflags
|= REDRAW_CARET
;