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