Skip to content

Commit 3312bf1

Browse files
committedMar 20, 2023
Move syntax checking capability to QgsCodeEditorPython
1 parent ac5f8da commit 3312bf1

File tree

9 files changed

+112
-8
lines changed

9 files changed

+112
-8
lines changed
 

‎python/core/auto_additions/qgis.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2590,7 +2590,8 @@
25902590
Qgis.ScriptLanguage.baseClass = Qgis
25912591
# monkey patching scoped based enum
25922592
Qgis.ScriptLanguageCapability.Reformat.__doc__ = "Language supports automatic code reformatting"
2593-
Qgis.ScriptLanguageCapability.__doc__ = 'Script language capabilities.\n\nThe flags reflect the support capabilities of a scripting language.\n\n.. versionadded:: 3.32\n\n' + '* ``Reformat``: ' + Qgis.ScriptLanguageCapability.Reformat.__doc__
2593+
Qgis.ScriptLanguageCapability.CheckSyntax.__doc__ = "Language supports syntax checking"
2594+
Qgis.ScriptLanguageCapability.__doc__ = 'Script language capabilities.\n\nThe flags reflect the support capabilities of a scripting language.\n\n.. versionadded:: 3.32\n\n' + '* ``Reformat``: ' + Qgis.ScriptLanguageCapability.Reformat.__doc__ + '\n' + '* ``CheckSyntax``: ' + Qgis.ScriptLanguageCapability.CheckSyntax.__doc__
25942595
# --
25952596
Qgis.ScriptLanguageCapability.baseClass = Qgis
25962597
Qgis.ScriptLanguageCapabilities.baseClass = Qgis

‎python/core/auto_generated/qgis.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,7 @@ The development version
15921592
enum class ScriptLanguageCapability
15931593
{
15941594
Reformat,
1595+
CheckSyntax,
15951596
};
15961597

15971598
typedef QFlags<Qgis::ScriptLanguageCapability> ScriptLanguageCapabilities;

‎python/gui/auto_generated/codeeditors/qgscodeeditor.sip.in

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,15 @@ Applies code reformatting to the editor.
427427

428428
This is only supported for editors which return the Qgis.ScriptLanguageCapability.Reformat capability from :py:func:`~QgsCodeEditor.languageCapabilities`.
429429

430+
.. versionadded:: 3.32
431+
%End
432+
433+
virtual bool checkSyntax();
434+
%Docstring
435+
Applies syntax checking to the editor.
436+
437+
This is only supported for editors which return the Qgis.ScriptLanguageCapability.CheckSyntax capability from :py:func:`~QgsCodeEditor.languageCapabilities`.
438+
430439
.. versionadded:: 3.32
431440
%End
432441

‎python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ Updates the editor capabilities.
8585
.. versionadded:: 3.32
8686
%End
8787

88+
virtual bool checkSyntax();
89+
90+
8891
public slots:
8992

9093
void searchSelectedTextInPyQGISDocs();

‎src/core/qgis.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,6 +2735,7 @@ class CORE_EXPORT Qgis
27352735
enum class ScriptLanguageCapability : int
27362736
{
27372737
Reformat = 1 << 0, //!< Language supports automatic code reformatting
2738+
CheckSyntax = 1 << 1, //!< Language supports syntax checking
27382739
};
27392740
Q_ENUM( ScriptLanguageCapability )
27402741

‎src/gui/codeeditors/qgscodeeditor.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,16 +224,29 @@ void QgsCodeEditor::contextMenuEvent( QContextMenuEvent *event )
224224
QMenu *menu = createStandardContextMenu();
225225
menu->setAttribute( Qt::WA_DeleteOnClose );
226226

227-
if ( languageCapabilities() & Qgis::ScriptLanguageCapability::Reformat )
227+
if ( ( languageCapabilities() & Qgis::ScriptLanguageCapability::Reformat ) ||
228+
( languageCapabilities() & Qgis::ScriptLanguageCapability::CheckSyntax ) )
228229
{
229230
menu->addSeparator();
231+
}
230232

233+
if ( languageCapabilities() & Qgis::ScriptLanguageCapability::Reformat )
234+
{
231235
QAction *reformatAction = new QAction( tr( "Reformat Code" ), menu );
236+
reformatAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconFormatCode.svg" ) ) );
232237
reformatAction->setEnabled( !isReadOnly() );
233238
connect( reformatAction, &QAction::triggered, this, &QgsCodeEditor::reformatCode );
234239
menu->addAction( reformatAction );
235240
}
236241

242+
if ( languageCapabilities() & Qgis::ScriptLanguageCapability::Reformat )
243+
{
244+
QAction *syntaxCheckAction = new QAction( tr( "Check Syntax" ), menu );
245+
syntaxCheckAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconSyntaxErrorConsole.svg" ) ) );
246+
connect( syntaxCheckAction, &QAction::triggered, this, &QgsCodeEditor::checkSyntax );
247+
menu->addAction( syntaxCheckAction );
248+
}
249+
237250
populateContextMenu( menu );
238251

239252
menu->exec( mapToGlobal( event->pos() ) );
@@ -709,6 +722,11 @@ void QgsCodeEditor::reformatCode()
709722
endUndoAction();
710723
}
711724

725+
bool QgsCodeEditor::checkSyntax()
726+
{
727+
return true;
728+
}
729+
712730
QStringList QgsCodeEditor::history() const
713731
{
714732
return mHistory;

‎src/gui/codeeditors/qgscodeeditor.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,15 @@ class GUI_EXPORT QgsCodeEditor : public QsciScintilla
446446
*/
447447
void reformatCode();
448448

449+
/**
450+
* Applies syntax checking to the editor.
451+
*
452+
* This is only supported for editors which return the Qgis::ScriptLanguageCapability::CheckSyntax capability from languageCapabilities().
453+
*
454+
* \since QGIS 3.32
455+
*/
456+
virtual bool checkSyntax();
457+
449458
signals:
450459

451460
/**

‎src/gui/codeeditors/qgscodeeditorpython.cpp

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,13 +374,13 @@ QString QgsCodeEditorPython::reformatCodeString( const QString &string )
374374
if ( settings.value( "pythonConsole/sortImports", true ).toBool() )
375375
{
376376
const QString defineSortImports = QStringLiteral(
377-
"def __qgis_sort_imports(str):\n"
377+
"def __qgis_sort_imports(script):\n"
378378
" try:\n"
379379
" import isort\n"
380380
" except ImportError:\n"
381381
" return '_ImportError'\n"
382382
" options={'line_length': %1, 'profile': '%2', 'known_first_party': ['qgis', 'console', 'processing', 'plugins']}\n"
383-
" return isort.code(str, **options)\n" )
383+
" return isort.code(script, **options)\n" )
384384
.arg( maxLineLength )
385385
.arg( formatter == QLatin1String( "black" ) ? QStringLiteral( "black" ) : QString() );
386386

@@ -415,13 +415,13 @@ QString QgsCodeEditorPython::reformatCodeString( const QString &string )
415415
const int level = settings.value( QStringLiteral( "pythonConsole/autopep8Level" ), 1 ).toInt();
416416

417417
const QString defineReformat = QStringLiteral(
418-
"def __qgis_reformat(str):\n"
418+
"def __qgis_reformat(script):\n"
419419
" try:\n"
420420
" import autopep8\n"
421421
" except ImportError:\n"
422422
" return '_ImportError'\n"
423423
" options={'aggressive': %1, 'max_line_length': %2}\n"
424-
" return autopep8.fix_code(str, options=options)\n" )
424+
" return autopep8.fix_code(script, options=options)\n" )
425425
.arg( level )
426426
.arg( maxLineLength );
427427

@@ -455,13 +455,13 @@ QString QgsCodeEditorPython::reformatCodeString( const QString &string )
455455
const bool normalize = settings.value( QStringLiteral( "pythonConsole/blackNormalizeQuotes" ), true ).toBool();
456456

457457
const QString defineReformat = QStringLiteral(
458-
"def __qgis_reformat(str):\n"
458+
"def __qgis_reformat(script):\n"
459459
" try:\n"
460460
" import black\n"
461461
" except ImportError:\n"
462462
" return '_ImportError'\n"
463463
" options={'string_normalization': %1, 'line_length': %2}\n"
464-
" return black.format_str(str, mode=black.Mode(**options))\n" )
464+
" return black.format_str(script, mode=black.Mode(**options))\n" )
465465
.arg( QgsProcessingUtils::variantToPythonLiteral( normalize ) )
466466
.arg( maxLineLength );
467467

@@ -621,12 +621,72 @@ void QgsCodeEditorPython::updateCapabilities()
621621
if ( !QgsPythonRunner::isValid() )
622622
return;
623623

624+
mCapabilities |= Qgis::ScriptLanguageCapability::CheckSyntax;
625+
624626
// we could potentially check for autopep8/black import here and reflect the capabilty accordingly.
625627
// (current approach is to to always indicate this capability and raise a user-friendly warning
626628
// when attempting to reformat if the libraries can't be imported)
627629
mCapabilities |= Qgis::ScriptLanguageCapability::Reformat;
628630
}
629631

632+
bool QgsCodeEditorPython::checkSyntax()
633+
{
634+
clearWarnings();
635+
636+
if ( !QgsPythonRunner::isValid() )
637+
{
638+
return true;
639+
}
640+
641+
const QString originalText = text();
642+
643+
const QString defineCheckSyntax = QStringLiteral(
644+
"def __check_syntax(script):\n"
645+
" try:\n"
646+
" compile(script.encode('utf-8'), '', 'exec')\n"
647+
" except SyntaxError as detail:\n"
648+
" eline = detail.lineno or 1\n"
649+
" eline -= 1\n"
650+
" ecolumn = detail.offset or 1\n"
651+
" edescr = detail.msg\n"
652+
" return '!!!!'.join([str(eline), str(ecolumn), edescr])\n"
653+
" return ''" );
654+
655+
if ( !QgsPythonRunner::run( defineCheckSyntax ) )
656+
{
657+
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( defineCheckSyntax ) );
658+
return true;
659+
}
660+
661+
const QString script = QStringLiteral( "__check_syntax(%1)" ).arg( QgsProcessingUtils::stringToPythonLiteral( originalText ) );
662+
QString result;
663+
if ( QgsPythonRunner::eval( script, result ) )
664+
{
665+
if ( result.size() == 0 )
666+
{
667+
return true;
668+
}
669+
else
670+
{
671+
const QStringList parts = result.split( QStringLiteral( "!!!!" ) );
672+
if ( parts.size() == 3 )
673+
{
674+
const int line = parts.at( 0 ).toInt();
675+
const int column = parts.at( 1 ).toInt();
676+
addWarning( line, parts.at( 2 ) );
677+
setCursorPosition( line, column - 1 );
678+
ensureLineVisible( line );
679+
}
680+
return false;
681+
}
682+
}
683+
else
684+
{
685+
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( script ) );
686+
return true;
687+
}
688+
}
689+
630690
void QgsCodeEditorPython::searchSelectedTextInPyQGISDocs()
631691
{
632692
if ( !hasSelectedText() )

‎src/gui/codeeditors/qgscodeeditorpython.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor
104104
*/
105105
void updateCapabilities();
106106

107+
bool checkSyntax() override;
108+
107109
public slots:
108110

109111
/**

0 commit comments

Comments
 (0)
Please sign in to comment.