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:
202
crates/settings_content/src/action.rs
Normal file
202
crates/settings_content/src/action.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
873
crates/settings_content/src/agent.rs
Normal file
873
crates/settings_content/src/agent.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
1152
crates/settings_content/src/editor.rs
Normal file
1152
crates/settings_content/src/editor.rs
Normal file
File diff suppressed because it is too large
Load Diff
45
crates/settings_content/src/extension.rs
Normal file
45
crates/settings_content/src/extension.rs
Normal 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,
|
||||
},
|
||||
}
|
||||
112
crates/settings_content/src/fallible_options.rs
Normal file
112
crates/settings_content/src/fallible_options.rs
Normal 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
1253
crates/settings_content/src/language.rs
Normal file
1253
crates/settings_content/src/language.rs
Normal file
File diff suppressed because it is too large
Load Diff
499
crates/settings_content/src/language_model.rs
Normal file
499
crates/settings_content/src/language_model.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
174
crates/settings_content/src/merge_from.rs
Normal file
174
crates/settings_content/src/merge_from.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
798
crates/settings_content/src/project.rs
Normal file
798
crates/settings_content/src/project.rs
Normal 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,
|
||||
}
|
||||
135
crates/settings_content/src/serde_helper.rs
Normal file
135
crates/settings_content/src/serde_helper.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
1331
crates/settings_content/src/settings_content.rs
Normal file
1331
crates/settings_content/src/settings_content.rs
Normal file
File diff suppressed because it is too large
Load Diff
547
crates/settings_content/src/terminal.rs
Normal file
547
crates/settings_content/src/terminal.rs
Normal 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()))
|
||||
);
|
||||
}
|
||||
}
|
||||
1461
crates/settings_content/src/theme.rs
Normal file
1461
crates/settings_content/src/theme.rs
Normal file
File diff suppressed because it is too large
Load Diff
126
crates/settings_content/src/title_bar.rs
Normal file
126
crates/settings_content/src/title_bar.rs
Normal 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>,
|
||||
}
|
||||
1029
crates/settings_content/src/workspace.rs
Normal file
1029
crates/settings_content/src/workspace.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user