Skip to main content

cgraph/fetch/
mod.rs

1#![doc = include_str!("README.md")]
2
3pub mod lsp;
4pub mod treesitter;
5
6use lsp::{
7    HierarchyClient as LspHierarchyClient, LspProvider,
8    WorkspaceSymbolClient as LspWorkspaceSymbolClient,
9};
10use treesitter::{
11    HierarchyClient as TreeSitterHierarchyClient, TreeSitterProvider,
12    WorkspaceSymbolClient as TreeSitterWorkspaceSymbolClient,
13};
14
15use crate::state::{HierarchyDirection, SymbolIdentity};
16use tower_lsp::lsp_types::{Range, SymbolKind, Url};
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct WorkspaceSymbolMatch {
20    /// Provider-normalized display name, including the language-appropriate
21    /// class or implementation qualifier for methods when it is known.
22    pub name: String,
23    pub kind: SymbolKind,
24    pub container_name: Option<String>,
25    pub uri: Url,
26    pub range: Option<Range>,
27}
28
29impl WorkspaceSymbolMatch {
30    pub fn display_name(&self) -> String {
31        self.name.clone()
32    }
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum FetchSource {
37    Lsp,
38    TreeSitter,
39}
40
41#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
42pub enum CachePolicy {
43    #[default]
44    UseCache,
45    Refresh,
46}
47
48/// Backend-independent description of one lazy hierarchy expansion.
49#[derive(Clone, Debug, Eq, PartialEq)]
50pub struct HierarchyQuery {
51    pub symbol: SymbolIdentity,
52    pub direction: HierarchyDirection,
53}
54
55/// Normalized one-level result returned by either LSP or Tree-sitter.
56#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct HierarchyResponse {
58    pub query: HierarchyQuery,
59    pub children: Vec<SymbolIdentity>,
60    pub source: FetchSource,
61}
62
63#[derive(Clone, Debug)]
64pub enum WorkspaceSymbolClient {
65    Lsp(LspWorkspaceSymbolClient),
66    TreeSitter(TreeSitterWorkspaceSymbolClient),
67}
68
69impl WorkspaceSymbolClient {
70    pub fn set_workspace_only(&mut self, workspace_only: bool) {
71        if let Self::Lsp(client) = self {
72            client.set_workspace_only(workspace_only);
73        }
74    }
75
76    pub async fn query(&self, query: &str) -> anyhow::Result<Vec<WorkspaceSymbolMatch>> {
77        match self {
78            Self::Lsp(client) => client.query(query).await,
79            Self::TreeSitter(client) => client.query(query).await,
80        }
81    }
82}
83
84impl From<LspWorkspaceSymbolClient> for WorkspaceSymbolClient {
85    fn from(client: LspWorkspaceSymbolClient) -> Self {
86        Self::Lsp(client)
87    }
88}
89
90impl From<TreeSitterWorkspaceSymbolClient> for WorkspaceSymbolClient {
91    fn from(client: TreeSitterWorkspaceSymbolClient) -> Self {
92        Self::TreeSitter(client)
93    }
94}
95
96#[derive(Clone, Debug)]
97pub enum HierarchyClient {
98    Lsp(LspHierarchyClient),
99    TreeSitter(TreeSitterHierarchyClient),
100    Hybrid {
101        lsp: LspHierarchyClient,
102        tree_sitter: TreeSitterHierarchyClient,
103    },
104}
105
106impl HierarchyClient {
107    pub fn set_workspace_only(&mut self, workspace_only: bool) {
108        match self {
109            Self::Lsp(client) => client.set_workspace_only(workspace_only),
110            Self::TreeSitter(_) => {}
111            Self::Hybrid { lsp, .. } => lsp.set_workspace_only(workspace_only),
112        }
113    }
114
115    pub fn with_fallback(lsp: LspHierarchyClient, tree_sitter: TreeSitterHierarchyClient) -> Self {
116        Self::Hybrid { lsp, tree_sitter }
117    }
118
119    pub async fn query(&self, query: HierarchyQuery) -> anyhow::Result<HierarchyResponse> {
120        match self {
121            Self::Lsp(client) => client.query(query).await,
122            Self::TreeSitter(client) => client.query(query).await,
123            Self::Hybrid { lsp, tree_sitter } => {
124                if lsp.supports(query.symbol.kind) {
125                    lsp.query(query).await
126                } else {
127                    tree_sitter.query(query).await
128                }
129            }
130        }
131    }
132}
133
134impl From<LspHierarchyClient> for HierarchyClient {
135    fn from(client: LspHierarchyClient) -> Self {
136        Self::Lsp(client)
137    }
138}
139
140impl From<TreeSitterHierarchyClient> for HierarchyClient {
141    fn from(client: TreeSitterHierarchyClient) -> Self {
142        Self::TreeSitter(client)
143    }
144}
145
146#[derive(Debug, Default)]
147pub struct FetchCoordinator {
148    lsp: Option<LspProvider>,
149    tree_sitter: Option<TreeSitterProvider>,
150}
151
152impl FetchCoordinator {
153    pub fn with_lsp(lsp: LspProvider) -> Self {
154        Self {
155            lsp: Some(lsp),
156            tree_sitter: None,
157        }
158    }
159
160    pub fn with_tree_sitter(tree_sitter: TreeSitterProvider) -> Self {
161        Self {
162            lsp: None,
163            tree_sitter: Some(tree_sitter),
164        }
165    }
166
167    pub fn lsp(&self) -> Option<&LspProvider> {
168        self.lsp.as_ref()
169    }
170
171    pub fn workspace_symbol_client(&self) -> Option<WorkspaceSymbolClient> {
172        self.lsp
173            .as_ref()
174            .map(LspProvider::workspace_symbol_client)
175            .map(WorkspaceSymbolClient::from)
176            .or_else(|| {
177                self.tree_sitter
178                    .as_ref()
179                    .map(TreeSitterProvider::workspace_symbol_client)
180                    .map(WorkspaceSymbolClient::from)
181            })
182    }
183
184    pub fn hierarchy_client(&self) -> Option<HierarchyClient> {
185        self.lsp
186            .as_ref()
187            .map(LspProvider::hierarchy_client)
188            .map(HierarchyClient::from)
189            .or_else(|| {
190                self.tree_sitter
191                    .as_ref()
192                    .map(TreeSitterProvider::hierarchy_client)
193                    .map(HierarchyClient::from)
194            })
195    }
196
197    pub async fn workspace_symbols(
198        &self,
199        query: &str,
200    ) -> anyhow::Result<Vec<WorkspaceSymbolMatch>> {
201        self.workspace_symbol_client()
202            .ok_or_else(|| anyhow::anyhow!("no workspace-symbol provider is configured"))?
203            .query(query)
204            .await
205    }
206
207    pub async fn hierarchy(&self, query: HierarchyQuery) -> anyhow::Result<HierarchyResponse> {
208        self.hierarchy_client()
209            .ok_or_else(|| anyhow::anyhow!("no hierarchy provider is configured"))?
210            .query(query)
211            .await
212    }
213}