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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 7ee7e6a was 7ee7e6a, checked in by Jakub Jermar <jakub@…>, 8 years ago

Further reduce the number of inclusions of sys/types.h

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