mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-23 18:32:17 +00:00
Merge branch 'main' into edit-prediction-syntax-highlighting
This commit is contained in:
commit
96ed0f4432
15 changed files with 327 additions and 264 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -16267,7 +16267,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.171.0"
|
||||
version = "0.172.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
|
|
@ -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;
|
||||
|
|
197
crates/assistant/src/assistant_configuration.rs
Normal file
197
crates/assistant/src/assistant_configuration.rs
Normal file
|
@ -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<LanguageModelProviderId, AnyView>,
|
||||
_registry_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> 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<Self>) {
|
||||
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<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let configuration_view = provider.configuration_view(cx);
|
||||
self.configuration_views
|
||||
.insert(provider.id(), configuration_view);
|
||||
}
|
||||
|
||||
fn render_provider_view(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> 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<Self>) -> impl IntoElement {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
let provider_views = providers
|
||||
.into_iter()
|
||||
.map(|provider| self.render_provider_view(&provider, cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<dyn LanguageModelProvider>),
|
||||
}
|
||||
|
||||
impl EventEmitter<ConfigurationViewEvent> 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<SharedString> {
|
||||
Some("Configuration".into())
|
||||
}
|
||||
}
|
|
@ -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<LanguageModelProviderId, AnyView>,
|
||||
_registry_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
fn new(cx: &mut ViewContext<Self>) -> 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<Self>) {
|
||||
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<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let configuration_view = provider.configuration_view(cx);
|
||||
self.configuration_views
|
||||
.insert(provider.id(), configuration_view);
|
||||
}
|
||||
|
||||
fn render_provider_view(
|
||||
&mut self,
|
||||
provider: &Arc<dyn LanguageModelProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> 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<Self>) -> impl IntoElement {
|
||||
let providers = LanguageModelRegistry::read_global(cx).providers();
|
||||
let provider_views = providers
|
||||
.into_iter()
|
||||
.map(|provider| self.render_provider_view(&provider, cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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<dyn LanguageModelProvider>),
|
||||
}
|
||||
|
||||
impl EventEmitter<ConfigurationViewEvent> 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<SharedString> {
|
||||
Some("Configuration".into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -482,11 +482,17 @@ enum InlineCompletionText {
|
|||
Edit(HighlightedEdits),
|
||||
}
|
||||
|
||||
pub(crate) enum EditDisplayMode {
|
||||
TabAccept,
|
||||
DiffPopover,
|
||||
Inline,
|
||||
}
|
||||
|
||||
enum InlineCompletion {
|
||||
Edit {
|
||||
edits: Vec<(Range<Anchor>, String)>,
|
||||
edit_preview: Option<EditPreview>,
|
||||
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<Anchor>, 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
|
||||
}
|
||||
|
|
|
@ -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<Anchor>, 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,
|
||||
|
|
|
@ -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<Buffer>,
|
||||
|
@ -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<Buffer>,
|
||||
|
|
|
@ -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<View<ApplicationMenu>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
git_ui_enabled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
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::<GitUiFeatureFlag, _>({
|
||||
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<Self>,
|
||||
) -> Option<impl IntoElement> {
|
||||
// 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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <hi@zed.dev>"]
|
||||
|
|
|
@ -399,6 +399,7 @@ fn initialize_panels(prompt_builder: Arc<PromptBuilder>, cx: &mut ViewContext<Wo
|
|||
_ = timeout => false,
|
||||
}
|
||||
};
|
||||
|
||||
let git_panel = if git_ui_enabled {
|
||||
Some(git_ui::git_panel::GitPanel::load(workspace_handle.clone(), cx.clone()).await?)
|
||||
} else {
|
||||
|
|
|
@ -1059,6 +1059,10 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
|
|||
true
|
||||
}
|
||||
|
||||
fn show_tab_accept_marker() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_enabled(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
|
|
Loading…
Reference in a new issue