Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/syntax_highlighting/inject.rs')
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs189
1 files changed, 58 insertions, 131 deletions
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 7955f5ac0d..26d2bb5e02 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -1,16 +1,13 @@
//! "Recursive" Syntax highlighting for code in doctests and fixtures.
-use std::mem;
-
-use either::Either;
-use hir::{EditionedFileId, HirFileId, InFile, Semantics, sym};
-use ide_db::range_mapper::RangeMapper;
+use hir::{EditionedFileId, HirFileId, InFile, Semantics};
use ide_db::{
- SymbolKind, defs::Definition, documentation::docs_with_rangemap, rust_doc::is_rust_fence,
+ SymbolKind, defs::Definition, documentation::Documentation, range_mapper::RangeMapper,
+ rust_doc::is_rust_fence,
};
use syntax::{
- AstToken, NodeOrToken, SyntaxNode, TextRange, TextSize,
- ast::{self, AstNode, IsString, QuoteOffsets},
+ SyntaxNode, TextRange, TextSize,
+ ast::{self, IsString},
};
use crate::{
@@ -96,118 +93,79 @@ pub(super) fn doc_comment(
None => return,
};
let src_file_id: HirFileId = src_file_id.into();
+ let Some(docs) = attributes.hir_docs(sema.db) else { return };
// Extract intra-doc links and emit highlights for them.
- if let Some((docs, doc_mapping)) = docs_with_rangemap(sema.db, &attributes) {
- extract_definitions_from_docs(&docs)
- .into_iter()
- .filter_map(|(range, link, ns)| {
- doc_mapping
- .map(range)
- .filter(|(mapping, _)| mapping.file_id == src_file_id)
- .and_then(|(InFile { value: mapped_range, .. }, attr_id)| {
- Some(mapped_range).zip(resolve_doc_path_for_def(
- sema.db,
- def,
- &link,
- ns,
- attr_id.is_inner_attr(),
- ))
- })
- })
- .for_each(|(range, def)| {
- hl.add(HlRange {
- range,
- highlight: module_def_to_hl_tag(def)
- | HlMod::Documentation
- | HlMod::Injected
- | HlMod::IntraDocLink,
- binding_hash: None,
+ extract_definitions_from_docs(&Documentation::new_borrowed(docs.docs()))
+ .into_iter()
+ .filter_map(|(range, link, ns)| {
+ docs.find_ast_range(range)
+ .filter(|(mapping, _)| mapping.file_id == src_file_id)
+ .and_then(|(InFile { value: mapped_range, .. }, is_inner)| {
+ Some(mapped_range)
+ .zip(resolve_doc_path_for_def(sema.db, def, &link, ns, is_inner))
})
+ })
+ .for_each(|(range, def)| {
+ hl.add(HlRange {
+ range,
+ highlight: module_def_to_hl_tag(def)
+ | HlMod::Documentation
+ | HlMod::Injected
+ | HlMod::IntraDocLink,
+ binding_hash: None,
})
- }
+ });
// Extract doc-test sources from the docs and calculate highlighting for them.
let mut inj = RangeMapper::default();
inj.add_unmapped("fn doctest() {\n");
- let attrs_source_map = attributes.source_map(sema.db);
-
let mut is_codeblock = false;
let mut is_doctest = false;
- let mut new_comments = Vec::new();
- let mut string;
+ let mut has_doctests = false;
+
+ let mut docs_offset = TextSize::new(0);
+ for mut line in docs.docs().split('\n') {
+ let mut line_docs_offset = docs_offset;
+ docs_offset += TextSize::of(line) + TextSize::of("\n");
+
+ match RUSTDOC_FENCES.into_iter().find_map(|fence| line.find(fence)) {
+ Some(idx) => {
+ is_codeblock = !is_codeblock;
+ // Check whether code is rust by inspecting fence guards
+ let guards = &line[idx + RUSTDOC_FENCE_LENGTH..];
+ let is_rust = is_rust_fence(guards);
+ is_doctest = is_codeblock && is_rust;
+ continue;
+ }
+ None if !is_doctest => continue,
+ None => (),
+ }
+
+ // lines marked with `#` should be ignored in output, we skip the `#` char
+ if line.starts_with('#') {
+ line_docs_offset += TextSize::of("#");
+ line = &line["#".len()..];
+ }
- for attr in attributes.by_key(sym::doc).attrs() {
- let InFile { file_id, value: src } = attrs_source_map.source_of(attr);
+ let Some((InFile { file_id, value: mapped_range }, _)) =
+ docs.find_ast_range(TextRange::at(line_docs_offset, TextSize::of(line)))
+ else {
+ continue;
+ };
if file_id != src_file_id {
continue;
}
- let (line, range) = match &src {
- Either::Left(it) => {
- string = match find_doc_string_in_attr(attr, it) {
- Some(it) => it,
- None => continue,
- };
- let text = string.text();
- let text_range = string.syntax().text_range();
- match string.quote_offsets() {
- Some(QuoteOffsets { contents, .. }) => {
- (&text[contents - text_range.start()], contents)
- }
- None => (text, text_range),
- }
- }
- Either::Right(comment) => {
- let value = comment.prefix().len();
- let range = comment.syntax().text_range();
- (
- &comment.text()[value..],
- TextRange::new(range.start() + TextSize::try_from(value).unwrap(), range.end()),
- )
- }
- };
-
- let mut range_start = range.start();
- for line in line.split('\n') {
- let line_len = TextSize::from(line.len() as u32);
- let prev_range_start = {
- let next_range_start = range_start + line_len + TextSize::from(1);
- mem::replace(&mut range_start, next_range_start)
- };
- let mut pos = TextSize::from(0);
-
- match RUSTDOC_FENCES.into_iter().find_map(|fence| line.find(fence)) {
- Some(idx) => {
- is_codeblock = !is_codeblock;
- // Check whether code is rust by inspecting fence guards
- let guards = &line[idx + RUSTDOC_FENCE_LENGTH..];
- let is_rust = is_rust_fence(guards);
- is_doctest = is_codeblock && is_rust;
- continue;
- }
- None if !is_doctest => continue,
- None => (),
- }
- // whitespace after comment is ignored
- if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) {
- pos += TextSize::of(ws);
- }
- // lines marked with `#` should be ignored in output, we skip the `#` char
- if line[pos.into()..].starts_with('#') {
- pos += TextSize::of('#');
- }
-
- new_comments.push(TextRange::at(prev_range_start, pos));
- inj.add(&line[pos.into()..], TextRange::new(pos, line_len) + prev_range_start);
- inj.add_unmapped("\n");
- }
+ has_doctests = true;
+ inj.add(line, mapped_range);
+ inj.add_unmapped("\n");
}
- if new_comments.is_empty() {
+ if !has_doctests {
return; // no need to run an analysis on an empty file
}
@@ -240,37 +198,6 @@ pub(super) fn doc_comment(
}
}
}
-
- for range in new_comments {
- hl.add(HlRange {
- range,
- highlight: HlTag::Comment | HlMod::Documentation,
- binding_hash: None,
- });
- }
-}
-
-fn find_doc_string_in_attr(attr: &hir::Attr, it: &ast::Attr) -> Option<ast::String> {
- match it.expr() {
- // #[doc = lit]
- Some(ast::Expr::Literal(lit)) => match lit.kind() {
- ast::LiteralKind::String(it) => Some(it),
- _ => None,
- },
- // #[cfg_attr(..., doc = "", ...)]
- None => {
- // We gotta hunt the string token manually here
- let text = attr.string_value()?.as_str();
- // FIXME: We just pick the first string literal that has the same text as the doc attribute
- // This means technically we might highlight the wrong one
- it.syntax()
- .descendants_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .filter_map(ast::String::cast)
- .find(|string| string.text().get(1..string.text().len() - 1) == Some(text))
- }
- _ => None,
- }
}
fn module_def_to_hl_tag(def: Definition) -> HlTag {