use std::io::IsTerminal;
use std::path::PathBuf;
use ansi_term::{Color, Style};
use glua_code_analysis::{DbIndex, FileId, LuaDocument};
use lsp_types::{Diagnostic, DiagnosticSeverity};
#[derive(Debug)]
pub struct TerminalDisplay {
workspace: PathBuf,
supports_color: bool,
supports_underline: bool,
}
impl TerminalDisplay {
pub fn new(workspace: PathBuf) -> Self {
let supports_color = std::io::stdout().is_terminal();
let supports_underline = supports_color && Self::check_underline_support();
Self {
workspace,
supports_color,
supports_underline,
}
}
fn check_underline_support() -> bool {
std::env::var("TERM").is_ok() || std::env::var("WT_SESSION").is_ok()
}
pub fn display_diagnostics(
&mut self,
db: &DbIndex,
file_id: FileId,
diagnostics: Vec<Diagnostic>,
) {
if diagnostics.is_empty() {
return;
}
let file_path = self.get_relative_path(db, file_id);
let document = db.get_vfs().get_document(&file_id).unwrap();
let text = document.get_text();
let text_lines = text.lines().collect::<Vec<&str>>();
let mut error_count = 0;
let mut warning_count = 0;
let mut info_count = 0;
let mut hint_count = 0;
for diagnostic in &diagnostics {
match diagnostic.severity {
Some(DiagnosticSeverity::ERROR) => error_count += 1,
Some(DiagnosticSeverity::WARNING) => warning_count += 1,
Some(DiagnosticSeverity::INFORMATION) => info_count += 1,
Some(DiagnosticSeverity::HINT) => hint_count += 1,
_ => {}
}
}
self.print_file_header(
&file_path,
error_count,
warning_count,
info_count,
hint_count,
);
for diagnostic in diagnostics {
self.display_single_diagnostic(&file_path, &document, &text_lines, diagnostic);
}
println!(); }
fn get_relative_path(&self, db: &DbIndex, file_id: FileId) -> String {
let mut file_path = db.get_vfs().get_file_path(&file_id).unwrap().clone();
if let Ok(new_file_path) = file_path.strip_prefix(&self.workspace) {
file_path = new_file_path.to_path_buf();
}
file_path.to_string_lossy().to_string()
}
fn print_file_header(
&self,
file_path: &str,
error_count: usize,
warning_count: usize,
info_count: usize,
hint_count: usize,
) {
if self.supports_color {
print!("{}", Color::Cyan.bold().paint("--- "));
print!("{}", Color::White.bold().paint(file_path));
print!("{}", Color::Cyan.bold().paint(" "));
} else {
print!("--- {} ", file_path);
}
let mut parts = Vec::new();
if error_count > 0 {
let text = format!(
"{} error{}",
error_count,
if error_count > 1 { "s" } else { "" }
);
if self.supports_color {
parts.push(Color::Red.bold().paint(text).to_string());
} else {
parts.push(text);
}
}
if warning_count > 0 {
let text = format!(
"{} warning{}",
warning_count,
if warning_count > 1 { "s" } else { "" }
);
if self.supports_color {
parts.push(Color::Yellow.bold().paint(text).to_string());
} else {
parts.push(text);
}
}
if info_count > 0 {
let text = format!("{} info", info_count);
if self.supports_color {
parts.push(Color::Purple.bold().paint(text).to_string());
} else {
parts.push(text);
}
}
if hint_count > 0 {
let text = format!(
"{} hint{}",
hint_count,
if hint_count > 1 { "s" } else { "" }
);
if self.supports_color {
parts.push(Color::Cyan.bold().paint(text).to_string());
} else {
parts.push(text);
}
}
if !parts.is_empty() {
print!("[{}]", parts.join(", "));
}
println!();
}
fn display_single_diagnostic(
&mut self,
file_path: &str,
document: &LuaDocument,
lines: &[&str],
diagnostic: Diagnostic,
) {
let range = diagnostic.range;
let (level_color, level_symbol, _level_name) = match diagnostic.severity {
Some(DiagnosticSeverity::ERROR) => (Color::Red, "error", "error"),
Some(DiagnosticSeverity::WARNING) => (Color::Yellow, "warning", "warning"),
Some(DiagnosticSeverity::INFORMATION) => (Color::Purple, "info", "info"),
Some(DiagnosticSeverity::HINT) => (Color::Cyan, "hint", "hint"),
_ => (Color::Red, "error", "error"),
};
let code = match diagnostic.code {
Some(code) => match code {
lsp_types::NumberOrString::Number(code) => format!("[{}]", code),
lsp_types::NumberOrString::String(code) => format!("[{}]", code),
},
_ => String::new(),
};
let start_line = range.start.line as usize;
let start_character = range.start.character as usize;
let Some(start_col) = document.get_col_offset_at_line(start_line, start_character) else {
return;
};
let start_col = u32::from(start_col) as usize;
let end_line = range.end.line as usize;
let end_character = range.end.character as usize;
let Some(end_col) = document.get_col_offset_at_line(end_line, end_character) else {
return;
};
let end_col = u32::from(end_col) as usize;
if start_line >= lines.len() {
return;
}
if self.supports_color {
print!("{}: ", level_color.bold().paint(level_symbol));
print!("{}", Style::new().bold().paint(&diagnostic.message));
if !code.is_empty() {
print!(" {}", Color::Fixed(8).paint(&code)); }
} else {
print!("{}: {}", level_symbol, diagnostic.message);
if !code.is_empty() {
print!(" {}", code);
}
}
println!();
if self.supports_color {
println!(
" {}: {}:{}:{}",
Color::Fixed(8).paint("-->"), file_path,
start_line + 1,
start_character + 1
);
} else {
println!(
" --> {}:{}:{}",
file_path,
start_line + 1,
start_character + 1
);
}
let context_start = if start_line > 0 { start_line - 1 } else { 0 };
let context_end = std::cmp::min(end_line + 1, lines.len() - 1);
let max_line_num = context_end + 1;
let line_num_width = max_line_num.to_string().len();
println!();
for (i, line_text) in lines
.iter()
.enumerate()
.take(context_end + 1)
.skip(context_start)
{
let line_num = i + 1;
if self.supports_color {
print!(
" {} │ ",
Color::Cyan.paint(format!("{:width$}", line_num, width = line_num_width))
);
} else {
print!(" {:width$} | ", line_num, width = line_num_width);
}
if i >= start_line && i <= end_line {
if i == start_line && i == end_line {
let prefix = &line_text[..std::cmp::min(start_col, line_text.len())];
let error_part = if start_col < line_text.len() && end_col <= line_text.len() {
&line_text[start_col..end_col]
} else if start_col < line_text.len() {
&line_text[start_col..]
} else {
""
};
let suffix = if end_col < line_text.len() {
&line_text[end_col..]
} else {
""
};
print!("{}", prefix);
if self.supports_color && !error_part.is_empty() {
let mut style = level_color.bold();
if self.supports_underline {
style = style.underline();
}
print!("{}", style.paint(error_part));
} else {
print!("{}", error_part);
}
println!("{}", suffix);
} else {
if self.supports_color {
let mut style = level_color.bold();
if self.supports_underline {
style = style.underline();
}
println!("{}", style.paint(*line_text));
} else {
println!("{}", line_text);
}
}
} else {
println!("{}", line_text);
}
}
println!(); }
pub fn print_summary(
&self,
total_errors: usize,
total_warnings: usize,
total_info: usize,
total_hints: usize,
) {
if total_errors == 0 && total_warnings == 0 && total_info == 0 && total_hints == 0 {
if self.supports_color {
println!("{}", Color::Green.bold().paint("No issues found"));
} else {
println!("No issues found");
}
return;
}
println!();
if self.supports_color {
println!("{}", Color::Cyan.bold().paint("Summary"));
} else {
println!("Summary");
}
if total_errors > 0 {
let text = format!(
" {} error{}",
total_errors,
if total_errors > 1 { "s" } else { "" }
);
if self.supports_color {
println!("{}", Color::Red.bold().paint(text));
} else {
println!("{}", text);
}
}
if total_warnings > 0 {
let text = format!(
" {} warning{}",
total_warnings,
if total_warnings > 1 { "s" } else { "" }
);
if self.supports_color {
println!("{}", Color::Yellow.bold().paint(text));
} else {
println!("{}", text);
}
}
if total_info > 0 {
let text = format!(" {} info", total_info);
if self.supports_color {
println!("{}", Color::Purple.bold().paint(text));
} else {
println!("{}", text);
}
}
if total_hints > 0 {
let text = format!(
" {} hint{}",
total_hints,
if total_hints > 1 { "s" } else { "" }
);
if self.supports_color {
println!("{}", Color::Cyan.bold().paint(text));
} else {
println!("{}", text);
}
}
if total_errors > 0 {
if self.supports_color {
println!("\n{}", Color::Red.bold().paint("Check failed"));
} else {
println!("\nCheck failed");
}
} else if total_warnings > 0 {
if self.supports_color {
println!(
"\n{}",
Color::Yellow.bold().paint("Check completed with warnings")
);
} else {
println!("\nCheck completed with warnings");
}
} else if self.supports_color {
println!("\n{}", Color::Green.bold().paint("Check successful"));
} else {
println!("\nCheck successful");
}
}
}