parent
8d259a9dbe
commit
6259ad559b
5 changed files with 128 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -675,6 +675,7 @@ dependencies = [
|
|||
"collections",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"language",
|
||||
"language_model",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
|
|
|
@ -18,6 +18,7 @@ chrono.workspace = true
|
|||
collections.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
|
@ -29,4 +30,5 @@ util.workspace = true
|
|||
rand.workspace = true
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -2,6 +2,7 @@ mod edit_files_tool;
|
|||
mod list_directory_tool;
|
||||
mod now_tool;
|
||||
mod read_file_tool;
|
||||
mod regex_search;
|
||||
|
||||
use assistant_tool::ToolRegistry;
|
||||
use gpui::App;
|
||||
|
@ -10,6 +11,7 @@ use crate::edit_files_tool::EditFilesTool;
|
|||
use crate::list_directory_tool::ListDirectoryTool;
|
||||
use crate::now_tool::NowTool;
|
||||
use crate::read_file_tool::ReadFileTool;
|
||||
use crate::regex_search::RegexSearchTool;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
assistant_tool::init(cx);
|
||||
|
@ -19,4 +21,5 @@ pub fn init(cx: &mut App) {
|
|||
registry.register_tool(ReadFileTool);
|
||||
registry.register_tool(ListDirectoryTool);
|
||||
registry.register_tool(EditFilesTool);
|
||||
registry.register_tool(RegexSearchTool);
|
||||
}
|
||||
|
|
119
crates/assistant_tools/src/regex_search.rs
Normal file
119
crates/assistant_tools/src/regex_search.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use assistant_tool::Tool;
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::OffsetRangeExt;
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use project::{search::SearchQuery, Project};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp, fmt::Write, sync::Arc};
|
||||
use util::paths::PathMatcher;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RegexSearchToolInput {
|
||||
/// A regex pattern to search for in the entire project. Note that the regex
|
||||
/// will be parsed by the Rust `regex` crate.
|
||||
pub regex: String,
|
||||
}
|
||||
|
||||
pub struct RegexSearchTool;
|
||||
|
||||
impl Tool for RegexSearchTool {
|
||||
fn name(&self) -> String {
|
||||
"regex-search".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
include_str!("./regex_search_tool/description.md").into()
|
||||
}
|
||||
|
||||
fn input_schema(&self) -> serde_json::Value {
|
||||
let schema = schemars::schema_for!(RegexSearchToolInput);
|
||||
serde_json::to_value(&schema).unwrap()
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
input: serde_json::Value,
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
const CONTEXT_LINES: u32 = 2;
|
||||
|
||||
let input = match serde_json::from_value::<RegexSearchToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
};
|
||||
|
||||
let query = match SearchQuery::regex(
|
||||
&input.regex,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
PathMatcher::default(),
|
||||
PathMatcher::default(),
|
||||
None,
|
||||
) {
|
||||
Ok(query) => query,
|
||||
Err(error) => return Task::ready(Err(error)),
|
||||
};
|
||||
|
||||
let results = project.update(cx, |project, cx| project.search(query, cx));
|
||||
cx.spawn(|cx| async move {
|
||||
futures::pin_mut!(results);
|
||||
|
||||
let mut output = String::new();
|
||||
while let Some(project::search::SearchResult::Buffer { buffer, ranges }) =
|
||||
results.next().await
|
||||
{
|
||||
if ranges.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer.read_with(&cx, |buffer, cx| {
|
||||
if let Some(path) = buffer.file().map(|file| file.full_path(cx)) {
|
||||
writeln!(output, "### Found matches in {}:\n", path.display()).unwrap();
|
||||
let mut ranges = ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let mut point_range = range.to_point(buffer);
|
||||
point_range.start.row =
|
||||
point_range.start.row.saturating_sub(CONTEXT_LINES);
|
||||
point_range.start.column = 0;
|
||||
point_range.end.row = cmp::min(
|
||||
buffer.max_point().row,
|
||||
point_range.end.row + CONTEXT_LINES,
|
||||
);
|
||||
point_range.end.column = buffer.line_len(point_range.end.row);
|
||||
point_range
|
||||
})
|
||||
.peekable();
|
||||
|
||||
while let Some(mut range) = ranges.next() {
|
||||
while let Some(next_range) = ranges.peek() {
|
||||
if range.end.row >= next_range.start.row {
|
||||
range.end = next_range.end;
|
||||
ranges.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(output, "```").unwrap();
|
||||
output.extend(buffer.text_for_range(range));
|
||||
writeln!(output, "\n```\n").unwrap();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
Ok("No matches found".into())
|
||||
} else {
|
||||
Ok(output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
Searches the entire project for the given regular expression.
|
||||
|
||||
Returns a list of paths that matched the query. For each path, it returns a list of excerpts of the matched text.
|
Loading…
Add table
Add a link
Reference in a new issue