Compare commits
12 commits
main
...
v0.197.1-p
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f6f7762f32 | ||
![]() |
c015ef64dc | ||
![]() |
d3b2f604a9 | ||
![]() |
b8849d83e6 | ||
![]() |
77dda2eca8 | ||
![]() |
ece9dd2c43 | ||
![]() |
c60f37a044 | ||
![]() |
ca646e2951 | ||
![]() |
b5433a9a54 | ||
![]() |
4727ae35d2 | ||
![]() |
d61db1fae7 | ||
![]() |
45d211a555 |
20 changed files with 549 additions and 416 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -20170,7 +20170,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.197.0"
|
version = "0.197.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"agent",
|
"agent",
|
||||||
|
|
|
@ -41,6 +41,9 @@ use std::{
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
|
pub static ZED_STATELESS: std::sync::LazyLock<bool> =
|
||||||
|
std::sync::LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()));
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
#[serde(rename = "json")]
|
#[serde(rename = "json")]
|
||||||
|
@ -874,7 +877,11 @@ impl ThreadsDatabase {
|
||||||
|
|
||||||
let needs_migration_from_heed = mdb_path.exists();
|
let needs_migration_from_heed = mdb_path.exists();
|
||||||
|
|
||||||
let connection = Connection::open_file(&sqlite_path.to_string_lossy());
|
let connection = if *ZED_STATELESS {
|
||||||
|
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
|
||||||
|
} else {
|
||||||
|
Connection::open_file(&sqlite_path.to_string_lossy())
|
||||||
|
};
|
||||||
|
|
||||||
connection.exec(indoc! {"
|
connection.exec(indoc! {"
|
||||||
CREATE TABLE IF NOT EXISTS threads (
|
CREATE TABLE IF NOT EXISTS threads (
|
||||||
|
|
|
@ -185,6 +185,13 @@ impl AgentConfiguration {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let is_signed_in = self
|
||||||
|
.workspace
|
||||||
|
.read_with(cx, |workspace, _| {
|
||||||
|
workspace.client().status().borrow().is_connected()
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.when(is_expanded, |this| this.mb_2())
|
.when(is_expanded, |this| this.mb_2())
|
||||||
.child(
|
.child(
|
||||||
|
@ -230,8 +237,8 @@ impl AgentConfiguration {
|
||||||
.size(LabelSize::Large),
|
.size(LabelSize::Large),
|
||||||
)
|
)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if is_zed_provider {
|
if is_zed_provider && is_signed_in {
|
||||||
this.gap_2().child(
|
this.child(
|
||||||
self.render_zed_plan_info(current_plan, cx),
|
self.render_zed_plan_info(current_plan, cx),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -564,6 +564,17 @@ impl AgentPanel {
|
||||||
let inline_assist_context_store =
|
let inline_assist_context_store =
|
||||||
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
|
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
|
||||||
|
|
||||||
|
let thread_id = thread.read(cx).id().clone();
|
||||||
|
|
||||||
|
let history_store = cx.new(|cx| {
|
||||||
|
HistoryStore::new(
|
||||||
|
thread_store.clone(),
|
||||||
|
context_store.clone(),
|
||||||
|
[HistoryEntryId::Thread(thread_id)],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
|
@ -573,22 +584,13 @@ impl AgentPanel {
|
||||||
prompt_store.clone(),
|
prompt_store.clone(),
|
||||||
thread_store.downgrade(),
|
thread_store.downgrade(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
|
Some(history_store.downgrade()),
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let thread_id = thread.read(cx).id().clone();
|
|
||||||
let history_store = cx.new(|cx| {
|
|
||||||
HistoryStore::new(
|
|
||||||
thread_store.clone(),
|
|
||||||
context_store.clone(),
|
|
||||||
[HistoryEntryId::Thread(thread_id)],
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
|
cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
let active_thread = cx.new(|cx| {
|
let active_thread = cx.new(|cx| {
|
||||||
|
@ -851,6 +853,7 @@ impl AgentPanel {
|
||||||
self.prompt_store.clone(),
|
self.prompt_store.clone(),
|
||||||
self.thread_store.downgrade(),
|
self.thread_store.downgrade(),
|
||||||
self.context_store.downgrade(),
|
self.context_store.downgrade(),
|
||||||
|
Some(self.history_store.downgrade()),
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -1124,6 +1127,7 @@ impl AgentPanel {
|
||||||
self.prompt_store.clone(),
|
self.prompt_store.clone(),
|
||||||
self.thread_store.downgrade(),
|
self.thread_store.downgrade(),
|
||||||
self.context_store.downgrade(),
|
self.context_store.downgrade(),
|
||||||
|
Some(self.history_store.downgrade()),
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -1901,85 +1905,96 @@ impl AgentPanel {
|
||||||
)
|
)
|
||||||
.anchor(Corner::TopRight)
|
.anchor(Corner::TopRight)
|
||||||
.with_handle(self.new_thread_menu_handle.clone())
|
.with_handle(self.new_thread_menu_handle.clone())
|
||||||
.menu(move |window, cx| {
|
.menu({
|
||||||
let active_thread = active_thread.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
move |window, cx| {
|
||||||
menu = menu
|
let active_thread = active_thread.clone();
|
||||||
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
|
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
|
||||||
this.header("Zed Agent")
|
menu = menu
|
||||||
})
|
.context(focus_handle.clone())
|
||||||
.item(
|
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
|
||||||
ContextMenuEntry::new("New Thread")
|
this.header("Zed Agent")
|
||||||
.icon(IconName::NewThread)
|
})
|
||||||
.icon_color(Color::Muted)
|
.item(
|
||||||
.handler(move |window, cx| {
|
ContextMenuEntry::new("New Thread")
|
||||||
window.dispatch_action(NewThread::default().boxed_clone(), cx);
|
.icon(IconName::NewThread)
|
||||||
}),
|
.icon_color(Color::Muted)
|
||||||
)
|
.action(NewThread::default().boxed_clone())
|
||||||
.item(
|
.handler(move |window, cx| {
|
||||||
ContextMenuEntry::new("New Text Thread")
|
window.dispatch_action(
|
||||||
.icon(IconName::NewTextThread)
|
NewThread::default().boxed_clone(),
|
||||||
.icon_color(Color::Muted)
|
cx,
|
||||||
.handler(move |window, cx| {
|
);
|
||||||
window.dispatch_action(NewTextThread.boxed_clone(), cx);
|
}),
|
||||||
}),
|
)
|
||||||
)
|
.item(
|
||||||
.when_some(active_thread, |this, active_thread| {
|
ContextMenuEntry::new("New Text Thread")
|
||||||
let thread = active_thread.read(cx);
|
.icon(IconName::NewTextThread)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.action(NewTextThread.boxed_clone())
|
||||||
|
.handler(move |window, cx| {
|
||||||
|
window.dispatch_action(NewTextThread.boxed_clone(), cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.when_some(active_thread, |this, active_thread| {
|
||||||
|
let thread = active_thread.read(cx);
|
||||||
|
|
||||||
if !thread.is_empty() {
|
if !thread.is_empty() {
|
||||||
let thread_id = thread.id().clone();
|
let thread_id = thread.id().clone();
|
||||||
this.item(
|
this.item(
|
||||||
ContextMenuEntry::new("New From Summary")
|
ContextMenuEntry::new("New From Summary")
|
||||||
.icon(IconName::NewFromSummary)
|
.icon(IconName::NewFromSummary)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.handler(move |window, cx| {
|
.handler(move |window, cx| {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
Box::new(NewThread {
|
Box::new(NewThread {
|
||||||
from_thread_id: Some(thread_id.clone()),
|
from_thread_id: Some(thread_id.clone()),
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
|
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
|
||||||
this.separator()
|
this.separator()
|
||||||
.header("External Agents")
|
.header("External Agents")
|
||||||
.item(
|
.item(
|
||||||
ContextMenuEntry::new("New Gemini Thread")
|
ContextMenuEntry::new("New Gemini Thread")
|
||||||
.icon(IconName::AiGemini)
|
.icon(IconName::AiGemini)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.handler(move |window, cx| {
|
.handler(move |window, cx| {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
NewExternalAgentThread {
|
NewExternalAgentThread {
|
||||||
agent: Some(crate::ExternalAgent::Gemini),
|
agent: Some(crate::ExternalAgent::Gemini),
|
||||||
}
|
}
|
||||||
.boxed_clone(),
|
.boxed_clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.item(
|
.item(
|
||||||
ContextMenuEntry::new("New Claude Code Thread")
|
ContextMenuEntry::new("New Claude Code Thread")
|
||||||
.icon(IconName::AiClaude)
|
.icon(IconName::AiClaude)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.handler(move |window, cx| {
|
.handler(move |window, cx| {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
NewExternalAgentThread {
|
NewExternalAgentThread {
|
||||||
agent: Some(crate::ExternalAgent::ClaudeCode),
|
agent: Some(
|
||||||
}
|
crate::ExternalAgent::ClaudeCode,
|
||||||
.boxed_clone(),
|
),
|
||||||
cx,
|
}
|
||||||
);
|
.boxed_clone(),
|
||||||
}),
|
cx,
|
||||||
)
|
);
|
||||||
});
|
}),
|
||||||
menu
|
)
|
||||||
}))
|
});
|
||||||
|
menu
|
||||||
|
}))
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let agent_panel_menu = PopoverMenu::new("agent-options-menu")
|
let agent_panel_menu = PopoverMenu::new("agent-options-menu")
|
||||||
|
@ -2272,20 +2287,21 @@ impl AgentPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self.active_view {
|
match &self.active_view {
|
||||||
ActiveView::Thread { thread, .. } => thread
|
ActiveView::Thread { .. } | ActiveView::TextThread { .. } => {
|
||||||
.read(cx)
|
let history_is_empty = self
|
||||||
.thread()
|
.history_store
|
||||||
.read(cx)
|
.update(cx, |store, cx| store.recent_entries(1, cx).is_empty());
|
||||||
.configured_model()
|
|
||||||
.map_or(true, |model| {
|
let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
|
||||||
model.provider.id() == language_model::ZED_CLOUD_PROVIDER_ID
|
.providers()
|
||||||
}),
|
.iter()
|
||||||
ActiveView::TextThread { .. } => LanguageModelRegistry::global(cx)
|
.any(|provider| {
|
||||||
.read(cx)
|
provider.is_authenticated(cx)
|
||||||
.default_model()
|
&& provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
|
||||||
.map_or(true, |model| {
|
});
|
||||||
model.provider.id() == language_model::ZED_CLOUD_PROVIDER_ID
|
|
||||||
}),
|
history_is_empty || !has_configured_non_zed_providers
|
||||||
|
}
|
||||||
ActiveView::ExternalAgentThread { .. }
|
ActiveView::ExternalAgentThread { .. }
|
||||||
| ActiveView::History
|
| ActiveView::History
|
||||||
| ActiveView::Configuration => false,
|
| ActiveView::Configuration => false,
|
||||||
|
@ -2306,9 +2322,8 @@ impl AgentPanel {
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
div()
|
div()
|
||||||
.size_full()
|
|
||||||
.when(thread_view, |this| {
|
.when(thread_view, |this| {
|
||||||
this.bg(cx.theme().colors().panel_background)
|
this.size_full().bg(cx.theme().colors().panel_background)
|
||||||
})
|
})
|
||||||
.when(text_thread_view, |this| {
|
.when(text_thread_view, |this| {
|
||||||
this.bg(cx.theme().colors().editor_background)
|
this.bg(cx.theme().colors().editor_background)
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::ui::{
|
||||||
MaxModeTooltip,
|
MaxModeTooltip,
|
||||||
preview::{AgentPreview, UsageCallout},
|
preview::{AgentPreview, UsageCallout},
|
||||||
};
|
};
|
||||||
|
use agent::history_store::HistoryStore;
|
||||||
use agent::{
|
use agent::{
|
||||||
context::{AgentContextKey, ContextLoadResult, load_context},
|
context::{AgentContextKey, ContextLoadResult, load_context},
|
||||||
context_store::ContextStoreEvent,
|
context_store::ContextStoreEvent,
|
||||||
|
@ -29,8 +30,9 @@ use fs::Fs;
|
||||||
use futures::future::Shared;
|
use futures::future::Shared;
|
||||||
use futures::{FutureExt as _, future};
|
use futures::{FutureExt as _, future};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Animation, AnimationExt, App, Entity, EventEmitter, Focusable, KeyContext, Subscription, Task,
|
Animation, AnimationExt, App, Entity, EventEmitter, Focusable, IntoElement, KeyContext,
|
||||||
TextStyle, WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
|
Subscription, Task, TextStyle, WeakEntity, linear_color_stop, linear_gradient, point,
|
||||||
|
pulsating_between,
|
||||||
};
|
};
|
||||||
use language::{Buffer, Language, Point};
|
use language::{Buffer, Language, Point};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
|
@ -80,6 +82,7 @@ pub struct MessageEditor {
|
||||||
user_store: Entity<UserStore>,
|
user_store: Entity<UserStore>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
|
history_store: Option<WeakEntity<HistoryStore>>,
|
||||||
context_strip: Entity<ContextStrip>,
|
context_strip: Entity<ContextStrip>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
model_selector: Entity<AgentModelSelector>,
|
model_selector: Entity<AgentModelSelector>,
|
||||||
|
@ -161,6 +164,7 @@ impl MessageEditor {
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
text_thread_store: WeakEntity<TextThreadStore>,
|
text_thread_store: WeakEntity<TextThreadStore>,
|
||||||
|
history_store: Option<WeakEntity<HistoryStore>>,
|
||||||
thread: Entity<Thread>,
|
thread: Entity<Thread>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -233,6 +237,7 @@ impl MessageEditor {
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
prompt_store,
|
prompt_store,
|
||||||
|
history_store,
|
||||||
context_strip,
|
context_strip,
|
||||||
context_picker_menu_handle,
|
context_picker_menu_handle,
|
||||||
load_context_task: None,
|
load_context_task: None,
|
||||||
|
@ -625,7 +630,7 @@ impl MessageEditor {
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
IconButton::new("follow-agent", IconName::Crosshair)
|
IconButton::new("follow-agent", IconName::Crosshair)
|
||||||
.disabled(is_model_selected)
|
.disabled(!is_model_selected)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.toggle_state(following)
|
.toggle_state(following)
|
||||||
|
@ -1661,32 +1666,36 @@ impl Render for MessageEditor {
|
||||||
|
|
||||||
let line_height = TextSize::Small.rems(cx).to_pixels(window.rem_size()) * 1.5;
|
let line_height = TextSize::Small.rems(cx).to_pixels(window.rem_size()) * 1.5;
|
||||||
|
|
||||||
let in_pro_trial = matches!(
|
let has_configured_providers = LanguageModelRegistry::read_global(cx)
|
||||||
self.user_store.read(cx).current_plan(),
|
.providers()
|
||||||
Some(proto::Plan::ZedProTrial)
|
.iter()
|
||||||
);
|
.filter(|provider| {
|
||||||
|
provider.is_authenticated(cx) && provider.id() != ZED_CLOUD_PROVIDER_ID
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
> 0;
|
||||||
|
|
||||||
let pro_user = matches!(
|
let is_signed_out = self
|
||||||
self.user_store.read(cx).current_plan(),
|
.workspace
|
||||||
Some(proto::Plan::ZedPro)
|
.read_with(cx, |workspace, _| {
|
||||||
);
|
workspace.client().status().borrow().is_signed_out()
|
||||||
|
})
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
let configured_providers: Vec<(IconName, SharedString)> =
|
let has_history = self
|
||||||
LanguageModelRegistry::read_global(cx)
|
.history_store
|
||||||
.providers()
|
.as_ref()
|
||||||
.iter()
|
.and_then(|hs| hs.update(cx, |hs, cx| hs.entries(cx).len() > 0).ok())
|
||||||
.filter(|provider| {
|
.unwrap_or(false)
|
||||||
provider.is_authenticated(cx) && provider.id() != ZED_CLOUD_PROVIDER_ID
|
|| self
|
||||||
})
|
.thread
|
||||||
.map(|provider| (provider.icon(), provider.name().0.clone()))
|
.read_with(cx, |thread, _| thread.messages().len() > 0);
|
||||||
.collect();
|
|
||||||
let has_existing_providers = configured_providers.len() > 0;
|
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.bg(cx.theme().colors().panel_background)
|
.bg(cx.theme().colors().panel_background)
|
||||||
.when(
|
.when(
|
||||||
has_existing_providers && !in_pro_trial && !pro_user,
|
!has_history && is_signed_out && has_configured_providers,
|
||||||
|this| this.child(cx.new(ApiKeysWithProviders::new)),
|
|this| this.child(cx.new(ApiKeysWithProviders::new)),
|
||||||
)
|
)
|
||||||
.when(changed_buffers.len() > 0, |parent| {
|
.when(changed_buffers.len() > 0, |parent| {
|
||||||
|
@ -1778,6 +1787,7 @@ impl AgentPreview for MessageEditor {
|
||||||
None,
|
None,
|
||||||
thread_store.downgrade(),
|
thread_store.downgrade(),
|
||||||
text_thread_store.downgrade(),
|
text_thread_store.downgrade(),
|
||||||
|
None,
|
||||||
thread,
|
thread,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -5,7 +5,6 @@ mod end_trial_upsell;
|
||||||
mod new_thread_button;
|
mod new_thread_button;
|
||||||
mod onboarding_modal;
|
mod onboarding_modal;
|
||||||
pub mod preview;
|
pub mod preview;
|
||||||
mod upsell;
|
|
||||||
|
|
||||||
pub use agent_notification::*;
|
pub use agent_notification::*;
|
||||||
pub use burn_mode_tooltip::*;
|
pub use burn_mode_tooltip::*;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||||
use ai_onboarding::{AgentPanelOnboardingCard, BulletItem};
|
use ai_onboarding::{AgentPanelOnboardingCard, BulletItem};
|
||||||
use client::zed_urls;
|
use client::zed_urls;
|
||||||
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
|
||||||
use ui::{Divider, List, prelude::*};
|
use ui::{Divider, List, Tooltip, prelude::*};
|
||||||
|
|
||||||
#[derive(IntoElement, RegisterComponent)]
|
#[derive(IntoElement, RegisterComponent)]
|
||||||
pub struct EndTrialUpsell {
|
pub struct EndTrialUpsell {
|
||||||
|
@ -33,14 +33,19 @@ impl RenderOnce for EndTrialUpsell {
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
List::new()
|
List::new()
|
||||||
.child(BulletItem::new("500 prompts per month with Claude models"))
|
.child(BulletItem::new("500 prompts with Claude models"))
|
||||||
.child(BulletItem::new("Unlimited edit predictions")),
|
.child(BulletItem::new(
|
||||||
|
"Unlimited edit predictions with Zeta, our open-source model",
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("cta-button", "Upgrade to Zed Pro")
|
Button::new("cta-button", "Upgrade to Zed Pro")
|
||||||
.full_width()
|
.full_width()
|
||||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
.on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))),
|
.on_click(move |_, _window, cx| {
|
||||||
|
telemetry::event!("Upgrade To Pro Clicked", state = "end-of-trial");
|
||||||
|
cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let free_section = v_flex()
|
let free_section = v_flex()
|
||||||
|
@ -55,37 +60,43 @@ impl RenderOnce for EndTrialUpsell {
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.buffer_font(cx),
|
.buffer_font(cx),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
Label::new("(Current Plan)")
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Custom(cx.theme().colors().text_muted.opacity(0.6)))
|
||||||
|
.buffer_font(cx),
|
||||||
|
)
|
||||||
.child(Divider::horizontal()),
|
.child(Divider::horizontal()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
List::new()
|
List::new()
|
||||||
.child(BulletItem::new(
|
.child(BulletItem::new("50 prompts with the Claude models"))
|
||||||
"50 prompts per month with the Claude models",
|
.child(BulletItem::new("2,000 accepted edit predictions")),
|
||||||
))
|
|
||||||
.child(BulletItem::new(
|
|
||||||
"2000 accepted edit predictions using our open-source Zeta model",
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("dismiss-button", "Stay on Free")
|
|
||||||
.full_width()
|
|
||||||
.style(ButtonStyle::Outlined)
|
|
||||||
.on_click({
|
|
||||||
let callback = self.dismiss_upsell.clone();
|
|
||||||
move |_, window, cx| callback(window, cx)
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
AgentPanelOnboardingCard::new()
|
AgentPanelOnboardingCard::new()
|
||||||
.child(Headline::new("Your Zed Pro trial has expired."))
|
.child(Headline::new("Your Zed Pro Trial has expired"))
|
||||||
.child(
|
.child(
|
||||||
Label::new("You've been automatically reset to the Free plan.")
|
Label::new("You've been automatically reset to the Free plan.")
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.mb_1(),
|
.mb_2(),
|
||||||
)
|
)
|
||||||
.child(pro_section)
|
.child(pro_section)
|
||||||
.child(free_section)
|
.child(free_section)
|
||||||
|
.child(
|
||||||
|
h_flex().absolute().top_4().right_4().child(
|
||||||
|
IconButton::new("dismiss_onboarding", IconName::Close)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.tooltip(Tooltip::text("Dismiss"))
|
||||||
|
.on_click({
|
||||||
|
let callback = self.dismiss_upsell.clone();
|
||||||
|
move |_, window, cx| {
|
||||||
|
telemetry::event!("Banner Dismissed", source = "AI Onboarding");
|
||||||
|
callback(window, cx)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,163 +0,0 @@
|
||||||
use component::{Component, ComponentScope, single_example};
|
|
||||||
use gpui::{
|
|
||||||
AnyElement, App, ClickEvent, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
|
|
||||||
Window,
|
|
||||||
};
|
|
||||||
use theme::ActiveTheme;
|
|
||||||
use ui::{
|
|
||||||
Button, ButtonCommon, ButtonStyle, Checkbox, Clickable, Color, Label, LabelCommon,
|
|
||||||
RegisterComponent, ToggleState, h_flex, v_flex,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A component that displays an upsell message with a call-to-action button
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// let upsell = Upsell::new(
|
|
||||||
/// "Upgrade to Zed Pro",
|
|
||||||
/// "Get access to advanced AI features and more",
|
|
||||||
/// "Upgrade Now",
|
|
||||||
/// Box::new(|_, _window, cx| {
|
|
||||||
/// cx.open_url("https://zed.dev/pricing");
|
|
||||||
/// }),
|
|
||||||
/// Box::new(|_, _window, cx| {
|
|
||||||
/// // Handle dismiss
|
|
||||||
/// }),
|
|
||||||
/// Box::new(|checked, window, cx| {
|
|
||||||
/// // Handle don't show again
|
|
||||||
/// }),
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
#[derive(IntoElement, RegisterComponent)]
|
|
||||||
pub struct Upsell {
|
|
||||||
title: SharedString,
|
|
||||||
message: SharedString,
|
|
||||||
cta_text: SharedString,
|
|
||||||
on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
|
||||||
on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
|
||||||
on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Upsell {
|
|
||||||
/// Create a new upsell component
|
|
||||||
pub fn new(
|
|
||||||
title: impl Into<SharedString>,
|
|
||||||
message: impl Into<SharedString>,
|
|
||||||
cta_text: impl Into<SharedString>,
|
|
||||||
on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
|
||||||
on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
|
||||||
on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
title: title.into(),
|
|
||||||
message: message.into(),
|
|
||||||
cta_text: cta_text.into(),
|
|
||||||
on_click,
|
|
||||||
on_dismiss,
|
|
||||||
on_dont_show_again,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for Upsell {
|
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
v_flex()
|
|
||||||
.w_full()
|
|
||||||
.p_4()
|
|
||||||
.gap_3()
|
|
||||||
.bg(cx.theme().colors().surface_background)
|
|
||||||
.rounded_md()
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Label::new(self.title)
|
|
||||||
.size(ui::LabelSize::Large)
|
|
||||||
.weight(gpui::FontWeight::BOLD),
|
|
||||||
)
|
|
||||||
.child(Label::new(self.message).color(Color::Muted)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.justify_between()
|
|
||||||
.items_center()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.items_center()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
Checkbox::new("dont-show-again", ToggleState::Unselected).on_click(
|
|
||||||
move |_, window, cx| {
|
|
||||||
(self.on_dont_show_again)(true, window, cx);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Label::new("Don't show again")
|
|
||||||
.color(Color::Muted)
|
|
||||||
.size(ui::LabelSize::Small),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
Button::new("dismiss-button", "No Thanks")
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.on_click(self.on_dismiss),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("cta-button", self.cta_text)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.on_click(self.on_click),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Upsell {
|
|
||||||
fn scope() -> ComponentScope {
|
|
||||||
ComponentScope::Agent
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name() -> &'static str {
|
|
||||||
"Upsell"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description() -> Option<&'static str> {
|
|
||||||
Some("A promotional component that displays a message with a call-to-action.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
|
||||||
let examples = vec![
|
|
||||||
single_example(
|
|
||||||
"Default",
|
|
||||||
Upsell::new(
|
|
||||||
"Upgrade to Zed Pro",
|
|
||||||
"Get unlimited access to AI features and more with Zed Pro. Unlock advanced AI capabilities and other premium features.",
|
|
||||||
"Upgrade Now",
|
|
||||||
Box::new(|_, _, _| {}),
|
|
||||||
Box::new(|_, _, _| {}),
|
|
||||||
Box::new(|_, _, _| {}),
|
|
||||||
).render(window, cx).into_any_element(),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Short Message",
|
|
||||||
Upsell::new(
|
|
||||||
"Try Zed Pro for free",
|
|
||||||
"Start your 7-day trial today.",
|
|
||||||
"Start Trial",
|
|
||||||
Box::new(|_, _, _| {}),
|
|
||||||
Box::new(|_, _, _| {}),
|
|
||||||
Box::new(|_, _, _| {}),
|
|
||||||
).render(window, cx).into_any_element(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
Some(v_flex().gap_4().children(examples).into_any_element())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -61,6 +61,11 @@ impl Render for AgentPanelOnboarding {
|
||||||
Some(proto::Plan::ZedProTrial)
|
Some(proto::Plan::ZedProTrial)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let is_pro_user = matches!(
|
||||||
|
self.user_store.read(cx).current_plan(),
|
||||||
|
Some(proto::Plan::ZedPro)
|
||||||
|
);
|
||||||
|
|
||||||
AgentPanelOnboardingCard::new()
|
AgentPanelOnboardingCard::new()
|
||||||
.child(
|
.child(
|
||||||
ZedAiOnboarding::new(
|
ZedAiOnboarding::new(
|
||||||
|
@ -75,7 +80,7 @@ impl Render for AgentPanelOnboarding {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if enrolled_in_trial || self.configured_providers.len() >= 1 {
|
if enrolled_in_trial || is_pro_user || self.configured_providers.len() >= 1 {
|
||||||
this
|
this
|
||||||
} else {
|
} else {
|
||||||
this.child(ApiKeysWithoutProviders::new())
|
this.child(ApiKeysWithoutProviders::new())
|
||||||
|
|
|
@ -16,6 +16,7 @@ use client::{Client, UserStore, zed_urls};
|
||||||
use gpui::{AnyElement, Entity, IntoElement, ParentElement, SharedString};
|
use gpui::{AnyElement, Entity, IntoElement, ParentElement, SharedString};
|
||||||
use ui::{Divider, List, ListItem, RegisterComponent, TintColor, Tooltip, prelude::*};
|
use ui::{Divider, List, ListItem, RegisterComponent, TintColor, Tooltip, prelude::*};
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
pub struct BulletItem {
|
pub struct BulletItem {
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
}
|
}
|
||||||
|
@ -28,18 +29,27 @@ impl BulletItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for BulletItem {
|
impl RenderOnce for BulletItem {
|
||||||
type Element = AnyElement;
|
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
|
let line_height = 0.85 * window.line_height();
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
|
||||||
ListItem::new("list-item")
|
ListItem::new("list-item")
|
||||||
.selectable(false)
|
.selectable(false)
|
||||||
.start_slot(
|
.child(
|
||||||
Icon::new(IconName::Dash)
|
h_flex()
|
||||||
.size(IconSize::XSmall)
|
.w_full()
|
||||||
.color(Color::Hidden),
|
.min_w_0()
|
||||||
|
.gap_1()
|
||||||
|
.items_start()
|
||||||
|
.child(
|
||||||
|
h_flex().h(line_height).justify_center().child(
|
||||||
|
Icon::new(IconName::Dash)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Hidden),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(div().w_full().min_w_0().child(Label::new(self.label))),
|
||||||
)
|
)
|
||||||
.child(div().w_full().child(Label::new(self.label)))
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,7 +247,7 @@ impl ZedAiOnboarding {
|
||||||
.icon_color(Color::Muted)
|
.icon_color(Color::Muted)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.on_click(move |_, _window, cx| {
|
.on_click(move |_, _window, cx| {
|
||||||
telemetry::event!("Review Terms of Service Click");
|
telemetry::event!("Review Terms of Service Clicked");
|
||||||
cx.open_url(&zed_urls::terms_of_service(cx))
|
cx.open_url(&zed_urls::terms_of_service(cx))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -248,7 +258,7 @@ impl ZedAiOnboarding {
|
||||||
.on_click({
|
.on_click({
|
||||||
let callback = self.accept_terms_of_service.clone();
|
let callback = self.accept_terms_of_service.clone();
|
||||||
move |_, window, cx| {
|
move |_, window, cx| {
|
||||||
telemetry::event!("Accepted Terms of Service");
|
telemetry::event!("Terms of Service Accepted");
|
||||||
(callback)(window, cx)}
|
(callback)(window, cx)}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -373,7 +383,9 @@ impl ZedAiOnboarding {
|
||||||
.child(
|
.child(
|
||||||
List::new()
|
List::new()
|
||||||
.child(BulletItem::new("500 prompts with Claude models"))
|
.child(BulletItem::new("500 prompts with Claude models"))
|
||||||
.child(BulletItem::new("Unlimited edit predictions")),
|
.child(BulletItem::new(
|
||||||
|
"Unlimited edit predictions with Zeta, our open-source model",
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("pro", "Continue with Zed Pro")
|
Button::new("pro", "Continue with Zed Pro")
|
||||||
|
|
|
@ -767,6 +767,11 @@ impl ContextStore {
|
||||||
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
|
pub static ZED_STATELESS: LazyLock<bool> =
|
||||||
|
LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()));
|
||||||
|
if *ZED_STATELESS {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
fs.create_dir(contexts_dir()).await?;
|
fs.create_dir(contexts_dir()).await?;
|
||||||
|
|
||||||
let mut paths = fs.read_dir(contexts_dir()).await?;
|
let mut paths = fs.read_dir(contexts_dir()).await?;
|
||||||
|
|
|
@ -765,12 +765,14 @@ impl UserStore {
|
||||||
|
|
||||||
pub fn current_plan(&self) -> Option<proto::Plan> {
|
pub fn current_plan(&self) -> Option<proto::Plan> {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
if let Ok(plan) = std::env::var("ZED_SIMULATE_ZED_PRO_PLAN").as_ref() {
|
if let Ok(plan) = std::env::var("ZED_SIMULATE_PLAN").as_ref() {
|
||||||
return match plan.as_str() {
|
return match plan.as_str() {
|
||||||
"free" => Some(proto::Plan::Free),
|
"free" => Some(proto::Plan::Free),
|
||||||
"trial" => Some(proto::Plan::ZedProTrial),
|
"trial" => Some(proto::Plan::ZedProTrial),
|
||||||
"pro" => Some(proto::Plan::ZedPro),
|
"pro" => Some(proto::Plan::ZedPro),
|
||||||
_ => None,
|
_ => {
|
||||||
|
panic!("ZED_SIMULATE_PLAN must be one of 'free', 'trial', or 'pro'");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16837,7 +16837,7 @@ async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
|
async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
let cols = 4;
|
let cols = 4;
|
||||||
|
|
|
@ -12,6 +12,7 @@ use language::{self, Buffer, Point};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
|
cmp,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
pin::pin,
|
pin::pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -45,38 +46,60 @@ impl TextDiffView {
|
||||||
) -> Option<Task<Result<Entity<Self>>>> {
|
) -> Option<Task<Result<Entity<Self>>>> {
|
||||||
let source_editor = diff_data.editor.clone();
|
let source_editor = diff_data.editor.clone();
|
||||||
|
|
||||||
let source_editor_buffer_and_range = source_editor.update(cx, |editor, cx| {
|
let selection_data = source_editor.update(cx, |editor, cx| {
|
||||||
let multibuffer = editor.buffer().read(cx);
|
let multibuffer = editor.buffer().read(cx);
|
||||||
let source_buffer = multibuffer.as_singleton()?.clone();
|
let source_buffer = multibuffer.as_singleton()?.clone();
|
||||||
let selections = editor.selections.all::<Point>(cx);
|
let selections = editor.selections.all::<Point>(cx);
|
||||||
let buffer_snapshot = source_buffer.read(cx);
|
let buffer_snapshot = source_buffer.read(cx);
|
||||||
let first_selection = selections.first()?;
|
let first_selection = selections.first()?;
|
||||||
let selection_range = if first_selection.is_empty() {
|
let max_point = buffer_snapshot.max_point();
|
||||||
Point::new(0, 0)..buffer_snapshot.max_point()
|
|
||||||
} else {
|
|
||||||
first_selection.start..first_selection.end
|
|
||||||
};
|
|
||||||
|
|
||||||
Some((source_buffer, selection_range))
|
if first_selection.is_empty() {
|
||||||
|
let full_range = Point::new(0, 0)..max_point;
|
||||||
|
return Some((source_buffer, full_range));
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = first_selection.start;
|
||||||
|
let end = first_selection.end;
|
||||||
|
let expanded_start = Point::new(start.row, 0);
|
||||||
|
|
||||||
|
let expanded_end = if end.column > 0 {
|
||||||
|
let next_row = end.row + 1;
|
||||||
|
cmp::min(max_point, Point::new(next_row, 0))
|
||||||
|
} else {
|
||||||
|
end
|
||||||
|
};
|
||||||
|
Some((source_buffer, expanded_start..expanded_end))
|
||||||
});
|
});
|
||||||
|
|
||||||
let Some((source_buffer, selected_range)) = source_editor_buffer_and_range else {
|
let Some((source_buffer, expanded_selection_range)) = selection_data else {
|
||||||
log::warn!("There should always be at least one selection in Zed. This is a bug.");
|
log::warn!("There should always be at least one selection in Zed. This is a bug.");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let clipboard_text = diff_data.clipboard_text.clone();
|
source_editor.update(cx, |source_editor, cx| {
|
||||||
|
source_editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
let workspace = workspace.weak_handle();
|
s.select_ranges(vec![
|
||||||
|
expanded_selection_range.start..expanded_selection_range.end,
|
||||||
let diff_buffer = cx.new(|cx| {
|
]);
|
||||||
let source_buffer_snapshot = source_buffer.read(cx).snapshot();
|
})
|
||||||
let diff = BufferDiff::new(&source_buffer_snapshot.text, cx);
|
|
||||||
diff
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let clipboard_buffer =
|
let source_buffer_snapshot = source_buffer.read(cx).snapshot();
|
||||||
build_clipboard_buffer(clipboard_text, &source_buffer, selected_range.clone(), cx);
|
let mut clipboard_text = diff_data.clipboard_text.clone();
|
||||||
|
|
||||||
|
if !clipboard_text.ends_with("\n") {
|
||||||
|
clipboard_text.push_str("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspace = workspace.weak_handle();
|
||||||
|
let diff_buffer = cx.new(|cx| BufferDiff::new(&source_buffer_snapshot.text, cx));
|
||||||
|
let clipboard_buffer = build_clipboard_buffer(
|
||||||
|
clipboard_text,
|
||||||
|
&source_buffer,
|
||||||
|
expanded_selection_range.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
let task = window.spawn(cx, async move |cx| {
|
let task = window.spawn(cx, async move |cx| {
|
||||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
|
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
|
||||||
|
@ -89,7 +112,7 @@ impl TextDiffView {
|
||||||
clipboard_buffer,
|
clipboard_buffer,
|
||||||
source_editor,
|
source_editor,
|
||||||
source_buffer,
|
source_buffer,
|
||||||
selected_range,
|
expanded_selection_range,
|
||||||
diff_buffer,
|
diff_buffer,
|
||||||
project,
|
project,
|
||||||
window,
|
window,
|
||||||
|
@ -208,9 +231,9 @@ impl TextDiffView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_clipboard_buffer(
|
fn build_clipboard_buffer(
|
||||||
clipboard_text: String,
|
text: String,
|
||||||
source_buffer: &Entity<Buffer>,
|
source_buffer: &Entity<Buffer>,
|
||||||
selected_range: Range<Point>,
|
replacement_range: Range<Point>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Entity<Buffer> {
|
) -> Entity<Buffer> {
|
||||||
let source_buffer_snapshot = source_buffer.read(cx).snapshot();
|
let source_buffer_snapshot = source_buffer.read(cx).snapshot();
|
||||||
|
@ -219,9 +242,9 @@ fn build_clipboard_buffer(
|
||||||
let language = source_buffer.read(cx).language().cloned();
|
let language = source_buffer.read(cx).language().cloned();
|
||||||
buffer.set_language(language, cx);
|
buffer.set_language(language, cx);
|
||||||
|
|
||||||
let range_start = source_buffer_snapshot.point_to_offset(selected_range.start);
|
let range_start = source_buffer_snapshot.point_to_offset(replacement_range.start);
|
||||||
let range_end = source_buffer_snapshot.point_to_offset(selected_range.end);
|
let range_end = source_buffer_snapshot.point_to_offset(replacement_range.end);
|
||||||
buffer.edit([(range_start..range_end, clipboard_text)], None, cx);
|
buffer.edit([(range_start..range_end, text)], None, cx);
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
})
|
})
|
||||||
|
@ -293,7 +316,7 @@ impl Item for TextDiffView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
Some("Diff View Opened")
|
Some("Selection Diff View Opened")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
@ -395,21 +418,13 @@ pub fn selection_location_text(editor: &Editor, cx: &App) -> Option<String> {
|
||||||
let buffer_snapshot = buffer.snapshot(cx);
|
let buffer_snapshot = buffer.snapshot(cx);
|
||||||
let first_selection = editor.selections.disjoint.first()?;
|
let first_selection = editor.selections.disjoint.first()?;
|
||||||
|
|
||||||
let (start_row, start_column, end_row, end_column) =
|
let selection_start = first_selection.start.to_point(&buffer_snapshot);
|
||||||
if first_selection.start == first_selection.end {
|
let selection_end = first_selection.end.to_point(&buffer_snapshot);
|
||||||
let max_point = buffer_snapshot.max_point();
|
|
||||||
(0, 0, max_point.row, max_point.column)
|
|
||||||
} else {
|
|
||||||
let selection_start = first_selection.start.to_point(&buffer_snapshot);
|
|
||||||
let selection_end = first_selection.end.to_point(&buffer_snapshot);
|
|
||||||
|
|
||||||
(
|
let start_row = selection_start.row;
|
||||||
selection_start.row,
|
let start_column = selection_start.column;
|
||||||
selection_start.column,
|
let end_row = selection_end.row;
|
||||||
selection_end.row,
|
let end_column = selection_end.column;
|
||||||
selection_end.column,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let range_text = if start_row == end_row {
|
let range_text = if start_row == end_row {
|
||||||
format!("L{}:{}-{}", start_row + 1, start_column + 1, end_column + 1)
|
format!("L{}:{}-{}", start_row + 1, start_column + 1, end_column + 1)
|
||||||
|
@ -435,14 +450,13 @@ impl Render for TextDiffView {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use editor::test::editor_test_context::assert_state_with_diff;
|
||||||
use editor::{actions, test::editor_test_context::assert_state_with_diff};
|
|
||||||
use gpui::{TestAppContext, VisualContext};
|
use gpui::{TestAppContext, VisualContext};
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use unindent::unindent;
|
use unindent::unindent;
|
||||||
use util::path;
|
use util::{path, test::marked_text_ranges};
|
||||||
|
|
||||||
fn init_test(cx: &mut TestAppContext) {
|
fn init_test(cx: &mut TestAppContext) {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
|
@ -457,52 +471,236 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_diffing_clipboard_against_specific_selection(cx: &mut TestAppContext) {
|
async fn test_diffing_clipboard_against_empty_selection_uses_full_buffer_selection(
|
||||||
base_test(true, cx).await;
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
"def process_incoming_inventory(items, warehouse_id):\n pass\n",
|
||||||
|
"def process_outgoing_inventory(items, warehouse_id):\n passˇ\n",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- def process_incoming_inventory(items, warehouse_id):
|
||||||
|
+ ˇdef process_outgoing_inventory(items, warehouse_id):
|
||||||
|
pass
|
||||||
|
",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-L3:1",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-L3:1", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_diffing_clipboard_against_empty_selection_uses_full_buffer(
|
async fn test_diffing_clipboard_against_multiline_selection_expands_to_full_lines(
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) {
|
) {
|
||||||
base_test(false, cx).await;
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
"def process_incoming_inventory(items, warehouse_id):\n pass\n",
|
||||||
|
"«def process_outgoing_inventory(items, warehouse_id):\n passˇ»\n",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- def process_incoming_inventory(items, warehouse_id):
|
||||||
|
+ ˇdef process_outgoing_inventory(items, warehouse_id):
|
||||||
|
pass
|
||||||
|
",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-L3:1",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-L3:1", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn base_test(select_all_text: bool, cx: &mut TestAppContext) {
|
#[gpui::test]
|
||||||
|
async fn test_diffing_clipboard_against_single_line_selection(cx: &mut TestAppContext) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
"a",
|
||||||
|
"«bbˇ»",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- a
|
||||||
|
+ ˇbb",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-3",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-3", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diffing_clipboard_with_leading_whitespace_against_line(cx: &mut TestAppContext) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
" a",
|
||||||
|
"«bbˇ»",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- a
|
||||||
|
+ ˇbb",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-3",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-3", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diffing_clipboard_against_line_with_leading_whitespace(cx: &mut TestAppContext) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
"a",
|
||||||
|
" «bbˇ»",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- a
|
||||||
|
+ ˇ bb",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-7",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-7", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diffing_clipboard_against_line_with_leading_whitespace_included_in_selection(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
"a",
|
||||||
|
"« bbˇ»",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- a
|
||||||
|
+ ˇ bb",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-7",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-7", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diffing_clipboard_with_leading_whitespace_against_line_with_leading_whitespace(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
" a",
|
||||||
|
" «bbˇ»",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- a
|
||||||
|
+ ˇ bb",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-7",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-7", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diffing_clipboard_with_leading_whitespace_against_line_with_leading_whitespace_included_in_selection(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
" a",
|
||||||
|
"« bbˇ»",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- a
|
||||||
|
+ ˇ bb",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-7",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-7", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diffing_clipboard_against_partial_selection_expands_to_include_trailing_characters(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
base_test(
|
||||||
|
path!("/test"),
|
||||||
|
path!("/test/text.txt"),
|
||||||
|
"a",
|
||||||
|
"«bˇ»b",
|
||||||
|
&unindent(
|
||||||
|
"
|
||||||
|
- a
|
||||||
|
+ ˇbb",
|
||||||
|
),
|
||||||
|
"Clipboard ↔ text.txt @ L1:1-3",
|
||||||
|
&format!("Clipboard ↔ {} @ L1:1-3", path!("test/text.txt")),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn base_test(
|
||||||
|
project_root: &str,
|
||||||
|
file_path: &str,
|
||||||
|
clipboard_text: &str,
|
||||||
|
editor_text: &str,
|
||||||
|
expected_diff: &str,
|
||||||
|
expected_tab_title: &str,
|
||||||
|
expected_tab_tooltip: &str,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|
||||||
|
let file_name = std::path::Path::new(file_path)
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.executor());
|
let fs = FakeFs::new(cx.executor());
|
||||||
fs.insert_tree(
|
fs.insert_tree(
|
||||||
path!("/test"),
|
project_root,
|
||||||
json!({
|
json!({
|
||||||
"a": {
|
file_name: editor_text
|
||||||
"b": {
|
|
||||||
"text.txt": "new line 1\nline 2\nnew line 3\nline 4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
|
let project = Project::test(fs, [project_root.as_ref()], cx).await;
|
||||||
|
|
||||||
let (workspace, mut cx) =
|
let (workspace, mut cx) =
|
||||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| project.open_local_buffer(file_path, cx))
|
||||||
project.open_local_buffer(path!("/test/a/b/text.txt"), cx)
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let editor = cx.new_window_entity(|window, cx| {
|
let editor = cx.new_window_entity(|window, cx| {
|
||||||
let mut editor = Editor::for_buffer(buffer, None, window, cx);
|
let mut editor = Editor::for_buffer(buffer, None, window, cx);
|
||||||
editor.set_text("new line 1\nline 2\nnew line 3\nline 4\n", window, cx);
|
let (unmarked_text, selection_ranges) = marked_text_ranges(editor_text, false);
|
||||||
|
editor.set_text(unmarked_text, window, cx);
|
||||||
if select_all_text {
|
editor.change_selections(Default::default(), window, cx, |s| {
|
||||||
editor.select_all(&actions::SelectAll, window, cx);
|
s.select_ranges(selection_ranges)
|
||||||
}
|
});
|
||||||
|
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
@ -511,7 +709,7 @@ mod tests {
|
||||||
.update_in(cx, |workspace, window, cx| {
|
.update_in(cx, |workspace, window, cx| {
|
||||||
TextDiffView::open(
|
TextDiffView::open(
|
||||||
&DiffClipboardWithSelectionData {
|
&DiffClipboardWithSelectionData {
|
||||||
clipboard_text: "old line 1\nline 2\nold line 3\nline 4\n".to_string(),
|
clipboard_text: clipboard_text.to_string(),
|
||||||
editor,
|
editor,
|
||||||
},
|
},
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -528,26 +726,14 @@ mod tests {
|
||||||
assert_state_with_diff(
|
assert_state_with_diff(
|
||||||
&diff_view.read_with(cx, |diff_view, _| diff_view.diff_editor.clone()),
|
&diff_view.read_with(cx, |diff_view, _| diff_view.diff_editor.clone()),
|
||||||
&mut cx,
|
&mut cx,
|
||||||
&unindent(
|
expected_diff,
|
||||||
"
|
|
||||||
- old line 1
|
|
||||||
+ ˇnew line 1
|
|
||||||
line 2
|
|
||||||
- old line 3
|
|
||||||
+ new line 3
|
|
||||||
line 4
|
|
||||||
",
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
diff_view.read_with(cx, |diff_view, cx| {
|
diff_view.read_with(cx, |diff_view, cx| {
|
||||||
assert_eq!(
|
assert_eq!(diff_view.tab_content_text(0, cx), expected_tab_title);
|
||||||
diff_view.tab_content_text(0, cx),
|
|
||||||
"Clipboard ↔ text.txt @ L1:1-L5:1"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
diff_view.tab_tooltip_text(cx).unwrap(),
|
diff_view.tab_tooltip_text(cx).unwrap(),
|
||||||
format!("Clipboard ↔ {}", path!("test/a/b/text.txt @ L1:1-L5:1"))
|
expected_tab_tooltip
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -845,9 +845,15 @@ impl crate::Keystroke {
|
||||||
{
|
{
|
||||||
if key.is_ascii_graphic() {
|
if key.is_ascii_graphic() {
|
||||||
key_utf8.to_lowercase()
|
key_utf8.to_lowercase()
|
||||||
// map ctrl-a to a
|
// map ctrl-a to `a`
|
||||||
} else if key_utf32 <= 0x1f {
|
// ctrl-0..9 may emit control codes like ctrl-[, but
|
||||||
((key_utf32 as u8 + 0x60) as char).to_string()
|
// we don't want to map them to `[`
|
||||||
|
} else if key_utf32 <= 0x1f
|
||||||
|
&& !name.chars().next().is_some_and(|c| c.is_ascii_digit())
|
||||||
|
{
|
||||||
|
((key_utf32 as u8 + 0x40) as char)
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.to_string()
|
||||||
} else {
|
} else {
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|
|
@ -1159,19 +1159,20 @@ impl RenderOnce for ZedAiConfiguration {
|
||||||
|
|
||||||
let manage_subscription_buttons = if is_pro {
|
let manage_subscription_buttons = if is_pro {
|
||||||
Button::new("manage_settings", "Manage Subscription")
|
Button::new("manage_settings", "Manage Subscription")
|
||||||
|
.full_width()
|
||||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
.on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx)))
|
.on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx)))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
} else if self.plan.is_none() || self.eligible_for_trial {
|
} else if self.plan.is_none() || self.eligible_for_trial {
|
||||||
Button::new("start_trial", "Start 14-day Free Pro Trial")
|
Button::new("start_trial", "Start 14-day Free Pro Trial")
|
||||||
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
|
|
||||||
.full_width()
|
.full_width()
|
||||||
|
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
.on_click(|_, _, cx| cx.open_url(&zed_urls::start_trial_url(cx)))
|
.on_click(|_, _, cx| cx.open_url(&zed_urls::start_trial_url(cx)))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
} else {
|
} else {
|
||||||
Button::new("upgrade", "Upgrade to Pro")
|
Button::new("upgrade", "Upgrade to Pro")
|
||||||
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
|
|
||||||
.full_width()
|
.full_width()
|
||||||
|
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
.on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx)))
|
.on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx)))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,18 +48,29 @@ pub enum Model {
|
||||||
#[serde(rename = "codestral-latest", alias = "codestral-latest")]
|
#[serde(rename = "codestral-latest", alias = "codestral-latest")]
|
||||||
#[default]
|
#[default]
|
||||||
CodestralLatest,
|
CodestralLatest,
|
||||||
|
|
||||||
#[serde(rename = "mistral-large-latest", alias = "mistral-large-latest")]
|
#[serde(rename = "mistral-large-latest", alias = "mistral-large-latest")]
|
||||||
MistralLargeLatest,
|
MistralLargeLatest,
|
||||||
#[serde(rename = "mistral-medium-latest", alias = "mistral-medium-latest")]
|
#[serde(rename = "mistral-medium-latest", alias = "mistral-medium-latest")]
|
||||||
MistralMediumLatest,
|
MistralMediumLatest,
|
||||||
#[serde(rename = "mistral-small-latest", alias = "mistral-small-latest")]
|
#[serde(rename = "mistral-small-latest", alias = "mistral-small-latest")]
|
||||||
MistralSmallLatest,
|
MistralSmallLatest,
|
||||||
|
|
||||||
|
#[serde(rename = "magistral-medium-latest", alias = "magistral-medium-latest")]
|
||||||
|
MagistralMediumLatest,
|
||||||
|
#[serde(rename = "magistral-small-latest", alias = "magistral-small-latest")]
|
||||||
|
MagistralSmallLatest,
|
||||||
|
|
||||||
#[serde(rename = "open-mistral-nemo", alias = "open-mistral-nemo")]
|
#[serde(rename = "open-mistral-nemo", alias = "open-mistral-nemo")]
|
||||||
OpenMistralNemo,
|
OpenMistralNemo,
|
||||||
#[serde(rename = "open-codestral-mamba", alias = "open-codestral-mamba")]
|
#[serde(rename = "open-codestral-mamba", alias = "open-codestral-mamba")]
|
||||||
OpenCodestralMamba,
|
OpenCodestralMamba,
|
||||||
|
|
||||||
|
#[serde(rename = "devstral-medium-latest", alias = "devstral-medium-latest")]
|
||||||
|
DevstralMediumLatest,
|
||||||
#[serde(rename = "devstral-small-latest", alias = "devstral-small-latest")]
|
#[serde(rename = "devstral-small-latest", alias = "devstral-small-latest")]
|
||||||
DevstralSmallLatest,
|
DevstralSmallLatest,
|
||||||
|
|
||||||
#[serde(rename = "pixtral-12b-latest", alias = "pixtral-12b-latest")]
|
#[serde(rename = "pixtral-12b-latest", alias = "pixtral-12b-latest")]
|
||||||
Pixtral12BLatest,
|
Pixtral12BLatest,
|
||||||
#[serde(rename = "pixtral-large-latest", alias = "pixtral-large-latest")]
|
#[serde(rename = "pixtral-large-latest", alias = "pixtral-large-latest")]
|
||||||
|
@ -89,8 +100,11 @@ impl Model {
|
||||||
"mistral-large-latest" => Ok(Self::MistralLargeLatest),
|
"mistral-large-latest" => Ok(Self::MistralLargeLatest),
|
||||||
"mistral-medium-latest" => Ok(Self::MistralMediumLatest),
|
"mistral-medium-latest" => Ok(Self::MistralMediumLatest),
|
||||||
"mistral-small-latest" => Ok(Self::MistralSmallLatest),
|
"mistral-small-latest" => Ok(Self::MistralSmallLatest),
|
||||||
|
"magistral-medium-latest" => Ok(Self::MagistralMediumLatest),
|
||||||
|
"magistral-small-latest" => Ok(Self::MagistralSmallLatest),
|
||||||
"open-mistral-nemo" => Ok(Self::OpenMistralNemo),
|
"open-mistral-nemo" => Ok(Self::OpenMistralNemo),
|
||||||
"open-codestral-mamba" => Ok(Self::OpenCodestralMamba),
|
"open-codestral-mamba" => Ok(Self::OpenCodestralMamba),
|
||||||
|
"devstral-medium-latest" => Ok(Self::DevstralMediumLatest),
|
||||||
"devstral-small-latest" => Ok(Self::DevstralSmallLatest),
|
"devstral-small-latest" => Ok(Self::DevstralSmallLatest),
|
||||||
"pixtral-12b-latest" => Ok(Self::Pixtral12BLatest),
|
"pixtral-12b-latest" => Ok(Self::Pixtral12BLatest),
|
||||||
"pixtral-large-latest" => Ok(Self::PixtralLargeLatest),
|
"pixtral-large-latest" => Ok(Self::PixtralLargeLatest),
|
||||||
|
@ -104,8 +118,11 @@ impl Model {
|
||||||
Self::MistralLargeLatest => "mistral-large-latest",
|
Self::MistralLargeLatest => "mistral-large-latest",
|
||||||
Self::MistralMediumLatest => "mistral-medium-latest",
|
Self::MistralMediumLatest => "mistral-medium-latest",
|
||||||
Self::MistralSmallLatest => "mistral-small-latest",
|
Self::MistralSmallLatest => "mistral-small-latest",
|
||||||
|
Self::MagistralMediumLatest => "magistral-medium-latest",
|
||||||
|
Self::MagistralSmallLatest => "magistral-small-latest",
|
||||||
Self::OpenMistralNemo => "open-mistral-nemo",
|
Self::OpenMistralNemo => "open-mistral-nemo",
|
||||||
Self::OpenCodestralMamba => "open-codestral-mamba",
|
Self::OpenCodestralMamba => "open-codestral-mamba",
|
||||||
|
Self::DevstralMediumLatest => "devstral-medium-latest",
|
||||||
Self::DevstralSmallLatest => "devstral-small-latest",
|
Self::DevstralSmallLatest => "devstral-small-latest",
|
||||||
Self::Pixtral12BLatest => "pixtral-12b-latest",
|
Self::Pixtral12BLatest => "pixtral-12b-latest",
|
||||||
Self::PixtralLargeLatest => "pixtral-large-latest",
|
Self::PixtralLargeLatest => "pixtral-large-latest",
|
||||||
|
@ -119,8 +136,11 @@ impl Model {
|
||||||
Self::MistralLargeLatest => "mistral-large-latest",
|
Self::MistralLargeLatest => "mistral-large-latest",
|
||||||
Self::MistralMediumLatest => "mistral-medium-latest",
|
Self::MistralMediumLatest => "mistral-medium-latest",
|
||||||
Self::MistralSmallLatest => "mistral-small-latest",
|
Self::MistralSmallLatest => "mistral-small-latest",
|
||||||
|
Self::MagistralMediumLatest => "magistral-medium-latest",
|
||||||
|
Self::MagistralSmallLatest => "magistral-small-latest",
|
||||||
Self::OpenMistralNemo => "open-mistral-nemo",
|
Self::OpenMistralNemo => "open-mistral-nemo",
|
||||||
Self::OpenCodestralMamba => "open-codestral-mamba",
|
Self::OpenCodestralMamba => "open-codestral-mamba",
|
||||||
|
Self::DevstralMediumLatest => "devstral-medium-latest",
|
||||||
Self::DevstralSmallLatest => "devstral-small-latest",
|
Self::DevstralSmallLatest => "devstral-small-latest",
|
||||||
Self::Pixtral12BLatest => "pixtral-12b-latest",
|
Self::Pixtral12BLatest => "pixtral-12b-latest",
|
||||||
Self::PixtralLargeLatest => "pixtral-large-latest",
|
Self::PixtralLargeLatest => "pixtral-large-latest",
|
||||||
|
@ -136,8 +156,11 @@ impl Model {
|
||||||
Self::MistralLargeLatest => 131000,
|
Self::MistralLargeLatest => 131000,
|
||||||
Self::MistralMediumLatest => 128000,
|
Self::MistralMediumLatest => 128000,
|
||||||
Self::MistralSmallLatest => 32000,
|
Self::MistralSmallLatest => 32000,
|
||||||
|
Self::MagistralMediumLatest => 40000,
|
||||||
|
Self::MagistralSmallLatest => 40000,
|
||||||
Self::OpenMistralNemo => 131000,
|
Self::OpenMistralNemo => 131000,
|
||||||
Self::OpenCodestralMamba => 256000,
|
Self::OpenCodestralMamba => 256000,
|
||||||
|
Self::DevstralMediumLatest => 128000,
|
||||||
Self::DevstralSmallLatest => 262144,
|
Self::DevstralSmallLatest => 262144,
|
||||||
Self::Pixtral12BLatest => 128000,
|
Self::Pixtral12BLatest => 128000,
|
||||||
Self::PixtralLargeLatest => 128000,
|
Self::PixtralLargeLatest => 128000,
|
||||||
|
@ -160,8 +183,11 @@ impl Model {
|
||||||
| Self::MistralLargeLatest
|
| Self::MistralLargeLatest
|
||||||
| Self::MistralMediumLatest
|
| Self::MistralMediumLatest
|
||||||
| Self::MistralSmallLatest
|
| Self::MistralSmallLatest
|
||||||
|
| Self::MagistralMediumLatest
|
||||||
|
| Self::MagistralSmallLatest
|
||||||
| Self::OpenMistralNemo
|
| Self::OpenMistralNemo
|
||||||
| Self::OpenCodestralMamba
|
| Self::OpenCodestralMamba
|
||||||
|
| Self::DevstralMediumLatest
|
||||||
| Self::DevstralSmallLatest
|
| Self::DevstralSmallLatest
|
||||||
| Self::Pixtral12BLatest
|
| Self::Pixtral12BLatest
|
||||||
| Self::PixtralLargeLatest => true,
|
| Self::PixtralLargeLatest => true,
|
||||||
|
@ -177,8 +203,11 @@ impl Model {
|
||||||
| Self::MistralSmallLatest => true,
|
| Self::MistralSmallLatest => true,
|
||||||
Self::CodestralLatest
|
Self::CodestralLatest
|
||||||
| Self::MistralLargeLatest
|
| Self::MistralLargeLatest
|
||||||
|
| Self::MagistralMediumLatest
|
||||||
|
| Self::MagistralSmallLatest
|
||||||
| Self::OpenMistralNemo
|
| Self::OpenMistralNemo
|
||||||
| Self::OpenCodestralMamba
|
| Self::OpenCodestralMamba
|
||||||
|
| Self::DevstralMediumLatest
|
||||||
| Self::DevstralSmallLatest => false,
|
| Self::DevstralSmallLatest => false,
|
||||||
Self::Custom {
|
Self::Custom {
|
||||||
supports_images, ..
|
supports_images, ..
|
||||||
|
|
|
@ -55,6 +55,7 @@ fn get_max_tokens(name: &str) -> u64 {
|
||||||
"codellama" | "starcoder2" => 16384,
|
"codellama" | "starcoder2" => 16384,
|
||||||
"mistral" | "codestral" | "mixstral" | "llava" | "qwen2" | "qwen2.5-coder"
|
"mistral" | "codestral" | "mixstral" | "llava" | "qwen2" | "qwen2.5-coder"
|
||||||
| "dolphin-mixtral" => 32768,
|
| "dolphin-mixtral" => 32768,
|
||||||
|
"magistral" => 40000,
|
||||||
"llama3.1" | "llama3.2" | "llama3.3" | "phi3" | "phi3.5" | "phi4" | "command-r"
|
"llama3.1" | "llama3.2" | "llama3.3" | "phi3" | "phi3.5" | "phi4" | "command-r"
|
||||||
| "qwen3" | "gemma3" | "deepseek-coder-v2" | "deepseek-v3" | "deepseek-r1" | "yi-coder"
|
| "qwen3" | "gemma3" | "deepseek-coder-v2" | "deepseek-v3" | "deepseek-r1" | "yi-coder"
|
||||||
| "devstral" => 128000,
|
| "devstral" => 128000,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
description = "The fast, collaborative code editor."
|
description = "The fast, collaborative code editor."
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.197.0"
|
version = "0.197.1"
|
||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
authors = ["Zed Team <hi@zed.dev>"]
|
authors = ["Zed Team <hi@zed.dev>"]
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
dev
|
preview
|
Loading…
Add table
Add a link
Reference in a new issue