Skip to content

Commit d436f1a

Browse files
committed
Enhance README and refactor App component
- Updated README.md to reflect changes in search functionality and added ACP integration details. - Refactored App.tsx to improve socket handling by extracting connection logic into dedicated functions. - Introduced utility functions for path manipulation in utils.ts, enhancing code readability and maintainability. - Removed unused constants related to panel sizes from constants.ts. - Updated backend terminal handling to utilize a new current_dir utility function for better cross-platform compatibility.
1 parent 8477e3f commit d436f1a

8 files changed

Lines changed: 150 additions & 106 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
- **LSP integration**: LSP support for intelligent code completion, go-to-definition, hover information and real-time diagnostics.
1313
- **File system integration**: WebSocket-based backend for browsing and editing files from your local filesystem.
1414
- **Integrated terminal**: Full-featured terminal emulator with WebSocket-based communication, supporting real-time command execution and output.
15-
- **Search functionality**: Quickly find text within files using `Win + f` (`Command + f` for MacOS).
15+
- **Search functionality**: Quickly find text within files using `Meta + f`.
16+
- **ACP integration**: Agent Client Protocol (ACP) support for seamless integration with AI agents. Start, manage, and interact with multiple AI agents directly from the IDE, with support for tool calls, tool results, and real-time message streaming.
1617

1718
## Architecture
1819

anycode-backend/src/handlers/io_handler.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::utils::{abs_file, is_ignored_path};
77
use crate::app_state::*;
88
use crate::error_ack;
99
use lsp_types::{TextDocumentContentChangeEvent, Range, Position};
10+
use std::path::PathBuf;
1011

1112

1213
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -64,7 +65,7 @@ pub async fn handle_dir_list(
6465
info!("Received dir:list: {:?}", request);
6566

6667
let dir = match request.path.as_str().trim() {
67-
"" | "." | "./" => crate::utils::current_dir(),
68+
"" | "." | "./" => crate::utils::current_dir().to_string_lossy().into_owned(),
6869
d => d.to_string(),
6970
};
7071

@@ -402,24 +403,26 @@ pub async fn handle_create(
402403
let name = &request.name;
403404
let is_file = request.is_file;
404405

406+
// Build path using PathBuf for cross-platform compatibility
405407
let full_path = if parent_path.is_empty() || parent_path == "." || parent_path == "./" {
406-
name.clone()
408+
PathBuf::from(name)
407409
} else {
408-
format!("{}/{}", parent_path, name)
410+
PathBuf::from(parent_path).join(name)
409411
};
410412

411413
// For relative paths, we need to join with current directory
412-
let full_path = if full_path.starts_with('/') {
414+
let full_path = if full_path.is_absolute() {
413415
full_path
414416
} else {
415417
// Relative path, join with current directory
416418
let current_dir = std::env::current_dir().unwrap_or_default();
417-
current_dir.join(&full_path).to_string_lossy().to_string()
419+
current_dir.join(&full_path)
418420
};
421+
422+
let full_path_str = full_path.to_string_lossy().to_string();
419423

420424
// Create parent directories if they don't exist
421-
let path_buf = std::path::PathBuf::from(&full_path);
422-
if let Some(parent) = path_buf.parent() {
425+
if let Some(parent) = full_path.parent() {
423426
if let Err(e) = std::fs::create_dir_all(parent) {
424427
error_ack!(ack, &request.name, "Failed to create parent directories: {:?}", e);
425428
}
@@ -429,15 +432,15 @@ pub async fn handle_create(
429432
// Create empty file
430433
match std::fs::File::create(&full_path) {
431434
Ok(_) => {
432-
info!("File created successfully: {}", full_path);
435+
info!("File created successfully: {}", full_path_str);
433436
let mut f2c = state.file2code.lock().await;
434-
let code = f2c.entry(full_path.clone()).or_insert_with(|| {
437+
let code = f2c.entry(full_path_str.clone()).or_insert_with(|| {
435438
Code::new()
436439
});
437-
code.set_file_name(full_path.clone());
440+
code.set_file_name(full_path_str.clone());
438441

439-
socket.broadcast().emit("file:created", &full_path).await.ok();
440-
ack.send(&json!({ "success": true, "file": full_path, "is_file": true })).ok();
442+
socket.broadcast().emit("file:created", &full_path_str).await.ok();
443+
ack.send(&json!({ "success": true, "file": full_path_str, "is_file": true })).ok();
441444
},
442445
Err(e) => {
443446
error_ack!(ack, &request.name, "Failed to create file: {:?}", e);
@@ -447,9 +450,9 @@ pub async fn handle_create(
447450
// Create directory
448451
match std::fs::create_dir(&full_path) {
449452
Ok(_) => {
450-
info!("Directory created successfully: {}", full_path);
451-
socket.broadcast().emit("dir:created", &full_path).await.ok();
452-
ack.send(&json!({ "success": true, "dir": full_path, "is_file": false })).ok();
453+
info!("Directory created successfully: {}", full_path_str);
454+
socket.broadcast().emit("dir:created", &full_path_str).await.ok();
455+
ack.send(&json!({ "success": true, "dir": full_path_str, "is_file": false })).ok();
453456
},
454457
Err(e) => {
455458
error_ack!(ack, &request.name, "Failed to create directory: {:?}", e);

anycode-backend/src/terminal.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use tokio::sync::mpsc;
33
use std::io::{Read, Write};
44
use anyhow::Result;
55
use std::path::{Path, PathBuf};
6+
use crate::utils::current_dir;
67

78
pub struct Terminal {
89
name: String,
@@ -34,7 +35,7 @@ impl Terminal {
3435
let command_str = cmd.unwrap_or_else(Self::default_shell);
3536
let mut cmd_builder = CommandBuilder::new(command_str);
3637

37-
let working_dir = cwd.unwrap_or_else(|| Self::get_current_dir());
38+
let working_dir = cwd.unwrap_or_else(|| current_dir());
3839
cmd_builder.cwd(working_dir);
3940

4041
let child = pair.slave.spawn_command(cmd_builder)?;
@@ -78,13 +79,6 @@ impl Terminal {
7879
.to_string()
7980
}
8081

81-
fn get_current_dir() -> PathBuf {
82-
std::env::current_dir().unwrap_or_else(|_| {
83-
// Fallback to home directory or root
84-
dirs::home_dir().unwrap_or_else(|| PathBuf::from("/"))
85-
})
86-
}
87-
8882
fn spawn_pty_reader(
8983
mut reader: Box<dyn Read + Send>,
9084
pty_output_tx: mpsc::Sender<String>,

anycode-backend/src/utils.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,17 @@ pub fn relative_to_current_dir(path: &Path) -> Option<PathBuf> {
139139
path.strip_prefix(&current_dir).ok().map(|p| p.to_path_buf())
140140
}
141141

142-
pub fn current_dir() -> String {
143-
std::env::current_dir().unwrap()
144-
.to_string_lossy().into_owned()
142+
pub fn current_dir() -> PathBuf {
143+
std::env::current_dir().unwrap_or_else(|_| {
144+
// Fallback to home directory or platform-specific root
145+
dirs::home_dir().unwrap_or_else(|| {
146+
if cfg!(target_os = "windows") {
147+
PathBuf::from("C:\\")
148+
} else {
149+
PathBuf::from("/")
150+
}
151+
})
152+
})
145153
}
146154

147155
pub fn get_file_name(input: &str) -> String {

anycode/App.tsx

Lines changed: 64 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
import React, { useState, useEffect, useRef, useCallback } from 'react';
22
import { io, Socket } from 'socket.io-client';
3-
import { AnycodeEditorReact, AnycodeEditor, Edit, Operation } from 'anycode-react';
3+
import { AnycodeEditorReact, AnycodeEditor } from 'anycode-react';
44
import type { Change, Position } from '../anycode-base/src/code';
5-
import { WatcherCreate, WatcherEdits, WatcherRemove, type Cursor, type CursorHistory, type Terminal, type AcpSession, type AcpMessage, type AcpToolCall, type AcpPromptStateMessage} from './types';
6-
import { loadTerminals, loadTerminalSelected, loadTerminalVisible, loadLeftPanelVisible, loadAcpPanelVisible } from './storage';
5+
import { WatcherCreate, WatcherEdits, WatcherRemove,
6+
type CursorHistory, type Terminal, type AcpSession,
7+
type AcpMessage, type AcpPromptStateMessage
8+
} from './types';
9+
import { loadTerminals, loadTerminalSelected, loadTerminalVisible,
10+
loadLeftPanelVisible, loadAcpPanelVisible
11+
} from './storage';
712
import { Allotment } from 'allotment';
813
import 'allotment/dist/style.css';
9-
import { TreeNodeComponent, TreeNode, FileState, TerminalComponent, TerminalTabs, AcpDialog, AgentSettingsDialog } from './components';
10-
import { getAllAgents, getDefaultAgent, updateAgents, getDefaultAgentId, ensureDefaultAgents } from './agents';
14+
import { TreeNodeComponent, TreeNode, FileState, TerminalComponent,
15+
TerminalTabs, AcpDialog
16+
} from './components';
17+
import { getAllAgents, getDefaultAgent, updateAgents, getDefaultAgentId,
18+
ensureDefaultAgents
19+
} from './agents';
1120
import { AcpAgent } from './types';
12-
import { DEFAULT_FILE, DEFAULT_FILE_CONTENT, BACKEND_URL, MIN_LEFT_PANEL_SIZE, LANGUAGE_EXTENSIONS } from './constants';
21+
import { DEFAULT_FILE, DEFAULT_FILE_CONTENT, BACKEND_URL } from './constants';
1322
import './App.css';
1423
import {
1524
Completion, CompletionRequest, Diagnostic, DiagnosticResponse,
1625
DefinitionRequest, DefinitionResponse
1726
} from '../anycode-base/src/lsp';
27+
import { normalizePath, getFileName, getParentPath, joinPath,
28+
getLanguageFromFileName
29+
} from './utils';
1830

1931
const App: React.FC = () => {
2032
console.log('App rendered');
@@ -342,36 +354,10 @@ const App: React.FC = () => {
342354
const ws = io(BACKEND_URL, { transports: ['websocket'] });
343355
wsRef.current = ws;
344356

345-
ws.on('connect', () => {
346-
console.log('Connected to backend');
347-
setIsConnected(true);
348-
setConnectionError(null);
349-
reconnectAttemptsRef.current = 0;
350-
openFolder('.');
351-
352-
terminals.forEach(term => {
353-
console.log('App: Initializing terminal:', term.name);
354-
initializeTerminal(term);
355-
reattachTerminalListener(term.name);
356-
});
357-
358-
// Reconnect to ACP agents
359-
reconnectToAcpAgents();
360-
});
361-
ws.on('disconnect', (reason) => {
362-
console.log('Disconnected from backend', reason);
363-
setIsConnected(false);
364-
attemptReconnect();
365-
});
366-
ws.on('connect_error', (error) => {
367-
console.error('Socket connect error:', error);
368-
setIsConnected(false);
369-
setConnectionError('Failed to connect to backend');
370-
});
371-
ws.on('error', (data) => {
372-
console.error('Backend error:', data);
373-
setConnectionError(data.message);
374-
});
357+
ws.on('connect', handleSocketConnect);
358+
ws.on('disconnect', handleSocketDisconnect);
359+
ws.on('connect_error', handleSocketConnectError);
360+
ws.on('error', handleSocketError);
375361
ws.on("lsp:diagnostics", handleDiagnostics);
376362
ws.on("watcher:edits", handleWatcherEdits);
377363
ws.on("watcher:create", handleWatcherCreate);
@@ -384,6 +370,39 @@ const App: React.FC = () => {
384370
}
385371
};
386372

373+
const handleSocketConnect = () => {
374+
console.log('Connected to backend');
375+
setIsConnected(true);
376+
setConnectionError(null);
377+
reconnectAttemptsRef.current = 0;
378+
openFolder('.');
379+
380+
terminals.forEach(term => {
381+
console.log('App: Initializing terminal:', term.name);
382+
initializeTerminal(term);
383+
reattachTerminalListener(term.name);
384+
});
385+
386+
reconnectToAcpAgents();
387+
};
388+
389+
const handleSocketDisconnect = (reason: string) => {
390+
console.log('Disconnected from backend', reason);
391+
setIsConnected(false);
392+
attemptReconnect();
393+
};
394+
395+
const handleSocketConnectError = (error: Error) => {
396+
console.error('Socket connect error:', error);
397+
setIsConnected(false);
398+
setConnectionError('Failed to connect to backend');
399+
};
400+
401+
const handleSocketError = (data: { message: string }) => {
402+
console.error('Backend error:', data);
403+
setConnectionError(data.message);
404+
};
405+
387406
const handleDiagnostics = (diagnosticsResponse: DiagnosticResponse) => {
388407
console.log("lsp:diagnostics", diagnosticsResponse);
389408

@@ -644,25 +663,20 @@ const App: React.FC = () => {
644663
};
645664

646665
const handleOpenFileResponse = (path: string, content: string) => {
647-
const fileName = path.split('/').pop() || 'untitled';
666+
const fileName = getFileName(path);
648667
const language = getLanguageFromFileName(fileName);
649668
savedFileContentsRef.current.set(path, content);
650669
const newFile: FileState = { id: path, name: fileName, language };
651670
setFiles(prev => [...prev, newFile]);
652671
setActiveFileId(newFile.id);
653672
};
654673

655-
const getLanguageFromFileName = (fileName: string): string => {
656-
const ext = fileName.split('.').pop()?.toLowerCase();
657-
return LANGUAGE_EXTENSIONS[ext || ''] || 'javascript';
658-
};
659-
660674
const convertToTree = (files: string[], dirs: string[], basePath: string): TreeNode[] => {
661675
const treeNodes: TreeNode[] = [];
662676

663677
// Add directories first
664678
dirs.forEach(dirName => {
665-
const dirPath = basePath === '.' ? dirName : `${basePath}/${dirName}`;
679+
const dirPath = basePath === '.' ? dirName : joinPath(basePath, dirName);
666680
treeNodes.push({
667681
id: dirPath,
668682
name: dirName,
@@ -678,7 +692,7 @@ const App: React.FC = () => {
678692

679693
// Add files
680694
files.forEach(fileName => {
681-
const filePath = basePath === '.' ? fileName : `${basePath}/${fileName}`;
695+
const filePath = basePath === '.' ? fileName : joinPath(basePath, fileName);
682696
treeNodes.push({
683697
id: filePath,
684698
name: fileName,
@@ -793,7 +807,7 @@ const App: React.FC = () => {
793807
const line = range.start.line; const column = range.start.character;
794808

795809
const filePath = uri.replace('file://', '');
796-
const fileName = filePath.split('/').pop() || '';
810+
const fileName = getFileName(filePath);
797811

798812
pendingPositions.current.set(filePath, { line, column });
799813

@@ -838,7 +852,7 @@ const App: React.FC = () => {
838852

839853
if (prevPosition && prevPosition.file) {
840854
const filePath = prevPosition.file;
841-
const fileName = filePath.split('/').pop() || '';
855+
const fileName = getFileName(filePath);
842856
const { line, column } = prevPosition.cursor;
843857

844858
pendingPositions.current.set(filePath, { line, column });
@@ -875,7 +889,7 @@ const App: React.FC = () => {
875889
const nextPosition = cursorHistory.current.redoStack.pop();
876890
if (nextPosition && nextPosition.file) {
877891
const filePath = nextPosition.file;
878-
const fileName = filePath.split('/').pop() || '';
892+
const fileName = getFileName(filePath);
879893
const { line, column } = nextPosition.cursor;
880894

881895
pendingPositions.current.set(filePath, { line, column });
@@ -908,9 +922,8 @@ const App: React.FC = () => {
908922
const { path, isFile } = watcherCreate;
909923

910924
// Extract parent path and filename
911-
const parts = path.split('/');
912-
const fileName = parts[parts.length - 1];
913-
const parentPath = parts.slice(0, -1).join('/') || ".";
925+
const fileName = getFileName(path);
926+
const parentPath = getParentPath(path);
914927

915928
setFileTree(prevTree => {
916929
const addNode = (nodes: TreeNode[]): TreeNode[] => {
@@ -1544,7 +1557,6 @@ const App: React.FC = () => {
15441557
{terminalContentPanel}
15451558
</Allotment.Pane>
15461559
</Allotment>
1547-
{/* <div className="terminal-spacer"></div> */}
15481560
</div>
15491561
);
15501562

@@ -1591,8 +1603,6 @@ const App: React.FC = () => {
15911603
</div>
15921604
))}
15931605
</div>
1594-
1595-
{/* <span className="language-indicator">{activeFile?.language.toUpperCase()}</span> */}
15961606
</div>
15971607
);
15981608

@@ -1603,7 +1613,7 @@ const App: React.FC = () => {
16031613
<Allotment vertical={true} defaultSizes={[70, 30]} separator={true} onVisibleChange={handleTerminalPanelVisibleChange}>
16041614
<Allotment.Pane >
16051615
<Allotment vertical={false} defaultSizes={[20,80]} separator={false} onVisibleChange={handleLeftPanelVisibleChange}>
1606-
<Allotment.Pane snap visible={leftPanelVisible} minSize={MIN_LEFT_PANEL_SIZE}>
1616+
<Allotment.Pane snap visible={leftPanelVisible}>
16071617
{fileTreePanel}
16081618
</Allotment.Pane>
16091619
<Allotment.Pane snap>

anycode/constants.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,3 @@ function hello() {
3333
// if curent port is 5173 then use 3000 else use current port
3434
const port = window.location.port === '5173' ? '3000' : window.location.port;
3535
export const BACKEND_URL = `${window.location.protocol}//${window.location.hostname}:${port}`;
36-
37-
// Default panel sizes
38-
export const DEFAULT_LEFT_PANEL_SIZE = 30;
39-
export const DEFAULT_RIGHT_PANEL_SIZE = 70;
40-
export const MIN_LEFT_PANEL_SIZE = 0;
41-
42-
// File extensions mapping
43-
export const LANGUAGE_EXTENSIONS: { [key: string]: string } = {
44-
'js': 'javascript',
45-
'ts': 'typescript',
46-
'jsx': 'javascript',
47-
'tsx': 'typescript',
48-
'py': 'python',
49-
'cpp': 'cpp',
50-
'c': 'c',
51-
'java': 'java',
52-
'html': 'html',
53-
'css': 'css',
54-
'json': 'json',
55-
'rs': 'rust',
56-
'go': 'go',
57-
'rb': 'ruby',
58-
'php': 'php',
59-
'sh': 'bash'
60-
};

anycode/imgs/screen.png

310 KB
Loading

0 commit comments

Comments
 (0)