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

added y/n prompt #144

Merged
merged 14 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
17 changes: 16 additions & 1 deletion cpp-terminal/private/conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>

#ifdef _WIN32
#include <stdio.h>
#else

#include <sys/ioctl.h>

#endif

namespace Term::Private {
static constexpr uint8_t UTF8_ACCEPT = 0;
static constexpr uint8_t UTF8_REJECT = 0xf;

namespace Term::Private {

inline uint8_t utf8_decode_step(uint8_t state, uint8_t octet, uint32_t* cpp) {
static const uint32_t utf8_classtab[0x10] = {
0x88888888UL, 0x88888888UL, 0x99999999UL, 0x99999999UL,
Expand Down Expand Up @@ -93,6 +98,7 @@ inline std::string utf32_to_utf8(const std::u32string& s) {
}
return r;
}

// coverts a string into an integer
inline int convert_string_to_int(const char* string,
const char* format,
Expand All @@ -107,4 +113,13 @@ inline int convert_string_to_int(const char* string,
#endif
}

// converts a vector of char into a string
inline std::string vector_to_string(const std::vector<char>& vector) {
std::string string;
for (char i : vector) {
string.push_back(i);
}
return string;
}

} // namespace Term::Private
121 changes: 117 additions & 4 deletions cpp-terminal/prompt.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,121 @@
#include <cpp-terminal/input.hpp>
#include <cpp-terminal/prompt.hpp>
#include <cpp-terminal/base.hpp>
#include <iostream>
#include "private/conversion.hpp"
#include "private/platform.hpp"

Term::Result Term::prompt_blocking(std::string message,
std::string first_option,
std::string second_option,
std::string prompt_indicator) {
Terminal term(false, true, true);
std::cout << message << " [" << first_option << '/' << second_option << ']'
<< prompt_indicator << ' ' << std::flush;

if (!Term::is_stdin_a_tty()) {
Term::write("\n");
return Result::ERROR;
}

std::vector<char> input;
unsigned short int length = 0;
int key;
while (true) {
key = Term::read_key();
if (key >= 'a' && key <= 'z') {
std::cout << (char)key << std::flush;
length++;
input.push_back(static_cast<char>(key));
} else if (key >= 'A' && key <= 'Z') {
std::cout << (char)key << std::flush;
length++;
input.push_back(static_cast<char>(
key + 32)); // convert upper case to lowercase
} else if (key == Term::Key::CTRL + 'c') {
std::cout << '\n';
return Result::ABORT;
} else if (key == Term::Key::BACKSPACE) {
if (length != 0) {
std::cout
<< "\033[D \033[D"
<< std::flush; // erase last line and move the cursor back
length--;
input.pop_back();
}
} else if (key == Term::Key::ENTER) {
if (Private::vector_to_string(input) == "y" ||
Private::vector_to_string(input) == "yes") {
Term::write("\n");
return Result::YES;
} else if (Private::vector_to_string(input) == "n" ||
Private::vector_to_string(input) == "no") {
Term::write("\n");
return Result::NO;
} else if (length == 0) {
Term::write("\n");
return Result::NONE;
} else {
Term::write("\n");
return Result::INVALID;
}
}
}
// should be unreachable
return Result::ERROR;
}

Term::Result Term::prompt_non_blocking(std::string message,
std::string first_option,
std::string second_option,
std::string prompt_indicator) {
Terminal term(false, true, true);
std::cout << message << " [" << first_option << '/' << second_option << ']'
<< prompt_indicator << ' ' << std::flush;

if (!Term::is_stdin_a_tty()) {
Term::write("\n");
return Result::ERROR;
}

int key;
while (true) {
key = Term::read_key();
if (key == 'y' || key == 'Y') {
Term::write("\n");
return Result::YES;
} else if (key == 'n' || key == 'N') {
Term::write("\n");
return Result::NO;
} else if (key == Term::Key::CTRL + 'c') {
Term::write("\n");
return Result::ABORT;
} else if (key == Term::Key::ENTER) {
Term::write("\n");
return Result::NONE;
} else {
Term::write("\n");
return Result::INVALID;
}
}
}

Term::Result_simple Term::prompt_simple(std::string message) {
switch (prompt_blocking(message, "Y", "N", ":")) {
case Result::YES:
return Result_simple::YES;
case Result::ABORT:
return Result_simple::ABORT;
case Result::NO: // falls through
case Result::ERROR: // falls through
case Result::NONE: // falls through
case Result::INVALID:
return Result_simple::NO;
}
// shouldn't be reached
return Result_simple::NO;
}

std::string Term::concat(const std::vector<std::string>& lines) {
std::string s;
for (auto& line : lines) {
Expand Down Expand Up @@ -72,10 +184,11 @@ void Term::render(Term::Window& scr, const Model& m, size_t cols) {
scr.set_cursor_pos(m.prompt_string.size() + m.cursor_col, m.cursor_row);
}

std::string Term::prompt(Terminal& term,
const std::string& prompt_string,
std::vector<std::string>& history,
std::function<bool(std::string)>& iscomplete) {
std::string Term::prompt_multiline(
Terminal& term,
const std::string& prompt_string,
std::vector<std::string>& history,
std::function<bool(std::string)>& iscomplete) {
int row, col;
bool term_attached = Private::is_stdin_a_tty();
if (term_attached) {
Expand Down
44 changes: 40 additions & 4 deletions cpp-terminal/prompt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,42 @@
#include <functional>

namespace Term {
/* Basic prompt */

// indicates the results of prompt_blocking() and prompt_non_blocking
enum class Result {
YES,
NO,
ERROR,
NONE,
ABORT,
INVALID,
};
// indicates the results of prompt_simple()
enum class Result_simple { YES, NO, ABORT };

// A simple yes/no prompt, requires the user to press the ENTER key to continue
// The arguments are used like this: 1 [2/3]4 <user Input>
Result prompt_blocking(std::string message,
std::string first_option,
std::string second_option,
std::string prompt_indicator);
MCWertGaming marked this conversation as resolved.
Show resolved Hide resolved

// A simple yes/no prompt, returns immediately after the first key press
// The arguments are used like this: 1 [2/3]4 <user Input>
Result prompt_non_blocking(std::string message,
std::string first_option,
std::string second_option,
std::string prompt_indicator);

// The most simple prompt possible, requires the user to press enter to continue
// The arguments are used like this: 1 [y/N]:
// Invalid input, errors (like no attached terminal) all result in 'no' as
// default
Result_simple prompt_simple(std::string message);

/* Multiline prompt */

// This model contains all the information about the state of the prompt in an
// abstract way, irrespective of where or how it is rendered.
struct Model {
Expand All @@ -25,8 +61,8 @@ void print_left_curly_bracket(Term::Window&, int, int, int);

void render(Term::Window&, const Model&, size_t);

std::string prompt(Terminal&,
const std::string&,
std::vector<std::string>&,
std::function<bool(std::string)>&);
std::string prompt_multiline(Terminal&,
const std::string&,
std::vector<std::string>&,
std::function<bool(std::string)>&);
} // namespace Term
15 changes: 12 additions & 3 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
add_executable(prompt prompt.cpp)
target_link_libraries(prompt cpp-terminal)
add_executable(prompt_multiline prompt_multiline.cpp)
target_link_libraries(prompt_multiline cpp-terminal)

add_executable(prompt_blocking prompt_blocking.cpp)
target_link_libraries(prompt_blocking cpp-terminal)

add_executable(prompt_non_blocking prompt_non_blocking.cpp)
target_link_libraries(prompt_non_blocking cpp-terminal)

add_executable(prompt_simple prompt_simple.cpp)
target_link_libraries(prompt_simple cpp-terminal)

add_executable(kilo kilo.cpp)
target_link_libraries(kilo cpp-terminal)
Expand All @@ -20,7 +29,7 @@ add_executable(colors colors.cpp)
target_link_libraries(colors cpp-terminal)

# enable warnings and set compile features
foreach(target prompt kilo menu menu_window keys colors)
foreach(target kilo menu menu_window keys colors)
# Force Microsoft Visual Studio to decode sources files in UTF-8
if (MSVC)
target_compile_options(${target} PUBLIC "/utf-8")
Expand Down
41 changes: 41 additions & 0 deletions examples/prompt_blocking.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <cpp-terminal/version.h>
#include <cpp-terminal/prompt.hpp>
#include <iostream>

int main() {
std::cout << "Running cpp-terminal version: "
<< CPP_TERMINAL_VERSION_COMPLETE << std::endl;
try {
std::cout << "CPP-Terminal basic prompt example: \n\n";
switch (Term::prompt_blocking("Proceed?", "Y", "n", ":")) {
case Term::Result::NONE: // no input was given
std::cout << "No input given, proceeding anyway...\n";
break;
case Term::Result::INVALID:
std::cout << "Invalid input given, proceeding anyway\n";
break;
case Term::Result::YES:
std::cout << "Proceeding...\n";
break;
case Term::Result::NO:
std::cout << "Stopping...\n";
break;
case Term::Result::ABORT:
std::cout << "Exit signal received, exiting now...\n";
break;
case Term::Result::ERROR:
std::cout << "Error while capturing input, is your terminal "
"attached to a TTY?\n";
std::cout << "Aborting...\n";
break;
}

} catch (const std::runtime_error& re) {
std::cerr << "Runtime error: " << re.what() << std::endl;
return 2;
} catch (...) {
std::cerr << "Unknown error." << std::endl;
return 1;
}
return 0;
}
5 changes: 3 additions & 2 deletions examples/prompt.cpp → examples/prompt_multiline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <vector>

using Term::Key;
using Term::prompt;
using Term::prompt_multiline;
using Term::Terminal;

bool determine_completeness([[maybe_unused]] std::string command) {
Expand Down Expand Up @@ -38,7 +38,8 @@ int main() {
std::vector<std::string> history;
std::function<bool(std::string)> iscomplete = determine_completeness;
while (true) {
std::string answer = prompt(term, "> ", history, iscomplete);
std::string answer =
Term::prompt_multiline(term, "> ", history, iscomplete);
if (answer.size() == 1 && answer[0] == Key::CTRL + 'd')
break;
std::cout << "Submitted text: " << answer << std::endl;
Expand Down
41 changes: 41 additions & 0 deletions examples/prompt_non_blocking.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <cpp-terminal/version.h>
#include <cpp-terminal/prompt.hpp>
#include <iostream>

int main() {
std::cout << "Running cpp-terminal version: "
<< CPP_TERMINAL_VERSION_COMPLETE << std::endl;
try {
std::cout << "CPP-Terminal basic prompt example: \n\n";
switch (Term::prompt_non_blocking("Proceed?", "Y", "n", ":")) {
MCWertGaming marked this conversation as resolved.
Show resolved Hide resolved
case Term::Result::NONE: // no input was given
std::cout << "No input given, proceeding anyway...\n";
break;
case Term::Result::INVALID:
std::cout << "Invalid input given, proceeding anyway\n";
break;
case Term::Result::YES:
std::cout << "Proceeding...\n";
break;
case Term::Result::NO:
std::cout << "Stopping...\n";
break;
case Term::Result::ABORT:
std::cout << "Exit signal received, exiting now...\n";
break;
case Term::Result::ERROR:
std::cout << "Error while capturing input, is your terminal "
"attached to a TTY?\n";
std::cout << "Aborting...\n";
break;
}

} catch (const std::runtime_error& re) {
std::cerr << "Runtime error: " << re.what() << std::endl;
return 2;
} catch (...) {
std::cerr << "Unknown error." << std::endl;
return 1;
}
return 0;
}
30 changes: 30 additions & 0 deletions examples/prompt_simple.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <cpp-terminal/version.h>
#include <cpp-terminal/prompt.hpp>
#include <iostream>

int main() {
std::cout << "Running cpp-terminal version: "
<< CPP_TERMINAL_VERSION_COMPLETE << std::endl;
try {
std::cout << "CPP-Terminal basic prompt example: \n\n";
switch (Term::prompt_simple("Proceed?")) {
case Term::Result_simple::YES:
MCWertGaming marked this conversation as resolved.
Show resolved Hide resolved
std::cout << "Proceeding...\n";
break;
case Term::Result_simple::NO:
std::cout << "Stopping...\n";
break;
case Term::Result_simple::ABORT:
std::cout << "Exit signal received, exiting now...\n";
break;
}

} catch (const std::runtime_error& re) {
std::cerr << "Runtime error: " << re.what() << std::endl;
return 2;
} catch (...) {
std::cerr << "Unknown error." << std::endl;
return 1;
}
return 0;
}