first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+65 -28
View File
@@ -1,19 +1,20 @@
use galaxyui::elements::PartialClickableElement;
use std::collections::HashMap;
use std::ops::Range;
use urlocator::{UrlLocation, UrlLocator};
use string_offset::ByteOffset;
use urlocator::{UrlLocation, UrlLocator};
use galaxyui::elements::{MouseStateHandle, PartialClickableElement};
use galaxyui::platform::Cursor;
use galaxyui::text::char_slice;
use galaxyui::Action;
use crate::ai::agent::{AIAgentActionType, AIAgentOutput, AIAgentTextSection, ReadFilesRequest};
use crate::ai::blocklist::block::view_impl::output::LinkActionConstructors;
use crate::ai::blocklist::block::TextLocation;
use crate::terminal::links::should_directly_open_link;
use crate::terminal::model::grid::grid_handler::FILE_LINK_SEPARATORS;
use crate::terminal::model::grid::grid_handler::is_file_link_separator;
use crate::terminal::ShellLaunchData;
use galaxyui::elements::MouseStateHandle;
use galaxyui::text::char_slice;
use galaxyui::Action;
cfg_if::cfg_if! {
if #[cfg(feature = "local_fs")] {
@@ -213,22 +214,41 @@ fn addr_of(s: &str) -> usize {
s.as_ptr() as usize
}
/// Given a word with no whitespace in it, returns all the possible file paths within the word
/// from longest to shortest. File paths within a word can be split by a list of FILE_LINK_SEPARATORS,
/// and those separators may be part of file paths themselves.
/// Possible file paths begin after a separator and end before a separator.
/// For example, given /path/to/file:16:hello, it will return
/// ["/path/to/file:16:hello", "/path/to/file:16", "/path/to/file", "16:hello", "hello"]
/// Maximum byte length of a token to search for file paths in. Used as a guard against scanning huge non-path tokens.
/// - Linux PATH_MAX: 4096 bytes.
/// - macOS PATH_MAX: 1024 bytes.
/// - Windows long-path cap: 32,767 UTF-16 units = 98,301 bytes.
const MAX_WORD_LEN_FOR_FILE_PATH: usize = 96 * 1024;
/// Maximum [`is_file_link_separator`] characters per token, to bound candidate substrings.
/// 256 keeps per-token allocations under ~1 MiB and is far above any real path.
const MAX_SEPARATORS_PER_WORD: usize = 256;
/// A separator's byte range in the original word.
///
/// File path candidates start after one separator and end before another. Using [`ByteOffset`]
/// keeps the byte-indexing semantics explicit when separators are multi-byte characters like
/// box-drawing glyphs.
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
fn possible_file_paths_in_word(word: &str) -> impl Iterator<Item = &str> {
type SeparatorByteRange = Range<ByteOffset>;
/// Returns separator byte ranges in `word`, framed by zero-width virtual separators at
/// the start and end of the word. Returns empty if either safety cap is exceeded.
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
fn separator_byte_ranges_for_file_path_search(word: &str) -> Vec<SeparatorByteRange> {
if word.len() > MAX_WORD_LEN_FOR_FILE_PATH {
return Vec::new();
}
// To include any substrings starting at the beginning of the word, we
// pretend there's a separator before the first character.
let mut separator_byte_indices = vec![-1];
// pretend there's a zero-width separator before the first character.
let mut separator_byte_ranges = vec![ByteOffset::zero()..ByteOffset::zero()];
// We use char_indices() to get byte indices of each char which are used to index the string,
// rather than chars().enumerate() would give char indices.
for (i, c) in word.char_indices() {
if FILE_LINK_SEPARATORS.contains(&c) {
separator_byte_indices.push(i as i32);
if is_file_link_separator(c) {
if separator_byte_ranges.len() > MAX_SEPARATORS_PER_WORD {
return Vec::new();
}
separator_byte_ranges.push(ByteOffset::from(i)..ByteOffset::from(i + c.len_utf8()));
}
}
// Consider trailing periods to be separators. This is because
@@ -237,24 +257,41 @@ fn possible_file_paths_in_word(word: &str) -> impl Iterator<Item = &str> {
// periods can also be part of a valid file path.
let word_ends_with_period = word.ends_with('.');
if word_ends_with_period {
separator_byte_indices.push((word.len() - 1) as i32);
separator_byte_ranges.push(ByteOffset::from(word.len() - 1)..ByteOffset::from(word.len()));
}
// To include any substrings ending at the end of the word, we pretend there's
// a separator after the last character.
separator_byte_indices.push(word.len() as i32);
// a zero-width separator after the last character.
separator_byte_ranges.push(ByteOffset::from(word.len())..ByteOffset::from(word.len()));
separator_byte_ranges
}
/// Given a word with no whitespace in it, returns all the possible file paths within the word
/// from longest to shortest. File paths within a word can be split by [`is_file_link_separator`]
/// characters, and those separators may be part of file paths themselves.
/// Possible file paths begin after a separator and end before a separator.
/// For example, given /path/to/file:16:hello, it will return
/// ["/path/to/file:16:hello", "/path/to/file:16", "/path/to/file", "16:hello", "hello"]
///
/// Tokens exceeding [`MAX_WORD_LEN_FOR_FILE_PATH`] or [`MAX_SEPARATORS_PER_WORD`]
/// yield no candidates to bound the substring enumeration.
#[cfg_attr(not(feature = "local_fs"), allow(dead_code))]
fn possible_file_paths_in_word(word: &str) -> impl Iterator<Item = &str> {
let separator_byte_ranges = separator_byte_ranges_for_file_path_search(word);
let mut possible_path_byte_ranges = vec![];
for (i, start_index) in separator_byte_indices.iter().cloned().enumerate() {
for end_index in separator_byte_indices.iter().skip(i + 1).cloned() {
if start_index + 1 < end_index {
possible_path_byte_ranges.push(start_index + 1..end_index);
for (i, start_separator) in separator_byte_ranges.iter().cloned().enumerate() {
for end_separator in separator_byte_ranges.iter().skip(i + 1).cloned() {
if start_separator.end < end_separator.start {
possible_path_byte_ranges.push(start_separator.end..end_separator.start);
}
}
}
// Sort by longest to shortest.
possible_path_byte_ranges.sort_by(|a, b| (b.end - b.start).cmp(&(a.end - a.start)));
possible_path_byte_ranges.sort_by(|a, b| {
(b.end.as_usize() - b.start.as_usize()).cmp(&(a.end.as_usize() - a.start.as_usize()))
});
possible_path_byte_ranges
.into_iter()
.map(|range| &word[(range.start as usize)..(range.end as usize)])
.map(|range| &word[range.start.as_usize()..range.end.as_usize()])
}
/// Returns a DetectedLink::FilePath if expanded_path is a valid path that actually exists on the file system.
@@ -474,7 +511,7 @@ fn detect_line_ranges_after_file_path(
.char_indices()
.map(|(offs, ch)| (offs + file_path_byte_end, ch));
// Finds an opening paranthesis, allowing some whitespace after file path, or returns None on failure
// Finds an opening parenthesis, allowing some whitespace after file path, or returns None on failure
let mut paren_start_idx = None;
for (char_idx, ch) in chars_iter {
if ch == '(' {
@@ -486,7 +523,7 @@ fn detect_line_ranges_after_file_path(
}
let paren_start_idx = paren_start_idx?;
// Find the matching closing paranthesis, or returns None on failure
// Find the matching closing parenthesis, or returns None on failure
let paren_end_index = paren_start_idx + text[paren_start_idx..].find(')')?;
// Extract the content between parentheses, and parse valid line ranges
@@ -718,5 +755,5 @@ pub(crate) fn detect_links(
}
#[cfg(test)]
#[path = "link_detection_test.rs"]
#[path = "link_detection_tests.rs"]
mod tests;