File indexing completed on 2024-04-21 11:13:54

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2014 Lucas Hermann Negri <lucashnegri@gmail.com>
0004     SPDX-FileCopyrightText: 2023 Alexander Semke <alexander.semke@web.de>
0005 */
0006 
0007 #include "luasession.h"
0008 #include "luaexpression.h"
0009 #include "luacompletionobject.h"
0010 #include "luahighlighter.h"
0011 #include "luahelper.h"
0012 #include <settings.h>
0013 #include "ui_settings.h"
0014 
0015 #ifndef Q_OS_WIN
0016 #include <signal.h>
0017 #endif
0018 
0019 #include <QProcess>
0020 
0021 const QString LuaSession::LUA_PROMPT = QLatin1String("> ");
0022 const QString LuaSession::LUA_SUBPROMPT = QLatin1String(">> ");
0023 
0024 LuaSession::LuaSession(Cantor::Backend* backend) : Session(backend) {
0025 }
0026 
0027 LuaSession::~LuaSession()
0028 {
0029     if (m_process)
0030     {
0031         m_process->kill();
0032         m_process->deleteLater();
0033         m_process = nullptr;
0034     }
0035 }
0036 
0037 void LuaSession::login()
0038 {
0039     emit loginStarted();
0040 
0041     m_process = new QProcess(this);
0042 
0043     const auto& path = LuaSettings::self()->path().toLocalFile();
0044     QFileInfo fi(path);
0045     if (fi.baseName() != QLatin1String("luajit"))
0046         m_luaJIT = false;
0047 
0048     m_process->setProgram(path);
0049     m_process->setArguments(QStringList() << QLatin1String("-i"));
0050     m_process->setProcessChannelMode(QProcess::SeparateChannels);
0051 
0052     connect(m_process, &QProcess::readyReadStandardOutput, this, &LuaSession::readIntroMessage);
0053     connect(m_process, &QProcess::started, this, &LuaSession::processStarted);
0054     m_process->start();
0055     m_process->waitForStarted();
0056     m_process->waitForReadyRead();
0057 
0058     // TODO: load the auto-scripts
0059 
0060     // we need this for tab completion
0061     m_L = luaL_newstate();
0062     luaL_openlibs(m_L);
0063 
0064     changeStatus(Cantor::Session::Done);
0065     emit loginDone();
0066 }
0067 
0068 bool LuaSession::isLuaJIT() const
0069 {
0070     return m_luaJIT;
0071 }
0072 
0073 void LuaSession::readIntroMessage()
0074 {
0075     QString output;
0076     while(m_process->bytesAvailable())
0077         output += QString::fromLocal8Bit(m_process->readLine()) + QLatin1String("\n");
0078 
0079     if(!output.isEmpty() && output.trimmed().endsWith(QLatin1String(">"))) {
0080         qDebug() << " reading the intro message " << output ;
0081 
0082         disconnect(m_process, &QProcess::readyReadStandardOutput, this , &LuaSession::readIntroMessage);
0083         connect(m_process, &QProcess::readyReadStandardError, this, &LuaSession::readError);
0084         connect(m_process, &QProcess::readyReadStandardOutput, this, &LuaSession::readOutput);
0085     }
0086 }
0087 
0088 void LuaSession::readOutput()
0089 {
0090     if (m_luaJIT)
0091         readOutputLuaJIT();
0092     else
0093         readOutputLua();
0094 }
0095 
0096 void LuaSession::readOutputLuaJIT()
0097 {
0098     QString output;
0099     while(m_process->bytesAvailable()) {
0100         QString line = QString::fromLocal8Bit(m_process->readLine());
0101         if (line.endsWith(QLatin1String("\n")))
0102             line.chop(1);
0103 
0104         // join multiple lines with Lua's promt so we can parse the lines as the separate results later
0105         if (!output.isEmpty())
0106             output += QLatin1String("> ");
0107         output += line;
0108     }
0109 
0110     if (m_lastExpression)
0111         m_lastExpression->parseOutput(output);
0112 }
0113 
0114 void LuaSession::readOutputLua()
0115 {
0116     /*
0117      * parse the output
0118      * clear all the garbage
0119      * set it as output
0120     */
0121     // keep reading till the output ends with '>'.
0122     // '>' marks the end of output for a particular command;
0123     while(m_process->bytesAvailable()) {
0124         QString line = QString::fromLocal8Bit(m_process->readLine());
0125         if (line.endsWith(QLatin1String("\n")))
0126             line.chop(1);
0127         m_output.append(line);
0128     }
0129 
0130     qDebug()<<"parsing the initial output of Lua " << m_output;
0131 
0132     if (expressionQueue().size() > 0)
0133     {
0134         // How parsing works:
0135         // For each line of input command, which for example we name as X
0136         // lua prints output in this form (common form, 90% of output)
0137         // X + "\n" + command_output + "\n" + "> " or ">> " + "\n"
0138         // or (merged form, rare, only 10% of output
0139         // X + "\n" + command_output + "\n" + ("> " or ">> " in beginning of next X)
0140         // Sometimes empty lines also apears in command output
0141 
0142         // In this realisation we iterate over input lines and output line and copy only output lines
0143         int input_idx = 0;
0144         int previous_output_idx = 0;
0145         int output_idx = 0;
0146         QString output;
0147         // qDebug() << "m_inputCommands" << m_inputCommands;
0148         while (output_idx < m_output.size() && input_idx < m_inputCommands.size())
0149         {
0150             const QString& line = m_output[output_idx];
0151             bool previousLineIsPrompt = (output_idx >= 1 ? isPromptString(m_output[output_idx-1]) : true);
0152             bool commonInputMatch = (line == m_inputCommands[input_idx] && previousLineIsPrompt);
0153             bool mergedInputMatch = (line == LUA_PROMPT + m_inputCommands[input_idx] || line == LUA_SUBPROMPT + m_inputCommands[input_idx]);
0154 
0155             if (commonInputMatch || mergedInputMatch)
0156             {
0157                 if (previous_output_idx + 1 < output_idx)
0158                 {
0159                     for (int i = previous_output_idx+1; i < output_idx; i++)
0160                     {
0161                         const QString& copiedLine = m_output[i];
0162                         bool isLastLine = i == output_idx-1;
0163                         if (!(isLastLine && previousLineIsPrompt))
0164                             output += copiedLine + QLatin1String("\n");
0165                     }
0166                 }
0167                 previous_output_idx = output_idx;
0168                 input_idx++;
0169             }
0170             output_idx++;
0171         }
0172 
0173         if (input_idx == m_inputCommands.size() && m_output[m_output.size()-1] == LUA_PROMPT)
0174         {
0175             //We parse all output, so copy tail of the output and pass it to lua expression
0176             for (int i = previous_output_idx+1; i < m_output.size()-1; i++)
0177             {
0178                 const QString& copiedLine = m_output[i];
0179                 bool isLastLine = i == m_output.size()-1;
0180                 if (isLastLine)
0181                     output += copiedLine;
0182                 else
0183                     output += copiedLine + QLatin1String("\n");
0184             }
0185 
0186             if (m_lastExpression)
0187                 m_lastExpression->parseOutput(output);
0188             m_output.clear();
0189         }
0190     }
0191 }
0192 
0193 bool LuaSession::isPromptString(const QString& s)
0194 {
0195     return s == LUA_PROMPT || s == LUA_SUBPROMPT;
0196 }
0197 
0198 void LuaSession::readError()
0199 {
0200     qDebug() << "readError";
0201     if (!m_lastExpression)
0202         return;
0203 
0204     QString error = QString::fromLocal8Bit(m_process->readAllStandardError());
0205     m_lastExpression->parseError(error);
0206 }
0207 
0208 void LuaSession::processStarted()
0209 {
0210     qDebug() << m_process->program() << " pid   " << m_process->processId() << "  started";
0211 }
0212 
0213 void LuaSession::logout()
0214 {
0215     if (!m_process)
0216         return;
0217 
0218     if(status() == Cantor::Session::Running)
0219         interrupt();
0220 
0221     m_process->kill();
0222     m_process->deleteLater();
0223     m_process = nullptr;
0224 
0225     Session::logout();
0226 }
0227 
0228 void LuaSession::interrupt()
0229 {
0230     if(!expressionQueue().isEmpty())
0231     {
0232         qDebug()<<"interrupting " << expressionQueue().first()->command();
0233         if(m_process && m_process->state() != QProcess::NotRunning)
0234         {
0235 #ifndef Q_OS_WIN
0236             const int pid = m_process->processId();
0237             kill(pid, SIGINT);
0238 #else
0239             ; //TODO: interrupt the process on windows
0240 #endif
0241         }
0242 
0243         for (auto* expression : expressionQueue())
0244             expression->setStatus(Cantor::Expression::Interrupted);
0245         expressionQueue().clear();
0246     }
0247 
0248     changeStatus(Cantor::Session::Done);
0249 }
0250 
0251 Cantor::Expression* LuaSession::evaluateExpression(const QString& cmd, Cantor::Expression::FinishingBehavior behave, bool internal)
0252 {
0253     changeStatus(Cantor::Session::Running);
0254 
0255     auto* expression = new LuaExpression(this, internal);
0256 
0257     expression->setFinishingBehavior(behave);
0258     expression->setCommand(cmd);
0259     expression->evaluate();
0260 
0261     return expression;
0262 }
0263 
0264 void LuaSession::runFirstExpression()
0265 {
0266     // in the error case LuaJIT sends the prompt with the empty output to stdout _and_ the error message to stderr after this.
0267     // here we need to remember the expression since the queue is already empty when readError() is called.
0268     m_lastExpression = static_cast<LuaExpression*>(expressionQueue().first());
0269 
0270     connect(m_lastExpression, &Cantor::Expression::statusChanged, this, &LuaSession::expressionFinished);
0271     QString command = m_lastExpression->internalCommand();
0272 
0273     m_inputCommands = command.split(QLatin1String("\n"));
0274     m_output.clear();
0275 
0276     command += QLatin1String("\n");
0277     qDebug() << "final command to be executed " << command;
0278 
0279     m_lastExpression->setStatus(Cantor::Expression::Computing);
0280     m_process->write(command.toLocal8Bit());
0281 }
0282 
0283 Cantor::CompletionObject* LuaSession::completionFor(const QString& command, int index)
0284 {
0285     return new LuaCompletionObject(command, index, this);
0286 }
0287 
0288 void LuaSession::expressionFinished(Cantor::Expression::Status status)
0289 {
0290     switch(status)
0291     {
0292         case Cantor::Expression::Done:
0293         case Cantor::Expression::Error:
0294             finishFirstExpression();
0295             break;
0296         default:
0297             break;
0298     }
0299 }
0300 
0301 QSyntaxHighlighter* LuaSession::syntaxHighlighter(QObject* parent)
0302 {
0303     return new LuaHighlighter(parent);
0304 }
0305 
0306 lua_State* LuaSession::getState() const
0307 {
0308     return m_L;
0309 }