0 gutter decorations, task list highlighting, fix line_decors init
This commit is contained in:
parent
81fdb9146c
commit
9c4c359056
|
|
@ -16,7 +16,7 @@ use iced_widget::text_input;
|
||||||
use iced_wgpu::core::text::highlighter::Format;
|
use iced_wgpu::core::text::highlighter::Format;
|
||||||
use iced_wgpu::core::widget::Id as WidgetId;
|
use iced_wgpu::core::widget::Id as WidgetId;
|
||||||
use crate::palette;
|
use crate::palette;
|
||||||
use crate::syntax::{self, SyntaxHighlighter, SyntaxSettings};
|
use crate::syntax::{self, SyntaxHighlighter, SyntaxSettings, LineDecor, compute_line_decors};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
@ -754,6 +754,7 @@ impl EditorState {
|
||||||
let text = self.content.text();
|
let text = self.content.text();
|
||||||
let result_mask: Vec<bool> = text.lines().map(|l| is_result_line(l)).collect();
|
let result_mask: Vec<bool> = text.lines().map(|l| is_result_line(l)).collect();
|
||||||
let source_line_count = result_mask.iter().filter(|r| !**r).count();
|
let source_line_count = result_mask.iter().filter(|r| !**r).count();
|
||||||
|
let decors = compute_line_decors(&text);
|
||||||
let gutter = Gutter {
|
let gutter = Gutter {
|
||||||
line_count: self.content.line_count(),
|
line_count: self.content.line_count(),
|
||||||
source_line_count,
|
source_line_count,
|
||||||
|
|
@ -762,6 +763,7 @@ impl EditorState {
|
||||||
cursor_line: self.content.cursor().position.line,
|
cursor_line: self.content.cursor().position.line,
|
||||||
top_pad,
|
top_pad,
|
||||||
result_mask,
|
result_mask,
|
||||||
|
line_decors: decors,
|
||||||
};
|
};
|
||||||
let gw = gutter.gutter_width();
|
let gw = gutter.gutter_width();
|
||||||
|
|
||||||
|
|
@ -941,6 +943,7 @@ struct Gutter {
|
||||||
cursor_line: usize,
|
cursor_line: usize,
|
||||||
top_pad: f32,
|
top_pad: f32,
|
||||||
result_mask: Vec<bool>,
|
result_mask: Vec<bool>,
|
||||||
|
line_decors: Vec<LineDecor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gutter {
|
impl Gutter {
|
||||||
|
|
@ -997,12 +1000,47 @@ impl canvas::Program<Message, Theme, iced_wgpu::Renderer> for Gutter {
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let decor = if line_idx < self.line_decors.len() {
|
||||||
|
self.line_decors[line_idx]
|
||||||
|
} else {
|
||||||
|
LineDecor::None
|
||||||
|
};
|
||||||
|
let p = palette::current();
|
||||||
|
|
||||||
|
match decor {
|
||||||
|
LineDecor::CodeBlock | LineDecor::FenceMarker => {
|
||||||
|
frame.fill_rectangle(
|
||||||
|
Point::new(0.0, y),
|
||||||
|
iced_wgpu::core::Size::new(gw, lh),
|
||||||
|
Color { a: 0.15, ..p.surface2 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LineDecor::Blockquote => {
|
||||||
|
frame.fill_rectangle(
|
||||||
|
Point::new(gw - 3.0, y),
|
||||||
|
iced_wgpu::core::Size::new(3.0, lh),
|
||||||
|
p.lavender,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LineDecor::HorizontalRule => {
|
||||||
|
let mid_y = y + lh / 2.0;
|
||||||
|
let path = canvas::Path::line(
|
||||||
|
Point::new(4.0, mid_y),
|
||||||
|
Point::new(gw - 4.0, mid_y),
|
||||||
|
);
|
||||||
|
frame.stroke(&path, canvas::Stroke::default()
|
||||||
|
.with_width(1.0)
|
||||||
|
.with_color(p.overlay1));
|
||||||
|
}
|
||||||
|
LineDecor::None => {}
|
||||||
|
}
|
||||||
|
|
||||||
let is_result = line_idx < self.result_mask.len() && self.result_mask[line_idx];
|
let is_result = line_idx < self.result_mask.len() && self.result_mask[line_idx];
|
||||||
if is_result {
|
if is_result {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
source_num += 1;
|
source_num += 1;
|
||||||
let p = palette::current();
|
|
||||||
let color = if line_idx == self.cursor_line {
|
let color = if line_idx == self.cursor_line {
|
||||||
p.overlay1
|
p.overlay1
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ const MD_LIST_MARKER: u8 = 38;
|
||||||
const MD_FENCE_MARKER: u8 = 39;
|
const MD_FENCE_MARKER: u8 = 39;
|
||||||
const MD_CODE_BLOCK: u8 = 40;
|
const MD_CODE_BLOCK: u8 = 40;
|
||||||
const MD_HR: u8 = 41;
|
const MD_HR: u8 = 41;
|
||||||
|
const MD_TASK_OPEN: u8 = 42;
|
||||||
|
const MD_TASK_DONE: u8 = 43;
|
||||||
|
const MD_BOLD_ITALIC: u8 = 44;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct SyntaxSettings {
|
pub struct SyntaxSettings {
|
||||||
|
|
@ -39,6 +42,15 @@ pub struct SyntaxHighlight {
|
||||||
pub kind: u8,
|
pub kind: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum LineDecor {
|
||||||
|
None,
|
||||||
|
CodeBlock,
|
||||||
|
Blockquote,
|
||||||
|
HorizontalRule,
|
||||||
|
FenceMarker,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SyntaxHighlighter {
|
pub struct SyntaxHighlighter {
|
||||||
lang: String,
|
lang: String,
|
||||||
spans: Vec<HighlightSpan>,
|
spans: Vec<HighlightSpan>,
|
||||||
|
|
@ -46,6 +58,7 @@ pub struct SyntaxHighlighter {
|
||||||
line_kinds: Vec<LineKind>,
|
line_kinds: Vec<LineKind>,
|
||||||
in_fenced_code: bool,
|
in_fenced_code: bool,
|
||||||
current_line: usize,
|
current_line: usize,
|
||||||
|
line_decors: Vec<LineDecor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SyntaxHighlighter {
|
impl SyntaxHighlighter {
|
||||||
|
|
@ -59,6 +72,31 @@ impl SyntaxHighlighter {
|
||||||
}
|
}
|
||||||
let classified = classify_document(source);
|
let classified = classify_document(source);
|
||||||
self.line_kinds = classified.into_iter().map(|cl| cl.kind).collect();
|
self.line_kinds = classified.into_iter().map(|cl| cl.kind).collect();
|
||||||
|
|
||||||
|
self.line_decors.clear();
|
||||||
|
let mut in_fence = false;
|
||||||
|
for (i, raw_line) in source.split('\n').enumerate() {
|
||||||
|
let is_md = i < self.line_kinds.len() && self.line_kinds[i] == LineKind::Markdown;
|
||||||
|
if is_md {
|
||||||
|
let trimmed = raw_line.trim_start();
|
||||||
|
if trimmed.starts_with("```") {
|
||||||
|
in_fence = !in_fence;
|
||||||
|
self.line_decors.push(LineDecor::FenceMarker);
|
||||||
|
} else if in_fence {
|
||||||
|
self.line_decors.push(LineDecor::CodeBlock);
|
||||||
|
} else if is_horizontal_rule(trimmed) {
|
||||||
|
self.line_decors.push(LineDecor::HorizontalRule);
|
||||||
|
} else if trimmed.starts_with("> ") || trimmed == ">" {
|
||||||
|
self.line_decors.push(LineDecor::Blockquote);
|
||||||
|
} else {
|
||||||
|
self.line_decors.push(LineDecor::None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if in_fence { in_fence = false; }
|
||||||
|
self.line_decors.push(LineDecor::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.in_fenced_code = false;
|
self.in_fenced_code = false;
|
||||||
self.current_line = 0;
|
self.current_line = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -104,10 +142,15 @@ impl SyntaxHighlighter {
|
||||||
return spans;
|
return spans;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(marker_len) = list_marker_len(trimmed) {
|
if let Some(list_info) = list_marker_info(trimmed) {
|
||||||
|
let (marker_len, marker_kind) = match list_info {
|
||||||
|
ListKind::TaskOpen(n) => (n, MD_TASK_OPEN),
|
||||||
|
ListKind::TaskDone(n) => (n, MD_TASK_DONE),
|
||||||
|
ListKind::Plain(n) => (n, MD_LIST_MARKER),
|
||||||
|
};
|
||||||
let marker_end = leading + marker_len;
|
let marker_end = leading + marker_len;
|
||||||
let mut spans = vec![
|
let mut spans = vec![
|
||||||
(0..marker_end, SyntaxHighlight { kind: MD_LIST_MARKER }),
|
(0..marker_end, SyntaxHighlight { kind: marker_kind }),
|
||||||
];
|
];
|
||||||
if marker_end < line.len() {
|
if marker_end < line.len() {
|
||||||
let content = &line[marker_end..];
|
let content = &line[marker_end..];
|
||||||
|
|
@ -144,22 +187,32 @@ fn is_horizontal_rule(trimmed: &str) -> bool {
|
||||||
trimmed.bytes().all(|b| b == first || b == b' ')
|
trimmed.bytes().all(|b| b == first || b == b' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_marker_len(trimmed: &str) -> Option<usize> {
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum ListKind {
|
||||||
|
Plain(usize),
|
||||||
|
TaskOpen(usize),
|
||||||
|
TaskDone(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_marker_info(trimmed: &str) -> Option<ListKind> {
|
||||||
let bytes = trimmed.as_bytes();
|
let bytes = trimmed.as_bytes();
|
||||||
if bytes.is_empty() { return None; }
|
if bytes.is_empty() { return None; }
|
||||||
|
|
||||||
if matches!(bytes[0], b'-' | b'*' | b'+') && bytes.get(1) == Some(&b' ') {
|
if matches!(bytes[0], b'-' | b'*' | b'+') && bytes.get(1) == Some(&b' ') {
|
||||||
if trimmed.starts_with("- [ ] ") || trimmed.starts_with("- [x] ") || trimmed.starts_with("- [X] ") {
|
if trimmed.starts_with("- [ ] ") {
|
||||||
return Some(6);
|
return Some(ListKind::TaskOpen(6));
|
||||||
}
|
}
|
||||||
return Some(2);
|
if trimmed.starts_with("- [x] ") || trimmed.starts_with("- [X] ") {
|
||||||
|
return Some(ListKind::TaskDone(6));
|
||||||
|
}
|
||||||
|
return Some(ListKind::Plain(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < bytes.len() && bytes[i].is_ascii_digit() { i += 1; }
|
while i < bytes.len() && bytes[i].is_ascii_digit() { i += 1; }
|
||||||
if i > 0 && i < bytes.len() && matches!(bytes[i], b'.' | b')') {
|
if i > 0 && i < bytes.len() && matches!(bytes[i], b'.' | b')') {
|
||||||
if bytes.get(i + 1) == Some(&b' ') {
|
if bytes.get(i + 1) == Some(&b' ') {
|
||||||
return Some(i + 2);
|
return Some(ListKind::Plain(i + 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
@ -172,11 +225,36 @@ fn parse_inline(text: &str, base: usize) -> Vec<(Range<usize>, SyntaxHighlight)>
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
||||||
while i < len {
|
while i < len {
|
||||||
|
if bytes[i] == b'\\' && i + 1 < len && is_md_punctuation(bytes[i + 1]) {
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i + 2 < len && bytes[i] == b'*' && bytes[i + 1] == b'*' && bytes[i + 2] == b'*' {
|
||||||
|
if let Some(end) = find_triple_star(bytes, i + 3) {
|
||||||
|
spans.push((base + i..base + i + 3, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
|
if i + 3 < end {
|
||||||
|
spans.push((base + i + 3..base + end, SyntaxHighlight { kind: MD_BOLD_ITALIC }));
|
||||||
|
}
|
||||||
|
spans.push((base + end..base + end + 3, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
|
i = end + 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'*' {
|
if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'*' {
|
||||||
if let Some(end) = find_closing(bytes, i + 2, b'*', b'*') {
|
if let Some(end) = find_closing(bytes, i + 2, b'*', b'*') {
|
||||||
spans.push((base + i..base + i + 2, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
spans.push((base + i..base + i + 2, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
if i + 2 < end {
|
if i + 2 < end {
|
||||||
spans.push((base + i + 2..base + end, SyntaxHighlight { kind: MD_BOLD }));
|
let inner = parse_inline(&text[i + 2..end], base + i + 2);
|
||||||
|
if inner.is_empty() {
|
||||||
|
spans.push((base + i + 2..base + end, SyntaxHighlight { kind: MD_BOLD }));
|
||||||
|
} else {
|
||||||
|
for (r, h) in inner {
|
||||||
|
let kind = if h.kind == MD_ITALIC { MD_BOLD_ITALIC } else { h.kind };
|
||||||
|
spans.push((r, SyntaxHighlight { kind }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spans.push((base + end..base + end + 2, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
spans.push((base + end..base + end + 2, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
i = end + 2;
|
i = end + 2;
|
||||||
|
|
@ -186,24 +264,27 @@ fn parse_inline(text: &str, base: usize) -> Vec<(Range<usize>, SyntaxHighlight)>
|
||||||
|
|
||||||
if bytes[i] == b'*' && (i + 1 >= len || bytes[i + 1] != b'*') {
|
if bytes[i] == b'*' && (i + 1 >= len || bytes[i + 1] != b'*') {
|
||||||
if let Some(end) = find_single_closing(bytes, i + 1, b'*') {
|
if let Some(end) = find_single_closing(bytes, i + 1, b'*') {
|
||||||
spans.push((base + i..base + i + 1, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
if end > i + 1 && bytes[end - 1] != b'*' {
|
||||||
if i + 1 < end {
|
spans.push((base + i..base + i + 1, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
spans.push((base + i + 1..base + end, SyntaxHighlight { kind: MD_ITALIC }));
|
if i + 1 < end {
|
||||||
|
spans.push((base + i + 1..base + end, SyntaxHighlight { kind: MD_ITALIC }));
|
||||||
|
}
|
||||||
|
spans.push((base + end..base + end + 1, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
|
i = end + 1;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
spans.push((base + end..base + end + 1, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
|
||||||
i = end + 1;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes[i] == b'`' {
|
if bytes[i] == b'`' {
|
||||||
if let Some(end) = find_single_closing(bytes, i + 1, b'`') {
|
let tick_count = count_backticks(bytes, i);
|
||||||
spans.push((base + i..base + i + 1, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
if let Some(end) = find_backtick_close(bytes, i + tick_count, tick_count) {
|
||||||
if i + 1 < end {
|
spans.push((base + i..base + i + tick_count, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
spans.push((base + i + 1..base + end, SyntaxHighlight { kind: MD_INLINE_CODE }));
|
if i + tick_count < end {
|
||||||
|
spans.push((base + i + tick_count..base + end, SyntaxHighlight { kind: MD_INLINE_CODE }));
|
||||||
}
|
}
|
||||||
spans.push((base + end..base + end + 1, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
spans.push((base + end..base + end + tick_count, SyntaxHighlight { kind: MD_FORMAT_MARKER }));
|
||||||
i = end + 1;
|
i = end + tick_count;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,6 +311,40 @@ fn parse_inline(text: &str, base: usize) -> Vec<(Range<usize>, SyntaxHighlight)>
|
||||||
spans
|
spans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_md_punctuation(b: u8) -> bool {
|
||||||
|
matches!(b, b'\\' | b'`' | b'*' | b'_' | b'{' | b'}' | b'[' | b']'
|
||||||
|
| b'(' | b')' | b'#' | b'+' | b'-' | b'.' | b'!' | b'|')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_triple_star(bytes: &[u8], start: usize) -> Option<usize> {
|
||||||
|
let mut i = start;
|
||||||
|
while i + 2 < bytes.len() {
|
||||||
|
if bytes[i] == b'*' && bytes[i + 1] == b'*' && bytes[i + 2] == b'*' {
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_backticks(bytes: &[u8], start: usize) -> usize {
|
||||||
|
let mut n = 0;
|
||||||
|
while start + n < bytes.len() && bytes[start + n] == b'`' { n += 1; }
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_backtick_close(bytes: &[u8], start: usize, count: usize) -> Option<usize> {
|
||||||
|
if count == 0 { return None; }
|
||||||
|
let mut i = start;
|
||||||
|
while i + count <= bytes.len() {
|
||||||
|
if count_backticks(bytes, i) == count {
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn find_closing(bytes: &[u8], start: usize, c1: u8, c2: u8) -> Option<usize> {
|
fn find_closing(bytes: &[u8], start: usize, c1: u8, c2: u8) -> Option<usize> {
|
||||||
let mut i = start;
|
let mut i = start;
|
||||||
while i + 1 < bytes.len() {
|
while i + 1 < bytes.len() {
|
||||||
|
|
@ -287,6 +402,7 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
||||||
line_kinds: Vec::new(),
|
line_kinds: Vec::new(),
|
||||||
in_fenced_code: false,
|
in_fenced_code: false,
|
||||||
current_line: 0,
|
current_line: 0,
|
||||||
|
line_decors: Vec::new(),
|
||||||
};
|
};
|
||||||
h.rebuild(&settings.source);
|
h.rebuild(&settings.source);
|
||||||
h
|
h
|
||||||
|
|
@ -412,6 +528,9 @@ pub fn highlight_color(kind: u8) -> Color {
|
||||||
MD_FENCE_MARKER => p.overlay0,
|
MD_FENCE_MARKER => p.overlay0,
|
||||||
MD_CODE_BLOCK => p.text,
|
MD_CODE_BLOCK => p.text,
|
||||||
MD_HR => p.overlay1,
|
MD_HR => p.overlay1,
|
||||||
|
MD_TASK_OPEN => p.overlay2,
|
||||||
|
MD_TASK_DONE => p.green,
|
||||||
|
MD_BOLD_ITALIC => p.peach,
|
||||||
_ => p.text,
|
_ => p.text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -419,14 +538,47 @@ pub fn highlight_color(kind: u8) -> Color {
|
||||||
pub fn highlight_font(kind: u8) -> Option<Font> {
|
pub fn highlight_font(kind: u8) -> Option<Font> {
|
||||||
match kind {
|
match kind {
|
||||||
MD_HEADING_MARKER => Some(Font { weight: Weight::Bold, ..Font::MONOSPACE }),
|
MD_HEADING_MARKER => Some(Font { weight: Weight::Bold, ..Font::MONOSPACE }),
|
||||||
MD_H1 | MD_H2 | MD_H3 => Some(Font { weight: Weight::Bold, ..Font::DEFAULT }),
|
MD_H1 => Some(Font { weight: Weight::Black, ..Font::DEFAULT }),
|
||||||
|
MD_H2 => Some(Font { weight: Weight::Bold, ..Font::DEFAULT }),
|
||||||
|
MD_H3 => Some(Font { weight: Weight::Semibold, ..Font::DEFAULT }),
|
||||||
MD_BOLD => Some(Font { weight: Weight::Bold, ..Font::DEFAULT }),
|
MD_BOLD => Some(Font { weight: Weight::Bold, ..Font::DEFAULT }),
|
||||||
MD_ITALIC => Some(Font { style: FontStyle::Italic, ..Font::DEFAULT }),
|
MD_ITALIC => Some(Font { style: FontStyle::Italic, ..Font::DEFAULT }),
|
||||||
|
MD_BOLD_ITALIC => Some(Font { weight: Weight::Bold, style: FontStyle::Italic, ..Font::DEFAULT }),
|
||||||
MD_INLINE_CODE => Some(Font::MONOSPACE),
|
MD_INLINE_CODE => Some(Font::MONOSPACE),
|
||||||
MD_FORMAT_MARKER => Some(Font::MONOSPACE),
|
MD_FORMAT_MARKER => Some(Font::MONOSPACE),
|
||||||
MD_BLOCKQUOTE => Some(Font { style: FontStyle::Italic, ..Font::DEFAULT }),
|
MD_BLOCKQUOTE => Some(Font { style: FontStyle::Italic, ..Font::DEFAULT }),
|
||||||
MD_FENCE_MARKER => Some(Font::MONOSPACE),
|
MD_FENCE_MARKER => Some(Font::MONOSPACE),
|
||||||
MD_CODE_BLOCK => Some(Font::MONOSPACE),
|
MD_CODE_BLOCK => Some(Font::MONOSPACE),
|
||||||
|
MD_TASK_DONE => Some(Font { weight: Weight::Bold, ..Font::MONOSPACE }),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn compute_line_decors(source: &str) -> Vec<LineDecor> {
|
||||||
|
let classified = classify_document(source);
|
||||||
|
let line_kinds: Vec<LineKind> = classified.into_iter().map(|cl| cl.kind).collect();
|
||||||
|
let mut decors = Vec::new();
|
||||||
|
let mut in_fence = false;
|
||||||
|
for (i, raw_line) in source.split('\n').enumerate() {
|
||||||
|
let is_md = i < line_kinds.len() && line_kinds[i] == LineKind::Markdown;
|
||||||
|
if is_md {
|
||||||
|
let trimmed = raw_line.trim_start();
|
||||||
|
if trimmed.starts_with("```") {
|
||||||
|
in_fence = !in_fence;
|
||||||
|
decors.push(LineDecor::FenceMarker);
|
||||||
|
} else if in_fence {
|
||||||
|
decors.push(LineDecor::CodeBlock);
|
||||||
|
} else if is_horizontal_rule(trimmed) {
|
||||||
|
decors.push(LineDecor::HorizontalRule);
|
||||||
|
} else if trimmed.starts_with("> ") || trimmed == ">" {
|
||||||
|
decors.push(LineDecor::Blockquote);
|
||||||
|
} else {
|
||||||
|
decors.push(LineDecor::None);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if in_fence { in_fence = false; }
|
||||||
|
decors.push(LineDecor::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decors
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue