mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
fix(repl): Complete declarations (#10963)
This commit is contained in:
parent
4cbc4a7eb3
commit
b4026dac9c
2 changed files with 117 additions and 51 deletions
|
@ -2085,6 +2085,35 @@ mod integration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn pty_complete_declarations() {
|
||||||
|
use std::io::Read;
|
||||||
|
use util::pty::fork::*;
|
||||||
|
let deno_exe = util::deno_exe_path();
|
||||||
|
let fork = Fork::from_ptmx().unwrap();
|
||||||
|
if let Ok(mut master) = fork.is_parent() {
|
||||||
|
master.write_all(b"class MyClass {}\n").unwrap();
|
||||||
|
master.write_all(b"My\t\n").unwrap();
|
||||||
|
master.write_all(b"let myVar;\n").unwrap();
|
||||||
|
master.write_all(b"myV\t\n").unwrap();
|
||||||
|
master.write_all(b"close();\n").unwrap();
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
master.read_to_string(&mut output).unwrap();
|
||||||
|
|
||||||
|
assert!(output.contains("> MyClass"));
|
||||||
|
assert!(output.contains("> myVar"));
|
||||||
|
|
||||||
|
fork.wait().unwrap();
|
||||||
|
} else {
|
||||||
|
std::env::set_var("NO_COLOR", "1");
|
||||||
|
let err = exec::Command::new(deno_exe).arg("repl").exec();
|
||||||
|
println!("err {}", err);
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn pty_ignore_symbols() {
|
fn pty_ignore_symbols() {
|
||||||
|
|
|
@ -50,47 +50,34 @@ impl EditorHelper {
|
||||||
self.message_tx.send((method.to_string(), params))?;
|
self.message_tx.send((method.to_string(), params))?;
|
||||||
self.response_rx.recv()?
|
self.response_rx.recv()?
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn is_word_boundary(c: char) -> bool {
|
fn get_global_lexical_scope_names(&self) -> Vec<String> {
|
||||||
if c == '.' {
|
let evaluate_response = self
|
||||||
false
|
.post_message(
|
||||||
} else {
|
"Runtime.globalLexicalScopeNames",
|
||||||
char::is_ascii_whitespace(&c) || char::is_ascii_punctuation(&c)
|
Some(json!({
|
||||||
|
"executionContextId": self.context_id,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
evaluate_response
|
||||||
|
.get("names")
|
||||||
|
.unwrap()
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|n| n.as_str().unwrap().to_string())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Completer for EditorHelper {
|
|
||||||
type Candidate = String;
|
|
||||||
|
|
||||||
fn complete(
|
|
||||||
&self,
|
|
||||||
line: &str,
|
|
||||||
pos: usize,
|
|
||||||
_ctx: &Context<'_>,
|
|
||||||
) -> Result<(usize, Vec<String>), ReadlineError> {
|
|
||||||
let start = line[..pos].rfind(is_word_boundary).map_or_else(|| 0, |i| i);
|
|
||||||
let end = line[pos..]
|
|
||||||
.rfind(is_word_boundary)
|
|
||||||
.map_or_else(|| pos, |i| pos + i);
|
|
||||||
|
|
||||||
let word = &line[start..end];
|
|
||||||
let word = word.strip_prefix(is_word_boundary).unwrap_or(word);
|
|
||||||
let word = word.strip_suffix(is_word_boundary).unwrap_or(word);
|
|
||||||
|
|
||||||
let fallback = format!(".{}", word);
|
|
||||||
|
|
||||||
let (prefix, suffix) = match word.rfind('.') {
|
|
||||||
Some(index) => word.split_at(index),
|
|
||||||
None => ("globalThis", fallback.as_str()),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
fn get_expression_property_names(&self, expr: &str) -> Vec<String> {
|
||||||
let evaluate_response = self
|
let evaluate_response = self
|
||||||
.post_message(
|
.post_message(
|
||||||
"Runtime.evaluate",
|
"Runtime.evaluate",
|
||||||
Some(json!({
|
Some(json!({
|
||||||
"contextId": self.context_id,
|
"contextId": self.context_id,
|
||||||
"expression": prefix,
|
"expression": expr,
|
||||||
"throwOnSideEffect": true,
|
"throwOnSideEffect": true,
|
||||||
"timeout": 200,
|
"timeout": 200,
|
||||||
})),
|
})),
|
||||||
|
@ -98,8 +85,7 @@ impl Completer for EditorHelper {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if evaluate_response.get("exceptionDetails").is_some() {
|
if evaluate_response.get("exceptionDetails").is_some() {
|
||||||
let candidates = Vec::new();
|
return Vec::new();
|
||||||
return Ok((pos, candidates));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(result) = evaluate_response.get("result") {
|
if let Some(result) = evaluate_response.get("result") {
|
||||||
|
@ -113,32 +99,83 @@ impl Completer for EditorHelper {
|
||||||
|
|
||||||
if let Ok(get_properties_response) = get_properties_response {
|
if let Ok(get_properties_response) = get_properties_response {
|
||||||
if let Some(result) = get_properties_response.get("result") {
|
if let Some(result) = get_properties_response.get("result") {
|
||||||
let candidates = result
|
let property_names = result
|
||||||
.as_array()
|
.as_array()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|r| {
|
.map(|r| r.get("name").unwrap().as_str().unwrap().to_string())
|
||||||
let name = r.get("name").unwrap().as_str().unwrap().to_string();
|
|
||||||
|
|
||||||
if name.starts_with("Symbol(") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if name.starts_with(&suffix[1..]) {
|
|
||||||
return Some(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return Ok((pos - (suffix.len() - 1), candidates));
|
return property_names;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((pos, Vec::new()))
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_word_boundary(c: char) -> bool {
|
||||||
|
if c == '.' {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
char::is_ascii_whitespace(&c) || char::is_ascii_punctuation(&c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_expr_from_line_at_pos(line: &str, cursor_pos: usize) -> &str {
|
||||||
|
let start = line[..cursor_pos]
|
||||||
|
.rfind(is_word_boundary)
|
||||||
|
.map_or_else(|| 0, |i| i);
|
||||||
|
let end = line[cursor_pos..]
|
||||||
|
.rfind(is_word_boundary)
|
||||||
|
.map_or_else(|| cursor_pos, |i| cursor_pos + i);
|
||||||
|
|
||||||
|
let word = &line[start..end];
|
||||||
|
let word = word.strip_prefix(is_word_boundary).unwrap_or(word);
|
||||||
|
let word = word.strip_suffix(is_word_boundary).unwrap_or(word);
|
||||||
|
|
||||||
|
word
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for EditorHelper {
|
||||||
|
type Candidate = String;
|
||||||
|
|
||||||
|
fn complete(
|
||||||
|
&self,
|
||||||
|
line: &str,
|
||||||
|
pos: usize,
|
||||||
|
_ctx: &Context<'_>,
|
||||||
|
) -> Result<(usize, Vec<String>), ReadlineError> {
|
||||||
|
let expr = get_expr_from_line_at_pos(line, pos);
|
||||||
|
|
||||||
|
// check if the expression is in the form `obj.prop`
|
||||||
|
if let Some(index) = expr.rfind('.') {
|
||||||
|
let sub_expr = &expr[..index];
|
||||||
|
let prop_name = &expr[index + 1..];
|
||||||
|
let candidates = self
|
||||||
|
.get_expression_property_names(sub_expr)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|n| !n.starts_with("Symbol(") && n.starts_with(prop_name))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok((pos - prop_name.len(), candidates))
|
||||||
|
} else {
|
||||||
|
// combine results of declarations and globalThis properties
|
||||||
|
let mut candidates = self
|
||||||
|
.get_expression_property_names("globalThis")
|
||||||
|
.into_iter()
|
||||||
|
.chain(self.get_global_lexical_scope_names())
|
||||||
|
.filter(|n| n.starts_with(expr))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// sort and remove duplicates
|
||||||
|
candidates.sort();
|
||||||
|
candidates.dedup(); // make sure to sort first
|
||||||
|
|
||||||
|
Ok((pos - expr.len(), candidates))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue