Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/ra_hir_ty/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ fn type_at(content: &str) -> String {
type_at_pos(&db, file_pos)
}

fn infer(content: &str) -> String {
infer_with_mismatches(content, false)
fn infer(ra_fixture: &str) -> String {
infer_with_mismatches(ra_fixture, false)
}

fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
Expand Down
34 changes: 25 additions & 9 deletions crates/ra_ide/src/call_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use hir::Semantics;
use ra_ide_db::RootDatabase;
use ra_syntax::{
ast::{self, ArgListOwner},
match_ast, AstNode, SyntaxNode,
match_ast, AstNode, SyntaxNode, SyntaxToken,
};
use test_utils::tested_by;

Expand All @@ -16,7 +16,13 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
let file = file.syntax();
let token = file.token_at_offset(position.offset).next()?;
let token = sema.descend_into_macros(token);
call_info_for_token(&sema, token)
}

pub(crate) fn call_info_for_token(
sema: &Semantics<RootDatabase>,
token: SyntaxToken,
) -> Option<CallInfo> {
// Find the calling expression and it's NameRef
let calling_node = FnCallNode::with_node(&token.parent())?;

Expand All @@ -27,21 +33,23 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
match callable_def {
hir::CallableDef::FunctionId(it) => {
let fn_def = it.into();
(CallInfo::with_fn(db, fn_def), fn_def.has_self_param(db))
(CallInfo::with_fn(sema.db, fn_def), fn_def.has_self_param(sema.db))
}
hir::CallableDef::StructId(it) => {
(CallInfo::with_struct(sema.db, it.into())?, false)
}
hir::CallableDef::StructId(it) => (CallInfo::with_struct(db, it.into())?, false),
hir::CallableDef::EnumVariantId(it) => {
(CallInfo::with_enum_variant(db, it.into())?, false)
(CallInfo::with_enum_variant(sema.db, it.into())?, false)
}
}
}
FnCallNode::MethodCallExpr(method_call) => {
let function = sema.resolve_method_call(&method_call)?;
(CallInfo::with_fn(db, function), function.has_self_param(db))
(CallInfo::with_fn(sema.db, function), function.has_self_param(sema.db))
}
FnCallNode::MacroCallExpr(macro_call) => {
let macro_def = sema.resolve_macro_call(&macro_call)?;
(CallInfo::with_macro(db, macro_def)?, false)
(CallInfo::with_macro(sema.db, macro_def)?, false)
}
};

Expand All @@ -61,7 +69,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
let num_args_at_callsite = arg_list.args().count();

let arg_list_range = arg_list.syntax().text_range();
if !arg_list_range.contains_inclusive(position.offset) {
if !arg_list_range.contains_inclusive(token.text_range().start()) {
tested_by!(call_info_bad_offset);
return None;
}
Expand All @@ -70,7 +78,9 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
num_args_at_callsite,
arg_list
.args()
.take_while(|arg| arg.syntax().text_range().end() < position.offset)
.take_while(|arg| {
arg.syntax().text_range().end() < token.text_range().start()
})
.count(),
);

Expand Down Expand Up @@ -100,7 +110,13 @@ impl FnCallNode {
match_ast! {
match node {
ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) },
ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) },
ast::MethodCallExpr(it) => {
let arg_list = it.arg_list()?;
if !syntax.text_range().is_subrange(&arg_list.syntax().text_range()) {
return None;
}
Some(FnCallNode::MethodCallExpr(it))
},
ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) },
_ => { None },
}
Expand Down
16 changes: 8 additions & 8 deletions crates/ra_ide/src/mock_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,28 +124,28 @@ impl MockAnalysis {
}

/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) {
let (mock, position) = MockAnalysis::with_files_and_position(fixture);
pub fn analysis_and_position(ra_fixture: &str) -> (Analysis, FilePosition) {
let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
(mock.analysis(), position)
}

/// Creates analysis for a single file.
pub fn single_file(code: &str) -> (Analysis, FileId) {
pub fn single_file(ra_fixture: &str) -> (Analysis, FileId) {
let mut mock = MockAnalysis::new();
let file_id = mock.add_file("/main.rs", code);
let file_id = mock.add_file("/main.rs", ra_fixture);
(mock.analysis(), file_id)
}

/// Creates analysis for a single file, returns position marked with <|>.
pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) {
pub fn single_file_with_position(ra_fixture: &str) -> (Analysis, FilePosition) {
let mut mock = MockAnalysis::new();
let pos = mock.add_file_with_position("/main.rs", code);
let pos = mock.add_file_with_position("/main.rs", ra_fixture);
(mock.analysis(), pos)
}

/// Creates analysis for a single file, returns range marked with a pair of <|>.
pub fn single_file_with_range(code: &str) -> (Analysis, FileRange) {
pub fn single_file_with_range(ra_fixture: &str) -> (Analysis, FileRange) {
let mut mock = MockAnalysis::new();
let pos = mock.add_file_with_range("/main.rs", code);
let pos = mock.add_file_with_range("/main.rs", ra_fixture);
(mock.analysis(), pos)
}
59 changes: 55 additions & 4 deletions crates/ra_ide/src/syntax_highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use ra_ide_db::{
};
use ra_prof::profile;
use ra_syntax::{
ast, AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind::*, TextRange, WalkEvent, T,
ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind::*, SyntaxToken,
TextRange, WalkEvent, T,
};
use rustc_hash::FxHashMap;

use crate::{references::classify_name_ref, FileId};
use crate::{call_info::call_info_for_token, references::classify_name_ref, Analysis, FileId};

pub(crate) use html::highlight_as_html;
pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
Expand Down Expand Up @@ -94,11 +95,12 @@ pub(crate) fn highlight(
WalkEvent::Enter(it) => it,
WalkEvent::Leave(_) => continue,
};

let range = element.text_range();

let element_to_highlight = if current_macro_call.is_some() {
// Inside a macro -- expand it first
let token = match element.into_token() {
let token = match element.clone().into_token() {
Some(it) if it.parent().kind() == TOKEN_TREE => it,
_ => continue,
};
Expand All @@ -110,9 +112,17 @@ pub(crate) fn highlight(
_ => token.into(),
}
} else {
element
element.clone()
};

if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
let expanded = element_to_highlight.as_token().unwrap().clone();
if highlight_injection(&mut res, &sema, token, expanded).is_some() {
eprintln!("res = {:?}", res);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a leftover?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, I've removed this during the weekend I think

continue;
}
}

if let Some((highlight, binding_hash)) =
highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
{
Expand Down Expand Up @@ -281,3 +291,44 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
_ => default,
}
}

fn highlight_injection(
acc: &mut Vec<HighlightedRange>,
sema: &Semantics<RootDatabase>,
literal: ast::RawString,
expanded: SyntaxToken,
) -> Option<()> {
let call_info = call_info_for_token(&sema, expanded)?;
let idx = call_info.active_parameter?;
let name = call_info.signature.parameter_names.get(idx)?;
if name != "ra_fixture" {
Copy link
Contributor

@edwin0cheng edwin0cheng Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to enable the syntax highlight inside string literal, the parameter name need to be called "ra_fixture", right ?

return None;
}
let value = literal.value()?;
let (analysis, tmp_file_id) = Analysis::from_single_file(value);

if let Some(range) = literal.open_quote_text_range() {
acc.push(HighlightedRange {
range,
highlight: HighlightTag::LiteralString.into(),
binding_hash: None,
})
}

for mut h in analysis.highlight(tmp_file_id).unwrap() {
if let Some(r) = literal.map_range_up(h.range) {
h.range = r;
acc.push(h)
}
}

if let Some(range) = literal.close_quote_text_range() {
acc.push(HighlightedRange {
range,
highlight: HighlightTag::LiteralString.into(),
binding_hash: None,
})
}

Some(())
}
44 changes: 43 additions & 1 deletion crates/ra_ide_db/src/line_index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! `LineIndex` maps flat `TextUnit` offsets into `(Line, Column)`
//! representation.
use std::iter;

use ra_syntax::TextUnit;
use ra_syntax::{TextRange, TextUnit};
use rustc_hash::FxHashMap;
use superslice::Ext;

Expand Down Expand Up @@ -87,6 +88,19 @@ impl LineIndex {
self.newlines[line_col.line as usize] + col
}

pub fn lines(&self, range: TextRange) -> impl Iterator<Item = TextRange> + '_ {
let lo = self.newlines.lower_bound(&range.start());
let hi = self.newlines.upper_bound(&range.end());
let all = iter::once(range.start())
.chain(self.newlines[lo..hi].iter().copied())
.chain(iter::once(range.end()));

all.clone()
.zip(all.skip(1))
.map(|(lo, hi)| TextRange::from_to(lo, hi))
.filter(|it| !it.is_empty())
}

fn utf8_to_utf16_col(&self, line: u32, mut col: TextUnit) -> usize {
if let Some(utf16_chars) = self.utf16_lines.get(&line) {
let mut correction = TextUnit::from_usize(0);
Expand Down Expand Up @@ -221,4 +235,32 @@ const C: char = \"メ メ\";

assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextUnit::from_usize(15));
}

#[test]
fn test_splitlines() {
fn r(lo: u32, hi: u32) -> TextRange {
TextRange::from_to(lo.into(), hi.into())
}

let text = "a\nbb\nccc\n";
let line_index = LineIndex::new(text);

let actual = line_index.lines(r(0, 9)).collect::<Vec<_>>();
let expected = vec![r(0, 2), r(2, 5), r(5, 9)];
assert_eq!(actual, expected);

let text = "";
let line_index = LineIndex::new(text);

let actual = line_index.lines(r(0, 0)).collect::<Vec<_>>();
let expected = vec![];
assert_eq!(actual, expected);

let text = "\n";
let line_index = LineIndex::new(text);

let actual = line_index.lines(r(0, 1)).collect::<Vec<_>>();
let expected = vec![r(0, 1)];
assert_eq!(actual, expected)
}
}
30 changes: 30 additions & 0 deletions crates/ra_syntax/src/ast/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,36 @@ impl RawString {
let inside_str = &text[start_of_inside..end_of_inside];
Some(inside_str.to_string())
}

pub fn open_quote_text_range(&self) -> Option<TextRange> {
let text = self.text().as_str();
let usual_string_range = find_usual_string_range(text)?;

let start = self.syntax().text_range().start();
let len = usual_string_range.start() + TextUnit::of_char('"');
Some(TextRange::offset_len(start, len))
}

pub fn close_quote_text_range(&self) -> Option<TextRange> {
let text = self.text().as_str();
let usual_string_range = find_usual_string_range(text)?;

let end = self.syntax().text_range().end();
let len = TextUnit::of_str(text) - usual_string_range.end();
Some(TextRange::from_to(end - len, end))
}

pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
// FIXME: handle escapes here properly
let text = self.text().as_str();
let usual_string_range = find_usual_string_range(text)?;
Some(
range
+ self.syntax().text_range().start()
+ TextUnit::of_char('"')
+ usual_string_range.start(),
)
}
}

fn find_usual_string_range(s: &str) -> Option<TextRange> {
Expand Down
11 changes: 9 additions & 2 deletions crates/rust-analyzer/src/main_loop/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1078,13 +1078,20 @@ pub fn handle_semantic_tokens(
let _p = profile("handle_semantic_tokens");

let file_id = params.text_document.try_conv_with(&world)?;
let text = world.analysis().file_text(file_id)?;
let line_index = world.analysis().file_line_index(file_id)?;

let mut builder = SemanticTokensBuilder::default();

for highlight_range in world.analysis().highlight(file_id)?.into_iter() {
let (token_type, token_modifiers) = highlight_range.highlight.conv();
builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers);
let (token_index, modifier_bitset) = highlight_range.highlight.conv();
for mut range in line_index.lines(highlight_range.range) {
if text[range].ends_with('\n') {
range = TextRange::from_to(range.start(), range.end() - TextUnit::of_char('\n'));
}
let range = range.conv_with(&line_index);
builder.push(range, token_index, modifier_bitset);
}
}

let tokens = SemanticTokens { data: builder.build(), ..Default::default() };
Expand Down