Syntax highlighting bug fix for external.
Also timing for tree-sitter checks
This commit is contained in:
parent
9b5dbbdf2b
commit
24958d4896
|
|
@ -2007,18 +2007,30 @@ impl Interpreter {
|
|||
Value::Array(a) => a,
|
||||
_ => return Err("for loop requires an array or range".into()),
|
||||
};
|
||||
let prev = self.vars.remove(var);
|
||||
let prev_type = self.var_types.remove(var);
|
||||
let mut iterations = 0;
|
||||
let mut last = Value::Void;
|
||||
let mut loop_err: Option<String> = None;
|
||||
for item in &items {
|
||||
iterations += 1;
|
||||
if iterations > MAX_ITERATIONS {
|
||||
return Err(format!("loop exceeded {} iterations", MAX_ITERATIONS));
|
||||
loop_err = Some(format!("loop exceeded {} iterations", MAX_ITERATIONS));
|
||||
break;
|
||||
}
|
||||
self.vars.insert(var.clone(), item.clone());
|
||||
for s in body {
|
||||
last = self.exec_stmt(s, depth)?;
|
||||
match self.exec_stmt(s, depth) {
|
||||
Ok(v) => last = v,
|
||||
Err(e) => { loop_err = Some(e); break; }
|
||||
}
|
||||
}
|
||||
if loop_err.is_some() { break; }
|
||||
}
|
||||
self.vars.remove(var);
|
||||
if let Some(v) = prev { self.vars.insert(var.clone(), v); }
|
||||
if let Some(t) = prev_type { self.var_types.insert(var.clone(), t); }
|
||||
if let Some(e) = loop_err { return Err(e); }
|
||||
Ok(last)
|
||||
}
|
||||
Stmt::FnDef { name, params, return_type, body } => {
|
||||
|
|
@ -2213,17 +2225,28 @@ impl Interpreter {
|
|||
}
|
||||
_ => None,
|
||||
};
|
||||
let type_tag = type_tag.ok_or_else(||
|
||||
format!("cannot call .{}() — receiver has no __type", method)
|
||||
)?;
|
||||
let fndef = self.methods.get(&(type_tag.clone(), method.clone()))
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("no method '{}' on type '{}'", method, type_tag))?;
|
||||
// typed receiver → look up method on the type
|
||||
if let Some(tag) = type_tag {
|
||||
if let Some(fndef) = self.methods.get(&(tag.clone(), method.clone())).cloned() {
|
||||
let mut eval_args = vec![recv_val];
|
||||
for a in args {
|
||||
eval_args.push(self.eval_expr(a, depth)?);
|
||||
}
|
||||
self.call_fndef(&fndef, &eval_args, depth)
|
||||
return self.call_fndef(&fndef, &eval_args, depth);
|
||||
}
|
||||
return Err(format!("no method '{}' on type '{}'", method, tag));
|
||||
}
|
||||
// untyped receiver → desugar to method(receiver, args...)
|
||||
let mut call_args: Vec<Expr> = Vec::with_capacity(args.len() + 1);
|
||||
let placeholder = format!("__method_recv_{}", depth);
|
||||
self.vars.insert(placeholder.clone(), recv_val);
|
||||
call_args.push(Expr::Ident(placeholder.clone()));
|
||||
for a in args {
|
||||
call_args.push(a.clone());
|
||||
}
|
||||
let result = self.eval_call(method, &call_args, depth);
|
||||
self.vars.remove(&placeholder);
|
||||
result
|
||||
}
|
||||
Expr::StaticCall(type_name_str, method, args) => {
|
||||
let fndef = self.methods.get(&(type_name_str.clone(), method.clone()))
|
||||
|
|
@ -3430,25 +3453,33 @@ fn builtin_constant(name: &str) -> Option<Value> {
|
|||
}
|
||||
|
||||
fn value_is_kind(v: &Value, kind: &str) -> bool {
|
||||
match (kind, v) {
|
||||
match (canonical_type(kind), v) {
|
||||
("int", Value::Number(n)) => *n == n.trunc() && n.is_finite(),
|
||||
("float", Value::Number(_)) => true,
|
||||
("number", Value::Number(_)) => true,
|
||||
("bool", Value::Bool(_)) => true,
|
||||
("str", Value::Str(_)) => true,
|
||||
("array", Value::Array(_)) => true,
|
||||
("struct", Value::Struct(_)) => true,
|
||||
("ring", Value::Ring(_)) => true,
|
||||
("void", Value::Void) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_cast(v: &Value, target: &str) -> Option<Value> {
|
||||
match (target, v) {
|
||||
match (canonical_type(target), v) {
|
||||
("int", Value::Number(n)) if *n == n.trunc() && n.is_finite() => {
|
||||
Some(Value::Number(*n))
|
||||
}
|
||||
("float", Value::Number(_)) => Some(v.clone()),
|
||||
("number", Value::Number(_)) => Some(v.clone()),
|
||||
("bool", Value::Bool(_)) => Some(v.clone()),
|
||||
("str", Value::Str(_)) => Some(v.clone()),
|
||||
("array", Value::Array(_)) => Some(v.clone()),
|
||||
("struct", Value::Struct(_)) => Some(v.clone()),
|
||||
("ring", Value::Ring(_)) => Some(v.clone()),
|
||||
("void", Value::Void) => Some(Value::Void),
|
||||
|
||||
("bool", Value::Number(n)) => {
|
||||
if *n == 0.0 {
|
||||
|
|
@ -3476,6 +3507,7 @@ fn try_cast(v: &Value, target: &str) -> Option<Value> {
|
|||
.filter(|n| *n == n.trunc() && n.is_finite())
|
||||
.map(Value::Number),
|
||||
("float", Value::Str(s)) => s.parse::<f64>().ok().map(Value::Number),
|
||||
("str", Value::Array(_)) => Some(Value::Str(v.display())),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -3486,7 +3518,13 @@ fn coerce_to(val: &Value, target: &str) -> Result<Value, String> {
|
|||
return Err(format!("unknown type annotation: {}", target));
|
||||
}
|
||||
|
||||
let t1 = match try_cast(val, target) {
|
||||
let canon = canonical_type(target);
|
||||
|
||||
if value_is_kind(val, canon) {
|
||||
return Ok(val.clone());
|
||||
}
|
||||
|
||||
let t1 = match try_cast(val, canon) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(format!(
|
||||
|
|
@ -3498,6 +3536,10 @@ fn coerce_to(val: &Value, target: &str) -> Result<Value, String> {
|
|||
}
|
||||
};
|
||||
|
||||
if matches!(canon, "array" | "struct" | "ring" | "void") {
|
||||
return Ok(t1);
|
||||
}
|
||||
|
||||
if values_equal(&t1, val) {
|
||||
return Ok(t1);
|
||||
}
|
||||
|
|
@ -3536,7 +3578,25 @@ fn coerce_to(val: &Value, target: &str) -> Result<Value, String> {
|
|||
}
|
||||
|
||||
fn is_known_type(t: &str) -> bool {
|
||||
matches!(t, "int" | "float" | "bool" | "str")
|
||||
matches!(canonical_type(t),
|
||||
"int" | "float" | "bool" | "str" | "number"
|
||||
| "array" | "struct" | "void" | "ring")
|
||||
}
|
||||
|
||||
/// folds aliases onto a canonical type name.
|
||||
fn canonical_type(t: &str) -> &str {
|
||||
match t {
|
||||
"arr" | "array" | "vec" | "[]" | "list" => "array",
|
||||
"int" | "integer" => "int",
|
||||
"float" | "f64" | "f32" => "float",
|
||||
"bool" | "boolean" => "bool",
|
||||
"str" | "string" | "String" => "str",
|
||||
"number" | "num" => "number",
|
||||
"void" | "null" | "nil" | "none" | "unit" | "()" => "void",
|
||||
"struct" | "obj" | "object" | "map" | "dict" => "struct",
|
||||
"ring" => "ring",
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn values_equal(a: &Value, b: &Value) -> bool {
|
||||
|
|
@ -5962,6 +6022,97 @@ fn find(arr, target) {
|
|||
assert!(result.unwrap_err().contains("missing required method 'bounds'"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_return_type_arr_accepts_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn which(degree) -> arr {\n return [0, 0]\n}").unwrap();
|
||||
let v = i.eval_expr_str("which(5)").unwrap();
|
||||
match v {
|
||||
Value::Array(a) => assert_eq!(a.len(), 2),
|
||||
other => panic!("expected array, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_return_type_array_aliases() {
|
||||
let mut i = Interpreter::new();
|
||||
for ty in ["arr", "array", "vec", "list"] {
|
||||
i.exec_line(&format!("fn f_{}() -> {} {{ return [1, 2, 3] }}", ty, ty)).unwrap();
|
||||
let v = i.eval_expr_str(&format!("f_{}()", ty)).unwrap();
|
||||
assert!(matches!(v, Value::Array(_)), "{} return type rejected", ty);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_return_type_null_accepts_void() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn noop() -> null { }").unwrap();
|
||||
let v = i.eval_expr_str("noop()").unwrap();
|
||||
assert!(matches!(v, Value::Void));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_with_array_type_annotation() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let pos: arr = [1, 2, 3, 4]").unwrap();
|
||||
let v = i.get_var("pos").unwrap();
|
||||
assert!(matches!(v, Value::Array(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_array_type_aliases() {
|
||||
for ty in ["arr", "array", "vec", "list"] {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line(&format!("let x: {} = [1, 2]", ty)).unwrap();
|
||||
assert!(matches!(i.get_var("x").unwrap(), Value::Array(_)),
|
||||
"alias {} failed", ty);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_len_on_array() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let pos = [1, 2, 3, 4]").unwrap();
|
||||
let v = i.eval_expr_str("pos.len()").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 4.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_call_on_array_literal() {
|
||||
let mut i = Interpreter::new();
|
||||
let v = i.eval_expr_str("[1, 2, 3].len()").unwrap();
|
||||
assert!(matches!(v, Value::Number(n) if n == 3.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_loop_does_not_leak_var() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let x = [1, 2, 3]").unwrap();
|
||||
i.exec_line("for x in 0..3 { let _ = x }").unwrap();
|
||||
let after = i.get_var("x").unwrap();
|
||||
assert!(matches!(after, Value::Array(_)), "for loop leaked: x is now {:?}", after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_loop_in_while_with_shadowing() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("let pos = [10, 20, 30, 40]").unwrap();
|
||||
i.exec_line("let count = 0").unwrap();
|
||||
i.exec_line("let iters = 0").unwrap();
|
||||
i.exec_line("while iters < 2 {\n for pos in 0..len(pos) {\n count = count + 1\n }\n iters = iters + 1\n}").unwrap();
|
||||
let count = i.get_var("count").unwrap();
|
||||
assert!(matches!(count, Value::Number(n) if n == 8.0),
|
||||
"expected 8 inner iterations, got {:?}", count);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_return_type_struct_accepts_struct() {
|
||||
let mut i = Interpreter::new();
|
||||
i.exec_line("fn make() -> struct {\n return {x: 1, y: 2}\n}").unwrap();
|
||||
let v = i.eval_expr_str("make()").unwrap();
|
||||
assert!(matches!(v, Value::Struct(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_on_untyped_struct_errors() {
|
||||
let mut i = Interpreter::new();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ pub fn highlight_preview(source: &str) -> Vec<PreviewLine> {
|
|||
source: source.to_string(),
|
||||
user_idents: crate::syntax::scan_user_idents_in(source),
|
||||
rules: crate::syntax::SyntaxRules::cordial(),
|
||||
heavy_token: 0,
|
||||
};
|
||||
let mut highlighter = SyntaxHighlighter::new(&settings);
|
||||
|
||||
|
|
|
|||
|
|
@ -398,14 +398,6 @@ impl super::EditorState {
|
|||
!tb.selection.is_empty() || tb.spillover.is_some()
|
||||
}
|
||||
|
||||
/// jumps the focused text block's internal scroll to the given fraction (0.0..1.0).
|
||||
pub(super) fn jump_to_fraction(&mut self, frac: f32) {
|
||||
let frac = frac.clamp(0.0, 1.0);
|
||||
let line_count = self.content().line_count().max(1);
|
||||
let target = ((line_count as f32 * frac) as usize).min(line_count.saturating_sub(1));
|
||||
self.content_mut().jump_to_line(target);
|
||||
}
|
||||
|
||||
/// scrolls the viewport and places the cursor at the given line.
|
||||
pub(super) fn jump_to_line(&mut self, line: usize) {
|
||||
let clamped = line.min(self.content().line_count().saturating_sub(1));
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ impl super::EditorState {
|
|||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
if self.heavy_pending && self.last_edit.elapsed().as_millis() >= 300 {
|
||||
self.heavy_token = self.heavy_token.wrapping_add(1);
|
||||
self.heavy_pending = false;
|
||||
}
|
||||
if self.render_mode != RenderMode::Live { return; }
|
||||
if self.eval_dirty && self.last_edit.elapsed().as_millis() >= EVAL_DEBOUNCE_MS {
|
||||
self.eval_dirty = false;
|
||||
|
|
@ -85,6 +89,7 @@ impl super::EditorState {
|
|||
self.parsed = markdown::parse(&text).collect();
|
||||
self.rebuild_modules();
|
||||
self.refresh_text_caches();
|
||||
self.heavy_pending = true;
|
||||
}
|
||||
|
||||
pub(super) fn build_block_infos(&self) -> Vec<crate::module::BlockInfo> {
|
||||
|
|
|
|||
|
|
@ -247,6 +247,7 @@ impl EditorState {
|
|||
source: tb.content.text(),
|
||||
user_idents: self.cached_user_idents.clone(),
|
||||
rules: self.syntax_rules.clone(),
|
||||
heavy_token: self.heavy_token,
|
||||
};
|
||||
let editor_el: Element<'_, Message, Theme, iced_wgpu::Renderer> = editor
|
||||
.highlight_with::<SyntaxHighlighter>(
|
||||
|
|
@ -715,6 +716,7 @@ impl EditorState {
|
|||
source: tb.content.text(),
|
||||
user_idents: self.cached_user_idents.clone(),
|
||||
rules: self.syntax_rules.clone(),
|
||||
heavy_token: self.heavy_token,
|
||||
};
|
||||
editor
|
||||
.highlight_with::<SyntaxHighlighter>(
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ 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,
|
||||
/// tree-sitter rebuild gate, bumped on idle.
|
||||
pub(super) heavy_token: u64,
|
||||
/// deferred-rebuild arm; set on edit, cleared on debounce fire.
|
||||
pub(super) heavy_pending: bool,
|
||||
}
|
||||
|
||||
impl EditorState {
|
||||
|
|
@ -178,6 +182,8 @@ impl EditorState {
|
|||
cached_user_idents: HashMap::new(),
|
||||
cached_minimap_lines: Vec::new(),
|
||||
syntax_rules: crate::syntax::SyntaxRules::cordial(),
|
||||
heavy_token: 0,
|
||||
heavy_pending: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +1,21 @@
|
|||
//! helpers for embedding EditorState inside an external iced application.
|
||||
//!
|
||||
//! the editor produces a fully-functional iced widget via EditorState::view().
|
||||
//! callers also need to drive periodic state work (eval debounce, autosave hints)
|
||||
//! by calling EditorState::tick() once per frame, and drain pending clipboard or
|
||||
//! shell-action output. these helpers wrap the common patterns.
|
||||
//!
|
||||
//! example:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use acord_viewport::{EditorState, embed};
|
||||
//!
|
||||
//! struct App { editor: EditorState }
|
||||
//!
|
||||
//! enum Msg { Acord(acord_viewport::Message), Tick }
|
||||
//!
|
||||
//! fn update(&mut self, msg: Msg) {
|
||||
//! match msg {
|
||||
//! Msg::Acord(m) => self.editor.update(m),
|
||||
//! Msg::Tick => {
|
||||
//! self.editor.tick();
|
||||
//! let pending = self.editor.drain_pending();
|
||||
//! if let Some(text) = pending.clipboard {
|
||||
//! // copy to host clipboard
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn view(&self) -> Element<'_, Msg> {
|
||||
//! self.editor.view().map(Msg::Acord)
|
||||
//! }
|
||||
//!
|
||||
//! fn subscription(&self) -> Subscription<Msg> {
|
||||
//! iced::time::every(embed::TICK_INTERVAL).map(|_| Msg::Tick)
|
||||
//! }
|
||||
//! ```
|
||||
//! embedding helpers for EditorState inside an external iced application.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::editor::EditorState;
|
||||
|
||||
/// recommended tick interval for embedded use (60 fps cadence).
|
||||
/// tick cadence for embedded use.
|
||||
pub const TICK_INTERVAL: Duration = Duration::from_millis(16);
|
||||
|
||||
/// snapshot of host-handled state the editor produced this frame.
|
||||
/// host-handled output produced during the current frame.
|
||||
pub struct Pending {
|
||||
/// text the editor wants written to the host clipboard, if any.
|
||||
pub clipboard: Option<String>,
|
||||
/// numeric command the host shell should act on, if any.
|
||||
pub shell_action: Option<crate::editor::ShellAction>,
|
||||
/// widget that should receive iced focus this frame, if any.
|
||||
pub focus: Option<iced_wgpu::core::widget::Id>,
|
||||
}
|
||||
|
||||
impl EditorState {
|
||||
/// pulls every host-handled output for this frame and clears it from the editor.
|
||||
/// pulls clipboard, shell-action, and focus output and clears it.
|
||||
pub fn drain_pending(&mut self) -> Pending {
|
||||
Pending {
|
||||
clipboard: self.pending_clipboard.take(),
|
||||
|
|
@ -63,8 +24,7 @@ impl EditorState {
|
|||
}
|
||||
}
|
||||
|
||||
/// records the surface size so the minimap, scrollable math, and free-layer
|
||||
/// placement can size themselves correctly.
|
||||
/// records the current surface size.
|
||||
pub fn set_viewport_size(&mut self, width: f32, height: f32) {
|
||||
self.viewport_size = (width, height);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
use iced_wgpu::core::{
|
||||
alignment, mouse, Color, Element, Font, Length, Theme,
|
||||
};
|
||||
use iced_wgpu::core::{Color, Element, Font, Length, Theme};
|
||||
use iced_widget::{column, container, mouse_area, text};
|
||||
|
||||
use crate::palette;
|
||||
|
|
@ -143,7 +141,7 @@ fn parse_impl_entry(rest: &str, line: usize) -> MapEntry {
|
|||
}
|
||||
}
|
||||
|
||||
/// strips `pub`, `pub(crate)`, `pub(super)`, `pub(in path)` from the front.
|
||||
/// strips a leading Rust visibility modifier.
|
||||
fn strip_visibility(s: &str) -> &str {
|
||||
if let Some(rest) = s.strip_prefix("pub(") {
|
||||
if let Some(end) = rest.find(')') {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ pub struct SyntaxSettings {
|
|||
pub user_idents: HashMap<String, u8>,
|
||||
/// optional extra keywords/builtins layered on top of Cordial.
|
||||
pub rules: SyntaxRules,
|
||||
/// rebuild gate; tree-sitter re-runs only when this changes.
|
||||
pub heavy_token: u64,
|
||||
}
|
||||
|
||||
/// extensible token table for languages built on top of Cordial.
|
||||
|
|
@ -88,6 +90,8 @@ pub struct SyntaxRules {
|
|||
pub extra_types: std::collections::BTreeSet<String>,
|
||||
/// when true, the hardcoded Cordial keywords/builtins remain active.
|
||||
pub include_cordial: bool,
|
||||
/// routes every non-fenced line through the Cordial scanner, bypassing the line classifier.
|
||||
pub assume_cordial: bool,
|
||||
}
|
||||
|
||||
impl SyntaxRules {
|
||||
|
|
@ -98,6 +102,7 @@ impl SyntaxRules {
|
|||
extra_builtins: Default::default(),
|
||||
extra_types: Default::default(),
|
||||
include_cordial: true,
|
||||
assume_cordial: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,9 +113,16 @@ impl SyntaxRules {
|
|||
extra_builtins: Default::default(),
|
||||
extra_types: Default::default(),
|
||||
include_cordial: false,
|
||||
assume_cordial: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// builder for the assume_cordial flag.
|
||||
pub fn assume_cordial(mut self, on: bool) -> Self {
|
||||
self.assume_cordial = on;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn keyword(mut self, w: impl Into<String>) -> Self {
|
||||
self.extra_keywords.insert(w.into());
|
||||
self
|
||||
|
|
@ -171,11 +183,11 @@ pub struct SyntaxHighlighter {
|
|||
/// per-line tree-sitter spans for fenced code body lines, keyed by absolute line index.
|
||||
code_block_spans: HashMap<usize, Vec<(Range<usize>, SyntaxHighlight)>>,
|
||||
rules: SyntaxRules,
|
||||
last_heavy_token: Option<u64>,
|
||||
}
|
||||
|
||||
impl SyntaxHighlighter {
|
||||
fn rebuild(&mut self, source: &str) {
|
||||
self.spans = highlight_source(source, &self.lang);
|
||||
fn rebuild(&mut self, source: &str, heavy_token: u64) {
|
||||
self.line_offsets.clear();
|
||||
let mut offset = 0;
|
||||
for line in source.split('\n') {
|
||||
|
|
@ -212,7 +224,11 @@ impl SyntaxHighlighter {
|
|||
self.in_fenced_code = false;
|
||||
self.current_line = 0;
|
||||
|
||||
if self.last_heavy_token != Some(heavy_token) {
|
||||
self.spans = highlight_source(source, &self.lang);
|
||||
self.scan_fenced_code_blocks(source);
|
||||
self.last_heavy_token = Some(heavy_token);
|
||||
}
|
||||
}
|
||||
|
||||
/// highlights language-tagged fenced blocks via tree-sitter, stashing per-line spans.
|
||||
|
|
@ -718,7 +734,17 @@ fn is_cordial_builtin(w: &str) -> bool {
|
|||
}
|
||||
|
||||
fn is_cordial_type_annotation(w: &str) -> bool {
|
||||
matches!(w, "int" | "float" | "bool" | "str" | "number" | "array" | "vec")
|
||||
matches!(w,
|
||||
"int" | "integer"
|
||||
| "float" | "f64" | "f32"
|
||||
| "number" | "num"
|
||||
| "bool" | "boolean"
|
||||
| "str" | "string"
|
||||
| "array" | "arr" | "vec" | "list"
|
||||
| "struct" | "obj" | "object" | "map" | "dict"
|
||||
| "void" | "null" | "nil" | "none" | "unit"
|
||||
| "ring"
|
||||
)
|
||||
}
|
||||
|
||||
fn last_token_is_colon(spans: &[(Range<usize>, SyntaxHighlight)]) -> bool {
|
||||
|
|
@ -969,8 +995,9 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
|||
user_idents: settings.user_idents.clone(),
|
||||
code_block_spans: HashMap::new(),
|
||||
rules: settings.rules.clone(),
|
||||
last_heavy_token: None,
|
||||
};
|
||||
h.rebuild(&settings.source);
|
||||
h.rebuild(&settings.source, settings.heavy_token);
|
||||
h
|
||||
}
|
||||
|
||||
|
|
@ -978,7 +1005,7 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
|||
self.lang = new_settings.lang.clone();
|
||||
self.user_idents = new_settings.user_idents.clone();
|
||||
self.rules = new_settings.rules.clone();
|
||||
self.rebuild(&new_settings.source);
|
||||
self.rebuild(&new_settings.source, new_settings.heavy_token);
|
||||
}
|
||||
|
||||
fn change_line(&mut self, line: usize) {
|
||||
|
|
@ -1003,6 +1030,12 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
|||
// pure-code mode bypasses cordial and markdown classifiers.
|
||||
let is_pure_code = !self.lang.is_empty();
|
||||
|
||||
if !is_pure_code && self.rules.assume_cordial && !self.in_fenced_code {
|
||||
if !trimmed.starts_with("```") {
|
||||
return highlight_cordial(line, &self.user_idents, &self.rules).into_iter();
|
||||
}
|
||||
}
|
||||
|
||||
if !is_pure_code
|
||||
&& ln < self.line_kinds.len()
|
||||
&& matches!(self.line_kinds[ln], LineKind::Cordial | LineKind::Eval | LineKind::Comment)
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ impl<Message: Clone + 'static> Block<Message> for TextBlock {
|
|||
user_idents: syntax::scan_user_idents_in(&source),
|
||||
rules: syntax::SyntaxRules::cordial(),
|
||||
source,
|
||||
heavy_token: 0,
|
||||
};
|
||||
let editor_el: Element<'a, Message, Theme, iced_wgpu::Renderer> = editor
|
||||
.highlight_with::<SyntaxHighlighter>(
|
||||
|
|
|
|||
Loading…
Reference in New Issue