QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
qgscodeeditorpython.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscodeeditorpython.cpp - A Python editor based on QScintilla
3 --------------------------------------
4 Date : 06-Oct-2013
5 Copyright : (C) 2013 by Salvatore Larosa
6 Email : lrssvtml (at) gmail (dot) com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgscodeeditorpython.h"
17
18#include "qgis.h"
19#include "qgsapplication.h"
20#include "qgslogger.h"
21#include "qgsprocessingutils.h"
22#include "qgspythonrunner.h"
23#include "qgssettings.h"
26#include "qgssymbollayerutils.h"
27
28#include <QAction>
29#include <QDesktopServices>
30#include <QFileInfo>
31#include <QFont>
32#include <QKeyEvent>
33#include <QMenu>
34#include <QMessageBox>
35#include <QString>
36#include <QTextStream>
37#include <QUrl>
38#include <QWidget>
39#include <Qsci/qscilexerpython.h>
40
41#include "moc_qgscodeeditorpython.cpp"
42
43using namespace Qt::StringLiterals;
44
45const QMap<QString, QString> QgsCodeEditorPython::sCompletionPairs { { "(", ")" }, { "[", "]" }, { "{", "}" }, { "'", "'" }, { "\"", "\"" } };
46const QStringList QgsCodeEditorPython::sCompletionSingleCharacters { "`", "*" };
48const QgsSettingsEntryString *QgsCodeEditorPython::settingCodeFormatter = new QgsSettingsEntryString( u"formatter"_s, sTreePythonCodeEditor, u"autopep8"_s, u"Python code autoformatter"_s );
49const QgsSettingsEntryInteger *QgsCodeEditorPython::settingMaxLineLength = new QgsSettingsEntryInteger( u"max-line-length"_s, sTreePythonCodeEditor, 80, u"Maximum line length"_s );
50const QgsSettingsEntryBool *QgsCodeEditorPython::settingSortImports
51 = new QgsSettingsEntryBool( u"sort-imports"_s, sTreePythonCodeEditor, true, u"Whether imports should be sorted when auto-formatting code"_s );
52const QgsSettingsEntryInteger *QgsCodeEditorPython::settingAutopep8Level = new QgsSettingsEntryInteger( u"autopep8-level"_s, sTreePythonCodeEditor, 1, u"Autopep8 aggressive level"_s );
53const QgsSettingsEntryBool *QgsCodeEditorPython::settingBlackNormalizeQuotes
54 = new QgsSettingsEntryBool( u"black-normalize-quotes"_s, sTreePythonCodeEditor, true, u"Whether quotes should be normalized when auto-formatting code using black"_s );
55const QgsSettingsEntryString *QgsCodeEditorPython::settingExternalPythonEditorCommand
56 = new QgsSettingsEntryString( u"external-editor"_s, sTreePythonCodeEditor, QString(), u"Command to launch an external Python code editor. Use the token <file> to insert the filename, <line> to insert line number, and <col> to insert the column number."_s );
57const QgsSettingsEntryEnumFlag<Qgis::DocumentationBrowser> *QgsCodeEditorPython::settingContextHelpBrowser = new QgsSettingsEntryEnumFlag<
58 Qgis::DocumentationBrowser>( u"context-help-browser"_s, sTreePythonCodeEditor, Qgis::DocumentationBrowser::DeveloperToolsPanel, u"Web browser used to display the api documentation"_s );
60
61
62QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList<QString> &filenames, Mode mode, Flags flags )
63 : QgsCodeEditor( parent, QString(), false, false, flags, mode )
64 , mAPISFilesList( filenames )
65{
66 if ( !parent )
67 {
68 setTitle( tr( "Python Editor" ) );
69 }
70
71 setCaretWidth( 2 );
72
74
76
78}
79
84
89
91{
92 // current line
93 setEdgeMode( QsciScintilla::EdgeLine );
94 setEdgeColumn( settingMaxLineLength->value() );
96
97 setWhitespaceVisibility( QsciScintilla::WsVisibleAfterIndent );
98
99 SendScintilla( QsciScintillaBase::SCI_SETPROPERTY, "highlight.current.word", "1" );
100
101 QFont font = lexerFont();
103
104 QsciLexerPython *pyLexer = new QgsQsciLexerPython( this );
105
106 pyLexer->setIndentationWarning( QsciLexerPython::Inconsistent );
107 pyLexer->setFoldComments( true );
108 pyLexer->setFoldQuotes( true );
109
110 pyLexer->setDefaultFont( font );
111 pyLexer->setDefaultColor( defaultColor );
112 pyLexer->setDefaultPaper( lexerColor( QgsCodeEditorColorScheme::ColorRole::Background ) );
113 pyLexer->setFont( font, -1 );
114
115 font.setItalic( true );
116 pyLexer->setFont( font, QsciLexerPython::Comment );
117 pyLexer->setFont( font, QsciLexerPython::CommentBlock );
118
119 font.setItalic( false );
120 font.setBold( true );
121 pyLexer->setFont( font, QsciLexerPython::SingleQuotedString );
122 pyLexer->setFont( font, QsciLexerPython::DoubleQuotedString );
123
124 pyLexer->setColor( defaultColor, QsciLexerPython::Default );
125 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Error ), QsciLexerPython::UnclosedString );
126 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Class ), QsciLexerPython::ClassName );
127 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Method ), QsciLexerPython::FunctionMethodName );
128 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Number ), QsciLexerPython::Number );
129 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Operator ), QsciLexerPython::Operator );
130 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Identifier ), QsciLexerPython::Identifier );
131 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Comment ), QsciLexerPython::Comment );
132 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::CommentBlock ), QsciLexerPython::CommentBlock );
133 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Keyword ), QsciLexerPython::Keyword );
134 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Decoration ), QsciLexerPython::Decorator );
135 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::SingleQuote ), QsciLexerPython::SingleQuotedString );
136 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::SingleQuote ), QsciLexerPython::SingleQuotedFString );
137 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::DoubleQuote ), QsciLexerPython::DoubleQuotedString );
138 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::DoubleQuote ), QsciLexerPython::DoubleQuotedFString );
139 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::TripleSingleQuote ), QsciLexerPython::TripleSingleQuotedString );
140 pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::TripleDoubleQuote ), QsciLexerPython::TripleDoubleQuotedString );
141
142 auto apis = std::make_unique<QsciAPIs>( pyLexer );
143
144 QgsSettings settings;
145 if ( mAPISFilesList.isEmpty() )
146 {
147 if ( settings.value( u"pythonConsole/preloadAPI"_s, true ).toBool() )
148 {
149 mPapFile = QgsApplication::pkgDataPath() + u"/python/qsci_apis/PyQGIS.pap"_s;
150 apis->loadPrepared( mPapFile );
151 }
152 else if ( settings.value( u"pythonConsole/usePreparedAPIFile"_s, false ).toBool() )
153 {
154 apis->loadPrepared( settings.value( u"pythonConsole/preparedAPIFile"_s ).toString() );
155 }
156 else
157 {
158 const QStringList apiPaths = settings.value( u"pythonConsole/userAPI"_s ).toStringList();
159 for ( const QString &path : apiPaths )
160 {
161 if ( !QFileInfo::exists( path ) )
162 {
163 QgsDebugError( u"The apis file %1 was not found"_s.arg( path ) );
164 }
165 else
166 {
167 apis->load( path );
168 }
169 }
170 apis->prepare();
171 }
172 }
173 else if ( mAPISFilesList.length() == 1 && mAPISFilesList[0].right( 3 ) == "pap"_L1 )
174 {
175 if ( !QFileInfo::exists( mAPISFilesList[0] ) )
176 {
177 QgsDebugError( u"The apis file %1 not found"_s.arg( mAPISFilesList.at( 0 ) ) );
178 return;
179 }
180 mPapFile = mAPISFilesList[0];
181 apis->loadPrepared( mPapFile );
182 }
183 else
184 {
185 for ( const QString &path : std::as_const( mAPISFilesList ) )
186 {
187 if ( !QFileInfo::exists( path ) )
188 {
189 QgsDebugError( u"The apis file %1 was not found"_s.arg( path ) );
190 }
191 else
192 {
193 apis->load( path );
194 }
195 }
196 apis->prepare();
197 }
198 pyLexer->setAPIs( apis.release() );
199
200 setLexer( pyLexer );
201
202 const int threshold = settings.value( u"pythonConsole/autoCompThreshold"_s, 2 ).toInt();
203 setAutoCompletionThreshold( threshold );
204 if ( !settings.value( "pythonConsole/autoCompleteEnabled", true ).toBool() )
205 {
206 setAutoCompletionSource( AcsNone );
207 }
208 else
209 {
210 const QString autoCompleteSource = settings.value( u"pythonConsole/autoCompleteSource"_s, u"fromAPI"_s ).toString();
211 if ( autoCompleteSource == "fromDoc"_L1 )
212 setAutoCompletionSource( AcsDocument );
213 else if ( autoCompleteSource == "fromDocAPI"_L1 )
214 setAutoCompletionSource( AcsAll );
215 else
216 setAutoCompletionSource( AcsAPIs );
217 }
218
219 setLineNumbersVisible( true );
220 setIndentationsUseTabs( false );
221 setIndentationGuides( true );
222
224}
225
227{
228 // If editor is readOnly, use the default implementation
229 if ( isReadOnly() )
230 {
232 }
233
234 const QgsSettings settings;
235
236 bool autoCloseBracket = settings.value( u"/pythonConsole/autoCloseBracket"_s, true ).toBool();
237 bool autoSurround = settings.value( u"/pythonConsole/autoSurround"_s, true ).toBool();
238 bool autoInsertImport = settings.value( u"/pythonConsole/autoInsertImport"_s, false ).toBool();
239
240 // Get entered text and cursor position
241 const QString eText = event->text();
242 int line, column;
243 getCursorPosition( &line, &column );
244
245 // If some text is selected and user presses an opening character
246 // surround the selection with the opening-closing pair
247 if ( hasSelectedText() && autoSurround )
248 {
249 if ( sCompletionPairs.contains( eText ) )
250 {
251 int startLine, startPos, endLine, endPos;
252 getSelection( &startLine, &startPos, &endLine, &endPos );
253
254 // Special case for Multi line quotes (insert triple quotes)
255 if ( startLine != endLine && ( eText == "\"" || eText == "'" ) )
256 {
257 replaceSelectedText( QString( "%1%1%1%2%3%3%3" ).arg( eText, selectedText(), sCompletionPairs[eText] ) );
258 setSelection( startLine, startPos + 3, endLine, endPos + 3 );
259 }
260 else
261 {
262 replaceSelectedText( QString( "%1%2%3" ).arg( eText, selectedText(), sCompletionPairs[eText] ) );
263 setSelection( startLine, startPos + 1, endLine, endPos + 1 );
264 }
265 event->accept();
266 return;
267 }
268 else if ( sCompletionSingleCharacters.contains( eText ) )
269 {
270 int startLine, startPos, endLine, endPos;
271 getSelection( &startLine, &startPos, &endLine, &endPos );
272 replaceSelectedText( QString( "%1%2%1" ).arg( eText, selectedText() ) );
273 setSelection( startLine, startPos + 1, endLine, endPos + 1 );
274 event->accept();
275 return;
276 }
277 }
278
279 // No selected text
280 else
281 {
282 // Automatically insert "import" after "from xxx " if option is enabled
283 if ( autoInsertImport && eText == " " )
284 {
285 const QString lineText = text( line );
286 const thread_local QRegularExpression re( u"^from [\\w.]+$"_s );
287 if ( re.match( lineText.trimmed() ).hasMatch() )
288 {
289 insert( u" import"_s );
290 setCursorPosition( line, column + 7 );
292 }
293 }
294
295 // Handle automatic bracket insertion/deletion if option is enabled
296 else if ( autoCloseBracket )
297 {
298 const QString prevChar = characterBeforeCursor();
299 const QString nextChar = characterAfterCursor();
300
301 // When backspace is pressed inside an opening/closing pair, remove both characters
302 if ( event->key() == Qt::Key_Backspace )
303 {
304 if ( sCompletionPairs.contains( prevChar ) && sCompletionPairs[prevChar] == nextChar )
305 {
306 setSelection( line, column - 1, line, column + 1 );
307 removeSelectedText();
308 event->accept();
309 // Update calltips (cursor position has changed)
310 callTip();
311 }
312 else
313 {
315 }
316 return;
317 }
318
319 // When closing character is entered inside an opening/closing pair, shift the cursor
320 else if ( sCompletionPairs.key( eText ) != "" && nextChar == eText )
321 {
322 setCursorPosition( line, column + 1 );
323 event->accept();
324
325 // Will hide calltips when a closing parenthesis is entered
326 callTip();
327 return;
328 }
329
330 // Else, if not inside a string or comment and an opening character
331 // is entered, also insert the closing character, provided the next
332 // character is a space, a colon, or a closing character
334 && sCompletionPairs.contains( eText )
335 && ( nextChar.isEmpty() || nextChar.at( 0 ).isSpace() || nextChar == ":" || sCompletionPairs.key( nextChar ) != "" ) )
336 {
337 // Check if user is not entering triple quotes
338 if ( !( ( eText == "\"" || eText == "'" ) && prevChar == eText ) )
339 {
341 insert( sCompletionPairs[eText] );
342 event->accept();
343 return;
344 }
345 }
346 }
347 }
348
349 // Let QgsCodeEditor handle the keyboard event
351}
352
353QString QgsCodeEditorPython::reformatCodeString( const QString &string )
354{
356 {
357 return string;
358 }
359
360 const QString formatter = settingCodeFormatter->value();
361 const int maxLineLength = settingMaxLineLength->value();
362
363 QString newText = string;
364
365 QStringList missingModules;
366
367 if ( settingSortImports->value() )
368 {
369 const QString defineSortImports = QStringLiteral(
370 "def __qgis_sort_imports(script):\n"
371 " try:\n"
372 " import isort\n"
373 " except ImportError:\n"
374 " return '_ImportError'\n"
375 " options={'line_length': %1, 'profile': '%2', 'known_first_party': ['qgis', 'console', 'processing', 'plugins']}\n"
376 " return isort.code(script, **options)\n"
377 )
378 .arg( maxLineLength )
379 .arg( formatter == "black"_L1 ? u"black"_s : QString() );
380
381 if ( !QgsPythonRunner::run( defineSortImports ) )
382 {
383 QgsDebugError( u"Error running script: %1"_s.arg( defineSortImports ) );
384 return string;
385 }
386
387 const QString script = u"__qgis_sort_imports(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
388 QString result;
389 if ( QgsPythonRunner::eval( script, result ) )
390 {
391 if ( result == "_ImportError"_L1 )
392 {
393 missingModules << u"isort"_s;
394 }
395 else
396 {
397 newText = result;
398 }
399 }
400 else
401 {
402 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
403 return newText;
404 }
405 }
406
407 if ( formatter == "autopep8"_L1 )
408 {
409 const int level = settingAutopep8Level->value();
410
411 const QString defineReformat = QStringLiteral(
412 "def __qgis_reformat(script):\n"
413 " try:\n"
414 " import autopep8\n"
415 " except ImportError:\n"
416 " return '_ImportError'\n"
417 " options={'aggressive': %1, 'max_line_length': %2}\n"
418 " return autopep8.fix_code(script, options=options)\n"
419 )
420 .arg( level )
421 .arg( maxLineLength );
422
423 if ( !QgsPythonRunner::run( defineReformat ) )
424 {
425 QgsDebugError( u"Error running script: %1"_s.arg( defineReformat ) );
426 return newText;
427 }
428
429 const QString script = u"__qgis_reformat(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
430 QString result;
431 if ( QgsPythonRunner::eval( script, result ) )
432 {
433 if ( result == "_ImportError"_L1 )
434 {
435 missingModules << u"autopep8"_s;
436 }
437 else
438 {
439 newText = result;
440 }
441 }
442 else
443 {
444 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
445 return newText;
446 }
447 }
448 else if ( formatter == "black"_L1 )
449 {
450 const bool normalize = settingBlackNormalizeQuotes->value();
451
452 if ( !checkSyntax() )
453 {
454 showMessage( tr( "Reformat Code" ), tr( "Code formatting failed -- the code contains syntax errors" ), Qgis::MessageLevel::Warning );
455 return newText;
456 }
457
458 const QString defineReformat = QStringLiteral(
459 "def __qgis_reformat(script):\n"
460 " try:\n"
461 " import black\n"
462 " except ImportError:\n"
463 " return '_ImportError'\n"
464 " options={'string_normalization': %1, 'line_length': %2}\n"
465 " return black.format_str(script, mode=black.Mode(**options))\n"
466 )
468 .arg( maxLineLength );
469
470 if ( !QgsPythonRunner::run( defineReformat ) )
471 {
472 QgsDebugError( u"Error running script: %1"_s.arg( defineReformat ) );
473 return string;
474 }
475
476 const QString script = u"__qgis_reformat(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
477 QString result;
478 if ( QgsPythonRunner::eval( script, result ) )
479 {
480 if ( result == "_ImportError"_L1 )
481 {
482 missingModules << u"black"_s;
483 }
484 else
485 {
486 newText = result;
487 }
488 }
489 else
490 {
491 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
492 return newText;
493 }
494 }
495
496 if ( !missingModules.empty() )
497 {
498 if ( missingModules.size() == 1 )
499 {
500 showMessage( tr( "Reformat Code" ), tr( "The Python module %1 is missing" ).arg( missingModules.at( 0 ) ), Qgis::MessageLevel::Warning );
501 }
502 else
503 {
504 const QString modules = missingModules.join( ", "_L1 );
505 showMessage( tr( "Reformat Code" ), tr( "The Python modules %1 are missing" ).arg( modules ), Qgis::MessageLevel::Warning );
506 }
507 }
508
509 return newText;
510}
511
513{
515
516 QString text = selectedText();
517 if ( text.isEmpty() )
518 {
519 text = wordAtPoint( mapFromGlobal( QCursor::pos() ) );
520 }
521 if ( text.isEmpty() )
522 {
523 return;
524 }
525
526 QAction *pyQgisHelpAction = new QAction( QgsApplication::getThemeIcon( u"console/iconHelpConsole.svg"_s ), tr( "Search Selection in PyQGIS Documentation" ), menu );
527
528 pyQgisHelpAction->setEnabled( hasSelectedText() );
529 pyQgisHelpAction->setShortcut( QKeySequence::StandardKey::HelpContents );
530 connect( pyQgisHelpAction, &QAction::triggered, this, [text, this] { showApiDocumentation( text ); } );
531
532 menu->addSeparator();
533 menu->addAction( pyQgisHelpAction );
534}
535
537{
538 switch ( autoCompletionSource() )
539 {
540 case AcsDocument:
541 autoCompleteFromDocument();
542 break;
543
544 case AcsAPIs:
545 autoCompleteFromAPIs();
546 break;
547
548 case AcsAll:
549 autoCompleteFromAll();
550 break;
551
552 case AcsNone:
553 break;
554 }
555}
556
557void QgsCodeEditorPython::loadAPIs( const QList<QString> &filenames )
558{
559 mAPISFilesList = filenames;
560 //QgsDebugMsgLevel( u"The apis files: %1"_s.arg( mAPISFilesList[0] ), 2 );
562}
563
564bool QgsCodeEditorPython::loadScript( const QString &script )
565{
566 QgsDebugMsgLevel( u"The script file: %1"_s.arg( script ), 2 );
567 QFile file( script );
568 if ( !file.open( QIODevice::ReadOnly ) )
569 {
570 return false;
571 }
572
573 QTextStream in( &file );
574 setText( in.readAll().trimmed() );
575 file.close();
576
578 return true;
579}
580
582{
583 int position = linearPosition();
584
585 // Special case: cursor at the end of the document. Style will always be Default,
586 // so we have to check the style of the previous character.
587 // It it is an unclosed string (triple string, unclosed, or comment),
588 // consider cursor is inside a string.
589 if ( position >= length() && position > 0 )
590 {
591 long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position - 1 );
592 return style == QsciLexerPython::Comment
593 || style == QsciLexerPython::TripleSingleQuotedString
594 || style == QsciLexerPython::TripleDoubleQuotedString
595 || style == QsciLexerPython::TripleSingleQuotedFString
596 || style == QsciLexerPython::TripleDoubleQuotedFString
597 || style == QsciLexerPython::UnclosedString;
598 }
599 else
600 {
601 long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position );
602 return style == QsciLexerPython::Comment
603 || style == QsciLexerPython::DoubleQuotedString
604 || style == QsciLexerPython::SingleQuotedString
605 || style == QsciLexerPython::TripleSingleQuotedString
606 || style == QsciLexerPython::TripleDoubleQuotedString
607 || style == QsciLexerPython::CommentBlock
608 || style == QsciLexerPython::UnclosedString
609 || style == QsciLexerPython::DoubleQuotedFString
610 || style == QsciLexerPython::SingleQuotedFString
611 || style == QsciLexerPython::TripleSingleQuotedFString
612 || style == QsciLexerPython::TripleDoubleQuotedFString;
613 }
614}
615
617{
618 int position = linearPosition();
619 if ( position <= 0 )
620 {
621 return QString();
622 }
623 return text( position - 1, position );
624}
625
627{
628 int position = linearPosition();
629 if ( position >= length() )
630 {
631 return QString();
632 }
633 return text( position, position + 1 );
634}
635
637{
639
641 return;
642
644
645 // we could potentially check for autopep8/black import here and reflect the capability accordingly.
646 // (current approach is to to always indicate this capability and raise a user-friendly warning
647 // when attempting to reformat if the libraries can't be imported)
649}
650
652{
654
656 {
657 return true;
658 }
659
660 const QString originalText = text();
661
662 const QString defineCheckSyntax = QStringLiteral(
663 "def __check_syntax(script):\n"
664 " try:\n"
665 " compile(script.encode('utf-8'), '', 'exec')\n"
666 " except SyntaxError as detail:\n"
667 " eline = detail.lineno or 1\n"
668 " eline -= 1\n"
669 " ecolumn = detail.offset or 1\n"
670 " edescr = detail.msg\n"
671 " return '!!!!'.join([str(eline), str(ecolumn), edescr])\n"
672 " return ''"
673 );
674
675 if ( !QgsPythonRunner::run( defineCheckSyntax ) )
676 {
677 QgsDebugError( u"Error running script: %1"_s.arg( defineCheckSyntax ) );
678 return true;
679 }
680
681 const QString script = u"__check_syntax(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( originalText ) );
682 QString result;
683 if ( QgsPythonRunner::eval( script, result ) )
684 {
685 if ( result.size() == 0 )
686 {
687 return true;
688 }
689 else
690 {
691 const QStringList parts = result.split( u"!!!!"_s );
692 if ( parts.size() == 3 )
693 {
694 const int line = parts.at( 0 ).toInt();
695 const int column = parts.at( 1 ).toInt();
696 addWarning( line, parts.at( 2 ) );
697 setCursorPosition( line, column - 1 );
698 ensureLineVisible( line );
699 }
700 return false;
701 }
702 }
703 else
704 {
705 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
706 return true;
707 }
708}
709
714
716{
717 QString searchText = text;
718 searchText = searchText.replace( ">>> "_L1, QString() ).replace( "... "_L1, QString() ).trimmed(); // removing prompts
719
720 QRegularExpression qtExpression( "^Q[A-Z][a-zA-Z]" );
721
722 if ( qtExpression.match( searchText ).hasMatch() )
723 {
724 const QString qtVersion = QString( qVersion() ).split( '.' ).mid( 0, 2 ).join( '.' );
725 QString baseUrl = QString( "https://doc.qt.io/qt-%1" ).arg( qtVersion );
726 QDesktopServices::openUrl( QUrl( u"%1/%2.html"_s.arg( baseUrl, searchText.toLower() ) ) );
727 return;
728 }
729 const QString qgisVersion = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' );
730 if ( searchText.isEmpty() )
731 {
732 QDesktopServices::openUrl( QUrl( u"https://qgis.org/pyqgis/%1/"_s.arg( qgisVersion ) ) );
733 }
734 else
735 {
736 QDesktopServices::openUrl( QUrl( u"https://qgis.org/pyqgis/%1/search.html?q=%2"_s.arg( qgisVersion, searchText ) ) );
737 }
738}
739
744
746//
747// QgsQsciLexerPython
748//
749QgsQsciLexerPython::QgsQsciLexerPython( QObject *parent )
750 : QsciLexerPython( parent )
751{}
752
753const char *QgsQsciLexerPython::keywords( int set ) const
754{
755 if ( set == 1 )
756 {
757 return "True False and as assert break class continue def del elif else except "
758 "finally for from global if import in is lambda None not or pass "
759 "raise return try while with yield async await nonlocal";
760 }
761
762 return QsciLexerPython::keywords( set );
763}
static QString version()
Version string.
Definition qgis.cpp:682
@ Warning
Warning message.
Definition qgis.h:162
@ CheckSyntax
Language supports syntax checking.
Definition qgis.h:4647
@ Reformat
Language supports automatic code reformatting.
Definition qgis.h:4646
@ ToggleComment
Language supports comment toggling.
Definition qgis.h:4648
ScriptLanguage
Scripting languages.
Definition qgis.h:4622
@ Python
Python.
Definition qgis.h:4628
DocumentationBrowser
Documentation API browser.
Definition qgis.h:6370
@ DeveloperToolsPanel
Embedded webview in the DevTools panel.
Definition qgis.h:6371
QFlags< ScriptLanguageCapability > ScriptLanguageCapabilities
Script language capabilities.
Definition qgis.h:4657
static QString pkgDataPath()
Returns the common root path of all application data directories.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
@ TripleSingleQuote
Triple single quote color.
@ TripleDoubleQuote
Triple double quote color.
void autoComplete()
Triggers the autocompletion popup.
QString characterAfterCursor() const
Returns the character after the cursor, or an empty string if the cursor is set at end.
bool isCursorInsideStringLiteralOrComment() const
Check whether the current cursor position is inside a string literal or a comment.
QString reformatCodeString(const QString &string) override
Applies code reformatting to a string and returns the result.
void searchSelectedTextInPyQGISDocs()
Searches the selected text in the official PyQGIS online documentation.
Qgis::ScriptLanguage language() const override
Returns the associated scripting language.
void loadAPIs(const QList< QString > &filenames)
Load APIs from one or more files.
void toggleComment() override
Toggle comment for the selected text.
virtual void showApiDocumentation(const QString &item)
Displays the given text in the official APIs (PyQGIS, C++ QGIS or Qt) documentation.
void initializeLexer() override
Called when the dialect specific code lexer needs to be initialized (or reinitialized).
PRIVATE QgsCodeEditorPython(QWidget *parent=nullptr, const QList< QString > &filenames=QList< QString >(), QgsCodeEditor::Mode mode=QgsCodeEditor::Mode::ScriptEditor, QgsCodeEditor::Flags flags=QgsCodeEditor::Flag::CodeFolding)
Construct a new Python editor.
bool checkSyntax() override
Applies syntax checking to the editor.
void updateCapabilities()
Updates the editor capabilities.
Qgis::ScriptLanguageCapabilities languageCapabilities() const override
Returns the associated scripting language capabilities.
void keyPressEvent(QKeyEvent *event) override
bool loadScript(const QString &script)
Loads a script file.
void populateContextMenu(QMenu *menu) override
Called when the context menu for the widget is about to be shown, after it has been fully populated w...
QString characterBeforeCursor() const
Returns the character before the cursor, or an empty string if cursor is set at start.
QgsCodeEditor::Mode mode() const
Returns the code editor mode.
Mode
Code editor modes.
void keyPressEvent(QKeyEvent *event) override
virtual void populateContextMenu(QMenu *menu)
Called when the context menu for the widget is about to be shown, after it has been fully populated w...
QFlags< Flag > Flags
Flags controlling behavior of code editor.
void callTip() override
void setText(const QString &text) override
void runPostLexerConfigurationTasks()
Performs tasks which must be run after a lexer has been set for the widget.
bool event(QEvent *event) override
virtual void showMessage(const QString &title, const QString &message, Qgis::MessageLevel level)
Shows a user facing message (eg a warning message).
int linearPosition() const
Convenience function to return the cursor position as a linear index.
void setTitle(const QString &title)
Set the widget title.
QgsCodeEditor(QWidget *parent=nullptr, const QString &title=QString(), bool folding=false, bool margin=false, QgsCodeEditor::Flags flags=QgsCodeEditor::Flags(), QgsCodeEditor::Mode mode=QgsCodeEditor::Mode::ScriptEditor)
Construct a new code editor.
void clearWarnings()
Clears all warning messages from the editor.
void helpRequested(const QString &word)
Emitted when documentation was requested for the specified word.
void setLineNumbersVisible(bool visible)
Sets whether line numbers should be visible in the editor.
QFont lexerFont() const
Returns the font to use in the lexer.
void toggleLineComments(const QString &commentPrefix)
Toggles comment for selected lines with the given comment prefix.
QColor lexerColor(QgsCodeEditorColorScheme::ColorRole role) const
Returns the color to use in the lexer for the specified role.
static QColor defaultColor(QgsCodeEditorColorScheme::ColorRole role, const QString &theme=QString())
Returns the default color for the specified role.
void addWarning(int lineNumber, const QString &warning)
Adds a warning message and indicator to the specified a lineNumber.
static QString stringToPythonLiteral(const QString &string)
Converts a string to a Python string literal.
static QString variantToPythonLiteral(const QVariant &value)
Converts a variant to a Python literal.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool eval(const QString &command, QString &result)
Eval a Python statement.
static bool isValid()
Returns true if the runner has an instance (and thus is able to run commands).
A boolean settings entry.
A template class for enum and flag settings entry.
An integer settings entry.
A string settings entry.
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59