Skip to content

Commit

Permalink
infrastructure for custom completions
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinushey committed Mar 30, 2023
1 parent 74ddaae commit 29139da
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 92 deletions.
3 changes: 2 additions & 1 deletion extensions/positron-r/amalthea/crates/ark/src/lsp/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,8 @@ impl LanguageServer for Backend {
});

// request signature help
let result = unsafe { signature_help(document.value(), &params) };
let position = params.text_document_position_params.position;
let result = unsafe { signature_help(document.value(), &position) };

// unwrap errors
let result = unwrap!(result, Err(error) => {
Expand Down
131 changes: 65 additions & 66 deletions extensions/positron-r/amalthea/crates/ark/src/lsp/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use harp::r_symbol;
use harp::string::r_string_decode;
use harp::utils::r_envir_name;
use harp::utils::r_formals;
use harp::utils::r_typeof;
use lazy_static::lazy_static;
use libR_sys::*;
use log::*;
Expand Down Expand Up @@ -51,6 +52,7 @@ use crate::lsp::backend::Backend;
use crate::lsp::documents::Document;
use crate::lsp::help::RHtmlHelp;
use crate::lsp::indexer;
use crate::lsp::signature_help::signature_help;
use crate::lsp::traits::cursor::TreeCursorExt;
use crate::lsp::traits::point::PointExt;
use crate::lsp::traits::position::PositionExt;
Expand All @@ -77,6 +79,7 @@ pub enum CompletionData {
ScopeParameter { name: String },
ScopeVariable { name: String },
Snippet { text: String },
Unknown,
}

#[derive(Debug)]
Expand Down Expand Up @@ -195,6 +198,7 @@ pub unsafe fn resolve_completion_item(item: &mut CompletionItem, data: &Completi
CompletionData::ScopeVariable { name } => Ok(false),
CompletionData::ScopeParameter { name } => Ok(false),
CompletionData::Snippet { text } => Ok(false),
CompletionData::Unknown => Ok(false),
}

}
Expand Down Expand Up @@ -345,6 +349,14 @@ pub fn completion_item_from_function<T: AsRef<str>>(name: &str, envir: Option<&s
return Ok(item);
}

// TODO
unsafe fn completion_item_from_dataset(name: &str) -> Result<CompletionItem> {

let mut item = completion_item(name.to_string(), CompletionData::Unknown)?;
item.kind = Some(CompletionItemKind::STRUCT);
Ok(item)

}
unsafe fn completion_item_from_data_variable(name: &str, owner: &str, enquote: bool) -> Result<CompletionItem> {

let mut item = completion_item(name.to_string(), CompletionData::DataVariable {
Expand Down Expand Up @@ -632,82 +644,69 @@ unsafe fn append_subset_completions(_context: &CompletionContext, callee: &str,

}

unsafe fn append_call_library_completions(context: &CompletionContext, cursor: &Node, node: &Node, completions: &mut Vec<CompletionItem>) -> Result<bool> {

// Try to figure out the callee (if any).
let ok = local! {

info!("Cursor: {}", cursor.to_sexp());
info!("Node: {}", node.to_sexp());

// Get the parent node.
let mut parent = cursor.parent()?;
if matches!(parent.kind(), "argument") {
parent = parent.parent()?;
if matches!(parent.kind(), "arguments") {
parent = parent.parent()?;
}
}

// Make sure it matches the call node.
info!("Parent: {}", parent.to_sexp());
(parent == *node).into_option()?;

// Get the callee.
let mut callee = node.child(0)?;
info!("Callee: {}", callee.to_sexp());

// Resolve the callee for namespaced calls.
if matches!(callee.kind(), "::" | ":::") {

// Check for callable lhs.
let lhs = callee.child_by_field_name("lhs")?;
if !matches!(lhs.kind(), "identifier" | "string") {
return None;
}

// Make sure it matches base.
let contents = unwrap!(lhs.utf8_text(context.source.as_bytes()), Err(_) => {
return None;
});

(contents == "base").into_option()?;
unsafe fn append_custom_completions(
context: &CompletionContext,
cursor: &Node,
completions: &mut Vec<CompletionItem>) -> Result<bool>
{

// Update the callee.
callee = callee.child_by_field_name("rhs")?;
// Use the signature help tools to figure out the necessary pieces.
let position = cursor.start_position().as_position();
let signatures = signature_help(context.document, &position)?;

}

// Make sure we have an identifier.
(callee.kind() == "identifier").into_option()?;
let signatures = unwrap!(signatures, None => {
return Ok(false);
});

// Check for call to handled functions.
let callee = callee.utf8_text(context.source.as_bytes()).unwrap_or_default();
info!("Callee text: {}", callee);
if !matches!(callee, "library" | "require" | "requireNamespace") {
return None;
// Pull out the relevant signature information.
let signature = signatures.signatures.get(0).into_result()?;
let mut name = signature.label.clone();
let parameters = signature.parameters.as_ref().into_result()?;
let index = signature.active_parameter.into_result()?;
let parameter = parameters.get(index as usize).into_result()?;

// Extract the argument text.
let argument = match parameter.label.clone() {
tower_lsp::lsp_types::ParameterLabel::LabelOffsets([start, end]) => {
let label = signature.label.as_str();
let substring = label.get((start as usize)..(end as usize));
substring.unwrap().to_string()
},
tower_lsp::lsp_types::ParameterLabel::Simple(string) => {
string
}

Some(true)

};

if ok.is_none() {
return Ok(false);
// Trim off the function arguments from the signature.
if let Some(index) = name.find('(') {
name = name[0..index].to_string();
}

let packages = RFunction::new("base", ".packages")
.param("all.available", true)
.call()?
.to::<Vec<String>>()?;
// Call our custom completion function.
let r_completions = RFunction::from(".ps.completions.getCustomCallCompletions")
.param("name", name)
.param("argument", argument)
.call()?;

for package in packages {
let item = completion_item_from_package(package.as_str(), false)?;
completions.push(item);
if r_typeof(*r_completions) == VECSXP {
let values = VECTOR_ELT(*r_completions, 0);
let kind = VECTOR_ELT(*r_completions, 1);
if let Ok(values) = RObject::view(values).to::<Vec<String>>() {
let kind = RObject::view(kind).to::<String>().unwrap_or("unknown".to_string());
for value in values.iter() {
let item = match kind.as_str() {
"package" => completion_item_from_package(value, false),
"dataset" => completion_item_from_dataset(value),
_ => completion_item(value, CompletionData::Unknown),
};
if let Ok(item) = item {
completions.push(item);
}
}
}
}

Ok(true)

}

unsafe fn append_call_completions(context: &CompletionContext, _cursor: &Node, node: &Node, completions: &mut Vec<CompletionItem>) -> Result<()> {
Expand Down Expand Up @@ -814,7 +813,7 @@ unsafe fn append_argument_completions(_context: &CompletionContext, callee: &str

if Rf_isFunction(*value) != 0 {

let strings = RFunction::from(".ps.formalNames")
let strings = RFunction::from(".ps.completions.formalNames")
.add(*value)
.call()?
.to::<Vec<String>>()?;
Expand Down Expand Up @@ -1169,7 +1168,7 @@ pub unsafe fn append_session_completions(context: &CompletionContext, completion
found_call_completions = true;

// Check for library() completions.
match append_call_library_completions(context, &cursor, &node, completions) {
match append_custom_completions(context, &cursor, completions) {
Ok(done) => if done { return Ok(()) },
Err(error) => error!("{}", error),
}
Expand Down
49 changes: 29 additions & 20 deletions extensions/positron-r/amalthea/crates/ark/src/lsp/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use harp::r_string;
use harp::r_symbol;
use libR_sys::*;
use stdext::local;
use stdext::unwrap::IntoResult;
use std::collections::HashMap;
use std::env;
use std::path::Path;
Expand Down Expand Up @@ -53,12 +54,12 @@ pub struct RModuleInfo {
// We can't use a crate like 'notify' here as the file watchers they try
// to add seem to conflict with the ones added by VSCode; at least, that's
// what I observed on macOS.
struct Watcher {
struct RModuleWatcher {
pub path: PathBuf,
pub cache: HashMap<PathBuf, std::fs::Metadata>,
}

impl Watcher {
impl RModuleWatcher {

pub fn new(path: PathBuf) -> Self {
Self {
Expand All @@ -69,13 +70,18 @@ impl Watcher {

pub fn watch(&mut self) -> anyhow::Result<()> {

let public = self.path.join("public");
let private = self.path.join("private");

// initialize
let entries = std::fs::read_dir(&self.path)?;
for entry in entries.into_iter() {
if let Ok(entry) = entry {
let path = entry.path();
let meta = path.metadata()?;
self.cache.insert(path, meta);
for path in [public, private] {
let entries = std::fs::read_dir(path)?;
for entry in entries.into_iter() {
if let Ok(entry) = entry {
let path = entry.path();
let meta = path.metadata()?;
self.cache.insert(path, meta);
}
}
}

Expand All @@ -87,7 +93,11 @@ impl Watcher {
for (path, oldmeta) in self.cache.iter_mut() {
let newmeta = path.metadata()?;
if oldmeta.modified()? != newmeta.modified()? {
r_lock! { import(path) };
r_lock! {
if let Err(error) = import(path) {
log::error!("{}", error);
}
};
*oldmeta = newmeta;
}
}
Expand Down Expand Up @@ -169,7 +179,7 @@ pub unsafe fn initialize() -> anyhow::Result<RModuleInfo> {
let path = file.path();
if let Some(ext) = path.extension() {
if ext == "R" {
import(path);
import(path).unwrap();
}
}
}
Expand All @@ -178,7 +188,7 @@ pub unsafe fn initialize() -> anyhow::Result<RModuleInfo> {
std::thread::spawn({
let root = root.clone();
move || {
let mut watcher = Watcher::new(root);
let mut watcher = RModuleWatcher::new(root);
match watcher.watch() {
Ok(_) => {},
Err(error) => log::error!("[watcher] Error watching files: {}", error),
Expand All @@ -196,11 +206,11 @@ pub unsafe fn initialize() -> anyhow::Result<RModuleInfo> {
});
}

pub unsafe fn import(file: &Path) {
pub unsafe fn import(file: &Path) -> anyhow::Result<()> {

// Figure out if this is a 'private' or 'public' component.
let parent = file.parent().unwrap();
let name = parent.file_name().unwrap();
let parent = file.parent().into_result()?;
let name = parent.file_name().into_result()?;
let envir = if name == "private" {
log::info!("Loading private module: {:?}", file);
POSITRON_PRIVATE_ENVIRONMENT
Expand All @@ -217,24 +227,23 @@ pub unsafe fn import(file: &Path) {
RFunction::new("base", "sys.source")
.param("file", file)
.param("envir", envir)
.call()
.unwrap();
.call()?;

// Get a list of bindings from the public environment.
let bindings = RFunction::new("base", "as.list.environment")
.param("x", POSITRON_PUBLIC_ENVIRONMENT)
.param("all.names", true)
.call()
.unwrap();
.call()?;

// Update bindings in the attached environment.
// TODO: It might be fine to just do this only after importing
// all files?
RFunction::new("base", "list2env")
.param("x", *bindings)
.param("envir", POSITRON_ATTACHED_ENVIRONMENT)
.call()
.unwrap();
.call()?;

Ok(())

}

Loading

0 comments on commit 29139da

Please sign in to comment.