From 59b8b99577ce38bc2b3cad330c1108eeb4faa447 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sat, 12 Nov 2022 12:37:35 +0100 Subject: [PATCH] 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 `` and friends. For for example, :cd d/f 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 --- src/commands.cc | 6 ++++-- src/file.cc | 8 ++++++++ src/ranked_match.cc | 4 +++- src/ranked_match.hh | 3 ++- 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('\n')) + for (auto&& candidate : output | split('\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('\n')) + for (auto c : output | split('\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 subsequence_match_smart_case(StringView str, StringVi template 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 @@ -48,6 +48,7 @@ private: friend constexpr bool with_bit_ops(Meta::Type) { 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;