1use hir::{EditionedFileId, HirFileId, InFile, Semantics};
4use ide_db::{
5 SymbolKind, defs::Definition, documentation::Documentation, range_mapper::RangeMapper,
6 rust_doc::is_rust_fence,
7};
8use syntax::{
9 SyntaxNode, TextRange, TextSize,
10 ast::{self, IsString},
11};
12
13use crate::{
14 Analysis, HlMod, HlRange, HlTag, RootDatabase,
15 doc_links::{doc_attributes, extract_definitions_from_docs, resolve_doc_path_for_def},
16 syntax_highlighting::{HighlightConfig, highlights::Highlights},
17};
18
19pub(super) fn ra_fixture(
20 hl: &mut Highlights,
21 sema: &Semantics<'_, RootDatabase>,
22 config: &HighlightConfig<'_>,
23 literal: &ast::String,
24 expanded: &ast::String,
25) -> Option<()> {
26 let (analysis, fixture_analysis) = Analysis::from_ra_fixture_with_on_cursor(
27 sema,
28 literal.clone(),
29 expanded,
30 config.minicore,
31 &mut |range| {
32 hl.add(HlRange {
33 range,
34 highlight: HlTag::Keyword | HlMod::Injected,
35 binding_hash: None,
36 });
37 },
38 )?;
39
40 if let Some(range) = literal.open_quote_text_range() {
41 hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None })
42 }
43
44 for tmp_file_id in fixture_analysis.files() {
45 for mut hl_range in analysis
46 .highlight(
47 HighlightConfig {
48 syntactic_name_ref_highlighting: false,
49 comments: true,
50 punctuation: true,
51 operator: true,
52 strings: true,
53 specialize_punctuation: config.specialize_punctuation,
54 specialize_operator: config.operator,
55 inject_doc_comment: config.inject_doc_comment,
56 macro_bang: config.macro_bang,
57 minicore: config.minicore,
60 },
61 tmp_file_id,
62 )
63 .unwrap()
64 {
65 for range in fixture_analysis.map_range_up(tmp_file_id, hl_range.range) {
66 hl_range.range = range;
67 hl_range.highlight |= HlMod::Injected;
68 hl.add(hl_range);
69 }
70 }
71 }
72
73 if let Some(range) = literal.close_quote_text_range() {
74 hl.add(HlRange { range, highlight: HlTag::StringLiteral.into(), binding_hash: None })
75 }
76
77 Some(())
78}
79
80const RUSTDOC_FENCE_LENGTH: usize = 3;
81const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
82
83pub(super) fn doc_comment(
85 hl: &mut Highlights,
86 sema: &Semantics<'_, RootDatabase>,
87 config: &HighlightConfig<'_>,
88 src_file_id: EditionedFileId,
89 node: &SyntaxNode,
90) {
91 let (attributes, def) = match doc_attributes(sema, node) {
92 Some(it) => it,
93 None => return,
94 };
95 let src_file_id: HirFileId = src_file_id.into();
96 let Some(docs) = attributes.hir_docs(sema.db) else { return };
97
98 extract_definitions_from_docs(&Documentation::new_borrowed(docs.docs()))
100 .into_iter()
101 .filter_map(|(range, link, ns)| {
102 docs.find_ast_range(range)
103 .filter(|(mapping, _)| mapping.file_id == src_file_id)
104 .and_then(|(InFile { value: mapped_range, .. }, is_inner)| {
105 Some(mapped_range)
106 .zip(resolve_doc_path_for_def(sema.db, def, &link, ns, is_inner))
107 })
108 })
109 .for_each(|(range, def)| {
110 hl.add(HlRange {
111 range,
112 highlight: module_def_to_hl_tag(def)
113 | HlMod::Documentation
114 | HlMod::Injected
115 | HlMod::IntraDocLink,
116 binding_hash: None,
117 })
118 });
119
120 let mut inj = RangeMapper::default();
123 inj.add_unmapped("fn doctest() {\n");
124
125 let mut is_codeblock = false;
126 let mut is_doctest = false;
127
128 let mut has_doctests = false;
129
130 let mut docs_offset = TextSize::new(0);
131 for mut line in docs.docs().split('\n') {
132 let mut line_docs_offset = docs_offset;
133 docs_offset += TextSize::of(line) + TextSize::of("\n");
134
135 match RUSTDOC_FENCES.into_iter().find_map(|fence| line.find(fence)) {
136 Some(idx) => {
137 is_codeblock = !is_codeblock;
138 let guards = &line[idx + RUSTDOC_FENCE_LENGTH..];
140 let is_rust = is_rust_fence(guards);
141 is_doctest = is_codeblock && is_rust;
142 continue;
143 }
144 None if !is_doctest => continue,
145 None => (),
146 }
147
148 if line.starts_with('#') {
150 line_docs_offset += TextSize::of("#");
151 line = &line["#".len()..];
152 }
153
154 let Some((InFile { file_id, value: mapped_range }, _)) =
155 docs.find_ast_range(TextRange::at(line_docs_offset, TextSize::of(line)))
156 else {
157 continue;
158 };
159 if file_id != src_file_id {
160 continue;
161 }
162
163 has_doctests = true;
164 inj.add(line, mapped_range);
165 inj.add_unmapped("\n");
166 }
167
168 if !has_doctests {
169 return; }
171
172 inj.add_unmapped("\n}");
173
174 let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text());
175
176 if let Ok(ranges) = analysis.with_db(|db| {
177 super::highlight(
178 db,
179 &HighlightConfig {
180 syntactic_name_ref_highlighting: true,
181 comments: true,
182 punctuation: true,
183 operator: true,
184 strings: true,
185 specialize_punctuation: config.specialize_punctuation,
186 specialize_operator: config.operator,
187 inject_doc_comment: config.inject_doc_comment,
188 macro_bang: config.macro_bang,
189 minicore: config.minicore,
190 },
191 tmp_file_id,
192 None,
193 )
194 }) {
195 for HlRange { range, highlight, binding_hash } in ranges {
196 for range in inj.map_range_up(range) {
197 hl.add(HlRange { range, highlight: highlight | HlMod::Injected, binding_hash });
198 }
199 }
200 }
201}
202
203fn module_def_to_hl_tag(def: Definition) -> HlTag {
204 let symbol = match def {
205 Definition::Module(_) | Definition::Crate(_) | Definition::ExternCrateDecl(_) => {
206 SymbolKind::Module
207 }
208 Definition::Function(_) => SymbolKind::Function,
209 Definition::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct,
210 Definition::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum,
211 Definition::Adt(hir::Adt::Union(_)) => SymbolKind::Union,
212 Definition::Variant(_) => SymbolKind::Variant,
213 Definition::Const(_) => SymbolKind::Const,
214 Definition::Static(_) => SymbolKind::Static,
215 Definition::Trait(_) => SymbolKind::Trait,
216 Definition::TypeAlias(_) => SymbolKind::TypeAlias,
217 Definition::BuiltinLifetime(_) => SymbolKind::LifetimeParam,
218 Definition::BuiltinType(_) => return HlTag::BuiltinType,
219 Definition::Macro(_) => SymbolKind::Macro,
220 Definition::Field(_) | Definition::TupleField(_) => SymbolKind::Field,
221 Definition::SelfType(_) => SymbolKind::Impl,
222 Definition::Local(_) => SymbolKind::Local,
223 Definition::GenericParam(gp) => match gp {
224 hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam,
225 hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
226 hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam,
227 },
228 Definition::Label(_) => SymbolKind::Label,
229 Definition::BuiltinAttr(_) => SymbolKind::BuiltinAttr,
230 Definition::ToolModule(_) => SymbolKind::ToolModule,
231 Definition::DeriveHelper(_) => SymbolKind::DeriveHelper,
232 Definition::InlineAsmRegOrRegClass(_) => SymbolKind::InlineAsmRegOrRegClass,
233 Definition::InlineAsmOperand(_) => SymbolKind::Local,
234 };
235 HlTag::Symbol(symbol)
236}