cymbal 0.3.1

search for symbols in a codebase
use anyhow::Context;
use tree_sitter::{Query as TreeSitterQuery, QueryMatch};

use crate::ext::ResultExt;

pub struct Template {
  items: Vec<Item>,
}

pub enum Item {
  Text(String),
  Index(u32),
}

impl Template {
  pub fn parse<S: AsRef<str>>(s: S, query: &TreeSitterQuery) -> Result<Self, anyhow::Error> {
    let s = s.as_ref();
    let mut items = Vec::new();
    let mut rest = s;

    while let Some(start) = rest.find('{') {
      if start > 0 {
        items.push(Item::Text(rest[..start].to_string()));
      }

      let Some(end) = rest[start..].find('}') else {
        anyhow::bail!("Unmatched '{{' in template")
      };

      let name = &rest[start + 1..start + end];
      let index = query
        .capture_index_for_name(name)
        .with_context(|| "non-captured name {name:?}")?;
      items.push(Item::Index(index));

      rest = &rest[start + end + 1..];
    }

    if !rest.is_empty() {
      items.push(Item::Text(rest.to_string()));
    }

    Ok(Template { items })
  }

  pub fn render(&self, m: &QueryMatch, content: &str) -> Result<String, anyhow::Error> {
    self
      .items
      .iter()
      .map(|item| match item {
        Item::Text(s) => s,
        Item::Index(idx) => m
          .captures
          .iter()
          .find(|c| *idx == c.index)
          .map_or("", |c| &content[c.node.start_byte()..c.node.end_byte()]),
      })
      .collect::<Vec<&str>>()
      .join("")
      .ok()
  }
}