cymbal 0.8.0

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

use crate::ext::{IntoExt, StrExt};

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> {
    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<S: AsRef<[u8]>>(&self, m: &QueryMatch, content: S) -> Result<String> {
    let content = content.as_ref();

    self
      .items
      .iter()
      .filter_map(|item| match item {
        Item::Text(s) => s.as_str().some(),
        Item::Index(idx) => m
          .captures
          .iter()
          .find(|c| *idx == c.index)
          .map_or("".some(), |c| (&content[c.node.start_byte()..c.node.end_byte()]).to_str()),
      })
      .collect::<Vec<&str>>()
      .join("")
      .ok()
  }
}