File indexing completed on 2024-10-06 09:24:51
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 }