Skip to content

Commit 8dd2a31

Browse files
committed
Extract creating workspaces in the server
1 parent c6b311c commit 8dd2a31

8 files changed

Lines changed: 163 additions & 49 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ruff_server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ rustc-hash = { workspace = true }
3737
serde = { workspace = true }
3838
serde_json = { workspace = true }
3939
shellexpand = { workspace = true }
40+
thiserror = { workspace = true }
4041
tracing = { workspace = true }
4142
tracing-subscriber = { workspace = true }
4243

crates/ruff_server/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
pub use edit::{DocumentKey, NotebookDocument, PositionEncoding, TextDocument};
44
use lsp_types::CodeActionKind;
5-
pub use server::Server;
5+
pub use server::{Server, Workspace, Workspaces};
66
pub use session::{ClientSettings, DocumentQuery, DocumentSnapshot, Session};
77

88
#[macro_use]

crates/ruff_server/src/server.rs

Lines changed: 139 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
33
use lsp_server as lsp;
44
use lsp_types as types;
5+
use lsp_types::InitializeParams;
6+
use lsp_types::WorkspaceFolder;
57
use std::num::NonZeroUsize;
8+
use std::ops::Deref;
69
use std::panic::PanicInfo;
710
use std::str::FromStr;
11+
use thiserror::Error;
812
use types::ClientCapabilities;
913
use types::CodeActionKind;
1014
use types::CodeActionOptions;
@@ -18,6 +22,7 @@ use types::OneOf;
1822
use types::TextDocumentSyncCapability;
1923
use types::TextDocumentSyncKind;
2024
use types::TextDocumentSyncOptions;
25+
use types::Url;
2126
use types::WorkDoneProgressOptions;
2227
use types::WorkspaceFoldersServerCapabilities;
2328

@@ -29,6 +34,7 @@ use self::schedule::Task;
2934
use crate::session::AllSettings;
3035
use crate::session::ClientSettings;
3136
use crate::session::Session;
37+
use crate::session::WorkspaceSettingsMap;
3238
use crate::PositionEncoding;
3339

3440
mod api;
@@ -71,17 +77,23 @@ impl Server {
7177

7278
crate::message::init_messenger(connection.make_sender());
7379

80+
let InitializeParams {
81+
initialization_options,
82+
workspace_folders,
83+
client_info,
84+
..
85+
} = init_params;
86+
7487
let mut all_settings = AllSettings::from_value(
75-
init_params
76-
.initialization_options
88+
initialization_options
7789
.unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::default())),
7890
);
7991
if let Some(preview) = preview {
8092
all_settings.set_preview(preview);
8193
}
8294
let AllSettings {
8395
global_settings,
84-
mut workspace_settings,
96+
workspace_settings,
8597
} = all_settings;
8698

8799
crate::trace::init_tracing(
@@ -91,34 +103,13 @@ impl Server {
91103
.log_level
92104
.unwrap_or(crate::trace::LogLevel::Info),
93105
global_settings.tracing.log_file.as_deref(),
94-
init_params.client_info.as_ref(),
106+
client_info.as_ref(),
95107
);
96108

97-
let mut workspace_for_url = |url: lsp_types::Url| {
98-
let Some(workspace_settings) = workspace_settings.as_mut() else {
99-
return (url, ClientSettings::default());
100-
};
101-
let settings = workspace_settings.remove(&url).unwrap_or_else(|| {
102-
tracing::warn!("No workspace settings found for {}", url);
103-
ClientSettings::default()
104-
});
105-
(url, settings)
106-
};
107-
108-
let workspaces = init_params
109-
.workspace_folders
110-
.filter(|folders| !folders.is_empty())
111-
.map(|folders| folders.into_iter().map(|folder| {
112-
workspace_for_url(folder.uri)
113-
}).collect())
114-
.or_else(|| {
115-
tracing::warn!("No workspace(s) were provided during initialization. Using the current working directory as a default workspace...");
116-
let uri = types::Url::from_file_path(std::env::current_dir().ok()?).ok()?;
117-
Some(vec![workspace_for_url(uri)])
118-
})
119-
.ok_or_else(|| {
120-
anyhow::anyhow!("Failed to get the current working directory while creating a default workspace.")
121-
})?;
109+
let workspaces = Workspaces::from_workspace_folders(
110+
workspace_folders,
111+
workspace_settings.unwrap_or_default(),
112+
)?;
122113

123114
Ok(Self {
124115
connection,
@@ -127,7 +118,7 @@ impl Server {
127118
&client_capabilities,
128119
position_encoding,
129120
global_settings,
130-
workspaces,
121+
&workspaces,
131122
)?,
132123
client_capabilities,
133124
})
@@ -462,3 +453,121 @@ impl FromStr for SupportedCommand {
462453
})
463454
}
464455
}
456+
457+
#[derive(Debug)]
458+
pub struct Workspaces(Vec<Workspace>);
459+
460+
impl Workspaces {
461+
pub fn new(workspaces: Vec<Workspace>) -> Self {
462+
Self(workspaces)
463+
}
464+
465+
/// Create the workspaces from the provided workspace folders as provided by the client during
466+
/// initialization.
467+
fn from_workspace_folders(
468+
workspace_folders: Option<Vec<WorkspaceFolder>>,
469+
mut workspace_settings: WorkspaceSettingsMap,
470+
) -> std::result::Result<Workspaces, WorkspacesError> {
471+
let mut client_settings_for_url = |url: &Url| {
472+
workspace_settings.remove(url).unwrap_or_else(|| {
473+
tracing::info!(
474+
"No workspace settings found for {}, using default settings",
475+
url
476+
);
477+
ClientSettings::default()
478+
})
479+
};
480+
481+
let workspaces =
482+
if let Some(folders) = workspace_folders.filter(|folders| !folders.is_empty()) {
483+
folders
484+
.into_iter()
485+
.map(|folder| {
486+
let settings = client_settings_for_url(&folder.uri);
487+
Workspace::new(folder.uri).with_settings(settings)
488+
})
489+
.collect()
490+
} else {
491+
let current_dir = std::env::current_dir().map_err(WorkspacesError::Io)?;
492+
tracing::info!(
493+
"No workspace(s) were provided during initialization. \
494+
Using the current working directory as a default workspace: {}",
495+
current_dir.display()
496+
);
497+
let uri = Url::from_file_path(current_dir)
498+
.map_err(|()| WorkspacesError::InvalidCurrentDir)?;
499+
let settings = client_settings_for_url(&uri);
500+
vec![Workspace::default(uri).with_settings(settings)]
501+
};
502+
503+
Ok(Workspaces(workspaces))
504+
}
505+
}
506+
507+
impl Deref for Workspaces {
508+
type Target = [Workspace];
509+
510+
fn deref(&self) -> &Self::Target {
511+
&self.0
512+
}
513+
}
514+
515+
#[derive(Error, Debug)]
516+
enum WorkspacesError {
517+
#[error(transparent)]
518+
Io(#[from] std::io::Error),
519+
#[error("Failed to create a URL from the current working directory")]
520+
InvalidCurrentDir,
521+
}
522+
523+
#[derive(Debug)]
524+
pub struct Workspace {
525+
/// The [`Url`] pointing to the root of the workspace.
526+
url: Url,
527+
/// The client settings for this workspace.
528+
settings: Option<ClientSettings>,
529+
/// Whether this is the default workspace as created by the server. This will be the case when
530+
/// no workspace folders were provided during initialization.
531+
is_default: bool,
532+
}
533+
534+
impl Workspace {
535+
/// Create a new workspace with the given root URL.
536+
pub fn new(url: Url) -> Self {
537+
Self {
538+
url,
539+
settings: None,
540+
is_default: false,
541+
}
542+
}
543+
544+
/// Create a new default workspace with the given root URL.
545+
pub fn default(url: Url) -> Self {
546+
Self {
547+
url,
548+
settings: None,
549+
is_default: true,
550+
}
551+
}
552+
553+
/// Set the client settings for this workspace.
554+
pub fn with_settings(mut self, settings: ClientSettings) -> Self {
555+
self.settings = Some(settings);
556+
self
557+
}
558+
559+
/// Returns the root URL of the workspace.
560+
pub(crate) fn url(&self) -> &Url {
561+
&self.url
562+
}
563+
564+
/// Returns the client settings for this workspace.
565+
pub(crate) fn settings(&self) -> Option<&ClientSettings> {
566+
self.settings.as_ref()
567+
}
568+
569+
/// Returns true if this is the default workspace.
570+
pub(crate) fn is_default(&self) -> bool {
571+
self.is_default
572+
}
573+
}

crates/ruff_server/src/server/api/notifications/did_change_workspace.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl super::SyncNotificationHandler for DidChangeWorkspace {
2020
) -> Result<()> {
2121
for types::WorkspaceFolder { uri, .. } in params.event.added {
2222
session
23-
.open_workspace_folder(&uri)
23+
.open_workspace_folder(uri)
2424
.with_failure_code(lsp_server::ErrorCode::InvalidParams)?;
2525
}
2626
for types::WorkspaceFolder { uri, .. } in params.event.removed {

crates/ruff_server/src/session.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use std::sync::Arc;
55
use lsp_types::{ClientCapabilities, NotebookDocumentCellChange, Url};
66

77
use crate::edit::{DocumentKey, DocumentVersion, NotebookDocument};
8+
use crate::server::Workspaces;
89
use crate::{PositionEncoding, TextDocument};
910

1011
pub(crate) use self::capabilities::ResolvedClientCapabilities;
1112
pub use self::index::DocumentQuery;
12-
pub(crate) use self::settings::AllSettings;
1313
pub use self::settings::ClientSettings;
14+
pub(crate) use self::settings::{AllSettings, WorkspaceSettingsMap};
1415

1516
mod capabilities;
1617
mod index;
@@ -42,11 +43,11 @@ impl Session {
4243
client_capabilities: &ClientCapabilities,
4344
position_encoding: PositionEncoding,
4445
global_settings: ClientSettings,
45-
workspace_folders: Vec<(Url, ClientSettings)>,
46+
workspaces: &Workspaces,
4647
) -> crate::Result<Self> {
4748
Ok(Self {
4849
position_encoding,
49-
index: index::Index::new(workspace_folders, &global_settings)?,
50+
index: index::Index::new(workspaces, &global_settings)?,
5051
global_settings,
5152
resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new(
5253
client_capabilities,
@@ -136,7 +137,7 @@ impl Session {
136137
}
137138

138139
/// Open a workspace folder at the given `url`.
139-
pub(crate) fn open_workspace_folder(&mut self, url: &Url) -> crate::Result<()> {
140+
pub(crate) fn open_workspace_folder(&mut self, url: Url) -> crate::Result<()> {
140141
self.index.open_workspace_folder(url, &self.global_settings)
141142
}
142143

crates/ruff_server/src/session/index.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_hash::FxHashMap;
1010
pub(crate) use ruff_settings::RuffSettings;
1111

1212
use crate::edit::LanguageId;
13+
use crate::server::{Workspace, Workspaces};
1314
use crate::{
1415
edit::{DocumentKey, DocumentVersion, NotebookDocument},
1516
PositionEncoding, TextDocument,
@@ -67,12 +68,12 @@ pub enum DocumentQuery {
6768

6869
impl Index {
6970
pub(super) fn new(
70-
workspace_folders: Vec<(Url, ClientSettings)>,
71+
workspaces: &Workspaces,
7172
global_settings: &ClientSettings,
7273
) -> crate::Result<Self> {
7374
let mut settings = WorkspaceSettingsIndex::default();
74-
for (url, workspace_settings) in workspace_folders {
75-
settings.register_workspace(&url, Some(workspace_settings), global_settings)?;
75+
for workspace in &**workspaces {
76+
settings.register_workspace(workspace, global_settings)?;
7677
}
7778

7879
Ok(Self {
@@ -167,11 +168,12 @@ impl Index {
167168

168169
pub(super) fn open_workspace_folder(
169170
&mut self,
170-
url: &Url,
171+
url: Url,
171172
global_settings: &ClientSettings,
172173
) -> crate::Result<()> {
173174
// TODO(jane): Find a way for workspace client settings to be added or changed dynamically.
174-
self.settings.register_workspace(url, None, global_settings)
175+
self.settings
176+
.register_workspace(&Workspace::new(url), global_settings)
175177
}
176178

177179
pub(super) fn num_documents(&self) -> usize {
@@ -398,10 +400,10 @@ impl WorkspaceSettingsIndex {
398400
/// workspace. Otherwise, the global settings are used exclusively.
399401
fn register_workspace(
400402
&mut self,
401-
workspace_url: &Url,
402-
workspace_settings: Option<ClientSettings>,
403+
workspace: &Workspace,
403404
global_settings: &ClientSettings,
404405
) -> crate::Result<()> {
406+
let workspace_url = workspace.url();
405407
if workspace_url.scheme() != "file" {
406408
tracing::info!("Ignoring non-file workspace URL: {workspace_url}");
407409
show_warn_msg!("Ruff does not support non-file workspaces; Ignoring {workspace_url}");
@@ -411,8 +413,8 @@ impl WorkspaceSettingsIndex {
411413
anyhow!("Failed to convert workspace URL to file path: {workspace_url}")
412414
})?;
413415

414-
let client_settings = if let Some(workspace_settings) = workspace_settings {
415-
ResolvedClientSettings::with_workspace(&workspace_settings, global_settings)
416+
let client_settings = if let Some(workspace_settings) = workspace.settings() {
417+
ResolvedClientSettings::with_workspace(workspace_settings, global_settings)
416418
} else {
417419
ResolvedClientSettings::global(global_settings)
418420
};

crates/ruff_server/tests/notebook.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use lsp_types::{
88
Position, Range, TextDocumentContentChangeEvent, VersionedTextDocumentIdentifier,
99
};
1010
use ruff_notebook::SourceValue;
11-
use ruff_server::ClientSettings;
11+
use ruff_server::{ClientSettings, Workspace, Workspaces};
1212

1313
const SUPER_RESOLUTION_OVERVIEW_PATH: &str =
1414
"./resources/test/fixtures/tensorflow_test_notebook.ipynb";
@@ -32,10 +32,10 @@ fn super_resolution_overview() {
3232
&ClientCapabilities::default(),
3333
ruff_server::PositionEncoding::UTF16,
3434
ClientSettings::default(),
35-
vec![(
35+
&Workspaces::new(vec![Workspace::new(
3636
lsp_types::Url::from_file_path(file_path.parent().unwrap()).unwrap(),
37-
ClientSettings::default(),
38-
)],
37+
)
38+
.with_settings(ClientSettings::default())]),
3939
)
4040
.unwrap();
4141

0 commit comments

Comments
 (0)