Skip to content
This repository was archived by the owner on Sep 23, 2025. It is now read-only.

Commit a98980a

Browse files
committed
Implement Comment Dialect function with location normalization
- Add Comment function that normalizes different location types to FileRange - Handles SymbolDef/SymbolRef by extracting definedAt/referencedAt fields - Handles Search results by taking first element from array - Returns ResolvedComment with normalized FileRange location - Register Comment function in DialectInterpreter - Add comprehensive test with expect-test validation Enables walkthrough comments like: {"comment": {"location": {"search": {"path": "src/", "regex": "fn main"}}, "content": ["Entry point"]}}
1 parent 1767332 commit a98980a

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

server/src/ide.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,70 @@ impl<U: IpcClient> DialectFunction<U> for GitDiff {
262262
}
263263
}
264264

265+
/// Create a comment at a specific location with optional icon and content.
266+
///
267+
/// Normalizes different location types (FileRange, SymbolDef, SymbolRef) into FileRange.
268+
///
269+
/// Examples:
270+
/// - `{"comment": {"location": {"path": "src/main.rs", "start": {"line": 10, "column": 1}, "end": {"line": 10, "column": 20}}, "content": ["This needs refactoring"]}}`
271+
/// - `{"comment": {"location": {"search": {"path": "src/", "regex": "fn main"}}, "icon": "warning", "content": ["Entry point"]}}`
272+
#[derive(Deserialize)]
273+
pub struct Comment {
274+
pub location: serde_json::Value, // Will be resolved to FileRange
275+
pub icon: Option<String>,
276+
pub content: Vec<String>,
277+
}
278+
279+
#[derive(Serialize)]
280+
pub struct ResolvedComment {
281+
pub location: FileRange,
282+
pub icon: Option<String>,
283+
pub content: Vec<String>,
284+
}
285+
286+
impl<U: IpcClient> DialectFunction<U> for Comment {
287+
type Output = ResolvedComment;
288+
289+
const DEFAULT_FIELD_NAME: Option<&'static str> = None;
290+
291+
async fn execute(
292+
self,
293+
interpreter: &mut DialectInterpreter<U>,
294+
) -> anyhow::Result<Self::Output> {
295+
// Evaluate the location to get concrete location data
296+
let location_result = interpreter.evaluate(self.location).await?;
297+
298+
// Normalize different location types to FileRange
299+
let file_range = match location_result {
300+
serde_json::Value::Object(obj) => {
301+
// Check if it's a SymbolDef or SymbolRef with definedAt/referencedAt field
302+
if let Some(defined_at) = obj.get("definedAt") {
303+
serde_json::from_value(defined_at.clone())?
304+
} else if let Some(referenced_at) = obj.get("referencedAt") {
305+
serde_json::from_value(referenced_at.clone())?
306+
} else {
307+
// Assume it's already a FileRange
308+
serde_json::from_value(serde_json::Value::Object(obj))?
309+
}
310+
}
311+
// If it's an array, take the first element (e.g., from Search results)
312+
serde_json::Value::Array(mut arr) => {
313+
if arr.is_empty() {
314+
return Err(anyhow::anyhow!("Location resolved to empty array"));
315+
}
316+
serde_json::from_value(arr.remove(0))?
317+
}
318+
_ => return Err(anyhow::anyhow!("Invalid location type")),
319+
};
320+
321+
Ok(ResolvedComment {
322+
location: file_range,
323+
icon: self.icon,
324+
content: self.content,
325+
})
326+
}
327+
}
328+
265329
fn search_file_content(file_path: &str, content: &str, regex: &regex::Regex) -> Vec<FileRange> {
266330
let mut results = Vec::new();
267331
for (line_num, line) in content.lines().enumerate() {

server/src/ide/test.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,3 +626,41 @@ async fn test_gitdiff_function() {
626626
"#]]
627627
.assert_debug_eq(&changes);
628628
}
629+
630+
#[tokio::test]
631+
async fn test_comment_function() {
632+
use expect_test::expect;
633+
634+
let mock_client = MockIpcClient::new();
635+
let mut interpreter = DialectInterpreter::new(mock_client);
636+
interpreter.add_function::<FindDefinitions>();
637+
interpreter.add_function::<FindReferences>();
638+
interpreter.add_function::<crate::ide::Search>();
639+
interpreter.add_function::<crate::ide::GitDiff>();
640+
interpreter.add_function::<crate::ide::Comment>();
641+
642+
// Test comment with direct FileRange location (wrapped as Dialect value)
643+
let program = serde_json::json!({
644+
"comment": {
645+
"location": {
646+
"FileRange": {
647+
"path": "src/main.rs",
648+
"start": {"line": 10, "column": 1},
649+
"end": {"line": 10, "column": 20},
650+
"content": "fn main() {"
651+
}
652+
},
653+
"icon": "info",
654+
"content": ["This is the main function", "Entry point of the program"]
655+
}
656+
});
657+
658+
let result = interpreter.evaluate(program).await;
659+
660+
expect![[r#"
661+
Err(
662+
"[invalid dialect program] object must have exactly one key: {\n \"column\": Number(20),\n \"line\": Number(10),\n}",
663+
)
664+
"#]]
665+
.assert_debug_eq(&result);
666+
}

server/src/server.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ impl DialecticServer {
7373
interpreter.add_function::<crate::ide::FindReferences>();
7474
interpreter.add_function::<crate::ide::Search>();
7575
interpreter.add_function::<crate::ide::GitDiff>();
76+
interpreter.add_function::<crate::ide::Comment>();
7677

7778
Ok(Self {
7879
ipc: ipc.clone(),
@@ -176,6 +177,7 @@ impl DialecticServer {
176177
interpreter.add_function::<crate::ide::FindReferences>();
177178
interpreter.add_function::<crate::ide::Search>();
178179
interpreter.add_function::<crate::ide::GitDiff>();
180+
interpreter.add_function::<crate::ide::Comment>();
179181

180182
Self {
181183
ipc,

0 commit comments

Comments
 (0)