INSERT/UPDATE/DELETE + value model + auto-show, with polish
DSL data operations (ADR-0014): - insert into T [(cols)] values (vals); short form insert into T (vals) omits values keyword for friendlier syntax. - update T set ... where col=val | --all-rows; delete from T where col=val | --all-rows; show data T. - Value AST (Number/Text/Bool/Null) with per-column-type validation in the executor: int/real/decimal/bool/date/ datetime/shortid each accept a documented literal shape and produce friendly format errors naming the column. - INSERT short form fills non-auto-generated columns in schema order; auto-fills serial via SQLite and shortid via the new generator (T2). - `add column [to table] T: c (type)` -- `to table` now optional. Database: - insert/update/delete via prepared statements with bound rusqlite::types::Value parameters. - InsertResult/UpdateResult/DeleteResult: writes return rows_affected plus the affected row(s) only (not the whole table), so users see exactly what changed. - INSERT shows the just-inserted row via last_insert_rowid. - UPDATE captures matching rowids up-front and fetches them post-update -- works even if the UPDATE changed the WHERE column. - DELETE reports per-relationship cascade effects by row- count diffing inbound child tables; UPDATE-side cascades are not yet detected (would need value diffing). - query_data formats cells (booleans true/false, NULLs as None). FK error enrichment: - Now lists both outbound (INSERT/UPDATE relevance) and inbound (DELETE/UPDATE on parent relevance) FKs from the metadata, so RESTRICT errors point at the children blocking the delete. - RelationshipSelector has a proper Display impl -- "no such relationship" reads cleanly. Relationship display: - target_table for AddRelationship/DropRelationship now returns the parent (1-side); structure rendering after add/drop shows that side's "Referenced by:" entry, matching the `from <Parent>` direction of the command. - [ok] summary uses display_subject so relationship commands show both endpoints (`from P.col to C.col`) rather than a single misleading table name. - Auto-name format `<Parent>_<pcol>_to_<Child>_<ccol>` (matches the from..to direction). Output rendering and scrolling: - Wrap-aware scroll: renderer reports both visible-row count and total wrapped-row count to App; scroll math caps against actual displayable rows. Long lines wrap; the bottom line is always reachable; PageUp/PageDown work correctly even after paging past the buffer top. - Multi-line messages (FK error enrichment, cascade summary) split into single-line OutputLines at creation time so wrap/scroll math agree. Runtime / events: - New AppEvent variants for Insert/Update/Delete success carrying typed result structs; DslDataSucceeded reserved for show-data queries. Docs: - ADR-0014 covers data-op grammar, value model, --all-rows safety, auto-show. - requirements.md: C5 done, T2 done, V2 partial (basic data view), V5 partial (show data added). New entries: C5a complex WHERE expressions; H1 progress note for FK enrichment; H1a (strong syntax-help in parse errors). Tests: 200 passing (183 lib + 17 integration), 0 skipped. Includes parser, type-validation, DB write/read, FK-failure enrichment, cascade-delete propagation, focused-auto-show behaviour, scroll-cap invariants. Clippy clean with nursery enabled.
This commit is contained in:
@@ -129,31 +129,73 @@ fn render_output_panel(app: &mut App, theme: &Theme, frame: &mut Frame<'_>, area
|
||||
vertical: 1,
|
||||
});
|
||||
|
||||
// Show a window of the buffer ending `output_scroll` lines
|
||||
// above the most recent entry. With scroll == 0 the last
|
||||
// line shown is the most recent; PageUp increases the
|
||||
// scroll, revealing older lines. We report the visible row
|
||||
// count back to App so input handling can cap scroll
|
||||
// correctly between renders (otherwise scroll could drift
|
||||
// past the top and slide the window off).
|
||||
// Render every output line into a wrapped Paragraph and let
|
||||
// ratatui handle the wrapping; we then use the wrapped row
|
||||
// count to cap scroll correctly. Bottom-anchoring (most
|
||||
// recent visible by default) is achieved by computing the
|
||||
// scroll offset relative to the bottom of the wrapped view.
|
||||
let visible = inner.height as usize;
|
||||
app.note_output_viewport(visible);
|
||||
let total = app.output.len();
|
||||
let max_scroll = total.saturating_sub(visible);
|
||||
let effective_scroll = app.output_scroll.min(max_scroll);
|
||||
let end = total - effective_scroll;
|
||||
let start = end.saturating_sub(visible);
|
||||
|
||||
// Compute the total wrapped row count first, working from
|
||||
// OutputLines directly (so the borrow ends before the
|
||||
// mutable `note_output_viewport` call below).
|
||||
let total_wrapped = approximate_wrapped_rows_from_output(&app.output, inner.width);
|
||||
app.note_output_viewport(visible, total_wrapped);
|
||||
|
||||
let lines: Vec<Line<'_>> = app
|
||||
.output
|
||||
.iter()
|
||||
.skip(start)
|
||||
.take(end - start)
|
||||
.map(|line| render_output_line(line, theme))
|
||||
.collect();
|
||||
let paragraph = Paragraph::new(lines).wrap(Wrap { trim: false });
|
||||
|
||||
let max_scroll = total_wrapped.saturating_sub(visible);
|
||||
let effective_scroll = app.output_scroll.min(max_scroll);
|
||||
// Paragraph::scroll((y, _)) sets the topmost visible row.
|
||||
// We want bottom-anchored: y = max_scroll - effective_scroll
|
||||
// so scroll==0 shows the bottom and PageUp moves y down to
|
||||
// reveal older content.
|
||||
let scroll_y = max_scroll.saturating_sub(effective_scroll);
|
||||
let scroll_y_u16 = u16::try_from(scroll_y).unwrap_or(u16::MAX);
|
||||
|
||||
frame.render_widget(block, area);
|
||||
let paragraph = Paragraph::new(lines).wrap(Wrap { trim: false });
|
||||
frame.render_widget(paragraph, inner);
|
||||
frame.render_widget(paragraph.scroll((scroll_y_u16, 0)), inner);
|
||||
}
|
||||
|
||||
/// Approximate the number of display rows the output buffer
|
||||
/// will occupy after width-wrapping. Computed directly from
|
||||
/// `OutputLine`s (rather than the rendered `Line` objects) so
|
||||
/// the calculation can run before we hand `&mut App` to the
|
||||
/// scroll-cap update. Ratatui's exact `line_count` is gated
|
||||
/// behind an unstable feature; this character-based
|
||||
/// approximation is close enough for scroll capping — off-by-one
|
||||
/// at boundaries is acceptable.
|
||||
fn approximate_wrapped_rows_from_output(
|
||||
output: &std::collections::VecDeque<OutputLine>,
|
||||
width: u16,
|
||||
) -> usize {
|
||||
if width == 0 {
|
||||
return output.len();
|
||||
}
|
||||
let w = usize::from(width);
|
||||
output
|
||||
.iter()
|
||||
.map(|line| {
|
||||
// Tag width matches `render_output_line` exactly so
|
||||
// the row count is right when the panel is too narrow
|
||||
// for the natural line.
|
||||
let tag_len = match line.kind {
|
||||
OutputKind::Echo => match line.mode_at_submission {
|
||||
Mode::Simple => "[simple] ".len(),
|
||||
Mode::Advanced => "[advanced] ".len(),
|
||||
},
|
||||
OutputKind::System => "[system] ".len(),
|
||||
OutputKind::Error => "[error] ".len(),
|
||||
};
|
||||
let total = tag_len + line.text.chars().count();
|
||||
if total == 0 { 1 } else { total.div_ceil(w) }
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn render_output_line<'a>(line: &'a OutputLine, theme: &Theme) -> Line<'a> {
|
||||
|
||||
Reference in New Issue
Block a user