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