-
Notifications
You must be signed in to change notification settings - Fork 899
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
<!-- Thank you for contributing to Ruff! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary Add support for hover menu to ruff_server, as requested in [10595](#10595). Majority of new code is in hover.rs. I reused the regex from ruff-lsp's implementation. Also reused the format_rule_text function from ruff/src/commands/rule.rs Added capability registration in server.rs, and added the handler to api.rs. ## Test Plan Tested in NVIM v0.10.0-dev-2582+g2a8cef6bd, configured with lspconfig using the default options (other than cmd pointing to my test build, with options "server" and "--preview"). OS: Ubuntu 24.04, kernel 6.8.0-22. --------- Co-authored-by: Jane Lewis <me@jane.engineering>
- Loading branch information
1 parent
455d22c
commit 7c8c1c7
Showing
6 changed files
with
121 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use crate::server::{client::Notifier, Result}; | ||
use crate::session::DocumentSnapshot; | ||
use lsp_types::{self as types, request as req}; | ||
use regex::Regex; | ||
use ruff_diagnostics::FixAvailability; | ||
use ruff_linter::registry::{Linter, Rule, RuleNamespace}; | ||
use ruff_source_file::OneIndexed; | ||
|
||
pub(crate) struct Hover; | ||
|
||
impl super::RequestHandler for Hover { | ||
type RequestType = req::HoverRequest; | ||
} | ||
|
||
impl super::BackgroundDocumentRequestHandler for Hover { | ||
fn document_url(params: &types::HoverParams) -> std::borrow::Cow<lsp_types::Url> { | ||
std::borrow::Cow::Borrowed(¶ms.text_document_position_params.text_document.uri) | ||
} | ||
fn run_with_snapshot( | ||
snapshot: DocumentSnapshot, | ||
_notifier: Notifier, | ||
params: types::HoverParams, | ||
) -> Result<Option<types::Hover>> { | ||
Ok(hover(&snapshot, ¶ms.text_document_position_params)) | ||
} | ||
} | ||
|
||
pub(crate) fn hover( | ||
snapshot: &DocumentSnapshot, | ||
position: &types::TextDocumentPositionParams, | ||
) -> Option<types::Hover> { | ||
let document = snapshot.document(); | ||
let line_number: usize = position | ||
.position | ||
.line | ||
.try_into() | ||
.expect("line number should fit within a usize"); | ||
let line_range = document.index().line_range( | ||
OneIndexed::from_zero_indexed(line_number), | ||
document.contents(), | ||
); | ||
|
||
let line = &document.contents()[line_range]; | ||
|
||
// Get the list of codes. | ||
let noqa_regex = Regex::new(r"(?i:# (?:(?:ruff|flake8): )?(?P<noqa>noqa))(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?").unwrap(); | ||
let noqa_captures = noqa_regex.captures(line)?; | ||
let codes_match = noqa_captures.name("codes")?; | ||
let codes_start = codes_match.start(); | ||
let code_regex = Regex::new(r"[A-Z]+[0-9]+").unwrap(); | ||
let cursor: usize = position | ||
.position | ||
.character | ||
.try_into() | ||
.expect("column number should fit within a usize"); | ||
let word = code_regex.find_iter(codes_match.as_str()).find(|code| { | ||
cursor >= (code.start() + codes_start) && cursor < (code.end() + codes_start) | ||
})?; | ||
|
||
// Get rule for the code under the cursor. | ||
let rule = Rule::from_code(word.as_str()); | ||
let output = if let Ok(rule) = rule { | ||
format_rule_text(rule) | ||
} else { | ||
format!("{}: Rule not found", word.as_str()) | ||
}; | ||
|
||
let hover = types::Hover { | ||
contents: types::HoverContents::Markup(types::MarkupContent { | ||
kind: types::MarkupKind::Markdown, | ||
value: output, | ||
}), | ||
range: None, | ||
}; | ||
|
||
Some(hover) | ||
} | ||
|
||
fn format_rule_text(rule: Rule) -> String { | ||
let mut output = String::new(); | ||
output.push_str(&format!("# {} ({})", rule.as_ref(), rule.noqa_code())); | ||
output.push('\n'); | ||
output.push('\n'); | ||
|
||
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap(); | ||
output.push_str(&format!("Derived from the **{}** linter.", linter.name())); | ||
output.push('\n'); | ||
output.push('\n'); | ||
|
||
let fix_availability = rule.fixable(); | ||
if matches!( | ||
fix_availability, | ||
FixAvailability::Always | FixAvailability::Sometimes | ||
) { | ||
output.push_str(&fix_availability.to_string()); | ||
output.push('\n'); | ||
output.push('\n'); | ||
} | ||
|
||
if rule.is_preview() || rule.is_nursery() { | ||
output.push_str(r"This rule is in preview and is not stable."); | ||
output.push('\n'); | ||
output.push('\n'); | ||
} | ||
|
||
if let Some(explanation) = rule.explanation() { | ||
output.push_str(explanation.trim()); | ||
} else { | ||
tracing::warn!("Rule {} does not have an explanation", rule.noqa_code()); | ||
output.push_str("An issue occurred: an explanation for this rule was not found."); | ||
} | ||
output | ||
} |