/[svn]/linuxsampler/trunk/src/shell/lscp.cpp
ViewVC logotype

Annotation of /linuxsampler/trunk/src/shell/lscp.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2535 - (hide annotations) (download)
Tue Apr 15 19:35:35 2014 UTC (10 years ago) by schoenebeck
File size: 24625 byte(s)
* LSCP server: optimized server side processing of LSCP shell tasks (caused
  a sluggish behavior, i.e. when using arrow up/down keys in LSCP shell).
* LSCP shell: fixed crash on server disconnection.
* LSCP shell: fixed trash printed on terminal for LSCP documentation
  sometimes.
* Automake: tried to address a compilation error with automake 1.9
  (see bug #216), seems that it did not fix it though.
* Bumped version (1.0.0.svn38).

1 schoenebeck 2515 /*
2     * LSCP Shell
3     *
4     * Copyright (c) 2014 Christian Schoenebeck
5     *
6     * This program is part of LinuxSampler and released under the same terms.
7     */
8    
9     #include <stdio.h>
10     #include <stdlib.h>
11     #include <iostream>
12     #include <sstream>
13     #include <string.h>
14    
15 schoenebeck 2535 #include "lscp.h"
16 schoenebeck 2515 #include "LSCPClient.h"
17     #include "KeyboardReader.h"
18     #include "TerminalCtrl.h"
19     #include "CFmt.h"
20     #include "CCursor.h"
21 schoenebeck 2528 #include "TerminalPrinter.h"
22 schoenebeck 2535 #if DEBUG_LSCP_SHELL
23     # include <stdarg.h>
24     # include <sys/timeb.h>
25     #endif // DEBUG_LSCP_SHELL
26 schoenebeck 2515 #include "../common/global.h"
27 schoenebeck 2517 #include "../common/global_private.h"
28 schoenebeck 2515 #include "../common/Condition.h"
29    
30     #define LSCP_DEFAULT_HOST "localhost"
31     #define LSCP_DEFAULT_PORT 8888
32    
33     using namespace std;
34     using namespace LinuxSampler;
35    
36 schoenebeck 2517 static LSCPClient* g_client = NULL;
37     static KeyboardReader* g_keyboardReader = NULL;
38 schoenebeck 2515 static Condition g_todo;
39 schoenebeck 2516 static String g_goodPortion;
40     static String g_badPortion;
41     static String g_suggestedPortion;
42 schoenebeck 2528 static int g_linesActive = 0;
43 schoenebeck 2517 static const String g_prompt = "lscp=# ";
44 schoenebeck 2518 static std::vector<String> g_commandHistory;
45     static int g_commandHistoryIndex = -1;
46 schoenebeck 2534 static String g_doc;
47 schoenebeck 2535 static bool g_quitAppRequested = false;
48     static int g_exitCode = 0;
49     #if DEBUG_LSCP_SHELL
50     static FILE* g_df; ///< handle to output log file (just for debugging)
51     #endif
52 schoenebeck 2515
53 schoenebeck 2535 /**
54     * Log the given debug message to a log file. This works only if
55     * DEBUG_LSCP_SHELL was turned on in lscp.h. Otherwise this function does
56     * nothing. Usage of this function is equivalent to printf().
57     */
58     void lscpLog(const char* format, ...) {
59     #if DEBUG_LSCP_SHELL
60     // assemble variable argument list given to this function call
61     va_list arg;
62     va_start(arg, format);
63     // write current timestamp to log file
64     struct timeb tp;
65     ftime(&tp);
66     fprintf(g_df, "[%d.%03d] ", tp.time, tp.millitm);
67     // write actual debug message to log file
68     vfprintf(g_df, format, arg);
69     fflush(g_df);
70     va_end(arg);
71     #endif // DEBUG_LSCP_SHELL
72     }
73    
74 schoenebeck 2515 static void printUsage() {
75     cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl;
76     cout << endl;
77     cout << "Usage: lscp [-h HOSTNAME] [-p PORT]" << endl;
78     cout << endl;
79     cout << " -h Host name of LSCP server (default \"" << LSCP_DEFAULT_HOST << "\")." << endl;
80     cout << endl;
81     cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl;
82     cout << endl;
83 schoenebeck 2516 cout << " --no-auto-correct Don't perform auto correction of obvious syntax errors." << endl;
84     cout << endl;
85 schoenebeck 2534 cout << " --no-doc Don't show LSCP reference documentation on screen." << endl;
86     cout << endl;
87 schoenebeck 2515 }
88    
89 schoenebeck 2517 static void printWelcome() {
90     cout << "Welcome to lscp " << VERSION << ", the LinuxSampler Control Protocol (LSCP) shell." << endl;
91     cout << endl;
92     }
93    
94     static void printPrompt() {
95     cout << g_prompt << flush;
96     }
97    
98     static int promptOffset() {
99     return g_prompt.size();
100     }
101    
102 schoenebeck 2532 static void quitApp(int code = 0) {
103 schoenebeck 2535 //lscpLog("[quit app]\n");
104     g_exitCode = code;
105     g_quitAppRequested = true;
106 schoenebeck 2532 }
107    
108 schoenebeck 2515 // Called by the network reading thread, whenever new data arrived from the
109     // network connection.
110     static void onLSCPClientNewInputAvailable(LSCPClient* client) {
111     g_todo.Set(true);
112     }
113    
114     // Called by the keyboard reading thread, whenever a key stroke was received.
115     static void onNewKeyboardInputAvailable(KeyboardReader* reader) {
116     g_todo.Set(true);
117     }
118    
119 schoenebeck 2532 // Called on network error or when server side closed the TCP connection.
120     static void onLSCPClientErrorOccured(LSCPClient* client) {
121 schoenebeck 2535 //lscpLog("[client error callback]\n");
122 schoenebeck 2532 quitApp();
123     }
124    
125     /// Will be called when the user hits tab key to trigger auto completion.
126 schoenebeck 2516 static void autoComplete() {
127     if (g_suggestedPortion.empty()) return;
128     String s;
129     // let the server delete mistaken characters first
130     for (int i = 0; i < g_badPortion.size(); ++i) s += '\b';
131     // now add the suggested, correct characters
132     s += g_suggestedPortion;
133     g_suggestedPortion.clear();
134 schoenebeck 2517 g_client->send(s);
135 schoenebeck 2516 }
136    
137 schoenebeck 2518 static void commandFromHistory(int offset) {
138     if (g_commandHistoryIndex + offset < 0 ||
139     g_commandHistoryIndex + offset >= g_commandHistory.size()) return;
140     g_commandHistoryIndex += offset;
141     int len = g_goodPortion.size() + g_badPortion.size();
142 schoenebeck 2535 String command;
143 schoenebeck 2518 // erase current active line
144 schoenebeck 2535 for (int i = 0; i < len; ++i) command += '\b';
145 schoenebeck 2518 // transmit new/old line to LSCP server
146 schoenebeck 2535 command += g_commandHistory[g_commandHistory.size() - g_commandHistoryIndex - 1];
147 schoenebeck 2518 g_client->send(command);
148     }
149    
150 schoenebeck 2532 /// Will be called when the user hits arrow up key, to iterate to an older command line.
151 schoenebeck 2518 static void previousCommand() {
152     commandFromHistory(1);
153     }
154    
155 schoenebeck 2532 /// Will be called when the user hits arrow down key, to iterate to a more recent command line.
156 schoenebeck 2518 static void nextCommand() {
157     commandFromHistory(-1);
158     }
159    
160 schoenebeck 2532 /// Will be called whenever the user hits ENTER, to store the line in the command history.
161 schoenebeck 2518 static void storeCommandInHistory(const String& sCommand) {
162     g_commandHistoryIndex = -1; // reset history index
163     // don't save the command if the previous one was the same
164     if (g_commandHistory.empty() || g_commandHistory.back() != sCommand)
165     g_commandHistory.push_back(sCommand);
166     }
167    
168 schoenebeck 2534 /// Splits the given string into individual lines for the given screen resolution.
169     static std::vector<String> splitForScreen(const String& s, int cols, int rows) {
170     std::vector<String> lines;
171     if (rows <= 0 || cols <= 0) return lines;
172     String line;
173     for (int i = 0; i < s.size(); ++i) {
174     char c = s[i];
175     if (c == '\r') continue;
176     if (c == '\n') {
177     lines.push_back(line);
178     if (lines.size() >= rows) return lines;
179     line.clear();
180     continue;
181     }
182     line += c;
183     if (line.size() >= cols) {
184     lines.push_back(line);
185     if (lines.size() >= rows) return lines;
186     line.clear();
187     }
188     }
189     return lines;
190     }
191    
192 schoenebeck 2525 /**
193 schoenebeck 2534 * Will be called whenever the LSCP documentation reference to be shown, has
194     * been changed. This call will accordingly update the screen with the new
195     * documentation text received from LSCP server.
196     */
197     static void updateDoc() {
198     const int vOffset = 2;
199    
200     CCursor originalCursor = CCursor::now();
201     CCursor cursor = originalCursor;
202     cursor.toColumn(0).down(vOffset);
203    
204     // wipe out current documentation off screen
205     cursor.clearVerticalToBottom();
206    
207     if (g_doc.empty()) {
208     // restore original cursor position
209     cursor.up(vOffset).toColumn(originalCursor.column());
210     return;
211     }
212    
213     // get screen size (in characters)
214     const int cols = TerminalCtrl::columns();
215     const int rows = TerminalCtrl::rows();
216    
217     // convert the string block into individual lines according to screen resolution
218     std::vector<String> lines = splitForScreen(g_doc, cols - 1, rows);
219    
220     // print lines onto screen
221     for (int row = 0; row < lines.size(); ++row)
222     std::cout << lines[row] << std::endl;
223    
224     // restore original cursor position
225     cursor.up(vOffset + lines.size()).toColumn(originalCursor.column());
226     }
227    
228     /**
229 schoenebeck 2525 * This LSCP shell application is designed as thin client. That means the heavy
230     * LSCP grammar evaluation tasks are peformed by the LSCP server and the shell
231     * application's task are simply limited to forward individual characters typed
232     * by the user to the LSCP server and showing the result of the LSCP server's
233     * evaluation to the user on the screen. This has the big advantage that the
234     * shell works perfectly with any machine running (some minimum recent version
235     * of) LinuxSampler, no matter which precise LSCP version the server side
236     * is using. Which reduces the maintenance efforts for the shell application
237     * development tremendously.
238     *
239     * As soon as this application established a TCP connection to a LSCP server, it
240     * sends this command to the LSCP server:
241     * @code
242     * SET SHELL INTERACT 1
243     * @endcode
244     * which will inform the LSCP server that this LSCP client is actually a LSCP
245     * shell application. The shell will then simply forward every single character
246 schoenebeck 2532 * typed by the user immediately to the LSCP server. The LSCP server in turn
247     * will evaluate every single character received and will return immediately a
248 schoenebeck 2525 * specially formatted string to the shell application like (assuming the user's
249     * current command line was "CREATE AUasdf"):
250     * @code
251     * SHU:1:CREATE AU{{GF}}asdf{{CU}}{{SB}}DIO_OUTPUT_DEVICE
252     * @endcode
253     * which informs this shell application about the result of the LSCP grammar
254     * evaluation and allows the shell to easily show that result of the evaluation
255     * to the user on the screen. In the example reply above, the prefix "SHU:" just
256     * indicates to the shell application that this response line is the result
257     * of the latest grammar evaluation, the number followed (here 1) indicates the
258     * semantic status of the current command line:
259     *
260     * - 0: Command line is complete, thus ENTER key may be hit by the user now.
261     * - 1: Current command line contains syntax error(s).
262     * - 2: Command line is incomplete, but contains no syntax errors so far.
263     *
264     * Then the actual current command line follows, with special markers:
265     *
266     * - Left of "{{GF}}" the command line is syntactically correct, right of that
267     * marker the command line is syntactically wrong.
268     *
269     * - Marker "{{CU}}" indicates the current cursor position of the command line.
270     *
271     * - Right of "{{SB}}" follows the current auto completion suggestion, so that
272     * string portion was not typed by the user yet, but is expected to be typed
273 schoenebeck 2532 * by him next, to retain syntax correctness. The auto completion portion is
274     * added by the LSCP server only if there is one unique way to add characters
275     * to the current command line. If there are multiple possibilities, than this
276     * portion is missing due to ambiguity.
277     *
278     * - Optionally there might also be a "{{PB}" marker on right hand side of the
279     * line. The text right to that marker reflects all possibilities at the
280     * user's current input position (which cannot be auto completed) due to
281     * ambiguity, including abstract (a.k.a. "non-terminal") symbols like:
282     * @code
283     * <digit>, <text>, <number>, etc.
284     * @endcode
285     * This portion is added by the LSCP server only if there is not a unique way
286     * to add characters to the current command line.
287 schoenebeck 2525 */
288 schoenebeck 2515 int main(int argc, char *argv[]) {
289 schoenebeck 2535 #if DEBUG_LSCP_SHELL
290     g_df = fopen("lscp.log", "w");
291     if (!g_df) {
292     std::cerr << "Could not open lscp.log for writing!\n";
293     exit(-1);
294     }
295     #endif // DEBUG_LSCP_SHELL
296    
297 schoenebeck 2515 String host = LSCP_DEFAULT_HOST;
298     int port = LSCP_DEFAULT_PORT;
299 schoenebeck 2516 bool autoCorrect = true;
300 schoenebeck 2534 bool showDoc = true;
301 schoenebeck 2515
302     // parse command line arguments
303     for (int i = 0; i < argc; ++i) {
304     String s = argv[i];
305     if (s == "-h" || s == "--host") {
306     if (++i >= argc) {
307     printUsage();
308     return -1;
309     }
310     host = argv[i];
311     } else if (s == "-p" || s == "--port") {
312     if (++i >= argc) {
313     printUsage();
314     return -1;
315     }
316     port = atoi(argv[i]);
317     if (port <= 0) {
318     cerr << "Error: invalid port argument \"" << argv[i] << "\"\n";
319     return -1;
320     }
321 schoenebeck 2516 } else if (s == "--no-auto-correct") {
322     autoCorrect = false;
323 schoenebeck 2534 } else if (s == "--no-doc") {
324     showDoc = false;
325 schoenebeck 2515 } else if (s[0] == '-') { // invalid / unknown command line argument ...
326     printUsage();
327     return -1;
328     }
329     }
330    
331     // try to connect to the sampler's LSCP server and start a thread for
332     // receiving incoming network data from the sampler's LSCP server
333 schoenebeck 2517 g_client = new LSCPClient;
334 schoenebeck 2532 g_client->setErrorCallback(onLSCPClientErrorOccured);
335 schoenebeck 2517 if (!g_client->connect(host, port)) return -1;
336 schoenebeck 2534 g_client->sendCommandSync(
337     (showDoc) ? "SET SHELL DOC 1" : "SET SHELL DOC 0"
338     );
339 schoenebeck 2517 String sResponse = g_client->sendCommandSync(
340 schoenebeck 2516 (autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0"
341     );
342 schoenebeck 2517 sResponse = g_client->sendCommandSync("SET SHELL INTERACT 1");
343 schoenebeck 2515 if (sResponse.substr(0, 2) != "OK") {
344     cerr << "Error: sampler too old, it does not support shell instructions\n";
345     return -1;
346     }
347 schoenebeck 2534 g_client->setCallback(onLSCPClientNewInputAvailable);
348    
349 schoenebeck 2517 printWelcome();
350     printPrompt();
351 schoenebeck 2515
352     // start a thread for reading from the local text input keyboard
353     // (keyboard echo will be disabled as well to have a clean control on what
354     // is appearing on the screen)
355 schoenebeck 2517 g_keyboardReader = new KeyboardReader;
356     g_keyboardReader->setCallback(onNewKeyboardInputAvailable);
357     g_keyboardReader->startReading();
358 schoenebeck 2518
359     int iKbdEscapeCharsExpected = 0;
360     char kbdPrevEscapeChar;
361    
362 schoenebeck 2515 // main thread's loop
363 schoenebeck 2525 //
364     // This application runs 3 threads:
365     //
366     // - Keyboard thread: reads constantly on stdin for new characters (which
367     // will block this keyboard thread until new character(s) were typed by
368     // the user) and pushes the typed characters into a FIFO buffer.
369     //
370     // - Network thread: reads constantly on the TCP connection for new bytes
371     // being sent by the LSCP server (which will block this network thread
372     // until new bytes were received) and pushes the received bytes into a
373     // FIFO buffer.
374     //
375     // - Main thread: this thread runs in the loop below. The main thread sleeps
376 schoenebeck 2532 // (by using the "g_todo" condition variable) until either new keys on the
377     // keyboard were stroke by the user or until new bytes were received from
378     // the LSCP server. The main thread will then accordingly send the typed
379     // characters to the LSCP server and/or show the result of the LSCP
380     // server's latest evaluation to the user on the screen (by pulling those
381     // data from the other two thread's FIFO buffers).
382 schoenebeck 2535 while (!g_quitAppRequested) {
383 schoenebeck 2515 // sleep until either new data from the network or from keyboard arrived
384     g_todo.WaitIf(false);
385     // immediately unset the condition variable and unlock it
386     g_todo.Set(false);
387     g_todo.Unlock();
388    
389     // did network data arrive?
390 schoenebeck 2517 while (g_client->messageComplete()) {
391     String line = *g_client->popLine();
392 schoenebeck 2535 //lscpLog("[client] '%s'\n", line.c_str());
393 schoenebeck 2515 if (line.substr(0,4) == "SHU:") {
394     int code = 0, n = 0;
395     int res = sscanf(line.c_str(), "SHU:%d:%n", &code, &n);
396     if (res >= 1) {
397     String s = line.substr(n);
398    
399 schoenebeck 2516 // extract portion that is already syntactically correct
400     size_t iGood = s.find(LSCP_SHK_GOOD_FRONT);
401     String sGood = s.substr(0, iGood);
402     if (sGood.find(LSCP_SHK_CURSOR) != string::npos)
403     sGood.erase(sGood.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
404 schoenebeck 2515
405 schoenebeck 2516 // extract portion that was written syntactically incorrect
406     String sBad = s.substr(iGood + strlen(LSCP_SHK_GOOD_FRONT));
407     if (sBad.find(LSCP_SHK_CURSOR) != string::npos)
408     sBad.erase(sBad.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
409     if (sBad.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
410     sBad.erase(sBad.find(LSCP_SHK_SUGGEST_BACK)); // erase auto suggestion portion
411 schoenebeck 2528 if (sBad.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos)
412     sBad.erase(sBad.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion
413 schoenebeck 2515
414 schoenebeck 2516 // extract portion that is suggested for auto completion
415     String sSuggest;
416     if (s.find(LSCP_SHK_SUGGEST_BACK) != string::npos) {
417     sSuggest = s.substr(s.find(LSCP_SHK_SUGGEST_BACK) + strlen(LSCP_SHK_SUGGEST_BACK));
418     if (sSuggest.find(LSCP_SHK_CURSOR) != string::npos)
419     sSuggest.erase(sSuggest.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
420 schoenebeck 2528 if (sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos)
421     sSuggest.erase(sSuggest.find(LSCP_SHK_POSSIBILITIES_BACK)); // erase possibilities portion
422 schoenebeck 2516 }
423    
424 schoenebeck 2528 // extract portion that provides all current possibilities
425     // (that is all branches in the current grammar tree)
426     String sPossibilities;
427     if (s.find(LSCP_SHK_POSSIBILITIES_BACK) != string::npos) {
428     sPossibilities = s.substr(s.find(LSCP_SHK_POSSIBILITIES_BACK) + strlen(LSCP_SHK_POSSIBILITIES_BACK));
429     }
430    
431 schoenebeck 2516 // extract current cursor position
432     int cursorColumn = sGood.size();
433     String sCursor = s;
434     if (sCursor.find(LSCP_SHK_GOOD_FRONT) != string::npos)
435     sCursor.erase(sCursor.find(LSCP_SHK_GOOD_FRONT), strlen(LSCP_SHK_GOOD_FRONT)); // erase good/bad marker
436     if (sCursor.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
437     sCursor.erase(sCursor.find(LSCP_SHK_SUGGEST_BACK), strlen(LSCP_SHK_SUGGEST_BACK)); // erase suggestion marker
438     if (sCursor.find(LSCP_SHK_CURSOR) != string::npos)
439     cursorColumn = sCursor.find(LSCP_SHK_CURSOR);
440    
441     // store those informations globally for the auto-completion
442     // feature
443     g_goodPortion = sGood;
444     g_badPortion = sBad;
445     g_suggestedPortion = sSuggest;
446    
447     //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);
448    
449 schoenebeck 2528 // clear current command line on screen
450     // (which may have been printed over several lines)
451 schoenebeck 2516 CCursor cursor = CCursor::now().toColumn(0).clearLine();
452 schoenebeck 2528 for (int i = 0; i < g_linesActive; ++i)
453     cursor = cursor.down().clearLine();
454     if (g_linesActive) cursor = cursor.up(g_linesActive).toColumn(0);
455 schoenebeck 2517 printPrompt();
456 schoenebeck 2516
457 schoenebeck 2528 // print out the gathered informations on the screen
458     TerminalPrinter p;
459 schoenebeck 2515 CFmt cfmt;
460     if (code == LSCP_SHU_COMPLETE) cfmt.bold().green();
461     else cfmt.bold().white();
462 schoenebeck 2528 p << sGood;
463 schoenebeck 2515 cfmt.reset().red();
464 schoenebeck 2528 p << sBad;
465 schoenebeck 2516 cfmt.bold().yellow();
466 schoenebeck 2528 p << sSuggest;
467     if (!sPossibilities.empty())
468     p << " <- " << sPossibilities;
469 schoenebeck 2516
470 schoenebeck 2528 // move cursor back to the appropriate input position in
471     // the command line (which may be several lines above)
472     g_linesActive = p.linesAdvanced();
473     if (p.linesAdvanced()) cursor.up(p.linesAdvanced());
474 schoenebeck 2517 cursor.toColumn(cursorColumn + promptOffset());
475 schoenebeck 2515 }
476 schoenebeck 2534 } else if (line.substr(0,4) == "SHD:") { // new LSCP doc reference section received ...
477     int code = LSCP_SHD_NO_MATCH;
478     int res = sscanf(line.c_str(), "SHD:%d", &code);
479     g_doc.clear();
480     if (code == LSCP_SHD_MATCH) {
481     while (true) { // multi-line response expected (terminated by dot line) ...
482     if (line.substr(0, 1) == ".") break;
483     if (!g_client->lineAvailable()) break;
484     line = *g_client->popLine();
485     g_doc += line;
486     }
487     }
488     updateDoc();
489 schoenebeck 2515 } else if (line.substr(0,2) == "OK") { // single-line response expected ...
490     cout << endl << flush;
491 schoenebeck 2534
492     // wipe out potential current documentation off screen
493     CCursor cursor = CCursor::now();
494     cursor.clearVerticalToBottom();
495    
496 schoenebeck 2515 CFmt cfmt;
497     cfmt.green();
498     cout << line.substr(0,2) << flush;
499     cfmt.reset();
500     cout << line.substr(2) << endl << flush;
501 schoenebeck 2517 printPrompt();
502 schoenebeck 2515 } else if (line.substr(0,3) == "WRN") { // single-line response expected ...
503     cout << endl << flush;
504 schoenebeck 2534
505     // wipe out potential current documentation off screen
506     CCursor cursor = CCursor::now();
507     cursor.clearVerticalToBottom();
508    
509 schoenebeck 2515 CFmt cfmt;
510     cfmt.yellow();
511     cout << line.substr(0,3) << flush;
512     cfmt.reset();
513     cout << line.substr(3) << endl << flush;
514 schoenebeck 2517 printPrompt();
515 schoenebeck 2515 } else if (line.substr(0,3) == "ERR") { // single-line response expected ...
516     cout << endl << flush;
517 schoenebeck 2534
518     // wipe out potential current documentation off screen
519     CCursor cursor = CCursor::now();
520     cursor.clearVerticalToBottom();
521    
522 schoenebeck 2515 CFmt cfmt;
523     cfmt.bold().red();
524     cout << line.substr(0,3) << flush;
525     cfmt.reset();
526     cout << line.substr(3) << endl << flush;
527 schoenebeck 2517 printPrompt();
528     } else if (g_client->multiLine()) { // multi-line response expected ...
529 schoenebeck 2515 cout << endl << flush;
530 schoenebeck 2534
531     // wipe out potential current documentation off screen
532     CCursor cursor = CCursor::now();
533     cursor.clearVerticalToBottom();
534    
535 schoenebeck 2515 while (true) {
536     cout << line << endl << flush;
537     if (line.substr(0, 1) == ".") break;
538 schoenebeck 2517 if (!g_client->lineAvailable()) break;
539     line = *g_client->popLine();
540 schoenebeck 2515 }
541 schoenebeck 2517 printPrompt();
542 schoenebeck 2515 } else {
543 schoenebeck 2534 cout << endl << flush;
544    
545     // wipe out potential current documentation off screen
546     CCursor cursor = CCursor::now();
547     cursor.clearVerticalToBottom();
548    
549     cout << line << endl << flush;
550 schoenebeck 2517 printPrompt();
551 schoenebeck 2515 }
552     }
553    
554     // did keyboard input arrive?
555 schoenebeck 2517 while (g_keyboardReader->charAvailable()) {
556     char c = g_keyboardReader->popChar();
557 schoenebeck 2535 //lscpLog("[keyboard] '%c' (dec %d%s)'\n", c, (int)c, iKbdEscapeCharsExpected ? " ESC SEQ" : "");
558 schoenebeck 2515
559     //std::cout << c << "(" << int(c) << ")" << std::endl << std::flush;
560 schoenebeck 2518 if (iKbdEscapeCharsExpected) { // escape sequence (still) expected now ...
561     iKbdEscapeCharsExpected--;
562     if (iKbdEscapeCharsExpected) kbdPrevEscapeChar = c;
563     else { // escape sequence is complete ...
564     if (kbdPrevEscapeChar == 91 && c == 65) // up key
565     previousCommand();
566     else if (kbdPrevEscapeChar == 91 && c == 66) // down key
567     nextCommand();
568 schoenebeck 2531 else if (kbdPrevEscapeChar == 91 && c == 68) // left key
569     g_client->send(2); // custom usage of this ASCII code
570     else if (kbdPrevEscapeChar == 91 && c == 67) // right key
571     g_client->send(3); // custom usage of this ASCII code
572 schoenebeck 2518 }
573     continue; // don't send this escape sequence character to LSCP server
574     } else if (c == KBD_ESCAPE) { // escape sequence for special keys expected next ...
575     iKbdEscapeCharsExpected = 2;
576     continue; // don't send ESC character to LSCP server
577     } else if (c == KBD_BACKSPACE) {
578 schoenebeck 2528 c = '\b';
579 schoenebeck 2516 } else if (c == '\t') { // auto completion ...
580     autoComplete();
581     continue; // don't send tab character to LSCP server
582 schoenebeck 2518 } else if (c == '\n') {
583     storeCommandInHistory(g_goodPortion + g_badPortion);
584 schoenebeck 2515 }
585    
586 schoenebeck 2517 g_client->send(c);
587 schoenebeck 2515 }
588     }
589    
590 schoenebeck 2535 // Application is going to exit (due to user request or server
591     // disconnection). Clean up everything ...
592     std::cout << std::endl << std::flush;
593     if (g_client) delete g_client;
594     if (g_keyboardReader) delete g_keyboardReader;
595    
596     return g_exitCode;
597 schoenebeck 2515 }

  ViewVC Help
Powered by ViewVC