Skip to content

Commit 4c9e56e

Browse files
author
Anders Bjørn Nedergaard
authored
Yash arrow key control (#9)
* Yash arrow control Support for home, end, delete, left/right arrow and ctrl+left/right keys * Fixed comments Also fixed some unessesary copying/mem allocation
1 parent 8f3e734 commit 4c9e56e

File tree

2 files changed

+580
-29
lines changed

2 files changed

+580
-29
lines changed

include/Yash.h

Lines changed: 185 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ class Yash {
5353

5454
/// @brief Sets the print function to be used
5555
/// @param print The YashPrint funcion to be used
56-
void setPrint(YashPrint print) { m_printFunction = print; }
56+
void setPrint(YashPrint print) { m_printFunction = std::move(print); }
5757

5858
/// @brief Prints the specified text using the print function
5959
/// @param text The text to be printed
60-
void print(const char* text) { m_printFunction(text); }
60+
void print(const char* text) const { m_printFunction(text); }
6161

6262
/// @brief Prints the specified text using the print function. Printf style formating can be used
6363
void printf(const char* fmt, ...)
@@ -80,7 +80,7 @@ class Yash {
8080
/// @param requiredArguments A size_t with the number of required arguments (default 0)
8181
void addCommand(const std::string& command, const std::string& description, YashFunction function, size_t requiredArguments = 0)
8282
{
83-
addCommand(command, "", description, function, requiredArguments);
83+
addCommand(command, "", description, std::move(function), requiredArguments);
8484
}
8585

8686
/// @brief Adds a command with a sub command to the shell
@@ -92,7 +92,7 @@ class Yash {
9292
void addCommand(const std::string& command, const std::string& subCommand, const std::string& description, YashFunction function, size_t requiredArguments = 0)
9393
{
9494
auto fullCommand = subCommand.empty() ? command : command + s_commandDelimiter + subCommand;
95-
m_functions.emplace(fullCommand, Function(description, function, requiredArguments));
95+
m_functions.emplace(fullCommand, Function(description, std::move(function), requiredArguments));
9696
}
9797

9898
/// @brief Removes a command from the shell
@@ -119,7 +119,7 @@ class Yash {
119119
case '\n':
120120
case '\r':
121121
print("\r\n");
122-
if (m_command.size()) {
122+
if (!m_command.empty()) {
123123
runCommand();
124124
m_commands.push_back(m_command);
125125

@@ -130,21 +130,41 @@ class Yash {
130130
m_commandsIndex = m_commands.end();
131131
} else
132132
print(m_prompt.c_str());
133+
m_pos = m_command.length();
133134
break;
134135
case EndOfText:
135136
m_command.clear();
136137
printCommand();
138+
m_pos = m_command.length();
137139
break;
138140
case Del:
139141
case Backspace:
140-
if (m_command.size()) {
141-
print(s_clearCharacter);
142-
m_command.erase(m_command.size() - 1);
142+
if (!m_command.empty()) {
143+
if (m_pos == m_command.length()) {
144+
print(s_clearCharacter);
145+
m_command.erase(m_command.length() - 1);
146+
m_pos = m_command.length();
147+
} else if (m_pos) {
148+
m_command.erase(--m_pos, 1);
149+
print(s_moveCursorBackward);
150+
151+
for (size_t i = m_pos; i < m_command.length(); i++) {
152+
print(std::string(1, m_command.at(i)).c_str());
153+
}
154+
155+
print(std::string(1, ' ').c_str());
156+
print(s_clearCharacter); // clear unused char at the end
157+
158+
for (size_t i = m_pos; i < m_command.length(); i++) {
159+
print(s_moveCursorBackward);
160+
}
161+
}
143162
}
144163
break;
145164
case Tab:
146165
printDescriptions(true);
147166
printCommand();
167+
m_pos = m_command.length();
148168
break;
149169
case Esc:
150170
m_ctrlState = CtrlState::Esc;
@@ -154,23 +174,129 @@ class Yash {
154174
m_ctrlState = CtrlState::LeftBracket;
155175
return;
156176
default:
157-
if (character == Up && m_ctrlState == CtrlState::LeftBracket) {
158-
if (m_commandsIndex != m_commands.begin()) {
159-
m_command = *--m_commandsIndex;
160-
printCommand();
161-
}
162-
} else if (character == Down && m_ctrlState == CtrlState::LeftBracket) {
163-
if (m_commandsIndex != m_commands.end()) {
164-
++m_commandsIndex;
165-
if (m_commandsIndex != m_commands.end())
166-
m_command = *m_commandsIndex;
167-
else
168-
m_command.clear();
169-
printCommand();
177+
if (m_ctrlState == CtrlState::LeftBracket) {
178+
m_ctrlSeq += character;
179+
for (const auto &it : m_supportedCtrlSeq) {
180+
if (m_ctrlSeq.compare(0, m_ctrlSeq.length(), it.first, 0, m_ctrlSeq.length()) == 0) {
181+
if (m_ctrlSeq.length() == it.first.length()) {
182+
switch (it.second) {
183+
case CtrlSeq::Up:
184+
if (m_commandsIndex != m_commands.begin()) {
185+
m_command = *--m_commandsIndex;
186+
printCommand();
187+
m_pos = m_command.length();
188+
}
189+
break;
190+
case CtrlSeq::Down:
191+
if (m_commandsIndex != m_commands.end()) {
192+
++m_commandsIndex;
193+
if (m_commandsIndex != m_commands.end()) {
194+
m_command = *m_commandsIndex;
195+
} else {
196+
m_command.clear();
197+
}
198+
printCommand();
199+
m_pos = m_command.length();
200+
}
201+
break;
202+
case CtrlSeq::Right:
203+
if (m_pos != m_command.length()) {
204+
print(s_moveCursorForward);
205+
m_pos++;
206+
}
207+
break;
208+
case CtrlSeq::Left:
209+
if (m_pos) {
210+
print(s_moveCursorBackward);
211+
m_pos--;
212+
}
213+
break;
214+
case CtrlSeq::Home:
215+
while (m_pos) {
216+
print(s_moveCursorBackward);
217+
m_pos--;
218+
}
219+
break;
220+
case CtrlSeq::Delete:
221+
if (m_pos != m_command.length()) {
222+
m_command.erase(m_pos, 1);
223+
224+
print(std::string(1, ' ').c_str());
225+
print(s_clearCharacter); // clear deleted char
226+
227+
for (size_t i = m_pos; i < m_command.length(); i++) {
228+
print(std::string(1, m_command.at(i)).c_str());
229+
}
230+
231+
print(std::string(1, ' ').c_str());
232+
print(s_clearCharacter); // clear unused char at the end
233+
234+
for (size_t i = m_pos; i < m_command.length(); i++) {
235+
print(s_moveCursorBackward);
236+
}
237+
}
238+
break;
239+
case CtrlSeq::End:
240+
while (m_pos != m_command.length()) {
241+
print(s_moveCursorForward);
242+
m_pos++;
243+
}
244+
break;
245+
case CtrlSeq::CtrlRight:
246+
while (m_pos != m_command.length() && m_command.at(m_pos) == ' ') { // skip spaces until we find the first char
247+
print(s_moveCursorForward);
248+
m_pos++;
249+
}
250+
while (m_pos != m_command.length() && m_command.at(m_pos) != ' ') { // skip chars until we find the first space
251+
print(s_moveCursorForward);
252+
m_pos++;
253+
}
254+
break;
255+
case CtrlSeq::CtrlLeft:
256+
if (m_pos && m_pos == m_command.length()) { // step inside the m_command range
257+
print(s_moveCursorBackward);
258+
m_pos--;
259+
}
260+
if (m_pos && m_pos != m_command.length() && m_command.at(m_pos) != ' ') { // skip the first char
261+
print(s_moveCursorBackward);
262+
m_pos--;
263+
}
264+
while (m_pos && m_pos != m_command.length() && m_command.at(m_pos) == ' ') { // skip spaces until we find the first char
265+
print(s_moveCursorBackward);
266+
m_pos--;
267+
}
268+
while (m_pos && m_pos != m_command.length() && m_command.at(m_pos) != ' ') { // skip chars until we find the first space
269+
print(s_moveCursorBackward);
270+
m_pos--;
271+
}
272+
if (m_pos && m_pos != m_command.length() && m_command.at(m_pos) == ' ') { // step forward if we hit a space in order to highlight a char
273+
print(s_moveCursorForward);
274+
m_pos++;
275+
}
276+
break;
277+
}
278+
} else {
279+
return; // check the next ctrl character
280+
}
281+
}
170282
}
283+
m_ctrlSeq.clear();
171284
} else {
172-
print(std::string(1, character).c_str());
173-
m_command += character;
285+
if (m_pos == m_command.length()) {
286+
print(std::string(1, character).c_str());
287+
m_command += character;
288+
m_pos = m_command.length();
289+
} else {
290+
print(std::string(1, character).c_str());
291+
m_command.insert(m_pos++, 1, character);
292+
293+
for (size_t i = m_pos; i < m_command.length(); i++) {
294+
print(std::string(1, m_command.at(i)).c_str());
295+
}
296+
for (size_t i = m_pos; i < m_command.length(); i++) {
297+
print(s_moveCursorBackward);
298+
}
299+
}
174300
}
175301
break;
176302
}
@@ -186,6 +312,8 @@ class Yash {
186312
Esc = 27,
187313
Up = 65,
188314
Down = 66,
315+
Right = 67,
316+
Left = 68,
189317
LeftBracket = 91,
190318
Del = 127,
191319
};
@@ -197,15 +325,26 @@ class Yash {
197325
};
198326

199327
struct Function {
200-
Function(std::string description, YashFunction function, size_t requiredArguments)
201-
: m_description(description)
202-
, m_function(function)
328+
Function(std::string description, YashFunction function, size_t requiredArguments)
329+
: m_description(std::move(description))
330+
, m_function(std::move(function))
203331
, m_requiredArguments(requiredArguments) {}
204332

205333
std::string m_description;
206334
YashFunction m_function;
207335
size_t m_requiredArguments;
208336
};
337+
enum class CtrlSeq {
338+
Up,
339+
Down,
340+
Right,
341+
Left,
342+
Home,
343+
Delete,
344+
End,
345+
CtrlRight,
346+
CtrlLeft
347+
};
209348

210349
void runCommand()
211350
{
@@ -215,7 +354,7 @@ class Yash {
215354
auto args = m_command.substr(command.size());
216355
char *token = std::strtok(args.data(), s_commandDelimiter);
217356
while (token) {
218-
arguments.push_back(token);
357+
arguments.emplace_back(token);
219358
token = std::strtok(nullptr, s_commandDelimiter);
220359
}
221360

@@ -246,7 +385,9 @@ class Yash {
246385

247386
for (const auto& [command, description] : descriptions) {
248387
std::string alignment((maxCommandSize + 2) - command.size(), ' ');
249-
auto line { command + alignment + description + "\r\n" };
388+
std::string line;
389+
line.reserve(command.size() + alignment.size() + description.size() + 2);
390+
line.append(command).append(alignment).append(description).append("\r\n");
250391
print(line.c_str());
251392
}
252393
}
@@ -299,15 +440,30 @@ class Yash {
299440
static constexpr const char* s_clearLine = "\033[2K\033[100D";
300441
static constexpr const char* s_clearScreen = "\033[2J\x1B[H";
301442
static constexpr const char* s_clearCharacter = "\033[1D \033[1D";
443+
static constexpr const char* s_moveCursorForward = "\033[1C";
444+
static constexpr const char* s_moveCursorBackward = "\033[1D";
302445
static constexpr const char* s_commandDelimiter = " ";
303446
std::map<std::string, Function> m_functions;
304447
std::vector<std::string> m_commands;
305448
std::vector<std::string>::const_iterator m_commandsIndex;
449+
size_t m_pos{0};
306450
std::string m_command;
307451
std::string m_prompt;
308452
YashPrint m_printFunction;
309-
std::array<char, 128> m_buffer;
453+
std::array<char, 256> m_buffer{};
310454
CtrlState m_ctrlState { CtrlState::None };
455+
std::string m_ctrlSeq;
456+
const std::map<std::string, CtrlSeq> m_supportedCtrlSeq = {
457+
{"A", CtrlSeq::Up},
458+
{"B", CtrlSeq::Down},
459+
{"C", CtrlSeq::Right},
460+
{"D", CtrlSeq::Left},
461+
{"1~", CtrlSeq::Home},
462+
{"3~", CtrlSeq::Delete},
463+
{"4~", CtrlSeq::End},
464+
{"1;5C", CtrlSeq::CtrlRight},
465+
{"1;5D", CtrlSeq::CtrlLeft}
466+
};
311467
};
312468

313469
} // namespace Yash

0 commit comments

Comments
 (0)