17 |
#include "TerminalCtrl.h" |
#include "TerminalCtrl.h" |
18 |
#include "CFmt.h" |
#include "CFmt.h" |
19 |
#include "CCursor.h" |
#include "CCursor.h" |
20 |
|
#include "TerminalPrinter.h" |
21 |
|
|
22 |
#include "../common/global.h" |
#include "../common/global.h" |
23 |
#include "../common/global_private.h" |
#include "../common/global_private.h" |
35 |
static String g_goodPortion; |
static String g_goodPortion; |
36 |
static String g_badPortion; |
static String g_badPortion; |
37 |
static String g_suggestedPortion; |
static String g_suggestedPortion; |
38 |
|
static int g_linesActive = 0; |
39 |
static const String g_prompt = "lscp=# "; |
static const String g_prompt = "lscp=# "; |
40 |
static std::vector<String> g_commandHistory; |
static std::vector<String> g_commandHistory; |
41 |
static int g_commandHistoryIndex = -1; |
static int g_commandHistoryIndex = -1; |
66 |
return g_prompt.size(); |
return g_prompt.size(); |
67 |
} |
} |
68 |
|
|
69 |
|
static void quitApp(int code = 0) { |
70 |
|
std::cout << std::endl << std::flush; |
71 |
|
if (g_client) delete g_client; |
72 |
|
if (g_keyboardReader) delete g_keyboardReader; |
73 |
|
exit(code); |
74 |
|
} |
75 |
|
|
76 |
// Called by the network reading thread, whenever new data arrived from the |
// Called by the network reading thread, whenever new data arrived from the |
77 |
// network connection. |
// network connection. |
78 |
static void onLSCPClientNewInputAvailable(LSCPClient* client) { |
static void onLSCPClientNewInputAvailable(LSCPClient* client) { |
84 |
g_todo.Set(true); |
g_todo.Set(true); |
85 |
} |
} |
86 |
|
|
87 |
|
// Called on network error or when server side closed the TCP connection. |
88 |
|
static void onLSCPClientErrorOccured(LSCPClient* client) { |
89 |
|
quitApp(); |
90 |
|
} |
91 |
|
|
92 |
|
/// Will be called when the user hits tab key to trigger auto completion. |
93 |
static void autoComplete() { |
static void autoComplete() { |
94 |
if (g_suggestedPortion.empty()) return; |
if (g_suggestedPortion.empty()) return; |
95 |
String s; |
String s; |
113 |
g_client->send(command); |
g_client->send(command); |
114 |
} |
} |
115 |
|
|
116 |
|
/// Will be called when the user hits arrow up key, to iterate to an older command line. |
117 |
static void previousCommand() { |
static void previousCommand() { |
118 |
commandFromHistory(1); |
commandFromHistory(1); |
119 |
} |
} |
120 |
|
|
121 |
|
/// Will be called when the user hits arrow down key, to iterate to a more recent command line. |
122 |
static void nextCommand() { |
static void nextCommand() { |
123 |
commandFromHistory(-1); |
commandFromHistory(-1); |
124 |
} |
} |
125 |
|
|
126 |
|
/// Will be called whenever the user hits ENTER, to store the line in the command history. |
127 |
static void storeCommandInHistory(const String& sCommand) { |
static void storeCommandInHistory(const String& sCommand) { |
128 |
g_commandHistoryIndex = -1; // reset history index |
g_commandHistoryIndex = -1; // reset history index |
129 |
// don't save the command if the previous one was the same |
// don't save the command if the previous one was the same |
149 |
* @endcode |
* @endcode |
150 |
* which will inform the LSCP server that this LSCP client is actually a LSCP |
* which will inform the LSCP server that this LSCP client is actually a LSCP |
151 |
* shell application. The shell will then simply forward every single character |
* shell application. The shell will then simply forward every single character |
152 |
* typed by the user immediately to the LSCP server, which in turn will evaluate |
* typed by the user immediately to the LSCP server. The LSCP server in turn |
153 |
* every single character typed by the user and will return immediately a |
* will evaluate every single character received and will return immediately a |
154 |
* specially formatted string to the shell application like (assuming the user's |
* specially formatted string to the shell application like (assuming the user's |
155 |
* current command line was "CREATE AUasdf"): |
* current command line was "CREATE AUasdf"): |
156 |
* @code |
* @code |
176 |
* |
* |
177 |
* - Right of "{{SB}}" follows the current auto completion suggestion, so that |
* - Right of "{{SB}}" follows the current auto completion suggestion, so that |
178 |
* string portion was not typed by the user yet, but is expected to be typed |
* string portion was not typed by the user yet, but is expected to be typed |
179 |
* by him next to retain syntax correctness. |
* by him next, to retain syntax correctness. The auto completion portion is |
180 |
|
* added by the LSCP server only if there is one unique way to add characters |
181 |
|
* to the current command line. If there are multiple possibilities, than this |
182 |
|
* portion is missing due to ambiguity. |
183 |
|
* |
184 |
|
* - Optionally there might also be a "{{PB}" marker on right hand side of the |
185 |
|
* line. The text right to that marker reflects all possibilities at the |
186 |
|
* user's current input position (which cannot be auto completed) due to |
187 |
|
* ambiguity, including abstract (a.k.a. "non-terminal") symbols like: |
188 |
|
* @code |
189 |
|
* <digit>, <text>, <number>, etc. |
190 |
|
* @endcode |
191 |
|
* This portion is added by the LSCP server only if there is not a unique way |
192 |
|
* to add characters to the current command line. |
193 |
*/ |
*/ |
194 |
int main(int argc, char *argv[]) { |
int main(int argc, char *argv[]) { |
195 |
String host = LSCP_DEFAULT_HOST; |
String host = LSCP_DEFAULT_HOST; |
227 |
// receiving incoming network data from the sampler's LSCP server |
// receiving incoming network data from the sampler's LSCP server |
228 |
g_client = new LSCPClient; |
g_client = new LSCPClient; |
229 |
g_client->setCallback(onLSCPClientNewInputAvailable); |
g_client->setCallback(onLSCPClientNewInputAvailable); |
230 |
|
g_client->setErrorCallback(onLSCPClientErrorOccured); |
231 |
if (!g_client->connect(host, port)) return -1; |
if (!g_client->connect(host, port)) return -1; |
232 |
String sResponse = g_client->sendCommandSync( |
String sResponse = g_client->sendCommandSync( |
233 |
(autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0" |
(autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0" |
265 |
// FIFO buffer. |
// FIFO buffer. |
266 |
// |
// |
267 |
// - Main thread: this thread runs in the loop below. The main thread sleeps |
// - Main thread: this thread runs in the loop below. The main thread sleeps |
268 |
// (by using the "g_todo" semaphore) until either new keys on the keyboard |
// (by using the "g_todo" condition variable) until either new keys on the |
269 |
// were stroke by the user or until new bytes were received from the LSCP |
// keyboard were stroke by the user or until new bytes were received from |
270 |
// server. The main thread will then accordingly send the typed characters |
// the LSCP server. The main thread will then accordingly send the typed |
271 |
// to the LSCP server and/or show the result of the LSCP server's latest |
// characters to the LSCP server and/or show the result of the LSCP |
272 |
// evaluation to the user on the screen (by pulling those data from the |
// server's latest evaluation to the user on the screen (by pulling those |
273 |
// other two thread's FIFO buffers). |
// data from the other two thread's FIFO buffers). |
274 |
while (true) { |
while (true) { |
275 |
// sleep until either new data from the network or from keyboard arrived |
// sleep until either new data from the network or from keyboard arrived |
276 |
g_todo.WaitIf(false); |
g_todo.WaitIf(false); |
300 |
sBad.erase(sBad.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
sBad.erase(sBad.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
301 |
if (sBad.find(LSCP_SHK_SUGGEST_BACK) != string::npos) |
if (sBad.find(LSCP_SHK_SUGGEST_BACK) != string::npos) |
302 |
sBad.erase(sBad.find(LSCP_SHK_SUGGEST_BACK)); // erase auto suggestion portion |
sBad.erase(sBad.find(LSCP_SHK_SUGGEST_BACK)); // erase auto suggestion portion |
303 |
|
if (sBad.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) |
304 |
|
sBad.erase(sBad.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion |
305 |
|
|
306 |
// extract portion that is suggested for auto completion |
// extract portion that is suggested for auto completion |
307 |
String sSuggest; |
String sSuggest; |
309 |
sSuggest = s.substr(s.find(LSCP_SHK_SUGGEST_BACK) + strlen(LSCP_SHK_SUGGEST_BACK)); |
sSuggest = s.substr(s.find(LSCP_SHK_SUGGEST_BACK) + strlen(LSCP_SHK_SUGGEST_BACK)); |
310 |
if (sSuggest.find(LSCP_SHK_CURSOR) != string::npos) |
if (sSuggest.find(LSCP_SHK_CURSOR) != string::npos) |
311 |
sSuggest.erase(sSuggest.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
sSuggest.erase(sSuggest.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
312 |
|
if (sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) |
313 |
|
sSuggest.erase(sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion |
314 |
|
} |
315 |
|
|
316 |
|
// extract portion that provides all current possibilities |
317 |
|
// (that is all branches in the current grammar tree) |
318 |
|
String sPossibilities; |
319 |
|
if (s.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) { |
320 |
|
sPossibilities = s.substr(s.find(LSCP_SHK_POSSIBILITIES_BACK) + strlen(LSCP_SHK_POSSIBILITIES_BACK)); |
321 |
} |
} |
322 |
|
|
323 |
// extract current cursor position |
// extract current cursor position |
338 |
|
|
339 |
//printf("line '%s' good='%s' bad='%s' suggested='%s' cursor=%d\n", line.c_str(), sGood.c_str(), sBad.c_str(), sSuggest.c_str(), cursorColumn); |
//printf("line '%s' good='%s' bad='%s' suggested='%s' cursor=%d\n", line.c_str(), sGood.c_str(), sBad.c_str(), sSuggest.c_str(), cursorColumn); |
340 |
|
|
341 |
|
// clear current command line on screen |
342 |
|
// (which may have been printed over several lines) |
343 |
CCursor cursor = CCursor::now().toColumn(0).clearLine(); |
CCursor cursor = CCursor::now().toColumn(0).clearLine(); |
344 |
|
for (int i = 0; i < g_linesActive; ++i) |
345 |
|
cursor = cursor.down().clearLine(); |
346 |
|
if (g_linesActive) cursor = cursor.up(g_linesActive).toColumn(0); |
347 |
printPrompt(); |
printPrompt(); |
348 |
|
|
349 |
|
// print out the gathered informations on the screen |
350 |
|
TerminalPrinter p; |
351 |
CFmt cfmt; |
CFmt cfmt; |
352 |
if (code == LSCP_SHU_COMPLETE) cfmt.bold().green(); |
if (code == LSCP_SHU_COMPLETE) cfmt.bold().green(); |
353 |
else cfmt.bold().white(); |
else cfmt.bold().white(); |
354 |
cout << sGood << flush; |
p << sGood; |
355 |
cfmt.reset().red(); |
cfmt.reset().red(); |
356 |
cout << sBad << flush; |
p << sBad; |
357 |
cfmt.bold().yellow(); |
cfmt.bold().yellow(); |
358 |
cout << sSuggest << flush; |
p << sSuggest; |
359 |
|
if (!sPossibilities.empty()) |
360 |
|
p << " <- " << sPossibilities; |
361 |
|
|
362 |
|
// move cursor back to the appropriate input position in |
363 |
|
// the command line (which may be several lines above) |
364 |
|
g_linesActive = p.linesAdvanced(); |
365 |
|
if (p.linesAdvanced()) cursor.up(p.linesAdvanced()); |
366 |
cursor.toColumn(cursorColumn + promptOffset()); |
cursor.toColumn(cursorColumn + promptOffset()); |
367 |
} |
} |
368 |
} else if (line.substr(0,2) == "OK") { // single-line response expected ... |
} else if (line.substr(0,2) == "OK") { // single-line response expected ... |
408 |
while (g_keyboardReader->charAvailable()) { |
while (g_keyboardReader->charAvailable()) { |
409 |
char c = g_keyboardReader->popChar(); |
char c = g_keyboardReader->popChar(); |
410 |
|
|
|
CFmt cfmt; |
|
|
cfmt.white(); |
|
411 |
//std::cout << c << "(" << int(c) << ")" << std::endl << std::flush; |
//std::cout << c << "(" << int(c) << ")" << std::endl << std::flush; |
412 |
if (iKbdEscapeCharsExpected) { // escape sequence (still) expected now ... |
if (iKbdEscapeCharsExpected) { // escape sequence (still) expected now ... |
413 |
iKbdEscapeCharsExpected--; |
iKbdEscapeCharsExpected--; |
417 |
previousCommand(); |
previousCommand(); |
418 |
else if (kbdPrevEscapeChar == 91 && c == 66) // down key |
else if (kbdPrevEscapeChar == 91 && c == 66) // down key |
419 |
nextCommand(); |
nextCommand(); |
420 |
else if (kbdPrevEscapeChar == 91 && c == 68) { // left key |
else if (kbdPrevEscapeChar == 91 && c == 68) // left key |
421 |
//TODO: move cursor left |
g_client->send(2); // custom usage of this ASCII code |
422 |
} else if (kbdPrevEscapeChar == 91 && c == 67) { // right key |
else if (kbdPrevEscapeChar == 91 && c == 67) // right key |
423 |
//TODO: move cursor right |
g_client->send(3); // custom usage of this ASCII code |
|
} |
|
424 |
} |
} |
425 |
continue; // don't send this escape sequence character to LSCP server |
continue; // don't send this escape sequence character to LSCP server |
426 |
} else if (c == KBD_ESCAPE) { // escape sequence for special keys expected next ... |
} else if (c == KBD_ESCAPE) { // escape sequence for special keys expected next ... |
427 |
iKbdEscapeCharsExpected = 2; |
iKbdEscapeCharsExpected = 2; |
428 |
continue; // don't send ESC character to LSCP server |
continue; // don't send ESC character to LSCP server |
429 |
} else if (c == KBD_BACKSPACE) { |
} else if (c == KBD_BACKSPACE) { |
430 |
if (promptOffset() < CCursor::now().column()) |
c = '\b'; |
|
cout << "\b \b" << flush; |
|
|
c = '\b'; |
|
431 |
} else if (c == '\t') { // auto completion ... |
} else if (c == '\t') { // auto completion ... |
432 |
autoComplete(); |
autoComplete(); |
433 |
continue; // don't send tab character to LSCP server |
continue; // don't send tab character to LSCP server |
434 |
} else if (c == '\n') { |
} else if (c == '\n') { |
435 |
storeCommandInHistory(g_goodPortion + g_badPortion); |
storeCommandInHistory(g_goodPortion + g_badPortion); |
|
} else { // don't apply RETURN stroke yet, since the typed command might still be corrected by the sampler |
|
|
cout << c << flush; |
|
436 |
} |
} |
437 |
|
|
438 |
g_client->send(c); |
g_client->send(c); |