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