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