[feature][console] Add toggle comment action in the python console (#50341)

Adds a toggle comment action in the Python Console and script editors
This commit is contained in:
Yoann Quenach de Quivillic 2023-01-07 00:27:08 +01:00 committed by GitHub
parent 675933aa1e
commit 1c79b4927d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 155 additions and 83 deletions

View File

@ -110,7 +110,6 @@ class PythonConsole(QgsDockWidget):
super().__init__(parent)
self.setObjectName("PythonConsole")
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
# self.setAllowedAreas(Qt.BottomDockWidgetArea)
self.console = PythonConsoleWidget(self)
QgsGui.instance().optionsChanged.connect(self.console.updateSettings)
@ -167,13 +166,9 @@ class PythonConsoleWidget(QWidget):
self.splitter.addWidget(self.shellOutWidget)
self.splitter.addWidget(self.shell)
# self.splitterEditor.addWidget(self.tabEditorWidget)
self.splitterObj = QSplitter(self.splitterEditor)
self.splitterObj.setHandleWidth(3)
self.splitterObj.setOrientation(Qt.Horizontal)
# self.splitterObj.setSizes([0, 0])
# self.splitterObj.setStretchFactor(0, 1)
self.widgetEditor = QWidget(self.splitterObj)
self.widgetFind = QWidget(self)
@ -185,10 +180,6 @@ class PythonConsoleWidget(QWidget):
self.listClassMethod.setColumnHidden(1, True)
self.listClassMethod.setAlternatingRowColors(True)
# self.splitterEditor.addWidget(self.widgetEditor)
# self.splitterObj.addWidget(self.listClassMethod)
# self.splitterObj.addWidget(self.widgetEditor)
# Hide side editor on start up
self.splitterObj.hide()
self.listClassMethod.hide()
@ -286,26 +277,18 @@ class PythonConsoleWidget(QWidget):
self.runScriptEditorButton.setIconVisibleInMenu(True)
self.runScriptEditorButton.setToolTip(runScriptEditorBt)
self.runScriptEditorButton.setText(runScriptEditorBt)
# Action Run Script (subprocess)
commentEditorBt = QCoreApplication.translate("PythonConsole", "Comment")
self.commentEditorButton = QAction(self)
self.commentEditorButton.setCheckable(False)
self.commentEditorButton.setEnabled(True)
self.commentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg"))
self.commentEditorButton.setMenuRole(QAction.PreferencesRole)
self.commentEditorButton.setIconVisibleInMenu(True)
self.commentEditorButton.setToolTip(commentEditorBt)
self.commentEditorButton.setText(commentEditorBt)
# Action Run Script (subprocess)
uncommentEditorBt = QCoreApplication.translate("PythonConsole", "Uncomment")
self.uncommentEditorButton = QAction(self)
self.uncommentEditorButton.setCheckable(False)
self.uncommentEditorButton.setEnabled(True)
self.uncommentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconUncommentEditorConsole.svg"))
self.uncommentEditorButton.setMenuRole(QAction.PreferencesRole)
self.uncommentEditorButton.setIconVisibleInMenu(True)
self.uncommentEditorButton.setToolTip(uncommentEditorBt)
self.uncommentEditorButton.setText(uncommentEditorBt)
# Action Toggle comment
toggleText = QCoreApplication.translate("PythonConsole", "Toggle Comment")
self.toggleCommentEditorButton = QAction(self)
self.toggleCommentEditorButton.setCheckable(False)
self.toggleCommentEditorButton.setEnabled(True)
self.toggleCommentEditorButton.setIcon(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg"))
self.toggleCommentEditorButton.setMenuRole(QAction.PreferencesRole)
self.toggleCommentEditorButton.setIconVisibleInMenu(True)
self.toggleCommentEditorButton.setToolTip(toggleText + " <b>Ctrl+:</b>")
self.toggleCommentEditorButton.setText(toggleText)
# Action for Object browser
objList = QCoreApplication.translate("PythonConsole", "Object Inspector…")
self.objectListButton = QAction(self)
@ -433,8 +416,7 @@ class PythonConsoleWidget(QWidget):
self.toolBarEditor.addSeparator()
self.toolBarEditor.addAction(self.findTextButton)
self.toolBarEditor.addSeparator()
self.toolBarEditor.addAction(self.commentEditorButton)
self.toolBarEditor.addAction(self.uncommentEditorButton)
self.toolBarEditor.addAction(self.toggleCommentEditorButton)
self.toolBarEditor.addSeparator()
self.toolBarEditor.addAction(self.objectListButton)
@ -527,8 +509,7 @@ class PythonConsoleWidget(QWidget):
self.findTextButton.triggered.connect(self._toggleFind)
self.objectListButton.toggled.connect(self.toggleObjectListWidget)
self.commentEditorButton.triggered.connect(self.commentCode)
self.uncommentEditorButton.triggered.connect(self.uncommentCode)
self.toggleCommentEditorButton.triggered.connect(self.toggleComment)
self.runScriptEditorButton.triggered.connect(self.runScriptEditor)
self.cutEditorButton.triggered.connect(self.cutEditor)
self.copyEditorButton.triggered.connect(self.copyEditor)
@ -657,11 +638,8 @@ class PythonConsoleWidget(QWidget):
def runScriptEditor(self):
self.tabEditorWidget.currentWidget().newEditor.runScriptCode()
def commentCode(self):
self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(True)
def uncommentCode(self):
self.tabEditorWidget.currentWidget().newEditor.commentEditorCode(False)
def toggleComment(self):
self.tabEditorWidget.currentWidget().newEditor.toggleComment()
def openScriptFileExtEditor(self):
tabWidget = self.tabEditorWidget.currentWidget()

View File

@ -122,12 +122,6 @@ class Editor(QgsCodeEditorPython):
self.syntaxCheckScut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_4), self)
self.syntaxCheckScut.setContext(Qt.WidgetShortcut)
self.syntaxCheckScut.activated.connect(self.syntaxCheck)
self.commentScut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_3), self)
self.commentScut.setContext(Qt.WidgetShortcut)
self.commentScut.activated.connect(self.parent.pc.commentCode)
self.uncommentScut = QShortcut(QKeySequence(Qt.SHIFT + Qt.CTRL + Qt.Key_3), self)
self.uncommentScut.setContext(Qt.WidgetShortcut)
self.uncommentScut.activated.connect(self.parent.pc.uncommentCode)
self.modificationChanged.connect(self.parent.modified)
self.modificationAttempted.connect(self.fileReadOnly)
@ -178,11 +172,8 @@ class Editor(QgsCodeEditorPython):
self.selectAll, QKeySequence.SelectAll)
menu.addSeparator()
menu.addAction(QgsApplication.getThemeIcon("console/iconCommentEditorConsole.svg"),
QCoreApplication.translate("PythonConsole", "Comment"),
self.parent.pc.commentCode, 'Ctrl+3')
menu.addAction(QgsApplication.getThemeIcon("console/iconUncommentEditorConsole.svg"),
QCoreApplication.translate("PythonConsole", "Uncomment"),
self.parent.pc.uncommentCode, 'Shift+Ctrl+3')
QCoreApplication.translate("PythonConsole", "Toggle Comment"),
self.toggleComment, 'Ctrl+:')
menu.addSeparator()
gist_menu = QMenu(self)
gist_menu.setTitle(QCoreApplication.translate("PythonConsole", "Share on GitHub"))
@ -338,31 +329,6 @@ class Editor(QgsCodeEditorPython):
else:
self.openFindWidget()
def commentEditorCode(self, commentCheck):
self.beginUndoAction()
if self.hasSelectedText():
startLine, _, endLine, _ = self.getSelection()
for line in range(startLine, endLine + 1):
if commentCheck:
self.insertAt('#', line, 0)
else:
if not self.text(line).strip().startswith('#'):
continue
self.setSelection(line, self.indentation(line),
line, self.indentation(line) + 1)
self.removeSelectedText()
else:
line, pos = self.getCursorPosition()
if commentCheck:
self.insertAt('#', line, 0)
else:
if not self.text(line).strip().startswith('#'):
return
self.setSelection(line, self.indentation(line),
line, self.indentation(line) + 1)
self.removeSelectedText()
self.endUndoAction()
def createTempFile(self):
import tempfile
fd, path = tempfile.mkstemp()
@ -547,7 +513,7 @@ class Editor(QgsCodeEditorPython):
if re.match(ptrn, txt):
self.insert(' import')
self.setCursorPosition(line, pos + 7)
QsciScintilla.keyPressEvent(self, e)
QgsCodeEditorPython.keyPressEvent(self, e)
def focusInEvent(self, e):
pathfile = self.parent.path
@ -560,7 +526,6 @@ class Editor(QgsCodeEditorPython):
if pathfile and self.lastModified != QFileInfo(pathfile).lastModified():
self.beginUndoAction()
self.selectAll()
# fileReplaced = self.selectedText()
self.removeSelectedText()
file = open(pathfile, "r")
fileLines = file.readlines()
@ -658,7 +623,6 @@ class EditorTab(QWidget):
if overwrite:
try:
permis = os.stat(path).st_mode
# self.newEditor.lastModified = QFileInfo(path).lastModified()
os.chmod(path, permis)
except:
raise

View File

@ -62,6 +62,13 @@ Loads a ``script`` file.
Searches the selected text in the official PyQGIS online documentation.
.. versionadded:: 3.16
%End
void toggleComment();
%Docstring
Toggle comment for the selected text.
.. versionadded:: 3.30
%End
protected:
@ -69,6 +76,8 @@ Searches the selected text in the official PyQGIS online documentation.
virtual void initializeLexer();
virtual void keyPressEvent( QKeyEvent *event );
protected slots:
void autoComplete();

View File

@ -92,6 +92,8 @@ class ScriptEditorDialog(BASE, WIDGET):
QgsApplication.getThemeIcon('/mActionIncreaseFont.svg'))
self.actionDecreaseFontSize.setIcon(
QgsApplication.getThemeIcon('/mActionDecreaseFont.svg'))
self.actionToggleComment.setIcon(
QgsApplication.getThemeIcon('console/iconCommentEditorConsole.svg'))
# Connect signals and slots
self.actionOpenScript.triggered.connect(self.openScript)
@ -106,6 +108,7 @@ class ScriptEditorDialog(BASE, WIDGET):
self.actionFindReplace.toggled.connect(self.toggleSearchBox)
self.actionIncreaseFontSize.triggered.connect(self.editor.zoomIn)
self.actionDecreaseFontSize.triggered.connect(self.editor.zoomOut)
self.actionToggleComment.triggered.connect(self.editor.toggleComment)
self.editor.textChanged.connect(lambda: self.setHasChanged(True))
self.leFindText.returnPressed.connect(self.find)

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>721</width>
<width>600</width>
<height>578</height>
</rect>
</property>
@ -120,6 +120,8 @@
<addaction name="separator"/>
<addaction name="actionIncreaseFontSize"/>
<addaction name="actionDecreaseFontSize"/>
<addaction name="separator"/>
<addaction name="actionToggleComment"/>
</widget>
<action name="actionOpenScript">
<property name="text">
@ -240,11 +242,16 @@
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Find &amp;&amp; &amp;Replace</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="actionToggleComment">
<property name="text">
<string>Find &amp;&amp; &amp;Replace</string>
<string>Toggle Comment</string>
</property>
</action>
</widget>

View File

@ -21,7 +21,7 @@
#include "qgisapp.h"
#include "qgsmaptoolcapture.h"
QgsMapToolShapeRegularPolygonAbstract::QgsMapToolShapeRegularPolygonAbstract(const QString &id, QgsMapToolCapture *parentTool )
QgsMapToolShapeRegularPolygonAbstract::QgsMapToolShapeRegularPolygonAbstract( const QString &id, QgsMapToolCapture *parentTool )
: QgsMapToolShapeAbstract( id, parentTool )
{
}

View File

@ -28,7 +28,7 @@ class APP_EXPORT QgsMapToolShapeRegularPolygonAbstract: public QgsMapToolShapeAb
Q_OBJECT
public:
QgsMapToolShapeRegularPolygonAbstract(const QString &id, QgsMapToolCapture *parentTool);
QgsMapToolShapeRegularPolygonAbstract( const QString &id, QgsMapToolCapture *parentTool );
void clean() override;

View File

@ -29,6 +29,7 @@
#include <QTextStream>
#include <Qsci/qscilexerpython.h>
#include <QDesktopServices>
#include <QKeyEvent>
QgsCodeEditorPython::QgsCodeEditorPython( QWidget *parent, const QList<QString> &filenames, Mode mode )
: QgsCodeEditor( parent,
@ -185,6 +186,19 @@ void QgsCodeEditorPython::initializeLexer()
runPostLexerConfigurationTasks();
}
void QgsCodeEditorPython::keyPressEvent( QKeyEvent *event )
{
const bool ctrlModifier = event->modifiers() & Qt::ControlModifier;
if ( ctrlModifier && event->key() == Qt::Key_Colon )
{
event->accept();
toggleComment();
return;
}
return QgsCodeEditor::keyPressEvent( event );
}
void QgsCodeEditorPython::autoComplete()
{
switch ( autoCompletionSource() )
@ -242,6 +256,94 @@ void QgsCodeEditorPython::searchSelectedTextInPyQGISDocs()
QDesktopServices::openUrl( QUrl( QStringLiteral( "https://qgis.org/pyqgis/%1/search.html?q=%2" ).arg( version, text ) ) );
}
void QgsCodeEditorPython::toggleComment()
{
if ( isReadOnly() )
{
return;
}
beginUndoAction();
int startLine, startPos, endLine, endPos;
if ( hasSelectedText() )
{
getSelection( &startLine, &startPos, &endLine, &endPos );
}
else
{
getCursorPosition( &startLine, &startPos );
endLine = startLine;
endPos = startPos;
}
// Check comment state and minimum indentation for each selected line
bool allEmpty = true;
bool allCommented = true;
int minIndentation = -1;
for ( int line = startLine; line <= endLine; line++ )
{
const QString stripped = text( line ).trimmed();
if ( !stripped.isEmpty() )
{
allEmpty = false;
if ( !stripped.startsWith( '#' ) )
{
allCommented = false;
}
if ( minIndentation == -1 || minIndentation > indentation( line ) )
{
minIndentation = indentation( line );
}
}
}
// Special case, only empty lines
if ( allEmpty )
{
return;
}
// Selection shift to keep the same selected text after a # is added/removed
int delta = 0;
for ( int line = startLine; line <= endLine; line++ )
{
const QString stripped = text( line ).trimmed();
// Empty line
if ( stripped.isEmpty() )
{
continue;
}
if ( !allCommented )
{
insertAt( QStringLiteral( "# " ), line, minIndentation );
delta = -2;
}
else
{
if ( !stripped.startsWith( '#' ) )
{
continue;
}
if ( stripped.startsWith( QLatin1String( "# " ) ) )
{
delta = 2;
}
else
{
delta = 1;
}
setSelection( line, indentation( line ), line, indentation( line ) + delta );
removeSelectedText();
}
}
endUndoAction();
setSelection( startLine, startPos - delta, endLine, endPos - delta );
}
///@cond PRIVATE
//
// QgsQsciLexerPython
@ -256,9 +358,9 @@ const char *QgsQsciLexerPython::keywords( int set ) const
{
if ( set == 1 )
{
return "True False and as assert break class continue def del elif else except exec "
return "True False and as assert break class continue def del elif else except "
"finally for from global if import in is lambda None not or pass "
"print raise return try while with yield";
"raise return try while with yield async await nonlocal";
}
return QsciLexerPython::keywords( set );

View File

@ -84,10 +84,19 @@ class GUI_EXPORT QgsCodeEditorPython : public QgsCodeEditor
*/
void searchSelectedTextInPyQGISDocs();
/**
* Toggle comment for the selected text.
*
* \since QGIS 3.30
*/
void toggleComment();
protected:
void initializeLexer() override;
virtual void keyPressEvent( QKeyEvent *event ) override;
protected slots:
/**