HelenOS sources
This source file includes following definitions.
- main
- key_handle_press
- cursor_show
- cursor_hide
- cursor_setvis
- key_handle_unmod
- key_handle_shift
- key_handle_ctrl
- key_handle_shift_ctrl
- pos_handle
- caret_move
- key_handle_movement
- file_save
- file_save_as
- prompt
- file_insert
- file_save_range
- range_get_str
- pane_text_display
- pane_row_display
- pane_row_range_display
- pane_status_display
- pane_caret_display
- insert_char
- delete_char_before
- delete_char_after
- caret_update
- caret_move_relative
- caret_move_absolute
- pt_find_word_left
- pt_find_word_right
- caret_move_word_left
- caret_move_word_right
- caret_go_to_line_ask
- search_spt_producer
- search_spt_reverse_producer
- search_spt_mark
- search_spt_mark_free
- search_prompt
- search_repeat
- search
- selection_active
- selection_get_points
- selection_delete
- selection_sel_all
- selection_sel_range
- selection_copy
- insert_clipboard_data
- pt_get_sof
- pt_get_eof
- pt_get_sol
- pt_get_eol
- pt_is_word_beginning
- get_first_wchar
- pt_is_delimiter
- pt_is_punctuation
- tag_cmp
- spt_cmp
- coord_cmp
- status_display
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <vfs/vfs.h>
#include <io/console.h>
#include <io/style.h>
#include <io/keycode.h>
#include <errno.h>
#include <align.h>
#include <macros.h>
#include <clipboard.h>
#include <types/common.h>
#include "sheet.h"
#include "search.h"
enum redraw_flags {
REDRAW_TEXT = (1 << 0),
REDRAW_ROW = (1 << 1),
REDRAW_STATUS = (1 << 2),
REDRAW_CARET = (1 << 3)
};
typedef struct {
int rows, columns;
int sh_row, sh_column;
enum redraw_flags rflags;
tag_t caret_pos;
tag_t sel_start;
keymod_t keymod;
int ideal_column;
char *previous_search;
bool previous_search_reverse;
} pane_t;
typedef struct {
char *file_name;
sheet_t *sh;
} doc_t;
static console_ctrl_t *con;
static doc_t doc;
static bool done;
static pane_t pane;
static bool cursor_visible;
static sysarg_t scr_rows;
static sysarg_t scr_columns;
#define ROW_BUF_SIZE 4096
#define BUF_SIZE 64
#define TAB_WIDTH 8
#define INFNAME_MAX_LEN 128
static void cursor_show(void);
static void cursor_hide(void);
static void cursor_setvis(bool visible);
static void key_handle_press(kbd_event_t *ev);
static void key_handle_unmod(kbd_event_t const *ev);
static void key_handle_ctrl(kbd_event_t const *ev);
static void key_handle_shift(kbd_event_t const *ev);
static void key_handle_shift_ctrl(kbd_event_t const *ev);
static void key_handle_movement(unsigned int key, bool shift);
static void pos_handle(pos_event_t *ev);
static errno_t file_save(char const *fname);
static void file_save_as(void);
static errno_t file_insert(char *fname);
static errno_t file_save_range(char const *fname, spt_t const *spos,
spt_t const *epos);
static char *range_get_str(spt_t const *spos, spt_t const *epos);
static char *prompt(char const *prompt, char const *init_value);
static void pane_text_display(void);
static void pane_row_display(void);
static void pane_row_range_display(int r0, int r1);
static void pane_status_display(void);
static void pane_caret_display(void);
static void insert_char(char32_t c);
static void delete_char_before(void);
static void delete_char_after(void);
static void caret_update(void);
static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir, bool select);
static void caret_move_absolute(int row, int column, enum dir_spec align_dir, bool select);
static void caret_move(spt_t spt, bool select, bool update_ideal_column);
static void caret_move_word_left(bool select);
static void caret_move_word_right(bool select);
static void caret_go_to_line_ask(void);
static bool selection_active(void);
static void selection_sel_all(void);
static void selection_sel_range(spt_t pa, spt_t pb);
static void selection_get_points(spt_t *pa, spt_t *pb);
static void selection_delete(void);
static void selection_copy(void);
static void insert_clipboard_data(void);
static void search(char *pattern, bool reverse);
static void search_prompt(bool reverse);
static void search_repeat(void);
static void pt_get_sof(spt_t *pt);
static void pt_get_eof(spt_t *pt);
static void pt_get_sol(spt_t *cpt, spt_t *spt);
static void pt_get_eol(spt_t *cpt, spt_t *ept);
static bool pt_is_word_beginning(spt_t *pt);
static bool pt_is_delimiter(spt_t *pt);
static bool pt_is_punctuation(spt_t *pt);
static spt_t pt_find_word_left(spt_t spt);
static spt_t pt_find_word_left(spt_t spt);
static int tag_cmp(tag_t const *a, tag_t const *b);
static int spt_cmp(spt_t const *a, spt_t const *b);
static int coord_cmp(coord_t const *a, coord_t const *b);
static void status_display(char const *str);
int main(int argc, char *argv[])
{
cons_event_t ev;
bool new_file;
errno_t rc;
con = console_init(stdin, stdout);
console_clear(con);
console_get_size(con, &scr_columns, &scr_rows);
pane.rows = scr_rows - 1;
pane.columns = scr_columns;
pane.sh_row = 1;
pane.sh_column = 1;
rc = sheet_create(&doc.sh);
if (rc != EOK) {
printf("Out of memory.\n");
return -1;
}
spt_t sof;
pt_get_sof(&sof);
sheet_place_tag(doc.sh, &sof, &pane.caret_pos);
pane.ideal_column = 1;
if (argc == 2) {
doc.file_name = str_dup(argv[1]);
} else if (argc > 1) {
printf("Invalid arguments.\n");
return -2;
} else {
doc.file_name = NULL;
}
new_file = false;
if (doc.file_name == NULL || file_insert(doc.file_name) != EOK)
new_file = true;
sheet_place_tag(doc.sh, &sof, &pane.sel_start);
pt_get_sof(&sof);
caret_move(sof, true, true);
cursor_visible = true;
cursor_hide();
console_clear(con);
pane_text_display();
pane_status_display();
if (new_file && doc.file_name != NULL)
status_display("File not found. Starting empty file.");
pane_caret_display();
cursor_show();
done = false;
while (!done) {
console_get_event(con, &ev);
pane.rflags = 0;
switch (ev.type) {
case CEV_KEY:
pane.keymod = ev.ev.key.mods;
if (ev.ev.key.type == KEY_PRESS)
key_handle_press(&ev.ev.key);
break;
case CEV_POS:
pos_handle(&ev.ev.pos);
break;
}
cursor_hide();
if (pane.rflags & REDRAW_TEXT)
pane_text_display();
if (pane.rflags & REDRAW_ROW)
pane_row_display();
if (pane.rflags & REDRAW_STATUS)
pane_status_display();
if (pane.rflags & REDRAW_CARET)
pane_caret_display();
cursor_show();
}
console_clear(con);
return 0;
}
static void key_handle_press(kbd_event_t *ev)
{
if (((ev->mods & KM_ALT) == 0) &&
((ev->mods & KM_SHIFT) == 0) &&
(ev->mods & KM_CTRL) != 0) {
key_handle_ctrl(ev);
} else if (((ev->mods & KM_ALT) == 0) &&
((ev->mods & KM_CTRL) == 0) &&
(ev->mods & KM_SHIFT) != 0) {
key_handle_shift(ev);
} else if (((ev->mods & KM_ALT) == 0) &&
((ev->mods & KM_CTRL) != 0) &&
(ev->mods & KM_SHIFT) != 0) {
key_handle_shift_ctrl(ev);
} else if ((ev->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
key_handle_unmod(ev);
}
}
static void cursor_show(void)
{
cursor_setvis(true);
}
static void cursor_hide(void)
{
cursor_setvis(false);
}
static void cursor_setvis(bool visible)
{
if (cursor_visible != visible) {
console_cursor_visibility(con, visible);
cursor_visible = visible;
}
}
static void key_handle_unmod(kbd_event_t const *ev)
{
switch (ev->key) {
case KC_ENTER:
selection_delete();
insert_char('\n');
caret_update();
break;
case KC_LEFT:
case KC_RIGHT:
case KC_UP:
case KC_DOWN:
case KC_HOME:
case KC_END:
case KC_PAGE_UP:
case KC_PAGE_DOWN:
key_handle_movement(ev->key, false);
break;
case KC_BACKSPACE:
if (selection_active())
selection_delete();
else
delete_char_before();
caret_update();
break;
case KC_DELETE:
if (selection_active())
selection_delete();
else
delete_char_after();
caret_update();
break;
default:
if (ev->c >= 32 || ev->c == '\t') {
selection_delete();
insert_char(ev->c);
caret_update();
}
break;
}
}
static void key_handle_shift(kbd_event_t const *ev)
{
switch (ev->key) {
case KC_LEFT:
case KC_RIGHT:
case KC_UP:
case KC_DOWN:
case KC_HOME:
case KC_END:
case KC_PAGE_UP:
case KC_PAGE_DOWN:
key_handle_movement(ev->key, true);
break;
default:
if (ev->c >= 32 || ev->c == '\t') {
selection_delete();
insert_char(ev->c);
caret_update();
}
break;
}
}
static void key_handle_ctrl(kbd_event_t const *ev)
{
spt_t pt;
switch (ev->key) {
case KC_Q:
done = true;
break;
case KC_S:
if (doc.file_name != NULL)
file_save(doc.file_name);
else
file_save_as();
break;
case KC_E:
file_save_as();
break;
case KC_C:
selection_copy();
break;
case KC_V:
selection_delete();
insert_clipboard_data();
pane.rflags |= REDRAW_TEXT;
caret_update();
break;
case KC_X:
selection_copy();
selection_delete();
pane.rflags |= REDRAW_TEXT;
caret_update();
break;
case KC_A:
selection_sel_all();
break;
case KC_RIGHT:
caret_move_word_right(false);
break;
case KC_LEFT:
caret_move_word_left(false);
break;
case KC_L:
caret_go_to_line_ask();
break;
case KC_F:
search_prompt(false);
break;
case KC_N:
search_repeat();
break;
case KC_HOME:
pt_get_sof(&pt);
caret_move(pt, false, true);
break;
case KC_END:
pt_get_eof(&pt);
caret_move(pt, false, true);
break;
default:
break;
}
}
static void key_handle_shift_ctrl(kbd_event_t const *ev)
{
spt_t pt;
switch (ev->key) {
case KC_LEFT:
caret_move_word_left(true);
break;
case KC_RIGHT:
caret_move_word_right(true);
break;
case KC_F:
search_prompt(true);
break;
case KC_HOME:
pt_get_sof(&pt);
caret_move(pt, true, true);
break;
case KC_END:
pt_get_eof(&pt);
caret_move(pt, true, true);
break;
default:
break;
}
}
static void pos_handle(pos_event_t *ev)
{
coord_t bc;
spt_t pt;
bool select;
if (ev->type == POS_PRESS && ev->vpos < (unsigned)pane.rows) {
bc.row = pane.sh_row + ev->vpos;
bc.column = pane.sh_column + ev->hpos;
sheet_get_cell_pt(doc.sh, &bc, dir_before, &pt);
select = (pane.keymod & KM_SHIFT) != 0;
caret_move(pt, select, true);
}
}
static void caret_move(spt_t new_caret_pt, bool select, bool update_ideal_column)
{
spt_t old_caret_pt, old_sel_pt;
coord_t c_old, c_new;
bool had_sel;
tag_get_pt(&pane.caret_pos, &old_caret_pt);
tag_get_pt(&pane.sel_start, &old_sel_pt);
had_sel = !spt_equal(&old_caret_pt, &old_sel_pt);
sheet_remove_tag(doc.sh, &pane.caret_pos);
sheet_place_tag(doc.sh, &new_caret_pt, &pane.caret_pos);
if (select == false) {
sheet_remove_tag(doc.sh, &pane.sel_start);
sheet_place_tag(doc.sh, &new_caret_pt, &pane.sel_start);
}
spt_get_coord(&new_caret_pt, &c_new);
if (select) {
spt_get_coord(&old_caret_pt, &c_old);
if (c_old.row == c_new.row)
pane.rflags |= REDRAW_ROW;
else
pane.rflags |= REDRAW_TEXT;
} else if (had_sel == true) {
pane.rflags |= REDRAW_TEXT;
}
if (update_ideal_column)
pane.ideal_column = c_new.column;
caret_update();
}
static void key_handle_movement(unsigned int key, bool select)
{
spt_t pt;
switch (key) {
case KC_LEFT:
caret_move_relative(0, -1, dir_before, select);
break;
case KC_RIGHT:
caret_move_relative(0, 0, dir_after, select);
break;
case KC_UP:
caret_move_relative(-1, 0, dir_before, select);
break;
case KC_DOWN:
caret_move_relative(+1, 0, dir_before, select);
break;
case KC_HOME:
tag_get_pt(&pane.caret_pos, &pt);
pt_get_sol(&pt, &pt);
caret_move(pt, select, true);
break;
case KC_END:
tag_get_pt(&pane.caret_pos, &pt);
pt_get_eol(&pt, &pt);
caret_move(pt, select, true);
break;
case KC_PAGE_UP:
caret_move_relative(-pane.rows, 0, dir_before, select);
break;
case KC_PAGE_DOWN:
caret_move_relative(+pane.rows, 0, dir_before, select);
break;
default:
break;
}
}
static errno_t file_save(char const *fname)
{
spt_t sp, ep;
errno_t rc;
status_display("Saving...");
pt_get_sof(&sp);
pt_get_eof(&ep);
rc = file_save_range(fname, &sp, &ep);
switch (rc) {
case EINVAL:
status_display("Error opening file!");
break;
case EIO:
status_display("Error writing data!");
break;
default:
status_display("File saved.");
break;
}
return rc;
}
static void file_save_as(void)
{
const char *old_fname = (doc.file_name != NULL) ? doc.file_name : "";
char *fname;
fname = prompt("Save As", old_fname);
if (fname == NULL) {
status_display("Save cancelled.");
return;
}
errno_t rc = file_save(fname);
if (rc != EOK)
return;
if (doc.file_name != NULL)
free(doc.file_name);
doc.file_name = fname;
}
static char *prompt(char const *prompt, char const *init_value)
{
cons_event_t ev;
kbd_event_t *kev;
char *str;
char32_t buffer[INFNAME_MAX_LEN + 1];
int max_len;
int nc;
bool done;
asprintf(&str, "%s: %s", prompt, init_value);
status_display(str);
console_set_pos(con, 1 + str_length(str), scr_rows - 1);
free(str);
console_set_style(con, STYLE_INVERTED);
max_len = min(INFNAME_MAX_LEN, scr_columns - 4 - str_length(prompt));
str_to_wstr(buffer, max_len + 1, init_value);
nc = wstr_length(buffer);
done = false;
while (!done) {
console_get_event(con, &ev);
if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS) {
kev = &ev.ev.key;
if ((kev->mods & (KM_CTRL | KM_ALT)) == 0) {
switch (kev->key) {
case KC_ESCAPE:
return NULL;
case KC_BACKSPACE:
if (nc > 0) {
putchar('\b');
console_flush(con);
--nc;
}
break;
case KC_ENTER:
done = true;
break;
default:
if (kev->c >= 32 && nc < max_len) {
putuchar(kev->c);
console_flush(con);
buffer[nc++] = kev->c;
}
break;
}
}
}
}
buffer[nc] = '\0';
str = wstr_to_astr(buffer);
console_set_style(con, STYLE_NORMAL);
return str;
}
static errno_t file_insert(char *fname)
{
FILE *f;
char32_t c;
char buf[BUF_SIZE];
int bcnt;
int n_read;
size_t off;
f = fopen(fname, "rt");
if (f == NULL)
return EINVAL;
bcnt = 0;
while (true) {
if (bcnt < STR_BOUNDS(1)) {
n_read = fread(buf + bcnt, 1, BUF_SIZE - bcnt, f);
bcnt += n_read;
}
off = 0;
c = str_decode(buf, &off, bcnt);
if (c == '\0')
break;
bcnt -= off;
memcpy(buf, buf + off, bcnt);
insert_char(c);
}
fclose(f);
return EOK;
}
static errno_t file_save_range(char const *fname, spt_t const *spos,
spt_t const *epos)
{
FILE *f;
char buf[BUF_SIZE];
spt_t sp, bep;
size_t bytes, n_written;
f = fopen(fname, "wt");
if (f == NULL)
return EINVAL;
sp = *spos;
do {
sheet_copy_out(doc.sh, &sp, epos, buf, BUF_SIZE, &bep);
bytes = str_size(buf);
n_written = fwrite(buf, 1, bytes, f);
if (n_written != bytes) {
return EIO;
}
sp = bep;
} while (!spt_equal(&bep, epos));
if (fclose(f) < 0)
return EIO;
return EOK;
}
static char *range_get_str(spt_t const *spos, spt_t const *epos)
{
char *buf;
spt_t sp, bep;
size_t bytes;
size_t buf_size, bpos;
buf_size = 1;
buf = malloc(buf_size);
if (buf == NULL)
return NULL;
bpos = 0;
sp = *spos;
while (true) {
sheet_copy_out(doc.sh, &sp, epos, &buf[bpos], buf_size - bpos,
&bep);
bytes = str_size(&buf[bpos]);
bpos += bytes;
sp = bep;
if (spt_equal(&bep, epos))
break;
buf_size *= 2;
buf = realloc(buf, buf_size);
if (buf == NULL)
return NULL;
}
return buf;
}
static void pane_text_display(void)
{
int sh_rows, rows;
sheet_get_num_rows(doc.sh, &sh_rows);
rows = min(sh_rows - pane.sh_row + 1, pane.rows);
console_set_pos(con, 0, 0);
pane_row_range_display(0, rows);
int i;
sysarg_t j;
for (i = rows; i < pane.rows; ++i) {
console_set_pos(con, 0, i);
for (j = 0; j < scr_columns; ++j)
putchar(' ');
console_flush(con);
}
pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
pane.rflags &= ~REDRAW_ROW;
}
static void pane_row_display(void)
{
spt_t caret_pt;
coord_t coord;
int ridx;
tag_get_pt(&pane.caret_pos, &caret_pt);
spt_get_coord(&caret_pt, &coord);
ridx = coord.row - pane.sh_row;
pane_row_range_display(ridx, ridx + 1);
pane.rflags |= (REDRAW_STATUS | REDRAW_CARET);
}
static void pane_row_range_display(int r0, int r1)
{
int i, j, fill;
spt_t rb, re, dep, pt;
coord_t rbc, rec;
char row_buf[ROW_BUF_SIZE];
char32_t c;
size_t pos, size;
int s_column;
coord_t csel_start, csel_end, ctmp;
tag_get_pt(&pane.sel_start, &pt);
spt_get_coord(&pt, &csel_start);
tag_get_pt(&pane.caret_pos, &pt);
spt_get_coord(&pt, &csel_end);
if (coord_cmp(&csel_start, &csel_end) > 0) {
ctmp = csel_start;
csel_start = csel_end;
csel_end = ctmp;
}
console_set_pos(con, 0, 0);
for (i = r0; i < r1; ++i) {
rbc.row = pane.sh_row + i;
rbc.column = pane.sh_column;
sheet_get_cell_pt(doc.sh, &rbc, dir_before, &rb);
rec.row = pane.sh_row + i;
rec.column = pane.sh_column + pane.columns;
sheet_get_cell_pt(doc.sh, &rec, dir_before, &re);
sheet_copy_out(doc.sh, &rb, &re, row_buf, ROW_BUF_SIZE, &dep);
if (coord_cmp(&csel_start, &rbc) <= 0 &&
coord_cmp(&rbc, &csel_end) < 0) {
console_flush(con);
console_set_style(con, STYLE_SELECTED);
console_flush(con);
}
console_set_pos(con, 0, i);
size = str_size(row_buf);
pos = 0;
s_column = pane.sh_column;
while (pos < size) {
if ((csel_start.row == rbc.row) && (csel_start.column == s_column)) {
console_flush(con);
console_set_style(con, STYLE_SELECTED);
console_flush(con);
}
if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
console_flush(con);
console_set_style(con, STYLE_NORMAL);
console_flush(con);
}
c = str_decode(row_buf, &pos, size);
if (c != '\t') {
printf("%lc", (wint_t) c);
s_column += 1;
} else {
fill = 1 + ALIGN_UP(s_column, TAB_WIDTH) -
s_column;
for (j = 0; j < fill; ++j)
putchar(' ');
s_column += fill;
}
}
if ((csel_end.row == rbc.row) && (csel_end.column == s_column)) {
console_flush(con);
console_set_style(con, STYLE_NORMAL);
console_flush(con);
}
if ((unsigned)s_column - 1 < scr_columns)
fill = scr_columns - (s_column - 1);
else
fill = 0;
for (j = 0; j < fill; ++j)
putchar(' ');
console_flush(con);
console_set_style(con, STYLE_NORMAL);
}
pane.rflags |= REDRAW_CARET;
}
static void pane_status_display(void)
{
spt_t caret_pt;
coord_t coord;
int last_row;
char *fname;
char *p;
char *text;
size_t n;
int pos;
size_t nextra;
size_t fnw;
tag_get_pt(&pane.caret_pos, &caret_pt);
spt_get_coord(&caret_pt, &coord);
sheet_get_num_rows(doc.sh, &last_row);
if (doc.file_name != NULL) {
p = str_rchr(doc.file_name, '/');
if (p != NULL)
fname = str_dup(p + 1);
else
fname = str_dup(doc.file_name);
} else {
fname = str_dup("<unnamed>");
}
if (fname == NULL)
return;
console_set_pos(con, 0, scr_rows - 1);
console_set_style(con, STYLE_INVERTED);
while (true) {
int rc = asprintf(&text, " %d, %d (%d): File '%s'. Ctrl-Q Quit Ctrl-S Save "
"Ctrl-E Save As", coord.row, coord.column, last_row, fname);
if (rc < 0) {
n = 0;
goto finish;
}
n = str_width(text);
if (n <= scr_columns - 2)
break;
nextra = n - (scr_columns - 2);
fnw = str_width(fname);
if (nextra > fnw - 2)
goto finish;
if (fnw >= nextra + 2) {
p = fname + str_lsize(fname, fnw - nextra - 2);
} else {
p = fname;
}
p[0] = p[1] = '.';
p[2] = '\0';
free(text);
}
printf("%s", text);
free(text);
free(fname);
finish:
pos = scr_columns - 1 - n;
printf("%*s", pos, "");
console_flush(con);
console_set_style(con, STYLE_NORMAL);
pane.rflags |= REDRAW_CARET;
}
static void pane_caret_display(void)
{
spt_t caret_pt;
coord_t coord;
tag_get_pt(&pane.caret_pos, &caret_pt);
spt_get_coord(&caret_pt, &coord);
console_set_pos(con, coord.column - pane.sh_column,
coord.row - pane.sh_row);
}
static void insert_char(char32_t c)
{
spt_t pt;
char cbuf[STR_BOUNDS(1) + 1];
size_t offs;
tag_get_pt(&pane.caret_pos, &pt);
offs = 0;
chr_encode(c, cbuf, &offs, STR_BOUNDS(1) + 1);
cbuf[offs] = '\0';
(void) sheet_insert(doc.sh, &pt, dir_before, cbuf);
pane.rflags |= REDRAW_ROW;
if (c == '\n')
pane.rflags |= REDRAW_TEXT;
}
static void delete_char_before(void)
{
spt_t sp, ep;
coord_t coord;
tag_get_pt(&pane.caret_pos, &ep);
spt_get_coord(&ep, &coord);
coord.column -= 1;
sheet_get_cell_pt(doc.sh, &coord, dir_before, &sp);
(void) sheet_delete(doc.sh, &sp, &ep);
pane.rflags |= REDRAW_ROW;
if (coord.column < 1)
pane.rflags |= REDRAW_TEXT;
}
static void delete_char_after(void)
{
spt_t sp, ep;
coord_t sc, ec;
tag_get_pt(&pane.caret_pos, &sp);
spt_get_coord(&sp, &sc);
sheet_get_cell_pt(doc.sh, &sc, dir_after, &ep);
spt_get_coord(&ep, &ec);
(void) sheet_delete(doc.sh, &sp, &ep);
pane.rflags |= REDRAW_ROW;
if (ec.row != sc.row)
pane.rflags |= REDRAW_TEXT;
}
static void caret_update(void)
{
spt_t pt;
coord_t coord;
tag_get_pt(&pane.caret_pos, &pt);
spt_get_coord(&pt, &coord);
if (coord.row < pane.sh_row) {
pane.sh_row = coord.row;
pane.rflags |= REDRAW_TEXT;
}
if (coord.row > pane.sh_row + pane.rows - 1) {
pane.sh_row = coord.row - pane.rows + 1;
pane.rflags |= REDRAW_TEXT;
}
if (coord.column < pane.sh_column) {
pane.sh_column = coord.column;
pane.rflags |= REDRAW_TEXT;
}
if (coord.column > pane.sh_column + pane.columns - 1) {
pane.sh_column = coord.column - pane.columns + 1;
pane.rflags |= REDRAW_TEXT;
}
pane.rflags |= (REDRAW_CARET | REDRAW_STATUS);
}
static void caret_move_relative(int drow, int dcolumn, enum dir_spec align_dir,
bool select)
{
spt_t pt;
coord_t coord;
int num_rows;
bool pure_vertical;
tag_get_pt(&pane.caret_pos, &pt);
spt_get_coord(&pt, &coord);
coord.row += drow;
coord.column += dcolumn;
if (drow < 0 && coord.row < 1)
coord.row = 1;
if (dcolumn < 0 && coord.column < 1) {
if (coord.row < 2)
coord.column = 1;
else {
coord.row--;
sheet_get_row_width(doc.sh, coord.row, &coord.column);
}
}
if (drow > 0) {
sheet_get_num_rows(doc.sh, &num_rows);
if (coord.row > num_rows)
coord.row = num_rows;
}
pure_vertical = (dcolumn == 0 && align_dir == dir_before);
if (pure_vertical)
coord.column = pane.ideal_column;
sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
caret_move(pt, select, !pure_vertical);
}
static void caret_move_absolute(int row, int column, enum dir_spec align_dir,
bool select)
{
coord_t coord;
coord.row = row;
coord.column = column;
spt_t pt;
sheet_get_cell_pt(doc.sh, &coord, align_dir, &pt);
caret_move(pt, select, true);
}
static spt_t pt_find_word_left(spt_t spt)
{
do {
spt_prev_char(spt, &spt);
} while (!pt_is_word_beginning(&spt));
return spt;
}
static spt_t pt_find_word_right(spt_t spt)
{
do {
spt_next_char(spt, &spt);
} while (!pt_is_word_beginning(&spt));
return spt;
}
static void caret_move_word_left(bool select)
{
spt_t pt;
tag_get_pt(&pane.caret_pos, &pt);
spt_t word_left = pt_find_word_left(pt);
caret_move(word_left, select, true);
}
static void caret_move_word_right(bool select)
{
spt_t pt;
tag_get_pt(&pane.caret_pos, &pt);
spt_t word_right = pt_find_word_right(pt);
caret_move(word_right, select, true);
}
static void caret_go_to_line_ask(void)
{
char *sline;
sline = prompt("Go to line", "");
if (sline == NULL) {
status_display("Go to line cancelled.");
return;
}
char *endptr;
int line = strtol(sline, &endptr, 10);
if (*endptr != '\0') {
free(sline);
status_display("Invalid number entered.");
return;
}
free(sline);
caret_move_absolute(line, pane.ideal_column, dir_before, false);
}
static errno_t search_spt_producer(void *data, char32_t *ret)
{
assert(data != NULL);
assert(ret != NULL);
spt_t *spt = data;
*ret = spt_next_char(*spt, spt);
return EOK;
}
static errno_t search_spt_reverse_producer(void *data, char32_t *ret)
{
assert(data != NULL);
assert(ret != NULL);
spt_t *spt = data;
*ret = spt_prev_char(*spt, spt);
return EOK;
}
static errno_t search_spt_mark(void *data, void **mark)
{
assert(data != NULL);
assert(mark != NULL);
spt_t *spt = data;
spt_t *new = calloc(1, sizeof(spt_t));
*mark = new;
if (new == NULL)
return ENOMEM;
*new = *spt;
return EOK;
}
static void search_spt_mark_free(void *data)
{
free(data);
}
static search_ops_t search_spt_ops = {
.equals = char_exact_equals,
.producer = search_spt_producer,
.mark = search_spt_mark,
.mark_free = search_spt_mark_free,
};
static search_ops_t search_spt_reverse_ops = {
.equals = char_exact_equals,
.producer = search_spt_reverse_producer,
.mark = search_spt_mark,
.mark_free = search_spt_mark_free,
};
static void search_prompt(bool reverse)
{
char *pattern;
const char *prompt_text = "Find next";
if (reverse)
prompt_text = "Find previous";
const char *default_value = "";
if (pane.previous_search)
default_value = pane.previous_search;
pattern = prompt(prompt_text, default_value);
if (pattern == NULL) {
status_display("Search cancelled.");
return;
}
if (pane.previous_search)
free(pane.previous_search);
pane.previous_search = pattern;
pane.previous_search_reverse = reverse;
search(pattern, reverse);
}
static void search_repeat(void)
{
if (pane.previous_search == NULL) {
status_display("No previous search to repeat.");
return;
}
search(pane.previous_search, pane.previous_search_reverse);
}
static void search(char *pattern, bool reverse)
{
status_display("Searching...");
spt_t sp, producer_pos;
tag_get_pt(&pane.caret_pos, &sp);
if (!reverse) {
spt_next_char(sp, &sp);
} else {
spt_prev_char(sp, &sp);
}
producer_pos = sp;
search_ops_t ops = search_spt_ops;
if (reverse)
ops = search_spt_reverse_ops;
search_t *search = search_init(pattern, &producer_pos, ops, reverse);
if (search == NULL) {
status_display("Failed initializing search.");
return;
}
match_t match;
errno_t rc = search_next_match(search, &match);
if (rc != EOK) {
status_display("Failed searching.");
search_fini(search);
}
if (match.end) {
status_display("Match found.");
assert(match.end != NULL);
spt_t *end = match.end;
caret_move(*end, false, true);
while (match.length > 0) {
match.length--;
if (reverse) {
spt_next_char(*end, end);
} else {
spt_prev_char(*end, end);
}
}
caret_move(*end, true, true);
free(end);
} else {
status_display("Not found.");
}
search_fini(search);
}
static bool selection_active(void)
{
return (tag_cmp(&pane.caret_pos, &pane.sel_start) != 0);
}
static void selection_get_points(spt_t *pa, spt_t *pb)
{
spt_t pt;
tag_get_pt(&pane.sel_start, pa);
tag_get_pt(&pane.caret_pos, pb);
if (spt_cmp(pa, pb) > 0) {
pt = *pa;
*pa = *pb;
*pb = pt;
}
}
static void selection_delete(void)
{
spt_t pa, pb;
coord_t ca, cb;
int rel;
tag_get_pt(&pane.sel_start, &pa);
tag_get_pt(&pane.caret_pos, &pb);
spt_get_coord(&pa, &ca);
spt_get_coord(&pb, &cb);
rel = coord_cmp(&ca, &cb);
if (rel == 0)
return;
if (rel < 0)
sheet_delete(doc.sh, &pa, &pb);
else
sheet_delete(doc.sh, &pb, &pa);
if (ca.row == cb.row)
pane.rflags |= REDRAW_ROW;
else
pane.rflags |= REDRAW_TEXT;
}
static void selection_sel_all(void)
{
spt_t spt, ept;
pt_get_sof(&spt);
pt_get_eof(&ept);
selection_sel_range(spt, ept);
}
static void selection_sel_range(spt_t pa, spt_t pb)
{
sheet_remove_tag(doc.sh, &pane.sel_start);
sheet_place_tag(doc.sh, &pa, &pane.sel_start);
sheet_remove_tag(doc.sh, &pane.caret_pos);
sheet_place_tag(doc.sh, &pb, &pane.caret_pos);
pane.rflags |= REDRAW_TEXT;
caret_update();
}
static void selection_copy(void)
{
spt_t pa, pb;
char *str;
selection_get_points(&pa, &pb);
str = range_get_str(&pa, &pb);
if (str == NULL || clipboard_put_str(str) != EOK) {
status_display("Copying to clipboard failed!");
}
free(str);
}
static void insert_clipboard_data(void)
{
char *str;
size_t off;
char32_t c;
errno_t rc;
rc = clipboard_get_str(&str);
if (rc != EOK || str == NULL)
return;
off = 0;
while (true) {
c = str_decode(str, &off, STR_NO_LIMIT);
if (c == '\0')
break;
insert_char(c);
}
free(str);
}
static void pt_get_sof(spt_t *pt)
{
coord_t coord;
coord.row = coord.column = 1;
sheet_get_cell_pt(doc.sh, &coord, dir_before, pt);
}
static void pt_get_eof(spt_t *pt)
{
coord_t coord;
int num_rows;
sheet_get_num_rows(doc.sh, &num_rows);
coord.row = num_rows + 1;
coord.column = 1;
sheet_get_cell_pt(doc.sh, &coord, dir_after, pt);
}
static void pt_get_sol(spt_t *cpt, spt_t *spt)
{
coord_t coord;
spt_get_coord(cpt, &coord);
coord.column = 1;
sheet_get_cell_pt(doc.sh, &coord, dir_before, spt);
}
static void pt_get_eol(spt_t *cpt, spt_t *ept)
{
coord_t coord;
int row_width;
spt_get_coord(cpt, &coord);
sheet_get_row_width(doc.sh, coord.row, &row_width);
coord.column = row_width - 1;
sheet_get_cell_pt(doc.sh, &coord, dir_after, ept);
}
static bool pt_is_word_beginning(spt_t *pt)
{
spt_t lp, sfp, efp, slp, elp;
coord_t coord;
pt_get_sof(&sfp);
pt_get_eof(&efp);
pt_get_sol(pt, &slp);
pt_get_eol(pt, &elp);
if ((spt_cmp(&sfp, pt) == 0) || (spt_cmp(&efp, pt) == 0) ||
(spt_cmp(&slp, pt) == 0) || (spt_cmp(&elp, pt) == 0))
return true;
if (pt_is_delimiter(pt))
return false;
spt_get_coord(pt, &coord);
coord.column -= 1;
sheet_get_cell_pt(doc.sh, &coord, dir_before, &lp);
return pt_is_delimiter(&lp) ||
(pt_is_punctuation(pt) && !pt_is_punctuation(&lp)) ||
(pt_is_punctuation(&lp) && !pt_is_punctuation(pt));
}
static char32_t get_first_wchar(const char *str)
{
size_t offset = 0;
return str_decode(str, &offset, str_size(str));
}
static bool pt_is_delimiter(spt_t *pt)
{
spt_t rp;
coord_t coord;
char *ch = NULL;
spt_get_coord(pt, &coord);
coord.column += 1;
sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
ch = range_get_str(pt, &rp);
if (ch == NULL)
return false;
char32_t first_char = get_first_wchar(ch);
switch (first_char) {
case ' ':
case '\t':
case '\n':
return true;
default:
return false;
}
}
static bool pt_is_punctuation(spt_t *pt)
{
spt_t rp;
coord_t coord;
char *ch = NULL;
spt_get_coord(pt, &coord);
coord.column += 1;
sheet_get_cell_pt(doc.sh, &coord, dir_after, &rp);
ch = range_get_str(pt, &rp);
if (ch == NULL)
return false;
char32_t first_char = get_first_wchar(ch);
switch (first_char) {
case ',':
case '.':
case ';':
case ':':
case '/':
case '?':
case '\\':
case '|':
case '_':
case '+':
case '-':
case '*':
case '=':
case '<':
case '>':
return true;
default:
return false;
}
}
static int tag_cmp(tag_t const *a, tag_t const *b)
{
spt_t pa, pb;
tag_get_pt(a, &pa);
tag_get_pt(b, &pb);
return spt_cmp(&pa, &pb);
}
static int spt_cmp(spt_t const *a, spt_t const *b)
{
coord_t ca, cb;
spt_get_coord(a, &ca);
spt_get_coord(b, &cb);
return coord_cmp(&ca, &cb);
}
static int coord_cmp(coord_t const *a, coord_t const *b)
{
if (a->row - b->row != 0)
return a->row - b->row;
return a->column - b->column;
}
static void status_display(char const *str)
{
console_set_pos(con, 0, scr_rows - 1);
console_set_style(con, STYLE_INVERTED);
int pos = -(scr_columns - 3);
printf(" %*s ", pos, str);
console_flush(con);
console_set_style(con, STYLE_NORMAL);
pane.rflags |= REDRAW_CARET;
}
HelenOS homepage, sources at GitHub