Skip to content

Commit 4cdcdf1

Browse files
authored
Merge pull request #144 from MCWertGaming/simple_prompt
added y/n prompt
2 parents d22ea37 + 8cda3fc commit 4cdcdf1

File tree

8 files changed

+302
-14
lines changed

8 files changed

+302
-14
lines changed

cpp-terminal/private/conversion.hpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
#include <cstdint>
44
#include <stdexcept>
55
#include <string>
6+
#include <vector>
7+
68
#ifdef _WIN32
79
#include <stdio.h>
810
#else
11+
912
#include <sys/ioctl.h>
13+
1014
#endif
1115

12-
namespace Term::Private {
1316
static constexpr uint8_t UTF8_ACCEPT = 0;
1417
static constexpr uint8_t UTF8_REJECT = 0xf;
1518

19+
namespace Term::Private {
20+
1621
inline uint8_t utf8_decode_step(uint8_t state, uint8_t octet, uint32_t* cpp) {
1722
static const uint32_t utf8_classtab[0x10] = {
1823
0x88888888UL, 0x88888888UL, 0x99999999UL, 0x99999999UL,
@@ -93,6 +98,7 @@ inline std::string utf32_to_utf8(const std::u32string& s) {
9398
}
9499
return r;
95100
}
101+
96102
// coverts a string into an integer
97103
inline int convert_string_to_int(const char* string,
98104
const char* format,
@@ -107,4 +113,13 @@ inline int convert_string_to_int(const char* string,
107113
#endif
108114
}
109115

116+
// converts a vector of char into a string
117+
inline std::string vector_to_string(const std::vector<char>& vector) {
118+
std::string string;
119+
for (char i : vector) {
120+
string.push_back(i);
121+
}
122+
return string;
123+
}
124+
110125
} // namespace Term::Private

cpp-terminal/prompt.cpp

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,110 @@
1+
#include <cpp-terminal/base.hpp>
12
#include <cpp-terminal/input.hpp>
23
#include <cpp-terminal/prompt.hpp>
34
#include <iostream>
45
#include "private/conversion.hpp"
56
#include "private/platform.hpp"
67

8+
Term::Result Term::prompt(const std::string& message,
9+
const std::string& first_option,
10+
const std::string& second_option,
11+
const std::string& prompt_indicator,
12+
bool immediate) {
13+
Terminal term(false, true, true);
14+
std::cout << message << " [" << first_option << '/' << second_option << ']'
15+
<< prompt_indicator << ' ' << std::flush;
16+
17+
if (!Term::is_stdin_a_tty()) {
18+
Term::write("\n");
19+
return Result::ERROR;
20+
}
21+
22+
int key;
23+
24+
if (immediate) {
25+
while (true) {
26+
key = Term::read_key();
27+
if (key == 'y' || key == 'Y') {
28+
Term::write("\n");
29+
return Result::YES;
30+
} else if (key == 'n' || key == 'N') {
31+
Term::write("\n");
32+
return Result::NO;
33+
} else if (key == Term::Key::CTRL + 'c' ||
34+
key == Term::Key::CTRL + 'd') {
35+
Term::write("\n");
36+
return Result::ABORT;
37+
} else if (key == Term::Key::ENTER) {
38+
Term::write("\n");
39+
return Result::NONE;
40+
} else {
41+
Term::write("\n");
42+
return Result::INVALID;
43+
}
44+
}
45+
} else {
46+
std::vector<char> input;
47+
unsigned short int length = 0;
48+
while (true) {
49+
key = Term::read_key();
50+
if (key >= 'a' && key <= 'z') {
51+
std::cout << (char)key << std::flush;
52+
length++;
53+
input.push_back(static_cast<char>(key));
54+
} else if (key >= 'A' && key <= 'Z') {
55+
std::cout << (char)key << std::flush;
56+
length++;
57+
input.push_back(static_cast<char>(
58+
key + 32)); // convert upper case to lowercase
59+
} else if (key == Term::Key::CTRL + 'c' ||
60+
key == Term::Key::CTRL + 'd') {
61+
std::cout << '\n';
62+
return Result::ABORT;
63+
} else if (key == Term::Key::BACKSPACE) {
64+
if (length != 0) {
65+
std::cout << "\033[D \033[D"
66+
<< std::flush; // erase last line and move the
67+
// cursor back
68+
length--;
69+
input.pop_back();
70+
}
71+
} else if (key == Term::Key::ENTER) {
72+
if (Private::vector_to_string(input) == "y" ||
73+
Private::vector_to_string(input) == "yes") {
74+
Term::write("\n");
75+
return Result::YES;
76+
} else if (Private::vector_to_string(input) == "n" ||
77+
Private::vector_to_string(input) == "no") {
78+
Term::write("\n");
79+
return Result::NO;
80+
} else if (length == 0) {
81+
Term::write("\n");
82+
return Result::NONE;
83+
} else {
84+
Term::write("\n");
85+
return Result::INVALID;
86+
}
87+
}
88+
}
89+
}
90+
}
91+
92+
Term::Result_simple Term::prompt_simple(const std::string& message) {
93+
switch (prompt(message, "y", "N", ":", false)) {
94+
case Result::YES:
95+
return Result_simple::YES;
96+
case Result::ABORT:
97+
return Result_simple::ABORT;
98+
case Result::NO: // falls through
99+
case Result::ERROR: // falls through
100+
case Result::NONE: // falls through
101+
case Result::INVALID:
102+
return Result_simple::NO;
103+
}
104+
// shouldn't be reached
105+
return Result_simple::NO;
106+
}
107+
7108
std::string Term::concat(const std::vector<std::string>& lines) {
8109
std::string s;
9110
for (auto& line : lines) {
@@ -72,10 +173,11 @@ void Term::render(Term::Window& scr, const Model& m, size_t cols) {
72173
scr.set_cursor_pos(m.prompt_string.size() + m.cursor_col, m.cursor_row);
73174
}
74175

75-
std::string Term::prompt(Terminal& term,
76-
const std::string& prompt_string,
77-
std::vector<std::string>& history,
78-
std::function<bool(std::string)>& iscomplete) {
176+
std::string Term::prompt_multiline(
177+
Terminal& term,
178+
const std::string& prompt_string,
179+
std::vector<std::string>& history,
180+
std::function<bool(std::string)>& iscomplete) {
79181
int row, col;
80182
bool term_attached = Private::is_stdin_a_tty();
81183
if (term_attached) {

cpp-terminal/prompt.hpp

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,56 @@
33
#include <cpp-terminal/window.hpp>
44
#include <functional>
55

6+
// TODO: remove windows.h include and this undefine
7+
#undef ERROR
8+
69
namespace Term {
10+
/* Basic prompt */
11+
12+
// indicates the results of prompt_blocking() and prompt_non_blocking
13+
enum class Result {
14+
// returned if the user chose yes
15+
YES,
16+
// returned if the user chose no
17+
NO,
18+
// returned of no terminal is attached to the program
19+
ERROR,
20+
// returned of the enter key was pressed without additional input
21+
NONE,
22+
// returned if CTRL+C was pressed
23+
ABORT,
24+
// returned if the given input did not match the case 'yes' of 'no'
25+
INVALID
26+
};
27+
// indicates the results of prompt_simple()
28+
enum class Result_simple {
29+
// returned if the user chose yes
30+
YES,
31+
// returned if the user chose no or invalid / no input or if no terminal is
32+
// attached
33+
NO,
34+
// returned if CTRL+C was pressed
35+
ABORT
36+
};
37+
38+
// A simple yes/no prompt, requires the user to press the ENTER key to continue
39+
// The arguments are used like this: 1 [2/3]4 <user Input>
40+
// the immediate switch indicates toggles wether pressing enter for
41+
// confirming the input is required or not
42+
Result prompt(const std::string& message,
43+
const std::string& first_option,
44+
const std::string& second_option,
45+
const std::string& prompt_indicator,
46+
bool);
47+
48+
// The most simple prompt possible, requires the user to press enter to continue
49+
// The arguments are used like this: 1 [y/N]:
50+
// Invalid input, errors (like no attached terminal) all result in 'no' as
51+
// default
52+
Result_simple prompt_simple(const std::string& message);
53+
54+
/* Multiline prompt */
55+
756
// This model contains all the information about the state of the prompt in an
857
// abstract way, irrespective of where or how it is rendered.
958
struct Model {
@@ -25,8 +74,8 @@ void print_left_curly_bracket(Term::Window&, int, int, int);
2574

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

28-
std::string prompt(Terminal&,
29-
const std::string&,
30-
std::vector<std::string>&,
31-
std::function<bool(std::string)>&);
77+
std::string prompt_multiline(Terminal&,
78+
const std::string&,
79+
std::vector<std::string>&,
80+
std::function<bool(std::string)>&);
3281
} // namespace Term

examples/CMakeLists.txt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1-
add_executable(prompt prompt.cpp)
2-
target_link_libraries(prompt cpp-terminal)
1+
add_executable(prompt_multiline prompt_multiline.cpp)
2+
target_link_libraries(prompt_multiline cpp-terminal)
3+
4+
add_executable(prompt_immediate prompt_immediate.cpp)
5+
target_link_libraries(prompt_immediate cpp-terminal)
6+
7+
add_executable(prompt_not_immediate prompt_not_immediate.cpp)
8+
target_link_libraries(prompt_not_immediate cpp-terminal)
9+
10+
add_executable(prompt_simple prompt_simple.cpp)
11+
target_link_libraries(prompt_simple cpp-terminal)
312

413
add_executable(kilo kilo.cpp)
514
target_link_libraries(kilo cpp-terminal)
@@ -20,7 +29,7 @@ add_executable(colors colors.cpp)
2029
target_link_libraries(colors cpp-terminal)
2130

2231
# enable warnings and set compile features
23-
foreach(target prompt kilo menu menu_window keys colors)
32+
foreach(target kilo menu menu_window keys colors)
2433
# Force Microsoft Visual Studio to decode sources files in UTF-8
2534
if (MSVC)
2635
target_compile_options(${target} PUBLIC "/utf-8")

examples/prompt_immediate.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <cpp-terminal/version.h>
2+
#include <cpp-terminal/prompt.hpp>
3+
#include <iostream>
4+
5+
int main() {
6+
std::cout << "Running cpp-terminal version: "
7+
<< CPP_TERMINAL_VERSION_COMPLETE << std::endl;
8+
try {
9+
std::cout << "CPP-Terminal basic prompt example: \n\n";
10+
switch (Term::prompt("Proceed?", "Y", "n", ":", true)) {
11+
case Term::Result::NONE: // no input was given
12+
std::cout << "No input given, proceeding anyway...\n";
13+
break;
14+
case Term::Result::INVALID:
15+
std::cout << "Invalid input given, proceeding anyway\n";
16+
break;
17+
case Term::Result::YES:
18+
std::cout << "Proceeding...\n";
19+
break;
20+
case Term::Result::NO:
21+
std::cout << "Stopping...\n";
22+
break;
23+
case Term::Result::ABORT:
24+
std::cout << "Exit signal received, exiting now...\n";
25+
break;
26+
case Term::Result::ERROR:
27+
std::cout << "Error while capturing input, is your terminal "
28+
"attached to a TTY?\n";
29+
std::cout << "Aborting...\n";
30+
break;
31+
}
32+
33+
} catch (const std::runtime_error& re) {
34+
std::cerr << "Runtime error: " << re.what() << std::endl;
35+
return 2;
36+
} catch (...) {
37+
std::cerr << "Unknown error." << std::endl;
38+
return 1;
39+
}
40+
return 0;
41+
}

examples/prompt.cpp renamed to examples/prompt_multiline.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include <vector>
77

88
using Term::Key;
9-
using Term::prompt;
9+
using Term::prompt_multiline;
1010
using Term::Terminal;
1111

1212
bool determine_completeness([[maybe_unused]] std::string command) {
@@ -38,7 +38,8 @@ int main() {
3838
std::vector<std::string> history;
3939
std::function<bool(std::string)> iscomplete = determine_completeness;
4040
while (true) {
41-
std::string answer = prompt(term, "> ", history, iscomplete);
41+
std::string answer =
42+
Term::prompt_multiline(term, "> ", history, iscomplete);
4243
if (answer.size() == 1 && answer[0] == Key::CTRL + 'd')
4344
break;
4445
std::cout << "Submitted text: " << answer << std::endl;

examples/prompt_not_immediate.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <cpp-terminal/version.h>
2+
#include <cpp-terminal/prompt.hpp>
3+
#include <iostream>
4+
5+
int main() {
6+
std::cout << "Running cpp-terminal version: "
7+
<< CPP_TERMINAL_VERSION_COMPLETE << std::endl;
8+
try {
9+
std::cout << "CPP-Terminal basic prompt example: \n\n";
10+
switch (Term::prompt("Proceed?", "Y", "n", ":", false)) {
11+
case Term::Result::NONE: // no input was given
12+
std::cout << "No input given, proceeding anyway...\n";
13+
break;
14+
case Term::Result::INVALID:
15+
std::cout << "Invalid input given, proceeding anyway\n";
16+
break;
17+
case Term::Result::YES:
18+
std::cout << "Proceeding...\n";
19+
break;
20+
case Term::Result::NO:
21+
std::cout << "Stopping...\n";
22+
break;
23+
case Term::Result::ABORT:
24+
std::cout << "Exit signal received, exiting now...\n";
25+
break;
26+
case Term::Result::ERROR:
27+
std::cout << "Error while capturing input, is your terminal "
28+
"attached to a TTY?\n";
29+
std::cout << "Aborting...\n";
30+
break;
31+
}
32+
33+
} catch (const std::runtime_error& re) {
34+
std::cerr << "Runtime error: " << re.what() << std::endl;
35+
return 2;
36+
} catch (...) {
37+
std::cerr << "Unknown error." << std::endl;
38+
return 1;
39+
}
40+
return 0;
41+
}

0 commit comments

Comments
 (0)