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> {
|
||||
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 comment_depth: usize = 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; }
|
||||
result.push(ClassifiedLine { index: i, kind: LineKind::Cordial, content: line.to_string() });
|
||||
} 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 {
|
||||
let trimmed = line.trim();
|
||||
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.
|
||||
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 errors = Vec::new();
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ pub trait InterpreterHook {
|
|||
-> Option<Result<Value, String>> { None }
|
||||
fn extern_unop(&self, _i: &mut Interpreter, _op: Op, _operand: &Value, _depth: u32)
|
||||
-> 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 {
|
||||
|
|
|
|||
|
|
@ -158,6 +158,22 @@ impl Interpreter {
|
|||
.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.
|
||||
pub fn display_value(&self, v: &Value, fmt: DisplayFormat) -> String {
|
||||
if let Value::Extern(h) = v {
|
||||
|
|
|
|||
|
|
@ -571,15 +571,38 @@ impl EditorState {
|
|||
weight: iced_wgpu::core::font::Weight::Bold,
|
||||
..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![
|
||||
iced_widget::text("→ ")
|
||||
.font(syntax::EDITOR_FONT)
|
||||
.size(self.font_size)
|
||||
.color(arrow_color),
|
||||
iced_widget::text(value)
|
||||
.font(bold)
|
||||
.size(self.font_size)
|
||||
.color(value_color),
|
||||
value_mid,
|
||||
iced_widget::text(" ←")
|
||||
.font(syntax::EDITOR_FONT)
|
||||
.size(self.font_size)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ pub struct EditorState {
|
|||
pub(super) cached_minimap_lines: Vec<crate::minimap::MinimapLine>,
|
||||
/// custom keyword/builtin/type table layered on top of Cordial.
|
||||
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.
|
||||
pub(super) heavy_token: u64,
|
||||
/// deferred-rebuild arm; set on edit, cleared on debounce fire.
|
||||
|
|
@ -187,9 +189,15 @@ impl EditorState {
|
|||
heavy_token: 0,
|
||||
heavy_pending: false,
|
||||
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,
|
||||
/// provide shared state, seed vars, add module paths.
|
||||
pub fn set_interpreter_setup<F>(&mut self, setup: F)
|
||||
|
|
|
|||
|
|
@ -458,7 +458,10 @@ impl super::EditorState {
|
|||
let line_idx = cursor.position.line;
|
||||
if line_idx < lines.len() {
|
||||
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}");
|
||||
self.content_mut().perform(text_widget::Action::Move(Motion::End));
|
||||
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)> {
|
||||
let bytes = line.as_bytes();
|
||||
let len = bytes.len();
|
||||
|
|
|
|||
Loading…
Reference in New Issue