use std::collections::HashMap;use std::path::{Path, PathBuf}; use lsp::LspManagerModel;use pathfinder_color::ColorU;use pathfinder_geometry::rect::RectF;use pathfinder_geometry::vector::vec2f;use warp_core::channel::{Channel, ChannelState};use warp_core::features::FeatureFlag;use warp_core::ui::appearance::Appearance;use warp_core::ui::icons::ICON_DIMENSIONS;use warp_editor::render::element::VerticalExpansionBehavior;use warp_util::path::LineAndColumnArg;#[cfg(feature = "local_fs")]use warpui::clipboard::ClipboardContent;use warpui::elements::{ AcceptedByDropTarget, Align, Border, ChildAnchor, ChildView, Clipped, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Draggable, DraggableState, DropTarget, Empty, Expanded, Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle, OffsetPositioning, Padding, ParentAnchor, ParentElement, ParentOffsetBounds, Radius, Rect, SavePosition, Shrinkable, Stack, Text,};use warpui::fonts::{Properties, Style, Weight};use warpui::keymap::EditableBinding;use warpui::text::point::Point;use warpui::text_layout::ClipConfig;use warpui::ui_components::button::ButtonVariant;use warpui::ui_components::components::UiComponent;use warpui::{ AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle, WindowId, id,}; use super::buffer_location::LocalOrRemotePath;use super::diff_viewer::DiffViewer;use super::editor::view::{CodeEditorEvent, CodeEditorView};use super::editor_management::{CodeManager, CodeSource};use super::local_code_editor::{LocalCodeEditorEvent, LocalCodeEditorView};use crate::code::editor::scroll::ScrollPosition;use crate::code::editor::view::CodeEditorRenderOptions;use crate::code::editor_management::CodeEditorStatus;use crate::code::global_buffer_model::GlobalBufferModel;use crate::code::local_code_editor::ShowFindReferencesCard;use crate::code::{EditorTabBarDropTargetData, ImmediateSaveError, SaveOutcome, SaveStatus};use crate::editor::InteractionState;use crate::input::Vector2F;use crate::menu::{MenuItem, MenuItemFields};use crate::notebooks::file::{MarkdownDisplayMode, renders_in_warp_notebook_viewer};use crate::pane_group::focus_state::PaneFocusHandle;use crate::pane_group::pane::view::header::components::{ CenteredHeaderEdgeWidth, render_pane_header_buttons, render_pane_header_title_text, render_three_column_header,};use crate::pane_group::pane::view::header::render_pane_header_draggable;use crate::pane_group::pane::{ActionOrigin, PaneHeaderAction, view};use crate::pane_group::{ BackingView, CodePane, PaneConfiguration, PaneConfigurationEvent, PaneDragDropLocation, PaneEvent, TabBarAxis,};use crate::quit_warning::UnsavedStateSummary;use crate::search::ItemHighlightState;use crate::search::files::icon::icon_from_file_path;use crate::server::telemetry::CodeContextDestination;use crate::settings::CodeSettings;use crate::tab::TAB_BAR_BORDER_HEIGHT;use crate::terminal::cli_agent::{ build_selection_line_range_prompt, build_selection_substring_prompt,};use crate::terminal::view::CliAgentRouting;use crate::ui_components::blended_colors;use crate::ui_components::buttons::icon_button;use crate::util::path::{display_name_with_host, display_path_with_host};use crate::view_components::{DismissibleToast, MarkdownToggleEvent, MarkdownToggleView};use crate::workspace::util::get_context_target_terminal_view;use crate::workspace::{ActiveSession, TabBarDropTargetData, ToastStack, WorkspaceAction};use crate::{TelemetryEvent, send_telemetry_from_ctx}; type SaveCallback = Box<dyn FnOnce(SaveOutcome, &mut CodeView, &mut ViewContext<CodeView>) + Send + Sync + 'static>; const CLOSE_BUTTON_WIDTH: f32 = 24.;const LANGUAGE_ICON_WIDTH: f32 = 16.;const TAB_INTERNAL_MARGIN: f32 = 4.;const TAB_HORIZONTAL_MARGIN: f32 = 8.;const TAB_PADDING: f32 = 2.; // Keybinding constants - exported so AI document view can reusepub const SAVE_FILE_BINDING_NAME: &str = "code_view:save";pub const SAVE_FILE_BINDING_DESCRIPTION: &str = "Save file"; pub fn init(app: &mut AppContext) { super::editor::view::init(app); super::local_code_editor::init(app); let text_entry = id!("CodeEditorView") & !id!("IMEOpen"); app.register_editable_bindings([ EditableBinding::new( SAVE_FILE_BINDING_NAME, SAVE_FILE_BINDING_DESCRIPTION, CodeViewAction::SaveFile, ) .with_context_predicate(text_entry.clone()) .with_key_binding("cmdorctrl-s"), EditableBinding::new( "code_view:save_as", "Save file as", CodeViewAction::SaveFileAs, ) .with_context_predicate(text_entry.clone()) .with_key_binding("cmdorctrl-shift-S"), EditableBinding::new( "code_view:close_all_tabs", "Close all tabs", CodeViewAction::CloseAll, ) .with_context_predicate(id!("CodeEditorView")) .with_key_binding("cmdorctrl-r w"), EditableBinding::new( "code_view:close_saved_tabs", "Close saved tabs", CodeViewAction::CloseSaved, ) .with_context_predicate(id!("CodeEditorView")) .with_key_binding("cmdorctrl-r u"), ]);} const PADDING: f32 = 4.; pub use crate::util::openable_file_type::is_binary_file;/// Determines the `SavePosition` ID for a draggable tab based on its index.pub fn tab_position_id(index: usize) -> String { format!("file_tab_position_{index}")} #[derive(Debug, Clone)]enum TabBarDragPosition { BeforeTab { index: usize }, AfterTab { index: usize },} #[derive(Debug, Clone)]pub enum CodeViewAction { SaveFile, SaveFileAs, AcceptPendingDiffsAndSave, RejectPendingDiffs, SetCurrentTabIndex { index: usize, }, RemoveTabAtIndex { index: usize, }, CloseAll, CloseSaved, ToggleMaximized, #[cfg(feature = "local_fs")] CopyFilePath, /// Open the active code tab's file in the platform's file manager /// (Finder on macOS, Explorer on Windows). No-op when the active tab has /// no resolvable local path. #[cfg(feature = "local_fs")] RevealInFinder, #[cfg(feature = "local_fs")] RenderMarkdown, DragOverIndex { target: usize, drag_position: RectF, }, DropAtIndex { origin: usize, target: usize, drag_position: RectF, }, ClearEditorTabGroupDragPositions, ClearWorkspaceTabGroupDragPositions,} #[derive(Debug, Clone)]pub enum CodeViewEvent { Pane(PaneEvent), TabChanged { location: Option<LocalOrRemotePath>, tab_index: usize, }, FileOpened { location: LocalOrRemotePath, tab_index: usize, }, RunTabConfigSkill { path: PathBuf, }, OpenLspLogs { log_path: PathBuf, },} #[derive(Default, Clone)]struct TabDataMouseStateHandles { tab_handle: MouseStateHandle, close_handle: MouseStateHandle, accept_mouse_state: MouseStateHandle, reject_mouse_state: MouseStateHandle, tab_draggable_state: DraggableState,} #[derive(Clone)]pub struct TabData { location: Option<LocalOrRemotePath>, editor_view: ViewHandle<LocalCodeEditorView>, mouse_state_handles: TabDataMouseStateHandles, preview: bool,} #[derive(Debug, Clone)]pub enum PendingSaveIntent { Save, Discard, Cancel,} impl TabData { /// Returns the file location (local or remote), if any. pub fn location(&self) -> Option<&LocalOrRemotePath> { self.location.as_ref() } /// Returns the local filesystem path, if this tab is backed by a local file. /// Returns `None` for remote files and untitled tabs. pub fn local_path(&self) -> Option<PathBuf> { self.location .as_ref() .and_then(|loc| loc.to_local_path().map(Path::to_path_buf)) }} pub struct CodeView { tab_group: Vec<TabData>, active_tab_index: usize, pane_configuration: ModelHandle<PaneConfiguration>, focus_handle: Option<PaneFocusHandle>, source: CodeSource, window_id: WindowId, drag_position: Option<TabBarDragPosition>, markdown_mode_segmented_control: Option<ViewHandle<MarkdownToggleView>>,} impl CodeView { fn new_internal(source: CodeSource, ctx: &mut ViewContext<Self>) -> Self { let pane_configuration = ctx.add_model(|_ctx| PaneConfiguration::new("")); let window_id = ctx.window_id(); Self { tab_group: Default::default(), active_tab_index: 0, pane_configuration, focus_handle: None, source, window_id, drag_position: None, markdown_mode_segmented_control: None, } } pub fn new( source: CodeSource, line_col: Option<LineAndColumnArg>, ctx: &mut ViewContext<Self>, ) -> Self { let location = source.location(); let mut view = Self::new_internal(source, ctx); view.open_or_focus_existing(location, line_col, ctx); #[cfg(feature = "local_fs")] { view.update_markdown_mode_segmented_control(ctx); } view } #[cfg(feature = "local_fs")] fn update_markdown_mode_segmented_control(&mut self, ctx: &mut ViewContext<Self>) { let renders_in_notebook_viewer = self .tab_at(self.active_tab_index) .and_then(|t| t.location.as_ref()) .map(|loc| renders_in_warp_notebook_viewer(std::path::Path::new(&loc.display_path()))) .unwrap_or(false); if !renders_in_notebook_viewer { self.markdown_mode_segmented_control = None; ctx.notify(); return; } if self.markdown_mode_segmented_control.is_none() { let handle = ctx.add_typed_action_view(|ctx| { MarkdownToggleView::new(MarkdownDisplayMode::Raw, ctx) }); ctx.subscribe_to_view(&handle, |view, _, event, ctx| { let MarkdownToggleEvent::ModeSelected(mode) = event; match mode { MarkdownDisplayMode::Rendered => { view.handle_action(&CodeViewAction::RenderMarkdown, ctx); } MarkdownDisplayMode::Raw => {} } }); self.markdown_mode_segmented_control = Some(handle); } ctx.notify(); } /// Restore a code view from a persisted multi-tab snapshot. pub fn restore( tabs: &[crate::app_state::CodePaneTabSnapshot], active_tab_index: usize, source: CodeSource, ctx: &mut ViewContext<Self>, ) -> Self { let mut view = Self::new_internal(source, ctx); for tab_snapshot in tabs { let location = tab_snapshot.path.clone().map(LocalOrRemotePath::Local); let tab_data = view.build_tab_data(location, false, ctx); view.tab_group.push(tab_data); } let clamped_index = if view.tab_group.is_empty() { 0 } else { active_tab_index.min(view.tab_group.len() - 1) }; view.active_tab_index = clamped_index; view } /// Create a new "preview" code view for when a user is exploring the file tree. /// There is only one preview active at a time pub fn new_preview(source: CodeSource, ctx: &mut ViewContext<Self>) -> Self { let path = source.path(); let mut view = Self::new_internal(source, ctx); if let Some(path) = path { view.open_in_preview_or_promote(path, ctx); #[cfg(feature = "local_fs")] { view.update_markdown_mode_segmented_control(ctx); } } else { log::warn!("Preview CodeView constructed with no path"); } view } /// If a tab is a preview, promote it and emit "FileOpened" fn promote_if_preview(&mut self, ctx: &mut ViewContext<Self>) { if let Some(tab) = self.tab_group.get_mut(self.active_tab_index) && tab.preview { tab.preview = false; self.set_title_after_content_update(ctx); self.update_tab_bar_state(ctx); self.focus_contents(ctx); send_telemetry_from_ctx!(TelemetryEvent::PreviewPanePromoted, ctx); ctx.notify(); } } /// Construct an editor backed by the global shared buffer for the given location. /// /// For local files, additional features are wired up (selection-as-context, /// find-references, footer). Remote files skip these because LSP and /// related tooling run on the local machine. fn construct_editor_for_location( &mut self, location: LocalOrRemotePath, ctx: &mut ViewContext<Self>, ) -> ViewHandle<LocalCodeEditorView> { let is_local = matches!(location, LocalOrRemotePath::Local(_)); ctx.add_typed_action_view(|ctx| { let mut editor = LocalCodeEditorView::new_with_global_buffer( location, |buffer_state, ctx| { ctx.add_typed_action_view(|ctx| { CodeEditorView::new( None, Some(buffer_state.buffer), CodeEditorRenderOptions::new(VerticalExpansionBehavior::FillMaxHeight), ctx, ) .with_horizontal_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::Auto, true, ), ) }) }, false, None, ctx, ); if is_local { if FeatureFlag::HoaCodeReview.is_enabled() { editor = editor .with_selection_as_context(Box::new(get_context_target_terminal_view)); } let mut editor = editor.with_find_references_provider( ShowFindReferencesCard { editor_window_id: ctx.window_id(), parent_scrollable_position_id: None, }, ctx, ); editor.add_footer(ctx); editor } else { editor } }) } /// Construct an editor for a new (unsaved) file with no file backing. fn construct_new_file_editor( &mut self, ctx: &mut ViewContext<Self>, ) -> ViewHandle<LocalCodeEditorView> { let editor = ctx.add_typed_action_view(|ctx| { CodeEditorView::new( None, None, CodeEditorRenderOptions::new(VerticalExpansionBehavior::FillMaxHeight), ctx, ) .with_horizontal_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::Auto, true, ), ) }); ctx.add_typed_action_view(|ctx| { let mut local_editor = LocalCodeEditorView::new(editor, None, false, None, ctx); if FeatureFlag::HoaCodeReview.is_enabled() { local_editor = local_editor .with_selection_as_context(Box::new(get_context_target_terminal_view)); } local_editor.with_find_references_provider( ShowFindReferencesCard { editor_window_id: ctx.window_id(), parent_scrollable_position_id: None, }, ctx, ) }) } fn build_tab_data( &mut self, location: Option<LocalOrRemotePath>, preview: bool, ctx: &mut ViewContext<Self>, ) -> TabData { let (code_editor, tab_location) = match location { Some(loc) => { let editor = self.construct_editor_for_location(loc.clone(), ctx); (editor, Some(loc)) } None => (self.construct_new_file_editor(ctx), None), }; let editor = code_editor.as_ref(ctx).editor().clone(); ctx.subscribe_to_view(&editor, |me, _, event, ctx| match event { CodeEditorEvent::Focused => { me.promote_if_preview(ctx); ctx.emit(CodeViewEvent::Pane(PaneEvent::FocusSelf)); } CodeEditorEvent::ContentChanged { .. } => { me.set_title_after_content_update(ctx); } _ => {} }); // For new files (CodeSource::New), mark the editor as a new file and set default directory if tab_location.is_none() && matches!(self.source, CodeSource::New { .. }) { let default_directory = self.source.default_directory().cloned(); code_editor.update(ctx, |local_editor, _ctx| { local_editor.set_new_file(true); local_editor.set_default_directory(default_directory); }); } // Bundled skills cannot be edited. if self.source.is_bundled_skill() { editor.update(ctx, |editor, ctx| { editor.set_interaction_state(InteractionState::Selectable, ctx); }); }Lines 1–500 of 2493. Load more
use std::collections::HashMap; use std::path::{Path, PathBuf}; use lsp::LspManagerModel; use pathfinder_color::ColorU; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::vector::vec2f; use warp_core::channel::{Channel, ChannelState}; use warp_core::features::FeatureFlag; use warp_core::ui::appearance::Appearance; use warp_core::ui::icons::ICON_DIMENSIONS; use warp_editor::render::element::VerticalExpansionBehavior; use warp_util::path::LineAndColumnArg; #[cfg(feature = "local_fs")] use warpui::clipboard::ClipboardContent; use warpui::elements::{ AcceptedByDropTarget, Align, Border, ChildAnchor, ChildView, Clipped, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Draggable, DraggableState, DropTarget, Empty, Expanded, Flex, Hoverable, MainAxisAlignment, MainAxisSize, MouseStateHandle, OffsetPositioning, Padding, ParentAnchor, ParentElement, ParentOffsetBounds, Radius, Rect, SavePosition, Shrinkable, Stack, Text, }; use warpui::fonts::{Properties, Style, Weight}; use warpui::keymap::EditableBinding; use warpui::text::point::Point; use warpui::text_layout::ClipConfig; use warpui::ui_components::button::ButtonVariant; use warpui::ui_components::components::UiComponent; use warpui::{ AppContext, Element, Entity, ModelHandle, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle, WindowId, id, }; use super::buffer_location::LocalOrRemotePath; use super::diff_viewer::DiffViewer; use super::editor::view::{CodeEditorEvent, CodeEditorView}; use super::editor_management::{CodeManager, CodeSource}; use super::local_code_editor::{LocalCodeEditorEvent, LocalCodeEditorView}; use crate::code::editor::scroll::ScrollPosition; use crate::code::editor::view::CodeEditorRenderOptions; use crate::code::editor_management::CodeEditorStatus; use crate::code::global_buffer_model::GlobalBufferModel; use crate::code::local_code_editor::ShowFindReferencesCard; use crate::code::{EditorTabBarDropTargetData, ImmediateSaveError, SaveOutcome, SaveStatus}; use crate::editor::InteractionState; use crate::input::Vector2F; use crate::menu::{MenuItem, MenuItemFields}; use crate::notebooks::file::{MarkdownDisplayMode, renders_in_warp_notebook_viewer}; use crate::pane_group::focus_state::PaneFocusHandle; use crate::pane_group::pane::view::header::components::{ CenteredHeaderEdgeWidth, render_pane_header_buttons, render_pane_header_title_text, render_three_column_header, }; use crate::pane_group::pane::view::header::render_pane_header_draggable; use crate::pane_group::pane::{ActionOrigin, PaneHeaderAction, view}; use crate::pane_group::{ BackingView, CodePane, PaneConfiguration, PaneConfigurationEvent, PaneDragDropLocation, PaneEvent, TabBarAxis, }; use crate::quit_warning::UnsavedStateSummary; use crate::search::ItemHighlightState; use crate::search::files::icon::icon_from_file_path; use crate::server::telemetry::CodeContextDestination; use crate::settings::CodeSettings; use crate::tab::TAB_BAR_BORDER_HEIGHT; use crate::terminal::cli_agent::{ build_selection_line_range_prompt, build_selection_substring_prompt, }; use crate::terminal::view::CliAgentRouting; use crate::ui_components::blended_colors; use crate::ui_components::buttons::icon_button; use crate::util::path::{display_name_with_host, display_path_with_host}; use crate::view_components::{DismissibleToast, MarkdownToggleEvent, MarkdownToggleView}; use crate::workspace::util::get_context_target_terminal_view; use crate::workspace::{ActiveSession, TabBarDropTargetData, ToastStack, WorkspaceAction}; use crate::{TelemetryEvent, send_telemetry_from_ctx}; type SaveCallback = Box<dyn FnOnce(SaveOutcome, &mut CodeView, &mut ViewContext<CodeView>) + Send + Sync + 'static>; const CLOSE_BUTTON_WIDTH: f32 = 24.; const LANGUAGE_ICON_WIDTH: f32 = 16.; const TAB_INTERNAL_MARGIN: f32 = 4.; const TAB_HORIZONTAL_MARGIN: f32 = 8.; const TAB_PADDING: f32 = 2.; // Keybinding constants - exported so AI document view can reuse pub const SAVE_FILE_BINDING_NAME: &str = "code_view:save"; pub const SAVE_FILE_BINDING_DESCRIPTION: &str = "Save file"; pub fn init(app: &mut AppContext) { super::editor::view::init(app); super::local_code_editor::init(app); let text_entry = id!("CodeEditorView") & !id!("IMEOpen"); app.register_editable_bindings([ EditableBinding::new( SAVE_FILE_BINDING_NAME, SAVE_FILE_BINDING_DESCRIPTION, CodeViewAction::SaveFile, ) .with_context_predicate(text_entry.clone()) .with_key_binding("cmdorctrl-s"), EditableBinding::new( "code_view:save_as", "Save file as", CodeViewAction::SaveFileAs, ) .with_context_predicate(text_entry.clone()) .with_key_binding("cmdorctrl-shift-S"), EditableBinding::new( "code_view:close_all_tabs", "Close all tabs", CodeViewAction::CloseAll, ) .with_context_predicate(id!("CodeEditorView")) .with_key_binding("cmdorctrl-r w"), EditableBinding::new( "code_view:close_saved_tabs", "Close saved tabs", CodeViewAction::CloseSaved, ) .with_context_predicate(id!("CodeEditorView")) .with_key_binding("cmdorctrl-r u"), ]); } const PADDING: f32 = 4.; pub use crate::util::openable_file_type::is_binary_file; /// Determines the `SavePosition` ID for a draggable tab based on its index. pub fn tab_position_id(index: usize) -> String { format!("file_tab_position_{index}") } #[derive(Debug, Clone)] enum TabBarDragPosition { BeforeTab { index: usize }, AfterTab { index: usize }, } #[derive(Debug, Clone)] pub enum CodeViewAction { SaveFile, SaveFileAs, AcceptPendingDiffsAndSave, RejectPendingDiffs, SetCurrentTabIndex { index: usize, }, RemoveTabAtIndex { index: usize, }, CloseAll, CloseSaved, ToggleMaximized, #[cfg(feature = "local_fs")] CopyFilePath, /// Open the active code tab's file in the platform's file manager /// (Finder on macOS, Explorer on Windows). No-op when the active tab has /// no resolvable local path. #[cfg(feature = "local_fs")] RevealInFinder, #[cfg(feature = "local_fs")] RenderMarkdown, DragOverIndex { target: usize, drag_position: RectF, }, DropAtIndex { origin: usize, target: usize, drag_position: RectF, }, ClearEditorTabGroupDragPositions, ClearWorkspaceTabGroupDragPositions, } #[derive(Debug, Clone)] pub enum CodeViewEvent { Pane(PaneEvent), TabChanged { location: Option<LocalOrRemotePath>, tab_index: usize, }, FileOpened { location: LocalOrRemotePath, tab_index: usize, }, RunTabConfigSkill { path: PathBuf, }, OpenLspLogs { log_path: PathBuf, }, } #[derive(Default, Clone)] struct TabDataMouseStateHandles { tab_handle: MouseStateHandle, close_handle: MouseStateHandle, accept_mouse_state: MouseStateHandle, reject_mouse_state: MouseStateHandle, tab_draggable_state: DraggableState, } #[derive(Clone)] pub struct TabData { location: Option<LocalOrRemotePath>, editor_view: ViewHandle<LocalCodeEditorView>, mouse_state_handles: TabDataMouseStateHandles, preview: bool, } #[derive(Debug, Clone)] pub enum PendingSaveIntent { Save, Discard, Cancel, } impl TabData { /// Returns the file location (local or remote), if any. pub fn location(&self) -> Option<&LocalOrRemotePath> { self.location.as_ref() } /// Returns the local filesystem path, if this tab is backed by a local file. /// Returns `None` for remote files and untitled tabs. pub fn local_path(&self) -> Option<PathBuf> { self.location .as_ref() .and_then(|loc| loc.to_local_path().map(Path::to_path_buf)) } } pub struct CodeView { tab_group: Vec<TabData>, active_tab_index: usize, pane_configuration: ModelHandle<PaneConfiguration>, focus_handle: Option<PaneFocusHandle>, source: CodeSource, window_id: WindowId, drag_position: Option<TabBarDragPosition>, markdown_mode_segmented_control: Option<ViewHandle<MarkdownToggleView>>, } impl CodeView { fn new_internal(source: CodeSource, ctx: &mut ViewContext<Self>) -> Self { let pane_configuration = ctx.add_model(|_ctx| PaneConfiguration::new("")); let window_id = ctx.window_id(); Self { tab_group: Default::default(), active_tab_index: 0, pane_configuration, focus_handle: None, source, window_id, drag_position: None, markdown_mode_segmented_control: None, } } pub fn new( source: CodeSource, line_col: Option<LineAndColumnArg>, ctx: &mut ViewContext<Self>, ) -> Self { let location = source.location(); let mut view = Self::new_internal(source, ctx); view.open_or_focus_existing(location, line_col, ctx); #[cfg(feature = "local_fs")] { view.update_markdown_mode_segmented_control(ctx); } view } #[cfg(feature = "local_fs")] fn update_markdown_mode_segmented_control(&mut self, ctx: &mut ViewContext<Self>) { let renders_in_notebook_viewer = self .tab_at(self.active_tab_index) .and_then(|t| t.location.as_ref()) .map(|loc| renders_in_warp_notebook_viewer(std::path::Path::new(&loc.display_path()))) .unwrap_or(false); if !renders_in_notebook_viewer { self.markdown_mode_segmented_control = None; ctx.notify(); return; } if self.markdown_mode_segmented_control.is_none() { let handle = ctx.add_typed_action_view(|ctx| { MarkdownToggleView::new(MarkdownDisplayMode::Raw, ctx) }); ctx.subscribe_to_view(&handle, |view, _, event, ctx| { let MarkdownToggleEvent::ModeSelected(mode) = event; match mode { MarkdownDisplayMode::Rendered => { view.handle_action(&CodeViewAction::RenderMarkdown, ctx); } MarkdownDisplayMode::Raw => {} } }); self.markdown_mode_segmented_control = Some(handle); } ctx.notify(); } /// Restore a code view from a persisted multi-tab snapshot. pub fn restore( tabs: &[crate::app_state::CodePaneTabSnapshot], active_tab_index: usize, source: CodeSource, ctx: &mut ViewContext<Self>, ) -> Self { let mut view = Self::new_internal(source, ctx); for tab_snapshot in tabs { let location = tab_snapshot.path.clone().map(LocalOrRemotePath::Local); let tab_data = view.build_tab_data(location, false, ctx); view.tab_group.push(tab_data); } let clamped_index = if view.tab_group.is_empty() { 0 } else { active_tab_index.min(view.tab_group.len() - 1) }; view.active_tab_index = clamped_index; view } /// Create a new "preview" code view for when a user is exploring the file tree. /// There is only one preview active at a time pub fn new_preview(source: CodeSource, ctx: &mut ViewContext<Self>) -> Self { let path = source.path(); let mut view = Self::new_internal(source, ctx); if let Some(path) = path { view.open_in_preview_or_promote(path, ctx); #[cfg(feature = "local_fs")] { view.update_markdown_mode_segmented_control(ctx); } } else { log::warn!("Preview CodeView constructed with no path"); } view } /// If a tab is a preview, promote it and emit "FileOpened" fn promote_if_preview(&mut self, ctx: &mut ViewContext<Self>) { if let Some(tab) = self.tab_group.get_mut(self.active_tab_index) && tab.preview { tab.preview = false; self.set_title_after_content_update(ctx); self.update_tab_bar_state(ctx); self.focus_contents(ctx); send_telemetry_from_ctx!(TelemetryEvent::PreviewPanePromoted, ctx); ctx.notify(); } } /// Construct an editor backed by the global shared buffer for the given location. /// /// For local files, additional features are wired up (selection-as-context, /// find-references, footer). Remote files skip these because LSP and /// related tooling run on the local machine. fn construct_editor_for_location( &mut self, location: LocalOrRemotePath, ctx: &mut ViewContext<Self>, ) -> ViewHandle<LocalCodeEditorView> { let is_local = matches!(location, LocalOrRemotePath::Local(_)); ctx.add_typed_action_view(|ctx| { let mut editor = LocalCodeEditorView::new_with_global_buffer( location, |buffer_state, ctx| { ctx.add_typed_action_view(|ctx| { CodeEditorView::new( None, Some(buffer_state.buffer), CodeEditorRenderOptions::new(VerticalExpansionBehavior::FillMaxHeight), ctx, ) .with_horizontal_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::Auto, true, ), ) }) }, false, None, ctx, ); if is_local { if FeatureFlag::HoaCodeReview.is_enabled() { editor = editor .with_selection_as_context(Box::new(get_context_target_terminal_view)); } let mut editor = editor.with_find_references_provider( ShowFindReferencesCard { editor_window_id: ctx.window_id(), parent_scrollable_position_id: None, }, ctx, ); editor.add_footer(ctx); editor } else { editor } }) } /// Construct an editor for a new (unsaved) file with no file backing. fn construct_new_file_editor( &mut self, ctx: &mut ViewContext<Self>, ) -> ViewHandle<LocalCodeEditorView> { let editor = ctx.add_typed_action_view(|ctx| { CodeEditorView::new( None, None, CodeEditorRenderOptions::new(VerticalExpansionBehavior::FillMaxHeight), ctx, ) .with_horizontal_scrollbar_appearance( warpui::elements::new_scrollable::ScrollableAppearance::new( warpui::elements::ScrollbarWidth::Auto, true, ), ) }); ctx.add_typed_action_view(|ctx| { let mut local_editor = LocalCodeEditorView::new(editor, None, false, None, ctx); if FeatureFlag::HoaCodeReview.is_enabled() { local_editor = local_editor .with_selection_as_context(Box::new(get_context_target_terminal_view)); } local_editor.with_find_references_provider( ShowFindReferencesCard { editor_window_id: ctx.window_id(), parent_scrollable_position_id: None, }, ctx, ) }) } fn build_tab_data( &mut self, location: Option<LocalOrRemotePath>, preview: bool, ctx: &mut ViewContext<Self>, ) -> TabData { let (code_editor, tab_location) = match location { Some(loc) => { let editor = self.construct_editor_for_location(loc.clone(), ctx); (editor, Some(loc)) } None => (self.construct_new_file_editor(ctx), None), }; let editor = code_editor.as_ref(ctx).editor().clone(); ctx.subscribe_to_view(&editor, |me, _, event, ctx| match event { CodeEditorEvent::Focused => { me.promote_if_preview(ctx); ctx.emit(CodeViewEvent::Pane(PaneEvent::FocusSelf)); } CodeEditorEvent::ContentChanged { .. } => { me.set_title_after_content_update(ctx); } _ => {} }); // For new files (CodeSource::New), mark the editor as a new file and set default directory if tab_location.is_none() && matches!(self.source, CodeSource::New { .. }) { let default_directory = self.source.default_directory().cloned(); code_editor.update(ctx, |local_editor, _ctx| { local_editor.set_new_file(true); local_editor.set_default_directory(default_directory); }); } // Bundled skills cannot be edited. if self.source.is_bundled_skill() { editor.update(ctx, |editor, ctx| { editor.set_interaction_state(InteractionState::Selectable, ctx); }); }