From 7f1229b5feaac1cd16d4fc99cffb0c70fa656fc1 Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Sun, 20 Oct 2024 12:25:52 +0200 Subject: [PATCH] Lists --- src/elements/code.rs | 5 ++- src/elements/list.rs | 90 ++++++++++++++++++++++++++++++++-------- src/elements/script.rs | 85 +++++++++++++++++++++++++++++++++---- src/elements/style.rs | 41 ++++++++++++------ src/elements/variable.rs | 2 +- src/lsp/semantic.rs | 27 +++++++++++- 6 files changed, 207 insertions(+), 43 deletions(-) diff --git a/src/elements/code.rs b/src/elements/code.rs index 8cee7f0..415471e 100644 --- a/src/elements/code.rs +++ b/src/elements/code.rs @@ -307,9 +307,10 @@ impl CodeRule { ) .unwrap(), Regex::new( - r"``(?:\[((?:\\.|[^\\\\])*?)\])?(?:(.*?)(?:,|\n))?((?:\\(?:.|\n)|[^\\\\])*?)``", + r"``(?:\[((?:\\.|[^\\\\])*?)\])?(?:([^\r\n`]*?)(?:,|\n))?((?:\\(?:.|\n)|[^\\\\])*?)``", ) - .unwrap(), + .unwrap() + ], properties: PropertyParser { properties: props }, } diff --git a/src/elements/list.rs b/src/elements/list.rs index a431e08..8be12a2 100644 --- a/src/elements/list.rs +++ b/src/elements/list.rs @@ -11,6 +11,7 @@ use crate::document::document::DocumentAccessors; use crate::document::element::ContainerElement; use crate::document::element::ElemKind; use crate::document::element::Element; +use crate::lsp::semantic::Semantics; use crate::parser::parser::ParserState; use crate::parser::rule::Rule; use crate::parser::source::Cursor; @@ -48,7 +49,12 @@ impl Element for ListMarker { fn element_name(&self) -> &'static str { "List Marker" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { + fn compile( + &self, + compiler: &Compiler, + _document: &dyn Document, + _cursor: usize, + ) -> Result { match compiler.target() { Target::HTML => match (self.kind, self.numbered) { (MarkerKind::Close, true) => Ok("".to_string()), @@ -76,21 +82,26 @@ impl Element for ListEntry { fn element_name(&self) -> &'static str { "List Entry" } - fn compile(&self, compiler: &Compiler, document: &dyn Document, cursor: usize) -> Result { + fn compile( + &self, + compiler: &Compiler, + document: &dyn Document, + cursor: usize, + ) -> Result { match compiler.target() { Target::HTML => { let mut result = String::new(); - if let Some((numbered, number)) = self.numbering.last() - { + if let Some((numbered, number)) = self.numbering.last() { if *numbered { result += format!("
  • ").as_str(); - } - else { + } else { result += "
  • "; } } for elem in &self.content { - result += elem.compile(compiler, document, cursor+result.len())?.as_str(); + result += elem + .compile(compiler, document, cursor + result.len())? + .as_str(); } result += "
  • "; Ok(result) @@ -137,7 +148,7 @@ impl ListRule { Self { start_re: Regex::new(r"(?:^|\n)(?:[^\S\r\n]+)([*-]+)(?:\[((?:\\.|[^\\\\])*?)\])?(.*)") .unwrap(), - continue_re: Regex::new(r"(?:^|\n)([^\S\r\n]+)([^\s].*)").unwrap(), + continue_re: Regex::new(r"(?:^|\n)([^\S\r\n].*)").unwrap(), properties: PropertyParser { properties: props }, } } @@ -262,7 +273,8 @@ impl Rule for ListRule { fn next_match(&self, _state: &ParserState, cursor: &Cursor) -> Option<(usize, Box)> { self.start_re - .find_at(cursor.source.content(), cursor.pos).map(|m| (m.start(), Box::new([false; 0]) as Box)) + .find_at(cursor.source.content(), cursor.pos) + .map(|m| (m.start(), Box::new([false; 0]) as Box)) } fn on_match<'a>( @@ -304,7 +316,7 @@ impl Rule for ListRule { ) .finish(), ); - break; + return (cursor.at(captures.get(0).unwrap().end()), reports); } Ok(props) => (offset, bullet) = props, } @@ -323,19 +335,34 @@ impl Rule for ListRule { offset.unwrap_or(usize::MAX), ); + if let Some((sems, tokens)) = + Semantics::from_source(cursor.source.clone(), &state.shared.semantics) + { + sems.add(captures.get(1).unwrap().range(), tokens.list_bullet); + if let Some(props) = captures.get(2).map(|m| m.range()) { + sems.add(props.start - 1..props.start, tokens.list_props_sep); + sems.add(props.clone(), tokens.list_props); + sems.add(props.end..props.end + 1, tokens.list_props_sep); + } + } + // Content - let entry_start = captures.get(0).unwrap().start(); + let entry_start = captures.get(3).unwrap().start(); let mut entry_content = captures.get(3).unwrap().as_str().to_string(); let mut spacing: Option<(Range, &str)> = None; while let Some(captures) = self.continue_re.captures_at(content, end_cursor.pos) { // Break if next element is another entry if captures.get(0).unwrap().start() != end_cursor.pos || captures - .get(2) + .get(1) .unwrap() .as_str() .find(['*', '-']) - == Some(0) + .map(|delim| { + captures.get(1).unwrap().as_str()[0..delim] + .chars() + .fold(true, |val, c| val && c.is_whitespace()) + }) == Some(true) { break; } @@ -373,8 +400,8 @@ impl Rule for ListRule { spacing = Some((captures.get(1).unwrap().range(), current_spacing)); } - entry_content += " "; - entry_content += captures.get(2).unwrap().as_str(); + entry_content += "\n"; + entry_content += captures.get(1).unwrap().as_str(); } // Parse entry content @@ -402,7 +429,6 @@ impl Rule for ListRule { Ok(mut paragraph) => std::mem::take(&mut paragraph.content), }; - if let Some(previous_depth) = document .last_element::() .map(|ent| ent.numbering.clone()) @@ -449,7 +475,7 @@ mod tests { use crate::parser::langparser::LangParser; use crate::parser::parser::Parser; use crate::parser::source::SourceFile; - use crate::validate_document; + use crate::{validate_document, validate_semantics}; #[test] fn parser() { @@ -514,4 +540,34 @@ mod tests { ListMarker { numbered == false, kind == MarkerKind::Close }; ); } + + #[test] + fn semantic() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" + *[offset=5] First **bold** + Second line + *- Another +>@ + "# + .to_string(), + None, + )); + let parser = LangParser::default(); + let (_, state) = parser.parse( + ParserState::new_with_semantics(&parser, None), + source.clone(), + None, + ); + validate_semantics!(state, source.clone(), 0, + list_bullet { delta_line == 1, delta_start == 1, length == 1 }; + list_props_sep { delta_line == 0, delta_start == 1, length == 1 }; + list_props { delta_line == 0, delta_start == 1, length == 8 }; + list_props_sep { delta_line == 0, delta_start == 8, length == 1 }; + style_marker { delta_line == 0, delta_start == 8, length == 2 }; + style_marker { delta_line == 0, delta_start == 6, length == 2 }; + list_bullet { delta_line == 2, delta_start == 1, length == 2 }; + ); + } } diff --git a/src/elements/script.rs b/src/elements/script.rs index b86be07..efdf27e 100644 --- a/src/elements/script.rs +++ b/src/elements/script.rs @@ -1,4 +1,5 @@ use crate::document::document::Document; +use crate::lsp::semantic::Semantics; use crate::lua::kernel::Kernel; use crate::lua::kernel::KernelContext; use crate::parser::parser::ParserState; @@ -151,7 +152,7 @@ impl RegexRule for ScriptRule { let source = Rc::new(VirtualSource::new( Token::new(kernel_range, token.source()), format!( - "{}#{}:lua_kernel@{kernel_name}", + ":LUA:{kernel_name}#{}#{}", token.source().name(), matches.get(0).unwrap().start() ), @@ -170,10 +171,7 @@ impl RegexRule for ScriptRule { .with_message("Invalid kernel code") .with_label( Label::new((source.clone(), 0..source.content().len())) - .with_message(format!( - "Kernel execution failed:\n{}", - e - )) + .with_message(format!("Kernel execution failed:\n{}", e)) .with_color(state.parser.colors().error), ) .finish(), @@ -213,10 +211,7 @@ impl RegexRule for ScriptRule { .with_message("Invalid kernel code") .with_label( Label::new((source.clone(), 0..source.content().len())) - .with_message(format!( - "Kernel evaluation failed:\n{}", - e - )) + .with_message(format!("Kernel evaluation failed:\n{}", e)) .with_color(state.parser.colors().error), ) .finish(), @@ -283,6 +278,38 @@ impl RegexRule for ScriptRule { document, }; + if let Some((sems, tokens)) = + Semantics::from_source(token.source(), &state.shared.semantics) + { + let range = matches + .get(0) + .map(|m| { + if token.source().content().as_bytes()[m.start()] == b'\n' { + m.start() + 1..m.end() + } else { + m.range() + } + }) + .unwrap(); + sems.add(range.start..range.start + 2, tokens.script_sep); + if index == 0 { + if let Some(kernel) = matches.get(1).map(|m| m.range()) { + sems.add(kernel, tokens.script_kernel); + } + sems.add(matches.get(2).unwrap().range(), tokens.script_content); + } else { + if let Some(kernel) = matches.get(1).map(|m| m.range()) { + sems.add(kernel.start - 1..kernel.start, tokens.script_kernel_sep); + sems.add(kernel.clone(), tokens.script_kernel); + sems.add(kernel.end..kernel.end + 1, tokens.script_kernel_sep); + } + if let Some(kind) = matches.get(2).map(|m| m.range()) { + sems.add(kind, tokens.script_kind); + } + sems.add(matches.get(3).unwrap().range(), tokens.script_content); + } + sems.add(range.end - 2..range.end, tokens.script_sep); + } kernel.run_with_context(ctx, execute) } } @@ -299,6 +326,7 @@ mod tests { use crate::parser::parser::Parser; use crate::parser::source::SourceFile; use crate::validate_document; + use crate::validate_semantics; #[test] fn parser() { @@ -344,4 +372,43 @@ Evaluation: %% }; ); } + + #[test] + fn semantic() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +%<[test]! "Hello World">% +@
    @ + "# + .to_string(), + None, + )); + let parser = LangParser::default(); + let (_, state) = parser.parse( + ParserState::new_with_semantics(&parser, None), + source.clone(), + None, + ); + validate_semantics!(state, source.clone(), 0, + script_sep { delta_line == 1, delta_start == 0, length == 2 }; + script_kernel_sep { delta_line == 0, delta_start == 2, length == 1 }; + script_kernel { delta_line == 0, delta_start == 1, length == 4 }; + script_kernel_sep { delta_line == 0, delta_start == 4, length == 1 }; + script_kind { delta_line == 0, delta_start == 1, length == 1 }; + script_content { delta_line == 0, delta_start == 1, length == 14 }; + script_sep { delta_line == 0, delta_start == 14, length == 2 }; + + script_sep { delta_line == 1, delta_start == 0, length == 2 }; + script_kernel { delta_line == 0, delta_start == 2, length == 4 }; + script_content { delta_line == 1, delta_start == 0, length == 19 }; + script_content { delta_line == 1, delta_start == 0, length == 14 }; + script_content { delta_line == 1, delta_start == 0, length == 3 }; + script_sep { delta_line == 1, delta_start == 0, length == 2 }; + ); + } } diff --git a/src/elements/style.rs b/src/elements/style.rs index 10360d5..290f1ab 100644 --- a/src/elements/style.rs +++ b/src/elements/style.rs @@ -47,7 +47,12 @@ impl Element for Style { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Inline } fn element_name(&self) -> &'static str { "Style" } - fn compile(&self, compiler: &Compiler, _document: &dyn Document, _cursor: usize) -> Result { + fn compile( + &self, + compiler: &Compiler, + _document: &dyn Document, + _cursor: usize, + ) -> Result { match compiler.target() { Target::HTML => { Ok([ @@ -100,10 +105,13 @@ impl RuleState for StyleState { let paragraph = document.last_element::().unwrap(); let paragraph_end = paragraph .content - .last().map(|last| ( + .last() + .map(|last| { + ( last.location().source(), last.location().end() - 1..last.location().end(), - )) + ) + }) .unwrap(); reports.push( @@ -201,7 +209,8 @@ impl RegexRule for StyleRule { )), ); - if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.semantics) + if let Some((sems, tokens)) = + Semantics::from_source(token.source(), &state.shared.semantics) { sems.add(token.start()..token.end(), tokens.style_marker); } @@ -228,7 +237,9 @@ impl RegexRule for StyleRule { to: Some("toggle".to_string()), pos: 1, name: Some("style".to_string()), - cause: Arc::new(mlua::Error::external("Unknown style specified".to_string())), + cause: Arc::new(mlua::Error::external( + "Unknown style specified".to_string(), + )), }) } }; @@ -285,7 +296,8 @@ mod tests { use crate::parser::langparser::LangParser; use crate::parser::parser::Parser; use crate::parser::source::SourceFile; - use crate::{validate_document, validate_semantics}; + use crate::validate_document; + use crate::validate_semantics; use super::*; @@ -372,19 +384,22 @@ terminated here%% } #[test] - fn semantic() - { + fn semantic() { let source = Rc::new(SourceFile::with_content( - "".to_string(), - r#" + "".to_string(), + r#" **tešŸ“«st** `another` __te恋st__ *another* "# - .to_string(), - None, + .to_string(), + None, )); let parser = LangParser::default(); - let (_, state) = parser.parse(ParserState::new_with_semantics(&parser, None), source.clone(), None); + let (_, state) = parser.parse( + ParserState::new_with_semantics(&parser, None), + source.clone(), + None, + ); validate_semantics!(state, source.clone(), 0, style_marker { delta_line == 1, delta_start == 0, length == 2 }; diff --git a/src/elements/variable.rs b/src/elements/variable.rs index 74625e0..5454b8d 100644 --- a/src/elements/variable.rs +++ b/src/elements/variable.rs @@ -47,7 +47,7 @@ pub struct VariableRule { impl VariableRule { pub fn new() -> Self { Self { - re: [Regex::new(r"(?:^|\n)@([^[:alpha:]])?(.*?)=((?:\\\n|.)*)").unwrap()], + re: [Regex::new(r"(?:^|\n)@(')?(.*?)=((?:\\\n|.)*)").unwrap()], kinds: vec![("".into(), "Regular".into()), ("'".into(), "Path".into())], } } diff --git a/src/lsp/semantic.rs b/src/lsp/semantic.rs index a1e3bca..5244a49 100644 --- a/src/lsp/semantic.rs +++ b/src/lsp/semantic.rs @@ -129,6 +129,16 @@ pub struct Tokens { pub code_lang: (u32, u32), pub code_title: (u32, u32), pub code_content: (u32, u32), + + pub script_sep: (u32, u32), + pub script_kernel_sep: (u32, u32), + pub script_kernel: (u32, u32), + pub script_kind: (u32, u32), + pub script_content: (u32, u32), + + pub list_bullet: (u32, u32), + pub list_props_sep: (u32, u32), + pub list_props: (u32, u32), } impl Tokens { @@ -175,6 +185,16 @@ impl Tokens { code_lang: token!("function"), code_title: token!("number"), code_content: token!("string"), + + script_sep: token!("operator"), + script_kernel_sep: token!("operator"), + script_kernel: token!("function"), + script_kind: token!("function"), + script_content: token!("string"), + + list_bullet: token!("macro"), + list_props_sep: token!("operator"), + list_props: token!("enum"), } } } @@ -211,6 +231,10 @@ impl<'a> Semantics<'a> { semantics: &'a Option>, range: Range, ) -> Option<(Self, Ref<'a, Tokens>)> { + if source.name().starts_with(":LUA:") && source.downcast_ref::().is_some() { + return None; + } + if let Some(location) = source .clone() .downcast_rc::() @@ -269,7 +293,8 @@ impl<'a> Semantics<'a> { while cursor.pos != range.end { let end = self.source.content()[cursor.pos..range.end] .find('\n') - .unwrap_or(self.source.content().len() - 1) + 1; + .unwrap_or(self.source.content().len() - 1) + + 1; let len = usize::min(range.end - cursor.pos, end); let clen = self.source.content()[cursor.pos..cursor.pos + len] .chars()