Lists
This commit is contained in:
parent
527fafcc8b
commit
7f1229b5fe
6 changed files with 207 additions and 43 deletions
|
@ -307,9 +307,10 @@ impl CodeRule {
|
|||
)
|
||||
.unwrap(),
|
||||
Regex::new(
|
||||
r"``(?:\[((?:\\.|[^\\\\])*?)\])?(?:(.*?)(?:,|\n))?((?:\\(?:.|\n)|[^\\\\])*?)``",
|
||||
r"``(?:\[((?:\\.|[^\\\\])*?)\])?(?:([^\r\n`]*?)(?:,|\n))?((?:\\(?:.|\n)|[^\\\\])*?)``",
|
||||
)
|
||||
.unwrap(),
|
||||
.unwrap()
|
||||
|
||||
],
|
||||
properties: PropertyParser { properties: props },
|
||||
}
|
||||
|
|
|
@ -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<String, String> {
|
||||
fn compile(
|
||||
&self,
|
||||
compiler: &Compiler,
|
||||
_document: &dyn Document,
|
||||
_cursor: usize,
|
||||
) -> Result<String, String> {
|
||||
match compiler.target() {
|
||||
Target::HTML => match (self.kind, self.numbered) {
|
||||
(MarkerKind::Close, true) => Ok("</ol>".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<String, String> {
|
||||
fn compile(
|
||||
&self,
|
||||
compiler: &Compiler,
|
||||
document: &dyn Document,
|
||||
cursor: usize,
|
||||
) -> Result<String, String> {
|
||||
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!("<li value=\"{number}\">").as_str();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result += "<li>";
|
||||
}
|
||||
}
|
||||
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 += "</li>";
|
||||
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<dyn Any>)> {
|
||||
self.start_re
|
||||
.find_at(cursor.source.content(), cursor.pos).map(|m| (m.start(), Box::new([false; 0]) as Box<dyn Any>))
|
||||
.find_at(cursor.source.content(), cursor.pos)
|
||||
.map(|m| (m.start(), Box::new([false; 0]) as Box<dyn Any>))
|
||||
}
|
||||
|
||||
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<usize>, &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::<ListEntry>()
|
||||
.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 };
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: %<! make_ref("hello", "id")>%
|
|||
};
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic() {
|
||||
let source = Rc::new(SourceFile::with_content(
|
||||
"".to_string(),
|
||||
r#"
|
||||
%<[test]! "Hello World">%
|
||||
@<main
|
||||
function add(x, y)
|
||||
return x + y
|
||||
end
|
||||
>@
|
||||
"#
|
||||
.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 };
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, String> {
|
||||
fn compile(
|
||||
&self,
|
||||
compiler: &Compiler,
|
||||
_document: &dyn Document,
|
||||
_cursor: usize,
|
||||
) -> Result<String, String> {
|
||||
match compiler.target() {
|
||||
Target::HTML => {
|
||||
Ok([
|
||||
|
@ -100,10 +105,13 @@ impl RuleState for StyleState {
|
|||
let paragraph = document.last_element::<Paragraph>().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%<nml.style.toggle("Italic")>%
|
|||
}
|
||||
|
||||
#[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 };
|
||||
|
|
|
@ -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())],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RefCell<SemanticsHolder>>,
|
||||
range: Range<usize>,
|
||||
) -> Option<(Self, Ref<'a, Tokens>)> {
|
||||
if source.name().starts_with(":LUA:") && source.downcast_ref::<VirtualSource>().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(location) = source
|
||||
.clone()
|
||||
.downcast_rc::<VirtualSource>()
|
||||
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue