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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2516 - (show annotations) (download)
Thu Feb 6 21:11:23 2014 UTC (10 years, 1 month ago) by schoenebeck
File size: 9878 byte(s)
* WIP: LSCP Shell: implemented support for auto-correction of       
  obvious and trivial LSCP syntax mistakes, support for
  auto-completion by tab key and visual completion suggestion
  while typing.
* Bumped version (1.0.0.svn29).

1 /*
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 #include "LSCPClient.h"
16 #include "KeyboardReader.h"
17 #include "TerminalCtrl.h"
18 #include "CFmt.h"
19 #include "CCursor.h"
20
21 #include "../common/global.h"
22 #include "../common/Condition.h"
23
24 #define LSCP_DEFAULT_HOST "localhost"
25 #define LSCP_DEFAULT_PORT 8888
26
27 using namespace std;
28 using namespace LinuxSampler;
29
30 static LSCPClient g_client;
31 static KeyboardReader g_keyboardReader;
32 static Condition g_todo;
33 static String g_goodPortion;
34 static String g_badPortion;
35 static String g_suggestedPortion;
36
37 static void printUsage() {
38 cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl;
39 cout << endl;
40 cout << "Usage: lscp [-h HOSTNAME] [-p PORT]" << endl;
41 cout << endl;
42 cout << " -h Host name of LSCP server (default \"" << LSCP_DEFAULT_HOST << "\")." << endl;
43 cout << endl;
44 cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl;
45 cout << endl;
46 cout << " --no-auto-correct Don't perform auto correction of obvious syntax errors." << endl;
47 cout << endl;
48 }
49
50 // Called by the network reading thread, whenever new data arrived from the
51 // network connection.
52 static void onLSCPClientNewInputAvailable(LSCPClient* client) {
53 g_todo.Set(true);
54 }
55
56 // Called by the keyboard reading thread, whenever a key stroke was received.
57 static void onNewKeyboardInputAvailable(KeyboardReader* reader) {
58 g_todo.Set(true);
59 }
60
61 static void autoComplete() {
62 if (g_suggestedPortion.empty()) return;
63 String s;
64 // let the server delete mistaken characters first
65 for (int i = 0; i < g_badPortion.size(); ++i) s += '\b';
66 // now add the suggested, correct characters
67 s += g_suggestedPortion;
68 g_suggestedPortion.clear();
69 g_client.send(s);
70 }
71
72 int main(int argc, char *argv[]) {
73 String host = LSCP_DEFAULT_HOST;
74 int port = LSCP_DEFAULT_PORT;
75 bool autoCorrect = true;
76
77 // parse command line arguments
78 for (int i = 0; i < argc; ++i) {
79 String s = argv[i];
80 if (s == "-h" || s == "--host") {
81 if (++i >= argc) {
82 printUsage();
83 return -1;
84 }
85 host = argv[i];
86 } else if (s == "-p" || s == "--port") {
87 if (++i >= argc) {
88 printUsage();
89 return -1;
90 }
91 port = atoi(argv[i]);
92 if (port <= 0) {
93 cerr << "Error: invalid port argument \"" << argv[i] << "\"\n";
94 return -1;
95 }
96 } else if (s == "--no-auto-correct") {
97 autoCorrect = false;
98 } else if (s[0] == '-') { // invalid / unknown command line argument ...
99 printUsage();
100 return -1;
101 }
102 }
103
104 // try to connect to the sampler's LSCP server and start a thread for
105 // receiving incoming network data from the sampler's LSCP server
106 g_client.setCallback(onLSCPClientNewInputAvailable);
107 if (!g_client.connect(host, port)) return -1;
108 String sResponse = g_client.sendCommandSync(
109 (autoCorrect) ? "SET SHELL AUTO_CORRECT 1" : "SET SHELL AUTO_CORRECT 0"
110 );
111 sResponse = g_client.sendCommandSync("SET SHELL INTERACT 1");
112 if (sResponse.substr(0, 2) != "OK") {
113 cerr << "Error: sampler too old, it does not support shell instructions\n";
114 return -1;
115 }
116
117 // start a thread for reading from the local text input keyboard
118 // (keyboard echo will be disabled as well to have a clean control on what
119 // is appearing on the screen)
120 g_keyboardReader.setCallback(onNewKeyboardInputAvailable);
121 g_keyboardReader.startReading();
122
123 // main thread's loop
124 while (true) {
125 // sleep until either new data from the network or from keyboard arrived
126 g_todo.WaitIf(false);
127 // immediately unset the condition variable and unlock it
128 g_todo.Set(false);
129 g_todo.Unlock();
130
131 // did network data arrive?
132 while (g_client.messageComplete()) {
133 String line = *g_client.popLine();
134 //printf("line '%s'\n", line.c_str());
135 if (line.substr(0,4) == "SHU:") {
136 int code = 0, n = 0;
137 int res = sscanf(line.c_str(), "SHU:%d:%n", &code, &n);
138 if (res >= 1) {
139 String s = line.substr(n);
140
141 // extract portion that is already syntactically correct
142 size_t iGood = s.find(LSCP_SHK_GOOD_FRONT);
143 String sGood = s.substr(0, iGood);
144 if (sGood.find(LSCP_SHK_CURSOR) != string::npos)
145 sGood.erase(sGood.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
146
147 // extract portion that was written syntactically incorrect
148 String sBad = s.substr(iGood + strlen(LSCP_SHK_GOOD_FRONT));
149 if (sBad.find(LSCP_SHK_CURSOR) != string::npos)
150 sBad.erase(sBad.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
151 if (sBad.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
152 sBad.erase(sBad.find(LSCP_SHK_SUGGEST_BACK)); // erase auto suggestion portion
153
154 // extract portion that is suggested for auto completion
155 String sSuggest;
156 if (s.find(LSCP_SHK_SUGGEST_BACK) != string::npos) {
157 sSuggest = s.substr(s.find(LSCP_SHK_SUGGEST_BACK) + strlen(LSCP_SHK_SUGGEST_BACK));
158 if (sSuggest.find(LSCP_SHK_CURSOR) != string::npos)
159 sSuggest.erase(sSuggest.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker
160 }
161
162 // extract current cursor position
163 int cursorColumn = sGood.size();
164 String sCursor = s;
165 if (sCursor.find(LSCP_SHK_GOOD_FRONT) != string::npos)
166 sCursor.erase(sCursor.find(LSCP_SHK_GOOD_FRONT), strlen(LSCP_SHK_GOOD_FRONT)); // erase good/bad marker
167 if (sCursor.find(LSCP_SHK_SUGGEST_BACK) != string::npos)
168 sCursor.erase(sCursor.find(LSCP_SHK_SUGGEST_BACK), strlen(LSCP_SHK_SUGGEST_BACK)); // erase suggestion marker
169 if (sCursor.find(LSCP_SHK_CURSOR) != string::npos)
170 cursorColumn = sCursor.find(LSCP_SHK_CURSOR);
171
172 // store those informations globally for the auto-completion
173 // feature
174 g_goodPortion = sGood;
175 g_badPortion = sBad;
176 g_suggestedPortion = sSuggest;
177
178 //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);
179
180 CCursor cursor = CCursor::now().toColumn(0).clearLine();
181
182 CFmt cfmt;
183 if (code == LSCP_SHU_COMPLETE) cfmt.bold().green();
184 else cfmt.bold().white();
185 cout << sGood << flush;
186 cfmt.reset().red();
187 cout << sBad << flush;
188 cfmt.bold().yellow();
189 cout << sSuggest << flush;
190
191 cursor.toColumn(cursorColumn);
192 }
193 } else if (line.substr(0,2) == "OK") { // single-line response expected ...
194 cout << endl << flush;
195 CFmt cfmt;
196 cfmt.green();
197 cout << line.substr(0,2) << flush;
198 cfmt.reset();
199 cout << line.substr(2) << endl << flush;
200 } else if (line.substr(0,3) == "WRN") { // single-line response expected ...
201 cout << endl << flush;
202 CFmt cfmt;
203 cfmt.yellow();
204 cout << line.substr(0,3) << flush;
205 cfmt.reset();
206 cout << line.substr(3) << endl << flush;
207 } else if (line.substr(0,3) == "ERR") { // single-line response expected ...
208 cout << endl << flush;
209 CFmt cfmt;
210 cfmt.bold().red();
211 cout << line.substr(0,3) << flush;
212 cfmt.reset();
213 cout << line.substr(3) << endl << flush;
214 } else if (g_client.multiLine()) { // multi-line response expected ...
215 cout << endl << flush;
216 while (true) {
217 cout << line << endl << flush;
218 if (line.substr(0, 1) == ".") break;
219 if (!g_client.lineAvailable()) break;
220 line = *g_client.popLine();
221 }
222 } else {
223 cout << endl << line << endl << flush;
224 }
225 }
226
227 // did keyboard input arrive?
228 while (g_keyboardReader.charAvailable()) {
229 char c = g_keyboardReader.popChar();
230
231 CFmt cfmt;
232 cfmt.white();
233 //std::cout << c << "(" << int(c) << ")" << std::endl << std::flush;
234 if (c == KBD_BACKSPACE) {
235 cout << "\b \b" << flush;
236 c = '\b';
237 } else if (c == '\t') { // auto completion ...
238 autoComplete();
239 continue; // don't send tab character to LSCP server
240 } else if (c != '\n') { // don't apply RETURN stroke yet, since the typed command might still be corrected by the sampler
241 cout << c << flush;
242 }
243
244 g_client.send(c);
245 }
246 }
247
248 return 0;
249 }

  ViewVC Help
Powered by ViewVC