use serde_json::Value;
pub(super) struct SymbolInfo {
pub name: String,
pub kind: u32,
pub file_path: String,
pub line: u32,
pub character: u32,
}
pub(super) const SK_MODULE: u32 = 2;
pub(super) const SK_NAMESPACE: u32 = 3;
pub(super) const SK_PACKAGE: u32 = 4;
pub(super) const SK_CLASS: u32 = 5;
pub(super) const SK_METHOD: u32 = 6;
pub(super) const SK_CONSTRUCTOR: u32 = 9;
pub(super) const SK_ENUM: u32 = 10;
pub(super) const SK_INTERFACE: u32 = 11;
pub(super) const SK_FUNCTION: u32 = 12;
pub(super) const SK_VARIABLE: u32 = 13;
pub(super) const SK_CONSTANT: u32 = 14;
pub(super) const SK_STRING: u32 = 15;
pub(super) const SK_OBJECT: u32 = 19;
pub(super) const SK_KEY: u32 = 20;
pub(super) const SK_STRUCT: u32 = 23;
pub(super) const fn format_symbol_kind(kind: u32) -> &'static str {
match kind {
1 => "File",
SK_MODULE => "Module",
SK_NAMESPACE => "Namespace",
SK_PACKAGE => "Package",
SK_CLASS => "Class",
SK_METHOD => "Method",
7 => "Property",
8 => "Field",
SK_CONSTRUCTOR => "Constructor",
SK_ENUM => "Enum",
SK_INTERFACE => "Interface",
SK_FUNCTION => "Function",
SK_VARIABLE => "Variable",
SK_CONSTANT => "Constant",
SK_STRING => "String",
16 => "Number",
17 => "Boolean",
18 => "Array",
SK_OBJECT => "Object",
SK_KEY => "Key",
21 => "Null",
22 => "EnumMember",
SK_STRUCT => "Struct",
24 => "Event",
25 => "Operator",
26 => "TypeParameter",
_ => "Unknown",
}
}
pub(super) const fn is_outline_kind(kind: u32) -> bool {
matches!(
kind,
SK_STRUCT
| SK_CLASS
| SK_ENUM
| SK_INTERFACE
| SK_MODULE
| SK_NAMESPACE
| SK_PACKAGE
| SK_CONSTANT
| SK_OBJECT
| SK_STRING
| SK_KEY
)
}
pub(super) fn extract_symbol_infos(response: &Value) -> Vec<SymbolInfo> {
let Some(arr) = response.as_array() else {
return Vec::new();
};
let mut result = Vec::new();
for item in arr {
let Some(name) = item.get("name").and_then(Value::as_str) else {
continue;
};
let kind = item
.get("kind")
.and_then(Value::as_u64)
.and_then(|n| u32::try_from(n).ok())
.unwrap_or(0);
if let Some(location) = item.get("location")
&& let Some(info) = symbol_from_location(name, kind, location)
{
result.push(info);
continue;
}
if let Some(uri_str) = item
.get("location")
.and_then(|l| l.get("uri"))
.and_then(Value::as_str)
&& let Some(range) = item.get("location").and_then(|l| l.get("range"))
{
let line = range_start_line(range);
let character = range_start_character(range);
if let Some(path) = uri_to_path(uri_str) {
result.push(SymbolInfo {
name: name.to_string(),
kind,
file_path: path,
line,
character,
});
}
}
}
result
}
pub(super) fn extract_locations(response: &Value) -> Vec<(String, u32, u32)> {
if response.is_null() {
return Vec::new();
}
if let Some(arr) = response.as_array() {
let mut result = Vec::new();
for item in arr {
if let Some(uri) = item.get("targetUri").and_then(Value::as_str) {
if let Some(path) = uri_to_path(uri) {
let line = item.get("targetRange").map_or(0, range_start_line);
let character = item.get("targetRange").map_or(0, range_start_character);
result.push((path, line, character));
}
}
else if let Some(uri) = item.get("uri").and_then(Value::as_str)
&& let Some(path) = uri_to_path(uri)
{
let line = item.get("range").map_or(0, range_start_line);
let character = item.get("range").map_or(0, range_start_character);
result.push((path, line, character));
}
}
return result;
}
if let Some(uri) = response.get("uri").and_then(Value::as_str)
&& let Some(path) = uri_to_path(uri)
{
let line = response.get("range").map_or(0, range_start_line);
let character = response.get("range").map_or(0, range_start_character);
return vec![(path, line, character)];
}
Vec::new()
}
fn symbol_from_location(name: &str, kind: u32, location: &Value) -> Option<SymbolInfo> {
let uri = location.get("uri")?.as_str()?;
let path = uri_to_path(uri)?;
let range = location.get("range")?;
Some(SymbolInfo {
name: name.to_string(),
kind,
file_path: path,
line: range_start_line(range),
character: range_start_character(range),
})
}
fn range_start_line(range: &Value) -> u32 {
range
.get("start")
.and_then(|s| s.get("line"))
.and_then(Value::as_u64)
.and_then(|n| u32::try_from(n).ok())
.unwrap_or(0)
}
fn range_start_character(range: &Value) -> u32 {
range
.get("start")
.and_then(|s| s.get("character"))
.and_then(Value::as_u64)
.and_then(|n| u32::try_from(n).ok())
.unwrap_or(0)
}
pub(super) fn uri_to_path(uri: &str) -> Option<String> {
uri.strip_prefix("file://").map(str::to_string)
}