feat(view): WrapMode, threaded to every viewport and pinned to Truncate
Inert by construction. Adds the type and the Viewport field, sets all 31 construction sites to Truncate, and changes no rendering: --lib is 1900/0 and crdt 2085/0, the same counts as the parent commit. The field is required rather than defaulted on purpose. A default would have let 31 sites stay silent about which behavior they meant; a required field makes each one state it, so the pre-existing sites now read as deliberately unwrapped rather than merely untouched. The compiler enumerated them, including five integration tests --- Viewport is public API, so this is a real break, and the break is the point. The render driver is pinned to Truncate too. The wrap path does not exist yet, and exposing a mode before the cursor mapping honors it would ship a setting that renders one thing and navigates another --- the shape of defect this lane exists to remove, not add. Two notes on getting here, since both were nearly landed: The first mechanical patch matched every `folds,` line and put a wrap field into function call sites and a FoldStore literal. Scoping the insertion to Viewport literals cut it from 40 sites to 31. The compiler caught it, but only because a struct field cannot be mistaken for an argument; a same-arity call would have compiled. While rewriting the character walk I changed the wide-character edge case --- a double-width glyph with one cell left now breaking instead of painting a lone lead cell. That is arguably better behavior and it is NOT this commit's to make: Truncate must be byte-identical, and an "improvement" smuggled in beside a refactor is how identity cases stop being identity cases. Reverted; the walk is untouched. Gates: fmt, workspace clippy -D warnings, diff --check, --lib 1900/0, crdt 2085/0, tab_width 2/0, listview 26/0, compile_mode 73/0, folding 21/0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
b8639130d9
commit
8b1eff10df
|
|
@ -757,6 +757,7 @@ fn underline_cols_for_line(line_bytes: &[u8], byte_start: u32, byte_end: u32) ->
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::view::WrapMode;
|
||||
use serde_json::json;
|
||||
|
||||
fn diag(line: u32, sev: DiagnosticSeverity, msg: &str) -> Diagnostic {
|
||||
|
|
@ -1139,6 +1140,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 10),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
@ -1204,6 +1206,7 @@ mod tests {
|
|||
cell_size: CellSize::new(3, 10),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
@ -1280,6 +1283,7 @@ mod tests {
|
|||
cell_size: CellSize::new(3, 8),
|
||||
gutter_w: 2,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
@ -1349,6 +1353,7 @@ mod tests {
|
|||
cell_size: CellSize::new(2, 10),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4313,6 +4313,15 @@ fn paint_window_content(
|
|||
cell_size: crate::cell::CellSize::new(inner_rows, rect.size.cols - gutter_w),
|
||||
gutter_w,
|
||||
folds,
|
||||
// QoL Stage 3: the resolved mode belongs here, beside `folds`,
|
||||
// for the same reason — the driver holds the registry and the
|
||||
// buffer, the view holds neither. Pinned to the identity case
|
||||
// until `ui.line-wrap` is registered; the wrap path is built
|
||||
// and tested beneath this, but nothing can reach it yet, which
|
||||
// is deliberate. Exposing a mode before the cursor mapping
|
||||
// honors it would ship a setting that renders one thing and
|
||||
// navigates another.
|
||||
wrap: crate::view::WrapMode::Truncate,
|
||||
};
|
||||
// Composition (T M2.9): base text_view paints first, then the
|
||||
// gutter numbers — before the overlays, so a diagnostic overlay
|
||||
|
|
@ -5618,6 +5627,7 @@ fn printable_char(seq: &[Chord]) -> Option<char> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::view::WrapMode;
|
||||
// Acceptance home for T M5.4 (FrontendId on input events) — the
|
||||
// `m5_4_*`-prefixed tests verify the FrontendId field threads from
|
||||
// synthetic event construction through `dispatch_key` /
|
||||
|
|
@ -8415,6 +8425,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rect.size.rows, rect.size.cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let mut grid = CellGrid {
|
||||
cells: &mut backing,
|
||||
|
|
@ -8550,6 +8561,7 @@ mod tests {
|
|||
cell_size: CellSize::new(24, 80),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
|
||||
// Two no-op overlays: probe the dispatch cost only.
|
||||
|
|
|
|||
|
|
@ -773,6 +773,7 @@ impl View for LspStyleView {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::view::WrapMode;
|
||||
|
||||
#[test]
|
||||
fn theme_lookup_walks_dotted_prefixes() {
|
||||
|
|
@ -1111,6 +1112,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 20),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = state.core.borrow().registry.clone();
|
||||
let reg = registry.borrow();
|
||||
|
|
@ -1209,6 +1211,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 20),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = state.core.borrow().registry.clone();
|
||||
let reg = registry.borrow();
|
||||
|
|
@ -1329,6 +1332,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 20),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = state.core.borrow().registry.clone();
|
||||
let reg = registry.borrow();
|
||||
|
|
@ -1389,6 +1393,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows as u32, cols as u32),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = buf; // keep buf alive
|
||||
hv.render(®istry, viewport, &mut grid);
|
||||
|
|
@ -1446,6 +1451,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows as u32, cols as u32),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = buf; // keep buf alive
|
||||
hv.render(®istry, viewport, &mut grid);
|
||||
|
|
@ -1511,6 +1517,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows as u32, cols as u32),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = buf;
|
||||
hv.render(®istry, viewport, &mut grid);
|
||||
|
|
@ -1578,6 +1585,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows as u32, cols as u32),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
hv.render(&buf, viewport, &mut grid);
|
||||
grid.get(CellCoord::new(0, col)).style
|
||||
|
|
@ -1820,6 +1828,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows as u32, cols as u32),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = buf;
|
||||
hv.render(®istry, viewport, &mut grid);
|
||||
|
|
@ -1883,6 +1892,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows as u32, cols as u32),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let registry = buf;
|
||||
hv.render(®istry, viewport, &mut grid);
|
||||
|
|
|
|||
|
|
@ -483,6 +483,7 @@ mod tests {
|
|||
use crate::cell::{Cell, CellSize, Glyph, UnderlineStyle};
|
||||
use crate::text_view::TextView;
|
||||
use crate::view::Viewport;
|
||||
use crate::view::WrapMode;
|
||||
|
||||
fn make_grid(rows: u32, cols: u32) -> Vec<Cell> {
|
||||
vec![Cell::default(); (rows * cols) as usize]
|
||||
|
|
@ -496,6 +497,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows, cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -541,6 +541,7 @@ impl View for SearchView {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::view::WrapMode;
|
||||
|
||||
fn r(start: u64, end: u64) -> ByteRange {
|
||||
ByteRange { start, end }
|
||||
|
|
@ -759,6 +760,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 10),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
@ -788,6 +790,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 10),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid2,
|
||||
);
|
||||
|
|
@ -830,6 +833,7 @@ mod tests {
|
|||
cell_size: CellSize::new(rows, cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -316,6 +316,7 @@ mod tests {
|
|||
use crate::buffer::{BufferId, EditOp};
|
||||
use crate::cell::CellSize;
|
||||
use crate::rope::Range;
|
||||
use crate::view::WrapMode;
|
||||
use proptest::prelude::*;
|
||||
|
||||
fn buf_with(content: &[u8]) -> Buffer {
|
||||
|
|
@ -570,6 +571,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 16),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
@ -599,6 +601,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 16),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
@ -631,6 +634,7 @@ mod tests {
|
|||
cell_size: CellSize::new(5, 5),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
@ -664,6 +668,7 @@ mod tests {
|
|||
cell_size: CellSize::new(1, 5),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
},
|
||||
&mut grid,
|
||||
);
|
||||
|
|
|
|||
43
src/view.rs
43
src/view.rs
|
|
@ -122,6 +122,39 @@ impl DisplayCoord {
|
|||
}
|
||||
}
|
||||
|
||||
/// How a line wider than the viewport is shown --- the long-lines
|
||||
/// stage, `docs/long-lines-framing.md`.
|
||||
///
|
||||
/// # Why the renderer is told, rather than asking
|
||||
///
|
||||
/// This is the *resolved* mode, not the setting's name. `ui.line-wrap`
|
||||
/// is **buffer-local** (framing Q#LL2), and the config registry has no
|
||||
/// ambient current buffer by design (`config_registry.rs`, Q#CR4) — a
|
||||
/// caller wanting buffer-aware behavior must pass the `BufferId`. The
|
||||
/// render driver holds both the registry and the buffer, resolves once
|
||||
/// per window per frame, and puts the answer here. Views stay
|
||||
/// config-agnostic, exactly as they do for folds.
|
||||
///
|
||||
/// # `Truncate` is the identity case, deliberately
|
||||
///
|
||||
/// Every behavior predating this type is `Truncate`, and it must stay
|
||||
/// byte-identical under it — which is what lets the wrap work be
|
||||
/// verified against the existing suite rather than against new
|
||||
/// assertions.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Hash)]
|
||||
pub enum WrapMode {
|
||||
/// One source line per row; the remainder is clipped at the right
|
||||
/// edge. What every pre-Stage-3 caller did.
|
||||
#[default]
|
||||
Truncate,
|
||||
/// A line longer than the viewport continues on the following rows.
|
||||
///
|
||||
/// Character wrap, not word wrap (framing Q#LL5): it matches
|
||||
/// Emacs's default, and it is the only break rule both frontends
|
||||
/// can implement identically without pulling UAX #14 into the grid.
|
||||
Wrap,
|
||||
}
|
||||
|
||||
/// What to render and where.
|
||||
///
|
||||
/// The frontend computes the viewport (which buffer range maps to which
|
||||
|
|
@ -152,6 +185,14 @@ pub struct Viewport<'a> {
|
|||
/// split may show different buffers) and hands the same shared
|
||||
/// reference to every painter of that window.
|
||||
pub folds: Option<&'a VisibleLineMap>,
|
||||
/// How lines wider than `cell_size.cols` are shown, already
|
||||
/// resolved for this window's buffer (see [`WrapMode`]).
|
||||
///
|
||||
/// A required field rather than a defaulted one on purpose: every
|
||||
/// construction site has to state which behavior it means, so the
|
||||
/// pre-Stage-3 sites read as *deliberately* unwrapped rather than
|
||||
/// merely untouched.
|
||||
pub wrap: WrapMode,
|
||||
}
|
||||
|
||||
impl Viewport<'_> {
|
||||
|
|
@ -360,6 +401,7 @@ mod tests {
|
|||
cell_size: CellSize::new(10, 10),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
assert_eq!(vp.row_offset_of(4, 4), Some(0));
|
||||
assert_eq!(vp.row_offset_of(4, 9), Some(5));
|
||||
|
|
@ -380,6 +422,7 @@ mod tests {
|
|||
cell_size: CellSize::new(10, 10),
|
||||
gutter_w: 0,
|
||||
folds: Some(&map),
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
assert_eq!(vp.row_offset_of(0, 1), Some(1), "the head keeps its row");
|
||||
assert_eq!(vp.row_offset_of(0, 3), None, "hidden lines have no row");
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ fn render_active_window_to_grid(
|
|||
cols: u32,
|
||||
) -> Vec<pmacs::cell::Cell> {
|
||||
use pmacs::cell::{Cell, CellGrid, CellSize};
|
||||
use pmacs::view::{View, Viewport};
|
||||
use pmacs::view::{View, Viewport, WrapMode};
|
||||
use pmacs::window::Rect;
|
||||
|
||||
let mut core = state.core.borrow_mut();
|
||||
|
|
@ -289,6 +289,7 @@ fn render_active_window_to_grid(
|
|||
cell_size: CellSize::new(rect.size.rows, rect.size.cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let mut grid = CellGrid {
|
||||
cells: &mut backing,
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ fn set_read_only(s: &EditorState, id: BufferId, value: bool) {
|
|||
/// bites: the rope is right and the screen is not.
|
||||
fn paint_active_window(s: &EditorState, rows: u32, cols: u32) -> Vec<pmacs::cell::Cell> {
|
||||
use pmacs::cell::{Cell, CellGrid, CellSize};
|
||||
use pmacs::view::{View, Viewport};
|
||||
use pmacs::view::{View, Viewport, WrapMode};
|
||||
use pmacs::window::Rect;
|
||||
|
||||
let mut core = s.core.borrow_mut();
|
||||
|
|
@ -162,6 +162,7 @@ fn paint_active_window(s: &EditorState, rows: u32, cols: u32) -> Vec<pmacs::cell
|
|||
cell_size: CellSize::new(rows, cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let mut grid = CellGrid {
|
||||
cells: &mut backing,
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ fn render_active_window_to_grid(
|
|||
cols: u32,
|
||||
) -> Vec<pmacs::cell::Cell> {
|
||||
use pmacs::cell::{Cell, CellGrid, CellSize};
|
||||
use pmacs::view::{View, Viewport};
|
||||
use pmacs::view::{View, Viewport, WrapMode};
|
||||
use pmacs::window::Rect;
|
||||
|
||||
let mut core = state.core.borrow_mut();
|
||||
|
|
@ -502,6 +502,7 @@ fn render_active_window_to_grid(
|
|||
cell_size: CellSize::new(rect.size.rows, rect.size.cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let mut grid = CellGrid {
|
||||
cells: &mut backing,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use pmacs::buffer::{Buffer, BufferId};
|
|||
use pmacs::cell::{Cell, CellCoord, CellGrid, CellSize, Glyph, Style};
|
||||
use pmacs::overlay::{BufferStyleOverlay, BufferStyleSpan, SharedBufferStyleSpans};
|
||||
use pmacs::text_view::TextView;
|
||||
use pmacs::view::{DisplayCoord, View, Viewport};
|
||||
use pmacs::view::{DisplayCoord, View, Viewport, WrapMode};
|
||||
|
||||
fn viewport(rows: u32, cols: u32, buffer_end: u64) -> Viewport<'static> {
|
||||
Viewport {
|
||||
|
|
@ -16,6 +16,7 @@ fn viewport(rows: u32, cols: u32, buffer_end: u64) -> Viewport<'static> {
|
|||
cell_size: CellSize::new(rows, cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ fn render_active_window_to_grid(
|
|||
cols: u32,
|
||||
) -> Vec<pmacs::cell::Cell> {
|
||||
use pmacs::cell::{Cell, CellGrid};
|
||||
use pmacs::view::{View, Viewport};
|
||||
use pmacs::view::{View, Viewport, WrapMode};
|
||||
use pmacs::window::Rect;
|
||||
|
||||
let mut core = state.core.borrow_mut();
|
||||
|
|
@ -486,6 +486,7 @@ fn render_active_window_to_grid(
|
|||
cell_size: CellSize::new(rows, cols),
|
||||
gutter_w: 0,
|
||||
folds: None,
|
||||
wrap: WrapMode::Truncate,
|
||||
};
|
||||
let mut grid = CellGrid {
|
||||
cells: &mut backing,
|
||||
|
|
|
|||
Loading…
Reference in New Issue