Skip to content

Commit 79d52c1

Browse files
committed
perf(git): cache status updates and optimize editor refresh
Rework backend git status handling around cached repository metadata and faster per-file updates. Backend git changes: - include head_hash in full and patch git status payloads so clients can distinguish HEAD changes from regular file patches - cache the discovered git repository, metadata snapshots, parsed index entries, HEAD blob SHA values, HEAD blob content, and numstat results - make check_status_changed inspect .git/index, HEAD, current refs, packed refs, and merge/rebase marker files before recomputing a full status - replace the per-file git2 diff path with custom index parsing plus in-memory diffing, while keeping git status --porcelain as a fallback - compute numstat with similar::utils::diff_slices instead of git2 diff iteration - track HEAD changes in the status cache and force full updates when branch or HEAD hash changes - stop creating merge commits with a synthetic Anycode User identity and surface a clear error when git user.name/user.email are not configured Editor and frontend changes: - make Code.init tolerate unsupported languages and tree-sitter WASM load failures by clearing syntax state and resetting the language - centralize tree-sitter language loading through a shared loadLanguage helper with pending-load caching - return an empty language for unknown file extensions instead of defaulting to javascript - remove ruby and php from supported language extension mappings and add toml support - invalidate syntax highlighting cache only from the edited line downward instead of clearing the whole cache - update renderer change refresh to compare visual rows directly and reuse precomputed syntax nodes - avoid requesting git:file-original on patch updates so open editors only refresh original content after full git status updates Tests: - add backend git tests for numstat correctness, subdirectory workdirs, deleted files, and staged-then-deleted states - add editor cache invalidation coverage for preserving cached lines above edits
1 parent f301257 commit 79d52c1

13 files changed

Lines changed: 1128 additions & 200 deletions

File tree

anycode-backend/src/acp.rs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ struct AcpClientImpl {
121121

122122
impl AcpClientImpl {
123123
async fn request_permission(
124-
&self, args: RequestPermissionRequest,
124+
&self,
125+
args: RequestPermissionRequest,
125126
) -> acp::Result<RequestPermissionResponse> {
126127
info!(
127128
"request_permission called for agent {}: {:?}",
@@ -161,7 +162,8 @@ impl AcpClientImpl {
161162
}
162163

163164
async fn write_text_file(
164-
&self, args: acp::WriteTextFileRequest,
165+
&self,
166+
args: acp::WriteTextFileRequest,
165167
) -> acp::Result<acp::WriteTextFileResponse> {
166168
info!(
167169
"write_text_file called for agent {}: path={:?}, content_len={}",
@@ -219,7 +221,8 @@ impl AcpClientImpl {
219221
}
220222

221223
async fn read_text_file(
222-
&self, args: acp::ReadTextFileRequest,
224+
&self,
225+
args: acp::ReadTextFileRequest,
223226
) -> acp::Result<acp::ReadTextFileResponse> {
224227
info!(
225228
"read_text_file called for agent {}: path={:?}",
@@ -274,7 +277,8 @@ impl AcpClientImpl {
274277
}
275278

276279
async fn create_terminal(
277-
&self, _args: acp::CreateTerminalRequest,
280+
&self,
281+
_args: acp::CreateTerminalRequest,
278282
) -> acp::Result<acp::CreateTerminalResponse> {
279283
info!(
280284
"create_terminal called for agent {}: {:?}",
@@ -284,7 +288,8 @@ impl AcpClientImpl {
284288
}
285289

286290
async fn terminal_output(
287-
&self, _args: acp::TerminalOutputRequest,
291+
&self,
292+
_args: acp::TerminalOutputRequest,
288293
) -> acp::Result<acp::TerminalOutputResponse> {
289294
info!(
290295
"terminal_output called for agent {}: {:?}",
@@ -294,7 +299,8 @@ impl AcpClientImpl {
294299
}
295300

296301
async fn release_terminal(
297-
&self, _args: acp::ReleaseTerminalRequest,
302+
&self,
303+
_args: acp::ReleaseTerminalRequest,
298304
) -> acp::Result<acp::ReleaseTerminalResponse> {
299305
info!(
300306
"release_terminal called for agent {}: {:?}",
@@ -304,7 +310,8 @@ impl AcpClientImpl {
304310
}
305311

306312
async fn wait_for_terminal_exit(
307-
&self, _args: acp::WaitForTerminalExitRequest,
313+
&self,
314+
_args: acp::WaitForTerminalExitRequest,
308315
) -> acp::Result<acp::WaitForTerminalExitResponse> {
309316
info!(
310317
"wait_for_terminal_exit called for agent {}: {:?}",
@@ -314,7 +321,8 @@ impl AcpClientImpl {
314321
}
315322

316323
async fn kill_terminal_command(
317-
&self, _args: acp::KillTerminalRequest,
324+
&self,
325+
_args: acp::KillTerminalRequest,
318326
) -> acp::Result<acp::KillTerminalResponse> {
319327
info!(
320328
"kill_terminal_command called for agent {}: {:?}",
@@ -323,9 +331,7 @@ impl AcpClientImpl {
323331
Err(acp::Error::method_not_found())
324332
}
325333

326-
async fn session_notification(
327-
&self, args: acp::SessionNotification,
328-
) -> acp::Result<()> {
334+
async fn session_notification(&self, args: acp::SessionNotification) -> acp::Result<()> {
329335
info!(
330336
"session_notification received for agent {}: {:?}",
331337
self.agent_id, args.update
@@ -347,7 +353,8 @@ impl AcpClientImpl {
347353
}
348354

349355
fn append_to_previous_raw_chunk(
350-
history: &mut [AcpMessage], update: &acp::SessionUpdate,
356+
history: &mut [AcpMessage],
357+
update: &acp::SessionUpdate,
351358
) -> bool {
352359
let (expected_kind, chunk_text) = match update {
353360
acp::SessionUpdate::AgentMessageChunk(chunk) => {
@@ -384,9 +391,7 @@ impl AcpClientImpl {
384391
false
385392
}
386393

387-
fn append_or_push_error_message(
388-
history: &mut Vec<AcpMessage>, text: &str,
389-
) -> AcpMessage {
394+
fn append_or_push_error_message(history: &mut Vec<AcpMessage>, text: &str) -> AcpMessage {
390395
if let Some(last_idx) = history.len().checked_sub(1) {
391396
if let AcpMessage::Error(AcpError { message }) = &history[last_idx] {
392397
let mut merged = message.clone();

anycode-backend/src/background_tasks.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ fn spawn_git_status_watcher(state: &AppState, io: &Arc<SocketIo>) {
3636
let socket = io.clone();
3737

3838
tokio::spawn(async move {
39-
let mut ticker = time::interval(Duration::from_secs(1));
39+
let mut ticker = time::interval(Duration::from_secs(2));
40+
ticker.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
41+
4042
loop {
4143
ticker.tick().await;
4244

@@ -55,11 +57,7 @@ fn spawn_git_status_watcher(state: &AppState, io: &Arc<SocketIo>) {
5557
});
5658
}
5759

58-
fn spawn_acp_fs(
59-
state: &AppState,
60-
io: &Arc<SocketIo>,
61-
acp_fs_rx: Receiver<acp_fs::AcpFsCommand>,
62-
) {
60+
fn spawn_acp_fs(state: &AppState, io: &Arc<SocketIo>, acp_fs_rx: Receiver<acp_fs::AcpFsCommand>) {
6361
let file2code = state.file2code.clone();
6462
let lsp_manager = state.lsp_manager.clone();
6563
let config = state.config.as_ref().clone();
@@ -74,10 +72,7 @@ fn spawn_acp_fs(
7472
));
7573
}
7674

77-
fn spawn_diagnostics(
78-
io: &Arc<SocketIo>,
79-
mut diagnostics_rx: Receiver<PublishDiagnosticsParams>,
80-
) {
75+
fn spawn_diagnostics(io: &Arc<SocketIo>, mut diagnostics_rx: Receiver<PublishDiagnosticsParams>) {
8176
let socket = io.clone();
8277
tokio::spawn(async move {
8378
while let Some(diagnostic_message) = diagnostics_rx.recv().await {
@@ -89,10 +84,7 @@ fn spawn_diagnostics(
8984
});
9085
}
9186

92-
fn spawn_file_watcher(
93-
state: &AppState,
94-
io: &Arc<SocketIo>,
95-
) -> Result<notify::RecommendedWatcher> {
87+
fn spawn_file_watcher(state: &AppState, io: &Arc<SocketIo>) -> Result<notify::RecommendedWatcher> {
9688
let file2code = state.file2code.clone();
9789
let socket2data = state.socket2data.clone();
9890
let git_manager = state.git_manager.clone();

anycode-backend/src/config.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ mod congif_tests {
232232
#[test]
233233
fn test_themes() {
234234
assert!(Themes::iter().count() > 0);
235-
let default_theme = Themes::get("default-theme.json").expect("default-theme.json should be embedded");
235+
let default_theme =
236+
Themes::get("default-theme.json").expect("default-theme.json should be embedded");
236237
let content = std::str::from_utf8(default_theme.data.as_ref()).unwrap();
237238
assert!(content.contains("themes"));
238239
}

0 commit comments

Comments
 (0)