File indexing completed on 2024-04-28 11:20:41
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com> 0004 SPDX-FileCopyrightText: 2023 Alexander Semke <alexander.semke@web.de> 0005 */ 0006 0007 #include "sageexpression.h" 0008 0009 #include "sagesession.h" 0010 #include "textresult.h" 0011 #include "imageresult.h" 0012 #include "animationresult.h" 0013 #include "helpresult.h" 0014 0015 #include <QDebug> 0016 #include <KLocalizedString> 0017 #include <QMimeDatabase> 0018 #include <QRegularExpression> 0019 0020 SageExpression::SageExpression( Cantor::Session* session, bool internal ) : Cantor::Expression(session, internal) 0021 { 0022 } 0023 0024 void SageExpression::evaluate() 0025 { 0026 m_imagePath.clear(); 0027 0028 m_isHelpRequest=false; 0029 0030 //check if this is a ?command or help command 0031 if( command().startsWith(QLatin1Char('?')) 0032 || command().endsWith(QLatin1Char('?')) 0033 || command().contains(QLatin1String("help(")) 0034 ) 0035 m_isHelpRequest = true; 0036 0037 //coun't how many newlines are in the command, 0038 //as sage will output one "sage: " or "....:" for 0039 //each. 0040 m_promptCount = command().count(QLatin1Char('\n'))+2; 0041 0042 session()->enqueueExpression(this); 0043 } 0044 0045 void SageExpression::parseOutput(const QString& text) 0046 { 0047 if (m_syntaxError) 0048 { 0049 setErrorMessage(i18n("Syntax Error")); 0050 setStatus(Cantor::Expression::Error); 0051 return; 0052 } 0053 0054 if (text.startsWith(QLatin1String("Launched png viewer")) 0055 || text.startsWith(QLatin1String("Launched pdf viewer")) 0056 || text.startsWith(QLatin1String("Launched gif viewer")) ) 0057 { 0058 evalFinished(); 0059 return; 0060 } 0061 0062 QString output = text; 0063 //remove carriage returns, we only use \n internally 0064 output.remove(QLatin1Char('\r')); 0065 //replace appearing backspaces, as they mess the whole output up 0066 //with QRegularExpression/PCRE to make \b match a backspace put it inside [] 0067 //see https://perldoc.perl.org/perlrecharclass.html#Bracketed-Character-Classes 0068 output.remove(QRegularExpression(QStringLiteral(".[\b]"))); 0069 //replace Escape sequences (only tested with `ls` command) 0070 const QChar ESC(0x1b); 0071 output.remove(QRegularExpression(QString(ESC)+QLatin1String("\\][^\a]*\a"))); 0072 0073 const QString promptRegexpBase(QLatin1String("(^|\\n)%1")); 0074 const QRegularExpression promptRegexp(promptRegexpBase.arg( 0075 QRegularExpression::escape(QLatin1String(SageSession::SagePrompt)))); 0076 const QRegularExpression altPromptRegexp(promptRegexpBase.arg( 0077 QRegularExpression::escape(QLatin1String(SageSession::SageAlternativePrompt)))); 0078 0079 bool endsWithAlternativePrompt=output.endsWith(QLatin1String(SageSession::SageAlternativePrompt)); 0080 0081 //remove all prompts. we do this in a loop, because after we removed the first prompt, 0082 //there could be a second one, that isn't matched by promptRegexp in the first run, because 0083 //it originally isn't at the beginning of a line. 0084 int index =- 1, index2 =- 1; 0085 while ( (index=output.indexOf(promptRegexp)) != -1 || (index2=output.indexOf(altPromptRegexp)) != -1 ) 0086 { 0087 qDebug() << "got prompt" << index << " " << index2; 0088 if(index != -1) 0089 { 0090 m_promptCount--; 0091 0092 //remove this prompt, the if is needed, because, if the prompt is on the 0093 //beginning of the string, index points to the "s", if it is within the string 0094 //index points to the newline 0095 if(output[index] == QLatin1Char('\n')) 0096 output.remove(index + 1, SageSession::SagePrompt.length()); 0097 else 0098 output.remove(index, SageSession::SagePrompt.length()); 0099 } 0100 0101 if(index2 != -1) 0102 { 0103 m_promptCount--; 0104 0105 //see comment above, for the reason for this "if" 0106 if(output[index2] == QLatin1Char('\n')) 0107 output.remove(index2 + 1, SageSession::SageAlternativePrompt.length()); 0108 else 0109 output.remove(index2, SageSession::SageAlternativePrompt.length()); 0110 } 0111 0112 //reset the indices 0113 index = index2 = -1; 0114 } 0115 0116 m_outputCache+=output; 0117 0118 if(m_promptCount<=0) 0119 { 0120 if(m_promptCount<0) 0121 qDebug()<<"got too many prompts"; 0122 0123 //if the output ends with an AlternativePrompt, this means that 0124 //Sage is expecting additional input, although m_promptCount==0 0125 //indicates that all information has been passed to sage. 0126 //This means that the user has entered an invalid command. 0127 //interrupt it and show an error message 0128 if(endsWithAlternativePrompt) 0129 { 0130 // Exit from sage additional input mode 0131 static_cast<SageSession*>(session())->sendInputToProcess(QLatin1String("\x03")); 0132 m_syntaxError = true; 0133 } 0134 else 0135 evalFinished(); 0136 } 0137 } 0138 0139 void SageExpression::parseError(const QString& text) 0140 { 0141 qDebug() << "error"; 0142 setErrorMessage(text); 0143 setStatus(Cantor::Expression::Error); 0144 } 0145 0146 void SageExpression::addFileResult( const QString& path ) 0147 { 0148 QUrl url = QUrl::fromLocalFile(path); 0149 QMimeDatabase db; 0150 QMimeType type = db.mimeTypeForUrl(url); 0151 0152 if(m_imagePath.isEmpty()||type.name().contains(QLatin1String("image"))||path.endsWith(QLatin1String(".png"))||path.endsWith(QLatin1String(".gif"))) 0153 m_imagePath = path; 0154 } 0155 0156 void SageExpression::evalFinished() 0157 { 0158 qDebug()<<"evaluation finished"; 0159 qDebug()<<m_outputCache; 0160 0161 //check if our image path contains a valid image that we can try to show 0162 const bool hasImage=!m_imagePath.isNull(); 0163 0164 if (!m_outputCache.isEmpty()) 0165 { 0166 QString stripped = m_outputCache; 0167 const bool isHtml = stripped.contains(QLatin1String("<html>")); 0168 const bool isLatex = m_outputCache.contains(QLatin1String("\\newcommand{\\Bold}")); //Check if it's latex stuff 0169 if(isLatex) //It's latex stuff so encapsulate it into an eqnarray environment 0170 { 0171 int bol_command_len = QLatin1String("\\newcommand{\\Bold}[1]{\\mathbf{#1}}").size(); 0172 int curr_index = stripped.indexOf(QLatin1String("\\newcommand{\\Bold}[1]{\\mathbf{#1}}"))+bol_command_len; 0173 // Add an & for the align environment 0174 stripped.insert(curr_index, QLatin1String("&")); 0175 // Strip away any additional "\\newcommand;{\\Bold}" so that it's compilable by LaTeX 0176 if(stripped.count(QLatin1String("\\newcommand{\\Bold}")) > 1){ 0177 while(curr_index != -1){ 0178 curr_index = stripped.indexOf(QLatin1String("\\newcommand{\\Bold}[1]{\\mathbf{#1}}"), curr_index); 0179 stripped.remove(curr_index, bol_command_len); 0180 // Also add an & for left alignment 0181 stripped.insert(curr_index, QLatin1String("&")); 0182 } 0183 } 0184 // Replace new-line characters with \\ for LaTeX's newline intepretation 0185 stripped.replace(QLatin1Char('\n'), QLatin1String("\\\\")); 0186 stripped.prepend(QLatin1String("\\begin{align*}")); 0187 stripped.append(QLatin1String("\\end{align*}")); 0188 // TODO: Remove for final merge 0189 qDebug() << "NewCommand"; 0190 qDebug() << stripped; 0191 } 0192 0193 //strip html tags 0194 if(isHtml) 0195 stripped.remove( QRegularExpression( QStringLiteral("<[a-zA-Z\\/][^>]*>") ) ); 0196 0197 if (stripped.endsWith(QLatin1Char('\n'))) 0198 stripped.chop(1); 0199 0200 if (m_isHelpRequest) 0201 { 0202 stripped = stripped.toHtmlEscaped(); 0203 stripped.replace(QLatin1Char(' '), QLatin1String(" ")); 0204 stripped.replace(QLatin1Char('\n'), QLatin1String("<br/>\n")); 0205 0206 //make things quoted in `` `` bold 0207 stripped.replace(QRegularExpression(QStringLiteral("``([^`]*)``")), QStringLiteral("<b>\\1</b>")); 0208 0209 addResult(new Cantor::HelpResult(stripped, true)); 0210 } 0211 else 0212 { 0213 auto* result=new Cantor::TextResult(stripped); 0214 if(isLatex) 0215 result->setFormat(Cantor::TextResult::LatexFormat); 0216 addResult(result); 0217 } 0218 } 0219 qDebug()<<"has image " << hasImage; 0220 0221 if (hasImage) 0222 { 0223 QMimeDatabase db; 0224 QMimeType type = db.mimeTypeForUrl(QUrl::fromLocalFile(m_imagePath)); 0225 qDebug()<<"mime type " << type; 0226 qDebug()<<"image path " << m_imagePath; 0227 if(type.inherits(QLatin1String("image/gif"))) 0228 { 0229 qDebug()<<"adding animation"; 0230 addResult( new Cantor::AnimationResult(QUrl::fromLocalFile(m_imagePath), i18n("Result of %1" , command() ) ) ); 0231 } 0232 else 0233 { 0234 qDebug()<<"adding image"; 0235 addResult( new Cantor::ImageResult(QUrl::fromLocalFile(m_imagePath ), i18n("Result of %1" , command() ) ) ); 0236 } 0237 } 0238 setStatus(Cantor::Expression::Done); 0239 } 0240 0241 void SageExpression::onProcessError(const QString& msg) 0242 { 0243 QString errMsg = i18n("%1\nThe last output was: \n %2", msg, m_outputCache.trimmed()); 0244 setErrorMessage(errMsg); 0245 setStatus(Cantor::Expression::Error); 0246 } 0247 0248 QString SageExpression::additionalLatexHeaders() 0249 { 0250 //The LaTeX sage needs the amsmath package and some specific macros. 0251 //So include them in the header. 0252 //More about the macros requirement in bug #312738 0253 return QLatin1String("\\usepackage{amsmath}\n" \ 0254 "\\newcommand{\\ZZ}{\\Bold{Z}}\n" \ 0255 "\\newcommand{\\NN}{\\Bold{N}}\n" \ 0256 "\\newcommand{\\RR}{\\Bold{R}}\n" \ 0257 "\\newcommand{\\CC}{\\Bold{C}}\n" \ 0258 "\\newcommand{\\QQ}{\\Bold{Q}}\n" \ 0259 "\\newcommand{\\QQbar}{\\overline{\\QQ}}\n" \ 0260 "\\newcommand{\\GF}[1]{\\Bold{F}_{#1}}\n" \ 0261 "\\newcommand{\\Zp}[1]{\\ZZ_{#1}}\n" \ 0262 "\\newcommand{\\Qp}[1]{\\QQ_{#1}}\n" \ 0263 "\\newcommand{\\Zmod}[1]{\\ZZ/#1\\ZZ}\n" \ 0264 "\\newcommand{\\CDF}{\\Bold{C}}\n" \ 0265 "\\newcommand{\\CIF}{\\Bold{C}}\n" \ 0266 "\\newcommand{\\CLF}{\\Bold{C}}\n" \ 0267 "\\newcommand{\\RDF}{\\Bold{R}}\n" \ 0268 "\\newcommand{\\RIF}{\\Bold{I} \\Bold{R}}\n"\ 0269 "\\newcommand{\\RLF}{\\Bold{R}}\n" \ 0270 "\\newcommand{\\CFF}{\\Bold{CFF}}\n"); 0271 }