Skip to main content

lsp_hierarchy/
lsp_hierarchy.rs

1use std::{env, path::PathBuf, time::Duration};
2
3use anyhow::{Context, Result, anyhow, bail};
4use cgraph::{
5    fetch::{
6        HierarchyQuery,
7        lsp::{LspConfig, LspProvider},
8    },
9    state::{HierarchyDirection, HierarchyKind, SourceLocation, SymbolIdentity},
10};
11use tower_lsp::lsp_types::Url;
12
13#[tokio::main]
14async fn main() -> Result<()> {
15    let mut args = env::args_os().skip(1);
16    let program = args.next().context(
17        "usage: lsp_hierarchy <SERVER> <call|type> <incoming|outgoing> <SYMBOL> [WORKSPACE]",
18    )?;
19    let kind = match utf8_arg(args.next(), "hierarchy kind")?.as_str() {
20        "call" => HierarchyKind::Call,
21        "type" => HierarchyKind::Type,
22        value => bail!("unknown hierarchy kind {value:?}; expected call or type"),
23    };
24    let direction = match utf8_arg(args.next(), "direction")?.as_str() {
25        "incoming" => HierarchyDirection::Incoming,
26        "outgoing" => HierarchyDirection::Outgoing,
27        value => bail!("unknown direction {value:?}; expected incoming or outgoing"),
28    };
29    let symbol = utf8_arg(args.next(), "symbol")?;
30    let workspace_root = match args.next() {
31        Some(path) => PathBuf::from(path),
32        None => env::current_dir().context("failed to determine current directory")?,
33    };
34    let location = match args.next() {
35        Some(file) => {
36            let file = PathBuf::from(file)
37                .canonicalize()
38                .context("failed to resolve source file")?;
39            let line = utf8_arg(args.next(), "one-based line")?
40                .parse::<u32>()
41                .context("line must be a positive integer")?;
42            let character = utf8_arg(args.next(), "one-based character")?
43                .parse::<u32>()
44                .context("character must be a positive integer")?;
45            if line == 0 || character == 0 {
46                bail!("line and character are one-based and must be positive");
47            }
48            let uri = Url::from_file_path(&file)
49                .map_err(|()| anyhow!("source file cannot be represented as a file URI"))?;
50            Some(SourceLocation {
51                uri: uri.to_string(),
52                line: Some(line - 1),
53                character: Some(character - 1),
54            })
55        }
56        None => None,
57    };
58
59    let lsp = LspProvider::start(LspConfig::for_server(program, workspace_root)).await?;
60    tokio::time::sleep(Duration::from_secs(12)).await;
61    let response = lsp
62        .hierarchy_client()
63        .query(HierarchyQuery {
64            symbol: SymbolIdentity {
65                symbol,
66                kind,
67                location,
68            },
69            direction,
70        })
71        .await;
72
73    let shutdown_result = lsp.shutdown().await;
74    let response = response?;
75    for child in response.children {
76        let location = child.location.map_or_else(
77            || "unknown location".to_owned(),
78            |location| {
79                format!(
80                    "{}:{}:{}",
81                    location.uri,
82                    location.line.unwrap_or(0) + 1,
83                    location.character.unwrap_or(0) + 1
84                )
85            },
86        );
87        println!("{}\t{location}", child.symbol);
88    }
89    shutdown_result
90}
91
92fn utf8_arg(value: Option<std::ffi::OsString>, name: &str) -> Result<String> {
93    value
94        .with_context(|| format!("missing {name}"))?
95        .into_string()
96        .map_err(|_| anyhow!("{name} must be valid UTF-8"))
97}