Consider --preview flag for server subcommand (#12208)

## Summary

This PR removes the requirement of `--preview` flag to run the `ruff
server` and instead considers it to be an indicator to turn on preview
mode for the linter and the formatter.

resolves: #12161 

## Test Plan

Add test cases to assert the `preview` value is updated accordingly.

In an editor context, I used the local `ruff` executable in Neovim with
the `--preview` flag and verified that the preview-only violations are
being highlighted.

Running with:
```lua
require('lspconfig').ruff.setup({
  cmd = {
    '/Users/dhruv/work/astral/ruff/target/debug/ruff',
    'server',
    '--preview',
  },
})
```
The screenshot shows that `E502` is highlighted with the below config in
`pyproject.toml`:

<img width="877" alt="Screenshot 2024-07-17 at 16 43 09"
src="https://github.com/user-attachments/assets/c7016ef3-55b1-4a14-bbd3-a07b1bcdd323">
This commit is contained in:
Dhruv Manilawala 2024-07-18 11:05:01 +05:30 committed by GitHub
parent ebe5b06c95
commit 2e77b775b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 145 additions and 37 deletions

View file

@ -84,6 +84,20 @@ pub struct ClientSettings {
pub(crate) tracing: TracingSettings,
}
impl ClientSettings {
/// Update the preview flag for the linter and the formatter with the given value.
pub(crate) fn set_preview(&mut self, preview: bool) {
match self.lint.as_mut() {
None => self.lint = Some(LintOptions::default().with_preview(preview)),
Some(lint) => lint.set_preview(preview),
}
match self.format.as_mut() {
None => self.format = Some(FormatOptions::default().with_preview(preview)),
Some(format) => format.set_preview(preview),
}
}
}
/// Settings needed to initialize tracing. These will only be
/// read from the global configuration.
#[derive(Debug, Deserialize, Default)]
@ -107,7 +121,7 @@ struct WorkspaceSettings {
workspace: Url,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Default, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[serde(rename_all = "camelCase")]
struct LintOptions {
@ -118,6 +132,17 @@ struct LintOptions {
ignore: Option<Vec<String>>,
}
impl LintOptions {
fn with_preview(mut self, preview: bool) -> LintOptions {
self.preview = Some(preview);
self
}
fn set_preview(&mut self, preview: bool) {
self.preview = Some(preview);
}
}
#[derive(Debug, Default, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[serde(rename_all = "camelCase")]
@ -125,6 +150,17 @@ struct FormatOptions {
preview: Option<bool>,
}
impl FormatOptions {
fn with_preview(mut self, preview: bool) -> FormatOptions {
self.preview = Some(preview);
self
}
fn set_preview(&mut self, preview: bool) {
self.preview = Some(preview);
}
}
#[derive(Debug, Default, Deserialize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[serde(rename_all = "camelCase")]
@ -159,6 +195,7 @@ enum InitializationOptions {
}
/// Built from the initialization options provided by the client.
#[derive(Debug)]
pub(crate) struct AllSettings {
pub(crate) global_settings: ClientSettings,
/// If this is `None`, the client only passed in global settings.
@ -179,6 +216,16 @@ impl AllSettings {
)
}
/// Update the preview flag for both the global and all workspace settings.
pub(crate) fn set_preview(&mut self, preview: bool) {
self.global_settings.set_preview(preview);
if let Some(workspace_settings) = self.workspace_settings.as_mut() {
for settings in workspace_settings.values_mut() {
settings.set_preview(preview);
}
}
}
fn from_init_options(options: InitializationOptions) -> Self {
let (global_settings, workspace_settings) = match options {
InitializationOptions::GlobalOnly { settings } => (settings, None),
@ -393,6 +440,11 @@ mod tests {
const EMPTY_INIT_OPTIONS_FIXTURE: &str =
include_str!("../../resources/test/fixtures/settings/empty.json");
// This fixture contains multiple workspaces with empty initialization options. It only sets
// the `cwd` and the `workspace` value.
const EMPTY_MULTIPLE_WORKSPACE_INIT_OPTIONS_FIXTURE: &str =
include_str!("../../resources/test/fixtures/settings/empty_multiple_workspace.json");
fn deserialize_fixture<T: DeserializeOwned>(content: &str) -> T {
serde_json::from_str(content).expect("test fixture JSON should deserialize")
}
@ -456,7 +508,9 @@ mod tests {
exclude: None,
line_length: None,
configuration_preference: None,
show_syntax_errors: None,
show_syntax_errors: Some(
true,
),
tracing: TracingSettings {
log_level: None,
log_file: None,
@ -509,7 +563,9 @@ mod tests {
exclude: None,
line_length: None,
configuration_preference: None,
show_syntax_errors: None,
show_syntax_errors: Some(
true,
),
tracing: TracingSettings {
log_level: None,
log_file: None,
@ -575,7 +631,9 @@ mod tests {
exclude: None,
line_length: None,
configuration_preference: None,
show_syntax_errors: None,
show_syntax_errors: Some(
true,
),
tracing: TracingSettings {
log_level: None,
log_file: None,
@ -771,4 +829,30 @@ mod tests {
assert_eq!(options, InitializationOptions::default());
}
fn assert_preview_client_settings(settings: &ClientSettings, preview: bool) {
assert_eq!(settings.lint.as_ref().unwrap().preview.unwrap(), preview);
assert_eq!(settings.format.as_ref().unwrap().preview.unwrap(), preview);
}
fn assert_preview_all_settings(all_settings: &AllSettings, preview: bool) {
assert_preview_client_settings(&all_settings.global_settings, preview);
if let Some(workspace_settings) = all_settings.workspace_settings.as_ref() {
for settings in workspace_settings.values() {
assert_preview_client_settings(settings, preview);
}
}
}
#[test]
fn test_preview_flag() {
let options = deserialize_fixture(EMPTY_MULTIPLE_WORKSPACE_INIT_OPTIONS_FIXTURE);
let mut all_settings = AllSettings::from_init_options(options);
all_settings.set_preview(false);
assert_preview_all_settings(&all_settings, false);
all_settings.set_preview(true);
assert_preview_all_settings(&all_settings, true);
}
}