1171 lines · 35.6 KB · Rust
RawDownload
use crate::markdown_elements::*;use gpui::FontWeight;use pulldown_cmark::{Alignment, Event, Options, Parser, Tag, TagEnd};use std::{ops::Range, path::PathBuf}; pub fn parse_markdown(    markdown_input: &str,    file_location_directory: Option<PathBuf>,) -> ParsedMarkdown {    let options = Options::all();    let parser = Parser::new_ext(markdown_input, options);    let parser = MarkdownParser::new(parser.into_offset_iter().collect(), file_location_directory);    let renderer = parser.parse_document();    ParsedMarkdown {        children: renderer.parsed,    }} struct MarkdownParser<'a> {    tokens: Vec<(Event<'a>, Range<usize>)>,    /// The current index in the tokens array    cursor: usize,    /// The blocks that we have successfully parsed so far    parsed: Vec<ParsedMarkdownElement>,    file_location_directory: Option<PathBuf>,} impl<'a> MarkdownParser<'a> {    fn new(        tokens: Vec<(Event<'a>, Range<usize>)>,        file_location_directory: Option<PathBuf>,    ) -> Self {        Self {            tokens,            file_location_directory,            cursor: 0,            parsed: vec![],        }    }     fn eof(&self) -> bool {        if self.tokens.is_empty() {            return true;        }        self.cursor >= self.tokens.len() - 1    }     fn peek(&self, steps: usize) -> Option<&(Event, Range<usize>)> {        if self.eof() || (steps + self.cursor) >= self.tokens.len() {            return self.tokens.last();        }        return self.tokens.get(self.cursor + steps);    }     fn previous(&self) -> Option<&(Event, Range<usize>)> {        if self.cursor == 0 || self.cursor > self.tokens.len() {            return None;        }        return self.tokens.get(self.cursor - 1);    }     fn current(&self) -> Option<&(Event, Range<usize>)> {        return self.peek(0);    }     fn is_text_like(event: &Event) -> bool {        match event {            Event::Text(_)            // Represent an inline code block            | Event::Code(_)            | Event::Html(_)            | Event::FootnoteReference(_)            | Event::Start(Tag::Link { link_type: _, dest_url: _, title: _, id: _ })            | Event::Start(Tag::Emphasis)            | Event::Start(Tag::Strong)            | Event::Start(Tag::Strikethrough)            | Event::Start(Tag::Image { link_type: _, dest_url: _, title: _, id: _ }) => {                return true;            }            _ => return false,        }    }     fn parse_document(mut self) -> Self {        while !self.eof() {            if let Some(block) = self.parse_block() {                self.parsed.push(block);            }        }        self    }     fn parse_block(&mut self) -> Option<ParsedMarkdownElement> {        let (current, source_range) = self.current().unwrap();        match current {            Event::Start(tag) => match tag {                Tag::Paragraph => {                    self.cursor += 1;                    let text = self.parse_text(false);                    Some(ParsedMarkdownElement::Paragraph(text))                }                Tag::Heading {                    level,                    id: _,                    classes: _,                    attrs: _,                } => {                    let level = *level;                    self.cursor += 1;                    let heading = self.parse_heading(level);                    Some(ParsedMarkdownElement::Heading(heading))                }                Tag::Table(alignment) => {                    let alignment = alignment.clone();                    self.cursor += 1;                    let table = self.parse_table(alignment);                    Some(ParsedMarkdownElement::Table(table))                }                Tag::List(order) => {                    let order = *order;                    self.cursor += 1;                    let list = self.parse_list(1, order);                    Some(ParsedMarkdownElement::List(list))                }                Tag::BlockQuote => {                    self.cursor += 1;                    let block_quote = self.parse_block_quote();                    Some(ParsedMarkdownElement::BlockQuote(block_quote))                }                Tag::CodeBlock(kind) => {                    let language = match kind {                        pulldown_cmark::CodeBlockKind::Indented => None,                        pulldown_cmark::CodeBlockKind::Fenced(language) => {                            if language.is_empty() {                                None                            } else {                                Some(language.to_string())                            }                        }                    };                     self.cursor += 1;                     let code_block = self.parse_code_block(language);                    Some(ParsedMarkdownElement::CodeBlock(code_block))                }                _ => {                    self.cursor += 1;                    None                }            },            Event::Rule => {                let source_range = source_range.clone();                self.cursor += 1;                Some(ParsedMarkdownElement::HorizontalRule(source_range))            }            _ => {                self.cursor += 1;                None            }        }    }     fn parse_text(&mut self, should_complete_on_soft_break: bool) -> ParsedMarkdownText {        let (_current, source_range) = self.previous().unwrap();        let source_range = source_range.clone();         let mut text = String::new();        let mut bold_depth = 0;        let mut italic_depth = 0;        let mut strikethrough_depth = 0;        let mut link: Option<Link> = None;        let mut region_ranges: Vec<Range<usize>> = vec![];        let mut regions: Vec<ParsedRegion> = vec![];        let mut highlights: Vec<(Range<usize>, MarkdownHighlight)> = vec![];         loop {            if self.eof() {                break;            }             let (current, _source_range) = self.current().unwrap();            let prev_len = text.len();            match current {                Event::SoftBreak => {                    if should_complete_on_soft_break {                        break;                    }                     // `Some text\nSome more text` should be treated as a single line.                    text.push(' ');                }                 Event::HardBreak => {                    break;                }                 Event::Text(t) => {                    text.push_str(t.as_ref());                     let mut style = MarkdownHighlightStyle::default();                     if bold_depth > 0 {                        style.weight = FontWeight::BOLD;                    }                     if italic_depth > 0 {                        style.italic = true;                    }                     if strikethrough_depth > 0 {                        style.strikethrough = true;                    }                     if let Some(link) = link.clone() {                        region_ranges.push(prev_len..text.len());                        regions.push(ParsedRegion {                            code: false,                            link: Some(link),                        });                        style.underline = true;                    }                     if style != MarkdownHighlightStyle::default() {                        let mut new_highlight = true;                        if let Some((last_range, MarkdownHighlight::Style(last_style))) =                            highlights.last_mut()                        {                            if last_range.end == prev_len && last_style == &style {                                last_range.end = text.len();                                new_highlight = false;                            }                        }                        if new_highlight {                            let range = prev_len..text.len();                            highlights.push((range, MarkdownHighlight::Style(style)));                        }                    }                }                 // Note: This event means "inline code" and not "code block"                Event::Code(t) => {                    text.push_str(t.as_ref());                    region_ranges.push(prev_len..text.len());                     if link.is_some() {                        highlights.push((                            prev_len..text.len(),                            MarkdownHighlight::Style(MarkdownHighlightStyle {                                underline: true,                                ..Default::default()                            }),                        ));                    }                     regions.push(ParsedRegion {                        code: true,                        link: link.clone(),                    });                }                 Event::Start(tag) => match tag {                    Tag::Emphasis => italic_depth += 1,                    Tag::Strong => bold_depth += 1,                    Tag::Strikethrough => strikethrough_depth += 1,                    Tag::Link {                        link_type: _,                        dest_url,                        title: _,                        id: _,                    } => {                        link = Link::identify(                            self.file_location_directory.clone(),                            dest_url.to_string(),                        );                    }                    _ => {                        break;                    }                },                 Event::End(tag) => match tag {                    TagEnd::Emphasis => {                        italic_depth -= 1;                    }                    TagEnd::Strong => {                        bold_depth -= 1;                    }                    TagEnd::Strikethrough => {                        strikethrough_depth -= 1;                    }                    TagEnd::Link => {                        link = None;                    }                    TagEnd::Paragraph => {                        self.cursor += 1;                        break;                    }                    _ => {                        break;                    }                },                 _ => {                    break;                }            }             self.cursor += 1;        }         ParsedMarkdownText {            source_range,            contents: text,            highlights,            regions,            region_ranges,        }    }     fn parse_heading(&mut self, level: pulldown_cmark::HeadingLevel) -> ParsedMarkdownHeading {        let (_event, source_range) = self.previous().unwrap();        let source_range = source_range.clone();        let text = self.parse_text(true);         // Advance past the heading end tag        self.cursor += 1;         ParsedMarkdownHeading {            source_range: source_range.clone(),            level: match level {                pulldown_cmark::HeadingLevel::H1 => HeadingLevel::H1,                pulldown_cmark::HeadingLevel::H2 => HeadingLevel::H2,                pulldown_cmark::HeadingLevel::H3 => HeadingLevel::H3,                pulldown_cmark::HeadingLevel::H4 => HeadingLevel::H4,                pulldown_cmark::HeadingLevel::H5 => HeadingLevel::H5,                pulldown_cmark::HeadingLevel::H6 => HeadingLevel::H6,            },            contents: text,        }    }     fn parse_table(&mut self, alignment: Vec<Alignment>) -> ParsedMarkdownTable {        let (_event, source_range) = self.previous().unwrap();        let source_range = source_range.clone();        let mut header = ParsedMarkdownTableRow::new();        let mut body = vec![];        let mut current_row = vec![];        let mut in_header = true;        let column_alignments = alignment            .iter()            .map(|a| Self::convert_alignment(a))            .collect();         loop {            if self.eof() {                break;            }             let (current, _source_range) = self.current().unwrap();            match current {                Event::Start(Tag::TableHead)                | Event::Start(Tag::TableRow)                | Event::End(TagEnd::TableCell) => {                    self.cursor += 1;                }                Event::Start(Tag::TableCell) => {                    self.cursor += 1;                    let cell_contents = self.parse_text(false);                    current_row.push(cell_contents);                }                Event::End(TagEnd::TableHead) | Event::End(TagEnd::TableRow) => {                    self.cursor += 1;                    let new_row = std::mem::replace(&mut current_row, vec![]);                    if in_header {                        header.children = new_row;                        in_header = false;                    } else {                        let row = ParsedMarkdownTableRow::with_children(new_row);                        body.push(row);                    }                }                Event::End(TagEnd::Table) => {                    self.cursor += 1;                    break;                }                _ => {                    break;                }            }        }         ParsedMarkdownTable {            source_range,            header,            body,            column_alignments,        }    }     fn convert_alignment(alignment: &Alignment) -> ParsedMarkdownTableAlignment {        match alignment {            Alignment::None => ParsedMarkdownTableAlignment::None,            Alignment::Left => ParsedMarkdownTableAlignment::Left,            Alignment::Center => ParsedMarkdownTableAlignment::Center,            Alignment::Right => ParsedMarkdownTableAlignment::Right,        }    }     fn parse_list(&mut self, depth: u16, order: Option<u64>) -> ParsedMarkdownList {        let (_event, source_range) = self.previous().unwrap();        let source_range = source_range.clone();        let mut children = vec![];        let mut inside_list_item = false;        let mut order = order;        let mut task_item = None;         let mut current_list_items: Vec<Box<ParsedMarkdownElement>> = vec![];         while !self.eof() {            let (current, _source_range) = self.current().unwrap();            match current {                Event::Start(Tag::List(order)) => {                    let order = *order;                    self.cursor += 1;                     let inner_list = self.parse_list(depth + 1, order);                    let block = ParsedMarkdownElement::List(inner_list);                    current_list_items.push(Box::new(block));                }                Event::End(TagEnd::List(_)) => {                    self.cursor += 1;                    break;                }                Event::Start(Tag::Item) => {                    self.cursor += 1;                    inside_list_item = true;                     // Check for task list marker (`- [ ]` or `- [x]`)                    if let Some(next) = self.current() {                        match next.0 {                            Event::TaskListMarker(checked) => {                                task_item = Some(checked);                                self.cursor += 1;                            }                            _ => {}                        }                    }                     if let Some(next) = self.current() {                        // This is a plain list item.                        // For example `- some text` or `1. [Docs](./docs.md)`                        if MarkdownParser::is_text_like(&next.0) {                            let text = self.parse_text(false);                            let block = ParsedMarkdownElement::Paragraph(text);                            current_list_items.push(Box::new(block));                        } else {                            let block = self.parse_block();                            if let Some(block) = block {                                current_list_items.push(Box::new(block));                            }                        }                    }                }                Event::End(TagEnd::Item) => {                    self.cursor += 1;                     let item_type = if let Some(checked) = task_item {                        ParsedMarkdownListItemType::Task(checked)                    } else if let Some(order) = order {                        ParsedMarkdownListItemType::Ordered(order)                    } else {                        ParsedMarkdownListItemType::Unordered                    };                     if let Some(current) = order {                        order = Some(current + 1);                    }                     let contents = std::mem::replace(&mut current_list_items, vec![]);                     children.push(ParsedMarkdownListItem {                        contents,                        depth,                        item_type,                    });                     inside_list_item = false;                    task_item = None;                }                _ => {                    if !inside_list_item {                        break;                    }                     let block = self.parse_block();                    if let Some(block) = block {                        current_list_items.push(Box::new(block));                    }                }

Lines 1–500 of 1171.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t