Skip to content

Commit 57b007f

Browse files
committed
Improve completion.
- still not there, but moving forward
1 parent 3541533 commit 57b007f

7 files changed

Lines changed: 453 additions & 7 deletions

File tree

crates/rb-cli/src/completion.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,156 @@ fn extract_rubies_dir_from_line(words: &[&str]) -> Option<PathBuf> {
4949
None
5050
}
5151

52+
/// Suggest directories for completion
53+
fn suggest_directories(current: &str) {
54+
let current_path = std::path::Path::new(current);
55+
56+
let (search_dir, prefix) = if current.is_empty() {
57+
(std::path::PathBuf::from("."), "")
58+
} else if current.ends_with('/') || current.ends_with(std::path::MAIN_SEPARATOR) {
59+
(current_path.to_path_buf(), "")
60+
} else {
61+
match current_path.parent() {
62+
Some(parent) if !parent.as_os_str().is_empty() => {
63+
let prefix = current_path
64+
.file_name()
65+
.and_then(|n| n.to_str())
66+
.unwrap_or("");
67+
(parent.to_path_buf(), prefix)
68+
}
69+
_ => (std::path::PathBuf::from("."), current),
70+
}
71+
};
72+
73+
let Ok(entries) = std::fs::read_dir(&search_dir) else {
74+
return;
75+
};
76+
77+
let mut candidates = Vec::new();
78+
79+
for entry in entries.flatten() {
80+
let Ok(file_type) = entry.file_type() else {
81+
continue;
82+
};
83+
84+
if !file_type.is_dir() {
85+
continue;
86+
}
87+
88+
let file_name = entry.file_name();
89+
let Some(name) = file_name.to_str() else {
90+
continue;
91+
};
92+
93+
if name.starts_with('.') && !prefix.starts_with('.') {
94+
continue;
95+
}
96+
97+
if !name.starts_with(prefix) {
98+
continue;
99+
}
100+
101+
let candidate_path =
102+
if current.ends_with('/') || current.ends_with(std::path::MAIN_SEPARATOR) {
103+
format!("{}{}/", current, name)
104+
} else if let Some(parent) = current_path.parent() {
105+
if parent.as_os_str().is_empty() || parent == std::path::Path::new(".") {
106+
format!("{}/", name)
107+
} else {
108+
format!("{}/{}/", parent.display(), name)
109+
}
110+
} else {
111+
format!("{}/", name)
112+
};
113+
114+
candidates.push(candidate_path);
115+
}
116+
117+
candidates.sort();
118+
for candidate in candidates {
119+
println!("{}", candidate);
120+
}
121+
}
122+
123+
/// Suggest files and directories for completion
124+
fn suggest_files(current: &str) {
125+
let current_path = std::path::Path::new(current);
126+
127+
let (search_dir, prefix) = if current.is_empty() {
128+
(std::path::PathBuf::from("."), "")
129+
} else if current.ends_with('/') || current.ends_with(std::path::MAIN_SEPARATOR) {
130+
(current_path.to_path_buf(), "")
131+
} else {
132+
match current_path.parent() {
133+
Some(parent) if !parent.as_os_str().is_empty() => {
134+
let prefix = current_path
135+
.file_name()
136+
.and_then(|n| n.to_str())
137+
.unwrap_or("");
138+
(parent.to_path_buf(), prefix)
139+
}
140+
_ => (std::path::PathBuf::from("."), current),
141+
}
142+
};
143+
144+
let Ok(entries) = std::fs::read_dir(&search_dir) else {
145+
return;
146+
};
147+
148+
let mut candidates = Vec::new();
149+
150+
for entry in entries.flatten() {
151+
let Ok(file_type) = entry.file_type() else {
152+
continue;
153+
};
154+
155+
let file_name = entry.file_name();
156+
let Some(name) = file_name.to_str() else {
157+
continue;
158+
};
159+
160+
if name.starts_with('.') && !prefix.starts_with('.') {
161+
continue;
162+
}
163+
164+
if !name.starts_with(prefix) {
165+
continue;
166+
}
167+
168+
let candidate_path =
169+
if current.ends_with('/') || current.ends_with(std::path::MAIN_SEPARATOR) {
170+
if file_type.is_dir() {
171+
format!("{}{}/", current, name)
172+
} else {
173+
format!("{}{}", current, name)
174+
}
175+
} else if let Some(parent) = current_path.parent() {
176+
if parent.as_os_str().is_empty() || parent == std::path::Path::new(".") {
177+
if file_type.is_dir() {
178+
format!("{}/", name)
179+
} else {
180+
name.to_string()
181+
}
182+
} else if file_type.is_dir() {
183+
format!("{}/{}/", parent.display(), name)
184+
} else {
185+
format!("{}/{}", parent.display(), name)
186+
}
187+
} else if file_type.is_dir() {
188+
format!("{}/", name)
189+
} else {
190+
name.to_string()
191+
};
192+
193+
candidates.push(candidate_path);
194+
}
195+
196+
candidates.sort();
197+
for candidate in candidates {
198+
println!("{}", candidate);
199+
}
200+
}
201+
52202
/// Generate dynamic completions based on current line and cursor position
53203
pub fn generate_completions(
54204
line: &str,
@@ -83,6 +233,26 @@ pub fn generate_completions(
83233
suggest_ruby_versions(rubies_dir, current_word);
84234
return;
85235
}
236+
if prev == "-R" || prev == "--rubies-dir" {
237+
suggest_directories(current_word);
238+
return;
239+
}
240+
if prev == "-C" || prev == "--work-dir" {
241+
suggest_directories(current_word);
242+
return;
243+
}
244+
if prev == "-G" || prev == "--gem-home" {
245+
suggest_directories(current_word);
246+
return;
247+
}
248+
if prev == "-c" || prev == "--config" {
249+
suggest_files(current_word);
250+
return;
251+
}
252+
if prev == "-P" || prev == "--project" {
253+
suggest_files(current_word);
254+
return;
255+
}
86256
if prev == "shell-integration" {
87257
if "bash".starts_with(current_word) {
88258
println!("bash");

crates/rb-cli/src/config/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ pub struct RbConfig {
1919
long = "rubies-dir",
2020
global = true,
2121
help = "Designate the directory containing your Ruby installations",
22-
env = "RB_RUBIES_DIR"
22+
env = "RB_RUBIES_DIR",
23+
value_hint = clap::ValueHint::DirPath
2324
)]
2425
#[serde(rename = "rubies-dir", skip_serializing_if = "Option::is_none")]
2526
pub rubies_dir: Option<PathBuf>,
@@ -41,7 +42,8 @@ pub struct RbConfig {
4142
long = "gem-home",
4243
global = true,
4344
help = "Specify custom gem base directory for gem installations",
44-
env = "RB_GEM_HOME"
45+
env = "RB_GEM_HOME",
46+
value_hint = clap::ValueHint::DirPath
4547
)]
4648
#[serde(rename = "gem-home", skip_serializing_if = "Option::is_none")]
4749
pub gem_home: Option<PathBuf>,
@@ -64,7 +66,8 @@ pub struct RbConfig {
6466
long = "work-dir",
6567
global = true,
6668
help = "Run as if started in the specified directory",
67-
env = "RB_WORK_DIR"
69+
env = "RB_WORK_DIR",
70+
value_hint = clap::ValueHint::DirPath
6871
)]
6972
#[serde(rename = "work-dir", skip_serializing_if = "Option::is_none")]
7073
pub work_dir: Option<PathBuf>,

crates/rb-cli/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ pub struct Cli {
8989
long = "config",
9090
global = true,
9191
help = "Specify custom configuration file location",
92-
env = "RB_CONFIG"
92+
env = "RB_CONFIG",
93+
value_hint = clap::ValueHint::FilePath
9394
)]
9495
pub config_file: Option<std::path::PathBuf>,
9596

@@ -99,7 +100,8 @@ pub struct Cli {
99100
long = "project",
100101
global = true,
101102
help = "Specify custom rbproject.toml location (skips autodetection)",
102-
env = "RB_PROJECT"
103+
env = "RB_PROJECT",
104+
value_hint = clap::ValueHint::FilePath
103105
)]
104106
pub project_file: Option<std::path::PathBuf>,
105107

spec/behaviour/bash_completion_spec.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,6 @@ EOF
368368
echo "source 'https://rubygems.org'" > "$TEST_PROJECT_DIR/Gemfile"
369369

370370
# Create bundler binstubs directory with versioned ruby path using actual Ruby ABI
371-
local ruby_abi
372371
ruby_abi=$(get_ruby_abi_version "$LATEST_RUBY")
373372
BUNDLER_BIN="$TEST_PROJECT_DIR/.rb/vendor/bundler/ruby/$ruby_abi/bin"
374373
mkdir -p "$BUNDLER_BIN"

0 commit comments

Comments
 (0)