lsp_hierarchy/
lsp_hierarchy.rs1use 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::new(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}