Skip to content

Commit

Permalink
Add dotty assignment support (#543)
Browse files Browse the repository at this point in the history
* Add pipe utilities to `NodeTypeExt`

* Rework completion pipe utilities on top of `NodeExt` utilities

* Add dotty support
  • Loading branch information
DavisVaughan authored Sep 24, 2024
1 parent 6647177 commit 3069e1f
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 66 deletions.
2 changes: 1 addition & 1 deletion crates/ark/src/lsp/completions/sources/composite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn completions_from_composite_sources(

let mut completions: Vec<CompletionItem> = vec![];

let root = find_pipe_root(context);
let root = find_pipe_root(context)?;

// Try argument completions
if let Some(mut additional_completions) = completions_from_call(context, root.clone())? {
Expand Down
97 changes: 38 additions & 59 deletions crates/ark/src/lsp/completions/sources/composite/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@
//
//

use anyhow::Result;
use harp::error::Error;
use harp::eval::RParseEvalOptions;
use harp::object::RObject;
use stdext::local;
use stdext::IntoOption;
use tower_lsp::lsp_types::CompletionItem;
use tree_sitter::Node;

use crate::lsp::completions::sources::utils::completions_from_object_names;
use crate::lsp::document_context::DocumentContext;
use crate::lsp::traits::rope::RopeExt;
use crate::treesitter::BinaryOperatorType;
use crate::treesitter::NodeType;
use crate::treesitter::NodeTypeExt;

#[derive(Clone)]
Expand All @@ -30,7 +25,9 @@ pub(super) struct PipeRoot {
pub(super) object: Option<RObject>,
}

pub(super) fn completions_from_pipe(root: Option<PipeRoot>) -> Result<Option<Vec<CompletionItem>>> {
pub(super) fn completions_from_pipe(
root: Option<PipeRoot>,
) -> anyhow::Result<Option<Vec<CompletionItem>>> {
let Some(root) = root else {
// No pipe
return Ok(None);
Expand All @@ -54,7 +51,7 @@ pub(super) fn completions_from_pipe(root: Option<PipeRoot>) -> Result<Option<Vec

/// Loop should be kept in sync with `completions_from_call()` so they find
/// the same call to detect the pipe root of
pub(super) fn find_pipe_root(context: &DocumentContext) -> Option<PipeRoot> {
pub(super) fn find_pipe_root(context: &DocumentContext) -> anyhow::Result<Option<PipeRoot>> {
log::info!("find_pipe_root()");

let mut node = context.node;
Expand All @@ -80,17 +77,17 @@ pub(super) fn find_pipe_root(context: &DocumentContext) -> Option<PipeRoot> {
}

if !has_call {
return None;
return Ok(None);
}

let name = find_pipe_root_name(context, &node);
let name = find_pipe_root_name(context, &node)?;

let object = match &name {
Some(name) => eval_pipe_root(name),
None => None,
};

name.map(|name| PipeRoot { name, object })
Ok(name.map(|name| PipeRoot { name, object }))
}

fn eval_pipe_root(name: &str) -> Option<RObject> {
Expand Down Expand Up @@ -124,69 +121,51 @@ fn eval_pipe_root(name: &str) -> Option<RObject> {
Some(value)
}

fn find_pipe_root_name(context: &DocumentContext, node: &Node) -> Option<String> {
// Try to figure out the code associated with the 'root' of the pipe expression.
let root = local! {

let root = find_pipe_root_node(context, *node)?;
is_pipe_operator(context, &root).into_option()?;
fn find_pipe_root_name(context: &DocumentContext, node: &Node) -> anyhow::Result<Option<String>> {
// Try to figure out the code associated with the 'root' of the pipe expression
let Some(root) = find_pipe_root_node(context, *node)? else {
return Ok(None);
};
if !root.is_pipe_operator(&context.document.contents)? {
return Ok(None);
}

// Get the left-hand side of the pipe expression.
let mut lhs = root.child_by_field_name("lhs")?;
while is_pipe_operator(context, &lhs) {
lhs = lhs.child_by_field_name("lhs")?;
}
// Get the left-hand side of the pipe expression
let Some(mut lhs) = root.child_by_field_name("lhs") else {
return Ok(None);
};

// Try to evaluate the left-hand side
let root = context.document.contents.node_slice(&lhs).ok()?.to_string();
Some(root)
while lhs.is_pipe_operator(&context.document.contents)? {
lhs = match lhs.child_by_field_name("lhs") {
Some(lhs) => lhs,
None => return Ok(None),
};
}

};
// Try to evaluate the left-hand side
let root = context.document.contents.node_slice(&lhs)?.to_string();

root.map(|x| x.to_string())
Ok(Some(root))
}

fn find_pipe_root_node<'a>(context: &DocumentContext, mut node: Node<'a>) -> Option<Node<'a>> {
fn find_pipe_root_node<'a>(
context: &DocumentContext,
mut node: Node<'a>,
) -> anyhow::Result<Option<Node<'a>>> {
let mut root = None;

loop {
if is_pipe_operator(context, &node) {
if node.is_pipe_operator(&context.document.contents)? {
root = Some(node);
}

node = match node.parent() {
Some(node) => node,
None => return root,
None => return Ok(root),
}
}
}

fn is_pipe_operator(context: &DocumentContext, node: &Node) -> bool {
let node_type = node.node_type();

if node_type == NodeType::BinaryOperator(BinaryOperatorType::Pipe) {
// Native pipe
return true;
}

if node_type == NodeType::BinaryOperator(BinaryOperatorType::Special) {
// magrittr pipe
let Some(node) = node.child_by_field_name("operator") else {
return false;
};

match context.document.contents.node_slice(&node) {
Ok(slice) => return slice == "%>%",
Err(err) => {
log::error!("{err:?}");
return false;
},
}
}

return false;
}

#[cfg(test)]
mod tests {
use harp::eval::RParseEvalOptions;
Expand All @@ -205,7 +184,7 @@ mod tests {
let document = Document::new("x |> foo() %>% bar()", None);
let context = DocumentContext::new(&document, point, None);

let root = find_pipe_root(&context).unwrap();
let root = find_pipe_root(&context).unwrap().unwrap();
assert_eq!(root.name, "x".to_string());
assert!(root.object.is_none());
});
Expand All @@ -217,7 +196,7 @@ mod tests {
let document = Document::new("x |> foo() %||% bar()", None);
let context = DocumentContext::new(&document, point, None);

let root = find_pipe_root(&context);
let root = find_pipe_root(&context).unwrap();
assert!(root.is_none());
});
}
Expand All @@ -235,14 +214,14 @@ mod tests {
let document = Document::new("x %>% foo()", None);
let context = DocumentContext::new(&document, point, None);

let root = find_pipe_root(&context).unwrap();
let root = find_pipe_root(&context).unwrap().unwrap();
assert_eq!(root.name, "x".to_string());
assert!(root.object.is_none());

// Set up a real `x` and try again
harp::parse_eval("x <- data.frame(a = 1)", options.clone()).unwrap();

let root = find_pipe_root(&context).unwrap();
let root = find_pipe_root(&context).unwrap().unwrap();
assert_eq!(root.name, "x".to_string());
assert!(root.object.is_some());

Expand Down
Loading

0 comments on commit 3069e1f

Please sign in to comment.