diff --git a/.travis.yml b/.travis.yml index 41c6eafdc84..cccb0027f46 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,13 @@ os: - osx rust: - nightly -install: +install: | # Required for Racer autoconfiguration rustup component add rust-src + rustup component add rust-analysis script: | #!/bin/bash set -e - cargo build -v cargo test -v - set +e diff --git a/README.md b/README.md index b8a5872bcee..997230f5e31 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,8 @@ Currently we accept the following options: * `no_default_features` (`bool`, defaults to `false`) disables default Cargo features * `racer_completion` (`bool`, defaults to `true`) enables code completion using - racer (which is, at the moment, our only code completion backend) + racer (which is, at the moment, our only code completion backend). Also enables + hover tooltips to fall back to racer when save-analysis data is unavailable. * `clippy_preference` (`String`, defaults to `"opt-in"`) controls eagerness of clippy diagnostics when available. Valid values are _(case-insensitive)_: - `"off"` Disable clippy lints. @@ -131,6 +132,14 @@ and the following unstable options: * `cfg_test` (`bool`, defaults to `false`) checks the project as if you were running `cargo test` rather than `cargo build`. I.e., compiles (but does not run) test code. +* `full_docs` (`bool`, defaults to `false`) instructs rustc to populate the + save-analysis data with full source documentation. When set to `false`, only the + first paragraph is recorded. This option _currently_ has little to no effect on + hover tooltips. The save-analysis docs are only used if source extraction fails. + This option has no effect on the standard library. +* `show_hover_context` show additional context in hover tooltips when available. + This is often the local variable declaration. When set to false the content is + only availabe when holding the `ctrl` key in some editors. ## Troubleshooting diff --git a/appveyor.yml b/appveyor.yml index fb4d3b519fc..91c7be99cc1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,6 +22,7 @@ install: - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin # Required for Racer autoconfiguration - rustup component add rust-src + - rustup component add rust-analysis # Print version info - rustc -Vv - cargo -V diff --git a/src/actions/hover.rs b/src/actions/hover.rs new file mode 100644 index 00000000000..a0692486685 --- /dev/null +++ b/src/actions/hover.rs @@ -0,0 +1,2065 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::actions::requests; +use crate::actions::InitActionContext; +use crate::config::FmtConfig; +use crate::lsp_data::*; +use crate::server::ResponseError; + +use racer; +use rls_analysis::{Def, DefKind}; +use rls_span::{Column, Row, Span, ZeroIndexed}; +use rls_vfs::{self as vfs, Vfs}; +use rustfmt_nightly::{Session as FmtSession, Input as FmtInput, NewlineStyle}; + +use std::path::{Path, PathBuf}; +use log::*; + +/// Cleanup documentation code blocks. The `docs` are expected to have +/// the preceeding `///` or `//!` prefixes already trimmed away. Rust code +/// blocks will ignore lines beginning with `#`. Code block annotations +/// that are common to Rust will be converted to `rust` allow for markdown +/// syntax coloring. +pub fn process_docs(docs: &str) -> String { + trace!("process_docs"); + let mut in_codeblock = false; + let mut in_rust_codeblock = false; + let mut processed_docs = Vec::new(); + let mut last_line_ignored = false; + for line in docs.lines() { + let mut trimmed = line.trim(); + if trimmed.starts_with("```") { + in_rust_codeblock = trimmed == "```" + || trimmed.contains("rust") + || trimmed.contains("no_run") + || trimmed.contains("ignore") + || trimmed.contains("should_panic") + || trimmed.contains("compile_fail"); + in_codeblock = !in_codeblock; + if !in_codeblock { + in_rust_codeblock = false; + } + } + let mut line = if in_rust_codeblock && trimmed.starts_with("```") { + "```rust".into() + } else { + line.to_string() + }; + + // Racer sometimes pulls out comment block headers from the standard library + let ignore_slashes = line.starts_with("////"); + + let maybe_attribute = trimmed.starts_with("#[") || trimmed.starts_with("#!["); + let is_attribute = maybe_attribute && in_rust_codeblock; + let is_hidden = trimmed.starts_with('#') && in_rust_codeblock && !is_attribute; + + let ignore_whitespace = last_line_ignored && trimmed.is_empty(); + let ignore_line = ignore_slashes || ignore_whitespace || is_hidden; + + if !ignore_line { + processed_docs.push(line); + last_line_ignored = false; + } else { + last_line_ignored = true; + } + } + + processed_docs.join("\n") +} + +/// Extracts documentation from the `file` at the specified `row_start`. +/// If the row is equal to `0`, the scan will include the current row +/// and move _downward_. Otherwise, the scan will ignore the specified +/// row and move _upward_. +pub fn extract_docs( + vfs: &Vfs, + file: &Path, + row_start: Row, +) -> Result, vfs::Error> { + let up = row_start.0 > 0; + debug!( + "extract_docs: row_start = {:?}, up = {:?}, file = {:?}", + row_start, up, file + ); + + let mut docs: Vec = Vec::new(); + let mut row = if up { + Row::new_zero_indexed(row_start.0.saturating_sub(1)) + } else { + Row::new_zero_indexed(row_start.0) + }; + let mut in_meta = false; + let mut hit_top = false; + loop { + let line = vfs.load_line(file, row)?; + + let next_row = if up { + Row::new_zero_indexed(row.0.saturating_sub(1)) + } else { + Row::new_zero_indexed(row.0.saturating_add(1)) + }; + + if row == next_row { + hit_top = true; + } else { + row = next_row; + } + + let line = line.trim(); + + let attr_start = line.starts_with("#[") || line.starts_with("#!["); + + if attr_start && line.ends_with(']') && !hit_top { + // Ignore single line attributes + continue; + } + + // Continue with the next line when transitioning out of a + // multi-line attribute + if attr_start || (line.ends_with(']') && !line.starts_with("//")) { + in_meta = !in_meta; + if !in_meta && !hit_top { + continue; + }; + } + + if in_meta { + // Ignore milti-line attributes + continue; + } else if line.starts_with("////") { + trace!( + "extract_docs: break on comment header block, next_row: {:?}, up: {}", + next_row, + up + ); + break; + } else if line.starts_with("///") && !up { + trace!( + "extract_docs: break on non-module docs, next_row: {:?}, up: {}", + next_row, + up + ); + break; + } else if line.starts_with("//!") && up { + trace!( + "extract_docs: break on module docs, next_row: {:?}, up: {}", + next_row, + up + ); + break; + } else if line.starts_with("///") || line.starts_with("//!") { + let pos = if line + .chars() + .nth(3) + .map(|c| c.is_whitespace()) + .unwrap_or(false) + { + 4 + } else { + 3 + }; + let doc_line = line[pos..].into(); + if up { + docs.insert(0, doc_line); + } else { + docs.push(doc_line); + } + } else if hit_top { + // The top of the file was reached + debug!( + "extract_docs: bailing out: prev_row == next_row; next_row = {:?}, up = {}", + next_row, up + ); + break; + } else if line.starts_with("//") { + trace!( + "extract_docs: ignoring non-doc comment, next_row: {:?}, up: {}", + next_row, + up + ); + continue; + } else if line.is_empty() { + trace!( + "extract_docs: ignoring empty line, next_row: {:?}, up: {}", + next_row, + up + ); + continue; + } else { + trace!( + "extract_docs: end of docs, next_row: {:?}, up: {}", + next_row, + up + ); + break; + } + } + debug!( + "extract_docs: complete: row_end = {:?} (exclusive), up = {:?}, file = {:?}", + row, up, file + ); + Ok(docs) +} + +fn extract_and_process_docs(vfs: &Vfs, file: &Path, row_start: Row) -> Option { + extract_docs(vfs, &file, row_start) + .map_err(|e| { + error!( + "failed to extract docs: row: {:?}, file: {:?} ({:?})", + row_start, file, e + ); + }) + .ok() + .map(|docs| docs.join("\n")) + .map(|docs| process_docs(&docs)) + .and_then(empty_to_none) +} + +/// Extracts a function, method, struct, enum, or trait decleration +/// from source. +pub fn extract_decl( + vfs: &Vfs, + file: &Path, + mut row: Row, +) -> Result, vfs::Error> { + debug!("extract_decl: row_start: {:?}, file: {:?}", row, file); + let mut lines = Vec::new(); + loop { + match vfs.load_line(file, row) { + Ok(line) => { + row = Row::new_zero_indexed(row.0.saturating_add(1)); + let mut line = line.trim(); + if let Some(pos) = line.rfind('{') { + line = &line[0..pos].trim_right(); + lines.push(line.into()); + break; + } else if line.ends_with(';') { + let pos = line.len() - 1; + line = &line[0..pos].trim_right(); + lines.push(line.into()); + break; + } else { + lines.push(line.into()); + } + } + Err(e) => { + error!("extract_decl: error: {:?}", e); + return Err(e); + } + } + } + Ok(lines) +} + +fn tooltip_local_variable_usage( + ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_local_variable_usage: {}", def.name); + let vfs = ctx.vfs.clone(); + + let the_type = def.value.trim().into(); + let mut context = String::new(); + if ctx.config.lock().unwrap().show_hover_context { + match vfs.load_line(&def.span.file, def.span.range.row_start) { + Ok(line) => { + context.push_str(line.trim()); + } + Err(e) => { + error!("tooltip_local_variable_usage: error = {:?}", e); + } + } + if context.ends_with('{') { + context.push_str(" ... }"); + } + } + + let context = empty_to_none(context); + let docs = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_type(ctx: &InitActionContext, def: &Def, doc_url: Option) -> Vec { + debug!("tooltip_type: {}", def.name); + + let vfs = ctx.vfs.clone(); + + let the_type = || def.value.trim().into(); + let the_type = def_decl(def, &vfs, the_type); + let docs = def_docs(def, &vfs); + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_field_or_variant( + ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_field_or_variant: {}", def.name); + + let vfs = ctx.vfs.clone(); + + let the_type = def.value.trim().into(); + let docs = def_docs(def, &vfs); + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_struct_enum_union_trait( + ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_struct_enum_union_trait: {}", def.name); + + let vfs = ctx.vfs.clone(); + let fmt_config = ctx.fmt_config(); + + // fallback in case source extration fails + let the_type = || match def.kind { + DefKind::Struct => format!("struct {}", def.name), + DefKind::Enum => format!("enum {}", def.name), + DefKind::Union => format!("union {}", def.name), + DefKind::Trait => format!("trait {}", def.value), + _ => def.value.trim().to_string(), + }; + + let decl = def_decl(def, &vfs, the_type); + + let the_type = format_object(&fmt_config, decl); + let docs = def_docs(def, &vfs); + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_mod(ctx: &InitActionContext, def: &Def, doc_url: Option) -> Vec { + debug!("tooltip_mod: name: {}", def.name); + + let vfs = ctx.vfs.clone(); + + let the_type = def.value.trim(); + let the_type = the_type.replace("\\\\", "/"); + let the_type = the_type.replace("\\", "/"); + + let mod_path = if let Some(dir) = ctx.current_project.file_name() { + if Path::new(&the_type).starts_with(dir) { + the_type.chars().skip(dir.len() + 1).collect() + } else { + the_type + } + } else { + the_type + }; + + let docs = def_docs(def, &vfs); + let context = None; + + create_tooltip(mod_path, doc_url, context, docs) +} + +fn tooltip_function_method( + ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_function_method: {}", def.name); + + let vfs = ctx.vfs.clone(); + let fmt_config = ctx.fmt_config(); + + let the_type = || { + def.value + .trim() + .replacen("fn ", &format!("fn {}", def.name), 1) + .replace("> (", ">(") + .replace("->(", "-> (") + }; + + let decl = def_decl(def, &vfs, the_type); + + let the_type = format_method(&fmt_config, decl); + let docs = def_docs(def, &vfs); + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_local_variable_decl( + _ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_local_variable_decl: {}", def.name); + + let the_type = def.value.trim().into(); + let docs = None; + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_function_arg_usage( + _ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_function_arg_usage: {}", def.name); + + let the_type = def.value.trim().into(); + let docs = None; + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_function_signature_arg( + _ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_function_signature_arg: {}", def.name); + + let the_type = def.value.trim().into(); + let docs = None; + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn tooltip_static_const_decl( + ctx: &InitActionContext, + def: &Def, + doc_url: Option, +) -> Vec { + debug!("tooltip_static_const_decl: {}", def.name); + + let vfs = ctx.vfs.clone(); + + let the_type = def.value.trim().into(); + + let the_type = def_decl(def, &vfs, || the_type); + let docs = def_docs(def, &vfs); + let context = None; + + create_tooltip(the_type, doc_url, context, docs) +} + +fn empty_to_none(s: String) -> Option { + if s.trim().is_empty() { + None + } else { + Some(s) + } +} + +/// Extract and process source documentation for the give `def`. +fn def_docs(def: &Def, vfs: &Vfs) -> Option { + let save_analysis_docs = || empty_to_none(def.docs.trim().into()); + extract_and_process_docs(&vfs, def.span.file.as_ref(), def.span.range.row_start) + .or_else(save_analysis_docs) + .filter(|docs| !docs.trim().is_empty()) +} + +/// Returns the type or function declaration from source. If source +/// extraction fails, the result of `the_type` is used as a fallback. +fn def_decl(def: &Def, vfs: &Vfs, the_type: F) -> String +where + F: FnOnce() -> String, +{ + extract_decl(vfs, &def.span.file, def.span.range.row_start) + .map(|lines| lines.join("\n")) + .ok() + .or_else(|| Some(the_type())) + .unwrap() +} + +/// Creates a tooltip using the function, type or other declaration and +/// optional doc URL, context, or markdown documentation. No additional +/// processing or formatting is performed. +fn create_tooltip( + the_type: String, + doc_url: Option, + context: Option, + docs: Option, +) -> Vec { + let mut tooltip = vec![]; + let rust = "rust".to_string(); + if !the_type.trim().is_empty() { + tooltip.push(MarkedString::from_language_code(rust.clone(), the_type)); + } + if let Some(doc_url) = doc_url { + tooltip.push(MarkedString::from_markdown(doc_url)); + } + if let Some(context) = context { + tooltip.push(MarkedString::from_language_code(rust.clone(), context)); + } + if let Some(docs) = docs { + tooltip.push(MarkedString::from_markdown(docs)); + } + tooltip +} + +/// Skips `skip_components` from the `path` if the path starts with `prefix`. +/// Returns the original path if there is no match. +/// +/// # Examples +/// +/// ``` +/// # use std::path::Path; +/// +/// let base_path = Path::from(".rustup/toolchains/nightly-x86_64-pc-windows-msvc/lib/rustlib/src/rust/src/liballoc/string.rs"); +/// let tidy_path = skip_path_components(base_path.to_path_buf(), ".rustup", 8); +/// assert_eq!("liballoc/string.rs", tidy_path); +/// +/// let base_path = Path::from(".cargo/registry/src/github.com-1ecc6299db9ec823/smallvec-0.6.2/lib.rs"); +/// let tidy_path = skip_path_components(base_path.to_path_buf(), ".cargo", 4); +/// assert_eq!("smallvec-0.6.2/lib.rs", tidy_path); +/// +/// let base_path = Path::from("some/unknown/path/lib.rs"); +/// let tidy_path = skip_path_components(base_path.to_path_buf(), ".rustup", 4); +/// assert_eq!("some/unknown/path/lib.rs", tidy_path); +/// ``` +fn skip_path_components>( + path: PathBuf, + prefix: P, + skip_components: usize, +) -> PathBuf { + if path.starts_with(prefix) { + let comps: PathBuf = path.components().skip(skip_components).fold( + PathBuf::new(), + |mut comps, comp| { + comps.push(comp); + comps + }, + ); + comps + } else { + path + } +} + +/// Collapse parent directory references inside of paths. +/// +/// # Example +/// +/// ``` +/// # use std::path::PathBuf; +/// +/// let path = PathBuf::from("libstd/../liballoc/string.rs"); +/// let collapsed = collapse_parents(path); +/// let expected = PathBuf::from("liballoc/string.rs"); +/// assert_eq!(expected, collapsed); +/// ``` +fn collapse_parents(path: PathBuf) -> PathBuf { + use std::path::Component; + let mut components = Vec::new(); + let mut skip; + let mut skip_prev = false; + for comp in path.components().rev() { + if comp == Component::ParentDir { + skip = true; + } else { + skip = false; + } + if !skip && !skip_prev { + components.insert(0, comp); + } + skip_prev = skip; + } + + components.iter().fold(PathBuf::new(), |mut path, comp| { + path.push(comp); + path + }) +} + +/// Converts a racer `Match` to a save-analysis `Def`. Returns +/// `None` if the coordinates are not available on the match. +fn racer_match_to_def(ctx: &InitActionContext, m: &racer::Match) -> Option { + use racer::MatchType::*; + let kind = match m.mtype { + Struct | Impl | TraitImpl => DefKind::Struct, + Module => DefKind::Mod, + MatchArm => DefKind::Local, + Function => DefKind::Function, + Crate => DefKind::Mod, + Let | IfLet | WhileLet | For => DefKind::Local, + StructField => DefKind::Field, + Enum => DefKind::Enum, + EnumVariant(_) => DefKind::StructVariant, + Type | TypeParameter(_) => DefKind::Type, + FnArg => DefKind::Local, + Trait => DefKind::Trait, + Const => DefKind::Const, + Static => DefKind::Static, + Macro => DefKind::Macro, + Builtin => DefKind::Macro, + }; + + let contextstr = if kind == DefKind::Mod { + use std::env; + + let home = env::var("HOME") + .or_else(|_| env::var("USERPROFILE")) + .map(|dir| PathBuf::from(&dir)) + .unwrap_or_else(|_| PathBuf::new()); + + let contextstr = m.contextstr.replacen("\\\\?\\", "", 1); + let contextstr_path = PathBuf::from(&contextstr); + let contextstr_path = collapse_parents(contextstr_path); + + // Tidy up the module path + contextstr_path + // Strip current project dir prefix + .strip_prefix(&ctx.current_project) + // Strip home directory prefix + .or_else(|_| contextstr_path.strip_prefix(&home)) + .ok() + .map(|path| path.to_path_buf()) + .map(|path| skip_path_components(path, ".rustup", 8)) + .map(|path| skip_path_components(path, ".cargo", 4)) + .and_then(|path| path.to_str().map(|s| s.to_string())) + .unwrap_or_else(|| contextstr.to_string()) + } else { + m.contextstr.trim_right_matches('{').trim().to_string() + }; + + let filepath = m.filepath.clone(); + let matchstr = m.matchstr.clone(); + let matchstr_len = matchstr.len() as u32; + let docs = m.docs.trim().to_string(); + m.coords.map(|coords| { + assert!(coords.row.0 > 0, "racer_match_to_def: racer returned `0` for a 1-based row: {:?}", m); + let (row, col1) = requests::from_racer_coord(coords); + let col2 = Column::new_zero_indexed(col1.0 + matchstr_len); + let row = Row::new_zero_indexed(row.0 - 1); + let span = Span::new(row, row, col1, col2, filepath); + let def = Def { + kind, + span, + name: matchstr, + value: contextstr, + qualname: "".to_string(), + distro_crate: false, + parent: None, + docs, + }; + trace!( + "racer_match_to_def: Def {{ kind: {:?}, span: {:?}, name: {:?}, \ + value: {:?}, qualname: {:?}, distro_crate: {:?}, \ + parent: {:?}, docs.is_empty: {:?} }}", + def.kind, + def.span, + def.name, + def.value, + def.qualname, + def.distro_crate, + def.parent, + def.docs.is_empty() + ); + def + }) +} + +/// Use racer to synthesize a `Def` for the given `span`. If no appropriate +/// match is found with coordinates, `None` is returned. +fn racer_def(ctx: &InitActionContext, span: &Span) -> Option { + let vfs = ctx.vfs.clone(); + let file_path = &span.file; + + if !file_path.as_path().exists() { + error!("racer_def: skipping non-existant file: {:?}", file_path); + return None; + } + + let name = vfs + .load_line(file_path.as_path(), span.range.row_start) + .ok() + .and_then(|line| { + let col_start = span.range.col_start.0 as usize; + let col_end = span.range.col_end.0 as usize; + line.get(col_start..col_end).map(|line| line.to_string()) + }); + + debug!("racer_def: name: {:?}", name); + + let results = ::std::panic::catch_unwind(move || { + let cache = requests::racer_cache(vfs); + let session = racer::Session::new(&cache); + let row = span.range.row_end.one_indexed(); + let coord = requests::racer_coord(row, span.range.col_end); + let location = racer::Location::Coords(coord); + trace!( + "racer_def: file_path: {:?}, location: {:?}", + file_path, + location + ); + let racer_match = racer::find_definition(file_path, location, &session); + trace!("racer_def: match: {:?}", racer_match); + racer_match + // Avoid creating tooltip text that is exactly the item being hovered over + .filter(|m| name.as_ref().map(|name| name != &m.contextstr).unwrap_or(true)) + .and_then(|m| racer_match_to_def(ctx, &m)) + }); + + let results = results.map_err(|_| { + error!("racer_def: racer panicked"); + }); + + results.unwrap_or(None) +} + +/// Formats a struct, enum, union, or trait. The original type is returned +/// in the event of an error. +fn format_object(fmt_config: &FmtConfig, the_type: String) -> String { + debug!("format_object: {}", the_type); + let mut config = fmt_config.get_rustfmt_config().clone(); + config.set().newline_style(NewlineStyle::Unix); + let trimmed = the_type.trim(); + + // Normalize the ending for rustfmt + let object = if trimmed.ends_with(')') { + format!("{};", trimmed) + } else if trimmed.ends_with('}') || trimmed.ends_with(';') { + trimmed.to_string() + } else if trimmed.ends_with('{') { + let trimmed = trimmed.trim_right_matches('{').to_string(); + format!("{}{{}}", trimmed) + } else { + format!("{}{{}}", trimmed) + }; + + let mut out = Vec::::with_capacity(the_type.len()); + let input = FmtInput::Text(object.clone()); + let mut session = FmtSession::new(config, Some(&mut out)); + let formatted = match session.format(input) { + Ok(_) => { + let utf8 = session.out + .as_ref() + .map(|out| String::from_utf8(out.to_vec())) + .unwrap_or_else(|| Ok(trimmed.to_string())); + match utf8.map(|lines| (lines.rfind('{'), lines)) { + Ok((Some(pos), lines)) => lines[0..pos].into(), + Ok((None, lines)) => lines, + _ => trimmed.into(), + } + } + Err(e) => { + error!("format_object: error: {:?}, input: {:?}", e, object); + trimmed.to_string() + } + }; + + // If it's a tuple, remove the trailing ';' and hide non-pub components + // for pub types + let result = if formatted.trim().ends_with(';') { + let mut decl = formatted.trim().trim_right_matches(';'); + if let (Some(pos), true) = (decl.rfind('('), decl.ends_with(')')) { + let mut hidden_count = 0; + let tuple_parts = decl[pos + 1..decl.len() - 1] + .split(',') + .map(|part| { + let part = part.trim(); + if decl.starts_with("pub") && !part.starts_with("pub") { + hidden_count += 1; + "_".to_string() + } else { + part.to_string() + } + }) + .collect::>(); + decl = &decl[0..pos]; + if hidden_count != tuple_parts.len() { + format!("{}({})", decl, tuple_parts.join(", ")) + } else { + decl.to_string() + } + } else { + // not a tuple + decl.into() + } + } else { + // not a tuple or unit struct + formatted + }; + + result.trim().into() +} + +/// Formats a method or function. The original type is returned +/// in the event of an error. +fn format_method(fmt_config: &FmtConfig, the_type: String) -> String { + trace!("format_method: {}", the_type); + let the_type = the_type.trim().trim_right_matches(';').to_string(); + let mut config = fmt_config.get_rustfmt_config().clone(); + config.set().newline_style(NewlineStyle::Unix); + let tab_spaces = config.tab_spaces(); + let method = format!("impl Dummy {{ {} {{ unimplmented!() }} }}", the_type); + let mut out = Vec::::with_capacity(the_type.len()); + let input = FmtInput::Text(method.clone()); + let mut session = FmtSession::new(config, Some(&mut out)); + let result = match session.format(input) { + Ok(_) => { + let lines = session.out + .as_ref() + .map(|out| String::from_utf8(out.to_vec())); + if let Some(Ok(mut lines)) = lines { + if let Some(front_pos) = lines.find('{') { + lines = lines[front_pos..].chars().skip(1).collect(); + } + if let Some(back_pos) = lines.rfind('{') { + lines = lines[0..back_pos].into(); + } + lines + .lines() + .filter(|line| line.trim() != "") + .map(|line| { + let mut spaces = tab_spaces + 1; + let should_trim = |c: char| { + spaces = spaces.saturating_sub(1); + spaces > 0 && c.is_whitespace() + }; + let line = line.trim_left_matches(should_trim); + format!("{}\n", line) + }) + .collect() + } else { + the_type + } + } + Err(e) => { + error!("format_method: error: {:?}, input: {:?}", e, method); + the_type + } + }; + + result.trim().into() +} + +/// Builds a hover tooltip composed of the function signature or type decleration, doc URL +/// (if available in the save-analysis), source extracted documentation, and code context +/// for local variables. +pub fn tooltip( + ctx: &InitActionContext, + params: &TextDocumentPositionParams, +) -> Result, ResponseError> { + let analysis = &ctx.analysis; + + let hover_file_path = parse_file_path!(¶ms.text_document.uri, "hover")?; + let hover_span = ctx.convert_pos_to_span(hover_file_path, params.position); + let hover_span_doc = analysis.docs(&hover_span).unwrap_or_else(|_| String::new()); + let hover_span_typ = analysis + .show_type(&hover_span) + .unwrap_or_else(|_| String::new()); + let hover_span_def = analysis.id(&hover_span).and_then(|id| analysis.get_def(id)); + + trace!("tooltip: span: {:?}", hover_span); + trace!("tooltip: span_doc: {:?}", hover_span_doc); + trace!("tooltip: span_typ: {:?}", hover_span_typ); + trace!("tooltip: span_def: {:?}", hover_span_def); + + let racer_fallback_enabled = ctx.config.lock().unwrap().racer_completion; + + // Fallback to racer if the def was not available and + // racer is enabled. + let hover_span_def = hover_span_def.or_else(|e| { + debug!( + "tooltip: racer_fallback_enabled: {}", + racer_fallback_enabled + ); + if racer_fallback_enabled { + debug!("tooltip: span_def is empty, attempting with racer"); + racer_def(&ctx, &hover_span).ok_or_else(|| { + debug!("tooltip: racer returned an empty result"); + e + }) + } else { + Err(e) + } + }); + + let doc_url = analysis.doc_url(&hover_span).ok(); + + let contents = if let Ok(def) = hover_span_def { + if def.kind == DefKind::Local && def.span == hover_span && def.qualname.contains('$') { + tooltip_local_variable_decl(&ctx, &def, doc_url) + } else if def.kind == DefKind::Local + && def.span != hover_span + && !def.qualname.contains('$') + { + tooltip_function_arg_usage(&ctx, &def, doc_url) + } else if def.kind == DefKind::Local && def.span != hover_span && def.qualname.contains('$') + { + tooltip_local_variable_usage(&ctx, &def, doc_url) + } else if def.kind == DefKind::Local && def.span == hover_span { + tooltip_function_signature_arg(&ctx, &def, doc_url) + } else { + match def.kind { + DefKind::TupleVariant | DefKind::StructVariant | DefKind::Field => { + tooltip_field_or_variant(&ctx, &def, doc_url) + } + DefKind::Enum | DefKind::Union | DefKind::Struct | DefKind::Trait => { + tooltip_struct_enum_union_trait(&ctx, &def, doc_url) + } + DefKind::Function | DefKind::Method => tooltip_function_method(&ctx, &def, doc_url), + DefKind::Mod => tooltip_mod(&ctx, &def, doc_url), + DefKind::Static | DefKind::Const => tooltip_static_const_decl(&ctx, &def, doc_url), + DefKind::Type => tooltip_type(&ctx, &def, doc_url), + _ => { + debug!( + "tooltip: ignoring def: \ + name: {:?}, \ + kind: {:?}, \ + value: {:?}, \ + qualname: {:?}, \ + parent: {:?}", + def.name, def.kind, def.value, def.qualname, def.parent + ); + + Vec::default() + } + } + } + } else { + debug!("tooltip: def is empty"); + Vec::default() + }; + debug!("tooltip: contents.len: {}", contents.len()); + Ok(contents) +} + +#[cfg(test)] +pub mod test { + use super::*; + + use crate::build::BuildPriority; + use crate::config; + use crate::lsp_data::{ClientCapabilities, InitializationOptions}; + use crate::lsp_data::{Position, TextDocumentIdentifier, TextDocumentPositionParams}; + use crate::server::{Output, RequestId}; + use rls_analysis as analysis; + use serde_derive::{Serialize, Deserialize}; + use serde_json as json; + use url::Url; + + use std::env; + use std::fs; + use std::path::PathBuf; + use std::process; + use std::sync::{Arc, Mutex}; + + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] + pub struct Test { + /// Relative to the project _source_ dir (e.g. relative to test_data/hover/src) + pub file: String, + /// One-based line number + pub line: u64, + /// One-based column number + pub col: u64, + } + + impl Test { + fn load_result(&self, dir: &Path) -> Result { + let path = self.path(dir); + let file = fs::File::open(path.clone()) + .map_err(|e| format!("failed to open hover test result: {:?} ({:?})", path, e))?; + let result: Result = json::from_reader(file).map_err(|e| { + format!( + "failed to deserialize hover test result: {:?} ({:?})", + path, e + ) + }); + result + } + } + + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] + struct TestResult { + test: Test, + data: Result, String>, + } + + // MarkedString nad LanguageString don't implement clone + impl Clone for TestResult { + fn clone(&self) -> TestResult { + let ls_clone = |ls: &LanguageString| LanguageString { + language: ls.language.clone(), + value: ls.value.clone(), + }; + let ms_clone = |ms: &MarkedString| match ms { + MarkedString::String(ref s) => MarkedString::String(s.clone()), + MarkedString::LanguageString(ref ls) => MarkedString::LanguageString(ls_clone(ls)), + }; + let test = self.test.clone(); + let data = match self.data { + Ok(ref data) => Ok(data.iter().map(|ms| ms_clone(ms)).collect()), + Err(ref e) => Err(e.clone()), + }; + TestResult { test, data } + } + } + + impl TestResult { + fn save(&self, result_dir: &Path) -> Result<(), String> { + let path = self.test.path(result_dir); + let data = json::to_string_pretty(&self).map_err(|e| { + format!( + "failed to serialize hover test result: {:?} ({:?})", + path, e + ) + })?; + fs::write(path.clone(), data) + .map_err(|e| format!("failed to save hover test result: {:?} ({:?})", path, e)) + } + } + + impl Test { + pub fn new(file: &str, line: u64, col: u64) -> Test { + Test { + file: file.into(), + line, + col, + } + } + + fn path(&self, result_dir: &Path) -> PathBuf { + result_dir.join(format!( + "{}.{:04}_{:03}.json", + self.file, self.line, self.col + )) + } + + fn run(&self, project_dir: &Path, ctx: &InitActionContext) -> TestResult { + let url = + Url::from_file_path(project_dir.join("src").join(&self.file)).expect(&self.file); + let doc_id = TextDocumentIdentifier::new(url.clone()); + let position = Position::new(self.line - 1u64, self.col - 1u64); + let params = TextDocumentPositionParams::new(doc_id, position); + let result = tooltip(&ctx, ¶ms).map_err(|e| format!("tooltip error: {:?}", e)); + + TestResult { + test: self.clone(), + data: result, + } + } + } + + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] + pub struct TestFailure { + /// The test case, indicating file, line, and column + pub test: Test, + /// The location of the loaded result input. + pub expect_file: PathBuf, + /// The location of the saved result output. + pub actual_file: PathBuf, + /// The expected outcome. The outer `Result` relates to errors while + /// loading saved data. The inner `Result` is the saved output from + /// `hover::tooltip`. + pub expect_data: Result, String>, String>, + /// The current output from `hover::tooltip`. The inner `Result` + /// is the output from `hover::tooltip`. + pub actual_data: Result, String>, ()>, + } + + #[derive(Clone, Default)] + pub struct LineOutput { + req_id: Arc>, + lines: Arc>>, + } + + impl LineOutput { + /// Clears and returns the recorded output lines + pub fn reset(&self) -> Vec { + let mut lines = self.lines.lock().unwrap(); + let mut swaped = Vec::new(); + ::std::mem::swap(&mut *lines, &mut swaped); + swaped + } + } + + impl Output for LineOutput { + fn response(&self, output: String) { + self.lines.lock().unwrap().push(output); + } + + fn provide_id(&self) -> RequestId { + let mut id = self.req_id.lock().unwrap(); + *id += 1; + RequestId::Num(*id as u64) + } + } + + pub struct TooltipTestHarness { + ctx: InitActionContext, + project_dir: PathBuf, + working_dir: PathBuf, + } + + impl TooltipTestHarness { + /// Creates a new `TooltipTestHarness`. The `project_dir` must contain + /// a valid rust project with a `Cargo.toml`. + pub fn new(project_dir: PathBuf, output: &O) -> TooltipTestHarness { + let pid = process::id(); + let client_caps = ClientCapabilities { + code_completion_has_snippet_support: true, + related_information_support: true, + }; + let mut config = config::Config::default(); + let cur_dir = env::current_dir().unwrap(); + let target_dir = env::var("CARGO_TARGET_DIR") + .map(|s| Path::new(&s).to_owned()) + .unwrap_or_else(|_| cur_dir.join("target")); + + let working_dir = target_dir.join("tests").join("hover").join("working_dir"); + + config.target_dir = config::Inferrable::Specified(Some(working_dir.clone())); + + let config = Arc::new(Mutex::new(config)); + let analysis = Arc::new(analysis::AnalysisHost::new(analysis::Target::Debug)); + let vfs = Arc::new(Vfs::new()); + + let ctx = InitActionContext::new( + analysis.clone(), + vfs.clone(), + config.clone(), + client_caps, + project_dir.clone(), + pid, + true, + ); + + let init_options = InitializationOptions::default(); + ctx.init(&init_options, output); + ctx.build(&project_dir, BuildPriority::Immediate, output); + + TooltipTestHarness { + ctx, + project_dir, + working_dir, + } + } + + /// Execute a series of tooltip tests. The test results will be saved in `save_dir`. + /// Each test will attempt to load a previous result from the `load_dir` and compare + /// the results. If a matching file can't be found or the compared data mismatches, + /// the test case fails. The output file names are derived from the source filename, + /// line number, and column. The execution will return an `Err` if either the save or + /// load directories do not exist nor could be created. + pub fn run_tests( + &self, + tests: &[Test], + load_dir: PathBuf, + save_dir: PathBuf, + ) -> Result, String> { + fs::create_dir_all(&load_dir).map_err(|e| { + format!( + "load_dir does not exist and could not be created: {:?} ({:?})", + load_dir, e + ) + })?; + fs::create_dir_all(&save_dir).map_err(|e| { + format!( + "save_dir does not exist and could not be created: {:?} ({:?})", + save_dir, e + ) + })?; + self.ctx.block_on_build(); + + let results: Vec = tests + .iter() + .map(|test| { + let result = test.run(&self.project_dir, &self.ctx); + result.save(&save_dir).unwrap(); + result + }) + .collect(); + + let failures: Vec = results + .iter() + .map(|actual_result: &TestResult| { + let actual_result = actual_result.clone(); + match actual_result.test.load_result(&load_dir) { + Ok(expect_result) => { + if actual_result.test != expect_result.test { + let e = format!("Mismatched test: {:?}", expect_result.test); + Some((Err(e), actual_result)) + } else if actual_result == expect_result { + None + } else { + Some((Ok(expect_result), actual_result)) + } + } + Err(e) => Some((Err(e), actual_result)), + } + }) + .filter(|failed_result| failed_result.is_some()) + .map(|failed_result| failed_result.unwrap()) + .map(|failed_result| match failed_result { + (Ok(expect_result), actual_result) => { + let load_file = actual_result.test.path(&load_dir); + let save_file = actual_result.test.path(&save_dir); + TestFailure { + test: actual_result.test, + expect_data: Ok(expect_result.data), + expect_file: load_file, + actual_data: Ok(actual_result.data), + actual_file: save_file, + } + } + (Err(e), actual_result) => { + let load_file = actual_result.test.path(&load_dir); + let save_file = actual_result.test.path(&save_dir); + TestFailure { + test: actual_result.test, + expect_data: Err(e), + expect_file: load_file, + actual_data: Ok(actual_result.data), + actual_file: save_file, + } + } + }) + .collect(); + + Ok(failures) + } + } + + impl Drop for TooltipTestHarness { + fn drop(&mut self) { + if fs::metadata(&self.working_dir).is_ok() { + fs::remove_dir_all(&self.working_dir).expect("failed to tidy up"); + } + } + } + + /// Strips indentation from string literals by examining + /// the indent of the first non-empty line. Preceeding + /// and trailing whitespace is also removed. + fn noindent(text: &str) -> String { + let indent = text + .lines() + .filter(|line| !line.trim().is_empty()) + .peekable() + .peek() + .map(|first_non_empty_line| { + first_non_empty_line + .chars() + .scan(0, |_, ch| if ch.is_whitespace() { Some(1) } else { None }) + .fuse() + .sum() + }) + .unwrap_or(0); + + text.lines() + .map(|line| line.chars().skip(indent).collect::()) + .collect::>() + .join("\n") + .trim() + .to_string() + } + + #[test] + fn test_noindent() { + let lines = noindent( + " + + Hello, world ! ! ! + The next line + Indented line + Last line + + ", + ); + assert_eq!( + "Hello, world ! ! !\nThe next line\n Indented line\nLast line", + &lines + ); + + let lines = noindent( + " + + Hello, world ! ! ! + The next line + Indented line + Last line + + ", + ); + assert_eq!( + "Hello, world ! ! !\nThe next line\n Indented line\nLast line", + &lines + ); + } + + #[test] + fn test_process_docs_rust_blocks() { + let docs = &noindent(" + Brief one liner. + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vitae ex + vel mi egestas semper in non dolor. Proin ut arcu at odio hendrerit consequat. + + # Examples + + Donec ullamcorper risus quis massa sollicitudin, id faucibus nibh bibendum. + + ## Hidden code lines and proceeding whitespace is removed and meta attributes are preserved + + ``` + # extern crate foo; + + use foo::bar; + + #[derive(Debug)] + struct Baz(u32); + + let baz = Baz(1); + ``` + + ## Rust code block attributes are converted to 'rust' + + ```compile_fail,E0123 + let foo = \"compile_fail\" + ``` + + ```no_run + let foo = \"no_run\"; + ``` + + ```ignore + let foo = \"ignore\"; + ``` + + ```should_panic + let foo = \"should_panic\"; + ``` + + ```should_panic,ignore,no_run,compile_fail + let foo = \"should_panic,ignore,no_run,compile_fail\"; + ``` + + ## Inner comments and indentation is preserved + + ``` + /// inner doc comment + fn foobar() { + // inner comment + let indent = 1; + } + ``` + + ## Module attributes are preserved + + ``` + #![allow(dead_code, unused_imports)] + ``` + "); + + let expected = noindent(" + Brief one liner. + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vitae ex + vel mi egestas semper in non dolor. Proin ut arcu at odio hendrerit consequat. + + # Examples + + Donec ullamcorper risus quis massa sollicitudin, id faucibus nibh bibendum. + + ## Hidden code lines and proceeding whitespace is removed and meta attributes are preserved + + ```rust + use foo::bar; + + #[derive(Debug)] + struct Baz(u32); + + let baz = Baz(1); + ``` + + ## Rust code block attributes are converted to 'rust' + + ```rust + let foo = \"compile_fail\" + ``` + + ```rust + let foo = \"no_run\"; + ``` + + ```rust + let foo = \"ignore\"; + ``` + + ```rust + let foo = \"should_panic\"; + ``` + + ```rust + let foo = \"should_panic,ignore,no_run,compile_fail\"; + ``` + + ## Inner comments and indentation is preserved + + ```rust + /// inner doc comment + fn foobar() { + // inner comment + let indent = 1; + } + ``` + + ## Module attributes are preserved + + ```rust + #![allow(dead_code, unused_imports)] + ``` + "); + + let actual = process_docs(docs); + assert_eq!(expected, actual); + } + + #[test] + fn test_process_docs_bash_block() { + let expected = noindent( + " + Brief one liner. + + ```bash + # non rust-block comment lines are preserved + ls -la + ``` + ", + ); + + let actual = process_docs(&expected); + assert_eq!(expected, actual); + } + + #[test] + fn test_process_docs_racer_returns_extra_slashes() { + let docs = noindent( + " + //////////////////////////////////////////////////////////////////////////////// + + Spawns a new thread, returning a [`JoinHandle`] for it. + + The join handle will implicitly *detach* the child thread upon being + dropped. In this case, the child thread may outlive the parent (unless + ", + ); + + let expected = noindent( + " + Spawns a new thread, returning a [`JoinHandle`] for it. + + The join handle will implicitly *detach* the child thread upon being + dropped. In this case, the child thread may outlive the parent (unless + ", + ); + + let actual = process_docs(&docs); + assert_eq!(expected, actual); + } + + #[test] + fn test_format_method() { + let config = &FmtConfig::default(); + + let input = "fn foo() -> ()"; + let result = format_method(config, input.into()); + assert_eq!(input, &result, "function explicit void return"); + + let input = "fn foo()"; + let expected = "fn foo()"; + let result = format_method(config, input.into()); + assert_eq!(expected, &result, "function"); + + let input = "fn foo() -> Thing"; + let expected = "fn foo() -> Thing"; + let result = format_method(config, input.into()); + assert_eq!(expected, &result, "function with return"); + + let input = "fn foo(&self);"; + let expected = "fn foo(&self)"; + let result = format_method(config, input.into()); + assert_eq!(expected, &result, "method"); + + let input = "fn foo(t: T) where T: Copy"; + let expected = noindent( + " + fn foo(t: T) + where + T: Copy, + ", + ); + let result = format_method(config, input.into()); + assert_eq!(expected, result, "function with generic parameters"); + + let input = "fn foo(&self, t: T) where T: Copy"; + let expected = noindent( + " + fn foo(&self, t: T) + where + T: Copy, + ", + ); + let result = format_method(config, input.into()); + assert_eq!(expected, result, "method with type parameters"); + + let input = noindent( + " fn foo( + &self, + t: T) + where + T: Copy + + ", + ); + let expected = noindent( + " + fn foo(&self, t: T) + where + T: Copy, + ", + ); + let result = format_method(config, input.into()); + assert_eq!( + expected, result, + "method with type parameters; corrected spacing" + ); + + let input = "fn really_really_really_really_long_name(foo_thing: String, bar_thing: Thing, baz_thing: Vec, foo_other: u32, bar_other: i32) -> Thing"; + let expected = noindent( + " + fn really_really_really_really_long_name( + foo_thing: String, + bar_thing: Thing, + baz_thing: Vec, + foo_other: u32, + bar_other: i32, + ) -> Thing + ", + ); + let result = format_method(config, input.into()); + assert_eq!(expected, result, "long function signature"); + + let input = "fn really_really_really_really_long_name(&self, foo_thing: String, bar_thing: Thing, baz_thing: Vec, foo_other: u32, bar_other: i32) -> Thing"; + let expected = noindent( + " + fn really_really_really_really_long_name( + &self, + foo_thing: String, + bar_thing: Thing, + baz_thing: Vec, + foo_other: u32, + bar_other: i32, + ) -> Thing + ", + ); + let result = format_method(config, input.into()); + assert_eq!(expected, result, "long method signature with generic"); + + let input = noindent( + " + fn matrix_something( + _a_matrix: [[f32; 4]; 4], + _b_matrix: [[f32; 4]; 4], + _c_matrix: [[f32; 4]; 4], + _d_matrix: [[f32; 4]; 4], + ) + ", + ); + let expected = noindent( + " + fn matrix_something( + _a_matrix: [[f32; 4]; 4], + _b_matrix: [[f32; 4]; 4], + _c_matrix: [[f32; 4]; 4], + _d_matrix: [[f32; 4]; 4], + ) + ", + ); + let result = format_method(config, input.into()); + assert_eq!(expected, result, "function with multiline args"); + } + + #[test] + fn test_extract_decl() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_decl.rs"); + + let expected = "pub fn foo() -> Foo"; + let row_start = Row::new_zero_indexed(10); + let actual = extract_decl(&vfs, file, row_start) + .expect("fuction decleration") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "pub struct Foo"; + let row_start = Row::new_zero_indexed(15); + let actual = extract_decl(&vfs, file, row_start) + .expect("struct decleration") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "pub enum Bar"; + let row_start = Row::new_zero_indexed(20); + let actual = extract_decl(&vfs, file, row_start) + .expect("enum decleration") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "pub struct NewType(pub u32, f32)"; + let row_start = Row::new_zero_indexed(25); + let actual = extract_decl(&vfs, file, row_start) + .expect("tuple decleration") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "pub fn new() -> NewType"; + let row_start = Row::new_zero_indexed(28); + let actual = extract_decl(&vfs, file, row_start) + .expect("struct function decleration") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "pub fn bar(&self, the_really_long_name_string: String, the_really_long_name_foo: Foo) -> Vec<(String, Foo)>"; + let row_start = Row::new_zero_indexed(32); + let actual = extract_decl(&vfs, file, row_start) + .expect("long struct method decleration with generics") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "pub trait Baz where T: Copy"; + let row_start = Row::new_zero_indexed(37); + let actual = extract_decl(&vfs, file, row_start) + .expect("enum decleration") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "fn make_copy(&self) -> Self"; + let row_start = Row::new_zero_indexed(38); + let actual = extract_decl(&vfs, file, row_start) + .expect("trait method decleration") + .join("\n"); + assert_eq!(expected, actual); + + let expected = "fn make_copy(&self) -> Self"; + let row_start = Row::new_zero_indexed(42); + let actual = extract_decl(&vfs, file, row_start) + .expect("trait method implementation") + .join("\n"); + assert_eq!(expected, actual); + + let expected = noindent( + " + pub trait Qeh + where T: Copy, + U: Clone + ", + ); + let row_start = Row::new_zero_indexed(47); + let actual = extract_decl(&vfs, file, row_start) + .expect("trait decleration multiline") + .join("\n"); + assert_eq!(expected, actual); + + let expected = noindent( + " + pub fn multiple_lines( + s: String, + i: i32 + ) + ", + ); + let row_start = Row::new_zero_indexed(53); + let actual = extract_decl(&vfs, file, row_start) + .expect("function decleration multiline") + .join("\n"); + assert_eq!(expected, actual); + } + + #[test] + fn test_format_object() { + let config = &FmtConfig::default(); + + let input = "pub struct Box(Unique);"; + let result = format_object(config, input.into()); + assert_eq!( + "pub struct Box", &result, + "tuple struct with all private fields has hidden components" + ); + + let input = "pub struct Thing(pub u32);"; + let result = format_object(config, input.into()); + assert_eq!( + "pub struct Thing(pub u32)", &result, + "tuple struct with trailing ';' from racer" + ); + + let input = "pub struct Thing(pub u32)"; + let result = format_object(config, input.into()); + assert_eq!("pub struct Thing(pub u32)", &result, "pub tuple struct"); + + let input = "pub struct Thing(pub u32, i32)"; + let result = format_object(config, input.into()); + assert_eq!( + "pub struct Thing(pub u32, _)", &result, + "non-pub components of pub tuples should be hidden" + ); + + let input = "struct Thing(u32, i32)"; + let result = format_object(config, input.into()); + assert_eq!( + "struct Thing(u32, i32)", &result, + "private tuple struct may show private components" + ); + + let input = "pub struct Thing"; + let result = format_object(config, input.into()); + assert_eq!("pub struct Thing", &result, "pub struct"); + + let input = "pub struct Thing {"; + let result = format_object(config, input.into()); + assert_eq!( + "pub struct Thing", &result, + "pub struct with trailing '{{' from racer" + ); + + let input = "pub struct Thing { x: i32 }"; + let result = format_object(config, input.into()); + assert_eq!("pub struct Thing", &result, "pub struct with body"); + + let input = "pub enum Foobar { Foo, Bar }"; + let result = format_object(config, input.into()); + assert_eq!("pub enum Foobar", &result, "pub enum with body"); + + let input = "pub trait Thing where T: Copy + Sized, U: Clone"; + let expected = noindent( + " + pub trait Thing + where + T: Copy + Sized, + U: Clone, + ", + ); + let result = format_object(config, input.into()); + assert_eq!(expected, result, "trait with where clause"); + } + + #[test] + fn test_extract_decl_multiline_empty_function() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_decl_multiline_empty_function.rs"); + + let expected = noindent( + " + fn matrix_something( + _a_matrix: [[f32; 4]; 4], + _b_matrix: [[f32; 4]; 4], + _c_matrix: [[f32; 4]; 4], + _d_matrix: [[f32; 4]; 4], + ) + ", + ); + let row_start = Row::new_zero_indexed(21); + let actual = extract_decl(&vfs, file, row_start) + .expect("the empty body should not be extracted") + .join("\n"); + assert_eq!(expected, actual); + } + + #[test] + fn test_extract_docs_module_docs_with_attribute() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_docs_module_docs_with_attribute.rs"); + let row_start = Row::new_zero_indexed(0); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + Begin module docs + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas + tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus. + In hac habitasse platea dictumst. + + End module docs. + ", + ); + + assert_eq!(expected, actual, "module docs without a copyright header"); + } + + #[test] + fn test_extract_docs_module_docs_no_copyright() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_docs_module_docs_no_copyright.rs"); + let row_start = Row::new_zero_indexed(0); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + Begin module docs + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas + tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus. + In hac habitasse platea dictumst. + + End module docs. + ", + ); + + assert_eq!(expected, actual, "module docs without a copyright header"); + } + + #[test] + fn test_extract_docs_comment_block() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_docs_comment_block.rs"); + let row_start = Row::new_zero_indexed(21); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + The standard library often has comment header blocks that should not be + included. + + Nam efficitur dapibus lectus consequat porta. Pellentesque augue metus, + vestibulum nec massa at, aliquet consequat ex. + + End of spawn docs + ", + ); + + assert_eq!(expected, actual); + } + + #[test] + fn test_extract_docs_empty_line_before_decl() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_docs_empty_line_before_decl.rs"); + let row_start = Row::new_zero_indexed(18); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + Begin empty before decl + + Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus, + iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel + lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat. + + End empty line before decl. + ", + ); + + assert_eq!(expected, actual); + } + + #[test] + fn test_extract_docs_module_docs() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_docs_module_docs.rs"); + + let row_start = Row::new_zero_indexed(0); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + Begin module docs + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas + tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus. + In hac habitasse platea dictumst. + + End module docs. + ", + ); + + assert_eq!(expected, actual); + + let row_start = Row::new_zero_indexed(21); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + Begin first item docs + + The first item docs should not pick up the module docs. + ", + ); + + assert_eq!(expected, actual); + } + + #[test] + fn test_extract_docs_attributes() { + let vfs = Vfs::new(); + let file = Path::new("test_data/hover/src/test_extract_docs_attributes.rs"); + + let row_start = Row::new_zero_indexed(21); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + Begin multiline attribute + + Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus, + iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel + lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat. + + End multiline attribute + ", + ); + + assert_eq!(expected, actual); + + let row_start = Row::new_zero_indexed(32); + let actual = extract_docs(&vfs, &file, row_start) + .expect(&format!("failed to extract docs: {:?}", file)) + .join("\n"); + + let expected = noindent( + " + Begin single line attribute + + Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus, + iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel + lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat. + + End single line attribute. + ", + ); + + assert_eq!(expected, actual); + } + + #[test] + fn test_tooltip() -> Result<(), Box> { + use self::test::{LineOutput, Test, TooltipTestHarness}; + use std::env; + + let tests = vec![ + Test::new("test_tooltip_01.rs", 13, 11), + Test::new("test_tooltip_01.rs", 15, 7), + Test::new("test_tooltip_01.rs", 17, 7), + Test::new("test_tooltip_01.rs", 21, 13), + Test::new("test_tooltip_01.rs", 23, 9), + Test::new("test_tooltip_01.rs", 23, 16), + Test::new("test_tooltip_01.rs", 25, 8), + Test::new("test_tooltip_01.rs", 27, 8), + Test::new("test_tooltip_01.rs", 27, 8), + Test::new("test_tooltip_01.rs", 30, 11), + Test::new("test_tooltip_01.rs", 32, 10), + Test::new("test_tooltip_01.rs", 32, 19), + Test::new("test_tooltip_01.rs", 32, 26), + Test::new("test_tooltip_01.rs", 32, 35), + Test::new("test_tooltip_01.rs", 32, 49), + Test::new("test_tooltip_01.rs", 33, 11), + Test::new("test_tooltip_01.rs", 34, 16), + Test::new("test_tooltip_01.rs", 34, 23), + Test::new("test_tooltip_01.rs", 35, 16), + Test::new("test_tooltip_01.rs", 35, 23), + Test::new("test_tooltip_01.rs", 36, 16), + Test::new("test_tooltip_01.rs", 36, 23), + Test::new("test_tooltip_01.rs", 42, 15), + Test::new("test_tooltip_01.rs", 56, 6), + Test::new("test_tooltip_01.rs", 66, 6), + Test::new("test_tooltip_01.rs", 67, 30), + Test::new("test_tooltip_01.rs", 68, 11), + Test::new("test_tooltip_01.rs", 68, 26), + Test::new("test_tooltip_01.rs", 75, 10), + Test::new("test_tooltip_01.rs", 80, 11), + Test::new("test_tooltip_01.rs", 85, 14), + Test::new("test_tooltip_01.rs", 85, 50), + Test::new("test_tooltip_01.rs", 85, 54), + Test::new("test_tooltip_01.rs", 86, 7), + Test::new("test_tooltip_01.rs", 86, 10), + Test::new("test_tooltip_01.rs", 87, 20), + Test::new("test_tooltip_01.rs", 88, 18), + Test::new("test_tooltip_01.rs", 93, 11), + Test::new("test_tooltip_01.rs", 93, 18), + Test::new("test_tooltip_01.rs", 95, 25), + Test::new("test_tooltip_01.rs", 109, 21), + Test::new("test_tooltip_01.rs", 113, 21), + Test::new("test_tooltip_mod.rs", 22, 14), + Test::new("test_tooltip_mod_use.rs", 11, 14), + Test::new("test_tooltip_mod_use.rs", 12, 14), + Test::new("test_tooltip_mod_use.rs", 12, 25), + Test::new("test_tooltip_mod_use.rs", 13, 28), + Test::new("test_tooltip_mod_use_external.rs", 11, 7), + Test::new("test_tooltip_mod_use_external.rs", 11, 7), + Test::new("test_tooltip_mod_use_external.rs", 12, 7), + Test::new("test_tooltip_mod_use_external.rs", 12, 12), + Test::new("test_tooltip_mod_use_external.rs", 14, 12), + Test::new("test_tooltip_mod_use_external.rs", 15, 12), + Test::new("test_tooltip_std.rs", 18, 15), + Test::new("test_tooltip_std.rs", 18, 27), + Test::new("test_tooltip_std.rs", 19, 7), + Test::new("test_tooltip_std.rs", 19, 12), + Test::new("test_tooltip_std.rs", 20, 12), + Test::new("test_tooltip_std.rs", 20, 20), + Test::new("test_tooltip_std.rs", 21, 25), + Test::new("test_tooltip_std.rs", 22, 33), + Test::new("test_tooltip_std.rs", 23, 11), + Test::new("test_tooltip_std.rs", 23, 18), + Test::new("test_tooltip_std.rs", 24, 24), + Test::new("test_tooltip_std.rs", 25, 17), + Test::new("test_tooltip_std.rs", 25, 25), + ]; + + let cwd = env::current_dir()?; + let out = LineOutput::default(); + let proj_dir = cwd.join("test_data").join("hover"); + let save_dir = cwd + .join("target") + .join("tests") + .join("hover") + .join("save_data"); + let load_dir = proj_dir.join("save_data"); + + let harness = TooltipTestHarness::new(proj_dir, &out); + + out.reset(); + + let failures = harness.run_tests(&tests, load_dir, save_dir)?; + + if failures.is_empty() { + Ok(()) + } else { + eprintln!("{}\n\n", out.reset().join("\n")); + eprintln!("{:#?}\n\n", failures); + Err(format!("{} of {} tooltip tests failed", failures.len(), tests.len()).into()) + } + } +} diff --git a/src/actions/mod.rs b/src/actions/mod.rs index f7971b20bae..ac4b350476e 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -63,6 +63,7 @@ pub mod notifications; pub mod progress; pub mod diagnostics; pub mod run; +pub mod hover; /// Persistent context shared across all requests and notifications. pub enum ActionContext { diff --git a/src/actions/requests.rs b/src/actions/requests.rs index 765db6916e7..cf3bb9344d9 100644 --- a/src/actions/requests.rs +++ b/src/actions/requests.rs @@ -22,6 +22,7 @@ use itertools::Itertools; use serde_derive::{Serialize, Deserialize}; use log::{debug, log, trace}; +use crate::actions::hover; use crate::actions::work_pool; use crate::actions::work_pool::WorkDescription; use crate::actions::run::collect_run_actions; @@ -159,28 +160,10 @@ impl RequestAction for Hover { ctx: InitActionContext, params: Self::Params, ) -> Result { - let file_path = parse_file_path!(¶ms.text_document.uri, "hover")?; - let span = ctx.convert_pos_to_span(file_path, params.position); - - trace!("hover: {:?}", span); - - let analysis = ctx.analysis; - let ty = analysis.show_type(&span).unwrap_or_else(|_| String::new()); - let docs = analysis.docs(&span).unwrap_or_else(|_| String::new()); - let doc_url = analysis.doc_url(&span).unwrap_or_else(|_| String::new()); + let tooltip = hover::tooltip(&ctx, ¶ms)?; - let mut contents = vec![]; - if !docs.is_empty() { - contents.push(MarkedString::from_markdown(docs)); - } - if !doc_url.is_empty() { - contents.push(MarkedString::from_markdown(doc_url)); - } - if !ty.is_empty() { - contents.push(MarkedString::from_language_code("rust".into(), ty)); - } Ok(lsp_data::Hover { - contents: HoverContents::Array(contents), + contents: HoverContents::Array(tooltip), range: None, // TODO: maybe add? }) } @@ -846,14 +829,14 @@ impl RequestAction for ResolveCompletion { } } -fn racer_coord( +pub(crate) fn racer_coord( row: span::Row, col: span::Column, ) -> racer::Coordinate { racer::Coordinate { row, col } } -fn from_racer_coord( +pub(crate) fn from_racer_coord( coord: racer::Coordinate, ) -> (span::Row, span::Column) { (coord.row, coord.col) @@ -908,7 +891,7 @@ impl RequestAction for CodeLensRequest { } } -fn racer_cache(vfs: Arc) -> racer::FileCache { +pub(crate) fn racer_cache(vfs: Arc) -> racer::FileCache { struct RacerVfs(Arc); impl racer::FileLoader for RacerVfs { fn load_file(&self, path: &Path) -> io::Result { diff --git a/src/build/cargo.rs b/src/build/cargo.rs index 5cd344990c5..a1f60c99e46 100644 --- a/src/build/cargo.rs +++ b/src/build/cargo.rs @@ -441,6 +441,9 @@ impl Executor for RlsExecutor { let mut save_config = rls_data::config::Config::default(); save_config.pub_only = true; save_config.reachable_only = true; + save_config.full_docs = self.config.lock() + .map(|config| *config.full_docs.as_ref()) + .unwrap(); let save_config = serde_json::to_string(&save_config)?; cmd.env("RUST_SAVE_ANALYSIS_CONFIG", &OsString::from(save_config)); diff --git a/src/config.rs b/src/config.rs index 4fdb5194598..ef07d23072d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -147,6 +147,17 @@ pub struct Config { pub racer_completion: bool, #[serde(deserialize_with = "deserialize_clippy_preference")] pub clippy_preference: ClippyPreference, + /// Instructs cargo to enable full documentation extraction during save-analysis + /// while building the crate. This has no effect on the pre-built standard library, + /// which is built without full_docs enabled. Hover tooltips currently extract + /// documentation from source due this limitation. The docs provided by the save-analysis + /// are used in the event that source extraction fails. This may prove to be more useful + /// in the future. + pub full_docs: Inferrable, + /// Show additional context in hover tooltips when available. This is often the type + /// local variable declaration. When set to false, the content is only availabe when + /// holding the `ctrl` key in some editors. + pub show_hover_context: bool, } impl Default for Config { @@ -173,6 +184,8 @@ impl Default for Config { all_targets: true, racer_completion: true, clippy_preference: ClippyPreference::OptIn, + full_docs: Inferrable::Inferred(false), + show_hover_context: true, }; result.normalise(); result diff --git a/src/lsp_data.rs b/src/lsp_data.rs index 5926f47437c..ecd7df1d16d 100644 --- a/src/lsp_data.rs +++ b/src/lsp_data.rs @@ -22,6 +22,8 @@ use rls_vfs::FileContents; use languageserver_types as ls_types; use serde_derive::{Serialize, Deserialize}; +use crate::actions::hover; + pub use languageserver_types::*; pub use languageserver_types::request::Request as LSPRequest; pub use languageserver_types::notification::Notification as LSPNotification; @@ -234,7 +236,7 @@ pub fn completion_item_from_racer_match(m: &racer::Match) -> CompletionItem { if !m.docs.is_empty() { item.documentation = Some(Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, - value: m.docs.clone(), + value: hover::process_docs(&m.docs) })); } diff --git a/src/test/mod.rs b/src/test/mod.rs index a5c5d90cc5f..8f232374bff 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -238,7 +238,7 @@ fn test_hover() { results.clone(), &[ ExpectedMessage::new(Some(11)) - .expect_contains(r#"[{"language":"rust","value":"&str"}]"#), + .expect_contains(r#"[{"language":"rust","value":"&str"},{"language":"rust","value":"let world = \"world\";"}]"#), ], ); } diff --git a/test_data/hover/Cargo.lock b/test_data/hover/Cargo.lock new file mode 100644 index 00000000000..d1b92049ac3 --- /dev/null +++ b/test_data/hover/Cargo.lock @@ -0,0 +1,14 @@ +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hover" +version = "0.1.0" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" diff --git a/test_data/hover/Cargo.toml b/test_data/hover/Cargo.toml new file mode 100644 index 00000000000..4da620218b6 --- /dev/null +++ b/test_data/hover/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "hover" +version = "0.1.0" +authors = ["hover "] + +[dependencies] +fnv = "1.0.6" \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0013_011.json b/test_data/hover/save_data/test_tooltip_01.rs.0013_011.json new file mode 100644 index 00000000000..bd7026c911c --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0013_011.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 13, + "col": 11 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub enum Foo" + }, + "Foo enum" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0015_007.json b/test_data/hover/save_data/test_tooltip_01.rs.0015_007.json new file mode 100644 index 00000000000..90e6d3ea155 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0015_007.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 15, + "col": 7 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "Foo::Bar" + }, + "Bar variant" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0017_007.json b/test_data/hover/save_data/test_tooltip_01.rs.0017_007.json new file mode 100644 index 00000000000..95679184a19 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0017_007.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 17, + "col": 7 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "Foo::Baz" + }, + "Baz variant" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0021_013.json b/test_data/hover/save_data/test_tooltip_01.rs.0021_013.json new file mode 100644 index 00000000000..c7684954ea4 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0021_013.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 21, + "col": 13 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Bar" + }, + "Bar struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0023_009.json b/test_data/hover/save_data/test_tooltip_01.rs.0023_009.json new file mode 100644 index 00000000000..73c7ba97458 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0023_009.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 23, + "col": 9 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Tuple" + }, + "The first field" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0023_016.json b/test_data/hover/save_data/test_tooltip_01.rs.0023_016.json new file mode 100644 index 00000000000..5c2cb72f043 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0023_016.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 23, + "col": 16 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Tuple(pub u32, _)" + }, + "Tuple struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0025_008.json b/test_data/hover/save_data/test_tooltip_01.rs.0025_008.json new file mode 100644 index 00000000000..7563e0c2100 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0025_008.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 25, + "col": 8 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "T" + }, + "The second field" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0027_008.json b/test_data/hover/save_data/test_tooltip_01.rs.0027_008.json new file mode 100644 index 00000000000..8f3ca594701 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0027_008.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 27, + "col": 8 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Foo" + }, + "The third field" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0030_011.json b/test_data/hover/save_data/test_tooltip_01.rs.0030_011.json new file mode 100644 index 00000000000..0b6b2df54f9 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0030_011.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 30, + "col": 11 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Bar" + }, + "Bar struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0032_010.json b/test_data/hover/save_data/test_tooltip_01.rs.0032_010.json new file mode 100644 index 00000000000..10723dc6816 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0032_010.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 32, + "col": 10 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fn new(one: Tuple, two: T, three: Foo) -> Bar" + }, + "Create a new Bar" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0032_019.json b/test_data/hover/save_data/test_tooltip_01.rs.0032_019.json new file mode 100644 index 00000000000..5d35c242e36 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0032_019.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 32, + "col": 19 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Tuple(pub u32, _)" + }, + "Tuple struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0032_026.json b/test_data/hover/save_data/test_tooltip_01.rs.0032_026.json new file mode 100644 index 00000000000..f3409dd884d --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0032_026.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 32, + "col": 26 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "T" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0032_035.json b/test_data/hover/save_data/test_tooltip_01.rs.0032_035.json new file mode 100644 index 00000000000..5be8d57648e --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0032_035.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 32, + "col": 35 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Foo" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0032_049.json b/test_data/hover/save_data/test_tooltip_01.rs.0032_049.json new file mode 100644 index 00000000000..d12f34c891d --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0032_049.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 32, + "col": 49 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Bar" + }, + "Bar struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0033_011.json b/test_data/hover/save_data/test_tooltip_01.rs.0033_011.json new file mode 100644 index 00000000000..c6c82cd1bae --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0033_011.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 33, + "col": 11 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Bar" + }, + "Bar struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0034_016.json b/test_data/hover/save_data/test_tooltip_01.rs.0034_016.json new file mode 100644 index 00000000000..25bdeebfe97 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0034_016.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 34, + "col": 16 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Tuple" + }, + "The first field" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0034_023.json b/test_data/hover/save_data/test_tooltip_01.rs.0034_023.json new file mode 100644 index 00000000000..0f9b680a775 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0034_023.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 34, + "col": 23 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Tuple" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0035_016.json b/test_data/hover/save_data/test_tooltip_01.rs.0035_016.json new file mode 100644 index 00000000000..bc0b954f390 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0035_016.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 35, + "col": 16 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "T" + }, + "The second field" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0035_023.json b/test_data/hover/save_data/test_tooltip_01.rs.0035_023.json new file mode 100644 index 00000000000..6d76822ac07 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0035_023.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 35, + "col": 23 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "T" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0036_016.json b/test_data/hover/save_data/test_tooltip_01.rs.0036_016.json new file mode 100644 index 00000000000..f0395538b38 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0036_016.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 36, + "col": 16 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Foo" + }, + "The third field" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0036_023.json b/test_data/hover/save_data/test_tooltip_01.rs.0036_023.json new file mode 100644 index 00000000000..78805c93bea --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0036_023.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 36, + "col": 23 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Foo" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0042_015.json b/test_data/hover/save_data/test_tooltip_01.rs.0042_015.json new file mode 100644 index 00000000000..cc4f4b0852f --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0042_015.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 42, + "col": 15 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Tuple(pub u32, _)" + }, + "Tuple struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0056_006.json b/test_data/hover/save_data/test_tooltip_01.rs.0056_006.json new file mode 100644 index 00000000000..fb69fb9f1fe --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0056_006.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 56, + "col": 6 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fn bar(thing: T) -> Bar" + }, + "Bar function\n\n# Examples\n\n```rust\nuse does_not_exist::other;\n\nlet foo = bar(1.0);\nother(foo);\n```" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0066_006.json b/test_data/hover/save_data/test_tooltip_01.rs.0066_006.json new file mode 100644 index 00000000000..fbc8afbf4f2 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0066_006.json @@ -0,0 +1,10 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 66, + "col": 6 + }, + "data": { + "Ok": [] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0067_030.json b/test_data/hover/save_data/test_tooltip_01.rs.0067_030.json new file mode 100644 index 00000000000..81460d4eacd --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0067_030.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 67, + "col": 30 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Foo" + }, + "The third field" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0068_011.json b/test_data/hover/save_data/test_tooltip_01.rs.0068_011.json new file mode 100644 index 00000000000..1d5abfd6414 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0068_011.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 68, + "col": 11 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "&mut test_tooltip_01::Bar" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0068_026.json b/test_data/hover/save_data/test_tooltip_01.rs.0068_026.json new file mode 100644 index 00000000000..1ae40c072e2 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0068_026.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 68, + "col": 26 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Foo" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0075_010.json b/test_data/hover/save_data/test_tooltip_01.rs.0075_010.json new file mode 100644 index 00000000000..1930161024f --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0075_010.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 75, + "col": 10 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fn bar(thing: T) -> Bar" + }, + "Bar function\n\n# Examples\n\n```rust\nuse does_not_exist::other;\n\nlet foo = bar(1.0);\nother(foo);\n```" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0080_011.json b/test_data/hover/save_data/test_tooltip_01.rs.0080_011.json new file mode 100644 index 00000000000..dd47cecea96 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0080_011.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 80, + "col": 11 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Bar" + }, + "Bar struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0085_014.json b/test_data/hover/save_data/test_tooltip_01.rs.0085_014.json new file mode 100644 index 00000000000..3ee4728839a --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0085_014.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 85, + "col": 14 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Bar" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0085_050.json b/test_data/hover/save_data/test_tooltip_01.rs.0085_050.json new file mode 100644 index 00000000000..6227dca0a0c --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0085_050.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 85, + "col": 50 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub enum Foo" + }, + "Foo enum" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0085_054.json b/test_data/hover/save_data/test_tooltip_01.rs.0085_054.json new file mode 100644 index 00000000000..cc05736835a --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0085_054.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 85, + "col": 54 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "Foo::Bar" + }, + "Bar variant" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0086_007.json b/test_data/hover/save_data/test_tooltip_01.rs.0086_007.json new file mode 100644 index 00000000000..fe2499aeb7c --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0086_007.json @@ -0,0 +1,19 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 86, + "col": 7 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "test_tooltip_01::Bar" + }, + { + "language": "rust", + "value": "let mut bar = Bar::new(Tuple(3, 1.0), 2.0, Foo::Bar);" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0086_010.json b/test_data/hover/save_data/test_tooltip_01.rs.0086_010.json new file mode 100644 index 00000000000..b224fcb0b03 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0086_010.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 86, + "col": 10 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fn bar(&mut self, thing: T) -> Bar\nwhere\n T: Copy," + }, + "Bar method" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0087_020.json b/test_data/hover/save_data/test_tooltip_01.rs.0087_020.json new file mode 100644 index 00000000000..5e308ee4d0c --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0087_020.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 87, + "col": 20 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "Foo::Baz" + }, + "Baz variant" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0088_018.json b/test_data/hover/save_data/test_tooltip_01.rs.0088_018.json new file mode 100644 index 00000000000..2336f326845 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0088_018.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 88, + "col": 18 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct Tuple(pub u32, _)" + }, + "Tuple struct" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0093_011.json b/test_data/hover/save_data/test_tooltip_01.rs.0093_011.json new file mode 100644 index 00000000000..ba22411747b --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0093_011.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 93, + "col": 11 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "type Foo: Other" + }, + "Foo other type" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0093_018.json b/test_data/hover/save_data/test_tooltip_01.rs.0093_018.json new file mode 100644 index 00000000000..e5f967e2f5e --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0093_018.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 93, + "col": 18 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "trait Other" + }, + "The other trait" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0095_025.json b/test_data/hover/save_data/test_tooltip_01.rs.0095_025.json new file mode 100644 index 00000000000..48b6f240f66 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0095_025.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 95, + "col": 25 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "type Foo: Other" + }, + "Foo other type" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0109_021.json b/test_data/hover/save_data/test_tooltip_01.rs.0109_021.json new file mode 100644 index 00000000000..a138b71d82b --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0109_021.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 109, + "col": 21 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "const FOO: &'static str = \"FOO\"" + }, + "The constant FOO" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_01.rs.0113_021.json b/test_data/hover/save_data/test_tooltip_01.rs.0113_021.json new file mode 100644 index 00000000000..9f711996e7c --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_01.rs.0113_021.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_01.rs", + "line": 113, + "col": 21 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "static BAR: u32 = 123" + }, + "The static BAR" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod.rs.0022_014.json b/test_data/hover/save_data/test_tooltip_mod.rs.0022_014.json new file mode 100644 index 00000000000..f4d2e05e7bc --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod.rs.0022_014.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod.rs", + "line": 22, + "col": 14 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct First" + }, + "Begin first item docs\n\nThe first item docs should not pick up the module docs." + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use.rs.0011_014.json b/test_data/hover/save_data/test_tooltip_mod_use.rs.0011_014.json new file mode 100644 index 00000000000..5fb5fc52673 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use.rs.0011_014.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use.rs", + "line": 11, + "col": 14 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "src/test_tooltip_mod.rs" + }, + "Begin module docs\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas\ntincidunt tristique maximus. Sed venenatis urna vel sagittis tempus.\nIn hac habitasse platea dictumst.\n\nEnd module docs." + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use.rs.0012_014.json b/test_data/hover/save_data/test_tooltip_mod_use.rs.0012_014.json new file mode 100644 index 00000000000..9a51587ebef --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use.rs.0012_014.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use.rs", + "line": 12, + "col": 14 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "src/test_tooltip_mod.rs" + }, + "Begin module docs\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas\ntincidunt tristique maximus. Sed venenatis urna vel sagittis tempus.\nIn hac habitasse platea dictumst.\n\nEnd module docs." + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use.rs.0012_025.json b/test_data/hover/save_data/test_tooltip_mod_use.rs.0012_025.json new file mode 100644 index 00000000000..3a034225c6e --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use.rs.0012_025.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use.rs", + "line": 12, + "col": 25 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct First" + }, + "Begin first item docs\n\nThe first item docs should not pick up the module docs." + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use.rs.0013_028.json b/test_data/hover/save_data/test_tooltip_mod_use.rs.0013_028.json new file mode 100644 index 00000000000..83a0b79b8c7 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use.rs.0013_028.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use.rs", + "line": 13, + "col": 28 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "src/test_tooltip_mod.rs" + }, + "Submodule first line\n\nMauris vel lobortis lacus, in condimentum dolor.\n\nSubmodule last line" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0011_007.json b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0011_007.json new file mode 100644 index 00000000000..02b3eacc4bf --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0011_007.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use_external.rs", + "line": 11, + "col": 7 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fnv-1.0.6/lib.rs" + }, + "An implementation of the [Fowler–Noll–Vo hash function][chongo].\n\n## About\n\nThe FNV hash function is a custom `Hasher` implementation that is more\nefficient for smaller hash keys.\n\n[The Rust FAQ states that][faq] while the default `Hasher` implementation,\nSipHash, is good in many cases, it is notably slower than other algorithms\nwith short keys, such as when you have a map of integers to other values.\nIn cases like these, [FNV is demonstrably faster][graphs].\n\nIts disadvantages are that it performs badly on larger inputs, and\nprovides no protection against collision attacks, where a malicious user\ncan craft specific keys designed to slow a hasher down. Thus, it is\nimportant to profile your program to ensure that you are using small hash\nkeys, and be certain that your program could not be exposed to malicious\ninputs (including being a networked server).\n\nThe Rust compiler itself uses FNV, as it is not worried about\ndenial-of-service attacks, and can assume that its inputs are going to be\nsmall—a perfect use case for FNV.\n\n\n## Using FNV in a `HashMap`\n\nThe `FnvHashMap` type alias is the easiest way to use the standard library’s\n`HashMap` with FNV.\n\n```rust\nuse fnv::FnvHashMap;\n\nlet mut map = FnvHashMap::default();\nmap.insert(1, \"one\");\nmap.insert(2, \"two\");\n\nmap = FnvHashMap::with_capacity_and_hasher(10, Default::default());\nmap.insert(1, \"one\");\nmap.insert(2, \"two\");\n```\n\nNote, the standard library’s `HashMap::new` and `HashMap::with_capacity`\nare only implemented for the `RandomState` hasher, so using `Default` to\nget the hasher is the next best option.\n\n## Using FNV in a `HashSet`\n\nSimilarly, `FnvHashSet` is a type alias for the standard library’s `HashSet`\nwith FNV.\n\n```rust\nuse fnv::FnvHashSet;\n\nlet mut set = FnvHashSet::default();\nset.insert(1);\nset.insert(2);\n\nset = FnvHashSet::with_capacity_and_hasher(10, Default::default());\nset.insert(1);\nset.insert(2);\n```\n\n[chongo]: http://www.isthe.com/chongo/tech/comp/fnv/index.html\n[faq]: https://www.rust-lang.org/en-US/faq.html#why-are-rusts-hashmaps-slow\n[graphs]: http://cglab.ca/~abeinges/blah/hash-rs/" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0012_007.json b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0012_007.json new file mode 100644 index 00000000000..dda85bdcbde --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0012_007.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use_external.rs", + "line": 12, + "col": 7 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fnv-1.0.6/lib.rs" + }, + "An implementation of the [Fowler–Noll–Vo hash function][chongo].\n\n## About\n\nThe FNV hash function is a custom `Hasher` implementation that is more\nefficient for smaller hash keys.\n\n[The Rust FAQ states that][faq] while the default `Hasher` implementation,\nSipHash, is good in many cases, it is notably slower than other algorithms\nwith short keys, such as when you have a map of integers to other values.\nIn cases like these, [FNV is demonstrably faster][graphs].\n\nIts disadvantages are that it performs badly on larger inputs, and\nprovides no protection against collision attacks, where a malicious user\ncan craft specific keys designed to slow a hasher down. Thus, it is\nimportant to profile your program to ensure that you are using small hash\nkeys, and be certain that your program could not be exposed to malicious\ninputs (including being a networked server).\n\nThe Rust compiler itself uses FNV, as it is not worried about\ndenial-of-service attacks, and can assume that its inputs are going to be\nsmall—a perfect use case for FNV.\n\n\n## Using FNV in a `HashMap`\n\nThe `FnvHashMap` type alias is the easiest way to use the standard library’s\n`HashMap` with FNV.\n\n```rust\nuse fnv::FnvHashMap;\n\nlet mut map = FnvHashMap::default();\nmap.insert(1, \"one\");\nmap.insert(2, \"two\");\n\nmap = FnvHashMap::with_capacity_and_hasher(10, Default::default());\nmap.insert(1, \"one\");\nmap.insert(2, \"two\");\n```\n\nNote, the standard library’s `HashMap::new` and `HashMap::with_capacity`\nare only implemented for the `RandomState` hasher, so using `Default` to\nget the hasher is the next best option.\n\n## Using FNV in a `HashSet`\n\nSimilarly, `FnvHashSet` is a type alias for the standard library’s `HashSet`\nwith FNV.\n\n```rust\nuse fnv::FnvHashSet;\n\nlet mut set = FnvHashSet::default();\nset.insert(1);\nset.insert(2);\n\nset = FnvHashSet::with_capacity_and_hasher(10, Default::default());\nset.insert(1);\nset.insert(2);\n```\n\n[chongo]: http://www.isthe.com/chongo/tech/comp/fnv/index.html\n[faq]: https://www.rust-lang.org/en-US/faq.html#why-are-rusts-hashmaps-slow\n[graphs]: http://cglab.ca/~abeinges/blah/hash-rs/" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0012_012.json b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0012_012.json new file mode 100644 index 00000000000..d1697b7f5bf --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0012_012.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use_external.rs", + "line": 12, + "col": 12 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub struct FnvHasher" + }, + "An implementation of the Fowler–Noll–Vo hash function.\n\nSee the [crate documentation](index.html) for more details." + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0014_012.json b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0014_012.json new file mode 100644 index 00000000000..6565b210df7 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0014_012.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use_external.rs", + "line": 14, + "col": 12 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "libstd/sync/mod.rs" + }, + "Useful synchronization primitives.\n\nThis module contains useful safe and unsafe synchronization primitives.\nMost of the primitives in this module do not provide any sort of locking\nand/or blocking at all, but rather provide the necessary tools to build\nother types of concurrent primitives." + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0015_012.json b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0015_012.json new file mode 100644 index 00000000000..b725546375c --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_mod_use_external.rs.0015_012.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_mod_use_external.rs", + "line": 15, + "col": 12 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "libstd/sync/mod.rs" + }, + "Useful synchronization primitives.\n\nThis module contains useful safe and unsafe synchronization primitives.\nMost of the primitives in this module do not provide any sort of locking\nand/or blocking at all, but rather provide the necessary tools to build\nother types of concurrent primitives." + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0018_015.json b/test_data/hover/save_data/test_tooltip_std.rs.0018_015.json new file mode 100644 index 00000000000..39efe384356 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0018_015.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 18, + "col": 15 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "std::vec::Vec" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0018_027.json b/test_data/hover/save_data/test_tooltip_std.rs.0018_027.json new file mode 100644 index 00000000000..a5a09c0d19c --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0018_027.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 18, + "col": 27 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub const fn new() -> Vec" + }, + "Constructs a new, empty `Vec`.\n\nThe vector will not allocate until elements are pushed onto it.\n\n# Examples\n\n```rust\nlet mut vec: Vec = Vec::new();\n```" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0019_007.json b/test_data/hover/save_data/test_tooltip_std.rs.0019_007.json new file mode 100644 index 00000000000..9b8ea36a4ce --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0019_007.json @@ -0,0 +1,19 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 19, + "col": 7 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "std::vec::Vec" + }, + { + "language": "rust", + "value": "let mut vec1 = Vec::new();" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0019_012.json b/test_data/hover/save_data/test_tooltip_std.rs.0019_012.json new file mode 100644 index 00000000000..c7da6548aff --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0019_012.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 19, + "col": 12 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub fn push(&mut self, value: T)" + }, + "Appends an element to the back of a collection.\n\n# Panics\n\nPanics if the number of elements in the vector overflows a `usize`.\n\n# Examples\n\n```rust\nlet mut vec = vec![1, 2];\nvec.push(3);\nassert_eq!(vec, [1, 2, 3]);\n```" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0020_012.json b/test_data/hover/save_data/test_tooltip_std.rs.0020_012.json new file mode 100644 index 00000000000..74637efceff --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0020_012.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 20, + "col": 12 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "&[i32]" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0020_020.json b/test_data/hover/save_data/test_tooltip_std.rs.0020_020.json new file mode 100644 index 00000000000..eb9a12077fe --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0020_020.json @@ -0,0 +1,19 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 20, + "col": 20 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "std::vec::Vec" + }, + { + "language": "rust", + "value": "let mut vec1 = Vec::new();" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0021_025.json b/test_data/hover/save_data/test_tooltip_std.rs.0021_025.json new file mode 100644 index 00000000000..c7239458029 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0021_025.json @@ -0,0 +1,17 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 21, + "col": 25 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fn clone(&self) -> Self" + }, + "https://doc.rust-lang.org/nightly/core/clone/Clone.t.html#clone.v", + "Returns a copy of the value.\n\n# Examples\n\n```rust\nlet hello = \"Hello\"; // &str implements Clone\n\nassert_eq!(\"Hello\", hello.clone());\n```" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0022_033.json b/test_data/hover/save_data/test_tooltip_std.rs.0022_033.json new file mode 100644 index 00000000000..11326fd2697 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0022_033.json @@ -0,0 +1,17 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 22, + "col": 33 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "fn default() -> Self" + }, + "https://doc.rust-lang.org/nightly/core/default/Default.t.html#default.v", + "Returns the \"default value\" for a type.\n\nDefault values are often some kind of initial value, identity value, or anything else that\nmay make sense as a default.\n\n# Examples\n\nUsing built-in default values:\n\n```rust\nlet i: i8 = Default::default();\nlet (x, y): (Option, f64) = Default::default();\nlet (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();\n```\n\nMaking your own:\n\n```rust\nenum Kind {\n A,\n B,\n C,\n}\n\nimpl Default for Kind {\n fn default() -> Kind { Kind::A }\n}\n```" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0023_011.json b/test_data/hover/save_data/test_tooltip_std.rs.0023_011.json new file mode 100644 index 00000000000..2a9c5bd0edc --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0023_011.json @@ -0,0 +1,15 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 23, + "col": 11 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "i32" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0023_018.json b/test_data/hover/save_data/test_tooltip_std.rs.0023_018.json new file mode 100644 index 00000000000..47d1e2d7aba --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0023_018.json @@ -0,0 +1,19 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 23, + "col": 18 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "&[i32]" + }, + { + "language": "rust", + "value": "let slice = &vec1[0..];" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0024_024.json b/test_data/hover/save_data/test_tooltip_std.rs.0024_024.json new file mode 100644 index 00000000000..052ddc736df --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0024_024.json @@ -0,0 +1,19 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 24, + "col": 24 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "&[i32]" + }, + { + "language": "rust", + "value": "let slice = &vec1[0..];" + } + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0025_017.json b/test_data/hover/save_data/test_tooltip_std.rs.0025_017.json new file mode 100644 index 00000000000..24a275d584d --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0025_017.json @@ -0,0 +1,16 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 25, + "col": 17 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "liballoc/string.rs" + }, + "A UTF-8 encoded, growable string.\n\nThis module contains the [`String`] type, a trait for converting\n[`ToString`]s, and several error types that may result from working with\n[`String`]s.\n\n[`ToString`]: trait.ToString.html\n\n# Examples\n\nThere are multiple ways to create a new [`String`] from a string literal:\n\n```rust\nlet s = \"Hello\".to_string();\n\nlet s = String::from(\"world\");\nlet s: String = \"also this\".into();\n```\n\nYou can create a new [`String`] from an existing one by concatenating with\n`+`:\n\n[`String`]: struct.String.html\n\n```rust\nlet s = \"Hello\".to_string();\n\nlet message = s + \" world!\";\n```\n\nIf you have a vector of valid UTF-8 bytes, you can make a [`String`] out of\nit. You can do the reverse too.\n\n```rust\nlet sparkle_heart = vec![240, 159, 146, 150];\n\n// We know these bytes are valid, so we'll use `unwrap()`.\nlet sparkle_heart = String::from_utf8(sparkle_heart).unwrap();\n\nassert_eq!(\"💖\", sparkle_heart);\n\nlet bytes = sparkle_heart.into_bytes();\n\nassert_eq!(bytes, [240, 159, 146, 150]);\n```" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/save_data/test_tooltip_std.rs.0025_025.json b/test_data/hover/save_data/test_tooltip_std.rs.0025_025.json new file mode 100644 index 00000000000..b5ee18f2b28 --- /dev/null +++ b/test_data/hover/save_data/test_tooltip_std.rs.0025_025.json @@ -0,0 +1,17 @@ +{ + "test": { + "file": "test_tooltip_std.rs", + "line": 25, + "col": 25 + }, + "data": { + "Ok": [ + { + "language": "rust", + "value": "pub trait ToString" + }, + "https://doc.rust-lang.org/nightly/alloc/string/ToString.t.html", + "A trait for converting a value to a `String`.\n\nThis trait is automatically implemented for any type which implements the\n[`Display`] trait. As such, `ToString` shouldn't be implemented directly:\n[`Display`] should be implemented instead, and you get the `ToString`\nimplementation for free.\n\n[`Display`]: ../../std/fmt/trait.Display.html" + ] + } +} \ No newline at end of file diff --git a/test_data/hover/src/main.rs b/test_data/hover/src/main.rs new file mode 100644 index 00000000000..c862e1bdcc9 --- /dev/null +++ b/test_data/hover/src/main.rs @@ -0,0 +1,17 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +#![allow(dead_code, unused_imports)] + +extern crate fnv; + +pub mod test_tooltip_01; +pub mod test_tooltip_mod; +pub mod test_tooltip_mod_use; +pub mod test_tooltip_std; \ No newline at end of file diff --git a/test_data/hover/src/test_extract_decl.rs b/test_data/hover/src/test_extract_decl.rs new file mode 100644 index 00000000000..b832f089887 --- /dev/null +++ b/test_data/hover/src/test_extract_decl.rs @@ -0,0 +1,64 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +pub fn foo() -> Foo { + Foo { t: 1 } +} + +#[derive(Debug)] +pub struct Foo { + pub t: T +} + +#[derive(Debug)] +pub enum Bar { + Baz +} + +#[derive(Debug)] +pub struct NewType(pub u32, f32); + +impl NewType { + pub fn new() -> NewType { + NewType(1, 2.0) + } + + pub fn bar(&self, the_really_long_name_string: String, the_really_long_name_foo: Foo) -> Vec<(String, Foo)> { + Vec::default() + } +} + +pub trait Baz where T: Copy { + fn make_copy(&self) -> Self; +} + +impl Baz for Foo where T: Copy { + fn make_copy(&self) -> Self { + Foo { t: self.t } + } +} + +pub trait Qeh +where T: Copy, +U: Clone { + +} + +pub fn multiple_lines( + s: String, + i: i32 +) { + drop(s); + drop(i); +} + +pub fn bar() -> Bar { + Bar::Baz +} diff --git a/test_data/hover/src/test_extract_decl_multiline_empty_function.rs b/test_data/hover/src/test_extract_decl_multiline_empty_function.rs new file mode 100644 index 00000000000..a98e942c510 --- /dev/null +++ b/test_data/hover/src/test_extract_decl_multiline_empty_function.rs @@ -0,0 +1,27 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// Do nothing with matices. +fn matrix_something( + _a_matrix: [[f32; 4]; 4], + _b_matrix: [[f32; 4]; 4], + _c_matrix: [[f32; 4]; 4], + _d_matrix: [[f32; 4]; 4], +) {} \ No newline at end of file diff --git a/test_data/hover/src/test_extract_docs_attributes.rs b/test_data/hover/src/test_extract_docs_attributes.rs new file mode 100644 index 00000000000..bef8c0b5692 --- /dev/null +++ b/test_data/hover/src/test_extract_docs_attributes.rs @@ -0,0 +1,33 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// Begin multiline attribute +/// +/// Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus, +/// iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel +/// lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat. +/// +/// End multiline attribute +#[derive( + Copy, + Clone +)] +struct MultilineAttribute; + + +/// Begin single line attribute +/// +/// Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus, +/// iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel +/// lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat. +/// +/// End single line attribute. +#[derive(Debug)] +struct SingleLineAttribute; \ No newline at end of file diff --git a/test_data/hover/src/test_extract_docs_comment_block.rs b/test_data/hover/src/test_extract_docs_comment_block.rs new file mode 100644 index 00000000000..1e39007e787 --- /dev/null +++ b/test_data/hover/src/test_extract_docs_comment_block.rs @@ -0,0 +1,26 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//////////////////////////////////////////////////////////////////////////////// +// Free functions +//////////////////////////////////////////////////////////////////////////////// + +/// The standard library often has comment header blocks that should not be +/// included. +/// +/// Nam efficitur dapibus lectus consequat porta. Pellentesque augue metus, +/// vestibulum nec massa at, aliquet consequat ex. +/// +/// End of spawn docs +pub fn spawn(_f: F) -> JoinHandle where + F: FnOnce() -> T, F: Send + 'static, T: Send + 'static +{ + unimplemented!() +} \ No newline at end of file diff --git a/test_data/hover/src/test_extract_docs_empty_line_before_decl.rs b/test_data/hover/src/test_extract_docs_empty_line_before_decl.rs new file mode 100644 index 00000000000..4e020317862 --- /dev/null +++ b/test_data/hover/src/test_extract_docs_empty_line_before_decl.rs @@ -0,0 +1,19 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// Begin empty before decl +/// +/// Cras malesuada mattis massa quis ornare. Suspendisse in ex maximus, +/// iaculis ante non, ultricies nulla. Nam ultrices convallis ex, vel +/// lacinia est rhoncus sed. Nullam sollicitudin finibus ex at placerat. +/// +/// End empty line before decl. + +struct EmptyLineBeforeDecl; \ No newline at end of file diff --git a/test_data/hover/src/test_extract_docs_module_docs.rs b/test_data/hover/src/test_extract_docs_module_docs.rs new file mode 100644 index 00000000000..35b48a96f0d --- /dev/null +++ b/test_data/hover/src/test_extract_docs_module_docs.rs @@ -0,0 +1,22 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Begin module docs +//! +//! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +//! tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus. +//! In hac habitasse platea dictumst. +//! +//! End module docs. + +/// Begin first item docs +/// +/// The first item docs should not pick up the module docs. +struct First; \ No newline at end of file diff --git a/test_data/hover/src/test_extract_docs_module_docs_no_copyright.rs b/test_data/hover/src/test_extract_docs_module_docs_no_copyright.rs new file mode 100644 index 00000000000..a2739d20bec --- /dev/null +++ b/test_data/hover/src/test_extract_docs_module_docs_no_copyright.rs @@ -0,0 +1,12 @@ +//! Begin module docs +//! +//! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +//! tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus. +//! In hac habitasse platea dictumst. +//! +//! End module docs. + +/// Begin first item docs +/// +/// The first item docs should not pick up the module docs. +struct First; \ No newline at end of file diff --git a/test_data/hover/src/test_extract_docs_module_docs_with_attribute.rs b/test_data/hover/src/test_extract_docs_module_docs_with_attribute.rs new file mode 100644 index 00000000000..65a178d95f5 --- /dev/null +++ b/test_data/hover/src/test_extract_docs_module_docs_with_attribute.rs @@ -0,0 +1,24 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Begin module docs +//! +//! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +//! tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus. +//! In hac habitasse platea dictumst. +//! +//! End module docs. + +#![allow(dead_code, unused_imports)] + +/// Begin first item docs +/// +/// The first item docs should not pick up the module docs. +struct First; \ No newline at end of file diff --git a/test_data/hover/src/test_tooltip_01.rs b/test_data/hover/src/test_tooltip_01.rs new file mode 100644 index 00000000000..5320ce41cb7 --- /dev/null +++ b/test_data/hover/src/test_tooltip_01.rs @@ -0,0 +1,114 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// Foo enum +#[derive(Copy, Clone)] +pub enum Foo { + /// Bar variant + Bar, + /// Baz variant + Baz +} + +/// Bar struct +pub struct Bar { + /// The first field + field_1: Tuple, + /// The second field + field_2: T, + /// The third field + field_3: Foo, +} + +impl Bar { + /// Create a new Bar + fn new(one: Tuple, two: T, three: Foo) -> Bar { + Bar { + field_1: one, + field_2: two, + field_3: three, + } + } +} + +/// Tuple struct +pub struct Tuple(pub u32, f32); + +/// Bar function +/// +/// # Examples +/// +/// ```no_run,ignore +/// # extern crate does_not_exist; +/// +/// use does_not_exist::other; +/// +/// let foo = bar(1.0); +/// other(foo); +/// ``` +fn bar(thing: T) -> Bar { + Bar { + field_1: Tuple(1, 3.0), + field_2: thing, + field_3: Foo::Bar, + } +} + +impl Bar { + /// Foo method + fn foo(&mut self, foo: Foo) -> Foo { + let other = self.field_3; + self.field_3 = foo; + other + } + + /// Bar method + fn bar(&mut self, thing: T) -> Bar where T: Copy { + self.field_2 = thing; + bar(self.field_2) + } + + /// Other method + fn other(&self, tuple: Tuple) -> Bar { + Bar::new(Tuple(3, 1.0), tuple.1, Foo::Bar) + } +} + +fn foo() { + let mut bar = Bar::new(Tuple(3, 1.0), 2.0, Foo::Bar); + bar.bar(4.0); + bar.foo(Foo::Baz); + bar.other(Tuple(4, 5.0)); +} + +trait Baz { + /// Foo other type + type Foo: Other; + + fn foo() -> Self::Foo; + +} + +/// The other trait +trait Other {} + +/// The constant FOO +const FOO: &'static str = "FOO"; + +/// The static BAR +static BAR: u32 = 123; + +pub fn print_foo() { + println!("{}", FOO); +} + +pub fn print_bar() { + println!("{}", BAR); +} \ No newline at end of file diff --git a/test_data/hover/src/test_tooltip_mod.rs b/test_data/hover/src/test_tooltip_mod.rs new file mode 100644 index 00000000000..08f8a1c35ac --- /dev/null +++ b/test_data/hover/src/test_tooltip_mod.rs @@ -0,0 +1,31 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Begin module docs +//! +//! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +//! tincidunt tristique maximus. Sed venenatis urna vel sagittis tempus. +//! In hac habitasse platea dictumst. +//! +//! End module docs. + +/// Begin first item docs +/// +/// The first item docs should not pick up the module docs. +pub struct First; + +/// Submodule first line +/// +/// Mauris vel lobortis lacus, in condimentum dolor. +/// +/// Submodule last line +pub mod sub_module { + +} \ No newline at end of file diff --git a/test_data/hover/src/test_tooltip_mod_use.rs b/test_data/hover/src/test_tooltip_mod_use.rs new file mode 100644 index 00000000000..a0c7e88944d --- /dev/null +++ b/test_data/hover/src/test_tooltip_mod_use.rs @@ -0,0 +1,13 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use test_tooltip_mod; +use test_tooltip_mod::First; +use test_tooltip_mod::sub_module; \ No newline at end of file diff --git a/test_data/hover/src/test_tooltip_mod_use_external.rs b/test_data/hover/src/test_tooltip_mod_use_external.rs new file mode 100644 index 00000000000..6cfacd00fd5 --- /dev/null +++ b/test_data/hover/src/test_tooltip_mod_use_external.rs @@ -0,0 +1,15 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use fnv; +use fnv::FnvHasher; + +use std::sync; +use std::sync::Arc; \ No newline at end of file diff --git a/test_data/hover/src/test_tooltip_std.rs b/test_data/hover/src/test_tooltip_std.rs new file mode 100644 index 00000000000..44eb9fc2cef --- /dev/null +++ b/test_data/hover/src/test_tooltip_std.rs @@ -0,0 +1,26 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Spot check several stdlib items and verify that the the doc_url +// is correctly included for traits. The tests around the stdlib +// are subject to breakage due to changes in docs, so these tests +// are not very comprehensive. + + +fn test() { + let mut vec1 = Vec::new(); + vec1.push(1); + let slice = &vec1[0..]; + let _vec2 = vec1.clone(); + let _vec3 = Vec::::default(); + let _one = slice[0]; + let _one_ref = &slice[0]; + use std::string::ToString; +} \ No newline at end of file