feat: preconfigure Python with Astral ty and Ruff
This commit is contained in:
+115
-38
@@ -10,10 +10,10 @@ pub fn resolve(value: Value) -> Result<Vec<ServerConfig>, String> {
|
||||
if language_id.is_empty() || language_id.chars().any(char::is_control) {
|
||||
return Err("LSP language keys must not be empty or contain control characters".into());
|
||||
}
|
||||
let preset: Option<(&[&str], &str)> = match language_id.as_str() {
|
||||
"rust" => Some((&["rust-analyzer"], "rs")),
|
||||
"python" => Some((&["pylsp"], "py")),
|
||||
"ruby" => Some((&["solargraph", "stdio"], "rb")),
|
||||
let preset: Option<(&[&[&str]], &str)> = match language_id.as_str() {
|
||||
"rust" => Some((&[&["rust-analyzer"]], "rs")),
|
||||
"python" => Some((&[&["ty", "server"], &["ruff", "server"]], "py")),
|
||||
"ruby" => Some((&[&["solargraph", "stdio"]], "rb")),
|
||||
_ => None,
|
||||
};
|
||||
let mut options = match value {
|
||||
@@ -36,55 +36,134 @@ pub fn resolve(value: Value) -> Result<Vec<ServerConfig>, String> {
|
||||
for key in options.keys() {
|
||||
if !matches!(
|
||||
key.as_str(),
|
||||
"command" | "extensions" | "initialization_options" | "settings"
|
||||
"command" | "extensions" | "initialization_options" | "settings" | "servers"
|
||||
) {
|
||||
return Err(format!(
|
||||
"LSP language {language_id:?} has unknown option {key:?}"
|
||||
));
|
||||
}
|
||||
}
|
||||
let command = match options.remove("command") {
|
||||
Some(value) => command(value, &language_id)?,
|
||||
None => match preset {
|
||||
Some((command, _)) => command.iter().map(|argument| (*argument).into()).collect(),
|
||||
None => {
|
||||
return Err(format!(
|
||||
"LSP language {language_id:?} requires command argv"
|
||||
));
|
||||
}
|
||||
},
|
||||
};
|
||||
let extensions = match options.remove("extensions") {
|
||||
let mut extensions = match options.remove("extensions") {
|
||||
Some(value) => extensions(value, &language_id)?,
|
||||
None => match preset {
|
||||
Some((_, extension)) => vec![extension.into()],
|
||||
None => return Err(format!("LSP language {language_id:?} requires extensions")),
|
||||
},
|
||||
};
|
||||
let initialization_options = object(
|
||||
options.remove("initialization_options"),
|
||||
&language_id,
|
||||
"initialization_options",
|
||||
)?;
|
||||
let settings = object(options.remove("settings"), &language_id, "settings")?;
|
||||
servers.push(ServerConfig {
|
||||
language_id,
|
||||
command,
|
||||
extensions,
|
||||
initialization_options,
|
||||
settings,
|
||||
});
|
||||
if servers.len() > 16 {
|
||||
return Err("LSP configuration supports at most 16 enabled languages".into());
|
||||
let explicit_servers = options.contains_key("servers");
|
||||
let configurations = match options.remove("servers") {
|
||||
Some(value) => {
|
||||
if ["command", "initialization_options", "settings"]
|
||||
.iter()
|
||||
.any(|key| options.contains_key(*key))
|
||||
{
|
||||
return Err(format!(
|
||||
"LSP language {language_id:?} servers cannot be combined with command, initialization_options, or settings"
|
||||
));
|
||||
}
|
||||
let Value::Array(configurations) = value else {
|
||||
return Err(format!(
|
||||
"LSP language {language_id:?} servers must be a nonempty array of configuration objects"
|
||||
));
|
||||
};
|
||||
if configurations.is_empty() {
|
||||
return Err(format!(
|
||||
"LSP language {language_id:?} servers must be a nonempty array of configuration objects"
|
||||
));
|
||||
}
|
||||
configurations
|
||||
}
|
||||
None if options.contains_key("command") => vec![Value::Object(options)],
|
||||
None => {
|
||||
let Some((commands, _)) = preset else {
|
||||
return Err(format!(
|
||||
"LSP language {language_id:?} requires command argv or servers"
|
||||
));
|
||||
};
|
||||
let mut configurations = Vec::with_capacity(commands.len());
|
||||
for (index, command) in commands.iter().enumerate() {
|
||||
let mut configuration = if index + 1 == commands.len() {
|
||||
std::mem::take(&mut options)
|
||||
} else {
|
||||
options.clone()
|
||||
};
|
||||
configuration.insert(
|
||||
"command".into(),
|
||||
Value::Array(
|
||||
command
|
||||
.iter()
|
||||
.map(|argument| Value::from(*argument))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
if language_id == "python" && index == 0 {
|
||||
configuration
|
||||
.entry("initialization_options")
|
||||
.or_insert_with(
|
||||
|| serde_json::json!({"experimental": {"useUv": "off"}}),
|
||||
);
|
||||
}
|
||||
configurations.push(Value::Object(configuration));
|
||||
}
|
||||
configurations
|
||||
}
|
||||
};
|
||||
if servers.len() + configurations.len() > 16 {
|
||||
return Err("LSP configuration supports at most 16 configured servers".into());
|
||||
}
|
||||
let configuration_count = configurations.len();
|
||||
let mut language_id = language_id;
|
||||
for (index, configuration) in configurations.into_iter().enumerate() {
|
||||
let context = if explicit_servers {
|
||||
format!("LSP language {language_id:?} servers[{index}]")
|
||||
} else {
|
||||
format!("LSP language {language_id:?}")
|
||||
};
|
||||
let Value::Object(mut configuration) = configuration else {
|
||||
return Err(format!("{context} must be a configuration object"));
|
||||
};
|
||||
for key in configuration.keys() {
|
||||
if !matches!(
|
||||
key.as_str(),
|
||||
"command" | "initialization_options" | "settings"
|
||||
) {
|
||||
return Err(format!("{context} has unknown option {key:?}"));
|
||||
}
|
||||
}
|
||||
let command = match configuration.remove("command") {
|
||||
Some(value) => command(value, &context)?,
|
||||
None => return Err(format!("{context} requires command argv")),
|
||||
};
|
||||
let initialization_options = object(
|
||||
configuration.remove("initialization_options"),
|
||||
&context,
|
||||
"initialization_options",
|
||||
)?;
|
||||
let settings = object(configuration.remove("settings"), &context, "settings")?;
|
||||
servers.push(ServerConfig {
|
||||
language_id: if index + 1 == configuration_count {
|
||||
std::mem::take(&mut language_id)
|
||||
} else {
|
||||
language_id.clone()
|
||||
},
|
||||
command,
|
||||
extensions: if index + 1 == configuration_count {
|
||||
std::mem::take(&mut extensions)
|
||||
} else {
|
||||
extensions.clone()
|
||||
},
|
||||
initialization_options,
|
||||
settings,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(servers)
|
||||
}
|
||||
|
||||
fn command(value: Value, language: &str) -> Result<Vec<String>, String> {
|
||||
fn command(value: Value, context: &str) -> Result<Vec<String>, String> {
|
||||
let error = || {
|
||||
format!(
|
||||
"LSP language {language:?} command must be nonempty argv with a nonempty executable and no NUL bytes"
|
||||
"{context} command must be nonempty argv with a nonempty executable and no NUL bytes"
|
||||
)
|
||||
};
|
||||
let Value::Array(values) = value else {
|
||||
@@ -137,12 +216,10 @@ fn extensions(value: Value, language: &str) -> Result<Vec<String>, String> {
|
||||
Ok(extensions)
|
||||
}
|
||||
|
||||
fn object(value: Option<Value>, language: &str, option: &str) -> Result<Value, String> {
|
||||
fn object(value: Option<Value>, context: &str, option: &str) -> Result<Value, String> {
|
||||
match value {
|
||||
None => Ok(Value::Object(serde_json::Map::new())),
|
||||
Some(value) if value.is_object() => Ok(value),
|
||||
Some(_) => Err(format!(
|
||||
"LSP language {language:?} {option} must be a JSON object"
|
||||
)),
|
||||
Some(_) => Err(format!("{context} {option} must be a JSON object")),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user