Skip to content

Commit 302f0f9

Browse files
committed
Fix: Fixes progressive update, TODO: Implement file watcher.
1 parent edb8fb0 commit 302f0f9

File tree

5 files changed

+133
-8
lines changed

5 files changed

+133
-8
lines changed

crates/code-agent-sdk/src/cli/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ enum Commands {
3636
#[arg(short, long, conflicts_with_all = ["file", "line", "column"])]
3737
name: Option<String>,
3838
/// File containing the symbol (for position-based search)
39-
#[arg(short, long, requires_all = ["line", "column"])]
39+
#[arg(short, long, requires_ifs = [("line", "column"), ("column", "line")])]
4040
file: Option<PathBuf>,
4141
/// Row number (1-based)
4242
#[arg(short, long)]

crates/code-agent-sdk/src/lsp/client.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,24 @@ impl LspClient {
194194
.await
195195
}
196196

197+
/// Notify server about file system changes
198+
///
199+
/// # Arguments
200+
/// * `params` - File change events
201+
pub async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) -> Result<()> {
202+
self.send_notification("workspace/didChangeWatchedFiles", json!(params))
203+
.await
204+
}
205+
206+
/// Notify server about document content changes
207+
///
208+
/// # Arguments
209+
/// * `params` - Document change parameters
210+
pub async fn did_change(&self, params: DidChangeTextDocumentParams) -> Result<()> {
211+
self.send_notification("textDocument/didChange", json!(params))
212+
.await
213+
}
214+
197215
/// Generic LSP request handler with automatic response parsing
198216
async fn send_lsp_request<T, R>(&self, method: &str, params: T) -> Result<Option<R>>
199217
where

crates/code-agent-sdk/src/lsp/config.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl LspConfig {
5353
}),
5454
diagnostic: Some(DiagnosticClientCapabilities {
5555
dynamic_registration: Some(true),
56-
related_document_support: Some(false),
56+
related_document_support: Some(true),
5757
}),
5858
..Default::default()
5959
}),
@@ -70,6 +70,10 @@ impl LspConfig {
7070
diagnostic: Some(DiagnosticWorkspaceClientCapabilities {
7171
refresh_support: Some(true),
7272
}),
73+
did_change_watched_files: Some(DidChangeWatchedFilesClientCapabilities {
74+
dynamic_registration: Some(true),
75+
relative_pattern_support: Some(true),
76+
}),
7377
..Default::default()
7478
}),
7579
..Default::default()

crates/code-agent-sdk/src/sdk/services/coding_service.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,72 @@ impl LspCodingService {
3434
pub fn new(workspace_service: Box<dyn WorkspaceService>) -> Self {
3535
Self { workspace_service }
3636
}
37+
38+
/// Send proper LSP notifications for file changes
39+
async fn notify_file_changes(
40+
&self,
41+
workspace_manager: &mut WorkspaceManager,
42+
workspace_edit: &WorkspaceEdit,
43+
) -> Result<()> {
44+
use lsp_types::{DidChangeTextDocumentParams, VersionedTextDocumentIdentifier, TextDocumentContentChangeEvent};
45+
use std::collections::HashSet;
46+
47+
let mut changed_files = HashSet::new();
48+
49+
// Collect unique files from changes field
50+
if let Some(changes) = &workspace_edit.changes {
51+
for uri in changes.keys() {
52+
if let Ok(file_path) = uri.to_file_path() {
53+
// Only notify for files that are actually opened in LSP
54+
if workspace_manager.is_file_opened(&file_path) {
55+
changed_files.insert((uri.clone(), file_path));
56+
}
57+
}
58+
}
59+
}
60+
61+
// Collect unique files from document_changes field
62+
if let Some(document_changes) = &workspace_edit.document_changes {
63+
if let lsp_types::DocumentChanges::Edits(edits) = document_changes {
64+
for edit in edits {
65+
if let Ok(file_path) = edit.text_document.uri.to_file_path() {
66+
// Only notify for files that are actually opened in LSP
67+
if workspace_manager.is_file_opened(&file_path) {
68+
changed_files.insert((edit.text_document.uri.clone(), file_path));
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
// Send didChange with full content for each opened file
76+
for (uri, file_path) in changed_files {
77+
// Get next version number first
78+
let version = workspace_manager.get_next_version(&file_path);
79+
80+
if let Ok(Some(client)) = workspace_manager.get_client_for_file(&file_path).await {
81+
// Read current file content
82+
if let Ok(content) = std::fs::read_to_string(&file_path) {
83+
let params = DidChangeTextDocumentParams {
84+
text_document: VersionedTextDocumentIdentifier {
85+
uri: uri.clone(),
86+
version,
87+
},
88+
content_changes: vec![TextDocumentContentChangeEvent {
89+
range: None, // Full document update (safer)
90+
range_length: None,
91+
text: content,
92+
}],
93+
};
94+
95+
tracing::trace!("Sending didChange for opened file: {:?}, version: {}", file_path, version);
96+
let _ = client.did_change(params).await;
97+
}
98+
}
99+
}
100+
101+
Ok(())
102+
}
37103
}
38104

39105
#[cfg(test)]
@@ -107,6 +173,10 @@ impl CodingService for LspCodingService {
107173
use crate::utils::apply_workspace_edit;
108174
if let Err(e) = apply_workspace_edit(workspace_edit) {
109175
tracing::trace!("Failed to apply workspace edit: {}", e);
176+
} else {
177+
// Send workspace change notifications.
178+
// TODO: This needs to be improved to have a propper fileWatcher for the repository.
179+
self.notify_file_changes(workspace_manager, workspace_edit).await?;
110180
}
111181
}
112182
} else {

crates/code-agent-sdk/src/sdk/workspace_manager.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@ use crate::config::ConfigManager;
22
use crate::lsp::LspRegistry;
33
use crate::model::types::{LspInfo, WorkspaceInfo};
44
use anyhow::Result;
5-
use std::collections::HashSet;
5+
use std::collections::{HashMap, HashSet};
66
use std::path::{Path, PathBuf};
77
use tracing::warn;
88
use url::Url;
99

10+
/// Tracks file state in LSP servers
11+
#[derive(Debug, Clone)]
12+
pub struct FileState {
13+
pub version: i32,
14+
pub is_open: bool,
15+
}
16+
1017
/// Manages workspace detection and LSP client lifecycle
1118
#[derive(Debug)]
1219
pub struct WorkspaceManager {
1320
workspace_root: PathBuf,
1421
registry: LspRegistry,
1522
initialized: bool,
16-
opened_files: HashSet<PathBuf>,
23+
opened_files: HashMap<PathBuf, FileState>, // Track version and open state
1724
workspace_info: Option<WorkspaceInfo>,
1825
}
1926

@@ -34,7 +41,7 @@ impl WorkspaceManager {
3441
workspace_root: resolved_root,
3542
registry,
3643
initialized: false,
37-
opened_files: HashSet::new(),
44+
opened_files: HashMap::new(),
3845
workspace_info: None,
3946
}
4047
}
@@ -285,12 +292,38 @@ impl WorkspaceManager {
285292

286293
/// Check if a file is already opened
287294
pub fn is_file_opened(&self, file_path: &Path) -> bool {
288-
self.opened_files.contains(file_path)
295+
self.opened_files.get(file_path).map_or(false, |state| state.is_open)
289296
}
290297

291-
/// Mark a file as opened
298+
/// Mark a file as opened with initial version
292299
pub fn mark_file_opened(&mut self, file_path: PathBuf) {
293-
self.opened_files.insert(file_path);
300+
self.opened_files.insert(file_path, FileState {
301+
version: 1,
302+
is_open: true,
303+
});
304+
}
305+
306+
/// Get next version for file and increment it
307+
pub fn get_next_version(&mut self, file_path: &Path) -> i32 {
308+
if let Some(state) = self.opened_files.get_mut(file_path) {
309+
state.version += 1;
310+
state.version
311+
} else {
312+
// File not tracked, start at version 1
313+
self.opened_files.insert(file_path.to_path_buf(), FileState {
314+
version: 1,
315+
is_open: true,
316+
});
317+
1
318+
}
319+
}
320+
321+
/// Mark file as closed
322+
pub fn mark_file_closed(&mut self, file_path: &Path) {
323+
if let Some(state) = self.opened_files.get_mut(file_path) {
324+
state.is_open = false;
325+
state.version = 0;
326+
}
294327
}
295328

296329
/// Get detected workspace languages (cached)

0 commit comments

Comments
 (0)