QGIS/src/gui/codeeditors/qgscodeeditorpython.cpp

811 lines
26 KiB
C++

/***************************************************************************
qgscodeeditorpython.cpp - A Python editor based on QScintilla
--------------------------------------
Date : 06-Oct-2013
Copyright : (C) 2013 by Salvatore Larosa
Email : lrssvtml (at) gmail (dot) com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsapplication.h"
#include "qgscodeeditorpython.h"
#include "qgslogger.h"
#include "qgssymbollayerutils.h"
#include "qgssettings.h"
#include "qgis.h"
#include "qgspythonrunner.h"
#include "qgsprocessingutils.h"
#include <QWidget>
#include <QString>
#include <QFont>
#include <QUrl>
#include <QFileInfo>
#include <QMessageBox>
#include <QTextStream>
#include <Qsci/qscilexerpython.h>
#include <QDesktopServices>
#include <QKeyEvent>
const QMap<QString, QString> QgsCodeEditorPython::sCompletionPairs
{
{"(", ")"},
{"[", "]"},
{"{", "}"},
{"'", "'"},
{"\"", "\""}
};
const QStringList QgsCodeEditorPython::sCompletionSingleCharacters{"`", "*"};
QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList<QString> &filenames, Mode mode )
: QgsCodeEditor( parent,
QString(),
false,
false,
QgsCodeEditor::Flag::CodeFolding, mode )
, mAPISFilesList( filenames )
{
if ( !parent )
{
setTitle( tr( "Python Editor" ) );
}
setCaretWidth( 2 );
QgsCodeEditorPython::initializeLexer();
updateCapabilities();
}
Qgis::ScriptLanguage QgsCodeEditorPython::language() const
{
return Qgis::ScriptLanguage::Python;
}
Qgis::ScriptLanguageCapabilities QgsCodeEditorPython::languageCapabilities() const
{
return mCapabilities;
}
void QgsCodeEditorPython::initializeLexer()
{
const QgsSettings settings;
// current line
setEdgeMode( QsciScintilla::EdgeLine );
setEdgeColumn( settings.value( QStringLiteral( "pythonConsole/maxLineLength" ), 80 ).toInt() );
setEdgeColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Edge ) );
setWhitespaceVisibility( QsciScintilla::WsVisibleAfterIndent );
QFont font = lexerFont();
const QColor defaultColor = lexerColor( QgsCodeEditorColorScheme::ColorRole::Default );
QsciLexerPython *pyLexer = new QgsQsciLexerPython( this );
pyLexer->setIndentationWarning( QsciLexerPython::Inconsistent );
pyLexer->setFoldComments( true );
pyLexer->setFoldQuotes( true );
pyLexer->setDefaultFont( font );
pyLexer->setDefaultColor( defaultColor );
pyLexer->setDefaultPaper( lexerColor( QgsCodeEditorColorScheme::ColorRole::Background ) );
pyLexer->setFont( font, -1 );
font.setItalic( true );
pyLexer->setFont( font, QsciLexerPython::Comment );
pyLexer->setFont( font, QsciLexerPython::CommentBlock );
font.setItalic( false );
font.setBold( true );
pyLexer->setFont( font, QsciLexerPython::SingleQuotedString );
pyLexer->setFont( font, QsciLexerPython::DoubleQuotedString );
pyLexer->setColor( defaultColor, QsciLexerPython::Default );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Class ), QsciLexerPython::ClassName );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Method ), QsciLexerPython::FunctionMethodName );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Number ), QsciLexerPython::Number );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Operator ), QsciLexerPython::Operator );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Identifier ), QsciLexerPython::Identifier );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Comment ), QsciLexerPython::Comment );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::CommentBlock ), QsciLexerPython::CommentBlock );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Keyword ), QsciLexerPython::Keyword );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Decoration ), QsciLexerPython::Decorator );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::SingleQuote ), QsciLexerPython::SingleQuotedString );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::DoubleQuote ), QsciLexerPython::DoubleQuotedString );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::TripleSingleQuote ), QsciLexerPython::TripleSingleQuotedString );
pyLexer->setColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::TripleDoubleQuote ), QsciLexerPython::TripleDoubleQuotedString );
std::unique_ptr< QsciAPIs > apis = std::make_unique< QsciAPIs >( pyLexer );
if ( mAPISFilesList.isEmpty() )
{
if ( settings.value( QStringLiteral( "pythonConsole/preloadAPI" ), true ).toBool() )
{
mPapFile = QgsApplication::pkgDataPath() + QStringLiteral( "/python/qsci_apis/PyQGIS.pap" );
apis->loadPrepared( mPapFile );
}
else if ( settings.value( QStringLiteral( "pythonConsole/usePreparedAPIFile" ), false ).toBool() )
{
apis->loadPrepared( settings.value( QStringLiteral( "pythonConsole/preparedAPIFile" ) ).toString() );
}
else
{
const QStringList apiPaths = settings.value( QStringLiteral( "pythonConsole/userAPI" ) ).toStringList();
for ( const QString &path : apiPaths )
{
if ( !QFileInfo::exists( path ) )
{
QgsDebugMsg( QStringLiteral( "The apis file %1 was not found" ).arg( path ) );
}
else
{
apis->load( path );
}
}
apis->prepare();
}
}
else if ( mAPISFilesList.length() == 1 && mAPISFilesList[0].right( 3 ) == QLatin1String( "pap" ) )
{
if ( !QFileInfo::exists( mAPISFilesList[0] ) )
{
QgsDebugMsg( QStringLiteral( "The apis file %1 not found" ).arg( mAPISFilesList.at( 0 ) ) );
return;
}
mPapFile = mAPISFilesList[0];
apis->loadPrepared( mPapFile );
}
else
{
for ( const QString &path : std::as_const( mAPISFilesList ) )
{
if ( !QFileInfo::exists( path ) )
{
QgsDebugMsg( QStringLiteral( "The apis file %1 was not found" ).arg( path ) );
}
else
{
apis->load( path );
}
}
apis->prepare();
}
if ( apis )
pyLexer->setAPIs( apis.release() );
setLexer( pyLexer );
const int threshold = settings.value( QStringLiteral( "pythonConsole/autoCompThreshold" ), 2 ).toInt();
setAutoCompletionThreshold( threshold );
if ( !settings.value( "pythonConsole/autoCompleteEnabled", true ).toBool() )
{
setAutoCompletionSource( AcsNone );
}
else
{
const QString autoCompleteSource = settings.value( QStringLiteral( "pythonConsole/autoCompleteSource" ), QStringLiteral( "fromAPI" ) ).toString();
if ( autoCompleteSource == QLatin1String( "fromDoc" ) )
setAutoCompletionSource( AcsDocument );
else if ( autoCompleteSource == QLatin1String( "fromDocAPI" ) )
setAutoCompletionSource( AcsAll );
else
setAutoCompletionSource( AcsAPIs );
}
setLineNumbersVisible( true );
setIndentationsUseTabs( false );
setIndentationGuides( true );
runPostLexerConfigurationTasks();
}
void QgsCodeEditorPython::keyPressEvent( QKeyEvent *event )
{
// If editor is readOnly, use the default implementation
if ( isReadOnly() )
{
return QgsCodeEditor::keyPressEvent( event );
}
const bool ctrlModifier = event->modifiers() & Qt::ControlModifier;
// Toggle comment when user presses Ctrl+:
if ( ctrlModifier && event->key() == Qt::Key_Colon )
{
event->accept();
toggleComment();
return;
}
const QgsSettings settings;
bool autoCloseBracket = settings.value( QStringLiteral( "/pythonConsole/autoCloseBracket" ), true ).toBool();
bool autoSurround = settings.value( QStringLiteral( "/pythonConsole/autoSurround" ), true ).toBool();
bool autoInsertImport = settings.value( QStringLiteral( "/pythonConsole/autoInsertImport" ), false ).toBool();
// Update calltips when cursor position changes with left and right keys
if ( event->key() == Qt::Key_Left ||
event->key() == Qt::Key_Right ||
event->key() == Qt::Key_Up ||
event->key() == Qt::Key_Down )
{
QgsCodeEditor::keyPressEvent( event );
callTip();
return;
}
// Get entered text and cursor position
const QString eText = event->text();
int line, column;
getCursorPosition( &line, &column );
// If some text is selected and user presses an opening character
// surround the selection with the opening-closing pair
if ( hasSelectedText() && autoSurround )
{
if ( sCompletionPairs.contains( eText ) )
{
int startLine, startPos, endLine, endPos;
getSelection( &startLine, &startPos, &endLine, &endPos );
// Special case for Multi line quotes (insert triple quotes)
if ( startLine != endLine && ( eText == "\"" || eText == "'" ) )
{
replaceSelectedText(
QString( "%1%1%1%2%3%3%3" ).arg( eText, selectedText(), sCompletionPairs[eText] )
);
setSelection( startLine, startPos + 3, endLine, endPos + 3 );
}
else
{
replaceSelectedText(
QString( "%1%2%3" ).arg( eText, selectedText(), sCompletionPairs[eText] )
);
setSelection( startLine, startPos + 1, endLine, endPos + 1 );
}
event->accept();
return;
}
else if ( sCompletionSingleCharacters.contains( eText ) )
{
int startLine, startPos, endLine, endPos;
getSelection( &startLine, &startPos, &endLine, &endPos );
replaceSelectedText(
QString( "%1%2%1" ).arg( eText, selectedText() )
);
setSelection( startLine, startPos + 1, endLine, endPos + 1 );
event->accept();
return;
}
}
// No selected text
else
{
// Automatically insert "import" after "from xxx " if option is enabled
if ( autoInsertImport && eText == " " )
{
const QString lineText = text( line );
const thread_local QRegularExpression re( QStringLiteral( "^from [\\w.]+$" ) );
if ( re.match( lineText.trimmed() ).hasMatch() )
{
insert( QStringLiteral( " import" ) );
setCursorPosition( line, column + 7 );
return QgsCodeEditor::keyPressEvent( event );
}
}
// Handle automatic bracket insertion/deletion if option is enabled
else if ( autoCloseBracket )
{
const QString prevChar = characterBeforeCursor();
const QString nextChar = characterAfterCursor();
// When backspace is pressed inside an opening/closing pair, remove both characters
if ( event->key() == Qt::Key_Backspace )
{
if ( sCompletionPairs.contains( prevChar ) && sCompletionPairs[prevChar] == nextChar )
{
setSelection( line, column - 1, line, column + 1 );
removeSelectedText();
event->accept();
}
else
{
QgsCodeEditor::keyPressEvent( event );
}
// Update calltips (cursor position has changed)
callTip();
return;
}
// When closing character is entered inside an opening/closing pair, shift the cursor
else if ( sCompletionPairs.key( eText ) != "" && nextChar == eText )
{
setCursorPosition( line, column + 1 );
event->accept();
// Will hide calltips when a closing parenthesis is entered
callTip();
return;
}
// Else, if not inside a string or comment and an opening character
// is entered, also insert the closing character
else if ( !isCursorInsideStringLiteralOrComment() && sCompletionPairs.contains( eText ) )
{
// Check if user is not entering triple quotes
if ( !( ( eText == "\"" || eText == "'" ) && prevChar == eText ) )
{
QgsCodeEditor::keyPressEvent( event );
insert( sCompletionPairs[eText] );
event->accept();
return;
}
}
}
}
// Let QgsCodeEditor handle the keyboard event
return QgsCodeEditor::keyPressEvent( event );
}
QString QgsCodeEditorPython::reformatCodeString( const QString &string )
{
if ( !QgsPythonRunner::isValid() )
{
return string;
}
QgsSettings settings;
const QString formatter = settings.value( QStringLiteral( "pythonConsole/formatter" ), QStringLiteral( "autopep8" ) ).toString();
const int maxLineLength = settings.value( QStringLiteral( "pythonConsole/maxLineLength" ), 80 ).toInt();
QString newText = string;
QStringList missingModules;
if ( settings.value( "pythonConsole/sortImports", true ).toBool() )
{
const QString defineSortImports = QStringLiteral(
"def __qgis_sort_imports(script):\n"
" try:\n"
" import isort\n"
" except ImportError:\n"
" return '_ImportError'\n"
" options={'line_length': %1, 'profile': '%2', 'known_first_party': ['qgis', 'console', 'processing', 'plugins']}\n"
" return isort.code(script, **options)\n" )
.arg( maxLineLength )
.arg( formatter == QLatin1String( "black" ) ? QStringLiteral( "black" ) : QString() );
if ( !QgsPythonRunner::run( defineSortImports ) )
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( defineSortImports ) );
return string;
}
const QString script = QStringLiteral( "__qgis_sort_imports(%1)" ).arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
QString result;
if ( QgsPythonRunner::eval( script, result ) )
{
if ( result == QLatin1String( "_ImportError" ) )
{
missingModules << QStringLiteral( "isort" );
}
else
{
newText = result;
}
}
else
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( script ) );
return newText;
}
}
if ( formatter == QLatin1String( "autopep8" ) )
{
const int level = settings.value( QStringLiteral( "pythonConsole/autopep8Level" ), 1 ).toInt();
const QString defineReformat = QStringLiteral(
"def __qgis_reformat(script):\n"
" try:\n"
" import autopep8\n"
" except ImportError:\n"
" return '_ImportError'\n"
" options={'aggressive': %1, 'max_line_length': %2}\n"
" return autopep8.fix_code(script, options=options)\n" )
.arg( level )
.arg( maxLineLength );
if ( !QgsPythonRunner::run( defineReformat ) )
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( defineReformat ) );
return newText;
}
const QString script = QStringLiteral( "__qgis_reformat(%1)" ).arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
QString result;
if ( QgsPythonRunner::eval( script, result ) )
{
if ( result == QLatin1String( "_ImportError" ) )
{
missingModules << QStringLiteral( "autopep8" );
}
else
{
newText = result;
}
}
else
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( script ) );
return newText;
}
}
else if ( formatter == QLatin1String( "black" ) )
{
const bool normalize = settings.value( QStringLiteral( "pythonConsole/blackNormalizeQuotes" ), true ).toBool();
const QString defineReformat = QStringLiteral(
"def __qgis_reformat(script):\n"
" try:\n"
" import black\n"
" except ImportError:\n"
" return '_ImportError'\n"
" options={'string_normalization': %1, 'line_length': %2}\n"
" return black.format_str(script, mode=black.Mode(**options))\n" )
.arg( QgsProcessingUtils::variantToPythonLiteral( normalize ) )
.arg( maxLineLength );
if ( !QgsPythonRunner::run( defineReformat ) )
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( defineReformat ) );
return string;
}
const QString script = QStringLiteral( "__qgis_reformat(%1)" ).arg( QgsProcessingUtils::stringToPythonLiteral( newText ) );
QString result;
if ( QgsPythonRunner::eval( script, result ) )
{
if ( result == QLatin1String( "_ImportError" ) )
{
missingModules << QStringLiteral( "black" );
}
else
{
newText = result;
}
}
else
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( script ) );
return newText;
}
}
if ( !missingModules.empty() )
{
if ( missingModules.size() == 1 )
{
showMessage( tr( "Reformat Code" ), tr( "The Python module %1 is missing" ).arg( missingModules.at( 0 ) ), Qgis::MessageLevel::Warning );
}
else
{
const QString modules = missingModules.join( QStringLiteral( ", " ) );
showMessage( tr( "Reformat Code" ), tr( "The Python modules %1 are missing" ).arg( modules ), Qgis::MessageLevel::Warning );
}
}
return newText;
}
void QgsCodeEditorPython::autoComplete()
{
switch ( autoCompletionSource() )
{
case AcsDocument:
autoCompleteFromDocument();
break;
case AcsAPIs:
autoCompleteFromAPIs();
break;
case AcsAll:
autoCompleteFromAll();
break;
case AcsNone:
break;
}
}
void QgsCodeEditorPython::loadAPIs( const QList<QString> &filenames )
{
mAPISFilesList = filenames;
//QgsDebugMsg( QStringLiteral( "The apis files: %1" ).arg( mAPISFilesList[0] ) );
initializeLexer();
}
bool QgsCodeEditorPython::loadScript( const QString &script )
{
QgsDebugMsgLevel( QStringLiteral( "The script file: %1" ).arg( script ), 2 );
QFile file( script );
if ( !file.open( QIODevice::ReadOnly ) )
{
return false;
}
QTextStream in( &file );
setText( in.readAll().trimmed() );
file.close();
initializeLexer();
return true;
}
bool QgsCodeEditorPython::isCursorInsideStringLiteralOrComment() const
{
int line, index;
getCursorPosition( &line, &index );
int position = positionFromLineIndex( line, index );
// Special case: cursor at the end of the document. Style will always be Default,
// so we have to check the style of the previous character.
// It it is an unclosed string (triple string, unclosed, or comment),
// consider cursor is inside a string.
if ( position >= length() && position > 0 )
{
long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position - 1 );
return style == QsciLexerPython::Comment
|| style == QsciLexerPython::TripleSingleQuotedString
|| style == QsciLexerPython::TripleDoubleQuotedString
|| style == QsciLexerPython::TripleSingleQuotedFString
|| style == QsciLexerPython::TripleDoubleQuotedFString
|| style == QsciLexerPython::UnclosedString;
}
else
{
long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position );
return style == QsciLexerPython::Comment
|| style == QsciLexerPython::DoubleQuotedString
|| style == QsciLexerPython::SingleQuotedString
|| style == QsciLexerPython::TripleSingleQuotedString
|| style == QsciLexerPython::TripleDoubleQuotedString
|| style == QsciLexerPython::CommentBlock
|| style == QsciLexerPython::UnclosedString
|| style == QsciLexerPython::DoubleQuotedFString
|| style == QsciLexerPython::SingleQuotedFString
|| style == QsciLexerPython::TripleSingleQuotedFString
|| style == QsciLexerPython::TripleDoubleQuotedFString;
}
}
QString QgsCodeEditorPython::characterBeforeCursor() const
{
int line, index;
getCursorPosition( &line, &index );
int position = positionFromLineIndex( line, index );
if ( position <= 0 )
{
return QString();
}
return text( position - 1, position );
}
QString QgsCodeEditorPython::characterAfterCursor() const
{
int line, index;
getCursorPosition( &line, &index );
int position = positionFromLineIndex( line, index );
if ( position >= length() )
{
return QString();
}
return text( position, position + 1 );
}
void QgsCodeEditorPython::updateCapabilities()
{
mCapabilities = Qgis::ScriptLanguageCapabilities();
if ( !QgsPythonRunner::isValid() )
return;
mCapabilities |= Qgis::ScriptLanguageCapability::CheckSyntax;
// we could potentially check for autopep8/black import here and reflect the capabilty accordingly.
// (current approach is to to always indicate this capability and raise a user-friendly warning
// when attempting to reformat if the libraries can't be imported)
mCapabilities |= Qgis::ScriptLanguageCapability::Reformat;
}
bool QgsCodeEditorPython::checkSyntax()
{
clearWarnings();
if ( !QgsPythonRunner::isValid() )
{
return true;
}
const QString originalText = text();
const QString defineCheckSyntax = QStringLiteral(
"def __check_syntax(script):\n"
" try:\n"
" compile(script.encode('utf-8'), '', 'exec')\n"
" except SyntaxError as detail:\n"
" eline = detail.lineno or 1\n"
" eline -= 1\n"
" ecolumn = detail.offset or 1\n"
" edescr = detail.msg\n"
" return '!!!!'.join([str(eline), str(ecolumn), edescr])\n"
" return ''" );
if ( !QgsPythonRunner::run( defineCheckSyntax ) )
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( defineCheckSyntax ) );
return true;
}
const QString script = QStringLiteral( "__check_syntax(%1)" ).arg( QgsProcessingUtils::stringToPythonLiteral( originalText ) );
QString result;
if ( QgsPythonRunner::eval( script, result ) )
{
if ( result.size() == 0 )
{
return true;
}
else
{
const QStringList parts = result.split( QStringLiteral( "!!!!" ) );
if ( parts.size() == 3 )
{
const int line = parts.at( 0 ).toInt();
const int column = parts.at( 1 ).toInt();
addWarning( line, parts.at( 2 ) );
setCursorPosition( line, column - 1 );
ensureLineVisible( line );
}
return false;
}
}
else
{
QgsDebugMsg( QStringLiteral( "Error running script: %1" ).arg( script ) );
return true;
}
}
void QgsCodeEditorPython::searchSelectedTextInPyQGISDocs()
{
if ( !hasSelectedText() )
return;
QString text = selectedText();
text = text.replace( QLatin1String( ">>> " ), QString() ).replace( QLatin1String( "... " ), QString() ).trimmed(); // removing prompts
const QString version = QString( Qgis::version() ).split( '.' ).mid( 0, 2 ).join( '.' );
QDesktopServices::openUrl( QUrl( QStringLiteral( "https://qgis.org/pyqgis/%1/search.html?q=%2" ).arg( version, text ) ) );
}
void QgsCodeEditorPython::toggleComment()
{
if ( isReadOnly() )
{
return;
}
beginUndoAction();
int startLine, startPos, endLine, endPos;
if ( hasSelectedText() )
{
getSelection( &startLine, &startPos, &endLine, &endPos );
}
else
{
getCursorPosition( &startLine, &startPos );
endLine = startLine;
endPos = startPos;
}
// Check comment state and minimum indentation for each selected line
bool allEmpty = true;
bool allCommented = true;
int minIndentation = -1;
for ( int line = startLine; line <= endLine; line++ )
{
const QString stripped = text( line ).trimmed();
if ( !stripped.isEmpty() )
{
allEmpty = false;
if ( !stripped.startsWith( '#' ) )
{
allCommented = false;
}
if ( minIndentation == -1 || minIndentation > indentation( line ) )
{
minIndentation = indentation( line );
}
}
}
// Special case, only empty lines
if ( allEmpty )
{
return;
}
// Selection shift to keep the same selected text after a # is added/removed
int delta = 0;
for ( int line = startLine; line <= endLine; line++ )
{
const QString stripped = text( line ).trimmed();
// Empty line
if ( stripped.isEmpty() )
{
continue;
}
if ( !allCommented )
{
insertAt( QStringLiteral( "# " ), line, minIndentation );
delta = -2;
}
else
{
if ( !stripped.startsWith( '#' ) )
{
continue;
}
if ( stripped.startsWith( QLatin1String( "# " ) ) )
{
delta = 2;
}
else
{
delta = 1;
}
setSelection( line, indentation( line ), line, indentation( line ) + delta );
removeSelectedText();
}
}
endUndoAction();
setSelection( startLine, startPos - delta, endLine, endPos - delta );
}
///@cond PRIVATE
//
// QgsQsciLexerPython
//
QgsQsciLexerPython::QgsQsciLexerPython( QObject *parent )
: QsciLexerPython( parent )
{
}
const char *QgsQsciLexerPython::keywords( int set ) const
{
if ( set == 1 )
{
return "True False and as assert break class continue def del elif else except "
"finally for from global if import in is lambda None not or pass "
"raise return try while with yield async await nonlocal";
}
return QsciLexerPython::keywords( set );
}
///@endcond PRIVATE