30 |
static LSCPClient g_client; |
static LSCPClient g_client; |
31 |
static KeyboardReader g_keyboardReader; |
static KeyboardReader g_keyboardReader; |
32 |
static Condition g_todo; |
static Condition g_todo; |
33 |
|
static String g_goodPortion; |
34 |
|
static String g_badPortion; |
35 |
|
static String g_suggestedPortion; |
36 |
|
|
37 |
static void printUsage() { |
static void printUsage() { |
38 |
cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl; |
cout << "lscp - The LinuxSampler Control Protocol (LSCP) Shell." << endl; |
43 |
cout << endl; |
cout << endl; |
44 |
cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl; |
cout << " -p TCP port number of LSCP server (default " << LSCP_DEFAULT_PORT << ")." << endl; |
45 |
cout << endl; |
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 |
// Called by the network reading thread, whenever new data arrived from the |
58 |
g_todo.Set(true); |
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[]) { |
int main(int argc, char *argv[]) { |
73 |
String host = LSCP_DEFAULT_HOST; |
String host = LSCP_DEFAULT_HOST; |
74 |
int port = LSCP_DEFAULT_PORT; |
int port = LSCP_DEFAULT_PORT; |
75 |
|
bool autoCorrect = true; |
76 |
|
|
77 |
// parse command line arguments |
// parse command line arguments |
78 |
for (int i = 0; i < argc; ++i) { |
for (int i = 0; i < argc; ++i) { |
93 |
cerr << "Error: invalid port argument \"" << argv[i] << "\"\n"; |
cerr << "Error: invalid port argument \"" << argv[i] << "\"\n"; |
94 |
return -1; |
return -1; |
95 |
} |
} |
96 |
|
} else if (s == "--no-auto-correct") { |
97 |
|
autoCorrect = false; |
98 |
} else if (s[0] == '-') { // invalid / unknown command line argument ... |
} else if (s[0] == '-') { // invalid / unknown command line argument ... |
99 |
printUsage(); |
printUsage(); |
100 |
return -1; |
return -1; |
105 |
// receiving incoming network data from the sampler's LSCP server |
// receiving incoming network data from the sampler's LSCP server |
106 |
g_client.setCallback(onLSCPClientNewInputAvailable); |
g_client.setCallback(onLSCPClientNewInputAvailable); |
107 |
if (!g_client.connect(host, port)) return -1; |
if (!g_client.connect(host, port)) return -1; |
108 |
String sResponse = g_client.sendCommandSync("SET SHELL INTERACT 1"); |
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") { |
if (sResponse.substr(0, 2) != "OK") { |
113 |
cerr << "Error: sampler too old, it does not support shell instructions\n"; |
cerr << "Error: sampler too old, it does not support shell instructions\n"; |
114 |
return -1; |
return -1; |
138 |
if (res >= 1) { |
if (res >= 1) { |
139 |
String s = line.substr(n); |
String s = line.substr(n); |
140 |
|
|
141 |
String key = LSCP_SHK_GOOD_FRONT; |
// extract portion that is already syntactically correct |
142 |
size_t i = s.find(key); |
size_t iGood = s.find(LSCP_SHK_GOOD_FRONT); |
143 |
String sGood = s.substr(0, i); |
String sGood = s.substr(0, iGood); |
144 |
String sBad = s.substr(i + key.length()); |
if (sGood.find(LSCP_SHK_CURSOR) != string::npos) |
145 |
//printf("line '%s' good='%s' bad='%s'\n", line.c_str(), sGood.c_str(), sBad.c_str()); |
sGood.erase(sGood.find(LSCP_SHK_CURSOR), strlen(LSCP_SHK_CURSOR)); // erase cursor marker |
146 |
|
|
147 |
CCursor cursor = CCursor::now(); |
// extract portion that was written syntactically incorrect |
148 |
cursor.toColumn(0); |
String sBad = s.substr(iGood + strlen(LSCP_SHK_GOOD_FRONT)); |
149 |
cursor.clearLine(); |
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; |
CFmt cfmt; |
183 |
if (code == LSCP_SHU_COMPLETE) cfmt.bold().green(); |
if (code == LSCP_SHU_COMPLETE) cfmt.bold().green(); |
185 |
cout << sGood << flush; |
cout << sGood << flush; |
186 |
cfmt.reset().red(); |
cfmt.reset().red(); |
187 |
cout << sBad << flush; |
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 ... |
} else if (line.substr(0,2) == "OK") { // single-line response expected ... |
194 |
cout << endl << flush; |
cout << endl << flush; |
234 |
if (c == KBD_BACKSPACE) { |
if (c == KBD_BACKSPACE) { |
235 |
cout << "\b \b" << flush; |
cout << "\b \b" << flush; |
236 |
c = '\b'; |
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 |
} 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; |
cout << c << flush; |
242 |
} |
} |