Merge branch 'scrollback-search-extend-to-end-of-word'

This commit is contained in:
Daniel Eklöf 2019-12-03 20:01:53 +01:00
commit ba399fa874
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
7 changed files with 228 additions and 110 deletions

View file

@ -118,6 +118,17 @@ available:
Search _forward_ for next match
* <kbd>ctrl</kbd>+<kbd>w</kbd>
Extend current selection (and thus the search criteria) to the end
of the word, or the next word if currently at a word separating
character.
* <kbd>ctrl</kbd>+<kbd>shift</kbd><kbd>w</kbd>
Same as <kbd>ctrl</kbd>+<kbd>w</kbd>, except that the only word
separating characters are whitespace characters.
* <kbd>escape</kbd>, <kbd>ctrl</kbd>+<kbd>g</kbd>
Cancel the search

View file

@ -75,6 +75,7 @@ executable(
'input.c', 'input.h',
'log.c', 'log.h',
'main.c',
'misc.c', 'misc.h',
'osc.c', 'osc.h',
'render.c', 'render.h',
'search.c', 'search.h',

24
misc.c Normal file
View file

@ -0,0 +1,24 @@
#include "misc.h"
#include <wctype.h>
bool
isword(wchar_t wc, bool spaces_only)
{
if (spaces_only)
return iswgraph(wc);
switch (wc) {
default: return iswgraph(wc);
case L'(': case L')':
case L'[': case L']':
case L'{': case L'}':
case L'<': case L'>':
case L'': case L'|':
case L',':
case L'`': case L'"': case L'\'':
case L':':
return false;
}
}

6
misc.h Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include <stdbool.h>
#include <wchar.h>
bool isword(wchar_t wc, bool spaces_only);

View file

@ -795,7 +795,8 @@ render_search_box(struct terminal *term)
wl_subsurface_set_position(
term->window->search_sub_surface,
term->width - width - margin, term->height - height - margin);
max(0, term->width - width - margin),
max(0, term->height - height - margin));
wl_surface_damage_buffer(term->window->search_surface, 0, 0, width, height);
wl_surface_attach(term->window->search_surface, buf->wl_buf, 0, 0);

271
search.c
View file

@ -11,12 +11,32 @@
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "grid.h"
#include "misc.h"
#include "render.h"
#include "selection.h"
#include "shm.h"
#define max(x, y) ((x) > (y) ? (x) : (y))
static bool
search_ensure_size(struct terminal *term, size_t wanted_size)
{
while (wanted_size >= term->search.sz) {
size_t new_sz = term->search.sz == 0 ? 64 : term->search.sz * 2;
wchar_t *new_buf = realloc(term->search.buf, new_sz * sizeof(term->search.buf[0]));
if (new_buf == NULL) {
LOG_ERRNO("failed to resize search buffer");
return false;
}
term->search.buf = new_buf;
term->search.sz = new_sz;
}
return true;
}
static void
search_cancel_keep_selection(struct terminal *term)
{
@ -64,7 +84,75 @@ search_cancel(struct terminal *term)
}
static void
search_update(struct terminal *term)
search_update_selection(struct terminal *term,
int start_row, int start_col,
int end_row, int end_col)
{
int old_view = term->grid->view;
int new_view = start_row;
/* Prevent scrolling in uninitialized rows */
bool all_initialized = false;
do {
all_initialized = true;
for (int i = 0; i < term->rows; i++) {
int row_no = (new_view + i) % term->grid->num_rows;
if (term->grid->rows[row_no] == NULL) {
all_initialized = false;
new_view--;
break;
}
}
} while (!all_initialized);
/* Don't scroll past scrollback history */
int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows;
if (end >= term->grid->offset) {
/* Not wrapped */
if (new_view >= term->grid->offset && new_view <= end)
new_view = term->grid->offset;
} else {
if (new_view >= term->grid->offset || new_view <= end)
new_view = term->grid->offset;
}
/* Update view */
term->grid->view = new_view;
if (new_view != old_view)
term_damage_view(term);
/* Selection endpoint is inclusive */
assert(end_col > 0);
end_col--;
/* Begin a new selection if the start coords changed */
if (start_row != term->search.match.row ||
start_col != term->search.match.col)
{
int selection_row = start_row - term->grid->view;
while (selection_row < 0)
selection_row += term->grid->num_rows;
assert(selection_row >= 0 &&
selection_row < term->grid->num_rows);
selection_start(term, start_col, selection_row);
}
/* Update selection endpoint */
{
int selection_row = end_row - term->grid->view;
while (selection_row < 0)
selection_row += term->grid->num_rows;
assert(selection_row >= 0 &&
selection_row < term->grid->num_rows);
selection_update(term, end_col, selection_row);
}
}
static void
search_find_next(struct terminal *term)
{
bool backward = term->search.direction == SEARCH_BACKWARD;
term->search.direction = SEARCH_BACKWARD;
@ -106,14 +194,14 @@ search_update(struct terminal *term)
r < term->grid->num_rows;
backward ? ROW_DEC(start_row) : ROW_INC(start_row), r++)
{
const struct row *row = term->grid->rows[start_row];
if (row == NULL)
continue;
for (;
backward ? start_col >= 0 : start_col < term->cols;
backward ? start_col-- : start_col++)
{
const struct row *row = term->grid->rows[start_row];
if (row == NULL)
continue;
if (wcsncasecmp(&row->cells[start_col].wc, term->search.buf, 1) != 0)
continue;
@ -129,10 +217,7 @@ search_update(struct terminal *term)
size_t match_len = 0;
for (size_t i = 0; i < term->search.len; i++, match_len++) {
if (wcsncasecmp(&row->cells[end_col].wc, &term->search.buf[i], 1) != 0)
break;
if (++end_col >= term->cols) {
if (end_col >= term->cols) {
if (end_row + 1 > grid_row_absolute(term->grid, term->grid->offset + term->rows - 1)) {
/* Don't continue past end of the world */
break;
@ -142,6 +227,11 @@ search_update(struct terminal *term)
end_col = 0;
row = term->grid->rows[end_row];
}
if (wcsncasecmp(&row->cells[end_col].wc, &term->search.buf[i], 1) != 0)
break;
end_col++;
}
if (match_len != term->search.len) {
@ -153,70 +243,7 @@ search_update(struct terminal *term)
* We matched the entire buffer. Move view to ensure the
* match is visible, create a selection and return.
*/
int old_view = term->grid->view;
int new_view = start_row;
/* Prevent scrolling in uninitialized rows */
bool all_initialized = false;
do {
all_initialized = true;
for (int i = 0; i < term->rows; i++) {
int row_no = (new_view + i) % term->grid->num_rows;
if (term->grid->rows[row_no] == NULL) {
all_initialized = false;
new_view--;
break;
}
}
} while (!all_initialized);
/* Don't scroll past scrollback history */
int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows;
if (end >= term->grid->offset) {
/* Not wrapped */
if (new_view >= term->grid->offset && new_view <= end)
new_view = term->grid->offset;
} else {
if (new_view >= term->grid->offset || new_view <= end)
new_view = term->grid->offset;
}
/* Update view */
term->grid->view = new_view;
if (new_view != old_view)
term_damage_view(term);
/* Selection endpoint is inclusive */
if (--end_col < 0) {
end_col = term->cols - 1;
start_row--;
}
/* Begin a new selection if the start coords changed */
if (start_row != term->search.match.row ||
start_col != term->search.match.col)
{
int selection_row = start_row - term->grid->view;
while (selection_row < 0)
selection_row += term->grid->num_rows;
assert(selection_row >= 0 &&
selection_row < term->grid->num_rows);
selection_start(term, start_col, selection_row);
}
/* Update selection endpoint */
{
int selection_row = end_row - term->grid->view;
while (selection_row < 0)
selection_row += term->grid->num_rows;
assert(selection_row >= 0 &&
selection_row < term->grid->num_rows);
selection_update(term, end_col, selection_row);
}
search_update_selection(term, start_row, start_col, end_row, end_col);
/* Update match state */
term->search.match.row = start_row;
@ -237,6 +264,77 @@ search_update(struct terminal *term)
#undef ROW_DEC
}
static void
search_match_to_end_of_word(struct terminal *term, bool spaces_only)
{
if (term->search.match_len == 0)
return;
assert(term->search.match.row != -1);
assert(term->search.match.col != -1);
int end_row = term->search.match.row;
int end_col = term->search.match.col;
size_t len = term->search.match_len;
/* Calculate end coord - note: assumed to be valid */
for (size_t i = 0; i < len; i++) {
if (++end_col >= term->cols)
end_row = (end_row + 1) % term->grid->num_rows;
}
tll(wchar_t) new_chars = tll_init();
/* Always append at least one character *if* possible */
bool first = true;
for (size_t r = 0;
r < term->grid->num_rows;
end_row = (end_row + 1) % term->grid->num_rows, r++)
{
const struct row *row = term->grid->rows[end_row];
if (row == NULL)
break;
bool done = false;
for (; end_col < term->cols; end_col++) {
wchar_t wc = row->cells[end_col].wc;
if (wc == 0 || (!first && !isword(wc, spaces_only))) {
done = true;
break;
}
first = false;
tll_push_back(new_chars, wc);
}
if (done)
break;
}
if (tll_length(new_chars) == 0)
return;
if (!search_ensure_size(term, term->search.len + tll_length(new_chars)))
return;
/* Keep cursor at the end, but don't move it if not */
bool move_cursor = term->search.cursor == term->search.len;
/* Append newly found characters to the search buffer */
tll_foreach(new_chars, it)
term->search.buf[term->search.len++] = it->item;
term->search.buf[term->search.len] = L'\0';
if (move_cursor)
term->search.cursor += tll_length(new_chars);
tll_free(new_chars);
search_update_selection(
term, term->search.match.row, term->search.match.col, end_row, end_col);
}
static size_t
distance_next_word(const struct terminal *term)
{
@ -257,7 +355,6 @@ distance_next_word(const struct terminal *term)
break;
}
LOG_INFO("cursor = %zu, iswspace() = %d", cursor, iswspace(term->search.buf[cursor - 1]));
assert(cursor == term->search.len || !iswspace(term->search.buf[cursor - 1]));
if (cursor < term->search.len && !iswspace(term->search.buf[cursor]))
@ -299,7 +396,7 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask
const xkb_mod_mask_t ctrl = 1 << term->wl->kbd.mod_ctrl;
const xkb_mod_mask_t alt = 1 << term->wl->kbd.mod_alt;
//const xkb_mod_mask_t shift = 1 << term->wl->kbd.mod_shift;
const xkb_mod_mask_t shift = 1 << term->wl->kbd.mod_shift;
//const xkb_mod_mask_t meta = 1 << term->wl->kbd.mod_meta;
enum xkb_compose_status compose_status = xkb_compose_state_get_status(
@ -446,6 +543,12 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask
}
}
else if (mods == ctrl && sym == XKB_KEY_w)
search_match_to_end_of_word(term, false);
else if (mods == (ctrl | shift) && sym == XKB_KEY_W)
search_match_to_end_of_word(term, true);
else {
uint8_t buf[64] = {0};
int count = 0;
@ -454,6 +557,8 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask
count = xkb_compose_state_get_utf8(
term->wl->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
xkb_compose_state_reset(term->wl->kbd.xkb_compose_state);
} else if (compose_status == XKB_COMPOSE_CANCELLED) {
count = 0;
} else {
count = xkb_state_key_get_utf8(
term->wl->kbd.xkb_state, key, (char *)buf, sizeof(buf));
@ -468,18 +573,8 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask
return;
}
while (term->search.len + wchars >= term->search.sz) {
size_t new_sz = term->search.sz == 0 ? 64 : term->search.sz * 2;
wchar_t *new_buf = realloc(term->search.buf, new_sz * sizeof(term->search.buf[0]));
if (new_buf == NULL) {
LOG_ERRNO("failed to resize search buffer");
return;
}
term->search.buf = new_buf;
term->search.sz = new_sz;
}
if (!search_ensure_size(term, term->search.len + wchars))
return;
assert(term->search.len + wchars < term->search.sz);
@ -497,7 +592,7 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask
}
LOG_DBG("search: buffer: %S", term->search.buf);
search_update(term);
search_find_next(term);
render_refresh(term);
render_search_box(term);
}

View file

@ -15,6 +15,7 @@
#include "async.h"
#include "grid.h"
#include "misc.h"
#include "render.h"
#include "vt.h"
@ -222,27 +223,6 @@ selection_cancel(struct terminal *term)
}
}
static bool
isword(wint_t c, bool spaces_only)
{
if (spaces_only)
return !iswspace(c);
switch (c) {
default: return !iswspace(c);
case L'{': case L'}':
case L'[': case L']':
case L'(': case L')':
case L'`':
case L'\'':
case L'"':
case L',': case L'.':
case L':': case L';':
return false;
}
}
void
selection_mark_word(struct terminal *term, int col, int row, bool spaces_only,
uint32_t serial)