use either::Either;
use hir::{InFile, Semantics};
-use ide_db::{call_info::ActiveParameter, helpers::rust_doc::is_rust_fence, SymbolKind};
+use ide_db::{
+ active_parameter::ActiveParameter, defs::Definition, rust_doc::is_rust_fence, SymbolKind,
+};
use syntax::{
- ast::{self, AstNode, IsString},
- AstToken, NodeOrToken, SyntaxNode, SyntaxToken, TextRange, TextSize,
+ ast::{self, AstNode, IsString, QuoteOffsets},
+ AstToken, NodeOrToken, SyntaxNode, TextRange, TextSize,
};
use crate::{
- doc_links::{doc_attributes, extract_definitions_from_markdown, resolve_doc_path_for_def},
+ doc_links::{doc_attributes, extract_definitions_from_docs, resolve_doc_path_for_def},
+ syntax_highlighting::{highlights::Highlights, injector::Injector},
Analysis, HlMod, HlRange, HlTag, RootDatabase,
};
-use super::{highlights::Highlights, injector::Injector};
-
pub(super) fn ra_fixture(
hl: &mut Highlights,
sema: &Semantics<RootDatabase>,
- literal: ast::String,
- expanded: SyntaxToken,
+ literal: &ast::String,
+ expanded: &ast::String,
) -> Option<()> {
- let active_parameter = ActiveParameter::at_token(&sema, expanded)?;
+ let active_parameter = ActiveParameter::at_token(sema, expanded.syntax().clone())?;
if !active_parameter.ident().map_or(false, |name| name.text().starts_with("ra_fixture")) {
return None;
}
}
}
- let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string());
+ let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text());
for mut hl_range in analysis.highlight(tmp_file_id).unwrap() {
for range in inj.map_range_up(hl_range.range) {
Some(())
}
-const RUSTDOC_FENCE: &'static str = "```";
+const RUSTDOC_FENCE_LENGTH: usize = 3;
+const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
/// Injection of syntax highlighting of doctests.
pub(super) fn doc_comment(
hl: &mut Highlights,
sema: &Semantics<RootDatabase>,
- node: InFile<&SyntaxNode>,
+ InFile { file_id: src_file_id, value: node }: InFile<&SyntaxNode>,
) {
- let (attributes, def) = match doc_attributes(sema, node.value) {
+ let (attributes, def) = match doc_attributes(sema, node) {
Some(it) => it,
None => return,
};
- let mut inj = Injector::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;
-
- // Replace the original, line-spanning comment ranges by new, only comment-prefix
- // spanning comment ranges.
- let mut new_comments = Vec::new();
- let mut string;
-
+ // Extract intra-doc links and emit highlights for them.
if let Some((docs, doc_mapping)) = attributes.docs_with_rangemap(sema.db) {
- extract_definitions_from_markdown(docs.as_str())
+ extract_definitions_from_docs(&docs)
.into_iter()
.filter_map(|(range, link, ns)| {
- let def = resolve_doc_path_for_def(sema.db, def, &link, ns)?;
- let InFile { file_id, value: range } = doc_mapping.map(range)?;
- (file_id == node.file_id).then(|| (range, def))
+ doc_mapping.map(range).filter(|mapping| mapping.file_id == src_file_id).and_then(
+ |InFile { value: mapped_range, .. }| {
+ Some(mapped_range).zip(resolve_doc_path_for_def(sema.db, def, &link, ns))
+ },
+ )
})
.for_each(|(range, def)| {
hl.add(HlRange {
});
}
+ // Extract doc-test sources from the docs and calculate highlighting for them.
+
+ let mut inj = Injector::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;
+
for attr in attributes.by_key("doc").attrs() {
- let InFile { file_id, value: src } = attrs_source_map.source_of(&attr);
- if file_id != node.file_id {
+ let InFile { file_id, value: src } = attrs_source_map.source_of(attr);
+ if file_id != src_file_id {
continue;
}
- let (line, range, prefix) = match &src {
+ let (line, range) = match &src {
Either::Left(it) => {
string = match find_doc_string_in_attr(attr, it) {
Some(it) => it,
None => continue,
};
- let text_range = string.syntax().text_range();
- let text_range = TextRange::new(
- text_range.start() + TextSize::from(1),
- text_range.end() - TextSize::from(1),
- );
let text = string.text();
- (&text[1..text.len() - 1], text_range, "")
+ 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) => {
- (comment.text(), comment.syntax().text_range(), comment.prefix())
+ 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 pos = TextSize::from(prefix.len() as u32);
let mut range_start = range.start();
for line in line.split('\n') {
let line_len = TextSize::from(line.len() as u32);
let next_range_start = range_start + line_len + TextSize::from(1);
mem::replace(&mut range_start, next_range_start)
};
- // only first line has the prefix so take it away for future iterations
- let mut pos = mem::take(&mut pos);
+ let mut pos = TextSize::from(0);
- match line.find(RUSTDOC_FENCE) {
+ 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.len()..];
+ let guards = &line[idx + RUSTDOC_FENCE_LENGTH..];
let is_rust = is_rust_fence(guards);
is_doctest = is_codeblock && is_rust;
continue;
inj.add_unmapped("\n}");
- let (analysis, tmp_file_id) = Analysis::from_single_file(inj.text().to_string());
+ let (analysis, tmp_file_id) = Analysis::from_single_file(inj.take_text());
- for HlRange { range, highlight, binding_hash } in
- analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap()
- {
- for range in inj.map_range_up(range) {
- hl.add(HlRange { range, highlight: highlight | HlMod::Injected, binding_hash });
+ if let Ok(ranges) = analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)) {
+ for HlRange { range, highlight, binding_hash } in ranges {
+ for range in inj.map_range_up(range) {
+ hl.add(HlRange { range, highlight: highlight | HlMod::Injected, binding_hash });
+ }
}
}
string.text().get(1..string.text().len() - 1).map_or(false, |it| it == text)
})
}
- _ => return None,
+ _ => None,
}
}
-fn module_def_to_hl_tag(def: hir::ModuleDef) -> HlTag {
+fn module_def_to_hl_tag(def: Definition) -> HlTag {
let symbol = match def {
- hir::ModuleDef::Module(_) => SymbolKind::Module,
- hir::ModuleDef::Function(_) => SymbolKind::Function,
- hir::ModuleDef::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct,
- hir::ModuleDef::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum,
- hir::ModuleDef::Adt(hir::Adt::Union(_)) => SymbolKind::Union,
- hir::ModuleDef::Variant(_) => SymbolKind::Variant,
- hir::ModuleDef::Const(_) => SymbolKind::Const,
- hir::ModuleDef::Static(_) => SymbolKind::Static,
- hir::ModuleDef::Trait(_) => SymbolKind::Trait,
- hir::ModuleDef::TypeAlias(_) => SymbolKind::TypeAlias,
- hir::ModuleDef::BuiltinType(_) => return HlTag::BuiltinType,
+ Definition::Module(_) => SymbolKind::Module,
+ Definition::Function(_) => SymbolKind::Function,
+ Definition::Adt(hir::Adt::Struct(_)) => SymbolKind::Struct,
+ Definition::Adt(hir::Adt::Enum(_)) => SymbolKind::Enum,
+ Definition::Adt(hir::Adt::Union(_)) => SymbolKind::Union,
+ Definition::Variant(_) => SymbolKind::Variant,
+ Definition::Const(_) => SymbolKind::Const,
+ Definition::Static(_) => SymbolKind::Static,
+ Definition::Trait(_) => SymbolKind::Trait,
+ Definition::TypeAlias(_) => SymbolKind::TypeAlias,
+ Definition::BuiltinType(_) => return HlTag::BuiltinType,
+ Definition::Macro(_) => SymbolKind::Macro,
+ Definition::Field(_) => SymbolKind::Field,
+ Definition::SelfType(_) => SymbolKind::Impl,
+ Definition::Local(_) => SymbolKind::Local,
+ Definition::GenericParam(gp) => match gp {
+ hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam,
+ hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
+ hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam,
+ },
+ Definition::Label(_) => SymbolKind::Label,
+ Definition::BuiltinAttr(_) => SymbolKind::BuiltinAttr,
+ Definition::ToolModule(_) => SymbolKind::ToolModule,
};
HlTag::Symbol(symbol)
}