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