From 9fcccdd1372dc04c1621e2a016d62e86ef5fc3a8 Mon Sep 17 00:00:00 2001
From: ef3d0c3e <ef3d0c3e@pundalik.org>
Date: Fri, 25 Oct 2024 14:53:31 +0200
Subject: [PATCH] Fix blockquotes

---
 src/elements/blockquote.rs | 41 ++++++++++++++++++----
 src/elements/graphviz.rs   |  8 ++---
 src/lsp/semantic.rs        | 70 ++++++++++++++++++++++++++++++++++++--
 src/parser/langparser.rs   |  4 +++
 4 files changed, 111 insertions(+), 12 deletions(-)

diff --git a/src/elements/blockquote.rs b/src/elements/blockquote.rs
index 6e4aa09..300cd1f 100644
--- a/src/elements/blockquote.rs
+++ b/src/elements/blockquote.rs
@@ -6,6 +6,7 @@ use std::rc::Rc;
 use blockquote_style::AuthorPos::After;
 use blockquote_style::AuthorPos::Before;
 use blockquote_style::BlockquoteStyle;
+use lsp::semantic::Semantics;
 use regex::Match;
 use regex::Regex;
 use runtime_format::FormatArgs;
@@ -196,8 +197,8 @@ impl BlockquoteRule {
 		);
 
 		Self {
-			start_re: Regex::new(r"(?:^|\n)>(?:\[((?:\\.|[^\\\\])*?)\])?\s*?(.*)").unwrap(),
-			continue_re: Regex::new(r"(?:^|\n)>\s*?(.*)").unwrap(),
+			start_re: Regex::new(r"(?:^|\n)>(?:\[((?:\\.|[^\\\\])*?)\])?[^\S\r\n]*(.*)").unwrap(),
+			continue_re: Regex::new(r"(?:^|\n)>[^\S\r\n]*(.*)").unwrap(),
 			properties: PropertyParser { properties: props },
 		}
 	}
@@ -282,27 +283,55 @@ impl Rule for BlockquoteRule {
 				}
 			}
 
+			if let Some((sems, tokens)) =
+				Semantics::from_source(cursor.source.clone(), &state.shared.lsp)
+			{
+				let range = captures.get(0).unwrap().range();
+				let start = if content.as_bytes()[range.start] == b'\n' { range.start+1 } else { range.start };
+				sems.add(start..start+1, tokens.blockquote_marker);
+				if let Some(props) = captures.get(1).map(|m| m.range()) {
+					sems.add(props.start - 1..props.start, tokens.blockquote_props_sep);
+					sems.add(props.clone(), tokens.blockquote_props);
+					sems.add(props.end..props.end + 1, tokens.blockquote_props_sep);
+				}
+			}
+
 			// Content
-			let entry_start = captures.get(0).unwrap().start();
+			let entry_start = captures.get(2).unwrap().start();
 			let mut entry_content = captures.get(2).unwrap().as_str().to_string();
+			let mut offsets = vec![];
 			while let Some(captures) = self.continue_re.captures_at(content, end_cursor.pos) {
 				if captures.get(0).unwrap().start() != end_cursor.pos {
 					break;
 				}
 				// Advance cursor
 				end_cursor = end_cursor.at(captures.get(0).unwrap().end());
+				// Offset
+				let last = offsets.last().map_or(0, |(_, last)| *last);
+				offsets.push((
+						entry_content.len(),
+						last + (captures.get(1).unwrap().start() - captures.get(0).unwrap().start() - 1) as isize
+				));
 
-				let trimmed = captures.get(1).unwrap().as_str().trim_start().trim_end();
 				entry_content += "\n";
-				entry_content += trimmed;
+				entry_content += captures.get(1).unwrap().as_str();
+
+				if let Some((sems, tokens)) =
+					Semantics::from_source(cursor.source.clone(), &state.shared.lsp)
+				{
+					let range = captures.get(0).unwrap().range();
+					let start = if content.as_bytes()[range.start] == b'\n' { range.start+1 } else { range.start };
+					sems.add_to_queue(start..start+1, tokens.blockquote_marker);
+				}
 			}
 
 			// Parse entry content
 			let token = Token::new(entry_start..end_cursor.pos, end_cursor.source.clone());
-			let entry_src = Rc::new(VirtualSource::new(
+			let entry_src = Rc::new(VirtualSource::new_offsets(
 				token.clone(),
 				"Blockquote Entry".to_string(),
 				entry_content,
+				offsets
 			));
 			// Parse content
 			let parsed_doc = state.with_state(|new_state| {
diff --git a/src/elements/graphviz.rs b/src/elements/graphviz.rs
index 81d88c1..d71e97f 100644
--- a/src/elements/graphviz.rs
+++ b/src/elements/graphviz.rs
@@ -352,11 +352,11 @@ impl RegexRule for GraphRule {
 				tokens.graph_sep,
 			);
 			if let Some(props) = matches.get(1).map(|m| m.range()) {
-				sems.add(props.start - 1..props.start, tokens.tex_props_sep);
-				sems.add(props.clone(), tokens.tex_props);
-				sems.add(props.end..props.end + 1, tokens.tex_props_sep);
+				sems.add(props.start - 1..props.start, tokens.graph_props_sep);
+				sems.add(props.clone(), tokens.graph_props);
+				sems.add(props.end..props.end + 1, tokens.graph_props_sep);
 			}
-			sems.add(matches.get(2).unwrap().range(), tokens.tex_content);
+			sems.add(matches.get(2).unwrap().range(), tokens.graph_content);
 			sems.add(
 				range.end - 8..range.end,
 				tokens.graph_sep,
diff --git a/src/lsp/semantic.rs b/src/lsp/semantic.rs
index aff4359..a997a69 100644
--- a/src/lsp/semantic.rs
+++ b/src/lsp/semantic.rs
@@ -1,5 +1,6 @@
 use std::cell::Ref;
 use std::cell::RefCell;
+use std::collections::VecDeque;
 use std::ops::Range;
 use std::rc::Rc;
 
@@ -144,6 +145,10 @@ pub struct Tokens {
 	pub list_props_sep: (u32, u32),
 	pub list_props: (u32, u32),
 
+	pub blockquote_marker: (u32, u32),
+	pub blockquote_props_sep: (u32, u32),
+	pub blockquote_props: (u32, u32),
+
 	pub raw_sep: (u32, u32),
 	pub raw_props_sep: (u32, u32),
 	pub raw_props: (u32, u32),
@@ -223,6 +228,10 @@ impl Tokens {
 			list_props_sep: token!("operator"),
 			list_props: token!("enum"),
 
+			blockquote_marker: token!("macro"),
+			blockquote_props_sep: token!("operator"),
+			blockquote_props: token!("enum"),
+
 			raw_sep: token!("operator"),
 			raw_props_sep: token!("operator"),
 			raw_props: token!("enum"),
@@ -253,6 +262,9 @@ pub struct SemanticsData {
 	/// The current cursor
 	cursor: RefCell<LineCursor>,
 
+	/// Semantic tokens that can't be added directly
+	pub semantic_queue: RefCell<VecDeque<(Range<usize>, (u32, u32))>>,
+
 	/// Semantic tokens
 	pub tokens: RefCell<Vec<SemanticToken>>,
 }
@@ -261,6 +273,7 @@ impl SemanticsData {
 	pub fn new(source: Rc<dyn Source>) -> Self {
 		Self {
 			cursor: RefCell::new(LineCursor::new(source)),
+			semantic_queue: RefCell::new(VecDeque::new()),
 			tokens: RefCell::new(vec![]),
 		}
 	}
@@ -329,8 +342,41 @@ impl<'a> Semantics<'a> {
 		Self::from_source_impl(source.clone(), lsp, source)
 	}
 
-	pub fn add(&self, range: Range<usize>, token: (u32, u32)) {
-		let range = self.original_source.original_range(range).1;
+	/// Method that should be called at the end of parsing
+	///
+	/// This function will process the end of the semantic queue
+	pub fn on_document_end(lsp: &'a Option<RefCell<LSPData>>, source: Rc<dyn Source>)
+	{
+		if source.content().is_empty()
+		{
+			return;
+		}
+		let pos = source.original_position(source.content().len() - 1).1;
+		if let Some((sems, _)) = Self::from_source(source, lsp)
+		{
+			sems.process_queue(pos);
+		}
+	}
+
+	/// Processes the semantic queue up to a certain position
+	fn process_queue(&self, pos: usize)
+	{
+		let mut queue = self.sems.semantic_queue.borrow_mut();
+		while !queue.is_empty()
+		{
+			let (range, token) = queue.front().unwrap();
+			if range.start > pos
+			{
+				break;
+			}
+
+			self.add_impl(range.to_owned(), token.to_owned());
+			queue.pop_front();
+		}
+	}
+
+	fn add_impl(&self, range: Range<usize>, token: (u32, u32))
+	{
 		let mut tokens = self.sems.tokens.borrow_mut();
 		let mut cursor = self.sems.cursor.borrow_mut();
 		let mut current = cursor.clone();
@@ -368,6 +414,26 @@ impl<'a> Semantics<'a> {
 			cursor.move_to(pos + len);
 		}
 	}
+
+	/// Add a semantic token to be processed instantly
+	pub fn add(&self, range: Range<usize>, token: (u32, u32)) {
+		let range = self.original_source.original_range(range).1;
+		self.process_queue(range.start);
+		self.add_impl(range, token);
+	}
+
+	/// Add a semantic token to be processed in a future call to `add()`
+	pub fn add_to_queue(&self, range: Range<usize>, token: (u32, u32))
+	{
+		let range = self.original_source.original_range(range).1;
+		let mut queue = self.sems.semantic_queue.borrow_mut();
+		match queue.binary_search_by_key(&range.start, |(range, _)| range.start)
+		{
+			Ok(pos) | Err(pos) => {
+				queue.insert(pos, (range, token))
+			},
+		}
+	}
 }
 
 #[cfg(test)]
diff --git a/src/parser/langparser.rs b/src/parser/langparser.rs
index d7e8c7e..dbc52d0 100644
--- a/src/parser/langparser.rs
+++ b/src/parser/langparser.rs
@@ -6,6 +6,7 @@ use crate::document::element::DocumentEnd;
 use crate::document::langdocument::LangDocument;
 use crate::elements::text::Text;
 use crate::lsp::hints::HintsData;
+use crate::lsp::semantic::Semantics;
 use crate::lsp::semantic::SemanticsData;
 
 use super::parser::ParseMode;
@@ -144,6 +145,9 @@ impl<'b> Parser for LangParser<'b> {
 			}
 		}
 
+		// Process the end of the semantics queue
+		Semantics::on_document_end(&state.shared.lsp, source.clone());
+
 		// Rule States
 		self.handle_reports(state.shared.rule_state.borrow_mut().on_scope_end(
 			&state,