QGIS API Documentation 4.1.0-Master (659fe69c07c)
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 setLexer( pyLexer );
143
144 QgsSettings settings;
145 const int threshold = settings.value( u"pythonConsole/autoCompThreshold"_s, 2 ).toInt();
146 setAutoCompletionThreshold( threshold );
147 if ( !settings.value( "pythonConsole/autoCompleteEnabled", true ).toBool() )
148 {
149 setAutoCompletionSource( AcsNone );
150 }
151 else
152 {
153 const QString autoCompleteSource = settings.value( u"pythonConsole/autoCompleteSource"_s, u"fromAPI"_s ).toString();
154 if ( autoCompleteSource == "fromDoc"_L1 )
155 setAutoCompletionSource( AcsDocument );
156 else if ( autoCompleteSource == "fromDocAPI"_L1 )
157 setAutoCompletionSource( AcsAll );
158 else
159 setAutoCompletionSource( AcsAPIs );
160 }
161
162 setLineNumbersVisible( true );
163 setIndentationsUseTabs( false );
164 setIndentationGuides( true );
165
167
168 mInitializedLexer = false;
169 if ( isVisible() )
170 deferredInitializeLexer();
171}
172
173void QgsCodeEditorPython::deferredInitializeLexer()
174{
175 QgsSettings settings;
176 auto apis = std::make_unique<QsciAPIs>( lexer() );
177
178 if ( mAPISFilesList.isEmpty() )
179 {
180 if ( settings.value( u"pythonConsole/preloadAPI"_s, true ).toBool() )
181 {
182 mPapFile = QgsApplication::pkgDataPath() + u"/python/qsci_apis/PyQGIS.pap"_s;
183 apis->loadPrepared( mPapFile );
184 }
185 else if ( settings.value( u"pythonConsole/usePreparedAPIFile"_s, false ).toBool() )
186 {
187 apis->loadPrepared( settings.value( u"pythonConsole/preparedAPIFile"_s ).toString() );
188 }
189 else
190 {
191 const QStringList apiPaths = settings.value( u"pythonConsole/userAPI"_s ).toStringList();
192 for ( const QString &path : apiPaths )
193 {
194 if ( !QFileInfo::exists( path ) )
195 {
196 QgsDebugError( u"The apis file %1 was not found"_s.arg( path ) );
197 }
198 else
199 {
200 apis->load( path );
201 }
202 }
203 apis->prepare();
204 }
205 }
206 else if ( mAPISFilesList.length() == 1 && mAPISFilesList[0].right( 3 ) == "pap"_L1 )
207 {
208 if ( !QFileInfo::exists( mAPISFilesList[0] ) )
209 {
210 QgsDebugError( u"The apis file %1 not found"_s.arg( mAPISFilesList.at( 0 ) ) );
211 return;
212 }
213 mPapFile = mAPISFilesList[0];
214 apis->loadPrepared( mPapFile );
215 }
216 else
217 {
218 for ( const QString &path : std::as_const( mAPISFilesList ) )
219 {
220 if ( !QFileInfo::exists( path ) )
221 {
222 QgsDebugError( u"The apis file %1 was not found"_s.arg( path ) );
223 }
224 else
225 {
226 apis->load( path );
227 }
228 }
229 apis->prepare();
230 }
231 lexer()->setAPIs( apis.release() );
232
233 mInitializedLexer = true;
234}
235
237{
238 // If editor is readOnly, use the default implementation
239 if ( isReadOnly() )
240 {
242 }
243
244 const QgsSettings settings;
245
246 bool autoCloseBracket = settings.value( u"/pythonConsole/autoCloseBracket"_s, true ).toBool();
247 bool autoSurround = settings.value( u"/pythonConsole/autoSurround"_s, true ).toBool();
248 bool autoInsertImport = settings.value( u"/pythonConsole/autoInsertImport"_s, false ).toBool();
249
250 // Get entered text and cursor position
251 const QString eText = event->text();
252 int line, column;
253 getCursorPosition( &line, &column );
254
255 // If some text is selected and user presses an opening character
256 // surround the selection with the opening-closing pair
257 if ( hasSelectedText() && autoSurround )
258 {
259 if ( sCompletionPairs.contains( eText ) )
260 {
261 int startLine, startPos, endLine, endPos;
262 getSelection( &startLine, &startPos, &endLine, &endPos );
263
264 // Special case for Multi line quotes (insert triple quotes)
265 if ( startLine != endLine && ( eText == "\"" || eText == "'" ) )
266 {
267 replaceSelectedText( QString( "%1%1%1%2%3%3%3" ).arg( eText, selectedText(), sCompletionPairs[eText] ) );
268 setSelection( startLine, startPos + 3, endLine, endPos + 3 );
269 }
270 else
271 {
272 replaceSelectedText( QString( "%1%2%3" ).arg( eText, selectedText(), sCompletionPairs[eText] ) );
273 setSelection( startLine, startPos + 1, endLine, endPos + 1 );
274 }
275 event->accept();
276 return;
277 }
278 else if ( sCompletionSingleCharacters.contains( eText ) )
279 {
280 int startLine, startPos, endLine, endPos;
281 getSelection( &startLine, &startPos, &endLine, &endPos );
282 replaceSelectedText( QString( "%1%2%1" ).arg( eText, selectedText() ) );
283 setSelection( startLine, startPos + 1, endLine, endPos + 1 );
284 event->accept();
285 return;
286 }
287 }
288
289 // No selected text
290 else
291 {
292 // Automatically insert "import" after "from xxx " if option is enabled
293 if ( autoInsertImport && eText == " " )
294 {
295 const QString lineText = text( line );
296 const thread_local QRegularExpression re( u"^from [\\w.]+$"_s );
297 if ( re.match( lineText.trimmed() ).hasMatch() )
298 {
299 insert( u" import"_s );
300 setCursorPosition( line, column + 7 );
302 }
303 }
304
305 // Handle automatic bracket insertion/deletion if option is enabled
306 else if ( autoCloseBracket )
307 {
308 const QString prevChar = characterBeforeCursor();
309 const QString nextChar = characterAfterCursor();
310
311 // When backspace is pressed inside an opening/closing pair, remove both characters
312 if ( event->key() == Qt::Key_Backspace )
313 {
314 if ( sCompletionPairs.contains( prevChar ) && sCompletionPairs[prevChar] == nextChar )
315 {
316 setSelection( line, column - 1, line, column + 1 );
317 removeSelectedText();
318 event->accept();
319 // Update calltips (cursor position has changed)
320 callTip();
321 }
322 else
323 {
325 }
326 return;
327 }
328
329 // When closing character is entered inside an opening/closing pair, shift the cursor
330 else if ( sCompletionPairs.key( eText ) != "" && nextChar == eText )
331 {
332 setCursorPosition( line, column + 1 );
333 event->accept();
334
335 // Will hide calltips when a closing parenthesis is entered
336 callTip();
337 return;
338 }
339
340 // Else, if not inside a string or comment and an opening character
341 // is entered, also insert the closing character, provided the next
342 // character is a space, a colon, or a closing character
344 && sCompletionPairs.contains( eText )
345 && ( nextChar.isEmpty() || nextChar.at( 0 ).isSpace() || nextChar == ":" || sCompletionPairs.key( nextChar ) != "" ) )
346 {
347 // Check if user is not entering triple quotes
348 if ( !( ( eText == "\"" || eText == "'" ) && prevChar == eText ) )
349 {
351 insert( sCompletionPairs[eText] );
352 event->accept();
353 return;
354 }
355 }
356 }
357 }
358
359 // Let QgsCodeEditor handle the keyboard event
361}
362
364{
365 if ( !mInitializedLexer )
366 {
367 deferredInitializeLexer();
368 }
369 QgsCodeEditor::showEvent( event );
370}
371
372QString QgsCodeEditorPython::reformatCodeString( const QString &string )
373{
375 {
376 return string;
377 }
378
379 const QString formatter = settingCodeFormatter->value();
380 const int maxLineLength = settingMaxLineLength->value();
381
382 QString newText = string;
383
384 QStringList missingModules;
385
386 if ( settingSortImports->value() )
387 {
388 const QString defineSortImports = QStringLiteral(
389 "def __qgis_sort_imports(script):\n"
390 " try:\n"
391 " import isort\n"
392 " except ImportError:\n"
393 " return '_ImportError'\n"
394 " options={'line_length': %1, 'profile': '%2', 'known_first_party': ['qgis', 'console', 'processing', 'plugins']}\n"
395 " return isort.code(script, **options)\n"
396 )
397 .arg( maxLineLength )
398 .arg( formatter == "black"_L1 ? u"black"_s : QString() );
399
400 if ( !QgsPythonRunner::run( defineSortImports ) )
401 {
402 QgsDebugError( u"Error running script: %1"_s.arg( defineSortImports ) );
403 return string;
404 }
405
406 const QString script = u"__qgis_sort_imports(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
407 QString result;
408 if ( QgsPythonRunner::eval( script, result ) )
409 {
410 if ( result == "_ImportError"_L1 )
411 {
412 missingModules << u"isort"_s;
413 }
414 else
415 {
416 newText = result;
417 }
418 }
419 else
420 {
421 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
422 return newText;
423 }
424 }
425
426 if ( formatter == "autopep8"_L1 )
427 {
428 const int level = settingAutopep8Level->value();
429
430 const QString defineReformat = QStringLiteral(
431 "def __qgis_reformat(script):\n"
432 " try:\n"
433 " import autopep8\n"
434 " except ImportError:\n"
435 " return '_ImportError'\n"
436 " options={'aggressive': %1, 'max_line_length': %2}\n"
437 " return autopep8.fix_code(script, options=options)\n"
438 )
439 .arg( level )
440 .arg( maxLineLength );
441
442 if ( !QgsPythonRunner::run( defineReformat ) )
443 {
444 QgsDebugError( u"Error running script: %1"_s.arg( defineReformat ) );
445 return newText;
446 }
447
448 const QString script = u"__qgis_reformat(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
449 QString result;
450 if ( QgsPythonRunner::eval( script, result ) )
451 {
452 if ( result == "_ImportError"_L1 )
453 {
454 missingModules << u"autopep8"_s;
455 }
456 else
457 {
458 newText = result;
459 }
460 }
461 else
462 {
463 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
464 return newText;
465 }
466 }
467 else if ( formatter == "black"_L1 )
468 {
469 const bool normalize = settingBlackNormalizeQuotes->value();
470
471 if ( !checkSyntax() )
472 {
473 showMessage( tr( "Reformat Code" ), tr( "Code formatting failed -- the code contains syntax errors" ), Qgis::MessageLevel::Warning );
474 return newText;
475 }
476
477 const QString defineReformat = QStringLiteral(
478 "def __qgis_reformat(script):\n"
479 " try:\n"
480 " import black\n"
481 " except ImportError:\n"
482 " return '_ImportError'\n"
483 " options={'string_normalization': %1, 'line_length': %2}\n"
484 " return black.format_str(script, mode=black.Mode(**options))\n"
485 )
487 .arg( maxLineLength );
488
489 if ( !QgsPythonRunner::run( defineReformat ) )
490 {
491 QgsDebugError( u"Error running script: %1"_s.arg( defineReformat ) );
492 return string;
493 }
494
495 const QString script = u"__qgis_reformat(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
496 QString result;
497 if ( QgsPythonRunner::eval( script, result ) )
498 {
499 if ( result == "_ImportError"_L1 )
500 {
501 missingModules << u"black"_s;
502 }
503 else
504 {
505 newText = result;
506 }
507 }
508 else
509 {
510 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
511 return newText;
512 }
513 }
514
515 if ( !missingModules.empty() )
516 {
517 if ( missingModules.size() == 1 )
518 {
519 showMessage( tr( "Reformat Code" ), tr( "The Python module %1 is missing" ).arg( missingModules.at( 0 ) ), Qgis::MessageLevel::Warning );
520 }
521 else
522 {
523 const QString modules = missingModules.join( ", "_L1 );
524 showMessage( tr( "Reformat Code" ), tr( "The Python modules %1 are missing" ).arg( modules ), Qgis::MessageLevel::Warning );
525 }
526 }
527
528 return newText;
529}
530
532{
534
535 QString text = selectedText();
536 if ( text.isEmpty() )
537 {
538 text = wordAtPoint( mapFromGlobal( QCursor::pos() ) );
539 }
540 if ( text.isEmpty() )
541 {
542 return;
543 }
544
545 QAction *pyQgisHelpAction = new QAction( QgsApplication::getThemeIcon( u"console/iconHelpConsole.svg"_s ), tr( "Search Selection in PyQGIS Documentation" ), menu );
546
547 pyQgisHelpAction->setEnabled( hasSelectedText() );
548 pyQgisHelpAction->setShortcut( QKeySequence::StandardKey::HelpContents );
549 connect( pyQgisHelpAction, &QAction::triggered, this, [text, this] { showApiDocumentation( text ); } );
550
551 menu->addSeparator();
552 menu->addAction( pyQgisHelpAction );
553}
554
556{
557 switch ( autoCompletionSource() )
558 {
559 case AcsDocument:
560 autoCompleteFromDocument();
561 break;
562
563 case AcsAPIs:
564 autoCompleteFromAPIs();
565 break;
566
567 case AcsAll:
568 autoCompleteFromAll();
569 break;
570
571 case AcsNone:
572 break;
573 }
574}
575
576void QgsCodeEditorPython::loadAPIs( const QList<QString> &filenames )
577{
578 mAPISFilesList = filenames;
579 //QgsDebugMsgLevel( u"The apis files: %1"_s.arg( mAPISFilesList[0] ), 2 );
581}
582
583bool QgsCodeEditorPython::loadScript( const QString &script )
584{
585 QgsDebugMsgLevel( u"The script file: %1"_s.arg( script ), 2 );
586 QFile file( script );
587 if ( !file.open( QIODevice::ReadOnly ) )
588 {
589 return false;
590 }
591
592 QTextStream in( &file );
593 setText( in.readAll().trimmed() );
594 file.close();
595
597 return true;
598}
599
601{
602 int position = linearPosition();
603
604 // Special case: cursor at the end of the document. Style will always be Default,
605 // so we have to check the style of the previous character.
606 // It it is an unclosed string (triple string, unclosed, or comment),
607 // consider cursor is inside a string.
608 if ( position >= length() && position > 0 )
609 {
610 long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position - 1 );
611 return style == QsciLexerPython::Comment
612 || style == QsciLexerPython::TripleSingleQuotedString
613 || style == QsciLexerPython::TripleDoubleQuotedString
614 || style == QsciLexerPython::TripleSingleQuotedFString
615 || style == QsciLexerPython::TripleDoubleQuotedFString
616 || style == QsciLexerPython::UnclosedString;
617 }
618 else
619 {
620 long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position );
621 return style == QsciLexerPython::Comment
622 || style == QsciLexerPython::DoubleQuotedString
623 || style == QsciLexerPython::SingleQuotedString
624 || style == QsciLexerPython::TripleSingleQuotedString
625 || style == QsciLexerPython::TripleDoubleQuotedString
626 || style == QsciLexerPython::CommentBlock
627 || style == QsciLexerPython::UnclosedString
628 || style == QsciLexerPython::DoubleQuotedFString
629 || style == QsciLexerPython::SingleQuotedFString
630 || style == QsciLexerPython::TripleSingleQuotedFString
631 || style == QsciLexerPython::TripleDoubleQuotedFString;
632 }
633}
634
636{
637 int position = linearPosition();
638 if ( position <= 0 )
639 {
640 return QString();
641 }
642 return text( position - 1, position );
643}
644
646{
647 int position = linearPosition();
648 if ( position >= length() )
649 {
650 return QString();
651 }
652 return text( position, position + 1 );
653}
654
656{
658
660 return;
661
663
664 // we could potentially check for autopep8/black import here and reflect the capability accordingly.
665 // (current approach is to to always indicate this capability and raise a user-friendly warning
666 // when attempting to reformat if the libraries can't be imported)
668}
669
671{
673
675 {
676 return true;
677 }
678
679 const QString originalText = text();
680
681 const QString defineCheckSyntax = QStringLiteral(
682 "def __check_syntax(script):\n"
683 " try:\n"
684 " compile(script.encode('utf-8'), '', 'exec')\n"
685 " except SyntaxError as detail:\n"
686 " eline = detail.lineno or 1\n"
687 " eline -= 1\n"
688 " ecolumn = detail.offset or 1\n"
689 " edescr = detail.msg\n"
690 " return '!!!!'.join([str(eline), str(ecolumn), edescr])\n"
691 " return ''"
692 );
693
694 if ( !QgsPythonRunner::run( defineCheckSyntax ) )
695 {
696 QgsDebugError( u"Error running script: %1"_s.arg( defineCheckSyntax ) );
697 return true;
698 }
699
700 const QString script = u"__check_syntax(%1)"_s.arg( QgsProcessingUtils::stringToPythonLiteral( originalText ) );
701 QString result;
702 if ( QgsPythonRunner::eval( script, result ) )
703 {
704 if ( result.size() == 0 )
705 {
706 return true;
707 }
708 else
709 {
710 const QStringList parts = result.split( u"!!!!"_s );
711 if ( parts.size() == 3 )
712 {
713 const int line = parts.at( 0 ).toInt();
714 const int column = parts.at( 1 ).toInt();
715 addWarning( line, parts.at( 2 ) );
716 setCursorPosition( line, column - 1 );
717 ensureLineVisible( line );
718 }
719 return false;
720 }
721 }
722 else
723 {
724 QgsDebugError( u"Error running script: %1"_s.arg( script ) );
725 return true;
726 }
727}
728
733
735{
736 QString searchText = text;
737 searchText = searchText.replace( ">>> "_L1, QString() ).replace( "... "_L1, QString() ).trimmed(); // removing prompts
738
739 QRegularExpression qtExpression( "^Q[A-Z][a-zA-Z]" );
740
741 if ( qtExpression.match( searchText ).hasMatch() )
742 {
743 const QString qtVersion = QString( qVersion() ).split( '.' ).mid( 0, 2 ).join( '.' );
744 QString baseUrl = QString( "https://doc.qt.io/qt-%1" ).arg( qtVersion );
745 QDesktopServices::openUrl( QUrl( u"%1/%2.html"_s.arg( baseUrl, searchText.toLower() ) ) );
746 return;
747 }
748 const QString qgisVersion = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' );
749 if ( searchText.isEmpty() )
750 {
751 QDesktopServices::openUrl( QUrl( u"https://qgis.org/pyqgis/%1/"_s.arg( qgisVersion ) ) );
752 }
753 else
754 {
755 QDesktopServices::openUrl( QUrl( u"https://qgis.org/pyqgis/%1/search.html?q=%2"_s.arg( qgisVersion, searchText ) ) );
756 }
757}
758
763
765//
766// QgsQsciLexerPython
767//
768QgsQsciLexerPython::QgsQsciLexerPython( QObject *parent )
769 : QsciLexerPython( parent )
770{}
771
772const char *QgsQsciLexerPython::keywords( int set ) const
773{
774 if ( set == 1 )
775 {
776 return "True False and as assert break class continue def del elif else except "
777 "finally for from global if import in is lambda None not or pass "
778 "raise return try while with yield async await nonlocal";
779 }
780
781 return QsciLexerPython::keywords( set );
782}
static QString version()
Version string.
Definition qgis.cpp:682
@ Warning
Warning message.
Definition qgis.h:162
@ CheckSyntax
Language supports syntax checking.
Definition qgis.h:4901
@ Reformat
Language supports automatic code reformatting.
Definition qgis.h:4900
@ ToggleComment
Language supports comment toggling.
Definition qgis.h:4902
ScriptLanguage
Scripting languages.
Definition qgis.h:4876
@ Python
Python.
Definition qgis.h:4882
DocumentationBrowser
Documentation API browser.
Definition qgis.h:6686
@ DeveloperToolsPanel
Embedded webview in the DevTools panel.
Definition qgis.h:6687
QFlags< ScriptLanguageCapability > ScriptLanguageCapabilities
Script language capabilities.
Definition qgis.h:4911
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.
void showEvent(QShowEvent *event) override
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:80
#define QgsDebugError(str)
Definition qgslogger.h:71