logiguard fork v3: full patch set on verified 8c74db0 tree

Includes prior-session patches (carry forward so the app compiles):
  - crates/gpui/build.rs: cross-compile manifest fix
  - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method
  - crates/gpui/src/window.rs: Window::activate_with_token public API
  - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix

Plus the focus-serial fix:
  - serial.rs: SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; latest_serial_of()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 01:52:12 +03:30
commit b9819977a5
3984 changed files with 1487015 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
use std::borrow::Cow;
use std::fmt::{Display, Formatter, Result};
use collections::HashMap;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use settings_macros::MergeFrom;
/// The name of a registered GPUI action, serialized as a plain JSON string, for
/// example, "editor::Cancel"` or `"workspace::CloseActiveItem"`.
///
/// This newtype exists so that settings fields like `command_aliases`, or the
/// keymap file bindings, can request JSON-schema auto completion over the set
/// of actions known at runtime.
#[derive(Serialize, Deserialize, Default, MergeFrom, Clone, Debug, PartialEq)]
#[serde(transparent)]
pub struct ActionName(String);
/// Small helper function to populate the schema's `deprecationMessage` field with the
/// provided deprecation message.
fn add_deprecation(schema: &mut Schema, message: String) {
schema.insert("deprecationMessage".into(), Value::String(message));
}
/// Small helper function to populate the schema's `description` field with the
/// provided description.
fn add_description(schema: &mut Schema, description: &str) {
schema.insert("description".into(), Value::String(description.to_string()));
}
impl ActionName {
pub fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
/// Build the JSON schema to be used for `$defs/ActionName`, basically an
/// `anyOf` of all of the available actions with per-action documentation
/// and deprecation metadata attached.
pub fn build_schema<'a>(
action_names: impl IntoIterator<Item = &'a str>,
action_documentation: &HashMap<&str, &str>,
deprecations: &HashMap<&str, &str>,
deprecation_messages: &HashMap<&str, &str>,
) -> Schema {
let mut alternatives = Vec::new();
for action_name in action_names {
let mut entry = json_schema!({
"type": "string",
"const": action_name
});
if let Some(message) = deprecation_messages.get(action_name) {
add_deprecation(&mut entry, message.to_string());
} else if let Some(new_name) = deprecations.get(action_name) {
add_deprecation(&mut entry, format!("Deprecated, use {new_name}"));
}
if let Some(description) = action_documentation.get(action_name) {
add_description(&mut entry, description);
}
alternatives.push(entry);
}
json_schema!({ "anyOf": alternatives })
}
}
impl Display for ActionName {
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
write!(formatter, "{}", self.0)
}
}
impl AsRef<str> for ActionName {
fn as_ref(&self) -> &str {
&self.0
}
}
impl JsonSchema for ActionName {
/// The name under which this type should be stored in a generator's `$defs`
/// map when schemars encounters it during schema generation.
/// Keeping it stable as `"ActionName"` lets consumers reference it by
/// `#/$defs/ActionName` and lets [`util::schemars::replace_subschema`] look
/// it up at runtime to swap in the real schema.
fn schema_name() -> Cow<'static, str> {
"ActionName".into()
}
/// Returns `true` as a placeholder.
///
/// The real schema, an `anyOf` of every registered action name with action
/// documentation and deprecation metadata, cannot be produced here because
/// `JsonSchema::json_schema` receives no runtime context. It is instead
/// built by call sites that do have access to the GPUI action registry
/// using [`ActionName::build_schema`].
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!(true)
}
}
/// A GPUI action together with its input data, serialized as a two-element JSON
/// array of the form `["namespace::Name", { ... }]`, for example,
/// `["pane::ActivateItem", { "index": 0 }]`.
#[derive(Deserialize, Default)]
#[serde(transparent)]
pub struct ActionWithArguments(pub Value);
impl JsonSchema for ActionWithArguments {
/// The name under which this type should be stored in a generator's `$defs`
/// map when schemars encounters it during schema generation.
/// Keeping it stable as `"ActionWithArguments"` lets consumers reference it
/// by `#/$defs/ActionWithArguments` and lets
/// [`util::schemars::replace_subschema`] look it up at runtime to swap in
/// the real schema.
fn schema_name() -> Cow<'static, str> {
"ActionWithArguments".into()
}
/// Returns `true` as a placeholder.
///
/// The real schema, an `anyOf` of every registered action name that
/// supports arguments, with action documentation and deprecation metadata,
/// cannot be produced here because `JsonSchema::json_schema` receives no
/// runtime context. At the time of writing, it is instead built by
/// [`KeymapFile::generate_json_schema`], where all of the runtime
/// information is available.
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_schema_produces_anyof_of_consts_per_name() {
let mut action_documentation = HashMap::default();
let mut deprecations = HashMap::default();
let mut deprecation_messages = HashMap::default();
action_documentation.insert("editor::Cancel", "Cancel the current operation.");
deprecations.insert("workspace::CloseCurrentItem", "workspace::CloseActiveItem");
deprecation_messages.insert("editor::Explode", "DO NOT USE!");
let schema = ActionName::build_schema(
[
"editor::Cancel",
"editor::Explode",
"workspace::CloseCurrentItem",
"workspace::CloseActiveItem",
],
&action_documentation,
&deprecations,
&deprecation_messages,
);
let value = schema.to_value();
let values = value
.pointer("/anyOf")
.and_then(|v| v.as_array())
.expect("anyOf should be present");
assert_eq!(values.len(), 4);
let (name, schema_type, description) = (
values[0].get("const").and_then(Value::as_str),
values[0].get("type").and_then(Value::as_str),
values[0].get("description").and_then(Value::as_str),
);
assert_eq!(name, Some("editor::Cancel"));
assert_eq!(schema_type, Some("string"));
assert_eq!(description, Some("Cancel the current operation."));
let (name, schema_type, message) = (
values[1].get("const").and_then(Value::as_str),
values[1].get("type").and_then(Value::as_str),
values[1].get("deprecationMessage").and_then(Value::as_str),
);
assert_eq!(name, Some("editor::Explode"));
assert_eq!(schema_type, Some("string"));
assert_eq!(message, Some("DO NOT USE!"));
let (name, schema_type, message) = (
values[2].get("const").and_then(Value::as_str),
values[2].get("type").and_then(Value::as_str),
values[2].get("deprecationMessage").and_then(Value::as_str),
);
assert_eq!(name, Some("workspace::CloseCurrentItem"));
assert_eq!(schema_type, Some("string"));
assert_eq!(message, Some("Deprecated, use workspace::CloseActiveItem"));
let (name, schema_type) = (
values[3].get("const").and_then(Value::as_str),
values[3].get("type").and_then(Value::as_str),
);
assert_eq!(name, Some("workspace::CloseActiveItem"));
assert_eq!(schema_type, Some("string"));
}
}

View File

@@ -0,0 +1,873 @@
use collections::{HashMap, IndexMap};
use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
use std::sync::Arc;
use std::{borrow::Cow, path::PathBuf};
use crate::ExtendingVec;
use crate::DockPosition;
/// Where to position the threads sidebar.
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum SidebarDockPosition {
/// Always show the sidebar on the left side.
#[default]
Left,
/// Always show the sidebar on the right side.
Right,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum SidebarSide {
#[default]
Left,
Right,
}
/// How thinking blocks should be displayed by default in the agent panel.
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum ThinkingBlockDisplay {
/// Thinking blocks fully expand during streaming, then auto-collapse
/// when the model finishes thinking. Users can re-expand after collapse.
#[default]
Auto,
/// Thinking blocks auto-expand with a height constraint during streaming,
/// then remain in their constrained state when complete. Users can click
/// to fully expand or collapse.
Preview,
/// Thinking blocks are always fully expanded by default (no height constraint).
AlwaysExpanded,
/// Thinking blocks are always collapsed by default.
AlwaysCollapsed,
}
#[with_fallible_options]
#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)]
pub struct AgentSettingsContent {
/// Whether the Agent is enabled.
///
/// Default: true
pub enabled: Option<bool>,
/// Whether to show the agent panel button in the status bar.
///
/// Default: true
pub button: Option<bool>,
/// Where to dock the agent panel.
///
/// Default: left
pub dock: Option<DockPosition>,
/// Whether the agent panel should use flexible (proportional) sizing.
///
/// Default: true
pub flexible: Option<bool>,
/// Where to position the threads sidebar.
///
/// Default: left
pub sidebar_side: Option<SidebarDockPosition>,
/// Default width in pixels when the agent panel is docked to the left or right.
///
/// Default: 640
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_width: Option<f32>,
/// Default height in pixels when the agent panel is docked to the bottom.
///
/// Default: 320
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_height: Option<f32>,
/// Whether to limit the content width in the agent panel. When enabled,
/// content will be constrained to `max_content_width` and centered when
/// the panel is wider than that value, for optimal readability.
///
/// Default: true
pub limit_content_width: Option<bool>,
/// Maximum content width in pixels for the agent panel. Content will be
/// centered when the panel is wider than this value.
///
/// Default: 850
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub max_content_width: Option<f32>,
/// The default model to use when creating new chats and for other features when a specific model is not specified.
pub default_model: Option<LanguageModelSelection>,
/// The model to use for subagents spawned via the `spawn_agent` tool. Defaults to the parent agent's model when not specified.
pub subagent_model: Option<LanguageModelSelection>,
/// Favorite models to show at the top of the model selector.
#[serde(default)]
pub favorite_models: Vec<LanguageModelSelection>,
/// Model to use for the inline assistant. Defaults to default_model when not specified.
pub inline_assistant_model: Option<LanguageModelSelection>,
/// Model to use for the inline assistant when streaming tools are enabled.
///
/// Default: true
pub inline_assistant_use_streaming_tools: Option<bool>,
/// Model to use for generating git commit messages. Defaults to default_model when not specified.
pub commit_message_model: Option<LanguageModelSelection>,
/// Model to use for generating thread summaries. Defaults to default_model when not specified.
pub thread_summary_model: Option<LanguageModelSelection>,
/// Additional models with which to generate alternatives when performing inline assists.
pub inline_alternatives: Option<Vec<LanguageModelSelection>>,
/// The default profile to use in the Agent.
///
/// Default: write
pub default_profile: Option<Arc<str>>,
/// The available agent profiles.
pub profiles: Option<IndexMap<Arc<str>, AgentProfileContent>>,
/// Where to show a popup notification when the agent is waiting for user input.
///
/// Default: "primary_screen"
pub notify_when_agent_waiting: Option<NotifyWhenAgentWaiting>,
/// When to play a sound when the agent has either completed its response, or needs user input.
///
/// Default: never
pub play_sound_when_agent_done: Option<PlaySoundWhenAgentDone>,
/// Whether to display agent edits in single-file editors in addition to the review multibuffer pane.
///
/// Default: true
pub 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)]
pub model_parameters: Vec<LanguageModelParameters>,
/// Whether to show thumb buttons for feedback in the agent panel.
///
/// Default: true
pub enable_feedback: Option<bool>,
/// Whether to have edit cards in the agent panel expanded, showing a preview of the full diff.
///
/// Default: true
pub expand_edit_card: Option<bool>,
/// Whether to have terminal cards in the agent panel expanded, showing the whole command output.
///
/// Default: true
pub expand_terminal_card: Option<bool>,
/// How thinking blocks should be displayed by default in the agent panel.
///
/// Default: automatic
pub thinking_display: Option<ThinkingBlockDisplay>,
/// Whether clicking the stop button on a running terminal tool should also cancel the agent's generation.
/// Note that this only applies to the stop button, not to ctrl+c inside the terminal.
///
/// Default: true
pub cancel_generation_on_terminal_stop: Option<bool>,
/// Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages in the agent panel.
///
/// Default: false
pub use_modifier_to_send: Option<bool>,
/// Minimum number of lines of height the agent message editor should have.
///
/// Default: 4
pub message_editor_min_lines: Option<usize>,
/// Whether to show turn statistics (elapsed time during generation, final turn duration).
///
/// Default: false
pub show_turn_stats: Option<bool>,
/// Whether to show the merge conflict indicator in the status bar
/// that offers to resolve conflicts using the agent.
///
/// Default: true
pub show_merge_conflict_indicator: Option<bool>,
/// Per-tool permission rules for granular control over which tool actions
/// require confirmation.
///
/// The global `default` applies when no tool-specific rules match.
/// For external agent servers (e.g. Claude Agent) that define their own
/// permission modes, "deny" and "confirm" still take precedence — the
/// external agent's permission system is only used when Zed would allow
/// the action. Per-tool regex patterns (`always_allow`, `always_deny`,
/// `always_confirm`) match against the tool's text input (command, path,
/// URL, etc.).
pub tool_permissions: Option<ToolPermissionsContent>,
}
impl AgentSettingsContent {
pub fn set_dock(&mut self, dock: DockPosition) {
self.dock = Some(dock);
}
pub fn set_sidebar_side(&mut self, position: SidebarDockPosition) {
self.sidebar_side = Some(position);
}
pub fn set_flexible_size(&mut self, flexible: bool) {
self.flexible = Some(flexible);
}
pub fn set_model(&mut self, language_model: LanguageModelSelection) {
self.default_model = Some(language_model)
}
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
self.inline_assistant_model = Some(LanguageModelSelection {
provider: provider.into(),
model,
enable_thinking: false,
effort: None,
speed: None,
});
}
pub fn set_profile(&mut self, profile_id: Arc<str>) {
self.default_profile = Some(profile_id);
}
pub fn add_favorite_model(&mut self, model: LanguageModelSelection) {
// Note: this is intentional to not compare using `PartialEq`here.
// Full equality would treat entries that differ just in thinking/effort/speed
// as distinct and silently produce duplicates.
if !self
.favorite_models
.iter()
.any(|m| m.provider == model.provider && m.model == model.model)
{
self.favorite_models.push(model);
}
}
pub fn remove_favorite_model(&mut self, model: &LanguageModelSelection) {
self.favorite_models
.retain(|m| !(m.provider == model.provider && m.model == model.model));
}
pub fn update_favorite_model<F>(&mut self, provider: &str, model: &str, f: F)
where
F: FnOnce(&mut LanguageModelSelection),
{
if let Some(entry) = self
.favorite_models
.iter_mut()
.find(|m| m.provider.0 == provider && m.model == model)
{
f(entry);
}
}
pub fn set_tool_default_permission(&mut self, tool_id: &str, mode: ToolPermissionMode) {
let tool_permissions = self.tool_permissions.get_or_insert_default();
let tool_rules = tool_permissions
.tools
.entry(Arc::from(tool_id))
.or_default();
tool_rules.default = Some(mode);
}
pub fn add_tool_allow_pattern(&mut self, tool_name: &str, pattern: String) {
let tool_permissions = self.tool_permissions.get_or_insert_default();
let tool_rules = tool_permissions
.tools
.entry(Arc::from(tool_name))
.or_default();
let always_allow = tool_rules.always_allow.get_or_insert_default();
if !always_allow.0.iter().any(|r| r.pattern == pattern) {
always_allow.0.push(ToolRegexRule {
pattern,
case_sensitive: None,
});
}
}
pub fn add_tool_deny_pattern(&mut self, tool_name: &str, pattern: String) {
let tool_permissions = self.tool_permissions.get_or_insert_default();
let tool_rules = tool_permissions
.tools
.entry(Arc::from(tool_name))
.or_default();
let always_deny = tool_rules.always_deny.get_or_insert_default();
if !always_deny.0.iter().any(|r| r.pattern == pattern) {
always_deny.0.push(ToolRegexRule {
pattern,
case_sensitive: None,
});
}
}
}
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
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>,
/// The default language model selected when using this profile.
pub default_model: Option<LanguageModelSelection>,
}
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ContextServerPresetContent {
pub tools: IndexMap<Arc<str>, bool>,
}
#[derive(
Copy,
Clone,
Default,
Debug,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum NotifyWhenAgentWaiting {
#[default]
PrimaryScreen,
AllScreens,
Never,
}
#[derive(
Copy,
Clone,
Default,
Debug,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum PlaySoundWhenAgentDone {
#[default]
Never,
WhenHidden,
Always,
}
impl PlaySoundWhenAgentDone {
pub fn should_play(&self, visible: bool) -> bool {
match self {
PlaySoundWhenAgentDone::Never => false,
PlaySoundWhenAgentDone::WhenHidden => !visible,
PlaySoundWhenAgentDone::Always => true,
}
}
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
pub struct LanguageModelSelection {
pub provider: LanguageModelProviderSetting,
pub model: String,
#[serde(default)]
pub enable_thinking: bool,
pub effort: Option<String>,
pub speed: Option<language_model_core::Speed>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
pub struct LanguageModelParameters {
pub provider: Option<LanguageModelProviderSetting>,
pub model: Option<String>,
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub temperature: Option<f32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, MergeFrom)]
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",
"opencode",
"openrouter",
"vercel_ai_gateway",
"x_ai",
"zed.dev"
]
},
{
"type": "string",
}
]
})
}
}
impl From<String> for LanguageModelProviderSetting {
fn from(provider: String) -> Self {
Self(provider)
}
}
impl From<&str> for LanguageModelProviderSetting {
fn from(provider: &str) -> Self {
Self(provider.to_string())
}
}
#[with_fallible_options]
#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug)]
#[serde(transparent)]
pub struct AllAgentServersSettings(pub HashMap<String, CustomAgentServerSettings>);
impl std::ops::Deref for AllAgentServersSettings {
type Target = HashMap<String, CustomAgentServerSettings>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for AllAgentServersSettings {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[with_fallible_options]
#[derive(Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CustomAgentServerSettings {
Custom {
#[serde(rename = "command")]
path: PathBuf,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<String>,
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
env: HashMap<String, String>,
/// The default mode to use for this agent.
///
/// Note: Not only all agents support modes.
///
/// Default: None
default_mode: Option<String>,
/// The default model to use for this agent.
///
/// This should be the model ID as reported by the agent.
///
/// Default: None
default_model: Option<String>,
/// The favorite models for this agent.
///
/// These are the model IDs as reported by the agent.
///
/// Default: []
#[serde(default, skip_serializing_if = "Vec::is_empty")]
favorite_models: Vec<String>,
/// Default values for session config options.
///
/// This is a map from config option ID to value ID.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
default_config_options: HashMap<String, String>,
/// Favorited values for session config options.
///
/// This is a map from config option ID to a list of favorited value IDs.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
favorite_config_option_values: HashMap<String, Vec<String>>,
},
Extension {
/// Additional environment variables to pass to the agent.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
env: HashMap<String, String>,
/// The default mode to use for this agent.
///
/// Note: Not only all agents support modes.
///
/// Default: None
default_mode: Option<String>,
/// The default model to use for this agent.
///
/// This should be the model ID as reported by the agent.
///
/// Default: None
default_model: Option<String>,
/// The favorite models for this agent.
///
/// These are the model IDs as reported by the agent.
///
/// Default: []
#[serde(default, skip_serializing_if = "Vec::is_empty")]
favorite_models: Vec<String>,
/// Default values for session config options.
///
/// This is a map from config option ID to value ID.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
default_config_options: HashMap<String, String>,
/// Favorited values for session config options.
///
/// This is a map from config option ID to a list of favorited value IDs.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
favorite_config_option_values: HashMap<String, Vec<String>>,
},
Registry {
/// Additional environment variables to pass to the agent.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
env: HashMap<String, String>,
/// The default mode to use for this agent.
///
/// Note: Not only all agents support modes.
///
/// Default: None
default_mode: Option<String>,
/// The default model to use for this agent.
///
/// This should be the model ID as reported by the agent.
///
/// Default: None
default_model: Option<String>,
/// The favorite models for this agent.
///
/// These are the model IDs as reported by the agent.
///
/// Default: []
#[serde(default, skip_serializing_if = "Vec::is_empty")]
favorite_models: Vec<String>,
/// Default values for session config options.
///
/// This is a map from config option ID to value ID.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
default_config_options: HashMap<String, String>,
/// Favorited values for session config options.
///
/// This is a map from config option ID to a list of favorited value IDs.
///
/// Default: {}
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
favorite_config_option_values: HashMap<String, Vec<String>>,
},
}
#[with_fallible_options]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ToolPermissionsContent {
/// Global default permission when no tool-specific rules match.
/// Individual tools can override this with their own default.
/// Default: confirm
#[serde(alias = "default_mode")]
pub default: Option<ToolPermissionMode>,
/// Per-tool permission rules.
/// Keys are tool names (e.g. terminal, edit_file, fetch) including MCP
/// tools (e.g. mcp:server_name:tool_name). Any tool name is accepted;
/// even tools without meaningful text input can have a `default` set.
#[serde(default)]
pub tools: HashMap<Arc<str>, ToolRulesContent>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ToolRulesContent {
/// Default mode when no regex rules match.
/// When unset, inherits from the global `tool_permissions.default`.
#[serde(alias = "default_mode")]
pub default: Option<ToolPermissionMode>,
/// Regexes for inputs to auto-approve.
/// For terminal: matches command. For file tools: matches path. For fetch: matches URL.
/// For `copy_path` and `move_path`, patterns are matched independently against each
/// path (source and destination).
/// Patterns accumulate across settings layers (user, project, profile) and cannot be
/// removed by a higher-priority layer—only new patterns can be added.
/// Default: []
pub always_allow: Option<ExtendingVec<ToolRegexRule>>,
/// Regexes for inputs to auto-reject.
/// **SECURITY**: These take precedence over ALL other rules, across ALL settings layers.
/// For `copy_path` and `move_path`, patterns are matched independently against each
/// path (source and destination).
/// Patterns accumulate across settings layers (user, project, profile) and cannot be
/// removed by a higher-priority layer—only new patterns can be added.
/// Default: []
pub always_deny: Option<ExtendingVec<ToolRegexRule>>,
/// Regexes for inputs that must always prompt.
/// Takes precedence over always_allow but not always_deny.
/// For `copy_path` and `move_path`, patterns are matched independently against each
/// path (source and destination).
/// Patterns accumulate across settings layers (user, project, profile) and cannot be
/// removed by a higher-priority layer—only new patterns can be added.
/// Default: []
pub always_confirm: Option<ExtendingVec<ToolRegexRule>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ToolRegexRule {
/// The regex pattern to match.
#[serde(default)]
pub pattern: String,
/// Whether the regex is case-sensitive.
/// Default: false (case-insensitive)
pub case_sensitive: Option<bool>,
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
)]
#[serde(rename_all = "snake_case")]
pub enum ToolPermissionMode {
/// Auto-approve without prompting.
Allow,
/// Auto-reject with an error.
Deny,
/// Always prompt for confirmation (default behavior).
#[default]
Confirm,
}
impl std::fmt::Display for ToolPermissionMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ToolPermissionMode::Allow => write!(f, "Allow"),
ToolPermissionMode::Deny => write!(f, "Deny"),
ToolPermissionMode::Confirm => write!(f, "Confirm"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_tool_default_permission_creates_structure() {
let mut settings = AgentSettingsContent::default();
assert!(settings.tool_permissions.is_none());
settings.set_tool_default_permission("terminal", ToolPermissionMode::Allow);
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
assert_eq!(terminal_rules.default, Some(ToolPermissionMode::Allow));
}
#[test]
fn test_set_tool_default_permission_updates_existing() {
let mut settings = AgentSettingsContent::default();
settings.set_tool_default_permission("terminal", ToolPermissionMode::Confirm);
settings.set_tool_default_permission("terminal", ToolPermissionMode::Allow);
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
assert_eq!(terminal_rules.default, Some(ToolPermissionMode::Allow));
}
#[test]
fn test_set_tool_default_permission_for_mcp_tool() {
let mut settings = AgentSettingsContent::default();
settings.set_tool_default_permission("mcp:github:create_issue", ToolPermissionMode::Allow);
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let mcp_rules = tool_permissions
.tools
.get("mcp:github:create_issue")
.unwrap();
assert_eq!(mcp_rules.default, Some(ToolPermissionMode::Allow));
}
#[test]
fn test_add_tool_allow_pattern_creates_structure() {
let mut settings = AgentSettingsContent::default();
assert!(settings.tool_permissions.is_none());
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(always_allow.0.len(), 1);
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
}
#[test]
fn test_add_tool_allow_pattern_appends_to_existing() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("terminal", "^npm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(always_allow.0.len(), 2);
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
assert_eq!(always_allow.0[1].pattern, "^npm\\s");
}
#[test]
fn test_add_tool_allow_pattern_does_not_duplicate() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(
always_allow.0.len(),
1,
"Duplicate patterns should not be added"
);
}
#[test]
fn test_add_tool_allow_pattern_for_different_tools() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_allow_pattern("fetch", "^https?://github\\.com".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
assert_eq!(
terminal_rules.always_allow.as_ref().unwrap().0[0].pattern,
"^cargo\\s"
);
let fetch_rules = tool_permissions.tools.get("fetch").unwrap();
assert_eq!(
fetch_rules.always_allow.as_ref().unwrap().0[0].pattern,
"^https?://github\\.com"
);
}
#[test]
fn test_add_tool_deny_pattern_creates_structure() {
let mut settings = AgentSettingsContent::default();
assert!(settings.tool_permissions.is_none());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(always_deny.0.len(), 1);
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
}
#[test]
fn test_add_tool_deny_pattern_appends_to_existing() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^sudo\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(always_deny.0.len(), 2);
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
assert_eq!(always_deny.0[1].pattern, "^sudo\\s");
}
#[test]
fn test_add_tool_deny_pattern_does_not_duplicate() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(
always_deny.0.len(),
1,
"Duplicate patterns should not be added"
);
}
#[test]
fn test_add_tool_deny_and_allow_patterns_separate() {
let mut settings = AgentSettingsContent::default();
settings.add_tool_allow_pattern("terminal", "^cargo\\s".to_string());
settings.add_tool_deny_pattern("terminal", "^rm\\s".to_string());
let tool_permissions = settings.tool_permissions.as_ref().unwrap();
let terminal_rules = tool_permissions.tools.get("terminal").unwrap();
let always_allow = terminal_rules.always_allow.as_ref().unwrap();
assert_eq!(always_allow.0.len(), 1);
assert_eq!(always_allow.0[0].pattern, "^cargo\\s");
let always_deny = terminal_rules.always_deny.as_ref().unwrap();
assert_eq!(always_deny.0.len(), 1);
assert_eq!(always_deny.0[0].pattern, "^rm\\s");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
use std::sync::Arc;
use collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ExtensionSettingsContent {
/// The extensions that should be automatically installed by Zed.
///
/// This is used to make functionality provided by extensions (e.g., language support)
/// available out-of-the-box.
///
/// Default: { "html": true }
#[serde(default)]
pub auto_install_extensions: HashMap<Arc<str>, bool>,
#[serde(default)]
pub auto_update_extensions: HashMap<Arc<str>, bool>,
/// The capabilities granted to extensions.
pub granted_extension_capabilities: Option<Vec<ExtensionCapabilityContent>>,
}
/// A capability for an extension.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ExtensionCapabilityContent {
#[serde(rename = "process:exec")]
ProcessExec {
/// The command to execute.
command: String,
/// The arguments to pass to the command. Use `*` for a single wildcard argument.
/// If the last element is `**`, then any trailing arguments are allowed.
args: Vec<String>,
},
DownloadFile {
host: String,
path: Vec<String>,
},
#[serde(rename = "npm:install")]
NpmInstallPackage {
package: String,
},
}

View File

@@ -0,0 +1,112 @@
use std::cell::RefCell;
use serde::Deserialize;
use crate::ParseStatus;
thread_local! {
static ERRORS: RefCell<Option<Vec<anyhow::Error>>> = const { RefCell::new(None) };
}
pub fn parse_json<'de, T>(json: &'de str) -> (Option<T>, ParseStatus)
where
T: Deserialize<'de>,
{
ERRORS.with_borrow_mut(|errors| {
errors.replace(Vec::default());
});
let mut deserializer = serde_json_lenient::Deserializer::from_str(json);
let value = T::deserialize(&mut deserializer);
let value = match value {
Ok(value) => value,
Err(error) => {
return (
None,
ParseStatus::Failed {
error: error.to_string(),
},
);
}
};
if let Some(errors) = ERRORS.with_borrow_mut(|errors| errors.take().filter(|e| !e.is_empty())) {
let error = errors
.into_iter()
.map(|e| e.to_string())
.flat_map(|e| ["\n".to_owned(), e])
.skip(1)
.collect::<String>();
return (Some(value), ParseStatus::Failed { error });
}
(Some(value), ParseStatus::Success)
}
pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de> + FallibleOption,
{
match T::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(e) => ERRORS.with_borrow_mut(|errors| {
if let Some(errors) = errors {
errors.push(anyhow::anyhow!("{}", e));
Ok(Default::default())
} else {
Err(e)
}
}),
}
}
pub trait FallibleOption: Default {}
impl<T> FallibleOption for Option<T> {}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use settings_macros::with_fallible_options;
use crate::ParseStatus;
#[with_fallible_options]
#[derive(Deserialize, Debug, PartialEq)]
struct Foo {
foo: Option<String>,
bar: Option<usize>,
baz: Option<bool>,
}
#[test]
fn test_fallible() {
let input = r#"
{"foo": "bar",
"bar": "foo",
"baz": 3,
}
"#;
let (settings, result) = crate::fallible_options::parse_json::<Foo>(&input);
assert_eq!(
settings.unwrap(),
Foo {
foo: Some("bar".into()),
bar: None,
baz: None,
}
);
assert!(settings_json::parse_json_with_comments::<Foo>(&input).is_err());
let ParseStatus::Failed { error } = result else {
panic!("Expected parse to fail")
};
assert_eq!(
error,
"invalid type: string \"foo\", expected usize at line 3 column 24\ninvalid type: integer `3`, expected a boolean at line 4 column 20".to_string()
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,499 @@
use crate::merge_from::MergeFrom;
use collections::HashMap;
use language_model_core::ReasoningEffort;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
use std::sync::Arc;
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct AllLanguageModelSettingsContent {
pub anthropic: Option<AnthropicSettingsContent>,
pub bedrock: Option<AmazonBedrockSettingsContent>,
pub deepseek: Option<DeepseekSettingsContent>,
pub google: Option<GoogleSettingsContent>,
pub lmstudio: Option<LmStudioSettingsContent>,
pub mistral: Option<MistralSettingsContent>,
pub ollama: Option<OllamaSettingsContent>,
pub opencode: Option<OpenCodeSettingsContent>,
pub open_router: Option<OpenRouterSettingsContent>,
pub openai: Option<OpenAiSettingsContent>,
pub openai_compatible: Option<HashMap<Arc<str>, OpenAiCompatibleSettingsContent>>,
pub vercel_ai_gateway: Option<VercelAiGatewaySettingsContent>,
pub x_ai: Option<XAiSettingsContent>,
#[serde(rename = "zed.dev")]
pub zed_dot_dev: Option<ZedDotDevSettingsContent>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct AnthropicSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<AnthropicAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct AnthropicAvailableModel {
/// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc
pub name: String,
/// The model's name in Zed's UI, such as in the model selector dropdown menu in the agent panel.
pub display_name: Option<String>,
/// The model's context window size.
pub max_tokens: u64,
/// A model `name` to substitute when calling tools, in case the primary model doesn't support tool calling.
pub tool_override: Option<String>,
/// Configuration of Anthropic's caching API.
pub cache_configuration: Option<LanguageModelCacheConfiguration>,
pub max_output_tokens: Option<u64>,
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_temperature: Option<f32>,
#[serde(default)]
pub extra_beta_headers: Vec<String>,
/// The model's mode (e.g. thinking)
pub mode: Option<ModelMode>,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct AmazonBedrockSettingsContent {
pub available_models: Option<Vec<BedrockAvailableModel>>,
pub endpoint_url: Option<String>,
pub region: Option<String>,
pub profile: Option<String>,
pub authentication_method: Option<BedrockAuthMethodContent>,
pub allow_global: Option<bool>,
/// The guardrail identifier (ARN or ID) to apply to Bedrock API requests.
pub guardrail_identifier: Option<String>,
/// The guardrail version to use. Defaults to "DRAFT" if not specified.
pub guardrail_version: Option<String>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct BedrockAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub cache_configuration: Option<LanguageModelCacheConfiguration>,
pub max_output_tokens: Option<u64>,
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_temperature: Option<f32>,
pub mode: Option<ModelMode>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub enum BedrockAuthMethodContent {
#[serde(rename = "named_profile")]
NamedProfile,
#[serde(rename = "sso")]
SingleSignOn,
#[serde(rename = "api_key")]
ApiKey,
/// IMDSv2, PodIdentity, env vars, etc.
#[serde(rename = "default")]
Automatic,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct OllamaSettingsContent {
pub api_url: Option<String>,
pub auto_discover: Option<bool>,
pub available_models: Option<Vec<OllamaAvailableModel>>,
pub context_window: Option<u64>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OllamaAvailableModel {
/// The model name in the Ollama API (e.g. "llama3.2:latest")
pub name: String,
/// The model's name in Zed's UI, such as in the model selector dropdown menu in the agent panel.
pub display_name: Option<String>,
/// The Context Length parameter to the model (aka num_ctx or n_ctx)
pub max_tokens: u64,
/// The number of seconds to keep the connection open after the last request
pub keep_alive: Option<KeepAlive>,
/// Whether the model supports tools
pub supports_tools: Option<bool>,
/// Whether the model supports vision
pub supports_images: Option<bool>,
/// Whether to enable think mode
pub supports_thinking: Option<bool>,
}
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema, MergeFrom)]
#[serde(untagged)]
pub enum KeepAlive {
/// Keep model alive for N seconds
Seconds(isize),
/// Keep model alive for a fixed duration. Accepts durations like "5m", "10m", "1h", "1d", etc.
Duration(String),
}
impl KeepAlive {
/// Keep model alive until a new model is loaded or until Ollama shuts down
pub fn indefinite() -> Self {
Self::Seconds(-1)
}
}
impl Default for KeepAlive {
fn default() -> Self {
Self::indefinite()
}
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct OpenCodeSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<OpenCodeAvailableModel>>,
/// Whether to show OpenCode Zen models. Defaults to true.
pub show_zen_models: Option<bool>,
/// Whether to show OpenCode Go models. Defaults to true.
pub show_go_models: Option<bool>,
/// Whether to show OpenCode Free models. Defaults to true.
pub show_free_models: Option<bool>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub enum OpenCodeModelSubscription {
Zen,
Go,
Free,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OpenCodeAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
/// The API protocol to use for this model: "anthropic", "openai_responses", "openai_chat", or "google".
pub protocol: String,
/// The subscription for this model: "zen", "go", or "free". Defaults to Zen.
pub subscription: Option<OpenCodeModelSubscription>,
/// Custom Model API URL to use for this model.
pub custom_model_api_url: Option<String>,
/// Supported reasoning effort levels, for example `["low", "medium", "high"].
pub reasoning_effort_levels: Option<Vec<ReasoningEffort>>,
/// When using OpenAiChat protocol, whether thinking tokens are sent as a dedicated `reasoning_content` field or inline in message text.
#[serde(default)]
pub interleaved_reasoning: bool,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct LmStudioSettingsContent {
pub api_url: Option<String>,
pub api_key: Option<String>,
pub available_models: Option<Vec<LmStudioAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct LmStudioAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub supports_tool_calls: bool,
pub supports_images: bool,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct DeepseekSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<DeepseekAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct DeepseekAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct MistralSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<MistralAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct MistralAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
pub supports_tools: Option<bool>,
pub supports_images: Option<bool>,
pub supports_thinking: Option<bool>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct OpenAiSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<OpenAiAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OpenAiAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
pub reasoning_effort: Option<OpenAiReasoningEffort>,
#[serde(default)]
pub capabilities: OpenAiModelCapabilities,
}
pub use language_model_core::ReasoningEffort as OpenAiReasoningEffort;
impl MergeFrom for OpenAiReasoningEffort {
fn merge_from(&mut self, other: &Self) {
*self = *other;
}
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct OpenAiCompatibleSettingsContent {
pub api_url: String,
pub available_models: Vec<OpenAiCompatibleAvailableModel>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OpenAiModelCapabilities {
#[serde(default = "default_true")]
pub chat_completions: bool,
#[serde(default = "default_true")]
pub images: bool,
}
impl Default for OpenAiModelCapabilities {
fn default() -> Self {
Self {
chat_completions: default_true(),
images: default_true(),
}
}
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OpenAiCompatibleAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
pub reasoning_effort: Option<OpenAiReasoningEffort>,
#[serde(default)]
pub capabilities: OpenAiCompatibleModelCapabilities,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OpenAiCompatibleModelCapabilities {
pub tools: bool,
pub images: bool,
pub parallel_tool_calls: bool,
pub prompt_cache_key: bool,
#[serde(default = "default_true")]
pub chat_completions: bool,
#[serde(default)]
pub interleaved_reasoning: bool,
}
impl Default for OpenAiCompatibleModelCapabilities {
fn default() -> Self {
Self {
tools: true,
images: false,
parallel_tool_calls: false,
prompt_cache_key: false,
chat_completions: default_true(),
interleaved_reasoning: false,
}
}
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct VercelAiGatewaySettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<VercelAiGatewayAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct VercelAiGatewayAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
#[serde(default)]
pub capabilities: OpenAiCompatibleModelCapabilities,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct GoogleSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<GoogleAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct GoogleAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub mode: Option<ModelMode>,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct XAiSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<XaiAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct XaiAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
pub supports_images: Option<bool>,
pub supports_tools: Option<bool>,
pub parallel_tool_calls: Option<bool>,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct ZedDotDevSettingsContent {
pub available_models: Option<Vec<ZedDotDevAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ZedDotDevAvailableModel {
/// The provider of the language model.
pub provider: ZedDotDevAvailableProvider,
/// The model's name in the provider's API. e.g. claude-3-5-sonnet-20240620
pub name: String,
/// The name displayed in the UI, such as in the agent panel model dropdown menu.
pub display_name: Option<String>,
/// The size of the context window, indicating the maximum number of tokens the model can process.
pub max_tokens: usize,
/// The maximum number of output tokens allowed by the model.
pub max_output_tokens: Option<u64>,
/// The maximum number of completion tokens allowed by the model (o1-* only)
pub max_completion_tokens: Option<u64>,
/// Override this model with a different Anthropic model for tool calls.
pub tool_override: Option<String>,
/// Indicates whether this custom model supports caching.
pub cache_configuration: Option<LanguageModelCacheConfiguration>,
/// The default temperature to use for this model.
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_temperature: Option<f32>,
/// Any extra beta headers to provide when using the model.
#[serde(default)]
pub extra_beta_headers: Vec<String>,
/// The model's mode (e.g. thinking)
pub mode: Option<ModelMode>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "lowercase")]
pub enum ZedDotDevAvailableProvider {
Anthropic,
OpenAi,
Google,
}
#[with_fallible_options]
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
pub struct OpenRouterSettingsContent {
pub api_url: Option<String>,
pub available_models: Option<Vec<OpenRouterAvailableModel>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OpenRouterAvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: u64,
pub max_output_tokens: Option<u64>,
pub max_completion_tokens: Option<u64>,
pub supports_tools: Option<bool>,
pub supports_images: Option<bool>,
pub mode: Option<ModelMode>,
pub provider: Option<OpenRouterProvider>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct OpenRouterProvider {
order: Option<Vec<String>>,
#[serde(default = "default_true")]
allow_fallbacks: bool,
#[serde(default)]
require_parameters: bool,
#[serde(default)]
data_collection: DataCollection,
only: Option<Vec<String>>,
ignore: Option<Vec<String>>,
quantizations: Option<Vec<String>>,
sort: Option<String>,
}
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "lowercase")]
pub enum DataCollection {
#[default]
Allow,
Disallow,
}
fn default_true() -> bool {
true
}
/// Configuration for caching language model messages.
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct LanguageModelCacheConfiguration {
pub max_cache_anchors: usize,
pub should_speculate: bool,
pub min_total_token: u64,
}
pub use language_model_core::ModelMode;
impl MergeFrom for ModelMode {
fn merge_from(&mut self, other: &Self) {
*self = *other;
}
}

View File

@@ -0,0 +1,174 @@
/// Trait for recursively merging settings structures.
///
/// When Zed starts it loads settings from `default.json` to initialize
/// everything. These may be further refined by loading the user's settings,
/// and any settings profiles; and then further refined by loading any
/// local project settings.
///
/// The default behaviour of merging is:
/// * For objects with named keys (HashMap, structs, etc.). The values are merged deeply
/// (so if the default settings has languages.JSON.prettier.allowed = true, and the user's settings has
/// languages.JSON.tab_size = 4; the merged settings file will have both settings).
/// * For options, a None value is ignored, but Some values are merged recursively.
/// * For other types (including Vec), a merge overwrites the current value.
///
/// If you want to break the rules you can (e.g. ExtendingVec, or SaturatingBool).
#[allow(unused)]
pub trait MergeFrom {
/// Merge from a source of the same type.
fn merge_from(&mut self, other: &Self);
/// Merge from an optional source of the same type.
fn merge_from_option(&mut self, other: Option<&Self>) {
if let Some(other) = other {
self.merge_from(other);
}
}
}
macro_rules! merge_from_overwrites {
($($type:ty),+ $(,)?) => {
$(
impl MergeFrom for $type {
fn merge_from(&mut self, other: &Self) {
*self = other.clone();
}
}
)+
}
}
merge_from_overwrites!(
u16,
u32,
u64,
usize,
i16,
i32,
i64,
bool,
f64,
f32,
char,
std::num::NonZeroUsize,
std::num::NonZeroU32,
String,
std::sync::Arc<str>,
std::path::PathBuf,
std::sync::Arc<std::path::Path>,
language_model_core::Speed,
);
impl<T: Clone + MergeFrom> MergeFrom for Option<T> {
fn merge_from(&mut self, other: &Self) {
let Some(other) = other else {
return;
};
if let Some(this) = self {
this.merge_from(other);
} else {
self.replace(other.clone());
}
}
}
impl<T: Clone> MergeFrom for Vec<T> {
fn merge_from(&mut self, other: &Self) {
*self = other.clone()
}
}
impl<T: MergeFrom> MergeFrom for Box<T> {
fn merge_from(&mut self, other: &Self) {
self.as_mut().merge_from(other.as_ref())
}
}
// Implementations for collections that extend/merge their contents
impl<K, V> MergeFrom for collections::HashMap<K, V>
where
K: Clone + std::hash::Hash + Eq,
V: Clone + MergeFrom,
{
fn merge_from(&mut self, other: &Self) {
for (key, value) in other {
if let Some(existing) = self.get_mut(key) {
existing.merge_from(value);
} else {
self.insert(key.clone(), value.clone());
}
}
}
}
impl<K, V> MergeFrom for collections::BTreeMap<K, V>
where
K: Clone + std::hash::Hash + Eq + Ord,
V: Clone + MergeFrom,
{
fn merge_from(&mut self, other: &Self) {
for (key, value) in other {
if let Some(existing) = self.get_mut(key) {
existing.merge_from(value);
} else {
self.insert(key.clone(), value.clone());
}
}
}
}
impl<K, V> MergeFrom for collections::IndexMap<K, V>
where
K: std::hash::Hash + Eq + Clone,
V: Clone + MergeFrom,
{
fn merge_from(&mut self, other: &Self) {
for (key, value) in other {
if let Some(existing) = self.get_mut(key) {
existing.merge_from(value);
} else {
self.insert(key.clone(), value.clone());
}
}
}
}
impl<T> MergeFrom for collections::BTreeSet<T>
where
T: Clone + Ord,
{
fn merge_from(&mut self, other: &Self) {
for item in other {
self.insert(item.clone());
}
}
}
impl<T> MergeFrom for collections::HashSet<T>
where
T: Clone + std::hash::Hash + Eq,
{
fn merge_from(&mut self, other: &Self) {
for item in other {
self.insert(item.clone());
}
}
}
impl MergeFrom for serde_json::Value {
fn merge_from(&mut self, other: &Self) {
match (self, other) {
(serde_json::Value::Object(this), serde_json::Value::Object(other)) => {
for (key, value) in other {
if let Some(existing) = this.get_mut(key) {
existing.merge_from(value);
} else {
this.insert(key.clone(), value.clone());
}
}
}
(this, other) => *this = other.clone(),
}
}
}

View File

@@ -0,0 +1,798 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::Context;
use collections::{BTreeMap, HashMap};
use gpui::Rgba;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings_json::parse_json_with_comments;
use settings_macros::{MergeFrom, with_fallible_options};
use util::serde::default_true;
use crate::{
AllLanguageSettingsContent, DelayMs, ExtendingVec, ParseStatus, ProjectTerminalSettingsContent,
RootUserSettings, SaturatingBool, fallible_options,
};
#[with_fallible_options]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct LspSettingsMap(pub HashMap<Arc<str>, LspSettings>);
impl IntoIterator for LspSettingsMap {
type Item = (Arc<str>, LspSettings);
type IntoIter = std::collections::hash_map::IntoIter<Arc<str>, LspSettings>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl RootUserSettings for ProjectSettingsContent {
fn parse_json(json: &str) -> (Option<Self>, ParseStatus) {
fallible_options::parse_json(json)
}
fn parse_json_with_comments(json: &str) -> anyhow::Result<Self> {
parse_json_with_comments(json)
}
}
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ProjectSettingsContent {
#[serde(flatten)]
pub all_languages: AllLanguageSettingsContent,
#[serde(flatten)]
pub worktree: WorktreeSettingsContent,
/// Configuration for language servers.
///
/// The following settings can be overridden for specific language servers:
/// - initialization_options
///
/// To override settings for a language, add an entry for that language server's
/// name to the lsp value.
/// Default: null
#[serde(default)]
pub lsp: LspSettingsMap,
pub terminal: Option<ProjectTerminalSettingsContent>,
/// Configuration for Debugger-related features
#[serde(default)]
pub dap: HashMap<Arc<str>, DapSettingsContent>,
/// Settings for context servers used for AI-related features.
#[serde(default)]
pub context_servers: HashMap<Arc<str>, ContextServerSettingsContent>,
/// Default timeout in seconds for context server tool calls.
/// Can be overridden per-server in context_servers configuration.
///
/// Default: 60
pub context_server_timeout: Option<u64>,
/// Configuration for how direnv configuration should be loaded
pub load_direnv: Option<DirenvSettings>,
/// The list of custom Git hosting providers.
pub git_hosting_providers: Option<ExtendingVec<GitHostingProviderConfig>>,
/// Whether to disable all AI features in Zed.
///
/// Default: false
pub disable_ai: Option<SaturatingBool>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct WorktreeSettingsContent {
/// Whether to prevent this project from being shared in public channels.
///
/// Default: false
#[serde(default)]
pub prevent_sharing_in_public_channels: bool,
/// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
/// `file_scan_inclusions`.
///
/// Default: [
/// "**/.git",
/// "**/.svn",
/// "**/.hg",
/// "**/.jj",
/// "**/CVS",
/// "**/.DS_Store",
/// "**/Thumbs.db",
/// "**/.classpath",
/// "**/.settings"
/// ]
pub file_scan_exclusions: Option<Vec<String>>,
/// Always include files that match these globs when scanning for files, even if they're
/// ignored by git. This setting is overridden by `file_scan_exclusions`.
/// Default: [
/// ".env*",
/// "docker-compose.*.yml",
/// ]
pub file_scan_inclusions: Option<Vec<String>>,
/// Treat the files matching these globs as `.env` files.
/// Default: ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"]
pub private_files: Option<ExtendingVec<String>>,
/// Treat the files matching these globs as hidden files. You can hide hidden files in the project panel.
/// Default: ["**/.*"]
pub hidden_files: Option<Vec<String>>,
/// Treat the files matching these globs as read-only. These files can be opened and viewed,
/// but cannot be edited. This is useful for generated files, build outputs, or files from
/// external dependencies that should not be modified directly.
/// Default: []
pub read_only_files: Option<Vec<String>>,
}
#[with_fallible_options]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash)]
#[serde(rename_all = "snake_case")]
pub struct LspSettings {
pub binary: Option<BinarySettings>,
/// Options passed to the language server at startup.
///
/// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
///
/// Consult the documentation for the specific language server to see which settings are supported.
pub initialization_options: Option<serde_json::Value>,
/// Language server settings.
///
/// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration
///
/// Consult the documentation for the specific language server to see which settings are supported.
pub settings: Option<serde_json::Value>,
/// If the server supports sending tasks over LSP extensions,
/// this setting can be used to enable or disable them in Zed.
/// Default: true
#[serde(default = "default_true")]
pub enable_lsp_tasks: bool,
pub fetch: Option<FetchSettings>,
}
impl Default for LspSettings {
fn default() -> Self {
Self {
binary: None,
initialization_options: None,
settings: None,
enable_lsp_tasks: true,
fetch: None,
}
}
}
#[with_fallible_options]
#[derive(
Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash,
)]
pub struct BinarySettings {
pub path: Option<String>,
pub arguments: Option<Vec<String>>,
pub env: Option<BTreeMap<String, String>>,
pub ignore_system_version: Option<bool>,
}
#[with_fallible_options]
#[derive(
Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash,
)]
pub struct FetchSettings {
// Whether to consider pre-releases for fetching
pub pre_release: Option<bool>,
}
/// Common language server settings.
#[with_fallible_options]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct GlobalLspSettingsContent {
/// Whether to show the LSP servers button in the status bar.
///
/// Default: `true`
pub button: Option<bool>,
/// The maximum amount of time to wait for responses from language servers, in seconds.
/// A value of `0` will result in no timeout being applied (causing all LSP responses to wait indefinitely until completed).
///
/// Default: `120`
pub request_timeout: Option<u64>,
/// Settings for language server notifications
pub notifications: Option<LspNotificationSettingsContent>,
/// Rules for rendering LSP semantic tokens.
pub semantic_token_rules: Option<SemanticTokenRules>,
}
#[with_fallible_options]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct LspNotificationSettingsContent {
/// Timeout in milliseconds for automatically dismissing language server notifications.
/// Set to 0 to disable auto-dismiss.
///
/// Default: 5000
pub dismiss_timeout_ms: Option<u64>,
}
/// Custom rules for rendering LSP semantic tokens.
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(transparent)]
pub struct SemanticTokenRules {
pub rules: Vec<SemanticTokenRule>,
}
impl SemanticTokenRules {
pub const FILE_NAME: &'static str = "semantic_token_rules.json";
pub fn load(file_path: &Path) -> anyhow::Result<Self> {
let rules_content = std::fs::read(file_path).with_context(|| {
anyhow::anyhow!(
"Could not read semantic token rules from {}",
file_path.display()
)
})?;
serde_json_lenient::from_slice::<SemanticTokenRules>(&rules_content).with_context(|| {
anyhow::anyhow!(
"Failed to parse semantic token rules from {}",
file_path.display()
)
})
}
}
impl crate::merge_from::MergeFrom for SemanticTokenRules {
fn merge_from(&mut self, other: &Self) {
self.rules.splice(0..0, other.rules.iter().cloned());
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct SemanticTokenRule {
pub token_type: Option<String>,
#[serde(default)]
pub token_modifiers: Vec<String>,
#[serde(default)]
pub style: Vec<String>,
pub foreground_color: Option<Rgba>,
pub background_color: Option<Rgba>,
pub underline: Option<SemanticTokenColorOverride>,
pub strikethrough: Option<SemanticTokenColorOverride>,
pub font_weight: Option<SemanticTokenFontWeight>,
pub font_style: Option<SemanticTokenFontStyle>,
}
impl SemanticTokenRule {
pub fn no_style_defined(&self) -> bool {
self.style.is_empty()
&& self.foreground_color.is_none()
&& self.background_color.is_none()
&& self.underline.is_none()
&& self.strikethrough.is_none()
&& self.font_weight.is_none()
&& self.font_style.is_none()
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
#[serde(untagged)]
pub enum SemanticTokenColorOverride {
InheritForeground(bool),
Replace(Rgba),
}
#[derive(
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum SemanticTokenFontWeight {
#[default]
Normal,
Bold,
}
#[derive(
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum SemanticTokenFontStyle {
#[default]
Normal,
Italic,
}
#[with_fallible_options]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub struct DapSettingsContent {
pub binary: Option<String>,
pub args: Option<Vec<String>>,
pub env: Option<HashMap<String, String>>,
}
#[with_fallible_options]
#[derive(
Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom,
)]
pub struct SessionSettingsContent {
/// 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
pub restore_unsaved_buffers: Option<bool>,
/// Whether or not to skip worktree trust checks.
/// When trusted, project settings are synchronized automatically,
/// language and MCP servers are downloaded and started automatically.
///
/// Default: false
pub trust_all_worktrees: Option<bool>,
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom, Debug)]
#[serde(untagged, rename_all = "snake_case")]
pub enum ContextServerSettingsContent {
Stdio {
/// Whether the context server is enabled.
#[serde(default = "default_true")]
enabled: bool,
/// Whether to run the context server on the remote server when using remote development.
///
/// If this is false, the context server will always run on the local machine.
///
/// Default: false
#[serde(default)]
remote: bool,
#[serde(flatten)]
command: ContextServerCommand,
},
Http {
/// Whether the context server is enabled.
#[serde(default = "default_true")]
enabled: bool,
/// The URL of the remote context server.
url: String,
/// Optional headers to send.
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
headers: HashMap<String, String>,
/// Timeout for tool calls in seconds. Defaults to global context_server_timeout if not specified.
timeout: Option<u64>,
},
Extension {
/// Whether the context server is enabled.
#[serde(default = "default_true")]
enabled: bool,
/// Whether to run the context server on the remote server when using remote development.
///
/// If this is false, the context server will always run on the local machine.
///
/// Default: false
#[serde(default)]
remote: bool,
/// The settings for this context server specified by the extension.
///
/// Consult the documentation for the context server to see what settings
/// are supported.
settings: serde_json::Value,
},
}
impl ContextServerSettingsContent {
pub fn set_enabled(&mut self, enabled: bool) {
match self {
ContextServerSettingsContent::Stdio {
enabled: custom_enabled,
..
} => {
*custom_enabled = enabled;
}
ContextServerSettingsContent::Extension {
enabled: ext_enabled,
..
} => *ext_enabled = enabled,
ContextServerSettingsContent::Http {
enabled: remote_enabled,
..
} => *remote_enabled = enabled,
}
}
}
#[with_fallible_options]
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom)]
pub struct ContextServerCommand {
#[serde(rename = "command")]
pub path: PathBuf,
pub args: Vec<String>,
pub env: Option<HashMap<String, String>>,
/// Timeout for tool calls in seconds. Defaults to 60 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 util::redact::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()
}
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct GitSettings {
/// Whether or not to enable git integration.
///
/// Default: true
#[serde(flatten)]
pub enabled: Option<GitEnabledSettings>,
/// Whether or not to show the git gutter.
///
/// Default: tracked_files
pub git_gutter: Option<GitGutterSetting>,
/// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.
///
/// Default: 0
pub gutter_debounce: Option<u64>,
/// Whether or not to show git blame data inline in
/// the currently focused line.
///
/// Default: on
pub inline_blame: Option<InlineBlameSettings>,
/// Git blame settings.
pub blame: Option<BlameSettings>,
/// Which information to show in the branch picker.
///
/// Default: on
pub branch_picker: Option<BranchPickerSettingsContent>,
/// How hunks are displayed visually in the editor.
///
/// Default: staged_hollow
pub hunk_style: Option<GitHunkStyleSetting>,
/// How file paths are displayed in the git gutter.
///
/// Default: file_name_first
pub path_style: Option<GitPathStyle>,
/// Directory where git worktrees are created, relative to the repository
/// working directory.
///
/// When the resolved directory is outside the project root, the
/// project's directory name is automatically appended so that
/// sibling repos don't collide. For example, with the default
/// `"../worktrees"` and a project at `~/code/zed`, worktrees are
/// created under `~/code/worktrees/zed/`.
///
/// When the resolved directory is inside the project root, no
/// extra component is added (it's already project-scoped).
///
/// Examples:
/// - `"../worktrees"` — `~/code/worktrees/<project>/` (default)
/// - `".git/zed-worktrees"` — `<project>/.git/zed-worktrees/`
/// - `"my-worktrees"` — `<project>/my-worktrees/`
///
/// Trailing slashes are ignored.
///
/// Default: ../worktrees
pub worktree_directory: Option<String>,
}
#[with_fallible_options]
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub struct GitEnabledSettings {
pub disable_git: Option<bool>,
pub enable_status: Option<bool>,
pub enable_diff: Option<bool>,
}
impl GitEnabledSettings {
pub fn is_git_status_enabled(&self) -> bool {
!self.disable_git.unwrap_or(false) && self.enable_status.unwrap_or(true)
}
pub fn is_git_diff_enabled(&self) -> bool {
!self.disable_git.unwrap_or(false) && self.enable_diff.unwrap_or(true)
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Default,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum GitGutterSetting {
/// Show git gutter in tracked files.
#[default]
TrackedFiles,
/// Hide git gutter
Hide,
}
#[with_fallible_options]
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub struct InlineBlameSettings {
/// Whether or not to show git blame data inline in
/// the currently focused line.
///
/// Default: true
pub enabled: Option<bool>,
/// Whether to only show the inline blame information
/// after a delay once the cursor stops moving.
///
/// Default: 0
pub delay_ms: Option<DelayMs>,
/// The amount of padding between the end of the source line and the start
/// of the inline blame in units of columns.
///
/// Default: 7
pub padding: Option<u32>,
/// The minimum column number to show the inline blame information at
///
/// Default: 0
pub min_column: Option<u32>,
/// Whether to show commit summary as part of the inline blame.
///
/// Default: false
pub show_commit_summary: Option<bool>,
}
#[with_fallible_options]
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub struct BlameSettings {
/// Whether to show the avatar of the author of the commit.
///
/// Default: true
pub show_avatar: Option<bool>,
}
#[with_fallible_options]
#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub struct BranchPickerSettingsContent {
/// Whether to show author name as part of the commit information.
///
/// Default: false
pub show_author_name: Option<bool>,
}
#[derive(
Clone,
Copy,
PartialEq,
Debug,
Default,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum GitHunkStyleSetting {
/// Show unstaged hunks with a filled background and staged hunks hollow.
#[default]
StagedHollow,
/// Show unstaged hunks hollow and staged hunks with a filled background.
UnstagedHollow,
}
#[with_fallible_options]
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Default,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum GitPathStyle {
/// Show file name first, then path
#[default]
FileNameFirst,
/// Show full path first
FilePathFirst,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct DiagnosticsSettingsContent {
/// Whether to show the project diagnostics button in the status bar.
pub button: Option<bool>,
/// Whether or not to include warning diagnostics.
///
/// Default: true
pub include_warnings: Option<bool>,
/// Settings for using LSP pull diagnostics mechanism in Zed.
pub lsp_pull_diagnostics: Option<LspPullDiagnosticsSettingsContent>,
/// Settings for showing inline diagnostics.
pub inline: Option<InlineDiagnosticsSettingsContent>,
}
#[with_fallible_options]
#[derive(
Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
)]
pub struct LspPullDiagnosticsSettingsContent {
/// Whether to pull for diagnostics or not.
///
/// Default: true
pub enabled: Option<bool>,
/// Minimum time to wait before pulling diagnostics from the language server(s).
/// 0 turns the debounce off.
///
/// Default: 50
pub debounce_ms: Option<DelayMs>,
}
#[with_fallible_options]
#[derive(
Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Eq,
)]
pub struct InlineDiagnosticsSettingsContent {
/// Whether or not to show inline diagnostics
///
/// Default: false
pub enabled: Option<bool>,
/// Whether to only show the inline diagnostics after a delay after the
/// last editor event.
///
/// Default: 150
pub update_debounce_ms: Option<DelayMs>,
/// The amount of padding between the end of the source line and the start
/// of the inline diagnostic in units of columns.
///
/// Default: 4
pub padding: Option<u32>,
/// The minimum column to display inline diagnostics. This setting can be
/// used to horizontally align inline diagnostics at some position. Lines
/// longer than this value will still push diagnostics further to the right.
///
/// Default: 0
pub min_column: Option<u32>,
pub max_severity: Option<DiagnosticSeverityContent>,
}
#[with_fallible_options]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct NodeBinarySettings {
/// The path to the Node binary.
pub path: Option<String>,
/// The path to the npm binary Zed should use (defaults to `.path/../npm`).
pub npm_path: Option<String>,
/// If enabled, Zed will download its own copy of Node.
pub ignore_system_version: Option<bool>,
}
#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub enum DirenvSettings {
/// Load direnv configuration through a shell hook
ShellHook,
/// Load direnv configuration directly using `direnv export json`
#[default]
Direct,
/// Do not load direnv configuration
Disabled,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
PartialEq,
Ord,
PartialOrd,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum DiagnosticSeverityContent {
// No diagnostics are shown.
Off,
Error,
Warning,
Info,
Hint,
All,
}
/// A custom Git hosting provider.
#[with_fallible_options]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct GitHostingProviderConfig {
/// The type of the provider.
///
/// Must be one of `github`, `gitlab`, `bitbucket`, `gitea`, `forgejo`, or `source_hut`.
pub provider: GitHostingProviderKind,
/// The base URL for the provider (e.g., "https://code.corp.big.com").
pub base_url: String,
/// The display name for the provider (e.g., "BigCorp GitHub").
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub enum GitHostingProviderKind {
Github,
Gitlab,
Bitbucket,
Gitea,
Forgejo,
SourceHut,
}

View File

@@ -0,0 +1,135 @@
use serde::Serializer;
/// Serializes an f32 value with 2 decimal places of precision.
///
/// This function rounds the value to 2 decimal places and formats it as a string,
/// then parses it back to f64 before serialization. This ensures clean JSON output
/// without IEEE 754 floating-point artifacts.
///
/// # Arguments
///
/// * `value` - The f32 value to serialize
/// * `serializer` - The serde serializer to use
///
/// # Returns
///
/// Result of the serialization operation
///
/// # Usage
///
/// This function can be used with Serde's `serialize_with` attribute:
/// ```
/// use serde::Serialize;
/// use settings_content::serialize_f32_with_two_decimal_places;
///
/// #[derive(Serialize)]
/// struct ExampleStruct(#[serde(serialize_with = "serialize_f32_with_two_decimal_places")] f32);
/// ```
pub fn serialize_f32_with_two_decimal_places<S>(
value: &f32,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let rounded = (value * 100.0).round() / 100.0;
let formatted = format!("{:.2}", rounded);
let clean_value: f64 = formatted.parse().unwrap_or(rounded as f64);
serializer.serialize_f64(clean_value)
}
/// Serializes an optional f32 value with 2 decimal places of precision.
///
/// This function handles `Option<f32>` types, serializing `Some` values with 2 decimal
/// places of precision and `None` values as null. For `Some` values, it rounds to 2 decimal
/// places and formats as a string, then parses back to f64 before serialization. This ensures
/// clean JSON output without IEEE 754 floating-point artifacts.
///
/// # Arguments
///
/// * `value` - The optional f32 value to serialize
/// * `serializer` - The serde serializer to use
///
/// # Returns
///
/// Result of the serialization operation
///
/// # Behavior
///
/// * `Some(v)` - Serializes the value rounded to 2 decimal places
/// * `None` - Serializes as JSON null
///
/// # Usage
///
/// This function can be used with Serde's `serialize_with` attribute:
/// ```
/// use serde::Serialize;
/// use settings_content::serialize_optional_f32_with_two_decimal_places;
///
/// #[derive(Serialize)]
/// struct ExampleStruct {
/// #[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
/// optional_value: Option<f32>,
/// }
/// ```
pub fn serialize_optional_f32_with_two_decimal_places<S>(
value: &Option<f32>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(v) => {
let rounded = (v * 100.0).round() / 100.0;
let formatted = format!("{:.2}", rounded);
let clean_value: f64 = formatted.parse().unwrap_or(rounded as f64);
serializer.serialize_some(&clean_value)
}
None => serializer.serialize_none(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct TestOptional {
#[serde(serialize_with = "serialize_optional_f32_with_two_decimal_places")]
value: Option<f32>,
}
#[derive(Serialize, Deserialize)]
struct TestNonOptional {
#[serde(serialize_with = "serialize_f32_with_two_decimal_places")]
value: f32,
}
#[test]
fn test_serialize_optional_f32_with_two_decimal_places() {
let cases = [
(Some(123.456789), r#"{"value":123.46}"#),
(Some(1.2), r#"{"value":1.2}"#),
(Some(300.00000), r#"{"value":300.0}"#),
];
for (value, expected) in cases {
let value = TestOptional { value };
assert_eq!(serde_json::to_string(&value).unwrap(), expected);
}
}
#[test]
fn test_serialize_f32_with_two_decimal_places() {
let cases = [
(123.456789, r#"{"value":123.46}"#),
(1.200, r#"{"value":1.2}"#),
(300.00000, r#"{"value":300.0}"#),
];
for (value, expected) in cases {
let value = TestNonOptional { value };
assert_eq!(serde_json::to_string(&value).unwrap(), expected);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,547 @@
use std::path::PathBuf;
use collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
use crate::{FontFamilyName, FontFeaturesContent, FontSize, FontWeightContent};
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct ProjectTerminalSettingsContent {
/// What shell to use when opening a terminal.
///
/// Default: system
pub shell: Option<Shell>,
/// What working directory to use when launching the terminal
///
/// Default: current_project_directory
pub working_directory: Option<WorkingDirectory>,
/// Any key-value pairs added to this list will be added to the terminal's
/// environment. Use `:` to separate multiple values.
///
/// Default: {}
pub env: Option<HashMap<String, String>>,
/// Activates the python virtual environment, if one is found, in the
/// terminal's working directory (as resolved by the working_directory
/// setting). Set this to "off" to disable this behavior.
///
/// Default: on
pub detect_venv: Option<VenvSettings>,
/// Regexes used to identify paths for hyperlink navigation.
///
/// Default: [
/// // Python-style diagnostics
/// "File \"(?<path>[^\"]+)\", line (?<line>[0-9]+)",
/// // Common path syntax with optional line, column, description, trailing punctuation, or
/// // surrounding symbols or quotes
/// [
/// "(?x)",
/// "# optionally starts with 0-2 opening prefix symbols",
/// "[({\\[<]{0,2}",
/// "# which may be followed by an opening quote",
/// "(?<quote>[\"'`])?",
/// "# `path` is the shortest sequence of any non-space character",
/// "(?<link>(?<path>[^ ]+?",
/// " # which may end with a line and optionally a column,",
/// " (?<line_column>:+[0-9]+(:[0-9]+)?|:?\\([0-9]+([,:][0-9]+)?\\))?",
/// "))",
/// "# which must be followed by a matching quote",
/// "(?(<quote>)\\k<quote>)",
/// "# and optionally a single closing symbol",
/// "[)}\\]>]?",
/// "# if line/column matched, may be followed by a description",
/// "(?(<line_column>):[^ 0-9][^ ]*)?",
/// "# which may be followed by trailing punctuation",
/// "[.,:)}\\]>]*",
/// "# and always includes trailing whitespace or end of line",
/// "([ ]+|$)"
/// ]
/// ]
pub path_hyperlink_regexes: Option<Vec<PathHyperlinkRegex>>,
/// Timeout for hover and Cmd-click path hyperlink discovery in milliseconds.
///
/// Default: 1
pub path_hyperlink_timeout_ms: Option<u64>,
}
#[with_fallible_options]
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
pub struct TerminalSettingsContent {
#[serde(flatten)]
pub project: ProjectTerminalSettingsContent,
/// Sets the terminal's font size.
///
/// If this option is not included,
/// the terminal will default to matching the buffer's font size.
pub font_size: Option<FontSize>,
/// Sets the terminal's font family.
///
/// If this option is not included,
/// the terminal will default to matching the buffer's font family.
pub font_family: Option<FontFamilyName>,
/// Sets the terminal's font fallbacks.
///
/// If this option is not included,
/// the terminal will default to matching the buffer's font fallbacks.
#[schemars(extend("uniqueItems" = true))]
pub font_fallbacks: Option<Vec<FontFamilyName>>,
/// Sets the terminal's line height.
///
/// Default: comfortable
pub line_height: Option<TerminalLineHeight>,
pub font_features: Option<FontFeaturesContent>,
/// Sets the terminal's font weight in CSS weight units 0-900.
pub font_weight: Option<FontWeightContent>,
/// Default cursor shape for the terminal.
/// Can be "bar", "block", "underline", or "hollow".
///
/// Default: "block"
pub cursor_shape: Option<CursorShapeContent>,
/// Sets the cursor blinking behavior in the terminal.
///
/// Default: terminal_controlled
pub blinking: Option<TerminalBlink>,
/// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
/// Alternate Scroll mode converts mouse scroll events into up / down key
/// presses when in the alternate screen (e.g. when running applications
/// like vim or less). The terminal can still set and unset this mode.
///
/// Default: on
pub alternate_scroll: Option<AlternateScroll>,
/// Sets whether the option key behaves as the meta key.
///
/// Default: false
pub option_as_meta: Option<bool>,
/// Whether or not selecting text in the terminal will automatically
/// copy to the system clipboard.
///
/// Default: false
pub copy_on_select: Option<bool>,
/// Whether to keep the text selection after copying it to the clipboard.
///
/// Default: true
pub keep_selection_on_copy: Option<bool>,
/// Whether to show the terminal button in the status bar.
///
/// Default: true
pub button: Option<bool>,
pub dock: Option<TerminalDockPosition>,
/// Whether the terminal panel should use flexible (proportional) sizing.
///
/// Default: true
pub flexible: Option<bool>,
/// Default width when the terminal is docked to the left or right.
///
/// Default: 640
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_width: Option<f32>,
/// Default height when the terminal is docked to the bottom.
///
/// Default: 320
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub default_height: Option<f32>,
/// The maximum number of lines to keep in the scrollback history.
/// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
/// 0 disables the scrolling.
/// Existing terminals will not pick up this change until they are recreated.
/// See <a href="https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213">Alacritty documentation</a> for more information.
///
/// Default: 10_000
pub max_scroll_history_lines: Option<usize>,
/// The multiplier for scrolling with the mouse wheel.
///
/// Default: 1.0
pub scroll_multiplier: Option<f32>,
/// Toolbar related settings
pub toolbar: Option<TerminalToolbarContent>,
/// Scrollbar-related settings
pub scrollbar: Option<ScrollbarSettingsContent>,
/// The minimum APCA perceptual contrast between foreground and background colors.
///
/// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
/// especially for dark mode. Values range from 0 to 106.
///
/// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
/// https://readtech.org/ARC/tests/bronze-simple-mode/
/// - 0: No contrast adjustment
/// - 45: Minimum for large fluent text (36px+)
/// - 60: Minimum for other content text
/// - 75: Minimum for body text
/// - 90: Preferred for body text
///
/// Default: 45
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub minimum_contrast: Option<f32>,
/// Whether to show a badge on the terminal panel icon with the count of open terminals.
///
/// Default: false
pub show_count_badge: Option<bool>,
/// What to do when the `BEL` character (`\a`) is printed to terminal.
///
/// Default: "system"
pub bell: Option<TerminalBell>,
}
/// Shell configuration to open the terminal with.
#[derive(
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::EnumDiscriminants,
)]
#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
#[serde(rename_all = "snake_case")]
pub enum Shell {
/// Use the system's default terminal configuration in /etc/passwd
#[default]
System,
/// Use a specific program with no arguments.
Program(String),
/// Use a specific program with arguments.
WithArguments {
/// The program to run.
program: String,
/// The arguments to pass to the program.
args: Vec<String>,
/// An optional string to override the title of the terminal tab
title_override: Option<String>,
},
}
#[derive(
Clone,
Debug,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::EnumDiscriminants,
)]
#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
#[serde(rename_all = "snake_case")]
pub enum WorkingDirectory {
/// Use the current file's directory, falling back to the project directory,
/// then the first project in the workspace.
CurrentFileDirectory,
/// Use the current file's project directory. Fallback to the
/// first project directory strategy if unsuccessful.
CurrentProjectDirectory,
/// Use the first project in this workspace's directory. Fallback to using
/// this platform's home directory.
FirstProjectDirectory,
/// Always use this platform's home directory (if it can be found).
AlwaysHome,
/// Always use a specific directory. This value will be shell expanded.
/// If this path is not a valid directory the terminal will default to
/// this platform's home directory (if it can be found).
Always { directory: String },
}
#[with_fallible_options]
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
)]
pub struct ScrollbarSettingsContent {
/// When to show the scrollbar in the terminal.
///
/// Default: inherits editor scrollbar settings
pub show: Option<ShowScrollbar>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)]
#[serde(rename_all = "snake_case")]
pub enum TerminalLineHeight {
/// Use a line height that's comfortable for reading, 1.618
#[default]
Comfortable,
/// Use a standard line height, 1.3. This option is useful for TUIs,
/// particularly if they use box characters
Standard,
/// Use a custom line height.
Custom(#[serde(serialize_with = "crate::serialize_f32_with_two_decimal_places")] f32),
}
impl TerminalLineHeight {
pub fn value(&self) -> f32 {
match self {
TerminalLineHeight::Comfortable => 1.618,
TerminalLineHeight::Standard => 1.3,
TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
}
}
}
/// When to show the scrollbar.
///
/// Default: auto
#[derive(
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
Eq,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum ShowScrollbar {
/// Show the scrollbar if there's important information or
/// follow the system's configured behavior.
#[default]
Auto,
/// Match the system's configured behavior.
System,
/// Always show the scrollbar.
Always,
/// Never show the scrollbar.
Never,
}
#[derive(
Clone,
Copy,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
// todo() -> combine with CursorShape
pub enum CursorShapeContent {
/// Cursor is a block like `█`.
#[default]
Block,
/// Cursor is an underscore like `_`.
Underline,
/// Cursor is a vertical bar like `⎸`.
Bar,
/// Cursor is a hollow box like `▯`.
Hollow,
}
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum TerminalBlink {
/// Never blink the cursor, ignoring the terminal mode.
Off,
/// Default the cursor blink to off, but allow the terminal to
/// set blinking.
TerminalControlled,
/// Always blink the cursor, ignoring the terminal mode.
On,
}
#[derive(
Clone,
Copy,
Debug,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum AlternateScroll {
On,
Off,
}
// Toolbar related settings
#[with_fallible_options]
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
pub struct TerminalToolbarContent {
/// Whether to display the terminal title in breadcrumbs inside the terminal pane.
/// Only shown if the terminal title is not empty.
///
/// The shell running in the terminal needs to be configured to emit the title.
/// Example: `echo -e "\e]2;New Title\007";`
///
/// Default: true
pub breadcrumbs: Option<bool>,
}
#[derive(
Copy,
Clone,
Debug,
Default,
PartialEq,
Eq,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum TerminalBell {
/// Play an OS-specific alert sound.
#[default]
System,
/// Do not play any sound.
Off,
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
)]
#[serde(rename_all = "snake_case")]
pub enum CondaManager {
/// Automatically detect the conda manager
#[default]
Auto,
/// Use conda
Conda,
/// Use mamba
Mamba,
/// Use micromamba
Micromamba,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub enum VenvSettings {
#[default]
Off,
On {
/// Default directories to search for virtual environments, relative
/// to the current working directory. We recommend overriding this
/// in your project's settings, rather than globally.
activate_script: Option<ActivateScript>,
venv_name: Option<String>,
directories: Option<Vec<PathBuf>>,
/// Preferred Conda manager to use when activating Conda environments.
///
/// Default: auto
conda_manager: Option<CondaManager>,
},
}
#[with_fallible_options]
pub struct VenvSettingsContent<'a> {
pub activate_script: ActivateScript,
pub venv_name: &'a str,
pub directories: &'a [PathBuf],
pub conda_manager: CondaManager,
}
impl VenvSettings {
pub fn as_option(&self) -> Option<VenvSettingsContent<'_>> {
match self {
VenvSettings::Off => None,
VenvSettings::On {
activate_script,
venv_name,
directories,
conda_manager,
} => Some(VenvSettingsContent {
activate_script: activate_script.unwrap_or(ActivateScript::Default),
venv_name: venv_name.as_deref().unwrap_or(""),
directories: directories.as_deref().unwrap_or(&[]),
conda_manager: conda_manager.unwrap_or(CondaManager::Auto),
}),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
#[serde(untagged)]
pub enum PathHyperlinkRegex {
SingleLine(String),
MultiLine(Vec<String>),
}
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
PartialEq,
Eq,
strum::VariantArray,
strum::VariantNames,
)]
#[serde(rename_all = "snake_case")]
pub enum TerminalDockPosition {
Left,
Bottom,
Right,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub enum ActivateScript {
#[default]
Default,
Csh,
Fish,
Nushell,
PowerShell,
Pyenv,
}
#[cfg(test)]
mod test {
use serde_json::json;
use crate::{ProjectSettingsContent, Shell};
#[test]
#[ignore]
fn test_project_settings() {
let project_content =
json!({"terminal": {"shell": {"program": "/bin/project"}}, "option_as_meta": true});
let _user_content =
json!({"terminal": {"shell": {"program": "/bin/user"}}, "option_as_meta": false});
let project_settings =
serde_json::from_value::<ProjectSettingsContent>(project_content).unwrap();
assert_eq!(
project_settings.terminal.unwrap().shell,
Some(Shell::Program("/bin/project".to_owned()))
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
use gpui::WindowButtonLayout;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
/// The layout of window control buttons as represented by user settings.
///
/// Custom layout strings use the GNOME `button-layout` format (e.g.
/// `"close:minimize,maximize"`).
#[derive(
Clone,
PartialEq,
Debug,
Serialize,
Deserialize,
JsonSchema,
MergeFrom,
Default,
strum::EnumDiscriminants,
)]
#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
#[schemars(schema_with = "window_button_layout_schema")]
#[serde(from = "String", into = "String")]
pub enum WindowButtonLayoutContent {
/// Follow the system/desktop configuration.
#[default]
PlatformDefault,
/// Use Zed's built-in standard layout, regardless of system config.
Standard,
/// A raw GNOME-style layout string.
Custom(String),
}
impl WindowButtonLayoutContent {
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub fn into_layout(self) -> Option<WindowButtonLayout> {
use util::ResultExt;
match self {
Self::PlatformDefault => None,
Self::Standard => Some(WindowButtonLayout::linux_default()),
Self::Custom(layout) => WindowButtonLayout::parse(&layout).log_err(),
}
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
pub fn into_layout(self) -> Option<WindowButtonLayout> {
None
}
}
fn window_button_layout_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({
"anyOf": [
{ "enum": ["platform_default", "standard"] },
{ "type": "string" }
]
})
}
impl From<WindowButtonLayoutContent> for String {
fn from(value: WindowButtonLayoutContent) -> Self {
match value {
WindowButtonLayoutContent::PlatformDefault => "platform_default".to_string(),
WindowButtonLayoutContent::Standard => "standard".to_string(),
WindowButtonLayoutContent::Custom(s) => s,
}
}
}
impl From<String> for WindowButtonLayoutContent {
fn from(layout_string: String) -> Self {
match layout_string.as_str() {
"platform_default" => Self::PlatformDefault,
"standard" => Self::Standard,
_ => Self::Custom(layout_string),
}
}
}
#[with_fallible_options]
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
pub struct TitleBarSettingsContent {
/// Whether to show git status indicators on the branch icon in the title bar.
/// When enabled, the branch icon changes to reflect the current repository
/// status (e.g. modified, added, deleted, or conflict).
///
/// Default: false
pub show_branch_status_icon: Option<bool>,
/// Whether to show onboarding banners in the title bar.
///
/// Default: true
pub show_onboarding_banner: Option<bool>,
/// Whether to show user avatar in the title bar.
///
/// Default: true
pub show_user_picture: Option<bool>,
/// Whether to show the branch name button in the titlebar.
///
/// Default: true
pub show_branch_name: Option<bool>,
/// Whether to show the project host and name in the titlebar.
///
/// Default: true
pub show_project_items: Option<bool>,
/// Whether to show the sign in button in the title bar.
///
/// Default: true
pub show_sign_in: Option<bool>,
/// Whether to show the user menu button in the title bar.
///
/// Default: true
pub show_user_menu: Option<bool>,
/// Whether to show the menus in the title bar.
///
/// Default: false
pub show_menus: Option<bool>,
/// The layout of window control buttons in the title bar (Linux only).
///
/// This can be set to "platform_default" to follow the system configuration, or
/// "standard" to use Zed's built-in layout. For custom layouts, use a
/// GNOME-style layout string like "close:minimize,maximize".
///
/// Default: "platform_default"
pub button_layout: Option<WindowButtonLayoutContent>,
}

File diff suppressed because it is too large Load Diff