diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 3bce3cb9ee..e5e3bc6ee7 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -259,6 +259,10 @@ jobs:
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
tool: cargo-insta
+ - name: "Install uv"
+ uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
+ with:
+ enable-cache: "true"
- name: ty mdtests (GitHub annotations)
if: ${{ needs.determine_changes.outputs.ty == 'true' }}
env:
@@ -317,6 +321,10 @@ jobs:
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
tool: cargo-insta
+ - name: "Install uv"
+ uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
+ with:
+ enable-cache: "true"
- name: "Run tests"
shell: bash
env:
@@ -340,6 +348,10 @@ jobs:
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
with:
tool: cargo-nextest
+ - name: "Install uv"
+ uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
+ with:
+ enable-cache: "true"
- name: "Run tests"
shell: bash
env:
diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs
index 65f0bcf8a9..d765af4704 100644
--- a/crates/ruff_formatter/src/lib.rs
+++ b/crates/ruff_formatter/src/lib.rs
@@ -81,14 +81,19 @@ impl IndentStyle {
pub const fn is_space(&self) -> bool {
matches!(self, IndentStyle::Space)
}
+
+ /// Returns the string representation of the indent style.
+ pub const fn as_str(&self) -> &'static str {
+ match self {
+ IndentStyle::Tab => "tab",
+ IndentStyle::Space => "space",
+ }
+ }
}
impl std::fmt::Display for IndentStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- IndentStyle::Tab => std::write!(f, "tab"),
- IndentStyle::Space => std::write!(f, "space"),
- }
+ f.write_str(self.as_str())
}
}
diff --git a/crates/ruff_formatter/src/printer/printer_options/mod.rs b/crates/ruff_formatter/src/printer/printer_options/mod.rs
index a8895690f6..a841f85744 100644
--- a/crates/ruff_formatter/src/printer/printer_options/mod.rs
+++ b/crates/ruff_formatter/src/printer/printer_options/mod.rs
@@ -139,4 +139,16 @@ impl LineEnding {
LineEnding::CarriageReturn => "\r",
}
}
+
+ /// Returns the string used to configure this line ending.
+ ///
+ /// See [`LineEnding::as_str`] for the actual string representation of the line ending.
+ #[inline]
+ pub const fn as_setting_str(&self) -> &'static str {
+ match self {
+ LineEnding::LineFeed => "lf",
+ LineEnding::CarriageReturnLineFeed => "crlf",
+ LineEnding::CarriageReturn => "cr",
+ }
+ }
}
diff --git a/crates/ruff_python_formatter/src/options.rs b/crates/ruff_python_formatter/src/options.rs
index 26916d66ea..ec84fa65fb 100644
--- a/crates/ruff_python_formatter/src/options.rs
+++ b/crates/ruff_python_formatter/src/options.rs
@@ -252,15 +252,20 @@ impl QuoteStyle {
pub const fn is_preserve(self) -> bool {
matches!(self, QuoteStyle::Preserve)
}
+
+ /// Returns the string representation of the quote style.
+ pub const fn as_str(&self) -> &'static str {
+ match self {
+ QuoteStyle::Single => "single",
+ QuoteStyle::Double => "double",
+ QuoteStyle::Preserve => "preserve",
+ }
+ }
}
impl fmt::Display for QuoteStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Single => write!(f, "single"),
- Self::Double => write!(f, "double"),
- Self::Preserve => write!(f, "preserve"),
- }
+ f.write_str(self.as_str())
}
}
@@ -302,10 +307,10 @@ impl MagicTrailingComma {
impl fmt::Display for MagicTrailingComma {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Respect => write!(f, "respect"),
- Self::Ignore => write!(f, "ignore"),
- }
+ f.write_str(match self {
+ MagicTrailingComma::Respect => "respect",
+ MagicTrailingComma::Ignore => "ignore",
+ })
}
}
diff --git a/crates/ruff_server/Cargo.toml b/crates/ruff_server/Cargo.toml
index 4bfaf47663..0d53439406 100644
--- a/crates/ruff_server/Cargo.toml
+++ b/crates/ruff_server/Cargo.toml
@@ -50,5 +50,8 @@ insta = { workspace = true }
[target.'cfg(target_vendor = "apple")'.dependencies]
libc = { workspace = true }
+[features]
+test-uv = []
+
[lints]
workspace = true
diff --git a/crates/ruff_server/src/format.rs b/crates/ruff_server/src/format.rs
index d63d94f5ec..44f5466e11 100644
--- a/crates/ruff_server/src/format.rs
+++ b/crates/ruff_server/src/format.rs
@@ -1,18 +1,52 @@
+use std::io::Write;
use std::path::Path;
+use std::process::{Command, Stdio};
-use ruff_formatter::PrintedRange;
+use anyhow::Context;
+
+use ruff_formatter::{FormatOptions, PrintedRange};
use ruff_python_ast::PySourceType;
-use ruff_python_formatter::{FormatModuleError, format_module_source};
+use ruff_python_formatter::{FormatModuleError, PyFormatOptions, format_module_source};
+use ruff_source_file::LineIndex;
use ruff_text_size::TextRange;
use ruff_workspace::FormatterSettings;
use crate::edit::TextDocument;
+/// The backend to use for formatting.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub(crate) enum FormatBackend {
+ /// Use the built-in Ruff formatter.
+ ///
+ /// The formatter version will match the LSP version.
+ #[default]
+ Internal,
+ /// Use uv for formatting.
+ ///
+ /// The formatter version may differ from the LSP version.
+ Uv,
+}
+
pub(crate) fn format(
document: &TextDocument,
source_type: PySourceType,
formatter_settings: &FormatterSettings,
path: &Path,
+ backend: FormatBackend,
+) -> crate::Result