Reformatted autocomplete
This commit is contained in:
parent
28783a5d1b
commit
05d6a40efb
@ -8,3 +8,4 @@ My starting point is [this article](https://www.joshmcguigan.com/blog/build-your
|
|||||||
## To fix
|
## To fix
|
||||||
* Autocomplete : when nothing found, return input
|
* Autocomplete : when nothing found, return input
|
||||||
* Autocomplete : Weird permission issues sometimes (ignore unallowed files and folders)
|
* Autocomplete : Weird permission issues sometimes (ignore unallowed files and folders)
|
||||||
|
* Bug with find_common_chars (if I type Cargo.t and press tab, we can't choose between Cargo.toml and Cargo.lock)
|
||||||
|
@ -248,8 +248,9 @@ pub mod shell {
|
|||||||
if *&mycommand.len() == 0 {
|
if *&mycommand.len() == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Search
|
//Search
|
||||||
let (res, list) = autocomplete(&mycommand);
|
let (res, list) = Search::build(&mycommand).unwrap().autocomplete();
|
||||||
write!(stdout, "\r\n{}\r\n", list);
|
write!(stdout, "\r\n{}\r\n", list);
|
||||||
mycommand.clear();
|
mycommand.clear();
|
||||||
mycommand.push_str(&res);
|
mycommand.push_str(&res);
|
||||||
|
@ -4,76 +4,118 @@ use std::path::PathBuf;
|
|||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
|
|
||||||
pub fn autocomplete(input: &String) -> (String, String) {
|
///Contains all necesseary info and methods to perform an autocomplete search.
|
||||||
if !input.contains(' ') && !input.contains("./") {
|
///Built from user input.
|
||||||
return autocomplete_cmd(input);
|
#[derive(Debug)]
|
||||||
} else {
|
pub struct Search {
|
||||||
match autocomplete_file(input) {
|
//Left part of user input. May be empty. in "cat foo", command is cat and searchee is foo.
|
||||||
Ok(t) => return t,
|
command: String,
|
||||||
Err(e) => {
|
//Where to search. Current_folder by default, but the user may have given a path
|
||||||
println!("\n\r{}", e);
|
//.eg "cat /foo/bar"
|
||||||
return (String::from(input), String::new());
|
search_path: PathBuf,
|
||||||
},
|
//What to search for
|
||||||
|
searchee: String,
|
||||||
|
//Set to true if we are searching in current folder.
|
||||||
|
local_search: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Search {
|
||||||
|
///Returns a Search instance, taking the command line input.
|
||||||
|
pub fn build(input: &String) -> std::io::Result<Search> {
|
||||||
|
|
||||||
|
//Getting cmd and searched string
|
||||||
|
let mut words = input.split(' ').collect::<Vec<&str>>();
|
||||||
|
let buff_searchee = String::from(*words.last().unwrap_or(&""));
|
||||||
|
words.pop();
|
||||||
|
let input_cmd = words.join(" ");
|
||||||
|
|
||||||
|
//Are we looking in a specific path ?
|
||||||
|
//eg. am I doing cat mystuff.txt or cat folder/subfolder/mystuff.txt ?
|
||||||
|
let buff_search = buff_searchee
|
||||||
|
.replace(" ", "");
|
||||||
|
let mut buff_search = buff_search
|
||||||
|
.split('/')
|
||||||
|
.collect::<Vec<&str>>();
|
||||||
|
let searchee = String::from(*buff_search.last().unwrap_or(&""));
|
||||||
|
buff_search.pop();
|
||||||
|
let mut search_path = PathBuf::new();
|
||||||
|
let mut local = false;
|
||||||
|
|
||||||
|
//We may need to modify the searchpath before storing it as a PathBuf
|
||||||
|
let mut path_temp = String::new();
|
||||||
|
//NOT local path;
|
||||||
|
if buff_search.len() > 0 {
|
||||||
|
*&mut path_temp = buff_search.join("/");
|
||||||
|
//Making sure we have the final / in the search_path
|
||||||
|
if !path_temp.ends_with('/') {
|
||||||
|
*&mut path_temp.push_str("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
//local path;
|
||||||
|
} else {
|
||||||
|
*&mut path_temp = env::current_dir()?
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap();
|
||||||
|
if !&path_temp.ends_with('/') {
|
||||||
|
*&mut path_temp.push_str("/");
|
||||||
|
}
|
||||||
|
local = true;
|
||||||
|
}
|
||||||
|
search_path.push(&path_temp);
|
||||||
|
|
||||||
|
let s = Search {
|
||||||
|
command: input_cmd,
|
||||||
|
search_path: search_path,
|
||||||
|
searchee: searchee,
|
||||||
|
local_search: local,
|
||||||
};
|
};
|
||||||
|
return Ok(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
///Performs the search and returns (most likely result, result lines to display)
|
||||||
|
///A found file is preferred to a found command
|
||||||
|
pub fn autocomplete(&self) -> (String, String) {
|
||||||
|
let (found_cmd, found_cmd_lines) = autocomplete_cmd(&self.searchee);
|
||||||
|
let (found_file, found_file_lines) = match autocomplete_file(&self){
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(_) => (String::new(), String::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res_lines = String::new();
|
||||||
|
if found_file.len() > 0 {
|
||||||
|
res_lines = found_file_lines.clone();
|
||||||
|
} else {
|
||||||
|
res_lines = found_cmd_lines.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = String::new();
|
||||||
|
if found_file.len() > 0 {
|
||||||
|
res = String::from(&found_file);
|
||||||
|
} else {
|
||||||
|
res = String::from(&found_cmd);
|
||||||
|
}
|
||||||
|
if !&self.local_search {
|
||||||
|
let return_val = format!("{} {}{}", &self.command, &self.search_path.display(), res);
|
||||||
|
return(return_val, res_lines);
|
||||||
|
} else {
|
||||||
|
let return_val = format!("{} {}", &self.command, res);
|
||||||
|
return(return_val, res_lines);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///Look for a file using the string after the last space in the input,
|
///Look for a file using the string after the last space in the input,
|
||||||
///in the current folder.
|
///in the current folder.
|
||||||
fn autocomplete_file(input: &String) -> std::io::Result<(String, String)> {
|
fn autocomplete_file(search: &Search) -> std::io::Result<(String, String)> {
|
||||||
|
|
||||||
//Keep the last part of the cmd...
|
|
||||||
let mut input_searchee = match input
|
|
||||||
.split(' ')
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.last()
|
|
||||||
.copied() {
|
|
||||||
Some(c) => c,
|
|
||||||
None => {
|
|
||||||
return Ok((String::from(""), String::from("")));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
//And also the beginning !
|
|
||||||
let mut input_buff = input
|
|
||||||
.split(' ')
|
|
||||||
.collect::<Vec<&str>>();
|
|
||||||
_ = input_buff.pop();
|
|
||||||
let input_origin = format!("{} ", input_buff.join(" "));
|
|
||||||
|
|
||||||
//Store the results
|
|
||||||
let mut results = Vec::new();
|
|
||||||
|
|
||||||
//Where do we search ?
|
|
||||||
let mut folder_to_search = PathBuf::new();
|
|
||||||
//Display folder in search result ?
|
|
||||||
let mut searching_not_current = false;
|
|
||||||
//Searching somewhere else...
|
|
||||||
//Here we need to correctly divide the string into the folder
|
|
||||||
//and the searched file itself. The next 10 lines or so of code
|
|
||||||
//suck, but sometimes Rust pisses in your boots.
|
|
||||||
if input_searchee.contains('/') {
|
|
||||||
//Correctly taking first and last part...
|
|
||||||
let splitted_searchstring = &input_searchee.split('/');
|
|
||||||
let trimmed_searchee = &splitted_searchstring.clone().last().unwrap();
|
|
||||||
let splitted_searchstring = &mut input_searchee.clone().split('/').collect::<Vec<&str>>();
|
|
||||||
splitted_searchstring.pop();
|
|
||||||
let mut tosearch = splitted_searchstring.join("/");
|
|
||||||
tosearch.push_str("/");
|
|
||||||
folder_to_search.push(tosearch);
|
|
||||||
*&mut input_searchee = &trimmed_searchee;
|
|
||||||
*&mut searching_not_current = true;
|
|
||||||
//Searching the current folder
|
|
||||||
} else {
|
|
||||||
folder_to_search = env::current_dir()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Search happens 'round here
|
|
||||||
let mut all_res = String::new();
|
let mut all_res = String::new();
|
||||||
let mut nbr_found: u16 = 0;
|
let mut nbr_found: u16 = 0;
|
||||||
let mut last_found = String::new();
|
let mut last_found = String::new();
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
for file in fs::read_dir(&folder_to_search)? {
|
for file in fs::read_dir(search.search_path.clone())? {
|
||||||
let filepath = file.unwrap().path();
|
let filepath = file.unwrap().path();
|
||||||
let filename = filepath
|
let filename = filepath
|
||||||
.iter()
|
.iter()
|
||||||
@ -81,20 +123,20 @@ fn autocomplete_file(input: &String) -> std::io::Result<(String, String)> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let re = format!("^{}.*$", input_searchee);
|
let re = format!("^{}.*$", search.searchee);
|
||||||
let regex = Regex::new(&re).unwrap();
|
let regex = Regex::new(&re).unwrap();
|
||||||
if regex.is_match(filename) {
|
if regex.is_match(filename) {
|
||||||
if !searching_not_current {
|
if search.local_search {
|
||||||
*&mut results.push(format!("{}", filename));
|
*&mut results.push(format!("{}", filename));
|
||||||
} else {
|
} else {
|
||||||
*&mut results.push(format!("{}{}", &folder_to_search.display(), filename));
|
*&mut results.push(format!("{}{}", &search.search_path.display(), filename));
|
||||||
}
|
}
|
||||||
*&mut nbr_found += 1;
|
*&mut nbr_found += 1;
|
||||||
let matchline = &format!("\r\n* {}", filename);
|
let matchline = &format!("\r\n* {}", filename);
|
||||||
&mut all_res.push_str(&matchline);
|
&mut all_res.push_str(&matchline);
|
||||||
|
|
||||||
//if what we found is a folder, we add an / after
|
//if what we found is a folder, we add an / after
|
||||||
let fullpath = format!("{}/{}", &folder_to_search.display(), filename);
|
let fullpath = format!("{}/{}", &search.search_path.display(), filename);
|
||||||
let md = fs::metadata(fullpath).unwrap();
|
let md = fs::metadata(fullpath).unwrap();
|
||||||
//Will be added to the end of the result
|
//Will be added to the end of the result
|
||||||
let mut ending = String::new();
|
let mut ending = String::new();
|
||||||
@ -102,15 +144,12 @@ fn autocomplete_file(input: &String) -> std::io::Result<(String, String)> {
|
|||||||
*&mut ending.push_str("/");
|
*&mut ending.push_str("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
if searching_not_current {
|
if !search.local_search {
|
||||||
*&mut last_found = format!("{}{}{}{}",
|
*&mut last_found = format!("{}{}",
|
||||||
&input_origin,
|
|
||||||
&folder_to_search.display(),
|
|
||||||
filename,
|
filename,
|
||||||
ending);
|
ending);
|
||||||
} else {
|
} else {
|
||||||
*&mut last_found = format!("{}{}{}",
|
*&mut last_found = format!("{}{}",
|
||||||
&input_origin,
|
|
||||||
filename,
|
filename,
|
||||||
ending);
|
ending);
|
||||||
}
|
}
|
||||||
@ -123,8 +162,7 @@ fn autocomplete_file(input: &String) -> std::io::Result<(String, String)> {
|
|||||||
//Otherwise, just display a list and use what all the results have in common
|
//Otherwise, just display a list and use what all the results have in common
|
||||||
} else {
|
} else {
|
||||||
let longest = find_common_chars(results);
|
let longest = find_common_chars(results);
|
||||||
let ret_string = format!("{}{}", input_origin, &longest);
|
return Ok((longest, all_res));
|
||||||
return Ok((ret_string, all_res));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +180,7 @@ fn autocomplete_cmd(input: &String) -> (String, String) {
|
|||||||
return (res_string, res_list);
|
return (res_string, res_list);
|
||||||
} else if found_bins.len() == 0 {
|
} else if found_bins.len() == 0 {
|
||||||
let list = String::from("Nothing adequate.");
|
let list = String::from("Nothing adequate.");
|
||||||
let res = String::from(input);
|
let res = String::new();
|
||||||
return (res, list);
|
return (res, list);
|
||||||
} else {
|
} else {
|
||||||
let mut all_res = String::new();
|
let mut all_res = String::new();
|
||||||
|
205
src/shell/autocomplete_old.rs
Normal file
205
src/shell/autocomplete_old.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
use which::which_re;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn autocomplete(input: &String) -> (String, String) {
|
||||||
|
if !input.contains(' ') && !input.contains("./") {
|
||||||
|
return autocomplete_cmd(input);
|
||||||
|
} else {
|
||||||
|
match autocomplete_file(input) {
|
||||||
|
Ok(t) => return t,
|
||||||
|
Err(e) => {
|
||||||
|
println!("\n\r{}", e);
|
||||||
|
return (String::from(input), String::new());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Look for a file using the string after the last space in the input,
|
||||||
|
///in the current folder.
|
||||||
|
fn autocomplete_file(input: &String) -> std::io::Result<(String, String)> {
|
||||||
|
|
||||||
|
//Keep the last part of the cmd...
|
||||||
|
let mut input_searchee = match input
|
||||||
|
.split(' ')
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.last()
|
||||||
|
.copied() {
|
||||||
|
Some(c) => c,
|
||||||
|
None => {
|
||||||
|
return Ok((String::from(""), String::from("")));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//And also the beginning !
|
||||||
|
let mut input_buff = input
|
||||||
|
.split(' ')
|
||||||
|
.collect::<Vec<&str>>();
|
||||||
|
_ = input_buff.pop();
|
||||||
|
let input_origin = format!("{} ", input_buff.join(" "));
|
||||||
|
|
||||||
|
//Store the results
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
//Where do we search ?
|
||||||
|
let mut folder_to_search = PathBuf::new();
|
||||||
|
//Display folder in search result ?
|
||||||
|
let mut searching_not_current = false;
|
||||||
|
//Searching somewhere else...
|
||||||
|
//Here we need to correctly divide the string into the folder
|
||||||
|
//and the searched file itself. The next 10 lines or so of code
|
||||||
|
//suck, but sometimes Rust pisses in your boots.
|
||||||
|
if input_searchee.contains('/') {
|
||||||
|
//Correctly taking first and last part...
|
||||||
|
let splitted_searchstring = &input_searchee.split('/');
|
||||||
|
let trimmed_searchee = &splitted_searchstring.clone().last().unwrap();
|
||||||
|
let splitted_searchstring = &mut input_searchee.clone().split('/').collect::<Vec<&str>>();
|
||||||
|
splitted_searchstring.pop();
|
||||||
|
let mut tosearch = splitted_searchstring.join("/");
|
||||||
|
tosearch.push_str("/");
|
||||||
|
folder_to_search.push(tosearch);
|
||||||
|
*&mut input_searchee = &trimmed_searchee;
|
||||||
|
*&mut searching_not_current = true;
|
||||||
|
//Searching the current folder
|
||||||
|
} else {
|
||||||
|
folder_to_search = env::current_dir()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Search happens 'round here
|
||||||
|
let mut all_res = String::new();
|
||||||
|
let mut nbr_found: u16 = 0;
|
||||||
|
let mut last_found = String::new();
|
||||||
|
|
||||||
|
for file in fs::read_dir(&folder_to_search)? {
|
||||||
|
let filepath = file.unwrap().path();
|
||||||
|
let filename = filepath
|
||||||
|
.iter()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
let re = format!("^{}.*$", input_searchee);
|
||||||
|
let regex = Regex::new(&re).unwrap();
|
||||||
|
if regex.is_match(filename) {
|
||||||
|
if !searching_not_current {
|
||||||
|
*&mut results.push(format!("{}", filename));
|
||||||
|
} else {
|
||||||
|
*&mut results.push(format!("{}{}", &folder_to_search.display(), filename));
|
||||||
|
}
|
||||||
|
*&mut nbr_found += 1;
|
||||||
|
let matchline = &format!("\r\n* {}", filename);
|
||||||
|
&mut all_res.push_str(&matchline);
|
||||||
|
|
||||||
|
//if what we found is a folder, we add an / after
|
||||||
|
let fullpath = format!("{}/{}", &folder_to_search.display(), filename);
|
||||||
|
let md = fs::metadata(fullpath).unwrap();
|
||||||
|
//Will be added to the end of the result
|
||||||
|
let mut ending = String::new();
|
||||||
|
if md.is_dir() {
|
||||||
|
*&mut ending.push_str("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
if searching_not_current {
|
||||||
|
*&mut last_found = format!("{}{}{}{}",
|
||||||
|
&input_origin,
|
||||||
|
&folder_to_search.display(),
|
||||||
|
filename,
|
||||||
|
ending);
|
||||||
|
} else {
|
||||||
|
*&mut last_found = format!("{}{}{}",
|
||||||
|
&input_origin,
|
||||||
|
filename,
|
||||||
|
ending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Found one or zero, use what has been found directly
|
||||||
|
if nbr_found < 2 {
|
||||||
|
|
||||||
|
return Ok((last_found, String::new()));
|
||||||
|
//Otherwise, just display a list and use what all the results have in common
|
||||||
|
} else {
|
||||||
|
let longest = find_common_chars(results);
|
||||||
|
let ret_string = format!("{}{}", input_origin, &longest);
|
||||||
|
return Ok((ret_string, all_res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn autocomplete_cmd(input: &String) -> (String, String) {
|
||||||
|
let found_bins = find_bin(input);
|
||||||
|
if found_bins.len() == 1 {
|
||||||
|
let aaa = found_bins.clone();
|
||||||
|
let bbb = &aaa[0]
|
||||||
|
.iter()
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
let res_string = String::from(bbb.to_str().unwrap());
|
||||||
|
let res_list = res_string.clone();
|
||||||
|
return (res_string, res_list);
|
||||||
|
} else if found_bins.len() == 0 {
|
||||||
|
let list = String::from("Nothing adequate.");
|
||||||
|
let res = String::from(input);
|
||||||
|
return (res, list);
|
||||||
|
} else {
|
||||||
|
let mut all_res = String::new();
|
||||||
|
for path in found_bins {
|
||||||
|
let buff = String::from(path
|
||||||
|
.iter()
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap());
|
||||||
|
let res_line = format!("* {}\r\n", buff);
|
||||||
|
all_res.push_str(&res_line);
|
||||||
|
}
|
||||||
|
let first_res = String::from(input);
|
||||||
|
return(first_res, all_res)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Takes a string and returns a Vector of PathBuf containing all matchings
|
||||||
|
///commands
|
||||||
|
fn find_bin(command: &String) -> Vec<PathBuf> {
|
||||||
|
let re = format!("^{}.*", command);
|
||||||
|
let re = Regex::new(&re).unwrap();
|
||||||
|
let binaries: Vec<PathBuf> = which_re(re).unwrap().collect();
|
||||||
|
return binaries;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_common_chars(mut strvec: Vec<String>) -> String {
|
||||||
|
let first_word = strvec[0].clone();
|
||||||
|
strvec.remove(0);
|
||||||
|
let mut answer = String::new();
|
||||||
|
let mut counter: usize = 0;
|
||||||
|
let mut still_ok = true;
|
||||||
|
|
||||||
|
for letter in first_word.chars() {
|
||||||
|
for word in &strvec {
|
||||||
|
if *&still_ok == true {
|
||||||
|
match word.chars().nth(counter) {
|
||||||
|
Some(l) => {
|
||||||
|
if l != letter {
|
||||||
|
*&mut still_ok = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
*&mut still_ok = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if still_ok == true {
|
||||||
|
*&mut answer.push(letter);
|
||||||
|
*&mut counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//answer.pop();
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user