fix: wrap ui2 pager lines

This commit is contained in:
BreezeDelegate 2026-08-23 18:31:35 +00:00
parent ba8c41f423
commit 2c5aa8a98b
2 changed files with 157 additions and 5 deletions

View file

@ -324,6 +324,7 @@ pub enum RedrawEvent {
/// [nvim_open_win]: https://neovim.io/doc/user/api.html#nvim_open_win()
WindowFloatPosition {
grid: u64,
window: Value,
anchor: WindowAnchor,
anchor_grid: u64,
anchor_row: f64,
@ -884,12 +885,13 @@ fn parse_window_anchor(value: Value) -> Result<WindowAnchor> {
fn parse_win_float_pos(win_float_pos_arguments: Vec<Value>) -> Result<RedrawEvent> {
let (
[grid, _window, anchor, anchor_grid, anchor_row, anchor_column, mouse_enabled, z_index],
[grid, window, anchor, anchor_grid, anchor_row, anchor_column, mouse_enabled, z_index],
[comp_index, screen_row, screen_col],
) = extract_values_with_optional(win_float_pos_arguments)?;
Ok(RedrawEvent::WindowFloatPosition {
grid: parse_u64(grid)?,
window,
anchor: parse_window_anchor(anchor)?,
anchor_grid: parse_u64(anchor_grid)?,
anchor_row: parse_f64(anchor_row)?,
@ -1184,7 +1186,28 @@ pub fn parse_progress_bar_event(value: Option<&Value>) -> Option<UserEvent> {
mod tests {
use rmpv::Value;
use super::{MessageKind, RedrawEvent, parse_msg_show};
use super::{MessageKind, RedrawEvent, parse_msg_show, parse_win_float_pos};
#[test]
fn win_float_pos_preserves_window_handle() {
let window = Value::Ext(1, vec![0, 0, 3, 234]);
let event = parse_win_float_pos(vec![
Value::from(8),
window.clone(),
Value::from("SE"),
Value::from(1),
Value::from(9.0),
Value::from(10_000.0),
Value::from(true),
Value::from(197),
])
.unwrap();
match event {
RedrawEvent::WindowFloatPosition { window: parsed, .. } => assert_eq!(parsed, window),
event => panic!("expected WindowFloatPosition, got {event:?}"),
}
}
#[test]
fn message_kind_marks_error_variants() {

View file

@ -1,12 +1,15 @@
use std::fmt;
use std::sync::{
Arc, Mutex, RwLock,
atomic::{AtomicBool, Ordering},
};
use std::{
collections::{HashMap, HashSet},
fmt,
};
use async_trait::async_trait;
use log::{trace, warn};
use nvim_rs::{Handler, Neovim, call_args};
use nvim_rs::{Handler, Neovim, call_args, rpc::IntoVal};
use rmpv::Value;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use winit::event_loop::EventLoopProxy;
@ -74,6 +77,8 @@ struct NeovimState {
nvim: Option<Neovim<NeovimWriter>>,
can_support_ime_api: bool,
pre_attach_cmdheight: Option<Value>,
grid_sizes: HashMap<u64, (u64, u64)>,
ui2_pager_grids: HashSet<u64>,
}
#[derive(Clone)]
@ -137,6 +142,8 @@ impl NeovimHandler {
if let Ok(mut guard) = self.current_neovim.write() {
guard.nvim = Some(neovim);
guard.can_support_ime_api = can_support_ime_api;
guard.grid_sizes.clear();
guard.ui2_pager_grids.clear();
}
}
@ -167,6 +174,83 @@ impl NeovimHandler {
pub fn send_redraw_event(&self, event: RedrawEvent) {
let _ = self.redraw_event_sender.send(event);
}
fn update_grid_size(&self, grid: u64, width: u64, height: u64) {
if let Ok(mut guard) = self.current_neovim.write() {
guard.grid_sizes.insert(grid, (width, height));
}
}
fn remove_grid_size(&self, grid: u64) {
if let Ok(mut guard) = self.current_neovim.write() {
guard.grid_sizes.remove(&grid);
guard.ui2_pager_grids.remove(&grid);
}
}
fn remember_ui2_pager(&self, grid: u64) {
if let Ok(mut guard) = self.current_neovim.write() {
guard.ui2_pager_grids.insert(grid);
}
}
fn oversized_grid_resize_target(&self, grid: u64) -> Option<(u64, u64)> {
let guard = self.current_neovim.read().ok()?;
oversized_grid_resize_target(&guard.grid_sizes, grid)
}
fn ui2_pager_resize_targets(&self, changed_grid: u64) -> Vec<(u64, u64, u64)> {
let Ok(guard) = self.current_neovim.read() else {
return Vec::new();
};
ui2_pager_resize_targets(&guard.grid_sizes, &guard.ui2_pager_grids, changed_grid)
}
}
fn ui2_pager_resize_targets(
grid_sizes: &HashMap<u64, (u64, u64)>,
ui2_pager_grids: &HashSet<u64>,
changed_grid: u64,
) -> Vec<(u64, u64, u64)> {
let Some(&(base_width, _)) = grid_sizes.get(&1) else {
return Vec::new();
};
ui2_pager_grids
.iter()
.filter(|&&grid| changed_grid == 1 || changed_grid == grid)
.filter_map(|&grid| {
let &(width, height) = grid_sizes.get(&grid)?;
(width != base_width).then_some((grid, base_width, height))
})
.collect()
}
fn oversized_grid_resize_target(
grid_sizes: &HashMap<u64, (u64, u64)>,
grid: u64,
) -> Option<(u64, u64)> {
let &(base_width, _) = grid_sizes.get(&1)?;
let &(width, height) = grid_sizes.get(&grid)?;
(width > base_width).then_some((base_width, height))
}
async fn is_ui2_pager(nvim: &Neovim<NeovimWriter>, window: &Value) -> bool {
nvim.exec_lua(
"local win = ...; if not vim.api.nvim_win_is_valid(win) then return false end; local buf = vim.api.nvim_win_get_buf(win); return vim.bo[buf].filetype == 'pager' and vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ':t') == '[Pager]'",
call_args![window.clone()],
)
.await
.ok()
.and_then(|value| value.as_bool())
.unwrap_or(false)
}
async fn resize_grid(nvim: &Neovim<NeovimWriter>, grid: u64, width: u64, height: u64) {
if let Err(error) = nvim.ui_try_resize_grid(grid as i64, width as i64, height as i64).await {
warn!("Failed to resize ui2 pager grid: {error}");
}
}
#[async_trait]
@ -221,6 +305,28 @@ impl Handler for NeovimHandler {
continue;
}
match &parsed_event {
RedrawEvent::Resize { grid, width, height } => {
self.update_grid_size(*grid, *width, *height);
for (pager_grid, width, height) in
self.ui2_pager_resize_targets(*grid)
{
resize_grid(&neovim, pager_grid, width, height).await;
}
}
RedrawEvent::Destroy { grid } => self.remove_grid_size(*grid),
RedrawEvent::WindowFloatPosition { grid, window, .. } => {
if let Some((width, height)) =
self.oversized_grid_resize_target(*grid)
&& is_ui2_pager(&neovim, window).await
{
self.remember_ui2_pager(*grid);
resize_grid(&neovim, *grid, width, height).await;
}
}
_ => {}
}
match parsed_event {
RedrawEvent::Restart { details } => {
let payload = EventPayload::for_route(
@ -386,13 +492,36 @@ fn is_guifont_option_set(event: &RedrawEvent) -> bool {
#[cfg(test)]
mod tests {
use std::{
collections::{HashMap, HashSet},
panic::{AssertUnwindSafe, catch_unwind},
sync::{Arc, Mutex},
};
use super::{ClipboardRequestError, handle_clipboard_request};
use super::{
ClipboardRequestError, handle_clipboard_request, oversized_grid_resize_target,
ui2_pager_resize_targets,
};
use crate::clipboard::{Clipboard, ClipboardError, ClipboardHandle, ProviderState};
#[test]
fn oversized_grid_uses_base_width_and_preserves_height() {
let grid_sizes = HashMap::from([(1, (80, 24)), (8, (10_000, 2))]);
assert_eq!(oversized_grid_resize_target(&grid_sizes, 8), Some((80, 2)));
}
#[test]
fn normal_grid_is_not_resized() {
let grid_sizes = HashMap::from([(1, (80, 24)), (8, (60, 2))]);
assert_eq!(oversized_grid_resize_target(&grid_sizes, 8), None);
}
#[test]
fn known_pager_tracks_base_grid_growth() {
let grid_sizes = HashMap::from([(1, (80, 24)), (8, (40, 3))]);
let pager_grids = HashSet::from([8]);
assert_eq!(ui2_pager_resize_targets(&grid_sizes, &pager_grids, 1), vec![(8, 80, 3)]);
}
fn unavailable_clipboard(error: ClipboardError) -> Arc<Mutex<Clipboard>> {
Arc::new(Mutex::new(Clipboard::from_provider_states_for_test(
ProviderState::unavailable_for_test(error.clone()),