use cloud_llm_client::predict_edits_v3::{self, Line};use language::{Language, LanguageId};use project::ProjectEntryId;use std::ops::Range;use std::sync::Arc;use std::{borrow::Cow, path::Path};use text::{Bias, BufferId, Rope};use util::paths::{path_ends_with, strip_path_suffix};use util::rel_path::RelPath; use crate::outline::OutlineDeclaration; #[derive(Debug, Clone, Eq, PartialEq, Hash)]pub struct Identifier { pub name: Arc<str>, pub language_id: LanguageId,} slotmap::new_key_type! { pub struct DeclarationId;} #[derive(Debug, Clone)]pub enum Declaration { File { project_entry_id: ProjectEntryId, declaration: FileDeclaration, cached_path: CachedDeclarationPath, }, Buffer { project_entry_id: ProjectEntryId, buffer_id: BufferId, rope: Rope, declaration: BufferDeclaration, cached_path: CachedDeclarationPath, },} const ITEM_TEXT_TRUNCATION_LENGTH: usize = 1024; impl Declaration { pub fn identifier(&self) -> &Identifier { match self { Declaration::File { declaration, .. } => &declaration.identifier, Declaration::Buffer { declaration, .. } => &declaration.identifier, } } pub fn parent(&self) -> Option<DeclarationId> { match self { Declaration::File { declaration, .. } => declaration.parent, Declaration::Buffer { declaration, .. } => declaration.parent, } } pub fn as_buffer(&self) -> Option<&BufferDeclaration> { match self { Declaration::File { .. } => None, Declaration::Buffer { declaration, .. } => Some(declaration), } } pub fn as_file(&self) -> Option<&FileDeclaration> { match self { Declaration::Buffer { .. } => None, Declaration::File { declaration, .. } => Some(declaration), } } pub fn project_entry_id(&self) -> ProjectEntryId { match self { Declaration::File { project_entry_id, .. } => *project_entry_id, Declaration::Buffer { project_entry_id, .. } => *project_entry_id, } } pub fn cached_path(&self) -> &CachedDeclarationPath { match self { Declaration::File { cached_path, .. } => cached_path, Declaration::Buffer { cached_path, .. } => cached_path, } } pub fn item_range(&self) -> Range<usize> { match self { Declaration::File { declaration, .. } => declaration.item_range.clone(), Declaration::Buffer { declaration, .. } => declaration.item_range.clone(), } } pub fn item_line_range(&self) -> Range<Line> { match self { Declaration::File { declaration, .. } => declaration.item_line_range.clone(), Declaration::Buffer { declaration, rope, .. } => { Line(rope.offset_to_point(declaration.item_range.start).row) ..Line(rope.offset_to_point(declaration.item_range.end).row) } } } pub fn item_text(&self) -> (Cow<'_, str>, bool) { match self { Declaration::File { declaration, .. } => ( declaration.text.as_ref().into(), declaration.text_is_truncated, ), Declaration::Buffer { rope, declaration, .. } => ( rope.chunks_in_range(declaration.item_range.clone()) .collect::<Cow<str>>(), declaration.item_range_is_truncated, ), } } pub fn signature_text(&self) -> (Cow<'_, str>, bool) { match self { Declaration::File { declaration, .. } => ( declaration.text[self.signature_range_in_item_text()].into(), declaration.signature_is_truncated, ), Declaration::Buffer { rope, declaration, .. } => ( rope.chunks_in_range(declaration.signature_range.clone()) .collect::<Cow<str>>(), declaration.signature_range_is_truncated, ), } } pub fn signature_range(&self) -> Range<usize> { match self { Declaration::File { declaration, .. } => declaration.signature_range.clone(), Declaration::Buffer { declaration, .. } => declaration.signature_range.clone(), } } pub fn signature_line_range(&self) -> Range<Line> { match self { Declaration::File { declaration, .. } => declaration.signature_line_range.clone(), Declaration::Buffer { declaration, rope, .. } => { Line(rope.offset_to_point(declaration.signature_range.start).row) ..Line(rope.offset_to_point(declaration.signature_range.end).row) } } } pub fn signature_range_in_item_text(&self) -> Range<usize> { let signature_range = self.signature_range(); let item_range = self.item_range(); signature_range.start.saturating_sub(item_range.start) ..(signature_range.end.saturating_sub(item_range.start)).min(item_range.len()) }} fn expand_range_to_line_boundaries_and_truncate( range: &Range<usize>, limit: usize, rope: &Rope,) -> (Range<usize>, Range<predict_edits_v3::Line>, bool) { let mut point_range = rope.offset_to_point(range.start)..rope.offset_to_point(range.end); point_range.start.column = 0; point_range.end.row += 1; point_range.end.column = 0; let mut item_range = rope.point_to_offset(point_range.start)..rope.point_to_offset(point_range.end); let is_truncated = item_range.len() > limit; if is_truncated { item_range.end = item_range.start + limit; } item_range.end = rope.clip_offset(item_range.end, Bias::Left); let line_range = predict_edits_v3::Line(point_range.start.row)..predict_edits_v3::Line(point_range.end.row); (item_range, line_range, is_truncated)} #[derive(Debug, Clone)]pub struct FileDeclaration { pub parent: Option<DeclarationId>, pub identifier: Identifier, /// offset range of the declaration in the file, expanded to line boundaries and truncated pub item_range: Range<usize>, /// line range of the declaration in the file, potentially truncated pub item_line_range: Range<predict_edits_v3::Line>, /// text of `item_range` pub text: Arc<str>, /// whether `text` was truncated pub text_is_truncated: bool, /// offset range of the signature in the file, expanded to line boundaries and truncated pub signature_range: Range<usize>, /// line range of the signature in the file, truncated pub signature_line_range: Range<Line>, /// whether `signature` was truncated pub signature_is_truncated: bool,} impl FileDeclaration { pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> FileDeclaration { let (item_range_in_file, item_line_range_in_file, text_is_truncated) = expand_range_to_line_boundaries_and_truncate( &declaration.item_range, ITEM_TEXT_TRUNCATION_LENGTH, rope, ); let (mut signature_range_in_file, signature_line_range, mut signature_is_truncated) = expand_range_to_line_boundaries_and_truncate( &declaration.signature_range, ITEM_TEXT_TRUNCATION_LENGTH, rope, ); if signature_range_in_file.start < item_range_in_file.start { signature_range_in_file.start = item_range_in_file.start; signature_is_truncated = true; } if signature_range_in_file.end > item_range_in_file.end { signature_range_in_file.end = item_range_in_file.end; signature_is_truncated = true; } FileDeclaration { parent: None, identifier: declaration.identifier, signature_range: signature_range_in_file, signature_line_range, signature_is_truncated, text: rope .chunks_in_range(item_range_in_file.clone()) .collect::<String>() .into(), text_is_truncated, item_range: item_range_in_file, item_line_range: item_line_range_in_file, } }} #[derive(Debug, Clone)]pub struct BufferDeclaration { pub parent: Option<DeclarationId>, pub identifier: Identifier, pub item_range: Range<usize>, pub item_range_is_truncated: bool, pub signature_range: Range<usize>, pub signature_range_is_truncated: bool,} impl BufferDeclaration { pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> Self { let (item_range, _item_line_range, item_range_is_truncated) = expand_range_to_line_boundaries_and_truncate( &declaration.item_range, ITEM_TEXT_TRUNCATION_LENGTH, rope, ); let (signature_range, _signature_line_range, signature_range_is_truncated) = expand_range_to_line_boundaries_and_truncate( &declaration.signature_range, ITEM_TEXT_TRUNCATION_LENGTH, rope, ); Self { parent: None, identifier: declaration.identifier, item_range, item_range_is_truncated, signature_range, signature_range_is_truncated, } }} #[derive(Debug, Clone)]pub struct CachedDeclarationPath { pub worktree_abs_path: Arc<Path>, pub rel_path: Arc<RelPath>, /// The relative path of the file, possibly stripped according to `import_path_strip_regex`. pub rel_path_after_regex_stripping: Arc<RelPath>,} impl CachedDeclarationPath { pub fn new( worktree_abs_path: Arc<Path>, path: &Arc<RelPath>, language: Option<&Arc<Language>>, ) -> Self { let rel_path = path.clone(); let rel_path_after_regex_stripping = if let Some(language) = language && let Some(strip_regex) = language.config().import_path_strip_regex.as_ref() && let Ok(stripped) = RelPath::unix(&Path::new( strip_regex.replace_all(rel_path.as_unix_str(), "").as_ref(), )) { Arc::from(stripped) } else { rel_path.clone() }; CachedDeclarationPath { worktree_abs_path, rel_path, rel_path_after_regex_stripping, } } #[cfg(test)] pub fn new_for_test(worktree_abs_path: &str, rel_path: &str) -> Self { let rel_path: Arc<RelPath> = util::rel_path::rel_path(rel_path).into(); CachedDeclarationPath { worktree_abs_path: std::path::PathBuf::from(worktree_abs_path).into(), rel_path_after_regex_stripping: rel_path.clone(), rel_path, } } pub fn ends_with_posix_path(&self, path: &Path) -> bool { if path.as_os_str().len() <= self.rel_path_after_regex_stripping.as_unix_str().len() { path_ends_with(self.rel_path_after_regex_stripping.as_std_path(), path) } else { if let Some(remaining) = strip_path_suffix(path, self.rel_path_after_regex_stripping.as_std_path()) { path_ends_with(&self.worktree_abs_path, remaining) } else { false } } } pub fn equals_absolute_path(&self, path: &Path) -> bool { if let Some(remaining) = strip_path_suffix(path, &self.rel_path_after_regex_stripping.as_std_path()) { self.worktree_abs_path.as_ref() == remaining } else { false } }}
use cloud_llm_client::predict_edits_v3::{self, Line};
use language::{Language, LanguageId};
use project::ProjectEntryId;
use std::ops::Range;
use std::sync::Arc;
use std::{borrow::Cow, path::Path};
use text::{Bias, BufferId, Rope};
use util::paths::{path_ends_with, strip_path_suffix};
use util::rel_path::RelPath;
use crate::outline::OutlineDeclaration;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Identifier {
pub name: Arc<str>,
pub language_id: LanguageId,
}
slotmap::new_key_type! {
pub struct DeclarationId;
}
#[derive(Debug, Clone)]
pub enum Declaration {
File {
project_entry_id: ProjectEntryId,
declaration: FileDeclaration,
cached_path: CachedDeclarationPath,
},
Buffer {
project_entry_id: ProjectEntryId,
buffer_id: BufferId,
rope: Rope,
declaration: BufferDeclaration,
cached_path: CachedDeclarationPath,
},
}
const ITEM_TEXT_TRUNCATION_LENGTH: usize = 1024;
impl Declaration {
pub fn identifier(&self) -> &Identifier {
match self {
Declaration::File { declaration, .. } => &declaration.identifier,
Declaration::Buffer { declaration, .. } => &declaration.identifier,
}
}
pub fn parent(&self) -> Option<DeclarationId> {
match self {
Declaration::File { declaration, .. } => declaration.parent,
Declaration::Buffer { declaration, .. } => declaration.parent,
}
}
pub fn as_buffer(&self) -> Option<&BufferDeclaration> {
match self {
Declaration::File { .. } => None,
Declaration::Buffer { declaration, .. } => Some(declaration),
}
}
pub fn as_file(&self) -> Option<&FileDeclaration> {
match self {
Declaration::Buffer { .. } => None,
Declaration::File { declaration, .. } => Some(declaration),
}
}
pub fn project_entry_id(&self) -> ProjectEntryId {
match self {
Declaration::File {
project_entry_id, ..
} => *project_entry_id,
Declaration::Buffer {
project_entry_id, ..
} => *project_entry_id,
}
}
pub fn cached_path(&self) -> &CachedDeclarationPath {
match self {
Declaration::File { cached_path, .. } => cached_path,
Declaration::Buffer { cached_path, .. } => cached_path,
}
}
pub fn item_range(&self) -> Range<usize> {
match self {
Declaration::File { declaration, .. } => declaration.item_range.clone(),
Declaration::Buffer { declaration, .. } => declaration.item_range.clone(),
}
}
pub fn item_line_range(&self) -> Range<Line> {
match self {
Declaration::File { declaration, .. } => declaration.item_line_range.clone(),
Declaration::Buffer {
declaration, rope, ..
} => {
Line(rope.offset_to_point(declaration.item_range.start).row)
..Line(rope.offset_to_point(declaration.item_range.end).row)
}
}
}
pub fn item_text(&self) -> (Cow<'_, str>, bool) {
match self {
Declaration::File { declaration, .. } => (
declaration.text.as_ref().into(),
declaration.text_is_truncated,
),
Declaration::Buffer {
rope, declaration, ..
} => (
rope.chunks_in_range(declaration.item_range.clone())
.collect::<Cow<str>>(),
declaration.item_range_is_truncated,
),
}
}
pub fn signature_text(&self) -> (Cow<'_, str>, bool) {
match self {
Declaration::File { declaration, .. } => (
declaration.text[self.signature_range_in_item_text()].into(),
declaration.signature_is_truncated,
),
Declaration::Buffer {
rope, declaration, ..
} => (
rope.chunks_in_range(declaration.signature_range.clone())
.collect::<Cow<str>>(),
declaration.signature_range_is_truncated,
),
}
}
pub fn signature_range(&self) -> Range<usize> {
match self {
Declaration::File { declaration, .. } => declaration.signature_range.clone(),
Declaration::Buffer { declaration, .. } => declaration.signature_range.clone(),
}
}
pub fn signature_line_range(&self) -> Range<Line> {
match self {
Declaration::File { declaration, .. } => declaration.signature_line_range.clone(),
Declaration::Buffer {
declaration, rope, ..
} => {
Line(rope.offset_to_point(declaration.signature_range.start).row)
..Line(rope.offset_to_point(declaration.signature_range.end).row)
}
}
}
pub fn signature_range_in_item_text(&self) -> Range<usize> {
let signature_range = self.signature_range();
let item_range = self.item_range();
signature_range.start.saturating_sub(item_range.start)
..(signature_range.end.saturating_sub(item_range.start)).min(item_range.len())
}
}
fn expand_range_to_line_boundaries_and_truncate(
range: &Range<usize>,
limit: usize,
rope: &Rope,
) -> (Range<usize>, Range<predict_edits_v3::Line>, bool) {
let mut point_range = rope.offset_to_point(range.start)..rope.offset_to_point(range.end);
point_range.start.column = 0;
point_range.end.row += 1;
point_range.end.column = 0;
let mut item_range =
rope.point_to_offset(point_range.start)..rope.point_to_offset(point_range.end);
let is_truncated = item_range.len() > limit;
if is_truncated {
item_range.end = item_range.start + limit;
}
item_range.end = rope.clip_offset(item_range.end, Bias::Left);
let line_range =
predict_edits_v3::Line(point_range.start.row)..predict_edits_v3::Line(point_range.end.row);
(item_range, line_range, is_truncated)
}
#[derive(Debug, Clone)]
pub struct FileDeclaration {
pub parent: Option<DeclarationId>,
pub identifier: Identifier,
/// offset range of the declaration in the file, expanded to line boundaries and truncated
pub item_range: Range<usize>,
/// line range of the declaration in the file, potentially truncated
pub item_line_range: Range<predict_edits_v3::Line>,
/// text of `item_range`
pub text: Arc<str>,
/// whether `text` was truncated
pub text_is_truncated: bool,
/// offset range of the signature in the file, expanded to line boundaries and truncated
pub signature_range: Range<usize>,
/// line range of the signature in the file, truncated
pub signature_line_range: Range<Line>,
/// whether `signature` was truncated
pub signature_is_truncated: bool,
}
impl FileDeclaration {
pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> FileDeclaration {
let (item_range_in_file, item_line_range_in_file, text_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.item_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
let (mut signature_range_in_file, signature_line_range, mut signature_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.signature_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
if signature_range_in_file.start < item_range_in_file.start {
signature_range_in_file.start = item_range_in_file.start;
signature_is_truncated = true;
}
if signature_range_in_file.end > item_range_in_file.end {
signature_range_in_file.end = item_range_in_file.end;
signature_is_truncated = true;
}
FileDeclaration {
parent: None,
identifier: declaration.identifier,
signature_range: signature_range_in_file,
signature_line_range,
signature_is_truncated,
text: rope
.chunks_in_range(item_range_in_file.clone())
.collect::<String>()
.into(),
text_is_truncated,
item_range: item_range_in_file,
item_line_range: item_line_range_in_file,
}
}
}
#[derive(Debug, Clone)]
pub struct BufferDeclaration {
pub parent: Option<DeclarationId>,
pub identifier: Identifier,
pub item_range: Range<usize>,
pub item_range_is_truncated: bool,
pub signature_range: Range<usize>,
pub signature_range_is_truncated: bool,
}
impl BufferDeclaration {
pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> Self {
let (item_range, _item_line_range, item_range_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.item_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
let (signature_range, _signature_line_range, signature_range_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.signature_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
Self {
parent: None,
identifier: declaration.identifier,
item_range,
item_range_is_truncated,
signature_range,
signature_range_is_truncated,
}
}
}
#[derive(Debug, Clone)]
pub struct CachedDeclarationPath {
pub worktree_abs_path: Arc<Path>,
pub rel_path: Arc<RelPath>,
/// The relative path of the file, possibly stripped according to `import_path_strip_regex`.
pub rel_path_after_regex_stripping: Arc<RelPath>,
}
impl CachedDeclarationPath {
pub fn new(
worktree_abs_path: Arc<Path>,
path: &Arc<RelPath>,
language: Option<&Arc<Language>>,
) -> Self {
let rel_path = path.clone();
let rel_path_after_regex_stripping = if let Some(language) = language
&& let Some(strip_regex) = language.config().import_path_strip_regex.as_ref()
&& let Ok(stripped) = RelPath::unix(&Path::new(
strip_regex.replace_all(rel_path.as_unix_str(), "").as_ref(),
)) {
Arc::from(stripped)
} else {
rel_path.clone()
};
CachedDeclarationPath {
worktree_abs_path,
rel_path,
rel_path_after_regex_stripping,
}
}
#[cfg(test)]
pub fn new_for_test(worktree_abs_path: &str, rel_path: &str) -> Self {
let rel_path: Arc<RelPath> = util::rel_path::rel_path(rel_path).into();
CachedDeclarationPath {
worktree_abs_path: std::path::PathBuf::from(worktree_abs_path).into(),
rel_path_after_regex_stripping: rel_path.clone(),
rel_path,
}
}
pub fn ends_with_posix_path(&self, path: &Path) -> bool {
if path.as_os_str().len() <= self.rel_path_after_regex_stripping.as_unix_str().len() {
path_ends_with(self.rel_path_after_regex_stripping.as_std_path(), path)
} else {
if let Some(remaining) =
strip_path_suffix(path, self.rel_path_after_regex_stripping.as_std_path())
{
path_ends_with(&self.worktree_abs_path, remaining)
} else {
false
}
}
}
pub fn equals_absolute_path(&self, path: &Path) -> bool {
if let Some(remaining) =
strip_path_suffix(path, &self.rel_path_after_regex_stripping.as_std_path())
{
self.worktree_abs_path.as_ref() == remaining
} else {
false
}
}
}