7758 lines · 305.0 KB · Rust
RawDownload
use std::collections::{HashMap, HashSet};use std::mem;use std::ops::Range;use std::path::{Path, PathBuf};use std::sync::Arc;use std::time::Duration; use ai::project_context::model::ProjectContextModel;use indexmap::IndexMap;use itertools::Itertools;#[cfg(feature = "local_fs")]use num_traits::SaturatingSub;use pathfinder_geometry::rect::RectF;use pathfinder_geometry::vector::{Vector2F, vec2f};use rand::Rng;use rand::distributions::Alphanumeric;use string_offset::CharOffset;use vec1::Vec1;use warp_core::channel::{Channel, ChannelState};use warp_core::features::FeatureFlag;use warp_core::ui::theme::color::internal_colors;use warp_core::{SessionId, safe_error, safe_info};use warp_editor::content::buffer::{AutoScrollBehavior, InitialBufferState, SelectionOffsets};use warp_editor::model::CoreEditorModel;use warp_editor::render::element::VerticalExpansionBehavior;#[cfg(not(target_family = "wasm"))]use warp_editor::render::model::AutoScrollMode;use warp_editor::render::model::LineCount;use warp_util::content_version::ContentVersion;use warp_util::path::LineAndColumnArg;use warp_util::standardized_path::StandardizedPath;use warpui::clipboard::ClipboardContent;use warpui::elements::new_scrollable::{    NewScrollable, NewScrollableElement, ScrollableAppearance, SingleAxisConfig,};use warpui::elements::{    Align, Border, ChildAnchor, ChildView, Clipped, ClippedScrollStateHandle, ConstrainedBox,    Container, CornerRadius, CrossAxisAlignment, DEFAULT_UI_LINE_HEIGHT_RATIO, DispatchEventResult,    DragBarSide, Element, Empty, EventHandler, Flex, Hoverable, List, ListState, MainAxisAlignment,    MainAxisSize, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement,    ParentOffsetBounds, Percentage, PositionedElementAnchor, PositionedElementOffsetBounds, Radius,    Rect, Resizable, ResizableStateHandle, SavePosition, ScrollOffset, ScrollStateHandle,    ScrollbarWidth, Shrinkable, Stack, Text, resizable_state_handle,};use warpui::fonts::{Properties, Weight};use warpui::keymap::Keystroke;use warpui::platform::Cursor;use warpui::text_layout::{ClipConfig, default_compute_baseline_position};use warpui::ui_components::button::{ButtonVariant, TextAndIcon, TextAndIconAlignment};use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};use warpui::units::Pixels;use warpui::{    AppContext, Entity, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext,    ViewHandle, WeakViewHandle, WindowId,}; use super::code_review_header::CodeReviewHeader;use super::comment_list_view::{CommentListDebugState, CommentListEvent, CommentListView};use super::comments::{AttachedReviewComment, CommentOrigin, attach_pending_imported_comments};use super::diff_size_limits::DiffSize;use super::git_dialog::{GitDialog, GitDialogEvent, GitDialogKind};use super::{GlobalCodeReviewEvent, GlobalCodeReviewModel};#[cfg(feature = "local_fs")]use crate::TelemetryEvent;use crate::ai::agent::{    AIAgentAttachment, AgentReviewCommentBatch, CurrentHead, DiffBase, DiffSetHunk,};use crate::ai::blocklist::agent_view::AgentViewEntryOrigin;use crate::appearance::Appearance;use crate::code::ShowCommentEditorProvider;#[cfg(not(target_family = "wasm"))]use crate::code::ShowFindReferencesCard;use crate::code::buffer_location::LocalOrRemotePath;use crate::code::editor::comment_editor::DEFAULT_COMMENT_MAX_WIDTH;use crate::code::editor::line::EditorLineLocation;use crate::code::editor::view::{CodeEditorEvent, CodeEditorRenderOptions, CodeEditorView};use crate::code::editor::{    CommentEditor, CommentEditorEvent, EditorCommentsModel, EditorReviewComment, GutterHoverTarget,    add_color, remove_color,};use crate::code::editor_management::CodeEditorStatus;use crate::code::footer::{CodeFooterView, CodeFooterViewEvent};use crate::code::global_buffer_model::GlobalBufferModel;use crate::code::local_code_editor::{    LocalCodeEditorEvent, LocalCodeEditorView, render_unsaved_circle_with_tooltip,};use crate::code::view::PendingSaveIntent;use crate::code_review::DiffSetScope;use crate::code_review::comments::{    AttachedReviewCommentTarget, CommentId, ReviewCommentBatch, ReviewCommentBatchEvent,};use crate::code_review::context::convert_file_diffs_to_diffset_hunks;#[cfg(feature = "local_fs")]use crate::code_review::context::{    create_attachment_reference_and_key, register_diffset_attachment,};use crate::code_review::diff_selector::{DiffSelector, DiffSelectorEvent, DiffTarget};use crate::code_review::diff_state::{    DiffHunk, DiffLineType, DiffMode, DiffState, DiffStateModel, DiffStateModelEvent, DiffStats,    FileDiff, FileDiffAndContent, FileStatusInfo, GitDiffWithBaseContent, GitFileStatus,};use crate::code_review::editor_state::CodeReviewEditorState;use crate::code_review::find_model::CodeReviewFindModel;use crate::code_review::git_repo_model::{GitRepoModels, GitRepoStatusEvent, GitRepoStatusModel};use crate::code_review::github_repo_model::{GitHubRepoEvent, GitHubRepoModel};use crate::code_review::hidden_lines::calculate_hidden_lines;#[cfg(feature = "local_fs")]use crate::code_review::telemetry_event::DiffSetContextScope;use crate::code_review::telemetry_event::{    AddToContextOrigin, CodeReviewContextDestination, CodeReviewTelemetryEvent, GitButtonKind,    PaneStateChange,};use crate::coding_panel_enablement_state::CodingPanelEnablementState;use crate::editor::InteractionState;use crate::menu::{Event as MenuEvent, Menu, MenuItem, MenuItemFields};use crate::pane_group::PaneId;use crate::pane_group::focus_state::{PaneFocusHandle, PaneGroupFocusEvent};use crate::pane_group::pane::{BackingView, PaneEvent, view};use crate::quit_warning::UnsavedStateSummary;use crate::send_telemetry_from_ctx;#[cfg(feature = "local_fs")]use crate::server::telemetry::CodePanelsFileOpenEntrypoint;use crate::settings::{AISettings, CodeSettings};use crate::settings_view::SettingsSection;use crate::terminal::cli_agent::{    build_selection_line_range_prompt, build_selection_substring_prompt,};use crate::terminal::input::MenuPositioning;use crate::terminal::view::{CliAgentRouting, InitProjectModel, TerminalAction, TerminalView};use crate::themes::theme::WarpTheme;use crate::ui_components::blended_colors::{neutral_2, neutral_3};use crate::ui_components::buttons::icon_button_with_color;use crate::ui_components::dialog::{Dialog, dialog_styles};use crate::ui_components::icons::Icon;use crate::ui_components::render_file_search_row::{FileSearchRowOptions, render_file_search_row};use crate::util::bindings::{    CustomAction, custom_tag_to_keystroke, keybinding_name_to_display_string,};#[cfg(feature = "local_fs")]use crate::util::file::external_editor::EditorSettings;use crate::util::git::{BranchEntry, PrInfo};#[cfg(feature = "local_fs")]use crate::util::openable_file_type::FileTarget;#[cfg(feature = "local_fs")]use crate::util::openable_file_type::resolve_file_target_with_editor_choice;use crate::view_components::DismissibleToast;use crate::view_components::action_button::{    ActionButton, ActionButtonTheme, AdjoinedSide, ButtonSize, DangerPrimaryTheme, KeystrokeSource,    NakedTheme, PaneHeaderTheme, SecondaryTheme, TooltipAlignment,};use crate::view_components::find::{Event as FindViewEvent, Find, FindEvent, FindWithinBlockState};use crate::workspace::view::right_panel::{ReviewDestination, ReviewSubmissionResult};use crate::workspace::{ToastStack, Workspace, WorkspaceAction}; pub struct CodeReviewHeaderFields {    pub is_in_split_pane: bool,    pub diff_state_model: ModelHandle<DiffStateModel>,    pub maximize_button: ViewHandle<ActionButton>,    pub diff_selector: ViewHandle<DiffSelector>,    pub header_menu: ViewHandle<Menu<CodeReviewAction>>,    pub header_menu_open: bool,    pub header_dropdown_button: ViewHandle<ActionButton>,    pub has_header_menu_items: bool,    pub file_nav_button: Option<ViewHandle<ActionButton>>,    pub primary_git_action_mode: PrimaryGitActionMode,    pub git_primary_action_button: ViewHandle<ActionButton>,    pub git_operations_chevron: ViewHandle<ActionButton>,    pub git_operations_menu: ViewHandle<Menu<CodeReviewAction>>,    pub git_operations_menu_open: bool,} #[derive(Clone, Debug, PartialEq)]pub struct CodeReviewCommentDebugState {    pub repo_path: Option<LocalOrRemotePath>,    pub has_active_comment_model: bool,    pub comment_list: CommentListDebugState,} /// Renders a file navigation button (sidebar toggle) that can be reused across views.pub fn render_file_navigation_button<F>(    appearance: &Appearance,    is_sidebar_expanded: bool,    mouse_state: MouseStateHandle,    on_click: F,) -> Box<dyn Element>where    F: Fn(&mut warpui::EventContext<'_>) + 'static,{    let ui_builder = appearance.ui_builder().clone();    let icon_color = appearance        .theme()        .sub_text_color(appearance.theme().background());    let button = icon_button_with_color(        appearance,        if is_sidebar_expanded {            Icon::LeftSidebarClose        } else {            Icon::LeftSidebarOpen        },        false,        mouse_state,        icon_color,    )    .with_tooltip(move || {        ui_builder            .tool_tip(if is_sidebar_expanded {                "Hide file navigation".to_owned()            } else {                "Show file navigation".to_owned()            })            .build()            .finish()    })    .with_tooltip_position(warpui::ui_components::button::ButtonTooltipPosition::BelowLeft)    .build()    .on_click(move |ctx: &mut warpui::EventContext<'_>, _, _| {        on_click(ctx);    });     Container::new(        ConstrainedBox::new(button.finish())            .with_height(24.)            .with_width(24.)            .finish(),    )    .with_margin_right(4.)    .finish()} /// Determines which primary git action the code review header should present.#[derive(Clone, Copy, Debug, PartialEq, Eq)]pub(crate) enum PrimaryGitActionMode {    /// There are uncommitted changes. Primary = Commit, dropdown shows    /// Commit / Push-or-Publish / Create PR with per-item disabled states.    Commit,    /// Branch has an upstream and unpushed local commits. Primary = Push,    /// dropdown shows Commit (greyed) / Push / Create PR.    Push,    /// Nothing to commit or push, and no existing PR. Primary = Create PR, chevron hidden.    CreatePr,    /// Nothing to commit or push, and a PR exists for this branch. Primary = PR #N, chevron hidden.    ViewPr,    /// No upstream tracking branch, but local commits exist. Primary = Publish, chevron hidden.    Publish,} const DEFAULT_FILE_SIDEBAR_WIDTH: f32 = 250.;const FILE_SIDEBAR_MIN_WIDTH: f32 = 150.;const FILE_SIDEBAR_MAX_WIDTH: f32 = 800.;/// This is a best guess effort at getting the height of the file header./// If the file header is changed, we should update this value.const FILE_HEADER_HEIGHT: f32 = 41.;/// The gap between editors in the viewported list.const EDITOR_GAP: f32 = 12.;const FILE_SIDEBAR_PANE_WIDTH_PERCENTAGE: f32 = 0.25;/// Vertical gap between the right panel header row and the code review content below it/// (sub-header in loaded state, loading text in loading state).pub(super) const CONTENT_TOP_MARGIN: f32 = 4.;/// Horizontal margins for the code review content area. The right panel header/// uses these same values so its edges align with the content below.pub(crate) const CONTENT_LEFT_MARGIN: f32 = 16.;pub(crate) const CONTENT_RIGHT_MARGIN: f32 = 4.;const CODE_REVIEW_EDITOR_LINE_HEIGHT_RATIO: f32 = 1.4;/// Extra scroll buffer (in pixels) added when scrolling to a line that has a comment editor below it.const COMMENT_EDITOR_SCROLL_BUFFER: f32 = 200.0; pub const CODE_REVIEW_TOOLTIP_TEXT: &str = "View changes";const REMOTE_TEXT: &str = "Diffs only work for local workspaces.";const DISABLED_TEXT: &str = "Diffs only work for git repositories.";const WSL_TEXT: &str = "Diffs don't currently work in WSL."; pub fn get_discard_button_disabled_tooltip(git_operation_blocked: bool) -> String {    if git_operation_blocked {        "Cannot discard changes while a git operation (merge, rebase, etc.) is in progress"            .to_string()    } else {        "No changes to discard".to_string()    }} /// Builds the tooltip text for the file navigation toggle button, including the/// live shortcut for `code_review:toggle_file_navigation` when one is bound.fn file_nav_button_tooltip(is_sidebar_expanded: bool, app: &AppContext) -> String {    let label = if is_sidebar_expanded {        "Hide file navigation"    } else {        "Show file navigation"    };    match keybinding_name_to_display_string("code_review:toggle_file_navigation", app) {        Some(shortcut) => format!("{label} ({shortcut})"),        None => label.to_string(),    }} /// Returns true if the file status changed between Deleted and non-Deleted states,/// which requires rebuilding the editor state because we can't use global buffer/// for files that don't exist on the file system.#[cfg(not(target_family = "wasm"))]fn file_status_changed_deleted_state(    current_status: &GitFileStatus,    new_status: &GitFileStatus,) -> bool {    matches!(current_status, GitFileStatus::Deleted) != matches!(new_status, GitFileStatus::Deleted)} #[cfg(target_family = "wasm")]fn file_status_changed_deleted_state(    _current_status: &GitFileStatus,    _new_status: &GitFileStatus,) -> bool {    false} #[derive(Clone, Debug, PartialEq)]pub enum CodeReviewAction {    OpenInNewTab {        path: String,        line_and_column: Option<LineAndColumnArg>,    },    ToggleFileExpanded(String),    OpenHeaderMenu,    SetDiffMode(DiffMode),    ToggleFileSidebar,    FileSelected(usize),    ToggleMaximize,    SaveAllUnsavedFiles,    SaveAllFiles {        paths: Vec<String>,    },    RefreshGitState,    UndoRevert,    Close,    EmitPaneEvent(PaneEvent),    ShowDiscardConfirmDialog(Option<String>),    ConfirmDiscardFile,    CancelDiscardFile,    ToggleStashChanges,    ToggleFileSelection(String),    AddDiffSetAsContext(DiffSetScope),    CopyFilePath(String),    OpenCommentComposerFromHeader,    ShowFindBar,    FocusView,    InitProjectForCurrentDirectory,    OpenRepository,    OpenCommitDialog,    ToggleGitOperationsMenu,    OpenPushDialog,    OpenCreatePrDialog,    ViewPr(String),    PublishBranch,    SubmitReviewComments,} pub struct FileState {    pub file_diff: FileDiff,    pub editor_state: Option<CodeReviewEditorState>,    pub is_expanded: bool,    sidebar_mouse_state: MouseStateHandle,    header_mouse_state: MouseStateHandle,    chevron_button: ViewHandle<ActionButton>,    open_in_tab_button: ViewHandle<ActionButton>,    discard_button: ViewHandle<ActionButton>,    add_context_button: ViewHandle<ActionButton>,    copy_path_button: ViewHandle<ActionButton>,} pub(crate) struct LoadedState {    /// Repo-relative file paths keyed as Strings; absolute file identities use    /// StandardizedPath or LocalOrRemotePath at API boundaries.    pub(crate) file_states: IndexMap<String, FileState>,    pub(crate) total_additions: usize,    pub(crate) total_deletions: usize,    pub(crate) files_changed: usize,} impl LoadedState {    pub(crate) fn to_diff_stats(&self) -> DiffStats {        DiffStats {            files_changed: self.files_changed,            total_additions: self.total_additions,            total_deletions: self.total_deletions,        }    }     /// Returns a list of pairs of code editor views and the host-aware    /// absolute paths of their underlying files.    fn editor_absolute_file_paths(        &self,        repo_path: &LocalOrRemotePath,    ) -> Vec<(ViewHandle<LocalCodeEditorView>, LocalOrRemotePath)> {        self.file_states            .values()            .filter_map(|file_state| {                let editor = file_state.editor_state.as_ref()?.editor().clone();                let file_location = repo_path.join(&file_state.file_diff.file_path);                Some((editor, file_location))            })            .collect()    }} /// State of the code review viewenum CodeReviewViewState {    None,    Loaded(LoadedState),    Error(String),    NoRepoFound,} struct UiStateHandles {    sidebar_scroll_state: ClippedScrollStateHandle,    sidebar_resizable_state: ResizableStateHandle,    retry_button_mouse_state: MouseStateHandle,} impl Default for UiStateHandles {    fn default() -> Self {        Self {            sidebar_scroll_state: Default::default(),            sidebar_resizable_state: resizable_state_handle(DEFAULT_FILE_SIDEBAR_WIDTH),            retry_button_mouse_state: Default::default(),        }    }} #[cfg_attr(target_family = "wasm", allow(dead_code))]struct GitSessionState {    enablement: CodingPanelEnablementState,} #[derive(Clone, Debug)]pub enum CodeReviewViewEvent {    Pane(PaneEvent),    #[cfg(feature = "local_fs")]    OpenFileWithTarget {        path: PathBuf,        target: FileTarget,        line_col: Option<LineAndColumnArg>,    },    ReviewSubmitted,    /// Emitted when review comments are ready to be submitted.    /// A higher-level view (RightPanelView) handles routing to an available terminal.    SubmitReviewComments {        comments: AgentReviewCommentBatch,        repo_path: LocalOrRemotePath,    },    /// Request to open a file in a new tab (e.g. goto-definition).    OpenFileInNewTab {        path: LocalOrRemotePath,        line_and_column: Option<LineAndColumnArg>,    },    /// Request to open LSP logs for the given log file path.    #[cfg(not(target_family = "wasm"))]    OpenLspLogs {        log_path: PathBuf,    },} #[derive(Clone, Debug, PartialEq)]pub enum DiscardOperationType {    AllUncommittedChanges,    FileUncommittedChanges,    AllChangesAgainstBranch(Option<String>),    FileChangesAgainstBranch(Option<String>),} impl DiscardOperationType {    pub fn title(&self) -> String {        match self {            DiscardOperationType::AllUncommittedChanges => {                "Discard uncommitted changes?".to_string()            }            DiscardOperationType::FileUncommittedChanges => {                "Discard all uncommitted changes to file?".to_string()            }            DiscardOperationType::AllChangesAgainstBranch(_) => "Discard all changes?".to_string(),            DiscardOperationType::FileChangesAgainstBranch(_) => {                "Discard all changes to file?".to_string()            }        }    }     pub fn description(&self) -> Option<String> {        match self {            DiscardOperationType::AllUncommittedChanges => Some("You're about to discard all local changes that haven't been committed.".to_string()),            DiscardOperationType::FileUncommittedChanges => Some("This will restore this file to the last committed version and discard local edits.".to_string()),            DiscardOperationType::AllChangesAgainstBranch(None) => Some("You're about to discard all committed and uncommitted changes.".to_string()),            DiscardOperationType::FileChangesAgainstBranch(None) => Some("This will restore this file to the main branch version and discard all committed and uncommitted edits.".to_string()),            DiscardOperationType::AllChangesAgainstBranch(Some(_)) => Some("You're about to discard all committed and uncommitted changes.".to_string()),            DiscardOperationType::FileChangesAgainstBranch(Some(branch)) => Some(format!("This will reset this file to the {branch} branch version and discard all committed and uncommitted edits.")),        }    }     fn is_uncommitted_changes(&self) -> bool {        matches!(            self,            DiscardOperationType::AllUncommittedChanges                | DiscardOperationType::FileUncommittedChanges        )

Lines 1–500 of 7758.