diff --git a/Cargo.lock b/Cargo.lock index 2544a53fc0..3db783c120 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16267,7 +16267,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.171.0" +version = "0.172.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index f01763e876..7c1e5b8406 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -1,5 +1,6 @@ #![cfg_attr(target_os = "windows", allow(unused, dead_code))] +mod assistant_configuration; pub mod assistant_panel; mod inline_assistant; pub mod slash_command_settings; diff --git a/crates/assistant/src/assistant_configuration.rs b/crates/assistant/src/assistant_configuration.rs new file mode 100644 index 0000000000..e4756e1e86 --- /dev/null +++ b/crates/assistant/src/assistant_configuration.rs @@ -0,0 +1,197 @@ +use std::sync::Arc; + +use collections::HashMap; +use gpui::{canvas, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription}; +use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry}; +use ui::{prelude::*, ElevationIndex}; +use workspace::Item; + +pub struct ConfigurationView { + focus_handle: FocusHandle, + configuration_views: HashMap, + _registry_subscription: Subscription, +} + +impl ConfigurationView { + pub fn new(cx: &mut ViewContext) -> Self { + let focus_handle = cx.focus_handle(); + + let registry_subscription = cx.subscribe( + &LanguageModelRegistry::global(cx), + |this, _, event: &language_model::Event, cx| match event { + language_model::Event::AddedProvider(provider_id) => { + let provider = LanguageModelRegistry::read_global(cx).provider(provider_id); + if let Some(provider) = provider { + this.add_configuration_view(&provider, cx); + } + } + language_model::Event::RemovedProvider(provider_id) => { + this.remove_configuration_view(provider_id); + } + _ => {} + }, + ); + + let mut this = Self { + focus_handle, + configuration_views: HashMap::default(), + _registry_subscription: registry_subscription, + }; + this.build_configuration_views(cx); + this + } + + fn build_configuration_views(&mut self, cx: &mut ViewContext) { + let providers = LanguageModelRegistry::read_global(cx).providers(); + for provider in providers { + self.add_configuration_view(&provider, cx); + } + } + + fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { + self.configuration_views.remove(provider_id); + } + + fn add_configuration_view( + &mut self, + provider: &Arc, + cx: &mut ViewContext, + ) { + let configuration_view = provider.configuration_view(cx); + self.configuration_views + .insert(provider.id(), configuration_view); + } + + fn render_provider_view( + &mut self, + provider: &Arc, + cx: &mut ViewContext, + ) -> Div { + let provider_id = provider.id().0.clone(); + let provider_name = provider.name().0.clone(); + let configuration_view = self.configuration_views.get(&provider.id()).cloned(); + + let open_new_context = cx.listener({ + let provider = provider.clone(); + move |_, _, cx| { + cx.emit(ConfigurationViewEvent::NewProviderContextEditor( + provider.clone(), + )) + } + }); + + v_flex() + .gap_2() + .child( + h_flex() + .justify_between() + .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small)) + .when(provider.is_authenticated(cx), move |this| { + this.child( + h_flex().justify_end().child( + Button::new( + SharedString::from(format!("new-context-{provider_id}")), + "Open New Chat", + ) + .icon_position(IconPosition::Start) + .icon(IconName::Plus) + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ModalSurface) + .on_click(open_new_context), + ), + ) + }), + ) + .child( + div() + .p(DynamicSpacing::Base08.rems(cx)) + .bg(cx.theme().colors().surface_background) + .border_1() + .border_color(cx.theme().colors().border_variant) + .rounded_md() + .when(configuration_view.is_none(), |this| { + this.child(div().child(Label::new(format!( + "No configuration view for {}", + provider_name + )))) + }) + .when_some(configuration_view, |this, configuration_view| { + this.child(configuration_view) + }), + ) + } +} + +impl Render for ConfigurationView { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let providers = LanguageModelRegistry::read_global(cx).providers(); + let provider_views = providers + .into_iter() + .map(|provider| self.render_provider_view(&provider, cx)) + .collect::>(); + + let mut element = v_flex() + .id("assistant-configuration-view") + .track_focus(&self.focus_handle(cx)) + .bg(cx.theme().colors().editor_background) + .size_full() + .overflow_y_scroll() + .child( + v_flex() + .p(DynamicSpacing::Base16.rems(cx)) + .border_b_1() + .border_color(cx.theme().colors().border) + .gap_1() + .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium)) + .child( + Label::new( + "At least one LLM provider must be configured to use the Assistant.", + ) + .color(Color::Muted), + ), + ) + .child( + v_flex() + .p(DynamicSpacing::Base16.rems(cx)) + .mt_1() + .gap_6() + .flex_1() + .children(provider_views), + ) + .into_any(); + + // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround + // because we couldn't the element to take up the size of the parent. + canvas( + move |bounds, cx| { + element.prepaint_as_root(bounds.origin, bounds.size.into(), cx); + element + }, + |_, mut element, cx| { + element.paint(cx); + }, + ) + .flex_1() + .w_full() + } +} + +pub enum ConfigurationViewEvent { + NewProviderContextEditor(Arc), +} + +impl EventEmitter for ConfigurationView {} + +impl FocusableView for ConfigurationView { + fn focus_handle(&self, _: &AppContext) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Item for ConfigurationView { + type Event = ConfigurationViewEvent; + + fn tab_content_text(&self, _cx: &WindowContext) -> Option { + Some("Configuration".into()) + } +} diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index ccafd3eca9..ede925a960 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1,3 +1,4 @@ +use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent}; use crate::{ terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, DeployPromptLibrary, InlineAssistant, NewContext, @@ -13,19 +14,15 @@ use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_slash_command::SlashCommandWorkingSet; use assistant_tool::ToolWorkingSet; use client::{proto, Client, Status}; -use collections::HashMap; use editor::{Editor, EditorEvent}; use fs::Fs; use gpui::{ - canvas, div, prelude::*, Action, AnyView, AppContext, AsyncWindowContext, EventEmitter, - ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, - ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, - Task, UpdateGlobal, View, WeakView, + prelude::*, Action, AppContext, AsyncWindowContext, EventEmitter, ExternalPaths, FocusHandle, + FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, Styled, + Subscription, Task, UpdateGlobal, View, WeakView, }; use language::LanguageRegistry; -use language_model::{ - LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID, -}; +use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID}; use language_model_selector::LanguageModelSelector; use project::Project; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary}; @@ -34,12 +31,11 @@ use settings::{update_settings_file, Settings}; use smol::stream::StreamExt; use std::{ops::ControlFlow, path::PathBuf, sync::Arc}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; -use ui::{prelude::*, ContextMenu, ElevationIndex, PopoverMenu, PopoverMenuHandle, Tooltip}; +use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip}; use util::{maybe, ResultExt}; use workspace::DraggedTab; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, - item::Item, pane, DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace, }; use zed_actions::assistant::{InlineAssist, ToggleFocus}; @@ -1371,193 +1367,3 @@ pub enum WorkflowAssistStatus { Done, Idle, } - -pub struct ConfigurationView { - focus_handle: FocusHandle, - configuration_views: HashMap, - _registry_subscription: Subscription, -} - -impl ConfigurationView { - fn new(cx: &mut ViewContext) -> Self { - let focus_handle = cx.focus_handle(); - - let registry_subscription = cx.subscribe( - &LanguageModelRegistry::global(cx), - |this, _, event: &language_model::Event, cx| match event { - language_model::Event::AddedProvider(provider_id) => { - let provider = LanguageModelRegistry::read_global(cx).provider(provider_id); - if let Some(provider) = provider { - this.add_configuration_view(&provider, cx); - } - } - language_model::Event::RemovedProvider(provider_id) => { - this.remove_configuration_view(provider_id); - } - _ => {} - }, - ); - - let mut this = Self { - focus_handle, - configuration_views: HashMap::default(), - _registry_subscription: registry_subscription, - }; - this.build_configuration_views(cx); - this - } - - fn build_configuration_views(&mut self, cx: &mut ViewContext) { - let providers = LanguageModelRegistry::read_global(cx).providers(); - for provider in providers { - self.add_configuration_view(&provider, cx); - } - } - - fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) { - self.configuration_views.remove(provider_id); - } - - fn add_configuration_view( - &mut self, - provider: &Arc, - cx: &mut ViewContext, - ) { - let configuration_view = provider.configuration_view(cx); - self.configuration_views - .insert(provider.id(), configuration_view); - } - - fn render_provider_view( - &mut self, - provider: &Arc, - cx: &mut ViewContext, - ) -> Div { - let provider_id = provider.id().0.clone(); - let provider_name = provider.name().0.clone(); - let configuration_view = self.configuration_views.get(&provider.id()).cloned(); - - let open_new_context = cx.listener({ - let provider = provider.clone(); - move |_, _, cx| { - cx.emit(ConfigurationViewEvent::NewProviderContextEditor( - provider.clone(), - )) - } - }); - - v_flex() - .gap_2() - .child( - h_flex() - .justify_between() - .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small)) - .when(provider.is_authenticated(cx), move |this| { - this.child( - h_flex().justify_end().child( - Button::new( - SharedString::from(format!("new-context-{provider_id}")), - "Open New Chat", - ) - .icon_position(IconPosition::Start) - .icon(IconName::Plus) - .style(ButtonStyle::Filled) - .layer(ElevationIndex::ModalSurface) - .on_click(open_new_context), - ), - ) - }), - ) - .child( - div() - .p(DynamicSpacing::Base08.rems(cx)) - .bg(cx.theme().colors().surface_background) - .border_1() - .border_color(cx.theme().colors().border_variant) - .rounded_md() - .when(configuration_view.is_none(), |this| { - this.child(div().child(Label::new(format!( - "No configuration view for {}", - provider_name - )))) - }) - .when_some(configuration_view, |this, configuration_view| { - this.child(configuration_view) - }), - ) - } -} - -impl Render for ConfigurationView { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let providers = LanguageModelRegistry::read_global(cx).providers(); - let provider_views = providers - .into_iter() - .map(|provider| self.render_provider_view(&provider, cx)) - .collect::>(); - - let mut element = v_flex() - .id("assistant-configuration-view") - .track_focus(&self.focus_handle(cx)) - .bg(cx.theme().colors().editor_background) - .size_full() - .overflow_y_scroll() - .child( - v_flex() - .p(DynamicSpacing::Base16.rems(cx)) - .border_b_1() - .border_color(cx.theme().colors().border) - .gap_1() - .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium)) - .child( - Label::new( - "At least one LLM provider must be configured to use the Assistant.", - ) - .color(Color::Muted), - ), - ) - .child( - v_flex() - .p(DynamicSpacing::Base16.rems(cx)) - .mt_1() - .gap_6() - .flex_1() - .children(provider_views), - ) - .into_any(); - - // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround - // because we couldn't the element to take up the size of the parent. - canvas( - move |bounds, cx| { - element.prepaint_as_root(bounds.origin, bounds.size.into(), cx); - element - }, - |_, mut element, cx| { - element.paint(cx); - }, - ) - .flex_1() - .w_full() - } -} - -pub enum ConfigurationViewEvent { - NewProviderContextEditor(Arc), -} - -impl EventEmitter for ConfigurationView {} - -impl FocusableView for ConfigurationView { - fn focus_handle(&self, _: &AppContext) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl Item for ConfigurationView { - type Event = ConfigurationViewEvent; - - fn tab_content_text(&self, _cx: &WindowContext) -> Option { - Some("Configuration".into()) - } -} diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 1d92243a89..b730b4ef97 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -397,8 +397,12 @@ impl Panel for AssistantPanel { "AssistantPanel2" } - fn position(&self, _cx: &WindowContext) -> DockPosition { - DockPosition::Right + fn position(&self, cx: &WindowContext) -> DockPosition { + match AssistantSettings::get_global(cx).dock { + AssistantDockPosition::Left => DockPosition::Left, + AssistantDockPosition::Bottom => DockPosition::Bottom, + AssistantDockPosition::Right => DockPosition::Right, + } } fn position_is_valid(&self, _: DockPosition) -> bool { diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index fb9db582cc..a9429995d8 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -865,18 +865,22 @@ impl CompletionsMenu { drop(completions); let mut entries = self.entries.borrow_mut(); - if let Some(CompletionEntry::InlineCompletionHint(_)) = entries.first() { + let new_selection = if let Some(CompletionEntry::InlineCompletionHint(_)) = entries.first() + { entries.truncate(1); if inline_completion_was_selected || matches.is_empty() { - self.selected_item = 0; + 0 } else { - self.selected_item = 1; + 1 } } else { entries.truncate(0); - self.selected_item = 0; - } + 0 + }; entries.extend(matches.into_iter().map(CompletionEntry::Match)); + self.selected_item = new_selection; + self.scroll_handle + .scroll_to_item(new_selection, ScrollStrategy::Top); } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 95ac65978b..ede4b37842 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -482,11 +482,17 @@ enum InlineCompletionText { Edit(HighlightedEdits), } +pub(crate) enum EditDisplayMode { + TabAccept, + DiffPopover, + Inline, +} + enum InlineCompletion { Edit { edits: Vec<(Range, String)>, edit_preview: Option, - single_line: bool, + display_mode: EditDisplayMode, }, Move(Anchor), } @@ -4933,13 +4939,23 @@ impl Editor { invalidation_row_range = edit_start_row..edit_end_row; - let single_line = first_edit_start_point.row == last_edit_end_point.row - && !edits.iter().any(|(_, edit)| edit.contains('\n')); + let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) { + if provider.show_tab_accept_marker() + && first_edit_start_point.row == last_edit_end_point.row + && !edits.iter().any(|(_, edit)| edit.contains('\n')) + { + EditDisplayMode::TabAccept + } else { + EditDisplayMode::Inline + } + } else { + EditDisplayMode::DiffPopover + }; completion = InlineCompletion::Edit { edits, - single_line, edit_preview: inline_completion.edit_preview, + display_mode, }; }; @@ -4984,7 +5000,7 @@ impl Editor { InlineCompletion::Edit { edits, edit_preview, - single_line: _, + display_mode: _, } => edit_preview .as_ref() .and_then(|edit_preview| { @@ -15225,3 +15241,31 @@ pub struct KillRing(ClipboardItem); impl Global for KillRing {} const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); + +fn all_edits_insertions_or_deletions( + edits: &Vec<(Range, String)>, + snapshot: &MultiBufferSnapshot, +) -> bool { + let mut all_insertions = true; + let mut all_deletions = true; + + for (range, new_text) in edits.iter() { + let range_is_empty = range.to_offset(&snapshot).is_empty(); + let text_is_empty = new_text.is_empty(); + + if range_is_empty != text_is_empty { + if range_is_empty { + all_deletions = false; + } else { + all_insertions = false; + } + } else { + return false; + } + + if !all_insertions && !all_deletions { + return false; + } + } + all_insertions || all_deletions +} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4cfc09248c..6549bcf6a4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -18,12 +18,13 @@ use crate::{ mouse_context_menu::{self, MenuPosition, MouseContextMenu}, scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair}, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow, - DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, - EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, - HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown, - LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, - SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, - GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, + DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode, + EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, + HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, + LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, + Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, + FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, }; use client::ParticipantIndex; use collections::{BTreeMap, HashMap, HashSet}; @@ -50,7 +51,7 @@ use language::{ use lsp::DiagnosticSeverity; use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint, - MultiBufferRow, MultiBufferSnapshot, ToOffset, + MultiBufferRow, ToOffset, }; use project::project_settings::{GitGutterSetting, ProjectSettings}; use settings::Settings; @@ -1628,7 +1629,8 @@ impl EditorElement { if let Some(inline_completion) = editor.active_inline_completion.as_ref() { match &inline_completion.completion { InlineCompletion::Edit { - single_line: true, .. + display_mode: EditDisplayMode::TabAccept, + .. } => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS, _ => {} } @@ -3389,7 +3391,7 @@ impl EditorElement { InlineCompletion::Edit { edits, edit_preview, - single_line, + display_mode, } => { if self.editor.read(cx).has_active_completions_menu() { return None; @@ -3414,8 +3416,8 @@ impl EditorElement { return None; } - if all_edits_insertions_or_deletions(edits, &editor_snapshot.buffer_snapshot) { - if *single_line { + match display_mode { + EditDisplayMode::TabAccept => { let range = &edits.first()?.0; let target_display_point = range.end.to_display_point(editor_snapshot); @@ -3437,8 +3439,8 @@ impl EditorElement { return Some(element); } - - return None; + EditDisplayMode::Inline => return None, + EditDisplayMode::DiffPopover => {} } let highlighted_edits = edit_preview.as_ref().and_then(|edit_preview| { @@ -5250,34 +5252,6 @@ fn inline_completion_tab_indicator( .into_any() } -fn all_edits_insertions_or_deletions( - edits: &Vec<(Range, String)>, - snapshot: &MultiBufferSnapshot, -) -> bool { - let mut all_insertions = true; - let mut all_deletions = true; - - for (range, new_text) in edits.iter() { - let range_is_empty = range.to_offset(&snapshot).is_empty(); - let text_is_empty = new_text.is_empty(); - - if range_is_empty != text_is_empty { - if range_is_empty { - all_deletions = false; - } else { - all_insertions = false; - } - } else { - return false; - } - - if !all_insertions && !all_deletions { - return false; - } - } - all_insertions || all_deletions -} - #[allow(clippy::too_many_arguments)] fn prepaint_gutter_button( button: IconButton, diff --git a/crates/inline_completion/src/inline_completion.rs b/crates/inline_completion/src/inline_completion.rs index 9a6ea346b9..be71dcf76e 100644 --- a/crates/inline_completion/src/inline_completion.rs +++ b/crates/inline_completion/src/inline_completion.rs @@ -23,6 +23,9 @@ pub trait InlineCompletionProvider: 'static + Sized { fn display_name() -> &'static str; fn show_completions_in_menu() -> bool; fn show_completions_in_normal_mode() -> bool; + fn show_tab_accept_marker() -> bool { + false + } fn is_enabled( &self, buffer: &Model, @@ -68,6 +71,7 @@ pub trait InlineCompletionProviderHandle { ) -> bool; fn show_completions_in_menu(&self) -> bool; fn show_completions_in_normal_mode(&self) -> bool; + fn show_tab_accept_marker(&self) -> bool; fn needs_terms_acceptance(&self, cx: &AppContext) -> bool; fn is_refreshing(&self, cx: &AppContext) -> bool; fn refresh( @@ -114,6 +118,10 @@ where T::show_completions_in_normal_mode() } + fn show_tab_accept_marker(&self) -> bool { + T::show_tab_accept_marker() + } + fn is_enabled( &self, buffer: &Model, diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 03442dd5b7..696c44bd57 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -15,7 +15,7 @@ use crate::platforms::{platform_linux, platform_mac, platform_windows}; use auto_update::AutoUpdateStatus; use call::ActiveCall; use client::{Client, UserStore}; -use feature_flags::{FeatureFlagAppExt, ZedPro}; +use feature_flags::{FeatureFlagAppExt, GitUiFeatureFlag, ZedPro}; use git_ui::repository_selector::RepositorySelector; use git_ui::repository_selector::RepositorySelectorPopoverMenu; use gpui::{ @@ -27,6 +27,7 @@ use project::Project; use rpc::proto; use settings::Settings as _; use smallvec::SmallVec; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use theme::ActiveTheme; use ui::{ @@ -43,7 +44,7 @@ pub use stories::*; const MAX_PROJECT_NAME_LENGTH: usize = 40; const MAX_BRANCH_NAME_LENGTH: usize = 40; -const BOOK_ONBOARDING: &str = "https://dub.sh/zed-onboarding"; +const BOOK_ONBOARDING: &str = "https://dub.sh/zed-c-onboarding"; actions!( collab, @@ -108,6 +109,7 @@ pub struct TitleBar { should_move: bool, application_menu: Option>, _subscriptions: Vec, + git_ui_enabled: Arc, } impl Render for TitleBar { @@ -289,6 +291,14 @@ impl TitleBar { subscriptions.push(cx.observe_window_activation(Self::window_activation_changed)); subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify())); + let is_git_ui_enabled = Arc::new(AtomicBool::new(false)); + subscriptions.push(cx.observe_flag::({ + let is_git_ui_enabled = is_git_ui_enabled.clone(); + move |enabled, _cx| { + is_git_ui_enabled.store(enabled, Ordering::SeqCst); + } + })); + Self { platform_style, content: div().id(id.into()), @@ -301,6 +311,7 @@ impl TitleBar { user_store, client, _subscriptions: subscriptions, + git_ui_enabled: is_git_ui_enabled, } } @@ -484,9 +495,14 @@ impl TitleBar { &self, cx: &mut ViewContext, ) -> Option { - // TODO what to render if no active repository? + if !self.git_ui_enabled.load(Ordering::SeqCst) { + return None; + } + let active_repository = self.project.read(cx).active_repository(cx)?; let display_name = active_repository.display_name(self.project.read(cx), cx); + + // TODO: what to render if no active repository? Some(RepositorySelectorPopoverMenu::new( self.repository_selector.clone(), ButtonLike::new("active-repository") diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index cf90d03a7d..6539aab09f 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -2043,17 +2043,21 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint } } - if open_range.start >= offset && line_range.contains(&open_range.start) { - let distance = open_range.start - offset; + if (open_range.contains(&offset) || open_range.start >= offset) + && line_range.contains(&open_range.start) + { + let distance = open_range.start.saturating_sub(offset); if distance < closest_distance { - closest_pair_destination = Some(close_range.end - 1); + closest_pair_destination = Some(close_range.start); closest_distance = distance; continue; } } - if close_range.start >= offset && line_range.contains(&close_range.start) { - let distance = close_range.start - offset; + if (close_range.contains(&offset) || close_range.start >= offset) + && line_range.contains(&close_range.start) + { + let distance = close_range.start.saturating_sub(offset); if distance < closest_distance { closest_pair_destination = Some(open_range.start); closest_distance = distance; diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index cf4886e1eb..40fe3cdea1 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -26,7 +26,7 @@ actions!(welcome, [ResetHints]); pub const FIRST_OPEN: &str = "first_open"; pub const DOCS_URL: &str = "https://zed.dev/docs/"; -const BOOK_ONBOARDING: &str = "https://dub.sh/zed-onboarding"; +const BOOK_ONBOARDING: &str = "https://dub.sh/zed-c-onboarding"; pub fn init(cx: &mut AppContext) { BaseKeymap::register(cx); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index fff53ca0e7..652fb8f3a9 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -2,7 +2,7 @@ description = "The fast, collaborative code editor." edition.workspace = true name = "zed" -version = "0.171.0" +version = "0.172.0" publish.workspace = true license = "GPL-3.0-or-later" authors = ["Zed Team "] diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 16504c3077..30ccb2afd1 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -399,6 +399,7 @@ fn initialize_panels(prompt_builder: Arc, cx: &mut ViewContext false, } }; + let git_panel = if git_ui_enabled { Some(git_ui::git_panel::GitPanel::load(workspace_handle.clone(), cx.clone()).await?) } else { diff --git a/crates/zeta/src/zeta.rs b/crates/zeta/src/zeta.rs index 06b6fc0cd6..9c3d1b94dd 100644 --- a/crates/zeta/src/zeta.rs +++ b/crates/zeta/src/zeta.rs @@ -1059,6 +1059,10 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide true } + fn show_tab_accept_marker() -> bool { + true + } + fn is_enabled( &self, buffer: &Model,