1
0
Fork 0
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:
David Sherret 2021-06-15 09:31:36 -04:00 committed by GitHub
parent 4cbc4a7eb3
commit b4026dac9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 51 deletions

View file

@ -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() {

View file

@ -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))
}
} }
} }