Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/codegraph-context/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ codegraph-core = { path = "../codegraph-core" }
codegraph-graph = { path = "../codegraph-graph" }
serde = { workspace = true }
serde_json = { workspace = true }

[dev-dependencies]
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
tempfile = "3"
codegraph-graph = { path = "../codegraph-graph", features = ["sqlite"] }
106 changes: 105 additions & 1 deletion crates/codegraph-context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ pub async fn build_response(
req: &ContextRequest,
) -> Result<ContextResponse> {
let idx = index.ensure_fresh().await;
let candidates = idx
// Try symbol-name search first, then fallback to file-path search.
let mut candidates = idx
.search_symbol_paged_resumable(
&req.query,
None,
Expand All @@ -87,6 +88,36 @@ pub async fn build_response(
)
.await?
.page;
if candidates.is_empty() {
// Fallback: query as filename (strip extension for symbol-name search).
let query_stripped = req
.query
.split('/')
.next_back()
.and_then(|f| {
let without_ext = f.rsplit_once('.')?.0;
if without_ext.is_empty() {
None
} else {
Some(without_ext.to_string())
}
})
.unwrap_or_else(|| req.query.clone());
candidates = idx
.search_symbol_paged_resumable(
&query_stripped,
None,
SymbolMatch::Contains,
Pagination {
limit: req.limit as usize,
offset: 0,
},
None,
None,
)
.await?
.page;
}

// Pre-load mỗi file một lần khi cần source.
let file_cache: HashMap<String, Vec<String>> = if req.include_source {
Expand Down Expand Up @@ -190,3 +221,76 @@ fn render_markdown(resp: &ContextResponse, strip: Option<&str>) -> String {
}
out
}

#[cfg(test)]
mod tests {
use super::*;
use codegraph_graph::SharedGraphIndex;
use std::sync::Arc;

fn sym(name: &str, id: u64) -> codegraph_core::Symbol {
codegraph_core::Symbol {
id,
name: name.to_string(),
kind: codegraph_core::SymbolKind::Function,
scope: codegraph_core::ScopeLevel::Global,
scope_id: 0,
type_ref: 0,
type_name: None,
file: "RestEndpoint.java".into(),
line: 1,
end_line: 2,
signature: None,
doc: None,
annotations: Vec::new(),
language: "java".into(),
}
}

#[tokio::test]
async fn context_fallback_matches_filename() {
// Tạo index với symbol "RestEndpoint" trong file "RestEndpoint.java" dùng sqlite temp.
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("test.db");
let db_str = format!("sqlite://{}", db_path.to_string_lossy());

{
let mut idx = codegraph_graph::GraphIndex::open(&db_str).await.unwrap();
let r = codegraph_graph::ParseResult {
path: "RestEndpoint.java".into(),
language: "java".into(),
bytes: 0,
lines: 0,
symbols: vec![sym("RestEndpoint", 100)],
chains: std::collections::HashMap::new(),
calls: vec![],
};
idx.ingest(&[r]).await.unwrap();
}

let sgi = SharedGraphIndex::open(Some(db_str.clone())).await.unwrap();

// Query "RestEndpoint.java" → không match theo tên symbol → fallback tìm "RestEndpoint".
let req = ContextRequest {
query: "RestEndpoint.java".into(),
depth: 1,
include_source: false,
limit: 5,
format: Format::Markdown,
strip_prefix: None,
};
let sgi_arc: Arc<SharedGraphIndex> = Arc::new(sgi);
let resp = build_response(&sgi_arc, &req).await.unwrap();
assert!(!resp.hits.is_empty(), "phải match qua fallback filename");
assert_eq!(resp.hits[0].symbol.name, "RestEndpoint");

// Query "RestEndpoint" (không có extension) → match trực tiếp.
let req2 = ContextRequest {
query: "RestEndpoint".into(),
..req
};
let resp2 = build_response(&sgi_arc, &req2).await.unwrap();
assert!(!resp2.hits.is_empty());
assert_eq!(resp2.hits[0].symbol.name, "RestEndpoint");
}
}
Loading
Loading