diff --git a/Cargo.lock b/Cargo.lock index 7e9644be7..5d5d71440 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2572,6 +2572,7 @@ dependencies = [ "comrak", "glob", "insta", + "jp_term", "proptest", "syntect", "two-face", diff --git a/crates/jp_cli/src/cmd/query/tool/prompter.rs b/crates/jp_cli/src/cmd/query/tool/prompter.rs index b0f30f77e..229c17b50 100644 --- a/crates/jp_cli/src/cmd/query/tool/prompter.rs +++ b/crates/jp_cli/src/cmd/query/tool/prompter.rs @@ -22,8 +22,8 @@ use jp_conversation::event::SelectOption; use jp_editor::{EditOutcome, EditorBackend}; use jp_inquire::{InlineOption, ReplyEditMode, ReplyOutcome, prompt::PromptBackend}; use jp_llm::tool::executor::PermissionInfo; -use jp_md::{format::DefaultBackground, shade::ShadedWriter}; use jp_printer::{Printer, PromptWriter}; +use jp_term::{background::DefaultBackground, shade::ShadedWriter}; use jp_tool::AnswerType; use serde_json::Value; diff --git a/crates/jp_cli/src/cmd/query/tool/prompter_tests.rs b/crates/jp_cli/src/cmd/query/tool/prompter_tests.rs index 53261d6bd..e07f2727f 100644 --- a/crates/jp_cli/src/cmd/query/tool/prompter_tests.rs +++ b/crates/jp_cli/src/cmd/query/tool/prompter_tests.rs @@ -2,8 +2,8 @@ use std::{sync::Arc, time::Duration}; use jp_editor::MockEditorBackend; use jp_inquire::{ReplyOutcome, prompt::MockPromptBackend}; -use jp_md::format::BackgroundFill; use jp_printer::{OutputFormat, PrintableExt as _, SharedBuffer}; +use jp_term::background::BackgroundFill; use serde_json::json; use super::*; diff --git a/crates/jp_cli/src/cmd/query/turn/coordinator.rs b/crates/jp_cli/src/cmd/query/turn/coordinator.rs index 1c6051476..2f657ac4e 100644 --- a/crates/jp_cli/src/cmd/query/turn/coordinator.rs +++ b/crates/jp_cli/src/cmd/query/turn/coordinator.rs @@ -9,8 +9,8 @@ use jp_llm::{ event::{Event, EventPart, FinishReason}, event_builder::EventBuilder, }; -use jp_md::format::DefaultBackground; use jp_printer::Printer; +use jp_term::background::DefaultBackground; use crate::{ cmd::query::{interrupt::InterruptAction, stream::TurnView}, diff --git a/crates/jp_cli/src/render/chat.rs b/crates/jp_cli/src/render/chat.rs index 878d5001d..e302adc2a 100644 --- a/crates/jp_cli/src/render/chat.rs +++ b/crates/jp_cli/src/render/chat.rs @@ -38,13 +38,11 @@ use jp_config::style::{ use jp_conversation::event::ChatResponse; use jp_md::{ buffer::{Buffer, Event, Fixups}, - format::{ - BackgroundFill, CodeBlockState, DefaultBackground, Formatter, TerminalOptions, - render_separator, - }, + format::{CodeBlockState, Formatter, TerminalOptions, render_separator}, theme, }; use jp_printer::{OutputWidth, PrintableExt as _, Printer, RegionStyle, StatusRegion}; +use jp_term::background::{BackgroundFill, DefaultBackground}; use tracing::warn; /// The kind of content last pushed into the renderer. diff --git a/crates/jp_cli/src/render/tool.rs b/crates/jp_cli/src/render/tool.rs index e499ec5a2..1c71dd8d9 100644 --- a/crates/jp_cli/src/render/tool.rs +++ b/crates/jp_cli/src/render/tool.rs @@ -22,12 +22,9 @@ use jp_config::{ }; use jp_conversation::event::ToolCallResponse; use jp_llm::{CommandResult, run_tool_command, tool::InvocationContext}; -use jp_md::{ - format::{DefaultBackground, Formatter}, - shade::ShadedWriter, -}; +use jp_md::format::Formatter; use jp_printer::{ErrChannel, LineSink, OutputLines, RegionStyle, StatusRegion}; -use jp_term::osc::hyperlink; +use jp_term::{background::DefaultBackground, osc::hyperlink, shade::ShadedWriter}; use serde_json::{Map, Value}; use tokio_util::sync::CancellationToken; use tracing::warn; diff --git a/crates/jp_cli/src/render/tool_tests.rs b/crates/jp_cli/src/render/tool_tests.rs index d3651d50a..36e7c678d 100644 --- a/crates/jp_cli/src/render/tool_tests.rs +++ b/crates/jp_cli/src/render/tool_tests.rs @@ -7,8 +7,8 @@ use jp_config::{ style::stderr_rows::{RowCount, StderrRows}, }; use jp_conversation::event::ToolCallResponse; -use jp_md::format::{BackgroundFill, DefaultBackground}; use jp_printer::{ErrChannel, OutputFormat, Printer, SharedBuffer, TerminalCapability}; +use jp_term::background::{BackgroundFill, DefaultBackground}; use serde_json::{Map, Value}; use super::*; diff --git a/crates/jp_cli/src/render/turn_view.rs b/crates/jp_cli/src/render/turn_view.rs index b47c88b43..64dfbc1de 100644 --- a/crates/jp_cli/src/render/turn_view.rs +++ b/crates/jp_cli/src/render/turn_view.rs @@ -20,8 +20,8 @@ use std::sync::{ use jp_config::style::StyleConfig; use jp_conversation::event::{ChatRequest, ChatResponse}; -use jp_md::format::DefaultBackground; use jp_printer::Printer; +use jp_term::background::DefaultBackground; use super::{ChatRenderer, RenderFlow, StructuredRenderer}; diff --git a/crates/jp_md/Cargo.toml b/crates/jp_md/Cargo.toml index d5ed2805b..633a8ece9 100644 --- a/crates/jp_md/Cargo.toml +++ b/crates/jp_md/Cargo.toml @@ -13,6 +13,8 @@ repository.workspace = true version.workspace = true [dependencies] +jp_term = { workspace = true } + comrak = { workspace = true } syntect = { workspace = true, features = ["regex-fancy", "default-themes", "default-syntaxes"] } two-face = { workspace = true, features = ["syntect-fancy"] } diff --git a/crates/jp_md/src/format.rs b/crates/jp_md/src/format.rs index 01b9ee02e..87bde5260 100644 --- a/crates/jp_md/src/format.rs +++ b/crates/jp_md/src/format.rs @@ -1,17 +1,20 @@ //! Markdown formatting utilities. -use std::{borrow::Cow, fmt, sync::LazyLock}; +use std::{fmt, sync::LazyLock}; use comrak::{ Arena, nodes::{NodeList, NodeValue}, options::{Extension, ListStyleType, Render}, }; +use jp_term::{ + ansi::{self, AnsiState, Segment}, + background::{DefaultBackground, line_fill}, +}; use syntect::{highlighting::Theme, parsing::SyntaxSet}; use two_face::syntax; use crate::{ - ansi::{self, AnsiState, Segment}, render::{self, HrOptions, RenderOptions}, table::TableOptions, theme, @@ -33,19 +36,6 @@ const DEFAULT_WIDTH: usize = 80; /// Default maximum column width for tables. const DEFAULT_TABLE_MAX_COL_WIDTH: usize = 40; -/// How a default background color fills each line. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BackgroundFill { - /// Fill to the last visible character on the line. - Content, - - /// Fill to a fixed column width (padding with spaces if needed). - Column(usize), - - /// Fill to the end of the terminal window via `\x1b[K`. - Terminal, -} - /// Controls how horizontal rules (`---`) are rendered in terminal output. #[derive(Debug, Clone, Copy, Default)] pub enum HrStyle { @@ -58,17 +48,6 @@ pub enum HrStyle { Line, } -/// A default background color applied to all content, with a fill mode -/// controlling how far it extends on each line. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct DefaultBackground { - /// SGR background parameter, e.g. `"48;5;236"` or `"48;2;80;73;69"`. - pub param: String, - - /// How far the background extends on each line. - pub fill: BackgroundFill, -} - /// Per-call options for [`Formatter::format_terminal_with`]. #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct TerminalOptions { @@ -484,28 +463,6 @@ fn ends_with_tight_list<'a>(root: &'a comrak::nodes::AstNode<'a>) -> bool { ) } -/// The text that extends a background from `column` to the end of the line. -/// -/// The one place [`BackgroundFill`] is interpreted. -/// Every writer that maintains a region background consults this so the three -/// modes cannot drift apart: `Content` adds nothing, `Terminal` defers to the -/// terminal's erase-to-end-of- line, and `Column` pads with real spaces — the -/// only form a host that lays out its own sub-window (an `fzf` preview pane) -/// renders, since it does not implement the erase. -/// -/// The caller is responsible for having the background active before writing -/// the result. -pub(crate) fn line_fill(fill: BackgroundFill, column: usize) -> Cow<'static, str> { - match fill { - BackgroundFill::Content => Cow::Borrowed(""), - BackgroundFill::Terminal => Cow::Borrowed("\x1b[K"), - BackgroundFill::Column(target) => match target.saturating_sub(column) { - 0 => Cow::Borrowed(""), - pad => Cow::Owned(" ".repeat(pad)), - }, - } -} - /// Render an inter-block separator (blank line) with optional background fill. /// Used between blocks and after closing code fences. /// diff --git a/crates/jp_md/src/format_tests.rs b/crates/jp_md/src/format_tests.rs index 3c15df71d..59460d306 100644 --- a/crates/jp_md/src/format_tests.rs +++ b/crates/jp_md/src/format_tests.rs @@ -1,3 +1,5 @@ +use jp_term::background::BackgroundFill; + use super::*; struct TestCase { diff --git a/crates/jp_md/src/lib.rs b/crates/jp_md/src/lib.rs index d611d0641..471599ea6 100644 --- a/crates/jp_md/src/lib.rs +++ b/crates/jp_md/src/lib.rs @@ -27,12 +27,10 @@ reason = "we don't host the docs, and use them mainly for LSP integration" )] -mod ansi; pub mod buffer; pub mod format; pub mod heading; mod render; -pub mod shade; mod table; pub mod theme; mod writer; diff --git a/crates/jp_md/src/render.rs b/crates/jp_md/src/render.rs index 39b500b0b..3935d9916 100644 --- a/crates/jp_md/src/render.rs +++ b/crates/jp_md/src/render.rs @@ -34,14 +34,17 @@ use comrak::{ NodeTaskItem, NodeValue, }, }; -use syntect::highlighting::Theme; - -use crate::{ +use jp_term::{ ansi::{ BG_END, BOLD_END, BOLD_START, FG_END, ITALIC_END, ITALIC_START, STRIKETHROUGH_END, STRIKETHROUGH_START, UNDERLINE_END, UNDERLINE_START, }, - format::{DefaultBackground, HrStyle, SYNTAXES}, + background::DefaultBackground, +}; +use syntect::highlighting::Theme; + +use crate::{ + format::{HrStyle, SYNTAXES}, table, writer::TerminalWriter, }; diff --git a/crates/jp_md/src/table.rs b/crates/jp_md/src/table.rs index 920662174..3d07616c6 100644 --- a/crates/jp_md/src/table.rs +++ b/crates/jp_md/src/table.rs @@ -22,13 +22,11 @@ use std::{cmp::min, fmt::Write as _}; use comrak::nodes::{NodeValue, TableAlignment}; +use jp_term::ansi::{self, AnsiState, RESET, Segment}; use unicode_segmentation::UnicodeSegmentation as _; use unicode_width::UnicodeWidthStr as _; -use crate::{ - ansi::{self, AnsiState, RESET, Segment}, - render::{RenderOptions, TerminalFormatter}, -}; +use crate::render::{RenderOptions, TerminalFormatter}; /// Type alias for comrak AST node references. type Node<'a> = &'a comrak::nodes::AstNode<'a>; diff --git a/crates/jp_md/src/writer.rs b/crates/jp_md/src/writer.rs index 70e15cc6c..9c0a01328 100644 --- a/crates/jp_md/src/writer.rs +++ b/crates/jp_md/src/writer.rs @@ -17,11 +17,13 @@ use std::{ fmt::{self, Write}, }; -use crate::{ +use jp_term::{ ansi::{self, AnsiState, RESET, Segment}, - format::{self, BackgroundFill, DefaultBackground}, + background::{BackgroundFill, DefaultBackground, line_fill}, }; +use crate::format; + /// ANSI-aware terminal writer with word-wrapping support. #[expect(clippy::struct_excessive_bools)] pub struct TerminalWriter<'w> { @@ -432,8 +434,8 @@ impl<'w> TerminalWriter<'w> { /// Extend the active background to the end of the current line. /// - /// The fill itself comes from [`format::line_fill`], the single - /// interpretation of [`BackgroundFill`]. + /// The fill itself comes from [`line_fill`], the single interpretation of + /// [`BackgroundFill`]. /// The background escape always precedes it so a temporary background /// (inline code, say) can't bleed into the fill. /// @@ -444,7 +446,7 @@ impl<'w> TerminalWriter<'w> { return Ok(()); }; - let fill = format::line_fill(bg.fill, self.column); + let fill = line_fill(bg.fill, self.column); if fill.is_empty() { return Ok(()); } diff --git a/crates/jp_md/src/writer_tests.rs b/crates/jp_md/src/writer_tests.rs index 27a65046c..070f9ac53 100644 --- a/crates/jp_md/src/writer_tests.rs +++ b/crates/jp_md/src/writer_tests.rs @@ -1,5 +1,4 @@ use super::*; -use crate::format::{BackgroundFill, DefaultBackground}; /// Regression test: a 1-byte prefix used to underflow in `write_prefix` because /// `prefix.len() - 2` wraps to `usize::MAX`. diff --git a/crates/jp_md/src/ansi.rs b/crates/jp_term/src/ansi.rs similarity index 89% rename from crates/jp_md/src/ansi.rs rename to crates/jp_term/src/ansi.rs index 224185f29..932bfdcec 100644 --- a/crates/jp_md/src/ansi.rs +++ b/crates/jp_term/src/ansi.rs @@ -1,8 +1,15 @@ -//! Shared ANSI SGR escape constants and state tracking. +//! ANSI SGR escape constants, state tracking, and escape-aware measurement. //! -//! This module provides the escape sequences, state tracking, and visual width -//! computation used by both the terminal renderer (`render.rs`) and the table -//! formatter (`table.rs`). +//! [`segments`] is the tokenizer everything else here is built on: it splits a +//! byte stream into visible text and complete escape sequences. +//! [`AnsiState`] tracks which attributes a stream has left active, so a writer +//! can close them at a line break and re-open them on the next line. +//! [`visual_width`] measures how many columns text occupies, skipping escapes. +//! [`advance_column`] answers a different question: where the cursor ends up, +//! which a tab or a carriage return moves somewhere the column count does not +//! predict. +//! `"a\tb"` occupies 3 columns and leaves the cursor at 9, so padding to a +//! fixed column has to go through `advance_column`. use unicode_width::UnicodeWidthStr as _; @@ -47,9 +54,10 @@ pub const RESET: &str = "\x1b[0m"; /// Tracks which ANSI SGR attributes are currently active. /// -/// Used to close formatting at line breaks and re-open it on the next line, -/// both for the terminal renderer's incremental wrapping and the table -/// formatter's batch wrapping. +/// Feed it every escape a stream emits, and it answers what a line break has to +/// close and what the next line has to re-open. +/// Attributes combined into one escape are tracked individually, so +/// `\x1b[1;48;5;236m` registers as both bold and a background. #[derive(Debug, Clone, Default)] #[expect(clippy::struct_excessive_bools)] pub struct AnsiState { @@ -80,7 +88,8 @@ pub struct AnsiState { impl AnsiState { /// Returns `true` if any attribute is currently active. - pub(crate) const fn is_active(&self) -> bool { + #[must_use] + pub const fn is_active(&self) -> bool { self.bold || self.italic || self.underline @@ -102,7 +111,7 @@ impl AnsiState { /// Returns `true` when the escape resets all attributes or sets/clears the /// background — the signal a default-background overlay uses to know it /// must re-assert its fill after the escape is forwarded. - pub(crate) fn update(&mut self, esc: &str) -> bool { + pub fn update(&mut self, esc: &str) -> bool { let Some(params) = esc.strip_prefix("\x1b[").and_then(|s| s.strip_suffix('m')) else { return false; }; @@ -165,7 +174,7 @@ impl AnsiState { } /// Update state by scanning all ANSI escape sequences in `s`. - pub(crate) fn update_from_str(&mut self, s: &str) { + pub fn update_from_str(&mut self, s: &str) { for segment in segments(s) { if let Segment::Escape(esc) = segment { let _affects_background = self.update(esc); @@ -174,7 +183,8 @@ impl AnsiState { } /// Builds a string that re-activates all currently active attributes. - pub(crate) fn restore_sequence(&self) -> String { + #[must_use] + pub fn restore_sequence(&self) -> String { let mut s = String::new(); if self.bold { s.push_str(BOLD_START); @@ -232,6 +242,7 @@ fn consume_color<'a, I: Iterator>(prefix: &str, tokens: &mut I) /// SGR is the only family [`AnsiState`] tracks, so this doubles as the test for /// whether an escape's effect can be closed with [`RESET`] or re-opened after a /// line break. +#[must_use] pub fn is_sgr(esc: &str) -> bool { esc.starts_with("\x1b[") && esc.ends_with('m') } @@ -250,10 +261,10 @@ pub enum Segment<'a> { /// Split `s` into visible-text runs and ANSI escape sequences. /// /// An escape sequence runs from `\x1b` through the first ASCII letter or `~` — -/// sufficient for the SGR/CSI sequences this crate emits and consumes. -/// This is the single tokenizer for every escape-aware routine in the crate -/// (width computation, state tracking, table wrapping), so the termination rule -/// cannot drift between call sites. +/// sufficient for the SGR/CSI sequences JP emits and consumes. +/// This is the single tokenizer every escape-aware routine goes through, so the +/// termination rule cannot drift between call sites. +#[must_use] pub const fn segments(s: &str) -> Segments<'_> { Segments { rest: s } } @@ -328,6 +339,7 @@ fn osc_terminator_end(body: &str) -> Option { /// Grapheme cluster boundaries are a property of this text, not of the escape /// separated runs it was built from, so anything measuring or cutting on /// cluster boundaries has to work from here. +#[must_use] pub fn visible_text(s: &str) -> String { let mut plain = String::new(); for segment in segments(s) { @@ -343,6 +355,7 @@ pub fn visible_text(s: &str) -> String { /// A tab counts as a single column. /// Use [`advance_column`] where the resulting cursor position matters, since a /// tab moves the cursor to the next tab stop instead. +#[must_use] pub fn visual_width(s: &str) -> usize { visible_text(s).width() } @@ -353,6 +366,7 @@ pub fn visual_width(s: &str) -> usize { /// of [`TAB_STOP`], and a carriage return returns to column 0 — the positions /// the display actually arrives at, so text padded to a fixed column lands /// there instead of overshooting. +#[must_use] pub fn advance_column(column: usize, s: &str) -> usize { let plain = visible_text(s); let mut column = column; diff --git a/crates/jp_md/src/ansi_tests.rs b/crates/jp_term/src/ansi_tests.rs similarity index 100% rename from crates/jp_md/src/ansi_tests.rs rename to crates/jp_term/src/ansi_tests.rs diff --git a/crates/jp_term/src/background.rs b/crates/jp_term/src/background.rs new file mode 100644 index 000000000..be8b03fba --- /dev/null +++ b/crates/jp_term/src/background.rs @@ -0,0 +1,58 @@ +//! A background colour applied to a region of terminal output. +//! +//! A region background is the fill behind a run of rows — the shading a +//! reasoning block sits in, the tint a status row is drawn against. +//! It is described by two things: the SGR parameter that sets the colour, and +//! how far along each row the colour extends. +//! +//! [`line_fill`] is the one place that second question is answered, so every +//! writer that maintains a background agrees on what a filled row looks like. + +use std::borrow::Cow; + +/// How a default background colour fills each line. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BackgroundFill { + /// Fill to the last visible character on the line. + Content, + + /// Fill to a fixed column width (padding with spaces if needed). + Column(usize), + + /// Fill to the end of the terminal window via `\x1b[K`. + Terminal, +} + +/// A default background colour applied to all content, with a fill mode +/// controlling how far it extends on each line. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DefaultBackground { + /// SGR background parameter, e.g. `"48;5;236"` or `"48;2;80;73;69"`. + pub param: String, + + /// How far the background extends on each line. + pub fill: BackgroundFill, +} + +/// The text that extends a background from `column` to the end of the line. +/// +/// The one place [`BackgroundFill`] is interpreted. +/// Every writer that maintains a region background consults this so the three +/// modes cannot drift apart: `Content` adds nothing, `Terminal` defers to the +/// terminal's erase-to-end-of-line, and `Column` pads with real spaces — the +/// only form a host that lays out its own sub-window (an `fzf` preview pane) +/// renders, since it does not implement the erase. +/// +/// The caller is responsible for having the background active before writing +/// the result. +#[must_use] +pub fn line_fill(fill: BackgroundFill, column: usize) -> Cow<'static, str> { + match fill { + BackgroundFill::Content => Cow::Borrowed(""), + BackgroundFill::Terminal => Cow::Borrowed("\x1b[K"), + BackgroundFill::Column(target) => match target.saturating_sub(column) { + 0 => Cow::Borrowed(""), + pad => Cow::Owned(" ".repeat(pad)), + }, + } +} diff --git a/crates/jp_term/src/lib.rs b/crates/jp_term/src/lib.rs index 4f79c0fb8..a7c90f05e 100644 --- a/crates/jp_term/src/lib.rs +++ b/crates/jp_term/src/lib.rs @@ -1,5 +1,8 @@ #![allow(clippy::print_stdout, clippy::print_stderr)] +pub mod ansi; +pub mod background; pub mod osc; +pub mod shade; pub mod table; pub mod width; diff --git a/crates/jp_md/src/shade.rs b/crates/jp_term/src/shade.rs similarity index 96% rename from crates/jp_md/src/shade.rs rename to crates/jp_term/src/shade.rs index e97b7e73f..1ec26fa57 100644 --- a/crates/jp_md/src/shade.rs +++ b/crates/jp_term/src/shade.rs @@ -9,18 +9,15 @@ //! no row past the region is painted. //! [`shade`] is the buffer-at-a-time convenience built on the same core. //! -//! Unlike the line-oriented [`apply_line_background`], the writer holds its -//! state across `write_str` calls, so it can keep `B` active across cursor -//! rewrites and escape sequences split between writes — the cases a pure -//! per-line transform cannot express. -//! -//! [`apply_line_background`]: crate::format::apply_line_background +//! The writer holds its state across `write_str` calls, so `B` survives a +//! cursor rewrite or an escape sequence split between two writes — the cases a +//! per-line transform over a finished string cannot express. use std::fmt::{self, Write}; use crate::{ ansi::{self, AnsiState, Segment, is_sgr}, - format::{self, BackgroundFill, DefaultBackground}, + background::{BackgroundFill, DefaultBackground, line_fill}, }; /// Wraps a writer and maintains a default-background invariant across the byte @@ -199,7 +196,7 @@ impl ShadedWriter { /// Fill the rest of the current line with the active background, ahead of a /// newline. fn fill_line(&mut self) -> fmt::Result { - let fill = format::line_fill(self.fill, self.column); + let fill = line_fill(self.fill, self.column); if fill.is_empty() { return Ok(()); } diff --git a/crates/jp_md/src/shade_tests.rs b/crates/jp_term/src/shade_tests.rs similarity index 100% rename from crates/jp_md/src/shade_tests.rs rename to crates/jp_term/src/shade_tests.rs diff --git a/docs/rfd/084-configurable-markdown-element-coloring.md b/docs/rfd/084-configurable-markdown-element-coloring.md index d8cbbecad..0dbc98723 100644 --- a/docs/rfd/084-configurable-markdown-element-coloring.md +++ b/docs/rfd/084-configurable-markdown-element-coloring.md @@ -369,7 +369,7 @@ build themes against it. ### Renderer changes -`AnsiState` (`crates/jp_md/src/ansi.rs`) grows an `intensity: Intensity` field +`AnsiState` (`crates/jp_term/src/ansi.rs`) grows an `intensity: Intensity` field in place of its existing `bold: bool`. `restore_sequence` re-emits SGR 1 or SGR 2 as appropriate after any SGR 22 transition, so wrap-break and pop-style restoration handle dim with the same @@ -778,7 +778,7 @@ Depends on Phase 2. This RFD extends that renderer's styling surface. - `jp_md::render::TerminalFormatter` — the AST walker whose hard-coded SGR calls this RFD replaces. -- `jp_md::ansi::AnsiState` — the existing state-tracking primitive that the +- `jp_term::ansi::AnsiState` — the existing state-tracking primitive that the proposed writer stack builds on. - `termimad`'s `MadSkin` — referenced as schema inspiration; not adopted. diff --git a/docs/rfd/091-printer-owned-status-region.md b/docs/rfd/091-printer-owned-status-region.md index 4ac4a4d13..2fa545b67 100644 --- a/docs/rfd/091-printer-owned-status-region.md +++ b/docs/rfd/091-printer-owned-status-region.md @@ -273,7 +273,7 @@ A line that leaves an attribute open is terminated with a reset, so child state cannot bleed into JP's own chrome below it. This is the policy `jp_md::table` already applies when truncating cells (retain -SGR, drop the rest, close with a reset), and `jp_md::ansi::is_sgr` is the same +SGR, drop the rest, close with a reset), and `jp_term::ansi::is_sgr` is the same predicate. In `jp_printer` it is a second policy over the existing `vte` parser that backs `AnsiStripper` — same crate, same parser, no new dependency. @@ -347,7 +347,7 @@ temp and progress rows as part of it: while a reasoning region with background produced by cursor-relative rewrites, and including the `\x1b[K` that erases them, which fills with whatever background is active when it runs. `ToolRenderer` holds that invariant today by routing its writes through -`jp_md::shade::ShadedWriter`. +`jp_term::shade::ShadedWriter`. A worker that draws and erases those rows itself, knowing nothing about the reasoning region, would punch an unshaded hole in the middle of a shaded one — the exact gap RFD 095 closed. @@ -1068,12 +1068,12 @@ contracts commits to. replaces. - `crates/jp_cli/src/cmd/query/stream/retry.rs` — `notify` and `clear_line`, the ninth hand-rolled mechanism. -- `crates/jp_md/src/shade.rs` — `ShadedWriter`, which holds the background +- `crates/jp_term/src/shade.rs` — `ShadedWriter`, which holds the background invariant for tool chrome today. - `crates/jp_printer/src/printer.rs` — the worker loop this RFD extends. - `crates/jp_printer/src/ansi.rs` — the `vte`-based `AnsiStripper` the SGR allowlist extends. -- `crates/jp_md/src/ansi.rs` — `is_sgr`, the predicate the allowlist reuses, +- `crates/jp_term/src/ansi.rs` — `is_sgr`, the predicate the allowlist reuses, and the retain-SGR-drop-the-rest precedent in `jp_md/src/table.rs`. - `crates/jp_mcp/src/client.rs` — `spawn_stderr_forwarder`, the stderr ring buffer, and `StartupSet`.