1 #include "scriptengine.h"
2 #include "filechooser.h"
3 #include "smallUsefulFunctions.h"
4 #include "qdocumentsearch.h"
5 #include "scriptobject.h"
6 #include "buildmanager.h"
7 #include "latexdocument.h"
8 #include "texstudio.h"
9 #include "PDFDocument.h"
10 #include "usermacro.h"
11 #include <QCryptographicHash>
12
13 //Q_DECLARE_METATYPE(QDocument *)
14 //Q_DECLARE_METATYPE(LatexDocuments *)
15 Q_DECLARE_METATYPE(BuildManager *)
16 Q_DECLARE_METATYPE(RunCommandFlags)
17 //Q_DECLARE_METATYPE(QAction *)
18 //Q_DECLARE_METATYPE(QMenu *)
19
20 Q_DECLARE_METATYPE(SubScriptObject *)
21 //Q_DECLARE_METATYPE(QEditor *)
22 Q_DECLARE_METATYPE(QList<LatexDocument *>)
23 #ifndef NO_POPPLER_PREVIEW
24 //Q_DECLARE_METATYPE(PDFDocument *)
25 //Q_DECLARE_METATYPE(PDFWidget *)
26 Q_DECLARE_METATYPE(QList<PDFDocument *>)
27 #endif
28 //Q_DECLARE_METATYPE(QString *)
29
30 BuildManager *scriptengine::buildManager = nullptr;
31 Texstudio *scriptengine::app = nullptr;
32
33 QList<Macro> *scriptengine::macros = nullptr;
34
35 int scriptengine::writeSecurityMode=2;
36 int scriptengine::readSecurityMode=2;
37 QStringList scriptengine::privilegedReadScripts=QStringList();
38 QStringList scriptengine::privilegedWriteScripts=QStringList();
39
qScriptValueFromQObject(QJSEngine * engine,Tp const & qobject)40 template <typename Tp> QJSValue qScriptValueFromQObject(QJSEngine *engine, Tp const &qobject)
41 {
42 return engine->newQObject(qobject);
43 }
44
qScriptValueToQObject(const QJSValue & value,Tp & qobject)45 template <typename Tp> void qScriptValueToQObject(const QJSValue &value, Tp &qobject)
46 {
47 qobject = qobject_cast<Tp>(value.toQObject());
48 }
49
50 //template <typename Tp> int qScriptRegisterQObjectMetaType( QJSEngine *engine, const QJSValue &prototype = QJSValue(), Tp * /* dummy */ = nullptr)
51 //{
52 // return qScriptRegisterMetaType<Tp>(engine, qScriptValueFromQObject, qScriptValueToQObject, prototype);
53 //}
54
qScriptValueFromDocumentCursor(QJSEngine * engine,QDocumentCursor const & cursor)55 QJSValue qScriptValueFromDocumentCursor(QJSEngine *engine, QDocumentCursor const &cursor)
56 {
57 return engine->newQObject(new QDocumentCursor(cursor));
58 }
qScriptValueToDocumentCursor(const QJSValue & value,QDocumentCursor & qobject)59 void qScriptValueToDocumentCursor(const QJSValue &value, QDocumentCursor &qobject)
60 {
61 Q_ASSERT(value.toQObject());
62 qobject = *qobject_cast<QDocumentCursor *>(value.toQObject());
63 }
64
qScriptValueFromQList(QJSEngine * engine,QList<Tp> const & list)65 template <typename Tp> QJSValue qScriptValueFromQList(QJSEngine *engine, QList<Tp> const &list)
66 {
67 QJSValue result = engine->newArray(list.size());
68 for (int i = 0; i < list.size(); i++)
69 result.setProperty(i, engine->newQObject(list[i])); //engine->newVariant(QVariant::fromValue<Tp>(list[i])));
70 return result;
71 }
72
cursorFromValue(const QJSValue & value)73 QDocumentCursor cursorFromValue(const QJSValue &value)
74 {
75 QDocumentCursor *c = qobject_cast<QDocumentCursor *> (value.toQObject());
76 if (!c) {
77 #if (QT_VERSION<QT_VERSION_CHECK(6,0,0) && QT_VERSION>=QT_VERSION_CHECK(5,12,0))
78 //if (value.engine() ) value.engine()->throwError(scriptengine::tr("Expected cursor object")); //TODO Qt6 ?
79 #endif
80 return QDocumentCursor();
81 }
82 return *c;
83 }
84
qScriptValueFromQFileInfo(QJSEngine * engine,QFileInfo const & fi)85 QJSValue qScriptValueFromQFileInfo(QJSEngine *engine, QFileInfo const &fi)
86 {
87 return engine->toScriptValue(fi.absoluteFilePath());
88 }
89
qScriptValueToQFileInfo(const QJSValue & value,QFileInfo & fi)90 void qScriptValueToQFileInfo(const QJSValue &value, QFileInfo &fi)
91 {
92 fi = QFileInfo(value.toString());
93 }
94
95
96 //map qstring* to scriptvalue object, for callbacks
97
98 static quintptr PointerObsfuscationKey = 0;
99
pointerObsfuscationKey()100 quintptr pointerObsfuscationKey() //hide true pointers from scripts
101 {
102 while (PointerObsfuscationKey == 0) {
103 for (unsigned int i = 0; i < sizeof(PointerObsfuscationKey); i++)
104 PointerObsfuscationKey = (PointerObsfuscationKey << 8) | (rand() & 0xFF);
105 }
106 return PointerObsfuscationKey;
107 }
108
getStrPtr(QJSValue value)109 QString *getStrPtr(QJSValue value)
110 {
111 if (value.property("dataStore").isUndefined())
112 return nullptr;
113 bool ok = false;
114 quintptr ptr = value.property("dataStore").toVariant().toULongLong(&ok);
115 if (!ok || ptr == 0 || ptr == pointerObsfuscationKey())
116 return nullptr;
117 return reinterpret_cast<QString *>(ptr ^ pointerObsfuscationKey());
118 }
119
getSetStrValue(QJSEngine *)120 QJSValue getSetStrValue(QJSEngine */*engine*/)
121 {
122 /*bool setterMode = context->argumentCount() == 1;
123 QString *s = getStrPtr(context->thisObject());
124 if (setterMode && !s) {
125 s = new QString();
126 context->thisObject().setProperty("dataStore", engine->newVariant((quintptr)(s) ^ pointerObsfuscationKey()));
127 }
128 if (!s) return engine->undefinedValue();
129 if (setterMode) {
130 if (!needPrivileges(engine, "string setting", context->argument(0).toString())){
131 delete s;
132 return engine->undefinedValue();
133 }
134 *s = context->argument(0).toString();
135 }
136 return engine->newVariant(*s);*/
137 return QJSValue();
138 }
139
scriptengine(QObject * parent)140 scriptengine::scriptengine(QObject *parent) : QObject(parent), triggerId(-1), m_editor(nullptr), m_allowWrite(false)
141 {
142 engine = new QJSEngine(this);
143 qmlRegisterType<QDocument>("com.txs.qmlcomponents", 1, 0, "QDocument");
144 //qmlRegisterAnonymousType<QDocumentCursor>("com.txs.qmlcomponents",1);
145 qmlRegisterType<QDocumentCursor>("com.txs.qmlcomponents",1,0,"QDocumentCursor");
146 //qmlRegisterType<QFileInfo>();
147 qmlRegisterType<ProcessX>("com.txs.qmlcomponents", 1, 0, "ProcessX");
148 //qmlRegisterType<SubScriptObject>();
149 qmlRegisterType<Texstudio>("com.txs.qmlcomponents", 1, 0, "Texstudio");
150 qmlRegisterType<QAction>("com.txs.qmlcomponents", 1, 0, "QAction");
151 qmlRegisterType<QMenu>("com.txs.qmlcomponents", 1, 0, "QMenu");
152 //qmlRegisterType<LatexEditorView>("com.txs.qmlcomponents", 1, 0, "LatexEditorView");
153 qmlRegisterType<LatexDocument>("com.txs.qmlcomponents", 1, 0, "LatexDocument");
154 qmlRegisterType<LatexDocuments>("com.txs.qmlcomponents", 1, 0, "LatexDocuments");
155 qmlRegisterType<QEditor>("com.txs.qmlcomponents", 1, 0, "QEditor");
156 #ifndef NO_POPPLER_PREVIEW
157 //qmlRegisterType<PDFDocument>("com.txs.qmlcomponents", 1, 0, "PDFDocument");
158 //qmlRegisterType<PDFWidget>("com.txs.qmlcomponents", 1, 0, "PDFWidget");
159 #endif
160
161 //qmlRegisterType<QList<LatexDocument *> >();
162 #ifndef NO_POPPLER_PREVIEW
163 //qmlRegisterType<QList<PDFDocument *> >("com.txs.qmlcomponents", 1, 0, "ListPDF");
164 #endif
165
166 qRegisterMetaType<RunCommandFlags>();
167
168 qmlRegisterType<BuildManager>("com.txs.qmlcomponents", 1, 0, "BuildManager");
169
170 //qmlRegisterType<QKeySequence>("com.txs.qmlcomponents", 1, 0, "QKeySequence");
171 //qmlRegisterType<QUILoader>();
172
173 // use config
174 ConfigManagerInterface::getInstance()->registerOption("Scripts/Privileged Read Scripts", &privilegedReadScripts);
175 ConfigManagerInterface::getInstance()->registerOption("Scripts/Read Security Mode", &readSecurityMode, 1);
176 ConfigManagerInterface::getInstance()->registerOption("Scripts/Privileged Write Scripts", &privilegedWriteScripts);
177 ConfigManagerInterface::getInstance()->registerOption("Scripts/Write Security Mode", &writeSecurityMode, 1);
178 }
179
~scriptengine()180 scriptengine::~scriptengine()
181 {
182 engine->collectGarbage();
183 delete engine;
184 engine=nullptr;
185 //don't delete globalObject, it has either been destroyed in run, or by another script
186 }
187
setScript(const QString & script,bool allowWrite)188 void scriptengine::setScript(const QString &script, bool allowWrite)
189 {
190 m_script = script;
191 m_allowWrite = allowWrite;
192 }
193
setEditorView(LatexEditorView * edView)194 void scriptengine::setEditorView(LatexEditorView *edView)
195 {
196 REQUIRE(edView);
197 m_editor = edView->editor;
198 m_editorView = edView;
199 }
200
run(const bool quiet)201 void scriptengine::run(const bool quiet)
202 {
203 QJSValue jsApp=engine->newQObject(app);
204 engine->globalObject().setProperty("app",jsApp);
205 QQmlEngine::setObjectOwnership(app, QQmlEngine::CppOwnership);
206
207 QDocumentCursor c( m_editor ? m_editor->cursorHandle() : nullptr); //create from handle, so modifying the cursor in the script directly affects the actual cursor
208 QJSValue cursorValue;
209 if (m_editorView)
210 engine->globalObject().setProperty("editorView", engine->newQObject(m_editorView));
211
212 QJSValue scriptJS=engine->newQObject(this);
213 // add general debug/warn functions
214 engine->globalObject().setProperty("alert", scriptJS.property("alert"));
215 engine->globalObject().setProperty("information", scriptJS.property("information"));
216 engine->globalObject().setProperty("critical", scriptJS.property("critical"));
217 engine->globalObject().setProperty("warning", scriptJS.property("warning"));
218 engine->globalObject().setProperty("confirm", scriptJS.property("confirm"));
219 engine->globalObject().setProperty("confirmWarning", scriptJS.property("confirmWarning"));
220 engine->globalObject().setProperty("debug", scriptJS.property("debug"));
221 #ifndef QT_NO_DEBUG
222 engine->globalObject().setProperty("crash_assert", scriptJS.property("crash_assert"));
223 #endif
224 engine->globalObject().setProperty("crash_sigsegv", scriptJS.property("crash_sigsegv"));
225 engine->globalObject().setProperty("crash_sigfpe", scriptJS.property("crash_sigfpe"));
226 engine->globalObject().setProperty("crash_sigstack", scriptJS.property("crash_sigstack"));
227 engine->globalObject().setProperty("crash_loop", scriptJS.property("crash_loop"));
228 engine->globalObject().setProperty("crash_throw", scriptJS.property("crash_throw"));
229
230 engine->globalObject().setProperty("readFile", scriptJS.property("readFile"));
231 engine->globalObject().setProperty("writeFile", scriptJS.property("writeFile"));
232 engine->globalObject().setProperty("hasPersistent", scriptJS.property("hasPersistent"));
233 engine->globalObject().setProperty("setPersistent", scriptJS.property("setPersistent"));
234 engine->globalObject().setProperty("getPersistent", scriptJS.property("getPersistent"));
235 engine->globalObject().setProperty("registerAsBackgroundScript", scriptJS.property("registerAsBackgroundScript"));
236 engine->globalObject().setProperty("system", scriptJS.property("system"));
237
238
239 if (m_editor) {
240 QJSValue editorValue = engine->newQObject(m_editor);
241 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
242 QQmlEngine::setObjectOwnership(m_editor, QQmlEngine::CppOwnership);
243 editorValue.setProperty("cutBuffer", m_editor->cutBuffer);
244 editorValue.setProperty("insertSnippet", scriptJS.property("insertSnippet"));
245 editorValue.setProperty("replaceSelectedText", scriptJS.property("replaceSelectedText"));
246 editorValue.setProperty("search", scriptJS.property("searchFunction"));
247 editorValue.setProperty("replace", scriptJS.property("replaceFunction"));
248 editorValue.setProperty("save", scriptJS.property("save"));
249 editorValue.setProperty("saveCopy", scriptJS.property("saveCopy"));
250 engine->globalObject().setProperty("editor", editorValue);
251
252 cursorValue = engine->newQObject(&c);
253 engine->globalObject().setProperty("cursor", cursorValue);
254 QQmlEngine::setObjectOwnership(&c, QQmlEngine::CppOwnership);
255
256 QJSValue matches = engine->newArray(triggerMatches.size());
257 for (int i = 0; i < triggerMatches.size(); i++) matches.setProperty(i, triggerMatches[i]);
258 engine->globalObject().setProperty("triggerMatches", matches);
259 }
260 engine->globalObject().setProperty("triggerId", QJSValue(triggerId));
261
262 //engine->globalObject().setProperty("include", engine->newFunction(include));
263 //QJSValue script= engine->newQObject(this);
264
265 engine->globalObject().setProperty("setTimeout", scriptJS.property("setTimeout"));
266
267 QJSValue qsMetaObject = engine->newQMetaObject(&QDocumentCursor::staticMetaObject);
268 engine->globalObject().setProperty("cursorEnums", qsMetaObject);
269 engine->globalObject().setProperty("QDocumentCursor", qsMetaObject);
270
271 QJSValue uidClass = engine->newQMetaObject(&UniversalInputDialogScript::staticMetaObject);
272 engine->globalObject().setProperty("UniversalInputDialog", uidClass);
273
274 FileChooser flchooser(nullptr, scriptengine::tr("File Chooser"));
275 engine->globalObject().setProperty("fileChooser", engine->newQObject(&flchooser));
276
277 engine->globalObject().setProperty("documentManager", engine->newQObject(&app->documents));
278 QQmlEngine::setObjectOwnership(&app->documents, QQmlEngine::CppOwnership);
279 engine->globalObject().setProperty("documents", qScriptValueFromQList(engine, app->documents.documents));
280 #ifndef NO_POPPLER_PREVIEW
281 engine->globalObject().setProperty("pdfs", qScriptValueFromQList(engine, PDFDocument::documentList()));
282 #endif
283 QJSValue bm = engine->newQObject(&app->buildManager);
284 QQmlEngine::setObjectOwnership(&app->buildManager, QQmlEngine::CppOwnership);
285
286 //bm.setProperty("runCommand", engine->newFunction(buildManagerRunCommandWrapper));
287 //bm.setProperty("commandLineRequested", engine->globalObject().property("buildManagerCommandLineRequestedWrapper"));
288 engine->globalObject().setProperty("buildManager", bm);
289 //connect(buildManager, SIGNAL(commandLineRequested(QString,QString*)), SLOT(buildManagerCommandLineRequestedWrapperSlot(const QString&, QString*)));
290
291 QJSValue result=engine->evaluate(m_script);
292
293 if (result.isError()) {
294 QString error = QString(tr("Uncaught exception at line %1: %2\n")).arg(result.property("lineNumber").toInt()).arg(result.toString());
295 qDebug() << error;
296 if(!quiet)
297 QMessageBox::critical(nullptr, tr("Script-Error"), error);
298 }
299
300 if (m_editor) {
301 if (!engine->globalObject().property("cursor").strictlyEquals(cursorValue))
302 m_editor->setCursor(cursorFromValue(engine->globalObject().property("cursor")));
303 }
304
305 }
306
insertSnippet(const QString & arg)307 void scriptengine::insertSnippet(const QString& arg)
308 {
309 CodeSnippet cs(arg);
310
311 if (!m_editor) return;
312 foreach (QDocumentCursor c, m_editor->cursors()) {
313 cs.insertAt(m_editor, &c);
314 }
315 }
316
317 #if ( QT_VERSION >= QT_VERSION_CHECK(5,12,0) )
318 #define SCRIPT_ASSERT(condition,message) \
319 if(!(condition)){ \
320 engine -> throwError(scriptengine::tr(message)); \
321 return QJSValue(); \
322 }
323 #else
324 #define SCRIPT_ASSERT(condition,message) \
325 if(!(condition)){ \
326 qDebug() << scriptengine::tr(message); \
327 return QJSValue(); \
328 }
329 #endif
330
replaceSelectedText(QJSValue replacementText,QJSValue options)331 QJSValue scriptengine::replaceSelectedText(QJSValue replacementText,QJSValue options)
332 {
333 bool noEmpty = false;
334 bool onlyEmpty = false;
335 bool append = false;
336 bool prepend = false;
337 bool macro = false;
338
339 if (!options.isUndefined() ) {
340 SCRIPT_ASSERT(options.isObject(), "2nd value needs to be an object")
341 noEmpty = options.property("noEmpty").toBool();
342 onlyEmpty = options.property("onlyEmpty").toBool();
343 append = options.property("append").toBool();
344 prepend = options.property("prepend").toBool();
345 macro = options.property("macro").toBool();
346 SCRIPT_ASSERT(!macro || !(append || prepend), "Macro option cannot be combined with append or prepend option.") //well it could, but there is no good way to define what should happen to the selection
347 }
348
349
350 QList<QDocumentCursor> cursors = m_editor->cursors();
351 QList<PlaceHolder> newMacroPlaceholder = macro ? m_editor->getPlaceHolders() : QList<PlaceHolder>();
352 QList<QDocumentCursor> newMacroCursors;
353
354 m_editor->document()->beginMacro();
355 foreach (QDocumentCursor c, cursors) {
356 QString st = c.selectedText();
357 if (noEmpty && st.isEmpty()) continue;
358 if (onlyEmpty && !st.isEmpty()) continue;
359 QString newst;
360 if (replacementText.isCallable()) {
361 QJSValue cb = replacementText.call(QJSValueList() << engine->toScriptValue(st) << engine->newQObject(&c));
362 newst = cb.toString();
363 } else {
364 newst = replacementText.toString();
365 }
366 if (!macro) {
367 if (append && prepend) newst = newst + st + newst;
368 else if (append) newst = st + newst;
369 else if (prepend) newst = newst + st;
370 c.replaceSelectedText(newst);
371 } else {
372 m_editor->clearPlaceHolders();
373 m_editor->clearCursorMirrors();
374 CodeSnippet cs(newst);
375 cs.insertAt(m_editor, &c);
376 newMacroPlaceholder << m_editor->getPlaceHolders();
377 if (m_editor->cursor().isValid()) {
378 newMacroCursors << m_editor->cursor();
379 newMacroCursors.last().setAutoUpdated(true);
380 } else newMacroCursors << c; //CodeSnippet does not select without placeholder. But here we do, since it is called replaceSelectedText
381 }
382 }
383 m_editor->document()->endMacro();
384 if (macro && (cursors.size() > 0 /*|| (append && prepend) disallowed*/)) { //inserting multiple macros destroyed the new cursors, we need to insert them again
385 if (noEmpty) {
386 foreach (QDocumentCursor c, cursors){
387 if (c.isValid() && c.selectedText().isEmpty()) newMacroCursors << c;
388 }
389 }
390 if (onlyEmpty){
391 foreach (QDocumentCursor c, cursors) {
392 if (c.isValid() && !c.selectedText().isEmpty()) newMacroCursors << c;
393 }
394 }
395 if (newMacroCursors.size()){
396 m_editor->setCursor(newMacroCursors.first());
397 }
398 for (int i = 1; i < newMacroCursors.size(); i++){
399 m_editor->addCursorMirror(newMacroCursors[i]);
400 }
401 m_editor->replacePlaceHolders(newMacroPlaceholder);
402 }
403 return QJSValue();
404 }
405
searchReplaceFunction(QJSValue searchText,QJSValue arg1,QJSValue arg2,QJSValue arg3,bool replace)406 QJSValue scriptengine::searchReplaceFunction(QJSValue searchText, QJSValue arg1, QJSValue arg2, QJSValue arg3, bool replace)
407 {
408 //read arguments
409 SCRIPT_ASSERT(m_editor, "invalid object")
410 SCRIPT_ASSERT(!replace || !arg1.isUndefined(), "at least two arguments are required")
411 SCRIPT_ASSERT(!searchText.isUndefined() , "at least one argument is required")
412 SCRIPT_ASSERT(searchText.isString() || searchText.isRegExp(), "first argument must be a string or regexp")
413 QDocumentSearch::Options flags = QDocumentSearch::Silent;
414 bool global = false, caseInsensitive = false;
415 QString searchFor;
416 if (searchText.isRegExp()) {
417 flags |= QDocumentSearch::RegExp;
418 #if QT_VERSION<QT_VERSION_CHECK(5,14,0)
419 QRegExp r2=searchText.toVariant().toRegExp(); // toRegularExpression seems not to work <5.14
420 QRegularExpression r(r2.pattern());
421 caseInsensitive = (r2.caseSensitivity() == Qt::CaseInsensitive);
422 if(caseInsensitive){
423 r.setPatternOptions(r.patternOptions()|QRegularExpression::CaseInsensitiveOption);
424 }
425 #else
426 QRegularExpression r = searchText.toVariant().toRegularExpression();
427 #endif
428 searchFor = r.pattern();
429 caseInsensitive = (r.patternOptions() & QRegularExpression::CaseInsensitiveOption)!=QRegularExpression::NoPatternOption;
430 //Q_ASSERT(caseInsensitive == searchText.property("ignoreCase").toBool()); //check assumption about javascript core
431 global = searchText.property("global").toBool();
432 } else searchFor = searchText.toString();
433 QJSValue handler;
434 QDocumentCursor m_scope = m_editor->document()->cursor(0, 0, m_editor->document()->lineCount(), 0);
435 int handlerCount = 0;
436 for (int i = 1; i < 4; i++){
437 QJSValue args;
438 switch (i)
439 {
440 case 1:
441 args=arg1;
442 break;
443 case 2:
444 args=arg2;
445 break;
446 case 3:
447 args=arg3;
448 break;
449 }
450 if(args.isUndefined())
451 break;
452 if (args.isString() || args.isCallable()) handlerCount++;
453 }
454 SCRIPT_ASSERT(handlerCount <= (replace ? 3 : 2), "too many string or function arguments")
455 for (int i = 1; i < 4; i++) {
456 QJSValue a;
457 switch (i)
458 {
459 case 1:
460 a=arg1;
461 break;
462 case 2:
463 a=arg2;
464 break;
465 case 3:
466 a=arg3;
467 break;
468 }
469 if(a.isUndefined())
470 break;
471 if (a.isCallable()) {
472 SCRIPT_ASSERT(handler.isUndefined(), "Multiple callbacks")
473 handler = a;
474 } else if (a.isString()) {
475 if (!replace || handlerCount > 1) {
476 QString s = a.toString().toLower();
477 global = s.contains("g");
478 caseInsensitive = s.contains("i");
479 if (s.contains("w")) flags |= QDocumentSearch::WholeWords;
480 } else {
481 SCRIPT_ASSERT(handler.isUndefined(), "Multiple callbacks")
482 handler = a;
483 }
484 handlerCount--;
485 } else if (a.isNumber()) flags |= QDocumentSearch::Options((int)a.toNumber());
486 else if (a.isObject()) m_scope = cursorFromValue(a);
487 else SCRIPT_ASSERT(false, "Invalid argument")
488 }
489 SCRIPT_ASSERT(!handler.isUndefined() || !replace, "No callback given")
490 if (!caseInsensitive) flags |= QDocumentSearch::CaseSensitive;
491 //search/replace
492 QDocumentSearch search(m_editor, searchFor, flags);
493 search.setScope(m_scope);
494 if (replace && handler.isString()) {
495 search.setReplaceText(handler.toString());
496 search.setOption(QDocumentSearch::Replace, true);
497 return search.next(false, global, false, false);
498 }
499 if (handler.isUndefined())
500 return search.next(false, global, true, false);
501 int count = 0;
502 while (search.next(false, false, true, false) && search.cursor().isValid()) {
503 count++;
504 QDocumentCursor temp = search.cursor();
505 QJSValue cb = handler.call(QJSValueList() << engine->newQObject(&temp));
506 if (replace && !cb.isError()) {
507 QDocumentCursor tmp = search.cursor();
508 tmp.replaceSelectedText(cb.toString());
509 search.setCursor(tmp.selectionEnd());
510 }
511 if (!global) break;
512 }
513 return count;
514 }
515
searchFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)516 QJSValue scriptengine::searchFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)
517 {
518 return searchReplaceFunction(searchFor,arg1,arg2,arg3, false);
519 }
520
replaceFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)521 QJSValue scriptengine::replaceFunction(QJSValue searchFor,QJSValue arg1,QJSValue arg2,QJSValue arg3)
522 {
523 return searchReplaceFunction(searchFor,arg1,arg2,arg3, true);
524 }
525
alert(const QString & message)526 void scriptengine::alert(const QString &message)
527 {
528 UtilsUi::txsInformation(message);
529 }
530
information(const QString & message)531 void scriptengine::information(const QString &message)
532 {
533 UtilsUi::txsInformation(message);
534 }
535
critical(const QString & message)536 void scriptengine::critical(const QString &message)
537 {
538 UtilsUi::txsCritical(message);
539 }
540
warning(const QString & message)541 void scriptengine::warning(const QString &message)
542 {
543 UtilsUi::txsWarning(message);
544 }
545
confirm(const QString & message)546 bool scriptengine::confirm(const QString &message)
547 {
548 return UtilsUi::txsConfirm(message);
549 }
550
confirmWarning(const QString & message)551 bool scriptengine::confirmWarning(const QString &message)
552 {
553 return UtilsUi::txsConfirmWarning(message);
554 }
555
debug(const QString & message)556 void scriptengine::debug(const QString &message)
557 {
558 qDebug() << message;
559 }
560 #ifndef QT_NO_DEBUG
crash_assert()561 void scriptengine::crash_assert()
562 {
563 Q_ASSERT(false);
564 }
565 #endif
566
crash_sigsegv()567 void scriptengine::crash_sigsegv()
568 {
569 if (!confirmWarning("Do you want to let txs crash with a SIGSEGV?")) return;
570 char *c = nullptr;
571 *c = 'A';
572 }
573
574 int global0 = 0;
crash_sigfpe()575 void scriptengine::crash_sigfpe()
576 {
577 if (!confirmWarning("Do you want to let txs crash with a SIGFPE?")) return;
578 int x = 1 / global0;
579 Q_UNUSED(x)
580 }
581
crash_stack()582 void scriptengine::crash_stack()
583 {
584 if (!confirmWarning("Do you want to let txs crash with a stack overflow?")) return;
585 int temp = global0;
586 crash_stack();
587 Q_UNUSED(temp)
588 }
589
crash_loop()590 void scriptengine::crash_loop()
591 {
592 if (!confirmWarning("Do you want to let txs freeze with an endless loop?")) return;
593 //#ifndef QT_NO_DEBUG // only used in the Q_ASSERT statements: prevent unused variable warning in release build
594 int a = 1, b = 2, c = 3, d = 4;
595 //#endif
596 while (true) {
597 void *x = malloc(16);
598 free(x);
599 Q_ASSERT(a == 1);
600 Q_ASSERT(b == 2);
601 Q_ASSERT(c == 3);
602 Q_ASSERT(d == 4); //make sure, no register suddenly change
603 };
604 }
605
crash_throw()606 void scriptengine::crash_throw()
607 {
608 if (!confirmWarning("Do you want to let txs crash with an exception?")) return;
609 throw "debug crash";
610 }
611
612
save(const QString fn)613 void scriptengine::save(const QString fn)
614 {
615 if(fn.isEmpty()){
616 m_editor->save();
617 }else{
618 m_editor->save(fn);
619 }
620 }
621
saveCopy(const QString & fileName)622 void scriptengine::saveCopy(const QString &fileName)
623 {
624 m_editor->saveCopy(fileName);
625 }
626
system(const QString & commandline,const QString & workingDirectory)627 ProcessX * scriptengine::system(const QString &commandline, const QString &workingDirectory)
628 {
629 if (!buildManager || !needWritePrivileges("system", commandline))
630 return nullptr;
631 ProcessX *p = nullptr;
632 if (commandline.contains(BuildManager::TXS_CMD_PREFIX) || !commandline.contains("|"))
633 p = buildManager->firstProcessOfDirectExpansion(commandline, QFileInfo());
634 else
635 p = buildManager->newProcessInternal(commandline, QFileInfo()); //use internal, so people can pass | to sh
636 if (!p) return nullptr;
637 connect(p, SIGNAL(finished(int, QProcess::ExitStatus)), p, SLOT(deleteLater()));
638 QMetaObject::invokeMethod(reinterpret_cast<QObject *>(app), "connectSubCommand", Q_ARG(ProcessX *, p), Q_ARG(bool, true));
639 if (!workingDirectory.isEmpty())
640 p->setWorkingDirectory(workingDirectory);
641 QString *buffer=new QString;
642 p->setStdoutBuffer(buffer);
643 p->startCommand();
644 p->waitForStarted();
645 return p;
646 }
647
writeFile(const QString & filename,const QString & content)648 void scriptengine::writeFile(const QString &filename, const QString &content)
649 {
650 if (!needWritePrivileges("writeFile", filename))
651 return;
652 QFile f(filename);
653 if (!f.open(QFile::WriteOnly))
654 return;
655 f.write(content.toUtf8());
656 f.close();
657 }
658
readFile(const QString & filename)659 QVariant scriptengine::readFile(const QString &filename)
660 {
661 if (!needReadPrivileges("readFile", filename))
662 return QVariant();
663 QFile f(filename);
664 if (!f.open(QFile::ReadOnly))
665 return QVariant();
666 QTextStream ts(&f);
667 ts.setAutoDetectUnicode(true);
668 return ts.readAll();
669 }
670
hasReadPrivileges()671 bool scriptengine::hasReadPrivileges()
672 {
673 if (readSecurityMode == 0)
674 return false;
675 if (readSecurityMode == 2
676 || privilegedReadScripts.contains(getScriptHash())
677 || privilegedWriteScripts.contains(getScriptHash()))
678 return true;
679 return false;
680
681 }
682
hasWritePrivileges()683 bool scriptengine::hasWritePrivileges()
684 {
685 if (writeSecurityMode == 0)
686 return false;
687 if (writeSecurityMode == 2
688 || privilegedWriteScripts.contains(getScriptHash()))
689 return true;
690 return false;
691
692 }
693
getScriptHash()694 QByteArray scriptengine::getScriptHash()
695 {
696 QByteArray m_scriptHash = QCryptographicHash::hash(m_script.toLatin1(), QCryptographicHash::Sha1).toHex();
697 return m_scriptHash;
698 }
699
registerAllowedWrite()700 void scriptengine::registerAllowedWrite()
701 {
702 QByteArray hash = getScriptHash();
703 if (!privilegedWriteScripts.contains(hash))
704 privilegedWriteScripts.append(hash);
705 }
706
needWritePrivileges(const QString & fn,const QString & param)707 bool scriptengine::needWritePrivileges(const QString &fn, const QString ¶m)
708 {
709 if (writeSecurityMode == 0) return false;
710 if (hasWritePrivileges()) return true;
711 int t = QMessageBox::question(nullptr, "TeXstudio script watcher",
712 tr("The current script has requested to enter privileged write mode and call following function:\n%1\n\nDo you trust this script?").arg(fn + "(\"" + param + "\")"), tr("Yes, allow this call"),
713 tr("Yes, allow all calls it will ever make"), tr("No, abort the call"), 0, 2);
714 if (t == 0) return true; //only now
715 if (t != 1) return false;
716 privilegedWriteScripts.append(getScriptHash());
717 return true;
718 }
719
needReadPrivileges(const QString & fn,const QString & param)720 bool scriptengine::needReadPrivileges(const QString &fn, const QString ¶m)
721 {
722 if (readSecurityMode == 0) return false;
723 if (hasReadPrivileges()) return true;
724 int t = QMessageBox::question(nullptr, "TeXstudio script watcher",
725 tr("The current script has requested to enter privileged mode and read the following value:\n%1\n\nDo you trust this script?").arg(fn + "(\"" + param + "\")"), tr("Yes, allow this reading"),
726 tr("Yes, grant permanent read access to everything"), tr("No, abort the call"), 0, 2);
727 if (t == 0) return true; //only now
728 if (t != 1) return false;
729 privilegedReadScripts.append(getScriptHash());
730 return true;
731 }
732
hasPersistent(const QString & name)733 bool scriptengine::hasPersistent(const QString &name)
734 {
735 return ConfigManagerInterface::getInstance()->existsOption(name);
736 }
737
setPersistent(const QString & name,const QVariant & value)738 void scriptengine::setPersistent(const QString &name, const QVariant &value)
739 {
740 if (!needWritePrivileges("setPersistent", name + "=" + value.toString())) return;
741 ConfigManagerInterface::getInstance()->setOption(name, value);
742 }
743
getPersistent(const QString & name)744 QVariant scriptengine::getPersistent(const QString &name)
745 {
746 if (!needReadPrivileges("getPersistent", name)) return QVariant();
747 return ConfigManagerInterface::getInstance()->getOption(name);
748 }
749
registerAsBackgroundScript(const QString & name)750 void scriptengine::registerAsBackgroundScript(const QString &name)
751 {
752 static QMap<QString, QPointer<scriptengine> > backgroundScripts;
753
754 QString realName = name.isEmpty() ? getScriptHash() : name;
755 if (!backgroundScripts.value(realName, QPointer<scriptengine>(nullptr)).isNull())
756 delete backgroundScripts.value(realName, QPointer<scriptengine>(nullptr)).data();
757 backgroundScripts.insert(realName, this);
758 //backgroundScript = true;
759 }
760
setTimeout(const QString & fun,const int timeout)761 bool scriptengine::setTimeout(const QString &fun,const int timeout)
762 {
763 QTimer *tm=new QTimer();
764 QStringList parts=fun.split(" ");
765 tm->singleShot(timeout,this,std::bind(&scriptengine::runTimed,this,parts.value(1)));
766
767 return true;
768 }
769
runTimed(const QString fun)770 void scriptengine::runTimed(const QString fun)
771 {
772 if(!fun.isEmpty()){
773 engine->evaluate(fun);
774 }
775 }
776
UniversalInputDialogScript(QWidget * parent)777 UniversalInputDialogScript::UniversalInputDialogScript(QWidget *parent): UniversalInputDialog(parent)
778 {
779 }
780
~UniversalInputDialogScript()781 UniversalInputDialogScript::~UniversalInputDialogScript()
782 {
783 for (int i = 0; i < properties.size(); i++) properties[i].deallocate();
784 }
785
add(const QJSValue & def,const QJSValue & description,const QJSValue & id)786 QWidget* UniversalInputDialogScript::add(const QJSValue &def, const QJSValue &description, const QJSValue &id)
787 {
788 QWidget *w = nullptr;
789 if (def.isArray()) {
790 QStringList options;
791 QJSValueIterator it(def);
792 while (it.hasNext()) {
793 it.next();
794 if(it.name()=="length") continue; // skip length property
795 if (it.value().isString() || it.value().isNumber()) options << it.value().toString();
796 //else engine->throwError("Invalid default value in array (must be string or number): " + it.value().toString());
797 }
798 w = addComboBox(ManagedProperty::fromValue(options), description.toString());
799 } else if (def.isBool()) {
800 w = addCheckBox(ManagedProperty::fromValue(def.toBool()), description.toString());
801 } else if (def.isNumber()) {
802 w = addDoubleSpinBox(ManagedProperty::fromValue(def.toNumber()), description.toString());
803 } else if (def.isString()) {
804 w = addLineEdit(ManagedProperty::fromValue(def.toString()), description.toString());
805 } else {
806
807 //engine->throwError(tr("Invalid default value: %1").arg(def.toString()));
808 return nullptr;
809 }
810 if (!id.isUndefined()) properties.last().name = id.toString();
811 return w;
812 }
813
814
execDialog()815 QJSValue UniversalInputDialogScript::execDialog()
816 {
817 if (!UniversalInputDialog::exec()) return QJSValue();
818 return getAll();
819 }
820
821
getAll()822 QJSValue UniversalInputDialogScript::getAll()
823 {
824 QJSValue res = engine->newArray(properties.size());
825 for (int i = 0; i < properties.size(); i++) {
826 res.setProperty(i, engine->toScriptValue(properties[i].valueToQVariant()));
827 if (!properties[i].name.isEmpty())
828 res.setProperty(properties[i].name, engine->toScriptValue(properties[i].valueToQVariant()));
829 }
830 return res;
831 }
832
get(const QJSValue & id)833 QVariant UniversalInputDialogScript::get(const QJSValue &id)
834 {
835 if (id.isNumber()) {
836 int i = id.toInt();
837 if (i < 0 || i > properties.size()) return QVariant();
838 return properties[i].valueToQVariant();
839 }
840 if (id.isString()) {
841 QString sid = id.toString();
842 foreach (const ManagedProperty &mp, properties)
843 if (mp.name == sid)
844 return mp.valueToQVariant();
845 return QVariant();
846 }
847 //engine->throwError(tr("Unknown variable %1").arg(id.toString()));
848 return QVariant();
849 }
850
851 #undef SCRIPT_ASSERT
852