markdown_elements.rs

  1use gpui::{px, FontStyle, FontWeight, HighlightStyle, SharedString, UnderlineStyle};
  2use language::HighlightId;
  3use std::{ops::Range, path::PathBuf};
  4
  5#[derive(Debug)]
  6#[cfg_attr(test, derive(PartialEq))]
  7pub enum ParsedMarkdownElement {
  8    Heading(ParsedMarkdownHeading),
  9    /// An ordered or unordered list of items.
 10    List(ParsedMarkdownList),
 11    Table(ParsedMarkdownTable),
 12    BlockQuote(ParsedMarkdownBlockQuote),
 13    CodeBlock(ParsedMarkdownCodeBlock),
 14    /// A paragraph of text and other inline elements.
 15    Paragraph(ParsedMarkdownText),
 16    HorizontalRule(Range<usize>),
 17}
 18
 19impl ParsedMarkdownElement {
 20    pub fn source_range(&self) -> Range<usize> {
 21        match self {
 22            Self::Heading(heading) => heading.source_range.clone(),
 23            Self::List(list) => list.source_range.clone(),
 24            Self::Table(table) => table.source_range.clone(),
 25            Self::BlockQuote(block_quote) => block_quote.source_range.clone(),
 26            Self::CodeBlock(code_block) => code_block.source_range.clone(),
 27            Self::Paragraph(text) => text.source_range.clone(),
 28            Self::HorizontalRule(range) => range.clone(),
 29        }
 30    }
 31}
 32
 33#[derive(Debug)]
 34#[cfg_attr(test, derive(PartialEq))]
 35pub struct ParsedMarkdown {
 36    pub children: Vec<ParsedMarkdownElement>,
 37}
 38
 39#[derive(Debug)]
 40#[cfg_attr(test, derive(PartialEq))]
 41pub struct ParsedMarkdownList {
 42    pub source_range: Range<usize>,
 43    pub children: Vec<ParsedMarkdownListItem>,
 44}
 45
 46#[derive(Debug)]
 47#[cfg_attr(test, derive(PartialEq))]
 48pub struct ParsedMarkdownListItem {
 49    /// How many indentations deep this item is.
 50    pub depth: u16,
 51    pub item_type: ParsedMarkdownListItemType,
 52    pub contents: Vec<Box<ParsedMarkdownElement>>,
 53}
 54
 55#[derive(Debug)]
 56#[cfg_attr(test, derive(PartialEq))]
 57pub enum ParsedMarkdownListItemType {
 58    Ordered(u64),
 59    Task(bool),
 60    Unordered,
 61}
 62
 63#[derive(Debug)]
 64#[cfg_attr(test, derive(PartialEq))]
 65pub struct ParsedMarkdownCodeBlock {
 66    pub source_range: Range<usize>,
 67    pub language: Option<String>,
 68    pub contents: SharedString,
 69}
 70
 71#[derive(Debug)]
 72#[cfg_attr(test, derive(PartialEq))]
 73pub struct ParsedMarkdownHeading {
 74    pub source_range: Range<usize>,
 75    pub level: HeadingLevel,
 76    pub contents: ParsedMarkdownText,
 77}
 78
 79#[derive(Debug, PartialEq)]
 80pub enum HeadingLevel {
 81    H1,
 82    H2,
 83    H3,
 84    H4,
 85    H5,
 86    H6,
 87}
 88
 89#[derive(Debug)]
 90pub struct ParsedMarkdownTable {
 91    pub source_range: Range<usize>,
 92    pub header: ParsedMarkdownTableRow,
 93    pub body: Vec<ParsedMarkdownTableRow>,
 94    pub column_alignments: Vec<ParsedMarkdownTableAlignment>,
 95}
 96
 97#[derive(Debug, Clone, Copy)]
 98#[cfg_attr(test, derive(PartialEq))]
 99pub enum ParsedMarkdownTableAlignment {
100    /// Default text alignment.
101    None,
102    Left,
103    Center,
104    Right,
105}
106
107#[derive(Debug)]
108#[cfg_attr(test, derive(PartialEq))]
109pub struct ParsedMarkdownTableRow {
110    pub children: Vec<ParsedMarkdownText>,
111}
112
113impl ParsedMarkdownTableRow {
114    pub fn new() -> Self {
115        Self {
116            children: Vec::new(),
117        }
118    }
119
120    pub fn with_children(children: Vec<ParsedMarkdownText>) -> Self {
121        Self { children }
122    }
123}
124
125#[derive(Debug)]
126#[cfg_attr(test, derive(PartialEq))]
127pub struct ParsedMarkdownBlockQuote {
128    pub source_range: Range<usize>,
129    pub children: Vec<Box<ParsedMarkdownElement>>,
130}
131
132#[derive(Debug)]
133pub struct ParsedMarkdownText {
134    /// Where the text is located in the source Markdown document.
135    pub source_range: Range<usize>,
136    /// The text content stripped of any formatting symbols.
137    pub contents: String,
138    /// The list of highlights contained in the Markdown document.
139    pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
140    /// The regions of the various ranges in the Markdown document.
141    pub region_ranges: Vec<Range<usize>>,
142    /// The regions of the Markdown document.
143    pub regions: Vec<ParsedRegion>,
144}
145
146/// A run of highlighted Markdown text.
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum MarkdownHighlight {
149    /// A styled Markdown highlight.
150    Style(MarkdownHighlightStyle),
151    /// A highlighted code block.
152    Code(HighlightId),
153}
154
155impl MarkdownHighlight {
156    /// Converts this [`MarkdownHighlight`] to a [`HighlightStyle`].
157    pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option<HighlightStyle> {
158        match self {
159            MarkdownHighlight::Style(style) => {
160                let mut highlight = HighlightStyle::default();
161
162                if style.italic {
163                    highlight.font_style = Some(FontStyle::Italic);
164                }
165
166                if style.underline {
167                    highlight.underline = Some(UnderlineStyle {
168                        thickness: px(1.),
169                        ..Default::default()
170                    });
171                }
172
173                if style.weight != FontWeight::default() {
174                    highlight.font_weight = Some(style.weight);
175                }
176
177                Some(highlight)
178            }
179
180            MarkdownHighlight::Code(id) => id.style(theme),
181        }
182    }
183}
184
185/// The style for a Markdown highlight.
186#[derive(Debug, Clone, Default, PartialEq, Eq)]
187pub struct MarkdownHighlightStyle {
188    /// Whether the text should be italicized.
189    pub italic: bool,
190    /// Whether the text should be underlined.
191    pub underline: bool,
192    /// The weight of the text.
193    pub weight: FontWeight,
194}
195
196/// A parsed region in a Markdown document.
197#[derive(Debug, Clone)]
198#[cfg_attr(test, derive(PartialEq))]
199pub struct ParsedRegion {
200    /// Whether the region is a code block.
201    pub code: bool,
202    /// The link contained in this region, if it has one.
203    pub link: Option<Link>,
204}
205
206/// A Markdown link.
207#[derive(Debug, Clone)]
208#[cfg_attr(test, derive(PartialEq))]
209pub enum Link {
210    /// A link to a webpage.
211    Web {
212        /// The URL of the webpage.
213        url: String,
214    },
215    /// A link to a path on the filesystem.
216    Path {
217        /// The path to the item.
218        path: PathBuf,
219    },
220}
221
222impl Link {
223    pub fn identify(file_location_directory: Option<PathBuf>, text: String) -> Option<Link> {
224        if text.starts_with("http") {
225            return Some(Link::Web { url: text });
226        }
227
228        let path = PathBuf::from(&text);
229        if path.is_absolute() && path.exists() {
230            return Some(Link::Path { path });
231        }
232
233        if let Some(file_location_directory) = file_location_directory {
234            let path = file_location_directory.join(text);
235            if path.exists() {
236                return Some(Link::Path { path });
237            }
238        }
239
240        None
241    }
242}