Skip to content

Commit 8843061

Browse files
committed
feat: Adds diagnostics listener and support
1 parent f64a4e3 commit 8843061

File tree

6 files changed

+328
-77
lines changed

6 files changed

+328
-77
lines changed

crates/chat-cli/src/cli/chat/tools/code.rs

Lines changed: 158 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::io::Write;
22

33
use eyre::Result;
44
use serde::Deserialize;
5-
use code_agent_sdk::SymbolInfo;
5+
use code_agent_sdk::{SymbolInfo, ApiDiagnosticInfo};
66
use super::{InvokeOutput, OutputKind};
77
use crate::cli::agent::{Agent, PermissionEvalResult};
88
use crate::cli::experiment::experiment_manager::{ExperimentManager, ExperimentName};
@@ -19,6 +19,7 @@ pub enum Code {
1919
Format(FormatCodeParams),
2020
GetDocumentSymbols(GetDocumentSymbolsParams),
2121
LookupSymbols(LookupSymbolsParams),
22+
GetDiagnostics(GetDiagnosticsParams),
2223
InitializeWorkspace,
2324
}
2425

@@ -83,6 +84,15 @@ pub struct LookupSymbolsParams {
8384
pub file_path: Option<String>,
8485
}
8586

87+
#[derive(Debug, Clone, Deserialize)]
88+
pub struct GetDiagnosticsParams {
89+
pub file_path: String,
90+
#[serde(default)]
91+
pub identifier: Option<String>,
92+
#[serde(default)]
93+
pub previous_result_id: Option<String>,
94+
}
95+
8696
fn default_tab_size() -> i32 { 4 }
8797
fn default_insert_spaces() -> bool { true }
8898

@@ -154,6 +164,10 @@ impl Code {
154164
}
155165
Ok(())
156166
}
167+
Code::GetDiagnostics(params) => {
168+
Self::validate_file_exists(os, &params.file_path)?;
169+
Ok(())
170+
}
157171
Code::InitializeWorkspace => Ok(()),
158172
}
159173
}
@@ -205,39 +219,12 @@ impl Code {
205219
if symbols.is_empty() {
206220
queue!(
207221
_stdout,
208-
style::Print("\n🔍 No symbols found matching \""),
209-
StyledText::warning_fg(),
210-
style::Print(&params.symbol_name),
211-
StyledText::reset(),
212-
style::Print("\"\n"),
222+
style::Print("\n⚠️ No symbols found\n"),
213223
)?;
214224
result = "No symbols found".to_string();
215225
} else {
216-
let limit = params.limit.unwrap_or(10) as usize;
217-
let is_truncated = symbols.len() >= limit;
218-
219-
queue!(
220-
_stdout,
221-
style::Print("\n🔍 Found "),
222-
StyledText::success_fg(),
223-
style::Print(&symbols.len().to_string()),
224-
StyledText::reset(),
225-
style::Print(" symbol(s)"),
226-
)?;
227-
228-
if is_truncated {
229-
queue!(
230-
_stdout,
231-
style::Print(" "),
232-
StyledText::warning_fg(),
233-
style::Print("(limited by max results)"),
234-
StyledText::reset(),
235-
)?;
236-
}
237-
238-
queue!(_stdout, style::Print(":\n"))?;
226+
queue!(_stdout, style::Print("\n"))?;
239227
Self::render_symbols(&symbols, _stdout)?;
240-
241228
result = format!("{:?}", symbols);
242229
}
243230
}
@@ -265,25 +252,12 @@ impl Code {
265252
if references.is_empty() {
266253
queue!(
267254
_stdout,
268-
style::Print("\n🔗 No references found for symbol at "),
269-
StyledText::warning_fg(),
270-
style::Print(&format!("{}:{}:{}", params.file_path, params.row, params.column)),
271-
StyledText::reset(),
272-
style::Print("\n"),
255+
style::Print("\n⚠️ No references found\n"),
273256
)?;
274257
result = "No references found".to_string();
275258
} else {
276-
queue!(
277-
_stdout,
278-
style::Print("\n🔗 Found "),
279-
StyledText::success_fg(),
280-
style::Print(&references.len().to_string()),
281-
StyledText::reset(),
282-
style::Print(" reference(s) across workspace:\n"),
283-
)?;
284-
259+
queue!(_stdout, style::Print("\n"))?;
285260
Self::render_references(&references, _stdout)?;
286-
287261
result = format!("{:?}", references);
288262
}
289263
}
@@ -507,6 +481,39 @@ impl Code {
507481
}
508482
}
509483
}
484+
Code::GetDiagnostics(params) => {
485+
let request = code_agent_sdk::model::types::GetDocumentDiagnosticsRequest {
486+
file_path: std::path::PathBuf::from(&params.file_path),
487+
identifier: params.identifier.clone(),
488+
previous_result_id: params.previous_result_id.clone(),
489+
};
490+
491+
match client.get_document_diagnostics(request).await {
492+
Ok(diagnostics) => {
493+
if diagnostics.is_empty() {
494+
queue!(
495+
_stdout,
496+
style::Print("\n✅ No diagnostics found\n"),
497+
)?;
498+
result = "No diagnostics found".to_string();
499+
} else {
500+
queue!(_stdout, style::Print("\n"))?;
501+
Self::render_diagnostics(&diagnostics, _stdout)?;
502+
result = format!("{:?}", diagnostics);
503+
}
504+
}
505+
Err(e) => {
506+
queue!(
507+
_stdout,
508+
StyledText::error_fg(),
509+
style::Print("❌ Failed to get diagnostics: "),
510+
StyledText::reset(),
511+
style::Print(&format!("{}\n", e)),
512+
)?;
513+
result = format!("❌ Failed to get diagnostics: {}", e);
514+
}
515+
}
516+
}
510517
Code::InitializeWorkspace => {
511518
match client.initialize().await {
512519
Ok(init_response) => {
@@ -592,6 +599,103 @@ impl Code {
592599
Ok(())
593600
}
594601

602+
fn render_diagnostics(diagnostics: &[ApiDiagnosticInfo], stdout: &mut impl Write) -> Result<()> {
603+
use crossterm::{queue, style};
604+
use crate::theme::StyledText;
605+
use code_agent_sdk::ApiDiagnosticSeverity;
606+
607+
for (i, diagnostic) in diagnostics.iter().enumerate() {
608+
// Determine severity icon and color
609+
let (severity_icon, severity_text) = match diagnostic.severity {
610+
ApiDiagnosticSeverity::Error => ("❌", "Error"),
611+
ApiDiagnosticSeverity::Warning => ("⚠️", "Warning"),
612+
ApiDiagnosticSeverity::Information => ("ℹ️", "Info"),
613+
ApiDiagnosticSeverity::Hint => ("💡", "Hint"),
614+
};
615+
616+
queue!(
617+
stdout,
618+
style::Print(&format!(" {}. {} ", i + 1, severity_icon)),
619+
)?;
620+
621+
// Color based on severity
622+
match diagnostic.severity {
623+
ApiDiagnosticSeverity::Error => {
624+
queue!(stdout, StyledText::error_fg())?;
625+
}
626+
ApiDiagnosticSeverity::Warning => {
627+
queue!(stdout, StyledText::warning_fg())?;
628+
}
629+
_ => {
630+
queue!(stdout, StyledText::info_fg())?;
631+
}
632+
}
633+
634+
queue!(
635+
stdout,
636+
style::Print(severity_text),
637+
StyledText::reset(),
638+
style::Print(&format!(" at line {}:{}",
639+
diagnostic.start_row,
640+
diagnostic.start_column)),
641+
)?;
642+
643+
// Show diagnostic source if available
644+
if let Some(source) = &diagnostic.source {
645+
queue!(
646+
stdout,
647+
style::Print(" ["),
648+
StyledText::info_fg(),
649+
style::Print(source),
650+
StyledText::reset(),
651+
style::Print("]"),
652+
)?;
653+
}
654+
655+
// Show diagnostic code if available
656+
if let Some(code) = &diagnostic.code {
657+
queue!(
658+
stdout,
659+
style::Print(" ("),
660+
style::Print(code),
661+
style::Print(")"),
662+
)?;
663+
}
664+
665+
queue!(stdout, style::Print("\n"))?;
666+
667+
// Show the diagnostic message (indented)
668+
queue!(
669+
stdout,
670+
style::Print(" "),
671+
style::Print(&diagnostic.message),
672+
style::Print("\n"),
673+
)?;
674+
675+
// Show related information if available
676+
if !diagnostic.related_information.is_empty() {
677+
queue!(
678+
stdout,
679+
style::Print(" Related:\n"),
680+
)?;
681+
for info in &diagnostic.related_information {
682+
queue!(
683+
stdout,
684+
style::Print(" • "),
685+
StyledText::info_fg(),
686+
style::Print(&info.file_path),
687+
StyledText::reset(),
688+
style::Print(&format!(":{}:{} - {}\n",
689+
info.start_row,
690+
info.start_column,
691+
info.message)),
692+
)?;
693+
}
694+
}
695+
}
696+
Ok(())
697+
}
698+
595699
pub fn queue_description(&self, output: &mut impl Write) -> Result<()> {
596700
use crossterm::{queue, style};
597701
use crate::theme::StyledText;
@@ -734,6 +838,15 @@ impl Code {
734838
)?;
735839
}
736840
}
841+
Code::GetDiagnostics(params) => {
842+
queue!(
843+
output,
844+
style::Print("🔍 Getting diagnostics for: "),
845+
StyledText::success_fg(),
846+
style::Print(&params.file_path),
847+
StyledText::reset(),
848+
)?;
849+
}
737850
Code::InitializeWorkspace => {
738851
queue!(
739852
output,

crates/chat-cli/src/cli/chat/tools/tool_index.json

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@
447447
},
448448
"code": {
449449
"name": "code",
450-
"description": "Code intelligence operations using LSP servers for symbol search, references, definitions, and workspace analysis.",
450+
"description": "Code intelligence operations using LSP servers for symbol search, references, definitions, diagnostics, and workspace analysis.",
451451
"input_schema": {
452452
"type": "object",
453453
"properties": {
@@ -461,6 +461,7 @@
461461
"format",
462462
"get_document_symbols",
463463
"lookup_symbols",
464+
"get_diagnostics",
464465
"initialize_workspace"
465466
],
466467
"description": "The code intelligence operation to perform"
@@ -471,48 +472,56 @@
471472
},
472473
"file_path": {
473474
"type": "string",
474-
"description": "Path to the file"
475+
"description": "Path to the file (required for most operations)"
475476
},
476477
"row": {
477478
"type": "integer",
478-
"description": "Line number (1-based)"
479+
"description": "Line number (1-based, required for find_references, goto_definition, rename_symbol)"
479480
},
480481
"column": {
481482
"type": "integer",
482-
"description": "Column number (1-based)"
483+
"description": "Column number (1-based, required for find_references, goto_definition, rename_symbol)"
483484
},
484485
"new_name": {
485486
"type": "string",
486-
"description": "New name for symbol rename"
487+
"description": "New name for symbol rename (required for rename_symbol operation)"
487488
},
488489
"symbols": {
489490
"type": "array",
490491
"items": {"type": "string"},
491-
"description": "List of symbol names to lookup"
492+
"description": "List of symbol names to lookup (required for lookup_symbols operation)"
492493
},
493494
"limit": {
494495
"type": "integer",
495-
"description": "Maximum results to return"
496+
"description": "Maximum results to return (optional for search_symbols)"
496497
},
497498
"exact_match": {
498499
"type": "boolean",
499-
"description": "Whether to use exact matching"
500+
"description": "Whether to use exact matching (optional for search_symbols)"
500501
},
501502
"show_source": {
502503
"type": "boolean",
503-
"description": "Whether to include source code"
504+
"description": "Whether to include source code (optional for goto_definition)"
504505
},
505506
"dry_run": {
506507
"type": "boolean",
507-
"description": "Preview changes without applying"
508+
"description": "Preview changes without applying (optional for rename_symbol)"
508509
},
509510
"tab_size": {
510511
"type": "integer",
511-
"description": "Tab size for formatting"
512+
"description": "Tab size for formatting (optional for format operation, default: 4)"
512513
},
513514
"insert_spaces": {
514515
"type": "boolean",
515-
"description": "Use spaces instead of tabs"
516+
"description": "Use spaces instead of tabs (optional for format operation, default: true)"
517+
},
518+
"identifier": {
519+
"type": "string",
520+
"description": "Optional identifier for the diagnostic request (optional for get_diagnostics)"
521+
},
522+
"previous_result_id": {
523+
"type": "string",
524+
"description": "Optional result ID from a previous diagnostic response for incremental updates (optional for get_diagnostics)"
516525
},
517526
"max_depth": {
518527
"type": "integer",
@@ -524,7 +533,7 @@
524533
},
525534
"symbol_type": {
526535
"type": "string",
527-
"description": "Symbol type filter"
536+
"description": "Symbol type filter (optional for search_symbols)"
528537
}
529538
},
530539
"required": ["operation"]

0 commit comments

Comments
 (0)