use glua_code_analysis::{LuaType, LuaUnionType};
use glua_parser::{LuaExpr, LuaLiteralToken, UnaryOperator};
use crate::handlers::{
completion::completion_data::CompletionColorInfo,
document_color::build_color::{
GmodColor, gmod_color_from_call_expr, gmod_hex_color_from_hex_text,
},
};
pub(crate) fn color_info_from_type(typ: &LuaType) -> Option<CompletionColorInfo> {
let text = match typ {
LuaType::StringConst(text) | LuaType::DocStringConst(text) => text.as_str(),
_ => return None,
};
if !text.starts_with('#') {
return None;
}
let hex_color = gmod_hex_color_from_hex_text(text)?;
Some(completion_color_info(hex_color.color, hex_color.has_alpha))
}
pub(crate) fn color_info_from_expr(expr: &LuaExpr) -> Option<CompletionColorInfo> {
match expr {
LuaExpr::CallExpr(call_expr) => {
let color = gmod_color_from_call_expr(call_expr)?;
let alpha = (color.alpha * 255.0).round() as u8;
let has_explicit_alpha = call_expr
.get_args_list()
.is_some_and(|args| args.get_args().count() >= 4);
Some(completion_color_info(
color,
has_explicit_alpha || alpha != u8::MAX,
))
}
LuaExpr::LiteralExpr(literal_expr) => {
let LuaLiteralToken::String(token) = literal_expr.get_literal()? else {
return None;
};
let text = token.get_value();
if !text.starts_with('#') {
return None;
}
let hex_color = gmod_hex_color_from_hex_text(&text)?;
Some(completion_color_info(hex_color.color, hex_color.has_alpha))
}
_ => None,
}
}
pub(super) fn scalar_literal_detail(typ: &LuaType) -> Option<String> {
let value = match typ {
LuaType::BooleanConst(value) | LuaType::DocBooleanConst(value) => value.to_string(),
LuaType::IntegerConst(value) | LuaType::DocIntegerConst(value) => value.to_string(),
LuaType::FloatConst(value) => value.to_string(),
LuaType::StringConst(value) | LuaType::DocStringConst(value) => {
format!("{:?}", value.as_str())
}
_ => return None,
};
Some(format!(" = {}", truncate_literal_value(&value)))
}
pub(super) fn scalar_literal_description(typ: &LuaType) -> Option<String> {
match typ {
LuaType::BooleanConst(_) | LuaType::DocBooleanConst(_) => Some("boolean".to_string()),
LuaType::IntegerConst(_) | LuaType::DocIntegerConst(_) => Some("integer".to_string()),
LuaType::FloatConst(_) => Some("number".to_string()),
LuaType::StringConst(_) | LuaType::DocStringConst(_) => Some("string".to_string()),
_ => None,
}
}
pub(super) fn gmod_constructor_literal_detail(expr: &LuaExpr) -> Option<String> {
let LuaExpr::CallExpr(call_expr) = expr else {
return None;
};
let prefix = call_expr.get_prefix_expr()?;
let LuaExpr::NameExpr(name_expr) = &prefix else {
return None;
};
let name_token = name_expr.get_name_token()?;
let constructor_name = name_token.get_name_text();
if !is_gmod_literal_constructor_name(&constructor_name) {
return None;
}
let args = call_expr.get_args_list()?.get_args().collect::<Vec<_>>();
if args.len() != 3 {
return None;
}
let components = args
.iter()
.map(numeric_literal_text)
.collect::<Option<Vec<_>>>()?;
Some(format!(
" = {}({})",
constructor_name,
components.join(", ")
))
}
pub(super) fn is_gmod_literal_constructor_type(typ: &LuaType) -> bool {
match typ {
LuaType::Ref(id) | LuaType::Def(id) => {
is_gmod_literal_constructor_name(&id.get_simple_name())
}
LuaType::Instance(instance) => is_gmod_literal_constructor_type(instance.get_base()),
LuaType::Union(union) => match union.as_ref() {
LuaUnionType::Nullable(typ) => is_gmod_literal_constructor_type(typ),
LuaUnionType::Multi(types) => types.iter().any(is_gmod_literal_constructor_type),
},
LuaType::Intersection(intersection) => intersection
.get_types()
.iter()
.any(is_gmod_literal_constructor_type),
_ => false,
}
}
pub(crate) fn is_color_type(typ: &LuaType) -> bool {
match typ {
LuaType::Ref(id) | LuaType::Def(id) => id.get_simple_name() == "Color",
LuaType::Instance(instance) => is_color_type(instance.get_base()),
LuaType::Union(union) => match union.as_ref() {
LuaUnionType::Nullable(typ) => is_color_type(typ),
LuaUnionType::Multi(types) => types.iter().any(is_color_type),
},
LuaType::Intersection(intersection) => intersection.get_types().iter().any(is_color_type),
_ => false,
}
}
pub(crate) fn color_label_detail(color: &CompletionColorInfo) -> String {
format!(" {}", color.gmod_display)
}
pub(crate) fn color_preview_documentation(color: &CompletionColorInfo) -> String {
format!("`{}`", color.gmod_display)
}
fn is_gmod_literal_constructor_name(name: &str) -> bool {
matches!(name, "Vector" | "Angle")
}
fn numeric_literal_text(expr: &LuaExpr) -> Option<String> {
match expr {
LuaExpr::LiteralExpr(literal_expr) => {
let LuaLiteralToken::Number(number) = literal_expr.get_literal()? else {
return None;
};
Some(number.get_number_value().to_string())
}
LuaExpr::UnaryExpr(unary_expr)
if unary_expr.get_op_token()?.get_op() == UnaryOperator::OpUnm =>
{
let inner = unary_expr.get_expr()?;
Some(format!("-{}", numeric_literal_text(&inner)?))
}
_ => None,
}
}
fn completion_color_info(color: GmodColor, include_alpha: bool) -> CompletionColorInfo {
let red = (color.red * 255.0).round() as u8;
let green = (color.green * 255.0).round() as u8;
let blue = (color.blue * 255.0).round() as u8;
let alpha = (color.alpha * 255.0).round() as u8;
let hex = if include_alpha {
format!("#{:02X}{:02X}{:02X}{:02X}", red, green, blue, alpha)
} else {
format!("#{:02X}{:02X}{:02X}", red, green, blue)
};
let gmod_display = if include_alpha {
format!("Color({}, {}, {}, {})", red, green, blue, alpha)
} else {
format!("Color({}, {}, {})", red, green, blue)
};
CompletionColorInfo {
red,
green,
blue,
alpha,
rgb: format!("rgb({}, {}, {})", red, green, blue),
rgba: format!("rgba({}, {}, {}, {})", red, green, blue, alpha),
gmod: format!("Color({}, {}, {}, {})", red, green, blue, alpha),
gmod_display,
hex,
}
}
fn truncate_literal_value(value: &str) -> String {
const MAX_LITERAL_DETAIL_CHARS: usize = 80;
if value.chars().count() <= MAX_LITERAL_DETAIL_CHARS {
return value.to_string();
}
let mut truncated = value
.chars()
.take(MAX_LITERAL_DETAIL_CHARS.saturating_sub(3))
.collect::<String>();
truncated.push_str("...");
truncated
}