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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user