Skip to content

Commit

Permalink
Fixed various things with path completion
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp-M committed Oct 27, 2022
1 parent 3518fd9 commit 9193aa4
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 24 deletions.
78 changes: 57 additions & 21 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3965,9 +3965,6 @@ pub fn completion_path(cx: &mut Context) {
let cur_line = text.char_to_line(cursor);
let begin_line = text.line_to_char(cur_line);
let line_until_cursor = text.slice(begin_line..cursor).to_string();
// TODO find a good regex for most use cases (especially Windows, which is not yet covered...)
// currently only one path match per line is possible in unix
static PATH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"((?:\.{0,2}/)+.*)$").unwrap());

// TODO: trigger_offset should be the cursor offset but we also need a starting offset from where we want to apply
// completion filtering. For example logger.te| should filter the initial suggestion list with "te".
Expand All @@ -3987,33 +3984,72 @@ pub fn completion_path(cx: &mut Context) {
// we're not in insert mode anymore
return;
}
// read dir for a possibly matched path
let items = PATH_REGEX
let doc = doc!(editor);

// TODO async path completion (for this probably the whole completion system has to be reworked to be async without producing race conditions)
let items = ui::PATH_REGEX
.find(&line_until_cursor)
.and_then(|m| {
let mut path = PathBuf::from(m.as_str());
if path.starts_with(".") {
path = std::env::current_dir().unwrap().join(path);
.and_then(|matched_path| {
let matched_path = matched_path.as_str();
let mut path = PathBuf::from(matched_path);
if path.is_relative() {
if let Some(doc_path) = doc.path().and_then(|dp| dp.parent()) {
path = doc_path.join(path);
}
}
std::fs::read_dir(path).ok().map(|rd| {
rd.filter_map(|re| re.ok())
.filter_map(|re| {
re.metadata().ok().map(|m| CompletionItem::Path {
path: re.path(),
permissions: m.permissions(),
path_type: if m.is_file() {
let ends_with_slash = match matched_path.chars().last() {
Some('/') => true, // TODO support Windows
None => return Some(vec![]),
_ => false,
};
// check if there are chars after the last slash, and if these chars represent a directory
let (dir_path, typed_file_name) = match std::fs::metadata(path.clone()).ok()
{
Some(m) if m.is_dir() && ends_with_slash => {
(Some(path.as_path()), None)
}
_ if !ends_with_slash => {
(path.parent(), path.file_name().and_then(|f| f.to_str()))
}
_ => return Some(vec![]),
};
// read dir for a possibly matched path
dir_path
.and_then(|path| std::fs::read_dir(path).ok())
.map(|read_dir| {
read_dir
.filter_map(|dir_entry| dir_entry.ok())
.filter_map(|dir_entry| {
let path = dir_entry.path();
// check if <chars> in <path>/<chars><cursor> matches the start of the filename
let filename_starts_with_prefix = match (
path.file_name().and_then(|f| f.to_str()),
typed_file_name,
) {
(Some(re_stem), Some(t)) => re_stem.starts_with(t),
_ => true,
};
if filename_starts_with_prefix {
dir_entry.metadata().ok().map(|md| (path, md))
} else {
None
}
})
.map(|(path, md)| CompletionItem::Path {
path,
permissions: md.permissions(),
path_type: if md.is_file() {
PathType::File
} else if m.is_dir() {
} else if md.is_dir() {
PathType::Dir
} else if m.is_symlink() {
} else if md.is_symlink() {
PathType::Symlink
} else {
PathType::Unknown
},
})
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
})
})
.unwrap_or_default();

Expand Down
25 changes: 23 additions & 2 deletions helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::compositor::{Component, Context, Event, EventResult};
use helix_core::regex::Regex;
use helix_view::{apply_transaction, editor::CompleteAction};
use once_cell::sync::Lazy;
use tui::buffer::Buffer as Surface;
use tui::text::Spans;

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

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

// TODO find a good regex for most use cases (especially Windows, which is not yet covered...)
// currently only one path match per line is possible in unix
pub static PATH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"((?:\.{0,2}/)+.*)$").unwrap());

#[derive(Debug, Clone)]
pub enum PathType {
Dir,
Expand Down Expand Up @@ -113,7 +119,7 @@ impl menu::Item for CompletionItem {
CompletionItem::Path { path_type, .. } => menu::Cell::from({
// TODO probably check permissions/or (coloring maybe)
match path_type {
PathType::Dir => "dir",
PathType::Dir => "folder",
PathType::File => "file",
PathType::Symlink => "symlink",
PathType::Unknown => "unknown",
Expand Down Expand Up @@ -192,8 +198,23 @@ impl Completion {
transaction
}
CompletionItem::Path { path, .. } => {
let text = doc.text().slice(..);
let cur_line = text.char_to_line(trigger_offset);
let begin_line = text.line_to_char(cur_line);
let path_head = path.file_name().unwrap().to_string_lossy();
let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
let line_until_trigger_offset =
Cow::from(doc.text().slice(begin_line..trigger_offset));
let mat = PATH_REGEX.find(&line_until_trigger_offset).unwrap();
let path = PathBuf::from(mat.as_str());
let mut prefix = path
.file_name()
.and_then(|f| f.to_str())
.unwrap_or_default()
.to_string();
// TODO support Windows
if path.to_str().map(|p| p.ends_with('/')).unwrap_or_default() {
prefix += "/";
}
let text = path_head.trim_start_matches::<&str>(&prefix);
Transaction::change(
doc.text(),
Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod text;

use crate::compositor::{Component, Compositor};
use crate::job::{self, Callback};
pub use completion::{Completion, CompletionItem, PathType};
pub use completion::{Completion, CompletionItem, PathType, PATH_REGEX};
pub use editor::EditorView;
pub use markdown::Markdown;
pub use menu::Menu;
Expand Down

0 comments on commit 9193aa4

Please sign in to comment.