Compare commits

..

18 Commits

Author SHA1 Message Date
Thomas Mickley-Doyle
77931bafa1 rebase 2025-04-16 13:40:17 -05:00
Marshall Bowers
f81c2a9abf collab: Add plan column to subscription_usages (#28889)
This PR adds a `plan` column to the `subscription_usages` table.

These tables don't have any records in them yet, so it's fine to make
the column required without a default.

Release Notes:

- N/A
2025-04-16 13:38:51 -05:00
Conrad Irwin
b447eb19ca Fix anchor_in_excerpt on replaced excerpts (#28880)
Release Notes:

- N/A
2025-04-16 13:38:51 -05:00
Bennet Bo Fenner
7ec2ee943c agent: Allow quoting selection when text thread is active (#28887)
This makes the `assistant: Quote selection` work again for text threads.
Next up is supporting this also in normal threads.

Release Notes:

- agent: Add support for inserting selections (assistant: Quote
selection) into text threads
2025-04-16 13:38:51 -05:00
Mikayla Maki
d5c83baa02 Remove bottom dock layout button (#28876)
Release Notes:

- Preview: Removed the layout button from the title bar. The
`bottom_dock_layout` setting still functions.
- Added a setting, `bottom_dock_layout`, for controlling the
relationship between the bottom dock and the left and right docks.
2025-04-16 13:38:51 -05:00
Marshall Bowers
efcd97f1d9 proto: Add ZedProTrial to Plan (#28885)
This PR adds the `ZedProTrial` member to the `Plan` enum.

Release Notes:

- N/A
2025-04-16 13:38:51 -05:00
Thomas Mickley-Doyle
c768cb022b Update crates/eval/src/eval.rs
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-04-16 13:31:17 -05:00
Thomas Mickley-Doyle
85acace7b2 Update clippy error 2025-04-16 13:21:57 -05:00
Thomas Mickley-Doyle
0292f64416 Add commit id to eval telemetry 2025-04-16 13:16:58 -05:00
Bennet Bo Fenner
1e25e6b3cc agent: Improve fuzzy matching for @mentions (#28883)
Make fuzzy search in @-mention match paths and context kinds as well
(e.g., typing "sym" should let me select the "Symbols" label, as opposed
to just paths)

Release Notes:

- agent: Improve fuzzy-matching when using @mentions
2025-04-16 17:44:07 +00:00
Anthony Eid
f565994da9 debugger: Remove or move breakpoints on file deletion/rename (#28882)
Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <m@cole-miller.net>
2025-04-16 17:41:24 +00:00
Danilo Leal
db94d6d767 agent: Add item to open Prompt Library in the panel's menu (#28877)
Release Notes:

- agent: Added a menu item to open the Prompt Library from the panel's
dropdown menu on the top right.
2025-04-16 14:31:34 -03:00
Bennet Bo Fenner
456e54b87c agent: Add websearch tool (#28621)
Staff only for now. We'll work on making this usable for non zed.dev
users later

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-04-16 19:25:00 +02:00
5brian
2b277123be vim: Fix LineUp (#27754)
Closes #27423

Release Notes:

- vim: Fixed cursor scrolling off screen with `ctrl-y`.

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-04-16 17:24:24 +00:00
Marshall Bowers
bb0b2a5b7b collab: Remove has_predict_edits_feature_flag from LlmTokenClaims (#28879)
This PR removes the `has_predict_edits_feature_flag` field from the
`LlmTokenClaims`.

We are no longer reading this anywhere.

Release Notes:

- N/A
2025-04-16 17:11:18 +00:00
Piotr Osiewicz
5c2c6d7e5e toolchain: Respect currently focused file when querying toolchains (#28875)
Closes #21743


https://github.com/user-attachments/assets/0230f233-58a4-494c-90af-28ce82f9fc1d


Release Notes:

- Virtual environment picker now looks up virtual environment based on
parent directory of active file; this enables having multiple active
virtual environments in a single worktree.
2025-04-16 19:05:57 +02:00
Danilo Leal
4f58bdee28 agent: Add small design tweaks (#28874)
Some small adjustments to simplify the agent panel's design.

Release Notes:

- N/A
2025-04-16 13:03:36 -03:00
Kirill Bulatov
486a9e4d61 Fix more panics when removing excerpts (#28836)
Release Notes:

- Fixed a panic when an excerpt removed has an edit suggestion inlay in
it

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-16 15:33:28 +00:00
59 changed files with 454 additions and 632 deletions

3
Cargo.lock generated
View File

@@ -702,10 +702,8 @@ version = "0.1.0"
dependencies = [
"anyhow",
"assistant_tool",
"buffer_diff",
"chrono",
"collections",
"editor",
"feature_flags",
"futures 0.3.31",
"gpui",
@@ -714,7 +712,6 @@ dependencies = [
"itertools 0.14.0",
"language",
"language_model",
"multi_buffer",
"open",
"project",
"rand 0.8.5",

View File

@@ -630,6 +630,7 @@
"ctrl-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenConfiguration",
"ctrl-alt-p": "assistant::OpenPromptLibrary",
"ctrl-i": "agent::ToggleProfileSelector",
"ctrl-alt-/": "assistant::ToggleModelSelector",
"ctrl-shift-a": "agent::ToggleContextPicker",

View File

@@ -286,6 +286,7 @@
"cmd-alt-n": "agent::NewTextThread",
"cmd-shift-h": "agent::OpenHistory",
"cmd-alt-c": "agent::OpenConfiguration",
"cmd-alt-p": "assistant::OpenPromptLibrary",
"cmd-i": "agent::ToggleProfileSelector",
"cmd-alt-/": "assistant::ToggleModelSelector",
"cmd-shift-a": "agent::ToggleContextPicker",

View File

@@ -767,11 +767,10 @@ impl ActiveThread {
self.thread.read(cx).summary_or_default()
}
pub fn cancel_last_completion(&mut self, window: &mut Window, cx: &mut App) -> bool {
pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
self.last_error.take();
self.thread.update(cx, |thread, cx| {
thread.cancel_last_completion(Some(window.window_handle()), cx)
})
self.thread
.update(cx, |thread, cx| thread.cancel_last_completion(cx))
}
pub fn last_error(&self) -> Option<ThreadError> {
@@ -1144,7 +1143,7 @@ impl ActiveThread {
fn confirm_editing_message(
&mut self,
_: &menu::Confirm,
window: &mut Window,
_: &mut Window,
cx: &mut Context<Self>,
) {
let Some((message_id, state)) = self.editing_message.take() else {
@@ -1173,12 +1172,7 @@ impl ActiveThread {
}
self.thread.update(cx, |thread, cx| {
thread.send_to_model(
model.model,
RequestKind::Chat,
Some(window.window_handle()),
cx,
)
thread.send_to_model(model.model, RequestKind::Chat, cx)
});
cx.notify();
}
@@ -2353,6 +2347,9 @@ impl ActiveThread {
rendered.input.clone(),
tool_use_markdown_style(window, cx),
)
.code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false,
})
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
@@ -2379,6 +2376,9 @@ impl ActiveThread {
rendered.output.clone(),
tool_use_markdown_style(window, cx),
)
.code_block_renderer(markdown::CodeBlockRenderer::Default {
copy_button: false,
})
.on_url_click({
let workspace = self.workspace.clone();
move |text, window, cx| {
@@ -2556,7 +2556,7 @@ impl ActiveThread {
)
} else {
v_flex()
.my_3()
.my_2()
.rounded_lg()
.border_1()
.border_color(self.tool_card_border_color(cx))
@@ -2837,7 +2837,7 @@ impl ActiveThread {
&mut self,
tool_use_id: LanguageModelToolUseId,
_: &ClickEvent,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
@@ -2853,7 +2853,6 @@ impl ActiveThread {
c.input.clone(),
&c.messages,
c.tool.clone(),
Some(window.window_handle()),
cx,
);
});
@@ -2865,12 +2864,11 @@ impl ActiveThread {
tool_use_id: LanguageModelToolUseId,
tool_name: Arc<str>,
_: &ClickEvent,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let window_handle = window.window_handle();
self.thread.update(cx, |thread, cx| {
thread.deny_tool_use(tool_use_id, tool_name, Some(window_handle), cx);
thread.deny_tool_use(tool_use_id, tool_name, cx);
});
}

View File

@@ -337,9 +337,14 @@ impl AssistantPanel {
&self.thread_store
}
fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
fn cancel(
&mut self,
_: &editor::actions::Cancel,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.thread
.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
}
fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
@@ -1108,16 +1113,16 @@ impl AssistantPanel {
"New Text Thread",
NewTextThread.boxed_clone(),
)
.action("Settings", OpenConfiguration.boxed_clone())
.action("Prompt Library", Box::new(OpenPromptLibrary))
.action("Settings", Box::new(OpenConfiguration))
.separator()
.action(
"Install MCPs",
zed_actions::Extensions {
Box::new(zed_actions::Extensions {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::ContextServers,
),
}
.boxed_clone(),
}),
)
},
))
@@ -1556,14 +1561,18 @@ impl AssistantPanel {
cx: &mut Context<Self>,
) -> AnyElement {
let error_message = match plan {
Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.",
Plan::ZedPro => {
"Model request limit reached. Upgrade to usage-based billing for more requests."
}
Plan::ZedProTrial => {
"Model request limit reached. Upgrade to Zed Pro for more requests."
}
Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.",
};
let call_to_action = match plan {
Plan::Free => "Upgrade to Zed Pro",
Plan::ZedPro => "Upgrade to usage-based billing",
Plan::ZedProTrial => "Upgrade to Zed Pro",
Plan::Free => "Upgrade to Zed Pro",
};
v_flex()
@@ -1783,10 +1792,27 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
fn quote_selection(
&self,
_workspace: &mut Workspace,
_creases: Vec<(String, String)>,
_window: &mut Window,
_cx: &mut Context<Workspace>,
workspace: &mut Workspace,
creases: Vec<(String, String)>,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
if !panel.focus_handle(cx).contains_focused(window, cx) {
workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
}
panel.update(cx, |_, cx| {
// Wait to create a new context until the workspace is no longer
// being updated.
cx.defer_in(window, move |panel, window, cx| {
if let Some(context) = panel.active_context_editor() {
context.update(cx, |context, cx| context.quote_creases(creases, window, cx));
};
});
});
}
}

View File

@@ -8,6 +8,7 @@ use std::sync::atomic::AtomicBool;
use anyhow::Result;
use editor::{CompletionProvider, Editor, ExcerptId};
use file_icons::FileIcons;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{App, Entity, Task, WeakEntity};
use http_client::HttpClientWithUrl;
use language::{Buffer, CodeLabel, HighlightId};
@@ -37,7 +38,24 @@ pub(crate) enum Match {
File(FileMatch),
Thread(ThreadMatch),
Fetch(SharedString),
Mode(ContextPickerMode),
Mode(ModeMatch),
}
pub struct ModeMatch {
mat: Option<StringMatch>,
mode: ContextPickerMode,
}
impl Match {
pub fn score(&self) -> f64 {
match self {
Match::File(file) => file.mat.score,
Match::Mode(mode) => mode.mat.as_ref().map(|mat| mat.score).unwrap_or(1.),
Match::Thread(_) => 1.,
Match::Symbol(_) => 1.,
Match::Fetch(_) => 1.,
}
}
}
fn search(
@@ -126,19 +144,54 @@ fn search(
matches.extend(
supported_context_picker_modes(&thread_store)
.into_iter()
.map(Match::Mode),
.map(|mode| Match::Mode(ModeMatch { mode, mat: None })),
);
Task::ready(matches)
} else {
let executor = cx.background_executor().clone();
let search_files_task =
search_files(query.clone(), cancellation_flag.clone(), &workspace, cx);
let modes = supported_context_picker_modes(&thread_store);
let mode_candidates = modes
.iter()
.enumerate()
.map(|(ix, mode)| StringMatchCandidate::new(ix, mode.mention_prefix()))
.collect::<Vec<_>>();
cx.background_spawn(async move {
search_files_task
let mut matches = search_files_task
.await
.into_iter()
.map(Match::File)
.collect()
.collect::<Vec<_>>();
let mode_matches = fuzzy::match_strings(
&mode_candidates,
&query,
false,
100,
&Arc::new(AtomicBool::default()),
executor,
)
.await;
matches.extend(mode_matches.into_iter().map(|mat| {
Match::Mode(ModeMatch {
mode: modes[mat.candidate_id],
mat: Some(mat),
})
}));
matches.sort_by(|a, b| {
b.score()
.partial_cmp(&a.score())
.unwrap_or(std::cmp::Ordering::Equal)
});
matches
})
}
}
@@ -548,7 +601,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
context_store.clone(),
http_client.clone(),
)),
Match::Mode(mode) => {
Match::Mode(ModeMatch { mode, .. }) => {
Some(Self::completion_for_mode(source_range.clone(), mode))
}
})

View File

@@ -263,7 +263,6 @@ impl MessageEditor {
let context_store = self.context_store.clone();
let git_store = self.project.read(cx).git_store().clone();
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
let window_handle = window.window_handle();
cx.spawn(async move |this, cx| {
let checkpoint = checkpoint.await.ok();
@@ -298,7 +297,7 @@ impl MessageEditor {
// Send to model after summaries are done
thread
.update(cx, |thread, cx| {
thread.send_to_model(model, request_kind, Some(window_handle), cx);
thread.send_to_model(model, request_kind, cx);
})
.log_err();
})
@@ -306,9 +305,9 @@ impl MessageEditor {
}
fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let cancelled = self.thread.update(cx, |thread, cx| {
thread.cancel_last_completion(Some(window.window_handle()), cx)
});
let cancelled = self
.thread
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
if cancelled {
self.set_editor_is_expanded(false, cx);

View File

@@ -13,9 +13,7 @@ use feature_flags::{self, FeatureFlagAppExt};
use futures::future::Shared;
use futures::{FutureExt, StreamExt as _};
use git::repository::DiffType;
use gpui::{
AnyWindowHandle, App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
};
use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
use language_model::{
ConfiguredModel, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
LanguageModelKnownError, LanguageModelRegistry, LanguageModelRequest,
@@ -849,7 +847,6 @@ impl Thread {
&mut self,
model: Arc<dyn LanguageModel>,
request_kind: RequestKind,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
let mut request = self.to_completion_request(request_kind, cx);
@@ -876,7 +873,7 @@ impl Thread {
};
}
self.stream_completion(request, model, window, cx);
self.stream_completion(request, model, cx);
}
pub fn used_tools_since_last_user_message(&self) -> bool {
@@ -1022,7 +1019,6 @@ impl Thread {
&mut self,
request: LanguageModelRequest,
model: Arc<dyn LanguageModel>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
let pending_completion_id = post_inc(&mut self.completion_count);
@@ -1150,7 +1146,7 @@ impl Thread {
match result.as_ref() {
Ok(stop_reason) => match stop_reason {
StopReason::ToolUse => {
let tool_uses = thread.use_pending_tools(window, cx);
let tool_uses = thread.use_pending_tools(cx);
cx.emit(ThreadEvent::UsePendingTools { tool_uses });
}
StopReason::EndTurn => {}
@@ -1195,7 +1191,7 @@ impl Thread {
}));
}
thread.cancel_last_completion(window, cx);
thread.cancel_last_completion(cx);
}
}
cx.emit(ThreadEvent::Stopped(result.map_err(Arc::new)));
@@ -1361,11 +1357,7 @@ impl Thread {
)
}
pub fn use_pending_tools(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) -> Vec<PendingToolUse> {
pub fn use_pending_tools(&mut self, cx: &mut Context<Self>) -> Vec<PendingToolUse> {
self.auto_capture_telemetry(cx);
let request = self.to_completion_request(RequestKind::Chat, cx);
let messages = Arc::new(request.messages);
@@ -1397,7 +1389,6 @@ impl Thread {
tool_use.input.clone(),
&messages,
tool,
window,
cx,
);
}
@@ -1414,10 +1405,9 @@ impl Thread {
input: serde_json::Value,
messages: &[LanguageModelRequestMessage],
tool: Arc<dyn Tool>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Thread>,
) {
let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, window, cx);
let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, cx);
self.tool_use
.run_pending_tool(tool_use_id, ui_text.into(), task);
}
@@ -1428,7 +1418,6 @@ impl Thread {
messages: &[LanguageModelRequestMessage],
input: serde_json::Value,
tool: Arc<dyn Tool>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Thread>,
) -> Task<()> {
let tool_name: Arc<str> = tool.name().into();
@@ -1441,7 +1430,6 @@ impl Thread {
messages,
self.project.clone(),
self.action_log.clone(),
window,
cx,
)
};
@@ -1464,7 +1452,7 @@ impl Thread {
output,
cx,
);
thread.tool_finished(tool_use_id, pending_tool_use, false, window, cx);
thread.tool_finished(tool_use_id, pending_tool_use, false, cx);
})
.ok();
}
@@ -1476,7 +1464,6 @@ impl Thread {
tool_use_id: LanguageModelToolUseId,
pending_tool_use: Option<PendingToolUse>,
canceled: bool,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
if self.all_tools_finished() {
@@ -1484,7 +1471,7 @@ impl Thread {
if let Some(ConfiguredModel { model, .. }) = model_registry.default_model() {
self.attach_tool_results(cx);
if !canceled {
self.send_to_model(model, RequestKind::Chat, window, cx);
self.send_to_model(model, RequestKind::Chat, cx);
}
}
}
@@ -1511,11 +1498,7 @@ impl Thread {
/// Cancels the last pending completion, if there are any pending.
///
/// Returns whether a completion was canceled.
pub fn cancel_last_completion(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) -> bool {
pub fn cancel_last_completion(&mut self, cx: &mut Context<Self>) -> bool {
let canceled = if self.pending_completions.pop().is_some() {
true
} else {
@@ -1526,7 +1509,6 @@ impl Thread {
pending_tool_use.id.clone(),
Some(pending_tool_use),
true,
window,
cx,
);
}
@@ -1950,7 +1932,6 @@ impl Thread {
&mut self,
tool_use_id: LanguageModelToolUseId,
tool_name: Arc<str>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
let err = Err(anyhow::anyhow!(
@@ -1959,7 +1940,7 @@ impl Thread {
self.tool_use
.insert_tool_output(tool_use_id.clone(), tool_name, err, cx);
self.tool_finished(tool_use_id.clone(), None, true, window, cx);
self.tool_finished(tool_use_id.clone(), None, true, cx);
}
}

View File

@@ -191,15 +191,12 @@ impl RenderOnce for ContextPill {
ContextPill::Suggested {
name,
icon_path: _,
kind,
kind: _,
focused,
on_click,
} => base_pill
.cursor_pointer()
.pr_1()
.when(*focused, |this| {
this.bg(color.element_background.opacity(0.5))
})
.border_dashed()
.border_color(if *focused {
color.border_focused
@@ -207,30 +204,17 @@ impl RenderOnce for ContextPill {
color.border
})
.hover(|style| style.bg(color.element_hover.opacity(0.5)))
.when(*focused, |this| {
this.bg(color.element_background.opacity(0.5))
})
.child(
div().px_0p5().max_w_64().child(
div().max_w_64().child(
Label::new(name.clone())
.size(LabelSize::Small)
.color(Color::Muted)
.truncate(),
),
)
.child(
Label::new(match kind {
ContextKind::File => "Active Tab",
ContextKind::Thread
| ContextKind::Directory
| ContextKind::FetchedUrl
| ContextKind::Symbol => "Active",
})
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.child(
Icon::new(IconName::Plus)
.size(IconSize::XSmall)
.into_any_element(),
)
.tooltip(|window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
})

View File

@@ -10,7 +10,6 @@ use std::sync::Arc;
use anyhow::Result;
use gpui::AnyElement;
use gpui::AnyWindowHandle;
use gpui::Context;
use gpui::IntoElement;
use gpui::Window;
@@ -156,7 +155,6 @@ pub trait Tool: 'static + Send + Sync {
messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult;
}

View File

@@ -14,10 +14,8 @@ path = "src/assistant_tools.rs"
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
collections.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
@@ -26,8 +24,6 @@ http_client.workspace = true
itertools.workspace = true
language.workspace = true
language_model.workspace = true
multi_buffer.workspace = true
open = { workspace = true }
project.workspace = true
regex.workspace = true
schemars.workspace = true
@@ -35,12 +31,12 @@ serde.workspace = true
serde_json.workspace = true
ui.workspace = true
util.workspace = true
worktree.workspace = true
open = { workspace = true }
web_search.workspace = true
workspace-hack.workspace = true
worktree.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
collections = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }

View File

@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult, ToolWorkingSet};
use futures::future::join_all;
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -218,7 +218,6 @@ impl Tool for BatchTool {
messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<BatchToolInput>(input) {
@@ -259,16 +258,7 @@ impl Tool for BatchTool {
let action_log = action_log.clone();
let messages = messages.clone();
let tool_result = cx
.update(|cx| {
tool.run(
invocation.input,
&messages,
project,
action_log,
window.clone(),
cx,
)
})
.update(|cx| tool.run(invocation.input, &messages, project, action_log, cx))
.map_err(|err| anyhow!("Failed to start tool '{}': {}", tool_name, err))?;
tasks.push(tool_result.output);

View File

@@ -1,6 +1,6 @@
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::{self, Anchor, Buffer, ToPointUtf16};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{self, LspAction, Project};
@@ -140,7 +140,6 @@ impl Tool for CodeActionTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CodeActionToolInput>(input) {

View File

@@ -6,7 +6,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use collections::IndexMap;
use gpui::{AnyWindowHandle, App, AsyncApp, Entity, Task};
use gpui::{App, AsyncApp, Entity, Task};
use language::{OutlineItem, ParseStatus, Point};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{Project, Symbol};
@@ -128,7 +128,6 @@ impl Tool for CodeSymbolsTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CodeSymbolsInput>(input) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use itertools::Itertools;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -102,7 +102,6 @@ impl Tool for ContentsTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<ContentsToolInput>(input) {

View File

@@ -1,7 +1,6 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::AnyWindowHandle;
use gpui::{App, AppContext, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -77,7 +76,6 @@ impl Tool for CopyPathTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CopyPathToolInput>(input) {

View File

@@ -1,7 +1,6 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::AnyWindowHandle;
use gpui::{App, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -68,7 +67,6 @@ impl Tool for CreateDirectoryTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CreateDirectoryToolInput>(input) {

View File

@@ -1,7 +1,6 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::AnyWindowHandle;
use gpui::{App, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -73,7 +72,6 @@ impl Tool for CreateFileTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CreateFileToolInput>(input) {

View File

@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::{SinkExt, StreamExt, channel::mpsc};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{Project, ProjectPath};
use schemars::JsonSchema;
@@ -62,7 +62,6 @@ impl Tool for DeletePathTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let path_str = match serde_json::from_value::<DeletePathToolInput>(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::{DiagnosticSeverity, OffsetRangeExt};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -82,7 +82,6 @@ impl Tool for DiagnosticsTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
match serde_json::from_value::<DiagnosticsToolInput>(input)

View File

@@ -6,7 +6,7 @@ use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow, bail};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::AsyncReadExt as _;
use gpui::{AnyWindowHandle, App, AppContext as _, Entity, Task};
use gpui::{App, AppContext as _, Entity, Task};
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
use http_client::{AsyncBody, HttpClientWithUrl};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -145,7 +145,6 @@ impl Tool for FetchTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<FetchToolInput>(input) {

View File

@@ -1,25 +1,13 @@
use crate::{replace::replace_with_flexible_indent, schema::json_schema_for};
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolCard, ToolResult, ToolUseStatus};
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use editor::{Editor, MultiBuffer, PathKey};
use gpui::{
AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, IntoElement, Task, Window,
};
use language::{
self, Anchor, Buffer, Capability, LanguageRegistry, LineEnding, OffsetRangeExt as _, Rope,
TextBuffer,
};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{App, AppContext, AsyncApp, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use ui::{Tooltip, prelude::*};
use util::ResultExt;
use std::{path::PathBuf, sync::Arc};
use ui::IconName;
use crate::replace::replace_exact;
@@ -144,274 +132,6 @@ pub struct FindReplaceFileToolInput {
pub replace: String,
}
pub struct FindReplaceFileToolCard {
path: PathBuf,
description: String,
editor: Entity<Editor>,
multibuffer: Entity<MultiBuffer>,
project: Entity<Project>,
diff_task: Option<Task<Result<()>>>,
}
impl FindReplaceFileToolCard {
fn new(
path: PathBuf,
description: String,
project: Entity<Project>,
window: &mut Window,
cx: &mut App,
) -> Self {
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadOnly));
let editor = cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
editor.disable_inline_diagnostics();
editor.set_expand_all_diff_hunks(cx);
editor
});
Self {
path,
description,
project,
editor,
multibuffer,
diff_task: None,
}
}
fn set_diff(
&mut self,
path: Arc<Path>,
old_text: String,
new_text: String,
cx: &mut Context<Self>,
) {
let language_registry = self.project.read(cx).languages().clone();
self.diff_task = Some(cx.spawn(async move |this, cx| {
let buffer = build_buffer(new_text, path.clone(), &language_registry, cx).await?;
let buffer_diff = build_buffer_diff(old_text, &buffer, &language_registry, cx).await?;
this.update(cx, |this, cx| {
this.multibuffer.update(cx, |multibuffer, cx| {
let snapshot = buffer.read(cx).snapshot();
let diff = buffer_diff.read(cx);
let diff_hunk_ranges = diff
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
.collect::<Vec<_>>();
let _is_newly_added = multibuffer.set_excerpts_for_path(
PathKey::for_buffer(&buffer, cx),
buffer,
diff_hunk_ranges,
editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx,
);
multibuffer.add_diff(buffer_diff, cx);
});
cx.notify();
})
}));
}
}
async fn build_buffer(
mut text: String,
path: Arc<Path>,
language_registry: &Arc<language::LanguageRegistry>,
cx: &mut AsyncApp,
) -> Result<Entity<Buffer>> {
let line_ending = LineEnding::detect(&text);
LineEnding::normalize(&mut text);
let text = Rope::from(text);
let language = cx
.update(|_cx| language_registry.language_for_file_path(&path))?
.await
.ok();
let buffer = cx.new(|cx| {
let buffer = TextBuffer::new_normalized(
0,
cx.entity_id().as_non_zero_u64().into(),
line_ending,
text,
);
let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
buffer.set_language(language, cx);
buffer
})?;
Ok(buffer)
}
async fn build_buffer_diff(
mut old_text: String,
buffer: &Entity<Buffer>,
language_registry: &Arc<LanguageRegistry>,
cx: &mut AsyncApp,
) -> Result<Entity<BufferDiff>> {
LineEnding::normalize(&mut old_text);
let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
let base_buffer = cx
.update(|cx| {
Buffer::build_snapshot(
old_text.clone().into(),
buffer.language().cloned(),
Some(language_registry.clone()),
cx,
)
})?
.await;
let diff_snapshot = cx
.update(|cx| {
BufferDiffSnapshot::new_with_base_buffer(
buffer.text.clone(),
Some(old_text.into()),
base_buffer,
cx,
)
})?
.await;
cx.new(|cx| {
let mut diff = BufferDiff::new(&buffer.text, cx);
diff.set_snapshot(diff_snapshot, &buffer.text, cx);
diff
})
}
impl ToolCard for FindReplaceFileToolCard {
fn render(
&mut self,
status: &ToolUseStatus,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let header = h_flex()
.id("tool-label-container")
.gap_1p5()
.max_w_full()
.overflow_x_scroll()
.child(
Icon::new(IconName::Pencil)
.size(IconSize::XSmall)
.color(Color::Muted),
)
.child(Label::new("Edit ").size(LabelSize::Small))
.child(
div()
.size(px(3.))
.rounded_full()
.bg(cx.theme().colors().text),
)
.child(Label::new(self.path.display().to_string()).size(LabelSize::Small))
.into_any_element();
let header2 = h_flex()
.id("code-block-header-label")
.w_full()
.max_w_full()
.px_1()
.gap_0p5()
.cursor_pointer()
.rounded_sm()
.hover(|item| item.bg(cx.theme().colors().element_hover.opacity(0.5)))
.tooltip(Tooltip::text("Jump to File"));
// todo!
// .child(
// h_flex()
// .gap_0p5()
// .children(
// file_icons::FileIcons::get_icon(&path_range.path, cx)
// .map(Icon::from_path)
// .map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)),
// )
// .child(content)
// .child(
// Icon::new(IconName::ArrowUpRight)
// .size(IconSize::XSmall)
// .color(Color::Ignored),
// ),
// )
// .on_click({
// let path_range = path_range.clone();
// move |_, window, cx| {
// workspace
// .update(cx, {
// |workspace, cx| {
// if let Some(project_path) = workspace
// .project()
// .read(cx)
// .find_project_path(&path_range.path, cx)
// {
// let target = path_range.range.as_ref().map(|range| {
// Point::new(
// // Line number is 1-based
// range.start.line.saturating_sub(1),
// range.start.col.unwrap_or(0),
// )
// });
// let open_task =
// workspace.open_path(project_path, None, true, window, cx);
// window
// .spawn(cx, async move |cx| {
// let item = open_task.await?;
// if let Some(target) = target {
// if let Some(active_editor) =
// item.downcast::<Editor>()
// {
// active_editor
// .downgrade()
// .update_in(cx, |editor, window, cx| {
// editor.go_to_singleton_buffer_point(
// target, window, cx,
// );
// })
// .log_err();
// }
// }
// anyhow::Ok(())
// })
// .detach_and_log_err(cx);
// }
// }
// })
// .ok();
// }
// })
// .into_any_element();
let content = match status {
ToolUseStatus::NeedsConfirmation | ToolUseStatus::Pending | ToolUseStatus::Running => {
div()
// .child(Label::new(&self.description).size(LabelSize::Small))
.into_any_element()
}
ToolUseStatus::Finished(str) => {
dbg!(&str);
self.editor.clone().into_any_element()
}
ToolUseStatus::Error(error) => div()
.child(
Label::new(error.to_string())
.color(Color::Error)
.size(LabelSize::Small),
)
.into_any_element(),
};
v_flex()
.my_2()
.border_1()
.border_color(cx.theme().colors().border)
.rounded_sm()
.gap_1()
.child(header)
.child(content)
}
}
pub struct FindReplaceFileTool;
impl Tool for FindReplaceFileTool {
@@ -448,32 +168,14 @@ impl Tool for FindReplaceFileTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<FindReplaceFileToolInput>(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))).into(),
};
let card = window.and_then(|window| {
window
.update(cx, |_, window, cx| {
cx.new(|cx| {
FindReplaceFileToolCard::new(
input.path.clone(),
input.display_description.clone(),
project.clone(),
window,
cx,
)
})
})
.ok()
});
let output = cx.spawn({
let card = card.clone();
async move |cx: &mut AsyncApp| {
cx.spawn(async move |cx: &mut AsyncApp| {
let project_path = project.read_with(cx, |project, cx| {
project
.find_project_path(&input.path, cx)
@@ -481,7 +183,7 @@ impl Tool for FindReplaceFileTool {
})??;
let buffer = project
.update(cx, |project, cx| project.open_buffer(project_path.clone(), cx))?
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
.await?;
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
@@ -553,29 +255,14 @@ impl Tool for FindReplaceFileTool {
project.save_buffer(buffer, cx)
})?.await?;
let new_text = snapshot.text();
let diff_str = cx.background_spawn({
// todo! probably don't need this
let old_text = old_text.clone();
let new_text = new_text.clone();
async move {
language::unified_diff(&old_text, &new_text)
}
let diff_str = cx.background_spawn(async move {
let new_text = snapshot.text();
language::unified_diff(&old_text, &new_text)
}).await;
if let Some(card) = card {
card.update(cx, |card, cx| {
card.set_diff(project_path.path.clone(), old_text, new_text, cx);
}).log_err();
}
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str))
}});
ToolResult {
output,
card: card.map(|card| card.into()),
}
}).into()
}
}

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -76,7 +76,6 @@ impl Tool for ListDirectoryTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<ListDirectoryToolInput>(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -89,7 +89,6 @@ impl Tool for MovePathTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<MovePathToolInput>(input) {

View File

@@ -4,7 +4,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use chrono::{Local, Utc};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -59,7 +59,6 @@ impl Tool for NowTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
_cx: &mut App,
) -> ToolResult {
let input: NowToolInput = match serde_json::from_value(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -52,7 +52,6 @@ impl Tool for OpenTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input: OpenToolInput = match serde_json::from_value(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -70,7 +70,6 @@ impl Tool for PathSearchTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let (offset, glob) = match serde_json::from_value::<PathSearchToolInput>(input) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use itertools::Itertools;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -87,7 +87,6 @@ impl Tool for ReadFileTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<ReadFileToolInput>(input) {

View File

@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::StreamExt;
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::OffsetRangeExt;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{
@@ -91,7 +91,6 @@ impl Tool for RegexSearchTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
const CONTEXT_LINES: u32 = 2;

View File

@@ -1,6 +1,6 @@
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::{self, Buffer, ToPointUtf16};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -87,7 +87,6 @@ impl Tool for RenameTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<RenameToolInput>(input) {

View File

@@ -1,6 +1,6 @@
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AsyncApp, Entity, Task};
use gpui::{App, AsyncApp, Entity, Task};
use language::{self, Anchor, Buffer, BufferSnapshot, Location, Point, ToPoint, ToPointUtf16};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -121,7 +121,6 @@ impl Tool for SymbolInfoTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<SymbolInfoToolInput>(input) {

View File

@@ -3,7 +3,7 @@ use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::io::BufReader;
use futures::{AsyncBufReadExt, AsyncReadExt, FutureExt};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -78,7 +78,6 @@ impl Tool for TerminalTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input: TerminalToolInput = match serde_json::from_value(input) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -50,7 +50,6 @@ impl Tool for ThinkingTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
_cx: &mut App,
) -> ToolResult {
// This tool just "thinks out loud" and doesn't perform any actions.

View File

@@ -5,8 +5,8 @@ use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolCard, ToolResult, ToolUseStatus};
use futures::{FutureExt, TryFutureExt};
use gpui::{
Animation, AnimationExt, AnyWindowHandle, App, AppContext, Context, Entity, IntoElement, Task,
Window, pulsating_between,
Animation, AnimationExt, App, AppContext, Context, Entity, IntoElement, Task, Window,
pulsating_between,
};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -55,7 +55,6 @@ impl Tool for WebSearchTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<WebSearchToolInput>(input) {

View File

@@ -0,0 +1,4 @@
alter table subscription_usages
add column plan text not null;
create index ix_subscription_usages_on_plan on subscription_usages (plan);

View File

@@ -1,4 +1,5 @@
use crate::db::UserId;
use crate::db::billing_subscription::SubscriptionKind;
use sea_orm::entity::prelude::*;
use time::PrimitiveDateTime;
@@ -10,6 +11,7 @@ pub struct Model {
pub user_id: UserId,
pub period_start_at: PrimitiveDateTime,
pub period_end_at: PrimitiveDateTime,
pub plan: SubscriptionKind,
pub model_requests: i32,
pub edit_predictions: i32,
}

View File

@@ -25,7 +25,6 @@ pub struct LlmTokenClaims {
pub is_staff: bool,
pub has_llm_closed_beta_feature_flag: bool,
pub bypass_account_age_check: bool,
pub has_predict_edits_feature_flag: bool,
pub has_llm_subscription: bool,
pub max_monthly_spend_in_cents: u32,
pub custom_llm_monthly_allowance_in_cents: Option<u32>,
@@ -70,9 +69,6 @@ impl LlmTokenClaims {
bypass_account_age_check: feature_flags
.iter()
.any(|flag| flag == "bypass-account-age-check"),
has_predict_edits_feature_flag: feature_flags
.iter()
.any(|flag| flag == "predict-edits"),
has_llm_subscription: has_legacy_llm_subscription,
max_monthly_spend_in_cents: billing_preferences
.map_or(DEFAULT_MAX_MONTHLY_SPEND.0, |preferences| {

View File

@@ -3707,7 +3707,9 @@ async fn count_language_model_tokens(
let rate_limit: Box<dyn RateLimit> = match session.current_plan(&session.db().await).await? {
proto::Plan::ZedPro => Box::new(ZedProCountLanguageModelTokensRateLimit),
proto::Plan::Free => Box::new(FreeCountLanguageModelTokensRateLimit),
proto::Plan::Free | proto::Plan::ZedProTrial => {
Box::new(FreeCountLanguageModelTokensRateLimit)
}
};
session
@@ -3827,7 +3829,7 @@ async fn compute_embeddings(
let rate_limit: Box<dyn RateLimit> = match session.current_plan(&session.db().await).await? {
proto::Plan::ZedPro => Box::new(ZedProComputeEmbeddingsRateLimit),
proto::Plan::Free => Box::new(FreeComputeEmbeddingsRateLimit),
proto::Plan::Free | proto::Plan::ZedProTrial => Box::new(FreeComputeEmbeddingsRateLimit),
};
session

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::{Result, anyhow, bail};
use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use icons::IconName;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -77,7 +77,6 @@ impl Tool for ContextServerTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {

View File

@@ -49,8 +49,8 @@ use language::{
};
use lsp::DiagnosticSeverity;
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
RowInfo, ToOffset, ToPoint,
Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferPoint, MultiBufferRow,
MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
};
use serde::Deserialize;
use std::{
@@ -574,6 +574,21 @@ impl DisplayMap {
self.block_map.read(snapshot, edits);
}
pub fn remove_inlays_for_excerpts(&mut self, excerpts_removed: &[ExcerptId]) {
let to_remove = self
.inlay_map
.current_inlays()
.filter_map(|inlay| {
if excerpts_removed.contains(&inlay.position.excerpt_id) {
Some(inlay.id)
} else {
None
}
})
.collect::<Vec<_>>();
self.inlay_map.splice(&to_remove, Vec::new());
}
fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
let language = buffer

View File

@@ -4170,10 +4170,13 @@ impl Editor {
if let Some(InlaySplice {
to_remove,
to_insert,
}) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
}) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
{
self.splice_inlays(&to_remove, to_insert, cx);
}
self.display_map.update(cx, |display_map, _| {
display_map.remove_inlays_for_excerpts(&excerpts_removed)
});
return;
}
InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),

View File

@@ -555,12 +555,12 @@ impl InlayHintCache {
/// Completely forget of certain excerpts that were removed from the multibuffer.
pub(super) fn remove_excerpts(
&mut self,
excerpts_removed: Vec<ExcerptId>,
excerpts_removed: &[ExcerptId],
) -> Option<InlaySplice> {
let mut to_remove = Vec::new();
for excerpt_to_remove in excerpts_removed {
self.update_tasks.remove(&excerpt_to_remove);
if let Some(cached_hints) = self.hints.remove(&excerpt_to_remove) {
self.update_tasks.remove(excerpt_to_remove);
if let Some(cached_hints) = self.hints.remove(excerpt_to_remove) {
let cached_hints = cached_hints.read();
to_remove.extend(cached_hints.ordered_hints.iter().copied());
}

View File

@@ -271,12 +271,12 @@ fn main() {
match judge_result {
Ok(judge_output) => {
const SCORES: [&str; 6] = ["💀", "😭", "😔", "😐", "🙂", "🤩"];
let score: u32 = judge_output.score;
let score_index = (score.min(5)) as usize;
println!(
"{} {}{}",
SCORES[judge_output.score.min(5) as usize],
example.log_prefix,
judge_output.score,
SCORES[score_index], example.log_prefix, judge_output.score,
);
judge_scores.push(judge_output.score);
}
@@ -304,7 +304,6 @@ fn main() {
std::thread::sleep(std::time::Duration::from_secs(2));
// Flush telemetry events before exiting
app_state.client.telemetry().flush_events();
cx.update(|cx| cx.quit())
@@ -330,7 +329,6 @@ async fn run_example(
for round in 0..judge_repetitions {
let judge_result = example.judge(model.clone(), diff.clone(), round, cx).await;
// Log telemetry for this judge result
if let Ok(judge_output) = &judge_result {
let cohort_id = example
.output_file_path
@@ -339,6 +337,9 @@ async fn run_example(
.map(|name| name.to_string_lossy().to_string())
.unwrap_or(chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string());
let path = std::path::Path::new(".");
let commit_id = get_current_commit_id(path).await.unwrap_or_default();
telemetry::event!(
"Agent Eval Completed",
cohort_id = cohort_id,
@@ -353,7 +354,8 @@ async fn run_example(
model_provider = model.provider_id().to_string(),
repository_url = example.base.url.clone(),
repository_revision = example.base.revision.clone(),
diagnostics_summary = run_output.diagnostics
diagnostics_summary = run_output.diagnostics,
commit_id = commit_id
);
}
@@ -524,3 +526,13 @@ pub fn authenticate_model_provider(
let model_provider = model_registry.provider(&provider_id).unwrap();
model_provider.authenticate(cx)
}
pub async fn get_current_commit_id(repo_path: &Path) -> Option<String> {
(run_git(repo_path, &["rev-parse", "HEAD"]).await).ok()
}
pub fn get_current_commit_id_sync(repo_path: &Path) -> String {
futures::executor::block_on(async {
get_current_commit_id(repo_path).await.unwrap_or_default()
})
}

View File

@@ -154,6 +154,9 @@ impl fmt::Display for ModelRequestLimitReachedError {
Plan::ZedPro => {
"Model request limit reached. Upgrade to usage-based billing for more requests."
}
Plan::ZedProTrial => {
"Model request limit reached. Upgrade to Zed Pro for more requests."
}
};
write!(f, "{message}")

View File

@@ -546,7 +546,6 @@ impl PickerDelegate for LanguageModelPickerDelegate {
use feature_flags::FeatureFlagAppExt;
let plan = proto::Plan::ZedPro;
let is_trial = false;
Some(
h_flex()
@@ -558,7 +557,6 @@ impl PickerDelegate for LanguageModelPickerDelegate {
.justify_between()
.when(cx.has_flag::<ZedPro>(), |this| {
this.child(match plan {
// Already a Zed Pro subscriber
Plan::ZedPro => Button::new("zed-pro", "Zed Pro")
.icon(IconName::ZedAssistant)
.icon_size(IconSize::Small)
@@ -568,10 +566,9 @@ impl PickerDelegate for LanguageModelPickerDelegate {
window
.dispatch_action(Box::new(zed_actions::OpenAccountSettings), cx)
}),
// Free user
Plan::Free => Button::new(
Plan::Free | Plan::ZedProTrial => Button::new(
"try-pro",
if is_trial {
if plan == Plan::ZedProTrial {
"Upgrade to Pro"
} else {
"Try Pro"

View File

@@ -71,7 +71,7 @@ impl Anchor {
if self_excerpt_id == ExcerptId::min() || self_excerpt_id == ExcerptId::max() {
return Ordering::Equal;
}
if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
if let Some(excerpt) = snapshot.excerpt(self_excerpt_id) {
let text_cmp = self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer);
if text_cmp.is_ne() {
return text_cmp;

View File

@@ -5170,6 +5170,7 @@ impl MultiBufferSnapshot {
excerpt_id: ExcerptId,
text_anchor: text::Anchor,
) -> Option<Anchor> {
let excerpt_id = self.latest_excerpt_id(excerpt_id);
let locator = self.excerpt_locator_for_id(excerpt_id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
cursor.seek(locator, Bias::Left, &());
@@ -6041,7 +6042,7 @@ impl MultiBufferSnapshot {
return &entry.locator;
}
}
panic!("invalid excerpt id {:?}", id)
panic!("invalid excerpt id {id:?}")
}
}

View File

@@ -18,7 +18,7 @@ use text::{Point, PointUtf16};
use crate::{Project, ProjectPath, buffer_store::BufferStore, worktree_store::WorktreeStore};
mod breakpoints_in_file {
use language::BufferEvent;
use language::{BufferEvent, DiskState};
use super::*;
@@ -32,8 +32,9 @@ mod breakpoints_in_file {
impl BreakpointsInFile {
pub(super) fn new(buffer: Entity<Buffer>, cx: &mut Context<BreakpointStore>) -> Self {
let subscription =
Arc::from(cx.subscribe(&buffer, |_, buffer, event, cx| match event {
let subscription = Arc::from(cx.subscribe(
&buffer,
|breakpoint_store, buffer, event, cx| match event {
BufferEvent::Saved => {
if let Some(abs_path) = BreakpointStore::abs_path_from_buffer(&buffer, cx) {
cx.emit(BreakpointStoreEvent::BreakpointsUpdated(
@@ -42,8 +43,44 @@ mod breakpoints_in_file {
));
}
}
BufferEvent::FileHandleChanged => {
let entity_id = buffer.entity_id();
if buffer.read(cx).file().is_none_or(|f| f.disk_state() == DiskState::Deleted) {
breakpoint_store.breakpoints.retain(|_, breakpoints_in_file| {
breakpoints_in_file.buffer.entity_id() != entity_id
});
cx.notify();
return;
}
if let Some(abs_path) = BreakpointStore::abs_path_from_buffer(&buffer, cx) {
if breakpoint_store.breakpoints.contains_key(&abs_path) {
return;
}
if let Some(old_path) = breakpoint_store
.breakpoints
.iter()
.find(|(_, in_file)| in_file.buffer.entity_id() == entity_id)
.map(|values| values.0)
.cloned()
{
let Some(breakpoints_in_file) =
breakpoint_store.breakpoints.remove(&old_path) else {
log::error!("Couldn't get breakpoints in file from old path during buffer rename handling");
return;
};
breakpoint_store.breakpoints.insert(abs_path, breakpoints_in_file);
cx.notify();
}
}
}
_ => {}
}));
},
));
BreakpointsInFile {
buffer,

View File

@@ -3094,6 +3094,9 @@ impl Project {
.map(|lister| lister.term())
}
pub fn toolchain_store(&self) -> Option<Entity<ToolchainStore>> {
self.toolchain_store.clone()
}
pub fn activate_toolchain(
&self,
path: ProjectPath,

View File

@@ -55,6 +55,7 @@ impl ToolchainStore {
});
Self(ToolchainStoreInner::Local(entity, subscription))
}
pub(super) fn remote(project_id: u64, client: AnyProtoClient, cx: &mut App) -> Self {
Self(ToolchainStoreInner::Remote(
cx.new(|_| RemoteToolchainStore { client, project_id }),
@@ -285,7 +286,7 @@ struct LocalStore(WeakEntity<LocalToolchainStore>);
struct RemoteStore(WeakEntity<RemoteToolchainStore>);
#[derive(Clone)]
pub(crate) enum ToolchainStoreEvent {
pub enum ToolchainStoreEvent {
ToolchainActivated,
}

View File

@@ -18,6 +18,7 @@ message GetPrivateUserInfoResponse {
enum Plan {
Free = 0;
ZedPro = 1;
ZedProTrial = 2;
}
message UpdateUserPlan {

View File

@@ -36,7 +36,7 @@ use ui::{
IconWithIndicator, Indicator, PopoverMenu, Tooltip, h_flex, prelude::*,
};
use util::ResultExt;
use workspace::{BottomDockLayout, Workspace, notifications::NotifyResultExt};
use workspace::{Workspace, notifications::NotifyResultExt};
use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};
pub use onboarding_banner::restore_banner;
@@ -210,7 +210,6 @@ impl Render for TitleBar {
.pr_1()
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.children(self.render_call_controls(window, cx))
.child(self.render_bottom_dock_layout_menu(cx))
.map(|el| {
let status = self.client.status();
let status = &*status.borrow();
@@ -302,7 +301,7 @@ impl TitleBar {
cx.notify()
}),
);
subscriptions.push(cx.subscribe(&project, |_, _, _, cx| cx.notify()));
subscriptions.push(cx.subscribe(&project, |_, _, _: &project::Event, cx| cx.notify()));
subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed));
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
@@ -623,101 +622,6 @@ impl TitleBar {
}
}
pub fn render_bottom_dock_layout_menu(&self, cx: &mut Context<Self>) -> impl IntoElement {
let workspace = self.workspace.upgrade().unwrap();
let current_layout = workspace.update(cx, |workspace, _cx| workspace.bottom_dock_layout());
PopoverMenu::new("layout-menu")
.trigger(
IconButton::new("toggle_layout", IconName::Layout)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Toggle Layout Menu")),
)
.anchor(gpui::Corner::TopRight)
.menu(move |window, cx| {
ContextMenu::build(window, cx, {
let workspace = workspace.clone();
move |menu, _, _| {
menu.label("Bottom Dock")
.separator()
.toggleable_entry(
"Contained",
current_layout == BottomDockLayout::Contained,
ui::IconPosition::End,
None,
{
let workspace = workspace.clone();
move |window, cx| {
workspace.update(cx, |workspace, cx| {
workspace.set_bottom_dock_layout(
BottomDockLayout::Contained,
window,
cx,
);
});
}
},
)
.toggleable_entry(
"Full",
current_layout == BottomDockLayout::Full,
ui::IconPosition::End,
None,
{
let workspace = workspace.clone();
move |window, cx| {
workspace.update(cx, |workspace, cx| {
workspace.set_bottom_dock_layout(
BottomDockLayout::Full,
window,
cx,
);
});
}
},
)
.toggleable_entry(
"Left Aligned",
current_layout == BottomDockLayout::LeftAligned,
ui::IconPosition::End,
None,
{
let workspace = workspace.clone();
move |window, cx| {
workspace.update(cx, |workspace, cx| {
workspace.set_bottom_dock_layout(
BottomDockLayout::LeftAligned,
window,
cx,
);
});
}
},
)
.toggleable_entry(
"Right Aligned",
current_layout == BottomDockLayout::RightAligned,
ui::IconPosition::End,
None,
{
let workspace = workspace.clone();
move |window, cx| {
workspace.update(cx, |workspace, cx| {
workspace.set_bottom_dock_layout(
BottomDockLayout::RightAligned,
window,
cx,
);
});
}
},
)
}
})
.into()
})
}
pub fn render_sign_in_button(&mut self, _: &mut Context<Self>) -> Button {
let client = self.client.clone();
Button::new("sign_in", "Sign in")
@@ -751,6 +655,7 @@ impl TitleBar {
None => "",
Some(proto::Plan::Free) => "Free",
Some(proto::Plan::ZedPro) => "Pro",
Some(proto::Plan::ZedProTrial) => "Pro (Trial)",
}
),
zed_actions::OpenAccountSettings.boxed_clone(),

View File

@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{path::Path, sync::Arc};
use editor::Editor;
use gpui::{
@@ -6,7 +6,7 @@ use gpui::{
WeakEntity, Window, div,
};
use language::{Buffer, BufferEvent, LanguageName, Toolchain};
use project::{Project, ProjectPath, WorktreeId};
use project::{Project, ProjectPath, WorktreeId, toolchain_store::ToolchainStoreEvent};
use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, SharedString, Tooltip};
use workspace::{StatusItemView, Workspace, item::ItemHandle};
@@ -22,6 +22,28 @@ pub struct ActiveToolchain {
impl ActiveToolchain {
pub fn new(workspace: &Workspace, window: &mut Window, cx: &mut Context<Self>) -> Self {
if let Some(store) = workspace.project().read(cx).toolchain_store() {
cx.subscribe_in(
&store,
window,
|this, _, _: &ToolchainStoreEvent, window, cx| {
let editor = this
.workspace
.update(cx, |workspace, cx| {
workspace
.active_item(cx)
.and_then(|item| item.downcast::<Editor>())
})
.ok()
.flatten();
if let Some(editor) = editor {
this.active_toolchain.take();
this.update_lister(editor, window, cx);
}
},
)
.detach();
}
Self {
active_toolchain: None,
active_buffer: None,
@@ -57,12 +79,19 @@ impl ActiveToolchain {
this.term = term;
cx.notify();
});
let worktree_id = active_file
.update(cx, |this, cx| Some(this.file()?.worktree_id(cx)))
let (worktree_id, path) = active_file
.update(cx, |this, cx| {
this.file().and_then(|file| {
Some((
file.worktree_id(cx),
Arc::<Path>::from(file.path().parent()?),
))
})
})
.ok()
.flatten()?;
let toolchain =
Self::active_toolchain(workspace, worktree_id, language_name, cx).await?;
Self::active_toolchain(workspace, worktree_id, path, language_name, cx).await?;
let _ = this.update(cx, |this, cx| {
this.active_toolchain = Some(toolchain);
@@ -101,6 +130,7 @@ impl ActiveToolchain {
fn active_toolchain(
workspace: WeakEntity<Workspace>,
worktree_id: WorktreeId,
relative_path: Arc<Path>,
language_name: LanguageName,
cx: &mut AsyncWindowContext,
) -> Task<Option<Toolchain>> {
@@ -114,7 +144,7 @@ impl ActiveToolchain {
this.project().read(cx).active_toolchain(
ProjectPath {
worktree_id,
path: Arc::from("".as_ref()),
path: relative_path.clone(),
},
language_name.clone(),
cx,
@@ -133,7 +163,7 @@ impl ActiveToolchain {
project.read(cx).available_toolchains(
ProjectPath {
worktree_id,
path: Arc::from("".as_ref()),
path: relative_path.clone(),
},
language_name,
cx,
@@ -144,7 +174,12 @@ impl ActiveToolchain {
if let Some(toolchain) = toolchains.toolchains.first() {
// Since we don't have a selected toolchain, pick one for user here.
workspace::WORKSPACE_DB
.set_toolchain(workspace_id, worktree_id, "".to_owned(), toolchain.clone())
.set_toolchain(
workspace_id,
worktree_id,
relative_path.to_string_lossy().into_owned(),
toolchain.clone(),
)
.await
.ok()?;
project
@@ -152,7 +187,7 @@ impl ActiveToolchain {
this.activate_toolchain(
ProjectPath {
worktree_id,
path: Arc::from("".as_ref()),
path: relative_path,
},
toolchain.clone(),
cx,

View File

@@ -50,6 +50,7 @@ impl ToolchainSelector {
let language_name = buffer.read(cx).language()?.name();
let worktree_id = buffer.read(cx).file()?.worktree_id(cx);
let relative_path: Arc<Path> = Arc::from(buffer.read(cx).file()?.path().parent()?);
let worktree_root_path = project
.read(cx)
.worktree_for_id(worktree_id, cx)?
@@ -58,8 +59,9 @@ impl ToolchainSelector {
let workspace_id = workspace.database_id()?;
let weak = workspace.weak_handle();
cx.spawn_in(window, async move |workspace, cx| {
let as_str = relative_path.to_string_lossy().into_owned();
let active_toolchain = workspace::WORKSPACE_DB
.toolchain(workspace_id, worktree_id, language_name.clone())
.toolchain(workspace_id, worktree_id, as_str, language_name.clone())
.await
.ok()
.flatten();
@@ -72,6 +74,7 @@ impl ToolchainSelector {
active_toolchain,
worktree_id,
worktree_root_path,
relative_path,
language_name,
window,
cx,
@@ -91,6 +94,7 @@ impl ToolchainSelector {
active_toolchain: Option<Toolchain>,
worktree_id: WorktreeId,
worktree_root: Arc<Path>,
relative_path: Arc<Path>,
language_name: LanguageName,
window: &mut Window,
cx: &mut Context<Self>,
@@ -104,6 +108,7 @@ impl ToolchainSelector {
worktree_id,
worktree_root,
project,
relative_path,
language_name,
window,
cx,
@@ -137,6 +142,7 @@ pub struct ToolchainSelectorDelegate {
workspace: WeakEntity<Workspace>,
worktree_id: WorktreeId,
worktree_abs_path_root: Arc<Path>,
relative_path: Arc<Path>,
placeholder_text: Arc<str>,
_fetch_candidates_task: Task<Option<()>>,
}
@@ -149,6 +155,7 @@ impl ToolchainSelectorDelegate {
worktree_id: WorktreeId,
worktree_abs_path_root: Arc<Path>,
project: Entity<Project>,
relative_path: Arc<Path>,
language_name: LanguageName,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
@@ -162,17 +169,26 @@ impl ToolchainSelectorDelegate {
})
.ok()?
.await?;
let placeholder_text = format!("Select a {}", term.to_lowercase()).into();
let relative_path = this
.update(cx, |this, _| this.delegate.relative_path.clone())
.ok()?;
let placeholder_text = format!(
"Select a {} for `{}`…",
term.to_lowercase(),
relative_path.to_string_lossy()
)
.into();
let _ = this.update_in(cx, move |this, window, cx| {
this.delegate.placeholder_text = placeholder_text;
this.refresh_placeholder(window, cx);
});
let available_toolchains = project
.update(cx, |this, cx| {
this.available_toolchains(
ProjectPath {
worktree_id,
path: Arc::from("".as_ref()),
path: relative_path.clone(),
},
language_name,
cx,
@@ -211,6 +227,7 @@ impl ToolchainSelectorDelegate {
worktree_id,
worktree_abs_path_root,
placeholder_text,
relative_path,
_fetch_candidates_task,
}
}
@@ -246,19 +263,18 @@ impl PickerDelegate for ToolchainSelectorDelegate {
{
let workspace = self.workspace.clone();
let worktree_id = self.worktree_id;
let path = self.relative_path.clone();
let relative_path = self.relative_path.to_string_lossy().into_owned();
cx.spawn_in(window, async move |_, cx| {
workspace::WORKSPACE_DB
.set_toolchain(workspace_id, worktree_id, "".to_owned(), toolchain.clone())
.set_toolchain(workspace_id, worktree_id, relative_path, toolchain.clone())
.await
.log_err();
workspace
.update(cx, |this, cx| {
this.project().update(cx, |this, cx| {
this.activate_toolchain(
ProjectPath {
worktree_id,
path: Arc::from("".as_ref()),
},
ProjectPath { worktree_id, path },
toolchain,
cx,
)

View File

@@ -132,7 +132,15 @@ fn scroll_editor(
let max_visible_row = top.row().0.saturating_add(
(visible_line_count as u32).saturating_sub(1 + vertical_scroll_margin),
);
let max_row = DisplayRow(map.max_point().row().0.max(max_visible_row));
// scroll off the end.
let max_row = if top.row().0 + visible_line_count as u32 >= map.max_point().row().0 {
map.max_point().row()
} else {
DisplayRow(
(top.row().0 + visible_line_count as u32)
.saturating_sub(1 + vertical_scroll_margin),
)
};
let new_row = if full_page_up {
// Special-casing ctrl-b/page-up, which is special-cased by Vim, it seems
@@ -372,14 +380,14 @@ mod test {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_scroll_height(10).await;
cx.neovim.set_option(&format!("scrolloff={}", 0)).await;
let content = "ˇ".to_owned() + &sample_text(26, 2, 'a');
cx.set_shared_state(&content).await;
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<EditorSettings>(cx, |s| {
s.scroll_beyond_last_line = Some(ScrollBeyondLastLine::Off)
s.scroll_beyond_last_line = Some(ScrollBeyondLastLine::Off);
// s.vertical_scroll_margin = Some(0.);
});
});
@@ -395,4 +403,24 @@ mod test {
cx.simulate_shared_keystrokes("ctrl-u").await;
cx.shared_state().await.assert_matches();
}
#[gpui::test]
async fn test_ctrl_y_e(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_scroll_height(10).await;
let content = "ˇ".to_owned() + &sample_text(26, 2, 'a');
cx.set_shared_state(&content).await;
for _ in 0..8 {
cx.simulate_shared_keystrokes("ctrl-e").await;
cx.shared_state().await.assert_matches();
}
for _ in 0..8 {
cx.simulate_shared_keystrokes("ctrl-y").await;
cx.shared_state().await.assert_matches();
}
}
}

View File

@@ -0,0 +1,35 @@
{"SetOption":{"value":"scrolloff=3"}}
{"SetOption":{"value":"lines=12"}}
{"Put":{"state":"ˇaa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nˇee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nˇff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\nˇgg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nˇhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nˇii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\nˇjj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nˇkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-e"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nˇll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nˇll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nˇll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nˇll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nˇkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\nˇjj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nˇii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nˇhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Key":"ctrl-y"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\nˇgg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}

View File

@@ -1,6 +1,5 @@
{"SetOption":{"value":"scrolloff=3"}}
{"SetOption":{"value":"lines=12"}}
{"SetOption":{"value":"scrolloff=0"}}
{"Put":{"state":"ˇaa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz"}}
{"Key":"shift-g"}
{"Key":"k"}
@@ -10,4 +9,4 @@
{"Key":"shift-g"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nˇzz","mode":"Normal"}}
{"Key":"ctrl-u"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nˇuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nˇrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}

View File

@@ -1334,17 +1334,18 @@ impl WorkspaceDb {
&self,
workspace_id: WorkspaceId,
worktree_id: WorktreeId,
relative_path: String,
language_name: LanguageName,
) -> Result<Option<Toolchain>> {
self.write(move |this| {
let mut select = this
.select_bound(sql!(
SELECT name, path, raw_json FROM toolchains WHERE workspace_id = ? AND language_name = ? AND worktree_id = ?
SELECT name, path, raw_json FROM toolchains WHERE workspace_id = ? AND language_name = ? AND worktree_id = ? AND relative_path = ?
))
.context("Preparing insertion")?;
let toolchain: Vec<(String, String, String)> =
select((workspace_id, language_name.as_ref().to_string(), worktree_id.to_usize()))?;
select((workspace_id, language_name.as_ref().to_string(), worktree_id.to_usize(), relative_path))?;
Ok(toolchain.into_iter().next().and_then(|(name, path, raw_json)| Some(Toolchain {
name: name.into(),

View File

@@ -75,6 +75,46 @@ Non-negative `float` values
`float` values
## Bottom Dock Layout
- Description: Control the layout of the bottom dock, relative to the left and right docks
- Setting: `bottom_dock_layout`
- Default: `"contained"`
**Options**
1. Contain the bottom dock, giving the full height of the window to the left and right docks
```json
{
"bottom_dock_layout": "contained"
}
```
2. Give the bottom dock the full width of the window, truncating the left and right docks
```json
{
"bottom_dock_layout": "full"
}
```
3. Left align the bottom dock, truncating the left dock and giving the right dock the full height of the window
```json
{
"bottom_dock_layout": "left_aligned"
}
```
3. Right align the bottom dock, giving the left dock the full height of the window and truncating the right dock.
```json
{
"bottom_dock_layout": "right_aligned"
}
```
## Auto Install extensions
- Description: Define extensions to be autoinstalled or never be installed.