Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / editors / sc-ide / core / sc_process.cpp
blobb667b970c570eb84efc2078f42c84e98599cdd3c
1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <QCoreApplication>
22 #include <QBuffer>
24 #include "SC_DirUtils.h"
26 #include "main.hpp"
27 #include "main_window.hpp"
28 #include "sc_introspection.hpp"
29 #include "sc_process.hpp"
30 #include "sc_server.hpp"
31 #include "settings/manager.hpp"
33 #include "yaml-cpp/node.h"
34 #include "yaml-cpp/parser.h"
36 namespace ScIDE {
38 SCProcess::SCProcess( Main *parent, ScResponder * responder, Settings::Manager * settings ):
39 QProcess( parent ),
40 mIpcServer( new QLocalServer(this) ),
41 mIpcSocket(NULL),
42 mIpcServerName("SCIde_" + QString::number(QCoreApplication::applicationPid()))
44 mIntrospectionParser = new ScIntrospectionParser( responder, this );
45 mIntrospectionParser->start();
47 prepareActions(settings);
49 connect(this, SIGNAL( readyRead() ),
50 this, SLOT( onReadyRead() ));
51 connect(mIpcServer, SIGNAL(newConnection()), this, SLOT(onNewIpcConnection()));
52 connect(mIntrospectionParser, SIGNAL(done(ScLanguage::Introspection*)),
53 this, SLOT(swapIntrospection(ScLanguage::Introspection*)));
56 void SCProcess::prepareActions(Settings::Manager * settings)
58 QAction * action;
59 mActions[StartSCLang] = action = new QAction(
60 QIcon::fromTheme("system-run"), tr("Start SCLang"), this);
61 connect(action, SIGNAL(triggered()), this, SLOT(startLanguage()) );
63 mActions[RecompileClassLibrary] = action = new QAction(
64 QIcon::fromTheme("system-reboot"), tr("Recompile Class Library"), this);
65 action->setShortcut(tr("Ctrl+Shift+l", "Recompile Class Library)"));
66 connect(action, SIGNAL(triggered()), this, SLOT(recompileClassLibrary()) );
68 mActions[StopSCLang] = action = new QAction(
69 QIcon::fromTheme("system-shutdown"), tr("Stop SCLang"), this);
70 connect(action, SIGNAL(triggered()), this, SLOT(stopLanguage()) );
72 mActions[RestartSCLang] = action = new QAction(
73 QIcon::fromTheme("system-reboot"), tr("Restart SCLang"), this);
74 connect(action, SIGNAL(triggered()), this, SLOT(restartLanguage()) );
76 mActions[RunMain] = action = new QAction(
77 QIcon::fromTheme("media-playback-start"), tr("Run Main"), this);
78 connect(action, SIGNAL(triggered()), this, SLOT(runMain()));
80 mActions[StopMain] = action = new QAction(
81 QIcon::fromTheme("process-stop"), tr("Stop Main"), this);
82 action->setShortcut(tr("Ctrl+.", "Stop Main (a.k.a. cmd-period)"));
83 connect(action, SIGNAL(triggered()), this, SLOT(stopMain()));
85 for (int i = 0; i < SCProcessActionCount; ++i)
86 settings->addAction( mActions[i] );
89 void SCProcess::startLanguage (void)
91 if (state() != QProcess::NotRunning) {
92 statusMessage("Interpreter is already running.");
93 return;
96 Settings::Manager *settings = Main::settings();
97 settings->beginGroup("IDE/interpreter");
99 QString workingDirectory = settings->value("runtimeDir").toString();
100 QString configFile = settings->value("configFile").toString();
102 settings->endGroup();
104 QString sclangCommand;
105 #ifdef Q_OS_MAC
106 char resourcePath[PATH_MAX];
107 sc_GetResourceDirectory(resourcePath, PATH_MAX);
109 sclangCommand = QString(resourcePath) + "/sclang";
110 #else
111 sclangCommand = "sclang";
112 #endif
114 QStringList sclangArguments;
115 if(!configFile.isEmpty())
116 sclangArguments << "-l" << configFile;
117 sclangArguments << "-i" << "scqt";
119 if(!workingDirectory.isEmpty())
120 setWorkingDirectory(workingDirectory);
122 QProcess::start(sclangCommand, sclangArguments);
123 bool processStarted = QProcess::waitForStarted();
124 if (!processStarted) {
125 emit statusMessage(tr("Failed to start interpreter!"));
126 } else
127 onSclangStart();
130 void SCProcess::recompileClassLibrary (void)
132 if(state() != QProcess::Running) {
133 emit statusMessage("Interpreter is not running!");
134 return;
137 write("\x18");
141 void SCProcess::stopLanguage (void)
143 if(state() != QProcess::Running) {
144 emit statusMessage("Interpreter is not running!");
145 return;
148 closeWriteChannel();
150 bool finished = waitForFinished(200);
151 if ( !finished && (state() != QProcess::NotRunning) ) {
152 #ifdef Q_OS_WIN32
153 kill();
154 #else
155 terminate();
156 #endif
157 bool reallyFinished = waitForFinished(200);
158 if (!reallyFinished)
159 emit statusMessage(tr("Failed to stop interpreter!"));
163 void SCProcess::restartLanguage()
165 stopLanguage();
166 startLanguage();
171 void SCProcess::onReadyRead(void)
173 QByteArray out = QProcess::readAll();
174 QString postString = QString::fromUtf8(out);
175 emit scPost(postString);
178 void SCProcess::evaluateCode(QString const & commandString, bool silent)
180 if(state() != QProcess::Running) {
181 emit statusMessage("Interpreter is not running!");
182 return;
185 QByteArray bytesToWrite = commandString.toUtf8();
186 size_t writtenBytes = write(bytesToWrite);
187 if (writtenBytes != bytesToWrite.size()) {
188 emit statusMessage("Error when passing data to interpreter!");
189 return;
192 char commandChar = silent ? '\x1b' : '\x0c';
194 write( &commandChar, 1 );
197 void SCProcess::onNewIpcConnection()
199 if (mIpcSocket)
200 // we can handle only one ipc connection at a time
201 mIpcSocket->disconnect();
203 mIpcSocket = mIpcServer->nextPendingConnection();
204 connect(mIpcSocket, SIGNAL(disconnected()), this, SLOT(finalizeConnection()));
205 connect(mIpcSocket, SIGNAL(readyRead()), this, SLOT(onIpcData()));
208 void SCProcess::finalizeConnection()
210 mIpcData.clear();
211 mIpcSocket->deleteLater();
212 mIpcSocket = NULL;
215 void SCProcess::onIpcData()
217 mIpcData.append(mIpcSocket->readAll());
219 QBuffer receivedData ( &mIpcData );
220 receivedData.open ( QIODevice::ReadOnly );
222 QDataStream in ( &receivedData );
223 in.setVersion ( QDataStream::Qt_4_6 );
224 QString id, message;
225 in >> id;
226 if ( in.status() != QDataStream::Ok )
227 return;
229 in >> message;
230 if ( in.status() != QDataStream::Ok )
231 return;
233 mIpcData.remove ( 0, receivedData.pos() );
235 emit response(id, message);
238 void SCProcess::onSclangStart()
240 if(!mIpcServer->isListening()) // avoid a warning on stderr
241 mIpcServer->listen(mIpcServerName);
243 QString command = QString("ScIDE.connect(\"%1\")").arg(mIpcServerName);
244 evaluateCode ( command, true );
245 sendActiveDocument();
248 void SCProcess::setActiveDocument(Document * document)
250 if (document)
251 mCurrentDocumentPath = document->filePath();
252 else
253 mCurrentDocumentPath.clear();
255 sendActiveDocument();
258 void SCProcess::sendActiveDocument()
260 if (state() != QProcess::Running)
261 return;
263 if (!mCurrentDocumentPath.isEmpty())
264 evaluateCode(QString("ScIDE.currentPath_(\"%1\")").arg(mCurrentDocumentPath), true);
265 else
266 evaluateCode(QString("ScIDE.currentPath_(nil)"), true);
269 void ScResponder::onResponse( const QString & selector, const QString & data )
271 static QString defaultServerRunningChangedSymbol("defaultServerRunningChanged");
272 static QString introspectionSymbol("introspection");
273 static QString requestCurrentPathSymbol("requestCurrentPath");
274 static QString openFileSymbol("openFile");
275 static QString classLibraryRecompiled("classLibraryRecompiled");
277 if (selector == defaultServerRunningChangedSymbol)
278 handleServerRunningChanged(data);
280 else if (selector == introspectionSymbol)
281 emit newIntrospectionData(data);
283 else if (selector == requestCurrentPathSymbol)
284 Main::scProcess()->sendActiveDocument();
286 else if (selector == classLibraryRecompiled)
287 Main::scProcess()->emitClassLibraryRecompiled();
289 else if (selector == openFileSymbol)
290 handleOpenFile(data);
294 void ScResponder::handleOpenFile( const QString & data ) const
296 std::stringstream stream;
297 stream << data.toStdString();
298 YAML::Parser parser(stream);
300 YAML::Node doc;
301 if (parser.GetNextDocument(doc)) {
302 if (doc.Type() != YAML::NodeType::Sequence)
303 return;
305 std::string path;
306 bool success = doc[0].Read(path);
307 if (!success)
308 return;
310 int position = 0;
311 doc[1].Read(position);
313 int selectionLength = 0;
314 doc[2].Read(selectionLength);
316 Main::documentManager()->open(QString(path.c_str()), position, selectionLength);
320 void ScResponder::handleServerRunningChanged( const QString & data )
322 std::stringstream stream;
323 stream << data.toStdString();
324 YAML::Parser parser(stream);
326 bool serverRunningState;
327 std::string hostName;
328 int port;
330 YAML::Node doc;
331 while(parser.GetNextDocument(doc)) {
332 assert(doc.Type() == YAML::NodeType::Sequence);
334 bool success = doc[0].Read(serverRunningState);
335 if (!success) return; // LATER: report error?
337 success = doc[1].Read(hostName);
338 if (!success) return; // LATER: report error?
340 success = doc[2].Read(port);
341 if (!success) return; // LATER: report error?
344 emit serverRunningChanged (serverRunningState, QString(hostName.c_str()), port);
345 return;
348 void ScIntrospectionParserWorker::process(const QString &input)
350 try {
351 ScLanguage::Introspection *introspection = new ScLanguage::Introspection (input);
352 emit done(introspection);
353 } catch (std::exception & e) {
354 MainWindow::instance()->showStatusMessage(e.what());
358 } // namespace ScIDE