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",
|
"collections",
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"language",
|
||||||
"language_model",
|
"language_model",
|
||||||
"project",
|
"project",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
|
|
@ -18,6 +18,7 @@ chrono.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
language.workspace = true
|
||||||
language_model.workspace = true
|
language_model.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
|
@ -29,4 +30,5 @@ util.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
collections = { workspace = true, features = ["test-support"] }
|
collections = { workspace = true, features = ["test-support"] }
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
language = { workspace = true, features = ["test-support"] }
|
||||||
project = { workspace = true, features = ["test-support"] }
|
project = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod edit_files_tool;
|
||||||
mod list_directory_tool;
|
mod list_directory_tool;
|
||||||
mod now_tool;
|
mod now_tool;
|
||||||
mod read_file_tool;
|
mod read_file_tool;
|
||||||
|
mod regex_search;
|
||||||
|
|
||||||
use assistant_tool::ToolRegistry;
|
use assistant_tool::ToolRegistry;
|
||||||
use gpui::App;
|
use gpui::App;
|
||||||
|
@ -10,6 +11,7 @@ use crate::edit_files_tool::EditFilesTool;
|
||||||
use crate::list_directory_tool::ListDirectoryTool;
|
use crate::list_directory_tool::ListDirectoryTool;
|
||||||
use crate::now_tool::NowTool;
|
use crate::now_tool::NowTool;
|
||||||
use crate::read_file_tool::ReadFileTool;
|
use crate::read_file_tool::ReadFileTool;
|
||||||
|
use crate::regex_search::RegexSearchTool;
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
assistant_tool::init(cx);
|
assistant_tool::init(cx);
|
||||||
|
@ -19,4 +21,5 @@ pub fn init(cx: &mut App) {
|
||||||
registry.register_tool(ReadFileTool);
|
registry.register_tool(ReadFileTool);
|
||||||
registry.register_tool(ListDirectoryTool);
|
registry.register_tool(ListDirectoryTool);
|
||||||
registry.register_tool(EditFilesTool);
|
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