refactor: DRY cleanup — extract shared helpers, remove duplication across tools, LSP, overlays, and runtime

Eliminate ~500 lines of duplicate code across 31 files by extracting
shared functions, helpers, and consolidating repeated patterns.

Highlights:
- Toast helpers (toast_info/success/warning/error) on AppStateRest
- push_event() helper for turn-event queue (19 callers consolidated)
- log_write_edit_tool() shared fn (turn.rs + engine.rs ~50 lines saved)
- resolve_api_key() shared fn (spawn.rs + provider.rs)
- LSP call_positional() helper on LspClient
- lsp_cursor_params() shared schema for 4 tool files
-overlay_block() helper for consistent overlay title/border styling
- cycle_selected_index(), path_not_found/a_directory() helpers
- mark_dirty(), save_settings() on AppStateRest
- Remove redundant Err(e) => Err(e) arms in LSP tools
- Consolidate generate_workspace_tree (turn.rs → workspace.rs)
- Simplify background-review wrapper args in auto/mod.rs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
asepharyana
2026-07-18 03:18:27 +07:00
co-authored by Claude Opus 4.8
parent b02754acd2
commit 9a6ab62562
31 changed files with 628 additions and 870 deletions
+57 -3
View File
@@ -50,6 +50,48 @@ fn known_extensions_for(language_id: &str) -> &[&'static str] {
}
}
/// Build the standard `server` + `path` + `line` + `column` parameter schema
/// used by cursor-based LSP tools (definition, references, completion).
///
/// When `with_language_id` is `true`, an optional `language_id` property is
/// included (for tools like hover that pass it to `didOpen`).
pub fn lsp_cursor_params(with_language_id: bool) -> serde_json::Value {
let mut props = serde_json::json!({
"server": {
"type": "string",
"description": "Name of the connected LSP server. Optional — auto-detected from the file extension if omitted."
},
"path": {
"type": "string",
"description": "Path to the file (relative to workspace root)"
},
"line": {
"type": "integer",
"description": "Line number (0-based)"
},
"column": {
"type": "integer",
"description": "Column number (0-based)"
}
});
if with_language_id {
if let Some(obj) = props.as_object_mut() {
obj.insert(
"language_id".to_string(),
serde_json::json!({
"type": "string",
"description": "Language identifier (e.g. 'rust', 'typescript'). Optional if already set via lsp_connect."
}),
);
}
}
serde_json::json!({
"type": "object",
"properties": props,
"required": ["path", "line", "column"]
})
}
/// Guess which connected LSP server should handle `path` based on its extension.
///
/// Flow: extract extension from `path` -> for each connected server, check
@@ -132,7 +174,16 @@ fn resolve_server_name(ctx: &ToolCtx, args: &Value, path: &str) -> Result<String
/// Opens the file on the server via `didOpen`, invokes the query closure,
/// then closes the file via `didClose`. Returns the query result along with
/// the 0-based line and column for post-processing.
fn run_lsp_query<F, R>(ctx: &ToolCtx, args: &Value, op: F) -> Result<(R, u32, u32)>
///
/// When `text` is `Some`, the provided content is used instead of reading
/// from disk (used by `LspDiagnostics` which receives the full text as an
/// argument).
fn run_lsp_query<F, R>(
ctx: &ToolCtx,
args: &Value,
text: Option<&str>,
op: F,
) -> Result<(R, u32, u32)>
where
F: FnOnce(&mut crate::app::lsp::LspClient, &str, u32, u32) -> Result<R>,
{
@@ -150,8 +201,11 @@ where
let abs_path = crate::tool::resolve_path(&ctx.workspaces, &rel_path)?;
let uri = path_to_lsp_uri(&abs_path.to_string_lossy());
let file_content =
std::fs::read_to_string(&abs_path).map_err(|e| anyhow!("failed to read file '{rel_path}': {e}"))?;
let file_content = match text {
Some(t) => t.to_string(),
None => std::fs::read_to_string(&abs_path)
.map_err(|e| anyhow!("failed to read file '{rel_path}': {e}"))?,
};
let manager = ctx
.lsp_manager