VSCode settings import refactor (#40513)

A small follow-up to the settings refactor of a few weeks ago to move
all the VSCode settings imports
to one place.

This should make it easier to spot missing imports, and easier to test
the importer.

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin
2025-10-17 17:47:05 +00:00
committed by GitHub
parent 62858f6a5c
commit 1fbe1e3512
19 changed files with 774 additions and 924 deletions
+1 -11
View File
@@ -10,7 +10,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection,
NotifyWhenAgentWaiting, Settings, SettingsContent,
NotifyWhenAgentWaiting, Settings,
};
pub use crate::agent_profile::*;
@@ -185,14 +185,4 @@ impl Settings for AgentSettings {
message_editor_min_lines: agent.message_editor_min_lines.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
if let Some(b) = vscode
.read_value("chat.agent.enabled")
.and_then(|b| b.as_bool())
{
current.agent.get_or_insert_default().enabled = Some(b);
current.agent.get_or_insert_default().button = Some(b);
}
}
}
-25
View File
@@ -138,10 +138,6 @@ impl Settings for ProxySettings {
proxy: content.proxy.clone(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
vscode.string_setting("http.proxy", &mut current.proxy);
}
}
pub fn init_settings(cx: &mut App) {
@@ -525,27 +521,6 @@ impl settings::Settings for TelemetrySettings {
metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
let mut telemetry = settings::TelemetrySettingsContent::default();
vscode.enum_setting("telemetry.telemetryLevel", &mut telemetry.metrics, |s| {
Some(s == "all")
});
vscode.enum_setting(
"telemetry.telemetryLevel",
&mut telemetry.diagnostics,
|s| Some(matches!(s, "all" | "error" | "crash")),
);
// we could translate telemetry.telemetryLevel, but just because users didn't want
// to send microsoft telemetry doesn't mean they don't want to send it to zed. their
// all/error/crash/off correspond to combinations of our "diagnostics" and "metrics".
if let Some(diagnostics) = telemetry.diagnostics {
current.telemetry.get_or_insert_default().diagnostics = Some(diagnostics)
}
if let Some(metrics) = telemetry.metrics {
current.telemetry.get_or_insert_default().metrics = Some(metrics)
}
}
}
impl Client {
+1 -207
View File
@@ -1,16 +1,14 @@
use core::num;
use std::num::NonZeroU32;
use gpui::App;
use language::CursorShape;
use project::project_settings::DiagnosticSeverity;
use settings::Settings;
pub use settings::{
CurrentLineHighlight, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
GoToDefinitionFallback, HideMouseMode, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
VsCodeSettings,
};
use settings::{Settings, SettingsContent};
use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar};
/// Imports from the VSCode settings at
@@ -270,208 +268,4 @@ impl Settings for EditorSettings {
minimum_contrast_for_highlights: editor.minimum_contrast_for_highlights.unwrap().0,
}
}
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) {
vscode.enum_setting(
"editor.cursorBlinking",
&mut current.editor.cursor_blink,
|s| match s {
"blink" | "phase" | "expand" | "smooth" => Some(true),
"solid" => Some(false),
_ => None,
},
);
vscode.enum_setting(
"editor.cursorStyle",
&mut current.editor.cursor_shape,
|s| match s {
"block" => Some(settings::CursorShape::Block),
"block-outline" => Some(settings::CursorShape::Hollow),
"line" | "line-thin" => Some(settings::CursorShape::Bar),
"underline" | "underline-thin" => Some(settings::CursorShape::Underline),
_ => None,
},
);
vscode.enum_setting(
"editor.renderLineHighlight",
&mut current.editor.current_line_highlight,
|s| match s {
"gutter" => Some(CurrentLineHighlight::Gutter),
"line" => Some(CurrentLineHighlight::Line),
"all" => Some(CurrentLineHighlight::All),
_ => None,
},
);
vscode.bool_setting(
"editor.selectionHighlight",
&mut current.editor.selection_highlight,
);
vscode.bool_setting(
"editor.roundedSelection",
&mut current.editor.rounded_selection,
);
vscode.bool_setting(
"editor.hover.enabled",
&mut current.editor.hover_popover_enabled,
);
vscode.u64_setting(
"editor.hover.delay",
&mut current.editor.hover_popover_delay,
);
let mut gutter = settings::GutterContent::default();
vscode.enum_setting(
"editor.showFoldingControls",
&mut gutter.folds,
|s| match s {
"always" | "mouseover" => Some(true),
"never" => Some(false),
_ => None,
},
);
vscode.enum_setting(
"editor.lineNumbers",
&mut gutter.line_numbers,
|s| match s {
"on" | "relative" => Some(true),
"off" => Some(false),
_ => None,
},
);
if let Some(old_gutter) = current.editor.gutter.as_mut() {
if gutter.folds.is_some() {
old_gutter.folds = gutter.folds
}
if gutter.line_numbers.is_some() {
old_gutter.line_numbers = gutter.line_numbers
}
} else if gutter != settings::GutterContent::default() {
current.editor.gutter = Some(gutter)
}
if let Some(b) = vscode.read_bool("editor.scrollBeyondLastLine") {
current.editor.scroll_beyond_last_line = Some(if b {
ScrollBeyondLastLine::OnePage
} else {
ScrollBeyondLastLine::Off
})
}
let mut scrollbar_axes = settings::ScrollbarAxesContent::default();
vscode.enum_setting(
"editor.scrollbar.horizontal",
&mut scrollbar_axes.horizontal,
|s| match s {
"auto" | "visible" => Some(true),
"hidden" => Some(false),
_ => None,
},
);
vscode.enum_setting(
"editor.scrollbar.vertical",
&mut scrollbar_axes.horizontal,
|s| match s {
"auto" | "visible" => Some(true),
"hidden" => Some(false),
_ => None,
},
);
if scrollbar_axes != settings::ScrollbarAxesContent::default() {
let scrollbar_settings = current.editor.scrollbar.get_or_insert_default();
let axes_settings = scrollbar_settings.axes.get_or_insert_default();
if let Some(vertical) = scrollbar_axes.vertical {
axes_settings.vertical = Some(vertical);
}
if let Some(horizontal) = scrollbar_axes.horizontal {
axes_settings.horizontal = Some(horizontal);
}
}
// TODO: check if this does the int->float conversion?
vscode.f32_setting(
"editor.cursorSurroundingLines",
&mut current.editor.vertical_scroll_margin,
);
vscode.f32_setting(
"editor.mouseWheelScrollSensitivity",
&mut current.editor.scroll_sensitivity,
);
vscode.f32_setting(
"editor.fastScrollSensitivity",
&mut current.editor.fast_scroll_sensitivity,
);
if Some("relative") == vscode.read_string("editor.lineNumbers") {
current.editor.relative_line_numbers = Some(true);
}
vscode.enum_setting(
"editor.find.seedSearchStringFromSelection",
&mut current.editor.seed_search_query_from_cursor,
|s| match s {
"always" => Some(SeedQuerySetting::Always),
"selection" => Some(SeedQuerySetting::Selection),
"never" => Some(SeedQuerySetting::Never),
_ => None,
},
);
vscode.bool_setting("search.smartCase", &mut current.editor.use_smartcase_search);
vscode.enum_setting(
"editor.multiCursorModifier",
&mut current.editor.multi_cursor_modifier,
|s| match s {
"ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl),
"alt" => Some(MultiCursorModifier::Alt),
_ => None,
},
);
vscode.bool_setting(
"editor.parameterHints.enabled",
&mut current.editor.auto_signature_help,
);
vscode.bool_setting(
"editor.parameterHints.enabled",
&mut current.editor.show_signature_help_after_edits,
);
if let Some(use_ignored) = vscode.read_bool("search.useIgnoreFiles") {
let search = current.editor.search.get_or_insert_default();
search.include_ignored = Some(use_ignored);
}
let mut minimap = settings::MinimapContent::default();
let minimap_enabled = vscode.read_bool("editor.minimap.enabled").unwrap_or(true);
let autohide = vscode.read_bool("editor.minimap.autohide");
let mut max_width_columns: Option<u32> = None;
vscode.u32_setting("editor.minimap.maxColumn", &mut max_width_columns);
if minimap_enabled {
if let Some(false) = autohide {
minimap.show = Some(ShowMinimap::Always);
} else {
minimap.show = Some(ShowMinimap::Auto);
}
} else {
minimap.show = Some(ShowMinimap::Never);
}
if let Some(max_width_columns) = max_width_columns {
minimap.max_width_columns = NonZeroU32::new(max_width_columns);
}
vscode.enum_setting(
"editor.minimap.showSlider",
&mut minimap.thumb,
|s| match s {
"always" => Some(MinimapThumb::Always),
"mouseover" => Some(MinimapThumb::Hover),
_ => None,
},
);
if minimap != settings::MinimapContent::default() {
current.editor.minimap = Some(minimap)
}
}
}
+1 -13
View File
@@ -2,7 +2,7 @@ use editor::EditorSettings;
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsContent, StatusStyle};
use settings::{Settings, StatusStyle};
use ui::{
px,
scrollbars::{ScrollbarVisibility, ShowScrollbar},
@@ -58,16 +58,4 @@ impl Settings for GitPanelSettings {
collapse_untracked_diff: git_panel.collapse_untracked_diff.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
if let Some(git_enabled) = vscode.read_bool("git.enabled") {
current.git_panel.get_or_insert_default().button = Some(git_enabled);
}
if let Some(default_branch) = vscode.read_string("git.defaultBranchName") {
current
.git_panel
.get_or_insert_default()
.fallback_branch_name = Some(default_branch.to_string());
}
}
}
+1 -126
View File
@@ -15,7 +15,7 @@ pub use settings::{
Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode,
RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
};
use settings::{ExtendingVec, Settings, SettingsContent, SettingsLocation, SettingsStore};
use settings::{Settings, SettingsLocation, SettingsStore};
use shellexpand;
use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
@@ -679,131 +679,6 @@ impl settings::Settings for AllLanguageSettings {
file_types,
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
let d = &mut current.project.all_languages.defaults;
if let Some(size) = vscode
.read_value("editor.tabSize")
.and_then(|v| v.as_u64())
.and_then(|n| NonZeroU32::new(n as u32))
{
d.tab_size = Some(size);
}
if let Some(v) = vscode.read_bool("editor.insertSpaces") {
d.hard_tabs = Some(!v);
}
vscode.enum_setting("editor.wordWrap", &mut d.soft_wrap, |s| match s {
"on" => Some(SoftWrap::EditorWidth),
"wordWrapColumn" => Some(SoftWrap::PreferLine),
"bounded" => Some(SoftWrap::Bounded),
"off" => Some(SoftWrap::None),
_ => None,
});
vscode.u32_setting("editor.wordWrapColumn", &mut d.preferred_line_length);
if let Some(arr) = vscode
.read_value("editor.rulers")
.and_then(|v| v.as_array())
.map(|v| v.iter().map(|n| n.as_u64().map(|n| n as usize)).collect())
{
d.wrap_guides = arr;
}
if let Some(b) = vscode.read_bool("editor.guides.indentation") {
d.indent_guides.get_or_insert_default().enabled = Some(b);
}
if let Some(b) = vscode.read_bool("editor.guides.formatOnSave") {
d.format_on_save = Some(if b {
FormatOnSave::On
} else {
FormatOnSave::Off
});
}
vscode.bool_setting(
"editor.trimAutoWhitespace",
&mut d.remove_trailing_whitespace_on_save,
);
vscode.bool_setting(
"files.insertFinalNewline",
&mut d.ensure_final_newline_on_save,
);
vscode.bool_setting("editor.inlineSuggest.enabled", &mut d.show_edit_predictions);
vscode.enum_setting("editor.renderWhitespace", &mut d.show_whitespaces, |s| {
Some(match s {
"boundary" => ShowWhitespaceSetting::Boundary,
"trailing" => ShowWhitespaceSetting::Trailing,
"selection" => ShowWhitespaceSetting::Selection,
"all" => ShowWhitespaceSetting::All,
_ => ShowWhitespaceSetting::None,
})
});
vscode.enum_setting(
"editor.autoSurround",
&mut d.use_auto_surround,
|s| match s {
"languageDefined" | "quotes" | "brackets" => Some(true),
"never" => Some(false),
_ => None,
},
);
vscode.bool_setting("editor.formatOnType", &mut d.use_on_type_format);
vscode.bool_setting("editor.linkedEditing", &mut d.linked_edits);
vscode.bool_setting("editor.formatOnPaste", &mut d.auto_indent_on_paste);
vscode.bool_setting(
"editor.suggestOnTriggerCharacters",
&mut d.show_completions_on_input,
);
if let Some(b) = vscode.read_bool("editor.suggest.showWords") {
let mode = if b {
WordsCompletionMode::Enabled
} else {
WordsCompletionMode::Disabled
};
d.completions.get_or_insert_default().words = Some(mode);
}
// TODO: pull ^ out into helper and reuse for per-language settings
// vscodes file association map is inverted from ours, so we flip the mapping before merging
let mut associations: HashMap<Arc<str>, ExtendingVec<String>> = HashMap::default();
if let Some(map) = vscode
.read_value("files.associations")
.and_then(|v| v.as_object())
{
for (k, v) in map {
let Some(v) = v.as_str() else { continue };
associations.entry(v.into()).or_default().0.push(k.clone());
}
}
// TODO: do we want to merge imported globs per filetype? for now we'll just replace
current
.project
.all_languages
.file_types
.get_or_insert_default()
.extend(associations);
// cursor global ignore list applies to cursor-tab, so transfer it to edit_predictions.disabled_globs
if let Some(disabled_globs) = vscode
.read_value("cursor.general.globalCursorIgnoreList")
.and_then(|v| v.as_array())
{
current
.project
.all_languages
.edit_predictions
.get_or_insert_default()
.disabled_globs
.get_or_insert_default()
.extend(
disabled_globs
.iter()
.filter_map(|glob| glob.as_str())
.map(|s| s.to_string()),
);
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
@@ -62,20 +62,4 @@ impl Settings for OutlinePanelSettings {
expand_outlines_with_depth: panel.expand_outlines_with_depth.unwrap(),
}
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(b) = vscode.read_bool("outline.icons") {
let outline_panel = current.outline_panel.get_or_insert_default();
outline_panel.file_icons = Some(b);
outline_panel.folder_icons = Some(b);
}
if let Some(b) = vscode.read_bool("git.decorations.enabled") {
let outline_panel = current.outline_panel.get_or_insert_default();
outline_panel.git_status = Some(b);
}
}
}
-6
View File
@@ -985,12 +985,6 @@ impl settings::Settings for DisableAiSettings {
disable_ai: content.disable_ai.unwrap().0,
}
}
fn import_from_vscode(
_vscode: &settings::VsCodeSettings,
_current: &mut settings::SettingsContent,
) {
}
}
impl Project {
-59
View File
@@ -522,65 +522,6 @@ impl Settings for ProjectSettings {
},
}
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
// this just sets the binary name instead of a full path so it relies on path lookup
// resolving to the one you want
let npm_path = vscode.read_enum("npm.packageManager", |s| match s {
v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()),
_ => None,
});
if npm_path.is_some() {
current.node.get_or_insert_default().npm_path = npm_path;
}
if let Some(b) = vscode.read_bool("git.blame.editorDecoration.enabled") {
current
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.enabled = Some(b);
}
#[derive(Deserialize)]
struct VsCodeContextServerCommand {
command: PathBuf,
args: Option<Vec<String>>,
env: Option<HashMap<String, String>>,
// note: we don't support envFile and type
}
if let Some(mcp) = vscode.read_value("mcp").and_then(|v| v.as_object()) {
current
.project
.context_servers
.extend(mcp.iter().filter_map(|(k, v)| {
Some((
k.clone().into(),
settings::ContextServerSettingsContent::Custom {
enabled: true,
command: serde_json::from_value::<VsCodeContextServerCommand>(
v.clone(),
)
.ok()
.map(|cmd| {
settings::ContextServerCommand {
path: cmd.command,
args: cmd.args.unwrap_or_default(),
env: cmd.env,
timeout: None,
}
})?,
},
))
}));
}
// TODO: translate lsp settings for rust-analyzer and other popular ones to old.lsp
}
}
pub enum SettingsObserverMode {
@@ -2,10 +2,7 @@ use editor::EditorSettings;
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
DockSide, ProjectPanelEntrySpacing, Settings, SettingsContent, ShowDiagnostics,
ShowIndentGuides,
};
use settings::{DockSide, ProjectPanelEntrySpacing, Settings, ShowDiagnostics, ShowIndentGuides};
use ui::{
px,
scrollbars::{ScrollbarVisibility, ShowScrollbar},
@@ -86,39 +83,4 @@ impl Settings for ProjectPanelSettings {
open_file_on_paste: project_panel.open_file_on_paste.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
if let Some(hide_gitignore) = vscode.read_bool("explorer.excludeGitIgnore") {
current.project_panel.get_or_insert_default().hide_gitignore = Some(hide_gitignore);
}
if let Some(auto_reveal) = vscode.read_bool("explorer.autoReveal") {
current
.project_panel
.get_or_insert_default()
.auto_reveal_entries = Some(auto_reveal);
}
if let Some(compact_folders) = vscode.read_bool("explorer.compactFolders") {
current.project_panel.get_or_insert_default().auto_fold_dirs = Some(compact_folders);
}
if Some(false) == vscode.read_bool("git.decorations.enabled") {
current.project_panel.get_or_insert_default().git_status = Some(false);
}
if Some(false) == vscode.read_bool("problems.decorations.enabled") {
current
.project_panel
.get_or_insert_default()
.show_diagnostics = Some(ShowDiagnostics::Off);
}
if let (Some(false), Some(false)) = (
vscode.read_bool("explorer.decorations.badges"),
vscode.read_bool("explorer.decorations.colors"),
) {
current.project_panel.get_or_insert_default().git_status = Some(false);
current
.project_panel
.get_or_insert_default()
.show_diagnostics = Some(ShowDiagnostics::Off);
}
}
}
+2 -9
View File
@@ -1,12 +1,9 @@
use std::fmt::{Display, Formatter};
use crate::{
self as settings,
settings_content::{BaseKeymapContent, SettingsContent},
};
use crate::{self as settings, settings_content::BaseKeymapContent};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, VsCodeSettings};
use settings::Settings;
/// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
///
@@ -133,8 +130,4 @@ impl Settings for BaseKeymap {
fn from_settings(s: &crate::settings_content::SettingsContent) -> Self {
s.base_keymap.unwrap().into()
}
fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut SettingsContent) {
current.base_keymap = Some(BaseKeymapContent::VSCode);
}
}
+14 -57
View File
@@ -70,10 +70,6 @@ pub trait Settings: 'static + Send + Sync + Sized {
/// and you should add a default to default.json for documentation.
fn from_settings(content: &SettingsContent) -> Self;
/// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known
/// equivalent settings from a vscode config to our config
fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut SettingsContent) {}
#[track_caller]
fn register(cx: &mut App)
where
@@ -208,11 +204,6 @@ trait AnySettingValue: 'static + Send + Sync {
fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)>;
fn set_global_value(&mut self, value: Box<dyn Any>);
fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>);
fn import_from_vscode(
&self,
vscode_settings: &VsCodeSettings,
settings_content: &mut SettingsContent,
);
}
impl SettingsStore {
@@ -614,10 +605,8 @@ impl SettingsStore {
}
pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String {
self.new_text_for_update(old_text, |settings_content| {
for v in self.setting_values.values() {
v.import_from_vscode(vscode, settings_content)
}
self.new_text_for_update(old_text, |content| {
content.merge_from(&vscode.settings_content())
})
}
@@ -1129,14 +1118,6 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
}
}
fn import_from_vscode(
&self,
vscode_settings: &VsCodeSettings,
settings_content: &mut SettingsContent,
) {
T::import_from_vscode(vscode_settings, settings_content);
}
}
#[cfg(test)]
@@ -1179,19 +1160,6 @@ mod tests {
git_status: content.git_status.unwrap(),
}
}
fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
let mut show = None;
vscode.bool_setting("workbench.editor.decorations.colors", &mut show);
if let Some(show) = show {
content
.tabs
.get_or_insert_default()
.git_status
.replace(show);
}
}
}
#[derive(Debug, PartialEq)]
@@ -1208,18 +1176,6 @@ mod tests {
preferred_line_length: content.preferred_line_length.unwrap(),
}
}
fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
let content = &mut content.project.all_languages.defaults;
if let Some(size) = vscode
.read_value("editor.tabSize")
.and_then(|v| v.as_u64())
.and_then(|n| NonZeroU32::new(n as u32))
{
content.tab_size = Some(size);
}
}
}
#[derive(Debug, PartialEq)]
@@ -1236,16 +1192,6 @@ mod tests {
buffer_font_fallbacks: content.buffer_font_fallbacks.unwrap(),
}
}
fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) {
let content = &mut content.theme;
vscode.font_family_setting(
"editor.fontFamily",
&mut content.buffer_font_family,
&mut content.buffer_font_fallbacks,
);
}
}
#[gpui::test]
@@ -1581,6 +1527,7 @@ mod tests {
.unindent(),
r#" { "editor.tabSize": 37 } "#.to_owned(),
r#"{
"base_keymap": "VSCode",
"tab_size": 37
}
"#
@@ -1598,6 +1545,7 @@ mod tests {
.unindent(),
r#"{ "editor.tabSize": 42 }"#.to_owned(),
r#"{
"base_keymap": "VSCode",
"tab_size": 42,
"preferred_line_length": 99,
}
@@ -1617,6 +1565,7 @@ mod tests {
.unindent(),
r#"{}"#.to_owned(),
r#"{
"base_keymap": "VSCode",
"preferred_line_length": 99,
"tab_size": 42
}
@@ -1632,8 +1581,15 @@ mod tests {
}
"#
.unindent(),
r#"{ "workbench.editor.decorations.colors": true }"#.to_owned(),
r#"{ "git.decorations.enabled": true }"#.to_owned(),
r#"{
"project_panel": {
"git_status": true
},
"outline_panel": {
"git_status": true
},
"base_keymap": "VSCode",
"tabs": {
"git_status": true
}
@@ -1652,6 +1608,7 @@ mod tests {
.unindent(),
r#"{ "editor.fontFamily": "Cascadia Code, 'Consolas', Courier New" }"#.to_owned(),
r#"{
"base_keymap": "VSCode",
"buffer_font_fallbacks": [
"Consolas",
"Courier New"
+750 -64
View File
@@ -1,10 +1,15 @@
use crate::*;
use anyhow::{Context as _, Result, anyhow};
use collections::HashMap;
use fs::Fs;
use paths::{cursor_settings_file_paths, vscode_settings_file_paths};
use serde::Deserialize;
use serde_json::{Map, Value};
use std::{path::Path, sync::Arc};
use crate::FontFamilyName;
use std::{
num::{NonZeroU32, NonZeroUsize},
path::{Path, PathBuf},
sync::Arc,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum VsCodeSettingsSource {
@@ -79,83 +84,53 @@ impl VsCodeSettings {
})
}
pub fn read_value(&self, setting: &str) -> Option<&Value> {
fn read_value(&self, setting: &str) -> Option<&Value> {
self.content.get(setting)
}
pub fn read_string(&self, setting: &str) -> Option<&str> {
fn read_str(&self, setting: &str) -> Option<&str> {
self.read_value(setting).and_then(|v| v.as_str())
}
pub fn read_bool(&self, setting: &str) -> Option<bool> {
fn read_string(&self, setting: &str) -> Option<String> {
self.read_value(setting)
.and_then(|v| v.as_str())
.map(|s| s.to_owned())
}
fn read_bool(&self, setting: &str) -> Option<bool> {
self.read_value(setting).and_then(|v| v.as_bool())
}
pub fn string_setting(&self, key: &str, setting: &mut Option<String>) {
if let Some(s) = self.content.get(key).and_then(Value::as_str) {
*setting = Some(s.to_owned())
}
fn read_f32(&self, setting: &str) -> Option<f32> {
self.read_value(setting)
.and_then(|v| v.as_f64())
.map(|v| v as f32)
}
pub fn bool_setting(&self, key: &str, setting: &mut Option<bool>) {
if let Some(s) = self.content.get(key).and_then(Value::as_bool) {
*setting = Some(s)
}
fn read_u64(&self, setting: &str) -> Option<u64> {
self.read_value(setting).and_then(|v| v.as_u64())
}
pub fn u32_setting(&self, key: &str, setting: &mut Option<u32>) {
if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
*setting = Some(s as u32)
}
fn read_usize(&self, setting: &str) -> Option<usize> {
self.read_value(setting)
.and_then(|v| v.as_u64())
.and_then(|v| v.try_into().ok())
}
pub fn u64_setting(&self, key: &str, setting: &mut Option<u64>) {
if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
*setting = Some(s)
}
fn read_u32(&self, setting: &str) -> Option<u32> {
self.read_value(setting)
.and_then(|v| v.as_u64())
.and_then(|v| v.try_into().ok())
}
pub fn usize_setting(&self, key: &str, setting: &mut Option<usize>) {
if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
*setting = Some(s.try_into().unwrap())
}
}
pub fn f32_setting(&self, key: &str, setting: &mut Option<f32>) {
if let Some(s) = self.content.get(key).and_then(Value::as_f64) {
*setting = Some(s as f32)
}
}
pub fn from_f32_setting<T: From<f32>>(&self, key: &str, setting: &mut Option<T>) {
if let Some(s) = self.content.get(key).and_then(Value::as_f64) {
*setting = Some(T::from(s as f32))
}
}
pub fn enum_setting<T>(
&self,
key: &str,
setting: &mut Option<T>,
f: impl FnOnce(&str) -> Option<T>,
) {
if let Some(s) = self.content.get(key).and_then(Value::as_str).and_then(f) {
*setting = Some(s)
}
}
pub fn read_enum<T>(&self, key: &str, f: impl FnOnce(&str) -> Option<T>) -> Option<T> {
fn read_enum<T>(&self, key: &str, f: impl FnOnce(&str) -> Option<T>) -> Option<T> {
self.content.get(key).and_then(Value::as_str).and_then(f)
}
pub fn font_family_setting(
&self,
key: &str,
font_family: &mut Option<FontFamilyName>,
font_fallbacks: &mut Option<Vec<FontFamilyName>>,
) {
fn read_fonts(&self, key: &str) -> (Option<FontFamilyName>, Option<Vec<FontFamilyName>>) {
let Some(css_name) = self.content.get(key).and_then(Value::as_str) else {
return;
return (None, None);
};
let mut name_buffer = String::new();
@@ -188,12 +163,723 @@ impl VsCodeSettings {
}
add_font(&mut name_buffer);
if fonts.is_empty() {
return (None, None);
}
(Some(fonts.remove(0)), skip_default(fonts))
}
let mut iter = fonts.into_iter();
*font_family = iter.next();
let fallbacks: Vec<_> = iter.collect();
if !fallbacks.is_empty() {
*font_fallbacks = Some(fallbacks);
pub fn settings_content(&self) -> SettingsContent {
SettingsContent {
agent: self.agent_settings_content(),
agent_servers: None,
audio: None,
auto_update: None,
base_keymap: Some(BaseKeymapContent::VSCode),
calls: None,
collaboration_panel: None,
debugger: None,
diagnostics: None,
disable_ai: None,
editor: self.editor_settings_content(),
extension: ExtensionSettingsContent::default(),
file_finder: None,
git: self.git_settings_content(),
git_panel: self.git_panel_settings_content(),
global_lsp_settings: None,
helix_mode: None,
image_viewer: None,
journal: None,
language_models: None,
line_indicator_format: None,
log: None,
message_editor: None,
node: self.node_binary_settings(),
notification_panel: None,
outline_panel: self.outline_panel_settings_content(),
preview_tabs: self.preview_tabs_settings_content(),
project: self.project_settings_content(),
project_panel: self.project_panel_settings_content(),
proxy: self.read_string("http.proxy"),
remote: RemoteSettingsContent::default(),
repl: None,
server_url: None,
session: None,
status_bar: self.status_bar_settings_content(),
tab_bar: self.tab_bar_settings_content(),
tabs: self.item_settings_content(),
telemetry: self.telemetry_settings_content(),
terminal: self.terminal_settings_content(),
theme: Box::new(self.theme_settings_content()),
title_bar: None,
vim: None,
vim_mode: None,
workspace: self.workspace_settings_content(),
}
}
fn agent_settings_content(&self) -> Option<AgentSettingsContent> {
let enabled = self.read_bool("chat.agent.enabled");
skip_default(AgentSettingsContent {
enabled: enabled,
button: enabled,
..Default::default()
})
}
fn editor_settings_content(&self) -> EditorSettingsContent {
EditorSettingsContent {
auto_signature_help: self.read_bool("editor.parameterHints.enabled"),
autoscroll_on_clicks: None,
cursor_blink: self.read_enum("editor.cursorBlinking", |s| match s {
"blink" | "phase" | "expand" | "smooth" => Some(true),
"solid" => Some(false),
_ => None,
}),
cursor_shape: self.read_enum("editor.cursorStyle", |s| match s {
"block" => Some(CursorShape::Block),
"block-outline" => Some(CursorShape::Hollow),
"line" | "line-thin" => Some(CursorShape::Bar),
"underline" | "underline-thin" => Some(CursorShape::Underline),
_ => None,
}),
current_line_highlight: self.read_enum("editor.renderLineHighlight", |s| match s {
"gutter" => Some(CurrentLineHighlight::Gutter),
"line" => Some(CurrentLineHighlight::Line),
"all" => Some(CurrentLineHighlight::All),
_ => None,
}),
diagnostics_max_severity: None,
double_click_in_multibuffer: None,
drag_and_drop_selection: None,
excerpt_context_lines: None,
expand_excerpt_lines: None,
fast_scroll_sensitivity: self.read_f32("editor.fastScrollSensitivity"),
go_to_definition_fallback: None,
gutter: self.gutter_content(),
hide_mouse: None,
horizontal_scroll_margin: None,
hover_popover_delay: self.read_u64("editor.hover.delay"),
hover_popover_enabled: self.read_bool("editor.hover.enabled"),
inline_code_actions: None,
jupyter: None,
lsp_document_colors: None,
lsp_highlight_debounce: None,
middle_click_paste: None,
minimap: self.minimap_content(),
minimum_contrast_for_highlights: None,
multi_cursor_modifier: self.read_enum("editor.multiCursorModifier", |s| match s {
"ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl),
"alt" => Some(MultiCursorModifier::Alt),
_ => None,
}),
redact_private_values: None,
relative_line_numbers: self.read_enum("editor.lineNumbers", |s| match s {
"relative" => Some(true),
_ => None,
}),
rounded_selection: self.read_bool("editor.roundedSelection"),
scroll_beyond_last_line: None,
scroll_sensitivity: self.read_f32("editor.mouseWheelScrollSensitivity"),
scrollbar: self.scrollbar_content(),
search: self.search_content(),
search_wrap: None,
seed_search_query_from_cursor: self.read_enum(
"editor.find.seedSearchStringFromSelection",
|s| match s {
"always" => Some(SeedQuerySetting::Always),
"selection" => Some(SeedQuerySetting::Selection),
"never" => Some(SeedQuerySetting::Never),
_ => None,
},
),
selection_highlight: self.read_bool("editor.selectionHighlight"),
show_signature_help_after_edits: self.read_bool("editor.parameterHints.enabled"),
snippet_sort_order: None,
toolbar: None,
use_smartcase_search: self.read_bool("search.smartCase"),
vertical_scroll_margin: self.read_f32("editor.cursorSurroundingLines"),
}
}
fn gutter_content(&self) -> Option<GutterContent> {
skip_default(GutterContent {
line_numbers: self.read_enum("editor.lineNumbers", |s| match s {
"on" | "relative" => Some(true),
"off" => Some(false),
_ => None,
}),
min_line_number_digits: None,
runnables: None,
breakpoints: None,
folds: self.read_enum("editor.showFoldingControls", |s| match s {
"always" | "mouseover" => Some(true),
"never" => Some(false),
_ => None,
}),
})
}
fn scrollbar_content(&self) -> Option<ScrollbarContent> {
let scrollbar_axes = skip_default(ScrollbarAxesContent {
horizontal: self.read_enum("editor.scrollbar.horizontal", |s| match s {
"auto" | "visible" => Some(true),
"hidden" => Some(false),
_ => None,
}),
vertical: self.read_enum("editor.scrollbar.vertical", |s| match s {
"auto" | "visible" => Some(true),
"hidden" => Some(false),
_ => None,
}),
})?;
Some(ScrollbarContent {
axes: Some(scrollbar_axes),
..Default::default()
})
}
fn search_content(&self) -> Option<SearchSettingsContent> {
skip_default(SearchSettingsContent {
include_ignored: self.read_bool("search.useIgnoreFiles"),
..Default::default()
})
}
fn minimap_content(&self) -> Option<MinimapContent> {
let minimap_enabled = self.read_bool("editor.minimap.enabled");
let autohide = self.read_bool("editor.minimap.autohide");
let show = match (minimap_enabled, autohide) {
(Some(true), Some(false)) => Some(ShowMinimap::Always),
(Some(true), _) => Some(ShowMinimap::Auto),
(Some(false), _) => Some(ShowMinimap::Never),
_ => None,
};
skip_default(MinimapContent {
show,
thumb: self.read_enum("editor.minimap.showSlider", |s| match s {
"always" => Some(MinimapThumb::Always),
"mouseover" => Some(MinimapThumb::Hover),
_ => None,
}),
max_width_columns: self
.read_u32("editor.minimap.maxColumn")
.and_then(|v| NonZeroU32::new(v)),
..Default::default()
})
}
fn git_panel_settings_content(&self) -> Option<GitPanelSettingsContent> {
skip_default(GitPanelSettingsContent {
button: self.read_bool("git.enabled"),
fallback_branch_name: self.read_string("git.defaultBranchName"),
..Default::default()
})
}
fn project_settings_content(&self) -> ProjectSettingsContent {
ProjectSettingsContent {
all_languages: AllLanguageSettingsContent {
features: None,
edit_predictions: self.edit_predictions_settings_content(),
defaults: self.default_language_settings_content(),
languages: Default::default(),
file_types: self.file_types(),
},
worktree: self.worktree_settings_content(),
lsp: Default::default(),
terminal: None,
dap: Default::default(),
context_servers: self.context_servers(),
load_direnv: None,
slash_commands: None,
git_hosting_providers: None,
}
}
fn default_language_settings_content(&self) -> LanguageSettingsContent {
LanguageSettingsContent {
allow_rewrap: None,
always_treat_brackets_as_autoclosed: None,
auto_indent: None,
auto_indent_on_paste: self.read_bool("editor.formatOnPaste"),
code_actions_on_format: None,
completions: skip_default(CompletionSettingsContent {
words: self.read_bool("editor.suggest.showWords").map(|b| {
if b {
WordsCompletionMode::Enabled
} else {
WordsCompletionMode::Disabled
}
}),
..Default::default()
}),
debuggers: None,
edit_predictions_disabled_in: None,
enable_language_server: None,
ensure_final_newline_on_save: self.read_bool("files.insertFinalNewline"),
extend_comment_on_newline: None,
format_on_save: self.read_bool("editor.guides.formatOnSave").map(|b| {
if b {
FormatOnSave::On
} else {
FormatOnSave::Off
}
}),
formatter: None,
hard_tabs: self.read_bool("editor.insertSpaces").map(|v| !v),
indent_guides: skip_default(IndentGuideSettingsContent {
enabled: self.read_bool("editor.guides.indentation"),
..Default::default()
}),
inlay_hints: None,
jsx_tag_auto_close: None,
language_servers: None,
linked_edits: self.read_bool("editor.linkedEditing"),
preferred_line_length: self.read_u32("editor.wordWrapColumn"),
prettier: None,
remove_trailing_whitespace_on_save: self.read_bool("editor.trimAutoWhitespace"),
show_completion_documentation: None,
show_completions_on_input: self.read_bool("editor.suggestOnTriggerCharacters"),
show_edit_predictions: self.read_bool("editor.inlineSuggest.enabled"),
show_whitespaces: self.read_enum("editor.renderWhitespace", |s| {
Some(match s {
"boundary" => ShowWhitespaceSetting::Boundary,
"trailing" => ShowWhitespaceSetting::Trailing,
"selection" => ShowWhitespaceSetting::Selection,
"all" => ShowWhitespaceSetting::All,
_ => ShowWhitespaceSetting::None,
})
}),
show_wrap_guides: None,
soft_wrap: self.read_enum("editor.wordWrap", |s| match s {
"on" => Some(SoftWrap::EditorWidth),
"wordWrapColumn" => Some(SoftWrap::PreferLine),
"bounded" => Some(SoftWrap::Bounded),
"off" => Some(SoftWrap::None),
_ => None,
}),
tab_size: self
.read_u32("editor.tabSize")
.and_then(|n| NonZeroU32::new(n)),
tasks: None,
use_auto_surround: self.read_enum("editor.autoSurround", |s| match s {
"languageDefined" | "quotes" | "brackets" => Some(true),
"never" => Some(false),
_ => None,
}),
use_autoclose: None,
use_on_type_format: self.read_bool("editor.formatOnType"),
whitespace_map: None,
wrap_guides: self
.read_value("editor.rulers")
.and_then(|v| v.as_array())
.map(|v| {
v.iter()
.flat_map(|n| n.as_u64().map(|n| n as usize))
.collect()
}),
}
}
fn file_types(&self) -> Option<HashMap<Arc<str>, ExtendingVec<String>>> {
// vscodes file association map is inverted from ours, so we flip the mapping before merging
let mut associations: HashMap<Arc<str>, ExtendingVec<String>> = HashMap::default();
let map = self.read_value("files.associations")?.as_object()?;
for (k, v) in map {
let Some(v) = v.as_str() else { continue };
associations.entry(v.into()).or_default().0.push(k.clone());
}
skip_default(associations)
}
fn edit_predictions_settings_content(&self) -> Option<EditPredictionSettingsContent> {
let disabled_globs = self
.read_value("cursor.general.globalCursorIgnoreList")?
.as_array()?;
skip_default(EditPredictionSettingsContent {
disabled_globs: skip_default(
disabled_globs
.iter()
.filter_map(|glob| glob.as_str())
.map(|s| s.to_string())
.collect(),
),
..Default::default()
})
}
fn outline_panel_settings_content(&self) -> Option<OutlinePanelSettingsContent> {
skip_default(OutlinePanelSettingsContent {
file_icons: self.read_bool("outline.icons"),
folder_icons: self.read_bool("outline.icons"),
git_status: self.read_bool("git.decorations.enabled"),
..Default::default()
})
}
fn node_binary_settings(&self) -> Option<NodeBinarySettings> {
// this just sets the binary name instead of a full path so it relies on path lookup
// resolving to the one you want
skip_default(NodeBinarySettings {
npm_path: self.read_enum("npm.packageManager", |s| match s {
v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()),
_ => None,
}),
..Default::default()
})
}
fn git_settings_content(&self) -> Option<GitSettings> {
let inline_blame = self.read_bool("git.blame.editorDecoration.enabled")?;
skip_default(GitSettings {
inline_blame: Some(InlineBlameSettings {
enabled: Some(inline_blame),
..Default::default()
}),
..Default::default()
})
}
fn context_servers(&self) -> HashMap<Arc<str>, ContextServerSettingsContent> {
#[derive(Deserialize)]
struct VsCodeContextServerCommand {
command: PathBuf,
args: Option<Vec<String>>,
env: Option<HashMap<String, String>>,
// note: we don't support envFile and type
}
let Some(mcp) = self.read_value("mcp").and_then(|v| v.as_object()) else {
return Default::default();
};
mcp.iter()
.filter_map(|(k, v)| {
Some((
k.clone().into(),
ContextServerSettingsContent::Custom {
enabled: true,
command: serde_json::from_value::<VsCodeContextServerCommand>(v.clone())
.ok()
.map(|cmd| ContextServerCommand {
path: cmd.command,
args: cmd.args.unwrap_or_default(),
env: cmd.env,
timeout: None,
})?,
},
))
})
.collect()
}
fn item_settings_content(&self) -> Option<ItemSettingsContent> {
skip_default(ItemSettingsContent {
git_status: self.read_bool("git.decorations.enabled"),
close_position: self.read_enum("workbench.editor.tabActionLocation", |s| match s {
"right" => Some(ClosePosition::Right),
"left" => Some(ClosePosition::Left),
_ => None,
}),
file_icons: self.read_bool("workbench.editor.showIcons"),
activate_on_close: self
.read_bool("workbench.editor.focusRecentEditorAfterClose")
.map(|b| {
if b {
ActivateOnClose::History
} else {
ActivateOnClose::LeftNeighbour
}
}),
show_diagnostics: None,
show_close_button: self
.read_bool("workbench.editor.tabActionCloseVisibility")
.map(|b| {
if b {
ShowCloseButton::Always
} else {
ShowCloseButton::Hidden
}
}),
})
}
fn preview_tabs_settings_content(&self) -> Option<PreviewTabsSettingsContent> {
skip_default(PreviewTabsSettingsContent {
enabled: self.read_bool("workbench.editor.enablePreview"),
enable_preview_from_file_finder: self
.read_bool("workbench.editor.enablePreviewFromQuickOpen"),
enable_preview_from_code_navigation: self
.read_bool("workbench.editor.enablePreviewFromCodeNavigation"),
})
}
fn tab_bar_settings_content(&self) -> Option<TabBarSettingsContent> {
skip_default(TabBarSettingsContent {
show: self.read_enum("workbench.editor.showTabs", |s| match s {
"multiple" => Some(true),
"single" | "none" => Some(false),
_ => None,
}),
show_nav_history_buttons: None,
show_tab_bar_buttons: self
.read_str("workbench.editor.editorActionsLocation")
.and_then(|str| if str == "hidden" { Some(false) } else { None }),
})
}
fn status_bar_settings_content(&self) -> Option<StatusBarSettingsContent> {
skip_default(StatusBarSettingsContent {
show: self.read_bool("workbench.statusBar.visible"),
active_language_button: None,
cursor_position_button: None,
})
}
fn project_panel_settings_content(&self) -> Option<ProjectPanelSettingsContent> {
let mut project_panel_settings = ProjectPanelSettingsContent {
auto_fold_dirs: self.read_bool("explorer.compactFolders"),
auto_reveal_entries: self.read_bool("explorer.autoReveal"),
button: None,
default_width: None,
dock: None,
drag_and_drop: None,
entry_spacing: None,
file_icons: None,
folder_icons: None,
git_status: self.read_bool("git.decorations.enabled"),
hide_gitignore: self.read_bool("explorer.excludeGitIgnore"),
hide_hidden: None,
hide_root: None,
indent_guides: None,
indent_size: None,
open_file_on_paste: None,
scrollbar: None,
show_diagnostics: self
.read_bool("problems.decorations.enabled")
.and_then(|b| if b { Some(ShowDiagnostics::Off) } else { None }),
starts_open: None,
sticky_scroll: None,
};
if let (Some(false), Some(false)) = (
self.read_bool("explorer.decorations.badges"),
self.read_bool("explorer.decorations.colors"),
) {
project_panel_settings.git_status = Some(false);
project_panel_settings.show_diagnostics = Some(ShowDiagnostics::Off);
}
skip_default(project_panel_settings)
}
fn telemetry_settings_content(&self) -> Option<TelemetrySettingsContent> {
self.read_enum("telemetry.telemetryLevel", |level| {
let (metrics, diagnostics) = match level {
"all" => (true, true),
"error" | "crash" => (false, true),
"off" => (false, false),
_ => return None,
};
Some(TelemetrySettingsContent {
metrics: Some(metrics),
diagnostics: Some(diagnostics),
})
})
}
fn terminal_settings_content(&self) -> Option<TerminalSettingsContent> {
let (font_family, font_fallbacks) = self.read_fonts("terminal.integrated.fontFamily");
skip_default(TerminalSettingsContent {
alternate_scroll: None,
blinking: self
.read_bool("terminal.integrated.cursorBlinking")
.map(|b| {
if b {
TerminalBlink::On
} else {
TerminalBlink::Off
}
}),
button: None,
copy_on_select: self.read_bool("terminal.integrated.copyOnSelection"),
cursor_shape: self.read_enum("terminal.integrated.cursorStyle", |s| match s {
"block" => Some(CursorShapeContent::Block),
"line" => Some(CursorShapeContent::Bar),
"underline" => Some(CursorShapeContent::Underline),
_ => None,
}),
default_height: None,
default_width: None,
dock: None,
font_fallbacks,
font_family,
font_features: None,
font_size: self.read_f32("terminal.integrated.fontSize"),
font_weight: None,
keep_selection_on_copy: None,
line_height: self
.read_f32("terminal.integrated.lineHeight")
.map(|lh| TerminalLineHeight::Custom(lh)),
max_scroll_history_lines: self.read_usize("terminal.integrated.scrollback"),
minimum_contrast: None,
option_as_meta: self.read_bool("terminal.integrated.macOptionIsMeta"),
project: self.project_terminal_settings_content(),
scrollbar: None,
toolbar: None,
})
}
fn project_terminal_settings_content(&self) -> ProjectTerminalSettingsContent {
#[cfg(target_os = "windows")]
let platform = "windows";
#[cfg(target_os = "linux")]
let platform = "linux";
#[cfg(target_os = "macos")]
let platform = "osx";
#[cfg(target_os = "freebsd")]
let platform = "freebsd";
let env = self
.read_value(&format!("terminal.integrated.env.{platform}"))
.and_then(|v| v.as_object())
.map(|v| v.iter().map(|(k, v)| (k.clone(), v.to_string())).collect());
ProjectTerminalSettingsContent {
// TODO: handle arguments
shell: self
.read_string(&format!("terminal.integrated.{platform}Exec"))
.map(|s| Shell::Program(s)),
working_directory: None,
env,
detect_venv: None,
}
}
fn theme_settings_content(&self) -> ThemeSettingsContent {
let (buffer_font_family, buffer_font_fallbacks) = self.read_fonts("editor.fontFamily");
ThemeSettingsContent {
ui_font_size: None,
ui_font_family: None,
ui_font_fallbacks: None,
ui_font_features: None,
ui_font_weight: None,
buffer_font_family,
buffer_font_fallbacks,
buffer_font_size: self.read_f32("editor.fontSize"),
buffer_font_weight: self.read_f32("editor.fontWeight").map(|w| w.into()),
buffer_line_height: None,
buffer_font_features: None,
agent_ui_font_size: None,
agent_buffer_font_size: None,
theme: None,
icon_theme: None,
ui_density: None,
unnecessary_code_fade: None,
experimental_theme_overrides: None,
theme_overrides: Default::default(),
}
}
fn workspace_settings_content(&self) -> WorkspaceSettingsContent {
WorkspaceSettingsContent {
active_pane_modifiers: self.active_pane_modifiers(),
autosave: self.read_enum("files.autoSave", |s| match s {
"off" => Some(AutosaveSetting::Off),
"afterDelay" => Some(AutosaveSetting::AfterDelay {
milliseconds: self
.read_value("files.autoSaveDelay")
.and_then(|v| v.as_u64())
.unwrap_or(1000),
}),
"onFocusChange" => Some(AutosaveSetting::OnFocusChange),
"onWindowChange" => Some(AutosaveSetting::OnWindowChange),
_ => None,
}),
bottom_dock_layout: None,
centered_layout: None,
close_on_file_delete: None,
command_aliases: Default::default(),
confirm_quit: self.read_enum("window.confirmBeforeClose", |s| match s {
"always" | "keyboardOnly" => Some(true),
"never" => Some(false),
_ => None,
}),
drop_target_size: None,
// workbench.editor.limit contains "enabled", "value", and "perEditorGroup"
// our semantics match if those are set to true, some N, and true respectively.
// we'll ignore "perEditorGroup" for now since we only support a global max
max_tabs: if self.read_bool("workbench.editor.limit.enabled") == Some(true) {
self.read_usize("workbench.editor.limit.value")
.and_then(|n| NonZeroUsize::new(n))
} else {
None
},
on_last_window_closed: None,
pane_split_direction_horizontal: None,
pane_split_direction_vertical: None,
resize_all_panels_in_dock: None,
restore_on_file_reopen: self.read_bool("workbench.editor.restoreViewState"),
restore_on_startup: None,
show_call_status_icon: None,
use_system_path_prompts: self.read_bool("files.simpleDialog.enable"),
use_system_prompts: None,
use_system_window_tabs: self.read_bool("window.nativeTabs"),
when_closing_with_no_tabs: self.read_bool("window.closeWhenEmpty").map(|b| {
if b {
CloseWindowWhenNoItems::CloseWindow
} else {
CloseWindowWhenNoItems::KeepWindowOpen
}
}),
zoomed_padding: None,
}
}
fn active_pane_modifiers(&self) -> Option<ActivePaneModifiers> {
if self.read_bool("accessibility.dimUnfocused.enabled") == Some(true)
&& let Some(opacity) = self.read_f32("accessibility.dimUnfocused.opacity")
{
Some(ActivePaneModifiers {
border_size: None,
inactive_opacity: Some(opacity),
})
} else {
None
}
}
fn worktree_settings_content(&self) -> WorktreeSettingsContent {
WorktreeSettingsContent {
project_name: None,
file_scan_exclusions: self
.read_value("files.watcherExclude")
.and_then(|v| v.as_array())
.map(|v| {
v.iter()
.filter_map(|n| n.as_str().map(str::to_owned))
.collect::<Vec<_>>()
})
.filter(|r| !r.is_empty()),
file_scan_inclusions: self
.read_value("files.watcherInclude")
.and_then(|v| v.as_array())
.map(|v| {
v.iter()
.filter_map(|n| n.as_str().map(str::to_owned))
.collect::<Vec<_>>()
})
.filter(|r| !r.is_empty()),
private_files: None,
}
}
}
fn skip_default<T: Default + PartialEq>(value: T) -> Option<T> {
if value == T::default() {
None
} else {
Some(value)
}
}
+2 -78
View File
@@ -8,9 +8,8 @@ use serde::{Deserialize, Serialize};
pub use settings::AlternateScroll;
use settings::{
CursorShapeContent, SettingsContent, ShowScrollbar, TerminalBlink, TerminalDockPosition,
TerminalLineHeight, TerminalSettingsContent, VenvSettings, WorkingDirectory,
merge_from::MergeFrom,
ShowScrollbar, TerminalBlink, TerminalDockPosition, TerminalLineHeight, VenvSettings,
WorkingDirectory, merge_from::MergeFrom,
};
use task::Shell;
use theme::FontFamilyName;
@@ -116,81 +115,6 @@ impl settings::Settings for TerminalSettings {
minimum_contrast: user_content.minimum_contrast.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) {
let mut default = TerminalSettingsContent::default();
let current = content.terminal.as_mut().unwrap_or(&mut default);
let name = |s| format!("terminal.integrated.{s}");
vscode.f32_setting(&name("fontSize"), &mut current.font_size);
vscode.font_family_setting(
&name("fontFamily"),
&mut current.font_family,
&mut current.font_fallbacks,
);
vscode.bool_setting(&name("copyOnSelection"), &mut current.copy_on_select);
vscode.bool_setting("macOptionIsMeta", &mut current.option_as_meta);
vscode.usize_setting("scrollback", &mut current.max_scroll_history_lines);
match vscode.read_bool(&name("cursorBlinking")) {
Some(true) => current.blinking = Some(TerminalBlink::On),
Some(false) => current.blinking = Some(TerminalBlink::Off),
None => {}
}
vscode.enum_setting(
&name("cursorStyle"),
&mut current.cursor_shape,
|s| match s {
"block" => Some(CursorShapeContent::Block),
"line" => Some(CursorShapeContent::Bar),
"underline" => Some(CursorShapeContent::Underline),
_ => None,
},
);
// they also have "none" and "outline" as options but just for the "Inactive" variant
if let Some(height) = vscode
.read_value(&name("lineHeight"))
.and_then(|v| v.as_f64())
{
current.line_height = Some(TerminalLineHeight::Custom(height as f32))
}
#[cfg(target_os = "windows")]
let platform = "windows";
#[cfg(target_os = "linux")]
let platform = "linux";
#[cfg(target_os = "macos")]
let platform = "osx";
#[cfg(target_os = "freebsd")]
let platform = "freebsd";
// TODO: handle arguments
let shell_name = format!("{platform}Exec");
if let Some(s) = vscode.read_string(&name(&shell_name)) {
current.project.shell = Some(settings::Shell::Program(s.to_owned()))
}
if let Some(env) = vscode
.read_value(&name(&format!("env.{platform}")))
.and_then(|v| v.as_object())
{
for (k, v) in env {
if v.is_null()
&& let Some(zed_env) = current.project.env.as_mut()
{
zed_env.remove(k);
}
let Some(v) = v.as_str() else { continue };
if let Some(zed_env) = current.project.env.as_mut() {
zed_env.insert(k.clone(), v.to_owned());
} else {
current.project.env = Some([(k.clone(), v.to_owned())].into_iter().collect())
}
}
}
if content.terminal.is_none() && default != TerminalSettingsContent::default() {
content.terminal = Some(default)
}
}
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-11
View File
@@ -727,15 +727,4 @@ impl settings::Settings for ThemeSettings {
unnecessary_code_fade: content.unnecessary_code_fade.unwrap().0.clamp(0.0, 0.9),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
vscode.from_f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight);
vscode.from_f32_setting("editor.fontSize", &mut current.theme.buffer_font_size);
vscode.font_family_setting(
"editor.fontFamily",
&mut current.theme.buffer_font_family,
&mut current.theme.buffer_font_fallbacks,
)
// TODO: possibly map editor.fontLigatures to buffer_font_features?
}
}
@@ -19,10 +19,6 @@ impl Settings for VimModeSetting {
fn from_settings(content: &SettingsContent) -> Self {
Self(content.vim_mode.unwrap())
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _content: &mut SettingsContent) {
// TODO: could possibly check if any of the `vim.<foo>` keys are set?
}
}
pub struct HelixModeSetting(pub bool);
@@ -31,6 +27,4 @@ impl Settings for HelixModeSetting {
fn from_settings(content: &SettingsContent) -> Self {
Self(content.helix_mode.unwrap())
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {}
}
-59
View File
@@ -76,40 +76,6 @@ impl Settings for ItemSettings {
show_close_button: tabs.show_close_button.unwrap(),
}
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(b) = vscode.read_bool("workbench.editor.tabActionCloseVisibility") {
current.tabs.get_or_insert_default().show_close_button = Some(if b {
ShowCloseButton::Always
} else {
ShowCloseButton::Hidden
})
}
if let Some(s) = vscode.read_enum("workbench.editor.tabActionLocation", |s| match s {
"right" => Some(ClosePosition::Right),
"left" => Some(ClosePosition::Left),
_ => None,
}) {
current.tabs.get_or_insert_default().close_position = Some(s)
}
if let Some(b) = vscode.read_bool("workbench.editor.focusRecentEditorAfterClose") {
current.tabs.get_or_insert_default().activate_on_close = Some(if b {
ActivateOnClose::History
} else {
ActivateOnClose::LeftNeighbour
})
}
if let Some(b) = vscode.read_bool("workbench.editor.showIcons") {
current.tabs.get_or_insert_default().file_icons = Some(b);
};
if let Some(b) = vscode.read_bool("git.decorations.enabled") {
current.tabs.get_or_insert_default().git_status = Some(b);
}
}
}
impl Settings for PreviewTabsSettings {
@@ -123,31 +89,6 @@ impl Settings for PreviewTabsSettings {
.unwrap(),
}
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(enabled) = vscode.read_bool("workbench.editor.enablePreview") {
current.preview_tabs.get_or_insert_default().enabled = Some(enabled);
}
if let Some(enable_preview_from_code_navigation) =
vscode.read_bool("workbench.editor.enablePreviewFromCodeNavigation")
{
current
.preview_tabs
.get_or_insert_default()
.enable_preview_from_code_navigation = Some(enable_preview_from_code_navigation)
}
if let Some(enable_preview_from_file_finder) =
vscode.read_bool("workbench.editor.enablePreviewFromQuickOpen")
{
current
.preview_tabs
.get_or_insert_default()
.enable_preview_from_file_finder = Some(enable_preview_from_file_finder)
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
-110
View File
@@ -108,91 +108,6 @@ impl Settings for WorkspaceSettings {
zoomed_padding: workspace.zoomed_padding.unwrap(),
}
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if vscode
.read_bool("accessibility.dimUnfocused.enabled")
.unwrap_or_default()
&& let Some(opacity) = vscode
.read_value("accessibility.dimUnfocused.opacity")
.and_then(|v| v.as_f64())
{
current
.workspace
.active_pane_modifiers
.get_or_insert_default()
.inactive_opacity = Some(opacity as f32);
}
vscode.enum_setting(
"window.confirmBeforeClose",
&mut current.workspace.confirm_quit,
|s| match s {
"always" | "keyboardOnly" => Some(true),
"never" => Some(false),
_ => None,
},
);
vscode.bool_setting(
"workbench.editor.restoreViewState",
&mut current.workspace.restore_on_file_reopen,
);
if let Some(b) = vscode.read_bool("window.closeWhenEmpty") {
current.workspace.when_closing_with_no_tabs = Some(if b {
settings::CloseWindowWhenNoItems::CloseWindow
} else {
settings::CloseWindowWhenNoItems::KeepWindowOpen
});
}
if let Some(b) = vscode.read_bool("files.simpleDialog.enable") {
current.workspace.use_system_path_prompts = Some(!b);
}
if let Some(v) = vscode.read_enum("files.autoSave", |s| match s {
"off" => Some(AutosaveSetting::Off),
"afterDelay" => Some(AutosaveSetting::AfterDelay {
milliseconds: vscode
.read_value("files.autoSaveDelay")
.and_then(|v| v.as_u64())
.unwrap_or(1000),
}),
"onFocusChange" => Some(AutosaveSetting::OnFocusChange),
"onWindowChange" => Some(AutosaveSetting::OnWindowChange),
_ => None,
}) {
current.workspace.autosave = Some(v);
}
// workbench.editor.limit contains "enabled", "value", and "perEditorGroup"
// our semantics match if those are set to true, some N, and true respectively.
// we'll ignore "perEditorGroup" for now since we only support a global max
if let Some(n) = vscode
.read_value("workbench.editor.limit.value")
.and_then(|v| v.as_u64())
.and_then(|n| NonZeroUsize::new(n as usize))
&& vscode
.read_bool("workbench.editor.limit.enabled")
.unwrap_or_default()
{
current.workspace.max_tabs = Some(n)
}
if let Some(b) = vscode.read_bool("window.nativeTabs") {
current.workspace.use_system_window_tabs = Some(b);
}
// some combination of "window.restoreWindows" and "workbench.startupEditor" might
// map to our "restore_on_startup"
// there doesn't seem to be a way to read whether the bottom dock's "justified"
// setting is enabled in vscode. that'd be our equivalent to "bottom_dock_layout"
}
}
impl Settings for TabBarSettings {
@@ -204,22 +119,6 @@ impl Settings for TabBarSettings {
show_tab_bar_buttons: tab_bar.show_tab_bar_buttons.unwrap(),
}
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(b) = vscode.read_enum("workbench.editor.showTabs", |s| match s {
"multiple" => Some(true),
"single" | "none" => Some(false),
_ => None,
}) {
current.tab_bar.get_or_insert_default().show = Some(b);
}
if Some("hidden") == vscode.read_string("workbench.editor.editorActionsLocation") {
current.tab_bar.get_or_insert_default().show_tab_bar_buttons = Some(false)
}
}
}
#[derive(Deserialize)]
@@ -238,13 +137,4 @@ impl Settings for StatusBarSettings {
cursor_position_button: status_bar.cursor_position_button.unwrap(),
}
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(show) = vscode.read_bool("workbench.statusBar.visible") {
current.status_bar.get_or_insert_default().show = Some(show);
}
}
}
+1 -26
View File
@@ -1,7 +1,7 @@
use std::path::Path;
use anyhow::Context as _;
use settings::{Settings, SettingsContent};
use settings::Settings;
use util::{
ResultExt,
paths::{PathMatcher, PathStyle},
@@ -64,31 +64,6 @@ impl Settings for WorktreeSettings {
.unwrap_or_default(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
if let Some(inclusions) = vscode
.read_value("files.watcherInclude")
.and_then(|v| v.as_array())
.and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
{
if let Some(old) = current.project.worktree.file_scan_inclusions.as_mut() {
old.extend(inclusions)
} else {
current.project.worktree.file_scan_inclusions = Some(inclusions)
}
}
if let Some(exclusions) = vscode
.read_value("files.watcherExclude")
.and_then(|v| v.as_array())
.and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
{
if let Some(old) = current.project.worktree.file_scan_exclusions.as_mut() {
old.extend(exclusions)
} else {
current.project.worktree.file_scan_exclusions = Some(exclusions)
}
}
}
}
fn path_matchers(mut values: Vec<String>, context: &'static str) -> anyhow::Result<PathMatcher> {
@@ -29,6 +29,4 @@ impl Settings for ZlogSettings {
scopes: content.log.clone().unwrap(),
}
}
fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {}
}