source: mainline/uspace/dist/src/c/demos/edit/edit.c@ a35b458

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a35b458 was a35b458, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 8 years ago

style: Remove trailing whitespace on _all_ lines, including empty ones, for particular file types.

Command used: tools/srepl '\s\+$' '' -- *.c *.h *.py *.sh *.s *.S *.ag

Currently, whitespace on empty lines is very inconsistent.
There are two basic choices: Either remove the whitespace, or keep empty lines
indented to the level of surrounding code. The former is AFAICT more common,
and also much easier to do automatically.

Alternatively, we could write script for automatic indentation, and use that
instead. However, if such a script exists, it's possible to use the indented
style locally, by having the editor apply relevant conversions on load/save,
without affecting remote repository. IMO, it makes more sense to adopt
the simpler rule.

  • Property mode set to 100644
File size: 34.5 KB
Line 
1/*
2 * Copyright (c) 2009 Jiri Svoboda
3 * Copyright (c) 2012 Martin Sucha
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>
39#include <stdlib.h>
40#include <stddef.h>
41#include <stdbool.h>
42#include <vfs/vfs.h>
43#include <io/console.h>
44#include <io/style.h>
45#include <io/keycode.h>
46#include <errno.h>
47#include <align.h>
48#include <macros.h>
49#include <clipboard.h>
50
51#include "sheet.h"
52#include "search.h"
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;
78
79 /** Start of selection */
80 tag_t sel_start;
81
82 /** Active keyboard modifiers */
83 keymod_t keymod;
84
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;
90
91 char *previous_search;
92 bool previous_search_reverse;
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;
101 sheet_t *sh;
102} doc_t;
103
104static console_ctrl_t *con;
105static doc_t doc;
106static bool done;
107static pane_t pane;
108static bool cursor_visible;
109
110static sysarg_t scr_rows;
111static sysarg_t scr_columns;
112
113#define ROW_BUF_SIZE 4096
114#define BUF_SIZE 64
115#define TAB_WIDTH 8
116
117/** Maximum filename length that can be entered. */
118#define INFNAME_MAX_LEN 128
119
120static void cursor_show(void);
121static void cursor_hide(void);
122static void cursor_setvis(bool visible);
123
124static void key_handle_press(kbd_event_t *ev);
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);
128static void key_handle_shift_ctrl(kbd_event_t const *ev);
129static void key_handle_movement(unsigned int key, bool shift);
130
131static void pos_handle(pos_event_t *ev);
132
133static int file_save(char const *fname);
134static void file_save_as(void);
135static int file_insert(char *fname);
136static int file_save_range(char const *fname, spt_t const *spos,
137 spt_t const *epos);
138static char *range_get_str(spt_t const *spos, spt_t const *epos);
139
140static char *prompt(char const *prompt, char const *init_value);
141
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);
147
148static void insert_char(wchar_t c);
149static void delete_char_before(void);
150static void delete_char_after(void);
151static void caret_update(void);
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);
157static void caret_go_to_line_ask(void);
158
159static bool selection_active(void);
160static void selection_sel_all(void);
161static void selection_sel_range(spt_t pa, spt_t pb);
162static void selection_get_points(spt_t *pa, spt_t *pb);
163static void selection_delete(void);
164static void selection_copy(void);
165static void insert_clipboard_data(void);
166
167static void search(char *pattern, bool reverse);
168static void search_prompt(bool reverse);
169static void search_repeat(void);
170
171static void pt_get_sof(spt_t *pt);
172static void pt_get_eof(spt_t *pt);
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);
178static spt_t pt_find_word_left(spt_t spt);
179static spt_t pt_find_word_left(spt_t spt);
180
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
185static void status_display(char const *str);
186
187
188int main(int argc, char *argv[])
189{
190 cons_event_t ev;
191 bool new_file;
192 errno_t rc;
193
194 con = console_init(stdin, stdout);
195 console_clear(con);
196
197 console_get_size(con, &scr_columns, &scr_rows);
198
199 pane.rows = scr_rows - 1;
200 pane.columns = scr_columns;
201 pane.sh_row = 1;
202 pane.sh_column = 1;
203
204 /* Start with an empty sheet. */
205 rc = sheet_create(&doc.sh);
206 if (rc != EOK) {
207 printf("Out of memory.\n");
208 return -1;
209 }
210
211 /* Place caret at the beginning of file. */
212 spt_t sof;
213 pt_get_sof(&sof);
214 sheet_place_tag(doc.sh, &sof, &pane.caret_pos);
215 pane.ideal_column = 1;
216
217 if (argc == 2) {
218 doc.file_name = str_dup(argv[1]);
219 } else if (argc > 1) {
220 printf("Invalid arguments.\n");
221 return -2;
222 } else {
223 doc.file_name = NULL;
224 }
225
226 new_file = false;
227
228 if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
229 new_file = true;
230
231 /* Place selection start tag. */
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);
237
238 /* Initial display */
239 cursor_visible = true;
240
241 cursor_hide();
242 console_clear(con);
243 pane_text_display();
244 pane_status_display();
245 if (new_file && doc.file_name != NULL)
246 status_display("File not found. Starting empty file.");
247 pane_caret_display();
248 cursor_show();
249
250 done = false;
251
252 while (!done) {
253 console_get_event(con, &ev);
254 pane.rflags = 0;
255
256 switch (ev.type) {
257 case CEV_KEY:
258 pane.keymod = ev.ev.key.mods;
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;
265 }
266
267 /* Redraw as necessary. */
268
269 cursor_hide();
270
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();
279
280 cursor_show();
281 }
282
283 console_clear(con);
284
285 return 0;
286}
287
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
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
326/** Handle key without modifier. */
327static void key_handle_unmod(kbd_event_t const *ev)
328{
329 switch (ev->key) {
330 case KC_ENTER:
331 selection_delete();
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:
343 key_handle_movement(ev->key, false);
344 break;
345 case KC_BACKSPACE:
346 if (selection_active())
347 selection_delete();
348 else
349 delete_char_before();
350 caret_update();
351 break;
352 case KC_DELETE:
353 if (selection_active())
354 selection_delete();
355 else
356 delete_char_after();
357 caret_update();
358 break;
359 default:
360 if (ev->c >= 32 || ev->c == '\t') {
361 selection_delete();
362 insert_char(ev->c);
363 caret_update();
364 }
365 break;
366 }
367}
368
369/** Handle Shift-key combination. */
370static void key_handle_shift(kbd_event_t const *ev)
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();
386 insert_char(ev->c);
387 caret_update();
388 }
389 break;
390 }
391}
392
393/** Handle Ctrl-key combination. */
394static void key_handle_ctrl(kbd_event_t const *ev)
395{
396 spt_t pt;
397 switch (ev->key) {
398 case KC_Q:
399 done = true;
400 break;
401 case KC_S:
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();
409 break;
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;
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;
428 case KC_RIGHT:
429 caret_move_word_right(false);
430 break;
431 case KC_LEFT:
432 caret_move_word_left(false);
433 break;
434 case KC_L:
435 caret_go_to_line_ask();
436 break;
437 case KC_F:
438 search_prompt(false);
439 break;
440 case KC_N:
441 search_repeat();
442 break;
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;
451 default:
452 break;
453 }
454}
455
456static void key_handle_shift_ctrl(kbd_event_t const *ev)
457{
458 spt_t pt;
459 switch(ev->key) {
460 case KC_LEFT:
461 caret_move_word_left(true);
462 break;
463 case KC_RIGHT:
464 caret_move_word_right(true);
465 break;
466 case KC_F:
467 search_prompt(true);
468 break;
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;
477 default:
478 break;
479 }
480}
481
482static void pos_handle(pos_event_t *ev)
483{
484 coord_t bc;
485 spt_t pt;
486 bool select;
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
493 select = (pane.keymod & KM_SHIFT) != 0;
494
495 caret_move(pt, select, true);
496 }
497}
498
499/** Move caret while preserving or resetting selection. */
500static void caret_move(spt_t new_caret_pt, bool select, bool update_ideal_column)
501{
502 spt_t old_caret_pt, old_sel_pt;
503 coord_t c_old, c_new;
504 bool had_sel;
505
506 /* Check if we had selection before. */
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);
510
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);
514
515 if (select == false) {
516 /* Move sel_start to the same point as caret. */
517 sheet_remove_tag(doc.sh, &pane.sel_start);
518 sheet_place_tag(doc.sh, &new_caret_pt, &pane.sel_start);
519 }
520
521 spt_get_coord(&new_caret_pt, &c_new);
522 if (select) {
523 spt_get_coord(&old_caret_pt, &c_old);
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 }
534
535 if (update_ideal_column)
536 pane.ideal_column = c_new.column;
537
538 caret_update();
539}
540
541static void key_handle_movement(unsigned int key, bool select)
542{
543 spt_t pt;
544 switch (key) {
545 case KC_LEFT:
546 caret_move_relative(0, -1, dir_before, select);
547 break;
548 case KC_RIGHT:
549 caret_move_relative(0, 0, dir_after, select);
550 break;
551 case KC_UP:
552 caret_move_relative(-1, 0, dir_before, select);
553 break;
554 case KC_DOWN:
555 caret_move_relative(+1, 0, dir_before, select);
556 break;
557 case KC_HOME:
558 tag_get_pt(&pane.caret_pos, &pt);
559 pt_get_sol(&pt, &pt);
560 caret_move(pt, select, true);
561 break;
562 case KC_END:
563 tag_get_pt(&pane.caret_pos, &pt);
564 pt_get_eol(&pt, &pt);
565 caret_move(pt, select, true);
566 break;
567 case KC_PAGE_UP:
568 caret_move_relative(-pane.rows, 0, dir_before, select);
569 break;
570 case KC_PAGE_DOWN:
571 caret_move_relative(+pane.rows, 0, dir_before, select);
572 break;
573 default:
574 break;
575 }
576}
577
578/** Save the document. */
579static int file_save(char const *fname)
580{
581 spt_t sp, ep;
582 errno_t 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);
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 }
601
602 return rc;
603}
604
605/** Change document name and save. */
606static void file_save_as(void)
607{
608 const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
609 char *fname;
610
611 fname = prompt("Save As", old_fname);
612 if (fname == NULL) {
613 status_display("Save cancelled.");
614 return;
615 }
616
617 errno_t rc = file_save(fname);
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
626/** Ask for a string. */
627static char *prompt(char const *prompt, char const *init_value)
628{
629 cons_event_t ev;
630 kbd_event_t *kev;
631 char *str;
632 wchar_t buffer[INFNAME_MAX_LEN + 1];
633 int max_len;
634 int nc;
635 bool done;
636
637 asprintf(&str, "%s: %s", prompt, init_value);
638 status_display(str);
639 console_set_pos(con, 1 + str_length(str), scr_rows - 1);
640 free(str);
641
642 console_set_style(con, STYLE_INVERTED);
643
644 max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
645 str_to_wstr(buffer, max_len + 1, init_value);
646 nc = wstr_length(buffer);
647 done = false;
648
649 while (!done) {
650 console_get_event(con, &ev);
651
652 if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS) {
653 kev = &ev.ev.key;
654
655 /* Handle key press. */
656 if (((kev->mods & KM_ALT) == 0) &&
657 (kev->mods & KM_CTRL) != 0) {
658 ;
659 } else if ((kev->mods & (KM_CTRL | KM_ALT)) == 0) {
660 switch (kev->key) {
661 case KC_ESCAPE:
662 return NULL;
663 case KC_BACKSPACE:
664 if (nc > 0) {
665 putchar('\b');
666 console_flush(con);
667 --nc;
668 }
669 break;
670 case KC_ENTER:
671 done = true;
672 break;
673 default:
674 if (kev->c >= 32 && nc < max_len) {
675 putchar(kev->c);
676 console_flush(con);
677 buffer[nc++] = kev->c;
678 }
679 break;
680 }
681 }
682 }
683 }
684
685 buffer[nc] = '\0';
686 str = wstr_to_astr(buffer);
687
688 console_set_style(con, STYLE_NORMAL);
689
690 return str;
691}
692
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 {
751 sheet_copy_out(doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
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
762 if (fclose(f) != EOK)
763 return EIO;
764
765 return EOK;
766}
767
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) {
786 sheet_copy_out(doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
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
804static void pane_text_display(void)
805{
806 int sh_rows, rows;
807
808 sheet_get_num_rows(doc.sh, &sh_rows);
809 rows = min(sh_rows - pane.sh_row + 1, pane.rows);
810
811 /* Draw rows from the sheet. */
812
813 console_set_pos(con, 0, 0);
814 pane_row_range_display(0, rows);
815
816 /* Clear the remaining rows if file is short. */
817
818 int i;
819 sysarg_t j;
820 for (i = rows; i < pane.rows; ++i) {
821 console_set_pos(con, 0, i);
822 for (j = 0; j < scr_columns; ++j)
823 putchar(' ');
824 console_flush(con);
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;
849 spt_t rb, re, dep, pt;
850 coord_t rbc, rec;
851 char row_buf[ROW_BUF_SIZE];
852 wchar_t c;
853 size_t pos, size;
854 int s_column;
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 }
870
871 /* Draw rows from the sheet. */
872
873 console_set_pos(con, 0, 0);
874 for (i = r0; i < r1; ++i) {
875 /* Starting point for row display */
876 rbc.row = pane.sh_row + i;
877 rbc.column = pane.sh_column;
878 sheet_get_cell_pt(doc.sh, &rbc, dir_before, &rb);
879
880 /* Ending point for row display */
881 rec.row = pane.sh_row + i;
882 rec.column = pane.sh_column + pane.columns;
883 sheet_get_cell_pt(doc.sh, &rec, dir_before, &re);
884
885 /* Copy the text of the row to the buffer. */
886 sheet_copy_out(doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
887
888 /* Display text from the buffer. */
889
890 if (coord_cmp(&csel_start, &rbc) <= 0 &&
891 coord_cmp(&rbc, &csel_end) < 0) {
892 console_flush(con);
893 console_set_style(con, STYLE_SELECTED);
894 console_flush(con);
895 }
896
897 console_set_pos(con, 0, i);
898 size = str_size(row_buf);
899 pos = 0;
900 s_column = pane.sh_column;
901 while (pos < size) {
902 if ((csel_start.row == rbc.row) && (csel_start.column == s_column)) {
903 console_flush(con);
904 console_set_style(con, STYLE_SELECTED);
905 console_flush(con);
906 }
907
908 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
909 console_flush(con);
910 console_set_style(con, STYLE_NORMAL);
911 console_flush(con);
912 }
913
914 c = str_decode(row_buf, &pos, size);
915 if (c != '\t') {
916 printf("%lc", (wint_t) c);
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
928 if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
929 console_flush(con);
930 console_set_style(con, STYLE_NORMAL);
931 console_flush(con);
932 }
933
934 /* Fill until the end of display area. */
935
936 if ((unsigned)s_column - 1 < scr_columns)
937 fill = scr_columns - (s_column - 1);
938 else
939 fill = 0;
940
941 for (j = 0; j < fill; ++j)
942 putchar(' ');
943 console_flush(con);
944 console_set_style(con, STYLE_NORMAL);
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;
955 int last_row;
956
957 tag_get_pt(&pane.caret_pos, &caret_pt);
958 spt_get_coord(&caret_pt, &coord);
959
960 sheet_get_num_rows(doc.sh, &last_row);
961
962 const char *fname = (doc.file_name != NULL) ? doc.file_name : "<unnamed>";
963
964 console_set_pos(con, 0, scr_rows - 1);
965 console_set_style(con, STYLE_INVERTED);
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);
968
969 int pos = scr_columns - 1 - n;
970 printf("%*s", pos, "");
971 console_flush(con);
972 console_set_style(con, STYLE_NORMAL);
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);
986 console_set_pos(con, coord.column - pane.sh_column,
987 coord.row - pane.sh_row);
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
1003 (void) sheet_insert(doc.sh, &pt, dir_before, cbuf);
1004
1005 pane.rflags |= REDRAW_ROW;
1006 if (c == '\n')
1007 pane.rflags |= REDRAW_TEXT;
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;
1020 sheet_get_cell_pt(doc.sh, &coord, dir_before, &sp);
1021
1022 (void) sheet_delete(doc.sh, &sp, &ep);
1023
1024 pane.rflags |= REDRAW_ROW;
1025 if (coord.column < 1)
1026 pane.rflags |= REDRAW_TEXT;
1027}
1028
1029/** Delete the character after the caret. */
1030static void delete_char_after(void)
1031{
1032 spt_t sp, ep;
1033 coord_t sc, ec;
1034
1035 tag_get_pt(&pane.caret_pos, &sp);
1036 spt_get_coord(&sp, &sc);
1037
1038 sheet_get_cell_pt(doc.sh, &sc, dir_after, &ep);
1039 spt_get_coord(&ep, &ec);
1040
1041 (void) sheet_delete(doc.sh, &sp, &ep);
1042
1043 pane.rflags |= REDRAW_ROW;
1044 if (ec.row != sc.row)
1045 pane.rflags |= REDRAW_TEXT;
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
1061 /* Scroll pane vertically. */
1062
1063 if (coord.row < pane.sh_row) {
1064 pane.sh_row = coord.row;
1065 pane.rflags |= REDRAW_TEXT;
1066 }
1067
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
1073 /* Scroll pane horizontally. */
1074
1075 if (coord.column < pane.sh_column) {
1076 pane.sh_column = coord.column;
1077 pane.rflags |= REDRAW_TEXT;
1078 }
1079
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);
1086}
1087
1088/** Relatively move caret position.
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.
1094 *
1095 * @param select true if the selection tag should stay where it is
1096 */
1097static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir,
1098 bool select)
1099{
1100 spt_t pt;
1101 coord_t coord;
1102 int num_rows;
1103 bool pure_vertical;
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;
1111 if (dcolumn < 0 && coord.column < 1) {
1112 if (coord.row < 2)
1113 coord.column = 1;
1114 else {
1115 coord.row--;
1116 sheet_get_row_width(doc.sh, coord.row, &coord.column);
1117 }
1118 }
1119 if (drow > 0) {
1120 sheet_get_num_rows(doc.sh, &num_rows);
1121 if (coord.row > num_rows) coord.row = num_rows;
1122 }
1123
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
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 */
1133 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1134
1135 /* For non-vertical movement set the new value for @c ideal_column. */
1136 caret_move(pt, select, !pure_vertical);
1137}
1138
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)
1149{
1150 coord_t coord;
1151 coord.row = row;
1152 coord.column = column;
1153
1154 spt_t pt;
1155 sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
1156
1157 caret_move(pt, select, true);
1158}
1159
1160/** Find beginning of a word to the left of spt */
1161static spt_t pt_find_word_left(spt_t spt)
1162{
1163 do {
1164 spt_prev_char(spt, &spt);
1165 } while (!pt_is_word_beginning(&spt));
1166 return spt;
1167}
1168
1169/** Find beginning of a word to the right of spt */
1170static spt_t pt_find_word_right(spt_t spt)
1171{
1172 do {
1173 spt_next_char(spt, &spt);
1174 } while (!pt_is_word_beginning(&spt));
1175 return spt;
1176}
1177
1178static void caret_move_word_left(bool select)
1179{
1180 spt_t pt;
1181 tag_get_pt(&pane.caret_pos, &pt);
1182 spt_t word_left = pt_find_word_left(pt);
1183 caret_move(word_left, select, true);
1184}
1185
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);
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') {
1208 free(sline);
1209 status_display("Invalid number entered.");
1210 return;
1211 }
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
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
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
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
1268/** Ask for line and go to it. */
1269static void search_prompt(bool reverse)
1270{
1271 char *pattern;
1272
1273 const char *prompt_text = "Find next";
1274 if (reverse)
1275 prompt_text = "Find previous";
1276
1277 const char *default_value = "";
1278 if (pane.previous_search)
1279 default_value = pane.previous_search;
1280
1281 pattern = prompt(prompt_text, default_value);
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;
1290 pane.previous_search_reverse = reverse;
1291
1292 search(pattern, reverse);
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 }
1301
1302 search(pane.previous_search, pane.previous_search_reverse);
1303}
1304
1305static void search(char *pattern, bool reverse)
1306{
1307 status_display("Searching...");
1308
1309 spt_t sp, producer_pos;
1310 tag_get_pt(&pane.caret_pos, &sp);
1311
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 }
1319 producer_pos = sp;
1320
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);
1326 if (search == NULL) {
1327 status_display("Failed initializing search.");
1328 return;
1329 }
1330
1331 match_t match;
1332 errno_t 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--;
1345 if (reverse) {
1346 spt_next_char(*end, end);
1347 }
1348 else {
1349 spt_prev_char(*end, end);
1350 }
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}
1361
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
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
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)
1399 sheet_delete(doc.sh, &pa, &pb);
1400 else
1401 sheet_delete(doc.sh, &pb, &pa);
1402
1403 if (ca.row == cb.row)
1404 pane.rflags |= REDRAW_ROW;
1405 else
1406 pane.rflags |= REDRAW_TEXT;
1407}
1408
1409/** Select all text in the editor */
1410static void selection_sel_all(void)
1411{
1412 spt_t spt, ept;
1413
1414 pt_get_sof(&spt);
1415 pt_get_eof(&ept);
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{
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);
1427
1428 pane.rflags |= REDRAW_TEXT;
1429 caret_update();
1430}
1431
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 errno_t 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
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;
1475 sheet_get_cell_pt(doc.sh, &coord, dir_before, pt);
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
1484 sheet_get_num_rows(doc.sh, &num_rows);
1485 coord.row = num_rows + 1;
1486 coord.column = 1;
1487
1488 sheet_get_cell_pt(doc.sh, &coord, dir_after, pt);
1489}
1490
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
1499 sheet_get_cell_pt(doc.sh, &coord, dir_before, spt);
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);
1509 sheet_get_row_width(doc.sh, coord.row, &row_width);
1510 coord.column = row_width - 1;
1511
1512 sheet_get_cell_pt(doc.sh, &coord, dir_after, ept);
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;
1538 sheet_get_cell_pt(doc.sh, &coord, dir_before, &lp);
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;
1560 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
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;
1586 sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
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
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
1646/** Display text in the status line. */
1647static void status_display(char const *str)
1648{
1649 console_set_pos(con, 0, scr_rows - 1);
1650 console_set_style(con, STYLE_INVERTED);
1651
1652 int pos = -(scr_columns - 3);
1653 printf(" %*s ", pos, str);
1654 console_flush(con);
1655 console_set_style(con, STYLE_NORMAL);
1656
1657 pane.rflags |= REDRAW_CARET;
1658}
1659
1660/** @}
1661 */
Note: See TracBrowser for help on using the repository browser.