Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add tui-select #47

Merged
merged 3 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/cpp-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
-readability-magic-numbers,\
-readability-uppercase-literal-suffix,\
-readability-function-cognitive-complexity,\
-readability-identifier-length,\
-bugprone-easily-swappable-parameters,\
-cppcoreguidelines-avoid-magic-numbers,\
-cppcoreguidelines-macro-usage,\
-cppcoreguidelines-pro-bounds-pointer-arithmetic,\
Expand Down
9 changes: 8 additions & 1 deletion scripts/choose.bash
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ch_hist() {
IFS= read -r -d ''
cat
} | grep -zi -- "$*" |
choose -r "\x00" -uet --delimit-not-at-end --flip -p "Select a line to edit then run.")
choose -r "\x00" -ue --delimit-not-at-end --flip -p "Select a line to edit then run.")

if [ -z "$LINE" ]; then
echo "empty line"
Expand All @@ -53,3 +53,10 @@ ch_hist() {
# run on current shell
source -- "$TEMPFILE"
}

ch_branch() {
local branch="$(git branch | grep -i -- "$*" | choose -re --tui-select '^\*' --sub '^[ *] ' '' --sort-reverse --delimit-not-at-end -p 'swap branch')"
if [ -n "$branch" ]; then
git checkout "$branch"
fi
}
15 changes: 12 additions & 3 deletions src/args.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ void print_help_message() {
#ifndef PCRE2_SUBSTITUTE_LITERAL
" WARNING PCRE2 version old: replacement is never literal\n"
#endif
" --tui-select <target>\n"
" place the tui cursor at the last matched token. inherits the\n"
" same match options as the positional argument. has a higher\n"
" priority than --end. implies --tui\n"
"options:\n"
" --auto-completion-strings\n"
" -b, --batch-delimiter <delimiter, default: <output-delimiter>>\n"
Expand Down Expand Up @@ -318,7 +322,7 @@ void print_help_message() {
" --delimit-on-empty\n"
" even if the output would be empty, place a batch delimiter\n"
" -e, --end\n"
" begin cursor and prompt at the bottom of the tui\n"
" begin cursor and prompt at the bottom of the tui. implies --tui\n"
" --flush\n"
" makes the input unbuffered, and the output is flushed after each\n"
" token is written. this is useful for long running inputs with -u\n"
Expand Down Expand Up @@ -517,6 +521,7 @@ Arguments handle_args(int argc, char* const* argv, FILE* input = NULL, FILE* out
{"prompt", required_argument, NULL, 'p'},
{"sub", required_argument, NULL, 0},
{"substitute", required_argument, NULL, 0},
{"tui-select", required_argument, NULL, 0},
{"filter", required_argument, NULL, 'f'},
{"field", required_argument, NULL, 0},
{"remove", required_argument, NULL, 0},
Expand Down Expand Up @@ -582,13 +587,13 @@ Arguments handle_args(int argc, char* const* argv, FILE* input = NULL, FILE* out
case '?':
arg_has_errors = true;
#ifndef CHOOSE_FUZZING_APPLIED
printf("Unknown option: %c\n", optopt);
printf("Unknown option: %s\n", argv[optind - 1]);
#endif
break;
case ':':
arg_has_errors = true;
#ifndef CHOOSE_FUZZING_APPLIED
printf("Mising arg for: %c\n", optopt);
printf("Missing arg for: %s\n", argv[optind - 1]);
#endif
break;
default:
Expand Down Expand Up @@ -721,6 +726,9 @@ Arguments handle_args(int argc, char* const* argv, FILE* input = NULL, FILE* out
++optind;
uncompiled_output.ordered_ops.push_back(uncompiled::UncompiledSubOp{argv[optind - 2], argv[optind - 1]});
}
} else if (strcmp("tui-select", name) == 0) {
uncompiled_output.ordered_ops.push_back(uncompiled::UncompiledTuiSelectOp{optarg});
ret.tui = true;
} else if (strcmp("load-factor", name) == 0) {
char* end_ptr; // NOLINT
ret.unique_load_factor = strtof(optarg, &end_ptr);
Expand Down Expand Up @@ -849,6 +857,7 @@ Arguments handle_args(int argc, char* const* argv, FILE* input = NULL, FILE* out
break;
case 'e':
ret.end = true;
ret.tui = true;
break;
case 'g':
ret.sort_type = general_numeric;
Expand Down
34 changes: 23 additions & 11 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,9 +449,9 @@ struct UIState {
int main(int argc, char* const* argv) {
choose::Arguments args = choose::handle_args(argc, argv);
setlocale(LC_ALL, args.locale);
std::vector<choose::Token> tokens;
choose::CreateTokensResult tokens_result;
try {
tokens = choose::create_tokens(args);
tokens_result = choose::create_tokens(args);
} catch (const choose::termination_request&) {
return EXIT_SUCCESS;
}
Expand All @@ -470,8 +470,8 @@ int main(int argc, char* const* argv) {
choose::nc::screen screen;

UIState state{
std::move(args), //
std::move(tokens), //
std::move(args), //
std::move(tokens_result.tokens), //
BatchOutputStream(state.args),
};

Expand All @@ -483,18 +483,30 @@ int main(int argc, char* const* argv) {
choose::nc::noecho();
curs_set(0); // invisible cursor

// I don't handle ERR for anything color or attribute related since
// the application still works, even on failure (just without color)
// I also don't check ERR for ncurses printing, since if that stuff
// is not working, it will be very apparent to the user
// ERR isn't handled for anything color or attribute related since the
// application still works, even on failure (just without color) similar
// thinking for ncurses printing, in that case it will be very apparent to
// the user
start_color();
use_default_colors();
init_pair(UIState::PAIR_SELECTED, COLOR_GREEN, -1);

state.scroll_position = 0;
state.selection_position = state.args.end ? (int)state.tokens.size() - 1 : 0;
if (tokens_result.initial_selected_token.has_value()) {
// best to do this association at the end, as the indices are moved
// around by sorting and uniqueness
for (int i = 0; i < (int)state.tokens.size(); ++i) {
if (std::equal(state.tokens[i].buffer.cbegin(), state.tokens[i].buffer.cend(), //
tokens_result.initial_selected_token->buffer.cbegin(), tokens_result.initial_selected_token->buffer.cend())) {
state.selection_position = i;
break;
}
}
} else {
// --tui-select has a higher priority than --end
state.selection_position = state.args.end ? (int)state.tokens.size() - 1 : 0;
}
state.tenacious_single_select_indicator = 0;

state.loop();
} catch (...) {
// a note on ncurses:
Expand All @@ -507,5 +519,5 @@ int main(int argc, char* const* argv) {
}
return EXIT_FAILURE;
}
return sigint_occurred ? 128 + 2 : EXIT_SUCCESS;
return sigint_occurred ? 128 + SIGINT : EXIT_SUCCESS;
}
27 changes: 25 additions & 2 deletions src/ordered_op.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@

namespace choose {

struct TuiSelectOp {
regex::code target;
regex::match_data match_data;

TuiSelectOp(regex::code&& target) : target(std::move(target)), match_data(regex::create_match_data(this->target)) {}

bool matches(const char* begin, const char* end) const {
int rc = regex::match(this->target, begin, end - begin, this->match_data, "tui selection target");
return rc > 0;
}
};

struct RmOrFilterOp {
enum Type { REMOVE, FILTER };
Type type;
Expand Down Expand Up @@ -183,10 +195,14 @@ struct IndexOp {
}
};

using OrderedOp = std::variant<RmOrFilterOp, SubOp, ReplaceOp, InLimitOp, IndexOp>;
using OrderedOp = std::variant<RmOrFilterOp, SubOp, ReplaceOp, InLimitOp, IndexOp, TuiSelectOp>;

namespace uncompiled {

struct UncompiledTuiSelectOp {
const char* target;
};

struct UncompiledRmOrFilterOp {
RmOrFilterOp::Type type;
const char* arg;
Expand All @@ -204,7 +220,12 @@ using UncompiledIndexOp = IndexOp;
// uncompiled ops are exclusively used in the args. They hold information as all the
// args are parsed. once the args are fully known, they are converted to
// there compiled counterparts.
using UncompiledOrderedOp = std::variant<UncompiledRmOrFilterOp, UncompiledSubOp, UncompiledReplaceOp, UncompiledInLimitOp, UncompiledIndexOp>;
using UncompiledOrderedOp = std::variant<UncompiledRmOrFilterOp, //
UncompiledSubOp,
UncompiledReplaceOp,
UncompiledInLimitOp,
UncompiledIndexOp,
UncompiledTuiSelectOp>;

OrderedOp compile(UncompiledOrderedOp op, uint32_t options) {
if (UncompiledRmOrFilterOp* rf_op = std::get_if<UncompiledRmOrFilterOp>(&op)) {
Expand All @@ -216,6 +237,8 @@ OrderedOp compile(UncompiledOrderedOp op, uint32_t options) {
return *o;
} else if (UncompiledInLimitOp* o = std::get_if<UncompiledInLimitOp>(&op)) {
return *o;
} else if (UncompiledTuiSelectOp* o = std::get_if<UncompiledTuiSelectOp>(&op)) {
return TuiSelectOp(regex::compile(o->target, options, "tui select"));
} else {
return std::get<UncompiledIndexOp>(op);
}
Expand Down
2 changes: 1 addition & 1 deletion src/regex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ template <typename T>
bool get_match_and_groups(const char* subject, int rc, const match_data& match_data, T handler, const char* identification) {
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data.get());
for (int i = 0; i < rc; ++i) {
auto m = Match{subject + ovector[2 * i], subject + ovector[2 * i + 1]};
auto m = Match{subject + ovector[2 * i], subject + ovector[2 * i + 1]}; // NOLINT
m.ensure_sane(identification);
if (handler(m)) {
return true;
Expand Down
Loading
Loading