Commit bed88818 authored by Alexandru Croitor's avatar Alexandru Croitor Committed by Nikolai Kosjar

C++: Implement context-aware expand / shrink selection actions.

Implement selection expanding / shrinking, that is aware of C++
semantics, thus giving smart selection changing.

Change-Id: I1386a20597fa6bb85c3aa0d8ddfb87cdb3fd7c38
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@theqtcompany.com>
parent 8bfdc82c
......@@ -48,6 +48,7 @@
#include <cpptools/cppcompletionassistprovider.h>
#include <cpptools/cppeditoroutline.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cppselectionchanger.h>
#include <cpptools/cppsemanticinfo.h>
#include <cpptools/cpptoolsconstants.h>
#include <cpptools/cpptoolsplugin.h>
......@@ -55,6 +56,7 @@
#include <cpptools/cppworkingcopy.h>
#include <cpptools/symbolfinder.h>
#include <texteditor/behaviorsettings.h>
#include <texteditor/completionsettings.h>
#include <texteditor/convenience.h>
#include <texteditor/textdocument.h>
......@@ -119,6 +121,8 @@ public:
QScopedPointer<FollowSymbolUnderCursor> m_followSymbolUnderCursor;
QToolButton *m_preprocessorButton;
CppSelectionChanger m_cppSelectionChanger;
};
CppEditorWidgetPrivate::CppEditorWidgetPrivate(CppEditorWidget *q)
......@@ -130,6 +134,7 @@ CppEditorWidgetPrivate::CppEditorWidgetPrivate(CppEditorWidget *q)
, m_declDefLinkFinder(new FunctionDeclDefLinkFinder(q))
, m_followSymbolUnderCursor(new FollowSymbolUnderCursor(q))
, m_preprocessorButton(0)
, m_cppSelectionChanger()
{
}
......@@ -201,6 +206,9 @@ void CppEditorWidget::finalizeInitialization()
connect(this, &CppEditorWidget::cursorPositionChanged, [this]() {
if (!d->m_localRenaming.isActive())
d->m_useSelectionsUpdater.scheduleUpdate();
// Notify selection expander about the changed cursor.
d->m_cppSelectionChanger.onCursorPositionChanged(textCursor());
});
// Tool bar creation
......@@ -328,6 +336,44 @@ void CppEditorWidget::renameUsages(const QString &replacement)
}
}
bool CppEditorWidget::selectBlockUp()
{
if (!behaviorSettings().m_smartSelectionChanging)
return TextEditorWidget::selectBlockUp();
QTextCursor cursor = textCursor();
d->m_cppSelectionChanger.startChangeSelection();
const bool changed =
d->m_cppSelectionChanger.changeSelection(
CppSelectionChanger::ExpandSelection,
cursor,
d->m_lastSemanticInfo.doc);
if (changed)
setTextCursor(cursor);
d->m_cppSelectionChanger.stopChangeSelection();
return changed;
}
bool CppEditorWidget::selectBlockDown()
{
if (!behaviorSettings().m_smartSelectionChanging)
return TextEditorWidget::selectBlockDown();
QTextCursor cursor = textCursor();
d->m_cppSelectionChanger.startChangeSelection();
const bool changed =
d->m_cppSelectionChanger.changeSelection(
CppSelectionChanger::ShrinkSelection,
cursor,
d->m_lastSemanticInfo.doc);
if (changed)
setTextCursor(cursor);
d->m_cppSelectionChanger.stopChangeSelection();
return changed;
}
void CppEditorWidget::renameSymbolUnderCursor()
{
d->m_useSelectionsUpdater.abortSchedule();
......
......@@ -92,6 +92,9 @@ public slots:
void renameSymbolUnderCursor();
void renameUsages(const QString &replacement = QString());
bool selectBlockUp() override;
bool selectBlockDown() override;
protected:
bool event(QEvent *e) override;
void contextMenuEvent(QContextMenuEvent *) override;
......
This diff is collapsed.
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#ifndef CPPSELECTIONCHANGER_H
#define CPPSELECTIONCHANGER_H
#include "cpptools_global.h"
#include <cplusplus/ASTPath.h>
#include <cplusplus/CppDocument.h>
#include <cplusplus/TranslationUnit.h>
#include <QObject>
#include <QTextCursor>
namespace CppTools {
class ASTNodePositions {
public:
ASTNodePositions() {}
ASTNodePositions(CPlusPlus::AST *_ast) : ast(_ast) {}
operator bool() const { return ast; }
CPlusPlus::AST *ast = 0;
unsigned firstTokenIndex = 0;
unsigned lastTokenIndex = 0;
unsigned secondToLastTokenIndex = 0;
int astPosStart = -1;
int astPosEnd = -1;
};
class CPPTOOLS_EXPORT CppSelectionChanger : public QObject
{
Q_OBJECT
public:
explicit CppSelectionChanger(QObject *parent = 0);
enum Direction {
ExpandSelection,
ShrinkSelection
};
enum NodeIndexAndStepState {
NodeIndexAndStepNotSet,
NodeIndexAndStepWholeDocument,
};
bool changeSelection(Direction direction,
QTextCursor &cursorToModify,
const CPlusPlus::Document::Ptr doc);
void startChangeSelection();
void stopChangeSelection();
public slots:
void onCursorPositionChanged(const QTextCursor &newCursor);
protected slots:
void fineTuneForStatementPositions(unsigned firstParensTokenIndex,
unsigned lastParensTokenIndex,
ASTNodePositions &positions) const;
private:
bool performSelectionChange(QTextCursor &cursorToModify);
ASTNodePositions getASTPositions(CPlusPlus::AST *ast, const QTextCursor &cursor) const;
void updateCursorSelection(QTextCursor &cursorToModify, ASTNodePositions positions);
int possibleASTStepCount(CPlusPlus::AST *ast) const;
int currentASTStep() const;
ASTNodePositions findNextASTStepPositions(const QTextCursor &cursor);
void fineTuneASTNodePositions(ASTNodePositions &positions) const;
ASTNodePositions getFineTunedASTPositions(CPlusPlus::AST *ast, const QTextCursor &cursor) const;
int getFirstCurrentStepForASTNode(CPlusPlus::AST *ast) const;
bool isLastPossibleStepForASTNode(CPlusPlus::AST *ast) const;
ASTNodePositions findRelevantASTPositionsFromCursor(const QList<CPlusPlus::AST *> &astPath,
const QTextCursor &cursor,
int startingFromNodeIndex = -1);
ASTNodePositions findRelevantASTPositionsFromCursorWhenNodeIndexNotSet(
const QList<CPlusPlus::AST *> astPath,
const QTextCursor &cursor);
ASTNodePositions findRelevantASTPositionsFromCursorWhenWholeDocumentSelected(
const QList<CPlusPlus::AST *> astPath,
const QTextCursor &cursor);
ASTNodePositions findRelevantASTPositionsFromCursorFromPreviousNodeIndex(
const QList<CPlusPlus::AST *> astPath,
const QTextCursor &cursor);
bool shouldSkipASTNodeBasedOnPosition(const ASTNodePositions &positions,
const QTextCursor &cursor) const;
void setNodeIndexAndStep(NodeIndexAndStepState state);
int getTokenStartCursorPosition(unsigned tokenIndex, const QTextCursor &cursor) const;
int getTokenEndCursorPosition(unsigned tokenIndex, const QTextCursor &cursor) const;
void printTokenDebugInfo(unsigned tokenIndex, const QTextCursor &cursor, QString prefix) const;
QTextCursor m_initialChangeSelectionCursor;
QTextCursor m_workingCursor;
CPlusPlus::Document::Ptr m_doc;
CPlusPlus::TranslationUnit *m_unit;
Direction m_direction;
int m_changeSelectionNodeIndex;
int m_nodeCurrentStep;
bool m_inChangeSelection;
};
} // namespace CppTools
#endif // CPPSELECTIONCHANGER_H
......@@ -43,6 +43,7 @@ HEADERS += \
cppprojectfile.h \
cppqtstyleindenter.h \
cpprefactoringchanges.h \
cppselectionchanger.h \
cppsemanticinfo.h \
cppsemanticinfoupdater.h \
cppsourceprocessor.h \
......@@ -117,6 +118,7 @@ SOURCES += \
cppprojectfile.cpp \
cppqtstyleindenter.cpp \
cpprefactoringchanges.cpp \
cppselectionchanger.cpp \
cppsemanticinfo.cpp \
cppsemanticinfoupdater.cpp \
cppsourceprocessor.cpp \
......
......@@ -66,6 +66,7 @@ QtcPlugin {
"cppprojectfile.cpp", "cppprojectfile.h",
"cppqtstyleindenter.cpp", "cppqtstyleindenter.h",
"cpprefactoringchanges.cpp", "cpprefactoringchanges.h",
"cppselectionchanger.cpp", "cppselectionchanger.h",
"cppsemanticinfo.cpp", "cppsemanticinfo.h",
"cppsemanticinfoupdater.cpp", "cppsemanticinfoupdater.h",
"cppsourceprocessor.cpp", "cppsourceprocessor.h",
......
......@@ -37,6 +37,7 @@ static const char constrainTooltips[] = "ConstrainTooltips";
static const char camelCaseNavigationKey[] = "CamelCaseNavigation";
static const char keyboardTooltips[] = "KeyboardTooltips";
static const char groupPostfix[] = "BehaviorSettings";
static const char smartSelectionChanging[] = "SmartSelectionChanging";
namespace TextEditor {
......@@ -46,7 +47,8 @@ BehaviorSettings::BehaviorSettings() :
m_scrollWheelZooming(true),
m_constrainHoverTooltips(false),
m_camelCaseNavigation(true),
m_keyboardTooltips(false)
m_keyboardTooltips(false),
m_smartSelectionChanging(true)
{
}
......@@ -69,6 +71,7 @@ void BehaviorSettings::toMap(const QString &prefix, QVariantMap *map) const
map->insert(prefix + QLatin1String(constrainTooltips), m_constrainHoverTooltips);
map->insert(prefix + QLatin1String(camelCaseNavigationKey), m_camelCaseNavigation);
map->insert(prefix + QLatin1String(keyboardTooltips), m_keyboardTooltips);
map->insert(prefix + QLatin1String(smartSelectionChanging), m_smartSelectionChanging);
}
void BehaviorSettings::fromMap(const QString &prefix, const QVariantMap &map)
......@@ -85,6 +88,9 @@ void BehaviorSettings::fromMap(const QString &prefix, const QVariantMap &map)
map.value(prefix + QLatin1String(camelCaseNavigationKey), m_camelCaseNavigation).toBool();
m_keyboardTooltips =
map.value(prefix + QLatin1String(keyboardTooltips), m_keyboardTooltips).toBool();
m_smartSelectionChanging =
map.value(prefix + QLatin1String(smartSelectionChanging), m_smartSelectionChanging)
.toBool();
}
bool BehaviorSettings::equals(const BehaviorSettings &ds) const
......@@ -95,6 +101,7 @@ bool BehaviorSettings::equals(const BehaviorSettings &ds) const
&& m_constrainHoverTooltips == ds.m_constrainHoverTooltips
&& m_camelCaseNavigation == ds.m_camelCaseNavigation
&& m_keyboardTooltips == ds.m_keyboardTooltips
&& m_smartSelectionChanging == ds.m_smartSelectionChanging
;
}
......
......@@ -59,6 +59,7 @@ public:
bool m_constrainHoverTooltips;
bool m_camelCaseNavigation;
bool m_keyboardTooltips;
bool m_smartSelectionChanging;
};
inline bool operator==(const BehaviorSettings &t1, const BehaviorSettings &t2) { return t1.equals(t2); }
......
......@@ -111,6 +111,8 @@ BehaviorSettingsWidget::BehaviorSettingsWidget(QWidget *parent)
this, &BehaviorSettingsWidget::slotBehaviorSettingsChanged);
connect(d->m_ui.keyboardTooltips, &QAbstractButton::clicked,
this, &BehaviorSettingsWidget::slotBehaviorSettingsChanged);
connect(d->m_ui.smartSelectionChanging, &QAbstractButton::clicked,
this, &BehaviorSettingsWidget::slotBehaviorSettingsChanged);
}
BehaviorSettingsWidget::~BehaviorSettingsWidget()
......@@ -198,6 +200,7 @@ void BehaviorSettingsWidget::setAssignedBehaviorSettings(const BehaviorSettings
d->m_ui.constrainTooltipsBox->setCurrentIndex(behaviorSettings.m_constrainHoverTooltips ? 1 : 0);
d->m_ui.camelCaseNavigation->setChecked(behaviorSettings.m_camelCaseNavigation);
d->m_ui.keyboardTooltips->setChecked(behaviorSettings.m_keyboardTooltips);
d->m_ui.smartSelectionChanging->setChecked(behaviorSettings.m_smartSelectionChanging);
updateConstrainTooltipsBoxTooltip();
}
......@@ -209,6 +212,7 @@ void BehaviorSettingsWidget::assignedBehaviorSettings(BehaviorSettings *behavior
behaviorSettings->m_constrainHoverTooltips = (d->m_ui.constrainTooltipsBox->currentIndex() == 1);
behaviorSettings->m_camelCaseNavigation = d->m_ui.camelCaseNavigation->isChecked();
behaviorSettings->m_keyboardTooltips = d->m_ui.keyboardTooltips->isChecked();
behaviorSettings->m_smartSelectionChanging = d->m_ui.smartSelectionChanging->isChecked();
}
void BehaviorSettingsWidget::setAssignedExtraEncodingSettings(
......
......@@ -357,6 +357,16 @@ Specifies how backspace interacts with indentation.
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="smartSelectionChanging">
<property name="toolTip">
<string>Using Select Block Up / Down actions will now provide smarter selections.</string>
</property>
<property name="text">
<string>Enable smart selection changing</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="keyboardTooltips">
<property name="toolTip">
......
......@@ -61,5 +61,13 @@ QString textAt(QTextCursor tc, int pos, int length)
return tc.selectedText().replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
}
QTextCursor flippedCursor(const QTextCursor &cursor)
{
QTextCursor flipped = cursor;
flipped.clearSelection();
flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
return flipped;
}
} // Util
} // TextEditor
......@@ -44,6 +44,8 @@ TEXTEDITOR_EXPORT bool convertPosition(const QTextDocument *document,
TEXTEDITOR_EXPORT QString textAt(QTextCursor tc, int pos, int length);
TEXTEDITOR_EXPORT QTextCursor flippedCursor(const QTextCursor &cursor);
} // Util
} // TextEditor
......
......@@ -1300,14 +1300,6 @@ void TextEditorWidget::gotoNextWordCamelCaseWithSelection()
setTextCursor(c);
}
static QTextCursor flippedCursor(const QTextCursor &cursor)
{
QTextCursor flipped = cursor;
flipped.clearSelection();
flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
return flipped;
}
bool TextEditorWidget::selectBlockUp()
{
QTextCursor cursor = textCursor();
......@@ -1321,7 +1313,7 @@ bool TextEditorWidget::selectBlockUp()
if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true))
return false;
setTextCursor(flippedCursor(cursor));
setTextCursor(Convenience::flippedCursor(cursor));
d->_q_matchParentheses();
return true;
}
......@@ -1346,7 +1338,7 @@ bool TextEditorWidget::selectBlockDown()
if ( cursor != d->m_selectBlockAnchor)
TextBlockUserData::findNextClosingParenthesis(&cursor, true);
setTextCursor(flippedCursor(cursor));
setTextCursor(Convenience::flippedCursor(cursor));
d->_q_matchParentheses();
return true;
}
......@@ -5346,6 +5338,11 @@ const MarginSettings &TextEditorWidget::marginSettings() const
return d->m_marginSettings;
}
const BehaviorSettings &TextEditorWidget::behaviorSettings() const
{
return d->m_behaviorSettings;
}
void TextEditorWidgetPrivate::handleHomeKey(bool anchor)
{
QTextCursor cursor = q->textCursor();
......
......@@ -294,6 +294,7 @@ public:
const DisplaySettings &displaySettings() const;
const MarginSettings &marginSettings() const;
const BehaviorSettings &behaviorSettings() const;
void ensureCursorVisible();
......@@ -389,8 +390,8 @@ public:
void gotoNextWordCamelCase();
void gotoNextWordCamelCaseWithSelection();
bool selectBlockUp();
bool selectBlockDown();
virtual bool selectBlockUp();
virtual bool selectBlockDown();
void moveLineUp();
void moveLineDown();
......@@ -498,6 +499,7 @@ protected:
void showDefaultContextMenu(QContextMenuEvent *e, Core::Id menuContextId);
virtual void finalizeInitialization() {}
virtual void finalizeInitializationAfterDuplication(TextEditorWidget *) {}
static QTextCursor flippedCursor(const QTextCursor &cursor);
public:
struct Link
......
......@@ -449,7 +449,7 @@ void TextEditorActionHandlerPrivate::createActions()
G_EDIT_BLOCKS, advancedEditMenu);
m_selectBlockDownAction = registerAction(SELECT_BLOCK_DOWN,
[this] (TextEditorWidget *w) { w->selectBlockDown(); }, true, tr("Select Block Down"),
QKeySequence(),
QKeySequence(tr("Ctrl+Shift+Alt+U")),
G_EDIT_BLOCKS, advancedEditMenu);
// register GOTO Actions
......
......@@ -11,6 +11,7 @@ SUBDIRS = \
typeprettyprinter \
misc \
c99 \
cppselectionchanger\
cxx11 \
checksymbols \
lexer \
......
......@@ -7,6 +7,7 @@ Project {
"c99/c99.qbs",
"checksymbols/checksymbols.qbs",
"codeformatter/codeformatter.qbs",
"cppselectionchanger/cppselectionchanger.qbs",
"cxx11/cxx11.qbs",
"fileiterationorder/fileiterationorder.qbs",
"findusages/findusages.qbs",
......
include(../shared/shared.pri)
# Inject the source dir for referencing test data from shadow builds.
DEFINES += SRCDIR=\\\"$$PWD\\\"
SOURCES += tst_cppselectionchangertest.cpp
DISTFILES += testCppFile.cpp \
cppselectionchanger.qbs
import qbs
import "../cplusplusautotest.qbs" as CPlusPlusAutotest
CPlusPlusAutotest {
name: "CPlusPlus selection changer autotest"
Group {
name: "Source Files"
files: "tst_cppselectionchangertest.cpp"
}
Group {
name: "Data Files"
fileTags: ["data"]
files: [
"testCppFile.cpp",
]
}
cpp.defines: base.concat(['SRCDIR="' + path + '"'])
}
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include <string>
#include <iostream>
#include <map>
#include <vector>
struct TestClass {
int a;
char b;
char x;
char y;
char z;
std::string c;
double d;
std::map<int, int> e;
};
int add(int a, int b) {
return a + b;
}
template <class T>
int subtract(T a, T b) {
return a - b;
}
namespace CustomNamespace {
extern int insideNamespace;
int foo() {
insideNamespace = 2;
return insideNamespace;
}
}
class CustomClass {
bool customClassMethod(const int &parameter) const volatile;
};
class SecondCustomClass {
public:
SecondCustomClass(int argc, char *argv[]);
void secondCustomClassFunction();
};
bool CustomClass::customClassMethod(const int &parameter) const volatile {
int secondParameter = parameter;
++secondParameter;
return secondParameter;
}
int main(int argc, char *argv[])
{
std::map<int,TestClass> a;
SecondCustomClass secondCustomClass(argc, argv);
secondCustomClass.secondCustomClassFunction();
TestClass bla;
bla.a = 1;
bla.b = 65;
bla.c = "Hello";
bla.d = 3.14f;
bla.e[3] = 3;
a[3] = bla;
std::vector<int> v;
v.push_back(1);
v.push_back(2);
if (5 == 5) {
std::cout << "Hello" << 'c' << 54545 << u8"utf8string" << U"unicodeString";
for (int i = 0; i < 3; ++i) {
std::cout << i;
}
for (auto val :v) {
std::cout << val;
}
}
std::cout << "\n After exec";
std::cout << argc << argv;
std::cout << add(1, add(2, add(10, 20)));
auto res = std::find(v.begin(), v.end(), 1);
if (res != v.end())
std::cout << *res;
std::cout << static_cast<int>(3);
auto aLambda = [=, &a](int lambdaArgument) -> int {
return lambdaArgument + 1;
};
aLambda(1);
std::cout << R"(
Raw literal
)";
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment