extend eval and highlight rule permissivity
This commit is contained in:
parent
48a871186b
commit
7112564d9d
|
|
@ -122,6 +122,17 @@ fn is_ident(s: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn classify_document(text: &str) -> Vec<ClassifiedLine> {
|
pub fn classify_document(text: &str) -> Vec<ClassifiedLine> {
|
||||||
|
classify_document_with(text, |_| None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// classifies a document, letting a caller override the Cordial/Markdown verdict per line.
|
||||||
|
/// the override feeds the same decision point as the built-in classifier, so brace tracking
|
||||||
|
/// engages for overridden Cordial openers and multi-line blocks follow. the `/=` eval sigil
|
||||||
|
/// is never overridable.
|
||||||
|
pub fn classify_document_with(
|
||||||
|
text: &str,
|
||||||
|
classify_override: impl Fn(&str) -> Option<bool>,
|
||||||
|
) -> Vec<ClassifiedLine> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
let mut comment_depth: usize = 0;
|
let mut comment_depth: usize = 0;
|
||||||
let mut brace_depth: i32 = 0;
|
let mut brace_depth: i32 = 0;
|
||||||
|
|
@ -140,7 +151,16 @@ pub fn classify_document(text: &str) -> Vec<ClassifiedLine> {
|
||||||
if brace_depth < 0 { brace_depth = 0; }
|
if brace_depth < 0 { brace_depth = 0; }
|
||||||
result.push(ClassifiedLine { index: i, kind: LineKind::Cordial, content: line.to_string() });
|
result.push(ClassifiedLine { index: i, kind: LineKind::Cordial, content: line.to_string() });
|
||||||
} else {
|
} else {
|
||||||
let cl = classify_line(i, line);
|
let base = classify_line(i, line);
|
||||||
|
let cl = if base.kind == LineKind::Eval {
|
||||||
|
base
|
||||||
|
} else {
|
||||||
|
match classify_override(line.trim()) {
|
||||||
|
Some(true) => ClassifiedLine { index: i, kind: LineKind::Cordial, content: line.to_string() },
|
||||||
|
Some(false) => ClassifiedLine { index: i, kind: LineKind::Markdown, content: line.to_string() },
|
||||||
|
None => base,
|
||||||
|
}
|
||||||
|
};
|
||||||
if cl.kind == LineKind::Cordial {
|
if cl.kind == LineKind::Cordial {
|
||||||
let trimmed = line.trim();
|
let trimmed = line.trim();
|
||||||
let opens = trimmed.matches('{').count() as i32;
|
let opens = trimmed.matches('{').count() as i32;
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,10 @@ pub fn evaluate_modules(sources: &[ModuleSource]) -> Vec<ModuleResult> {
|
||||||
|
|
||||||
/// evaluates a document's text using a pre-populated interpreter.
|
/// evaluates a document's text using a pre-populated interpreter.
|
||||||
pub fn evaluate_document_with_interp(interp: &mut interp::Interpreter, text: &str) -> DocumentResult {
|
pub fn evaluate_document_with_interp(interp: &mut interp::Interpreter, text: &str) -> DocumentResult {
|
||||||
let classified = classify_document(text);
|
let classified = {
|
||||||
|
let interp_ref: &interp::Interpreter = interp;
|
||||||
|
crate::doc::classify_document_with(text, |line| interp_ref.hook_classify_line(line))
|
||||||
|
};
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ pub trait InterpreterHook {
|
||||||
-> Option<Result<Value, String>> { None }
|
-> Option<Result<Value, String>> { None }
|
||||||
fn extern_unop(&self, _i: &mut Interpreter, _op: Op, _operand: &Value, _depth: u32)
|
fn extern_unop(&self, _i: &mut Interpreter, _op: Op, _operand: &Value, _depth: u32)
|
||||||
-> Option<Result<Value, String>> { None }
|
-> Option<Result<Value, String>> { None }
|
||||||
|
|
||||||
|
/// line-classification override: None defers, true = Cordial, false = Markdown.
|
||||||
|
fn classify_line(&self, _trimmed: &str) -> Option<bool> { None }
|
||||||
|
/// names the binding on a custom assignment line.
|
||||||
|
fn eval_binding_name(&self, _trimmed: &str) -> Option<String> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct HookList {
|
pub(crate) struct HookList {
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,22 @@ impl Interpreter {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// first hook with a line-classification verdict.
|
||||||
|
pub fn hook_classify_line(&self, trimmed: &str) -> Option<bool> {
|
||||||
|
for h in &self.hooks.hooks {
|
||||||
|
if let Some(v) = h.classify_line(trimmed) { return Some(v); }
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// first hook to name a custom binding line.
|
||||||
|
pub fn hook_eval_binding_name(&self, trimmed: &str) -> Option<String> {
|
||||||
|
for h in &self.hooks.hooks {
|
||||||
|
if let Some(v) = h.eval_binding_name(trimmed) { return Some(v); }
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// hook-aware Value rendering for inline/table/tree formats.
|
/// hook-aware Value rendering for inline/table/tree formats.
|
||||||
pub fn display_value(&self, v: &Value, fmt: DisplayFormat) -> String {
|
pub fn display_value(&self, v: &Value, fmt: DisplayFormat) -> String {
|
||||||
if let Value::Extern(h) = v {
|
if let Value::Extern(h) = v {
|
||||||
|
|
|
||||||
|
|
@ -571,15 +571,38 @@ impl EditorState {
|
||||||
weight: iced_wgpu::core::font::Weight::Bold,
|
weight: iced_wgpu::core::font::Weight::Bold,
|
||||||
..syntax::EDITOR_FONT
|
..syntax::EDITOR_FONT
|
||||||
};
|
};
|
||||||
|
let value_mid: Element<'a, Message, Theme, iced_wgpu::Renderer> = match &self.eval_highlight {
|
||||||
|
Some(rules) => {
|
||||||
|
let spans = syntax::highlight_eval_spans(&value, rules);
|
||||||
|
let mut rich: Vec<iced_wgpu::core::text::Span<'a, ()>> = Vec::new();
|
||||||
|
let mut cursor = 0usize;
|
||||||
|
for (range, kind) in &spans {
|
||||||
|
if range.start > cursor {
|
||||||
|
rich.push(iced_widget::span(value[cursor..range.start].to_string()).color(value_color));
|
||||||
|
}
|
||||||
|
let mut s = iced_widget::span(value[range.start..range.end].to_string())
|
||||||
|
.color(syntax::highlight_color(*kind));
|
||||||
|
if let Some(f) = syntax::highlight_font(*kind) { s = s.font(f); }
|
||||||
|
rich.push(s);
|
||||||
|
cursor = range.end;
|
||||||
|
}
|
||||||
|
if cursor < value.len() {
|
||||||
|
rich.push(iced_widget::span(value[cursor..].to_string()).color(value_color));
|
||||||
|
}
|
||||||
|
iced_widget::text::Rich::with_spans(rich).size(self.font_size).into()
|
||||||
|
}
|
||||||
|
None => iced_widget::text(value)
|
||||||
|
.font(bold)
|
||||||
|
.size(self.font_size)
|
||||||
|
.color(value_color)
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
let row = iced_widget::row![
|
let row = iced_widget::row![
|
||||||
iced_widget::text("→ ")
|
iced_widget::text("→ ")
|
||||||
.font(syntax::EDITOR_FONT)
|
.font(syntax::EDITOR_FONT)
|
||||||
.size(self.font_size)
|
.size(self.font_size)
|
||||||
.color(arrow_color),
|
.color(arrow_color),
|
||||||
iced_widget::text(value)
|
value_mid,
|
||||||
.font(bold)
|
|
||||||
.size(self.font_size)
|
|
||||||
.color(value_color),
|
|
||||||
iced_widget::text(" ←")
|
iced_widget::text(" ←")
|
||||||
.font(syntax::EDITOR_FONT)
|
.font(syntax::EDITOR_FONT)
|
||||||
.size(self.font_size)
|
.size(self.font_size)
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,8 @@ pub struct EditorState {
|
||||||
pub(super) cached_minimap_lines: Vec<crate::minimap::MinimapLine>,
|
pub(super) cached_minimap_lines: Vec<crate::minimap::MinimapLine>,
|
||||||
/// custom keyword/builtin/type table layered on top of Cordial.
|
/// custom keyword/builtin/type table layered on top of Cordial.
|
||||||
pub syntax_rules: crate::syntax::SyntaxRules,
|
pub syntax_rules: crate::syntax::SyntaxRules,
|
||||||
|
/// optional rules for highlighting inline eval output.
|
||||||
|
pub(super) eval_highlight: Option<crate::syntax::SyntaxRules>,
|
||||||
/// tree-sitter rebuild gate, bumped on idle.
|
/// tree-sitter rebuild gate, bumped on idle.
|
||||||
pub(super) heavy_token: u64,
|
pub(super) heavy_token: u64,
|
||||||
/// deferred-rebuild arm; set on edit, cleared on debounce fire.
|
/// deferred-rebuild arm; set on edit, cleared on debounce fire.
|
||||||
|
|
@ -187,9 +189,15 @@ impl EditorState {
|
||||||
heavy_token: 0,
|
heavy_token: 0,
|
||||||
heavy_pending: false,
|
heavy_pending: false,
|
||||||
interp_setup: None,
|
interp_setup: None,
|
||||||
|
eval_highlight: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// sets the rules for highlighting inline eval output.
|
||||||
|
pub fn set_eval_highlight(&mut self, rules: Option<crate::syntax::SyntaxRules>) {
|
||||||
|
self.eval_highlight = rules;
|
||||||
|
}
|
||||||
|
|
||||||
/// installs a routine run on each fresh eval interpreter — register hooks,
|
/// installs a routine run on each fresh eval interpreter — register hooks,
|
||||||
/// provide shared state, seed vars, add module paths.
|
/// provide shared state, seed vars, add module paths.
|
||||||
pub fn set_interpreter_setup<F>(&mut self, setup: F)
|
pub fn set_interpreter_setup<F>(&mut self, setup: F)
|
||||||
|
|
|
||||||
|
|
@ -458,7 +458,10 @@ impl super::EditorState {
|
||||||
let line_idx = cursor.position.line;
|
let line_idx = cursor.position.line;
|
||||||
if line_idx < lines.len() {
|
if line_idx < lines.len() {
|
||||||
let line = lines[line_idx].trim();
|
let line = lines[line_idx].trim();
|
||||||
if let Some(varname) = parse_let_binding(line) {
|
let varname = self.new_eval_interpreter()
|
||||||
|
.hook_eval_binding_name(line)
|
||||||
|
.or_else(|| parse_let_binding(line));
|
||||||
|
if let Some(varname) = varname {
|
||||||
let insert = format!("\n/= {varname}");
|
let insert = format!("\n/= {varname}");
|
||||||
self.content_mut().perform(text_widget::Action::Move(Motion::End));
|
self.content_mut().perform(text_widget::Action::Move(Motion::End));
|
||||||
self.content_mut().perform(text_widget::Action::Edit(
|
self.content_mut().perform(text_widget::Action::Edit(
|
||||||
|
|
|
||||||
|
|
@ -512,6 +512,15 @@ fn extract_paren_idents(s: &str, map: &mut HashMap<String, u8>, slot: &mut u32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// highlights a single eval-output line with the given rules into (range, kind) spans.
|
||||||
|
pub fn highlight_eval_spans(line: &str, rules: &SyntaxRules) -> Vec<(Range<usize>, u8)> {
|
||||||
|
let idents: HashMap<String, u8> = HashMap::new();
|
||||||
|
highlight_cordial(line, &idents, rules)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(r, SyntaxHighlight { kind })| (r, kind))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn highlight_cordial(line: &str, user_idents: &HashMap<String, u8>, rules: &SyntaxRules) -> Vec<(Range<usize>, SyntaxHighlight)> {
|
fn highlight_cordial(line: &str, user_idents: &HashMap<String, u8>, rules: &SyntaxRules) -> Vec<(Range<usize>, SyntaxHighlight)> {
|
||||||
let bytes = line.as_bytes();
|
let bytes = line.as_bytes();
|
||||||
let len = bytes.len();
|
let len = bytes.len();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue