/* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder */ #include "maximasession.h" #include "maximaexpression.h" #include "maximatabcompletionobject.h" #include "maximasyntaxhelpobject.h" #include "maximahighlighter.h" #include #include #include #include #include #include #include #include #include "settings.h" #include "result.h" //NOTE: the \\s in the expressions is needed, because Maxima seems to sometimes insert newlines/spaces between the letters //maybe this is caused by some behaviour if the Prompt is split into multiple "readStdout" calls //the Expressions are encapsulated in () to allow capturing for the text const QRegExp MaximaSession::MaximaPrompt=QRegExp("(\\(\\s*%\\s*I\\s*[0-9\\s]*\\))"); //Text, maxima outputs, if it's taking new input const QRegExp MaximaSession::MaximaOutputPrompt=QRegExp("(\\(\\s*%\\s*O\\s*[0-9\\s]*\\))"); //Text, maxima outputs, before any output static QByteArray initCmd="display2d:false$ \n"\ "inchar:%I$ \n"\ "outchar:%O$ \n"\ "print(____END_OF_INIT____); \n"; static QByteArray helperInitCmd="simp: false$ \n"; MaximaSession::MaximaSession( Cantor::Backend* backend) : Session(backend) { kDebug(); m_isInitialized=false; m_isHelperReady=false; m_server=0; m_maxima=0; m_process=0; m_helperProcess=0; m_helperMaxima=0; m_justRestarted=false; m_useLegacy=false; } MaximaSession::~MaximaSession() { kDebug(); } void MaximaSession::login() { kDebug()<<"login"; if (m_process) m_process->deleteLater(); if(!m_server||!m_server->isListening()) startServer(); m_maxima=0; m_process=new KProcess(this); QStringList args; //TODO: these parameters may need tweaking to run on windows (see wxmaxima for hints) if(m_useLegacy) args<<"-r"<serverPort()); else args<<"-r"<serverPort()); m_process->setProgram(MaximaSettings::self()->path().toLocalFile(),args); m_process->start(); connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartMaxima())); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); if(!m_helperQueue.isEmpty()) runNextHelperCommand(); } void MaximaSession::startServer() { kDebug()<<"starting up maxima server"; const int defaultPort=4060; int port=defaultPort; m_server=new QTcpServer(this); connect(m_server, SIGNAL(newConnection()), this, SLOT(newConnection())); while(! m_server->listen(QHostAddress::LocalHost, port) ) { kDebug()<<"Could not listen to "<defaultPort+50) { KMessageBox::error(0, i18n("Could not start the server."), i18n("Error - Cantor")); return; } } kDebug()<<"got a server on "<write(initCmd); } void MaximaSession::newHelperClient(QTcpSocket* socket) { kDebug()<<"got new helper client"; m_helperMaxima=socket; connect(m_helperMaxima, SIGNAL(readyRead()), this, SLOT(readHelperOut())); m_helperMaxima->write(helperInitCmd); m_helperMaxima->write(initCmd); } void MaximaSession::logout() { kDebug()<<"logout"; if(!m_process||!m_maxima) return; disconnect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartMaxima())); if(m_expressionQueue.isEmpty()) { m_maxima->write("quit();\n"); m_maxima->flush(); //evaluateExpression("quit();", Cantor::Expression::DeleteOnFinish); } else { m_expressionQueue.clear(); } //Give maxima time to clean up kDebug()<<"waiting for maxima to finish"; if(m_process->state()!=QProcess::NotRunning) { if(!m_maxima->waitForDisconnected(3000)) { m_process->kill(); m_maxima->waitForDisconnected(3000); } } m_maxima->close(); kDebug()<<"done logging out"; delete m_process; m_process=0; delete m_helperProcess; m_helperProcess=0; delete m_helperMaxima; m_helperMaxima=0; delete m_maxima; m_maxima=0; kDebug()<<"destroyed maxima"; m_expressionQueue.clear(); } void MaximaSession::newConnection() { kDebug()<<"new connection"; QTcpSocket* const socket=m_server->nextPendingConnection(); if(m_maxima==0) { newMaximaClient(socket); }else if (m_helperMaxima==0) { newHelperClient(socket); }else { kDebug()<<"got another client, without needing one"; } } Cantor::Expression* MaximaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave) { kDebug()<<"evaluating: "<setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } MaximaExpression* MaximaSession::evaluateHelperExpression(const QString& cmd) { if(!m_helperMaxima) startHelperProcess(); MaximaExpression* expr=new MaximaExpression(this, MaximaExpression::HelpExpression); expr->setFinishingBehavior(Cantor::Expression::DoNotDelete); expr->setCommand(cmd); expr->evaluate(); return expr; } void MaximaSession::appendExpressionToQueue(MaximaExpression* expr) { m_expressionQueue.append(expr); kDebug()<<"queue: "<readAll(); kDebug()<<"out: "<parseOutput(txt); m_cache.remove(0, max); } } void MaximaSession::killLabels() { Cantor::Expression* e=evaluateExpression("kill(labels);", Cantor::Expression::DeleteOnFinish); connect(e, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SIGNAL(ready())); } void MaximaSession::reportProcessError(QProcess::ProcessError e) { kDebug()<<"process error"; if(e==QProcess::FailedToStart) { changeStatus(Cantor::Session::Done); emit error(i18n("Failed to start Maxima")); } } void MaximaSession::readHelperOut() { kDebug()<<"reading stdOut from helper process"; QString out=m_helperMaxima->readAll(); kDebug()<<"out: "<needsLatexResult(); expr->parseOutput(out); if(expr->type()==MaximaExpression::TexExpression&&!expr->needsLatexResult()) { kDebug()<<"expression doesn't need latex anymore"; m_helperQueue.removeFirst(); runNextHelperCommand(); } } } void MaximaSession::currentExpressionChangedStatus(Cantor::Expression::Status status) { if(status!=Cantor::Expression::Computing) //The session is ready for the next command { kDebug()<<"expression finished"; MaximaExpression* expression=m_expressionQueue.first(); disconnect(expression, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); if(expression->needsLatexResult()) { kDebug()<<"asking for tex version"; expression->setType(MaximaExpression::TexExpression); m_helperQueue<internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); if(command.isEmpty()) { kDebug()<<"empty command"; expr->forceDone(); }else { kDebug()<<"writing "<write((command+'\n').toLatin1()); } } } void MaximaSession::runNextHelperCommand() { kDebug()<<"helperQueue: "<type()==MaximaExpression::TexExpression) { QStringList out=expr->output(); if(!out.isEmpty()) { QString texCmd; foreach(const QString& part, out) { if(part.isEmpty()) continue; kDebug()<<"running "<write(texCmd.toUtf8()); }else { kDebug()<<"current tex request is empty, so drop it"; m_helperQueue.removeFirst(); } }else { QString command=expr->internalCommand(); connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentHelperExpressionChangedStatus(Cantor::Expression::Status))); if(command.isEmpty()) { kDebug()<<"empty command"; expr->forceDone(); }else { kDebug()<<"writing "<write((command+'\n').toLatin1()); } } } } void MaximaSession::interrupt() { if(!m_expressionQueue.isEmpty()) m_expressionQueue.first()->interrupt(); m_expressionQueue.clear(); changeStatus(Cantor::Session::Done); } void MaximaSession::interrupt(MaximaExpression* expr) { Q_ASSERT(!m_expressionQueue.isEmpty()); if(expr==m_expressionQueue.first()) { disconnect(m_maxima, 0); disconnect(expr, 0, this, 0); restartMaxima(); kDebug()<<"done interrupting"; }else { m_expressionQueue.removeAll(expr); } } void MaximaSession::sendInputToProcess(const QString& input) { kDebug()<<"WARNING: use this method only if you know what you're doing. Use evaluateExpression to run commands"; kDebug()<<"running "<write(input.toLatin1()); } void MaximaSession::restartMaxima() { kDebug()<<"restarting maxima cooldown: "<serverPort()); else args<<"-r"<serverPort()); m_helperProcess->setProgram(MaximaSettings::self()->path().toLocalFile(),args); connect(m_helperProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(startHelperProcess())); m_helperProcess->start(); } void MaximaSession::setTypesettingEnabled(bool enable) { if(enable) { if(!m_isHelperReady) startHelperProcess(); //LaTeX and Display2d don't go together and even deliver wrong results evaluateExpression("display2d:false", Cantor::Expression::DeleteOnFinish); } else if(m_helperProcess) { disconnect(m_helperProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(startHelperProcess())); m_helperProcess->deleteLater(); m_helperProcess=0; m_helperMaxima=0; m_isHelperReady=false; } Cantor::Session::setTypesettingEnabled(enable); } Cantor::TabCompletionObject* MaximaSession::tabCompletionFor(const QString& command) { return new MaximaTabCompletionObject(command, this); } Cantor::SyntaxHelpObject* MaximaSession::syntaxHelpFor(const QString& command) { return new MaximaSyntaxHelpObject(command, this); } QSyntaxHighlighter* MaximaSession::syntaxHighlighter(QTextEdit* parent) { return new MaximaHighlighter(parent); } #include "maximasession.moc"