30#include <Qsci/qscilexerpython.h>
31#include <QDesktopServices>
34const QMap<QString, QString> QgsCodeEditorPython::sCompletionPairs
42const QStringList QgsCodeEditorPython::sCompletionSingleCharacters{
"`",
"*"};
50 , mAPISFilesList( filenames )
70 setEdgeMode( QsciScintilla::EdgeLine );
74 setWhitespaceVisibility( QsciScintilla::WsVisibleAfterIndent );
79 QsciLexerPython *pyLexer =
new QgsQsciLexerPython(
this );
81 pyLexer->setIndentationWarning( QsciLexerPython::Inconsistent );
82 pyLexer->setFoldComments(
true );
83 pyLexer->setFoldQuotes(
true );
85 pyLexer->setDefaultFont( font );
88 pyLexer->setFont( font, -1 );
90 font.setItalic(
true );
91 pyLexer->setFont( font, QsciLexerPython::Comment );
92 pyLexer->setFont( font, QsciLexerPython::CommentBlock );
94 font.setItalic(
false );
96 pyLexer->setFont( font, QsciLexerPython::SingleQuotedString );
97 pyLexer->setFont( font, QsciLexerPython::DoubleQuotedString );
99 pyLexer->setColor(
defaultColor, QsciLexerPython::Default );
114 std::unique_ptr< QsciAPIs > apis = std::make_unique< QsciAPIs >( pyLexer );
118 if ( mAPISFilesList.isEmpty() )
120 if ( settings.
value( QStringLiteral(
"pythonConsole/preloadAPI" ),
true ).toBool() )
123 apis->loadPrepared( mPapFile );
125 else if ( settings.
value( QStringLiteral(
"pythonConsole/usePreparedAPIFile" ),
false ).toBool() )
127 apis->loadPrepared( settings.
value( QStringLiteral(
"pythonConsole/preparedAPIFile" ) ).toString() );
131 const QStringList apiPaths = settings.
value( QStringLiteral(
"pythonConsole/userAPI" ) ).toStringList();
132 for (
const QString &path : apiPaths )
134 if ( !QFileInfo::exists( path ) )
136 QgsDebugMsg( QStringLiteral(
"The apis file %1 was not found" ).arg( path ) );
146 else if ( mAPISFilesList.length() == 1 && mAPISFilesList[0].right( 3 ) == QLatin1String(
"pap" ) )
148 if ( !QFileInfo::exists( mAPISFilesList[0] ) )
150 QgsDebugMsg( QStringLiteral(
"The apis file %1 not found" ).arg( mAPISFilesList.at( 0 ) ) );
153 mPapFile = mAPISFilesList[0];
154 apis->loadPrepared( mPapFile );
158 for (
const QString &path : std::as_const( mAPISFilesList ) )
160 if ( !QFileInfo::exists( path ) )
162 QgsDebugMsg( QStringLiteral(
"The apis file %1 was not found" ).arg( path ) );
172 pyLexer->setAPIs( apis.release() );
176 const int threshold = settings.
value( QStringLiteral(
"pythonConsole/autoCompThreshold" ), 2 ).toInt();
177 setAutoCompletionThreshold( threshold );
178 if ( !settings.
value(
"pythonConsole/autoCompleteEnabled",
true ).toBool() )
180 setAutoCompletionSource( AcsNone );
184 const QString autoCompleteSource = settings.
value( QStringLiteral(
"pythonConsole/autoCompleteSource" ), QStringLiteral(
"fromAPI" ) ).toString();
185 if ( autoCompleteSource == QLatin1String(
"fromDoc" ) )
186 setAutoCompletionSource( AcsDocument );
187 else if ( autoCompleteSource == QLatin1String(
"fromDocAPI" ) )
188 setAutoCompletionSource( AcsAll );
190 setAutoCompletionSource( AcsAPIs );
194 setIndentationsUseTabs(
false );
195 setIndentationGuides(
true );
207 const bool ctrlModifier =
event->modifiers() & Qt::ControlModifier;
210 if ( ctrlModifier && event->key() == Qt::Key_Colon )
219 bool autoCloseBracket = settings.
value( QStringLiteral(
"/pythonConsole/autoCloseBracket" ),
true ).toBool();
220 bool autoSurround = settings.
value( QStringLiteral(
"/pythonConsole/autoSurround" ),
true ).toBool();
221 bool autoInsertImport = settings.
value( QStringLiteral(
"/pythonConsole/autoInsertImport" ),
false ).toBool();
224 if ( event->key() == Qt::Key_Left ||
225 event->key() == Qt::Key_Right ||
226 event->key() == Qt::Key_Up ||
227 event->key() == Qt::Key_Down )
235 const QString eText =
event->text();
237 getCursorPosition( &line, &column );
241 if ( hasSelectedText() && autoSurround )
243 if ( sCompletionPairs.contains( eText ) )
245 int startLine, startPos, endLine, endPos;
246 getSelection( &startLine, &startPos, &endLine, &endPos );
249 if ( startLine != endLine && ( eText ==
"\"" || eText ==
"'" ) )
252 QString(
"%1%1%1%2%3%3%3" ).arg( eText, selectedText(), sCompletionPairs[eText] )
254 setSelection( startLine, startPos + 3, endLine, endPos + 3 );
259 QString(
"%1%2%3" ).arg( eText, selectedText(), sCompletionPairs[eText] )
261 setSelection( startLine, startPos + 1, endLine, endPos + 1 );
266 else if ( sCompletionSingleCharacters.contains( eText ) )
268 int startLine, startPos, endLine, endPos;
269 getSelection( &startLine, &startPos, &endLine, &endPos );
271 QString(
"%1%2%1" ).arg( eText, selectedText() )
273 setSelection( startLine, startPos + 1, endLine, endPos + 1 );
283 if ( autoInsertImport && eText ==
" " )
285 const QString lineText = text( line );
286 const thread_local QRegularExpression re( QStringLiteral(
"^from [\\w.]+$" ) );
287 if ( re.match( lineText.trimmed() ).hasMatch() )
289 insert( QStringLiteral(
" import" ) );
290 setCursorPosition( line, column + 7 );
296 else if ( autoCloseBracket )
302 if ( event->key() == Qt::Key_Backspace )
304 if ( sCompletionPairs.contains( prevChar ) && sCompletionPairs[prevChar] == nextChar )
306 setSelection( line, column - 1, line, column + 1 );
307 removeSelectedText();
321 else if ( sCompletionPairs.key( eText ) !=
"" && nextChar == eText )
323 setCursorPosition( line, column + 1 );
336 if ( !( ( eText ==
"\"" || eText ==
"'" ) && prevChar == eText ) )
339 insert( sCompletionPairs[eText] );
353 switch ( autoCompletionSource() )
356 autoCompleteFromDocument();
360 autoCompleteFromAPIs();
364 autoCompleteFromAll();
374 mAPISFilesList = filenames;
381 QgsDebugMsgLevel( QStringLiteral(
"The script file: %1" ).arg( script ), 2 );
382 QFile file( script );
383 if ( !file.open( QIODevice::ReadOnly ) )
388 QTextStream in( &file );
390 setText( in.readAll().trimmed() );
400 getCursorPosition( &line, &index );
401 int position = positionFromLineIndex( line, index );
407 if ( position >= length() && position > 0 )
409 long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position - 1 );
410 return style == QsciLexerPython::Comment
411 || style == QsciLexerPython::TripleSingleQuotedString
412 || style == QsciLexerPython::TripleDoubleQuotedString
413 || style == QsciLexerPython::TripleSingleQuotedFString
414 || style == QsciLexerPython::TripleDoubleQuotedFString
415 || style == QsciLexerPython::UnclosedString;
419 long style = SendScintilla( QsciScintillaBase::SCI_GETSTYLEAT, position );
420 return style == QsciLexerPython::Comment
421 || style == QsciLexerPython::DoubleQuotedString
422 || style == QsciLexerPython::SingleQuotedString
423 || style == QsciLexerPython::TripleSingleQuotedString
424 || style == QsciLexerPython::TripleDoubleQuotedString
425 || style == QsciLexerPython::CommentBlock
426 || style == QsciLexerPython::UnclosedString
427 || style == QsciLexerPython::DoubleQuotedFString
428 || style == QsciLexerPython::SingleQuotedFString
429 || style == QsciLexerPython::TripleSingleQuotedFString
430 || style == QsciLexerPython::TripleDoubleQuotedFString;
437 getCursorPosition( &line, &index );
438 int position = positionFromLineIndex( line, index );
443 return text( position - 1, position );
449 getCursorPosition( &line, &index );
450 int position = positionFromLineIndex( line, index );
451 if ( position >= length() )
455 return text( position, position + 1 );
460 if ( !hasSelectedText() )
463 QString text = selectedText();
464 text = text.replace( QLatin1String(
">>> " ), QString() ).replace( QLatin1String(
"... " ), QString() ).trimmed();
465 const QString version = QString(
Qgis::version() ).split(
'.' ).mid( 0, 2 ).join(
'.' );
466 QDesktopServices::openUrl( QUrl( QStringLiteral(
"https://qgis.org/pyqgis/%1/search.html?q=%2" ).arg( version, text ) ) );
477 int startLine, startPos, endLine, endPos;
478 if ( hasSelectedText() )
480 getSelection( &startLine, &startPos, &endLine, &endPos );
484 getCursorPosition( &startLine, &startPos );
490 bool allEmpty =
true;
491 bool allCommented =
true;
492 int minIndentation = -1;
493 for (
int line = startLine; line <= endLine; line++ )
495 const QString stripped = text( line ).trimmed();
496 if ( !stripped.isEmpty() )
499 if ( !stripped.startsWith(
'#' ) )
501 allCommented =
false;
503 if ( minIndentation == -1 || minIndentation > indentation( line ) )
505 minIndentation = indentation( line );
519 for (
int line = startLine; line <= endLine; line++ )
521 const QString stripped = text( line ).trimmed();
524 if ( stripped.isEmpty() )
531 insertAt( QStringLiteral(
"# " ), line, minIndentation );
536 if ( !stripped.startsWith(
'#' ) )
540 if ( stripped.startsWith( QLatin1String(
"# " ) ) )
548 setSelection( line, indentation( line ), line, indentation( line ) + delta );
549 removeSelectedText();
554 setSelection( startLine, startPos - delta, endLine, endPos - delta );
561QgsQsciLexerPython::QgsQsciLexerPython( QObject *parent )
562 : QsciLexerPython( parent )
567const char *QgsQsciLexerPython::keywords(
int set )
const
571 return "True False and as assert break class continue def del elif else except "
572 "finally for from global if import in is lambda None not or pass "
573 "raise return try while with yield async await nonlocal";
576 return QsciLexerPython::keywords( set );
static QString version()
Version string.
ScriptLanguage
Scripting languages.
static QString pkgDataPath()
Returns the common root path of all application data directories.
@ TripleSingleQuote
Triple single quote color.
@ CommentBlock
Comment block color.
@ Decoration
Decoration color.
@ Identifier
Identifier color.
@ DoubleQuote
Double quote color.
@ Default
Default text color.
@ Background
Background color.
@ SingleQuote
Single quote color.
@ Operator
Operator color.
@ TripleDoubleQuote
Triple double quote color.
void autoComplete()
Triggers the autocompletion popup.
QgsCodeEditorPython(QWidget *parent=nullptr, const QList< QString > &filenames=QList< QString >(), QgsCodeEditor::Mode mode=QgsCodeEditor::Mode::ScriptEditor)
Construct a new Python editor.
QString characterAfterCursor() const
Returns the character after the cursor, or an empty string if the cursot is set at end.
bool isCursorInsideStringLiteralOrComment() const
Check whether the current cursor position is inside a string literal or a comment.
void searchSelectedTextInPyQGISDocs()
Searches the selected text in the official PyQGIS online documentation.
void toggleComment()
Toggle comment for the selected text.
Qgis::ScriptLanguage language() const override
Returns the associated scripting language.
void loadAPIs(const QList< QString > &filenames)
Load APIs from one or more files.
void initializeLexer() override
Called when the dialect specific code lexer needs to be initialized (or reinitialized).
virtual void keyPressEvent(QKeyEvent *event) override
bool loadScript(const QString &script)
Loads a script file.
QString characterBeforeCursor() const
Returns the character before the cursor, or an empty string if cursor is set at start.
A text editor based on QScintilla2.
void keyPressEvent(QKeyEvent *event) override
void runPostLexerConfigurationTasks()
Performs tasks which must be run after a lexer has been set for the widget.
void setTitle(const QString &title)
Set the widget title.
Flag
Flags controlling behavior of code editor.
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.
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.
This class is a composition of two QSettings instances:
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define QgsDebugMsgLevel(str, level)