Skip to content

Commit

Permalink
Add Rust based LSP
Browse files Browse the repository at this point in the history
Signed-off-by: Micha Reiser <micha@reiser.io>
  • Loading branch information
MichaReiser committed Sep 25, 2023
1 parent 39ddad7 commit 729a4fc
Show file tree
Hide file tree
Showing 15 changed files with 3,907 additions and 3 deletions.
361 changes: 361 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/ruff_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ ruff_linter = { path = "../ruff_linter", features = ["clap"] }
ruff_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics" }
ruff_formatter = { path = "../ruff_formatter" }
ruff_lsp = { path = "../ruff_lsp" }
ruff_notebook = { path = "../ruff_notebook" }
ruff_macros = { path = "../ruff_macros" }
ruff_python_ast = { path = "../ruff_python_ast" }
Expand Down Expand Up @@ -59,6 +60,8 @@ similar = { workspace = true }
strum = { workspace = true, features = [] }
thiserror = { workspace = true }
tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { version="0.3.17",features = ["registry"] }
tracing-tree = "0.2.4"
walkdir = { version = "2.3.2" }
wild = { version = "2" }

Expand Down
42 changes: 39 additions & 3 deletions crates/ruff_cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ pub enum Command {
format: HelpFormat,
},
/// List or describe the available configuration options.
Config { option: Option<String> },
Config {
option: Option<String>,
},
/// List all supported upstream linters.
Linter {
/// Output format
Expand All @@ -63,11 +65,15 @@ pub enum Command {
Clean,
/// Generate shell completion.
#[clap(alias = "--generate-shell-completion", hide = true)]
GenerateShellCompletion { shell: clap_complete_command::Shell },
GenerateShellCompletion {
shell: clap_complete_command::Shell,
},
/// Run the Ruff formatter on the given files or directories.
#[doc(hidden)]
#[clap(hide = true)]
Format(FormatCommand),

Lsp(LspCommand),
}

// The `Parser` derive is for ruff_dev, for ruff_cli `Args` would be sufficient
Expand Down Expand Up @@ -397,6 +403,32 @@ pub struct FormatCommand {
no_preview: bool,
}

#[derive(Clone, Debug, clap::Parser)]
#[allow(clippy::struct_excessive_bools)]
pub struct LspCommand {
/// Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
#[arg(long, conflicts_with = "isolated")]
pub config: Option<PathBuf>,
/// Ignore all configuration files.
#[arg(long, conflicts_with = "config", help_heading = "Miscellaneous")]
pub isolated: bool,
/// Respect file exclusions via `.gitignore` and other standard ignore files.
#[arg(
long,
overrides_with("no_respect_gitignore"),
help_heading = "File selection"
)]
respect_gitignore: bool,
#[clap(long, overrides_with("respect_gitignore"), hide = true)]
no_respect_gitignore: bool,

/// Enable preview mode; checks will include unstable rules and fixes.
#[arg(long, overrides_with("no_preview"), hide = true)]
preview: bool,
#[clap(long, overrides_with("preview"), hide = true)]
no_preview: bool,
}

#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum HelpFormat {
Text,
Expand Down Expand Up @@ -565,7 +597,6 @@ pub struct CheckArguments {

/// CLI settings that are distinct from configuration (commands, lists of files,
/// etc.).
#[allow(clippy::struct_excessive_bools)]
pub struct FormatArguments {
pub check: bool,
pub config: Option<PathBuf>,
Expand All @@ -574,6 +605,11 @@ pub struct FormatArguments {
pub stdin_filename: Option<PathBuf>,
}

pub struct LspArguments {
pub config: Option<PathBuf>,
pub isolated: bool,
}

/// CLI settings that function as configuration overrides.
#[derive(Clone, Default)]
#[allow(clippy::struct_excessive_bools)]
Expand Down
51 changes: 51 additions & 0 deletions crates/ruff_cli/src/commands/lsp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::fmt::{Display, Formatter};
use std::io;
use std::num::NonZeroU16;
use std::path::{Path, PathBuf};
use std::time::Instant;

use anyhow::Result;
use colored::Colorize;
use rayon::iter::Either::{Left, Right};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use thiserror::Error;
use tracing::metadata::Level;
use tracing::{debug, warn};
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{Layer, Registry};
use tracing_tree::time::Uptime;

use ruff::fs;
use ruff::logging::LogLevel;
use ruff::settings::types::PreviewMode;
use ruff::warn_user_once;
use ruff_formatter::LineWidth;
use ruff_python_ast::{PySourceType, SourceType};
use ruff_python_formatter::{format_module, FormatModuleError, PyFormatOptions};
use ruff_source_file::{find_newline, LineEnding};
use ruff_workspace::resolver::python_files_in_path;

use crate::args::{FormatArguments, LspArguments, LspCommand, Overrides};
use crate::resolve::resolve;
use crate::ExitStatus;

/// Format a set of files, and return the exit status.
pub(crate) fn lsp(arguments: LspCommand, log_level: LogLevel) -> Result<ExitStatus> {
let subscriber = Registry::default().with(
tracing_tree::HierarchicalLayer::default()
.with_indent_lines(true)
.with_indent_amount(2)
.with_bracketed_fields(true)
.with_targets(true)
.with_writer(|| Box::new(std::io::stderr()))
.with_timer(Uptime::default())
.with_filter(LevelFilter::from(Some(Level::DEBUG))),
);
tracing::subscriber::with_default(subscriber, || {
ruff_lsp::stdio();
});

Ok(ExitStatus::Success)
}
1 change: 1 addition & 0 deletions crates/ruff_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) mod config;
pub(crate) mod format;
pub(crate) mod format_stdin;
pub(crate) mod linter;
pub(crate) mod lsp;
pub(crate) mod rule;
pub(crate) mod show_files;
pub(crate) mod show_settings;
2 changes: 2 additions & 0 deletions crates/ruff_cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use ruff_linter::{fs, warn_user, warn_user_once};
use ruff_workspace::Settings;

use crate::args::{Args, CheckCommand, Command, FormatCommand};
use crate::commands::lsp::lsp;
use crate::printer::{Flags as PrinterFlags, Printer};

pub mod args;
Expand Down Expand Up @@ -162,6 +163,7 @@ pub fn run(
}
Command::Check(args) => check(args, log_level),
Command::Format(args) => format(args, log_level),
Command::Lsp(args) => lsp(args, log_level),
}
}

Expand Down
33 changes: 33 additions & 0 deletions crates/ruff_lsp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "ruff_lsp"
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]
ruff_linter = { path = "../ruff_linter" }
ruff_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics" }
ruff_formatter = { path = "../ruff_formatter" }
ruff_notebook = { path = "../ruff_notebook" }
ruff_macros = { path = "../ruff_macros" }
ruff_python_ast = { path = "../ruff_python_ast" }
ruff_python_formatter = { path = "../ruff_python_formatter" }
ruff_source_file = { path = "../ruff_source_file" }
ruff_python_trivia = { path = "../ruff_python_trivia" }
ruff_workspace = { path = "../ruff_workspace" }
ruff_text_size = { path = "../ruff_text_size" }

tower-lsp = "0.20.0"
tracing = {workspace = true}
tokio = { version = "1.31.0", features = ["io-std", "rt", "rt-multi-thread"] }

[dev-dependencies]


77 changes: 77 additions & 0 deletions crates/ruff_lsp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use ruff::RUFF_PKG_VERSION;
use std::future::Future;
use std::pin::Pin;
use tower_lsp::jsonrpc::Result as LspResult;
use tower_lsp::lsp_types::{
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
DocumentFormattingParams, InitializeParams, InitializeResult, InitializedParams, MessageType,
PositionEncodingKind, ServerCapabilities, ServerInfo, TextDocumentSyncCapability,
TextDocumentSyncKind, TextEdit,
};
use tower_lsp::{Client, LanguageServer, LspService};

/// Creates a LSP server that reads from stdin and writes the output to stdout.
pub fn stdio() {
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();

let (service, socket) = LspService::new(|client| Server { client });
tower_lsp::Server::new(stdin, stdout, socket)
.serve(service)
.await;
});
}

struct Server {
client: Client,
}

#[tower_lsp::async_trait]
impl LanguageServer for Server {
#[tracing::instrument(level="debug", skip_all, err, fields(client=?params.client_info))]
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
let init = InitializeResult {
capabilities: ServerCapabilities {
// TODO
position_encoding: None,
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::INCREMENTAL,
)),
..Default::default()
},
server_info: Some(ServerInfo {
name: String::from(env!("CARGO_PKG_NAME")),
version: Some(RUFF_PKG_VERSION.to_string()),
}),
};

Ok(init)
}

#[tracing::instrument(skip_all)]
async fn initialized(&self, params: InitializedParams) {}

#[tracing::instrument(skip_all, fields(file=%params.text_document.uri))]
async fn did_open(&self, params: DidOpenTextDocumentParams) {}

#[tracing::instrument(skip_all, fields(file=%params.text_document.uri))]
async fn did_change(&self, params: DidChangeTextDocumentParams) {}

#[tracing::instrument(skip_all, fields(file=%params.text_document.uri))]
async fn did_close(&self, params: DidCloseTextDocumentParams) {}

#[tracing::instrument(skip_all, fields(file=%params.text_document.uri))]
async fn formatting(
&self,
params: DocumentFormattingParams,
) -> LspResult<Option<Vec<TextEdit>>> {
Ok(Some(vec![]))
}

#[tracing::instrument(skip_all, err)]
async fn shutdown(&self) -> LspResult<()> {
Ok(())
}
}
30 changes: 30 additions & 0 deletions editors/vscode/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
env: {
es2021: true,
node: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
overrides: [
{
env: {
node: true,
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
},
};
2 changes: 2 additions & 0 deletions editors/vscode/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
node_modules
8 changes: 8 additions & 0 deletions editors/vscode/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
**
!icon.png
!LICENSE
!out/main.js
!package.json
!server/ruff
!server/ruff.exe
!README.md
Loading

0 comments on commit 729a4fc

Please sign in to comment.