Skip to content

Commit

Permalink
add tui-select (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
jagprog5 committed Jul 2, 2024
1 parent 0a1009d commit 7ef96f7
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 87 deletions.
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

0 comments on commit 7ef96f7

Please sign in to comment.