Repository navigation
Expand file tree
/
Copy pathhover.rs
More file actions
151 lines (133 loc) · 4.67 KB
/
Copy pathhover.rs
File metadata and controls
151 lines (133 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::edit::{RangeExt, ToRangeExt};
use crate::server::Result;
use crate::session::{Client, DocumentSnapshot};
use anyhow::Context;
use lsp_types::{self as types, HoverRequest};
use ruff_linter::FixAvailability;
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
use ruff_linter::suppression::rule_identifier_range_at_offset;
use ruff_python_ast::SourceType;
use ruff_python_ast::token::TokenKind;
use ruff_python_parser::parse_unchecked_source;
use ruff_source_file::OneIndexed;
use ruff_text_size::Ranged;
use std::fmt::Write;
pub(crate) struct Hover;
impl super::RequestHandler for Hover {
type RequestType = HoverRequest;
}
impl super::BackgroundDocumentRequestHandler for Hover {
fn document_uri(params: &types::HoverParams) -> std::borrow::Cow<'_, lsp_types::Uri> {
std::borrow::Cow::Borrowed(¶ms.text_document_position_params.text_document.uri)
}
fn run_with_snapshot(
snapshot: Self::Snapshot,
_client: &Client,
params: types::HoverParams,
) -> Result<Option<types::Hover>> {
let snapshot = match snapshot {
Ok(snapshot) => snapshot,
Err(uri) => {
tracing::warn!(
"Returning no hover information because document `{uri}` isn't open."
);
return Ok(None);
}
};
Ok(hover(&snapshot, ¶ms.text_document_position_params))
}
}
fn hover(
snapshot: &DocumentSnapshot,
position: &types::TextDocumentPositionParams,
) -> Option<types::Hover> {
// Don't show noqa hover for non-Python documents (e.g., markdown files).
let SourceType::Python(source_type) = snapshot.query().source_type_for_lint() else {
return None;
};
let document = snapshot
.query()
.as_single_document()
.context("Failed to get text document for the hover request")
.unwrap();
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];
// Avoid parsing the document if the hovered line doesn't contain a comment.
memchr::memchr(b'#', line.as_bytes())?;
let cursor = types::Range::new(position.position, position.position)
.to_text_range(document.contents(), document.index(), snapshot.encoding())
.start();
let parsed = parse_unchecked_source(document.contents(), source_type);
let comment = parsed
.tokens()
.at_offset(cursor)
.find(|token| token.kind() == TokenKind::Comment)?;
let identifier_range =
rule_identifier_range_at_offset(document.contents(), comment.range(), cursor)?;
// Get the rule for the identifier under the cursor.
let identifier = &document.contents()[identifier_range];
let rule = Rule::from_code(identifier)
.ok()
.or_else(|| Rule::from_name(identifier).ok());
let output = if let Some(rule) = rule {
format_rule_text(rule)
} else {
format!("{identifier}: Rule not found")
};
let hover = types::Hover {
contents: types::MarkupContent {
kind: types::MarkupKind::Markdown,
value: output,
}
.into(),
range: Some(identifier_range.to_range(
document.contents(),
document.index(),
snapshot.encoding(),
)),
};
Some(hover)
}
fn format_rule_text(rule: Rule) -> String {
let mut output = String::new();
let _ = write!(&mut output, "# {} ({})", rule.name(), rule.noqa_code());
output.push('\n');
output.push('\n');
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
let _ = write!(
&mut output,
"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() {
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
}