2517 lines · 94.5 KB · Rust
RawDownload
#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]// Adding this file level gate as some of the code around editability is not used in WASM yet.use std::collections::{HashMap, HashSet};use std::fmt::Debug;use std::ops::Range;use std::path::Path; use ai::diff_validation::DiffDelta;use lazy_static::lazy_static;use num_traits::SaturatingSub;use pathfinder_geometry::vector::vec2f;use settings::Setting as _;use string_offset::CharOffset;use vec1::{Vec1, vec1};use vim::vim::{Direction, InsertPosition, VimMode, VimModel, VimState, VimSubscriber};use warp_core::platform::SessionPlatform;use warp_editor::content::buffer::{    Buffer, BufferEditAction, EditOrigin, InitialBufferState, ToBufferCharOffset as _,    ToBufferPoint,};use warp_editor::content::text::IndentUnit;use warp_editor::content::version::BufferVersion;use warp_editor::model::{CoreEditorModel, PlainTextEditorModel};use warp_editor::multiline::AnyMultilineString;use warp_editor::render::element::lens_element::RichTextElementLens;use warp_editor::render::element::{    DisplayOptions, DisplayStateHandle, RichTextElement, VerticalExpansionBehavior,};use warp_editor::render::model::{    AutoScrollMode, BlockSpacing, CODE_EDITOR_HIDDEN_SECTION_EXPANSION_LINES, Decoration,    ExpansionType, LineCount, ParagraphStyles, RichTextStyles,};use warp_editor::search::{MATCH_FILL, SELECTED_MATCH_FILL, SearchEvent, Searcher};use warp_util::content_version::ContentVersion;use warp_util::standardized_path::StandardizedPath;use warpui::elements::new_scrollable::{    AxisConfiguration, DualAxisConfig, NewScrollableElement, ScrollableAppearance,};use warpui::elements::{    ChildAnchor, ChildView, Dismiss, Fill, Flex, Margin, MouseStateHandle, NewScrollable,    OffsetPositioning, Padding, ParentAnchor, ParentElement, ParentOffsetBounds, ScrollStateHandle,    Shrinkable, Stack,};use warpui::event::ModifiersState;use warpui::keymap::Keystroke;use warpui::platform::Cursor;use warpui::prelude::RectF;use warpui::text::point::Point;use warpui::units::Pixels;use warpui::{    AppContext, BlurContext, CursorInfo, Element, Entity, FocusContext, ModelHandle,    SingletonEntity, View, ViewContext, ViewHandle, WeakViewHandle, WindowId,}; use crate::appearance::Appearance;use crate::code::editor::EditorReviewComment;use crate::code::editor::comment_editor::{CommentEditor, CommentEditorEvent};use crate::code::editor::comments::PendingComment;use crate::code::editor::diff::DiffStatus;use crate::code::editor::element::{    AddAsContextButton, CommentButton, EditorWrapper, EditorWrapperStateHandle, GutterHoverTarget,    GutterRange, InnerEditor, LineNumberConfig, RevertHunkButton,};use crate::code::editor::find::view::{CodeEditorFind as Find, Event as FindViewEvent};use crate::code::editor::goto_line::view::{Event as GoToLineEvent, GoToLineView};use crate::code::editor::line::EditorLineLocation;use crate::code::editor::model::{    CodeEditorModel, CodeEditorModelEvent, HoverableLink, LineBound, StableEditorLine,};use crate::code::editor::nav_bar::{NavBar, NavBarBehavior, NavBarEvent};use crate::code::editor::scroll::{ScrollPosition, ScrollTrigger, ScrollWheelBehavior};use crate::code::{    NoopCommentEditorProvider, NoopFindReferencesCardProvider, ShowCommentEditorProvider,    ShowFindReferencesCardProvider,};use crate::code_review::comments::{CommentId, CommentOrigin};use crate::editor::InteractionState;use crate::features::FeatureFlag;use crate::notebooks::editor::rich_text_styles;use crate::settings::{AppEditorSettings, CodeEditorLineNumberMode, FontSettings};use crate::view_components::find::FindDirection; mod actions;pub(super) use actions::CodeEditorViewAction;pub use actions::init; mod vim_handler; /// Limit the keybindings that conflict with the Agent Mode embedded editor.const NON_EDITABLE_KEYMAP_CONTEXT: &str = "NonEditableKeymapContext"; lazy_static! {    static ref AUTOCOMPLETE_SYMBOLS: HashMap<char, char> =        HashMap::from([('(', ')'), ('[', ']'), ('{', '}'), ('\'', '\''), ('"', '"'),]);    static ref CLOSING_SYMBOLS: HashSet<char> = AUTOCOMPLETE_SYMBOLS.values().cloned().collect();} pub enum CodeEditorEvent {    Focused,    ContentChanged {        origin: EditOrigin,    },    /// Emitted when a unified diff computation completes.    UnifiedDiffComputed,    SelectionChanged,    SelectionStart,    SelectionEnd,    CopiedEmptyText,    /// Emitted when the editor is in Vim mode and a user Escape while already in Normal mode.    VimEscapeInNormalMode,    /// Emitted when escape key is pressed (regardless of Vim state).    /// This allows parent views to handle escape for closing overlays.    EscapePressed,    /// Emitted when diff decorations are updated (line highlights, removed lines, etc.)    DiffUpdated,    /// Emitted when the plus icon is clicked to add diff hunk context    DiffHunkContextAdded {        #[allow(dead_code)]        line_range: Range<LineCount>,    },    /// Emitted when a diff hunk is reverted    DiffReverted,    /// Emitted when the inline comment editor is opened.    CommentEditorOpened,    HiddenSectionExpanded,    /// Emitted when a comment is saved. This gets propagated up so that it    /// can be augmented with the file and repo paths and saved to the comment model.    CommentSaved {        comment: EditorReviewComment,    },    RequestOpenComment(CommentId),    /// Emitted when the viewport is updated after layout    ViewportUpdated,    DelayedRenderingFlushed,    /// Emitted when the render state layout has been updated.    LayoutInvalidated,    #[cfg(windows)]    WindowsCtrlC {        /// True if the `ctrl-c` action was used to copy an active selection.        copied_selection: bool,    },    MouseHovered {        offset: CharOffset,        cmd: bool,        clamped: bool,        /// Whether the mouse move event was covered by an element above the editor.        is_covered: bool,    },    DeleteComment {        id: CommentId,    },    VimGotoDefinition,    VimFindReferences,    VimShowHover,} /// Store all states related to displaying the editor content.#[derive(Default)]struct DisplayHandles {    vertical_scroll_state: ScrollStateHandle,    horizontal_scroll_state: ScrollStateHandle,    display_state: DisplayStateHandle,    wrapper_state_handle: EditorWrapperStateHandle,} #[derive(Debug, Clone, Copy)]struct CodeEditorViewDisplayOptions {    vertical_expansion_behavior: VerticalExpansionBehavior,    /// If `false`, we will never show the diff UI in this code editor.    can_show_diff_ui: bool,    collapsible_diffs: bool,    show_line_numbers: bool,    scroll_wheel_behavior: ScrollWheelBehavior,    horizontal_scrollbar_appearance: ScrollableAppearance,    vertical_scrollbar_appearance: ScrollableAppearance,    show_nav_bar: bool,    /// The add as context button, or `None` if it is not currently visible.    diff_hunk_as_context: Option<AddAsContextButton>,    /// The revert diff button, or `None` if it is not currently visible.    revert_diff_hunk: Option<RevertHunkButton>,    /// The add comment button, or `None` if it is not currently visible.    comment_button: Option<CommentButton>,    /// Whether to expand the width of the diff indicator in the gutter on hover.    expand_diff_indicator_width_on_hover: bool,    // Provides a starting line number for markdown code blocks.    starting_line_number: Option<usize>,    gutter_hover_target: GutterHoverTarget,    line_height_override: Option<f32>,} #[derive(Clone, Debug)]pub(super) struct SavedComment {    uuid: CommentId,    location: EditorLineLocation,    mouse_state: MouseStateHandle,} impl SavedComment {    pub fn location(&self) -> &EditorLineLocation {        &self.location    }     pub fn mouse_state(&self) -> &MouseStateHandle {        &self.mouse_state    }     pub fn uuid(&self) -> CommentId {        self.uuid    }} #[derive(Debug)]pub struct CodeEditorRenderOptions {    vertical_expansion_behavior: VerticalExpansionBehavior,    line_height_override: Option<f32>,    lazy_layout: bool,    show_comment_editor_provider: Box<dyn ShowCommentEditorProvider>,    show_find_references_provider: Box<dyn ShowFindReferencesCardProvider>,} impl CodeEditorRenderOptions {    pub fn new(vertical_expansion_behavior: VerticalExpansionBehavior) -> Self {        Self {            vertical_expansion_behavior,            line_height_override: None,            lazy_layout: false,            show_comment_editor_provider: Box::new(NoopCommentEditorProvider),            show_find_references_provider: Box::new(NoopFindReferencesCardProvider),        }    }     pub fn lazy_layout(mut self) -> Self {        self.lazy_layout = true;        self    }     pub fn line_height_override(mut self, line_height: f32) -> Self {        self.line_height_override = Some(line_height);        self    }     pub fn with_show_comment_editor_provider(        mut self,        comment_editor_provider: impl ShowCommentEditorProvider,    ) -> Self {        self.show_comment_editor_provider = Box::new(comment_editor_provider);        self    }     pub fn with_show_find_references_provider(        mut self,        find_references_provider: impl ShowFindReferencesCardProvider,    ) -> Self {        self.show_find_references_provider = Box::new(find_references_provider);        self    }} pub struct CodeEditorView {    pub model: ModelHandle<CodeEditorModel>,    pub searcher: ModelHandle<Searcher>,    find_bar: Option<ViewHandle<Find>>,    goto_line_dialog: ViewHandle<GoToLineView>,    nav_bar: ViewHandle<NavBar>,    display_states: DisplayHandles,    is_selecting: bool,    self_handle: WeakViewHandle<Self>,    display_options: CodeEditorViewDisplayOptions,    pending_scroll: Option<ScrollTrigger>,    supports_vim_mode: bool,    vim_model: ModelHandle<VimModel>,    // Track the most recent Vim search direction to determine how to cycle (n/N) thereafter.    last_search_direction: Direction,    active_comment_editor: ViewHandle<CommentEditor>,    /// TODO: maybe turn into a map for fast UUID or range lookup    comment_locations: Vec<SavedComment>,    /// Save position of the comment button rendered within this code editor view.    comment_save_position_id: String,    show_comment_editor_provider: Box<dyn ShowCommentEditorProvider>,    /// Save position of the anchor point for find references card.    find_references_save_position_id: String,    show_find_references_provider: Box<dyn ShowFindReferencesCardProvider>,    /// The offset where find references card is anchored (if showing).    find_references_anchor_offset: Option<CharOffset>,    window_id: WindowId,} impl CodeEditorView {    /// A [`SessionPlatform`] is used to determine the default line ending for    /// the code editor. It should be provided if the code editor can modify    /// files on the user's system.    pub fn new(        session_platform: Option<SessionPlatform>,        buffer: Option<ModelHandle<Buffer>>,        render_options: CodeEditorRenderOptions,        ctx: &mut ViewContext<Self>,    ) -> Self {        let appearance_handle = Appearance::handle(ctx);        let font_settings_handle = FontSettings::handle(ctx);        let initial_styles = code_text_styles(            appearance_handle.as_ref(ctx),            font_settings_handle.as_ref(ctx),            render_options.line_height_override,        );        ctx.subscribe_to_model(&appearance_handle, |me, _, _, ctx| {            me.handle_appearance_or_font_change(ctx);        });        ctx.subscribe_to_model(&font_settings_handle, |me, _, _, ctx| {            me.handle_appearance_or_font_change(ctx);        });        let app_editor_settings_handle = AppEditorSettings::handle(ctx);        ctx.subscribe_to_model(&app_editor_settings_handle, |_, _, _, ctx| {            ctx.notify();        });         let model = ctx.add_model(|ctx| {            CodeEditorModel::new(                initial_styles,                session_platform,                render_options.lazy_layout,                buffer,                ctx,            )        });        ctx.subscribe_to_model(&model, |me, _, event, ctx| {            me.handle_editor_model_event(event, ctx);        });         // Creates a new model for searching the editor        let buffer = model.as_ref(ctx).buffer().clone();        let selection_model = model.as_ref(ctx).buffer_selection_model().clone();        let searcher = ctx.add_model(|ctx| Searcher::new(buffer, selection_model, ctx));        // Subscribes to events from Searcher model so that we can handle        ctx.subscribe_to_model(&searcher, |me, _, event, ctx| {            me.handle_searcher_event(event, ctx);        });         let find_bar = ctx.add_typed_action_view(|ctx| Find::new(searcher.clone(), ctx));        ctx.subscribe_to_view(&find_bar, move |me, _, event, ctx| {            me.handle_find_event(event, ctx);        });         let goto_line_dialog = ctx.add_typed_action_view(GoToLineView::new);        ctx.subscribe_to_view(&goto_line_dialog, |me, _, event, ctx| {            me.handle_goto_line_event(event, ctx);        });         let nav_bar = ctx.add_typed_action_view(|ctx| NavBar::new(model.clone(), ctx));        ctx.subscribe_to_view(&nav_bar, |me, _, event, ctx| match event {            NavBarEvent::Close => {                me.toggle_diff_nav(None, ctx);                ctx.notify();            }        });         // If feature flag is enabled, enable vim mode.        let supports_vim_mode = FeatureFlag::VimCodeEditor.is_enabled();         let vim_model = ctx.add_model(|_| VimModel::new());        ctx.subscribe_to_model(&vim_model, Self::handle_vim_event);         // Ensure CodeEditorView starts in Normal mode when Vim keybindings are enabled.        if supports_vim_mode && AppEditorSettings::as_ref(ctx).vim_mode_enabled() {            vim_model.update(ctx, |vim_model, ctx| {                if let Ok(escape) = Keystroke::parse("escape") {                    vim_model.keypress(&escape, ctx);                }            });        }        // Ensure that we re-render when the rendering model changes.        ctx.observe(&model.as_ref(ctx).render_state().clone(), |_, _, ctx| {            ctx.notify();        });         let comment_model = model.as_ref(ctx).comments().clone();        let comment_editor =            ctx.add_typed_action_view(|ctx| CommentEditor::new(ctx, comment_model));        ctx.subscribe_to_view(&comment_editor, |me, _, event, ctx| {            me.handle_comment_editor_event(event, ctx);        });         Self {            searcher,            find_bar: Some(find_bar),            goto_line_dialog,            model,            display_states: Default::default(),            is_selecting: false,            self_handle: ctx.handle(),            nav_bar,            comment_locations: Vec::new(),            display_options: CodeEditorViewDisplayOptions {                vertical_expansion_behavior: render_options.vertical_expansion_behavior,                can_show_diff_ui: true,                collapsible_diffs: true,                show_line_numbers: true,                starting_line_number: None,                show_nav_bar: true,                diff_hunk_as_context: Default::default(),                revert_diff_hunk: Default::default(),                comment_button: Default::default(),                // By default expand diff indicators on hover.                expand_diff_indicator_width_on_hover: true,                scroll_wheel_behavior: ScrollWheelBehavior::AlwaysHandle,                // By default, we should render the horizontal scrollbar as overlay to prevent it                // from truncating space for the code editor. We should not render it as an overlay                // for small code editors.                horizontal_scrollbar_appearance: ScrollableAppearance::new(                    warpui::elements::ScrollbarWidth::Auto,                    false,                ),                vertical_scrollbar_appearance: ScrollableAppearance::new(                    warpui::elements::ScrollbarWidth::Auto,                    false,                ),                gutter_hover_target: GutterHoverTarget::GutterElement,                line_height_override: render_options.line_height_override,            },            pending_scroll: None,            supports_vim_mode,            vim_model,            last_search_direction: Direction::Forward,            active_comment_editor: comment_editor,            comment_save_position_id: format!("code_editor_comment_{}", ctx.view_id()),            show_comment_editor_provider: render_options.show_comment_editor_provider,            find_references_save_position_id: format!(                "code_editor_find_references_{}",                ctx.view_id()            ),            show_find_references_provider: render_options.show_find_references_provider,            find_references_anchor_offset: None,            window_id: ctx.window_id(),        }    }     pub fn set_find_references_anchor_offset(&mut self, offset: Option<CharOffset>) {        self.find_references_anchor_offset = offset;    }     pub fn find_references_save_position_id(&self) -> &str {        &self.find_references_save_position_id    }     pub fn show_find_references_provider(&self) -> &dyn ShowFindReferencesCardProvider {        self.show_find_references_provider.as_ref()    }     pub fn window_id(&self) -> WindowId {        self.window_id    }     pub fn set_add_diff_hunk_as_context_button(        &mut self,        enabled: bool,        ctx: &mut ViewContext<Self>,    ) {        self.display_options.diff_hunk_as_context = Some(AddAsContextButton::new(enabled));        ctx.notify();    }     /// Enables the add context button (plus icon) in diff hunks. Only enable this for code review views.    pub fn with_add_context_button(mut self) -> Self {        self.display_options.diff_hunk_as_context =            Some(AddAsContextButton::new(true /* enabled */));        self    }     /// Enables the "revert" button on diff hunks. Only enable this for code review views.    pub fn with_revert_diff_hunk_button(mut self) -> Self {        self.display_options.revert_diff_hunk =            Some(RevertHunkButton::new(true /* is_enabled */));        self    }     /// Enables the "comment" button on diff hunks. Only enable this for code review views.    pub fn with_comment_button(mut self) -> Self {        self.display_options.comment_button = Some(CommentButton::default());        self    }     /// Disables the diff indicator expanding on hover.    pub fn disable_diff_indicator_expansion_on_hover(mut self) -> Self {        self.display_options.expand_diff_indicator_width_on_hover = false;        self    }     pub fn with_gutter_hover_target(mut self, target: GutterHoverTarget) -> Self {        self.display_options.gutter_hover_target = target;        self    }     /// Enables clicking on diff hunk gutter elements to collapse changed sections.    pub fn with_collapsible_diffs(mut self, enabled: bool) -> Self {        self.display_options.collapsible_diffs = enabled;        self    }     pub(crate) fn disable_find_and_replace(mut self) -> Self {        self.find_bar = None;        self

Lines 1–500 of 2517.