Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions resources/flashgrep/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
Place the prebuilt `flashgrep` daemon binary in this directory.

Pinned release:

- `v0.2.6` from `wgqqqqq/flashgrep`

Expected filenames:

- macOS x86_64: `flashgrep-x86_64-apple-darwin`
- macOS arm64: `flashgrep-aarch64-apple-darwin`
- Linux x86_64: `flashgrep-x86_64-unknown-linux-gnu`
- Linux arm64: `flashgrep-aarch64-unknown-linux-gnu`
- Linux x86_64: `flashgrep-x86_64-unknown-linux-musl`
- Linux arm64: `flashgrep-aarch64-unknown-linux-musl`
- Windows x86_64: `flashgrep-x86_64-pc-windows-msvc.exe`
- Windows arm64: `flashgrep-aarch64-pc-windows-msvc.exe`

macOS binaries are ad-hoc signed after download so local development can execute them directly.

BitFun dev/build scripts load the daemon from this repository-relative path.
5 changes: 5 additions & 0 deletions resources/flashgrep/VERSION.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"repo": "wgqqqqq/flashgrep",
"tag": "v0.2.6",
"published_at": "2026-05-11T06:49:11Z"
}
Binary file modified resources/flashgrep/flashgrep-aarch64-apple-darwin
Binary file not shown.
Binary file modified resources/flashgrep/flashgrep-aarch64-pc-windows-msvc.exe
Binary file not shown.
Binary file not shown.
Binary file modified resources/flashgrep/flashgrep-x86_64-apple-darwin
Binary file not shown.
Binary file modified resources/flashgrep/flashgrep-x86_64-pc-windows-msvc.exe
Binary file not shown.
Binary file not shown.
15 changes: 12 additions & 3 deletions scripts/prepare-flashgrep-resource.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ export function flashgrepBinaryNames() {
return ['flashgrep-aarch64-apple-darwin'];
}
if (process.platform === 'linux' && process.arch === 'x64') {
return ['flashgrep-x86_64-unknown-linux-gnu'];
return [
'flashgrep-x86_64-unknown-linux-musl',
'flashgrep-x86_64-unknown-linux-gnu',
];
}
if (process.platform === 'linux' && process.arch === 'arm64') {
return ['flashgrep-aarch64-unknown-linux-gnu'];
return [
'flashgrep-aarch64-unknown-linux-musl',
'flashgrep-aarch64-unknown-linux-gnu',
];
}
return [process.platform === 'win32' ? 'flashgrep.exe' : 'flashgrep'];
}
Expand All @@ -33,7 +39,10 @@ export function flashgrepBinaryName() {
}

export function flashgrepBinaryPath() {
return join(RESOURCE_DIR, flashgrepBinaryName());
const availableBinaryName =
flashgrepBinaryNames().find((binaryName) => existsSync(join(RESOURCE_DIR, binaryName))) ??
flashgrepBinaryName();
return join(RESOURCE_DIR, availableBinaryName);
}

export function ensureFlashgrepBinary() {
Expand Down
151 changes: 86 additions & 65 deletions src/apps/desktop/src/api/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use crate::api::path_target::{
write_text_file, DesktopPathTarget,
};
use crate::api::search_api::{
group_search_results, search_file_contents_via_workspace_search,
search_metadata_from_content_result, should_use_workspace_search, SearchMetadataResponse,
build_content_search_request, group_search_results, prepare_content_search_runner,
search_file_contents_via_workspace_search, search_metadata_from_content_result,
should_use_workspace_search, SearchMetadataResponse,
};
use crate::api::workspace_activation::spawn_workspace_background_warmup;
use bitfun_core::infrastructure::{
Expand Down Expand Up @@ -2551,73 +2552,92 @@ pub async fn search_files(
};

let use_workspace_search =
request.search_content && should_use_workspace_search(&request.root_path).await;
request.search_content && should_use_workspace_search(&state, &request.root_path).await;
let result = if request.search_content {
let filename_outcome = state
.filesystem_service
.search_file_names(
&request.root_path,
&request.pattern,
FileSearchOptions {
include_content: false,
include_directories: request.include_directories,
..options.clone()
},
cancel_flag.clone(),
)
.await?;
let mut filename_results = filename_outcome.results;

if filename_results.len() >= max_results {
Ok(filename_results)
} else {
let remaining = max_results - filename_results.len();
let mut content_outcome = if use_workspace_search {
if is_remote_path(request.root_path.trim()).await {
if !use_workspace_search {
Err("Remote content search requires workspace search support".to_string())
} else {
search_file_contents_via_workspace_search(
&state,
&request.root_path,
&request.pattern,
request.case_sensitive,
request.use_regex,
request.whole_word,
remaining,
max_results,
)
.await
.map(|result| result.outcome)?
.map(|result| result.outcome.results)
}
} else {
let filename_outcome = state
.filesystem_service
.search_file_names(
&request.root_path,
&request.pattern,
FileSearchOptions {
include_content: false,
include_directories: request.include_directories,
..options.clone()
},
cancel_flag.clone(),
)
.await?;
let mut filename_results = filename_outcome.results;

if filename_results.len() >= max_results {
Ok(filename_results)
} else {
state
.filesystem_service
.search_file_contents(
let remaining = max_results - filename_results.len();
let mut content_outcome = if use_workspace_search {
search_file_contents_via_workspace_search(
&state,
&request.root_path,
&request.pattern,
FileSearchOptions {
include_content: true,
include_directories: false,
max_results: Some(remaining),
..options
},
cancel_flag,
request.case_sensitive,
request.use_regex,
request.whole_word,
remaining,
)
.await?
};
if filename_outcome.truncated || content_outcome.truncated {
debug!(
"Legacy search truncated: root_path={}, pattern={}, search_content={}, limit={}",
request.root_path,
request.pattern,
request.search_content,
max_results
);
.await
.map(|result| result.outcome)?
} else {
state
.filesystem_service
.search_file_contents(
&request.root_path,
&request.pattern,
FileSearchOptions {
include_content: true,
include_directories: false,
max_results: Some(remaining),
..options
},
cancel_flag,
)
.await?
};
if filename_outcome.truncated || content_outcome.truncated {
debug!(
"Legacy search truncated: root_path={}, pattern={}, search_content={}, limit={}",
request.root_path,
request.pattern,
request.search_content,
max_results
);
}
filename_results.append(&mut content_outcome.results);
Ok(filename_results)
}
filename_results.append(&mut content_outcome.results);
Ok(filename_results)
}
} else {
state
.filesystem_service
.search_file_names(&request.root_path, &request.pattern, options, cancel_flag)
.await
.map(|outcome| outcome.results)
.map_err(|error| format!("Failed to search filenames: {}", error))
};
unregister_search(&state, search_id.as_deref());

Expand Down Expand Up @@ -2710,7 +2730,7 @@ pub async fn search_file_contents(
include_directories: false,
};

let result = if should_use_workspace_search(&request.root_path).await {
let result = if should_use_workspace_search(&state, &request.root_path).await {
search_file_contents_via_workspace_search(
&state,
&request.root_path,
Expand Down Expand Up @@ -2876,14 +2896,22 @@ pub async fn start_search_file_contents_stream(
};

let filesystem_service = state.filesystem_service.clone();
let workspace_search_service = state.workspace_search_service.clone();
let active_searches = state.active_searches.clone();
let root_path = request.root_path.clone();
let pattern = request.pattern.clone();
let case_sensitive = request.case_sensitive;
let use_regex = request.use_regex;
let whole_word = request.whole_word;
let use_workspace_search = should_use_workspace_search(&root_path).await;
let use_workspace_search = should_use_workspace_search(&state, &root_path).await;
let workspace_search_runner = if use_workspace_search {
Some(
prepare_content_search_runner(&state, &root_path)
.await
.map_err(|error| format!("Failed to prepare workspace search: {}", error))?,
)
} else {
None
};
let response_search_id = search_id.clone();
let progress_search_id = search_id.clone();
let progress_app_handle = app_handle.clone();
Expand All @@ -2902,23 +2930,17 @@ pub async fn start_search_file_contents_stream(

tokio::spawn(async move {
let result = if use_workspace_search {
let result = workspace_search_service
.search_content(bitfun_core::service::search::ContentSearchRequest {
repo_root: root_path.clone().into(),
search_path: None,
pattern: pattern.clone(),
output_mode: bitfun_core::service::search::ContentSearchOutputMode::Content,
let result = workspace_search_runner
.as_ref()
.expect("workspace search runner should exist when enabled")
.search_content(build_content_search_request(
&root_path,
&pattern,
case_sensitive,
use_regex,
whole_word,
multiline: false,
before_context: 0,
after_context: 0,
max_results: Some(limit),
globs: Vec::new(),
file_types: Vec::new(),
exclude_file_types: Vec::new(),
})
limit,
))
.await
.map(|result| {
let search_metadata = search_metadata_from_content_result(&result);
Expand All @@ -2941,7 +2963,6 @@ pub async fn start_search_file_contents_stream(
);
}
}

result.map_err(|error| {
bitfun_core::util::errors::BitFunError::service(format!(
"Failed to search file contents via workspace search: {}",
Expand Down
Loading
Loading