mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
Merge branch 'scrollback-search'
This commit is contained in:
commit
e673bd4ab0
12 changed files with 940 additions and 88 deletions
171
README.md
171
README.md
|
|
@ -2,6 +2,127 @@
|
|||
|
||||
**foot** is a fast Wayland terminal emulator.
|
||||
|
||||
## Index
|
||||
|
||||
1. [Features](#features)
|
||||
1. [Non-features](#non-features)
|
||||
1. [Fonts](#fonts)
|
||||
1. [Shortcuts](#shortcuts)
|
||||
1. [Keyboard](#keyboard)
|
||||
1. [Mouse](#mouse)
|
||||
1. [Requirements](#requirements)
|
||||
1. [Running](#running)
|
||||
1. [Building](#building)
|
||||
1. [Installing](#installing)
|
||||
1. [Arch Linux](#arch-linux)
|
||||
1. [Other](#other)
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Fast (**TODO** insert benchmark results here)
|
||||
* Wayland native
|
||||
* DE agnostic
|
||||
* User configurable font fallback
|
||||
* Scrollback search
|
||||
* Color emoji support
|
||||
|
||||
|
||||
## Non-features
|
||||
|
||||
This is a non-exhaustive list of things some people might consider
|
||||
being important features (i.e. _"must-haves"_), that are unlikely to
|
||||
ever be supported by foot.
|
||||
|
||||
* Tabs
|
||||
* Multiple windows
|
||||
|
||||
|
||||
## Fonts
|
||||
|
||||
**foot** supports all fonts that can be loaded by _freetype_,
|
||||
including **bitmap** fonts and **color emoji** fonts.
|
||||
|
||||
Foot uses _fontconfig_ to locate and configure the font(s) to
|
||||
use. Since fontconfig's fallback mechanism is imperfect, especially
|
||||
for monospace fonts (it doesn't prefer monospace fonts even though the
|
||||
requested font is one), foot allows you, the user, to configure the
|
||||
fallback fonts to use.
|
||||
|
||||
This also means you can configure _each_ fallback font individually;
|
||||
you want _that_ fallback font to use _this_ size, and you want that
|
||||
_other_ fallback font to be _italic_? No problem!
|
||||
|
||||
If a glyph cannot be found in _any_ of the user configured fallback
|
||||
fonts, _then_ fontconfig's list is used.
|
||||
|
||||
|
||||
## Shortcuts
|
||||
|
||||
At the moment, all shortcuts are hard coded and cannot be changed. It
|
||||
is **not** possible to define new key bindings.
|
||||
|
||||
|
||||
### Keyboard
|
||||
|
||||
* <kbd>shift</kbd>+<kbd>page up</kbd>/<kbd>page down</kbd>
|
||||
|
||||
Scroll up/down in history
|
||||
|
||||
* <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>c</kbd>
|
||||
|
||||
Copy selected text to the _clipboard_
|
||||
|
||||
* <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>v</kbd>
|
||||
|
||||
Paste from _clipboard_
|
||||
|
||||
* <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>r</kbd>
|
||||
|
||||
Start a scrollback search
|
||||
|
||||
While doing a scrollback search, the following shortcuts are
|
||||
available:
|
||||
|
||||
* <kbd>ctrl</kbd>+<kbd>r</kbd>
|
||||
|
||||
Search for next match
|
||||
|
||||
* <kbd>escape</kbd>
|
||||
|
||||
Cancel the search
|
||||
|
||||
* <kbd>ctrl</kbd>+<kbd>g</kbd>
|
||||
|
||||
Cancel the search (same as <kbd>escape</kbd>)
|
||||
|
||||
* <kbd>return</kbd>
|
||||
|
||||
Finish the search and put the current match to the primary selection
|
||||
|
||||
### Mouse
|
||||
|
||||
* <kbd>left</kbd> - **single-click**
|
||||
|
||||
Drag to select; when released, the selected text is copied to the
|
||||
_primary_ selection. Note that this feature is normally **disabled**
|
||||
whenever the client has enabled _mouse tracking_, but can be forced
|
||||
by holding <kbd>shift</kbd>.
|
||||
|
||||
* <kbd>left</kbd> - **double-click**
|
||||
|
||||
Selects the _word_ (separated by spaces, period, comma, parenthesis
|
||||
etc) under the pointer. Hold <kbd>ctrl</kbd> to select everything
|
||||
under the pointer up to, and until, the next space characters.
|
||||
|
||||
* <kbd>left</kbd> - **triple-click**
|
||||
|
||||
Selects the entire row
|
||||
|
||||
* <kbd>middle</kbd>
|
||||
|
||||
Paste from _primary_ selection
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
@ -25,46 +146,24 @@ In addition to the dev variant of the packages above, you need:
|
|||
* scdoc
|
||||
|
||||
|
||||
## Fonts
|
||||
## Installing
|
||||
|
||||
**foot** supports all fonts that can be loaded by freetype, including
|
||||
**bitmap** fonts and **color emoji** fonts.
|
||||
### Arch Linux
|
||||
|
||||
Foot uses its own font fallback mechanism, rather than relying on
|
||||
fontconfig's fallback. This is because fontconfig is quite bad at
|
||||
selecting fallback fonts suitable for a terminal (i.e. monospaced
|
||||
fonts).
|
||||
Use [makepkg](https://wiki.archlinux.org/index.php/Makepkg) to build
|
||||
the bundled `PKGBUILD` (just run `makepkg` in the source root
|
||||
directory)..
|
||||
|
||||
Instead, foot allows you to specify a font fallback list, where _each_
|
||||
font can be configured independently (for example, you can configure
|
||||
the size for each font individually).
|
||||
|
||||
If a glyph cannot be found in _any_ of the user configured fallback
|
||||
fonts, _then_ fontconfig's list is used.
|
||||
Note that it will do a profiling-guided build, and that this requires
|
||||
a running wayland session since it needs to run an intermediate build
|
||||
of foot.
|
||||
|
||||
|
||||
## Shortcuts
|
||||
### Other
|
||||
|
||||
At the moment, all shortcuts are hard coded and cannot be changed. It
|
||||
is **not** possible to define new key bindings.
|
||||
Foot uses _meson_. If you are unfamiliar with it, the official
|
||||
[tutorial](https://mesonbuild.com/Tutorial.html) might be a good
|
||||
starting point.
|
||||
|
||||
|
||||
### Keyboard
|
||||
|
||||
* `shift+page up/down` - scroll up/down in history
|
||||
* `ctrl+shift+c` - copy selected text to the _clipboard_
|
||||
* `ctrl+shift+v` - paste from _clipboard_
|
||||
|
||||
|
||||
### Mouse
|
||||
|
||||
* `left` - single-click: drag to select; when released, the selected
|
||||
text is copied to the _primary_ selection. Note that this feature is
|
||||
normally disabled whenever the client has enabled mouse tracking,
|
||||
but can be forced by holding `shift`.
|
||||
* `left` - double-click: selects the _word_ (separated by spaces,
|
||||
period, comma, parenthesis etc) under the pointer. Hold `ctrl` to
|
||||
select everything under the pointer up to, and until, the next space
|
||||
characters.
|
||||
* `left` - triple-click: selects the entire row
|
||||
* `middle` - paste from _primary_ selection
|
||||
I also recommend taking a look at that bundled Arch `PKGBUILD` file,
|
||||
to see how it builds foot.
|
||||
|
|
|
|||
16
grid.h
16
grid.h
|
|
@ -7,12 +7,24 @@ void grid_swap_row(struct grid *grid, int row_a, int row_b, bool initialize);
|
|||
struct row *grid_row_alloc(int cols, bool initialize);
|
||||
void grid_row_free(struct row *row);
|
||||
|
||||
static inline int
|
||||
grid_row_absolute(const struct grid *grid, int row_no)
|
||||
{
|
||||
return (grid->offset + row_no) & (grid->num_rows - 1);
|
||||
}
|
||||
|
||||
static inline int
|
||||
grid_row_absolute_in_view(const struct grid *grid, int row_no)
|
||||
{
|
||||
return (grid->view + row_no) & (grid->num_rows - 1);
|
||||
}
|
||||
|
||||
static inline struct row *
|
||||
_grid_row_maybe_alloc(struct grid *grid, int row_no, bool alloc_if_null)
|
||||
{
|
||||
assert(grid->offset >= 0);
|
||||
|
||||
int real_row = (grid->offset + row_no) & (grid->num_rows - 1);
|
||||
int real_row = grid_row_absolute(grid, row_no);
|
||||
struct row *row = grid->rows[real_row];
|
||||
|
||||
if (row == NULL && alloc_if_null) {
|
||||
|
|
@ -41,7 +53,7 @@ grid_row_in_view(struct grid *grid, int row_no)
|
|||
{
|
||||
assert(grid->view >= 0);
|
||||
|
||||
int real_row = (grid->view + row_no) & (grid->num_rows - 1);
|
||||
int real_row = grid_row_absolute_in_view(grid, row_no);
|
||||
struct row *row = grid->rows[real_row];
|
||||
|
||||
assert(row != NULL);
|
||||
|
|
|
|||
33
input.c
33
input.c
|
|
@ -18,11 +18,12 @@
|
|||
#define LOG_MODULE "input"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
#include "terminal.h"
|
||||
#include "render.h"
|
||||
#include "keymap.h"
|
||||
#include "commands.h"
|
||||
#include "keymap.h"
|
||||
#include "render.h"
|
||||
#include "search.h"
|
||||
#include "selection.h"
|
||||
#include "terminal.h"
|
||||
#include "vt.h"
|
||||
|
||||
static void
|
||||
|
|
@ -178,6 +179,12 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
xkb_mod_mask_t significant = ctrl | alt | shift | meta;
|
||||
xkb_mod_mask_t effective_mods = mods & ~consumed & significant;
|
||||
|
||||
if (term->is_searching) {
|
||||
start_repeater(term, key - 8);
|
||||
search_input(term, key, sym, effective_mods);
|
||||
return;
|
||||
}
|
||||
|
||||
#if 0
|
||||
for (size_t i = 0; i < 32; i++) {
|
||||
if (mods & (1 << i)) {
|
||||
|
|
@ -218,6 +225,12 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
|
||||
else if (sym == XKB_KEY_V) {
|
||||
selection_from_clipboard(term, serial);
|
||||
term_reset_view(term);
|
||||
found_map = true;
|
||||
}
|
||||
|
||||
else if (sym == XKB_KEY_R) {
|
||||
search_begin(term);
|
||||
found_map = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -243,11 +256,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
vt_to_slave(term, info->seq, strlen(info->seq));
|
||||
found_map = true;
|
||||
|
||||
if (term->grid->view != term->grid->offset) {
|
||||
term->grid->view = term->grid->offset;
|
||||
term_damage_all(term);
|
||||
}
|
||||
|
||||
term_reset_view(term);
|
||||
selection_cancel(term);
|
||||
break;
|
||||
}
|
||||
|
|
@ -308,13 +317,9 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
|
|||
vt_to_slave(term, "\x1b", 1);
|
||||
|
||||
vt_to_slave(term, buf, count);
|
||||
|
||||
if (term->grid->view != term->grid->offset) {
|
||||
term->grid->view = term->grid->offset;
|
||||
term_damage_all(term);
|
||||
}
|
||||
}
|
||||
|
||||
term_reset_view(term);
|
||||
selection_cancel(term);
|
||||
}
|
||||
}
|
||||
|
|
@ -436,6 +441,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
|
|||
|
||||
struct terminal *term = data;
|
||||
|
||||
search_cancel(term);
|
||||
|
||||
switch (state) {
|
||||
case WL_POINTER_BUTTON_STATE_PRESSED: {
|
||||
/* Time since last click */
|
||||
|
|
|
|||
122
main.c
122
main.c
|
|
@ -14,6 +14,7 @@
|
|||
#include <sys/sysinfo.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <freetype/tttables.h>
|
||||
#include <wayland-client.h>
|
||||
|
|
@ -22,6 +23,7 @@
|
|||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
|
||||
#include <xdg-output-unstable-v1.h>
|
||||
#include <xdg-decoration-unstable-v1.h>
|
||||
|
||||
#define LOG_MODULE "main"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
|
|
@ -129,8 +131,10 @@ output_scale(void *data, struct wl_output *wl_output, int32_t factor)
|
|||
{
|
||||
struct monitor *mon = data;
|
||||
mon->scale = factor;
|
||||
|
||||
int old_scale = mon->term->scale >= 1 ? mon->term->scale : 1;
|
||||
render_reload_cursor_theme(mon->term);
|
||||
render_resize(mon->term, mon->term->width, mon->term->height);
|
||||
render_resize(mon->term, mon->term->width / old_scale, mon->term->height / old_scale);
|
||||
}
|
||||
|
||||
static const struct wl_output_listener output_listener = {
|
||||
|
|
@ -197,6 +201,11 @@ handle_global(void *data, struct wl_registry *registry,
|
|||
term->wl.registry, name, &wl_compositor_interface, 4);
|
||||
}
|
||||
|
||||
else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
|
||||
term->wl.sub_compositor = wl_registry_bind(
|
||||
term->wl.registry, name, &wl_subcompositor_interface, 1);
|
||||
}
|
||||
|
||||
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
term->wl.shm = wl_registry_bind(
|
||||
term->wl.registry, name, &wl_shm_interface, 1);
|
||||
|
|
@ -210,6 +219,10 @@ handle_global(void *data, struct wl_registry *registry,
|
|||
xdg_wm_base_add_listener(term->wl.shell, &xdg_wm_base_listener, term);
|
||||
}
|
||||
|
||||
else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0)
|
||||
term->wl.xdg_decoration_manager = wl_registry_bind(
|
||||
term->wl.registry, name, &zxdg_decoration_manager_v1_interface, 1);
|
||||
|
||||
else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
term->wl.seat = wl_registry_bind(
|
||||
term->wl.registry, name, &wl_seat_interface, 5);
|
||||
|
|
@ -261,8 +274,9 @@ surface_enter(void *data, struct wl_surface *wl_surface,
|
|||
tll_push_back(term->wl.on_outputs, &it->item);
|
||||
|
||||
/* Resize, since scale-to-use may have changed */
|
||||
int scale = term->scale >= 1 ? term->scale : 1;
|
||||
render_reload_cursor_theme(term);
|
||||
render_resize(term, term->width, term->height);
|
||||
render_resize(term, term->width / scale, term->height / scale);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -283,8 +297,9 @@ surface_leave(void *data, struct wl_surface *wl_surface,
|
|||
tll_remove(term->wl.on_outputs, it);
|
||||
|
||||
/* Resize, since scale-to-use may have changed */
|
||||
int scale = term->scale >= 1 ? term->scale : 1;
|
||||
render_reload_cursor_theme(term);
|
||||
render_resize(term, term->width, term->height);
|
||||
render_resize(term, term->width / scale, term->height / scale);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +350,30 @@ static const struct xdg_surface_listener xdg_surface_listener = {
|
|||
.configure = &xdg_surface_configure,
|
||||
};
|
||||
|
||||
static void
|
||||
xdg_toplevel_decoration_configure(void *data,
|
||||
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
|
||||
uint32_t mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
|
||||
LOG_ERR("unimplemented: client-side decorations");
|
||||
break;
|
||||
|
||||
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
|
||||
LOG_DBG("using server-side decorations");
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = {
|
||||
.configure = &xdg_toplevel_decoration_configure,
|
||||
};
|
||||
|
||||
static void
|
||||
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
|
||||
{
|
||||
|
|
@ -630,7 +669,7 @@ main(int argc, char *const *argv)
|
|||
|
||||
term.cell_width = (int)ceil(term.fextents.max_x_advance);
|
||||
term.cell_height = (int)ceil(term.fextents.height);
|
||||
LOG_DBG("cell width=%d, height=%d", term.cell_width, term.cell_height);
|
||||
LOG_INFO("cell width=%d, height=%d", term.cell_width, term.cell_height);
|
||||
|
||||
term.wl.display = wl_display_connect(NULL);
|
||||
if (term.wl.display == NULL) {
|
||||
|
|
@ -718,6 +757,7 @@ main(int argc, char *const *argv)
|
|||
goto out;
|
||||
}
|
||||
|
||||
/* Main window */
|
||||
term.wl.surface = wl_compositor_create_surface(term.wl.compositor);
|
||||
if (term.wl.surface == NULL) {
|
||||
LOG_ERR("failed to create wayland surface");
|
||||
|
|
@ -735,6 +775,20 @@ main(int argc, char *const *argv)
|
|||
xdg_toplevel_set_app_id(term.wl.xdg_toplevel, "foot");
|
||||
term_set_window_title(&term, "foot");
|
||||
|
||||
/* Request server-side decorations */
|
||||
term.wl.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||
term.wl.xdg_decoration_manager, term.wl.xdg_toplevel);
|
||||
zxdg_toplevel_decoration_v1_set_mode(
|
||||
term.wl.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
zxdg_toplevel_decoration_v1_add_listener(
|
||||
term.wl.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &term);
|
||||
|
||||
/* Scrollback search box */
|
||||
term.wl.search_surface = wl_compositor_create_surface(term.wl.compositor);
|
||||
term.wl.search_sub_surface = wl_subcompositor_get_subsurface(
|
||||
term.wl.sub_compositor, term.wl.search_surface, term.wl.surface);
|
||||
wl_subsurface_set_desync(term.wl.search_sub_surface);
|
||||
|
||||
wl_surface_commit(term.wl.surface);
|
||||
wl_display_roundtrip(term.wl.display);
|
||||
|
||||
|
|
@ -835,8 +889,9 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
}
|
||||
|
||||
bool timeout_is_armed = false;
|
||||
int timeout_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||
|
||||
int timeout_ms = -1;
|
||||
while (true) {
|
||||
struct pollfd fds[] = {
|
||||
{.fd = wl_display_get_fd(term.wl.display), .events = POLLIN},
|
||||
|
|
@ -844,10 +899,11 @@ main(int argc, char *const *argv)
|
|||
{.fd = term.kbd.repeat.fd, .events = POLLIN},
|
||||
{.fd = term.flash.fd, .events = POLLIN},
|
||||
{.fd = term.blink.fd, .events = POLLIN},
|
||||
{.fd = timeout_fd, .events = POLLIN},
|
||||
};
|
||||
|
||||
wl_display_flush(term.wl.display);
|
||||
int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout_ms);
|
||||
int pret = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||
|
||||
if (pret == -1) {
|
||||
if (errno == EINTR)
|
||||
|
|
@ -857,13 +913,19 @@ main(int argc, char *const *argv)
|
|||
break;
|
||||
}
|
||||
|
||||
if (pret == 0 || (timeout_ms != -1 && !(fds[1].revents & POLLIN))) {
|
||||
/* Delayed rendering */
|
||||
render_refresh(&term);
|
||||
}
|
||||
if (fds[5].revents & POLLIN) {
|
||||
assert(timeout_is_armed);
|
||||
|
||||
/* Reset poll timeout to infinity */
|
||||
timeout_ms = -1;
|
||||
uint64_t unused;
|
||||
ssize_t ret = read(timeout_fd, &unused, sizeof(unused));
|
||||
|
||||
if (ret < 0 && errno != EAGAIN)
|
||||
LOG_ERRNO("failed to read timeout timer");
|
||||
else if (ret > 0) {
|
||||
timeout_is_armed = false;
|
||||
render_refresh(&term);
|
||||
}
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
wl_display_dispatch(term.wl.display);
|
||||
|
|
@ -915,7 +977,10 @@ main(int argc, char *const *argv)
|
|||
* ourselves we just received keyboard input, and in
|
||||
* this case *not* delay rendering?
|
||||
*/
|
||||
timeout_ms = 1;
|
||||
if (!timeout_is_armed) {
|
||||
timerfd_settime(timeout_fd, 0, &(struct itimerspec){.it_value = {.tv_nsec = 1000000}}, NULL);
|
||||
timeout_is_armed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -986,9 +1051,10 @@ main(int argc, char *const *argv)
|
|||
render_refresh(&term);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
close(timeout_fd);
|
||||
|
||||
out:
|
||||
mtx_lock(&term.render.workers.lock);
|
||||
assert(tll_length(term.render.workers.queue) == 0);
|
||||
|
|
@ -1014,12 +1080,6 @@ out:
|
|||
if (term.wl.xdg_output_manager != NULL)
|
||||
zxdg_output_manager_v1_destroy(term.wl.xdg_output_manager);
|
||||
|
||||
if (term.render.frame_callback != NULL)
|
||||
wl_callback_destroy(term.render.frame_callback);
|
||||
if (term.wl.xdg_toplevel != NULL)
|
||||
xdg_toplevel_destroy(term.wl.xdg_toplevel);
|
||||
if (term.wl.xdg_surface != NULL)
|
||||
xdg_surface_destroy(term.wl.xdg_surface);
|
||||
free(term.wl.pointer.theme_name);
|
||||
if (term.wl.pointer.theme != NULL)
|
||||
wl_cursor_theme_destroy(term.wl.pointer.theme);
|
||||
|
|
@ -1049,12 +1109,28 @@ out:
|
|||
zwp_primary_selection_device_manager_v1_destroy(term.wl.primary_selection_device_manager);
|
||||
if (term.wl.seat != NULL)
|
||||
wl_seat_destroy(term.wl.seat);
|
||||
if (term.wl.surface != NULL)
|
||||
wl_surface_destroy(term.wl.surface);
|
||||
if (term.wl.search_sub_surface != NULL)
|
||||
wl_subsurface_destroy(term.wl.search_sub_surface);
|
||||
if (term.wl.search_surface != NULL)
|
||||
wl_surface_destroy(term.wl.search_surface);
|
||||
if (term.render.frame_callback != NULL)
|
||||
wl_callback_destroy(term.render.frame_callback);
|
||||
if (term.wl.xdg_toplevel_decoration != NULL)
|
||||
zxdg_toplevel_decoration_v1_destroy(term.wl.xdg_toplevel_decoration);
|
||||
if (term.wl.xdg_decoration_manager != NULL)
|
||||
zxdg_decoration_manager_v1_destroy(term.wl.xdg_decoration_manager);
|
||||
if (term.wl.xdg_toplevel != NULL)
|
||||
xdg_toplevel_destroy(term.wl.xdg_toplevel);
|
||||
if (term.wl.xdg_surface != NULL)
|
||||
xdg_surface_destroy(term.wl.xdg_surface);
|
||||
if (term.wl.shell != NULL)
|
||||
xdg_wm_base_destroy(term.wl.shell);
|
||||
if (term.wl.surface != NULL)
|
||||
wl_surface_destroy(term.wl.surface);
|
||||
if (term.wl.shm != NULL)
|
||||
wl_shm_destroy(term.wl.shm);
|
||||
if (term.wl.sub_compositor != NULL)
|
||||
wl_subcompositor_destroy(term.wl.sub_compositor);
|
||||
if (term.wl.compositor != NULL)
|
||||
wl_compositor_destroy(term.wl.compositor);
|
||||
if (term.wl.registry != NULL)
|
||||
|
|
@ -1086,6 +1162,8 @@ out:
|
|||
for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++)
|
||||
font_destroy(&term.fonts[i]);
|
||||
|
||||
free(term.search.buf);
|
||||
|
||||
if (term.flash.fd != -1)
|
||||
close(term.flash.fd);
|
||||
if (term.blink.fd != -1)
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ wl_proto_headers = []
|
|||
wl_proto_src = []
|
||||
foreach prot : [
|
||||
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
|
||||
wayland_protocols_datadir + '/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
|
||||
wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml',
|
||||
wayland_protocols_datadir + '/unstable/primary-selection/primary-selection-unstable-v1.xml',
|
||||
]
|
||||
|
|
@ -90,6 +91,7 @@ executable(
|
|||
'main.c',
|
||||
'osc.c', 'osc.h',
|
||||
'render.c', 'render.h',
|
||||
'search.c', 'search.h',
|
||||
'selection.c', 'selection.h',
|
||||
'shm.c', 'shm.h',
|
||||
'slave.c', 'slave.h',
|
||||
|
|
|
|||
127
render.c
127
render.c
|
|
@ -72,14 +72,44 @@ pixman_color_dim(pixman_color_t *color)
|
|||
color->blue /= 2;
|
||||
}
|
||||
|
||||
static inline void
|
||||
pixman_color_dim_for_search(pixman_color_t *color)
|
||||
{
|
||||
color->red /= 3;
|
||||
color->green /= 3;
|
||||
color->blue /= 3;
|
||||
}
|
||||
|
||||
static inline int
|
||||
font_baseline(const struct terminal *term)
|
||||
{
|
||||
assert(term->fextents.ascent >= 0);
|
||||
assert(term->fextents.descent >= 0);
|
||||
|
||||
int diff = term->fextents.height - (term->fextents.ascent + term->fextents.descent);
|
||||
assert(diff >= 0);
|
||||
|
||||
#if 0
|
||||
LOG_INFO("height=%d, ascent=%d, descent=%d, diff=%d",
|
||||
term->fextents.height,
|
||||
term->fextents.ascent, term->fextents.descent,
|
||||
diff);
|
||||
#endif
|
||||
|
||||
return term->fextents.height - diff / 2 - term->fextents.descent;
|
||||
}
|
||||
|
||||
static void
|
||||
draw_bar(const struct terminal *term, pixman_image_t *pix,
|
||||
const struct font *font,
|
||||
const pixman_color_t *color, int x, int y)
|
||||
{
|
||||
/* TODO: investigate if this is the best way */
|
||||
int baseline = y + font_baseline(term) - term->fextents.ascent;
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_SRC, pix, color,
|
||||
1, &(pixman_rectangle16_t){x, y, 1, term->cell_height});
|
||||
1, &(pixman_rectangle16_t){
|
||||
x, baseline,
|
||||
font->underline.thickness, term->fextents.ascent + term->fextents.descent});
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -87,7 +117,7 @@ draw_underline(const struct terminal *term, pixman_image_t *pix,
|
|||
const struct font *font,
|
||||
const pixman_color_t *color, int x, int y, int cols)
|
||||
{
|
||||
int baseline = y + term->fextents.height - term->fextents.descent;
|
||||
int baseline = y + font_baseline(term);
|
||||
int width = font->underline.thickness;
|
||||
int y_under = baseline - font->underline.position - width / 2;
|
||||
|
||||
|
|
@ -101,7 +131,7 @@ draw_strikeout(const struct terminal *term, pixman_image_t *pix,
|
|||
const struct font *font,
|
||||
const pixman_color_t *color, int x, int y, int cols)
|
||||
{
|
||||
int baseline = y + term->fextents.height - term->fextents.descent;
|
||||
int baseline = y + font_baseline(term);
|
||||
int width = font->strikeout.thickness;
|
||||
int y_strike = baseline - font->strikeout.position - width / 2;
|
||||
|
||||
|
|
@ -204,6 +234,11 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
|||
bg = color_hex_to_pixman(term->cursor_color.cursor);
|
||||
}
|
||||
|
||||
if (term->is_searching && !is_selected) {
|
||||
pixman_color_dim_for_search(&fg);
|
||||
pixman_color_dim_for_search(&bg);
|
||||
}
|
||||
|
||||
struct font *font = attrs_to_font(term, &cell->attrs);
|
||||
const struct glyph *glyph = font_glyph_for_wc(font, cell->wc);
|
||||
|
||||
|
|
@ -216,12 +251,16 @@ render_cell(struct terminal *term, pixman_image_t *pix,
|
|||
|
||||
/* Non-block cursors */
|
||||
if (has_cursor && !block_cursor) {
|
||||
pixman_color_t cursor_color = term->cursor_color.text >> 31
|
||||
? color_hex_to_pixman(term->cursor_color.cursor)
|
||||
: fg;
|
||||
pixman_color_t cursor_color;
|
||||
if (term->cursor_color.text >> 31) {
|
||||
cursor_color = color_hex_to_pixman(term->cursor_color.cursor);
|
||||
if (term->is_searching)
|
||||
pixman_color_dim(&cursor_color);
|
||||
} else
|
||||
cursor_color = fg;
|
||||
|
||||
if (term->cursor_style == CURSOR_BAR)
|
||||
draw_bar(term, pix, &cursor_color, x, y);
|
||||
draw_bar(term, pix, font, &cursor_color, x, y);
|
||||
else if (term->cursor_style == CURSOR_UNDERLINE)
|
||||
draw_underline(
|
||||
term, pix, attrs_to_font(term, &cell->attrs), &cursor_color,
|
||||
|
|
@ -435,9 +474,10 @@ grid_render(struct terminal *term)
|
|||
term_damage_view(term);
|
||||
|
||||
/* If we resized the window, or is flashing, or just stopped flashing */
|
||||
if (term->render.last_buf != buf || term->flash.active || term->render.was_flashing) {
|
||||
LOG_DBG("new buffer");
|
||||
|
||||
if (term->render.last_buf != buf ||
|
||||
term->flash.active || term->render.was_flashing ||
|
||||
term->is_searching != term->render.was_searching)
|
||||
{
|
||||
/* Fill area outside the cell grid with the default background color */
|
||||
int rmargin = term->x_margin + term->cols * term->cell_width;
|
||||
int bmargin = term->y_margin + term->rows * term->cell_height;
|
||||
|
|
@ -445,8 +485,10 @@ grid_render(struct terminal *term)
|
|||
int bmargin_height = term->height - bmargin;
|
||||
|
||||
uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
|
||||
|
||||
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha);
|
||||
if (term->is_searching)
|
||||
pixman_color_dim(&bg);
|
||||
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_SRC, pix, &bg, 4,
|
||||
(pixman_rectangle16_t[]){
|
||||
|
|
@ -469,6 +511,7 @@ grid_render(struct terminal *term)
|
|||
|
||||
term->render.last_buf = buf;
|
||||
term->render.was_flashing = term->flash.active;
|
||||
term->render.was_searching = term->is_searching;
|
||||
}
|
||||
|
||||
tll_foreach(term->grid->scroll_damage, it) {
|
||||
|
|
@ -626,6 +669,7 @@ grid_render(struct terminal *term)
|
|||
|
||||
if (term->flash.active) {
|
||||
/* Note: alpha is pre-computed in each color component */
|
||||
/* TODO: dim while searching */
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_OVER, pix,
|
||||
&(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff},
|
||||
|
|
@ -669,6 +713,65 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
|
|||
grid_render(term);
|
||||
}
|
||||
|
||||
void
|
||||
render_search_box(struct terminal *term)
|
||||
{
|
||||
assert(term->wl.search_sub_surface != NULL);
|
||||
|
||||
/* TODO: at least sway allows the subsurface to extend outside the
|
||||
* main window. Do we want that? */
|
||||
const int scale = term->scale >= 1 ? term->scale : 1;
|
||||
const int margin = scale * 3;
|
||||
const int width = 2 * margin + max(20, term->search.len) * term->cell_width;
|
||||
const int height = 2 * margin + 1 * term->cell_height;
|
||||
|
||||
struct buffer *buf = shm_get_buffer(term->wl.shm, width, height, 1);
|
||||
|
||||
/* Background - yellow on empty/match, red on mismatch */
|
||||
pixman_color_t color = color_hex_to_pixman(
|
||||
term->search.match_len == term->search.len ? 0xffff00 : 0xff0000);
|
||||
|
||||
pixman_image_fill_rectangles(
|
||||
PIXMAN_OP_SRC, buf->pix, &color,
|
||||
1, &(pixman_rectangle16_t){0, 0, width, height});
|
||||
|
||||
struct font *font = &term->fonts[0];
|
||||
int x = margin;
|
||||
int y = margin;
|
||||
pixman_color_t fg = color_hex_to_pixman(0x000000);
|
||||
|
||||
/* Text (what the user entered - *not* match(es)) */
|
||||
for (size_t i = 0; i < term->search.len; i++) {
|
||||
if (i == term->search.cursor)
|
||||
draw_bar(term, buf->pix, font, &fg, x, y);
|
||||
|
||||
const struct glyph *glyph = font_glyph_for_wc(font, term->search.buf[i]);
|
||||
if (glyph == NULL)
|
||||
continue;
|
||||
|
||||
pixman_image_t *src = pixman_image_create_solid_fill(&fg);
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_OVER, src, glyph->pix, buf->pix, 0, 0, 0, 0,
|
||||
x + glyph->x, y + term->fextents.ascent - glyph->y,
|
||||
glyph->width, glyph->height);
|
||||
pixman_image_unref(src);
|
||||
|
||||
x += term->cell_width;
|
||||
}
|
||||
|
||||
if (term->search.cursor >= term->search.len)
|
||||
draw_bar(term, buf->pix, font, &fg, x, y);
|
||||
|
||||
wl_subsurface_set_position(
|
||||
term->wl.search_sub_surface,
|
||||
term->width - width - margin, term->height - height - margin);
|
||||
|
||||
wl_surface_damage_buffer(term->wl.search_surface, 0, 0, width, height);
|
||||
wl_surface_attach(term->wl.search_surface, buf->wl_buf, 0, 0);
|
||||
wl_surface_set_buffer_scale(term->wl.search_surface, scale);
|
||||
wl_surface_commit(term->wl.search_surface);
|
||||
}
|
||||
|
||||
static void
|
||||
reflow(struct row **new_grid, int new_cols, int new_rows,
|
||||
struct row *const *old_grid, int old_cols, int old_rows)
|
||||
|
|
|
|||
1
render.h
1
render.h
|
|
@ -10,6 +10,7 @@ void render_resize(struct terminal *term, int width, int height);
|
|||
void render_set_title(struct terminal *term, const char *title);
|
||||
void render_refresh(struct terminal *term);
|
||||
|
||||
void render_search_box(struct terminal *term);
|
||||
bool render_reload_cursor_theme(struct terminal *term);
|
||||
void render_update_cursor_surface(struct terminal *term);
|
||||
|
||||
|
|
|
|||
496
search.c
Normal file
496
search.c
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
#include "search.h"
|
||||
|
||||
#include <wchar.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
|
||||
#define LOG_MODULE "search"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
#include "grid.h"
|
||||
#include "render.h"
|
||||
#include "selection.h"
|
||||
#include "shm.h"
|
||||
|
||||
#define max(x, y) ((x) > (y) ? (x) : (y))
|
||||
|
||||
static void
|
||||
search_cancel_keep_selection(struct terminal *term)
|
||||
{
|
||||
wl_surface_attach(term->wl.search_surface, NULL, 0, 0);
|
||||
wl_surface_commit(term->wl.search_surface);
|
||||
|
||||
free(term->search.buf);
|
||||
term->search.buf = NULL;
|
||||
term->search.len = 0;
|
||||
term->search.sz = 0;
|
||||
term->search.cursor = 0;
|
||||
term->search.match = (struct coord){-1, -1};
|
||||
term->search.match_len = 0;
|
||||
term->is_searching = false;
|
||||
|
||||
render_refresh(term);
|
||||
}
|
||||
|
||||
void
|
||||
search_begin(struct terminal *term)
|
||||
{
|
||||
LOG_DBG("search: begin");
|
||||
|
||||
search_cancel_keep_selection(term);
|
||||
selection_cancel(term);
|
||||
|
||||
term->search.original_view = term->grid->view;
|
||||
term->search.view_followed_offset = term->grid->view == term->grid->offset;
|
||||
term->is_searching = true;
|
||||
|
||||
render_search_box(term);
|
||||
render_refresh(term);
|
||||
}
|
||||
|
||||
void
|
||||
search_cancel(struct terminal *term)
|
||||
{
|
||||
if (!term->is_searching)
|
||||
return;
|
||||
|
||||
search_cancel_keep_selection(term);
|
||||
selection_cancel(term);
|
||||
}
|
||||
|
||||
static void
|
||||
search_update(struct terminal *term)
|
||||
{
|
||||
bool backward = term->search.direction == SEARCH_BACKWARD;
|
||||
term->search.direction = SEARCH_BACKWARD;
|
||||
|
||||
if (term->search.len == 0) {
|
||||
term->search.match = (struct coord){-1, -1};
|
||||
term->search.match_len = 0;
|
||||
selection_cancel(term);
|
||||
return;
|
||||
}
|
||||
|
||||
int start_row = term->search.match.row;
|
||||
int start_col = term->search.match.col;
|
||||
size_t len __attribute__((unused)) = term->search.match_len;
|
||||
|
||||
assert((len == 0 && start_row == -1 && start_col == -1) ||
|
||||
(len > 0 && start_row >= 0 && start_col >= 0));
|
||||
|
||||
if (len == 0) {
|
||||
if (backward) {
|
||||
start_row = grid_row_absolute_in_view(term->grid, term->rows - 1);
|
||||
start_col = term->cols - 1;
|
||||
} else {
|
||||
start_row = grid_row_absolute_in_view(term->grid, 0);
|
||||
start_col = 0;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("search: update: %s: starting at row=%d col=%d (offset = %d, view = %d)",
|
||||
backward ? "backward" : "forward", start_row, start_col,
|
||||
term->grid->offset, term->grid->view);
|
||||
|
||||
#define ROW_DEC(_r) ((_r) = ((_r) - 1 + term->grid->num_rows) % term->grid->num_rows)
|
||||
#define ROW_INC(_r) ((_r) = ((_r) + 1) % term->grid->num_rows)
|
||||
|
||||
/* Scan backward from current end-of-output */
|
||||
/* TODO: don't search "scrollback" in alt screen? */
|
||||
for (size_t r = 0;
|
||||
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++)
|
||||
{
|
||||
if (wcsncasecmp(&row->cells[start_col].wc, term->search.buf, 1) != 0)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Got a match on the first letter. Now we'll see if the
|
||||
* rest of the search buffer matches.
|
||||
*/
|
||||
|
||||
LOG_DBG("search: initial match at row=%d, col=%d", start_row, start_col);
|
||||
|
||||
int end_row = start_row;
|
||||
int end_col = start_col;
|
||||
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_row + 1 > grid_row_absolute(term->grid, term->grid->offset + term->rows - 1)) {
|
||||
/* Don't continue past end of the world */
|
||||
break;
|
||||
}
|
||||
|
||||
end_row++;
|
||||
end_col = 0;
|
||||
row = term->grid->rows[end_row];
|
||||
}
|
||||
}
|
||||
|
||||
if (match_len != term->search.len) {
|
||||
/* Didn't match (completely) */
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/* Update match state */
|
||||
term->search.match.row = start_row;
|
||||
term->search.match.col = start_col;
|
||||
term->search.match_len = match_len;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
start_col = backward ? term->cols - 1 : 0;
|
||||
}
|
||||
|
||||
/* No match */
|
||||
LOG_DBG("no match");
|
||||
term->search.match = (struct coord){-1, -1};
|
||||
term->search.match_len = 0;
|
||||
selection_cancel(term);
|
||||
#undef ROW_DEC
|
||||
}
|
||||
|
||||
static size_t
|
||||
distance_next_word(const struct terminal *term)
|
||||
{
|
||||
size_t cursor = term->search.cursor;
|
||||
|
||||
/* First eat non-whitespace. This is the word we're skipping past */
|
||||
while (cursor < term->search.len) {
|
||||
if (iswspace(term->search.buf[cursor++]))
|
||||
break;
|
||||
}
|
||||
|
||||
assert(cursor == term->search.len || iswspace(term->search.buf[cursor - 1]));
|
||||
|
||||
/* Now skip past whitespace, so that we end up at the beginning of
|
||||
* the next word */
|
||||
while (cursor < term->search.len) {
|
||||
if (!iswspace(term->search.buf[cursor++]))
|
||||
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]))
|
||||
cursor--;
|
||||
|
||||
return cursor - term->search.cursor;
|
||||
}
|
||||
|
||||
static size_t
|
||||
distance_prev_word(const struct terminal *term)
|
||||
{
|
||||
int cursor = term->search.cursor;
|
||||
|
||||
/* First, eat whitespace prefix */
|
||||
while (cursor > 0) {
|
||||
if (!iswspace(term->search.buf[--cursor]))
|
||||
break;
|
||||
}
|
||||
|
||||
assert(cursor == 0 || !iswspace(term->search.buf[cursor]));
|
||||
|
||||
/* Now eat non-whitespace. This is the word we're skipping past */
|
||||
while (cursor > 0) {
|
||||
if (iswspace(term->search.buf[--cursor]))
|
||||
break;
|
||||
}
|
||||
|
||||
assert(cursor == 0 || iswspace(term->search.buf[cursor]));
|
||||
if (cursor > 0 && iswspace(term->search.buf[cursor]))
|
||||
cursor++;
|
||||
|
||||
return term->search.cursor - cursor;
|
||||
}
|
||||
|
||||
void
|
||||
search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods)
|
||||
{
|
||||
LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x", sym, sym, mods);
|
||||
|
||||
const xkb_mod_mask_t ctrl = 1 << term->kbd.mod_ctrl;
|
||||
const xkb_mod_mask_t alt = 1 << term->kbd.mod_alt;
|
||||
//const xkb_mod_mask_t shift = 1 << term->kbd.mod_shift;
|
||||
//const xkb_mod_mask_t meta = 1 << term->kbd.mod_meta;
|
||||
|
||||
enum xkb_compose_status compose_status = xkb_compose_state_get_status(
|
||||
term->kbd.xkb_compose_state);
|
||||
|
||||
/* Cancel search */
|
||||
if ((mods == 0 && sym == XKB_KEY_Escape) ||
|
||||
(mods == ctrl && sym == XKB_KEY_g))
|
||||
{
|
||||
if (term->search.view_followed_offset)
|
||||
term->grid->view = term->grid->offset;
|
||||
else
|
||||
term->grid->view = term->search.original_view;
|
||||
term_damage_view(term);
|
||||
search_cancel(term);
|
||||
return;
|
||||
}
|
||||
|
||||
/* "Commit" search - copy selection to primary and cancel search */
|
||||
else if (mods == 0 && sym == XKB_KEY_Return) {
|
||||
selection_finalize(term, term->input_serial);
|
||||
search_cancel_keep_selection(term);
|
||||
return;
|
||||
}
|
||||
|
||||
else if (mods == ctrl && sym == XKB_KEY_r) {
|
||||
if (term->search.match_len > 0) {
|
||||
int new_col = term->search.match.col - 1;
|
||||
int new_row = term->search.match.row;
|
||||
|
||||
if (new_col < 0) {
|
||||
new_col = term->cols - 1;
|
||||
new_row--;
|
||||
}
|
||||
|
||||
if (new_row >= 0) {
|
||||
term->search.match.col = new_col;
|
||||
term->search.match.row = new_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (mods == ctrl && sym == XKB_KEY_s) {
|
||||
if (term->search.match_len > 0) {
|
||||
int new_col = term->search.match.col + 1;
|
||||
int new_row = term->search.match.row;
|
||||
|
||||
if (new_col >= term->cols) {
|
||||
new_col = 0;
|
||||
new_row++;
|
||||
}
|
||||
|
||||
if (new_row < term->grid->num_rows) {
|
||||
term->search.match.col = new_col;
|
||||
term->search.match.row = new_row;
|
||||
term->search.direction = SEARCH_FORWARD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (mods == 0 && sym == XKB_KEY_Left) {
|
||||
if (term->search.cursor > 0)
|
||||
term->search.cursor--;
|
||||
}
|
||||
|
||||
else if ((mods == ctrl && sym == XKB_KEY_Left) ||
|
||||
(mods == alt && sym == XKB_KEY_b))
|
||||
{
|
||||
size_t diff = distance_prev_word(term);
|
||||
term->search.cursor -= diff;
|
||||
assert(term->search.cursor >= 0);
|
||||
assert(term->search.cursor <= term->search.len);
|
||||
}
|
||||
|
||||
else if (mods == 0 && sym == XKB_KEY_Right) {
|
||||
if (term->search.cursor < term->search.len)
|
||||
term->search.cursor++;
|
||||
}
|
||||
|
||||
else if ((mods == ctrl && sym == XKB_KEY_Right) ||
|
||||
(mods == alt && sym == XKB_KEY_f))
|
||||
{
|
||||
size_t diff = distance_next_word(term);
|
||||
term->search.cursor += diff;
|
||||
assert(term->search.cursor >= 0);
|
||||
assert(term->search.cursor <= term->search.len);
|
||||
}
|
||||
|
||||
else if ((mods == 0 && sym == XKB_KEY_Home) ||
|
||||
(mods == ctrl && sym == XKB_KEY_a))
|
||||
term->search.cursor = 0;
|
||||
|
||||
else if ((mods == 0 && sym == XKB_KEY_End) ||
|
||||
(mods == ctrl && sym == XKB_KEY_e))
|
||||
term->search.cursor = term->search.len;
|
||||
|
||||
else if (mods == 0 && sym == XKB_KEY_BackSpace) {
|
||||
if (term->search.cursor > 0) {
|
||||
memmove(
|
||||
&term->search.buf[term->search.cursor - 1],
|
||||
&term->search.buf[term->search.cursor],
|
||||
(term->search.len - term->search.cursor) * sizeof(wchar_t));
|
||||
term->search.cursor--;
|
||||
term->search.buf[--term->search.len] = L'\0';
|
||||
}
|
||||
}
|
||||
|
||||
else if ((mods == alt || mods == ctrl) && sym == XKB_KEY_BackSpace) {
|
||||
size_t diff = distance_prev_word(term);
|
||||
size_t old_cursor = term->search.cursor;
|
||||
size_t new_cursor = old_cursor - diff;
|
||||
|
||||
memmove(&term->search.buf[new_cursor],
|
||||
&term->search.buf[old_cursor],
|
||||
(term->search.len - old_cursor) * sizeof(wchar_t));
|
||||
|
||||
term->search.len -= diff;
|
||||
term->search.cursor = new_cursor;
|
||||
}
|
||||
|
||||
else if ((mods == alt && sym == XKB_KEY_d) ||
|
||||
(mods == ctrl && sym == XKB_KEY_Delete)) {
|
||||
size_t diff = distance_next_word(term);
|
||||
size_t cursor = term->search.cursor;
|
||||
|
||||
memmove(&term->search.buf[cursor],
|
||||
&term->search.buf[cursor + diff],
|
||||
(term->search.len - (cursor + diff)) * sizeof(wchar_t));
|
||||
|
||||
term->search.len -= diff;
|
||||
}
|
||||
|
||||
else if (mods == 0 && sym == XKB_KEY_Delete) {
|
||||
if (term->search.cursor < term->search.len) {
|
||||
memmove(
|
||||
&term->search.buf[term->search.cursor],
|
||||
&term->search.buf[term->search.cursor + 1],
|
||||
(term->search.len - term->search.cursor - 1) * sizeof(wchar_t));
|
||||
term->search.buf[--term->search.len] = L'\0';
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
uint8_t buf[64] = {0};
|
||||
int count = 0;
|
||||
|
||||
if (compose_status == XKB_COMPOSE_COMPOSED) {
|
||||
count = xkb_compose_state_get_utf8(
|
||||
term->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
|
||||
xkb_compose_state_reset(term->kbd.xkb_compose_state);
|
||||
} else {
|
||||
count = xkb_state_key_get_utf8(
|
||||
term->kbd.xkb_state, key, (char *)buf, sizeof(buf));
|
||||
}
|
||||
|
||||
const char *src = (const char *)buf;
|
||||
mbstate_t ps = {0};
|
||||
size_t wchars = mbsnrtowcs(NULL, &src, count, 0, &ps);
|
||||
|
||||
if (wchars == -1) {
|
||||
LOG_ERRNO("failed to convert %.*s to wchars", count, buf);
|
||||
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;
|
||||
}
|
||||
|
||||
assert(term->search.len + wchars < term->search.sz);
|
||||
|
||||
memmove(&term->search.buf[term->search.cursor + wchars],
|
||||
&term->search.buf[term->search.cursor],
|
||||
(term->search.len - term->search.cursor) * sizeof(wchar_t));
|
||||
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
mbsnrtowcs(&term->search.buf[term->search.cursor], &src, count,
|
||||
wchars, &ps);
|
||||
|
||||
term->search.len += wchars;
|
||||
term->search.cursor += wchars;
|
||||
term->search.buf[term->search.len] = L'\0';
|
||||
}
|
||||
|
||||
LOG_DBG("search: buffer: %S", term->search.buf);
|
||||
search_update(term);
|
||||
render_refresh(term);
|
||||
render_search_box(term);
|
||||
}
|
||||
8
search.h
Normal file
8
search.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include "terminal.h"
|
||||
|
||||
void search_begin(struct terminal *term);
|
||||
void search_cancel(struct terminal *term);
|
||||
void search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask_t mods);
|
||||
|
|
@ -20,7 +20,10 @@
|
|||
bool
|
||||
selection_enabled(const struct terminal *term)
|
||||
{
|
||||
return term->mouse_tracking == MOUSE_NONE || term->kbd.shift;
|
||||
return
|
||||
term->mouse_tracking == MOUSE_NONE ||
|
||||
term->kbd.shift ||
|
||||
term->is_searching;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
|||
10
terminal.c
10
terminal.c
|
|
@ -362,6 +362,16 @@ term_reverse_index(struct terminal *term)
|
|||
term_cursor_up(term, 1);
|
||||
}
|
||||
|
||||
void
|
||||
term_reset_view(struct terminal *term)
|
||||
{
|
||||
if (term->grid->view == term->grid->offset)
|
||||
return;
|
||||
|
||||
term->grid->view = term->grid->offset;
|
||||
term_damage_view(term);
|
||||
}
|
||||
|
||||
void
|
||||
term_restore_cursor(struct terminal *term)
|
||||
{
|
||||
|
|
|
|||
37
terminal.h
37
terminal.h
|
|
@ -41,15 +41,20 @@ struct wayland {
|
|||
struct wl_display *display;
|
||||
struct wl_registry *registry;
|
||||
struct wl_compositor *compositor;
|
||||
struct wl_surface *surface;
|
||||
struct wl_subcompositor *sub_compositor;
|
||||
struct wl_shm *shm;
|
||||
|
||||
struct wl_seat *seat;
|
||||
struct wl_keyboard *keyboard;
|
||||
struct zxdg_output_manager_v1 *xdg_output_manager;
|
||||
|
||||
/* Clipboard */
|
||||
struct wl_data_device_manager *data_device_manager;
|
||||
struct wl_data_device *data_device;
|
||||
struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager;
|
||||
struct zwp_primary_selection_device_v1 *primary_selection_device;
|
||||
struct wl_keyboard *keyboard;
|
||||
|
||||
/* Cursor */
|
||||
struct {
|
||||
struct wl_pointer *pointer;
|
||||
uint32_t serial;
|
||||
|
|
@ -60,9 +65,20 @@ struct wayland {
|
|||
int size;
|
||||
char *theme_name;
|
||||
} pointer;
|
||||
|
||||
/* Main window */
|
||||
struct wl_surface *surface;
|
||||
struct xdg_wm_base *shell;
|
||||
struct xdg_surface *xdg_surface;
|
||||
struct xdg_toplevel *xdg_toplevel;
|
||||
|
||||
struct zxdg_decoration_manager_v1 *xdg_decoration_manager;
|
||||
struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration;
|
||||
|
||||
/* Scrollback search */
|
||||
struct wl_surface *search_surface;
|
||||
struct wl_subsurface *search_sub_surface;
|
||||
|
||||
bool have_argb8888;
|
||||
tll(struct monitor) monitors; /* All available outputs */
|
||||
tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */
|
||||
|
|
@ -331,6 +347,20 @@ struct terminal {
|
|||
struct primary primary;
|
||||
} selection;
|
||||
|
||||
bool is_searching;
|
||||
struct {
|
||||
wchar_t *buf;
|
||||
size_t len;
|
||||
size_t sz;
|
||||
size_t cursor;
|
||||
enum { SEARCH_BACKWARD, SEARCH_FORWARD} direction;
|
||||
|
||||
int original_view;
|
||||
bool view_followed_offset;
|
||||
struct coord match;
|
||||
size_t match_len;
|
||||
} search;
|
||||
|
||||
struct grid normal;
|
||||
struct grid alt;
|
||||
struct grid *grid;
|
||||
|
|
@ -368,6 +398,7 @@ struct terminal {
|
|||
|
||||
struct buffer *last_buf; /* Buffer we rendered to last time */
|
||||
bool was_flashing; /* Flash was active last time we rendered */
|
||||
bool was_searching;
|
||||
} render;
|
||||
};
|
||||
|
||||
|
|
@ -379,6 +410,8 @@ void term_damage_rows_in_view(struct terminal *term, int start, int end);
|
|||
void term_damage_all(struct terminal *term);
|
||||
void term_damage_view(struct terminal *term);
|
||||
|
||||
void term_reset_view(struct terminal *term);
|
||||
|
||||
void term_damage_scroll(
|
||||
struct terminal *term, enum damage_type damage_type,
|
||||
struct scroll_region region, int lines);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue