Compare commits
15 Commits
david/rena
...
zb/dev-dri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40b4aa28f9 | ||
|
|
ea4bf00c23 | ||
|
|
7f4aa4b3fb | ||
|
|
34c98361ae | ||
|
|
38bb96a6c2 | ||
|
|
a014d55455 | ||
|
|
306f6f17a9 | ||
|
|
b233888f00 | ||
|
|
540cbd9085 | ||
|
|
0112f7f0e4 | ||
|
|
d0f0577ac7 | ||
|
|
dc56c33618 | ||
|
|
a95c18a8e1 | ||
|
|
e212dc2e8e | ||
|
|
c4f2eec865 |
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@@ -321,14 +321,30 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Dev Drive
|
||||
run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1
|
||||
|
||||
# actions/checkout does not let us clone into anywhere outside `github.workspace`, so we have to copy the clone
|
||||
- name: Copy Git Repo to Dev Drive
|
||||
env:
|
||||
RUFF_WORKSPACE: ${{ env.RUFF_WORKSPACE }}
|
||||
run: |
|
||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${env:RUFF_WORKSPACE}" -Recurse
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: ${{ env.RUFF_WORKSPACE }}
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
working-directory: ${{ env.RUFF_WORKSPACE }}
|
||||
run: rustup show
|
||||
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
working-directory: ${{ env.RUFF_WORKSPACE }}
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
|
||||
93
.github/workflows/setup-dev-drive.ps1
vendored
Normal file
93
.github/workflows/setup-dev-drive.ps1
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# Configures a drive for testing in CI.
|
||||
#
|
||||
# When using standard GitHub Actions runners, a `D:` drive is present and has
|
||||
# similar or better performance characteristics than a ReFS dev drive. Sometimes
|
||||
# using a larger runner is still more performant (e.g., when running the test
|
||||
# suite) and we need to create a dev drive. This script automatically configures
|
||||
# the appropriate drive.
|
||||
#
|
||||
# When using GitHub Actions' "larger runners", the `D:` drive is not present and
|
||||
# we create a DevDrive mount on `C:`. This is purported to be more performant
|
||||
# than an ReFS drive, though we did not see a change when we switched over.
|
||||
#
|
||||
# When using Depot runners, the underling infrastructure is EC2, which does not
|
||||
# support Hyper-V. The `New-VHD` commandlet only works with Hyper-V, but we can
|
||||
# create a ReFS drive using `diskpart` and `format` directory. We cannot use a
|
||||
# DevDrive, as that also requires Hyper-V. The Depot runners use `D:` already,
|
||||
# so we must check if it's a Depot runner first, and we use `V:` as the target
|
||||
# instead.
|
||||
|
||||
|
||||
if ($env:DEPOT_RUNNER -eq "1") {
|
||||
Write-Output "DEPOT_RUNNER detected, setting up custom dev drive..."
|
||||
|
||||
# Create VHD and configure drive using diskpart
|
||||
$vhdPath = "C:\ruff_dev_drive.vhdx"
|
||||
@"
|
||||
create vdisk file="$vhdPath" maximum=20480 type=expandable
|
||||
attach vdisk
|
||||
create partition primary
|
||||
active
|
||||
assign letter=V
|
||||
"@ | diskpart
|
||||
|
||||
# Format the drive as ReFS
|
||||
format V: /fs:ReFS /q /y
|
||||
$Drive = "V:"
|
||||
|
||||
Write-Output "Custom dev drive created at $Drive"
|
||||
} elseif (Test-Path "D:\") {
|
||||
# Note `Get-PSDrive` is not sufficient because the drive letter is assigned.
|
||||
Write-Output "Using existing drive at D:"
|
||||
$Drive = "D:"
|
||||
} else {
|
||||
# The size (20 GB) is chosen empirically to be large enough for our
|
||||
# workflows; larger drives can take longer to set up.
|
||||
$Volume = New-VHD -Path C:/ruff_dev_drive.vhdx -SizeBytes 20GB |
|
||||
Mount-VHD -Passthru |
|
||||
Initialize-Disk -Passthru |
|
||||
New-Partition -AssignDriveLetter -UseMaximumSize |
|
||||
Format-Volume -DevDrive -Confirm:$false -Force
|
||||
|
||||
$Drive = "$($Volume.DriveLetter):"
|
||||
|
||||
# Set the drive as trusted
|
||||
# See https://learn.microsoft.com/en-us/windows/dev-drive/#how-do-i-designate-a-dev-drive-as-trusted
|
||||
fsutil devdrv trust $Drive
|
||||
|
||||
# Disable antivirus filtering on dev drives
|
||||
# See https://learn.microsoft.com/en-us/windows/dev-drive/#how-do-i-configure-additional-filters-on-dev-drive
|
||||
fsutil devdrv enable /disallowAv
|
||||
|
||||
# Remount so the changes take effect
|
||||
Dismount-VHD -Path C:/ruff_dev_drive.vhdx
|
||||
Mount-VHD -Path C:/ruff_dev_drive.vhdx
|
||||
|
||||
# Show some debug information
|
||||
Write-Output $Volume
|
||||
fsutil devdrv query $Drive
|
||||
|
||||
Write-Output "Using Dev Drive at $Volume"
|
||||
}
|
||||
|
||||
$Tmp = "$($Drive)\ruff-tmp"
|
||||
|
||||
# Create the directory ahead of time in an attempt to avoid race-conditions
|
||||
New-Item $Tmp -ItemType Directory
|
||||
|
||||
# Move Cargo to the dev drive
|
||||
New-Item -Path "$($Drive)/.cargo/bin" -ItemType Directory -Force
|
||||
if (Test-Path "C:/Users/runneradmin/.cargo") {
|
||||
Copy-Item -Path "C:/Users/runneradmin/.cargo/*" -Destination "$($Drive)/.cargo/" -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Output `
|
||||
"DEV_DRIVE=$($Drive)" `
|
||||
"TMP=$($Tmp)" `
|
||||
"TEMP=$($Tmp)" `
|
||||
"UV_INTERNAL__TEST_DIR=$($Tmp)" `
|
||||
"RUSTUP_HOME=$($Drive)/.rustup" `
|
||||
"CARGO_HOME=$($Drive)/.cargo" `
|
||||
"RUFF_WORKSPACE=$($Drive)/ruff" `
|
||||
"PATH=$($Drive)/.cargo/bin;$env:PATH" `
|
||||
>> $env:GITHUB_ENV
|
||||
@@ -75,7 +75,7 @@ impl AlwaysFixableViolation for TypedArgumentDefaultInStub {
|
||||
/// ## Example
|
||||
///
|
||||
/// ```pyi
|
||||
/// def foo(arg=[]) -> None: ...
|
||||
/// def foo(arg=bar()) -> None: ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
@@ -120,7 +120,7 @@ impl AlwaysFixableViolation for ArgumentDefaultInStub {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```pyi
|
||||
/// foo: str = "..."
|
||||
/// foo: str = bar()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
|
||||
@@ -919,6 +919,9 @@ fn directory_renamed() -> anyhow::Result<()> {
|
||||
|
||||
#[test]
|
||||
fn directory_deleted() -> anyhow::Result<()> {
|
||||
use ruff_db::testing::setup_logging;
|
||||
let _logging = setup_logging();
|
||||
|
||||
let mut case = setup([
|
||||
("bar.py", "import sub.a"),
|
||||
("sub/__init__.py", ""),
|
||||
|
||||
@@ -83,15 +83,20 @@ impl ProjectDatabase {
|
||||
|
||||
/// Checks all open files in the project and its dependencies.
|
||||
pub fn check(&self) -> Vec<Diagnostic> {
|
||||
let mut reporter = DummyReporter;
|
||||
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter);
|
||||
self.project().check(self, reporter)
|
||||
self.check_with_mode(CheckMode::OpenFiles)
|
||||
}
|
||||
|
||||
/// Checks all open files in the project and its dependencies, using the given reporter.
|
||||
pub fn check_with_reporter(&self, reporter: &mut dyn Reporter) -> Vec<Diagnostic> {
|
||||
let reporter = AssertUnwindSafe(reporter);
|
||||
self.project().check(self, reporter)
|
||||
self.project().check(self, CheckMode::OpenFiles, reporter)
|
||||
}
|
||||
|
||||
/// Check the project with the given mode.
|
||||
pub fn check_with_mode(&self, mode: CheckMode) -> Vec<Diagnostic> {
|
||||
let mut reporter = DummyReporter;
|
||||
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn Reporter);
|
||||
self.project().check(self, mode, reporter)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
@@ -157,6 +162,17 @@ impl std::fmt::Debug for ProjectDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CheckMode {
|
||||
/// Checks only the open files in the project.
|
||||
OpenFiles,
|
||||
|
||||
/// Checks all files in the project, ignoring the open file set.
|
||||
///
|
||||
/// This includes virtual files, such as those created by the language server.
|
||||
AllFiles,
|
||||
}
|
||||
|
||||
/// Stores memory usage information.
|
||||
pub struct SalsaMemoryDump {
|
||||
total_fields: usize,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::glob::{GlobFilterCheckMode, IncludeResult};
|
||||
use crate::metadata::options::{OptionDiagnostic, ToSettingsError};
|
||||
use crate::walk::{ProjectFilesFilter, ProjectFilesWalker};
|
||||
pub use db::{Db, ProjectDatabase, SalsaMemoryDump};
|
||||
pub use db::{CheckMode, Db, ProjectDatabase, SalsaMemoryDump};
|
||||
use files::{Index, Indexed, IndexedFiles};
|
||||
use metadata::settings::Settings;
|
||||
pub use metadata::{ProjectMetadata, ProjectMetadataError};
|
||||
@@ -214,6 +214,7 @@ impl Project {
|
||||
pub(crate) fn check(
|
||||
self,
|
||||
db: &ProjectDatabase,
|
||||
mode: CheckMode,
|
||||
mut reporter: AssertUnwindSafe<&mut dyn Reporter>,
|
||||
) -> Vec<Diagnostic> {
|
||||
let project_span = tracing::debug_span!("Project::check");
|
||||
@@ -228,7 +229,11 @@ impl Project {
|
||||
.map(OptionDiagnostic::to_diagnostic),
|
||||
);
|
||||
|
||||
let files = ProjectFiles::new(db, self);
|
||||
let files = match mode {
|
||||
CheckMode::OpenFiles => ProjectFiles::new(db, self),
|
||||
// TODO: Consider open virtual files as well
|
||||
CheckMode::AllFiles => ProjectFiles::Indexed(self.files(db)),
|
||||
};
|
||||
reporter.set_files(files.len());
|
||||
|
||||
diagnostics.extend(
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
# Glossary
|
||||
|
||||
This document is meant to serve as a reference for ty developers, explaining the meaning of various
|
||||
terms used in the codebase and documentation.
|
||||
|
||||
## Local type, public type, imported type, attribute type, nonlocal type
|
||||
|
||||
To understand the difference between these terms, consider the following example:
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
x_imported = 1
|
||||
|
||||
reveal_type(x_imported) # revealed: Literal[1]
|
||||
|
||||
x_imported = 2
|
||||
|
||||
reveal_type(x_imported) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from module import x_imported
|
||||
|
||||
class C:
|
||||
x_attribute = 1
|
||||
x_attribute = 2
|
||||
|
||||
reveal_type(x_attribute) # revealed: Literal[2]
|
||||
|
||||
x_nonlocal = 1
|
||||
|
||||
def f():
|
||||
x_local = 1
|
||||
|
||||
reveal_type(x_local) # revealed: Literal[1]
|
||||
|
||||
# All of the following are considered "public" types:
|
||||
reveal_type(x_imported) # revealed: Unknown | Literal[2]
|
||||
reveal_type(C.x_attribute) # revealed: Unknown | Literal[2]
|
||||
reveal_type(x_nonlocal) # revealed: Unknown | Literal[1, 2]
|
||||
|
||||
x_local = 2
|
||||
|
||||
f()
|
||||
|
||||
x_nonlocal = 2
|
||||
|
||||
f()
|
||||
```
|
||||
|
||||
The first distinction we make is between "local types" and "public types".
|
||||
|
||||
A **local type** refers to the type of a symbol that is defined and accessed within the same scope
|
||||
(`x_local`). Local types are subject to flow-sensitive analysis: the type of `x_local` at the usage
|
||||
site in `f` is `Literal[1]`, despite the fact that `x_local` is later reassigned to `2`.
|
||||
|
||||
In contrast, the **public type** of a symbol refers to the type that is visible to external scopes.
|
||||
Since we can not generally analyze the full control flow of the program, we assume that these
|
||||
symbols could be modified by code that is not currently visible to us. We therefore use the union
|
||||
with `Unknown` for public uses of these symbols.
|
||||
|
||||
Public types can be further subdivided into:
|
||||
|
||||
- **Imported types**: The type of a symbol that is imported from another module. Notice how the
|
||||
imported type of `x_imported` in `main.py` only includes `Literal[2]`, but not `Literal[1]`. The
|
||||
imported type always refers to the local type that the symbol would have at the *end of the
|
||||
scope* in which it was defined.
|
||||
- **Attribute types**: The type of an attribute expression such as `C.x_attribute`. Similar to
|
||||
imported types, the attribute type refers to the local type of the symbol at the *end of the
|
||||
scope* in which it was defined.
|
||||
- **Nonlocal types**: Refers to the type of a symbol that is defined in an *enclosing scope*. In
|
||||
contrast to imported and attribute types, we consider *all reachable bindings* of the symbol in
|
||||
the enclosing scope. This is needed in the example above because `f()` is called from different
|
||||
positions in the enclosing scope. The nonlocal type therefore needs to include both `Literal[1]`
|
||||
and `Literal[2]`.
|
||||
|
||||
To learn more, see:
|
||||
|
||||
- [Public type of undeclared symbols](doc/public_type_undeclared_symbols.md)
|
||||
- [Nonlocal types](nonlocal_types.md)
|
||||
- [Boundness and declaredness: public uses](boundness_declaredness/public.md)
|
||||
@@ -23,7 +23,7 @@ reveal_type(a.z) # revealed: Literal[0]
|
||||
|
||||
# Make sure that we infer the narrowed type for eager
|
||||
# scopes (class, comprehension) and the non-narrowed
|
||||
# nonlocal type for lazy scopes (function)
|
||||
# public type for lazy scopes (function)
|
||||
class _:
|
||||
reveal_type(a.x) # revealed: Literal[0]
|
||||
reveal_type(a.y) # revealed: Literal[0]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Nonlocal types
|
||||
# Public types
|
||||
|
||||
## Basic
|
||||
|
||||
The "nonlocal type" of a symbol refers to the type that is inferred in a nested scope for a symbol
|
||||
The "public type" of a symbol refers to the type that is inferred in a nested scope for a symbol
|
||||
defined in an outer enclosing scope. Since it is not generally possible to analyze the full control
|
||||
flow of a program, we currently make the simplifying assumption that an inner scope (such as the
|
||||
`inner` function below) could be executed at any position in the enclosing scope. The nonlocal type
|
||||
`inner` function below) could be executed at any position in the enclosing scope. The public type
|
||||
should therefore be the union of all possible types that the symbol could have.
|
||||
|
||||
In the following example, depending on when `inner()` is called, the type of `x` could either be `A`
|
||||
@@ -33,8 +33,7 @@ def outer() -> None:
|
||||
inner()
|
||||
```
|
||||
|
||||
Similarly, if control flow in the outer scope can split, the nonlocal type of `x` should reflect
|
||||
that:
|
||||
Similarly, if control flow in the outer scope can split, the public type of `x` should reflect that:
|
||||
|
||||
```py
|
||||
def outer(flag: bool) -> None:
|
||||
@@ -56,7 +55,7 @@ def outer(flag: bool) -> None:
|
||||
inner()
|
||||
```
|
||||
|
||||
If a binding is not reachable, it is not considered in the nonlocal type:
|
||||
If a binding is not reachable, it is not considered in the public type:
|
||||
|
||||
```py
|
||||
def outer() -> None:
|
||||
@@ -118,8 +117,8 @@ def outer(flag: bool) -> None:
|
||||
inner()
|
||||
```
|
||||
|
||||
The nonlocal type is available, even if the end of the outer scope is unreachable. This is a
|
||||
regression test. A previous version of ty used the end-of-scope position to determine the nonlocal
|
||||
The public type is available, even if the end of the outer scope is unreachable. This is a
|
||||
regression test. A previous version of ty used the end-of-scope position to determine the public
|
||||
type, which would have resulted in incorrect type inference here:
|
||||
|
||||
```py
|
||||
@@ -216,7 +215,7 @@ def _():
|
||||
```
|
||||
|
||||
This pattern appears frequently with conditional imports. The `import` statement is both a
|
||||
declaration and a binding, but we still add `None` to the nonlocal type union in a situation like
|
||||
declaration and a binding, but we still add `None` to the public type union in a situation like
|
||||
this:
|
||||
|
||||
```py
|
||||
@@ -4,7 +4,7 @@ expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: dataclasses.md - Dataclasses - `dataclasses.KW_ONLY`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses.md
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
@@ -241,9 +241,9 @@ def f():
|
||||
|
||||
### Use of variable in nested function
|
||||
|
||||
This is a regression test for a behavior that previously caused problems when the nonlocal type
|
||||
still referred to the end-of-scope, which would result in an unresolved-reference error here since
|
||||
the end of the scope is unreachable.
|
||||
This is a regression test for a behavior that previously caused problems when the public type still
|
||||
referred to the end-of-scope, which would result in an unresolved-reference error here since the end
|
||||
of the scope is unreachable.
|
||||
|
||||
```py
|
||||
def outer():
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
//! resolving `f` at each of the three numbered positions will give you a `FunctionType`, which
|
||||
//! wraps a `FunctionLiteral`, which contain `OverloadLiteral`s only for the definitions that
|
||||
//! appear before that position. We rely on the fact that later definitions shadow earlier ones, so
|
||||
//! the final type of `f` is resolved at position 3, correctly giving you all of the overloads
|
||||
//! the public type of `f` is resolved at position 3, correctly giving you all of the overloads
|
||||
//! (and the implementation).
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
//! are written to `stderr` by default, which should appear in the logs for most LSP clients. A
|
||||
//! `logFile` path can also be specified in the settings, and output will be directed there
|
||||
//! instead.
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use serde::Deserialize;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::Layer;
|
||||
@@ -15,14 +14,14 @@ use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::fmt::writer::BoxMakeWriter;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
pub(crate) fn init_logging(log_level: LogLevel, log_file: Option<&Path>) {
|
||||
pub(crate) fn init_logging(log_level: LogLevel, log_file: Option<&SystemPath>) {
|
||||
let log_file = log_file
|
||||
.map(|path| {
|
||||
// this expands `logFile` so that tildes and environment variables
|
||||
// are replaced with their values, if possible.
|
||||
if let Some(expanded) = shellexpand::full(&path.to_string_lossy())
|
||||
if let Some(expanded) = shellexpand::full(&path.to_string())
|
||||
.ok()
|
||||
.and_then(|path| PathBuf::from_str(&path).ok())
|
||||
.map(|path| SystemPathBuf::from(&*path))
|
||||
{
|
||||
expanded
|
||||
} else {
|
||||
@@ -33,14 +32,11 @@ pub(crate) fn init_logging(log_level: LogLevel, log_file: Option<&Path>) {
|
||||
std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&path)
|
||||
.open(path.as_std_path())
|
||||
.map_err(|err| {
|
||||
#[expect(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!(
|
||||
"Failed to open file at {} for logging: {err}",
|
||||
path.display()
|
||||
);
|
||||
eprintln!("Failed to open file at {path} for logging: {err}");
|
||||
}
|
||||
})
|
||||
.ok()
|
||||
|
||||
@@ -173,6 +173,7 @@ impl Server {
|
||||
diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
|
||||
identifier: Some(crate::DIAGNOSTIC_NAME.into()),
|
||||
inter_file_dependencies: true,
|
||||
workspace_diagnostics: true,
|
||||
..Default::default()
|
||||
})),
|
||||
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
||||
|
||||
@@ -28,23 +28,28 @@ pub(super) fn request(req: server::Request) -> Task {
|
||||
let id = req.id.clone();
|
||||
|
||||
match req.method.as_str() {
|
||||
requests::DocumentDiagnosticRequestHandler::METHOD => background_request_task::<
|
||||
requests::DocumentDiagnosticRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::DocumentDiagnosticRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
requests::GotoTypeDefinitionRequestHandler::METHOD => background_request_task::<
|
||||
requests::WorkspaceDiagnosticRequestHandler::METHOD => background_request_task::<
|
||||
requests::WorkspaceDiagnosticRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
requests::GotoTypeDefinitionRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::GotoTypeDefinitionRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
requests::HoverRequestHandler::METHOD => background_request_task::<
|
||||
requests::HoverRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::HoverRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
requests::InlayHintRequestHandler::METHOD => background_request_task::<
|
||||
requests::InlayHintRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::InlayHintRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
requests::CompletionRequestHandler::METHOD => background_request_task::<
|
||||
requests::CompletionRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::CompletionRequestHandler,
|
||||
>(
|
||||
req, BackgroundSchedule::LatencySensitive
|
||||
@@ -135,7 +140,51 @@ where
|
||||
}))
|
||||
}
|
||||
|
||||
fn background_request_task<R: traits::BackgroundDocumentRequestHandler>(
|
||||
fn background_request_task<R: traits::BackgroundRequestHandler>(
|
||||
req: server::Request,
|
||||
schedule: BackgroundSchedule,
|
||||
) -> Result<Task>
|
||||
where
|
||||
<<R as RequestHandler>::RequestType as Request>::Params: UnwindSafe,
|
||||
{
|
||||
let retry = R::RETRY_ON_CANCELLATION.then(|| req.clone());
|
||||
let (id, params) = cast_request::<R>(req)?;
|
||||
|
||||
Ok(Task::background(schedule, move |session: &Session| {
|
||||
let cancellation_token = session
|
||||
.request_queue()
|
||||
.incoming()
|
||||
.cancellation_token(&id)
|
||||
.expect("request should have been tested for cancellation before scheduling");
|
||||
|
||||
let snapshot = session.take_workspace_snapshot();
|
||||
|
||||
Box::new(move |client| {
|
||||
let _span = tracing::debug_span!("request", %id, method = R::METHOD).entered();
|
||||
|
||||
// Test again if the request was cancelled since it was scheduled on the background task
|
||||
// and, if so, return early
|
||||
if cancellation_token.is_cancelled() {
|
||||
tracing::trace!(
|
||||
"Ignoring request id={id} method={} because it was cancelled",
|
||||
R::METHOD
|
||||
);
|
||||
|
||||
// We don't need to send a response here because the `cancel` notification
|
||||
// handler already responded with a message.
|
||||
return;
|
||||
}
|
||||
|
||||
let result = ruff_db::panic::catch_unwind(|| R::run(snapshot, client, params));
|
||||
|
||||
if let Some(response) = request_result_to_response::<R>(&id, client, result, retry) {
|
||||
respond::<R>(&id, response, client);
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn background_document_request_task<R: traits::BackgroundDocumentRequestHandler>(
|
||||
req: server::Request,
|
||||
schedule: BackgroundSchedule,
|
||||
) -> Result<Task>
|
||||
@@ -168,7 +217,7 @@ where
|
||||
};
|
||||
|
||||
let Some(snapshot) = session.take_snapshot(url) else {
|
||||
tracing::warn!("Ignoring request because snapshot for path `{path:?}` doesn't exist.");
|
||||
tracing::warn!("Ignoring request because snapshot for path `{path:?}` doesn't exist");
|
||||
return Box::new(|_| {});
|
||||
};
|
||||
|
||||
@@ -209,7 +258,7 @@ fn request_result_to_response<R>(
|
||||
request: Option<lsp_server::Request>,
|
||||
) -> Option<Result<<<R as RequestHandler>::RequestType as Request>::Result>>
|
||||
where
|
||||
R: traits::BackgroundDocumentRequestHandler,
|
||||
R: traits::RetriableRequestHandler,
|
||||
{
|
||||
match result {
|
||||
Ok(response) => Some(response),
|
||||
|
||||
@@ -166,7 +166,7 @@ pub(super) fn compute_diagnostics(
|
||||
|
||||
/// Converts the tool specific [`Diagnostic`][ruff_db::diagnostic::Diagnostic] to an LSP
|
||||
/// [`Diagnostic`].
|
||||
fn to_lsp_diagnostic(
|
||||
pub(super) fn to_lsp_diagnostic(
|
||||
db: &dyn Db,
|
||||
diagnostic: &ruff_db::diagnostic::Diagnostic,
|
||||
encoding: PositionEncoding,
|
||||
|
||||
@@ -41,7 +41,12 @@ impl SyncNotificationHandler for DidCloseTextDocumentHandler {
|
||||
);
|
||||
}
|
||||
|
||||
clear_diagnostics(&key, client);
|
||||
if !session.global_settings().diagnostic_mode().is_workspace() {
|
||||
// The server needs to clear the diagnostics regardless of whether the client supports
|
||||
// pull diagnostics or not. This is because the client only has the capability to fetch
|
||||
// the diagnostics but does not automatically clear them when a document is closed.
|
||||
clear_diagnostics(&key, client);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ mod goto_type_definition;
|
||||
mod hover;
|
||||
mod inlay_hints;
|
||||
mod shutdown;
|
||||
mod workspace_diagnostic;
|
||||
|
||||
pub(super) use completion::CompletionRequestHandler;
|
||||
pub(super) use diagnostic::DocumentDiagnosticRequestHandler;
|
||||
@@ -11,3 +12,4 @@ pub(super) use goto_type_definition::GotoTypeDefinitionRequestHandler;
|
||||
pub(super) use hover::HoverRequestHandler;
|
||||
pub(super) use inlay_hints::InlayHintRequestHandler;
|
||||
pub(super) use shutdown::ShutdownHandler;
|
||||
pub(super) use workspace_diagnostic::WorkspaceDiagnosticRequestHandler;
|
||||
|
||||
@@ -8,7 +8,9 @@ use ty_project::ProjectDatabase;
|
||||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::PositionExt;
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
|
||||
pub(crate) struct CompletionRequestHandler;
|
||||
@@ -18,8 +20,6 @@ impl RequestHandler for CompletionRequestHandler {
|
||||
}
|
||||
|
||||
impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
|
||||
const RETRY_ON_CANCELLATION: bool = true;
|
||||
|
||||
fn document_url(params: &CompletionParams) -> Cow<Url> {
|
||||
Cow::Borrowed(¶ms.text_document_position.text_document.uri)
|
||||
}
|
||||
@@ -65,3 +65,7 @@ impl BackgroundDocumentRequestHandler for CompletionRequestHandler {
|
||||
Ok(Some(response))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for CompletionRequestHandler {
|
||||
const RETRY_ON_CANCELLATION: bool = true;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use lsp_types::{
|
||||
|
||||
use crate::server::Result;
|
||||
use crate::server::api::diagnostics::{Diagnostics, compute_diagnostics};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::DocumentSnapshot;
|
||||
use crate::session::client::Client;
|
||||
use ty_project::ProjectDatabase;
|
||||
@@ -43,7 +45,9 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler {
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for DocumentDiagnosticRequestHandler {
|
||||
fn salsa_cancellation_error() -> lsp_server::ResponseError {
|
||||
lsp_server::ResponseError {
|
||||
code: lsp_server::ErrorCode::ServerCancelled as i32,
|
||||
|
||||
@@ -8,7 +8,9 @@ use ty_project::ProjectDatabase;
|
||||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::{PositionExt, ToLink};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
|
||||
pub(crate) struct GotoTypeDefinitionRequestHandler;
|
||||
@@ -70,3 +72,5 @@ impl BackgroundDocumentRequestHandler for GotoTypeDefinitionRequestHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for GotoTypeDefinitionRequestHandler {}
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::borrow::Cow;
|
||||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::{PositionExt, ToRangeExt};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
use lsp_types::request::HoverRequest;
|
||||
use lsp_types::{HoverContents, HoverParams, MarkupContent, Url};
|
||||
@@ -73,3 +75,5 @@ impl BackgroundDocumentRequestHandler for HoverRequestHandler {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for HoverRequestHandler {}
|
||||
|
||||
@@ -2,7 +2,9 @@ use std::borrow::Cow;
|
||||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::{RangeExt, TextSizeExt};
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
use lsp_types::request::InlayHintRequest;
|
||||
use lsp_types::{InlayHintParams, Url};
|
||||
@@ -64,3 +66,5 @@ impl BackgroundDocumentRequestHandler for InlayHintRequestHandler {
|
||||
Ok(Some(inlay_hints))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for InlayHintRequestHandler {}
|
||||
|
||||
108
crates/ty_server/src/server/api/requests/workspace_diagnostic.rs
Normal file
108
crates/ty_server/src/server/api/requests/workspace_diagnostic.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use lsp_types::request::WorkspaceDiagnosticRequest;
|
||||
use lsp_types::{
|
||||
FullDocumentDiagnosticReport, Url, WorkspaceDiagnosticParams, WorkspaceDiagnosticReport,
|
||||
WorkspaceDiagnosticReportResult, WorkspaceDocumentDiagnosticReport,
|
||||
WorkspaceFullDocumentDiagnosticReport,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use ty_project::CheckMode;
|
||||
|
||||
use crate::server::Result;
|
||||
use crate::server::api::diagnostics::to_lsp_diagnostic;
|
||||
use crate::server::api::traits::{
|
||||
BackgroundRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::WorkspaceSnapshot;
|
||||
use crate::session::client::Client;
|
||||
use crate::system::file_to_url;
|
||||
|
||||
pub(crate) struct WorkspaceDiagnosticRequestHandler;
|
||||
|
||||
impl RequestHandler for WorkspaceDiagnosticRequestHandler {
|
||||
type RequestType = WorkspaceDiagnosticRequest;
|
||||
}
|
||||
|
||||
impl BackgroundRequestHandler for WorkspaceDiagnosticRequestHandler {
|
||||
fn run(
|
||||
snapshot: WorkspaceSnapshot,
|
||||
_client: &Client,
|
||||
_params: WorkspaceDiagnosticParams,
|
||||
) -> Result<WorkspaceDiagnosticReportResult> {
|
||||
let index = snapshot.index();
|
||||
|
||||
if !index.global_settings().diagnostic_mode().is_workspace() {
|
||||
tracing::debug!("Workspace diagnostics is disabled; returning empty report");
|
||||
return Ok(WorkspaceDiagnosticReportResult::Report(
|
||||
WorkspaceDiagnosticReport { items: vec![] },
|
||||
));
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
|
||||
for db in snapshot.projects() {
|
||||
let diagnostics = db.check_with_mode(CheckMode::AllFiles);
|
||||
|
||||
// Group diagnostics by URL
|
||||
let mut diagnostics_by_url: FxHashMap<Url, Vec<_>> = FxHashMap::default();
|
||||
|
||||
for diagnostic in diagnostics {
|
||||
if let Some(span) = diagnostic.primary_span() {
|
||||
let file = span.expect_ty_file();
|
||||
let Some(url) = file_to_url(db, file) else {
|
||||
tracing::debug!("Failed to convert file to URL at {}", file.path(db));
|
||||
continue;
|
||||
};
|
||||
diagnostics_by_url.entry(url).or_default().push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
items.reserve(diagnostics_by_url.len());
|
||||
|
||||
// Convert to workspace diagnostic report format
|
||||
for (url, file_diagnostics) in diagnostics_by_url {
|
||||
let version = index
|
||||
.key_from_url(url.clone())
|
||||
.ok()
|
||||
.and_then(|key| index.make_document_ref(&key))
|
||||
.map(|doc| i64::from(doc.version()));
|
||||
|
||||
// Convert diagnostics to LSP format
|
||||
let lsp_diagnostics = file_diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| {
|
||||
to_lsp_diagnostic(db, &diagnostic, snapshot.position_encoding())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
items.push(WorkspaceDocumentDiagnosticReport::Full(
|
||||
WorkspaceFullDocumentDiagnosticReport {
|
||||
uri: url,
|
||||
version,
|
||||
full_document_diagnostic_report: FullDocumentDiagnosticReport {
|
||||
// TODO: We don't implement result ID caching yet
|
||||
result_id: None,
|
||||
items: lsp_diagnostics,
|
||||
},
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(WorkspaceDiagnosticReportResult::Report(
|
||||
WorkspaceDiagnosticReport { items },
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for WorkspaceDiagnosticRequestHandler {
|
||||
fn salsa_cancellation_error() -> lsp_server::ResponseError {
|
||||
lsp_server::ResponseError {
|
||||
code: lsp_server::ErrorCode::ServerCancelled as i32,
|
||||
message: "server cancelled the request".to_owned(),
|
||||
data: serde_json::to_value(lsp_types::DiagnosticServerCancellationData {
|
||||
retrigger_request: true,
|
||||
})
|
||||
.ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//! A stateful LSP implementation that calls into the ty API.
|
||||
|
||||
use crate::session::client::Client;
|
||||
use crate::session::{DocumentSnapshot, Session};
|
||||
use crate::session::{DocumentSnapshot, Session, WorkspaceSnapshot};
|
||||
|
||||
use lsp_types::notification::Notification as LSPNotification;
|
||||
use lsp_types::request::Request;
|
||||
@@ -25,11 +25,24 @@ pub(super) trait SyncRequestHandler: RequestHandler {
|
||||
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;
|
||||
}
|
||||
|
||||
/// A request handler that can be run on a background thread.
|
||||
pub(super) trait BackgroundDocumentRequestHandler: RequestHandler {
|
||||
/// Whether this request be retried if it was cancelled due to a modification to the Salsa database.
|
||||
pub(super) trait RetriableRequestHandler: RequestHandler {
|
||||
/// Whether this request can be cancelled if the Salsa database is modified.
|
||||
const RETRY_ON_CANCELLATION: bool = false;
|
||||
|
||||
/// The error to return if the request was cancelled due to a modification to the Salsa database.
|
||||
fn salsa_cancellation_error() -> lsp_server::ResponseError {
|
||||
lsp_server::ResponseError {
|
||||
code: lsp_server::ErrorCode::ContentModified as i32,
|
||||
message: "content modified".to_string(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A request handler that can be run on a background thread.
|
||||
///
|
||||
/// This handler is specific to requests that operate on a single document.
|
||||
pub(super) trait BackgroundDocumentRequestHandler: RetriableRequestHandler {
|
||||
fn document_url(
|
||||
params: &<<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> std::borrow::Cow<lsp_types::Url>;
|
||||
@@ -40,14 +53,15 @@ pub(super) trait BackgroundDocumentRequestHandler: RequestHandler {
|
||||
client: &Client,
|
||||
params: <<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;
|
||||
}
|
||||
|
||||
fn salsa_cancellation_error() -> lsp_server::ResponseError {
|
||||
lsp_server::ResponseError {
|
||||
code: lsp_server::ErrorCode::ContentModified as i32,
|
||||
message: "content modified".to_string(),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
/// A request handler that can be run on a background thread.
|
||||
pub(super) trait BackgroundRequestHandler: RetriableRequestHandler {
|
||||
fn run(
|
||||
snapshot: WorkspaceSnapshot,
|
||||
client: &Client,
|
||||
params: <<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;
|
||||
}
|
||||
|
||||
/// A supertrait for any server notification handler.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
@@ -224,6 +224,14 @@ impl Session {
|
||||
self.index().key_from_url(url)
|
||||
}
|
||||
|
||||
pub(crate) fn take_workspace_snapshot(&self) -> WorkspaceSnapshot {
|
||||
WorkspaceSnapshot {
|
||||
projects: AssertUnwindSafe(self.projects.values().cloned().collect()),
|
||||
index: self.index.clone().unwrap(),
|
||||
position_encoding: self.position_encoding,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn initialize_workspaces(&mut self, workspace_settings: Vec<(Url, ClientOptions)>) {
|
||||
assert!(!self.workspaces.all_initialized());
|
||||
|
||||
@@ -235,14 +243,7 @@ impl Session {
|
||||
// In the future, index the workspace directories to find all projects
|
||||
// and create a project database for each.
|
||||
let system = LSPSystem::new(self.index.as_ref().unwrap().clone());
|
||||
|
||||
let Some(system_path) = SystemPath::from_std_path(workspace.root()) else {
|
||||
tracing::warn!(
|
||||
"Ignore workspace `{}` because it's root contains non UTF8 characters",
|
||||
workspace.root().display()
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let system_path = workspace.root();
|
||||
|
||||
let root = system_path.to_path_buf();
|
||||
let project = ProjectMetadata::discover(&root, &system)
|
||||
@@ -382,6 +383,10 @@ impl Session {
|
||||
pub(crate) fn client_capabilities(&self) -> &ResolvedClientCapabilities {
|
||||
&self.resolved_client_capabilities
|
||||
}
|
||||
|
||||
pub(crate) fn global_settings(&self) -> Arc<ClientSettings> {
|
||||
self.index().global_settings()
|
||||
}
|
||||
}
|
||||
|
||||
/// A guard that holds the only reference to the index and allows modifying it.
|
||||
@@ -461,6 +466,27 @@ impl DocumentSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
/// An immutable snapshot of the current state of [`Session`].
|
||||
pub(crate) struct WorkspaceSnapshot {
|
||||
projects: AssertUnwindSafe<Vec<ProjectDatabase>>,
|
||||
index: Arc<index::Index>,
|
||||
position_encoding: PositionEncoding,
|
||||
}
|
||||
|
||||
impl WorkspaceSnapshot {
|
||||
pub(crate) fn projects(&self) -> &[ProjectDatabase] {
|
||||
&self.projects
|
||||
}
|
||||
|
||||
pub(crate) fn index(&self) -> &index::Index {
|
||||
&self.index
|
||||
}
|
||||
|
||||
pub(crate) fn position_encoding(&self) -> PositionEncoding {
|
||||
self.position_encoding
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Workspaces {
|
||||
workspaces: BTreeMap<Url, Workspace>,
|
||||
@@ -473,11 +499,15 @@ impl Workspaces {
|
||||
.to_file_path()
|
||||
.map_err(|()| anyhow!("Workspace URL is not a file or directory: {url:?}"))?;
|
||||
|
||||
// Realistically I don't think this can fail because we got the path from a Url
|
||||
let system_path = SystemPathBuf::from_path_buf(path)
|
||||
.map_err(|_| anyhow!("Workspace URL is not valid UTF8"))?;
|
||||
|
||||
self.workspaces.insert(
|
||||
url,
|
||||
Workspace {
|
||||
options,
|
||||
root: path,
|
||||
root: system_path,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -520,12 +550,12 @@ impl<'a> IntoIterator for &'a Workspaces {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Workspace {
|
||||
root: PathBuf,
|
||||
root: SystemPathBuf,
|
||||
options: ClientOptions,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub(crate) fn root(&self) -> &Path {
|
||||
pub(crate) fn root(&self) -> &SystemPath {
|
||||
&self.root
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use lsp_types::Url;
|
||||
use ruff_db::system::SystemPathBuf;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -47,6 +46,26 @@ pub(crate) struct ClientOptions {
|
||||
/// Settings under the `python.*` namespace in VS Code that are useful for the ty language
|
||||
/// server.
|
||||
python: Option<Python>,
|
||||
/// Diagnostic mode for the language server.
|
||||
diagnostic_mode: Option<DiagnosticMode>,
|
||||
}
|
||||
|
||||
/// Diagnostic mode for the language server.
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum DiagnosticMode {
|
||||
/// Check only currently open files.
|
||||
#[default]
|
||||
OpenFilesOnly,
|
||||
/// Check all files in the workspace.
|
||||
Workspace,
|
||||
}
|
||||
|
||||
impl DiagnosticMode {
|
||||
pub(crate) fn is_workspace(self) -> bool {
|
||||
matches!(self, DiagnosticMode::Workspace)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientOptions {
|
||||
@@ -58,6 +77,7 @@ impl ClientOptions {
|
||||
.and_then(|python| python.ty)
|
||||
.and_then(|ty| ty.disable_language_services)
|
||||
.unwrap_or_default(),
|
||||
diagnostic_mode: self.diagnostic_mode.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +109,7 @@ pub(crate) struct TracingOptions {
|
||||
pub(crate) log_level: Option<LogLevel>,
|
||||
|
||||
/// Path to the log file - tildes and environment variables are supported.
|
||||
pub(crate) log_file: Option<PathBuf>,
|
||||
pub(crate) log_file: Option<SystemPathBuf>,
|
||||
}
|
||||
|
||||
/// This is the exact schema for initialization options sent in by the client during
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use super::options::DiagnosticMode;
|
||||
|
||||
/// 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
|
||||
/// sends them.
|
||||
@@ -5,10 +7,15 @@
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub(crate) struct ClientSettings {
|
||||
pub(super) disable_language_services: bool,
|
||||
pub(super) diagnostic_mode: DiagnosticMode,
|
||||
}
|
||||
|
||||
impl ClientSettings {
|
||||
pub(crate) fn is_language_services_disabled(&self) -> bool {
|
||||
self.disable_language_services
|
||||
}
|
||||
|
||||
pub(crate) fn diagnostic_mode(&self) -> DiagnosticMode {
|
||||
self.diagnostic_mode
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user