Streaming markdown (#1920)

We wait until we have an entire newline, then format it with markdown and stream in to the UI. This reduces time to first token but is the right thing to do with our current rendering model IMO. Also lets us add word wrapping!
This commit is contained in:
easong-openai
2025-08-07 18:26:47 -07:00
committed by GitHub
parent fa0051190b
commit 2b7139859e
14 changed files with 1940 additions and 481 deletions

View File

@@ -75,7 +75,7 @@ impl TestScenario {
}
#[test]
fn hist_001_basic_insertion_no_wrap() {
fn basic_insertion_no_wrap() {
// Screen of 20x6; viewport is the last row (height=1 at y=5)
let area = Rect::new(0, 5, 20, 1);
let mut scenario = TestScenario::new(20, 6, area);
@@ -97,7 +97,7 @@ fn hist_001_basic_insertion_no_wrap() {
}
#[test]
fn hist_002_long_token_wraps() {
fn long_token_wraps() {
let area = Rect::new(0, 5, 20, 1);
let mut scenario = TestScenario::new(20, 6, area);
@@ -130,7 +130,7 @@ fn hist_002_long_token_wraps() {
}
#[test]
fn hist_003_emoji_and_cjk() {
fn emoji_and_cjk() {
let area = Rect::new(0, 5, 20, 1);
let mut scenario = TestScenario::new(20, 6, area);
@@ -148,7 +148,7 @@ fn hist_003_emoji_and_cjk() {
}
#[test]
fn hist_004_mixed_ansi_spans() {
fn mixed_ansi_spans() {
let area = Rect::new(0, 5, 20, 1);
let mut scenario = TestScenario::new(20, 6, area);
@@ -162,7 +162,7 @@ fn hist_004_mixed_ansi_spans() {
}
#[test]
fn hist_006_cursor_restoration() {
fn cursor_restoration() {
let area = Rect::new(0, 5, 20, 1);
let mut scenario = TestScenario::new(20, 6, area);
@@ -182,7 +182,39 @@ fn hist_006_cursor_restoration() {
}
#[test]
fn hist_005_pre_scroll_region_down() {
fn word_wrap_no_mid_word_split() {
// Screen of 40x10; viewport is the last row
let area = Rect::new(0, 9, 40, 1);
let mut scenario = TestScenario::new(40, 10, area);
let sample = "Years passed, and Willowmere thrived in peace and friendship. Miras herb garden flourished with both ordinary and enchanted plants, and travelers spoke of the kindness of the woman who tended them.";
let buf = scenario.run_insert(vec![Line::from(sample)]);
let rows = scenario.screen_rows_from_bytes(&buf);
let joined = rows.join("\n");
assert!(
!joined.contains("bo\nth"),
"word 'both' should not be split across lines:\n{joined}"
);
}
#[test]
fn em_dash_and_space_word_wrap() {
// Repro from report: ensure we break before "inside", not mid-word.
let area = Rect::new(0, 9, 40, 1);
let mut scenario = TestScenario::new(40, 10, area);
let sample = "Mara found an old key on the shore. Curious, she opened a tarnished box half-buried in sand—and inside lay a single, glowing seed.";
let buf = scenario.run_insert(vec![Line::from(sample)]);
let rows = scenario.screen_rows_from_bytes(&buf);
let joined = rows.join("\n");
assert!(
!joined.contains("insi\nde"),
"word 'inside' should not be split across lines:\n{joined}"
);
}
#[test]
fn pre_scroll_region_down() {
// Viewport not at bottom: y=3 (0-based), height=1
let area = Rect::new(0, 3, 20, 1);
let mut scenario = TestScenario::new(20, 6, area);