project.rs

  1use std::{path::PathBuf, sync::Arc};
  2
  3use collections::{BTreeMap, HashMap};
  4use schemars::JsonSchema;
  5use serde::{Deserialize, Serialize};
  6use settings_macros::{MergeFrom, with_fallible_options};
  7use util::serde::default_true;
  8
  9use crate::{
 10    AllLanguageSettingsContent, DelayMs, ExtendingVec, ProjectTerminalSettingsContent,
 11    SlashCommandSettings,
 12};
 13
 14#[with_fallible_options]
 15#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
 16pub struct LspSettingsMap(pub HashMap<Arc<str>, LspSettings>);
 17
 18impl IntoIterator for LspSettingsMap {
 19    type Item = (Arc<str>, LspSettings);
 20    type IntoIter = std::collections::hash_map::IntoIter<Arc<str>, LspSettings>;
 21
 22    fn into_iter(self) -> Self::IntoIter {
 23        self.0.into_iter()
 24    }
 25}
 26
 27#[with_fallible_options]
 28#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
 29pub struct ProjectSettingsContent {
 30    #[serde(flatten)]
 31    pub all_languages: AllLanguageSettingsContent,
 32
 33    #[serde(flatten)]
 34    pub worktree: WorktreeSettingsContent,
 35
 36    /// Configuration for language servers.
 37    ///
 38    /// The following settings can be overridden for specific language servers:
 39    /// - initialization_options
 40    ///
 41    /// To override settings for a language, add an entry for that language server's
 42    /// name to the lsp value.
 43    /// Default: null
 44    #[serde(default)]
 45    pub lsp: LspSettingsMap,
 46
 47    pub terminal: Option<ProjectTerminalSettingsContent>,
 48
 49    /// Configuration for Debugger-related features
 50    #[serde(default)]
 51    pub dap: HashMap<Arc<str>, DapSettingsContent>,
 52
 53    /// Settings for context servers used for AI-related features.
 54    #[serde(default)]
 55    pub context_servers: HashMap<Arc<str>, ContextServerSettingsContent>,
 56
 57    /// Default timeout in seconds for context server tool calls.
 58    /// Can be overridden per-server in context_servers configuration.
 59    ///
 60    /// Default: 60
 61    pub context_server_timeout: Option<u64>,
 62
 63    /// Configuration for how direnv configuration should be loaded
 64    pub load_direnv: Option<DirenvSettings>,
 65
 66    /// Settings for slash commands.
 67    pub slash_commands: Option<SlashCommandSettings>,
 68
 69    /// The list of custom Git hosting providers.
 70    pub git_hosting_providers: Option<ExtendingVec<GitHostingProviderConfig>>,
 71}
 72
 73#[with_fallible_options]
 74#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
 75pub struct WorktreeSettingsContent {
 76    /// The displayed name of this project. If not set or null, the root directory name
 77    /// will be displayed.
 78    ///
 79    /// Default: null
 80    pub project_name: Option<String>,
 81
 82    /// Whether to prevent this project from being shared in public channels.
 83    ///
 84    /// Default: false
 85    #[serde(default)]
 86    pub prevent_sharing_in_public_channels: bool,
 87
 88    /// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
 89    /// `file_scan_inclusions`.
 90    ///
 91    /// Default: [
 92    ///   "**/.git",
 93    ///   "**/.svn",
 94    ///   "**/.hg",
 95    ///   "**/.jj",
 96    ///   "**/CVS",
 97    ///   "**/.DS_Store",
 98    ///   "**/Thumbs.db",
 99    ///   "**/.classpath",
100    ///   "**/.settings"
101    /// ]
102    pub file_scan_exclusions: Option<Vec<String>>,
103
104    /// Always include files that match these globs when scanning for files, even if they're
105    /// ignored by git. This setting is overridden by `file_scan_exclusions`.
106    /// Default: [
107    ///  ".env*",
108    ///  "docker-compose.*.yml",
109    /// ]
110    pub file_scan_inclusions: Option<Vec<String>>,
111
112    /// Treat the files matching these globs as `.env` files.
113    /// Default: ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"]
114    pub private_files: Option<ExtendingVec<String>>,
115
116    /// Treat the files matching these globs as hidden files. You can hide hidden files in the project panel.
117    /// Default: ["**/.*"]
118    pub hidden_files: Option<Vec<String>>,
119
120    /// Treat the files matching these globs as read-only. These files can be opened and viewed,
121    /// but cannot be edited. This is useful for generated files, build outputs, or files from
122    /// external dependencies that should not be modified directly.
123    /// Default: []
124    pub read_only_files: Option<Vec<String>>,
125}
126
127#[with_fallible_options]
128#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash)]
129#[serde(rename_all = "snake_case")]
130pub struct LspSettings {
131    pub binary: Option<BinarySettings>,
132    /// Options passed to the language server at startup.
133    ///
134    /// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
135    ///
136    /// Consult the documentation for the specific language server to see which settings are supported.
137    pub initialization_options: Option<serde_json::Value>,
138    /// Language server settings.
139    ///
140    /// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration
141    ///
142    /// Consult the documentation for the specific language server to see which settings are supported.
143    pub settings: Option<serde_json::Value>,
144    /// If the server supports sending tasks over LSP extensions,
145    /// this setting can be used to enable or disable them in Zed.
146    /// Default: true
147    #[serde(default = "default_true")]
148    pub enable_lsp_tasks: bool,
149    pub fetch: Option<FetchSettings>,
150}
151
152impl Default for LspSettings {
153    fn default() -> Self {
154        Self {
155            binary: None,
156            initialization_options: None,
157            settings: None,
158            enable_lsp_tasks: true,
159            fetch: None,
160        }
161    }
162}
163
164#[with_fallible_options]
165#[derive(
166    Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash,
167)]
168pub struct BinarySettings {
169    pub path: Option<String>,
170    pub arguments: Option<Vec<String>>,
171    pub env: Option<BTreeMap<String, String>>,
172    pub ignore_system_version: Option<bool>,
173}
174
175#[with_fallible_options]
176#[derive(
177    Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash,
178)]
179pub struct FetchSettings {
180    // Whether to consider pre-releases for fetching
181    pub pre_release: Option<bool>,
182}
183
184/// Common language server settings.
185#[with_fallible_options]
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
187pub struct GlobalLspSettingsContent {
188    /// Whether to show the LSP servers button in the status bar.
189    ///
190    /// Default: `true`
191    pub button: Option<bool>,
192}
193
194#[with_fallible_options]
195#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
196#[serde(rename_all = "snake_case")]
197pub struct DapSettingsContent {
198    pub binary: Option<String>,
199    pub args: Option<Vec<String>>,
200    pub env: Option<HashMap<String, String>>,
201}
202
203#[with_fallible_options]
204#[derive(
205    Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom,
206)]
207pub struct SessionSettingsContent {
208    /// Whether or not to restore unsaved buffers on restart.
209    ///
210    /// If this is true, user won't be prompted whether to save/discard
211    /// dirty files when closing the application.
212    ///
213    /// Default: true
214    pub restore_unsaved_buffers: Option<bool>,
215    /// Whether or not to skip worktree trust checks.
216    /// When trusted, project settings are synchronized automatically,
217    /// language and MCP servers are downloaded and started automatically.
218    ///
219    /// Default: false
220    pub trust_all_worktrees: Option<bool>,
221}
222
223#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom, Debug)]
224#[serde(untagged, rename_all = "snake_case")]
225pub enum ContextServerSettingsContent {
226    Stdio {
227        /// Whether the context server is enabled.
228        #[serde(default = "default_true")]
229        enabled: bool,
230
231        #[serde(flatten)]
232        command: ContextServerCommand,
233    },
234    Http {
235        /// Whether the context server is enabled.
236        #[serde(default = "default_true")]
237        enabled: bool,
238        /// The URL of the remote context server.
239        url: String,
240        /// Optional headers to send.
241        #[serde(skip_serializing_if = "HashMap::is_empty", default)]
242        headers: HashMap<String, String>,
243        /// Timeout for tool calls in seconds. Defaults to global context_server_timeout if not specified.
244        timeout: Option<u64>,
245    },
246    Extension {
247        /// Whether the context server is enabled.
248        #[serde(default = "default_true")]
249        enabled: bool,
250        /// The settings for this context server specified by the extension.
251        ///
252        /// Consult the documentation for the context server to see what settings
253        /// are supported.
254        settings: serde_json::Value,
255    },
256}
257
258impl ContextServerSettingsContent {
259    pub fn set_enabled(&mut self, enabled: bool) {
260        match self {
261            ContextServerSettingsContent::Stdio {
262                enabled: custom_enabled,
263                ..
264            } => {
265                *custom_enabled = enabled;
266            }
267            ContextServerSettingsContent::Extension {
268                enabled: ext_enabled,
269                ..
270            } => *ext_enabled = enabled,
271            ContextServerSettingsContent::Http {
272                enabled: remote_enabled,
273                ..
274            } => *remote_enabled = enabled,
275        }
276    }
277}
278
279#[with_fallible_options]
280#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom)]
281pub struct ContextServerCommand {
282    #[serde(rename = "command")]
283    pub path: PathBuf,
284    pub args: Vec<String>,
285    pub env: Option<HashMap<String, String>>,
286    /// Timeout for tool calls in seconds. Defaults to 60 if not specified.
287    pub timeout: Option<u64>,
288}
289
290impl std::fmt::Debug for ContextServerCommand {
291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292        let filtered_env = self.env.as_ref().map(|env| {
293            env.iter()
294                .map(|(k, v)| {
295                    (
296                        k,
297                        if util::redact::should_redact(k) {
298                            "[REDACTED]"
299                        } else {
300                            v
301                        },
302                    )
303                })
304                .collect::<Vec<_>>()
305        });
306
307        f.debug_struct("ContextServerCommand")
308            .field("path", &self.path)
309            .field("args", &self.args)
310            .field("env", &filtered_env)
311            .finish()
312    }
313}
314
315#[with_fallible_options]
316#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
317pub struct GitSettings {
318    /// Whether or not to enable git integration.
319    ///
320    /// Default: true
321    #[serde(flatten)]
322    pub enabled: Option<GitEnabledSettings>,
323    /// Whether or not to show the git gutter.
324    ///
325    /// Default: tracked_files
326    pub git_gutter: Option<GitGutterSetting>,
327    /// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.
328    ///
329    /// Default: 0
330    pub gutter_debounce: Option<u64>,
331    /// Whether or not to show git blame data inline in
332    /// the currently focused line.
333    ///
334    /// Default: on
335    pub inline_blame: Option<InlineBlameSettings>,
336    /// Git blame settings.
337    pub blame: Option<BlameSettings>,
338    /// Which information to show in the branch picker.
339    ///
340    /// Default: on
341    pub branch_picker: Option<BranchPickerSettingsContent>,
342    /// How hunks are displayed visually in the editor.
343    ///
344    /// Default: staged_hollow
345    pub hunk_style: Option<GitHunkStyleSetting>,
346    /// How file paths are displayed in the git gutter.
347    ///
348    /// Default: file_name_first
349    pub path_style: Option<GitPathStyle>,
350}
351
352#[with_fallible_options]
353#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
354#[serde(rename_all = "snake_case")]
355pub struct GitEnabledSettings {
356    pub disable_git: Option<bool>,
357    pub enable_status: Option<bool>,
358    pub enable_diff: Option<bool>,
359}
360
361impl GitEnabledSettings {
362    pub fn is_git_status_enabled(&self) -> bool {
363        !self.disable_git.unwrap_or(false) && self.enable_status.unwrap_or(true)
364    }
365
366    pub fn is_git_diff_enabled(&self) -> bool {
367        !self.disable_git.unwrap_or(false) && self.enable_diff.unwrap_or(true)
368    }
369}
370
371#[derive(
372    Clone,
373    Copy,
374    Debug,
375    PartialEq,
376    Default,
377    Serialize,
378    Deserialize,
379    JsonSchema,
380    MergeFrom,
381    strum::VariantArray,
382    strum::VariantNames,
383)]
384#[serde(rename_all = "snake_case")]
385pub enum GitGutterSetting {
386    /// Show git gutter in tracked files.
387    #[default]
388    TrackedFiles,
389    /// Hide git gutter
390    Hide,
391}
392
393#[with_fallible_options]
394#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
395#[serde(rename_all = "snake_case")]
396pub struct InlineBlameSettings {
397    /// Whether or not to show git blame data inline in
398    /// the currently focused line.
399    ///
400    /// Default: true
401    pub enabled: Option<bool>,
402    /// Whether to only show the inline blame information
403    /// after a delay once the cursor stops moving.
404    ///
405    /// Default: 0
406    pub delay_ms: Option<DelayMs>,
407    /// The amount of padding between the end of the source line and the start
408    /// of the inline blame in units of columns.
409    ///
410    /// Default: 7
411    pub padding: Option<u32>,
412    /// The minimum column number to show the inline blame information at
413    ///
414    /// Default: 0
415    pub min_column: Option<u32>,
416    /// Whether to show commit summary as part of the inline blame.
417    ///
418    /// Default: false
419    pub show_commit_summary: Option<bool>,
420}
421
422#[with_fallible_options]
423#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
424#[serde(rename_all = "snake_case")]
425pub struct BlameSettings {
426    /// Whether to show the avatar of the author of the commit.
427    ///
428    /// Default: true
429    pub show_avatar: Option<bool>,
430}
431
432#[with_fallible_options]
433#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
434#[serde(rename_all = "snake_case")]
435pub struct BranchPickerSettingsContent {
436    /// Whether to show author name as part of the commit information.
437    ///
438    /// Default: false
439    pub show_author_name: Option<bool>,
440}
441
442#[derive(
443    Clone,
444    Copy,
445    PartialEq,
446    Debug,
447    Default,
448    Serialize,
449    Deserialize,
450    JsonSchema,
451    MergeFrom,
452    strum::VariantArray,
453    strum::VariantNames,
454)]
455#[serde(rename_all = "snake_case")]
456pub enum GitHunkStyleSetting {
457    /// Show unstaged hunks with a filled background and staged hunks hollow.
458    #[default]
459    StagedHollow,
460    /// Show unstaged hunks hollow and staged hunks with a filled background.
461    UnstagedHollow,
462}
463
464#[with_fallible_options]
465#[derive(
466    Copy,
467    Clone,
468    Debug,
469    PartialEq,
470    Default,
471    Serialize,
472    Deserialize,
473    JsonSchema,
474    MergeFrom,
475    strum::VariantArray,
476    strum::VariantNames,
477)]
478#[serde(rename_all = "snake_case")]
479pub enum GitPathStyle {
480    /// Show file name first, then path
481    #[default]
482    FileNameFirst,
483    /// Show full path first
484    FilePathFirst,
485}
486
487#[with_fallible_options]
488#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
489pub struct DiagnosticsSettingsContent {
490    /// Whether to show the project diagnostics button in the status bar.
491    pub button: Option<bool>,
492
493    /// Whether or not to include warning diagnostics.
494    pub include_warnings: Option<bool>,
495
496    /// Settings for using LSP pull diagnostics mechanism in Zed.
497    pub lsp_pull_diagnostics: Option<LspPullDiagnosticsSettingsContent>,
498
499    /// Settings for showing inline diagnostics.
500    pub inline: Option<InlineDiagnosticsSettingsContent>,
501}
502
503#[with_fallible_options]
504#[derive(
505    Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
506)]
507pub struct LspPullDiagnosticsSettingsContent {
508    /// Whether to pull for diagnostics or not.
509    ///
510    /// Default: true
511    pub enabled: Option<bool>,
512    /// Minimum time to wait before pulling diagnostics from the language server(s).
513    /// 0 turns the debounce off.
514    ///
515    /// Default: 50
516    pub debounce_ms: Option<DelayMs>,
517}
518
519#[with_fallible_options]
520#[derive(
521    Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Eq,
522)]
523pub struct InlineDiagnosticsSettingsContent {
524    /// Whether or not to show inline diagnostics
525    ///
526    /// Default: false
527    pub enabled: Option<bool>,
528    /// Whether to only show the inline diagnostics after a delay after the
529    /// last editor event.
530    ///
531    /// Default: 150
532    pub update_debounce_ms: Option<DelayMs>,
533    /// The amount of padding between the end of the source line and the start
534    /// of the inline diagnostic in units of columns.
535    ///
536    /// Default: 4
537    pub padding: Option<u32>,
538    /// The minimum column to display inline diagnostics. This setting can be
539    /// used to horizontally align inline diagnostics at some position. Lines
540    /// longer than this value will still push diagnostics further to the right.
541    ///
542    /// Default: 0
543    pub min_column: Option<u32>,
544
545    pub max_severity: Option<DiagnosticSeverityContent>,
546}
547
548#[with_fallible_options]
549#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
550pub struct NodeBinarySettings {
551    /// The path to the Node binary.
552    pub path: Option<String>,
553    /// The path to the npm binary Zed should use (defaults to `.path/../npm`).
554    pub npm_path: Option<String>,
555    /// If enabled, Zed will download its own copy of Node.
556    pub ignore_system_version: Option<bool>,
557}
558
559#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
560#[serde(rename_all = "snake_case")]
561pub enum DirenvSettings {
562    /// Load direnv configuration through a shell hook
563    ShellHook,
564    /// Load direnv configuration directly using `direnv export json`
565    #[default]
566    Direct,
567    /// Do not load direnv configuration
568    Disabled,
569}
570
571#[derive(
572    Clone,
573    Copy,
574    Debug,
575    Eq,
576    PartialEq,
577    Ord,
578    PartialOrd,
579    Serialize,
580    Deserialize,
581    JsonSchema,
582    MergeFrom,
583    strum::VariantArray,
584    strum::VariantNames,
585)]
586#[serde(rename_all = "snake_case")]
587pub enum DiagnosticSeverityContent {
588    // No diagnostics are shown.
589    Off,
590    Error,
591    Warning,
592    Info,
593    Hint,
594    All,
595}
596
597/// A custom Git hosting provider.
598#[with_fallible_options]
599#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
600pub struct GitHostingProviderConfig {
601    /// The type of the provider.
602    ///
603    /// Must be one of `github`, `gitlab`, `bitbucket`, `gitea`, `forgejo`, or `source_hut`.
604    pub provider: GitHostingProviderKind,
605
606    /// The base URL for the provider (e.g., "https://code.corp.big.com").
607    pub base_url: String,
608
609    /// The display name for the provider (e.g., "BigCorp GitHub").
610    pub name: String,
611}
612
613#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
614#[serde(rename_all = "snake_case")]
615pub enum GitHostingProviderKind {
616    Github,
617    Gitlab,
618    Bitbucket,
619    Gitea,
620    Forgejo,
621    SourceHut,
622}