mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
[ty] Use python version and path from Python extension (#19012)
This commit is contained in:
parent
26f736bc46
commit
90026047f9
22 changed files with 344 additions and 99 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4307,6 +4307,7 @@ dependencies = [
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"ruff_db",
|
"ruff_db",
|
||||||
"ruff_notebook",
|
"ruff_notebook",
|
||||||
|
"ruff_python_ast",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
"ruff_text_size",
|
"ruff_text_size",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
|
|
@ -121,17 +121,17 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||||
None => ProjectMetadata::discover(&project_path, &system)?,
|
None => ProjectMetadata::discover(&project_path, &system)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let options = args.into_options();
|
|
||||||
project_metadata.apply_options(options.clone());
|
|
||||||
project_metadata.apply_configuration_files(&system)?;
|
project_metadata.apply_configuration_files(&system)?;
|
||||||
|
|
||||||
|
let project_options_overrides = ProjectOptionsOverrides::new(config_file, args.into_options());
|
||||||
|
project_metadata.apply_overrides(&project_options_overrides);
|
||||||
|
|
||||||
let mut db = ProjectDatabase::new(project_metadata, system)?;
|
let mut db = ProjectDatabase::new(project_metadata, system)?;
|
||||||
|
|
||||||
if !check_paths.is_empty() {
|
if !check_paths.is_empty() {
|
||||||
db.project().set_included_paths(&mut db, check_paths);
|
db.project().set_included_paths(&mut db, check_paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_options_overrides = ProjectOptionsOverrides::new(config_file, options);
|
|
||||||
let (main_loop, main_loop_cancellation_token) =
|
let (main_loop, main_loop_cancellation_token) =
|
||||||
MainLoop::new(project_options_overrides, printer);
|
MainLoop::new(project_options_overrides, printer);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::panic::{AssertUnwindSafe, RefUnwindSafe};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{cmp, fmt};
|
use std::{cmp, fmt};
|
||||||
|
|
||||||
|
pub use self::changes::ChangeResult;
|
||||||
use crate::metadata::settings::file_settings;
|
use crate::metadata::settings::file_settings;
|
||||||
use crate::{DEFAULT_LINT_REGISTRY, DummyReporter};
|
use crate::{DEFAULT_LINT_REGISTRY, DummyReporter};
|
||||||
use crate::{ProgressReporter, Project, ProjectMetadata};
|
use crate::{ProgressReporter, Project, ProjectMetadata};
|
||||||
|
|
|
@ -41,8 +41,6 @@ impl ProjectDatabase {
|
||||||
let project_root = project.root(self).to_path_buf();
|
let project_root = project.root(self).to_path_buf();
|
||||||
let config_file_override =
|
let config_file_override =
|
||||||
project_options_overrides.and_then(|options| options.config_file_override.clone());
|
project_options_overrides.and_then(|options| options.config_file_override.clone());
|
||||||
let options =
|
|
||||||
project_options_overrides.map(|project_options| project_options.options.clone());
|
|
||||||
let program = Program::get(self);
|
let program = Program::get(self);
|
||||||
let custom_stdlib_versions_path = program
|
let custom_stdlib_versions_path = program
|
||||||
.custom_stdlib_search_path(self)
|
.custom_stdlib_search_path(self)
|
||||||
|
@ -218,16 +216,16 @@ impl ProjectDatabase {
|
||||||
};
|
};
|
||||||
match new_project_metadata {
|
match new_project_metadata {
|
||||||
Ok(mut metadata) => {
|
Ok(mut metadata) => {
|
||||||
if let Some(cli_options) = options {
|
|
||||||
metadata.apply_options(cli_options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(error) = metadata.apply_configuration_files(self.system()) {
|
if let Err(error) = metadata.apply_configuration_files(self.system()) {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Failed to apply configuration files, continuing without applying them: {error}"
|
"Failed to apply configuration files, continuing without applying them: {error}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(overrides) = project_options_overrides {
|
||||||
|
metadata.apply_overrides(overrides);
|
||||||
|
}
|
||||||
|
|
||||||
match metadata.to_program_settings(self.system(), self.vendored()) {
|
match metadata.to_program_settings(self.system(), self.vendored()) {
|
||||||
Ok(program_settings) => {
|
Ok(program_settings) => {
|
||||||
let program = Program::get(self);
|
let program = Program::get(self);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::glob::{GlobFilterCheckMode, IncludeResult};
|
use crate::glob::{GlobFilterCheckMode, IncludeResult};
|
||||||
use crate::metadata::options::{OptionDiagnostic, ToSettingsError};
|
use crate::metadata::options::{OptionDiagnostic, ToSettingsError};
|
||||||
use crate::walk::{ProjectFilesFilter, ProjectFilesWalker};
|
use crate::walk::{ProjectFilesFilter, ProjectFilesWalker};
|
||||||
pub use db::{CheckMode, Db, ProjectDatabase, SalsaMemoryDump};
|
pub use db::{ChangeResult, CheckMode, Db, ProjectDatabase, SalsaMemoryDump};
|
||||||
use files::{Index, Indexed, IndexedFiles};
|
use files::{Index, Indexed, IndexedFiles};
|
||||||
use metadata::settings::Settings;
|
use metadata::settings::Settings;
|
||||||
pub use metadata::{ProjectMetadata, ProjectMetadataError};
|
pub use metadata::{ProjectMetadata, ProjectMetadataError};
|
||||||
|
|
|
@ -7,6 +7,7 @@ use thiserror::Error;
|
||||||
use ty_python_semantic::ProgramSettings;
|
use ty_python_semantic::ProgramSettings;
|
||||||
|
|
||||||
use crate::combine::Combine;
|
use crate::combine::Combine;
|
||||||
|
use crate::metadata::options::ProjectOptionsOverrides;
|
||||||
use crate::metadata::pyproject::{Project, PyProject, PyProjectError, ResolveRequiresPythonError};
|
use crate::metadata::pyproject::{Project, PyProject, PyProjectError, ResolveRequiresPythonError};
|
||||||
use crate::metadata::value::ValueSource;
|
use crate::metadata::value::ValueSource;
|
||||||
pub use options::Options;
|
pub use options::Options;
|
||||||
|
@ -276,6 +277,10 @@ impl ProjectMetadata {
|
||||||
.to_program_settings(self.root(), self.name(), system, vendored)
|
.to_program_settings(self.root(), self.name(), system, vendored)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_overrides(&mut self, overrides: &ProjectOptionsOverrides) {
|
||||||
|
self.options = overrides.apply_to(std::mem::take(&mut self.options));
|
||||||
|
}
|
||||||
|
|
||||||
/// Combine the project options with the CLI options where the CLI options take precedence.
|
/// Combine the project options with the CLI options where the CLI options take precedence.
|
||||||
pub fn apply_options(&mut self, options: Options) {
|
pub fn apply_options(&mut self, options: Options) {
|
||||||
self.options = options.combine(std::mem::take(&mut self.options));
|
self.options = options.combine(std::mem::take(&mut self.options));
|
||||||
|
|
|
@ -121,6 +121,9 @@ impl Options {
|
||||||
ValueSource::File(path) => PythonVersionSource::ConfigFile(
|
ValueSource::File(path) => PythonVersionSource::ConfigFile(
|
||||||
PythonVersionFileSource::new(path.clone(), ranged_version.range()),
|
PythonVersionFileSource::new(path.clone(), ranged_version.range()),
|
||||||
),
|
),
|
||||||
|
ValueSource::PythonVSCodeExtension => {
|
||||||
|
PythonVersionSource::PythonVSCodeExtension
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -140,6 +143,7 @@ impl Options {
|
||||||
ValueSource::File(path) => {
|
ValueSource::File(path) => {
|
||||||
SysPrefixPathOrigin::ConfigFileSetting(path.clone(), python_path.range())
|
SysPrefixPathOrigin::ConfigFileSetting(path.clone(), python_path.range())
|
||||||
}
|
}
|
||||||
|
ValueSource::PythonVSCodeExtension => SysPrefixPathOrigin::PythonVSCodeExtension,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(PythonEnvironment::new(
|
Some(PythonEnvironment::new(
|
||||||
|
@ -702,6 +706,10 @@ impl Rules {
|
||||||
let lint_source = match source {
|
let lint_source = match source {
|
||||||
ValueSource::File(_) => LintSource::File,
|
ValueSource::File(_) => LintSource::File,
|
||||||
ValueSource::Cli => LintSource::Cli,
|
ValueSource::Cli => LintSource::Cli,
|
||||||
|
|
||||||
|
ValueSource::PythonVSCodeExtension => {
|
||||||
|
unreachable!("Can't configure rules from the Python VSCode extension")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if let Ok(severity) = Severity::try_from(**level) {
|
if let Ok(severity) = Severity::try_from(**level) {
|
||||||
selection.enable(lint, severity, lint_source);
|
selection.enable(lint, severity, lint_source);
|
||||||
|
@ -854,6 +862,7 @@ fn build_include_filter(
|
||||||
Severity::Info,
|
Severity::Info,
|
||||||
"The pattern was specified on the CLI",
|
"The pattern was specified on the CLI",
|
||||||
)),
|
)),
|
||||||
|
ValueSource::PythonVSCodeExtension => unreachable!("Can't configure includes from the Python VSCode extension"),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -936,6 +945,9 @@ fn build_exclude_filter(
|
||||||
Severity::Info,
|
Severity::Info,
|
||||||
"The pattern was specified on the CLI",
|
"The pattern was specified on the CLI",
|
||||||
)),
|
)),
|
||||||
|
ValueSource::PythonVSCodeExtension => unreachable!(
|
||||||
|
"Can't configure excludes from the Python VSCode extension"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -1497,8 +1509,11 @@ impl OptionDiagnostic {
|
||||||
/// This is a wrapper for options that actually get loaded from configuration files
|
/// This is a wrapper for options that actually get loaded from configuration files
|
||||||
/// and the CLI, which also includes a `config_file_override` option that overrides
|
/// and the CLI, which also includes a `config_file_override` option that overrides
|
||||||
/// default configuration discovery with an explicitly-provided path to a configuration file
|
/// default configuration discovery with an explicitly-provided path to a configuration file
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||||
pub struct ProjectOptionsOverrides {
|
pub struct ProjectOptionsOverrides {
|
||||||
pub config_file_override: Option<SystemPathBuf>,
|
pub config_file_override: Option<SystemPathBuf>,
|
||||||
|
pub fallback_python_version: Option<RangedValue<PythonVersion>>,
|
||||||
|
pub fallback_python: Option<RelativePathBuf>,
|
||||||
pub options: Options,
|
pub options: Options,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1507,8 +1522,22 @@ impl ProjectOptionsOverrides {
|
||||||
Self {
|
Self {
|
||||||
config_file_override,
|
config_file_override,
|
||||||
options,
|
options,
|
||||||
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_to(&self, options: Options) -> Options {
|
||||||
|
let mut combined = self.options.clone().combine(options);
|
||||||
|
|
||||||
|
// Set the fallback python version and path if set
|
||||||
|
combined.environment.combine_with(Some(EnvironmentOptions {
|
||||||
|
python_version: self.fallback_python_version.clone(),
|
||||||
|
python: self.fallback_python.clone(),
|
||||||
|
..EnvironmentOptions::default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
combined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait OrDefault {
|
trait OrDefault {
|
||||||
|
|
|
@ -27,6 +27,9 @@ pub enum ValueSource {
|
||||||
/// The value comes from a CLI argument, while it's left open if specified using a short argument,
|
/// The value comes from a CLI argument, while it's left open if specified using a short argument,
|
||||||
/// long argument (`--extra-paths`) or `--config key=value`.
|
/// long argument (`--extra-paths`) or `--config key=value`.
|
||||||
Cli,
|
Cli,
|
||||||
|
|
||||||
|
/// The value comes from an LSP client configuration.
|
||||||
|
PythonVSCodeExtension,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueSource {
|
impl ValueSource {
|
||||||
|
@ -34,6 +37,7 @@ impl ValueSource {
|
||||||
match self {
|
match self {
|
||||||
ValueSource::File(path) => Some(&**path),
|
ValueSource::File(path) => Some(&**path),
|
||||||
ValueSource::Cli => None,
|
ValueSource::Cli => None,
|
||||||
|
ValueSource::PythonVSCodeExtension => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +109,14 @@ impl<T> RangedValue<T> {
|
||||||
Self::with_range(value, ValueSource::Cli, TextRange::default())
|
Self::with_range(value, ValueSource::Cli, TextRange::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn python_extension(value: T) -> Self {
|
||||||
|
Self::with_range(
|
||||||
|
value,
|
||||||
|
ValueSource::PythonVSCodeExtension,
|
||||||
|
TextRange::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_range(value: T, source: ValueSource, range: TextRange) -> Self {
|
pub fn with_range(value: T, source: ValueSource, range: TextRange) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value,
|
value,
|
||||||
|
@ -327,6 +339,10 @@ impl RelativePathBuf {
|
||||||
Self::new(path, ValueSource::Cli)
|
Self::new(path, ValueSource::Cli)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn python_extension(path: impl AsRef<SystemPath>) -> Self {
|
||||||
|
Self::new(path, ValueSource::PythonVSCodeExtension)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the relative path as specified by the user.
|
/// Returns the relative path as specified by the user.
|
||||||
pub fn path(&self) -> &SystemPath {
|
pub fn path(&self) -> &SystemPath {
|
||||||
&self.0
|
&self.0
|
||||||
|
@ -354,7 +370,7 @@ impl RelativePathBuf {
|
||||||
pub fn absolute(&self, project_root: &SystemPath, system: &dyn System) -> SystemPathBuf {
|
pub fn absolute(&self, project_root: &SystemPath, system: &dyn System) -> SystemPathBuf {
|
||||||
let relative_to = match &self.0.source {
|
let relative_to = match &self.0.source {
|
||||||
ValueSource::File(_) => project_root,
|
ValueSource::File(_) => project_root,
|
||||||
ValueSource::Cli => system.current_directory(),
|
ValueSource::Cli | ValueSource::PythonVSCodeExtension => system.current_directory(),
|
||||||
};
|
};
|
||||||
|
|
||||||
SystemPath::absolute(&self.0, relative_to)
|
SystemPath::absolute(&self.0, relative_to)
|
||||||
|
@ -409,7 +425,7 @@ impl RelativeGlobPattern {
|
||||||
) -> Result<AbsolutePortableGlobPattern, PortableGlobError> {
|
) -> Result<AbsolutePortableGlobPattern, PortableGlobError> {
|
||||||
let relative_to = match &self.0.source {
|
let relative_to = match &self.0.source {
|
||||||
ValueSource::File(_) => project_root,
|
ValueSource::File(_) => project_root,
|
||||||
ValueSource::Cli => system.current_directory(),
|
ValueSource::Cli | ValueSource::PythonVSCodeExtension => system.current_directory(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pattern = PortableGlobPattern::parse(&self.0, kind)?;
|
let pattern = PortableGlobPattern::parse(&self.0, kind)?;
|
||||||
|
|
|
@ -113,6 +113,9 @@ pub enum PythonVersionSource {
|
||||||
/// long argument (`--extra-paths`) or `--config key=value`.
|
/// long argument (`--extra-paths`) or `--config key=value`.
|
||||||
Cli,
|
Cli,
|
||||||
|
|
||||||
|
/// The value comes from the Python VS Code extension (the selected interpreter).
|
||||||
|
PythonVSCodeExtension,
|
||||||
|
|
||||||
/// We fell back to a default value because the value was not specified via the CLI or a config file.
|
/// We fell back to a default value because the value was not specified via the CLI or a config file.
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
|
|
|
@ -1065,6 +1065,8 @@ pub enum SysPrefixPathOrigin {
|
||||||
ConfigFileSetting(Arc<SystemPathBuf>, Option<TextRange>),
|
ConfigFileSetting(Arc<SystemPathBuf>, Option<TextRange>),
|
||||||
/// The `sys.prefix` path came from a `--python` CLI flag
|
/// The `sys.prefix` path came from a `--python` CLI flag
|
||||||
PythonCliFlag,
|
PythonCliFlag,
|
||||||
|
/// The selected interpreter in the VS Code's Python extension.
|
||||||
|
PythonVSCodeExtension,
|
||||||
/// The `sys.prefix` path came from the `VIRTUAL_ENV` environment variable
|
/// The `sys.prefix` path came from the `VIRTUAL_ENV` environment variable
|
||||||
VirtualEnvVar,
|
VirtualEnvVar,
|
||||||
/// The `sys.prefix` path came from the `CONDA_PREFIX` environment variable
|
/// The `sys.prefix` path came from the `CONDA_PREFIX` environment variable
|
||||||
|
@ -1086,6 +1088,7 @@ impl SysPrefixPathOrigin {
|
||||||
Self::LocalVenv | Self::VirtualEnvVar => true,
|
Self::LocalVenv | Self::VirtualEnvVar => true,
|
||||||
Self::ConfigFileSetting(..)
|
Self::ConfigFileSetting(..)
|
||||||
| Self::PythonCliFlag
|
| Self::PythonCliFlag
|
||||||
|
| Self::PythonVSCodeExtension
|
||||||
| Self::DerivedFromPyvenvCfg
|
| Self::DerivedFromPyvenvCfg
|
||||||
| Self::CondaPrefixVar => false,
|
| Self::CondaPrefixVar => false,
|
||||||
}
|
}
|
||||||
|
@ -1097,7 +1100,9 @@ impl SysPrefixPathOrigin {
|
||||||
/// the `sys.prefix` directory, e.g. the `--python` CLI flag.
|
/// the `sys.prefix` directory, e.g. the `--python` CLI flag.
|
||||||
pub(crate) const fn must_point_directly_to_sys_prefix(&self) -> bool {
|
pub(crate) const fn must_point_directly_to_sys_prefix(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::PythonCliFlag | Self::ConfigFileSetting(..) => false,
|
Self::PythonCliFlag | Self::ConfigFileSetting(..) | Self::PythonVSCodeExtension => {
|
||||||
|
false
|
||||||
|
}
|
||||||
Self::VirtualEnvVar
|
Self::VirtualEnvVar
|
||||||
| Self::CondaPrefixVar
|
| Self::CondaPrefixVar
|
||||||
| Self::DerivedFromPyvenvCfg
|
| Self::DerivedFromPyvenvCfg
|
||||||
|
@ -1115,6 +1120,9 @@ impl std::fmt::Display for SysPrefixPathOrigin {
|
||||||
Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"),
|
Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"),
|
||||||
Self::DerivedFromPyvenvCfg => f.write_str("derived `sys.prefix` path"),
|
Self::DerivedFromPyvenvCfg => f.write_str("derived `sys.prefix` path"),
|
||||||
Self::LocalVenv => f.write_str("local virtual environment"),
|
Self::LocalVenv => f.write_str("local virtual environment"),
|
||||||
|
Self::PythonVSCodeExtension => {
|
||||||
|
f.write_str("selected interpreter in the VS Code Python extension")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,12 @@ pub fn add_inferred_python_version_hint_to_diagnostic(
|
||||||
or in a configuration file",
|
or in a configuration file",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
crate::PythonVersionSource::PythonVSCodeExtension => {
|
||||||
|
diagnostic.info(format_args!(
|
||||||
|
"Python {version} was assumed when {action} \
|
||||||
|
because it's the version of the selected Python interpreter in the VS Code Python extension",
|
||||||
|
));
|
||||||
|
}
|
||||||
crate::PythonVersionSource::InstallationDirectoryLayout {
|
crate::PythonVersionSource::InstallationDirectoryLayout {
|
||||||
site_packages_parent_dir,
|
site_packages_parent_dir,
|
||||||
} => {
|
} => {
|
||||||
|
|
|
@ -13,6 +13,7 @@ license = { workspace = true }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ruff_db = { workspace = true, features = ["os"] }
|
ruff_db = { workspace = true, features = ["os"] }
|
||||||
ruff_notebook = { workspace = true }
|
ruff_notebook = { workspace = true }
|
||||||
|
ruff_python_ast = { workspace = true }
|
||||||
ruff_source_file = { workspace = true }
|
ruff_source_file = { workspace = true }
|
||||||
ruff_text_size = { workspace = true }
|
ruff_text_size = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -40,22 +40,16 @@ impl SyncNotificationHandler for DidChangeTextDocumentHandler {
|
||||||
.update_text_document(&key, content_changes, version)
|
.update_text_document(&key, content_changes, version)
|
||||||
.with_failure_code(ErrorCode::InternalError)?;
|
.with_failure_code(ErrorCode::InternalError)?;
|
||||||
|
|
||||||
match key.path() {
|
let changes = match key.path() {
|
||||||
AnySystemPath::System(path) => {
|
AnySystemPath::System(path) => {
|
||||||
let db = match session.project_db_for_path_mut(path) {
|
vec![ChangeEvent::file_content_changed(path.clone())]
|
||||||
Some(db) => db,
|
|
||||||
None => session.default_project_db_mut(),
|
|
||||||
};
|
|
||||||
db.apply_changes(vec![ChangeEvent::file_content_changed(path.clone())], None);
|
|
||||||
}
|
}
|
||||||
AnySystemPath::SystemVirtual(virtual_path) => {
|
AnySystemPath::SystemVirtual(virtual_path) => {
|
||||||
let db = session.default_project_db_mut();
|
vec![ChangeEvent::ChangedVirtual(virtual_path.clone())]
|
||||||
db.apply_changes(
|
|
||||||
vec![ChangeEvent::ChangedVirtual(virtual_path.clone())],
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
session.apply_changes(key.path(), changes);
|
||||||
|
|
||||||
publish_diagnostics(session, &key, client);
|
publish_diagnostics(session, &key, client);
|
||||||
|
|
||||||
|
|
|
@ -88,12 +88,7 @@ impl SyncNotificationHandler for DidChangeWatchedFiles {
|
||||||
for (root, changes) in events_by_db {
|
for (root, changes) in events_by_db {
|
||||||
tracing::debug!("Applying changes to `{root}`");
|
tracing::debug!("Applying changes to `{root}`");
|
||||||
|
|
||||||
// SAFETY: Only paths that are part of the workspace are registered for file watching.
|
let result = session.apply_changes(&AnySystemPath::System(root), changes);
|
||||||
// So, virtual paths and paths that are outside of a workspace does not trigger this
|
|
||||||
// notification.
|
|
||||||
let db = session.project_db_for_path_mut(&*root).unwrap();
|
|
||||||
|
|
||||||
let result = db.apply_changes(changes, None);
|
|
||||||
|
|
||||||
project_changed |= result.project_changed();
|
project_changed |= result.project_changed();
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,10 +39,9 @@ impl SyncNotificationHandler for DidCloseTextDocumentHandler {
|
||||||
.with_failure_code(ErrorCode::InternalError)?;
|
.with_failure_code(ErrorCode::InternalError)?;
|
||||||
|
|
||||||
if let AnySystemPath::SystemVirtual(virtual_path) = key.path() {
|
if let AnySystemPath::SystemVirtual(virtual_path) = key.path() {
|
||||||
let db = session.default_project_db_mut();
|
session.apply_changes(
|
||||||
db.apply_changes(
|
key.path(),
|
||||||
vec![ChangeEvent::DeletedVirtual(virtual_path.clone())],
|
vec![ChangeEvent::DeletedVirtual(virtual_path.clone())],
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,10 +39,9 @@ impl SyncNotificationHandler for DidCloseNotebookHandler {
|
||||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||||
|
|
||||||
if let AnySystemPath::SystemVirtual(virtual_path) = key.path() {
|
if let AnySystemPath::SystemVirtual(virtual_path) = key.path() {
|
||||||
let db = session.default_project_db_mut();
|
session.apply_changes(
|
||||||
db.apply_changes(
|
key.path(),
|
||||||
vec![ChangeEvent::DeletedVirtual(virtual_path.clone())],
|
vec![ChangeEvent::DeletedVirtual(virtual_path.clone())],
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,11 +46,7 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler {
|
||||||
|
|
||||||
match key.path() {
|
match key.path() {
|
||||||
AnySystemPath::System(system_path) => {
|
AnySystemPath::System(system_path) => {
|
||||||
let db = match session.project_db_for_path_mut(system_path) {
|
session.apply_changes(key.path(), vec![ChangeEvent::Opened(system_path.clone())]);
|
||||||
Some(db) => db,
|
|
||||||
None => session.default_project_db_mut(),
|
|
||||||
};
|
|
||||||
db.apply_changes(vec![ChangeEvent::Opened(system_path.clone())], None);
|
|
||||||
}
|
}
|
||||||
AnySystemPath::SystemVirtual(virtual_path) => {
|
AnySystemPath::SystemVirtual(virtual_path) => {
|
||||||
let db = session.default_project_db_mut();
|
let db = session.default_project_db_mut();
|
||||||
|
|
|
@ -40,11 +40,7 @@ impl SyncNotificationHandler for DidOpenNotebookHandler {
|
||||||
|
|
||||||
match &path {
|
match &path {
|
||||||
AnySystemPath::System(system_path) => {
|
AnySystemPath::System(system_path) => {
|
||||||
let db = match session.project_db_for_path_mut(system_path) {
|
session.apply_changes(&path, vec![ChangeEvent::Opened(system_path.clone())]);
|
||||||
Some(db) => db,
|
|
||||||
None => session.default_project_db_mut(),
|
|
||||||
};
|
|
||||||
db.apply_changes(vec![ChangeEvent::Opened(system_path.clone())], None);
|
|
||||||
}
|
}
|
||||||
AnySystemPath::SystemVirtual(virtual_path) => {
|
AnySystemPath::SystemVirtual(virtual_path) => {
|
||||||
let db = session.default_project_db_mut();
|
let db = session.default_project_db_mut();
|
||||||
|
|
|
@ -33,7 +33,7 @@ impl BackgroundRequestHandler for WorkspaceDiagnosticRequestHandler {
|
||||||
let index = snapshot.index();
|
let index = snapshot.index();
|
||||||
|
|
||||||
if !index.global_settings().diagnostic_mode().is_workspace() {
|
if !index.global_settings().diagnostic_mode().is_workspace() {
|
||||||
tracing::debug!("Workspace diagnostics is disabled; returning empty report");
|
tracing::trace!("Workspace diagnostics is disabled; returning empty report");
|
||||||
return Ok(WorkspaceDiagnosticReportResult::Report(
|
return Ok(WorkspaceDiagnosticReportResult::Report(
|
||||||
WorkspaceDiagnosticReport { items: vec![] },
|
WorkspaceDiagnosticReport { items: vec![] },
|
||||||
));
|
));
|
||||||
|
|
|
@ -13,7 +13,8 @@ use ruff_db::Db;
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||||
use ty_project::metadata::Options;
|
use ty_project::metadata::Options;
|
||||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
use ty_project::watch::ChangeEvent;
|
||||||
|
use ty_project::{ChangeResult, ProjectDatabase, ProjectMetadata};
|
||||||
|
|
||||||
pub(crate) use self::capabilities::ResolvedClientCapabilities;
|
pub(crate) use self::capabilities::ResolvedClientCapabilities;
|
||||||
pub(crate) use self::index::DocumentQuery;
|
pub(crate) use self::index::DocumentQuery;
|
||||||
|
@ -49,7 +50,12 @@ pub(crate) struct Session {
|
||||||
/// The projects across all workspaces.
|
/// The projects across all workspaces.
|
||||||
projects: BTreeMap<SystemPathBuf, ProjectDatabase>,
|
projects: BTreeMap<SystemPathBuf, ProjectDatabase>,
|
||||||
|
|
||||||
default_project: ProjectDatabase,
|
/// The project to use for files outside any workspace. For example, if the user
|
||||||
|
/// opens the project `<home>/my_project` in VS code but they then opens a Python file from their Desktop.
|
||||||
|
/// This file isn't part of the active workspace, nor is it part of any project. But we still want
|
||||||
|
/// to provide some basic functionality like navigation, completions, syntax highlighting, etc.
|
||||||
|
/// That's what we use the default project for.
|
||||||
|
default_project: DefaultProject,
|
||||||
|
|
||||||
/// The global position encoding, negotiated during LSP initialization.
|
/// The global position encoding, negotiated during LSP initialization.
|
||||||
position_encoding: PositionEncoding,
|
position_encoding: PositionEncoding,
|
||||||
|
@ -77,26 +83,15 @@ impl Session {
|
||||||
|
|
||||||
let mut workspaces = Workspaces::default();
|
let mut workspaces = Workspaces::default();
|
||||||
for (url, options) in workspace_folders {
|
for (url, options) in workspace_folders {
|
||||||
workspaces.register(url, options)?;
|
workspaces.register(url, options.into_settings())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let default_project = {
|
|
||||||
let system = LSPSystem::new(index.clone());
|
|
||||||
let metadata = ProjectMetadata::from_options(
|
|
||||||
Options::default(),
|
|
||||||
system.current_directory().to_path_buf(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
ProjectDatabase::new(metadata, system).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
position_encoding,
|
position_encoding,
|
||||||
workspaces,
|
workspaces,
|
||||||
deferred_messages: VecDeque::new(),
|
deferred_messages: VecDeque::new(),
|
||||||
index: Some(index),
|
index: Some(index),
|
||||||
default_project,
|
default_project: DefaultProject::new(),
|
||||||
projects: BTreeMap::new(),
|
projects: BTreeMap::new(),
|
||||||
resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new(
|
resolved_client_capabilities: Arc::new(ResolvedClientCapabilities::new(
|
||||||
client_capabilities,
|
client_capabilities,
|
||||||
|
@ -109,7 +104,6 @@ impl Session {
|
||||||
pub(crate) fn request_queue(&self) -> &RequestQueue {
|
pub(crate) fn request_queue(&self) -> &RequestQueue {
|
||||||
&self.request_queue
|
&self.request_queue
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn request_queue_mut(&mut self) -> &mut RequestQueue {
|
pub(crate) fn request_queue_mut(&mut self) -> &mut RequestQueue {
|
||||||
&mut self.request_queue
|
&mut self.request_queue
|
||||||
}
|
}
|
||||||
|
@ -204,21 +198,46 @@ impl Session {
|
||||||
.map(|(_, db)| db)
|
.map(|(_, db)| db)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the default project [`ProjectDatabase`]. The default project is the
|
pub(crate) fn apply_changes(
|
||||||
/// minimum root path in the project map.
|
&mut self,
|
||||||
|
path: &AnySystemPath,
|
||||||
|
changes: Vec<ChangeEvent>,
|
||||||
|
) -> ChangeResult {
|
||||||
|
let overrides = path.as_system().and_then(|root| {
|
||||||
|
self.workspaces()
|
||||||
|
.for_path(root)?
|
||||||
|
.settings()
|
||||||
|
.project_options_overrides()
|
||||||
|
.cloned()
|
||||||
|
});
|
||||||
|
|
||||||
|
let db = match path {
|
||||||
|
AnySystemPath::System(path) => match self.project_db_for_path_mut(path) {
|
||||||
|
Some(db) => db,
|
||||||
|
None => self.default_project_db_mut(),
|
||||||
|
},
|
||||||
|
AnySystemPath::SystemVirtual(_) => self.default_project_db_mut(),
|
||||||
|
};
|
||||||
|
|
||||||
|
db.apply_changes(changes, overrides.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the default project [`ProjectDatabase`].
|
||||||
pub(crate) fn default_project_db(&self) -> &ProjectDatabase {
|
pub(crate) fn default_project_db(&self) -> &ProjectDatabase {
|
||||||
&self.default_project
|
self.default_project.get(self.index.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the default project [`ProjectDatabase`].
|
/// Returns a mutable reference to the default project [`ProjectDatabase`].
|
||||||
pub(crate) fn default_project_db_mut(&mut self) -> &mut ProjectDatabase {
|
pub(crate) fn default_project_db_mut(&mut self) -> &mut ProjectDatabase {
|
||||||
&mut self.default_project
|
self.default_project.get_mut(self.index.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable iterator over all project databases that have been initialized to this point.
|
||||||
|
///
|
||||||
|
/// This iterator will only yield the default project database if it has been used.
|
||||||
fn projects_mut(&mut self) -> impl Iterator<Item = &'_ mut ProjectDatabase> + '_ {
|
fn projects_mut(&mut self) -> impl Iterator<Item = &'_ mut ProjectDatabase> + '_ {
|
||||||
self.projects
|
let default_project = self.default_project.try_get_mut();
|
||||||
.values_mut()
|
self.projects.values_mut().chain(default_project)
|
||||||
.chain(std::iter::once(&mut self.default_project))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`DocumentKey`] for the given URL.
|
/// Returns the [`DocumentKey`] for the given URL.
|
||||||
|
@ -232,16 +251,18 @@ impl Session {
|
||||||
assert!(!self.workspaces.all_initialized());
|
assert!(!self.workspaces.all_initialized());
|
||||||
|
|
||||||
for (url, options) in workspace_settings {
|
for (url, options) in workspace_settings {
|
||||||
let Some(workspace) = self.workspaces.initialize(&url, options) else {
|
tracing::debug!("Initializing workspace `{url}`");
|
||||||
|
|
||||||
|
let settings = options.into_settings();
|
||||||
|
let Some((root, workspace)) = self.workspaces.initialize(&url, settings) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// For now, create one project database per workspace.
|
// For now, create one project database per workspace.
|
||||||
// In the future, index the workspace directories to find all projects
|
// In the future, index the workspace directories to find all projects
|
||||||
// and create a project database for each.
|
// and create a project database for each.
|
||||||
let system = LSPSystem::new(self.index.as_ref().unwrap().clone());
|
let system = LSPSystem::new(self.index.as_ref().unwrap().clone());
|
||||||
let system_path = workspace.root();
|
|
||||||
|
|
||||||
let root = system_path.to_path_buf();
|
|
||||||
let project = ProjectMetadata::discover(&root, &system)
|
let project = ProjectMetadata::discover(&root, &system)
|
||||||
.context("Failed to find project configuration")
|
.context("Failed to find project configuration")
|
||||||
.and_then(|mut metadata| {
|
.and_then(|mut metadata| {
|
||||||
|
@ -249,6 +270,11 @@ impl Session {
|
||||||
metadata
|
metadata
|
||||||
.apply_configuration_files(&system)
|
.apply_configuration_files(&system)
|
||||||
.context("Failed to apply configuration files")?;
|
.context("Failed to apply configuration files")?;
|
||||||
|
|
||||||
|
if let Some(overrides) = workspace.settings.project_options_overrides() {
|
||||||
|
metadata.apply_overrides(overrides);
|
||||||
|
}
|
||||||
|
|
||||||
ProjectDatabase::new(metadata, system)
|
ProjectDatabase::new(metadata, system)
|
||||||
.context("Failed to create project database")
|
.context("Failed to create project database")
|
||||||
});
|
});
|
||||||
|
@ -516,12 +542,19 @@ impl SessionSnapshot {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Workspaces {
|
pub(crate) struct Workspaces {
|
||||||
workspaces: BTreeMap<Url, Workspace>,
|
workspaces: BTreeMap<SystemPathBuf, Workspace>,
|
||||||
uninitialized: usize,
|
uninitialized: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Workspaces {
|
impl Workspaces {
|
||||||
pub(crate) fn register(&mut self, url: Url, options: ClientOptions) -> anyhow::Result<()> {
|
/// Registers a new workspace with the given URL and default settings for the workspace.
|
||||||
|
///
|
||||||
|
/// It's the caller's responsibility to later call [`initialize`] with the resolved settings
|
||||||
|
/// for this workspace. Registering and initializing a workspace is a two-step process because
|
||||||
|
/// the workspace are announced to the server during the `initialize` request, but the
|
||||||
|
/// resolved settings are only available after the client has responded to the `workspace/configuration`
|
||||||
|
/// request.
|
||||||
|
pub(crate) fn register(&mut self, url: Url, settings: ClientSettings) -> anyhow::Result<()> {
|
||||||
let path = url
|
let path = url
|
||||||
.to_file_path()
|
.to_file_path()
|
||||||
.map_err(|()| anyhow!("Workspace URL is not a file or directory: {url:?}"))?;
|
.map_err(|()| anyhow!("Workspace URL is not a file or directory: {url:?}"))?;
|
||||||
|
@ -530,35 +563,47 @@ impl Workspaces {
|
||||||
let system_path = SystemPathBuf::from_path_buf(path)
|
let system_path = SystemPathBuf::from_path_buf(path)
|
||||||
.map_err(|_| anyhow!("Workspace URL is not valid UTF8"))?;
|
.map_err(|_| anyhow!("Workspace URL is not valid UTF8"))?;
|
||||||
|
|
||||||
self.workspaces.insert(
|
self.workspaces
|
||||||
url,
|
.insert(system_path, Workspace { url, settings });
|
||||||
Workspace {
|
|
||||||
options,
|
|
||||||
root: system_path,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
self.uninitialized += 1;
|
self.uninitialized += 1;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes the workspace with the resolved client settings for the workspace.
|
||||||
|
///
|
||||||
|
/// ## Returns
|
||||||
|
///
|
||||||
|
/// `None` if URL doesn't map to a valid path or if the workspace is not registered.
|
||||||
pub(crate) fn initialize(
|
pub(crate) fn initialize(
|
||||||
&mut self,
|
&mut self,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
options: ClientOptions,
|
settings: ClientSettings,
|
||||||
) -> Option<&mut Workspace> {
|
) -> Option<(SystemPathBuf, &mut Workspace)> {
|
||||||
if let Some(workspace) = self.workspaces.get_mut(url) {
|
let path = url.to_file_path().ok()?;
|
||||||
workspace.options = options;
|
|
||||||
|
// Realistically I don't think this can fail because we got the path from a Url
|
||||||
|
let system_path = SystemPathBuf::from_path_buf(path).ok()?;
|
||||||
|
|
||||||
|
if let Some(workspace) = self.workspaces.get_mut(&system_path) {
|
||||||
|
workspace.settings = settings;
|
||||||
self.uninitialized -= 1;
|
self.uninitialized -= 1;
|
||||||
Some(workspace)
|
Some((system_path, workspace))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn for_path(&self, path: impl AsRef<SystemPath>) -> Option<&Workspace> {
|
||||||
|
self.workspaces
|
||||||
|
.range(..=path.as_ref().to_path_buf())
|
||||||
|
.next_back()
|
||||||
|
.map(|(_, db)| db)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn urls(&self) -> impl Iterator<Item = &Url> + '_ {
|
pub(crate) fn urls(&self) -> impl Iterator<Item = &Url> + '_ {
|
||||||
self.workspaces.keys()
|
self.workspaces.values().map(Workspace::url)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn all_initialized(&self) -> bool {
|
pub(crate) fn all_initialized(&self) -> bool {
|
||||||
|
@ -567,8 +612,8 @@ impl Workspaces {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Workspaces {
|
impl<'a> IntoIterator for &'a Workspaces {
|
||||||
type Item = (&'a Url, &'a Workspace);
|
type Item = (&'a SystemPathBuf, &'a Workspace);
|
||||||
type IntoIter = std::collections::btree_map::Iter<'a, Url, Workspace>;
|
type IntoIter = std::collections::btree_map::Iter<'a, SystemPathBuf, Workspace>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.workspaces.iter()
|
self.workspaces.iter()
|
||||||
|
@ -577,12 +622,61 @@ impl<'a> IntoIterator for &'a Workspaces {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Workspace {
|
pub(crate) struct Workspace {
|
||||||
root: SystemPathBuf,
|
/// The workspace root URL as sent by the client during initialization.
|
||||||
options: ClientOptions,
|
url: Url,
|
||||||
|
settings: ClientSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
pub(crate) fn root(&self) -> &SystemPath {
|
pub(crate) fn url(&self) -> &Url {
|
||||||
&self.root
|
&self.url
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn settings(&self) -> &ClientSettings {
|
||||||
|
&self.settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thin wrapper around the default project database that ensures it only gets initialized
|
||||||
|
/// when it's first accessed.
|
||||||
|
///
|
||||||
|
/// There are a few advantages to this:
|
||||||
|
///
|
||||||
|
/// 1. Salsa has a fast-path for query lookups for the first created database.
|
||||||
|
/// We really want that to be the actual project database and not our fallback database.
|
||||||
|
/// 2. The logs when the server starts can be confusing if it once shows it uses Python X (for the default db)
|
||||||
|
/// but then has another log that it uses Python Y (for the actual project db).
|
||||||
|
struct DefaultProject(std::sync::OnceLock<ProjectDatabase>);
|
||||||
|
|
||||||
|
impl DefaultProject {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
DefaultProject(std::sync::OnceLock::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get(&self, index: Option<&Arc<Index>>) -> &ProjectDatabase {
|
||||||
|
self.0.get_or_init(|| {
|
||||||
|
tracing::info!("Initialize default project");
|
||||||
|
|
||||||
|
let system = LSPSystem::new(index.unwrap().clone());
|
||||||
|
let metadata = ProjectMetadata::from_options(
|
||||||
|
Options::default(),
|
||||||
|
system.current_directory().to_path_buf(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
ProjectDatabase::new(metadata, system).unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_mut(&mut self, index: Option<&Arc<Index>>) -> &mut ProjectDatabase {
|
||||||
|
let _ = self.get(index);
|
||||||
|
|
||||||
|
// SAFETY: The `OnceLock` is guaranteed to be initialized at this point because
|
||||||
|
// we called `get` above, which initializes it if it wasn't already.
|
||||||
|
self.0.get_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn try_get_mut(&mut self) -> Option<&mut ProjectDatabase> {
|
||||||
|
self.0.get_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
use lsp_types::Url;
|
use lsp_types::Url;
|
||||||
use ruff_db::system::SystemPathBuf;
|
use ruff_db::system::SystemPathBuf;
|
||||||
|
use ruff_python_ast::PythonVersion;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use ty_project::metadata::Options;
|
||||||
|
use ty_project::metadata::options::ProjectOptionsOverrides;
|
||||||
|
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||||
|
|
||||||
use crate::logging::LogLevel;
|
use crate::logging::LogLevel;
|
||||||
use crate::session::settings::ClientSettings;
|
use crate::session::ClientSettings;
|
||||||
|
|
||||||
pub(crate) type WorkspaceOptionsMap = FxHashMap<Url, ClientOptions>;
|
pub(crate) type WorkspaceOptionsMap = FxHashMap<Url, ClientOptions>;
|
||||||
|
|
||||||
|
@ -43,7 +47,7 @@ struct WorkspaceOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a direct representation of the settings schema sent by the client.
|
/// This is a direct representation of the settings schema sent by the client.
|
||||||
#[derive(Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct ClientOptions {
|
pub(crate) struct ClientOptions {
|
||||||
|
@ -52,6 +56,8 @@ pub(crate) struct ClientOptions {
|
||||||
python: Option<Python>,
|
python: Option<Python>,
|
||||||
/// Diagnostic mode for the language server.
|
/// Diagnostic mode for the language server.
|
||||||
diagnostic_mode: Option<DiagnosticMode>,
|
diagnostic_mode: Option<DiagnosticMode>,
|
||||||
|
|
||||||
|
python_extension: Option<PythonExtension>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Diagnostic mode for the language server.
|
/// Diagnostic mode for the language server.
|
||||||
|
@ -75,6 +81,47 @@ impl DiagnosticMode {
|
||||||
impl ClientOptions {
|
impl ClientOptions {
|
||||||
/// Returns the client settings that are relevant to the language server.
|
/// Returns the client settings that are relevant to the language server.
|
||||||
pub(crate) fn into_settings(self) -> ClientSettings {
|
pub(crate) fn into_settings(self) -> ClientSettings {
|
||||||
|
let overrides = self.python_extension.and_then(|extension| {
|
||||||
|
let active_environment = extension.active_environment?;
|
||||||
|
|
||||||
|
let mut overrides = ProjectOptionsOverrides::new(None, Options::default());
|
||||||
|
|
||||||
|
overrides.fallback_python = if let Some(environment) = &active_environment.environment {
|
||||||
|
environment.folder_uri.to_file_path().ok().and_then(|path| {
|
||||||
|
Some(RelativePathBuf::python_extension(
|
||||||
|
SystemPathBuf::from_path_buf(path).ok()?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Some(RelativePathBuf::python_extension(
|
||||||
|
active_environment.executable.sys_prefix.clone(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
overrides.fallback_python_version =
|
||||||
|
active_environment.version.as_ref().and_then(|version| {
|
||||||
|
Some(RangedValue::python_extension(PythonVersion::from((
|
||||||
|
u8::try_from(version.major).ok()?,
|
||||||
|
u8::try_from(version.minor).ok()?,
|
||||||
|
))))
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(python) = &overrides.fallback_python {
|
||||||
|
tracing::debug!(
|
||||||
|
"Using the Python environment selected in the VS Code Python extension in case the configuration doesn't specify a Python environment: {python}",
|
||||||
|
python = python.path()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(version) = &overrides.fallback_python_version {
|
||||||
|
tracing::debug!(
|
||||||
|
"Using the Python version selected in the VS Code Python extension: {version} in case the configuration doesn't specify a Python version",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(overrides)
|
||||||
|
});
|
||||||
|
|
||||||
ClientSettings {
|
ClientSettings {
|
||||||
disable_language_services: self
|
disable_language_services: self
|
||||||
.python
|
.python
|
||||||
|
@ -82,6 +129,7 @@ impl ClientOptions {
|
||||||
.and_then(|ty| ty.disable_language_services)
|
.and_then(|ty| ty.disable_language_services)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
diagnostic_mode: self.diagnostic_mode.unwrap_or_default(),
|
diagnostic_mode: self.diagnostic_mode.unwrap_or_default(),
|
||||||
|
overrides,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,14 +138,63 @@ impl ClientOptions {
|
||||||
// would be useful to instead use `workspace/configuration` instead. This would be then used to get
|
// would be useful to instead use `workspace/configuration` instead. This would be then used to get
|
||||||
// all settings and not just the ones in "python.*".
|
// all settings and not just the ones in "python.*".
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct Python {
|
struct Python {
|
||||||
ty: Option<Ty>,
|
ty: Option<Ty>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct PythonExtension {
|
||||||
|
active_environment: Option<ActiveEnvironment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct ActiveEnvironment {
|
||||||
|
pub(crate) executable: PythonExecutable,
|
||||||
|
pub(crate) environment: Option<PythonEnvironment>,
|
||||||
|
pub(crate) version: Option<EnvironmentVersion>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct EnvironmentVersion {
|
||||||
|
pub(crate) major: i64,
|
||||||
|
pub(crate) minor: i64,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) patch: i64,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) sys_version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct PythonEnvironment {
|
||||||
|
pub(crate) folder_uri: Url,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) kind: String,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct PythonExecutable {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) uri: Url,
|
||||||
|
pub(crate) sys_prefix: SystemPathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct Ty {
|
struct Ty {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use super::options::DiagnosticMode;
|
use super::options::DiagnosticMode;
|
||||||
|
|
||||||
|
use ty_project::metadata::options::ProjectOptionsOverrides;
|
||||||
|
|
||||||
/// Resolved client settings for a specific document. These settings are meant to be
|
/// Resolved client settings for a specific document. These settings are meant to be
|
||||||
/// used directly by the server, and are *not* a 1:1 representation with how the client
|
/// used directly by the server, and are *not* a 1:1 representation with how the client
|
||||||
/// sends them.
|
/// sends them.
|
||||||
|
@ -8,6 +10,7 @@ use super::options::DiagnosticMode;
|
||||||
pub(crate) struct ClientSettings {
|
pub(crate) struct ClientSettings {
|
||||||
pub(super) disable_language_services: bool,
|
pub(super) disable_language_services: bool,
|
||||||
pub(super) diagnostic_mode: DiagnosticMode,
|
pub(super) diagnostic_mode: DiagnosticMode,
|
||||||
|
pub(super) overrides: Option<ProjectOptionsOverrides>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientSettings {
|
impl ClientSettings {
|
||||||
|
@ -18,4 +21,8 @@ impl ClientSettings {
|
||||||
pub(crate) fn diagnostic_mode(&self) -> DiagnosticMode {
|
pub(crate) fn diagnostic_mode(&self) -> DiagnosticMode {
|
||||||
self.diagnostic_mode
|
self.diagnostic_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn project_options_overrides(&self) -> Option<&ProjectOptionsOverrides> {
|
||||||
|
self.overrides.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue