Unnamed repository; edit this file 'description' to name the repository.
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
# Author : Timothy DeHerrera <[email protected]>
"comment".fg = "comment"

"constant".fg = "purple"
"constant.builtin".fg = "olive"
"constant.character".fg = "carnation"
"constant.character.escape".fg = "magenta"
"constant.numeric".fg = "cyan"
"constant.numeric.float".fg = "red"

"function".fg = "green"
"function.builtin".fg = "sand"
"function.macro".fg = "blue"
"function.method".fg = "opal"

"keyword" = { fg = "magenta", modifiers = ["bold"] }
"keyword.operator" = { fg = "coral", modifiers = ["bold"] }
"keyword.function" = { fg = "lilac", modifiers = ["bold"] }
"keyword.control" = { fg = "carnation", modifiers = ["bold"]}
"keyword.control.exception" = { fg = "red", modifiers = ["bold"] }
"keyword.storage" = { fg = "coral", modifiers = ["bold"] }

"operator".fg = "coral"

"punctuation".fg = "magenta"
"punctuation.delimiter".fg = "coral"
"punctuation.bracket".fg = "foreground"

"string".fg = "yellow"
"string.special".fg = "blue"
"string.regexp".fg = "red"
"tag".fg = "carnation"
"attribute".fg = "opal"

"type".fg = "opal"
"type.variant".fg = "sand"
"type.builtin".fg = "yellow"
"type.enum.variant".fg = "sand"

"variable".fg = "cyan"
"variable.builtin".fg = "olive"
"variable.other.member".fg = "lilac"
"variable.parameter" = { fg ="blue", modifiers = ["italic"] }

"namespace".fg = "olive"
"constructor".fg = "sand"
"special".fg = "magenta"
"label".fg = "magenta"

"diff.plus".fg = "green"
"diff.delta".fg = "blue"
"diff.minus".fg = "red"

"ui.background" = { fg = "foreground", bg = "background" }
"ui.cursor" =  { fg = "background", bg = "blue", modifiers = ["dim"] }
"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
"ui.help" = { fg = "foreground", bg = "background_dark" }
"ui.linenr" = { fg = "comment" }
"ui.linenr.selected" = { fg = "foreground" }
"ui.menu" = { fg = "foreground", bg = "background_dark" }
"ui.menu.selected" = { fg = "cyan", bg = "background_dark" }
"ui.popup" = { fg = "foreground", bg = "background_dark" }
"ui.selection" = { bg = "secondary_highlight" }
"ui.selection.primary" = { bg = "primary_highlight" }
"ui.cursorline" = { bg = "background_dark" }
"ui.statusline" = { fg = "foreground", bg = "background_dark" }
"ui.statusline.inactive" = { fg = "comment", bg = "background_dark" }
"ui.statusline.insert" = { fg = "olive", bg = "background_dark" }
"ui.statusline.normal" = { fg = "opal", bg = "background_dark" }
"ui.statusline.select" = { fg = "carnation", bg = "background_dark" }
"ui.text" = { fg = "foreground" }
"ui.text.focus" = { fg = "cyan" }
"ui.window" = { fg = "foreground" }
"ui.virtual.whitespace" = { fg = "comment" }
"ui.virtual.indent-guide" = { fg = "opal" }
"ui.virtual.ruler" = { bg = "background_dark" }

"error" = { fg = "red" }
"warning" = { fg = "cyan" }

"markup.heading" = { fg = "purple", modifiers = ["bold"] }
"markup.link.label" = { fg = "blue", modifiers = ["italic"] }
"markup.list" = "cyan"
"markup.bold" = { fg = "blue", modifiers = ["bold"] }
"markup.italic" = { fg = "yellow", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = "cyan"
"markup.link.text" = "magenta"
"markup.quote" = { fg = "yellow", modifiers = ["italic"] }
"markup.raw" = { fg = "foreground" }

[palette]
background = "#282a36"
background_dark = "#21222c"
primary_highlight = "#800049"
secondary_highlight = "#4d4f66"
foreground = "#eff0eb"
comment = "#a39e9b"

# main colors
red = "#ff5c57"
blue = "#57c7ff"
yellow = "#f3f99d"
green = "#5af78e"
purple = "#bd93f9"
cyan = "#9aedfe"
magenta = "#ff6ac1"

# aux colors
lilac = "#c9c5fb"
coral = "#f97c7c"
sand = "#ffab6f"
carnation = "#f99fc6"
olive = "#b6d37c"
opal = "#b1d7c7"
a> 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
//! Documentation attribute related utilities.
use either::Either;
use hir::{
    AttrId, AttrSourceMap, AttrsWithOwner, HasAttrs, InFile,
    db::{DefDatabase, HirDatabase},
    resolve_doc_path_on, sym,
};
use itertools::Itertools;
use span::{TextRange, TextSize};
use syntax::{
    AstToken,
    ast::{self, IsString},
};

/// Holds documentation
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Documentation(String);

impl Documentation {
    pub fn new(s: String) -> Self {
        Documentation(s)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl From<Documentation> for String {
    fn from(Documentation(string): Documentation) -> Self {
        string
    }
}

pub trait HasDocs: HasAttrs {
    fn docs(self, db: &dyn HirDatabase) -> Option<Documentation>;
    fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)>;
    fn resolve_doc_path(
        self,
        db: &dyn HirDatabase,
        link: &str,
        ns: Option<hir::Namespace>,
        is_inner_doc: bool,
    ) -> Option<hir::DocLinkDef>;
}
/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
#[derive(Debug)]
pub struct DocsRangeMap {
    source_map: AttrSourceMap,
    // (docstring-line-range, attr_index, attr-string-range)
    // a mapping from the text range of a line of the [`Documentation`] to the attribute index and
    // the original (untrimmed) syntax doc line
    mapping: Vec<(TextRange, AttrId, TextRange)>,
}

impl DocsRangeMap {
    /// Maps a [`TextRange`] relative to the documentation string back to its AST range
    pub fn map(&self, range: TextRange) -> Option<(InFile<TextRange>, AttrId)> {
        let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?;
        let (line_docs_range, idx, original_line_src_range) = self.mapping[found];
        if !line_docs_range.contains_range(range) {
            return None;
        }

        let relative_range = range - line_docs_range.start();

        let InFile { file_id, value: source } = self.source_map.source_of_id(idx);
        match source {
            Either::Left(attr) => {
                let string = get_doc_string_in_attr(attr)?;
                let text_range = string.open_quote_text_range()?;
                let range = TextRange::at(
                    text_range.end() + original_line_src_range.start() + relative_range.start(),
                    string.syntax().text_range().len().min(range.len()),
                );
                Some((InFile { file_id, value: range }, idx))
            }
            Either::Right(comment) => {
                let text_range = comment.syntax().text_range();
                let range = TextRange::at(
                    text_range.start()
                        + TextSize::try_from(comment.prefix().len()).ok()?
                        + original_line_src_range.start()
                        + relative_range.start(),
                    text_range.len().min(range.len()),
                );
                Some((InFile { file_id, value: range }, idx))
            }
        }
    }

    pub fn shift_docstring_line_range(self, offset: TextSize) -> DocsRangeMap {
        let mapping = self
            .mapping
            .into_iter()
            .map(|(buf_offset, id, base_offset)| {
                let buf_offset = buf_offset.checked_add(offset).unwrap();
                (buf_offset, id, base_offset)
            })
            .collect_vec();
        DocsRangeMap { source_map: self.source_map, mapping }
    }
}

pub fn docs_with_rangemap(
    db: &dyn DefDatabase,
    attrs: &AttrsWithOwner,
) -> Option<(Documentation, DocsRangeMap)> {
    let docs = attrs
        .by_key(sym::doc)
        .attrs()
        .filter_map(|attr| attr.string_value_unescape().map(|s| (s, attr.id)));
    let indent = doc_indent(attrs);
    let mut buf = String::new();
    let mut mapping = Vec::new();
    for (doc, idx) in docs {
        if !doc.is_empty() {
            let mut base_offset = 0;
            for raw_line in doc.split('\n') {
                let line = raw_line.trim_end();
                let line_len = line.len();
                let (offset, line) = match line.char_indices().nth(indent) {
                    Some((offset, _)) => (offset, &line[offset..]),
                    None => (0, line),
                };
                let buf_offset = buf.len();
                buf.push_str(line);
                mapping.push((
                    TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?),
                    idx,
                    TextRange::at(
                        (base_offset + offset).try_into().ok()?,
                        line_len.try_into().ok()?,
                    ),
                ));
                buf.push('\n');
                base_offset += raw_line.len() + 1;
            }
        } else {
            buf.push('\n');
        }
    }
    buf.pop();
    if buf.is_empty() {
        None
    } else {
        Some((Documentation(buf), DocsRangeMap { mapping, source_map: attrs.source_map(db) }))
    }
}

pub fn docs_from_attrs(attrs: &hir::Attrs) -> Option<String> {
    let docs = attrs.by_key(sym::doc).attrs().filter_map(|attr| attr.string_value_unescape());
    let indent = doc_indent(attrs);
    let mut buf = String::new();
    for doc in docs {
        // str::lines doesn't yield anything for the empty string
        if !doc.is_empty() {
            // We don't trim trailing whitespace from doc comments as multiple trailing spaces
            // indicates a hard line break in Markdown.
            let lines = doc.lines().map(|line| {
                line.char_indices().nth(indent).map_or(line, |(offset, _)| &line[offset..])
            });

            buf.extend(Itertools::intersperse(lines, "\n"));
        }
        buf.push('\n');
    }
    buf.pop();
    if buf.is_empty() { None } else { Some(buf) }
}

macro_rules! impl_has_docs {
    ($($def:ident,)*) => {$(
        impl HasDocs for hir::$def {
            fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
                docs_from_attrs(&self.attrs(db)).map(Documentation)
            }
            fn docs_with_rangemap(
                self,
                db: &dyn HirDatabase,
            ) -> Option<(Documentation, DocsRangeMap)> {
                docs_with_rangemap(db, &self.attrs(db))
            }
            fn resolve_doc_path(
                self,
                db: &dyn HirDatabase,
                link: &str,
                ns: Option<hir::Namespace>,
                is_inner_doc: bool,
            ) -> Option<hir::DocLinkDef> {
                resolve_doc_path_on(db, self, link, ns, is_inner_doc)
            }
        }
    )*};
}

impl_has_docs![
    Variant, Field, Static, Const, Trait, TypeAlias, Macro, Function, Adt, Module, Impl, Crate,
];

macro_rules! impl_has_docs_enum {
    ($($variant:ident),* for $enum:ident) => {$(
        impl HasDocs for hir::$variant {
            fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
                hir::$enum::$variant(self).docs(db)
            }

            fn docs_with_rangemap(
                self,
                db: &dyn HirDatabase,
            ) -> Option<(Documentation, DocsRangeMap)> {
                hir::$enum::$variant(self).docs_with_rangemap(db)
            }
            fn resolve_doc_path(
                self,
                db: &dyn HirDatabase,
                link: &str,
                ns: Option<hir::Namespace>,
                is_inner_doc: bool,
            ) -> Option<hir::DocLinkDef> {
                hir::$enum::$variant(self).resolve_doc_path(db, link, ns, is_inner_doc)
            }
        }
    )*};
}

impl_has_docs_enum![Struct, Union, Enum for Adt];

impl HasDocs for hir::AssocItem {
    fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
        match self {
            hir::AssocItem::Function(it) => it.docs(db),
            hir::AssocItem::Const(it) => it.docs(db),
            hir::AssocItem::TypeAlias(it) => it.docs(db),
        }
    }

    fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)> {
        match self {
            hir::AssocItem::Function(it) => it.docs_with_rangemap(db),
            hir::AssocItem::Const(it) => it.docs_with_rangemap(db),
            hir::AssocItem::TypeAlias(it) => it.docs_with_rangemap(db),
        }
    }

    fn resolve_doc_path(
        self,
        db: &dyn HirDatabase,
        link: &str,
        ns: Option<hir::Namespace>,
        is_inner_doc: bool,
    ) -> Option<hir::DocLinkDef> {
        match self {
            hir::AssocItem::Function(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
            hir::AssocItem::Const(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
            hir::AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns, is_inner_doc),
        }
    }
}

impl HasDocs for hir::ExternCrateDecl {
    fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
        let crate_docs = docs_from_attrs(&self.resolved_crate(db)?.root_module().attrs(db));
        let decl_docs = docs_from_attrs(&self.attrs(db));
        match (decl_docs, crate_docs) {
            (None, None) => None,
            (Some(decl_docs), None) => Some(decl_docs),
            (None, Some(crate_docs)) => Some(crate_docs),
            (Some(mut decl_docs), Some(crate_docs)) => {
                decl_docs.push('\n');
                decl_docs.push('\n');
                decl_docs += &crate_docs;
                Some(decl_docs)
            }
        }
        .map(Documentation::new)
    }

    fn docs_with_rangemap(self, db: &dyn HirDatabase) -> Option<(Documentation, DocsRangeMap)> {
        let crate_docs = docs_with_rangemap(db, &self.resolved_crate(db)?.root_module().attrs(db));
        let decl_docs = docs_with_rangemap(db, &self.attrs(db));
        match (decl_docs, crate_docs) {
            (None, None) => None,
            (Some(decl_docs), None) => Some(decl_docs),
            (None, Some(crate_docs)) => Some(crate_docs),
            (
                Some((Documentation(mut decl_docs), mut decl_range_map)),
                Some((Documentation(crate_docs), crate_range_map)),
            ) => {
                decl_docs.push('\n');
                decl_docs.push('\n');
                let offset = TextSize::new(decl_docs.len() as u32);
                decl_docs += &crate_docs;
                let crate_range_map = crate_range_map.shift_docstring_line_range(offset);
                decl_range_map.mapping.extend(crate_range_map.mapping);
                Some((Documentation(decl_docs), decl_range_map))
            }
        }
    }
    fn resolve_doc_path(
        self,
        db: &dyn HirDatabase,
        link: &str,
        ns: Option<hir::Namespace>,
        is_inner_doc: bool,
    ) -> Option<hir::DocLinkDef> {
        resolve_doc_path_on(db, self, link, ns, is_inner_doc)
    }
}

fn get_doc_string_in_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 => {
            // FIXME: See highlight injection for what to do here
            None
        }
        _ => None,
    }
}

fn doc_indent(attrs: &hir::Attrs) -> usize {
    let mut min = !0;
    for val in attrs.by_key(sym::doc).attrs().filter_map(|attr| attr.string_value_unescape()) {
        if let Some(m) =
            val.lines().filter_map(|line| line.chars().position(|c| !c.is_whitespace())).min()
        {
            min = min.min(m);
        }
    }
    min
}