Compare commits
13 Commits
display-on
...
zeta2-cont
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a4ee4fed7 | ||
|
|
ea4bf46a36 | ||
|
|
05545abab6 | ||
|
|
a85608566d | ||
|
|
69af5261ea | ||
|
|
b9e2f61a38 | ||
|
|
38bbb497dd | ||
|
|
0cc7b4a93c | ||
|
|
cc32bfdfdf | ||
|
|
50de8ddc28 | ||
|
|
f770011d7f | ||
|
|
f2a6b57909 | ||
|
|
96b67ac70e |
@@ -1,8 +1,8 @@
|
||||
name: Bug Report (Windows Beta)
|
||||
description: Zed Windows Beta Related Bugs
|
||||
name: Bug Report (Windows Alpha)
|
||||
description: Zed Windows Alpha Related Bugs
|
||||
type: "Bug"
|
||||
labels: ["windows"]
|
||||
title: "Windows Beta: <a short description of the Windows bug>"
|
||||
title: "Windows Alpha: <a short description of the Windows bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
@@ -1,6 +1,3 @@
|
||||
# IF YOU UPDATE THE NAME OF ANY GITHUB SECRET, YOU MUST CHERRY PICK THE COMMIT
|
||||
# TO BOTH STABLE AND PREVIEW CHANNELS
|
||||
|
||||
name: Release Actions
|
||||
|
||||
on:
|
||||
@@ -16,9 +13,9 @@ jobs:
|
||||
id: get-release-url
|
||||
run: |
|
||||
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
|
||||
URL="https://zed.dev/releases/preview"
|
||||
URL="https://zed.dev/releases/preview/latest"
|
||||
else
|
||||
URL="https://zed.dev/releases/stable"
|
||||
URL="https://zed.dev/releases/stable/latest"
|
||||
fi
|
||||
|
||||
echo "URL=$URL" >> "$GITHUB_OUTPUT"
|
||||
|
||||
459
Cargo.lock
generated
459
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,6 @@ members = [
|
||||
"crates/edit_prediction",
|
||||
"crates/edit_prediction_button",
|
||||
"crates/edit_prediction_context",
|
||||
"crates/edit_prediction_tools",
|
||||
"crates/editor",
|
||||
"crates/eval",
|
||||
"crates/explorer_command_injector",
|
||||
@@ -150,8 +149,9 @@ members = [
|
||||
"crates/semantic_version",
|
||||
"crates/session",
|
||||
"crates/settings",
|
||||
"crates/settings_macros",
|
||||
"crates/settings_profile_selector",
|
||||
"crates/settings_ui",
|
||||
"crates/settings_ui_macros",
|
||||
"crates/snippet",
|
||||
"crates/snippet_provider",
|
||||
"crates/snippets_ui",
|
||||
@@ -315,7 +315,6 @@ image_viewer = { path = "crates/image_viewer" }
|
||||
edit_prediction = { path = "crates/edit_prediction" }
|
||||
edit_prediction_button = { path = "crates/edit_prediction_button" }
|
||||
edit_prediction_context = { path = "crates/edit_prediction_context" }
|
||||
edit_prediction_tools = { path = "crates/edit_prediction_tools" }
|
||||
inspector_ui = { path = "crates/inspector_ui" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
jj = { path = "crates/jj" }
|
||||
@@ -383,6 +382,7 @@ semantic_version = { path = "crates/semantic_version" }
|
||||
session = { path = "crates/session" }
|
||||
settings = { path = "crates/settings" }
|
||||
settings_ui = { path = "crates/settings_ui" }
|
||||
settings_ui_macros = { path = "crates/settings_ui_macros" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
snippet_provider = { path = "crates/snippet_provider" }
|
||||
snippets_ui = { path = "crates/snippets_ui" }
|
||||
@@ -437,7 +437,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||
# External crates
|
||||
#
|
||||
|
||||
agent-client-protocol = { version = "0.4.0", features = ["unstable"] }
|
||||
agent-client-protocol = { version = "0.2.1", features = ["unstable"] }
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||
any_vec = "0.14"
|
||||
@@ -630,7 +630,6 @@ serde_json_lenient = { version = "0.2", features = [
|
||||
serde_path_to_error = "0.1.17"
|
||||
serde_repr = "0.1"
|
||||
serde_urlencoded = "0.7"
|
||||
serde_with = "3.4.0"
|
||||
sha2 = "0.10"
|
||||
shellexpand = "2.1.0"
|
||||
shlex = "1.3.0"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3010_383)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.71141 7.06133C3.76141 6.47267 3.78341 5.88133 3.81608 5.29133C4.10416 0.190201 11.896 0.190202 12.1841 5.29133C12.2174 5.898 12.2441 6.50333 12.3067 7.10733C12.6951 7.94202 14.3637 11.6214 13.4134 12.006C13.1894 12.096 12.8041 11.7227 12.3694 11.052C12.207 11.9614 11.7273 12.8132 11.0587 13.4467C11.7441 13.68 12.3334 13.998 12.3334 14.3333C12.3334 14.9176 3.66675 14.9257 3.66675 14.3333C3.66675 13.998 4.25608 13.68 4.94141 13.4467C4.26191 12.803 3.82279 11.9657 3.62408 11.056C3.19075 11.724 2.80608 12.096 2.58341 12.006C1.626 11.6185 3.31478 7.90684 3.71141 7.06133Z" stroke="#7B7B7B" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.11822 6.6L7.68822 7.89C7.85822 8.03 8.12822 8.03 8.29822 7.89L9.86822 6.6C10.1382 6.38 9.94822 6 9.56822 6H6.42822C6.04822 6 5.85822 6.38 6.12822 6.6H6.11822Z" fill="#7B7B7B"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3010_383">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1140,13 +1140,6 @@
|
||||
"ctrl-enter": "menu::Confirm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextServerToolsModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OnboardingAiConfigurationModal",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -1244,13 +1244,6 @@
|
||||
"cmd-enter": "menu::Confirm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextServerToolsModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OnboardingAiConfigurationModal",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -1160,13 +1160,6 @@
|
||||
"ctrl-enter": "menu::Confirm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ContextServerToolsModal",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"escape": "menu::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "OnboardingAiConfigurationModal",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -433,8 +433,6 @@
|
||||
"h": "vim::WrappingLeft",
|
||||
"l": "vim::WrappingRight",
|
||||
"y": "vim::HelixYank",
|
||||
"p": "vim::HelixPaste",
|
||||
"shift-p": ["vim::HelixPaste", { "before": true }],
|
||||
"alt-;": "vim::OtherEnd",
|
||||
"ctrl-r": "vim::Redo",
|
||||
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
|
||||
@@ -444,8 +442,9 @@
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"=": "vim::AutoIndent",
|
||||
"`": "vim::ConvertToLowerCase",
|
||||
"alt-`": "vim::ConvertToUpperCase",
|
||||
"g u": "vim::PushLowercase",
|
||||
"g shift-u": "vim::PushUppercase",
|
||||
"g ~": "vim::PushOppositeCase",
|
||||
"g q": "vim::PushRewrap",
|
||||
"g w": "vim::PushRewrap",
|
||||
"insert": "vim::InsertBefore",
|
||||
|
||||
@@ -311,7 +311,7 @@
|
||||
// bracket, brace, single or double quote characters.
|
||||
// For example, when you select text and type (, Zed will surround the text with ().
|
||||
"use_auto_surround": true,
|
||||
// Whether indentation should be adjusted based on the context whilst typing.
|
||||
/// Whether indentation should be adjusted based on the context whilst typing.
|
||||
"auto_indent": true,
|
||||
// Whether indentation of pasted content should be adjusted based on the context.
|
||||
"auto_indent_on_paste": true,
|
||||
@@ -408,21 +408,6 @@
|
||||
// Whether to show the menus in the titlebar.
|
||||
"show_menus": false
|
||||
},
|
||||
"audio": {
|
||||
// Opt into the new audio system.
|
||||
"experimental.rodio_audio": false,
|
||||
// Requires 'rodio_audio: true'
|
||||
//
|
||||
// Use the new audio systems automatic gain control for your microphone.
|
||||
// This affects how loud you sound to others.
|
||||
"experimental.control_input_volume": false,
|
||||
// Requires 'rodio_audio: true'
|
||||
//
|
||||
// Use the new audio systems automatic gain control on everyone in the
|
||||
// call. This makes call members who are too quite louder and those who are
|
||||
// too loud quieter. This only affects how things sound for you.
|
||||
"experimental.control_output_volume": false
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the editor.
|
||||
@@ -603,7 +588,6 @@
|
||||
// Toggle certain types of hints on and off, all switched on by default.
|
||||
"show_type_hints": true,
|
||||
"show_parameter_hints": true,
|
||||
"show_value_hints": true,
|
||||
// Corresponds to null/None LSP hint type value.
|
||||
"show_other_hints": true,
|
||||
// Whether to show a background for inlay hints.
|
||||
@@ -812,7 +796,7 @@
|
||||
"agent": {
|
||||
// Whether the agent is enabled.
|
||||
"enabled": true,
|
||||
// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
|
||||
/// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'.
|
||||
"preferred_completion_mode": "normal",
|
||||
// Whether to show the agent panel button in the status bar.
|
||||
"button": true,
|
||||
@@ -822,8 +806,6 @@
|
||||
"default_width": 640,
|
||||
// Default height when the agent panel is docked to the bottom.
|
||||
"default_height": 320,
|
||||
// The view to use by default (thread, or text_thread)
|
||||
"default_view": "thread",
|
||||
// The default model to use when creating new threads.
|
||||
"default_model": {
|
||||
// The provider to use.
|
||||
@@ -925,23 +907,27 @@
|
||||
|
||||
// Default: false
|
||||
"play_sound_when_agent_done": false,
|
||||
// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
|
||||
//
|
||||
// Default: true
|
||||
/// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
|
||||
///
|
||||
/// Default: true
|
||||
"expand_edit_card": true,
|
||||
// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
|
||||
//
|
||||
// Default: true
|
||||
/// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
|
||||
///
|
||||
/// Default: true
|
||||
"expand_terminal_card": true,
|
||||
// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
|
||||
//
|
||||
// Default: false
|
||||
"use_modifier_to_send": false,
|
||||
// Minimum number of lines to display in the agent message editor.
|
||||
//
|
||||
// Default: 4
|
||||
"message_editor_min_lines": 4
|
||||
},
|
||||
// The settings for slash commands.
|
||||
"slash_commands": {
|
||||
// Settings for the `/project` slash command.
|
||||
"project": {
|
||||
// Whether `/project` is enabled.
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
// Whether the screen sharing icon is shown in the os status bar.
|
||||
"show_call_status_icon": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
@@ -953,7 +939,6 @@
|
||||
//
|
||||
// This is typically customized on a per-language basis.
|
||||
"language_servers": ["..."],
|
||||
|
||||
// When to automatically save edited buffers. This setting can
|
||||
// take four values.
|
||||
//
|
||||
@@ -1285,13 +1270,7 @@
|
||||
// },
|
||||
// Whether edit predictions are enabled when editing text threads.
|
||||
// This setting has no effect if globally disabled.
|
||||
"enabled_in_text_threads": true,
|
||||
|
||||
"copilot": {
|
||||
"enterprise_uri": null,
|
||||
"proxy": null,
|
||||
"proxy_no_verify": null
|
||||
}
|
||||
"enabled_in_text_threads": true
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
@@ -1794,7 +1773,6 @@
|
||||
"anthropic": {
|
||||
"api_url": "https://api.anthropic.com"
|
||||
},
|
||||
"bedrock": {},
|
||||
"google": {
|
||||
"api_url": "https://generativelanguage.googleapis.com"
|
||||
},
|
||||
@@ -1816,30 +1794,14 @@
|
||||
},
|
||||
"mistral": {
|
||||
"api_url": "https://api.mistral.ai/v1"
|
||||
},
|
||||
"vercel": {
|
||||
"api_url": "https://api.v0.dev/v1"
|
||||
},
|
||||
"x_ai": {
|
||||
"api_url": "https://api.x.ai/v1"
|
||||
},
|
||||
"zed.dev": {}
|
||||
},
|
||||
"session": {
|
||||
// Whether or not to restore unsaved buffers on restart.
|
||||
//
|
||||
// If this is true, user won't be prompted whether to save/discard
|
||||
// dirty files when closing the application.
|
||||
//
|
||||
// Default: true
|
||||
"restore_unsaved_buffers": true
|
||||
}
|
||||
},
|
||||
// Zed's Prettier integration settings.
|
||||
// Allows to enable/disable formatting with Prettier
|
||||
// and configure default Prettier, used when no project-level Prettier installation is found.
|
||||
"prettier": {
|
||||
// // Whether to consider prettier formatter or not when attempting to format a file.
|
||||
"allowed": false
|
||||
// "allowed": false,
|
||||
//
|
||||
// // Use regular Prettier json configuration.
|
||||
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
|
||||
@@ -1872,10 +1834,6 @@
|
||||
// }
|
||||
// }
|
||||
},
|
||||
// DAP Specific settings.
|
||||
"dap": {
|
||||
// Specify the DAP name as a key here.
|
||||
},
|
||||
// Common language server settings.
|
||||
"global_lsp_settings": {
|
||||
// Whether to show the LSP servers button in the status bar.
|
||||
@@ -1883,8 +1841,7 @@
|
||||
},
|
||||
// Jupyter settings
|
||||
"jupyter": {
|
||||
"enabled": true,
|
||||
"kernel_selections": {}
|
||||
"enabled": true
|
||||
// Specify the language name as the key and the kernel name as the value.
|
||||
// "kernel_selections": {
|
||||
// "python": "conda-base"
|
||||
@@ -2015,11 +1972,5 @@
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
"profiles": [],
|
||||
|
||||
// A map of log scopes to the desired log level.
|
||||
// Useful for filtering out noisy logs or enabling more verbose logging.
|
||||
//
|
||||
// Example: {"log": {"client": "warn"}}
|
||||
"log": {}
|
||||
"profiles": []
|
||||
}
|
||||
|
||||
10
compose.yml
10
compose.yml
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
postgres:
|
||||
image: docker.io/library/postgres:15
|
||||
image: postgres:15
|
||||
container_name: zed_postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
- ./.blob_store:/data
|
||||
|
||||
livekit_server:
|
||||
image: docker.io/livekit/livekit-server
|
||||
image: livekit/livekit-server
|
||||
container_name: livekit_server
|
||||
entrypoint: /livekit-server --config /livekit.yaml
|
||||
ports:
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
- ./livekit.yaml:/livekit.yaml
|
||||
|
||||
postgrest_app:
|
||||
image: docker.io/postgrest/postgrest
|
||||
image: postgrest/postgrest
|
||||
container_name: postgrest_app
|
||||
ports:
|
||||
- 8081:8081
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
- postgres
|
||||
|
||||
postgrest_llm:
|
||||
image: docker.io/postgrest/postgrest
|
||||
image: postgrest/postgrest
|
||||
container_name: postgrest_llm
|
||||
ports:
|
||||
- 8082:8082
|
||||
@@ -60,7 +60,7 @@ services:
|
||||
- postgres
|
||||
|
||||
stripe-mock:
|
||||
image: docker.io/stripe/stripe-mock:v0.178.0
|
||||
image: stripe/stripe-mock:v0.178.0
|
||||
ports:
|
||||
- 12111:12111
|
||||
- 12112:12112
|
||||
|
||||
@@ -3,8 +3,6 @@ mod diff;
|
||||
mod mention;
|
||||
mod terminal;
|
||||
|
||||
use ::terminal::TerminalBuilder;
|
||||
use ::terminal::terminal_settings::{AlternateScroll, CursorShape};
|
||||
use agent_settings::AgentSettings;
|
||||
use collections::HashSet;
|
||||
pub use connection::*;
|
||||
@@ -1146,32 +1144,8 @@ impl AcpThread {
|
||||
|
||||
match update {
|
||||
ToolCallUpdate::UpdateFields(update) => {
|
||||
// Check if there's terminal output in the meta field
|
||||
let terminal_output_result = update
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|meta| meta.get("terminal_output"))
|
||||
.and_then(|terminal_output| {
|
||||
match (
|
||||
terminal_output.get("terminal_id").and_then(|v| v.as_str()),
|
||||
terminal_output.get("data").and_then(|v| v.as_str()),
|
||||
) {
|
||||
(Some(terminal_id_str), Some(data_str)) => {
|
||||
let data = data_str.as_bytes().to_vec();
|
||||
let terminal_id = acp::TerminalId(terminal_id_str.into());
|
||||
Some((terminal_id, data))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
|
||||
let location_updated = update.fields.locations.is_some();
|
||||
call.update_fields(update.fields, languages, &self.terminals, cx)?;
|
||||
|
||||
if let Some((terminal_id, data)) = terminal_output_result {
|
||||
// Silently ignore errors - terminal output streaming is best-effort
|
||||
let _ = self.write_terminal_output(terminal_id, &data, cx);
|
||||
}
|
||||
if location_updated {
|
||||
self.resolve_locations(update.id, cx);
|
||||
}
|
||||
@@ -1807,9 +1781,6 @@ impl AcpThread {
|
||||
reuse_shared_snapshot: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<String>> {
|
||||
// Args are 1-based, move to 0-based
|
||||
let line = line.unwrap_or_default().saturating_sub(1);
|
||||
let limit = limit.unwrap_or(u32::MAX);
|
||||
let project = self.project.clone();
|
||||
let action_log = self.action_log.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
@@ -1837,37 +1808,44 @@ impl AcpThread {
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log.buffer_read(buffer.clone(), cx);
|
||||
})?;
|
||||
|
||||
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?;
|
||||
this.update(cx, |this, _| {
|
||||
this.shared_buffers.insert(buffer.clone(), snapshot.clone());
|
||||
project.update(cx, |project, cx| {
|
||||
let position = buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.anchor_before(Point::new(line.unwrap_or_default(), 0));
|
||||
project.set_agent_location(
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
snapshot
|
||||
|
||||
buffer.update(cx, |buffer, _| buffer.snapshot())?
|
||||
};
|
||||
|
||||
let max_point = snapshot.max_point();
|
||||
if line >= max_point.row {
|
||||
anyhow::bail!(
|
||||
"Attempting to read beyond the end of the file, line {}:{}",
|
||||
max_point.row + 1,
|
||||
max_point.column
|
||||
);
|
||||
}
|
||||
this.update(cx, |this, _| {
|
||||
let text = snapshot.text();
|
||||
this.shared_buffers.insert(buffer.clone(), snapshot);
|
||||
if line.is_none() && limit.is_none() {
|
||||
return Ok(text);
|
||||
}
|
||||
let limit = limit.unwrap_or(u32::MAX) as usize;
|
||||
let Some(line) = line else {
|
||||
return Ok(text.lines().take(limit).collect::<String>());
|
||||
};
|
||||
|
||||
let start = snapshot.anchor_before(Point::new(line, 0));
|
||||
let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0));
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.set_agent_location(
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: start,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
|
||||
Ok(snapshot.text_for_range(start..end).collect::<String>())
|
||||
let count = text.lines().count();
|
||||
if count < line as usize {
|
||||
anyhow::bail!("There are only {} lines", count);
|
||||
}
|
||||
Ok(text
|
||||
.lines()
|
||||
.skip(line as usize + 1)
|
||||
.take(limit)
|
||||
.collect::<String>())
|
||||
})?
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1975,7 +1953,6 @@ impl AcpThread {
|
||||
extra_env: Vec<acp::EnvVariable>,
|
||||
cwd: Option<PathBuf>,
|
||||
output_byte_limit: Option<u64>,
|
||||
is_display_only: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
let env = match &cwd {
|
||||
@@ -2016,59 +1993,32 @@ impl AcpThread {
|
||||
)
|
||||
.redirect_stdin_to_dev_null()
|
||||
.build(Some(command), &args);
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_task(
|
||||
task::SpawnInTerminal {
|
||||
command: Some(command.clone()),
|
||||
args: args.clone(),
|
||||
cwd: cwd.clone(),
|
||||
env,
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let terminal = if is_display_only {
|
||||
cx.update(|cx| {
|
||||
TerminalBuilder::new_display_only(
|
||||
Some(format!("Display: {}", command).into()),
|
||||
CursorShape::Block,
|
||||
AlternateScroll::On,
|
||||
Some(10_000),
|
||||
cx,
|
||||
)
|
||||
})??
|
||||
} else {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_task(
|
||||
task::SpawnInTerminal {
|
||||
command: Some(command.clone()),
|
||||
args: args.clone(),
|
||||
cwd: cwd.clone(),
|
||||
env,
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?
|
||||
};
|
||||
|
||||
if is_display_only {
|
||||
// For display-only terminals, we need special handling
|
||||
cx.new(|cx| {
|
||||
Terminal::new_display_only(
|
||||
terminal_id,
|
||||
&format!("{} {}", command, args.join(" ")),
|
||||
cwd,
|
||||
output_byte_limit.map(|l| l as usize),
|
||||
terminal,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
cx.new(|cx| {
|
||||
Terminal::new(
|
||||
terminal_id,
|
||||
&format!("{} {}", command, args.join(" ")),
|
||||
cwd,
|
||||
output_byte_limit.map(|l| l as usize),
|
||||
terminal,
|
||||
language_registry,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
cx.new(|cx| {
|
||||
Terminal::new(
|
||||
terminal_id,
|
||||
&format!("{} {}", command, args.join(" ")),
|
||||
cwd,
|
||||
output_byte_limit.map(|l| l as usize),
|
||||
terminal,
|
||||
language_registry,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2114,26 +2064,8 @@ impl AcpThread {
|
||||
pub fn terminal(&self, terminal_id: acp::TerminalId) -> Result<Entity<Terminal>> {
|
||||
self.terminals
|
||||
.get(&terminal_id)
|
||||
.cloned()
|
||||
.context("Terminal not found")
|
||||
}
|
||||
|
||||
pub fn write_terminal_output(
|
||||
&mut self,
|
||||
terminal_id: acp::TerminalId,
|
||||
output: &[u8],
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
let terminal = self
|
||||
.terminals
|
||||
.get(&terminal_id)
|
||||
.context("Terminal not found")?;
|
||||
|
||||
terminal.update(cx, |terminal, cx| {
|
||||
terminal.write_output(output, cx);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn to_markdown(&self, cx: &App) -> String {
|
||||
@@ -2459,82 +2391,6 @@ mod tests {
|
||||
request.await.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_reading_from_line(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/tmp"), json!({"foo": "one\ntwo\nthree\nfour\n"}))
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(path!("/tmp/foo"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let connection = Rc::new(FakeAgentConnection::new());
|
||||
|
||||
let thread = cx
|
||||
.update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Whole file
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), None, None, false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "one\ntwo\nthree\nfour\n");
|
||||
|
||||
// Only start line
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), Some(3), None, false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "three\nfour\n");
|
||||
|
||||
// Only limit
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), None, Some(2), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "one\ntwo\n");
|
||||
|
||||
// Range
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), Some(2), Some(2), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "two\nthree\n");
|
||||
|
||||
// Invalid
|
||||
let err = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), Some(5), Some(2), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Attempting to read beyond the end of the file, line 5:0"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -82,70 +82,6 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_display_only(
|
||||
id: acp::TerminalId,
|
||||
command_label: &str,
|
||||
working_dir: Option<PathBuf>,
|
||||
output_byte_limit: Option<usize>,
|
||||
terminal: Entity<terminal::Terminal>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
// Display-only terminals don't have a real process, so there's no exit status
|
||||
let command_task = Task::ready(None);
|
||||
|
||||
Self {
|
||||
id,
|
||||
command: cx.new(|_cx| {
|
||||
// For display-only terminals, we don't need the markdown wrapper
|
||||
// The terminal itself will handle the display
|
||||
Markdown::new(
|
||||
format!("```\n{}\n```", command_label).into(),
|
||||
None,
|
||||
None,
|
||||
_cx,
|
||||
)
|
||||
}),
|
||||
working_dir,
|
||||
terminal,
|
||||
started_at: Instant::now(),
|
||||
output: None,
|
||||
output_byte_limit,
|
||||
_output_task: cx
|
||||
.spawn(async move |this, cx| {
|
||||
// Display-only terminals don't really exit, but we need to handle this
|
||||
let exit_status = command_task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
let (content, original_content_len) = this.truncated_output(cx);
|
||||
let content_line_count = this.terminal.read(cx).total_lines();
|
||||
|
||||
this.output = Some(TerminalOutput {
|
||||
ended_at: Instant::now(),
|
||||
exit_status,
|
||||
content,
|
||||
original_content_len,
|
||||
content_line_count,
|
||||
});
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
|
||||
acp::TerminalExitStatus {
|
||||
exit_code: None,
|
||||
signal: None,
|
||||
meta: None,
|
||||
}
|
||||
})
|
||||
.shared(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_output(&mut self, data: &[u8], cx: &mut Context<Self>) {
|
||||
self.terminal.update(cx, |terminal, cx| {
|
||||
terminal.write_output(data, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &acp::TerminalId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ impl AgentProfile {
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
update_settings_file(fs, cx, {
|
||||
update_settings_file::<AgentSettings>(fs, cx, {
|
||||
let id = id.clone();
|
||||
move |settings, _cx| {
|
||||
profile_settings.save_to_settings(id, settings).log_err();
|
||||
settings.create_profile(id, profile_settings).log_err();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -3272,7 +3272,7 @@ mod tests {
|
||||
|
||||
// Test-specific constants
|
||||
const TEST_RATE_LIMIT_RETRY_SECS: u64 = 30;
|
||||
use agent_settings::{AgentProfileId, AgentSettings};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelParameters};
|
||||
use assistant_tool::ToolRegistry;
|
||||
use assistant_tools;
|
||||
use futures::StreamExt;
|
||||
@@ -3289,7 +3289,7 @@ mod tests {
|
||||
use project::{FakeFs, Project};
|
||||
use prompt_store::PromptBuilder;
|
||||
use serde_json::json;
|
||||
use settings::{LanguageModelParameters, Settings, SettingsStore};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use theme::ThemeSettings;
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{HistoryStore, TerminalHandle, ThreadEnvironment, TitleUpdated, Token
|
||||
use acp_thread::{AcpThread, AgentModelSelector};
|
||||
use action_log::ActionLog;
|
||||
use agent_client_protocol as acp;
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{HashSet, IndexMap};
|
||||
use fs::Fs;
|
||||
@@ -20,7 +21,7 @@ use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||
use prompt_store::{
|
||||
ProjectContext, PromptId, PromptStore, RulesFileContext, UserRulesContext, WorktreeContext,
|
||||
};
|
||||
use settings::{LanguageModelSelection, update_settings_file};
|
||||
use settings::update_settings_file;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -872,17 +873,13 @@ impl AgentModelSelector for NativeAgentConnection {
|
||||
thread.set_model(model.clone(), cx);
|
||||
});
|
||||
|
||||
update_settings_file(self.0.read(cx).fs.clone(), cx, move |settings, _cx| {
|
||||
let provider = model.provider_id().0.to_string();
|
||||
let model = model.id().0.to_string();
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_model(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
});
|
||||
update_settings_file::<AgentSettings>(
|
||||
self.0.read(cx).fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
settings.set_model(model);
|
||||
},
|
||||
);
|
||||
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
@@ -1144,7 +1141,7 @@ impl ThreadEnvironment for AcpThreadEnvironment {
|
||||
cx: &mut AsyncApp,
|
||||
) -> Task<Result<Rc<dyn TerminalHandle>>> {
|
||||
let task = self.acp_thread.update(cx, |thread, cx| {
|
||||
thread.create_terminal(command, vec![], vec![], cwd, output_byte_limit, false, cx)
|
||||
thread.create_terminal(command, vec![], vec![], cwd, output_byte_limit, cx)
|
||||
});
|
||||
|
||||
let acp_thread = self.acp_thread.clone();
|
||||
|
||||
@@ -2477,11 +2477,8 @@ impl ToolCallEventStream {
|
||||
"always_allow" => {
|
||||
if let Some(fs) = fs.clone() {
|
||||
cx.update(|cx| {
|
||||
update_settings_file(fs, cx, |settings, _| {
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_always_allow_tool_actions(true);
|
||||
update_settings_file::<AgentSettings>(fs, cx, |settings, _| {
|
||||
settings.set_always_allow_tool_actions(true);
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -791,11 +791,14 @@ mod tests {
|
||||
// First, test with format_on_save enabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
|
||||
settings.project.all_languages.defaults.formatter =
|
||||
Some(language::language_settings::SelectedFormatter::Auto);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.format_on_save = Some(FormatOnSave::On);
|
||||
settings.defaults.formatter =
|
||||
Some(language::language_settings::SelectedFormatter::Auto);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -850,10 +853,12 @@ mod tests {
|
||||
// Next, test with format_on_save disabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.format_on_save =
|
||||
Some(FormatOnSave::Off);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.format_on_save = Some(FormatOnSave::Off);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -930,13 +935,12 @@ mod tests {
|
||||
// First, test with remove_trailing_whitespace_on_save enabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings
|
||||
.project
|
||||
.all_languages
|
||||
.defaults
|
||||
.remove_trailing_whitespace_on_save = Some(true);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.remove_trailing_whitespace_on_save = Some(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -987,13 +991,12 @@ mod tests {
|
||||
// Next, test with remove_trailing_whitespace_on_save disabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings
|
||||
.project
|
||||
.all_languages
|
||||
.defaults
|
||||
.remove_trailing_whitespace_on_save = Some(false);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.remove_trailing_whitespace_on_save = Some(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ mod tests {
|
||||
use super::*;
|
||||
use gpui::{TestAppContext, UpdateGlobal};
|
||||
use language::{Language, LanguageConfig, LanguageMatcher};
|
||||
use project::{FakeFs, Project};
|
||||
use project::{FakeFs, Project, WorktreeSettings};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use unindent::Unindent;
|
||||
@@ -827,21 +827,19 @@ mod tests {
|
||||
|
||||
cx.update(|cx| {
|
||||
use gpui::UpdateGlobal;
|
||||
use project::WorktreeSettings;
|
||||
use settings::SettingsStore;
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions = Some(vec![
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = Some(vec![
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1064,11 +1062,10 @@ mod tests {
|
||||
// Set global settings
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -214,7 +214,7 @@ mod tests {
|
||||
use super::*;
|
||||
use gpui::{TestAppContext, UpdateGlobal};
|
||||
use indoc::indoc;
|
||||
use project::{FakeFs, Project};
|
||||
use project::{FakeFs, Project, WorktreeSettings};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
@@ -421,20 +421,17 @@ mod tests {
|
||||
// Configure settings explicitly
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions = Some(vec![
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = Some(vec![
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
"**/.hidden_subdir".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -568,11 +565,10 @@ mod tests {
|
||||
// Set global settings
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,6 +201,7 @@ impl AgentTool for ReadFileTool {
|
||||
// Check if specific line ranges are provided
|
||||
let result = if input.start_line.is_some() || input.end_line.is_some() {
|
||||
let result = buffer.read_with(cx, |buffer, _cx| {
|
||||
let text = buffer.text();
|
||||
// .max(1) because despite instructions to be 1-indexed, sometimes the model passes 0.
|
||||
let start = input.start_line.unwrap_or(1).max(1);
|
||||
let start_row = start - 1;
|
||||
@@ -209,13 +210,13 @@ impl AgentTool for ReadFileTool {
|
||||
anchor = Some(buffer.anchor_before(Point::new(start_row, column)));
|
||||
}
|
||||
|
||||
let mut end_row = input.end_line.unwrap_or(u32::MAX);
|
||||
if end_row <= start_row {
|
||||
end_row = start_row + 1; // read at least one lines
|
||||
let lines = text.split('\n').skip(start_row as usize);
|
||||
if let Some(end) = input.end_line {
|
||||
let count = end.saturating_sub(start).saturating_add(1); // Ensure at least 1 line
|
||||
itertools::intersperse(lines.take(count as usize), "\n").collect::<String>()
|
||||
} else {
|
||||
itertools::intersperse(lines, "\n").collect::<String>()
|
||||
}
|
||||
let start = buffer.anchor_before(Point::new(start_row, 0));
|
||||
let end = buffer.anchor_before(Point::new(end_row, 0));
|
||||
buffer.text_for_range(start..end).collect::<String>()
|
||||
})?;
|
||||
|
||||
action_log.update(cx, |log, cx| {
|
||||
@@ -444,7 +445,7 @@ mod test {
|
||||
tool.run(input, ToolCallEventStream::test().0, cx)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap(), "Line 2\nLine 3\nLine 4\n".into());
|
||||
assert_eq!(result.unwrap(), "Line 2\nLine 3\nLine 4".into());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -474,7 +475,7 @@ mod test {
|
||||
tool.clone().run(input, ToolCallEventStream::test().0, cx)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap(), "Line 1\nLine 2\n".into());
|
||||
assert_eq!(result.unwrap(), "Line 1\nLine 2".into());
|
||||
|
||||
// end_line of 0 should result in at least 1 line
|
||||
let result = cx
|
||||
@@ -487,7 +488,7 @@ mod test {
|
||||
tool.clone().run(input, ToolCallEventStream::test().0, cx)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap(), "Line 1\n".into());
|
||||
assert_eq!(result.unwrap(), "Line 1".into());
|
||||
|
||||
// when start_line > end_line, should still return at least 1 line
|
||||
let result = cx
|
||||
@@ -500,7 +501,7 @@ mod test {
|
||||
tool.clone().run(input, ToolCallEventStream::test().0, cx)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(result.unwrap(), "Line 3\n".into());
|
||||
assert_eq!(result.unwrap(), "Line 3".into());
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
@@ -586,21 +587,19 @@ mod test {
|
||||
|
||||
cx.update(|cx| {
|
||||
use gpui::UpdateGlobal;
|
||||
use project::WorktreeSettings;
|
||||
use settings::SettingsStore;
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions = Some(vec![
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = Some(vec![
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -804,11 +803,10 @@ mod test {
|
||||
// Set global settings
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ action_log.workspace = true
|
||||
agent-client-protocol.workspace = true
|
||||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
env_logger = { workspace = true, optional = true }
|
||||
|
||||
@@ -13,7 +13,7 @@ use util::ResultExt as _;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{any::Any, cell::RefCell};
|
||||
use std::{path::Path, rc::Rc};
|
||||
use std::{path::Path, rc::Rc, sync::Arc};
|
||||
use thiserror::Error;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
@@ -505,7 +505,6 @@ struct ClientDelegate {
|
||||
cx: AsyncApp,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl acp::Client for ClientDelegate {
|
||||
async fn request_permission(
|
||||
&self,
|
||||
@@ -606,14 +605,6 @@ impl acp::Client for ClientDelegate {
|
||||
&self,
|
||||
args: acp::CreateTerminalRequest,
|
||||
) -> Result<acp::CreateTerminalResponse, acp::Error> {
|
||||
// Check if this is a display-only terminal from the metadata
|
||||
let is_display_only = args
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|meta| meta.get("display_only"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let terminal = self
|
||||
.session_thread(&args.session_id)?
|
||||
.update(&mut self.cx.clone(), |thread, cx| {
|
||||
@@ -623,7 +614,6 @@ impl acp::Client for ClientDelegate {
|
||||
args.env,
|
||||
args.cwd,
|
||||
args.output_byte_limit,
|
||||
is_display_only,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
@@ -648,11 +638,19 @@ impl acp::Client for ClientDelegate {
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
async fn ext_method(&self, _args: acp::ExtRequest) -> Result<acp::ExtResponse, acp::Error> {
|
||||
async fn ext_method(
|
||||
&self,
|
||||
_name: Arc<str>,
|
||||
_params: Arc<serde_json::value::RawValue>,
|
||||
) -> Result<Arc<serde_json::value::RawValue>, acp::Error> {
|
||||
Err(acp::Error::method_not_found())
|
||||
}
|
||||
|
||||
async fn ext_notification(&self, _args: acp::ExtNotification) -> Result<(), acp::Error> {
|
||||
async fn ext_notification(
|
||||
&self,
|
||||
_name: Arc<str>,
|
||||
_params: Arc<serde_json::value::RawValue>,
|
||||
) -> Result<(), acp::Error> {
|
||||
Err(acp::Error::method_not_found())
|
||||
}
|
||||
|
||||
|
||||
@@ -45,13 +45,8 @@ impl AgentServer for ClaudeCode {
|
||||
}
|
||||
|
||||
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
update_settings_file(fs, cx, |settings, _| {
|
||||
settings
|
||||
.agent_servers
|
||||
.get_or_insert_default()
|
||||
.claude
|
||||
.get_or_insert_default()
|
||||
.default_mode = mode_id.map(|m| m.to_string())
|
||||
update_settings_file::<AllAgentServersSettings>(fs, cx, |settings, _| {
|
||||
settings.claude.get_or_insert_default().default_mode = mode_id.map(|m| m.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -49,14 +49,8 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
|
||||
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
let name = self.name();
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.agent_servers
|
||||
.get_or_insert_default()
|
||||
.custom
|
||||
.get_mut(&name)
|
||||
.unwrap()
|
||||
.default_mode = mode_id.map(|m| m.to_string())
|
||||
update_settings_file::<AllAgentServersSettings>(fs, cx, move |settings, _| {
|
||||
settings.custom.get_mut(&name).unwrap().default_mode = mode_id.map(|m| m.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ convert_case.workspace = true
|
||||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
language_model.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use collections::IndexMap;
|
||||
use convert_case::{Case, Casing as _};
|
||||
use fs::Fs;
|
||||
use gpui::{App, SharedString};
|
||||
use settings::{
|
||||
AgentProfileContent, ContextServerPresetContent, Settings as _, SettingsContent,
|
||||
update_settings_file,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{AgentProfileId, AgentSettings};
|
||||
use crate::AgentSettings;
|
||||
|
||||
pub mod builtin_profiles {
|
||||
use super::AgentProfileId;
|
||||
@@ -25,6 +23,27 @@ pub mod builtin_profiles {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AgentProfileId(pub Arc<str>);
|
||||
|
||||
impl AgentProfileId {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AgentProfileId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AgentProfileId {
|
||||
fn default() -> Self {
|
||||
Self("write".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct AgentProfile {
|
||||
id: AgentProfileId,
|
||||
@@ -68,10 +87,10 @@ impl AgentProfile {
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
update_settings_file(fs, cx, {
|
||||
update_settings_file::<AgentSettings>(fs, cx, {
|
||||
let id = id.clone();
|
||||
move |settings, _cx| {
|
||||
profile_settings.save_to_settings(id, settings).log_err();
|
||||
settings.create_profile(id, profile_settings).log_err();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -110,71 +129,9 @@ impl AgentProfileSettings {
|
||||
.get(server_id)
|
||||
.is_some_and(|preset| preset.tools.get(tool_name) == Some(&true))
|
||||
}
|
||||
|
||||
pub fn save_to_settings(
|
||||
&self,
|
||||
profile_id: AgentProfileId,
|
||||
content: &mut SettingsContent,
|
||||
) -> Result<()> {
|
||||
let profiles = content
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.profiles
|
||||
.get_or_insert_default();
|
||||
if profiles.contains_key(&profile_id.0) {
|
||||
bail!("profile with ID '{profile_id}' already exists");
|
||||
}
|
||||
|
||||
profiles.insert(
|
||||
profile_id.0,
|
||||
AgentProfileContent {
|
||||
name: self.name.clone().into(),
|
||||
tools: self.tools.clone(),
|
||||
enable_all_context_servers: Some(self.enable_all_context_servers),
|
||||
context_servers: self
|
||||
.context_servers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(server_id, preset)| {
|
||||
(
|
||||
server_id,
|
||||
ContextServerPresetContent {
|
||||
tools: preset.tools,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AgentProfileContent> for AgentProfileSettings {
|
||||
fn from(content: AgentProfileContent) -> Self {
|
||||
Self {
|
||||
name: content.name.into(),
|
||||
tools: content.tools,
|
||||
enable_all_context_servers: content.enable_all_context_servers.unwrap_or_default(),
|
||||
context_servers: content
|
||||
.context_servers
|
||||
.into_iter()
|
||||
.map(|(server_id, preset)| (server_id, preset.into()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ContextServerPreset {
|
||||
pub tools: IndexMap<Arc<str>, bool>,
|
||||
}
|
||||
|
||||
impl From<settings::ContextServerPresetContent> for ContextServerPreset {
|
||||
fn from(content: settings::ContextServerPresetContent) -> Self {
|
||||
Self {
|
||||
tools: content.tools,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@ mod agent_profile;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, Pixels, px};
|
||||
use gpui::{App, Pixels, SharedString};
|
||||
use language_model::LanguageModel;
|
||||
use project::DisableAiSettings;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::{JsonSchema, json_schema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{
|
||||
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
|
||||
NotifyWhenAgentWaiting, Settings, SettingsContent,
|
||||
};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use crate::agent_profile::*;
|
||||
|
||||
@@ -24,11 +22,37 @@ pub fn init(cx: &mut App) {
|
||||
AgentSettings::register(cx);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AgentDockPosition {
|
||||
Left,
|
||||
#[default]
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DefaultView {
|
||||
#[default]
|
||||
Thread,
|
||||
TextThread,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NotifyWhenAgentWaiting {
|
||||
#[default]
|
||||
PrimaryScreen,
|
||||
AllScreens,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct AgentSettings {
|
||||
pub enabled: bool,
|
||||
pub button: bool,
|
||||
pub dock: DockPosition,
|
||||
pub dock: AgentDockPosition,
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_model: Option<LanguageModelSelection>,
|
||||
@@ -36,8 +60,9 @@ pub struct AgentSettings {
|
||||
pub commit_message_model: Option<LanguageModelSelection>,
|
||||
pub thread_summary_model: Option<LanguageModelSelection>,
|
||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||
pub using_outdated_settings_version: bool,
|
||||
pub default_profile: AgentProfileId,
|
||||
pub default_view: DefaultAgentView,
|
||||
pub default_view: DefaultView,
|
||||
pub profiles: IndexMap<AgentProfileId, AgentProfileSettings>,
|
||||
pub always_allow_tool_actions: bool,
|
||||
pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
|
||||
@@ -54,26 +79,13 @@ pub struct AgentSettings {
|
||||
}
|
||||
|
||||
impl AgentSettings {
|
||||
pub fn enabled(&self, cx: &App) -> bool {
|
||||
self.enabled && !DisableAiSettings::get_global(cx).disable_ai
|
||||
}
|
||||
|
||||
pub fn temperature_for_model(model: &Arc<dyn LanguageModel>, cx: &App) -> Option<f32> {
|
||||
let settings = Self::get_global(cx);
|
||||
for setting in settings.model_parameters.iter().rev() {
|
||||
if let Some(provider) = &setting.provider
|
||||
&& provider.0 != model.provider_id().0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(setting_model) = &setting.model
|
||||
&& *setting_model != model.id().0
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return setting.temperature;
|
||||
}
|
||||
return None;
|
||||
settings
|
||||
.model_parameters
|
||||
.iter()
|
||||
.rfind(|setting| setting.matches(model))
|
||||
.and_then(|m| m.temperature)
|
||||
}
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
@@ -102,6 +114,223 @@ impl AgentSettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct LanguageModelParameters {
|
||||
pub provider: Option<LanguageModelProviderSetting>,
|
||||
pub model: Option<SharedString>,
|
||||
pub temperature: Option<f32>,
|
||||
}
|
||||
|
||||
impl LanguageModelParameters {
|
||||
pub fn matches(&self, model: &Arc<dyn LanguageModel>) -> bool {
|
||||
if let Some(provider) = &self.provider
|
||||
&& provider.0 != model.provider_id().0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(setting_model) = &self.model
|
||||
&& *setting_model != model.id().0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentSettingsContent {
|
||||
pub fn set_dock(&mut self, dock: AgentDockPosition) {
|
||||
self.dock = Some(dock);
|
||||
}
|
||||
|
||||
pub fn set_model(&mut self, language_model: Arc<dyn LanguageModel>) {
|
||||
let model = language_model.id().0.to_string();
|
||||
let provider = language_model.provider_id().0.to_string();
|
||||
|
||||
self.default_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
self.inline_assistant_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
||||
self.commit_message_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
||||
self.thread_summary_model = Some(LanguageModelSelection {
|
||||
provider: provider.into(),
|
||||
model,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
|
||||
self.always_allow_tool_actions = Some(allow);
|
||||
}
|
||||
|
||||
pub fn set_play_sound_when_agent_done(&mut self, allow: bool) {
|
||||
self.play_sound_when_agent_done = Some(allow);
|
||||
}
|
||||
|
||||
pub fn set_single_file_review(&mut self, allow: bool) {
|
||||
self.single_file_review = Some(allow);
|
||||
}
|
||||
|
||||
pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
|
||||
self.use_modifier_to_send = Some(always_use);
|
||||
}
|
||||
|
||||
pub fn set_profile(&mut self, profile_id: AgentProfileId) {
|
||||
self.default_profile = Some(profile_id);
|
||||
}
|
||||
|
||||
pub fn create_profile(
|
||||
&mut self,
|
||||
profile_id: AgentProfileId,
|
||||
profile_settings: AgentProfileSettings,
|
||||
) -> Result<()> {
|
||||
let profiles = self.profiles.get_or_insert_default();
|
||||
if profiles.contains_key(&profile_id) {
|
||||
bail!("profile with ID '{profile_id}' already exists");
|
||||
}
|
||||
|
||||
profiles.insert(
|
||||
profile_id,
|
||||
AgentProfileContent {
|
||||
name: profile_settings.name.into(),
|
||||
tools: profile_settings.tools,
|
||||
enable_all_context_servers: Some(profile_settings.enable_all_context_servers),
|
||||
context_servers: profile_settings
|
||||
.context_servers
|
||||
.into_iter()
|
||||
.map(|(server_id, preset)| {
|
||||
(
|
||||
server_id,
|
||||
ContextServerPresetContent {
|
||||
tools: preset.tools,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "agent", fallback_key = "assistant")]
|
||||
pub struct AgentSettingsContent {
|
||||
/// Whether the Agent is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
enabled: Option<bool>,
|
||||
/// Whether to show the agent panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
button: Option<bool>,
|
||||
/// Where to dock the agent panel.
|
||||
///
|
||||
/// Default: right
|
||||
dock: Option<AgentDockPosition>,
|
||||
/// Default width in pixels when the agent panel is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
default_width: Option<f32>,
|
||||
/// Default height in pixels when the agent panel is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
default_height: Option<f32>,
|
||||
/// The default model to use when creating new chats and for other features when a specific model is not specified.
|
||||
default_model: Option<LanguageModelSelection>,
|
||||
/// Model to use for the inline assistant. Defaults to default_model when not specified.
|
||||
inline_assistant_model: Option<LanguageModelSelection>,
|
||||
/// Model to use for generating git commit messages. Defaults to default_model when not specified.
|
||||
commit_message_model: Option<LanguageModelSelection>,
|
||||
/// Model to use for generating thread summaries. Defaults to default_model when not specified.
|
||||
thread_summary_model: Option<LanguageModelSelection>,
|
||||
/// Additional models with which to generate alternatives when performing inline assists.
|
||||
inline_alternatives: Option<Vec<LanguageModelSelection>>,
|
||||
/// The default profile to use in the Agent.
|
||||
///
|
||||
/// Default: write
|
||||
default_profile: Option<AgentProfileId>,
|
||||
/// Which view type to show by default in the agent panel.
|
||||
///
|
||||
/// Default: "thread"
|
||||
default_view: Option<DefaultView>,
|
||||
/// The available agent profiles.
|
||||
pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
|
||||
/// Whenever a tool action would normally wait for your confirmation
|
||||
/// that you allow it, always choose to allow it.
|
||||
///
|
||||
/// This setting has no effect on external agents that support permission modes, such as Claude Code.
|
||||
///
|
||||
/// Set `agent_servers.claude.default_mode` to `bypassPermissions`, to disable all permission requests when using Claude Code.
|
||||
///
|
||||
/// Default: false
|
||||
always_allow_tool_actions: Option<bool>,
|
||||
/// Where to show a popup notification when the agent is waiting for user input.
|
||||
///
|
||||
/// Default: "primary_screen"
|
||||
notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
|
||||
/// Whether to play a sound when the agent has either completed its response, or needs user input.
|
||||
///
|
||||
/// Default: false
|
||||
play_sound_when_agent_done: Option<bool>,
|
||||
/// Whether to stream edits from the agent as they are received.
|
||||
///
|
||||
/// Default: false
|
||||
stream_edits: Option<bool>,
|
||||
/// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
|
||||
///
|
||||
/// Default: true
|
||||
single_file_review: Option<bool>,
|
||||
/// Additional parameters for language model requests. When making a request
|
||||
/// to a model, parameters will be taken from the last entry in this list
|
||||
/// that matches the model's provider and name. In each entry, both provider
|
||||
/// and model are optional, so that you can specify parameters for either
|
||||
/// one.
|
||||
///
|
||||
/// Default: []
|
||||
#[serde(default)]
|
||||
model_parameters: Vec<LanguageModelParameters>,
|
||||
/// What completion mode to enable for new threads
|
||||
///
|
||||
/// Default: normal
|
||||
preferred_completion_mode: Option<CompletionMode>,
|
||||
/// Whether to show thumb buttons for feedback in the agent panel.
|
||||
///
|
||||
/// Default: true
|
||||
enable_feedback: Option<bool>,
|
||||
/// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
|
||||
///
|
||||
/// Default: true
|
||||
expand_edit_card: Option<bool>,
|
||||
/// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
|
||||
///
|
||||
/// Default: true
|
||||
expand_terminal_card: Option<bool>,
|
||||
/// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
|
||||
///
|
||||
/// Default: false
|
||||
use_modifier_to_send: Option<bool>,
|
||||
/// Minimum number of lines of height the agent message editor should have.
|
||||
///
|
||||
/// Default: 4
|
||||
message_editor_min_lines: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CompletionMode {
|
||||
@@ -120,80 +349,215 @@ impl From<CompletionMode> for cloud_llm_client::CompletionMode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<settings::CompletionMode> for CompletionMode {
|
||||
fn from(value: settings::CompletionMode) -> Self {
|
||||
match value {
|
||||
settings::CompletionMode::Normal => CompletionMode::Normal,
|
||||
settings::CompletionMode::Burn => CompletionMode::Burn,
|
||||
}
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct LanguageModelSelection {
|
||||
pub provider: LanguageModelProviderSetting,
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct LanguageModelProviderSetting(pub String);
|
||||
|
||||
impl JsonSchema for LanguageModelProviderSetting {
|
||||
fn schema_name() -> Cow<'static, str> {
|
||||
"LanguageModelProviderSetting".into()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
// list the builtin providers as a subset so that we still auto complete them in the settings
|
||||
json_schema!({
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"amazon-bedrock",
|
||||
"anthropic",
|
||||
"copilot_chat",
|
||||
"deepseek",
|
||||
"google",
|
||||
"lmstudio",
|
||||
"mistral",
|
||||
"ollama",
|
||||
"openai",
|
||||
"openrouter",
|
||||
"vercel",
|
||||
"x_ai",
|
||||
"zed.dev"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AgentProfileId(pub Arc<str>);
|
||||
|
||||
impl AgentProfileId {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
impl From<String> for LanguageModelProviderSetting {
|
||||
fn from(provider: String) -> Self {
|
||||
Self(provider)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AgentProfileId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
impl From<&str> for LanguageModelProviderSetting {
|
||||
fn from(provider: &str) -> Self {
|
||||
Self(provider.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AgentProfileId {
|
||||
fn default() -> Self {
|
||||
Self("write".into())
|
||||
}
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AgentProfileContent {
|
||||
pub name: Arc<str>,
|
||||
#[serde(default)]
|
||||
pub tools: IndexMap<Arc<str>, bool>,
|
||||
/// Whether all context servers are enabled by default.
|
||||
pub enable_all_context_servers: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub context_servers: IndexMap<Arc<str>, ContextServerPresetContent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ContextServerPresetContent {
|
||||
pub tools: IndexMap<Arc<str>, bool>,
|
||||
}
|
||||
|
||||
impl Settings for AgentSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let agent = content.agent.clone().unwrap();
|
||||
Self {
|
||||
enabled: agent.enabled.unwrap(),
|
||||
button: agent.button.unwrap(),
|
||||
dock: agent.dock.unwrap(),
|
||||
default_width: px(agent.default_width.unwrap()),
|
||||
default_height: px(agent.default_height.unwrap()),
|
||||
default_model: Some(agent.default_model.unwrap()),
|
||||
inline_assistant_model: agent.inline_assistant_model,
|
||||
commit_message_model: agent.commit_message_model,
|
||||
thread_summary_model: agent.thread_summary_model,
|
||||
inline_alternatives: agent.inline_alternatives.unwrap_or_default(),
|
||||
default_profile: AgentProfileId(agent.default_profile.unwrap()),
|
||||
default_view: agent.default_view.unwrap(),
|
||||
profiles: agent
|
||||
.profiles
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(key, val)| (AgentProfileId(key), val.into()))
|
||||
.collect(),
|
||||
always_allow_tool_actions: agent.always_allow_tool_actions.unwrap(),
|
||||
notify_when_agent_waiting: agent.notify_when_agent_waiting.unwrap(),
|
||||
play_sound_when_agent_done: agent.play_sound_when_agent_done.unwrap(),
|
||||
stream_edits: agent.stream_edits.unwrap(),
|
||||
single_file_review: agent.single_file_review.unwrap(),
|
||||
model_parameters: agent.model_parameters,
|
||||
preferred_completion_mode: agent.preferred_completion_mode.unwrap().into(),
|
||||
enable_feedback: agent.enable_feedback.unwrap(),
|
||||
expand_edit_card: agent.expand_edit_card.unwrap(),
|
||||
expand_terminal_card: agent.expand_terminal_card.unwrap(),
|
||||
use_modifier_to_send: agent.use_modifier_to_send.unwrap(),
|
||||
message_editor_min_lines: agent.message_editor_min_lines.unwrap(),
|
||||
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
|
||||
|
||||
type FileContent = AgentSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut settings = AgentSettings::default();
|
||||
|
||||
for value in sources.defaults_and_customizations() {
|
||||
merge(&mut settings.enabled, value.enabled);
|
||||
merge(&mut settings.button, value.button);
|
||||
merge(&mut settings.dock, value.dock);
|
||||
merge(
|
||||
&mut settings.default_width,
|
||||
value.default_width.map(Into::into),
|
||||
);
|
||||
merge(
|
||||
&mut settings.default_height,
|
||||
value.default_height.map(Into::into),
|
||||
);
|
||||
settings.default_model = value
|
||||
.default_model
|
||||
.clone()
|
||||
.or(settings.default_model.take());
|
||||
settings.inline_assistant_model = value
|
||||
.inline_assistant_model
|
||||
.clone()
|
||||
.or(settings.inline_assistant_model.take());
|
||||
settings.commit_message_model = value
|
||||
.clone()
|
||||
.commit_message_model
|
||||
.or(settings.commit_message_model.take());
|
||||
settings.thread_summary_model = value
|
||||
.clone()
|
||||
.thread_summary_model
|
||||
.or(settings.thread_summary_model.take());
|
||||
merge(
|
||||
&mut settings.inline_alternatives,
|
||||
value.inline_alternatives.clone(),
|
||||
);
|
||||
merge(
|
||||
&mut settings.notify_when_agent_waiting,
|
||||
value.notify_when_agent_waiting,
|
||||
);
|
||||
merge(
|
||||
&mut settings.play_sound_when_agent_done,
|
||||
value.play_sound_when_agent_done,
|
||||
);
|
||||
merge(&mut settings.stream_edits, value.stream_edits);
|
||||
merge(&mut settings.single_file_review, value.single_file_review);
|
||||
merge(&mut settings.default_profile, value.default_profile.clone());
|
||||
merge(&mut settings.default_view, value.default_view);
|
||||
merge(
|
||||
&mut settings.preferred_completion_mode,
|
||||
value.preferred_completion_mode,
|
||||
);
|
||||
merge(&mut settings.enable_feedback, value.enable_feedback);
|
||||
merge(&mut settings.expand_edit_card, value.expand_edit_card);
|
||||
merge(
|
||||
&mut settings.expand_terminal_card,
|
||||
value.expand_terminal_card,
|
||||
);
|
||||
merge(
|
||||
&mut settings.use_modifier_to_send,
|
||||
value.use_modifier_to_send,
|
||||
);
|
||||
merge(
|
||||
&mut settings.message_editor_min_lines,
|
||||
value.message_editor_min_lines,
|
||||
);
|
||||
|
||||
settings
|
||||
.model_parameters
|
||||
.extend_from_slice(&value.model_parameters);
|
||||
|
||||
if let Some(profiles) = value.profiles.as_ref() {
|
||||
settings
|
||||
.profiles
|
||||
.extend(profiles.into_iter().map(|(id, profile)| {
|
||||
(
|
||||
id.clone(),
|
||||
AgentProfileSettings {
|
||||
name: profile.name.clone().into(),
|
||||
tools: profile.tools.clone(),
|
||||
enable_all_context_servers: profile
|
||||
.enable_all_context_servers
|
||||
.unwrap_or_default(),
|
||||
context_servers: profile
|
||||
.context_servers
|
||||
.iter()
|
||||
.map(|(context_server_id, preset)| {
|
||||
(
|
||||
context_server_id.clone(),
|
||||
ContextServerPreset {
|
||||
tools: preset.tools.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
!sources.default.always_allow_tool_actions.unwrap_or(false),
|
||||
"For security, agent.always_allow_tool_actions should always be false in default.json. If it's true, that is a bug that should be fixed!"
|
||||
);
|
||||
|
||||
// For security reasons, only trust the user's global settings for whether to always allow tool actions.
|
||||
// If this could be overridden locally, an attacker could (e.g. by committing to source control and
|
||||
// convincing you to switch branches) modify your project-local settings to disable the agent's safety checks.
|
||||
settings.always_allow_tool_actions = sources
|
||||
.user
|
||||
.and_then(|setting| setting.always_allow_tool_actions)
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
if let Some(b) = vscode
|
||||
.read_value("chat.agent.enabled")
|
||||
.and_then(|b| b.as_bool())
|
||||
{
|
||||
current.agent.get_or_insert_default().enabled = Some(b);
|
||||
current.agent.get_or_insert_default().button = Some(b);
|
||||
current.enabled = Some(b);
|
||||
current.button = Some(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge<T>(target: &mut T, value: Option<T>) {
|
||||
if let Some(value) = value {
|
||||
*target = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use acp_thread::{AgentConnection, Plan};
|
||||
use action_log::ActionLog;
|
||||
use agent_client_protocol::{self as acp, PromptCapabilities};
|
||||
use agent_servers::{AgentServer, AgentServerDelegate};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
|
||||
use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer};
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use arrayvec::ArrayVec;
|
||||
@@ -35,7 +35,7 @@ use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
||||
use project::{Project, ProjectEntryId};
|
||||
use prompt_store::{PromptId, PromptStore};
|
||||
use rope::Point;
|
||||
use settings::{NotifyWhenAgentWaiting, Settings as _, SettingsStore};
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -1591,7 +1591,7 @@ impl AcpThreadView {
|
||||
task.shell = shell;
|
||||
|
||||
let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
|
||||
terminal_panel.spawn_task(&login, window, cx)
|
||||
terminal_panel.spawn_task(login.clone(), window, cx)
|
||||
})?;
|
||||
|
||||
let terminal = terminal.await?;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
mod add_llm_provider_modal;
|
||||
mod configure_context_server_modal;
|
||||
mod configure_context_server_tools_modal;
|
||||
mod manage_profiles_modal;
|
||||
mod tool_picker;
|
||||
|
||||
@@ -26,8 +25,12 @@ use language_model::{
|
||||
};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, GEMINI_NAME},
|
||||
agent_server_store::{
|
||||
AgentServerCommand, AgentServerStore, AllAgentServersSettings, CLAUDE_CODE_NAME,
|
||||
CustomAgentServerSettings, GEMINI_NAME,
|
||||
},
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
};
|
||||
use settings::{Settings, SettingsStore, update_settings_file};
|
||||
use ui::{
|
||||
@@ -39,12 +42,12 @@ use workspace::{Workspace, create_and_open_local_file};
|
||||
use zed_actions::ExtensionCategoryFilter;
|
||||
|
||||
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
|
||||
pub(crate) use configure_context_server_tools_modal::ConfigureContextServerToolsModal;
|
||||
pub(crate) use manage_profiles_modal::ManageProfilesModal;
|
||||
|
||||
use crate::{
|
||||
AddContextServer,
|
||||
AddContextServer, ExternalAgent, NewExternalAgentThread,
|
||||
agent_configuration::add_llm_provider_modal::{AddLlmProviderModal, LlmCompatibleProvider},
|
||||
placeholder_command,
|
||||
};
|
||||
|
||||
pub struct AgentConfiguration {
|
||||
@@ -197,8 +200,9 @@ impl AgentConfiguration {
|
||||
.when(is_expanded, |this| this.mb_2())
|
||||
.child(
|
||||
div()
|
||||
.opacity(0.6)
|
||||
.px_2()
|
||||
.child(Divider::horizontal().color(DividerColor::BorderFaded)),
|
||||
.child(Divider::horizontal().color(DividerColor::Border)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -223,7 +227,7 @@ impl AgentConfiguration {
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.gap_2()
|
||||
.child(
|
||||
Icon::new(provider.icon())
|
||||
.size(IconSize::Small)
|
||||
@@ -341,8 +345,6 @@ impl AgentConfiguration {
|
||||
PopoverMenu::new("add-provider-popover")
|
||||
.trigger(
|
||||
Button::new("add-provider", "Add Provider")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -415,8 +417,8 @@ impl AgentConfiguration {
|
||||
always_allow_tool_actions,
|
||||
move |state, _window, cx| {
|
||||
let allow = state == &ToggleState::Selected;
|
||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
||||
settings.agent.get_or_insert_default().set_always_allow_tool_actions(allow);
|
||||
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
|
||||
settings.set_always_allow_tool_actions(allow);
|
||||
});
|
||||
},
|
||||
)
|
||||
@@ -433,11 +435,8 @@ impl AgentConfiguration {
|
||||
single_file_review,
|
||||
move |state, _window, cx| {
|
||||
let allow = state == &ToggleState::Selected;
|
||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_single_file_review(allow);
|
||||
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
|
||||
settings.set_single_file_review(allow);
|
||||
});
|
||||
},
|
||||
)
|
||||
@@ -456,8 +455,8 @@ impl AgentConfiguration {
|
||||
play_sound_when_agent_done,
|
||||
move |state, _window, cx| {
|
||||
let allow = state == &ToggleState::Selected;
|
||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
||||
settings.agent.get_or_insert_default().set_play_sound_when_agent_done(allow);
|
||||
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
|
||||
settings.set_play_sound_when_agent_done(allow);
|
||||
});
|
||||
},
|
||||
)
|
||||
@@ -476,8 +475,8 @@ impl AgentConfiguration {
|
||||
use_modifier_to_send,
|
||||
move |state, _window, cx| {
|
||||
let allow = state == &ToggleState::Selected;
|
||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
||||
settings.agent.get_or_insert_default().set_use_modifier_to_send(allow);
|
||||
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
|
||||
settings.set_use_modifier_to_send(allow);
|
||||
});
|
||||
},
|
||||
)
|
||||
@@ -534,6 +533,10 @@ impl AgentConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
fn card_item_bg_color(&self, cx: &mut Context<Self>) -> Hsla {
|
||||
cx.theme().colors().background.opacity(0.25)
|
||||
}
|
||||
|
||||
fn card_item_border_color(&self, cx: &mut Context<Self>) -> Hsla {
|
||||
cx.theme().colors().border.opacity(0.6)
|
||||
}
|
||||
@@ -543,61 +546,7 @@ impl AgentConfiguration {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let mut context_server_ids = self
|
||||
.context_server_store
|
||||
.read(cx)
|
||||
.server_ids(cx)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort context servers: ones without mcp-server- prefix first, then prefixed ones
|
||||
context_server_ids.sort_by(|a, b| {
|
||||
const MCP_PREFIX: &str = "mcp-server-";
|
||||
match (a.0.strip_prefix(MCP_PREFIX), b.0.strip_prefix(MCP_PREFIX)) {
|
||||
// If one has mcp-server- prefix and other doesn't, non-mcp comes first
|
||||
(Some(_), None) => std::cmp::Ordering::Greater,
|
||||
(None, Some(_)) => std::cmp::Ordering::Less,
|
||||
// If both have same prefix status, sort by appropriate key
|
||||
(Some(a), Some(b)) => a.cmp(b),
|
||||
(None, None) => a.0.cmp(&b.0),
|
||||
}
|
||||
});
|
||||
|
||||
let add_server_popover = PopoverMenu::new("add-server-popover")
|
||||
.trigger(
|
||||
Button::new("add-server", "Add Server")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.label_size(LabelSize::Small),
|
||||
)
|
||||
.anchor(gpui::Corner::TopRight)
|
||||
.menu({
|
||||
move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.entry("Add Custom Server", None, {
|
||||
|window, cx| window.dispatch_action(AddContextServer.boxed_clone(), cx)
|
||||
})
|
||||
.entry("Install from Extensions", None, {
|
||||
|window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::Extensions {
|
||||
category_filter: Some(
|
||||
ExtensionCategoryFilter::ContextServers,
|
||||
),
|
||||
id: None,
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
});
|
||||
let context_server_ids = self.context_server_store.read(cx).configured_server_ids();
|
||||
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
@@ -606,25 +555,17 @@ impl AgentConfiguration {
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.gap_1()
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("Model Context Protocol (MCP) Servers"))
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("Model Context Protocol (MCP) Servers"))
|
||||
.child(
|
||||
Label::new(
|
||||
"All MCP servers connected directly or via a Zed extension.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(add_server_popover),
|
||||
Label::new(
|
||||
"All context servers connected through the Model Context Protocol.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(v_flex().w_full().gap_1().map(|mut parent| {
|
||||
.map(|parent| {
|
||||
if context_server_ids.is_empty() {
|
||||
parent.child(
|
||||
h_flex()
|
||||
@@ -641,20 +582,56 @@ impl AgentConfiguration {
|
||||
),
|
||||
)
|
||||
} else {
|
||||
for (index, context_server_id) in context_server_ids.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
parent = parent.child(
|
||||
Divider::horizontal()
|
||||
.color(DividerColor::BorderFaded)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
parent =
|
||||
parent.child(self.render_context_server(context_server_id, window, cx));
|
||||
}
|
||||
parent
|
||||
parent.children(context_server_ids.into_iter().map(|context_server_id| {
|
||||
self.render_context_server(context_server_id, window, cx)
|
||||
}))
|
||||
}
|
||||
}))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
h_flex().w_full().child(
|
||||
Button::new("add-context-server", "Add Custom Server")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.full_width()
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(AddContextServer.boxed_clone(), cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().child(
|
||||
Button::new(
|
||||
"install-context-server-extensions",
|
||||
"Install MCP Extensions",
|
||||
)
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.full_width()
|
||||
.icon(IconName::ToolHammer)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(|_event, window, cx| {
|
||||
window.dispatch_action(
|
||||
zed_actions::Extensions {
|
||||
category_filter: Some(
|
||||
ExtensionCategoryFilter::ContextServers,
|
||||
),
|
||||
id: None,
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_context_server(
|
||||
@@ -747,7 +724,7 @@ impl AgentConfiguration {
|
||||
IconButton::new("context-server-config-menu", IconName::Settings)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small),
|
||||
Tooltip::text("Configure MCP Server"),
|
||||
Tooltip::text("Open MCP server options"),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.menu({
|
||||
@@ -756,8 +733,6 @@ impl AgentConfiguration {
|
||||
let language_registry = self.language_registry.clone();
|
||||
let context_server_store = self.context_server_store.clone();
|
||||
let workspace = self.workspace.clone();
|
||||
let tools = self.tools.clone();
|
||||
|
||||
move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.entry("Configure Server", None, {
|
||||
@@ -774,28 +749,7 @@ impl AgentConfiguration {
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}).when(tool_count >= 1, |this| this.entry("View Tools", None, {
|
||||
let context_server_id = context_server_id.clone();
|
||||
let tools = tools.clone();
|
||||
let workspace = workspace.clone();
|
||||
|
||||
move |window, cx| {
|
||||
let context_server_id = context_server_id.clone();
|
||||
let tools = tools.clone();
|
||||
let workspace = workspace.clone();
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
ConfigureContextServerToolsModal::toggle(
|
||||
context_server_id,
|
||||
tools,
|
||||
workspace,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}))
|
||||
})
|
||||
.separator()
|
||||
.entry("Uninstall", None, {
|
||||
let fs = fs.clone();
|
||||
@@ -841,14 +795,14 @@ impl AgentConfiguration {
|
||||
async move |cx| {
|
||||
uninstall_extension_task.await?;
|
||||
cx.update(|cx| {
|
||||
update_settings_file(
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
{
|
||||
let context_server_id =
|
||||
context_server_id.clone();
|
||||
move |settings, _| {
|
||||
settings.project
|
||||
settings
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
}
|
||||
@@ -866,11 +820,17 @@ impl AgentConfiguration {
|
||||
|
||||
v_flex()
|
||||
.id(item_id.clone())
|
||||
.border_1()
|
||||
.rounded_md()
|
||||
.border_color(self.card_item_border_color(cx))
|
||||
.bg(self.card_item_bg_color(cx))
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.p_1()
|
||||
.justify_between()
|
||||
.when(
|
||||
error.is_none() && are_tools_expanded && tool_count >= 1,
|
||||
error.is_some() || are_tools_expanded && tool_count >= 1,
|
||||
|element| {
|
||||
element
|
||||
.border_b_1()
|
||||
@@ -881,12 +841,31 @@ impl AgentConfiguration {
|
||||
h_flex()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.child(
|
||||
Disclosure::new(
|
||||
"tool-list-disclosure",
|
||||
are_tools_expanded || error.is_some(),
|
||||
)
|
||||
.disabled(tool_count == 0)
|
||||
.on_click(cx.listener({
|
||||
let context_server_id = context_server_id.clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_open = this
|
||||
.expanded_context_server_tools
|
||||
.entry(context_server_id.clone())
|
||||
.or_insert(false);
|
||||
|
||||
*is_open = !*is_open;
|
||||
}
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id(SharedString::from(format!("tooltip-{}", item_id)))
|
||||
.h_full()
|
||||
.w_3()
|
||||
.mr_2()
|
||||
.ml_1()
|
||||
.mr_1p5()
|
||||
.justify_center()
|
||||
.tooltip(Tooltip::text(tooltip_text))
|
||||
.child(status_indicator),
|
||||
@@ -923,61 +902,75 @@ impl AgentConfiguration {
|
||||
.flex_none()
|
||||
.child(context_server_configuration_menu)
|
||||
.child(
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager = self.context_server_store.clone();
|
||||
let fs = self.fs.clone();
|
||||
Switch::new("context-server-switch", is_running.into())
|
||||
.color(SwitchColor::Accent)
|
||||
.on_click({
|
||||
let context_server_manager =
|
||||
self.context_server_store.clone();
|
||||
let fs = self.fs.clone();
|
||||
|
||||
move |state, _window, cx| {
|
||||
let is_enabled = match state {
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
this.stop_server(&context_server_id, cx)
|
||||
.log_err();
|
||||
});
|
||||
false
|
||||
}
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(cx, |this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx);
|
||||
move |state, _window, cx| {
|
||||
let is_enabled = match state {
|
||||
ToggleState::Unselected
|
||||
| ToggleState::Indeterminate => {
|
||||
context_server_manager.update(
|
||||
cx,
|
||||
|this, cx| {
|
||||
this.stop_server(
|
||||
&context_server_id,
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
},
|
||||
);
|
||||
false
|
||||
}
|
||||
ToggleState::Selected => {
|
||||
context_server_manager.update(
|
||||
cx,
|
||||
|this, cx| {
|
||||
if let Some(server) =
|
||||
this.get_server(&context_server_id)
|
||||
{
|
||||
this.start_server(server, cx);
|
||||
}
|
||||
},
|
||||
);
|
||||
true
|
||||
}
|
||||
};
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
{
|
||||
let context_server_id =
|
||||
context_server_id.clone();
|
||||
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.entry(context_server_id.0)
|
||||
.or_insert_with(|| {
|
||||
ContextServerSettings::Extension {
|
||||
enabled: is_enabled,
|
||||
settings: serde_json::json!({}),
|
||||
}
|
||||
})
|
||||
.set_enabled(is_enabled);
|
||||
}
|
||||
});
|
||||
true
|
||||
}
|
||||
};
|
||||
update_settings_file(fs.clone(), cx, {
|
||||
let context_server_id = context_server_id.clone();
|
||||
|
||||
move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.context_servers
|
||||
.entry(context_server_id.0)
|
||||
.or_insert_with(|| {
|
||||
settings::ContextServerSettingsContent::Extension {
|
||||
enabled: is_enabled,
|
||||
settings: serde_json::json!({}),
|
||||
}
|
||||
})
|
||||
.set_enabled(is_enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
}),
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
if let Some(error) = error {
|
||||
return parent.child(
|
||||
h_flex()
|
||||
.p_2()
|
||||
.gap_2()
|
||||
.pr_4()
|
||||
.items_start()
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -1005,11 +998,37 @@ impl AgentConfiguration {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent
|
||||
parent.child(v_flex().py_1p5().px_1().gap_1().children(
|
||||
tools.iter().enumerate().map(|(ix, tool)| {
|
||||
h_flex()
|
||||
.id(("tool-item", ix))
|
||||
.px_1()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.hover(|style| style.bg(cx.theme().colors().element_hover))
|
||||
.rounded_sm()
|
||||
.child(
|
||||
Label::new(tool.name())
|
||||
.buffer_font(cx)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::Info)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Ignored),
|
||||
)
|
||||
.tooltip(Tooltip::text(tool.description()))
|
||||
}),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn render_agent_servers_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let custom_settings = cx
|
||||
.global::<SettingsStore>()
|
||||
.get::<AllAgentServersSettings>(None)
|
||||
.custom
|
||||
.clone();
|
||||
let user_defined_agents = self
|
||||
.agent_server_store
|
||||
.read(cx)
|
||||
@@ -1017,12 +1036,22 @@ impl AgentConfiguration {
|
||||
.filter(|name| name.0 != GEMINI_NAME && name.0 != CLAUDE_CODE_NAME)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let user_defined_agents = user_defined_agents
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
self.render_agent_server(IconName::Ai, name)
|
||||
.into_any_element()
|
||||
self.render_agent_server(
|
||||
IconName::Ai,
|
||||
name.clone(),
|
||||
ExternalAgent::Custom {
|
||||
name: name.clone().into(),
|
||||
command: custom_settings
|
||||
.get(&name.0)
|
||||
.map(|settings| settings.command.clone())
|
||||
.unwrap_or(placeholder_command()),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -1046,8 +1075,6 @@ impl AgentConfiguration {
|
||||
.child(Headline::new("External Agents"))
|
||||
.child(
|
||||
Button::new("add-agent", "Add Agent")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon(IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
@@ -1080,19 +1107,16 @@ impl AgentConfiguration {
|
||||
.child(self.render_agent_server(
|
||||
IconName::AiGemini,
|
||||
"Gemini CLI",
|
||||
ExternalAgent::Gemini,
|
||||
cx,
|
||||
))
|
||||
.child(Divider::horizontal().color(DividerColor::BorderFaded))
|
||||
.child(self.render_agent_server(
|
||||
IconName::AiClaude,
|
||||
"Claude Code",
|
||||
ExternalAgent::ClaudeCode,
|
||||
cx,
|
||||
))
|
||||
.map(|mut parent| {
|
||||
for agent in user_defined_agents {
|
||||
parent = parent.child(Divider::horizontal().color(DividerColor::BorderFaded))
|
||||
.child(agent);
|
||||
}
|
||||
parent
|
||||
})
|
||||
.children(user_defined_agents),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1100,18 +1124,47 @@ impl AgentConfiguration {
|
||||
&self,
|
||||
icon: IconName,
|
||||
name: impl Into<SharedString>,
|
||||
agent: ExternalAgent,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
h_flex().gap_1p5().justify_between().child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
|
||||
.child(Label::new(name.into()))
|
||||
.child(
|
||||
Icon::new(IconName::Check)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success),
|
||||
),
|
||||
)
|
||||
let name = name.into();
|
||||
h_flex()
|
||||
.p_1()
|
||||
.pl_2()
|
||||
.gap_1p5()
|
||||
.justify_between()
|
||||
.border_1()
|
||||
.rounded_md()
|
||||
.border_color(self.card_item_border_color(cx))
|
||||
.bg(self.card_item_bg_color(cx))
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
|
||||
.child(Label::new(name.clone())),
|
||||
)
|
||||
.child(
|
||||
Button::new(
|
||||
SharedString::from(format!("start_acp_thread-{name}")),
|
||||
"Start New Thread",
|
||||
)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::Thread)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
NewExternalAgentThread {
|
||||
agent: Some(agent.clone()),
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1207,12 +1260,15 @@ fn show_unable_to_uninstall_extension_with_context_server(
|
||||
let context_server_id = context_server_id.clone();
|
||||
async move |_workspace_handle, cx| {
|
||||
cx.update(|cx| {
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
});
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs,
|
||||
cx,
|
||||
move |settings, _| {
|
||||
settings
|
||||
.context_servers
|
||||
.remove(&context_server_id.0);
|
||||
},
|
||||
);
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
@@ -1250,7 +1306,7 @@ async fn open_new_agent_servers_entry_in_settings_editor(
|
||||
let settings = cx.global::<SettingsStore>();
|
||||
|
||||
let mut unique_server_name = None;
|
||||
let edits = settings.edits_for_update(&text, |settings| {
|
||||
let edits = settings.edits_for_update::<AllAgentServersSettings>(&text, |file| {
|
||||
let server_name: Option<SharedString> = (0..u8::MAX)
|
||||
.map(|i| {
|
||||
if i == 0 {
|
||||
@@ -1259,27 +1315,20 @@ async fn open_new_agent_servers_entry_in_settings_editor(
|
||||
format!("your_agent_{}", i).into()
|
||||
}
|
||||
})
|
||||
.find(|name| {
|
||||
!settings
|
||||
.agent_servers
|
||||
.as_ref()
|
||||
.is_some_and(|agent_servers| agent_servers.custom.contains_key(name))
|
||||
});
|
||||
.find(|name| !file.custom.contains_key(name));
|
||||
if let Some(server_name) = server_name {
|
||||
unique_server_name = Some(server_name.clone());
|
||||
settings
|
||||
.agent_servers
|
||||
.get_or_insert_default()
|
||||
.custom
|
||||
.insert(
|
||||
server_name,
|
||||
settings::CustomAgentServerSettings {
|
||||
file.custom.insert(
|
||||
server_name,
|
||||
CustomAgentServerSettings {
|
||||
command: AgentServerCommand {
|
||||
path: "path_to_executable".into(),
|
||||
args: vec![],
|
||||
env: Some(HashMap::default()),
|
||||
default_mode: None,
|
||||
},
|
||||
);
|
||||
default_mode: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ use collections::HashSet;
|
||||
use fs::Fs;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, Task};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_models::provider::open_ai_compatible::{AvailableModel, ModelCapabilities};
|
||||
use settings::{OpenAiCompatibleSettingsContent, update_settings_file};
|
||||
use language_models::{
|
||||
AllLanguageModelSettings, OpenAiCompatibleSettingsContent,
|
||||
provider::open_ai_compatible::{AvailableModel, ModelCapabilities},
|
||||
};
|
||||
use settings::update_settings_file;
|
||||
use ui::{
|
||||
Banner, Checkbox, KeyBinding, Modal, ModalFooter, ModalHeader, Section, ToggleState, prelude::*,
|
||||
};
|
||||
@@ -235,19 +238,14 @@ fn save_provider_to_settings(
|
||||
task.await
|
||||
.map_err(|_| "Failed to write API key to keychain")?;
|
||||
cx.update(|cx| {
|
||||
update_settings_file(fs, cx, |settings, _cx| {
|
||||
settings
|
||||
.language_models
|
||||
.get_or_insert_default()
|
||||
.openai_compatible
|
||||
.get_or_insert_default()
|
||||
.insert(
|
||||
provider_name,
|
||||
OpenAiCompatibleSettingsContent {
|
||||
api_url,
|
||||
available_models: models,
|
||||
},
|
||||
);
|
||||
update_settings_file::<AllLanguageModelSettings>(fs, cx, |settings, _cx| {
|
||||
settings.openai_compatible.get_or_insert_default().insert(
|
||||
provider_name,
|
||||
OpenAiCompatibleSettingsContent {
|
||||
api_url,
|
||||
available_models: models,
|
||||
},
|
||||
);
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -422,17 +422,18 @@ impl ConfigureContextServerModal {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let original_server_id = self.original_server_id.clone();
|
||||
update_settings_file(fs.clone(), cx, move |current, _| {
|
||||
if let Some(original_id) = original_server_id {
|
||||
if original_id != id {
|
||||
current.project.context_servers.remove(&original_id.0);
|
||||
update_settings_file::<ProjectSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |project_settings, _| {
|
||||
if let Some(original_id) = original_server_id {
|
||||
if original_id != id {
|
||||
project_settings.context_servers.remove(&original_id.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
current
|
||||
.project
|
||||
.context_servers
|
||||
.insert(id.0, settings.into());
|
||||
});
|
||||
project_settings.context_servers.insert(id.0, settings);
|
||||
},
|
||||
);
|
||||
});
|
||||
} else if let Some(existing_server) = existing_server {
|
||||
self.context_server_store
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use context_server::ContextServerId;
|
||||
use gpui::{
|
||||
DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Window, prelude::*,
|
||||
};
|
||||
use ui::{Divider, DividerColor, Modal, ModalHeader, WithScrollbar, prelude::*};
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
pub struct ConfigureContextServerToolsModal {
|
||||
context_server_id: ContextServerId,
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
focus_handle: FocusHandle,
|
||||
expanded_tools: std::collections::HashMap<String, bool>,
|
||||
scroll_handle: ScrollHandle,
|
||||
}
|
||||
|
||||
impl ConfigureContextServerToolsModal {
|
||||
fn new(
|
||||
context_server_id: ContextServerId,
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context_server_id,
|
||||
tools,
|
||||
focus_handle: cx.focus_handle(),
|
||||
expanded_tools: std::collections::HashMap::new(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(
|
||||
context_server_id: ContextServerId,
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
Self::new(context_server_id, tools, window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent)
|
||||
}
|
||||
|
||||
fn render_modal_content(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let tools_by_source = self.tools.read(cx).tools_by_source(cx);
|
||||
let server_tools = tools_by_source
|
||||
.get(&ToolSource::ContextServer {
|
||||
id: self.context_server_id.0.clone().into(),
|
||||
})
|
||||
.map(|tools| tools.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.pb_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.id("modal_content")
|
||||
.px_2()
|
||||
.gap_1()
|
||||
.max_h_128()
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.children(server_tools.iter().enumerate().flat_map(|(index, tool)| {
|
||||
let tool_name = tool.name();
|
||||
let is_expanded = self
|
||||
.expanded_tools
|
||||
.get(&tool_name)
|
||||
.copied()
|
||||
.unwrap_or(false);
|
||||
|
||||
let icon = if is_expanded {
|
||||
IconName::ChevronUp
|
||||
} else {
|
||||
IconName::ChevronDown
|
||||
};
|
||||
|
||||
let mut items = vec![
|
||||
v_flex()
|
||||
.child(
|
||||
h_flex()
|
||||
.id(SharedString::from(format!("tool-header-{}", index)))
|
||||
.py_1()
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.rounded_sm()
|
||||
.hover(|s| s.bg(cx.theme().colors().element_hover))
|
||||
.child(
|
||||
Label::new(tool_name.clone())
|
||||
.buffer_font(cx)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Icon::new(icon)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.on_click(cx.listener({
|
||||
move |this, _event, _window, _cx| {
|
||||
let current = this
|
||||
.expanded_tools
|
||||
.get(&tool_name)
|
||||
.copied()
|
||||
.unwrap_or(false);
|
||||
this.expanded_tools
|
||||
.insert(tool_name.clone(), !current);
|
||||
_cx.notify();
|
||||
}
|
||||
})),
|
||||
)
|
||||
.when(is_expanded, |this| {
|
||||
this.child(
|
||||
Label::new(tool.description()).color(Color::Muted).mx_1(),
|
||||
)
|
||||
})
|
||||
.into_any_element(),
|
||||
];
|
||||
|
||||
if index < server_tools.len() - 1 {
|
||||
items.push(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.child(Divider::horizontal().color(DividerColor::BorderVariant))
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
|
||||
items
|
||||
})),
|
||||
)
|
||||
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModalView for ConfigureContextServerToolsModal {}
|
||||
|
||||
impl Focusable for ConfigureContextServerToolsModal {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ConfigureContextServerToolsModal {}
|
||||
|
||||
impl Render for ConfigureContextServerToolsModal {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.key_context("ContextServerToolsModal")
|
||||
.occlude()
|
||||
.elevation_3(cx)
|
||||
.w(rems(34.))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.track_focus(&self.focus_handle)
|
||||
.child(
|
||||
Modal::new("configure-context-server-tools", None::<ScrollHandle>)
|
||||
.header(
|
||||
ModalHeader::new()
|
||||
.headline(format!("Tools from {}", self.context_server_id.0))
|
||||
.show_dismiss_button(true),
|
||||
)
|
||||
.child(self.render_modal_content(window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use agent_settings::{AgentProfileId, AgentProfileSettings};
|
||||
use agent_settings::{
|
||||
AgentProfileContent, AgentProfileId, AgentProfileSettings, AgentSettings, AgentSettingsContent,
|
||||
ContextServerPresetContent,
|
||||
};
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use fs::Fs;
|
||||
use gpui::{App, Context, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, Window};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use settings::{AgentProfileContent, ContextServerPresetContent, update_settings_file};
|
||||
use settings::update_settings_file;
|
||||
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
|
||||
@@ -263,19 +266,15 @@ impl PickerDelegate for ToolPickerDelegate {
|
||||
is_enabled
|
||||
};
|
||||
|
||||
update_settings_file(self.fs.clone(), cx, {
|
||||
update_settings_file::<AgentSettings>(self.fs.clone(), cx, {
|
||||
let profile_id = self.profile_id.clone();
|
||||
let default_profile = self.profile_settings.clone();
|
||||
let server_id = server_id.clone();
|
||||
let tool_name = tool_name.clone();
|
||||
move |settings, _cx| {
|
||||
let profiles = settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.profiles
|
||||
.get_or_insert_default();
|
||||
move |settings: &mut AgentSettingsContent, _cx| {
|
||||
let profiles = settings.profiles.get_or_insert_default();
|
||||
let profile = profiles
|
||||
.entry(profile_id.0)
|
||||
.entry(profile_id)
|
||||
.or_insert_with(|| AgentProfileContent {
|
||||
name: default_profile.name.into(),
|
||||
tools: default_profile.tools,
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::{
|
||||
ModelUsageContext,
|
||||
language_model_selector::{LanguageModelSelector, language_model_selector},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{Entity, FocusHandle, SharedString};
|
||||
use picker::popover_menu::PickerPopoverMenu;
|
||||
@@ -38,12 +39,14 @@ impl AgentModelSelector {
|
||||
let model_id = model.id().0.to_string();
|
||||
match &model_usage_context {
|
||||
ModelUsageContext::InlineAssistant => {
|
||||
update_settings_file(fs.clone(), cx, move |settings, _cx| {
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_inline_assistant_model(provider.clone(), model_id);
|
||||
});
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
settings
|
||||
.set_inline_assistant_model(provider.clone(), model_id);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::ops::Range;
|
||||
use std::ops::{Not, Range};
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -10,9 +10,6 @@ use project::agent_server_store::{
|
||||
AgentServerCommand, AllAgentServersSettings, CLAUDE_CODE_NAME, GEMINI_NAME,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{
|
||||
DefaultAgentView as DefaultView, LanguageModelProviderSetting, LanguageModelSelection,
|
||||
};
|
||||
use zed_actions::OpenBrowser;
|
||||
use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
|
||||
|
||||
@@ -36,7 +33,7 @@ use agent::{
|
||||
history_store::{HistoryEntryId, HistoryStore},
|
||||
thread_store::{TextThreadStore, ThreadStore},
|
||||
};
|
||||
use agent_settings::AgentSettings;
|
||||
use agent_settings::{AgentDockPosition, AgentSettings, DefaultView};
|
||||
use ai_onboarding::AgentPanelOnboarding;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
|
||||
@@ -662,43 +659,6 @@ impl AgentPanel {
|
||||
)
|
||||
});
|
||||
|
||||
let mut old_disable_ai = false;
|
||||
cx.observe_global_in::<SettingsStore>(window, move |panel, window, cx| {
|
||||
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
|
||||
if old_disable_ai != disable_ai {
|
||||
let agent_panel_id = cx.entity_id();
|
||||
let agent_panel_visible = panel
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let agent_dock_position = panel.position(window, cx);
|
||||
let agent_dock = workspace.dock_at_position(agent_dock_position);
|
||||
let agent_panel_focused = agent_dock
|
||||
.read(cx)
|
||||
.active_panel()
|
||||
.is_some_and(|panel| panel.panel_id() == agent_panel_id);
|
||||
|
||||
let active_panel_visible = agent_dock
|
||||
.read(cx)
|
||||
.visible_panel()
|
||||
.is_some_and(|panel| panel.panel_id() == agent_panel_id);
|
||||
|
||||
if agent_panel_focused {
|
||||
cx.dispatch_action(&ToggleFocus);
|
||||
}
|
||||
|
||||
active_panel_visible
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if agent_panel_visible {
|
||||
cx.emit(PanelEvent::Close);
|
||||
}
|
||||
|
||||
old_disable_ai = disable_ai;
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
active_view,
|
||||
workspace,
|
||||
@@ -711,9 +671,11 @@ impl AgentPanel {
|
||||
prompt_store,
|
||||
configuration: None,
|
||||
configuration_subscription: None,
|
||||
|
||||
inline_assist_context_store,
|
||||
previous_view: None,
|
||||
history_store: history_store.clone(),
|
||||
|
||||
new_thread_menu_handle: PopoverMenuHandle::default(),
|
||||
agent_panel_menu_handle: PopoverMenuHandle::default(),
|
||||
assistant_navigation_menu_handle: PopoverMenuHandle::default(),
|
||||
@@ -738,6 +700,7 @@ impl AgentPanel {
|
||||
if workspace
|
||||
.panel::<Self>(cx)
|
||||
.is_some_and(|panel| panel.read(cx).enabled(cx))
|
||||
&& !DisableAiSettings::get_global(cx).disable_ai
|
||||
{
|
||||
workspace.toggle_panel_focus::<Self>(window, cx);
|
||||
}
|
||||
@@ -1095,14 +1058,17 @@ impl AgentPanel {
|
||||
match self.active_view.which_font_size_used() {
|
||||
WhichFontSize::AgentFont => {
|
||||
if persist {
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
|
||||
let agent_font_size =
|
||||
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
|
||||
let _ = settings
|
||||
.theme
|
||||
.agent_font_size
|
||||
.insert(theme::clamp_font_size(agent_font_size).into());
|
||||
});
|
||||
update_settings_file::<ThemeSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, cx| {
|
||||
let agent_font_size =
|
||||
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
|
||||
let _ = settings
|
||||
.agent_font_size
|
||||
.insert(Some(theme::clamp_font_size(agent_font_size).into()));
|
||||
},
|
||||
);
|
||||
} else {
|
||||
theme::adjust_agent_font_size(cx, |size| size + delta);
|
||||
}
|
||||
@@ -1123,8 +1089,8 @@ impl AgentPanel {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if action.persist {
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.theme.agent_font_size = None;
|
||||
update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.agent_font_size = None;
|
||||
});
|
||||
} else {
|
||||
theme::reset_agent_font_size(cx);
|
||||
@@ -1209,17 +1175,11 @@ impl AgentPanel {
|
||||
.is_none_or(|model| model.provider.id() != provider.id())
|
||||
&& let Some(model) = provider.default_model(cx)
|
||||
{
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
let provider = model.provider_id().0.to_string();
|
||||
let model = model.id().0.to_string();
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_model(LanguageModelSelection {
|
||||
provider: LanguageModelProviderSetting(provider),
|
||||
model,
|
||||
})
|
||||
});
|
||||
update_settings_file::<AgentSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model),
|
||||
);
|
||||
}
|
||||
|
||||
self.new_thread(&NewThread::default(), window, cx);
|
||||
@@ -1464,7 +1424,11 @@ impl Focusable for AgentPanel {
|
||||
}
|
||||
|
||||
fn agent_panel_dock_position(cx: &App) -> DockPosition {
|
||||
AgentSettings::get_global(cx).dock.into()
|
||||
match AgentSettings::get_global(cx).dock {
|
||||
AgentDockPosition::Left => DockPosition::Left,
|
||||
AgentDockPosition::Bottom => DockPosition::Bottom,
|
||||
AgentDockPosition::Right => DockPosition::Right,
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PanelEvent> for AgentPanel {}
|
||||
@@ -1483,11 +1447,13 @@ impl Panel for AgentPanel {
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
|
||||
settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_dock(position.into());
|
||||
settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => AgentDockPosition::Left,
|
||||
DockPosition::Bottom => AgentDockPosition::Bottom,
|
||||
DockPosition::Right => AgentDockPosition::Right,
|
||||
};
|
||||
settings.set_dock(dock);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1533,7 +1499,7 @@ impl Panel for AgentPanel {
|
||||
}
|
||||
|
||||
fn enabled(&self, cx: &App) -> bool {
|
||||
AgentSettings::get_global(cx).enabled(cx)
|
||||
DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
|
||||
}
|
||||
|
||||
fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
|
||||
|
||||
@@ -14,6 +14,7 @@ mod message_editor;
|
||||
mod profile_selector;
|
||||
mod slash_command;
|
||||
mod slash_command_picker;
|
||||
mod slash_command_settings;
|
||||
mod terminal_codegen;
|
||||
mod terminal_inline_assistant;
|
||||
mod text_thread_editor;
|
||||
@@ -23,7 +24,7 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::ThreadId;
|
||||
use agent_settings::{AgentProfileId, AgentSettings};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
@@ -39,12 +40,13 @@ use project::agent_server_store::AgentServerCommand;
|
||||
use prompt_store::PromptBuilder;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{LanguageModelSelection, Settings as _, SettingsStore};
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use std::any::TypeId;
|
||||
|
||||
use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
|
||||
pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
|
||||
pub use crate::inline_assistant::InlineAssistant;
|
||||
use crate::slash_command_settings::SlashCommandSettings;
|
||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||
pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
|
||||
use zed_actions;
|
||||
@@ -255,6 +257,7 @@ pub fn init(
|
||||
cx: &mut App,
|
||||
) {
|
||||
AgentSettings::register(cx);
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
assistant_context::init(client.clone(), cx);
|
||||
rules_library::init(cx);
|
||||
@@ -410,6 +413,8 @@ fn register_slash_commands(cx: &mut App) {
|
||||
slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
|
||||
slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
|
||||
@@ -429,4 +434,21 @@ fn register_slash_commands(cx: &mut App) {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
update_slash_commands_from_settings(cx);
|
||||
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn update_slash_commands_from_settings(cx: &mut App) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
let settings = SlashCommandSettings::get_global(cx);
|
||||
|
||||
if settings.cargo_workspace.enabled {
|
||||
slash_command_registry
|
||||
.register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
|
||||
} else {
|
||||
slash_command_registry
|
||||
.unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,15 +743,15 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Task<Result<Vec<CompletionResponse>>> {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let position = buffer_position.to_point(&snapshot);
|
||||
let line_start = Point::new(position.row, 0);
|
||||
let offset_to_line = snapshot.point_to_offset(line_start);
|
||||
let mut lines = snapshot.text_for_range(line_start..position).lines();
|
||||
let Some(line) = lines.next() else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
let Some(state) = MentionCompletion::try_parse(line, offset_to_line) else {
|
||||
let state = buffer.update(cx, |buffer, _cx| {
|
||||
let position = buffer_position.to_point(buffer);
|
||||
let line_start = Point::new(position.row, 0);
|
||||
let offset_to_line = buffer.point_to_offset(line_start);
|
||||
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||
let line = lines.next()?;
|
||||
MentionCompletion::try_parse(line, offset_to_line)
|
||||
});
|
||||
let Some(state) = state else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
|
||||
@@ -761,6 +761,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let source_range = snapshot.anchor_before(state.source_range.start)
|
||||
..snapshot.anchor_after(state.source_range.end);
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ pub(crate) fn search_files(
|
||||
fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.as_str(),
|
||||
&None,
|
||||
None,
|
||||
false,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
|
||||
@@ -5,6 +5,7 @@ use extension::ExtensionManifest;
|
||||
use fs::Fs;
|
||||
use gpui::WeakEntity;
|
||||
use language::LanguageRegistry;
|
||||
use project::project_settings::ProjectSettings;
|
||||
use settings::update_settings_file;
|
||||
use ui::prelude::*;
|
||||
use util::ResultExt;
|
||||
@@ -68,9 +69,8 @@ fn remove_context_server_settings(
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
update_settings_file::<ProjectSettings>(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.context_servers
|
||||
.retain(|server_id, _| !context_server_ids.contains(server_id));
|
||||
});
|
||||
|
||||
@@ -144,7 +144,8 @@ impl InlineAssistant {
|
||||
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
let enabled = AgentSettings::get_global(cx).enabled(cx);
|
||||
let enabled = !DisableAiSettings::get_global(cx).disable_ai
|
||||
&& AgentSettings::get_global(cx).enabled;
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.set_assistant_enabled(enabled, cx)
|
||||
});
|
||||
@@ -256,7 +257,8 @@ impl InlineAssistant {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
if !AgentSettings::get_global(cx).enabled(cx) {
|
||||
let settings = AgentSettings::get_global(cx);
|
||||
if !settings.enabled || DisableAiSettings::get_global(cx).disable_ai {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -742,14 +744,19 @@ impl InlineAssistant {
|
||||
.update(cx, |editor, cx| {
|
||||
let scroll_top = editor.scroll_position(cx).y;
|
||||
let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.);
|
||||
editor_assists.scroll_lock = editor
|
||||
let prompt_row = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.map(|row| row.0 as f32)
|
||||
.filter(|prompt_row| (scroll_top..scroll_bottom).contains(&prompt_row))
|
||||
.map(|prompt_row| InlineAssistScrollLock {
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
|
||||
if (scroll_top..scroll_bottom).contains(&prompt_row) {
|
||||
editor_assists.scroll_lock = Some(InlineAssistScrollLock {
|
||||
assist_id,
|
||||
distance_from_top: prompt_row - scroll_top,
|
||||
});
|
||||
} else {
|
||||
editor_assists.scroll_lock = None;
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -910,12 +917,14 @@ impl InlineAssistant {
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let scroll_position = editor.scroll_position(cx);
|
||||
let target_scroll_top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32
|
||||
let target_scroll_top = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32
|
||||
- scroll_lock.distance_from_top;
|
||||
if target_scroll_top != scroll_position.y {
|
||||
editor.set_scroll_position(point(scroll_position.x, target_scroll_top), window, cx);
|
||||
}
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -959,14 +968,14 @@ impl InlineAssistant {
|
||||
if let Some(decorations) = assist.decorations.as_ref() {
|
||||
let distance_from_top = editor.update(cx, |editor, cx| {
|
||||
let scroll_top = editor.scroll_position(cx).y;
|
||||
let prompt_row =
|
||||
editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
|
||||
Some(prompt_row - scroll_top)
|
||||
let prompt_row = editor
|
||||
.row_for_block(decorations.prompt_block_id, cx)
|
||||
.unwrap()
|
||||
.0 as f32;
|
||||
prompt_row - scroll_top
|
||||
});
|
||||
|
||||
if distance_from_top.is_none_or(|distance_from_top| {
|
||||
distance_from_top != scroll_lock.distance_from_top
|
||||
}) {
|
||||
if distance_from_top != scroll_lock.distance_from_top {
|
||||
editor_assists.scroll_lock = None;
|
||||
}
|
||||
}
|
||||
@@ -1786,7 +1795,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||
_: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
if !AgentSettings::get_global(cx).enabled(cx) {
|
||||
if !AgentSettings::get_global(cx).enabled {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{ManageProfiles, ToggleProfileSelector};
|
||||
use agent_settings::{
|
||||
AgentProfile, AgentProfileId, AgentSettings, AvailableProfiles, builtin_profiles,
|
||||
AgentDockPosition, AgentProfile, AgentProfileId, AgentSettings, AvailableProfiles,
|
||||
builtin_profiles,
|
||||
};
|
||||
use fs::Fs;
|
||||
use gpui::{Action, Entity, FocusHandle, Subscription, prelude::*};
|
||||
use settings::{DockPosition, Settings as _, SettingsStore, update_settings_file};
|
||||
use settings::{Settings as _, SettingsStore, update_settings_file};
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
ContextMenu, ContextMenuEntry, DocumentationEdge, DocumentationSide, PopoverMenu,
|
||||
@@ -141,13 +142,10 @@ impl ProfileSelector {
|
||||
let fs = self.fs.clone();
|
||||
let provider = self.provider.clone();
|
||||
move |_window, cx| {
|
||||
update_settings_file(fs.clone(), cx, {
|
||||
update_settings_file::<AgentSettings>(fs.clone(), cx, {
|
||||
let profile_id = profile_id.clone();
|
||||
move |settings, _cx| {
|
||||
settings
|
||||
.agent
|
||||
.get_or_insert_default()
|
||||
.set_profile(profile_id.0);
|
||||
settings.set_profile(profile_id);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -218,10 +216,10 @@ impl Render for ProfileSelector {
|
||||
}
|
||||
}
|
||||
|
||||
fn documentation_side(position: DockPosition) -> DocumentationSide {
|
||||
fn documentation_side(position: AgentDockPosition) -> DocumentationSide {
|
||||
match position {
|
||||
DockPosition::Left => DocumentationSide::Right,
|
||||
DockPosition::Bottom => DocumentationSide::Left,
|
||||
DockPosition::Right => DocumentationSide::Left,
|
||||
AgentDockPosition::Left => DocumentationSide::Right,
|
||||
AgentDockPosition::Bottom => DocumentationSide::Left,
|
||||
AgentDockPosition::Right => DocumentationSide::Left,
|
||||
}
|
||||
}
|
||||
|
||||
37
crates/agent_ui/src/slash_command_settings.rs
Normal file
37
crates/agent_ui/src/slash_command_settings.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use anyhow::Result;
|
||||
use gpui::App;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
|
||||
|
||||
/// Settings for slash commands.
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "slash_commands")]
|
||||
pub struct SlashCommandSettings {
|
||||
/// Settings for the `/cargo-workspace` slash command.
|
||||
#[serde(default)]
|
||||
pub cargo_workspace: CargoWorkspaceCommandSettings,
|
||||
}
|
||||
|
||||
/// Settings for the `/cargo-workspace` slash command.
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
|
||||
pub struct CargoWorkspaceCommandSettings {
|
||||
/// Whether `/cargo-workspace` is enabled.
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Settings for SlashCommandSettings {
|
||||
type FileContent = Self;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
|
||||
SettingsSources::<Self::FileContent>::json_merge_with(
|
||||
[sources.default]
|
||||
.into_iter()
|
||||
.chain(sources.user)
|
||||
.chain(sources.server),
|
||||
)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
language_model_selector::{LanguageModelSelector, language_model_selector},
|
||||
ui::BurnModeTooltip,
|
||||
};
|
||||
use agent_settings::CompletionMode;
|
||||
use agent_settings::{AgentSettings, CompletionMode};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
|
||||
use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases};
|
||||
@@ -41,10 +41,7 @@ use project::{Project, Worktree};
|
||||
use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
|
||||
use rope::Point;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{
|
||||
LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore,
|
||||
update_settings_file,
|
||||
};
|
||||
use settings::{Settings, SettingsStore, update_settings_file};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp,
|
||||
@@ -297,16 +294,11 @@ impl TextThreadEditor {
|
||||
language_model_selector(
|
||||
|cx| LanguageModelRegistry::read_global(cx).default_model(),
|
||||
move |model, cx| {
|
||||
update_settings_file(fs.clone(), cx, move |settings, _| {
|
||||
let provider = model.provider_id().0.to_string();
|
||||
let model = model.id().0.to_string();
|
||||
settings.agent.get_or_insert_default().set_model(
|
||||
LanguageModelSelection {
|
||||
provider: LanguageModelProviderSetting(provider),
|
||||
model,
|
||||
},
|
||||
)
|
||||
});
|
||||
update_settings_file::<AgentSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
@@ -485,7 +477,7 @@ impl TextThreadEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
|
||||
let selections = self.editor.read(cx).selections.disjoint_anchors();
|
||||
let mut commands_by_range = HashMap::default();
|
||||
let workspace = self.workspace.clone();
|
||||
self.context.update(cx, |context, cx| {
|
||||
@@ -1831,7 +1823,7 @@ impl TextThreadEditor {
|
||||
|
||||
fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.context.update(cx, |context, cx| {
|
||||
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
|
||||
let selections = self.editor.read(cx).selections.disjoint_anchors();
|
||||
for selection in selections.as_ref() {
|
||||
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let range = selection
|
||||
|
||||
@@ -23,7 +23,6 @@ http_client.workspace = true
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
strum.workspace = true
|
||||
thiserror.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
@@ -8,7 +8,6 @@ use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::B
|
||||
use http_client::http::{self, HeaderMap, HeaderValue};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use settings::{AnthropicAvailableModel as AvailableModel, ModelMode};
|
||||
use strum::{EnumIter, EnumString};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -32,24 +31,6 @@ pub enum AnthropicModelMode {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ModelMode> for AnthropicModelMode {
|
||||
fn from(value: ModelMode) -> Self {
|
||||
match value {
|
||||
ModelMode::Default => AnthropicModelMode::Default,
|
||||
ModelMode::Thinking { budget_tokens } => AnthropicModelMode::Thinking { budget_tokens },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnthropicModelMode> for ModelMode {
|
||||
fn from(value: AnthropicModelMode) -> Self {
|
||||
match value {
|
||||
AnthropicModelMode::Default => ModelMode::Default,
|
||||
AnthropicModelMode::Thinking { budget_tokens } => ModelMode::Thinking { budget_tokens },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||
pub enum Model {
|
||||
|
||||
@@ -14,6 +14,7 @@ path = "src/assistant_slash_commands.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
cargo_toml.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
context_server.workspace = true
|
||||
@@ -34,6 +35,7 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
text.workspace = true
|
||||
toml.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod cargo_workspace_command;
|
||||
mod context_server_command;
|
||||
mod default_command;
|
||||
mod delta_command;
|
||||
@@ -11,6 +12,7 @@ mod streaming_example_command;
|
||||
mod symbols_command;
|
||||
mod tab_command;
|
||||
|
||||
pub use crate::cargo_workspace_command::*;
|
||||
pub use crate::context_server_command::*;
|
||||
pub use crate::default_command::*;
|
||||
pub use crate::delta_command::*;
|
||||
|
||||
158
crates/assistant_slash_commands/src/cargo_workspace_command.rs
Normal file
158
crates/assistant_slash_commands/src/cargo_workspace_command.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandResult,
|
||||
};
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use project::{Project, ProjectPath};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct CargoWorkspaceSlashCommand;
|
||||
|
||||
impl CargoWorkspaceSlashCommand {
|
||||
async fn build_message(fs: Arc<dyn Fs>, path_to_cargo_toml: &Path) -> Result<String> {
|
||||
let buffer = fs.load(path_to_cargo_toml).await?;
|
||||
let cargo_toml: cargo_toml::Manifest = toml::from_str(&buffer)?;
|
||||
|
||||
let mut message = String::new();
|
||||
writeln!(message, "You are in a Rust project.")?;
|
||||
|
||||
if let Some(workspace) = cargo_toml.workspace {
|
||||
writeln!(
|
||||
message,
|
||||
"The project is a Cargo workspace with the following members:"
|
||||
)?;
|
||||
for member in workspace.members {
|
||||
writeln!(message, "- {member}")?;
|
||||
}
|
||||
|
||||
if !workspace.default_members.is_empty() {
|
||||
writeln!(message, "The default members are:")?;
|
||||
for member in workspace.default_members {
|
||||
writeln!(message, "- {member}")?;
|
||||
}
|
||||
}
|
||||
|
||||
if !workspace.dependencies.is_empty() {
|
||||
writeln!(
|
||||
message,
|
||||
"The following workspace dependencies are installed:"
|
||||
)?;
|
||||
for dependency in workspace.dependencies.keys() {
|
||||
writeln!(message, "- {dependency}")?;
|
||||
}
|
||||
}
|
||||
} else if let Some(package) = cargo_toml.package {
|
||||
writeln!(
|
||||
message,
|
||||
"The project name is \"{name}\".",
|
||||
name = package.name
|
||||
)?;
|
||||
|
||||
let description = package
|
||||
.description
|
||||
.as_ref()
|
||||
.and_then(|description| description.get().ok().cloned());
|
||||
if let Some(description) = description.as_ref() {
|
||||
writeln!(message, "It describes itself as \"{description}\".")?;
|
||||
}
|
||||
|
||||
if !cargo_toml.dependencies.is_empty() {
|
||||
writeln!(message, "The following dependencies are installed:")?;
|
||||
for dependency in cargo_toml.dependencies.keys() {
|
||||
writeln!(message, "- {dependency}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
fn path_to_cargo_toml(project: Entity<Project>, cx: &mut App) -> Option<Arc<Path>> {
|
||||
let worktree = project.read(cx).worktrees(cx).next()?;
|
||||
let worktree = worktree.read(cx);
|
||||
let entry = worktree.entry_for_path("Cargo.toml")?;
|
||||
let path = ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: entry.path.clone(),
|
||||
};
|
||||
Some(Arc::from(
|
||||
project.read(cx).absolute_path(&path, cx)?.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl SlashCommand for CargoWorkspaceSlashCommand {
|
||||
fn name(&self) -> String {
|
||||
"cargo-workspace".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"insert project workspace metadata".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Insert Project Workspace Metadata".into()
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_cancel: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakEntity<Workspace>>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<Vec<ArgumentCompletion>>> {
|
||||
Task::ready(Err(anyhow!("this command does not require argument")))
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
_arguments: &[String],
|
||||
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
|
||||
_context_buffer: BufferSnapshot,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<SlashCommandResult> {
|
||||
let output = workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().clone();
|
||||
let fs = workspace.project().read(cx).fs().clone();
|
||||
let path = Self::path_to_cargo_toml(project, cx);
|
||||
let output = cx.background_spawn(async move {
|
||||
let path = path.with_context(|| "Cargo.toml not found")?;
|
||||
Self::build_message(fs, &path).await
|
||||
});
|
||||
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let text = output.await?;
|
||||
let range = 0..text.len();
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: vec![SlashCommandOutputSection {
|
||||
range,
|
||||
icon: IconName::FileTree,
|
||||
label: "Project".into(),
|
||||
metadata: None,
|
||||
}],
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.into_event_stream())
|
||||
})
|
||||
});
|
||||
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ impl DiagnosticsSlashCommand {
|
||||
fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.as_str(),
|
||||
&None,
|
||||
None,
|
||||
false,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
|
||||
@@ -104,7 +104,7 @@ impl FileSlashCommand {
|
||||
fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.as_str(),
|
||||
&None,
|
||||
None,
|
||||
false,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
|
||||
@@ -1445,8 +1445,8 @@ mod tests {
|
||||
|
||||
fn init_test_with_config(cx: &mut TestAppContext, data_dir: &Path) {
|
||||
cx.update(|cx| {
|
||||
paths::set_custom_data_dir(data_dir.to_str().unwrap());
|
||||
// Set custom data directory (config will be under data_dir/config)
|
||||
paths::set_custom_data_dir(data_dir.to_str().unwrap());
|
||||
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
@@ -1537,11 +1537,14 @@ mod tests {
|
||||
// First, test with format_on_save enabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
|
||||
settings.project.all_languages.defaults.formatter =
|
||||
Some(language::language_settings::SelectedFormatter::Auto);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.format_on_save = Some(FormatOnSave::On);
|
||||
settings.defaults.formatter =
|
||||
Some(language::language_settings::SelectedFormatter::Auto);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1600,10 +1603,12 @@ mod tests {
|
||||
// Next, test with format_on_save disabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.format_on_save =
|
||||
Some(FormatOnSave::Off);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.format_on_save = Some(FormatOnSave::Off);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1674,13 +1679,12 @@ mod tests {
|
||||
// First, test with remove_trailing_whitespace_on_save enabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings
|
||||
.project
|
||||
.all_languages
|
||||
.defaults
|
||||
.remove_trailing_whitespace_on_save = Some(true);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.remove_trailing_whitespace_on_save = Some(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1737,13 +1741,12 @@ mod tests {
|
||||
// Next, test with remove_trailing_whitespace_on_save disabled
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings
|
||||
.project
|
||||
.all_languages
|
||||
.defaults
|
||||
.remove_trailing_whitespace_on_save = Some(false);
|
||||
});
|
||||
store.update_user_settings::<language::language_settings::AllLanguageSettings>(
|
||||
cx,
|
||||
|settings| {
|
||||
settings.defaults.remove_trailing_whitespace_on_save = Some(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ mod tests {
|
||||
use gpui::{AppContext, TestAppContext, UpdateGlobal};
|
||||
use language::{Language, LanguageConfig, LanguageMatcher};
|
||||
use language_model::fake_provider::FakeLanguageModel;
|
||||
use project::{FakeFs, Project};
|
||||
use project::{FakeFs, Project, WorktreeSettings};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use unindent::Unindent;
|
||||
@@ -849,21 +849,19 @@ mod tests {
|
||||
|
||||
cx.update(|cx| {
|
||||
use gpui::UpdateGlobal;
|
||||
use project::WorktreeSettings;
|
||||
use settings::SettingsStore;
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions = Some(vec![
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = Some(vec![
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1160,11 +1158,10 @@ mod tests {
|
||||
// Set global settings
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -230,7 +230,7 @@ mod tests {
|
||||
use gpui::{AppContext, TestAppContext, UpdateGlobal};
|
||||
use indoc::indoc;
|
||||
use language_model::fake_provider::FakeLanguageModel;
|
||||
use project::{FakeFs, Project};
|
||||
use project::{FakeFs, Project, WorktreeSettings};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
@@ -507,20 +507,17 @@ mod tests {
|
||||
// Configure settings explicitly
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions = Some(vec![
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = Some(vec![
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
"**/.hidden_subdir".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -701,11 +698,10 @@ mod tests {
|
||||
// Set global settings
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -299,7 +299,7 @@ mod test {
|
||||
use gpui::{AppContext, TestAppContext, UpdateGlobal};
|
||||
use language::{Language, LanguageConfig, LanguageMatcher};
|
||||
use language_model::fake_provider::FakeLanguageModel;
|
||||
use project::{FakeFs, Project};
|
||||
use project::{FakeFs, Project, WorktreeSettings};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use util::path;
|
||||
@@ -677,21 +677,19 @@ mod test {
|
||||
|
||||
cx.update(|cx| {
|
||||
use gpui::UpdateGlobal;
|
||||
use project::WorktreeSettings;
|
||||
use settings::SettingsStore;
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions = Some(vec![
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = Some(vec![
|
||||
"**/.secretdir".to_string(),
|
||||
"**/.mymetadata".to_string(),
|
||||
]);
|
||||
settings.project.worktree.private_files = Some(
|
||||
vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
settings.private_files = Some(vec![
|
||||
"**/.mysecrets".to_string(),
|
||||
"**/*.privatekey".to_string(),
|
||||
"**/*.mysensitive".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -970,11 +968,10 @@ mod test {
|
||||
// Set global settings
|
||||
cx.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
|
||||
settings.project.worktree.private_files =
|
||||
Some(vec!["**/.env".to_string()].into());
|
||||
settings.private_files = Some(vec!["**/.env".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ gpui.workspace = true
|
||||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rodio = { workspace = true, features = [ "wav", "playback", "wav_output" ] }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
@@ -1,41 +1,62 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::App;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
|
||||
pub struct AudioSettings {
|
||||
/// Opt into the new audio system.
|
||||
#[serde(rename = "experimental.rodio_audio", default)]
|
||||
pub rodio_audio: bool, // default is false
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control for your microphone.
|
||||
/// This affects how loud you sound to others.
|
||||
#[serde(rename = "experimental.control_input_volume", default)]
|
||||
pub control_input_volume: bool,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control on everyone in the
|
||||
/// call. This makes call members who are too quite louder and those who are
|
||||
/// too loud quieter. This only affects how things sound for you.
|
||||
#[serde(rename = "experimental.control_output_volume", default)]
|
||||
pub control_output_volume: bool,
|
||||
}
|
||||
|
||||
/// Configuration of audio in Zed.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
|
||||
#[serde(default)]
|
||||
#[settings_key(key = "audio")]
|
||||
pub struct AudioSettingsContent {
|
||||
/// Opt into the new audio system.
|
||||
#[serde(rename = "experimental.rodio_audio", default)]
|
||||
pub rodio_audio: bool, // default is false
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control for your microphone.
|
||||
/// This affects how loud you sound to others.
|
||||
#[serde(rename = "experimental.control_input_volume", default)]
|
||||
pub control_input_volume: bool,
|
||||
/// Requires 'rodio_audio: true'
|
||||
///
|
||||
/// Use the new audio systems automatic gain control on everyone in the
|
||||
/// call. This makes call members who are too quite louder and those who are
|
||||
/// too loud quieter. This only affects how things sound for you.
|
||||
#[serde(rename = "experimental.control_output_volume", default)]
|
||||
pub control_output_volume: bool,
|
||||
}
|
||||
|
||||
/// Configuration of audio in Zed
|
||||
impl Settings for AudioSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let audio = &content.audio.as_ref().unwrap();
|
||||
AudioSettings {
|
||||
control_input_volume: audio.control_input_volume.unwrap(),
|
||||
control_output_volume: audio.control_output_volume.unwrap(),
|
||||
rodio_audio: audio.rodio_audio.unwrap(),
|
||||
}
|
||||
type FileContent = AudioSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
) {
|
||||
}
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
/// See docs on [LIVE_SETTINGS]
|
||||
|
||||
@@ -21,6 +21,7 @@ http_client.workspace = true
|
||||
log.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -8,8 +8,9 @@ use gpui::{
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use paths::remote_servers_dir;
|
||||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi};
|
||||
use smol::{fs, io::AsyncReadExt};
|
||||
use smol::{fs::File, process::Command};
|
||||
use std::{
|
||||
@@ -112,15 +113,47 @@ impl Drop for MacOsUnmounter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct AutoUpdateSetting(bool);
|
||||
|
||||
/// Whether or not to automatically check for updates.
|
||||
///
|
||||
/// Default: true
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey)]
|
||||
#[settings_key(None)]
|
||||
#[settings_ui(group = "Auto Update")]
|
||||
struct AutoUpdateSettingContent {
|
||||
pub auto_update: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for AutoUpdateSetting {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self(content.auto_update.unwrap())
|
||||
type FileContent = AutoUpdateSettingContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
let auto_update = [
|
||||
sources.server,
|
||||
sources.release_channel,
|
||||
sources.operating_system,
|
||||
sources.user,
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|value| value.and_then(|val| val.auto_update))
|
||||
.or(sources.default.auto_update)
|
||||
.ok_or_else(Self::missing_default)?;
|
||||
|
||||
Ok(Self(auto_update))
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
let mut cur = &mut Some(*current);
|
||||
vscode.enum_setting("update.mode", &mut cur, |s| match s {
|
||||
"none" | "manual" => Some(AutoUpdateSettingContent {
|
||||
auto_update: Some(false),
|
||||
}),
|
||||
_ => Some(AutoUpdateSettingContent {
|
||||
auto_update: Some(true),
|
||||
}),
|
||||
});
|
||||
*current = cur.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,7 +1002,7 @@ mod tests {
|
||||
#[gpui::test]
|
||||
fn test_auto_update_defaults_to_true(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let mut store = SettingsStore::new(cx, &settings::default_settings());
|
||||
let mut store = SettingsStore::new(cx);
|
||||
store
|
||||
.set_default_settings(&default_settings(), cx)
|
||||
.expect("Unable to set default settings");
|
||||
|
||||
@@ -35,6 +35,7 @@ language.workspace = true
|
||||
log.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
telemetry.workspace = true
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
use anyhow::Result;
|
||||
use gpui::App;
|
||||
use settings::Settings;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CallSettings {
|
||||
pub mute_on_join: bool,
|
||||
pub share_on_join: bool,
|
||||
}
|
||||
|
||||
/// Configuration of voice calls in Zed.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "calls")]
|
||||
pub struct CallSettingsContent {
|
||||
/// Whether the microphone should be muted when joining a channel or a call.
|
||||
///
|
||||
/// Default: false
|
||||
pub mute_on_join: Option<bool>,
|
||||
|
||||
/// Whether your current project should be shared when joining an empty channel.
|
||||
///
|
||||
/// Default: false
|
||||
pub share_on_join: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for CallSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let call = content.calls.clone().unwrap();
|
||||
CallSettings {
|
||||
mute_on_join: call.mute_on_join.unwrap(),
|
||||
share_on_join: call.share_on_join.unwrap(),
|
||||
}
|
||||
type FileContent = CallSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
) {
|
||||
}
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
@@ -339,70 +339,59 @@ fn main() -> Result<()> {
|
||||
"Dev servers were removed in v0.157.x please upgrade to SSH remoting: https://zed.dev/docs/remote-development"
|
||||
);
|
||||
|
||||
let sender: JoinHandle<anyhow::Result<()>> = thread::Builder::new()
|
||||
.name("CliReceiver".to_string())
|
||||
.spawn({
|
||||
let exit_status = exit_status.clone();
|
||||
let user_data_dir_for_thread = user_data_dir.clone();
|
||||
move || {
|
||||
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
||||
let (tx, rx) = (handshake.requests, handshake.responses);
|
||||
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
|
||||
let exit_status = exit_status.clone();
|
||||
let user_data_dir_for_thread = user_data_dir.clone();
|
||||
move || {
|
||||
let (_, handshake) = server.accept().context("Handshake after Zed spawn")?;
|
||||
let (tx, rx) = (handshake.requests, handshake.responses);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let wsl = args.wsl;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let wsl = None;
|
||||
#[cfg(target_os = "windows")]
|
||||
let wsl = args.wsl;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let wsl = None;
|
||||
|
||||
tx.send(CliRequest::Open {
|
||||
paths,
|
||||
urls,
|
||||
diff_paths,
|
||||
wsl,
|
||||
wait: args.wait,
|
||||
open_new_workspace,
|
||||
env,
|
||||
user_data_dir: user_data_dir_for_thread,
|
||||
})?;
|
||||
tx.send(CliRequest::Open {
|
||||
paths,
|
||||
urls,
|
||||
diff_paths,
|
||||
wsl,
|
||||
wait: args.wait,
|
||||
open_new_workspace,
|
||||
env,
|
||||
user_data_dir: user_data_dir_for_thread,
|
||||
})?;
|
||||
|
||||
while let Ok(response) = rx.recv() {
|
||||
match response {
|
||||
CliResponse::Ping => {}
|
||||
CliResponse::Stdout { message } => println!("{message}"),
|
||||
CliResponse::Stderr { message } => eprintln!("{message}"),
|
||||
CliResponse::Exit { status } => {
|
||||
exit_status.lock().replace(status);
|
||||
return Ok(());
|
||||
}
|
||||
while let Ok(response) = rx.recv() {
|
||||
match response {
|
||||
CliResponse::Ping => {}
|
||||
CliResponse::Stdout { message } => println!("{message}"),
|
||||
CliResponse::Stderr { message } => eprintln!("{message}"),
|
||||
CliResponse::Exit { status } => {
|
||||
exit_status.lock().replace(status);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
|
||||
let stdin_pipe_handle: Option<JoinHandle<anyhow::Result<()>>> =
|
||||
stdin_tmp_file.map(|mut tmp_file| {
|
||||
thread::Builder::new()
|
||||
.name("CliStdin".to_string())
|
||||
.spawn(move || {
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
if !io::IsTerminal::is_terminal(&stdin) {
|
||||
io::copy(&mut stdin, &mut tmp_file)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap()
|
||||
thread::spawn(move || {
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
if !io::IsTerminal::is_terminal(&stdin) {
|
||||
io::copy(&mut stdin, &mut tmp_file)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
let anonymous_fd_pipe_handles: Vec<_> = anonymous_fd_tmp_files
|
||||
.into_iter()
|
||||
.map(|(mut file, mut tmp_file)| {
|
||||
thread::Builder::new()
|
||||
.name("CliAnonymousFd".to_string())
|
||||
.spawn(move || io::copy(&mut file, &mut tmp_file))
|
||||
.unwrap()
|
||||
})
|
||||
.map(|(mut file, mut tmp_file)| thread::spawn(move || io::copy(&mut file, &mut tmp_file)))
|
||||
.collect();
|
||||
|
||||
if args.foreground {
|
||||
|
||||
@@ -41,6 +41,7 @@ rand.workspace = true
|
||||
regex.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc = { workspace = true, features = ["gpui"] }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_urlencoded.workspace = true
|
||||
|
||||
@@ -29,8 +29,9 @@ use proxy::connect_proxy_stream;
|
||||
use rand::prelude::*;
|
||||
use release_channel::{AppVersion, ReleaseChannel};
|
||||
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsContent};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
convert::TryFrom,
|
||||
@@ -95,22 +96,35 @@ actions!(
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
|
||||
#[settings_key(None)]
|
||||
pub struct ClientSettingsContent {
|
||||
server_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ClientSettings {
|
||||
pub server_url: String,
|
||||
}
|
||||
|
||||
impl Settings for ClientSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
type FileContent = ClientSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
let mut result = sources.json_merge::<Self>()?;
|
||||
if let Some(server_url) = &*ZED_SERVER_URL {
|
||||
return Self {
|
||||
server_url: server_url.clone(),
|
||||
};
|
||||
}
|
||||
Self {
|
||||
server_url: content.server_url.clone().unwrap(),
|
||||
result.server_url.clone_from(server_url)
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
|
||||
#[settings_key(None)]
|
||||
pub struct ProxySettingsContent {
|
||||
proxy: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
@@ -133,13 +147,19 @@ impl ProxySettings {
|
||||
}
|
||||
|
||||
impl Settings for ProxySettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
proxy: content.proxy.clone(),
|
||||
}
|
||||
type FileContent = ProxySettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
Ok(Self {
|
||||
proxy: sources
|
||||
.user
|
||||
.or(sources.server)
|
||||
.and_then(|value| value.proxy.clone())
|
||||
.or(sources.default.proxy.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.string_setting("http.proxy", &mut current.proxy);
|
||||
}
|
||||
}
|
||||
@@ -518,33 +538,37 @@ pub struct TelemetrySettings {
|
||||
pub metrics: bool,
|
||||
}
|
||||
|
||||
/// Control what info is collected by Zed.
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "telemetry")]
|
||||
pub struct TelemetrySettingsContent {
|
||||
/// Send debug info like crash reports.
|
||||
///
|
||||
/// Default: true
|
||||
pub diagnostics: Option<bool>,
|
||||
/// Send anonymized usage data like what languages you're using Zed with.
|
||||
///
|
||||
/// Default: true
|
||||
pub metrics: Option<bool>,
|
||||
}
|
||||
|
||||
impl settings::Settings for TelemetrySettings {
|
||||
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(),
|
||||
metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(),
|
||||
}
|
||||
type FileContent = TelemetrySettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
|
||||
let mut telemetry = settings::TelemetrySettingsContent::default();
|
||||
vscode.enum_setting("telemetry.telemetryLevel", &mut telemetry.metrics, |s| {
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.enum_setting("telemetry.telemetryLevel", &mut current.metrics, |s| {
|
||||
Some(s == "all")
|
||||
});
|
||||
vscode.enum_setting(
|
||||
"telemetry.telemetryLevel",
|
||||
&mut telemetry.diagnostics,
|
||||
|s| Some(matches!(s, "all" | "error" | "crash")),
|
||||
);
|
||||
vscode.enum_setting("telemetry.telemetryLevel", &mut current.diagnostics, |s| {
|
||||
Some(matches!(s, "all" | "error" | "crash"))
|
||||
});
|
||||
// we could translate telemetry.telemetryLevel, but just because users didn't want
|
||||
// to send microsoft telemetry doesn't mean they don't want to send it to zed. their
|
||||
// all/error/crash/off correspond to combinations of our "diagnostics" and "metrics".
|
||||
if let Some(diagnostics) = telemetry.diagnostics {
|
||||
current.telemetry.get_or_insert_default().diagnostics = Some(diagnostics)
|
||||
}
|
||||
if let Some(metrics) = telemetry.metrics {
|
||||
current.telemetry.get_or_insert_default().metrics = Some(metrics)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use futures::AsyncReadExt as _;
|
||||
use gpui::{App, Task};
|
||||
use gpui_tokio::Tokio;
|
||||
use http_client::http::request;
|
||||
use http_client::{AsyncBody, HttpClientWithUrl, HttpRequestExt, Method, Request, StatusCode};
|
||||
use http_client::{AsyncBody, HttpClientWithUrl, Method, Request, StatusCode};
|
||||
use parking_lot::RwLock;
|
||||
use yawc::WebSocket;
|
||||
|
||||
@@ -119,16 +119,15 @@ impl CloudApiClient {
|
||||
&self,
|
||||
system_id: Option<String>,
|
||||
) -> Result<CreateLlmTokenResponse> {
|
||||
let request_builder = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(
|
||||
self.http_client
|
||||
.build_zed_cloud_url("/client/llm_tokens", &[])?
|
||||
.as_ref(),
|
||||
)
|
||||
.when_some(system_id, |builder, system_id| {
|
||||
builder.header(ZED_SYSTEM_ID_HEADER_NAME, system_id)
|
||||
});
|
||||
let mut request_builder = Request::builder().method(Method::POST).uri(
|
||||
self.http_client
|
||||
.build_zed_cloud_url("/client/llm_tokens", &[])?
|
||||
.as_ref(),
|
||||
);
|
||||
|
||||
if let Some(system_id) = system_id {
|
||||
request_builder = request_builder.header(ZED_SYSTEM_ID_HEADER_NAME, system_id);
|
||||
}
|
||||
|
||||
let request = self.build_request(request_builder, AsyncBody::default())?;
|
||||
|
||||
|
||||
@@ -342,6 +342,79 @@ impl Database {
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns all feature flags.
|
||||
pub async fn list_feature_flags(&self) -> Result<Vec<feature_flag::Model>> {
|
||||
self.transaction(|tx| async move { Ok(feature_flag::Entity::find().all(&*tx).await?) })
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a new feature flag.
|
||||
pub async fn create_user_flag(&self, flag: &str, enabled_for_all: bool) -> Result<FlagId> {
|
||||
self.transaction(|tx| async move {
|
||||
let flag = feature_flag::Entity::insert(feature_flag::ActiveModel {
|
||||
flag: ActiveValue::set(flag.to_string()),
|
||||
enabled_for_all: ActiveValue::set(enabled_for_all),
|
||||
..Default::default()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?
|
||||
.last_insert_id;
|
||||
|
||||
Ok(flag)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Add the given user to the feature flag
|
||||
pub async fn add_user_flag(&self, user: UserId, flag: FlagId) -> Result<()> {
|
||||
self.transaction(|tx| async move {
|
||||
user_feature::Entity::insert(user_feature::ActiveModel {
|
||||
user_id: ActiveValue::set(user),
|
||||
feature_id: ActiveValue::set(flag),
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns the active flags for the user.
|
||||
pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
|
||||
self.transaction(|tx| async move {
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryAs {
|
||||
Flag,
|
||||
}
|
||||
|
||||
let flags_enabled_for_all = feature_flag::Entity::find()
|
||||
.filter(feature_flag::Column::EnabledForAll.eq(true))
|
||||
.select_only()
|
||||
.column(feature_flag::Column::Flag)
|
||||
.into_values::<_, QueryAs>()
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let flags_enabled_for_user = user::Model {
|
||||
id: user,
|
||||
..Default::default()
|
||||
}
|
||||
.find_linked(user::UserFlags)
|
||||
.select_only()
|
||||
.column(feature_flag::Column::Flag)
|
||||
.into_values::<_, QueryAs>()
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let mut all_flags = HashSet::from_iter(flags_enabled_for_all);
|
||||
all_flags.extend(flags_enabled_for_user);
|
||||
|
||||
Ok(all_flags.into_iter().collect())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_users_missing_github_user_created_at(&self) -> Result<Vec<user::Model>> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(user::Entity::find()
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod contributor;
|
||||
pub mod embedding;
|
||||
pub mod extension;
|
||||
pub mod extension_version;
|
||||
pub mod feature_flag;
|
||||
pub mod follower;
|
||||
pub mod language_server;
|
||||
pub mod notification;
|
||||
@@ -28,6 +29,7 @@ pub mod room_participant;
|
||||
pub mod server;
|
||||
pub mod signup;
|
||||
pub mod user;
|
||||
pub mod user_feature;
|
||||
pub mod worktree;
|
||||
pub mod worktree_diagnostic_summary;
|
||||
pub mod worktree_entry;
|
||||
|
||||
41
crates/collab/src/db/tables/feature_flag.rs
Normal file
41
crates/collab/src/db/tables/feature_flag.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
use crate::db::FlagId;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "feature_flags")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: FlagId,
|
||||
pub flag: String,
|
||||
pub enabled_for_all: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::user_feature::Entity")]
|
||||
UserFeature,
|
||||
}
|
||||
|
||||
impl Related<super::user_feature::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::UserFeature.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
pub struct FlaggedUsers;
|
||||
|
||||
impl Linked for FlaggedUsers {
|
||||
type FromEntity = Entity;
|
||||
|
||||
type ToEntity = super::user::Entity;
|
||||
|
||||
fn link(&self) -> Vec<RelationDef> {
|
||||
vec![
|
||||
super::user_feature::Relation::Flag.def().rev(),
|
||||
super::user_feature::Relation::User.def(),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,8 @@ pub enum Relation {
|
||||
HostedProjects,
|
||||
#[sea_orm(has_many = "super::channel_member::Entity")]
|
||||
ChannelMemberships,
|
||||
#[sea_orm(has_many = "super::user_feature::Entity")]
|
||||
UserFeatures,
|
||||
#[sea_orm(has_one = "super::contributor::Entity")]
|
||||
Contributor,
|
||||
}
|
||||
@@ -82,4 +84,25 @@ impl Related<super::channel_member::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::user_feature::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::UserFeatures.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
pub struct UserFlags;
|
||||
|
||||
impl Linked for UserFlags {
|
||||
type FromEntity = Entity;
|
||||
|
||||
type ToEntity = super::feature_flag::Entity;
|
||||
|
||||
fn link(&self) -> Vec<RelationDef> {
|
||||
vec![
|
||||
super::user_feature::Relation::User.def().rev(),
|
||||
super::user_feature::Relation::Flag.def(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
42
crates/collab/src/db/tables/user_feature.rs
Normal file
42
crates/collab/src/db/tables/user_feature.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
use crate::db::{FlagId, UserId};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "user_features")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub user_id: UserId,
|
||||
#[sea_orm(primary_key)]
|
||||
pub feature_id: FlagId,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::feature_flag::Entity",
|
||||
from = "Column::FeatureId",
|
||||
to = "super::feature_flag::Column::Id"
|
||||
)]
|
||||
Flag,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
User,
|
||||
}
|
||||
|
||||
impl Related<super::feature_flag::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Flag.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
@@ -6,6 +6,7 @@ mod db_tests;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod embedding_tests;
|
||||
mod extension_tests;
|
||||
mod feature_flag_tests;
|
||||
mod user_tests;
|
||||
|
||||
use crate::migrations::run_database_migrations;
|
||||
|
||||
66
crates/collab/src/db/tests/feature_flag_tests.rs
Normal file
66
crates/collab/src/db/tests/feature_flag_tests.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::{
|
||||
db::{Database, NewUserParams},
|
||||
test_both_dbs,
|
||||
};
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::sync::Arc;
|
||||
|
||||
test_both_dbs!(
|
||||
test_get_user_flags,
|
||||
test_get_user_flags_postgres,
|
||||
test_get_user_flags_sqlite
|
||||
);
|
||||
|
||||
async fn test_get_user_flags(db: &Arc<Database>) {
|
||||
let user_1 = db
|
||||
.create_user(
|
||||
"user1@example.com",
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user1".to_string(),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
let user_2 = db
|
||||
.create_user(
|
||||
"user2@example.com",
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "user2".to_string(),
|
||||
github_user_id: 2,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id;
|
||||
|
||||
const FEATURE_FLAG_ONE: &str = "brand-new-ux";
|
||||
const FEATURE_FLAG_TWO: &str = "cool-feature";
|
||||
const FEATURE_FLAG_THREE: &str = "feature-enabled-for-everyone";
|
||||
|
||||
let feature_flag_one = db.create_user_flag(FEATURE_FLAG_ONE, false).await.unwrap();
|
||||
let feature_flag_two = db.create_user_flag(FEATURE_FLAG_TWO, false).await.unwrap();
|
||||
db.create_user_flag(FEATURE_FLAG_THREE, true).await.unwrap();
|
||||
|
||||
db.add_user_flag(user_1, feature_flag_one).await.unwrap();
|
||||
db.add_user_flag(user_1, feature_flag_two).await.unwrap();
|
||||
|
||||
db.add_user_flag(user_2, feature_flag_one).await.unwrap();
|
||||
|
||||
let mut user_1_flags = db.get_user_flags(user_1).await.unwrap();
|
||||
user_1_flags.sort();
|
||||
assert_eq!(
|
||||
user_1_flags,
|
||||
&[FEATURE_FLAG_ONE, FEATURE_FLAG_TWO, FEATURE_FLAG_THREE]
|
||||
);
|
||||
|
||||
let mut user_2_flags = db.get_user_flags(user_2).await.unwrap();
|
||||
user_2_flags.sort();
|
||||
assert_eq!(user_2_flags, &[FEATURE_FLAG_ONE, FEATURE_FLAG_THREE]);
|
||||
}
|
||||
@@ -46,6 +46,27 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
||||
let mut first_user = None;
|
||||
let mut others = vec![];
|
||||
|
||||
let flag_names = ["language-models"];
|
||||
let mut flags = Vec::new();
|
||||
|
||||
let existing_feature_flags = db.list_feature_flags().await?;
|
||||
|
||||
for flag_name in flag_names {
|
||||
if existing_feature_flags
|
||||
.iter()
|
||||
.any(|flag| flag.flag == flag_name)
|
||||
{
|
||||
log::info!("Flag {flag_name:?} already exists");
|
||||
continue;
|
||||
}
|
||||
|
||||
let flag = db
|
||||
.create_user_flag(flag_name, false)
|
||||
.await
|
||||
.unwrap_or_else(|err| panic!("failed to create flag: '{flag_name}': {err}"));
|
||||
flags.push(flag);
|
||||
}
|
||||
|
||||
for admin_login in seed_config.admins {
|
||||
let user = fetch_github::<GithubUser>(
|
||||
&client,
|
||||
@@ -69,6 +90,15 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
||||
} else {
|
||||
others.push(user.user_id)
|
||||
}
|
||||
|
||||
for flag in &flags {
|
||||
db.add_user_flag(user.user_id, *flag)
|
||||
.await
|
||||
.context(format!(
|
||||
"Unable to enable flag '{}' for user '{}'",
|
||||
flag, user.user_id
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
for channel in seed_config.channels {
|
||||
@@ -96,16 +126,24 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result
|
||||
for github_user in github_users {
|
||||
log::info!("Seeding {:?} from GitHub", github_user.login);
|
||||
|
||||
db.update_or_create_user_by_github_account(
|
||||
&github_user.login,
|
||||
github_user.id,
|
||||
github_user.email.as_deref(),
|
||||
github_user.name.as_deref(),
|
||||
github_user.created_at,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("failed to insert user");
|
||||
let user = db
|
||||
.update_or_create_user_by_github_account(
|
||||
&github_user.login,
|
||||
github_user.id,
|
||||
github_user.email.as_deref(),
|
||||
github_user.name.as_deref(),
|
||||
github_user.created_at,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("failed to insert user");
|
||||
|
||||
for flag in &flags {
|
||||
db.add_user_flag(user.id, *flag).await.context(format!(
|
||||
"Unable to enable flag '{}' for user '{}'",
|
||||
flag, user.id
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use editor::{
|
||||
DocumentColorsRenderMode, Editor, RowInfo, SelectionEffects,
|
||||
DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, SelectionEffects,
|
||||
actions::{
|
||||
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
|
||||
ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
|
||||
@@ -18,16 +18,20 @@ use fs::Fs;
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc, lock::Mutex};
|
||||
use gpui::{App, Rgba, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::FakeLspAdapter;
|
||||
use language::{
|
||||
FakeLspAdapter,
|
||||
language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
};
|
||||
use lsp::LSP_REQUEST_TIMEOUT;
|
||||
use project::{
|
||||
ProjectPath, SERVER_PROGRESS_THROTTLE_TIMEOUT,
|
||||
lsp_store::lsp_ext_command::{ExpandedMacro, LspExtExpandMacro},
|
||||
project_settings::{InlineBlameSettings, ProjectSettings},
|
||||
};
|
||||
use recent_projects::disconnected_overlay::DisconnectedOverlay;
|
||||
use rpc::RECEIVE_TIMEOUT;
|
||||
use serde_json::json;
|
||||
use settings::{InlayHintSettingsContent, InlineBlameSettings, SettingsStore};
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
ops::{Deref as _, Range},
|
||||
@@ -1785,37 +1789,35 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.inlay_hints =
|
||||
Some(InlayHintSettingsContent {
|
||||
enabled: Some(true),
|
||||
show_value_hints: Some(true),
|
||||
edit_debounce_ms: Some(0),
|
||||
scroll_debounce_ms: Some(0),
|
||||
show_type_hints: Some(true),
|
||||
show_parameter_hints: Some(false),
|
||||
show_other_hints: Some(true),
|
||||
show_background: Some(false),
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
enabled: true,
|
||||
show_value_hints: true,
|
||||
edit_debounce_ms: 0,
|
||||
scroll_debounce_ms: 0,
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.inlay_hints =
|
||||
Some(InlayHintSettingsContent {
|
||||
show_value_hints: Some(true),
|
||||
enabled: Some(true),
|
||||
edit_debounce_ms: Some(0),
|
||||
scroll_debounce_ms: Some(0),
|
||||
show_type_hints: Some(true),
|
||||
show_parameter_hints: Some(false),
|
||||
show_other_hints: Some(true),
|
||||
show_background: Some(false),
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
show_value_hints: true,
|
||||
enabled: true,
|
||||
edit_debounce_ms: 0,
|
||||
scroll_debounce_ms: 0,
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1895,13 +1897,13 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
let closure_edits_made = Arc::clone(&edits_made);
|
||||
fake_language_server
|
||||
.set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let edits_made_2 = Arc::clone(&closure_edits_made);
|
||||
let task_edits_made = Arc::clone(&closure_edits_made);
|
||||
async move {
|
||||
assert_eq!(
|
||||
params.text_document.uri,
|
||||
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
|
||||
);
|
||||
let edits_made = AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
|
||||
let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
|
||||
Ok(Some(vec![lsp::InlayHint {
|
||||
position: lsp::Position::new(0, edits_made as u32),
|
||||
label: lsp::InlayHintLabel::String(edits_made.to_string()),
|
||||
@@ -2037,37 +2039,35 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.inlay_hints =
|
||||
Some(InlayHintSettingsContent {
|
||||
show_value_hints: Some(true),
|
||||
enabled: Some(false),
|
||||
edit_debounce_ms: Some(0),
|
||||
scroll_debounce_ms: Some(0),
|
||||
show_type_hints: Some(false),
|
||||
show_parameter_hints: Some(false),
|
||||
show_other_hints: Some(false),
|
||||
show_background: Some(false),
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
show_value_hints: true,
|
||||
enabled: false,
|
||||
edit_debounce_ms: 0,
|
||||
scroll_debounce_ms: 0,
|
||||
show_type_hints: false,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.all_languages.defaults.inlay_hints =
|
||||
Some(InlayHintSettingsContent {
|
||||
show_value_hints: Some(true),
|
||||
enabled: Some(true),
|
||||
edit_debounce_ms: Some(0),
|
||||
scroll_debounce_ms: Some(0),
|
||||
show_type_hints: Some(true),
|
||||
show_parameter_hints: Some(true),
|
||||
show_other_hints: Some(true),
|
||||
show_background: Some(false),
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
show_value_hints: true,
|
||||
enabled: true,
|
||||
edit_debounce_ms: 0,
|
||||
scroll_debounce_ms: 0,
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2241,15 +2241,15 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
||||
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::None);
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::None);
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2422,8 +2422,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
||||
|
||||
cx_b.update(|_, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Background);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2450,8 +2450,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
||||
|
||||
cx_b.update(|_, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Inlay);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2478,8 +2478,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
|
||||
|
||||
cx_a.update(|_, cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.editor.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
|
||||
store.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.lsp_document_colors = Some(DocumentColorsRenderMode::Border);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3306,20 +3306,20 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
cx_b.update(editor::init);
|
||||
// Turn inline-blame-off by default so no state is transferred without us explicitly doing so
|
||||
let inline_blame_off_settings = Some(InlineBlameSettings {
|
||||
enabled: Some(false),
|
||||
enabled: false,
|
||||
..Default::default()
|
||||
});
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
|
||||
store.update_user_settings::<ProjectSettings>(cx, |settings| {
|
||||
settings.git.inline_blame = inline_blame_off_settings;
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.git.get_or_insert_default().inline_blame = inline_blame_off_settings;
|
||||
store.update_user_settings::<ProjectSettings>(cx, |settings| {
|
||||
settings.git.inline_blame = inline_blame_off_settings;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ use gpui::{
|
||||
VisualContext, VisualTestContext, point,
|
||||
};
|
||||
use language::Capability;
|
||||
use project::WorktreeSettings;
|
||||
use rpc::proto::PeerId;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
@@ -1709,9 +1710,8 @@ async fn test_following_into_excluded_file(
|
||||
for cx in [&mut cx_a, &mut cx_b] {
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project.worktree.file_scan_exclusions =
|
||||
Some(vec!["**/.git".to_string()]);
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,9 @@ use gpui::{
|
||||
use language::{
|
||||
Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
|
||||
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
|
||||
language_settings::{Formatter, FormatterList, SelectedFormatter},
|
||||
language_settings::{
|
||||
AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter,
|
||||
},
|
||||
tree_sitter_rust, tree_sitter_typescript,
|
||||
};
|
||||
use lsp::{LanguageServerId, OneOf};
|
||||
@@ -36,7 +38,7 @@ use project::{
|
||||
use prompt_store::PromptBuilder;
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
use settings::{PrettierSettingsContent, SettingsStore};
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
env, future, mem,
|
||||
@@ -3505,14 +3507,10 @@ async fn test_local_settings(
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.map(|(path, content)| (
|
||||
path,
|
||||
content.all_languages.defaults.tab_size.map(Into::into)
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), Some(2)),
|
||||
(Path::new("a").into(), Some(8)),
|
||||
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
|
||||
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||
]
|
||||
)
|
||||
});
|
||||
@@ -3528,14 +3526,10 @@ async fn test_local_settings(
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.map(|(path, content)| (
|
||||
path,
|
||||
content.all_languages.defaults.tab_size.map(Into::into)
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), None),
|
||||
(Path::new("a").into(), Some(8)),
|
||||
(Path::new("").into(), r#"{}"#.to_string()),
|
||||
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||
]
|
||||
)
|
||||
});
|
||||
@@ -3561,14 +3555,10 @@ async fn test_local_settings(
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.map(|(path, content)| (
|
||||
path,
|
||||
content.all_languages.defaults.tab_size.map(Into::into)
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("a").into(), Some(8)),
|
||||
(Path::new("b").into(), Some(4)),
|
||||
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||
(Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
|
||||
]
|
||||
)
|
||||
});
|
||||
@@ -3597,9 +3587,8 @@ async fn test_local_settings(
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.map(|(path, content)| (path, content.all_languages.defaults.hard_tabs))
|
||||
.collect::<Vec<_>>(),
|
||||
&[(Path::new("a").into(), Some(true))],
|
||||
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -4609,15 +4598,15 @@ async fn test_formatting_buffer(
|
||||
// host's configuration is honored as opposed to using the guest's settings.
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |file| {
|
||||
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
|
||||
FormatterList::Single(Formatter::External {
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
|
||||
Formatter::External {
|
||||
command: "awk".into(),
|
||||
arguments: Some(
|
||||
vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
|
||||
),
|
||||
}),
|
||||
));
|
||||
},
|
||||
)));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4705,24 +4694,24 @@ async fn test_prettier_formatting_buffer(
|
||||
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |file| {
|
||||
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
|
||||
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
|
||||
allowed: Some(true),
|
||||
..Default::default()
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::Auto);
|
||||
file.defaults.prettier = Some(PrettierSettings {
|
||||
allowed: true,
|
||||
..PrettierSettings::default()
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |file| {
|
||||
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
|
||||
FormatterList::Single(Formatter::LanguageServer { name: None }),
|
||||
));
|
||||
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
|
||||
allowed: Some(true),
|
||||
..Default::default()
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
|
||||
Formatter::LanguageServer { name: None },
|
||||
)));
|
||||
file.defaults.prettier = Some(PrettierSettings {
|
||||
allowed: true,
|
||||
..PrettierSettings::default()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,10 @@ use gpui::{
|
||||
use http_client::BlockedHttpClient;
|
||||
use language::{
|
||||
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
|
||||
language_settings::{Formatter, FormatterList, SelectedFormatter, language_settings},
|
||||
language_settings::{
|
||||
AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter,
|
||||
language_settings,
|
||||
},
|
||||
tree_sitter_typescript,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
@@ -27,7 +30,7 @@ use remote::RemoteClient;
|
||||
use remote_server::{HeadlessAppState, HeadlessProject};
|
||||
use rpc::proto;
|
||||
use serde_json::json;
|
||||
use settings::{PrettierSettingsContent, SettingsStore};
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{Arc, atomic::AtomicUsize},
|
||||
@@ -496,24 +499,24 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |file| {
|
||||
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
|
||||
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
|
||||
allowed: Some(true),
|
||||
..Default::default()
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::Auto);
|
||||
file.defaults.prettier = Some(PrettierSettings {
|
||||
allowed: true,
|
||||
..PrettierSettings::default()
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
cx_b.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |file| {
|
||||
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
|
||||
FormatterList::Single(Formatter::LanguageServer { name: None }),
|
||||
));
|
||||
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
|
||||
allowed: Some(true),
|
||||
..Default::default()
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
|
||||
Formatter::LanguageServer { name: None },
|
||||
)));
|
||||
file.defaults.prettier = Some(PrettierSettings {
|
||||
allowed: true,
|
||||
..PrettierSettings::default()
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -553,11 +556,11 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings(cx, |file| {
|
||||
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
|
||||
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
|
||||
allowed: Some(true),
|
||||
..Default::default()
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
|
||||
file.defaults.formatter = Some(SelectedFormatter::Auto);
|
||||
file.defaults.prettier = Some(PrettierSettings {
|
||||
allowed: true,
|
||||
..PrettierSettings::default()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ picker.workspace = true
|
||||
project.workspace = true
|
||||
release_channel.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -2993,9 +2993,11 @@ impl Panel for CollabPanel {
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.collaboration_panel.get_or_insert_default().dock = Some(position.into())
|
||||
});
|
||||
settings::update_settings_file::<CollaborationPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.dock = Some(position),
|
||||
);
|
||||
}
|
||||
|
||||
fn size(&self, _window: &Window, cx: &App) -> Pixels {
|
||||
|
||||
@@ -11,6 +11,7 @@ use gpui::{
|
||||
App, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowDecorations, WindowKind, WindowOptions, point,
|
||||
};
|
||||
use panel_settings::MessageEditorSettings;
|
||||
pub use panel_settings::{CollaborationPanelSettings, NotificationPanelSettings};
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
@@ -20,6 +21,7 @@ use workspace::AppState;
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
|
||||
CollaborationPanelSettings::register(cx);
|
||||
NotificationPanelSettings::register(cx);
|
||||
MessageEditorSettings::register(cx);
|
||||
|
||||
channel_view::init(cx);
|
||||
collab_panel::init(cx);
|
||||
|
||||
@@ -621,9 +621,11 @@ impl Panel for NotificationPanel {
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
|
||||
settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.notification_panel.get_or_insert_default().dock = Some(position.into())
|
||||
});
|
||||
settings::update_settings_file::<NotificationPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.dock = Some(position),
|
||||
);
|
||||
}
|
||||
|
||||
fn size(&self, _: &Window, cx: &App) -> Pixels {
|
||||
|
||||
@@ -1,41 +1,102 @@
|
||||
use gpui::Pixels;
|
||||
use settings::Settings;
|
||||
use ui::px;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
|
||||
use workspace::dock::DockPosition;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CollaborationPanelSettings {
|
||||
pub button: bool,
|
||||
pub dock: DockPosition,
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "collaboration_panel")]
|
||||
pub struct PanelSettingsContent {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
/// Where to dock the panel.
|
||||
///
|
||||
/// Default: left
|
||||
pub dock: Option<DockPosition>,
|
||||
/// Default width of the panel in pixels.
|
||||
///
|
||||
/// Default: 240
|
||||
pub default_width: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct NotificationPanelSettings {
|
||||
pub button: bool,
|
||||
pub dock: DockPosition,
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
impl Settings for CollaborationPanelSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let panel = content.collaboration_panel.as_ref().unwrap();
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "notification_panel")]
|
||||
pub struct NotificationPanelSettingsContent {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
/// Where to dock the panel.
|
||||
///
|
||||
/// Default: right
|
||||
pub dock: Option<DockPosition>,
|
||||
/// Default width of the panel in pixels.
|
||||
///
|
||||
/// Default: 300
|
||||
pub default_width: Option<f32>,
|
||||
}
|
||||
|
||||
Self {
|
||||
button: panel.button.unwrap(),
|
||||
dock: panel.dock.unwrap().into(),
|
||||
default_width: panel.default_width.map(px).unwrap(),
|
||||
}
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "message_editor")]
|
||||
pub struct MessageEditorSettings {
|
||||
/// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||
/// For example: typing `:wave:` gets replaced with `👋`.
|
||||
///
|
||||
/// Default: false
|
||||
pub auto_replace_emoji_shortcode: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for CollaborationPanelSettings {
|
||||
type FileContent = PanelSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
impl Settings for NotificationPanelSettings {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let panel = content.notification_panel.as_ref().unwrap();
|
||||
return Self {
|
||||
button: panel.button.unwrap(),
|
||||
dock: panel.dock.unwrap().into(),
|
||||
default_width: panel.default_width.map(px).unwrap(),
|
||||
};
|
||||
type FileContent = NotificationPanelSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
impl Settings for MessageEditorSettings {
|
||||
type FileContent = MessageEditorSettings;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ pub type IndexMap<K, V> = indexmap::IndexMap<K, V>;
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
pub type IndexSet<T> = indexmap::IndexSet<T>;
|
||||
|
||||
pub use indexmap::Equivalent;
|
||||
pub use rustc_hash::FxHasher;
|
||||
pub use rustc_hash::{FxHashMap, FxHashSet};
|
||||
pub use std::collections::*;
|
||||
|
||||
@@ -25,9 +25,8 @@ net.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
schemars.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
tempfile.workspace = true
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
|
||||
@@ -12,9 +12,12 @@ use std::{fmt::Display, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use client::Client;
|
||||
use collections::HashMap;
|
||||
use gpui::AsyncApp;
|
||||
use parking_lot::RwLock;
|
||||
pub use settings::ContextServerCommand;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::redact::should_redact;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ContextServerId(pub Arc<str>);
|
||||
@@ -25,6 +28,32 @@ impl Display for ContextServerId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ContextServerCommand {
|
||||
#[serde(rename = "command")]
|
||||
pub path: PathBuf,
|
||||
pub args: Vec<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
/// Timeout for tool calls in milliseconds. Defaults to 60000 (60 seconds) if not specified.
|
||||
pub timeout: Option<u64>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ContextServerCommand {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let filtered_env = self.env.as_ref().map(|env| {
|
||||
env.iter()
|
||||
.map(|(k, v)| (k, if should_redact(k) { "[REDACTED]" } else { v }))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
f.debug_struct("ContextServerCommand")
|
||||
.field("path", &self.path)
|
||||
.field("args", &self.args)
|
||||
.field("env", &filtered_env)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
enum ContextServerTransport {
|
||||
Stdio(ContextServerCommand, Option<PathBuf>),
|
||||
Custom(Arc<dyn crate::transport::Transport>),
|
||||
|
||||
@@ -10,7 +10,6 @@ use fs::Fs;
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};
|
||||
use gpui::WeakEntity;
|
||||
use gpui::{App, AsyncApp, Global, prelude::*};
|
||||
use http_client::HttpRequestExt;
|
||||
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||
use itertools::Itertools;
|
||||
use paths::home_dir;
|
||||
@@ -742,7 +741,7 @@ async fn stream_completion(
|
||||
|
||||
let request_initiator = if is_user_initiated { "user" } else { "agent" };
|
||||
|
||||
let request_builder = HttpRequest::builder()
|
||||
let mut request_builder = HttpRequest::builder()
|
||||
.method(Method::POST)
|
||||
.uri(completion_url.as_ref())
|
||||
.header(
|
||||
@@ -755,10 +754,12 @@ async fn stream_completion(
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Copilot-Integration-Id", "vscode-chat")
|
||||
.header("X-Initiator", request_initiator)
|
||||
.when(is_vision_request, |builder| {
|
||||
builder.header("Copilot-Vision-Request", is_vision_request.to_string())
|
||||
});
|
||||
.header("X-Initiator", request_initiator);
|
||||
|
||||
if is_vision_request {
|
||||
request_builder =
|
||||
request_builder.header("Copilot-Vision-Request", is_vision_request.to_string());
|
||||
}
|
||||
|
||||
let is_streaming = request.stream;
|
||||
|
||||
|
||||
@@ -281,11 +281,14 @@ mod tests {
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
Point,
|
||||
language_settings::{CompletionSettingsContent, LspInsertMode, WordsCompletionMode},
|
||||
language_settings::{
|
||||
AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, LspInsertMode,
|
||||
WordsCompletionMode,
|
||||
},
|
||||
};
|
||||
use project::Project;
|
||||
use serde_json::json;
|
||||
use settings::{AllLanguageSettingsContent, SettingsStore};
|
||||
use settings::SettingsStore;
|
||||
use std::future::Future;
|
||||
use util::{
|
||||
path,
|
||||
@@ -296,11 +299,12 @@ mod tests {
|
||||
async fn test_copilot(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
// flaky
|
||||
init_test(cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettingsContent {
|
||||
words: Some(WordsCompletionMode::Disabled),
|
||||
words_min_length: Some(0),
|
||||
lsp_insert_mode: Some(LspInsertMode::Insert),
|
||||
..Default::default()
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -528,11 +532,12 @@ mod tests {
|
||||
) {
|
||||
// flaky
|
||||
init_test(cx, |settings| {
|
||||
settings.defaults.completions = Some(CompletionSettingsContent {
|
||||
words: Some(WordsCompletionMode::Disabled),
|
||||
words_min_length: Some(0),
|
||||
lsp_insert_mode: Some(LspInsertMode::Insert),
|
||||
..Default::default()
|
||||
settings.defaults.completions = Some(CompletionSettings {
|
||||
words: WordsCompletionMode::Disabled,
|
||||
words_min_length: 0,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::Insert,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1123,7 +1128,7 @@ mod tests {
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
SettingsStore::update_global(cx, |store: &mut SettingsStore, cx| {
|
||||
store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
|
||||
store.update_user_settings::<AllLanguageSettings>(cx, f);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -321,19 +321,16 @@ pub fn crash_server(socket: &Path) {
|
||||
let shutdown = Arc::new(AtomicBool::new(false));
|
||||
let has_connection = Arc::new(AtomicBool::new(false));
|
||||
|
||||
thread::Builder::new()
|
||||
.name("CrashServerTimeout".to_owned())
|
||||
.spawn({
|
||||
let shutdown = shutdown.clone();
|
||||
let has_connection = has_connection.clone();
|
||||
move || {
|
||||
std::thread::sleep(CRASH_HANDLER_CONNECT_TIMEOUT);
|
||||
if !has_connection.load(Ordering::SeqCst) {
|
||||
shutdown.store(true, Ordering::SeqCst);
|
||||
}
|
||||
std::thread::spawn({
|
||||
let shutdown = shutdown.clone();
|
||||
let has_connection = has_connection.clone();
|
||||
move || {
|
||||
std::thread::sleep(CRASH_HANDLER_CONNECT_TIMEOUT);
|
||||
if !has_connection.load(Ordering::SeqCst) {
|
||||
shutdown.store(true, Ordering::SeqCst);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
server
|
||||
.run(
|
||||
|
||||
@@ -118,7 +118,6 @@ impl DebugAdapterClient {
|
||||
R::COMMAND,
|
||||
sequence_id
|
||||
);
|
||||
log::debug!(" request: {request:?}");
|
||||
|
||||
self.send_message(Message::Request(request)).await?;
|
||||
|
||||
@@ -131,8 +130,6 @@ impl DebugAdapterClient {
|
||||
command,
|
||||
sequence_id
|
||||
);
|
||||
log::debug!(" response: {response:?}");
|
||||
|
||||
match response.success {
|
||||
true => {
|
||||
if let Some(json) = response.body {
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
use dap_types::SteppingGranularity;
|
||||
use gpui::App;
|
||||
use settings::{Settings, SettingsContent};
|
||||
use gpui::{App, Global};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DebugPanelDockPosition {
|
||||
Left,
|
||||
Bottom,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Clone, SettingsUi, SettingsKey)]
|
||||
#[serde(default)]
|
||||
// todo(settings_ui) @ben: I'm pretty sure not having the fields be optional here is a bug,
|
||||
// it means the defaults will override previously set values if a single key is missing
|
||||
#[settings_ui(group = "Debugger")]
|
||||
#[settings_key(key = "debugger")]
|
||||
pub struct DebuggerSettings {
|
||||
/// Determines the stepping granularity.
|
||||
///
|
||||
/// Default: line
|
||||
#[settings_ui(skip)]
|
||||
pub stepping_granularity: SteppingGranularity,
|
||||
/// Whether the breakpoints should be reused across Zed sessions.
|
||||
///
|
||||
@@ -30,32 +47,31 @@ pub struct DebuggerSettings {
|
||||
/// The dock position of the debug panel
|
||||
///
|
||||
/// Default: Bottom
|
||||
pub dock: settings::DockPosition,
|
||||
pub dock: DebugPanelDockPosition,
|
||||
}
|
||||
|
||||
impl Settings for DebuggerSettings {
|
||||
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
let content = content.debugger.clone().unwrap();
|
||||
impl Default for DebuggerSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stepping_granularity: dap_granularity_from_settings(
|
||||
content.stepping_granularity.unwrap(),
|
||||
),
|
||||
save_breakpoints: content.save_breakpoints.unwrap(),
|
||||
button: content.button.unwrap(),
|
||||
timeout: content.timeout.unwrap(),
|
||||
log_dap_communications: content.log_dap_communications.unwrap(),
|
||||
format_dap_log_messages: content.format_dap_log_messages.unwrap(),
|
||||
dock: content.dock.unwrap(),
|
||||
button: true,
|
||||
save_breakpoints: true,
|
||||
stepping_granularity: SteppingGranularity::Line,
|
||||
timeout: 2000,
|
||||
log_dap_communications: true,
|
||||
format_dap_log_messages: true,
|
||||
dock: DebugPanelDockPosition::Bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dap_granularity_from_settings(
|
||||
granularity: settings::SteppingGranularity,
|
||||
) -> dap_types::SteppingGranularity {
|
||||
match granularity {
|
||||
settings::SteppingGranularity::Instruction => dap_types::SteppingGranularity::Instruction,
|
||||
settings::SteppingGranularity::Line => dap_types::SteppingGranularity::Line,
|
||||
settings::SteppingGranularity::Statement => dap_types::SteppingGranularity::Statement,
|
||||
impl Settings for DebuggerSettings {
|
||||
type FileContent = Self;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
impl Global for DebuggerSettings {}
|
||||
|
||||
@@ -262,7 +262,6 @@ impl TransportDelegate {
|
||||
break;
|
||||
}
|
||||
}
|
||||
log::debug!("stderr: {line}");
|
||||
|
||||
for (kind, handler) in log_handlers.lock().iter_mut() {
|
||||
if matches!(kind, LogKind::Adapter) {
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::IndexMap;
|
||||
use dap::adapters::DebugAdapterName;
|
||||
use dap::debugger_settings::DebugPanelDockPosition;
|
||||
use dap::{DapRegistry, StartDebuggingRequestArguments};
|
||||
use dap::{client::SessionId, debugger_settings::DebuggerSettings};
|
||||
use editor::Editor;
|
||||
@@ -134,10 +135,6 @@ impl DebugPanel {
|
||||
.map(|session| session.read(cx).running_state().clone())
|
||||
}
|
||||
|
||||
pub fn project(&self) -> &Entity<Project> {
|
||||
&self.project
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
@@ -1403,7 +1400,11 @@ impl Panel for DebugPanel {
|
||||
}
|
||||
|
||||
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
|
||||
DebuggerSettings::get_global(cx).dock.into()
|
||||
match DebuggerSettings::get_global(cx).dock {
|
||||
DebugPanelDockPosition::Left => DockPosition::Left,
|
||||
DebugPanelDockPosition::Bottom => DockPosition::Bottom,
|
||||
DebugPanelDockPosition::Right => DockPosition::Right,
|
||||
}
|
||||
}
|
||||
|
||||
fn position_is_valid(&self, _: DockPosition) -> bool {
|
||||
@@ -1425,9 +1426,18 @@ impl Panel for DebugPanel {
|
||||
});
|
||||
}
|
||||
|
||||
settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.debugger.get_or_insert_default().dock = Some(position.into());
|
||||
});
|
||||
settings::update_settings_file::<DebuggerSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| {
|
||||
let dock = match position {
|
||||
DockPosition::Left => DebugPanelDockPosition::Left,
|
||||
DockPosition::Bottom => DebugPanelDockPosition::Bottom,
|
||||
DockPosition::Right => DebugPanelDockPosition::Right,
|
||||
};
|
||||
settings.dock = dock;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn size(&self, _window: &Window, _: &App) -> Pixels {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, bail};
|
||||
use collections::{FxHashMap, HashMap, HashSet};
|
||||
use language::{LanguageName, LanguageRegistry};
|
||||
use language::LanguageRegistry;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
@@ -22,7 +22,7 @@ use itertools::Itertools as _;
|
||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||
use project::{DebugScenarioContext, Project, TaskContexts, TaskSourceKind, task_store::TaskStore};
|
||||
use settings::Settings;
|
||||
use task::{DebugScenario, RevealTarget, VariableName, ZedDebugConfig};
|
||||
use task::{DebugScenario, RevealTarget, ZedDebugConfig};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||
@@ -978,7 +978,6 @@ pub(super) struct DebugDelegate {
|
||||
task_store: Entity<TaskStore>,
|
||||
candidates: Vec<(
|
||||
Option<TaskSourceKind>,
|
||||
Option<LanguageName>,
|
||||
DebugScenario,
|
||||
Option<DebugScenarioContext>,
|
||||
)>,
|
||||
@@ -1006,89 +1005,28 @@ impl DebugDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_task_subtitle(
|
||||
&self,
|
||||
task_kind: &Option<TaskSourceKind>,
|
||||
context: &Option<DebugScenarioContext>,
|
||||
cx: &mut App,
|
||||
) -> Option<String> {
|
||||
match task_kind {
|
||||
Some(TaskSourceKind::Worktree {
|
||||
id: worktree_id,
|
||||
directory_in_worktree,
|
||||
..
|
||||
}) => self
|
||||
.debug_panel
|
||||
.update(cx, |debug_panel, cx| {
|
||||
let project = debug_panel.project().read(cx);
|
||||
let worktrees: Vec<_> = project.visible_worktrees(cx).collect();
|
||||
|
||||
let mut path = if worktrees.len() > 1
|
||||
&& let Some(worktree) = project.worktree_for_id(*worktree_id, cx)
|
||||
{
|
||||
let worktree_path = worktree.read(cx).abs_path();
|
||||
let full_path = worktree_path.join(directory_in_worktree);
|
||||
full_path
|
||||
} else {
|
||||
directory_in_worktree.clone()
|
||||
};
|
||||
|
||||
match path
|
||||
.components()
|
||||
.next_back()
|
||||
.and_then(|component| component.as_os_str().to_str())
|
||||
{
|
||||
Some(".zed") => {
|
||||
path.push("debug.json");
|
||||
}
|
||||
Some(".vscode") => {
|
||||
path.push("launch.json");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(path.display().to_string())
|
||||
})
|
||||
.unwrap_or_else(|_| Some(directory_in_worktree.display().to_string())),
|
||||
Some(TaskSourceKind::AbsPath { abs_path, .. }) => {
|
||||
Some(abs_path.to_string_lossy().into_owned())
|
||||
}
|
||||
Some(TaskSourceKind::Lsp { language_name, .. }) => {
|
||||
Some(format!("LSP: {language_name}"))
|
||||
}
|
||||
Some(TaskSourceKind::Language { .. }) => None,
|
||||
_ => context.clone().and_then(|ctx| {
|
||||
ctx.task_context
|
||||
.task_variables
|
||||
.get(&VariableName::RelativeFile)
|
||||
.map(|f| format!("in {f}"))
|
||||
.or_else(|| {
|
||||
ctx.task_context
|
||||
.task_variables
|
||||
.get(&VariableName::Dirname)
|
||||
.map(|d| format!("in {d}/"))
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_scenario_language(
|
||||
fn get_scenario_kind(
|
||||
languages: &Arc<LanguageRegistry>,
|
||||
dap_registry: &DapRegistry,
|
||||
scenario: DebugScenario,
|
||||
) -> (Option<LanguageName>, DebugScenario) {
|
||||
) -> (Option<TaskSourceKind>, DebugScenario) {
|
||||
let language_names = languages.language_names();
|
||||
let language_name = dap_registry.adapter_language(&scenario.adapter);
|
||||
let language = dap_registry
|
||||
.adapter_language(&scenario.adapter)
|
||||
.map(|language| TaskSourceKind::Language { name: language.0 });
|
||||
|
||||
let language_name = language_name.or_else(|| {
|
||||
let language = language.or_else(|| {
|
||||
scenario.label.split_whitespace().find_map(|word| {
|
||||
language_names
|
||||
.iter()
|
||||
.find(|name| name.as_ref().eq_ignore_ascii_case(word))
|
||||
.cloned()
|
||||
.map(|name| TaskSourceKind::Language {
|
||||
name: name.to_owned().into(),
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
(language_name, scenario)
|
||||
(language, scenario)
|
||||
}
|
||||
|
||||
pub fn tasks_loaded(
|
||||
@@ -1142,9 +1080,9 @@ impl DebugDelegate {
|
||||
this.delegate.candidates = recent
|
||||
.into_iter()
|
||||
.map(|(scenario, context)| {
|
||||
let (language_name, scenario) =
|
||||
Self::get_scenario_language(&languages, dap_registry, scenario);
|
||||
(None, language_name, scenario, Some(context))
|
||||
let (kind, scenario) =
|
||||
Self::get_scenario_kind(&languages, dap_registry, scenario);
|
||||
(kind, scenario, Some(context))
|
||||
})
|
||||
.chain(
|
||||
scenarios
|
||||
@@ -1159,9 +1097,9 @@ impl DebugDelegate {
|
||||
})
|
||||
.filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter))
|
||||
.map(|(kind, scenario)| {
|
||||
let (language_name, scenario) =
|
||||
Self::get_scenario_language(&languages, dap_registry, scenario);
|
||||
(Some(kind), language_name, scenario, None)
|
||||
let (language, scenario) =
|
||||
Self::get_scenario_kind(&languages, dap_registry, scenario);
|
||||
(language.or(Some(kind)), scenario, None)
|
||||
}),
|
||||
)
|
||||
.collect();
|
||||
@@ -1207,7 +1145,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
let candidates: Vec<_> = candidates
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, (_, _, candidate, _))| {
|
||||
.map(|(index, (_, candidate, _))| {
|
||||
StringMatchCandidate::new(index, candidate.label.as_ref())
|
||||
})
|
||||
.collect();
|
||||
@@ -1376,7 +1314,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
.get(self.selected_index())
|
||||
.and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned());
|
||||
|
||||
let Some((kind, _, debug_scenario, context)) = debug_scenario else {
|
||||
let Some((kind, debug_scenario, context)) = debug_scenario else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1509,7 +1447,6 @@ impl PickerDelegate for DebugDelegate {
|
||||
cx: &mut Context<picker::Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let hit = &self.matches.get(ix)?;
|
||||
let (task_kind, language_name, _scenario, context) = &self.candidates[hit.candidate_id];
|
||||
|
||||
let highlighted_location = HighlightedMatch {
|
||||
text: hit.string.clone(),
|
||||
@@ -1517,40 +1454,33 @@ impl PickerDelegate for DebugDelegate {
|
||||
char_count: hit.string.chars().count(),
|
||||
color: Color::Default,
|
||||
};
|
||||
let task_kind = &self.candidates[hit.candidate_id].0;
|
||||
|
||||
let subtitle = self.get_task_subtitle(task_kind, context, cx);
|
||||
|
||||
let language_icon = language_name.as_ref().and_then(|lang| {
|
||||
file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&lang.0.to_lowercase(), cx)
|
||||
.map(Icon::from_path)
|
||||
});
|
||||
|
||||
let (icon, indicator) = match task_kind {
|
||||
Some(TaskSourceKind::UserInput) => (Some(Icon::new(IconName::Terminal)), None),
|
||||
Some(TaskSourceKind::AbsPath { .. }) => (Some(Icon::new(IconName::Settings)), None),
|
||||
Some(TaskSourceKind::Worktree { .. }) => (Some(Icon::new(IconName::FileTree)), None),
|
||||
Some(TaskSourceKind::Lsp { language_name, .. }) => (
|
||||
file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&language_name.to_lowercase(), cx)
|
||||
.map(Icon::from_path),
|
||||
Some(Indicator::icon(
|
||||
Icon::new(IconName::BoltFilled)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
)),
|
||||
),
|
||||
Some(TaskSourceKind::Language { name }) => (
|
||||
file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&name.to_lowercase(), cx)
|
||||
.map(Icon::from_path),
|
||||
None,
|
||||
),
|
||||
None => (Some(Icon::new(IconName::HistoryRerun)), None),
|
||||
let icon = match task_kind {
|
||||
Some(TaskSourceKind::UserInput) => Some(Icon::new(IconName::Terminal)),
|
||||
Some(TaskSourceKind::AbsPath { .. }) => Some(Icon::new(IconName::Settings)),
|
||||
Some(TaskSourceKind::Worktree { .. }) => Some(Icon::new(IconName::FileTree)),
|
||||
Some(TaskSourceKind::Lsp {
|
||||
language_name: name,
|
||||
..
|
||||
})
|
||||
| Some(TaskSourceKind::Language { name }) => file_icons::FileIcons::get(cx)
|
||||
.get_icon_for_type(&name.to_lowercase(), cx)
|
||||
.map(Icon::from_path),
|
||||
None => Some(Icon::new(IconName::HistoryRerun)),
|
||||
}
|
||||
.map(|icon| icon.color(Color::Muted).size(IconSize::Small));
|
||||
let indicator = if matches!(task_kind, Some(TaskSourceKind::Lsp { .. })) {
|
||||
Some(Indicator::icon(
|
||||
Icon::new(IconName::BoltFilled)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::Small),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let icon = language_icon.or(icon).map(|icon| {
|
||||
IconWithIndicator::new(icon.color(Color::Muted).size(IconSize::Small), indicator)
|
||||
let icon = icon.map(|icon| {
|
||||
IconWithIndicator::new(icon, indicator)
|
||||
.indicator_border_color(Some(cx.theme().colors().border_transparent))
|
||||
});
|
||||
|
||||
@@ -1560,18 +1490,7 @@ impl PickerDelegate for DebugDelegate {
|
||||
.start_slot::<IconWithIndicator>(icon)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(
|
||||
v_flex()
|
||||
.items_start()
|
||||
.child(highlighted_location.render(window, cx))
|
||||
.when_some(subtitle, |this, subtitle_text| {
|
||||
this.child(
|
||||
Label::new(subtitle_text)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
}),
|
||||
),
|
||||
.child(highlighted_location.render(window, cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1620,17 +1539,4 @@ impl NewProcessModal {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn debug_picker_candidate_subtitles(&self, cx: &mut App) -> Vec<String> {
|
||||
self.debug_picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.candidates
|
||||
.iter()
|
||||
.filter_map(|(task_kind, _, _, context)| {
|
||||
picker.delegate.get_task_subtitle(task_kind, context, cx)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1228,8 +1228,9 @@ impl RunningState {
|
||||
|
||||
terminal.read_with(cx, |terminal, _| {
|
||||
terminal
|
||||
.pty_info()
|
||||
.and_then(|info| info.pid().map(|pid| pid.as_u32()))
|
||||
.pty_info
|
||||
.pid()
|
||||
.map(|pid| pid.as_u32())
|
||||
.context("Terminal was spawned but PID was not available")
|
||||
})?
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ use text::Point;
|
||||
use util::path;
|
||||
|
||||
use crate::NewProcessMode;
|
||||
use crate::new_process_modal::NewProcessModal;
|
||||
use crate::tests::{init_test, init_test_workspace};
|
||||
|
||||
#[gpui::test]
|
||||
@@ -179,7 +178,13 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
crate::new_process_modal::NewProcessModal::show(
|
||||
workspace,
|
||||
window,
|
||||
NewProcessMode::Debug,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -187,7 +192,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
|
||||
|
||||
let modal = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace.active_modal::<NewProcessModal>(cx)
|
||||
workspace.active_modal::<crate::new_process_modal::NewProcessModal>(cx)
|
||||
})
|
||||
.unwrap()
|
||||
.expect("Modal should be active");
|
||||
@@ -276,73 +281,6 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut
|
||||
pretty_assertions::assert_eq!(expected_content, debug_json_content);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_debug_modal_subtitles_with_multiple_worktrees(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
|
||||
fs.insert_tree(
|
||||
path!("/workspace1"),
|
||||
json!({
|
||||
".zed": {
|
||||
"debug.json": r#"[
|
||||
{
|
||||
"adapter": "fake-adapter",
|
||||
"label": "Debug App 1",
|
||||
"request": "launch",
|
||||
"program": "./app1",
|
||||
"cwd": "."
|
||||
},
|
||||
{
|
||||
"adapter": "fake-adapter",
|
||||
"label": "Debug Tests 1",
|
||||
"request": "launch",
|
||||
"program": "./test1",
|
||||
"cwd": "."
|
||||
}
|
||||
]"#
|
||||
},
|
||||
"main.rs": "fn main() {}"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/workspace1").as_ref()], cx).await;
|
||||
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let modal = workspace
|
||||
.update(cx, |workspace, _, cx| {
|
||||
workspace.active_modal::<NewProcessModal>(cx)
|
||||
})
|
||||
.unwrap()
|
||||
.expect("Modal should be active");
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let subtitles = modal.update_in(cx, |modal, _, cx| {
|
||||
modal.debug_picker_candidate_subtitles(cx)
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
subtitles.as_slice(),
|
||||
[path!(".zed/debug.json"), path!(".zed/debug.json")]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -1 +1 @@
|
||||
../../LICENSE-GPL
|
||||
LICENSE-GPL
|
||||
@@ -79,12 +79,9 @@ impl<S: Source> Denoiser<S> {
|
||||
let (input_tx, input_rx) = mpsc::channel();
|
||||
let (denoised_tx, denoised_rx) = mpsc::channel();
|
||||
|
||||
thread::Builder::new()
|
||||
.name("NeuralDenoiser".to_owned())
|
||||
.spawn(move || {
|
||||
run_neural_denoiser(denoised_tx, input_rx);
|
||||
})
|
||||
.unwrap();
|
||||
thread::spawn(move || {
|
||||
run_neural_denoiser(denoised_tx, input_rx);
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
inner: source,
|
||||
|
||||
@@ -910,10 +910,8 @@ async fn open_disabled_globs_setting_in_editor(
|
||||
let settings = cx.global::<SettingsStore>();
|
||||
|
||||
// Ensure that we always have "edit_predictions { "disabled_globs": [] }"
|
||||
let edits = settings.edits_for_update(&text, |file| {
|
||||
file.project
|
||||
.all_languages
|
||||
.edit_predictions
|
||||
let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
|
||||
file.edit_predictions
|
||||
.get_or_insert_with(Default::default)
|
||||
.disabled_globs
|
||||
.get_or_insert_with(Vec::new);
|
||||
@@ -950,12 +948,9 @@ async fn open_disabled_globs_setting_in_editor(
|
||||
}
|
||||
|
||||
fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredictionProvider) {
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.all_languages
|
||||
.features
|
||||
.get_or_insert_default()
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.features
|
||||
.get_or_insert(Default::default())
|
||||
.edit_prediction_provider = Some(provider);
|
||||
});
|
||||
}
|
||||
@@ -967,24 +962,18 @@ fn toggle_show_edit_predictions_for_language(
|
||||
) {
|
||||
let show_edit_predictions =
|
||||
all_language_settings(None, cx).show_edit_predictions(Some(&language), cx);
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.all_languages
|
||||
.languages
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.languages
|
||||
.0
|
||||
.entry(language.name().0)
|
||||
.entry(language.name())
|
||||
.or_default()
|
||||
.show_edit_predictions = Some(!show_edit_predictions);
|
||||
});
|
||||
}
|
||||
|
||||
fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
settings
|
||||
.project
|
||||
.all_languages
|
||||
.features
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.features
|
||||
.get_or_insert(Default::default())
|
||||
.edit_prediction_provider = Some(EditPredictionProvider::None);
|
||||
});
|
||||
@@ -995,14 +984,13 @@ fn toggle_edit_prediction_mode(fs: Arc<dyn Fs>, mode: EditPredictionsMode, cx: &
|
||||
let current_mode = settings.edit_predictions_mode();
|
||||
|
||||
if current_mode != mode {
|
||||
update_settings_file(fs, cx, move |settings, _cx| {
|
||||
if let Some(edit_predictions) = settings.project.all_languages.edit_predictions.as_mut()
|
||||
{
|
||||
edit_predictions.mode = Some(mode);
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |settings, _cx| {
|
||||
if let Some(edit_predictions) = settings.edit_predictions.as_mut() {
|
||||
edit_predictions.mode = mode;
|
||||
} else {
|
||||
settings.project.all_languages.edit_predictions =
|
||||
Some(settings::EditPredictionSettingsContent {
|
||||
mode: Some(mode),
|
||||
settings.edit_predictions =
|
||||
Some(language_settings::EditPredictionSettingsContent {
|
||||
mode,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,14 +41,14 @@ impl Declaration {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_entry_id(&self) -> ProjectEntryId {
|
||||
pub fn project_entry_id(&self) -> Option<ProjectEntryId> {
|
||||
match self {
|
||||
Declaration::File {
|
||||
project_entry_id, ..
|
||||
} => *project_entry_id,
|
||||
} => Some(*project_entry_id),
|
||||
Declaration::Buffer {
|
||||
project_entry_id, ..
|
||||
} => *project_entry_id,
|
||||
} => Some(*project_entry_id),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,19 +113,19 @@ pub fn scored_snippets(
|
||||
.row;
|
||||
(
|
||||
true,
|
||||
(cursor_point.row as i32 - declaration_line as i32)
|
||||
.unsigned_abs(),
|
||||
(cursor_point.row as i32 - declaration_line as i32).abs()
|
||||
as u32,
|
||||
declaration,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Some((false, u32::MAX, declaration))
|
||||
Some((false, 0, declaration))
|
||||
}
|
||||
}
|
||||
Declaration::File { .. } => {
|
||||
// We can assume that a file declaration is in a different file,
|
||||
// because the current one must be open
|
||||
Some((false, u32::MAX, declaration))
|
||||
Some((false, 0, declaration))
|
||||
}
|
||||
})
|
||||
.sorted_by_key(|&(_, distance, _)| distance)
|
||||
@@ -204,7 +204,7 @@ fn score_snippet(
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let reference_line = r.range.start.to_point(current_buffer).row as i32;
|
||||
(cursor.row as i32 - reference_line).unsigned_abs()
|
||||
(cursor.row as i32 - reference_line).abs() as u32
|
||||
})
|
||||
.min()
|
||||
.unwrap();
|
||||
@@ -316,7 +316,7 @@ impl ScoreInputs {
|
||||
|
||||
Scores {
|
||||
signature: combined_score * self.containing_range_vs_signature_weighted_overlap,
|
||||
// declaration score gets boosted both by being multiplied by 2 and by there being more
|
||||
// declaration score gets boosted both by being multipled by 2 and by there being more
|
||||
// weighted overlap.
|
||||
declaration: 2.0 * combined_score * self.containing_range_vs_item_weighted_overlap,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user