use std::borrow::Cow;
use std::sync::atomic::Ordering::Relaxed;
use tower_lsp_server::ls_types::*;
use tree_sitter::{QueryCursor, Tree};
use crate::prelude::*;
use crate::backend::Backend;
use crate::backend::Text;
use crate::index::{_G, JsQuery};
use crate::model::PropertyKind;
use crate::utils::{ByteOffset, MaxVec, RangeExt, span_conv};
use tracing::instrument;
use ts_macros::query;
query! {
#[lang = "tree_sitter_javascript"]
OrmCallQuery(OrmObject, CallMethod, ModelArg, MethodArg);
(call_expression
function: (member_expression
object: (member_expression
object: (this)
property: (property_identifier) @ORM_OBJECT (#eq? @ORM_OBJECT "orm"))
property: (property_identifier) @CALL_METHOD (#eq? @CALL_METHOD "call"))
arguments: (arguments
. (string) @MODEL_ARG
. ","
. (string) @METHOD_ARG))
}
impl Backend {
pub fn on_change_js(
&self,
text: &Text,
uri: &Uri,
rope: RopeSlice<'_>,
old_rope: Option<Rope>,
) -> anyhow::Result<()> {
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_javascript::LANGUAGE.into())
.expect("bug: failed to init js parser");
self.update_ast(text, uri, rope, old_rope, parser)
}
pub fn js_jump_def(&self, params: GotoDefinitionParams, rope: RopeSlice<'_>) -> anyhow::Result<Option<Location>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let file_path = uri.to_file_path().unwrap();
let ast = self
.ast_map
.get(file_path.to_str().unwrap())
.ok_or_else(|| errloc!("Did not build AST for {}", uri.path().as_str()))?;
let ByteOffset(offset) = rope_conv(params.text_document_position_params.position, rope);
let contents = Cow::from(rope);
let query = JsQuery::query();
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(query, ast.root_node(), contents.as_bytes());
while let Some(match_) = matches.next() {
for capture in match_.captures {
let range = capture.node.byte_range();
if capture.index == JsQuery::TemplateName as u32 && range.contains(&offset) {
let key = some!(_G(&contents[range.shrink(1)]));
return Ok(some!(self.index.templates.get(&key)).location.clone().map(Into::into));
}
}
}
let query = OrmCallQuery::query();
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(query, ast.root_node(), contents.as_bytes());
while let Some(match_) = matches.next() {
let mut model_arg_node = None;
let mut method_arg_node = None;
for capture in match_.captures {
match OrmCallQuery::from(capture.index) {
Some(OrmCallQuery::ModelArg) => {
model_arg_node = Some(capture.node);
}
Some(OrmCallQuery::MethodArg) => {
method_arg_node = Some(capture.node);
}
_ => {}
}
}
if let Some(model_node) = model_arg_node
&& let range = model_node.byte_range()
&& range.contains_end(offset)
{
let range = range.shrink(1);
let model = &contents[range];
return self.index.jump_def_model(model);
}
if let Some(model_node) = model_arg_node
&& let Some(method_node) = method_arg_node
&& let range = method_node.byte_range()
&& range.contains_end(offset)
{
let model = &contents[model_node.byte_range().shrink(1)];
let method = &contents[range.shrink(1)];
return self.index.jump_def_property_name(method, model);
}
}
Ok(None)
}
pub fn js_references(&self, params: ReferenceParams, rope: RopeSlice<'_>) -> anyhow::Result<Option<Vec<Location>>> {
let uri = ¶ms.text_document_position.text_document.uri;
let file_path = uri.to_file_path().unwrap();
let ast = self
.ast_map
.get(file_path.to_str().unwrap())
.ok_or_else(|| errloc!("Did not build AST for {}", uri.path().as_str()))?;
let ByteOffset(offset) = rope_conv(params.text_document_position.position, rope);
let contents = Cow::from(rope);
let query = JsQuery::query();
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(query, ast.root_node(), contents.as_bytes());
while let Some(match_) = matches.next() {
for capture in match_.captures {
let range = capture.node.byte_range();
if capture.index == JsQuery::TemplateName as u32 && range.contains(&offset) {
let key = &contents[range.shrink(1)];
let key = some!(_G(key));
let template = some!(self.index.templates.get(&key));
return Ok(Some(
template
.descendants
.iter()
.flat_map(|tpl| tpl.location.clone().map(Into::into))
.collect(),
));
}
}
}
Ok(None)
}
pub fn js_hover(&self, params: HoverParams, rope: RopeSlice<'_>) -> anyhow::Result<Option<Hover>> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let file_path = uri.to_file_path().unwrap();
let ast = self
.ast_map
.get(file_path.to_str().unwrap())
.ok_or_else(|| errloc!("Did not build AST for {}", uri.path().as_str()))?;
let ByteOffset(offset) = rope_conv(params.text_document_position_params.position, rope);
let contents = Cow::from(rope);
let query = JsQuery::query();
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(query, ast.root_node(), contents.as_bytes());
while let Some(match_) = matches.next() {
for capture in match_.captures {
let range = capture.node.byte_range();
if capture.index == JsQuery::TemplateName as u32 && range.contains(&offset) {
return Ok(self
.index
.hover_template(&contents[range.shrink(1)], Some(span_conv(capture.node.range()))));
}
if capture.index == JsQuery::Name as u32 && range.contains(&offset) {
return Ok(self
.index
.hover_component(&contents[range], Some(span_conv(capture.node.range()))));
}
}
}
let query = OrmCallQuery::query();
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(query, ast.root_node(), contents.as_bytes());
while let Some(match_) = matches.next() {
let mut model_arg_node = None;
let mut method_arg_node = None;
for capture in match_.captures {
match OrmCallQuery::from(capture.index) {
Some(OrmCallQuery::ModelArg) => {
model_arg_node = Some(capture.node);
}
Some(OrmCallQuery::MethodArg) => {
method_arg_node = Some(capture.node);
}
_ => {}
}
}
if let Some(model_node) = model_arg_node
&& let range = model_node.byte_range()
&& range.contains_end(offset)
{
let range = range.shrink(1);
let model = &contents[range.clone()];
return (self.index).hover_model(model, Some(rope_conv(range.map_unit(ByteOffset), rope)), false, None);
}
if let Some(model_node) = model_arg_node
&& let Some(method_node) = method_arg_node
&& let range = method_node.byte_range()
&& range.contains_end(offset)
{
let range = range.shrink(1);
let model = &contents[model_node.byte_range().shrink(1)];
let method = &contents[range.clone()];
return self.index.hover_property_name(
method,
model,
Some(rope_conv(range.map_unit(ByteOffset), rope)),
);
}
}
Ok(None)
}
#[instrument(skip_all)]
pub async fn js_completions(
&self,
params: CompletionParams,
ast: Tree,
rope: RopeSlice<'_>,
) -> anyhow::Result<Option<CompletionResponse>> {
let position = params.text_document_position.position;
let ByteOffset(offset) = rope_conv(position, rope);
let path = some!(params.text_document_position.text_document.uri.to_file_path());
let completions_limit = self
.workspaces
.find_workspace_of(&path, |_, ws| ws.completions.limit)
.unwrap_or_else(|| self.project_config.completions_limit.load(Relaxed));
let contents = Cow::from(rope);
let query = OrmCallQuery::query();
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(query, ast.root_node(), contents.as_bytes());
while let Some(match_) = matches.next() {
let mut model_arg_node = None;
let mut method_arg_node = None;
for capture in match_.captures {
match OrmCallQuery::from(capture.index) {
Some(OrmCallQuery::ModelArg) => {
model_arg_node = Some(capture.node);
}
Some(OrmCallQuery::MethodArg) => {
method_arg_node = Some(capture.node);
}
_ => {}
}
}
if let Some(model_node) = model_arg_node {
let range = model_node.byte_range();
if range.contains(&offset) {
let inner_range = range.shrink(1);
let prefix = &contents[inner_range.start..offset];
let lsp_range = rope_conv(inner_range.map_unit(ByteOffset), rope);
let mut items = MaxVec::new(completions_limit);
self.index.complete_model(prefix, lsp_range, &mut items)?;
return Ok(Some(CompletionResponse::List(CompletionList {
is_incomplete: !items.has_space(),
items: items.into_inner(),
})));
}
}
if let Some(method_node) = method_arg_node {
let range = method_node.byte_range();
if range.contains(&offset) {
if let Some(model_node) = model_arg_node {
let model_range = model_node.byte_range().shrink(1);
let model_name = &contents[model_range];
let inner_range = range.clone().shrink(1);
let prefix = &contents[inner_range.start..offset];
let byte_range = inner_range.map_unit(ByteOffset);
let mut items = MaxVec::new(completions_limit);
self.index.complete_property_name(
prefix,
byte_range,
model_name.into(),
rope,
Some(PropertyKind::Method),
None,
true,
true,
&mut items,
)?;
return Ok(Some(CompletionResponse::List(CompletionList {
is_incomplete: !items.has_space(),
items: items.into_inner(),
})));
}
}
}
if let Some(model_node) = model_arg_node {
let contents_bytes = contents.as_bytes();
let model_end = model_node.byte_range().end;
let mut i = model_end;
while i < contents_bytes.len() && contents_bytes[i].is_ascii_whitespace() {
i += 1;
}
if i < contents_bytes.len() && contents_bytes[i] == b',' {
i += 1;
while i < contents_bytes.len() && contents_bytes[i].is_ascii_whitespace() {
i += 1;
}
if offset >= i
&& (method_arg_node.is_none() || offset < method_arg_node.unwrap().byte_range().start)
{
let model_range = model_node.byte_range().shrink(1);
let model_name = &contents[model_range];
let synthetic_range = ByteOffset(i)..ByteOffset(offset.max(i));
let mut items = MaxVec::new(100);
self.index.complete_property_name(
"",
synthetic_range,
model_name.into(),
rope,
Some(PropertyKind::Method),
None,
true,
true,
&mut items,
)?;
return Ok(Some(CompletionResponse::List(CompletionList {
is_incomplete: false,
items: items.into_inner(),
})));
}
}
}
}
Ok(None)
}
}