/[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 2516 - (hide annotations) (download)
Thu Feb 6 21:11:23 2014 UTC (10 years, 2 months 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 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     #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 schoenebeck 2516 static String g_goodPortion;
34     static String g_badPortion;
35     static String g_suggestedPortion;
36 schoenebeck 2515
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 schoenebeck 2516 cout << " --no-auto-correct Don't perform auto correction of obvious syntax errors." << endl;
47     cout << endl;
48 schoenebeck 2515 }
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 schoenebeck 2516 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 schoenebeck 2515 int main(int argc, char *argv[]) {
73     String host = LSCP_DEFAULT_HOST;
74     int port = LSCP_DEFAULT_PORT;
75 schoenebeck 2516 bool autoCorrect = true;
76 schoenebeck 2515
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 schoenebeck 2516 } else if (s == "--no-auto-correct") {
97     autoCorrect = false;
98 schoenebeck 2515 } 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 schoenebeck 2516 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 schoenebeck 2515 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 schoenebeck 2516 // 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 schoenebeck 2515
147 schoenebeck 2516 // 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 schoenebeck 2515
154 schoenebeck 2516 // 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 schoenebeck 2515 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 schoenebeck 2516 cfmt.bold().yellow();
189     cout << sSuggest << flush;
190    
191     cursor.toColumn(cursorColumn);
192 schoenebeck 2515 }
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 schoenebeck 2516 } else if (c == '\t') { // auto completion ...
238     autoComplete();
239     continue; // don't send tab character to LSCP server
240 schoenebeck 2515 } 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