use std::io::IsTerminal;
use std::path::Path;
use serde_json::{Value, json};
use sinter_core::{Edge, Node};
use sinter_resolve::qualified_of;
pub fn node_json(node: &Node) -> Value {
json!({
"id": node.id.as_str(),
"qualified": qualified_of(node.id.as_str()),
"name": node.name,
"kind": node.kind.as_str(),
"file": node.file,
"span": {"start": node.span.start, "end": node.span.end},
"signature": node.signature,
"doc": node.doc,
})
}
pub fn site_of(repo: &Path, edge: &Edge) -> Option<(String, Option<usize>)> {
let span = edge.site?;
let file = edge
.src
.as_str()
.split_once('#')
.map_or(edge.src.as_str(), |(f, _)| f);
Some((file.to_string(), line_of(repo, file, span.start)))
}
pub fn site_json(repo: &Path, edge: &Edge) -> Value {
match site_of(repo, edge) {
Some((file, Some(line))) => json!(format!("{file}:{line}")),
Some((file, None)) => json!(file),
None => Value::Null,
}
}
pub fn site_location(repo: &Path, edge: &Edge) -> Option<String> {
site_of(repo, edge).map(|(file, line)| location(repo, &file, line))
}
pub fn line_of(repo: &Path, file: &str, byte: u64) -> Option<usize> {
let source = std::fs::read_to_string(repo.join(file)).ok()?;
let upto = source.get(..(byte as usize).min(source.len()))?;
Some(upto.bytes().filter(|b| *b == b'\n').count() + 1)
}
pub fn ellipsize(s: &str, max: usize) -> String {
let chars: Vec<char> = s.chars().collect();
if chars.len() <= max || max < 5 {
return s.to_string();
}
let keep = max - 1;
let slack = (keep / 4).max(1);
let is_sep = |c: char| matches!(c, ',' | ' ' | '(' | ')' | '{');
let mut head_end = keep / 2;
if let Some(i) = (head_end.saturating_sub(slack)..head_end)
.rev()
.find(|&i| is_sep(chars[i]))
{
head_end = i + 1;
}
let mut tail_start = chars.len() - (keep - keep / 2);
if let Some(i) = (tail_start..(tail_start + slack).min(chars.len())).find(|&i| is_sep(chars[i]))
{
tail_start = (i + 1).min(chars.len());
while tail_start < chars.len() && chars[tail_start] == ' ' {
tail_start += 1;
}
}
let head: String = chars[..head_end].iter().collect();
let tail: String = chars[tail_start..].iter().collect();
format!("{}…{tail}", head.trim_end())
}
pub fn location(repo: &Path, file: &str, line: Option<usize>) -> String {
let text = match line {
Some(line) => format!("{file}:{line}"),
None => file.to_string(),
};
if std::io::stdout().is_terminal() {
let target = repo.join(file);
format!(
"\u{1b}]8;;file://{}\u{1b}\\{text}\u{1b}]8;;\u{1b}\\",
target.display()
)
} else {
text
}
}