QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
Loading...
Searching...
No Matches
qgscodeeditor.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscodeeditor.cpp - A base code editor for QGIS and plugins. Provides
3 a base editor using QScintilla for editors
4 --------------------------------------
5 Date : 06-Oct-2013
6 Copyright : (C) 2013 by Salvatore Larosa
7 Email : lrssvtml (at) gmail (dot) com
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17#include "qgsapplication.h"
18#include "qgscodeeditor.h"
19#include "moc_qgscodeeditor.cpp"
20#include "qgssettings.h"
21#include "qgssymbollayerutils.h"
22#include "qgsgui.h"
25#include "qgsstringutils.h"
26#include "qgsfontutils.h"
27
28#include <QLabel>
29#include <QWidget>
30#include <QFont>
31#include <QFontDatabase>
32#include <QDebug>
33#include <QFocusEvent>
34#include <Qsci/qscistyle.h>
35#include <QMenu>
36#include <QClipboard>
37#include <QScrollBar>
38#include <QMessageBox>
39#include "Qsci/qscilexer.h"
40
41QMap< QgsCodeEditorColorScheme::ColorRole, QString > QgsCodeEditor::sColorRoleToSettingsKey
42{
43 {QgsCodeEditorColorScheme::ColorRole::Default, QStringLiteral( "defaultFontColor" ) },
44 {QgsCodeEditorColorScheme::ColorRole::Keyword, QStringLiteral( "keywordFontColor" ) },
45 {QgsCodeEditorColorScheme::ColorRole::Class, QStringLiteral( "classFontColor" ) },
46 {QgsCodeEditorColorScheme::ColorRole::Method, QStringLiteral( "methodFontColor" ) },
47 {QgsCodeEditorColorScheme::ColorRole::Decoration, QStringLiteral( "decoratorFontColor" ) },
48 {QgsCodeEditorColorScheme::ColorRole::Number, QStringLiteral( "numberFontColor" ) },
49 {QgsCodeEditorColorScheme::ColorRole::Comment, QStringLiteral( "commentFontColor" ) },
50 {QgsCodeEditorColorScheme::ColorRole::CommentLine, QStringLiteral( "commentLineFontColor" ) },
51 {QgsCodeEditorColorScheme::ColorRole::CommentBlock, QStringLiteral( "commentBlockFontColor" ) },
52 {QgsCodeEditorColorScheme::ColorRole::Background, QStringLiteral( "paperBackgroundColor" ) },
53 {QgsCodeEditorColorScheme::ColorRole::Cursor, QStringLiteral( "cursorColor" ) },
54 {QgsCodeEditorColorScheme::ColorRole::CaretLine, QStringLiteral( "caretLineColor" ) },
55 {QgsCodeEditorColorScheme::ColorRole::Operator, QStringLiteral( "operatorFontColor" ) },
56 {QgsCodeEditorColorScheme::ColorRole::QuotedOperator, QStringLiteral( "quotedOperatorFontColor" ) },
57 {QgsCodeEditorColorScheme::ColorRole::Identifier, QStringLiteral( "identifierFontColor" ) },
58 {QgsCodeEditorColorScheme::ColorRole::QuotedIdentifier, QStringLiteral( "quotedIdentifierFontColor" ) },
59 {QgsCodeEditorColorScheme::ColorRole::Tag, QStringLiteral( "tagFontColor" ) },
60 {QgsCodeEditorColorScheme::ColorRole::UnknownTag, QStringLiteral( "unknownTagFontColor" ) },
61 {QgsCodeEditorColorScheme::ColorRole::SingleQuote, QStringLiteral( "singleQuoteFontColor" ) },
62 {QgsCodeEditorColorScheme::ColorRole::DoubleQuote, QStringLiteral( "doubleQuoteFontColor" ) },
63 {QgsCodeEditorColorScheme::ColorRole::TripleSingleQuote, QStringLiteral( "tripleSingleQuoteFontColor" ) },
64 {QgsCodeEditorColorScheme::ColorRole::TripleDoubleQuote, QStringLiteral( "tripleDoubleQuoteFontColor" ) },
65 {QgsCodeEditorColorScheme::ColorRole::MarginBackground, QStringLiteral( "marginBackgroundColor" ) },
66 {QgsCodeEditorColorScheme::ColorRole::MarginForeground, QStringLiteral( "marginForegroundColor" ) },
67 {QgsCodeEditorColorScheme::ColorRole::SelectionBackground, QStringLiteral( "selectionBackgroundColor" ) },
68 {QgsCodeEditorColorScheme::ColorRole::SelectionForeground, QStringLiteral( "selectionForegroundColor" ) },
69 {QgsCodeEditorColorScheme::ColorRole::MatchedBraceBackground, QStringLiteral( "matchedBraceBackground" ) },
70 {QgsCodeEditorColorScheme::ColorRole::MatchedBraceForeground, QStringLiteral( "matchedBraceColor" ) },
71 {QgsCodeEditorColorScheme::ColorRole::Edge, QStringLiteral( "edgeColor" ) },
72 {QgsCodeEditorColorScheme::ColorRole::Fold, QStringLiteral( "foldColor" ) },
73 {QgsCodeEditorColorScheme::ColorRole::Error, QStringLiteral( "stderrFontColor" ) },
74 {QgsCodeEditorColorScheme::ColorRole::ErrorBackground, QStringLiteral( "stderrBackgroundColor" ) },
75 {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, QStringLiteral( "foldIconForeground" ) },
76 {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, QStringLiteral( "foldIconHalo" ) },
77 {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, QStringLiteral( "indentationGuide" ) },
78 {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, QStringLiteral( "searchMatchBackground" ) }
79};
80
81QgsCodeEditor::QgsCodeEditor( QWidget *parent, const QString &title, bool folding, bool margin, QgsCodeEditor::Flags flags, QgsCodeEditor::Mode mode )
82 : QsciScintilla( parent )
83 , mWidgetTitle( title )
84 , mMargin( margin )
85 , mFlags( flags )
86 , mMode( mode )
87{
88 if ( !parent && mWidgetTitle.isEmpty() )
89 {
90 setWindowTitle( QStringLiteral( "Text Editor" ) );
91 }
92 else
93 {
94 setWindowTitle( mWidgetTitle );
95 }
96
97 if ( folding )
99
100 mSoftHistory.append( QString() );
101
102 setSciWidget();
103 setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
104
105 SendScintilla( SCI_SETADDITIONALSELECTIONTYPING, 1 );
106 SendScintilla( SCI_SETMULTIPASTE, 1 );
107 SendScintilla( SCI_SETVIRTUALSPACEOPTIONS, SCVS_RECTANGULARSELECTION );
108
109 SendScintilla( SCI_SETMARGINTYPEN, static_cast< int >( QgsCodeEditor::MarginRole::ErrorIndicators ), SC_MARGIN_SYMBOL );
110 SendScintilla( SCI_SETMARGINMASKN, static_cast< int >( QgsCodeEditor::MarginRole::ErrorIndicators ), 1 << MARKER_NUMBER );
111 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
112 setAnnotationDisplay( QsciScintilla::AnnotationBoxed );
113
114 connect( QgsGui::instance(), &QgsGui::optionsChanged, this, [ = ]
115 {
116 setSciWidget();
118 } );
119
120 switch ( mMode )
121 {
123 break;
124
126 {
127 // Don't want to see the horizontal scrollbar at all
128 SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 );
129
130 setWrapMode( QsciScintilla::WrapCharacter );
131 break;
132 }
133
135 {
136 // Don't want to see the horizontal scrollbar at all
137 SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 );
138
139 setWrapMode( QsciScintilla::WrapCharacter );
140 SendScintilla( QsciScintilla::SCI_EMPTYUNDOBUFFER );
141 break;
142 }
143 }
144
145#if QSCINTILLA_VERSION < 0x020d03
146 installEventFilter( this );
147#endif
148}
149
150// Workaround a bug in QScintilla 2.8.X
151void QgsCodeEditor::focusOutEvent( QFocusEvent *event )
152{
153#if QSCINTILLA_VERSION >= 0x020800 && QSCINTILLA_VERSION < 0x020900
154 if ( event->reason() != Qt::ActiveWindowFocusReason )
155 {
156 /* There's a bug in all QScintilla 2.8.X, where
157 a focus out event that is not due to ActiveWindowFocusReason doesn't
158 lead to the bliking caret being disabled. The hack consists in making
159 QsciScintilla::focusOutEvent believe that the event is a ActiveWindowFocusReason
160 The bug was fixed in 2.9 per:
161 2015-04-14 Phil Thompson <[email protected]>
162
163 * qt/qsciscintillabase.cpp:
164 Fixed a problem notifying when focus is lost to another application
165 widget.
166 [41734678234e]
167 */
168 QFocusEvent newFocusEvent( QEvent::FocusOut, Qt::ActiveWindowFocusReason );
169 QsciScintilla::focusOutEvent( &newFocusEvent );
170 }
171 else
172#endif
173 {
174 QsciScintilla::focusOutEvent( event );
175 }
176}
177
178// This workaround a likely bug in QScintilla. The ESC key should not be consumned
179// by the main entry, so that the default behavior (Dialog closing) can trigger,
180// but only is the auto-completion suggestion list isn't displayed
181void QgsCodeEditor::keyPressEvent( QKeyEvent *event )
182{
183 if ( isListActive() )
184 {
185 QsciScintilla::keyPressEvent( event );
186 return;
187 }
188
189 if ( event->key() == Qt::Key_Escape )
190 {
191 // Shortcut QScintilla and redirect the event to the QWidget handler
192 QWidget::keyPressEvent( event ); // NOLINT(bugprone-parent-virtual-call) clazy:exclude=skipped-base-method
193 return;
194 }
195
197 {
198 switch ( event->key() )
199 {
200 case Qt::Key_Return:
201 case Qt::Key_Enter:
202 runCommand( text() );
203 updatePrompt();
204 return;
205
206 case Qt::Key_Down:
208 updatePrompt();
209 return;
210
211 case Qt::Key_Up:
213 updatePrompt();
214 return;
215
216 default:
217 break;
218 }
219 }
220
221 const bool ctrlModifier = event->modifiers() & Qt::ControlModifier;
222 const bool altModifier = event->modifiers() & Qt::AltModifier;
223
224 // Ctrl+Alt+F: reformat code
226 if ( !isReadOnly() && canReformat && ctrlModifier && altModifier && event->key() == Qt::Key_F )
227 {
228 event->accept();
229 reformatCode();
230 return;
231 }
232
233 // Toggle comment when user presses Ctrl+:
235 if ( !isReadOnly() && canToggle && ctrlModifier && event->key() == Qt::Key_Colon )
236 {
237 event->accept();
239 return;
240 }
241
242 QsciScintilla::keyPressEvent( event );
243
244 // Update calltips unless event is autorepeat
245 if ( !event->isAutoRepeat() )
246 {
247 callTip();
248 }
249
250}
251
252void QgsCodeEditor::contextMenuEvent( QContextMenuEvent *event )
253{
254 switch ( mMode )
255 {
257 {
258 QMenu *menu = createStandardContextMenu();
259 menu->setAttribute( Qt::WA_DeleteOnClose );
260
263 {
264 menu->addSeparator();
265 }
266
268 {
269 QAction *reformatAction = new QAction( tr( "Reformat Code" ), menu );
270 reformatAction->setShortcut( QStringLiteral( "Ctrl+Alt+F" ) );
271 reformatAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconFormatCode.svg" ) ) );
272 reformatAction->setEnabled( !isReadOnly() );
273 connect( reformatAction, &QAction::triggered, this, &QgsCodeEditor::reformatCode );
274 menu->addAction( reformatAction );
275 }
276
278 {
279 QAction *syntaxCheckAction = new QAction( tr( "Check Syntax" ), menu );
280 syntaxCheckAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconSyntaxErrorConsole.svg" ) ) );
281 connect( syntaxCheckAction, &QAction::triggered, this, &QgsCodeEditor::checkSyntax );
282 menu->addAction( syntaxCheckAction );
283 }
284
286 {
287 QAction *toggleCommentAction = new QAction( tr( "Toggle Comment" ), menu );
288 toggleCommentAction->setShortcut( QStringLiteral( "Ctrl+:" ) );
289 toggleCommentAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconCommentEditorConsole.svg" ) ) );
290 toggleCommentAction->setEnabled( !isReadOnly() );
291 connect( toggleCommentAction, &QAction::triggered, this, &QgsCodeEditor::toggleComment );
292 menu->addAction( toggleCommentAction );
293 }
294
295 populateContextMenu( menu );
296
297 menu->exec( mapToGlobal( event->pos() ) );
298 break;
299 }
300
302 {
303 QMenu *menu = new QMenu( this );
304 QMenu *historySubMenu = new QMenu( tr( "Command History" ), menu );
305
306 historySubMenu->addAction( tr( "Show" ), this, &QgsCodeEditor::showHistory, QStringLiteral( "Ctrl+Shift+SPACE" ) );
307 historySubMenu->addAction( tr( "Clear File" ), this, &QgsCodeEditor::clearPersistentHistory );
308 historySubMenu->addAction( tr( "Clear Session" ), this, &QgsCodeEditor::clearSessionHistory );
309
310 menu->addMenu( historySubMenu );
311 menu->addSeparator();
312
313 QAction *copyAction = menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, &QgsCodeEditor::copy, QKeySequence::Copy );
314 QAction *pasteAction = menu->addAction( QgsApplication::getThemeIcon( "mActionEditPaste.svg" ), tr( "Paste" ), this, &QgsCodeEditor::paste, QKeySequence::Paste );
315 copyAction->setEnabled( hasSelectedText() );
316 pasteAction->setEnabled( !QApplication::clipboard()->text().isEmpty() );
317
318 populateContextMenu( menu );
319
320 menu->exec( mapToGlobal( event->pos() ) );
321 break;
322 }
323
325 QsciScintilla::contextMenuEvent( event );
326 break;
327 }
328}
329
330bool QgsCodeEditor::eventFilter( QObject *watched, QEvent *event )
331{
332#if QSCINTILLA_VERSION < 0x020d03
333 if ( watched == this && event->type() == QEvent::InputMethod )
334 {
335 // swallow input method events, which cause loss of selected text.
336 // See https://sourceforge.net/p/scintilla/bugs/1913/ , which was ported to QScintilla
337 // in version 2.13.3
338 return true;
339 }
340#endif
341
342 return QsciScintilla::eventFilter( watched, event );
343}
344
349
351{
352 if ( mUseDefaultSettings )
353 return color( role );
354
355 if ( !mOverrideColors )
356 {
357 return defaultColor( role, mColorScheme );
358 }
359 else
360 {
361 const QColor color = mCustomColors.value( role );
362 return !color.isValid() ? defaultColor( role ) : color;
363 }
364}
365
367{
368 if ( mUseDefaultSettings )
369 return getMonospaceFont();
370
371 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
372
373 const QgsSettings settings;
374 if ( !mFontFamily.isEmpty() )
375 QgsFontUtils::setFontFamily( font, mFontFamily );
376
377#ifdef Q_OS_MAC
378 if ( mFontSize > 0 )
379 font.setPointSize( mFontSize );
380 else
381 {
382 // The font size gotten from getMonospaceFont() is too small on Mac
383 font.setPointSize( QLabel().font().pointSize() );
384 }
385#else
386 if ( mFontSize > 0 )
387 font.setPointSize( mFontSize );
388 else
389 {
390 const int fontSize = settings.value( QStringLiteral( "qgis/stylesheet/fontPointSize" ), 10 ).toInt();
391 font.setPointSize( fontSize );
392 }
393#endif
394 font.setBold( false );
395
396 return font;
397}
398
400{
401 updateFolding();
402
405
406 SendScintilla( SCI_MARKERSETFORE, SC_MARKNUM_FOLDEROPEN, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconHalo ) );
407 SendScintilla( SCI_MARKERSETBACK, SC_MARKNUM_FOLDEROPEN, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconForeground ) );
408 SendScintilla( SCI_MARKERSETFORE, SC_MARKNUM_FOLDER, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconHalo ) );
409 SendScintilla( SCI_MARKERSETBACK, SC_MARKNUM_FOLDER, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconForeground ) );
410 SendScintilla( SCI_STYLESETFORE, STYLE_INDENTGUIDE, lexerColor( QgsCodeEditorColorScheme::ColorRole::IndentationGuide ) );
411 SendScintilla( SCI_STYLESETBACK, STYLE_INDENTGUIDE, lexerColor( QgsCodeEditorColorScheme::ColorRole::IndentationGuide ) );
412
413 SendScintilla( QsciScintilla::SCI_INDICSETSTYLE, SEARCH_RESULT_INDICATOR, QsciScintilla::INDIC_STRAIGHTBOX );
414 SendScintilla( QsciScintilla::SCI_INDICSETFORE, SEARCH_RESULT_INDICATOR, lexerColor( QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground ) );
415 SendScintilla( QsciScintilla::SCI_INDICSETALPHA, SEARCH_RESULT_INDICATOR, 100 );
416 SendScintilla( QsciScintilla::SCI_INDICSETUNDER, SEARCH_RESULT_INDICATOR, true );
417 SendScintilla( QsciScintilla::SCI_INDICGETOUTLINEALPHA, SEARCH_RESULT_INDICATOR, 255 );
418
420 {
421 setCaretLineVisible( false );
422 setLineNumbersVisible( false ); // NO linenumbers for the input line
423 // Margin 1 is used for the '>' prompt (console input)
424 setMarginLineNumbers( 1, true );
425 setMarginWidth( 1, "00000" );
426 setMarginType( 1, QsciScintilla::MarginType::TextMarginRightJustified );
427 setMarginsBackgroundColor( color( QgsCodeEditorColorScheme::ColorRole::Background ) );
428 setEdgeMode( QsciScintilla::EdgeNone );
429 }
430}
431
432void QgsCodeEditor::setSciWidget()
433{
434 const QFont font = lexerFont();
435 setFont( font );
436
437 setUtf8( true );
438 setCaretLineVisible( true );
439 setCaretLineBackgroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::CaretLine ) );
440 setCaretForegroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Cursor ) );
443
444 setBraceMatching( QsciScintilla::SloppyBraceMatch );
447
448 setLineNumbersVisible( false );
449
450 // temporarily disable folding, will be enabled later if required by updateFolding()
451 setFolding( QsciScintilla::NoFoldStyle );
452 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::FoldingControls ), 0 );
453
454 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
455
458 setIndentationGuidesForegroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::MarginForeground ) );
459 setIndentationGuidesBackgroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::MarginBackground ) );
460 // whether margin will be shown
461 updateFolding();
462 const QColor foldColor = lexerColor( QgsCodeEditorColorScheme::ColorRole::Fold );
463 setFoldMarginColors( foldColor, foldColor );
464 // indentation
465 setAutoIndent( true );
466 setIndentationWidth( 4 );
467 setTabIndents( true );
468 setBackspaceUnindents( true );
469 setTabWidth( 4 );
470 // autocomplete
471 setAutoCompletionThreshold( 2 );
472 setAutoCompletionSource( QsciScintilla::AcsAPIs );
473
474 markerDefine( QgsApplication::getThemePixmap( "console/iconSyntaxErrorConsoleParams.svg", lexerColor( QgsCodeEditorColorScheme::ColorRole::Error ),
476}
477
478void QgsCodeEditor::setTitle( const QString &title )
479{
480 setWindowTitle( title );
481}
482
487
492
494{
495 switch ( language )
496 {
498 return tr( "CSS" );
500 return tr( "Expression" );
502 return tr( "HTML" );
504 return tr( "JavaScript" );
506 return tr( "JSON" );
508 return tr( "Python" );
510 return tr( "R" );
512 return tr( "SQL" );
514 return tr( "Batch" );
516 return tr( "Bash" );
518 return QString();
519 }
521}
522
524{
525 mMargin = margin;
526 if ( margin )
527 {
528 QFont marginFont = lexerFont();
529 marginFont.setPointSize( 10 );
530 setMarginLineNumbers( 0, true );
531 setMarginsFont( marginFont );
532 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::LineNumbers ), QStringLiteral( "00000" ) );
535 }
536 else
537 {
538 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::LineNumbers ), 0 );
539 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
540 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::FoldingControls ), 0 );
541 }
542}
543
545{
546 if ( visible )
547 {
548 QFont marginFont = lexerFont();
549 marginFont.setPointSize( 10 );
550 setMarginLineNumbers( static_cast< int >( QgsCodeEditor::MarginRole::LineNumbers ), true );
551 setMarginsFont( marginFont );
552 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::LineNumbers ), QStringLiteral( "00000" ) );
555 }
556 else
557 {
558 setMarginLineNumbers( static_cast< int >( QgsCodeEditor::MarginRole::LineNumbers ), false );
559 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::LineNumbers ), 0 );
560 }
561}
562
564{
565 return marginLineNumbers( static_cast< int >( QgsCodeEditor::MarginRole::LineNumbers ) );
566}
567
569{
570 if ( folding )
571 {
573 }
574 else
575 {
576 mFlags &= ~( static_cast< int >( QgsCodeEditor::Flag::CodeFolding ) );
577 }
578 updateFolding();
579}
580
585
586void QgsCodeEditor::updateFolding()
587{
589 {
590 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::FoldingControls ), "0" );
593 setFolding( QsciScintilla::PlainFoldStyle );
594 }
595 else
596 {
597 setFolding( QsciScintilla::NoFoldStyle );
598 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::FoldingControls ), 0 );
599 }
600}
601
602bool QgsCodeEditor::readHistoryFile()
603{
604 if ( mHistoryFilePath.isEmpty() || !QFile::exists( mHistoryFilePath ) )
605 return false;
606
607 QFile file( mHistoryFilePath );
608 if ( file.open( QIODevice::ReadOnly ) )
609 {
610 QTextStream stream( &file );
611#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
612 // Always use UTF-8
613 stream.setCodec( "UTF-8" );
614#endif
615 QString line;
616 while ( !stream.atEnd() )
617 {
618 line = stream.readLine(); // line of text excluding '\n'
619 mHistory.append( line );
620 }
621 syncSoftHistory();
622 return true;
623 }
624
625 return false;
626}
627
628void QgsCodeEditor::syncSoftHistory()
629{
630 mSoftHistory = mHistory;
631 mSoftHistory.append( QString() );
632 mSoftHistoryIndex = mSoftHistory.length() - 1;
633}
634
636{
637 mSoftHistory[mSoftHistoryIndex] = text();
638}
639
640void QgsCodeEditor::updateHistory( const QStringList &commands, bool skipSoftHistory )
641{
642 if ( commands.size() > 1 )
643 {
644 mHistory.append( commands );
645 }
646 else if ( !commands.value( 0 ).isEmpty() )
647 {
648 const QString command = commands.value( 0 );
649 if ( mHistory.empty() || command != mHistory.constLast() )
650 mHistory.append( command );
651 }
652
653 if ( !skipSoftHistory )
654 syncSoftHistory();
655}
656
658{
659
660}
661
662QString QgsCodeEditor::reformatCodeString( const QString &string )
663{
664 return string;
665}
666
667void QgsCodeEditor::showMessage( const QString &title, const QString &message, Qgis::MessageLevel level )
668{
669 switch ( level )
670 {
671 case Qgis::Info:
672 case Qgis::Success:
673 case Qgis::NoLevel:
674 QMessageBox::information( this, title, message );
675 break;
676
677 case Qgis::Warning:
678 QMessageBox::warning( this, title, message );
679 break;
680
681 case Qgis::Critical:
682 QMessageBox::critical( this, title, message );
683 break;
684 }
685}
686
688{
689 if ( mInterpreter )
690 {
691 const QString prompt = mInterpreter->promptForState( mInterpreter->currentState() );
692 SendScintilla( QsciScintilla::SCI_MARGINSETTEXT, static_cast< uintptr_t >( 0 ), prompt.toUtf8().constData() );
693 }
694}
695
697{
698 return mInterpreter;
699}
700
702{
703 mInterpreter = newInterpreter;
704 updatePrompt();
705}
706
707// Find the source substring index that most closely matches the target string
708int findMinimalDistanceIndex( const QString &source, const QString &target )
709{
710 const int index = std::min( source.length(), target.length() );
711
712 const int d0 = QgsStringUtils::levenshteinDistance( source.left( index ), target );
713 if ( d0 == 0 )
714 return index;
715
716 int refDistanceMore = d0;
717 int refIndexMore = index;
718 if ( index < source.length() - 1 )
719 {
720 while ( true )
721 {
722 const int newDistance = QgsStringUtils::levenshteinDistance( source.left( refIndexMore + 1 ), target );
723 if ( newDistance <= refDistanceMore )
724 {
725 refDistanceMore = newDistance;
726 refIndexMore++;
727 if ( refIndexMore == source.length() - 1 )
728 break;
729 }
730 else
731 {
732 break;
733 }
734 }
735 }
736
737 int refDistanceLess = d0;
738 int refIndexLess = index;
739 if ( index > 0 )
740 {
741 while ( true )
742 {
743 const int newDistance = QgsStringUtils::levenshteinDistance( source.left( refIndexLess - 1 ), target );
744 if ( newDistance <= refDistanceLess )
745 {
746 refDistanceLess = newDistance;
747 refIndexLess--;
748 if ( refIndexLess == 0 )
749 break;
750 }
751 else
752 {
753 break;
754 }
755 }
756 }
757
758 if ( refDistanceMore < refDistanceLess )
759 return refIndexMore;
760 else
761 return refIndexLess;
762}
763
765{
767 return;
768
769 const QString textBeforeCursor = text( 0, linearPosition() );
770 const QString originalText = text();
771 const QString newText = reformatCodeString( originalText );
772
773 if ( originalText == newText )
774 return;
775
776 // try to preserve the cursor position and scroll position
777 const int oldScrollValue = verticalScrollBar()->value();
778 const int linearIndex = findMinimalDistanceIndex( newText, textBeforeCursor );
779
780 beginUndoAction();
781 selectAll();
782 removeSelectedText();
783 insert( newText );
784 setLinearPosition( linearIndex );
785 verticalScrollBar()->setValue( oldScrollValue );
786 endUndoAction();
787}
788
790{
791 return true;
792}
793
795{
796
797}
798
799QStringList QgsCodeEditor::history() const
800{
801 return mHistory;
802}
803
804void QgsCodeEditor::runCommand( const QString &command, bool skipHistory )
805{
806 if ( !skipHistory )
807 {
808 updateHistory( { command } );
811 }
812
813 if ( mInterpreter )
814 mInterpreter->exec( command );
815
816 clear();
818}
819
821{
822 mHistory.clear();
823 readHistoryFile();
824 syncSoftHistory();
825
827}
828
830{
831 mHistory.clear();
832
833 if ( !mHistoryFilePath.isEmpty() && QFile::exists( mHistoryFilePath ) )
834 {
835 QFile file( mHistoryFilePath );
836 file.open( QFile::WriteOnly | QFile::Truncate );
837 }
838
840}
841
843{
844 if ( mHistoryFilePath.isEmpty() )
845 return false;
846
847 QFile f( mHistoryFilePath );
848 if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
849 {
850 return false;
851 }
852
853 QTextStream ts( &f );
854#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
855 ts.setCodec( "UTF-8" );
856#endif
857 for ( const QString &command : std::as_const( mHistory ) )
858 {
859 ts << command + '\n';
860 }
861 return true;
862}
863
865{
866 if ( mSoftHistoryIndex < mSoftHistory.length() - 1 && !mSoftHistory.isEmpty() )
867 {
868 mSoftHistoryIndex += 1;
869 setText( mSoftHistory[mSoftHistoryIndex] );
871 }
872}
873
875{
876 if ( mSoftHistoryIndex > 0 && !mSoftHistory.empty() )
877 {
878 mSoftHistoryIndex -= 1;
879 setText( mSoftHistory[mSoftHistoryIndex] );
881 }
882}
883
885{
886 QgsCodeEditorHistoryDialog *dialog = new QgsCodeEditorHistoryDialog( this, this );
887 dialog->setAttribute( Qt::WA_DeleteOnClose );
888
889 dialog->show();
890 dialog->activateWindow();
891}
892
894{
895 // remove item from the command history (just for the current session)
896 mHistory.removeAt( index );
897 mSoftHistory.removeAt( index );
898 if ( index < mSoftHistoryIndex )
899 {
900 mSoftHistoryIndex -= 1;
901 if ( mSoftHistoryIndex < 0 )
902 mSoftHistoryIndex = mSoftHistory.length() - 1;
903 }
904}
905
906void QgsCodeEditor::insertText( const QString &text )
907{
908 // Insert the text or replace selected text
909 if ( hasSelectedText() )
910 {
911 replaceSelectedText( text );
912 }
913 else
914 {
915 int line, index;
916 getCursorPosition( &line, &index );
917 insertAt( text, line, index );
918 setCursorPosition( line, index + text.length() );
919 }
920}
921
923{
924 if ( theme.isEmpty() && QgsApplication::themeName() == QLatin1String( "default" ) )
925 {
926 // if using default theme, take certain colors from the palette
927 const QPalette pal = qApp->palette();
928
929 switch ( role )
930 {
932 return pal.color( QPalette::Highlight );
934 return pal.color( QPalette::HighlightedText );
935 default:
936 break;
937 }
938 }
939 else if ( theme.isEmpty() )
940 {
941 // non default theme (e.g. Blend of Gray). Take colors from theme ini file...
942 const QSettings ini( QgsApplication::uiThemes().value( QgsApplication::themeName() ) + "/qscintilla.ini", QSettings::IniFormat );
943
944 static const QMap< QgsCodeEditorColorScheme::ColorRole, QString > sColorRoleToIniKey
945 {
946 {QgsCodeEditorColorScheme::ColorRole::Default, QStringLiteral( "python/defaultFontColor" ) },
947 {QgsCodeEditorColorScheme::ColorRole::Keyword, QStringLiteral( "python/keywordFontColor" ) },
948 {QgsCodeEditorColorScheme::ColorRole::Class, QStringLiteral( "python/classFontColor" ) },
949 {QgsCodeEditorColorScheme::ColorRole::Method, QStringLiteral( "python/methodFontColor" ) },
950 {QgsCodeEditorColorScheme::ColorRole::Decoration, QStringLiteral( "python/decoratorFontColor" ) },
951 {QgsCodeEditorColorScheme::ColorRole::Number, QStringLiteral( "python/numberFontColor" ) },
952 {QgsCodeEditorColorScheme::ColorRole::Comment, QStringLiteral( "python/commentFontColor" ) },
953 {QgsCodeEditorColorScheme::ColorRole::CommentLine, QStringLiteral( "sql/commentLineFontColor" ) },
954 {QgsCodeEditorColorScheme::ColorRole::CommentBlock, QStringLiteral( "python/commentBlockFontColor" ) },
955 {QgsCodeEditorColorScheme::ColorRole::Background, QStringLiteral( "python/paperBackgroundColor" ) },
956 {QgsCodeEditorColorScheme::ColorRole::Cursor, QStringLiteral( "cursorColor" ) },
957 {QgsCodeEditorColorScheme::ColorRole::CaretLine, QStringLiteral( "caretLineColor" ) },
958 {QgsCodeEditorColorScheme::ColorRole::Operator, QStringLiteral( "sql/operatorFontColor" ) },
959 {QgsCodeEditorColorScheme::ColorRole::QuotedOperator, QStringLiteral( "sql/QuotedOperatorFontColor" ) },
960 {QgsCodeEditorColorScheme::ColorRole::Identifier, QStringLiteral( "sql/identifierFontColor" ) },
961 {QgsCodeEditorColorScheme::ColorRole::QuotedIdentifier, QStringLiteral( "sql/QuotedIdentifierFontColor" ) },
962 {QgsCodeEditorColorScheme::ColorRole::Tag, QStringLiteral( "html/tagFontColor" ) },
963 {QgsCodeEditorColorScheme::ColorRole::UnknownTag, QStringLiteral( "html/unknownTagFontColor" ) },
964 {QgsCodeEditorColorScheme::ColorRole::SingleQuote, QStringLiteral( "sql/singleQuoteFontColor" ) },
965 {QgsCodeEditorColorScheme::ColorRole::DoubleQuote, QStringLiteral( "sql/doubleQuoteFontColor" ) },
966 {QgsCodeEditorColorScheme::ColorRole::TripleSingleQuote, QStringLiteral( "python/tripleSingleQuoteFontColor" ) },
967 {QgsCodeEditorColorScheme::ColorRole::TripleDoubleQuote, QStringLiteral( "python/tripleDoubleQuoteFontColor" ) },
968 {QgsCodeEditorColorScheme::ColorRole::MarginBackground, QStringLiteral( "marginBackgroundColor" ) },
969 {QgsCodeEditorColorScheme::ColorRole::MarginForeground, QStringLiteral( "marginForegroundColor" ) },
970 {QgsCodeEditorColorScheme::ColorRole::SelectionBackground, QStringLiteral( "selectionBackgroundColor" ) },
971 {QgsCodeEditorColorScheme::ColorRole::SelectionForeground, QStringLiteral( "selectionForegroundColor" ) },
972 {QgsCodeEditorColorScheme::ColorRole::MatchedBraceBackground, QStringLiteral( "matchedBraceBackground" ) },
973 {QgsCodeEditorColorScheme::ColorRole::MatchedBraceForeground, QStringLiteral( "matchedBraceColor" ) },
974 {QgsCodeEditorColorScheme::ColorRole::Edge, QStringLiteral( "edgeColor" ) },
975 {QgsCodeEditorColorScheme::ColorRole::Fold, QStringLiteral( "foldColor" ) },
976 {QgsCodeEditorColorScheme::ColorRole::Error, QStringLiteral( "stderrFontColor" ) },
977 {QgsCodeEditorColorScheme::ColorRole::ErrorBackground, QStringLiteral( "stderrBackground" ) },
978 {QgsCodeEditorColorScheme::ColorRole::FoldIconForeground, QStringLiteral( "foldIconForeground" ) },
979 {QgsCodeEditorColorScheme::ColorRole::FoldIconHalo, QStringLiteral( "foldIconHalo" ) },
980 {QgsCodeEditorColorScheme::ColorRole::IndentationGuide, QStringLiteral( "indentationGuide" ) },
981 {QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, QStringLiteral( "searchMatchBackground" ) },
982 };
983
984 const QgsCodeEditorColorScheme defaultScheme = QgsGui::codeEditorColorSchemeRegistry()->scheme( QStringLiteral( "default" ) );
985 return QgsSymbolLayerUtils::decodeColor( ini.value( sColorRoleToIniKey.value( role ), defaultScheme.color( role ).name() ).toString() );
986 }
987
988 const QgsCodeEditorColorScheme scheme = QgsGui::codeEditorColorSchemeRegistry()->scheme( theme.isEmpty() ? QStringLiteral( "default" ) : theme );
989 return scheme.color( role );
990}
991
993{
994 const QgsSettings settings;
995 if ( !settings.value( QStringLiteral( "codeEditor/overrideColors" ), false, QgsSettings::Gui ).toBool() )
996 {
997 const QString theme = settings.value( QStringLiteral( "codeEditor/colorScheme" ), QString(), QgsSettings::Gui ).toString();
998 return defaultColor( role, theme );
999 }
1000 else
1001 {
1002 const QString color = settings.value( QStringLiteral( "codeEditor/%1" ).arg( sColorRoleToSettingsKey.value( role ) ), QString(), QgsSettings::Gui ).toString();
1003 return color.isEmpty() ? defaultColor( role ) : QgsSymbolLayerUtils::decodeColor( color );
1004 }
1005}
1006
1008{
1009 QgsSettings settings;
1010 if ( color.isValid() )
1011 {
1012 settings.setValue( QStringLiteral( "codeEditor/%1" ).arg( sColorRoleToSettingsKey.value( role ) ), color.name(), QgsSettings::Gui );
1013 }
1014 else
1015 {
1016 settings.remove( QStringLiteral( "codeEditor/%1" ).arg( sColorRoleToSettingsKey.value( role ) ), QgsSettings::Gui );
1017 }
1018}
1019
1020// Settings for font and fontsize
1021bool QgsCodeEditor::isFixedPitch( const QFont &font )
1022{
1023 return font.fixedPitch();
1024}
1025
1027{
1028 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
1029
1030 const QgsSettings settings;
1031 if ( !settings.value( QStringLiteral( "codeEditor/fontfamily" ), QString(), QgsSettings::Gui ).toString().isEmpty() )
1032 QgsFontUtils::setFontFamily( font, settings.value( QStringLiteral( "codeEditor/fontfamily" ), QString(), QgsSettings::Gui ).toString() );
1033
1034 const int fontSize = settings.value( QStringLiteral( "codeEditor/fontsize" ), 0, QgsSettings::Gui ).toInt();
1035
1036#ifdef Q_OS_MAC
1037 if ( fontSize > 0 )
1038 font.setPointSize( fontSize );
1039 else
1040 {
1041 // The font size gotten from getMonospaceFont() is too small on Mac
1042 font.setPointSize( QLabel().font().pointSize() );
1043 }
1044#else
1045 if ( fontSize > 0 )
1046 font.setPointSize( fontSize );
1047 else
1048 {
1049 const int fontSize = settings.value( QStringLiteral( "qgis/stylesheet/fontPointSize" ), 10 ).toInt();
1050 font.setPointSize( fontSize );
1051 }
1052#endif
1053 font.setBold( false );
1054
1055 return font;
1056}
1057
1058void QgsCodeEditor::setCustomAppearance( const QString &scheme, const QMap<QgsCodeEditorColorScheme::ColorRole, QColor> &customColors, const QString &fontFamily, int fontSize )
1059{
1060 mUseDefaultSettings = false;
1061 mOverrideColors = !customColors.isEmpty();
1062 mColorScheme = scheme;
1063 mCustomColors = customColors;
1064 mFontFamily = fontFamily;
1065 mFontSize = fontSize;
1066
1067 setSciWidget();
1069}
1070
1071void QgsCodeEditor::addWarning( const int lineNumber, const QString &warning )
1072{
1073 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::ErrorIndicators ), "000" );
1074 markerAdd( lineNumber, MARKER_NUMBER );
1075 QFont font = lexerFont();
1076 font.setItalic( true );
1077 const QsciStyle styleAnn = QsciStyle( -1, QStringLiteral( "Annotation" ),
1080 font,
1081 true );
1082 annotate( lineNumber, warning, styleAnn );
1083 mWarningLines.push_back( lineNumber );
1084}
1085
1087{
1088 for ( const int line : mWarningLines )
1089 {
1090 markerDelete( line );
1091 clearAnnotations( line );
1092 }
1093 setMarginWidth( static_cast< int >( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
1094 mWarningLines.clear();
1095}
1096
1098{
1099 int line = 0;
1100 int index = 0;
1101 getCursorPosition( &line, &index );
1102 return line == lines() - 1;
1103}
1104
1105void QgsCodeEditor::setHistoryFilePath( const QString &path )
1106{
1107 mHistoryFilePath = path;
1108 readHistoryFile();
1109}
1110
1112{
1113 setCursorPosition( 0, 0 );
1114 ensureCursorVisible();
1115 ensureLineVisible( 0 );
1116
1117 if ( mMode == QgsCodeEditor::Mode::CommandInput )
1118 updatePrompt();
1119}
1120
1122{
1123 const int endLine = lines() - 1;
1124 const int endLineLength = lineLength( endLine );
1125 setCursorPosition( endLine, endLineLength );
1126 ensureCursorVisible();
1127 ensureLineVisible( endLine );
1128
1129 if ( mMode == QgsCodeEditor::Mode::CommandInput )
1130 updatePrompt();
1131}
1132
1134{
1135 return static_cast<int>( SendScintilla( SCI_GETCURRENTPOS ) );
1136}
1137
1139{
1140 int line, index;
1141 lineIndexFromPosition( linearIndex, &line, &index );
1142 setCursorPosition( line, index );
1143}
1144
1146{
1147 int startLine, startIndex, _;
1148 getSelection( &startLine, &startIndex, &_, &_ );
1149 if ( startLine == -1 )
1150 {
1151 return linearPosition();
1152 }
1153 return positionFromLineIndex( startLine, startIndex );
1154}
1155
1157{
1158 int endLine, endIndex, _;
1159 getSelection( &_, &_, &endLine, &endIndex );
1160 if ( endLine == -1 )
1161 {
1162 return linearPosition();
1163 }
1164 return positionFromLineIndex( endLine, endIndex );
1165}
1166
1167void QgsCodeEditor::setLinearSelection( int start, int end )
1168{
1169 int startLine, startIndex, endLine, endIndex;
1170 lineIndexFromPosition( start, &startLine, &startIndex );
1171 lineIndexFromPosition( end, &endLine, &endIndex );
1172 setSelection( startLine, startIndex, endLine, endIndex );
1173}
1174
1176
1177int QgsCodeInterpreter::exec( const QString &command )
1178{
1179 mState = execCommandImpl( command );
1180 return mState;
1181}
1182
1183
1185{
1186 // If wrapping is disabled, return -1
1187 if ( wrapMode() == WrapNone )
1188 {
1189 return -1;
1190 }
1191 // Get the current line
1192 if ( line == -1 )
1193 {
1194 int _index;
1195 lineIndexFromPosition( linearPosition(), &line, &_index );
1196 }
1197
1198 // If line isn't wrapped, return -1
1199 if ( SendScintilla( SCI_WRAPCOUNT, line ) <= 1 )
1200 {
1201 return -1;
1202 }
1203
1204 // Get the linear position at the end of the current line
1205 const long endLine = SendScintilla( SCI_GETLINEENDPOSITION, line );
1206 // Get the y coordinates of the start of the last wrapped line
1207 const long y = SendScintilla( SCI_POINTYFROMPOSITION, 0, endLine );
1208 // Return the linear position of the start of the last wrapped line
1209 return static_cast<int>( SendScintilla( SCI_POSITIONFROMPOINT, 0, y ) );
1210}
1211
1212
1213// Adapted from QsciScintilla source code (qsciscintilla.cpp) to handle line wrap
1215{
1216 if ( callTipsStyle() == CallTipsNone || lexer() == nullptr )
1217 {
1218 return;
1219 }
1220
1221 QsciAbstractAPIs *apis = lexer()->apis();
1222
1223 if ( !apis )
1224 return;
1225
1226 int pos, commas = 0;
1227 bool found = false;
1228 char ch;
1229
1230 pos = linearPosition();
1231
1232 // Move backwards through the line looking for the start of the current
1233 // call tip and working out which argument it is.
1234 while ( ( ch = getCharacter( pos ) ) != '\0' )
1235 {
1236 if ( ch == ',' )
1237 ++commas;
1238 else if ( ch == ')' )
1239 {
1240 int depth = 1;
1241
1242 // Ignore everything back to the start of the corresponding
1243 // parenthesis.
1244 while ( ( ch = getCharacter( pos ) ) != '\0' )
1245 {
1246 if ( ch == ')' )
1247 ++depth;
1248 else if ( ch == '(' && --depth == 0 )
1249 break;
1250 }
1251 }
1252 else if ( ch == '(' )
1253 {
1254 found = true;
1255 break;
1256 }
1257 }
1258
1259 // Cancel any existing call tip.
1260 SendScintilla( SCI_CALLTIPCANCEL );
1261
1262 // Done if there is no new call tip to set.
1263 if ( !found )
1264 return;
1265
1266 int contextStart, lastWordStart;
1267 QStringList context = apiContext( pos, contextStart, lastWordStart );
1268
1269 if ( context.isEmpty() )
1270 return;
1271
1272 // The last word is complete, not partial.
1273 context << QString();
1274
1275 QList<int> ctShifts;
1276 QStringList ctEntries = apis->callTips( context, commas, callTipsStyle(), ctShifts );
1277
1278 int nbEntries = ctEntries.count();
1279
1280 if ( nbEntries == 0 )
1281 return;
1282
1283 const int maxNumberOfCallTips = callTipsVisible();
1284
1285 // Clip to at most maxNumberOfCallTips entries.
1286 if ( maxNumberOfCallTips > 0 && maxNumberOfCallTips < nbEntries )
1287 {
1288 ctEntries = ctEntries.mid( 0, maxNumberOfCallTips );
1289 nbEntries = maxNumberOfCallTips;
1290 }
1291
1292 int shift;
1293 QString ct;
1294
1295 int nbShifts = ctShifts.count();
1296
1297 if ( maxNumberOfCallTips < 0 && nbEntries > 1 )
1298 {
1299 shift = ( nbShifts > 0 ? ctShifts.first() : 0 );
1300 ct = ctEntries[0];
1301 ct.prepend( '\002' );
1302 }
1303 else
1304 {
1305 if ( nbShifts > nbEntries )
1306 nbShifts = nbEntries;
1307
1308 // Find the biggest shift.
1309 shift = 0;
1310
1311 for ( int i = 0; i < nbShifts; ++i )
1312 {
1313 int sh = ctShifts[i];
1314
1315 if ( shift < sh )
1316 shift = sh;
1317 }
1318
1319 ct = ctEntries.join( "\n" );
1320 }
1321
1322 QByteArray ctBa = ct.toLatin1();
1323 const char *cts = ctBa.data();
1324
1325 const int currentWrapPosition = wrapPosition();
1326
1327 if ( currentWrapPosition != -1 )
1328 {
1329 SendScintilla( SCI_CALLTIPSHOW, currentWrapPosition, cts );
1330 }
1331 else
1332 {
1333 // Shift the position of the call tip (to take any context into account) but
1334 // don't go before the start of the line.
1335 if ( shift )
1336 {
1337 int ctmin = static_cast<int>( SendScintilla( SCI_POSITIONFROMLINE, SendScintilla( SCI_LINEFROMPOSITION, ct ) ) );
1338 if ( lastWordStart - shift < ctmin )
1339 lastWordStart = ctmin;
1340 }
1341
1342 int line, index;
1343 lineIndexFromPosition( lastWordStart, &line, &index );
1344 SendScintilla( SCI_CALLTIPSHOW, positionFromLineIndex( line, index ), cts );
1345 }
1346
1347 // Done if there is more than one call tip.
1348 if ( nbEntries > 1 )
1349 return;
1350
1351 // Highlight the current argument.
1352 const char *astart;
1353
1354 if ( commas == 0 )
1355 astart = strchr( cts, '(' );
1356 else
1357 for ( astart = strchr( cts, ',' ); astart && --commas > 0; astart = strchr( astart + 1, ',' ) )
1358 ;
1359
1360 if ( !astart )
1361 return;
1362
1363 astart++;
1364 if ( !*astart )
1365 return;
1366
1367 // The end is at the next comma or unmatched closing parenthesis.
1368 const char *aend;
1369 int depth = 0;
1370
1371 for ( aend = astart; *aend; ++aend )
1372 {
1373 char ch = *aend;
1374
1375 if ( ch == ',' && depth == 0 )
1376 break;
1377 else if ( ch == '(' )
1378 ++depth;
1379 else if ( ch == ')' )
1380 {
1381 if ( depth == 0 )
1382 break;
1383
1384 --depth;
1385 }
1386 }
1387
1388 if ( astart != aend )
1389 SendScintilla( SCI_CALLTIPSETHLT, astart - cts, aend - cts );
1390}
1391
1392
1393// Duplicated from QsciScintilla source code (qsciscintilla.cpp)
1394// Get the "next" character (ie. the one before the current position) in the
1395// current line. The character will be '\0' if there are no more.
1396char QgsCodeEditor::getCharacter( int &pos ) const
1397{
1398 if ( pos <= 0 )
1399 return '\0';
1400
1401 char ch = static_cast<char>( SendScintilla( SCI_GETCHARAT, --pos ) );
1402
1403 // Don't go past the end of the previous line.
1404 if ( ch == '\n' || ch == '\r' )
1405 {
1406 ++pos;
1407 return '\0';
1408 }
1409
1410 return ch;
1411}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:154
@ NoLevel
No level.
Definition qgis.h:159
@ Warning
Warning message.
Definition qgis.h:156
@ Critical
Critical/error message.
Definition qgis.h:157
@ Info
Information message.
Definition qgis.h:155
@ Success
Used for reporting a successful operation.
Definition qgis.h:158
@ CheckSyntax
Language supports syntax checking.
@ Reformat
Language supports automatic code reformatting.
@ ToggleComment
Language supports comment toggling.
ScriptLanguage
Scripting languages.
Definition qgis.h:4173
@ QgisExpression
QGIS expressions.
@ Batch
Windows batch files.
@ JavaScript
JavaScript.
@ Bash
Bash scripts.
@ Unknown
Unknown/other language.
QFlags< ScriptLanguageCapability > ScriptLanguageCapabilities
Script language capabilities.
Definition qgis.h:4208
static QPixmap getThemePixmap(const QString &name, const QColor &foreColor=QColor(), const QColor &backColor=QColor(), int size=16)
Helper to get a theme icon as a pixmap.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QHash< QString, QString > uiThemes()
All themes found in ~/.qgis3/themes folder.
static QString themeName()
Set the active theme to the specified theme.
QgsCodeEditorColorScheme scheme(const QString &id) const
Returns the color scheme with matching id.
Defines a color scheme for use in QgsCodeEditor widgets.
@ TripleSingleQuote
Triple single quote color.
@ CommentBlock
Comment block color.
@ QuotedOperator
Quoted operator color.
@ DoubleQuote
Double quote color.
@ QuotedIdentifier
Quoted identifier color.
@ SelectionForeground
Selection foreground color.
@ CommentLine
Line comment color.
@ FoldIconForeground
Fold icon foreground color.
@ MarginForeground
Margin foreground color.
@ ErrorBackground
Error background color.
@ MatchedBraceBackground
Matched brace background color.
@ SearchMatchBackground
Background color for search matches.
@ IndentationGuide
Indentation guide line.
@ SingleQuote
Single quote color.
@ MarginBackground
Margin background color.
@ SelectionBackground
Selection background color.
@ MatchedBraceForeground
Matched brace foreground color.
@ TripleDoubleQuote
Triple double quote color.
@ FoldIconHalo
Fold icon halo color.
QColor color(ColorRole role) const
Returns the color to use in the editor for the specified role.
A dialog for displaying and managing command history for a QgsCodeEditor widget.
bool eventFilter(QObject *watched, QEvent *event) override
void sessionHistoryCleared()
Emitted when the history of commands run in the current session is cleared.
void showHistory()
Shows the command history dialog.
int wrapPosition(int line=-1)
Returns the linear position of the start of the last wrapped part for the specified line,...
void setCustomAppearance(const QString &scheme=QString(), const QMap< QgsCodeEditorColorScheme::ColorRole, QColor > &customColors=QMap< QgsCodeEditorColorScheme::ColorRole, QColor >(), const QString &fontFamily=QString(), int fontSize=0)
Sets a custom appearance for the widget, disconnecting it from using the standard appearance taken fr...
Mode
Code editor modes.
@ OutputDisplay
Read only mode for display of command outputs.
@ ScriptEditor
Standard mode, allows for display and edit of entire scripts.
@ CommandInput
Command input mode.
void reformatCode()
Applies code reformatting to the editor.
virtual void toggleComment()
Toggle comment for the selected text.
void contextMenuEvent(QContextMenuEvent *event) override
void clearPersistentHistory()
Clears the entire persistent history of commands run in the editor.
void removeHistoryCommand(int index)
Removes the command at the specified index from the history of the code editor.
static void setColor(QgsCodeEditorColorScheme::ColorRole role, const QColor &color)
Sets the color to use in the editor for the specified role.
void setHistoryFilePath(const QString &path)
Sets the file path to use for recording and retrieving previously executed commands.
void setLinearSelection(int start, int end)
Convenience function to set the selection using linear indexes.
QStringList history() const
Returns the list of commands previously executed in the editor.
static constexpr int SEARCH_RESULT_INDICATOR
Indicator index for search results.
void keyPressEvent(QKeyEvent *event) override
virtual void moveCursorToStart()
Moves the cursor to the start of the document and scrolls to ensure it is visible.
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 persistentHistoryCleared()
Emitted when the persistent history of commands run in the editor is cleared.
virtual void callTip() override
void runCommand(const QString &command, bool skipHistory=false)
Runs a command in the editor.
void setFoldingVisible(bool folding)
Set whether the folding controls are visible in the editor.
virtual Qgis::ScriptLanguageCapabilities languageCapabilities() const
Returns the associated scripting language capabilities.
void setInterpreter(QgsCodeInterpreter *newInterpreter)
Sets an attached code interpreter for executing commands when the editor is in the QgsCodeEditor::Mod...
@ FoldingControls
Folding controls.
@ ErrorIndicators
Error indicators.
@ LineNumbers
Line numbers.
void runPostLexerConfigurationTasks()
Performs tasks which must be run after a lexer has been set for the widget.
virtual void showMessage(const QString &title, const QString &message, Qgis::MessageLevel level)
Shows a user facing message (eg a warning message).
int selectionEnd() const
Convenience function to return the end of the selection as a linear index Contrary to the getSelectio...
virtual void initializeLexer()
Called when the dialect specific code lexer needs to be initialized (or reinitialized).
int linearPosition() const
Convenience function to return the cursor position as a linear index.
void setTitle(const QString &title)
Set the widget title.
void setLinearPosition(int position)
Convenience function to set the cursor position as a linear index.
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.
static QFont getMonospaceFont()
Returns the monospaced font to use for code editors.
void showNextCommand()
Shows the next command from the session in the editor.
void focusOutEvent(QFocusEvent *event) override
@ CodeFolding
Indicates that code folding should be enabled for the editor.
@ ImmediatelyUpdateHistory
Indicates that the history file should be immediately updated whenever a command is executed,...
bool isCursorOnLastLine() const
Returns true if the cursor is on the last line of the document.
static bool isFixedPitch(const QFont &font)
Returns true if a font is a fixed pitch font.
void updateSoftHistory()
Updates the soft history by storing the current editor text in the history.
void clearSessionHistory()
Clears the history of commands run in the current session.
void insertText(const QString &text)
Insert text at cursor position, or replace any selected text if user has made a selection.
bool writeHistoryFile()
Stores the commands executed in the editor to the persistent history file.
virtual void moveCursorToEnd()
Moves the cursor to the end of the document and scrolls to ensure it is visible.
static QString languageToString(Qgis::ScriptLanguage language)
Returns a user-friendly, translated name of the specified script language.
void setLineNumbersVisible(bool visible)
Sets whether line numbers should be visible in the editor.
virtual Qgis::ScriptLanguage language() const
Returns the associated scripting language.
QFont lexerFont() const
Returns the font to use in the lexer.
virtual QString reformatCodeString(const QString &string)
Applies code reformatting to a string and returns the result.
QgsCodeInterpreter * interpreter() const
Returns the attached code interpreter, or nullptr if not set.
bool lineNumbersVisible() const
Returns whether line numbers are visible in the editor.
QColor lexerColor(QgsCodeEditorColorScheme::ColorRole role) const
Returns the color to use in the lexer for the specified role.
bool foldingVisible()
Returns true if the folding controls are visible in the editor.
void showPreviousCommand()
Shows the previous command from the session in the editor.
Q_DECL_DEPRECATED void setMarginVisible(bool margin)
Set margin visible state.
void updatePrompt()
Triggers an update of the interactive prompt part of the editor.
static QColor defaultColor(QgsCodeEditorColorScheme::ColorRole role, const QString &theme=QString())
Returns the default color for the specified role.
int selectionStart() const
Convenience function to return the start of the selection as a linear index Contrary to the getSelect...
void addWarning(int lineNumber, const QString &warning)
Adds a warning message and indicator to the specified a lineNumber.
virtual bool checkSyntax()
Applies syntax checking to the editor.
static QColor color(QgsCodeEditorColorScheme::ColorRole role)
Returns the color to use in the editor for the specified role.
An interface for code interpreters.
virtual int execCommandImpl(const QString &command)=0
Pure virtual method for executing commands in the interpreter.
virtual int currentState() const
Returns the current interpreter state.
virtual QString promptForState(int state) const =0
Returns the interactive prompt string to use for the interpreter, given a state.
int exec(const QString &command)
Executes a command in the interpreter.
virtual ~QgsCodeInterpreter()
static void setFontFamily(QFont &font, const QString &family)
Sets the family for a font object.
void optionsChanged()
This signal is emitted whenever the application options have been changed.
static QgsGui * instance()
Returns a pointer to the singleton instance.
Definition qgsgui.cpp:78
static QgsCodeEditorColorSchemeRegistry * codeEditorColorSchemeRegistry()
Returns the global code editor color scheme registry, used for registering the color schemes for QgsC...
Definition qgsgui.cpp:164
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static int levenshteinDistance(const QString &string1, const QString &string2, bool caseSensitive=false)
Returns the Levenshtein edit distance between two strings.
static QColor decodeColor(const QString &str)
#define BUILTIN_UNREACHABLE
Definition qgis.h:6571
int findMinimalDistanceIndex(const QString &source, const QString &target)