Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,763 @@
|
||||
use super::{
|
||||
BlockLocation, LayOutArgs, layout_mermaid_diagram_block, layout_table_block, layout_text_block,
|
||||
};
|
||||
use crate::{
|
||||
content::{
|
||||
buffer::{StyledBufferRun, StyledTextBlock},
|
||||
edit::{ParsedUrl, highlight_urls, resolve_asset_source_relative_to_directory},
|
||||
mermaid_diagram::{mermaid_asset_source, mermaid_diagram_layout},
|
||||
text::{BufferBlockStyle, CodeBlockType, TextStylesWithMetadata},
|
||||
},
|
||||
render::{
|
||||
layout::{TextLayout, add_link_to_style_and_font, markdown_inline_to_text_and_style_runs},
|
||||
model::{BlockItem, test_utils::TEST_STYLES},
|
||||
},
|
||||
};
|
||||
use std::path::Path;
|
||||
use string_offset::CharOffset;
|
||||
use warp_core::features::FeatureFlag;
|
||||
use warpui::{
|
||||
App, SingletonEntity,
|
||||
assets::asset_cache::{AssetCache, AssetSource, AssetState},
|
||||
fonts::{Properties, Style, Weight},
|
||||
image_cache::ImageType,
|
||||
text_layout::{LayoutCache, StyleAndFont, TextStyle},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_highlight_urls() {
|
||||
let mut test_styled_buffer_runs = vec![
|
||||
StyledBufferRun {
|
||||
run: "https:".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default().bold(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "//".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default().italic(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "google.com".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
highlight_urls(&test_styled_buffer_runs),
|
||||
[ParsedUrl {
|
||||
url_range: 0..18,
|
||||
link: "https://google.com".to_string()
|
||||
},]
|
||||
);
|
||||
|
||||
test_styled_buffer_runs.extend(vec![
|
||||
StyledBufferRun {
|
||||
run: " abc ".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default().bold(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "https://warp.dev".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
highlight_urls(&test_styled_buffer_runs),
|
||||
[
|
||||
ParsedUrl {
|
||||
url_range: 0..18,
|
||||
link: "https://google.com".to_string()
|
||||
},
|
||||
ParsedUrl {
|
||||
url_range: 23..39,
|
||||
link: "https://warp.dev".to_string()
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_urls_unicode() {
|
||||
let test_runs = vec![StyledBufferRun {
|
||||
run: "This (not https://example.com) is a 🔥 link about a 🇨🇦 🏡:\u{a0}https://warp.dev"
|
||||
.to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
}];
|
||||
assert_eq!(
|
||||
highlight_urls(&test_runs),
|
||||
[
|
||||
ParsedUrl {
|
||||
url_range: 10..29,
|
||||
link: "https://example.com".to_string()
|
||||
},
|
||||
ParsedUrl {
|
||||
url_range: 57..73,
|
||||
link: "https://warp.dev".to_string()
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_incomplete_url() {
|
||||
// Tests that we can highlight the valid range of a URL that's still being typed.
|
||||
// URLs can't end in a `.`, so the detector stops at `www`.
|
||||
let test_runs = vec![StyledBufferRun {
|
||||
run: "Word https://www. later".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
}];
|
||||
assert_eq!(
|
||||
highlight_urls(&test_runs),
|
||||
[ParsedUrl {
|
||||
url_range: 5..16,
|
||||
link: "https://www".to_string()
|
||||
},]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_links_not_auto_highlighted() {
|
||||
// Test that links whose tags look like URLs aren't auto-linked, but also that they don't
|
||||
// prevent auto-linking other URLs.
|
||||
let runs = &[
|
||||
StyledBufferRun {
|
||||
run: "first link is https://warp.dev ".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "http://example.com".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default().link("https://warp.dev".to_string()),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: " second is https://google.com".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
highlight_urls(runs),
|
||||
&[
|
||||
ParsedUrl {
|
||||
url_range: 14..30,
|
||||
link: "https://warp.dev".to_string()
|
||||
},
|
||||
ParsedUrl {
|
||||
url_range: 60..78,
|
||||
link: "https://google.com".to_string()
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_url_before_link() {
|
||||
// Test that a URL right before an actual hyperlink is still highlighted.
|
||||
let runs = &[
|
||||
StyledBufferRun {
|
||||
run: "https://example.com".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "hyperlink".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default().link("https://example.com".to_string()),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "https://warp.dev".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
highlight_urls(runs),
|
||||
vec![
|
||||
ParsedUrl {
|
||||
url_range: 0..19,
|
||||
link: "https://example.com".to_string()
|
||||
},
|
||||
ParsedUrl {
|
||||
url_range: 28..44,
|
||||
link: "https://warp.dev".to_string()
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_around_link_not_auto_highlighted() {
|
||||
// Test that text which, without the link in the middle, would be a URL is not auto-linked.
|
||||
let runs = &[
|
||||
StyledBufferRun {
|
||||
run: "ht".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "alink".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default().link("https://warp.dev".to_string()),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "tps://example.com".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
];
|
||||
|
||||
assert!(highlight_urls(runs).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_partial_url() {
|
||||
// Regression test for laying out a partially-styled autodetected URL (CLD-871).
|
||||
App::test((), |app| async move {
|
||||
let layout_cache = LayoutCache::new();
|
||||
|
||||
let runs = vec![
|
||||
StyledBufferRun {
|
||||
run: "A link: https://www.".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "example.com".to_string(),
|
||||
text_styles: TextStylesWithMetadata::default().bold(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "/path text".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::PlainText,
|
||||
},
|
||||
];
|
||||
|
||||
app.read(|ctx| {
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
|
||||
let mut line = LayOutArgs::new();
|
||||
line.highlighted_urls = highlight_urls(&runs);
|
||||
line.next_url_index = 0;
|
||||
|
||||
for run in runs.iter() {
|
||||
line.layout_run(
|
||||
&text_layout,
|
||||
run,
|
||||
&text_layout.paragraph_styles(&BufferBlockStyle::PlainText),
|
||||
);
|
||||
}
|
||||
|
||||
let family_id = TEST_STYLES.base_text.font_family;
|
||||
let base_styles =
|
||||
StyleAndFont::new(family_id, Properties::default(), TextStyle::default());
|
||||
|
||||
assert_eq!(&line.text, "A link: https://www.example.com/path text");
|
||||
assert_eq!(
|
||||
&line.style_runs,
|
||||
&[
|
||||
(0..8, base_styles),
|
||||
(8..20, add_link_to_style_and_font(base_styles)),
|
||||
(
|
||||
20..31,
|
||||
add_link_to_style_and_font(StyleAndFont::new(
|
||||
family_id,
|
||||
Properties::default().weight(Weight::Bold),
|
||||
TextStyle::default()
|
||||
))
|
||||
),
|
||||
(31..36, add_link_to_style_and_font(base_styles)),
|
||||
(36..41, base_styles)
|
||||
]
|
||||
)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_mermaid_block_uses_loaded_svg_aspect_ratio() {
|
||||
App::test((), |app| async move {
|
||||
let _flag = FeatureFlag::MarkdownMermaid.override_enabled(true);
|
||||
let content = "graph TD\nA[Start] --> B[Finish]\n";
|
||||
let asset_source = mermaid_asset_source(content);
|
||||
|
||||
let mermaid_load = app.read(|ctx| {
|
||||
let asset_cache = AssetCache::as_ref(ctx);
|
||||
match asset_cache.load_asset::<ImageType>(asset_source.clone()) {
|
||||
AssetState::Loading { handle } => handle.when_loaded(asset_cache),
|
||||
AssetState::Loaded { .. } => None,
|
||||
AssetState::Evicted => panic!("Mermaid asset should not be evicted during test"),
|
||||
AssetState::FailedToLoad(err) => {
|
||||
panic!("Mermaid asset should load successfully: {err}")
|
||||
}
|
||||
}
|
||||
});
|
||||
if let Some(future) = mermaid_load {
|
||||
future.await;
|
||||
}
|
||||
|
||||
app.read(|ctx| {
|
||||
let layout_cache = LayoutCache::new();
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
800.,
|
||||
);
|
||||
let block_style = BufferBlockStyle::CodeBlock {
|
||||
code_block_type: CodeBlockType::Mermaid,
|
||||
};
|
||||
let block = StyledTextBlock {
|
||||
block: vec![StyledBufferRun {
|
||||
run: content.to_string(),
|
||||
text_styles: TextStylesWithMetadata::default(),
|
||||
block_style: block_style.clone(),
|
||||
}],
|
||||
style: block_style.clone(),
|
||||
content_length: CharOffset::from(content.chars().count()),
|
||||
};
|
||||
let spacing = TEST_STYLES.block_spacings.from_block_style(&block_style);
|
||||
let mermaid_diagram = mermaid_diagram_layout(content, &text_layout, spacing, ctx);
|
||||
|
||||
let (item, _has_trailing_newline) = layout_mermaid_diagram_block(
|
||||
block,
|
||||
mermaid_diagram.0,
|
||||
mermaid_diagram.1,
|
||||
BlockLocation::Middle,
|
||||
false,
|
||||
)
|
||||
.expect("Mermaid layout should succeed");
|
||||
|
||||
let asset_cache = AssetCache::as_ref(ctx);
|
||||
let svg = match asset_cache.load_asset::<ImageType>(asset_source.clone()) {
|
||||
AssetState::Loaded { data } => match data.as_ref() {
|
||||
ImageType::Svg { svg } => svg.clone(),
|
||||
_ => panic!("expected loaded svg asset"),
|
||||
},
|
||||
AssetState::Loading { .. } => panic!("Mermaid asset should already be loaded"),
|
||||
AssetState::Evicted => panic!("Mermaid asset should not be evicted during test"),
|
||||
AssetState::FailedToLoad(err) => {
|
||||
panic!("Mermaid asset should load successfully: {err}")
|
||||
}
|
||||
};
|
||||
|
||||
match &item {
|
||||
BlockItem::MermaidDiagram {
|
||||
content_length,
|
||||
config,
|
||||
..
|
||||
} => {
|
||||
let intrinsic_size = svg.size();
|
||||
let expected_width = (800.
|
||||
- TEST_STYLES
|
||||
.block_spacings
|
||||
.from_block_style(&block_style)
|
||||
.x_axis_offset()
|
||||
.as_f32())
|
||||
.min(intrinsic_size.width());
|
||||
let expected_height =
|
||||
expected_width * intrinsic_size.height() / intrinsic_size.width();
|
||||
assert_eq!(*content_length, CharOffset::from(content.chars().count()));
|
||||
assert!((config.width.as_f32() - expected_width).abs() < 0.5);
|
||||
assert!((config.height.as_f32() - expected_height).abs() < 0.5);
|
||||
assert!((item.content_height().as_f32() - config.height.as_f32()).abs() < 0.5);
|
||||
assert_eq!(item.lines(), 1.into());
|
||||
assert_eq!(item.first_line_height(), config.height.as_f32());
|
||||
}
|
||||
item => panic!("expected MermaidDiagram block, got {item:?}"),
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_asset_source_relative_to_directory_uses_base_directory() {
|
||||
let asset_source =
|
||||
resolve_asset_source_relative_to_directory("diagram.png", Some(Path::new("/tmp/session")));
|
||||
|
||||
match asset_source {
|
||||
AssetSource::LocalFile { path } => {
|
||||
assert_eq!(Path::new(&path), Path::new("/tmp/session/diagram.png"));
|
||||
}
|
||||
source => panic!("expected local file asset source, got {source:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_text_block_uses_rich_table_when_flag_enabled() {
|
||||
App::test((), |app| async move {
|
||||
app.read(|ctx| {
|
||||
let _flag = FeatureFlag::MarkdownTables.override_enabled(true);
|
||||
let layout_cache = LayoutCache::new();
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
let content = "short\tmuch longer\ncell\trow\n";
|
||||
let block = StyledTextBlock {
|
||||
block: vec![StyledBufferRun {
|
||||
run: content.to_string(),
|
||||
text_styles: TextStylesWithMetadata::default(),
|
||||
block_style: BufferBlockStyle::table(Vec::new()),
|
||||
}],
|
||||
style: BufferBlockStyle::table(Vec::new()),
|
||||
content_length: CharOffset::from(content.chars().count()),
|
||||
};
|
||||
|
||||
let (item, has_trailing_newline) =
|
||||
layout_text_block(block, &text_layout, BlockLocation::Middle, false)
|
||||
.expect("table layout should succeed");
|
||||
|
||||
assert!(matches!(item, BlockItem::Table(_)));
|
||||
assert!(!has_trailing_newline);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_text_block_uses_plain_text_when_flag_disabled() {
|
||||
App::test((), |app| async move {
|
||||
app.read(|ctx| {
|
||||
let _flag = FeatureFlag::MarkdownTables.override_enabled(false);
|
||||
let layout_cache = LayoutCache::new();
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
let content = "short\tmuch longer\ncell\trow\n";
|
||||
let block = StyledTextBlock {
|
||||
block: vec![StyledBufferRun {
|
||||
run: content.to_string(),
|
||||
text_styles: TextStylesWithMetadata::default(),
|
||||
block_style: BufferBlockStyle::table(Vec::new()),
|
||||
}],
|
||||
style: BufferBlockStyle::table(Vec::new()),
|
||||
content_length: CharOffset::from(content.chars().count()),
|
||||
};
|
||||
|
||||
let (item, _has_trailing_newline) =
|
||||
layout_text_block(block, &text_layout, BlockLocation::Middle, false)
|
||||
.expect("table layout should succeed");
|
||||
|
||||
assert!(matches!(item, BlockItem::Paragraph(_)));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_table_block_caches_cell_text_frames() {
|
||||
App::test((), |app| async move {
|
||||
app.read(|ctx| {
|
||||
let layout_cache = LayoutCache::new();
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
let content = "short\tmuch longer\ncell\trow\n";
|
||||
let block = StyledTextBlock {
|
||||
block: vec![StyledBufferRun {
|
||||
run: content.to_string(),
|
||||
text_styles: TextStylesWithMetadata::default(),
|
||||
block_style: BufferBlockStyle::table(Vec::new()),
|
||||
}],
|
||||
style: BufferBlockStyle::table(Vec::new()),
|
||||
content_length: CharOffset::from(content.chars().count()),
|
||||
};
|
||||
|
||||
let table = match layout_table_block(
|
||||
block,
|
||||
&text_layout,
|
||||
TEST_STYLES
|
||||
.block_spacings
|
||||
.from_block_style(&BufferBlockStyle::table(Vec::new())),
|
||||
)
|
||||
.expect("table layout should succeed")
|
||||
{
|
||||
BlockItem::Table(table) => table,
|
||||
item => panic!("expected table block, got {item:?}"),
|
||||
};
|
||||
|
||||
assert_eq!(table.cell_text_frames.len(), 2);
|
||||
assert_eq!(table.cell_text_frames[0].len(), 2);
|
||||
assert_eq!(table.cell_text_frames[1].len(), 2);
|
||||
assert_eq!(table.cell_layouts.len(), 2);
|
||||
assert_eq!(table.cell_layouts[0].len(), 2);
|
||||
assert_eq!(table.cell_layouts[1].len(), 2);
|
||||
assert!(
|
||||
table.cell_text_frames[0][1].max_width()
|
||||
<= table.column_widths[1].as_f32() - table.config.style.cell_padding * 2.0
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_table_block_clamps_cell_width_to_max() {
|
||||
App::test((), |app| async move {
|
||||
app.read(|ctx| {
|
||||
let layout_cache = LayoutCache::new();
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
// One long cell in the second column that would otherwise blow out the column
|
||||
// width. The paragraph has no natural break points within the first 500px, so the
|
||||
// cell must rely on the per-cell max width cap to keep the column size bounded.
|
||||
let long_content = "word ".repeat(400);
|
||||
let content = format!("short\t{long_content}\ncell\trow\n");
|
||||
let block = StyledTextBlock {
|
||||
block: vec![StyledBufferRun {
|
||||
run: content.clone(),
|
||||
text_styles: TextStylesWithMetadata::default(),
|
||||
block_style: BufferBlockStyle::table(Vec::new()),
|
||||
}],
|
||||
style: BufferBlockStyle::table(Vec::new()),
|
||||
content_length: CharOffset::from(content.chars().count()),
|
||||
};
|
||||
|
||||
let table = match layout_table_block(
|
||||
block,
|
||||
&text_layout,
|
||||
TEST_STYLES
|
||||
.block_spacings
|
||||
.from_block_style(&BufferBlockStyle::table(Vec::new())),
|
||||
)
|
||||
.expect("table layout should succeed")
|
||||
{
|
||||
BlockItem::Table(table) => table,
|
||||
item => panic!("expected table block, got {item:?}"),
|
||||
};
|
||||
|
||||
let cell_padding = table.config.style.cell_padding;
|
||||
let expected_max_cell_width = cell_padding * 2.0 + 500.0;
|
||||
assert!(
|
||||
table.column_widths[1].as_f32() <= expected_max_cell_width + f32::EPSILON,
|
||||
"long cell column width {} should be clamped to {}",
|
||||
table.column_widths[1].as_f32(),
|
||||
expected_max_cell_width,
|
||||
);
|
||||
// The clamped cell frame must be laid out within the clamped column's content
|
||||
// width so soft-wrap can occur inside the cell at paint time.
|
||||
let max_content_width =
|
||||
table.column_widths[1].as_f32() - table.config.style.cell_padding * 2.0;
|
||||
assert!(
|
||||
table.cell_text_frames[0][1].max_width() <= max_content_width + f32::EPSILON,
|
||||
"long cell frame max width {} should fit within clamped content width {}",
|
||||
table.cell_text_frames[0][1].max_width(),
|
||||
max_content_width,
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_inline_style_runs_apply_header_bold_default() {
|
||||
App::test((), |app| async move {
|
||||
let layout_cache = LayoutCache::new();
|
||||
app.read(|ctx| {
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
let mut header_style =
|
||||
text_layout.paragraph_styles(&BufferBlockStyle::table(Vec::new()));
|
||||
header_style.font_weight = Weight::Bold;
|
||||
let table = crate::content::text::table_from_internal_format_with_inline_markdown(
|
||||
"Header\tValue\nText\tCell\n",
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let layout_input = markdown_inline_to_text_and_style_runs(
|
||||
&table.headers[0],
|
||||
&header_style,
|
||||
Some(header_style.text_color),
|
||||
Some(TEST_STYLES.table_style.header_background),
|
||||
);
|
||||
|
||||
assert_eq!(layout_input.text, "Header");
|
||||
assert!(!layout_input.style_runs.is_empty());
|
||||
assert!(
|
||||
layout_input
|
||||
.style_runs
|
||||
.iter()
|
||||
.all(|(_, style)| style.properties.weight == Weight::Bold)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_inline_style_runs_preserve_markdown_cell_styles() {
|
||||
App::test((), |app| async move {
|
||||
let layout_cache = LayoutCache::new();
|
||||
app.read(|ctx| {
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
let body_style = text_layout.paragraph_styles(&BufferBlockStyle::table(Vec::new()));
|
||||
let table = crate::content::text::table_from_internal_format_with_inline_markdown(
|
||||
"Header\tValue\nText\t**Bold** *Italic* [Link](https://warp.dev) `code`\n",
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let layout_input = markdown_inline_to_text_and_style_runs(
|
||||
&table.rows[0][1],
|
||||
&body_style,
|
||||
Some(body_style.text_color),
|
||||
Some(TEST_STYLES.table_style.cell_background),
|
||||
);
|
||||
|
||||
assert_eq!(layout_input.text, "Bold Italic Link code");
|
||||
assert_eq!(layout_input.style_runs.len(), 7);
|
||||
|
||||
assert_eq!(layout_input.style_runs[0].0, 0..4);
|
||||
assert_eq!(layout_input.style_runs[0].1.properties.weight, Weight::Bold);
|
||||
|
||||
assert_eq!(layout_input.style_runs[2].0, 5..11);
|
||||
assert_eq!(layout_input.style_runs[2].1.properties.style, Style::Italic);
|
||||
|
||||
assert_eq!(layout_input.style_runs[4].0, 12..16);
|
||||
assert!(
|
||||
layout_input.style_runs[4]
|
||||
.1
|
||||
.style
|
||||
.foreground_color
|
||||
.is_some()
|
||||
);
|
||||
assert!(layout_input.style_runs[4].1.style.underline_color.is_some());
|
||||
|
||||
assert_eq!(layout_input.style_runs[6].0, 17..21);
|
||||
assert!(
|
||||
layout_input.style_runs[6]
|
||||
.1
|
||||
.style
|
||||
.background_color
|
||||
.is_some()
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_code_block_urls() {
|
||||
// Regression test for laying out URLs in a code block, which contains multiple lines.
|
||||
App::test((), |app| async move {
|
||||
let runs = vec![
|
||||
StyledBufferRun {
|
||||
run: "curl -o myfile.txt http://example.com/myfile.txt\n".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::CodeBlock {
|
||||
code_block_type: CodeBlockType::Shell,
|
||||
},
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "vim myfile.txt\n".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::CodeBlock {
|
||||
code_block_type: CodeBlockType::Shell,
|
||||
},
|
||||
},
|
||||
StyledBufferRun {
|
||||
run: "rsync myfile.txt ssh://user@server.com\n".to_string(),
|
||||
text_styles: Default::default(),
|
||||
block_style: BufferBlockStyle::CodeBlock {
|
||||
code_block_type: CodeBlockType::Shell,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
app.read(|ctx| {
|
||||
let layout_cache = LayoutCache::new();
|
||||
let text_layout = TextLayout::new(
|
||||
&layout_cache,
|
||||
ctx.font_cache().text_layout_system(),
|
||||
&TEST_STYLES,
|
||||
f32::MAX,
|
||||
);
|
||||
let paragraph_styles = text_layout.paragraph_styles(&BufferBlockStyle::CodeBlock {
|
||||
code_block_type: CodeBlockType::Shell,
|
||||
});
|
||||
let family_id = TEST_STYLES.code_text.font_family;
|
||||
let base_styles =
|
||||
StyleAndFont::new(family_id, Properties::default(), TextStyle::default());
|
||||
|
||||
let mut line = LayOutArgs::new();
|
||||
line.highlighted_urls = highlight_urls(&runs);
|
||||
line.next_url_index = 0;
|
||||
|
||||
// First, make sure that we detected the URLs correctly.
|
||||
assert_eq!(
|
||||
&line.highlighted_urls,
|
||||
&[
|
||||
ParsedUrl {
|
||||
url_range: 19..48,
|
||||
link: "http://example.com/myfile.txt".to_string()
|
||||
},
|
||||
ParsedUrl {
|
||||
// URL offsets count painted characters, not newlines.
|
||||
url_range: 79..100,
|
||||
link: "ssh://user@server.com".to_string()
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// Lay out each line of code 1 by 1 to verify the intermediate state.
|
||||
|
||||
assert!(line.layout_run(&text_layout, &runs[0], ¶graph_styles));
|
||||
assert_eq!(
|
||||
&line.text,
|
||||
"curl -o myfile.txt http://example.com/myfile.txt"
|
||||
);
|
||||
assert_eq!(
|
||||
&line.style_runs,
|
||||
&[
|
||||
(0..19, base_styles),
|
||||
(19..48, add_link_to_style_and_font(base_styles)),
|
||||
]
|
||||
);
|
||||
|
||||
line.reset_for_newline();
|
||||
assert!(line.layout_run(&text_layout, &runs[1], ¶graph_styles));
|
||||
assert_eq!(&line.text, "vim myfile.txt");
|
||||
assert_eq!(&line.style_runs, &[(0..14, base_styles)]);
|
||||
|
||||
line.reset_for_newline();
|
||||
assert!(line.layout_run(&text_layout, &runs[2], ¶graph_styles));
|
||||
assert_eq!(&line.text, "rsync myfile.txt ssh://user@server.com");
|
||||
assert_eq!(
|
||||
&line.style_runs,
|
||||
&[
|
||||
(0..17, base_styles),
|
||||
(17..38, add_link_to_style_and_font(base_styles)),
|
||||
]
|
||||
);
|
||||
});
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user