Skip to content

[red-knot] Implement basic LSP server #12624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 6, 2024
Merged
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ruff_workspace = { path = "crates/ruff_workspace" }

red_knot_module_resolver = { path = "crates/red_knot_module_resolver" }
red_knot_python_semantic = { path = "crates/red_knot_python_semantic" }
red_knot_server = { path = "crates/red_knot_server" }
red_knot_workspace = { path = "crates/red_knot_workspace" }

aho-corasick = { version = "1.1.3" }
Expand Down
1 change: 1 addition & 0 deletions crates/red_knot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ license.workspace = true
[dependencies]
red_knot_module_resolver = { workspace = true }
red_knot_workspace = { workspace = true }
red_knot_server = { workspace = true }

ruff_db = { workspace = true, features = ["os", "cache"] }

Expand Down
22 changes: 22 additions & 0 deletions crates/red_knot/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::num::NonZeroUsize;
use std::sync::Mutex;

use clap::Parser;
Expand Down Expand Up @@ -29,6 +30,9 @@ mod cli;
)]
#[command(version)]
struct Args {
#[command(subcommand)]
pub(crate) command: Command,

#[arg(
long,
help = "Changes the current working directory.",
Expand Down Expand Up @@ -65,6 +69,11 @@ struct Args {
watch: bool,
}

#[derive(Debug, clap::Subcommand)]
pub enum Command {
Server,
}

#[allow(
clippy::print_stdout,
clippy::unnecessary_wraps,
Expand All @@ -73,6 +82,7 @@ struct Args {
)]
pub fn main() -> anyhow::Result<()> {
let Args {
command,
current_directory,
custom_typeshed_dir,
extra_search_path: extra_paths,
Expand All @@ -83,6 +93,18 @@ pub fn main() -> anyhow::Result<()> {

let verbosity = verbosity.level();
countme::enable(verbosity == Some(VerbosityLevel::Trace));

if matches!(command, Command::Server) {
let four = NonZeroUsize::new(4).unwrap();

// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
let worker_threads = std::thread::available_parallelism()
.unwrap_or(four)
.max(four);

return red_knot_server::Server::new(worker_threads)?.run();
}

setup_tracing(verbosity);

let cwd = if let Some(cwd) = current_directory {
Expand Down
4 changes: 4 additions & 0 deletions crates/red_knot_module_resolver/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ pub(crate) mod tests {
&self.system
}

fn system_mut(&mut self) -> &mut dyn ruff_db::system::System {
&mut self.system
}

fn files(&self) -> &Files {
&self.files
}
Expand Down
4 changes: 4 additions & 0 deletions crates/red_knot_python_semantic/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ pub(crate) mod tests {
&self.system
}

fn system_mut(&mut self) -> &mut dyn System {
&mut self.system
}

fn files(&self) -> &Files {
&self.files
}
Expand Down
41 changes: 41 additions & 0 deletions crates/red_knot_server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[package]
name = "red_knot_server"
version = "0.0.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
license = { workspace = true }

[dependencies]
red_knot_workspace = { workspace = true }
ruff_db = { workspace = true }
ruff_linter = { workspace = true }
ruff_notebook = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }

anyhow = { workspace = true }
crossbeam = { workspace = true }
jod-thread = { workspace = true }
lsp-server = { workspace = true }
lsp-types = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
shellexpand = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

[dev-dependencies]

[target.'cfg(target_vendor = "apple")'.dependencies]
libc = { workspace = true }

[lints]
workspace = true
80 changes: 80 additions & 0 deletions crates/red_knot_server/src/edit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! Types and utilities for working with text, modifying source files, and `Ruff <-> LSP` type conversion.

mod notebook;
mod range;
mod text_document;

use lsp_types::{PositionEncodingKind, Url};
pub use notebook::NotebookDocument;
pub(crate) use range::RangeExt;
pub(crate) use text_document::DocumentVersion;
pub use text_document::TextDocument;

/// A convenient enumeration for supported text encodings. Can be converted to [`lsp_types::PositionEncodingKind`].
// Please maintain the order from least to greatest priority for the derived `Ord` impl.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum PositionEncoding {
/// UTF 16 is the encoding supported by all LSP clients.
#[default]
UTF16,

/// Second choice because UTF32 uses a fixed 4 byte encoding for each character (makes conversion relatively easy)
UTF32,

/// Ruff's preferred encoding
UTF8,
}

/// A unique document ID, derived from a URL passed as part of an LSP request.
/// This document ID can point to either be a standalone Python file, a full notebook, or a cell within a notebook.
#[derive(Clone, Debug)]
pub enum DocumentKey {
Notebook(Url),
NotebookCell(Url),
Text(Url),
}

impl DocumentKey {
/// Returns the URL associated with the key.
pub(crate) fn url(&self) -> &Url {
match self {
DocumentKey::NotebookCell(url)
| DocumentKey::Notebook(url)
| DocumentKey::Text(url) => url,
}
}
}

impl std::fmt::Display for DocumentKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotebookCell(url) | Self::Notebook(url) | Self::Text(url) => url.fmt(f),
}
}
}

impl From<PositionEncoding> for lsp_types::PositionEncodingKind {
fn from(value: PositionEncoding) -> Self {
match value {
PositionEncoding::UTF8 => lsp_types::PositionEncodingKind::UTF8,
PositionEncoding::UTF16 => lsp_types::PositionEncodingKind::UTF16,
PositionEncoding::UTF32 => lsp_types::PositionEncodingKind::UTF32,
}
}
}

impl TryFrom<&lsp_types::PositionEncodingKind> for PositionEncoding {
type Error = ();

fn try_from(value: &PositionEncodingKind) -> Result<Self, Self::Error> {
Ok(if value == &PositionEncodingKind::UTF8 {
PositionEncoding::UTF8
} else if value == &PositionEncodingKind::UTF16 {
PositionEncoding::UTF16
} else if value == &PositionEncodingKind::UTF32 {
PositionEncoding::UTF32
} else {
return Err(());
})
}
}
Loading
Loading