summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohannes Altmanninger <aclopte@gmail.com>2022-11-12 12:37:35 +0100
committerJohannes Altmanninger <aclopte@gmail.com>2022-11-19 15:20:31 +0100
commit59b8b99577ce38bc2b3cad330c1108eeb4faa447 (patch)
tree090fc69c9bdae6823a3049379e6dc315d0927375 /src
parent91d45a100a39345f06d9789ded9172fe60887c27 (diff)
Accept "cd dir/" again instead of using a subdirectory
Commit 69053d962 (Use menu behavior when completing change-directory, 2022-07-19) made ":cd dir/" actually run ":cd dir/first-subdir", which can be surprising. This is usually irrelevant because you rarely type the trailing slash. However it does happen after correcting an error with `<backspace>` and friends. For for example, :cd d<tab>/f<backspace> results in :cd dir/ We should probably fix user expectations here. Do this by adding "dir/" as valid completion. This requires us to allow empty candidates in "RankedMatch" but there's no harm in that. This means we need to filter out empty completions from shell-script-candidates elsewhere. Alternative fix: we could revert 69053d962. This would remove the convenient menu behavior but that wouldn't be a huge deal. Fixes #4775
Diffstat (limited to 'src')
-rw-r--r--src/commands.cc6
-rw-r--r--src/file.cc8
-rw-r--r--src/ranked_match.cc4
-rw-r--r--src/ranked_match.hh3
4 files changed, 17 insertions, 4 deletions
diff --git a/src/commands.cc b/src/commands.cc
index 123736b0..bb067699 100644
--- a/src/commands.cc
+++ b/src/commands.cc
@@ -241,7 +241,8 @@ struct ShellScriptCompleter
ShellManager::Flags::WaitForStdout,
shell_context).first;
CandidateList candidates;
- for (auto&& candidate : output | split<StringView>('\n'))
+ for (auto&& candidate : output | split<StringView>('\n')
+ | filter([](auto s) { return not s.empty(); }))
candidates.push_back(candidate.str());
return {0_byte, pos_in_token, std::move(candidates), m_flags};
@@ -274,7 +275,8 @@ struct ShellCandidatesCompleter
ShellManager::Flags::WaitForStdout,
shell_context).first;
m_candidates.clear();
- for (auto c : output | split<StringView>('\n'))
+ for (auto c : output | split<StringView>('\n')
+ | filter([](auto s) { return not s.empty(); }))
m_candidates.emplace_back(c.str(), used_letters(c));
m_token = token_to_complete;
}
diff --git a/src/file.cc b/src/file.cc
index ee358be0..c7092209 100644
--- a/src/file.cc
+++ b/src/file.cc
@@ -518,6 +518,14 @@ CandidateList complete_filename(StringView prefix, const Regex& ignored_regex,
if (RankedMatch match{file, fileprefix})
matches.push_back(match);
}
+ // Hack: when completing directories, also echo back the query if it
+ // is a valid directory. This enables menu completion to select the
+ // directory instead of a child.
+ if (only_dirs and not dirname.empty() and dirname.back() == '/' and fileprefix.empty()
+ and /* exists on disk */ not files.empty())
+ {
+ matches.push_back(RankedMatch{fileprefix, fileprefix});
+ }
std::sort(matches.begin(), matches.end());
const bool expand = (flags & FilenameFlags::Expand);
return candidates(matches, expand ? parsed_dirname : dirname);
diff --git a/src/ranked_match.cc b/src/ranked_match.cc
index 2a44d831..873a0613 100644
--- a/src/ranked_match.cc
+++ b/src/ranked_match.cc
@@ -112,12 +112,13 @@ static Optional<SubseqRes> subsequence_match_smart_case(StringView str, StringVi
template<typename TestFunc>
RankedMatch::RankedMatch(StringView candidate, StringView query, TestFunc func)
{
- if (candidate.empty() or query.length() > candidate.length())
+ if (query.length() > candidate.length())
return;
if (query.empty())
{
m_candidate = candidate;
+ m_matches = true;
return;
}
@@ -129,6 +130,7 @@ RankedMatch::RankedMatch(StringView candidate, StringView query, TestFunc func)
return;
m_candidate = candidate;
+ m_matches = true;
m_max_index = res->max_index;
if (res->single_word)
diff --git a/src/ranked_match.hh b/src/ranked_match.hh
index 379b855d..ec7fe626 100644
--- a/src/ranked_match.hh
+++ b/src/ranked_match.hh
@@ -27,7 +27,7 @@ struct RankedMatch
bool operator<(const RankedMatch& other) const;
bool operator==(const RankedMatch& other) const { return m_candidate == other.m_candidate; }
- explicit operator bool() const { return not m_candidate.empty(); }
+ explicit operator bool() const { return m_matches; }
private:
template<typename TestFunc>
@@ -48,6 +48,7 @@ private:
friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }
StringView m_candidate{};
+ bool m_matches = false;
Flags m_flags = Flags::None;
int m_word_boundary_match_count = 0;
int m_max_index = 0;