Skip to content

Commit

Permalink
feat(argparser): ✨ add auto-completion support (#911)
Browse files Browse the repository at this point in the history
When a command enables the `argparser`, omni will now provide
automatically `autocompletion` for that command using the information
that was provided to configure the `argparser`. This means that we can
auto-complete enum values for instance, as well as all parameters.

New argument types are introduced as `dir`, `file` and `repopath`
allowing to respectively auto-complete directories, files and repository
paths. When using those types, the value passed to the command will be
an absolute path.

Commands that have auto-completion enabled (`true`) will skip the
argparser auto-completion. There is also a new `partial` auto-completion
that is available when the argparser is configured, if the command
provider wants to offer extended auto-completion for the values of
specific parameters. The `partial` auto-completion will callout to the
underlying, trusted, command if and only if a _value_ should be
auto-completed for the current cursor position.

Closes #854
  • Loading branch information
xaf authored Jan 23, 2025
1 parent 7257d28 commit 0b53608
Show file tree
Hide file tree
Showing 22 changed files with 1,056 additions and 327 deletions.
580 changes: 549 additions & 31 deletions src/internal/commands/base.rs

Large diffs are not rendered by default.

90 changes: 18 additions & 72 deletions src/internal/commands/builtin/cd.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::process::exit;

use shell_escape::escape;

use crate::internal::commands::base::BuiltinCommand;
use crate::internal::commands::base::CommandAutocompletion;
use crate::internal::commands::utils::omni_cmd;
use crate::internal::commands::utils::path_auto_complete;
use crate::internal::commands::Command;
use crate::internal::config::config;
use crate::internal::config::parser::ParseArgsValue;
use crate::internal::config::CommandSyntax;
use crate::internal::config::SyntaxOptArg;
use crate::internal::config::SyntaxOptArgType;
use crate::internal::env::omni_cmd_file;
use crate::internal::env::user_home;
use crate::internal::env::Shell;
use crate::internal::git::ORG_LOADER;
use crate::internal::user_interface::StringColor;
use crate::internal::workdir;
Expand Down Expand Up @@ -213,6 +212,7 @@ impl BuiltinCommand for CdCommand {
.to_string()
),
arg_type: SyntaxOptArgType::Flag,
conflicts_with: vec!["--no-include-packages".to_string()],
..Default::default()
},
SyntaxOptArg {
Expand Down Expand Up @@ -271,77 +271,23 @@ impl BuiltinCommand for CdCommand {
exit(0);
}

fn autocompletion(&self) -> bool {
true
fn autocompletion(&self) -> CommandAutocompletion {
CommandAutocompletion::Partial
}

fn autocomplete(&self, comp_cword: usize, argv: Vec<String>) -> Result<(), ()> {
if comp_cword > 0 {
return Ok(());
}

let repo = if !argv.is_empty() {
argv[0].clone()
} else {
"".to_string()
};

// Figure out if this is a path, so we can avoid the expensive repository search
let path_only = repo.starts_with('/')
|| repo.starts_with('.')
|| repo.starts_with("~/")
|| repo == "~"
|| repo == "-";

// Print all the completion related to path completion
let (list_dir, strip_path_prefix, replace_home_prefix) = if repo == "~" {
(user_home(), false, true)
} else if let Some(repo) = repo.strip_prefix("~/") {
if let Some(slash) = repo.rfind('/') {
let abspath = format!("{}/{}", user_home(), &repo[..(slash + 1)]);
(abspath, false, true)
} else {
(user_home(), false, true)
}
} else if let Some(slash) = repo.rfind('/') {
(repo[..(slash + 1)].to_string(), false, false)
} else {
(".".to_string(), true, false)
};
if let Ok(files) = std::fs::read_dir(&list_dir) {
for path in files.flatten() {
if path.path().is_dir() {
let path_buf;
let path_obj = path.path();
let path = if strip_path_prefix {
path_obj.strip_prefix(&list_dir).unwrap()
} else if replace_home_prefix {
if let Ok(path_obj) = path_obj.strip_prefix(user_home()) {
path_buf = PathBuf::from("~").join(path_obj);
path_buf.as_path()
} else {
path_obj.as_path()
}
} else {
path_obj.as_path()
};
let path_str = path.to_str().unwrap();

if !path_str.starts_with(repo.as_str()) {
continue;
}

println!("{}/", path.display());
}
}
}

// Get all the repositories per org
if !path_only {
let add_space = if Shell::current().is_fish() { " " } else { "" };
for match_repo in ORG_LOADER.complete(&repo) {
println!("{}{}", match_repo, add_space);
}
fn autocomplete(
&self,
comp_cword: usize,
argv: Vec<String>,
parameter: Option<String>,
) -> Result<(), ()> {
// We only have the work directory to autocomplete
if parameter.unwrap_or_default() == "workdir" {
let repo = argv.get(comp_cword).map_or("", String::as_str);

path_auto_complete(repo, true, false)
.iter()
.for_each(|s| println!("{}", s));
}

Ok(())
Expand Down
14 changes: 10 additions & 4 deletions src/internal/commands/builtin/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use shell_words::join as shell_join;
use tokio::process::Command as TokioCommand;

use crate::internal::commands::base::BuiltinCommand;
use crate::internal::commands::base::CommandAutocompletion;
use crate::internal::commands::builtin::UpCommand;
use crate::internal::commands::utils::omni_cmd;
use crate::internal::commands::Command;
Expand Down Expand Up @@ -488,11 +489,16 @@ impl BuiltinCommand for CloneCommand {
exit(0);
}

fn autocompletion(&self) -> bool {
false
fn autocompletion(&self) -> CommandAutocompletion {
CommandAutocompletion::Null
}

fn autocomplete(&self, _comp_cword: usize, _argv: Vec<String>) -> Result<(), ()> {
Err(())
fn autocomplete(
&self,
_comp_cword: usize,
_argv: Vec<String>,
_parameter: Option<String>,
) -> Result<(), ()> {
Ok(())
}
}
17 changes: 9 additions & 8 deletions src/internal/commands/builtin/config/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use serde::Deserialize;
use serde::Serialize;

use crate::internal::commands::base::BuiltinCommand;
use crate::internal::commands::base::CommandAutocompletion;
use crate::internal::commands::builtin::TidyGitRepo;
use crate::internal::commands::utils::abs_path;
use crate::internal::commands::utils::file_auto_complete;
Expand Down Expand Up @@ -137,16 +138,16 @@ impl BuiltinCommand for ConfigBootstrapCommand {
exit(0);
}

fn autocompletion(&self) -> bool {
true
fn autocompletion(&self) -> CommandAutocompletion {
CommandAutocompletion::Null
}

fn autocomplete(&self, _comp_cword: usize, _argv: Vec<String>) -> Result<(), ()> {
println!("--organizations");
println!("--repo-path-format");
println!("--shell");
println!("--worktree");

fn autocomplete(
&self,
_comp_cword: usize,
_argv: Vec<String>,
_parameter: Option<String>,
) -> Result<(), ()> {
Ok(())
}
}
Expand Down
14 changes: 10 additions & 4 deletions src/internal/commands/builtin/config/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::process::exit;
use itertools::Itertools;

use crate::internal::commands::base::BuiltinCommand;
use crate::internal::commands::base::CommandAutocompletion;
use crate::internal::commands::frompath::PathCommand;
use crate::internal::commands::Command;
use crate::internal::config::config;
Expand Down Expand Up @@ -305,12 +306,17 @@ impl BuiltinCommand for ConfigCheckCommand {
self.filter_and_print_errors(&error_handler, &args);
}

fn autocompletion(&self) -> bool {
false
fn autocompletion(&self) -> CommandAutocompletion {
CommandAutocompletion::Null
}

fn autocomplete(&self, _comp_cword: usize, _argv: Vec<String>) -> Result<(), ()> {
Err(())
fn autocomplete(
&self,
_comp_cword: usize,
_argv: Vec<String>,
_parameter: Option<String>,
) -> Result<(), ()> {
Ok(())
}
}

Expand Down
14 changes: 10 additions & 4 deletions src/internal/commands/builtin/config/path/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use indicatif::ProgressBar;
use indicatif::ProgressStyle;

use crate::internal::commands::base::BuiltinCommand;
use crate::internal::commands::base::CommandAutocompletion;
use crate::internal::commands::builtin::CloneCommand;
use crate::internal::commands::builtin::TidyGitRepo;
use crate::internal::commands::builtin::UpCommand;
Expand Down Expand Up @@ -471,11 +472,16 @@ impl BuiltinCommand for ConfigPathSwitchCommand {
exit(0);
}

fn autocompletion(&self) -> bool {
false
fn autocompletion(&self) -> CommandAutocompletion {
CommandAutocompletion::Null
}

fn autocomplete(&self, _comp_cword: usize, _argv: Vec<String>) -> Result<(), ()> {
Err(())
fn autocomplete(
&self,
_comp_cword: usize,
_argv: Vec<String>,
_parameter: Option<String>,
) -> Result<(), ()> {
Ok(())
}
}
14 changes: 10 additions & 4 deletions src/internal/commands/builtin/config/reshim.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::process::exit;

use crate::internal::commands::base::BuiltinCommand;
use crate::internal::commands::base::CommandAutocompletion;
use crate::internal::commands::Command;
use crate::internal::config::up::utils::reshim;
use crate::internal::config::up::utils::PrintProgressHandler;
Expand Down Expand Up @@ -79,11 +80,16 @@ impl BuiltinCommand for ConfigReshimCommand {
exit(0);
}

fn autocompletion(&self) -> bool {
false
fn autocompletion(&self) -> CommandAutocompletion {
CommandAutocompletion::Null
}

fn autocomplete(&self, _comp_cword: usize, _argv: Vec<String>) -> Result<(), ()> {
Err(())
fn autocomplete(
&self,
_comp_cword: usize,
_argv: Vec<String>,
_parameter: Option<String>,
) -> Result<(), ()> {
Ok(())
}
}
40 changes: 16 additions & 24 deletions src/internal/commands/builtin/config/trust.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::process::exit;

use crate::internal::cache::WorkdirsCache;
use crate::internal::commands::base::BuiltinCommand;
use crate::internal::commands::base::CommandAutocompletion;
use crate::internal::commands::Command;
use crate::internal::config::parser::ParseArgsValue;
use crate::internal::config::CommandSyntax;
Expand All @@ -14,7 +14,6 @@ use crate::internal::workdir;
use crate::internal::workdir::add_trust;
use crate::internal::workdir::is_trusted;
use crate::internal::workdir::remove_trust;
use crate::internal::ORG_LOADER;
use crate::omni_error;
use crate::omni_info;

Expand Down Expand Up @@ -116,6 +115,7 @@ impl BuiltinCommand for ConfigTrustCommand {
)
.to_string(),
),
arg_type: SyntaxOptArgType::RepoPath,
..Default::default()
},
],
Expand All @@ -135,20 +135,8 @@ impl BuiltinCommand for ConfigTrustCommand {
.expect("should have args to parse"),
);

let path = if let Some(repo) = &args.workdir {
if let Some(repo_path) = ORG_LOADER.find_repo(repo, true, false, false) {
repo_path
} else {
omni_error!(format!("repository not found: {}", repo));
exit(1);
}
} else {
PathBuf::from(".")
};

let path_str = path.display().to_string();

let wd = workdir(path_str.as_str());
let path_str = args.workdir.as_deref().unwrap_or(".");
let wd = workdir(path_str);
let wd_id = match wd.id() {
Some(id) => id,
None => {
Expand All @@ -160,7 +148,7 @@ impl BuiltinCommand for ConfigTrustCommand {
}
};

let is_trusted = is_trusted(path_str.as_str());
let is_trusted = is_trusted(path_str);

if args.check_status {
if is_trusted {
Expand All @@ -185,7 +173,7 @@ impl BuiltinCommand for ConfigTrustCommand {
exit(0);
}

if add_trust(path_str.as_str()) {
if add_trust(path_str) {
omni_info!(
format!("work directory is now {}", "trusted".light_green()),
wd_id
Expand All @@ -212,7 +200,7 @@ impl BuiltinCommand for ConfigTrustCommand {
exit(1);
}

if remove_trust(path_str.as_str()) {
if remove_trust(path_str) {
omni_info!(
format!("work directory is now {}", "untrusted".light_red()),
wd_id
Expand All @@ -224,12 +212,16 @@ impl BuiltinCommand for ConfigTrustCommand {
}
}

fn autocompletion(&self) -> bool {
false
fn autocompletion(&self) -> CommandAutocompletion {
CommandAutocompletion::Null
}

fn autocomplete(&self, _comp_cword: usize, _argv: Vec<String>) -> Result<(), ()> {
// TODO: autocomplete repositories if first argument
Err(())
fn autocomplete(
&self,
_comp_cword: usize,
_argv: Vec<String>,
_parameter: Option<String>,
) -> Result<(), ()> {
Ok(())
}
}
Loading

0 comments on commit 0b53608

Please sign in to comment.