Skip to content
Closed
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
115 changes: 96 additions & 19 deletions app/src/code_review/code_review_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,12 @@ pub struct FileState {
pub file_diff: FileDiff,
pub editor_state: Option<CodeReviewEditorState>,
pub is_expanded: bool,
/// Stores the `content_at_head` for files whose editor construction was
/// deferred because they started collapsed. When the user expands such a
/// file, `ensure_editor_for_file` consumes this to build the editor lazily,
/// avoiding the ~50-100 MB per-file allocation for files that may never be
/// viewed.
pub deferred_content_at_head: Option<String>,
sidebar_mouse_state: MouseStateHandle,
header_mouse_state: MouseStateHandle,
chevron_button: ViewHandle<ActionButton>,
Expand Down Expand Up @@ -2580,26 +2586,32 @@ impl CodeReviewView {

let mut file_states = vec![];
for file in files {
let editor_state = {
// `LocalCodeEditorView::new_with_global_buffer` natively
// supports both `LocalOrRemotePath::Local` and `Remote`
// (it sets language by extension and skips local-only
// wiring like LSP for remote files), so we always go
// through the global-buffer path when we have a repo.
#[cfg(not(target_family = "wasm"))]
{
if self.repo_path().is_some() {
self.create_code_review_model_with_global_buffer(file, ctx)
} else {
let is_expanded = self.should_auto_expand_file(&file.file_diff);

// Defer editor construction for collapsed files to avoid
// allocating ~50-100 MB of styled buffer data per file that
// may never be viewed. The content_at_head is stored on the
// FileState and consumed lazily by ensure_editor_for_file
// when the user expands the file.
let (editor_state, deferred_content_at_head) = if is_expanded {
let state = {
#[cfg(not(target_family = "wasm"))]
{
if self.repo_path().is_some() {
self.create_code_review_model_with_global_buffer(file, ctx)
} else {
self.create_code_review_model(file, ctx)
}
}
#[cfg(target_family = "wasm")]
{
self.create_code_review_model(file, ctx)
}
}
#[cfg(target_family = "wasm")]
{
self.create_code_review_model(file, ctx)
}
};
(state, None)
} else {
(None, file.content_at_head.clone())
};
let is_expanded = self.should_auto_expand_file(&file.file_diff);

let file_path = file.file_diff.file_path.clone();
let file_line = file_line_for_open(&file.file_diff);
Expand Down Expand Up @@ -2686,6 +2698,7 @@ impl CodeReviewView {
file_diff: file.file_diff.clone(),
editor_state,
is_expanded,
deferred_content_at_head,
chevron_button,
open_in_tab_button,
discard_button,
Expand Down Expand Up @@ -2729,6 +2742,60 @@ impl CodeReviewView {
.unwrap_or(false)
}

/// Lazily constructs the editor for a file whose construction was deferred
/// because it was initially collapsed. Consumes `deferred_content_at_head`
/// and populates `editor_state`. No-ops if the editor already exists.
fn ensure_editor_for_file(&mut self, path: &str, ctx: &mut ViewContext<Self>) {
let Some(repo) = self.active_repo.as_mut() else {
return;
};
let CodeReviewViewState::Loaded(state) = &mut repo.state else {
return;
};
let Some(file_state) = state.file_states.get_mut(path) else {
return;
};

// Already has an editor — nothing to do.
if file_state.editor_state.is_some() {
return;
}

// Take the deferred content so we can build a FileDiffAndContent for
// the existing editor construction helpers.
let content_at_head = file_state.deferred_content_at_head.take();
let file = FileDiffAndContent {
file_diff: file_state.file_diff.clone(),
content_at_head,
};

let editor_state = {
#[cfg(not(target_family = "wasm"))]
{
if self.repo_path().is_some() {
self.create_code_review_model_with_global_buffer(&file, ctx)
} else {
self.create_code_review_model(&file, ctx)
}
}
#[cfg(target_family = "wasm")]
{
self.create_code_review_model(&file, ctx)
}
};

// Re-borrow mutably after the editor construction calls above.
let Some(repo) = self.active_repo.as_mut() else {
return;
};
let CodeReviewViewState::Loaded(state) = &mut repo.state else {
return;
};
if let Some(file_state) = state.file_states.get_mut(path) {
file_state.editor_state = editor_state;
}
}

fn get_existing_diffset_comment(&self, app: &AppContext) -> Option<AttachedReviewComment> {
self.active_comment_model
.as_ref()
Expand Down Expand Up @@ -7109,6 +7176,11 @@ impl TypedActionView for CodeReviewView {
}
};

// Lazily construct the editor if this file was deferred.
if now_expanded {
self.ensure_editor_for_file(path, ctx);
}

// Update the chevron button icon based on expanded state
chevron_button.update(ctx, |button, ctx| {
let icon = if now_expanded {
Expand Down Expand Up @@ -7156,7 +7228,7 @@ impl TypedActionView for CodeReviewView {
CodeReviewAction::FileSelected(file_index) => {
// Early-return when repo/state/file is missing to avoid calling
// invalidate_height_for_index or scroll_to with an invalid index.
let was_expanded = {
let (was_expanded, file_path) = {
let Some(repo) = self.active_repo.as_mut() else {
return;
};
Expand All @@ -7168,9 +7240,14 @@ impl TypedActionView for CodeReviewView {
};
let was_expanded = file.is_expanded;
file.is_expanded = true;
was_expanded
(was_expanded, file.file_diff.file_path.clone())
};

// Lazily construct the editor if this file was deferred.
if !was_expanded {
self.ensure_editor_for_file(&file_path, ctx);
}

self.viewported_list_state
.invalidate_height_for_index(*file_index);

Expand Down
1 change: 1 addition & 0 deletions app/src/code_review/code_review_view_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ fn create_loaded_state_with_editors(
},
editor_state: Some(CodeReviewEditorState::new_loaded(editor)),
is_expanded: true,
deferred_content_at_head: None,
sidebar_mouse_state: MouseStateHandle::default(),
header_mouse_state: MouseStateHandle::default(),
chevron_button,
Expand Down