Skip to content

Commit 1c79b49

Browse files
authoredJan 6, 2023
[feature][console] Add toggle comment action in the python console (#50341)
Adds a toggle comment action in the Python Console and script editors
1 parent 675933a commit 1c79b49

File tree

9 files changed

+155
-83
lines changed

9 files changed

+155
-83
lines changed
 

‎python/console/console.py

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ def __init__(self, parent=None):
110110
super().__init__(parent)
111111
self.setObjectName("PythonConsole")
112112
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
113-
# self.setAllowedAreas(Qt.BottomDockWidgetArea)
114113

115114
self.console = PythonConsoleWidget(self)
116115
QgsGui.instance().optionsChanged.connect(self.console.updateSettings)
@@ -167,13 +166,9 @@ def __init__(self, parent=None):
167166
self.splitter.addWidget(self.shellOutWidget)
168167
self.splitter.addWidget(self.shell)
169168

170-
# self.splitterEditor.addWidget(self.tabEditorWidget)
171-
172169
self.splitterObj = QSplitter(self.splitterEditor)
173170
self.splitterObj.setHandleWidth(3)
174171
self.splitterObj.setOrientation(Qt.Horizontal)
175-
# self.splitterObj.setSizes([0, 0])
176-
# self.splitterObj.setStretchFactor(0, 1)
177172

178173
self.widgetEditor = QWidget(self.splitterObj)
179174
self.widgetFind = QWidget(self)
@@ -185,10 +180,6 @@ def __init__(self, parent=None):
185180
self.listClassMethod.setColumnHidden(1, True)
186181
self.listClassMethod.setAlternatingRowColors(True)
187182

188-
# self.splitterEditor.addWidget(self.widgetEditor)
189-
# self.splitterObj.addWidget(self.listClassMethod)
190-
# self.splitterObj.addWidget(self.widgetEditor)
191-
192183
# Hide side editor on start up
193184
self.splitterObj.hide()
194185
self.listClassMethod.hide()
@@ -286,26 +277,18 @@ def __init__(self, parent=None):
286277
self.runScriptEditorButton.setIconVisibleInMenu(True)
287278
self.runScriptEditorButton.setToolTip(runScriptEditorBt)
288279
self.runScriptEditorButton.setText(runScriptEditorBt)
289-
# Action Run Script (subprocess)
290-
commentEditorBt = QCoreApplication.translate("PythonConsole", "Comment")
291-
self.commentEditorButton = QAction(self)
292-
self.commentEditorButton.setCheckable(False)
293-
self.commentEditorButton.setEnabled(True)
294-
self.commentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg"))
295-
self.commentEditorButton.setMenuRole(QAction.PreferencesRole)
296-
self.commentEditorButton.setIconVisibleInMenu(True)
297-
self.commentEditorButton.setToolTip(commentEditorBt)
298-
self.commentEditorButton.setText(commentEditorBt)
299-
# Action Run Script (subprocess)
300-
uncommentEditorBt = QCoreApplication.translate("PythonConsole", "Uncomment")
301-
self.uncommentEditorButton = QAction(self)
302-
self.uncommentEditorButton.setCheckable(False)
303-
self.uncommentEditorButton.setEnabled(True)
304-
self.uncommentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconUncommentEditorConsole.svg"))
305-
self.uncommentEditorButton.setMenuRole(QAction.PreferencesRole)
306-
self.uncommentEditorButton.setIconVisibleInMenu(True)
307-
self.uncommentEditorButton.setToolTip(uncommentEditorBt)
308-
self.uncommentEditorButton.setText(uncommentEditorBt)
280+
281+
# Action Toggle comment
282+
toggleText = QCoreApplication.translate("PythonConsole", "Toggle Comment")
283+
self.toggleCommentEditorButton = QAction(self)
284+
self.toggleCommentEditorButton.setCheckable(False)
285+
self.toggleCommentEditorButton.setEnabled(True)
286+
self.toggleCommentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg"))
287+
self.toggleCommentEditorButton.setMenuRole(QAction.PreferencesRole)
288+
self.toggleCommentEditorButton.setIconVisibleInMenu(True)
289+
self.toggleCommentEditorButton.setToolTip(toggleText + " <b>Ctrl+:</b>")
290+
self.toggleCommentEditorButton.setText(toggleText)
291+
309292
# Action for Object browser
310293
objList = QCoreApplication.translate("PythonConsole", "Object Inspector…")
311294
self.objectListButton = QAction(self)
@@ -433,8 +416,7 @@ def __init__(self, parent=None):
433416
self.toolBarEditor.addSeparator()
434417
self.toolBarEditor.addAction(self.findTextButton)
435418
self.toolBarEditor.addSeparator()
436-
self.toolBarEditor.addAction(self.commentEditorButton)
437-
self.toolBarEditor.addAction(self.uncommentEditorButton)
419+
self.toolBarEditor.addAction(self.toggleCommentEditorButton)
438420
self.toolBarEditor.addSeparator()
439421
self.toolBarEditor.addAction(self.objectListButton)
440422

@@ -527,8 +509,7 @@ def __init__(self, parent=None):
527509

528510
self.findTextButton.triggered.connect(self._toggleFind)
529511
self.objectListButton.toggled.connect(self.toggleObjectListWidget)
530-
self.commentEditorButton.triggered.connect(self.commentCode)
531-
self.uncommentEditorButton.triggered.connect(self.uncommentCode)
512+
self.toggleCommentEditorButton.triggered.connect(self.toggleComment)
532513
self.runScriptEditorButton.triggered.connect(self.runScriptEditor)
533514
self.cutEditorButton.triggered.connect(self.cutEditor)
534515
self.copyEditorButton.triggered.connect(self.copyEditor)
@@ -657,11 +638,8 @@ def copyEditor(self):
657638
def runScriptEditor(self):
658639
self.tabEditorWidget.currentWidget().newEditor.runScriptCode()
659640

660-
def commentCode(self):
661-
self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(True)
662-
663-
def uncommentCode(self):
664-
self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(False)
641+
def toggleComment(self):
642+
self.tabEditorWidget.currentWidget().newEditor.toggleComment()
665643

666644
def openScriptFileExtEditor(self):
667645
tabWidget = self.tabEditorWidget.currentWidget()

‎python/console/console_editor.py

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,6 @@ def __init__(self, parent=None):
122122
self.syntaxCheckScut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_4), self)
123123
self.syntaxCheckScut.setContext(Qt.WidgetShortcut)
124124
self.syntaxCheckScut.activated.connect(self.syntaxCheck)
125-
self.commentScut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_3), self)
126-
self.commentScut.setContext(Qt.WidgetShortcut)
127-
self.commentScut.activated.connect(self.parent.pc.commentCode)
128-
self.uncommentScut = QShortcut(QKeySequence(Qt.SHIFT + Qt.CTRL + Qt.Key_3), self)
129-
self.uncommentScut.setContext(Qt.WidgetShortcut)
130-
self.uncommentScut.activated.connect(self.parent.pc.uncommentCode)
131125
self.modificationChanged.connect(self.parent.modified)
132126
self.modificationAttempted.connect(self.fileReadOnly)
133127

@@ -178,11 +172,8 @@ def contextMenuEvent(self, e):
178172
self.selectAll, QKeySequence.SelectAll)
179173
menu.addSeparator()
180174
menu.addAction(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg"),
181-
QCoreApplication.translate("PythonConsole", "Comment"),
182-
self.parent.pc.commentCode, 'Ctrl+3')
183-
menu.addAction(QgsApplication.getThemeIcon("console/iconUncommentEditorConsole.svg"),
184-
QCoreApplication.translate("PythonConsole", "Uncomment"),
185-
self.parent.pc.uncommentCode, 'Shift+Ctrl+3')
175+
QCoreApplication.translate("PythonConsole", "Toggle Comment"),
176+
self.toggleComment, 'Ctrl+:')
186177
menu.addSeparator()
187178
gist_menu = QMenu(self)
188179
gist_menu.setTitle(QCoreApplication.translate("PythonConsole", "Share on GitHub"))
@@ -338,31 +329,6 @@ def toggleFindWidget(self):
338329
else:
339330
self.openFindWidget()
340331

341-
def commentEditorCode(self, commentCheck):
342-
self.beginUndoAction()
343-
if self.hasSelectedText():
344-
startLine, _, endLine, _ = self.getSelection()
345-
for line in range(startLine, endLine + 1):
346-
if commentCheck:
347-
self.insertAt('#', line, 0)
348-
else:
349-
if not self.text(line).strip().startswith('#'):
350-
continue
351-
self.setSelection(line, self.indentation(line),
352-
line, self.indentation(line) + 1)
353-
self.removeSelectedText()
354-
else:
355-
line, pos = self.getCursorPosition()
356-
if commentCheck:
357-
self.insertAt('#', line, 0)
358-
else:
359-
if not self.text(line).strip().startswith('#'):
360-
return
361-
self.setSelection(line, self.indentation(line),
362-
line, self.indentation(line) + 1)
363-
self.removeSelectedText()
364-
self.endUndoAction()
365-
366332
def createTempFile(self):
367333
import tempfile
368334
fd, path = tempfile.mkstemp()
@@ -547,7 +513,7 @@ def keyPressEvent(self, e):
547513
if re.match(ptrn, txt):
548514
self.insert(' import')
549515
self.setCursorPosition(line, pos + 7)
550-
QsciScintilla.keyPressEvent(self, e)
516+
QgsCodeEditorPython.keyPressEvent(self, e)
551517

552518
def focusInEvent(self, e):
553519
pathfile = self.parent.path
@@ -560,7 +526,6 @@ def focusInEvent(self, e):
560526
if pathfile and self.lastModified != QFileInfo(pathfile).lastModified():
561527
self.beginUndoAction()
562528
self.selectAll()
563-
# fileReplaced = self.selectedText()
564529
self.removeSelectedText()
565530
file = open(pathfile, "r")
566531
fileLines = file.readlines()
@@ -658,7 +623,6 @@ def save(self, fileName=None):
658623
if overwrite:
659624
try:
660625
permis = os.stat(path).st_mode
661-
# self.newEditor.lastModified = QFileInfo(path).lastModified()
662626
os.chmod(path, permis)
663627
except:
664628
raise

‎python/gui/auto_generated/codeeditors/qgscodeeditorpython.sip.in

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,22 @@ Loads a ``script`` file.
6262
Searches the selected text in the official PyQGIS online documentation.
6363

6464
.. versionadded:: 3.16
65+
%End
66+
67+
void toggleComment();
68+
%Docstring
69+
Toggle comment for the selected text.
70+
71+
.. versionadded:: 3.30
6572
%End
6673

6774
protected:
6875

6976
virtual void initializeLexer();
7077

7178

79+
virtual void keyPressEvent( QKeyEvent *event );
80+
7281
protected slots:
7382

7483
void autoComplete();

‎python/plugins/processing/script/ScriptEditorDialog.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ def __init__(self, filePath=None, parent=None):
9292
QgsApplication.getThemeIcon('/mActionIncreaseFont.svg'))
9393
self.actionDecreaseFontSize.setIcon(
9494
QgsApplication.getThemeIcon('/mActionDecreaseFont.svg'))
95+
self.actionToggleComment.setIcon(
96+
QgsApplication.getThemeIcon('console/iconCommentEditorConsole.svg'))
9597

9698
# Connect signals and slots
9799
self.actionOpenScript.triggered.connect(self.openScript)
@@ -106,6 +108,7 @@ def __init__(self, filePath=None, parent=None):
106108
self.actionFindReplace.toggled.connect(self.toggleSearchBox)
107109
self.actionIncreaseFontSize.triggered.connect(self.editor.zoomIn)
108110
self.actionDecreaseFontSize.triggered.connect(self.editor.zoomOut)
111+
self.actionToggleComment.triggered.connect(self.editor.toggleComment)
109112
self.editor.textChanged.connect(lambda: self.setHasChanged(True))
110113

111114
self.leFindText.returnPressed.connect(self.find)

‎python/plugins/processing/ui/DlgScriptEditor.ui

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>721</width>
9+
<width>600</width>
1010
<height>578</height>
1111
</rect>
1212
</property>
@@ -120,6 +120,8 @@
120120
<addaction name="separator"/>
121121
<addaction name="actionIncreaseFontSize"/>
122122
<addaction name="actionDecreaseFontSize"/>
123+
<addaction name="separator"/>
124+
<addaction name="actionToggleComment"/>
123125
</widget>
124126
<action name="actionOpenScript">
125127
<property name="text">
@@ -240,11 +242,16 @@
240242
<property name="checkable">
241243
<bool>true</bool>
242244
</property>
245+
<property name="text">
246+
<string>Find &amp;&amp; &amp;Replace</string>
247+
</property>
243248
<property name="shortcut">
244249
<string>Ctrl+F</string>
245250
</property>
251+
</action>
252+
<action name="actionToggleComment">
246253
<property name="text">
247-
<string>Find &amp;&amp; &amp;Replace</string>
254+
<string>Toggle Comment</string>
248255
</property>
249256
</action>
250257
</widget>

‎src/app/maptools/qgsmaptoolshaperegularpolygonabstract.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
#include "qgisapp.h"
2222
#include "qgsmaptoolcapture.h"
2323

24-
QgsMapToolShapeRegularPolygonAbstract::QgsMapToolShapeRegularPolygonAbstract(const QString &id, QgsMapToolCapture *parentTool )
24+
QgsMapToolShapeRegularPolygonAbstract::QgsMapToolShapeRegularPolygonAbstract( const QString &id, QgsMapToolCapture *parentTool )
2525
: QgsMapToolShapeAbstract( id, parentTool )
2626
{
2727
}

‎src/app/maptools/qgsmaptoolshaperegularpolygonabstract.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class APP_EXPORT QgsMapToolShapeRegularPolygonAbstract: public QgsMapToolShapeAb
2828
Q_OBJECT
2929

3030
public:
31-
QgsMapToolShapeRegularPolygonAbstract(const QString &id, QgsMapToolCapture *parentTool);
31+
QgsMapToolShapeRegularPolygonAbstract( const QString &id, QgsMapToolCapture *parentTool );
3232

3333
void clean() override;
3434

‎src/gui/codeeditors/qgscodeeditorpython.cpp

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <QTextStream>
3030
#include <Qsci/qscilexerpython.h>
3131
#include <QDesktopServices>
32+
#include <QKeyEvent>
3233

3334
QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList<QString> &filenames, Mode mode )
3435
: QgsCodeEditor( parent,
@@ -185,6 +186,19 @@ void QgsCodeEditorPython::initializeLexer()
185186
runPostLexerConfigurationTasks();
186187
}
187188

189+
void QgsCodeEditorPython::keyPressEvent( QKeyEvent *event )
190+
{
191+
const bool ctrlModifier = event->modifiers() & Qt::ControlModifier;
192+
193+
if ( ctrlModifier && event->key() == Qt::Key_Colon )
194+
{
195+
event->accept();
196+
toggleComment();
197+
return;
198+
}
199+
return QgsCodeEditor::keyPressEvent( event );
200+
}
201+
188202
void QgsCodeEditorPython::autoComplete()
189203
{
190204
switch ( autoCompletionSource() )
@@ -242,6 +256,94 @@ void QgsCodeEditorPython::searchSelectedTextInPyQGISDocs()
242256
QDesktopServices::openUrl( QUrl( QStringLiteral( "https://qgis.org/pyqgis/%1/search.html?q=%2" ).arg( version, text ) ) );
243257
}
244258

259+
void QgsCodeEditorPython::toggleComment()
260+
{
261+
if ( isReadOnly() )
262+
{
263+
return;
264+
}
265+
266+
beginUndoAction();
267+
int startLine, startPos, endLine, endPos;
268+
if ( hasSelectedText() )
269+
{
270+
getSelection( &startLine, &startPos, &endLine, &endPos );
271+
}
272+
else
273+
{
274+
getCursorPosition( &startLine, &startPos );
275+
endLine = startLine;
276+
endPos = startPos;
277+
}
278+
279+
// Check comment state and minimum indentation for each selected line
280+
bool allEmpty = true;
281+
bool allCommented = true;
282+
int minIndentation = -1;
283+
for ( int line = startLine; line <= endLine; line++ )
284+
{
285+
const QString stripped = text( line ).trimmed();
286+
if ( !stripped.isEmpty() )
287+
{
288+
allEmpty = false;
289+
if ( !stripped.startsWith( '#' ) )
290+
{
291+
allCommented = false;
292+
}
293+
if ( minIndentation == -1 || minIndentation > indentation( line ) )
294+
{
295+
minIndentation = indentation( line );
296+
}
297+
}
298+
}
299+
300+
// Special case, only empty lines
301+
if ( allEmpty )
302+
{
303+
return;
304+
}
305+
306+
// Selection shift to keep the same selected text after a # is added/removed
307+
int delta = 0;
308+
309+
for ( int line = startLine; line <= endLine; line++ )
310+
{
311+
const QString stripped = text( line ).trimmed();
312+
313+
// Empty line
314+
if ( stripped.isEmpty() )
315+
{
316+
continue;
317+
}
318+
319+
if ( !allCommented )
320+
{
321+
insertAt( QStringLiteral( "# " ), line, minIndentation );
322+
delta = -2;
323+
}
324+
else
325+
{
326+
if ( !stripped.startsWith( '#' ) )
327+
{
328+
continue;
329+
}
330+
if ( stripped.startsWith( QLatin1String( "# " ) ) )
331+
{
332+
delta = 2;
333+
}
334+
else
335+
{
336+
delta = 1;
337+
}
338+
setSelection( line, indentation( line ), line, indentation( line ) + delta );
339+
removeSelectedText();
340+
}
341+
}
342+
343+
endUndoAction();
344+
setSelection( startLine, startPos - delta, endLine, endPos - delta );
345+
}
346+
245347
///@cond PRIVATE
246348
//
247349
// QgsQsciLexerPython
@@ -256,9 +358,9 @@ const char *QgsQsciLexerPython::keywords( int set ) const
256358
{
257359
if ( set == 1 )
258360
{
259-
return "True False and as assert break class continue def del elif else except exec "
361+
return "True False and as assert break class continue def del elif else except "
260362
"finally for from global if import in is lambda None not or pass "
261-
"print raise return try while with yield";
363+
"raise return try while with yield async await nonlocal";
262364
}
263365

264366
return QsciLexerPython::keywords( set );

‎src/gui/codeeditors/qgscodeeditorpython.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,19 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor
8484
*/
8585
void searchSelectedTextInPyQGISDocs();
8686

87+
/**
88+
* Toggle comment for the selected text.
89+
*
90+
* \since QGIS 3.30
91+
*/
92+
void toggleComment();
93+
8794
protected:
8895

8996
void initializeLexer() override;
9097

98+
virtual void keyPressEvent( QKeyEvent *event ) override;
99+
91100
protected slots:
92101

93102
/**

0 commit comments

Comments
 (0)
Please sign in to comment.