QGIS API Documentation 4.1.0-Master (009143bf4b4)
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 "qgscodeeditor.h"
18
19#include "qgsapplication.h"
23#include "qgsfontutils.h"
24#include "qgsgui.h"
25#include "qgssettings.h"
27#include "qgsshortcutsmanager.h"
28#include "qgsstringutils.h"
29#include "qgssymbollayerutils.h"
30
31#include <QClipboard>
32#include <QDebug>
33#include <QFileInfo>
34#include <QFocusEvent>
35#include <QFont>
36#include <QFontDatabase>
37#include <QLabel>
38#include <QMenu>
39#include <QMessageBox>
40#include <QScrollBar>
41#include <QString>
42#include <QWidget>
43#include <Qsci/qscilexer.h>
44#include <Qsci/qscistyle.h>
45
46#include "moc_qgscodeeditor.cpp"
47
48using namespace Qt::StringLiterals;
49
51const QgsSettingsEntryBool *QgsCodeEditor::settingContextHelpHover = new QgsSettingsEntryBool( u"context-help-hover"_s, sTreeCodeEditor, false, u"Whether the context help should works on hovered words"_s );
52const QgsSettingsEntryString *QgsCodeEditor::settingFontFamily = new QgsSettingsEntryString( u"font-family"_s, sTreeCodeEditor, QString(), u"Code editor font family override"_s );
53const QgsSettingsEntryInteger *QgsCodeEditor::settingFontSize = new QgsSettingsEntryInteger( u"font-size"_s, sTreeCodeEditor, 0, u"Code editor font size override (0 for default)"_s );
55
56
57QMap<QgsCodeEditorColorScheme::ColorRole, QString> QgsCodeEditor::sColorRoleToSettingsKey {
58 { QgsCodeEditorColorScheme::ColorRole::Default, u"defaultFontColor"_s },
59 { QgsCodeEditorColorScheme::ColorRole::Keyword, u"keywordFontColor"_s },
60 { QgsCodeEditorColorScheme::ColorRole::Class, u"classFontColor"_s },
61 { QgsCodeEditorColorScheme::ColorRole::Method, u"methodFontColor"_s },
62 { QgsCodeEditorColorScheme::ColorRole::Decoration, u"decoratorFontColor"_s },
63 { QgsCodeEditorColorScheme::ColorRole::Number, u"numberFontColor"_s },
64 { QgsCodeEditorColorScheme::ColorRole::Comment, u"commentFontColor"_s },
65 { QgsCodeEditorColorScheme::ColorRole::CommentLine, u"commentLineFontColor"_s },
66 { QgsCodeEditorColorScheme::ColorRole::CommentBlock, u"commentBlockFontColor"_s },
67 { QgsCodeEditorColorScheme::ColorRole::Background, u"paperBackgroundColor"_s },
70 { QgsCodeEditorColorScheme::ColorRole::Operator, u"operatorFontColor"_s },
71 { QgsCodeEditorColorScheme::ColorRole::QuotedOperator, u"quotedOperatorFontColor"_s },
72 { QgsCodeEditorColorScheme::ColorRole::Identifier, u"identifierFontColor"_s },
73 { QgsCodeEditorColorScheme::ColorRole::QuotedIdentifier, u"quotedIdentifierFontColor"_s },
74 { QgsCodeEditorColorScheme::ColorRole::Tag, u"tagFontColor"_s },
75 { QgsCodeEditorColorScheme::ColorRole::UnknownTag, u"unknownTagFontColor"_s },
76 { QgsCodeEditorColorScheme::ColorRole::SingleQuote, u"singleQuoteFontColor"_s },
77 { QgsCodeEditorColorScheme::ColorRole::DoubleQuote, u"doubleQuoteFontColor"_s },
78 { QgsCodeEditorColorScheme::ColorRole::TripleSingleQuote, u"tripleSingleQuoteFontColor"_s },
79 { QgsCodeEditorColorScheme::ColorRole::TripleDoubleQuote, u"tripleDoubleQuoteFontColor"_s },
80 { QgsCodeEditorColorScheme::ColorRole::MarginBackground, u"marginBackgroundColor"_s },
81 { QgsCodeEditorColorScheme::ColorRole::MarginForeground, u"marginForegroundColor"_s },
82 { QgsCodeEditorColorScheme::ColorRole::SelectionBackground, u"selectionBackgroundColor"_s },
83 { QgsCodeEditorColorScheme::ColorRole::SelectionForeground, u"selectionForegroundColor"_s },
88 { QgsCodeEditorColorScheme::ColorRole::Error, u"stderrFontColor"_s },
89 { QgsCodeEditorColorScheme::ColorRole::ErrorBackground, u"stderrBackgroundColor"_s },
94};
95
96QgsCodeEditor::QgsCodeEditor( QWidget *parent, const QString &title, bool folding, bool margin, QgsCodeEditor::Flags flags, QgsCodeEditor::Mode mode )
97 : QsciScintilla( parent )
98 , mWidgetTitle( title )
99 , mMargin( margin )
100 , mFlags( flags )
101 , mMode( mode )
102{
103 if ( !parent && mWidgetTitle.isEmpty() )
104 {
105 setWindowTitle( u"Text Editor"_s );
106 }
107 else
108 {
109 setWindowTitle( mWidgetTitle );
110 }
111
112 if ( folding )
114
115 mSoftHistory.append( QString() );
116
117 setSciWidget();
118 setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
119
120 SendScintilla( SCI_SETADDITIONALSELECTIONTYPING, 1 );
121 SendScintilla( SCI_SETMULTIPASTE, 1 );
122 SendScintilla( SCI_SETVIRTUALSPACEOPTIONS, SCVS_RECTANGULARSELECTION );
123
124 SendScintilla( SCI_SETMARGINTYPEN, static_cast<int>( QgsCodeEditor::MarginRole::ErrorIndicators ), SC_MARGIN_SYMBOL );
125 SendScintilla( SCI_SETMARGINMASKN, static_cast<int>( QgsCodeEditor::MarginRole::ErrorIndicators ), 1 << MARKER_NUMBER );
126 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
127 setAnnotationDisplay( QsciScintilla::AnnotationBoxed );
128
129 connect( QgsGui::instance(), &QgsGui::optionsChanged, this, [this] {
130 setSciWidget();
132 } );
133
134 switch ( mMode )
135 {
137 break;
138
140 {
141 // Don't want to see the horizontal scrollbar at all
142 SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 );
143
144 setWrapMode( QsciScintilla::WrapCharacter );
145 break;
146 }
147
149 {
150 // Don't want to see the horizontal scrollbar at all
151 SendScintilla( QsciScintilla::SCI_SETHSCROLLBAR, 0 );
152
153 setWrapMode( QsciScintilla::WrapCharacter );
154 SendScintilla( QsciScintilla::SCI_EMPTYUNDOBUFFER );
155 break;
156 }
157 }
158
159#if QSCINTILLA_VERSION < 0x020d03
160 installEventFilter( this );
161#endif
162
163 mLastEditTimer = new QTimer( this );
164 mLastEditTimer->setSingleShot( true );
165 mLastEditTimer->setInterval( 1000 );
166 connect( mLastEditTimer, &QTimer::timeout, this, &QgsCodeEditor::onLastEditTimeout );
167 connect( this, &QgsCodeEditor::textChanged, mLastEditTimer, qOverload<>( &QTimer::start ) );
168}
169
170// Workaround a bug in QScintilla 2.8.X
172{
173#if QSCINTILLA_VERSION >= 0x020800 && QSCINTILLA_VERSION < 0x020900
174 if ( event->reason() != Qt::ActiveWindowFocusReason )
175 {
176 /* There's a bug in all QScintilla 2.8.X, where
177 a focus out event that is not due to ActiveWindowFocusReason doesn't
178 lead to the bliking caret being disabled. The hack consists in making
179 QsciScintilla::focusOutEvent believe that the event is a ActiveWindowFocusReason
180 The bug was fixed in 2.9 per:
181 2015-04-14 Phil Thompson <[email protected]>
182
183 * qt/qsciscintillabase.cpp:
184 Fixed a problem notifying when focus is lost to another application
185 widget.
186 [41734678234e]
187 */
188 QFocusEvent newFocusEvent( QEvent::FocusOut, Qt::ActiveWindowFocusReason );
189 QsciScintilla::focusOutEvent( &newFocusEvent );
190 }
191 else
192#endif
193 {
194 QsciScintilla::focusOutEvent( event );
195 }
196 onLastEditTimeout();
197}
198
199// This workaround a likely bug in QScintilla. The ESC key should not be consumned
200// by the main entry, so that the default behavior (Dialog closing) can trigger,
201// but only is the auto-completion suggestion list isn't displayed
203{
204 if ( isListActive() )
205 {
206 QsciScintilla::keyPressEvent( event );
207 return;
208 }
209
210 if ( event->key() == Qt::Key_Escape )
211 {
212 // Shortcut QScintilla and redirect the event to the QWidget handler
213 QWidget::keyPressEvent( event ); // NOLINT(bugprone-parent-virtual-call) clazy:exclude=skipped-base-method
214 return;
215 }
216
217 if ( event->matches( QKeySequence::StandardKey::HelpContents ) )
218 {
219 // Check if some text is selected
220 QString text = selectedText();
221
222 // Check if mouse is hovering over a word
223 if ( text.isEmpty() && settingContextHelpHover->value() )
224 {
225 text = wordAtPoint( mapFromGlobal( QCursor::pos() ) );
226 }
227
228 // Otherwise, check if there is a word at the current text cursor position
229 if ( text.isEmpty() )
230 {
231 int line, index;
232 getCursorPosition( &line, &index );
233 text = wordAtLineIndex( line, index );
234 }
235 emit helpRequested( text );
236 return;
237 }
238
239
241 {
242 switch ( event->key() )
243 {
244 case Qt::Key_Return:
245 case Qt::Key_Enter:
246 runCommand( text() );
247 updatePrompt();
248 return;
249
250 case Qt::Key_Down:
252 updatePrompt();
253 return;
254
255 case Qt::Key_Up:
257 updatePrompt();
258 return;
259
260 default:
261 break;
262 }
263 }
264
265 // check for reformat code sequence
267 if ( !isReadOnly() && canReformat )
268 {
270 if ( !reformatCodeSequence.isEmpty() && reformatCodeSequence.matches( event->key() | event->modifiers() ) )
271 {
272 event->accept();
273 reformatCode();
274 return;
275 }
276 }
277
278 // Check for toggle comment sequence
280 if ( !isReadOnly() && canToggle )
281 {
283 if ( !toggleCommentCodeSequence.isEmpty() && toggleCommentCodeSequence.matches( event->key() | event->modifiers() ) )
284 {
285 event->accept();
287 return;
288 }
289 }
290
291 QsciScintilla::keyPressEvent( event );
292
293 // Update calltips unless event is autorepeat
294 if ( !event->isAutoRepeat() )
295 {
296 callTip();
297 }
298}
299
300void QgsCodeEditor::contextMenuEvent( QContextMenuEvent *event )
301{
302 switch ( mMode )
303 {
305 {
306 QMenu *menu = createStandardContextMenu();
307 menu->setAttribute( Qt::WA_DeleteOnClose );
308
310 {
311 menu->addSeparator();
312 }
313
315 {
316 QAction *reformatAction = new QAction( tr( "Reformat Code" ), menu );
317 reformatAction->setShortcut( QgsGui::shortcutsManager()->sequenceForCommonAction( QgsShortcutsManager::CommonAction::CodeReformat ) );
318 reformatAction->setIcon( QgsApplication::getThemeIcon( u"console/iconFormatCode.svg"_s ) );
319 reformatAction->setEnabled( !isReadOnly() );
320 connect( reformatAction, &QAction::triggered, this, &QgsCodeEditor::reformatCode );
321 menu->addAction( reformatAction );
322 }
323
325 {
326 QAction *syntaxCheckAction = new QAction( tr( "Check Syntax" ), menu );
327 syntaxCheckAction->setIcon( QgsApplication::getThemeIcon( u"console/iconSyntaxErrorConsole.svg"_s ) );
328 connect( syntaxCheckAction, &QAction::triggered, this, &QgsCodeEditor::checkSyntax );
329 menu->addAction( syntaxCheckAction );
330 }
331
333 {
334 QAction *toggleCommentAction = new QAction( tr( "Toggle Comment" ), menu );
335 toggleCommentAction->setShortcut( QgsGui::shortcutsManager()->sequenceForCommonAction( QgsShortcutsManager::CommonAction::CodeToggleComment ) );
336 toggleCommentAction->setIcon( QgsApplication::getThemeIcon( u"console/iconCommentEditorConsole.svg"_s, palette().color( QPalette::ColorRole::WindowText ) ) );
337 toggleCommentAction->setEnabled( !isReadOnly() );
338 connect( toggleCommentAction, &QAction::triggered, this, &QgsCodeEditor::toggleComment );
339 menu->addAction( toggleCommentAction );
340 }
341
342 populateContextMenu( menu );
343
344 menu->exec( mapToGlobal( event->pos() ) );
345 break;
346 }
347
349 {
350 QMenu *menu = new QMenu( this );
351 QMenu *historySubMenu = new QMenu( tr( "Command History" ), menu );
352
353 historySubMenu->addAction( tr( "Show" ), this, &QgsCodeEditor::showHistory, u"Ctrl+Shift+SPACE"_s );
354 historySubMenu->addAction( tr( "Clear File" ), this, &QgsCodeEditor::clearPersistentHistory );
355 historySubMenu->addAction( tr( "Clear Session" ), this, &QgsCodeEditor::clearSessionHistory );
356
357 menu->addMenu( historySubMenu );
358 menu->addSeparator();
359
360 QAction *copyAction = menu->addAction( QgsApplication::getThemeIcon( "mActionEditCopy.svg" ), tr( "Copy" ), this, &QgsCodeEditor::copy, QKeySequence::Copy );
361 QAction *pasteAction = menu->addAction( QgsApplication::getThemeIcon( "mActionEditPaste.svg" ), tr( "Paste" ), this, &QgsCodeEditor::paste, QKeySequence::Paste );
362 copyAction->setEnabled( hasSelectedText() );
363 pasteAction->setEnabled( !QApplication::clipboard()->text().isEmpty() );
364
365 populateContextMenu( menu );
366
367 menu->exec( mapToGlobal( event->pos() ) );
368 break;
369 }
370
372 QsciScintilla::contextMenuEvent( event );
373 break;
374 }
375}
376
378{
379 if ( event->type() == QEvent::ShortcutOverride )
380 {
381 if ( QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( event ) )
382 {
383 if ( keyEvent->matches( QKeySequence::StandardKey::HelpContents ) )
384 {
385 // If the user pressed F1, we want to prevent the main help dialog to show
386 // and handle the event in QgsCodeEditor::keyPressEvent
387 keyEvent->accept();
388 return true;
389 }
390 }
391 }
392 return QsciScintilla::event( event );
393}
394
395bool QgsCodeEditor::eventFilter( QObject *watched, QEvent *event )
396{
397#if QSCINTILLA_VERSION < 0x020d03
398 if ( watched == this && event->type() == QEvent::InputMethod )
399 {
400 // swallow input method events, which cause loss of selected text.
401 // See https://sourceforge.net/p/scintilla/bugs/1913/ , which was ported to QScintilla
402 // in version 2.13.3
403 return true;
404 }
405#endif
406
407 return QsciScintilla::eventFilter( watched, event );
408}
409
412
414{
415 if ( mUseDefaultSettings )
416 return color( role );
417
418 if ( !mOverrideColors )
419 {
420 return defaultColor( role, mColorScheme );
421 }
422 else
423 {
424 const QColor color = mCustomColors.value( role );
425 return !color.isValid() ? defaultColor( role ) : color;
426 }
427}
428
430{
431 if ( mUseDefaultSettings )
432 return getMonospaceFont();
433
434 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
435
436 const QgsSettings settings;
437 if ( !mFontFamily.isEmpty() )
438 QgsFontUtils::setFontFamily( font, mFontFamily );
439
440#ifdef Q_OS_MAC
441 if ( mFontSize > 0 )
442 font.setPointSize( mFontSize );
443 else
444 {
445 // The font size gotten from getMonospaceFont() is too small on Mac
446 font.setPointSize( QLabel().font().pointSize() );
447 }
448#else
449 if ( mFontSize > 0 )
450 font.setPointSize( mFontSize );
451 else
452 {
453 const int fontSize = settings.value( u"qgis/stylesheet/fontPointSize"_s, 10 ).toInt();
454 font.setPointSize( fontSize );
455 }
456#endif
457 font.setBold( false );
458
459 return font;
460}
461
463{
464 updateFolding();
465
468
469 SendScintilla( SCI_MARKERSETFORE, SC_MARKNUM_FOLDEROPEN, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconHalo ) );
470 SendScintilla( SCI_MARKERSETBACK, SC_MARKNUM_FOLDEROPEN, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconForeground ) );
471 SendScintilla( SCI_MARKERSETFORE, SC_MARKNUM_FOLDER, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconHalo ) );
472 SendScintilla( SCI_MARKERSETBACK, SC_MARKNUM_FOLDER, lexerColor( QgsCodeEditorColorScheme::ColorRole::FoldIconForeground ) );
473 SendScintilla( SCI_STYLESETFORE, STYLE_INDENTGUIDE, lexerColor( QgsCodeEditorColorScheme::ColorRole::IndentationGuide ) );
474 SendScintilla( SCI_STYLESETBACK, STYLE_INDENTGUIDE, lexerColor( QgsCodeEditorColorScheme::ColorRole::IndentationGuide ) );
475
476 SendScintilla( QsciScintilla::SCI_INDICSETSTYLE, SEARCH_RESULT_INDICATOR, QsciScintilla::INDIC_STRAIGHTBOX );
477 SendScintilla( QsciScintilla::SCI_INDICSETFORE, SEARCH_RESULT_INDICATOR, lexerColor( QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground ) );
478 SendScintilla( QsciScintilla::SCI_INDICSETALPHA, SEARCH_RESULT_INDICATOR, 100 );
479 SendScintilla( QsciScintilla::SCI_INDICSETUNDER, SEARCH_RESULT_INDICATOR, true );
480 SendScintilla( QsciScintilla::SCI_INDICGETOUTLINEALPHA, SEARCH_RESULT_INDICATOR, 255 );
481
483 {
484 setCaretLineVisible( false );
485 setLineNumbersVisible( false ); // NO linenumbers for the input line
486 // Margin 1 is used for the '>' prompt (console input)
487 setMarginLineNumbers( 1, true );
488 setMarginWidth( 1, "00000" );
489 setMarginType( 1, QsciScintilla::MarginType::TextMarginRightJustified );
490 setMarginsBackgroundColor( color( QgsCodeEditorColorScheme::ColorRole::Background ) );
491 setEdgeMode( QsciScintilla::EdgeNone );
492 }
493}
494
495void QgsCodeEditor::onLastEditTimeout()
496{
497 mLastEditTimer->stop();
498 emit editingTimeout();
499}
500
501void QgsCodeEditor::setSciWidget()
502{
503 const QFont font = lexerFont();
504 setFont( font );
505
506 setUtf8( true );
507 setCaretLineVisible( true );
508 setCaretLineBackgroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::CaretLine ) );
509 setCaretForegroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::Cursor ) );
512
513 setBraceMatching( QsciScintilla::SloppyBraceMatch );
516
517 setLineNumbersVisible( false );
518
519 // temporarily disable folding, will be enabled later if required by updateFolding()
520 setFolding( QsciScintilla::NoFoldStyle );
521 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::FoldingControls ), 0 );
522
523 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
524
527 setIndentationGuidesForegroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::MarginForeground ) );
528 setIndentationGuidesBackgroundColor( lexerColor( QgsCodeEditorColorScheme::ColorRole::MarginBackground ) );
529 // whether margin will be shown
530 updateFolding();
531 const QColor foldColor = lexerColor( QgsCodeEditorColorScheme::ColorRole::Fold );
532 setFoldMarginColors( foldColor, foldColor );
533 // indentation
534 setAutoIndent( true );
535 setIndentationWidth( 4 );
536 setTabIndents( true );
537 setBackspaceUnindents( true );
538 setTabWidth( 4 );
539 // autocomplete
540 setAutoCompletionThreshold( 2 );
541 setAutoCompletionSource( QsciScintilla::AcsAPIs );
542
543 markerDefine( QgsApplication::getThemePixmap( "console/iconSyntaxErrorConsoleParams.svg", lexerColor( QgsCodeEditorColorScheme::ColorRole::Error ), lexerColor( QgsCodeEditorColorScheme::ColorRole::ErrorBackground ), 16 ), MARKER_NUMBER );
544}
545
546void QgsCodeEditor::setTitle( const QString &title )
547{
548 setWindowTitle( title );
549}
550
555
560
562{
563 switch ( language )
564 {
566 return tr( "CSS" );
568 return tr( "Expression" );
570 return tr( "HTML" );
572 return tr( "JavaScript" );
574 return tr( "JSON" );
576 return tr( "Python" );
578 return tr( "R" );
580 return tr( "SQL" );
582 return tr( "Batch" );
584 return tr( "Bash" );
586 return QString();
587 }
589}
590
592{
593 mMargin = margin;
594 if ( margin )
595 {
596 QFont marginFont = lexerFont();
597 marginFont.setPointSize( 10 );
598 setMarginLineNumbers( 0, true );
599 setMarginsFont( marginFont );
600 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::LineNumbers ), u"00000"_s );
603 }
604 else
605 {
606 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::LineNumbers ), 0 );
607 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
608 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::FoldingControls ), 0 );
609 }
610}
611
613{
614 if ( visible )
615 {
616 QFont marginFont = lexerFont();
617 marginFont.setPointSize( 10 );
618 setMarginLineNumbers( static_cast<int>( QgsCodeEditor::MarginRole::LineNumbers ), true );
619 setMarginsFont( marginFont );
620 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::LineNumbers ), u"00000"_s );
623 }
624 else
625 {
626 setMarginLineNumbers( static_cast<int>( QgsCodeEditor::MarginRole::LineNumbers ), false );
627 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::LineNumbers ), 0 );
628 }
629}
630
632{
633 return marginLineNumbers( static_cast<int>( QgsCodeEditor::MarginRole::LineNumbers ) );
634}
635
637{
638 if ( folding )
639 {
641 }
642 else
643 {
644 mFlags &= ~( static_cast<int>( QgsCodeEditor::Flag::CodeFolding ) );
645 }
646 updateFolding();
647}
648
653
654void QgsCodeEditor::updateFolding()
655{
657 {
658 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::FoldingControls ), "0" );
661 setFolding( QsciScintilla::PlainFoldStyle );
662 }
663 else
664 {
665 setFolding( QsciScintilla::NoFoldStyle );
666 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::FoldingControls ), 0 );
667 }
668}
669
670bool QgsCodeEditor::readHistoryFile()
671{
672 if ( mHistoryFilePath.isEmpty() || !QFile::exists( mHistoryFilePath ) )
673 return false;
674
675 QFile file( mHistoryFilePath );
676 if ( file.open( QIODevice::ReadOnly ) )
677 {
678 QTextStream stream( &file );
679 QString line;
680 while ( !stream.atEnd() )
681 {
682 line = stream.readLine(); // line of text excluding '\n'
683 mHistory.append( line );
684 }
685 syncSoftHistory();
686 return true;
687 }
688
689 return false;
690}
691
692void QgsCodeEditor::syncSoftHistory()
693{
694 mSoftHistory = mHistory;
695 mSoftHistory.append( QString() );
696 mSoftHistoryIndex = mSoftHistory.length() - 1;
697}
698
700{
701 mSoftHistory[mSoftHistoryIndex] = text();
702}
703
704void QgsCodeEditor::updateHistory( const QStringList &commands, bool skipSoftHistory )
705{
706 if ( commands.size() > 1 )
707 {
708 mHistory.append( commands );
709 }
710 else if ( !commands.value( 0 ).isEmpty() )
711 {
712 const QString command = commands.value( 0 );
713 if ( mHistory.empty() || command != mHistory.constLast() )
714 mHistory.append( command );
715 }
716
717 if ( !skipSoftHistory )
718 syncSoftHistory();
719}
720
723
724QString QgsCodeEditor::reformatCodeString( const QString &string )
725{
726 return string;
727}
728
729void QgsCodeEditor::showMessage( const QString &title, const QString &message, Qgis::MessageLevel level )
730{
731 switch ( level )
732 {
733 case Qgis::Info:
734 case Qgis::Success:
735 case Qgis::NoLevel:
736 QMessageBox::information( this, title, message );
737 break;
738
739 case Qgis::Warning:
740 QMessageBox::warning( this, title, message );
741 break;
742
743 case Qgis::Critical:
744 QMessageBox::critical( this, title, message );
745 break;
746 }
747}
748
750{
751 if ( mInterpreter )
752 {
753 const QString prompt = mInterpreter->promptForState( mInterpreter->currentState() );
754 SendScintilla( QsciScintilla::SCI_MARGINSETTEXT, static_cast<uintptr_t>( 0 ), prompt.toUtf8().constData() );
755 }
756}
757
759{
760 return mInterpreter;
761}
762
764{
765 mInterpreter = newInterpreter;
766 updatePrompt();
767}
768
769// Find the source substring index that most closely matches the target string
770int findMinimalDistanceIndex( const QString &source, const QString &target )
771{
772 const int index = std::min( source.length(), target.length() );
773
774 const int d0 = QgsStringUtils::levenshteinDistance( source.left( index ), target );
775 if ( d0 == 0 )
776 return index;
777
778 int refDistanceMore = d0;
779 int refIndexMore = index;
780 if ( index < source.length() - 1 )
781 {
782 while ( true )
783 {
784 const int newDistance = QgsStringUtils::levenshteinDistance( source.left( refIndexMore + 1 ), target );
785 if ( newDistance <= refDistanceMore )
786 {
787 refDistanceMore = newDistance;
788 refIndexMore++;
789 if ( refIndexMore == source.length() - 1 )
790 break;
791 }
792 else
793 {
794 break;
795 }
796 }
797 }
798
799 int refDistanceLess = d0;
800 int refIndexLess = index;
801 if ( index > 0 )
802 {
803 while ( true )
804 {
805 const int newDistance = QgsStringUtils::levenshteinDistance( source.left( refIndexLess - 1 ), target );
806 if ( newDistance <= refDistanceLess )
807 {
808 refDistanceLess = newDistance;
809 refIndexLess--;
810 if ( refIndexLess == 0 )
811 break;
812 }
813 else
814 {
815 break;
816 }
817 }
818 }
819
820 if ( refDistanceMore < refDistanceLess )
821 return refIndexMore;
822 else
823 return refIndexLess;
824}
825
827{
829 return;
830
831 const QString textBeforeCursor = text( 0, linearPosition() );
832 const QString originalText = text();
833 const QString newText = reformatCodeString( originalText );
834
835 if ( originalText == newText )
836 return;
837
838 // try to preserve the cursor position and scroll position
839 const int oldScrollValue = verticalScrollBar()->value();
840 const int linearIndex = findMinimalDistanceIndex( newText, textBeforeCursor );
841
842 beginUndoAction();
843 selectAll();
844 removeSelectedText();
845 insert( newText );
846 setLinearPosition( linearIndex );
847 verticalScrollBar()->setValue( oldScrollValue );
848 endUndoAction();
849}
850
852{
853 return true;
854}
855
858
859void QgsCodeEditor::toggleLineComments( const QString &commentPrefix )
860{
861 if ( isReadOnly() )
862 {
863 return;
864 }
865
866 beginUndoAction();
867 int startLine, startPos, endLine, endPos;
868 if ( hasSelectedText() )
869 {
870 getSelection( &startLine, &startPos, &endLine, &endPos );
871 }
872 else
873 {
874 getCursorPosition( &startLine, &startPos );
875 endLine = startLine;
876 endPos = startPos;
877 }
878
879 // Check comment state and minimum indentation for each selected line
880 bool allEmpty = true;
881 bool allCommented = true;
882 int minIndentation = -1;
883 for ( int line = startLine; line <= endLine; line++ )
884 {
885 const QString stripped = text( line ).trimmed();
886 if ( !stripped.isEmpty() )
887 {
888 allEmpty = false;
889 if ( !stripped.startsWith( commentPrefix ) )
890 {
891 allCommented = false;
892 }
893 if ( minIndentation == -1 || minIndentation > indentation( line ) )
894 {
895 minIndentation = indentation( line );
896 }
897 }
898 }
899
900 // Special case, only empty lines
901 if ( allEmpty )
902 {
903 return;
904 }
905
906 // Selection shift to keep the same selected text after the prefix is added/removed
907 int delta = 0;
908
909 const int prefixLength = static_cast<int>( commentPrefix.length() );
910
911 const bool startLineEmpty = ( text( startLine ).trimmed().isEmpty() );
912 const bool endLineEmpty = ( text( endLine ).trimmed().isEmpty() );
913
914 for ( int line = startLine; line <= endLine; line++ )
915 {
916 const QString stripped = text( line ).trimmed();
917
918 // Empty line
919 if ( stripped.isEmpty() )
920 {
921 continue;
922 }
923
924 if ( !allCommented )
925 {
926 insertAt( commentPrefix + ' ', line, minIndentation );
927 delta = -( prefixLength + 1 );
928 }
929 else
930 {
931 if ( !stripped.startsWith( commentPrefix ) )
932 {
933 continue;
934 }
935 if ( stripped.startsWith( commentPrefix + ' ' ) )
936 {
937 delta = prefixLength + 1;
938 }
939 else
940 {
941 delta = prefixLength;
942 }
943 setSelection( line, indentation( line ), line, indentation( line ) + delta );
944 removeSelectedText();
945 }
946 }
947
948 endUndoAction();
949 setSelection( startLine, startPos - ( startLineEmpty ? 0 : delta ), endLine, endPos - ( endLineEmpty ? 0 : delta ) );
950}
951
953{
954 // A zero width would make setScrollWidth crash
955 long maxWidth = 10;
956
957 // Get the number of lines
958 int lineCount = lines();
959
960 // Loop through all the lines to get the longest one
961 for ( int line = 0; line < lineCount; line++ )
962 {
963 // Get the linear position at the end of the current line
964 const long endLine = SendScintilla( SCI_GETLINEENDPOSITION, line );
965 // Get the x coordinates of the end of the line
966 const long x = SendScintilla( SCI_POINTXFROMPOSITION, 0, endLine );
967 maxWidth = std::max( maxWidth, x );
968 }
969
970 // Use the longest line width as the new scroll width
971 setScrollWidth( static_cast<int>( maxWidth ) );
972}
973
974void QgsCodeEditor::setText( const QString &text )
975{
976 disconnect( this, &QgsCodeEditor::textChanged, mLastEditTimer, qOverload<>( &QTimer::start ) );
977 QsciScintilla::setText( text );
978 connect( this, &QgsCodeEditor::textChanged, mLastEditTimer, qOverload<>( &QTimer::start ) );
979 onLastEditTimeout();
981}
982
984{
985 return mLastEditTimer->interval();
986}
987
989{
990 mLastEditTimer->setInterval( timeout );
991}
992
993
994QStringList QgsCodeEditor::history() const
995{
996 return mHistory;
997}
998
999void QgsCodeEditor::runCommand( const QString &command, bool skipHistory )
1000{
1001 if ( !skipHistory )
1002 {
1003 updateHistory( { command } );
1006 }
1007
1008 if ( mInterpreter )
1009 mInterpreter->exec( command );
1010
1011 clear();
1013}
1014
1016{
1017 mHistory.clear();
1018 readHistoryFile();
1019 syncSoftHistory();
1020
1021 emit sessionHistoryCleared();
1022}
1023
1025{
1026 mHistory.clear();
1027
1028 if ( !mHistoryFilePath.isEmpty() && QFile::exists( mHistoryFilePath ) )
1029 {
1030 QFile file( mHistoryFilePath );
1031 if ( !file.open( QFile::WriteOnly | QFile::Truncate ) )
1032 {
1033 QgsDebugError( u"Could not truncate %1"_s.arg( mHistoryFilePath ) );
1034 }
1035 }
1036
1038}
1039
1041{
1042 if ( mHistoryFilePath.isEmpty() )
1043 return false;
1044
1045 QFile f( mHistoryFilePath );
1046 if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
1047 {
1048 return false;
1049 }
1050
1051 QTextStream ts( &f );
1052 for ( const QString &command : std::as_const( mHistory ) )
1053 {
1054 ts << command + '\n';
1055 }
1056 return true;
1057}
1058
1060{
1061 if ( mSoftHistoryIndex < mSoftHistory.length() - 1 && !mSoftHistory.isEmpty() )
1062 {
1063 mSoftHistoryIndex += 1;
1064 setText( mSoftHistory[mSoftHistoryIndex] );
1066 }
1067}
1068
1070{
1071 if ( mSoftHistoryIndex > 0 && !mSoftHistory.empty() )
1072 {
1073 mSoftHistoryIndex -= 1;
1074 setText( mSoftHistory[mSoftHistoryIndex] );
1076 }
1077}
1078
1080{
1081 QgsCodeEditorHistoryDialog *dialog = new QgsCodeEditorHistoryDialog( this, this );
1082 dialog->setAttribute( Qt::WA_DeleteOnClose );
1083
1084 dialog->show();
1085 dialog->activateWindow();
1086}
1087
1089{
1090 // remove item from the command history (just for the current session)
1091 mHistory.removeAt( index );
1092 mSoftHistory.removeAt( index );
1093 if ( index < mSoftHistoryIndex )
1094 {
1095 mSoftHistoryIndex -= 1;
1096 if ( mSoftHistoryIndex < 0 )
1097 mSoftHistoryIndex = mSoftHistory.length() - 1;
1098 }
1099}
1100
1101void QgsCodeEditor::insertText( const QString &text )
1102{
1103 // Insert the text or replace selected text
1104 if ( hasSelectedText() )
1105 {
1106 replaceSelectedText( text );
1107 }
1108 else
1109 {
1110 int line, index;
1111 getCursorPosition( &line, &index );
1112 insertAt( text, line, index );
1113 setCursorPosition( line, index + text.length() );
1114 }
1115}
1116
1118{
1119 bool useDefault = QgsApplication::themeName() == "default"_L1;
1120 if ( !useDefault )
1121 {
1122 QFileInfo info( QgsApplication::instance()->applicationThemeRegistry()->themeFolder( QgsApplication::themeName() ) + "/qscintilla.ini" );
1123 useDefault = !info.exists();
1124 }
1125
1126 if ( theme.isEmpty() && useDefault )
1127 {
1128 // if using default theme, take certain colors from the palette
1129 const QPalette pal = qApp->palette();
1130
1131 switch ( role )
1132 {
1134 return pal.color( QPalette::Highlight );
1136 return pal.color( QPalette::HighlightedText );
1137 default:
1138 break;
1139 }
1140 }
1141 else if ( theme.isEmpty() )
1142 {
1143 // non default theme (e.g. Blend of Gray). Take colors from theme ini file...
1144 const QSettings ini( QgsApplication::instance()->applicationThemeRegistry()->themeFolder( QgsApplication::themeName() ) + "/qscintilla.ini", QSettings::IniFormat );
1145
1146 static const QMap<QgsCodeEditorColorScheme::ColorRole, QString> sColorRoleToIniKey {
1147 { QgsCodeEditorColorScheme::ColorRole::Default, u"python/defaultFontColor"_s },
1148 { QgsCodeEditorColorScheme::ColorRole::Keyword, u"python/keywordFontColor"_s },
1149 { QgsCodeEditorColorScheme::ColorRole::Class, u"python/classFontColor"_s },
1150 { QgsCodeEditorColorScheme::ColorRole::Method, u"python/methodFontColor"_s },
1151 { QgsCodeEditorColorScheme::ColorRole::Decoration, u"python/decoratorFontColor"_s },
1152 { QgsCodeEditorColorScheme::ColorRole::Number, u"python/numberFontColor"_s },
1153 { QgsCodeEditorColorScheme::ColorRole::Comment, u"python/commentFontColor"_s },
1154 { QgsCodeEditorColorScheme::ColorRole::CommentLine, u"sql/commentLineFontColor"_s },
1155 { QgsCodeEditorColorScheme::ColorRole::CommentBlock, u"python/commentBlockFontColor"_s },
1156 { QgsCodeEditorColorScheme::ColorRole::Background, u"python/paperBackgroundColor"_s },
1157 { QgsCodeEditorColorScheme::ColorRole::Cursor, u"cursorColor"_s },
1158 { QgsCodeEditorColorScheme::ColorRole::CaretLine, u"caretLineColor"_s },
1159 { QgsCodeEditorColorScheme::ColorRole::Operator, u"sql/operatorFontColor"_s },
1160 { QgsCodeEditorColorScheme::ColorRole::QuotedOperator, u"sql/QuotedOperatorFontColor"_s },
1161 { QgsCodeEditorColorScheme::ColorRole::Identifier, u"sql/identifierFontColor"_s },
1162 { QgsCodeEditorColorScheme::ColorRole::QuotedIdentifier, u"sql/QuotedIdentifierFontColor"_s },
1163 { QgsCodeEditorColorScheme::ColorRole::Tag, u"html/tagFontColor"_s },
1164 { QgsCodeEditorColorScheme::ColorRole::UnknownTag, u"html/unknownTagFontColor"_s },
1165 { QgsCodeEditorColorScheme::ColorRole::SingleQuote, u"sql/singleQuoteFontColor"_s },
1166 { QgsCodeEditorColorScheme::ColorRole::DoubleQuote, u"sql/doubleQuoteFontColor"_s },
1167 { QgsCodeEditorColorScheme::ColorRole::TripleSingleQuote, u"python/tripleSingleQuoteFontColor"_s },
1168 { QgsCodeEditorColorScheme::ColorRole::TripleDoubleQuote, u"python/tripleDoubleQuoteFontColor"_s },
1169 { QgsCodeEditorColorScheme::ColorRole::MarginBackground, u"marginBackgroundColor"_s },
1170 { QgsCodeEditorColorScheme::ColorRole::MarginForeground, u"marginForegroundColor"_s },
1171 { QgsCodeEditorColorScheme::ColorRole::SelectionBackground, u"selectionBackgroundColor"_s },
1172 { QgsCodeEditorColorScheme::ColorRole::SelectionForeground, u"selectionForegroundColor"_s },
1173 { QgsCodeEditorColorScheme::ColorRole::MatchedBraceBackground, u"matchedBraceBackground"_s },
1177 { QgsCodeEditorColorScheme::ColorRole::Error, u"stderrFontColor"_s },
1182 { QgsCodeEditorColorScheme::ColorRole::SearchMatchBackground, u"searchMatchBackground"_s },
1183 };
1184
1185 const QgsCodeEditorColorScheme defaultScheme = QgsGui::codeEditorColorSchemeRegistry()->scheme( u"default"_s );
1186 return QgsSymbolLayerUtils::decodeColor( ini.value( sColorRoleToIniKey.value( role ), defaultScheme.color( role ).name() ).toString() );
1187 }
1188
1189 const QgsCodeEditorColorScheme scheme = QgsGui::codeEditorColorSchemeRegistry()->scheme( theme.isEmpty() ? u"default"_s : theme );
1190 return scheme.color( role );
1191}
1192
1194{
1195 const QgsSettings settings;
1196 if ( !settings.value( u"codeEditor/overrideColors"_s, false, QgsSettings::Gui ).toBool() )
1197 {
1198 const QString theme = settings.value( u"codeEditor/colorScheme"_s, QString(), QgsSettings::Gui ).toString();
1199 return defaultColor( role, theme );
1200 }
1201 else
1202 {
1203 const QString color = settings.value( u"codeEditor/%1"_s.arg( sColorRoleToSettingsKey.value( role ) ), QString(), QgsSettings::Gui ).toString();
1204 return color.isEmpty() ? defaultColor( role ) : QgsSymbolLayerUtils::decodeColor( color );
1205 }
1206}
1207
1209{
1210 QgsSettings settings;
1211 if ( color.isValid() )
1212 {
1213 settings.setValue( u"codeEditor/%1"_s.arg( sColorRoleToSettingsKey.value( role ) ), color.name(), QgsSettings::Gui );
1214 }
1215 else
1216 {
1217 settings.remove( u"codeEditor/%1"_s.arg( sColorRoleToSettingsKey.value( role ) ), QgsSettings::Gui );
1218 }
1219}
1220
1221// Settings for font and fontsize
1222bool QgsCodeEditor::isFixedPitch( const QFont &font )
1223{
1224 return font.fixedPitch();
1225}
1226
1228{
1229 QFont font = QFontDatabase::systemFont( QFontDatabase::FixedFont );
1230
1231 const QString fontFamily = settingFontFamily->value();
1232 if ( !fontFamily.isEmpty() )
1233 QgsFontUtils::setFontFamily( font, fontFamily );
1234
1235 const int fontSize = settingFontSize->value();
1236
1237#ifdef Q_OS_MAC
1238 if ( fontSize > 0 )
1239 font.setPointSize( fontSize );
1240 else
1241 {
1242 // The font size gotten from getMonospaceFont() is too small on Mac
1243 font.setPointSize( QLabel().font().pointSize() );
1244 }
1245#else
1246 if ( fontSize > 0 )
1247 font.setPointSize( fontSize );
1248 else
1249 {
1250 const QgsSettings settings;
1251 const int fontSize = settings.value( u"qgis/stylesheet/fontPointSize"_s, 10 ).toInt();
1252 font.setPointSize( fontSize );
1253 }
1254#endif
1255 font.setBold( false );
1256
1257 return font;
1258}
1259
1260void QgsCodeEditor::setCustomAppearance( const QString &scheme, const QMap<QgsCodeEditorColorScheme::ColorRole, QColor> &customColors, const QString &fontFamily, int fontSize )
1261{
1262 mUseDefaultSettings = false;
1263 mOverrideColors = !customColors.isEmpty();
1264 mColorScheme = scheme;
1265 mCustomColors = customColors;
1266 mFontFamily = fontFamily;
1267 mFontSize = fontSize;
1268
1269 setSciWidget();
1271}
1272
1273void QgsCodeEditor::addWarning( const int lineNumber, const QString &warning )
1274{
1275 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::ErrorIndicators ), "000" );
1276 markerAdd( lineNumber, MARKER_NUMBER );
1277 QFont font = lexerFont();
1278 font.setItalic( true );
1279 const QsciStyle styleAnn = QsciStyle( -1, u"Annotation"_s, lexerColor( QgsCodeEditorColorScheme::ColorRole::Error ), lexerColor( QgsCodeEditorColorScheme::ColorRole::ErrorBackground ), font, true );
1280 annotate( lineNumber, warning, styleAnn );
1281 mWarningLines.push_back( lineNumber );
1282}
1283
1285{
1286 for ( const int line : mWarningLines )
1287 {
1288 markerDelete( line );
1289 clearAnnotations( line );
1290 }
1291 setMarginWidth( static_cast<int>( QgsCodeEditor::MarginRole::ErrorIndicators ), 0 );
1292 mWarningLines.clear();
1293}
1294
1296{
1297 int line = 0;
1298 int index = 0;
1299 getCursorPosition( &line, &index );
1300 return line == lines() - 1;
1301}
1302
1303void QgsCodeEditor::setHistoryFilePath( const QString &path )
1304{
1305 mHistoryFilePath = path;
1306 readHistoryFile();
1307}
1308
1310{
1311 setCursorPosition( 0, 0 );
1312 ensureCursorVisible();
1313 ensureLineVisible( 0 );
1314
1315 if ( mMode == QgsCodeEditor::Mode::CommandInput )
1316 updatePrompt();
1317}
1318
1320{
1321 const int endLine = lines() - 1;
1322 const int endLineLength = lineLength( endLine );
1323 setCursorPosition( endLine, endLineLength );
1324 ensureCursorVisible();
1325 ensureLineVisible( endLine );
1326
1327 if ( mMode == QgsCodeEditor::Mode::CommandInput )
1328 updatePrompt();
1329}
1330
1332{
1333 return static_cast<int>( SendScintilla( SCI_GETCURRENTPOS ) );
1334}
1335
1337{
1338 int line, index;
1339 lineIndexFromPosition( linearIndex, &line, &index );
1340 setCursorPosition( line, index );
1341}
1342
1344{
1345 int startLine, startIndex, _;
1346 getSelection( &startLine, &startIndex, &_, &_ );
1347 if ( startLine == -1 )
1348 {
1349 return linearPosition();
1350 }
1351 return positionFromLineIndex( startLine, startIndex );
1352}
1353
1355{
1356 int endLine, endIndex, _;
1357 getSelection( &_, &_, &endLine, &endIndex );
1358 if ( endLine == -1 )
1359 {
1360 return linearPosition();
1361 }
1362 return positionFromLineIndex( endLine, endIndex );
1363}
1364
1365void QgsCodeEditor::setLinearSelection( int start, int end )
1366{
1367 int startLine, startIndex, endLine, endIndex;
1368 lineIndexFromPosition( start, &startLine, &startIndex );
1369 lineIndexFromPosition( end, &endLine, &endIndex );
1370 setSelection( startLine, startIndex, endLine, endIndex );
1371}
1372
1374
1375int QgsCodeInterpreter::exec( const QString &command )
1376{
1377 mState = execCommandImpl( command );
1378 return mState;
1379}
1380
1381
1383{
1384 // If wrapping is disabled, return -1
1385 if ( wrapMode() == WrapNone )
1386 {
1387 return -1;
1388 }
1389 // Get the current line
1390 if ( line == -1 )
1391 {
1392 int _index;
1393 lineIndexFromPosition( linearPosition(), &line, &_index );
1394 }
1395
1396 // If line isn't wrapped, return -1
1397 if ( SendScintilla( SCI_WRAPCOUNT, line ) <= 1 )
1398 {
1399 return -1;
1400 }
1401
1402 // Get the linear position at the end of the current line
1403 const long endLine = SendScintilla( SCI_GETLINEENDPOSITION, line );
1404 // Get the y coordinates of the start of the last wrapped line
1405 const long y = SendScintilla( SCI_POINTYFROMPOSITION, 0, endLine );
1406 // Return the linear position of the start of the last wrapped line
1407 return static_cast<int>( SendScintilla( SCI_POSITIONFROMPOINT, 0, y ) );
1408}
1409
1410
1411// Adapted from QsciScintilla source code (qsciscintilla.cpp) to handle line wrap
1413{
1414 if ( callTipsStyle() == CallTipsNone || lexer() == nullptr )
1415 {
1416 return;
1417 }
1418
1419 QsciAbstractAPIs *apis = lexer()->apis();
1420
1421 if ( !apis )
1422 return;
1423
1424 int pos, commas = 0;
1425 bool found = false;
1426 char ch;
1427
1428 pos = linearPosition();
1429
1430 // Move backwards through the line looking for the start of the current
1431 // call tip and working out which argument it is.
1432 while ( ( ch = getCharacter( pos ) ) != '\0' )
1433 {
1434 if ( ch == ',' )
1435 ++commas;
1436 else if ( ch == ')' )
1437 {
1438 int depth = 1;
1439
1440 // Ignore everything back to the start of the corresponding
1441 // parenthesis.
1442 while ( ( ch = getCharacter( pos ) ) != '\0' )
1443 {
1444 if ( ch == ')' )
1445 ++depth;
1446 else if ( ch == '(' && --depth == 0 )
1447 break;
1448 }
1449 }
1450 else if ( ch == '(' )
1451 {
1452 found = true;
1453 break;
1454 }
1455 }
1456
1457 // Cancel any existing call tip.
1458 SendScintilla( SCI_CALLTIPCANCEL );
1459
1460 // Done if there is no new call tip to set.
1461 if ( !found )
1462 return;
1463
1464 int contextStart, lastWordStart;
1465 QStringList context = apiContext( pos, contextStart, lastWordStart );
1466
1467 if ( context.isEmpty() )
1468 return;
1469
1470 // The last word is complete, not partial.
1471 context << QString();
1472
1473 QList<int> ctShifts;
1474 QStringList ctEntries = apis->callTips( context, commas, callTipsStyle(), ctShifts );
1475
1476 int nbEntries = ctEntries.count();
1477
1478 if ( nbEntries == 0 )
1479 return;
1480
1481 const int maxNumberOfCallTips = callTipsVisible();
1482
1483 // Clip to at most maxNumberOfCallTips entries.
1484 if ( maxNumberOfCallTips > 0 && maxNumberOfCallTips < nbEntries )
1485 {
1486 ctEntries = ctEntries.mid( 0, maxNumberOfCallTips );
1487 nbEntries = maxNumberOfCallTips;
1488 }
1489
1490 int shift;
1491 QString ct;
1492
1493 int nbShifts = ctShifts.count();
1494
1495 if ( maxNumberOfCallTips < 0 && nbEntries > 1 )
1496 {
1497 shift = ( nbShifts > 0 ? ctShifts.first() : 0 );
1498 ct = ctEntries[0];
1499 ct.prepend( '\002' );
1500 }
1501 else
1502 {
1503 if ( nbShifts > nbEntries )
1504 nbShifts = nbEntries;
1505
1506 // Find the biggest shift.
1507 shift = 0;
1508
1509 for ( int i = 0; i < nbShifts; ++i )
1510 {
1511 int sh = ctShifts[i];
1512
1513 if ( shift < sh )
1514 shift = sh;
1515 }
1516
1517 ct = ctEntries.join( "\n" );
1518 }
1519
1520 QByteArray ctBa = ct.toLatin1();
1521 const char *cts = ctBa.data();
1522
1523 const int currentWrapPosition = wrapPosition();
1524
1525 if ( currentWrapPosition != -1 )
1526 {
1527 SendScintilla( SCI_CALLTIPSHOW, currentWrapPosition, cts );
1528 }
1529 else
1530 {
1531 // Shift the position of the call tip (to take any context into account) but
1532 // don't go before the start of the line.
1533 if ( shift )
1534 {
1535 int ctmin = static_cast<int>( SendScintilla( SCI_POSITIONFROMLINE, SendScintilla( SCI_LINEFROMPOSITION, ct ) ) );
1536 if ( lastWordStart - shift < ctmin )
1537 lastWordStart = ctmin;
1538 }
1539
1540 int line, index;
1541 lineIndexFromPosition( lastWordStart, &line, &index );
1542 SendScintilla( SCI_CALLTIPSHOW, positionFromLineIndex( line, index ), cts );
1543 }
1544
1545 // Done if there is more than one call tip.
1546 if ( nbEntries > 1 )
1547 return;
1548
1549 // Highlight the current argument.
1550 const char *astart;
1551
1552 if ( commas == 0 )
1553 astart = strchr( cts, '(' );
1554 else
1555 for ( astart = strchr( cts, ',' ); astart && --commas > 0; astart = strchr( astart + 1, ',' ) )
1556 ;
1557
1558 if ( !astart )
1559 return;
1560
1561 astart++;
1562 if ( !*astart )
1563 return;
1564
1565 // The end is at the next comma or unmatched closing parenthesis.
1566 const char *aend;
1567 int depth = 0;
1568
1569 for ( aend = astart; *aend; ++aend )
1570 {
1571 char ch = *aend;
1572
1573 if ( ch == ',' && depth == 0 )
1574 break;
1575 else if ( ch == '(' )
1576 ++depth;
1577 else if ( ch == ')' )
1578 {
1579 if ( depth == 0 )
1580 break;
1581
1582 --depth;
1583 }
1584 }
1585
1586 if ( astart != aend )
1587 SendScintilla( SCI_CALLTIPSETHLT, astart - cts, aend - cts );
1588}
1589
1590
1591// Duplicated from QsciScintilla source code (qsciscintilla.cpp)
1592// Get the "next" character (ie. the one before the current position) in the
1593// current line. The character will be '\0' if there are no more.
1594char QgsCodeEditor::getCharacter( int &pos ) const
1595{
1596 if ( pos <= 0 )
1597 return '\0';
1598
1599 char ch = static_cast<char>( SendScintilla( SCI_GETCHARAT, --pos ) );
1600
1601 // Don't go past the end of the previous line.
1602 if ( ch == '\n' || ch == '\r' )
1603 {
1604 ++pos;
1605 return '\0';
1606 }
1607
1608 return ch;
1609}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:160
@ NoLevel
No level.
Definition qgis.h:165
@ Warning
Warning message.
Definition qgis.h:162
@ Critical
Critical/error message.
Definition qgis.h:163
@ Info
Information message.
Definition qgis.h:161
@ Success
Used for reporting a successful operation.
Definition qgis.h:164
@ CheckSyntax
Language supports syntax checking.
Definition qgis.h:4777
@ Reformat
Language supports automatic code reformatting.
Definition qgis.h:4776
@ ToggleComment
Language supports comment toggling.
Definition qgis.h:4778
ScriptLanguage
Scripting languages.
Definition qgis.h:4752
@ QgisExpression
QGIS expressions.
Definition qgis.h:4754
@ Batch
Windows batch files.
Definition qgis.h:4761
@ JavaScript
JavaScript.
Definition qgis.h:4756
@ Bash
Bash scripts.
Definition qgis.h:4762
@ Unknown
Unknown/other language.
Definition qgis.h:4763
@ Python
Python.
Definition qgis.h:4758
QFlags< ScriptLanguageCapability > ScriptLanguageCapabilities
Script language capabilities.
Definition qgis.h:4787
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 QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
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.
@ SelectionForeground
Selection foreground color.
@ FoldIconForeground
Fold icon foreground color.
@ MatchedBraceBackground
Matched brace background color.
@ SearchMatchBackground
Background color for search matches.
@ SelectionBackground
Selection background color.
@ MatchedBraceForeground
Matched brace foreground color.
@ TripleDoubleQuote
Triple double quote 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.
static const QgsSettingsEntryBool * settingContextHelpHover
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,...
QgsCodeEditor::Mode mode() const
Returns the code editor mode.
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...
int editingTimeoutInterval() const
Returns the timeout (in milliseconds) threshold for the editingTimeout() signal to be emitted after a...
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 const QgsSettingsEntryString * settingFontFamily
Settings entry for code editor font family override.
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.
void callTip() override
void runCommand(const QString &command, bool skipHistory=false)
Runs a command in the editor.
void setText(const QString &text) override
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 setEditingTimeoutInterval(int timeout)
Sets the timeout (in milliseconds) threshold for the editingTimeout() signal to be emitted after an e...
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.
void runPostLexerConfigurationTasks()
Performs tasks which must be run after a lexer has been set for the widget.
bool event(QEvent *event) override
virtual void showMessage(const QString &title, const QString &message, Qgis::MessageLevel level)
Shows a user facing message (eg a warning message).
int 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,...
void helpRequested(const QString &word)
Emitted when documentation was requested for the specified word.
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.
static const QgsSettingsEntryInteger * settingFontSize
Settings entry for code editor font size override.
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.
void adjustScrollWidth()
Adjust the width of the scroll bar to fit the content.
virtual Qgis::ScriptLanguage language() const
Returns the associated scripting language.
QFont lexerFont() const
Returns the font to use in the lexer.
void toggleLineComments(const QString &commentPrefix)
Toggles comment for selected lines with the given comment prefix.
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.
void editingTimeout()
Emitted when either:
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.
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.
static QgsShortcutsManager * shortcutsManager()
Returns the global shortcuts manager, used for managing a QAction and QShortcut sequences.
Definition qgsgui.cpp:139
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:93
static QgsCodeEditorColorSchemeRegistry * codeEditorColorSchemeRegistry()
Returns the global code editor color scheme registry, used for registering the color schemes for QgsC...
Definition qgsgui.cpp:179
A boolean settings entry.
An integer settings entry.
A string settings entry.
Stores settings for use within QGIS.
Definition qgssettings.h:68
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
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.
@ CodeToggleComment
Toggle code comments.
QKeySequence sequenceForCommonAction(CommonAction action) const
Returns the key sequence which is associated with a common action, or an empty sequence if no shortcu...
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:7688
int findMinimalDistanceIndex(const QString &source, const QString &target)
#define QgsDebugError(str)
Definition qgslogger.h:59