Skip to content

Commit 24dff03

Browse files
committed
Fixed various things with path completion
1 parent 63b961d commit 24dff03

File tree

4 files changed

+83
-26
lines changed

4 files changed

+83
-26
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[toolchain]
2-
channel = "1.57.0"
2+
channel = "1.58.0"
33
components = ["rustfmt", "rust-src"]

helix-term/src/commands.rs

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3890,9 +3890,6 @@ pub fn completion_path(cx: &mut Context) {
38903890
let cur_line = text.char_to_line(cursor);
38913891
let begin_line = text.line_to_char(cur_line);
38923892
let line_until_cursor = text.slice(begin_line..cursor).to_string();
3893-
// TODO find a good regex for most use cases (especially Windows, which is not yet covered...)
3894-
// currently only one path match per line is possible in unix
3895-
static PATH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"((?:\.{0,2}/)+.*)$").unwrap());
38963893

38973894
// TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply
38983895
// completion filtering. For example logger.te| should filter the initial suggestion list with "te".
@@ -3908,37 +3905,76 @@ pub fn completion_path(cx: &mut Context) {
39083905
let callback = async move {
39093906
let call: job::Callback =
39103907
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
3911-
if doc!(editor).mode() != Mode::Insert {
3908+
let doc = doc!(editor);
3909+
if doc.mode() != Mode::Insert {
39123910
// we're not in insert mode anymore
39133911
return;
39143912
}
3915-
// read dir for a possibly matched path
3916-
let items = PATH_REGEX
3913+
3914+
// TODO async path completion (for this probably the whole completion system has to be reworked to be async without producing race conditions)
3915+
let items = ui::PATH_REGEX
39173916
.find(&line_until_cursor)
3918-
.and_then(|m| {
3919-
let mut path = PathBuf::from(m.as_str());
3920-
if path.starts_with(".") {
3921-
path = std::env::current_dir().unwrap().join(path);
3917+
.and_then(|matched_path| {
3918+
let matched_path = matched_path.as_str();
3919+
let mut path = PathBuf::from(matched_path);
3920+
if path.is_relative() {
3921+
if let Some(doc_path) = doc.path().and_then(|dp| dp.parent()) {
3922+
path = doc_path.join(path);
3923+
}
39223924
}
3923-
std::fs::read_dir(path).ok().map(|rd| {
3924-
rd.filter_map(|re| re.ok())
3925-
.filter_map(|re| {
3926-
re.metadata().ok().map(|m| CompletionItem::Path {
3927-
path: re.path(),
3928-
permissions: m.permissions(),
3929-
path_type: if m.is_file() {
3925+
let ends_with_slash = match matched_path.chars().last() {
3926+
Some('/') => true, // TODO support Windows
3927+
None => return Some(vec![]),
3928+
_ => false,
3929+
};
3930+
// check if there are chars after the last slash, and if these chars represent a directory
3931+
let (dir_path, typed_file_name) = match std::fs::metadata(path.clone()).ok()
3932+
{
3933+
Some(m) if m.is_dir() && ends_with_slash => {
3934+
(Some(path.as_path()), None)
3935+
}
3936+
_ if !ends_with_slash => {
3937+
(path.parent(), path.file_name().and_then(|f| f.to_str()))
3938+
}
3939+
_ => return Some(vec![]),
3940+
};
3941+
// read dir for a possibly matched path
3942+
dir_path
3943+
.and_then(|path| std::fs::read_dir(path).ok())
3944+
.map(|read_dir| {
3945+
read_dir
3946+
.filter_map(|dir_entry| dir_entry.ok())
3947+
.filter_map(|dir_entry| {
3948+
let path = dir_entry.path();
3949+
// check if <chars> in <path>/<chars><cursor> matches the start of the filename
3950+
let filename_starts_with_prefix = match (
3951+
path.file_name().and_then(|f| f.to_str()),
3952+
typed_file_name,
3953+
) {
3954+
(Some(re_stem), Some(t)) => re_stem.starts_with(t),
3955+
_ => true,
3956+
};
3957+
if filename_starts_with_prefix {
3958+
dir_entry.metadata().ok().map(|md| (path, md))
3959+
} else {
3960+
None
3961+
}
3962+
})
3963+
.map(|(path, md)| CompletionItem::Path {
3964+
path,
3965+
permissions: md.permissions(),
3966+
path_type: if md.is_file() {
39303967
PathType::File
3931-
} else if m.is_dir() {
3968+
} else if md.is_dir() {
39323969
PathType::Dir
3933-
} else if m.is_symlink() {
3970+
} else if md.is_symlink() {
39343971
PathType::Symlink
39353972
} else {
39363973
PathType::Unknown
39373974
},
39383975
})
3939-
})
3940-
.collect::<Vec<_>>()
3941-
})
3976+
.collect::<Vec<_>>()
3977+
})
39423978
})
39433979
.unwrap_or_default();
39443980

helix-term/src/ui/completion.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::compositor::{Component, Context, Event, EventResult};
2+
use helix_core::regex::Regex;
23
use helix_view::editor::CompleteAction;
4+
use once_cell::sync::Lazy;
35
use tui::buffer::Buffer as Surface;
46
use tui::text::Spans;
57

@@ -19,6 +21,10 @@ use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
1921

2022
use helix_lsp::{lsp, util, OffsetEncoding};
2123

24+
// TODO find a good regex for most use cases (especially Windows, which is not yet covered...)
25+
// currently only one path match per line is possible in unix
26+
pub static PATH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"((?:\.{0,2}/)+.*)$").unwrap());
27+
2228
#[derive(Debug, Clone)]
2329
pub enum PathType {
2430
Dir,
@@ -120,7 +126,7 @@ impl menu::Item for CompletionItem {
120126
CompletionItem::Path { path_type, .. } => menu::Cell::from({
121127
// TODO probably check permissions/or (coloring maybe)
122128
match path_type {
123-
PathType::Dir => "dir",
129+
PathType::Dir => "folder",
124130
PathType::File => "file",
125131
PathType::Symlink => "symlink",
126132
PathType::Unknown => "unknown",
@@ -192,8 +198,23 @@ impl Completion {
192198
transaction
193199
}
194200
CompletionItem::Path { path, .. } => {
201+
let text = doc.text().slice(..);
202+
let cur_line = text.char_to_line(trigger_offset);
203+
let begin_line = text.line_to_char(cur_line);
195204
let path_head = path.file_name().unwrap().to_string_lossy();
196-
let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
205+
let line_until_trigger_offset =
206+
Cow::from(doc.text().slice(begin_line..trigger_offset));
207+
let mat = PATH_REGEX.find(&line_until_trigger_offset).unwrap();
208+
let path = PathBuf::from(mat.as_str());
209+
let mut prefix = path
210+
.file_name()
211+
.and_then(|f| f.to_str())
212+
.unwrap_or_default()
213+
.to_string();
214+
// TODO support Windows
215+
if path.to_str().map(|p| p.ends_with('/')).unwrap_or_default() {
216+
prefix += "/";
217+
}
197218
let text = path_head.trim_start_matches::<&str>(&prefix);
198219
Transaction::change(
199220
doc.text(),

helix-term/src/ui/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mod spinner;
1212
mod statusline;
1313
mod text;
1414

15-
pub use completion::{Completion, CompletionItem, PathType};
15+
pub use completion::{Completion, CompletionItem, PathType, PATH_REGEX};
1616
pub use editor::EditorView;
1717
pub use markdown::Markdown;
1818
pub use menu::Menu;

0 commit comments

Comments
 (0)