cymbal 0.7.0

search for symbols in a codebase
use std::{fmt::Display, io::Write, thread::JoinHandle};

use anyhow::Context;
use crossbeam::channel::Sender;

use crate::{config::Language, ext::ResultExt, symbol::Symbol};

pub enum Message {
  Symbol(String),
  Stop,
}

#[derive(Clone)]
pub struct Writer {
  delimiter: char,
  separator: char,

  pub sink: Sender<Message>,
}

impl Writer {
  pub fn spawn(
    delimiter: char,
    separator: char,
    capacity: usize,
  ) -> Result<(Self, JoinHandle<Result<(), anyhow::Error>>), anyhow::Error> {
    let (send, recv) = crossbeam::channel::bounded(capacity);

    let handle = std::thread::spawn(move || {
      while let Ok(Message::Symbol(symbol)) = recv.recv() {
        let mut stdout = std::io::stdout().lock();
        write!(stdout, "{symbol}").context("failed to write to stdout")?;
      }

      Ok(())
    });

    (
      Self {
        delimiter,
        separator,
        sink: send,
      },
      handle,
    )
      .ok()
  }

  pub fn send<P, T>(&self, language: Language, symbol: &Symbol<P, T>) -> Result<(), anyhow::Error>
  where
    P: Display,
    T: Display,
  {
    self
      .sink
      .send(Message::Symbol(format!(
        "{lang}{dlm}{kind}{dlm}{path}{dlm}{line}{dlm}{col}{dlm}{lead}{dlm}{text}{dlm}{tail}{end}",
        lang = language.colored_abbreviation(),
        kind = symbol.kind.colored_abbreviation(),
        path = symbol.path,
        line = symbol.span.start.line,
        col = symbol.span.start.column,
        lead = symbol.lead,
        text = symbol.text,
        tail = symbol.tail,
        dlm = self.delimiter,
        end = self.separator,
      )))
      .context("failed to send message")
  }

  pub fn stop(&self) -> Result<(), anyhow::Error> {
    self.sink.send(Message::Stop).context("failed to send stop message")
  }
}