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;
......
/****************************************************************************
**
** 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 "cppselectionchanger.h"
#include <texteditor/convenience.h>
#include <utils/qtcassert.h>
#include <QDebug>
#include <QString>
#include <QTextBlock>
#include <QTextDocument>
using namespace CPlusPlus;
using namespace TextEditor::Convenience;
enum {
debug = false
};
namespace CppTools {
namespace Internal {
const int kChangeSelectionNodeIndexNotSet = -1;
const int kChangeSelectionNodeIndexWholeDocoument = -2;
}
using namespace CppTools::Internal;
CppSelectionChanger::CppSelectionChanger(QObject *parent)
: QObject(parent)
, m_initialChangeSelectionCursor()
, m_workingCursor()
, m_doc(0)
, m_unit(0)
, m_direction(ExpandSelection)
, m_changeSelectionNodeIndex(kChangeSelectionNodeIndexNotSet)
, m_nodeCurrentStep(kChangeSelectionNodeIndexNotSet)
, m_inChangeSelection(false)
{
}
void CppSelectionChanger::onCursorPositionChanged(const QTextCursor &newCursor)
{
// Reset the text cursor to be used for initial change selection behavior, only in the case
// that the cursor is not being modified by the actual change selection methods.
if (!m_inChangeSelection) {
m_initialChangeSelectionCursor = newCursor;
setNodeIndexAndStep(NodeIndexAndStepNotSet);
if (debug)
qDebug() << "Updating change selection cursor position:" << newCursor.position();
}
}
namespace {
bool hasNoSelectionAndShrinking(
CppSelectionChanger::Direction direction,
const QTextCursor &cursor)
{
if (direction == CppSelectionChanger::ShrinkSelection && !cursor.hasSelection()) {
if (debug)
qDebug() << "No selection to shrink, exiting early.";
return true;
}
return false;
}
void ensureCursorSelectionIsNotFlipped(QTextCursor &cursor)
{
if (cursor.hasSelection() && (cursor.anchor() > cursor.position()))
cursor = flippedCursor(cursor);
if (debug) {
int l, c;
convertPosition(cursor.document(), cursor.position(), &l, &c);
qDebug() << "Cursor details: " << cursor.anchor() << cursor.position()
<< " l,c:" << l << ":" << c;
}
}
bool isDocumentAvailable(const CPlusPlus::Document::Ptr doc)
{
if (!doc) {
if (debug)
qDebug() << "Document is not available.";
return false;
}
return true;
}
QTextCursor getWholeDocumentCursor(const QTextCursor &cursor)
{
QTextCursor newWholeDocumentCursor(cursor);
newWholeDocumentCursor.setPosition(0, QTextCursor::MoveAnchor);
newWholeDocumentCursor.setPosition(cursor.document()->characterCount() - 1,
QTextCursor::KeepAnchor);
return newWholeDocumentCursor;
}
bool isWholeDocumentSelectedAndExpanding(
CppSelectionChanger::Direction direction,
const QTextCursor &cursor)
{
if (direction == CppSelectionChanger::ExpandSelection && cursor.hasSelection()) {
const QTextCursor wholeDocumentCursor = getWholeDocumentCursor(cursor);
if (wholeDocumentCursor == cursor) {
if (debug)
qDebug() << "Selection is whole document, nothing to expand, exiting early.";
return true;
}
}
return false;
}
} // end of anonymous namespace
int CppSelectionChanger::getTokenStartCursorPosition(
unsigned tokenIndex,
const QTextCursor &cursor) const
{
unsigned startLine, startColumn;
m_unit->getTokenStartPosition(tokenIndex, &startLine, &startColumn);
const QTextDocument *document = cursor.document();
const int startPosition =
document->findBlockByNumber(static_cast<int>(startLine) - 1).position()
+ static_cast<int>(startColumn) - 1;
return startPosition;
}
int CppSelectionChanger::getTokenEndCursorPosition(
unsigned tokenIndex,
const QTextCursor &cursor) const
{
unsigned endLine, endColumn;
m_unit->getTokenEndPosition(tokenIndex, &endLine, &endColumn);
const QTextDocument *document = cursor.document();
const int endPosition =
document->findBlockByNumber(static_cast<int>(endLine) - 1).position()
+ static_cast<int>(endColumn) - 1;
return endPosition;
}
void CppSelectionChanger::printTokenDebugInfo(
unsigned tokenIndex,
const QTextCursor &cursor,
QString prefix) const
{
unsigned line, column;
const Token token = m_unit->tokenAt(tokenIndex);
m_unit->getTokenStartPosition(tokenIndex, &line, &column);
const int startPos = getTokenStartCursorPosition(tokenIndex, cursor);
const int endPos = getTokenEndCursorPosition(tokenIndex, cursor);
qDebug() << qSetFieldWidth(20) << prefix << qSetFieldWidth(0)
<< token.spell() << tokenIndex
<< " l, c:" << line << ":" << column
<< " offset: " << token.utf16chars() << startPos << endPos;
}
bool CppSelectionChanger::shouldSkipASTNodeBasedOnPosition(
const ASTNodePositions &positions,
const QTextCursor &cursor) const
{
bool shouldSkipNode = false;
bool isEqual = cursor.anchor() == positions.astPosStart
&& cursor.position() == positions.astPosEnd;
// New selections should include initial selection.
bool includesInitialSelection =
m_initialChangeSelectionCursor.anchor() >= positions.astPosStart &&
m_initialChangeSelectionCursor.position() <= positions.astPosEnd;
// Prefer new selections to start with initial cursor if anchor == position.
if (!m_initialChangeSelectionCursor.hasSelection()) {
includesInitialSelection =
m_initialChangeSelectionCursor.position() < positions.astPosEnd;
}
// When expanding: Skip if new selection is smaller than current cursor selection.
// When shrinking: Skip if new selection is bigger than current cursor selection.
bool isNewSelectionSmaller = positions.astPosStart > cursor.anchor()
|| positions.astPosEnd < cursor.position();
bool isNewSelectionBigger = positions.astPosStart < cursor.anchor()
|| positions.astPosEnd > cursor.position();
if (m_direction == CppSelectionChanger::ExpandSelection
&& (isNewSelectionSmaller || isEqual || !includesInitialSelection)) {
shouldSkipNode = true;
} else if (m_direction == CppSelectionChanger::ShrinkSelection
&& (isNewSelectionBigger || isEqual || !includesInitialSelection)) {
shouldSkipNode = true;
}
if (debug && shouldSkipNode) {
qDebug() << "isEqual:" << isEqual << "includesInitialSelection:" << includesInitialSelection
<< "isNewSelectionSmaller:" << isNewSelectionSmaller << "isNewSelectionBigger:"
<< isNewSelectionBigger;
}
return shouldSkipNode;
}
ASTNodePositions CppSelectionChanger::getASTPositions(AST *ast, const QTextCursor &cursor) const
{
ASTNodePositions positions(ast);
// An AST node's contents is bound by its first token start position inclusively,
// and its last token start position exclusively.
// So we are also interested in the second to last token, which is actually
// included in the bounds.
positions.firstTokenIndex = ast->firstToken();
positions.lastTokenIndex = ast->lastToken();
positions.secondToLastTokenIndex = positions.lastTokenIndex - 1;
// The AST position start is the start of the first token.
positions.astPosStart = getTokenStartCursorPosition(positions.firstTokenIndex, cursor);
// The end position depends on whether, there is only one token involved in the current AST
// node or multiple ones.
// Default we assume that there is only one token, so the end position of the AST node
// is the start of the last token.
// If there is more than one (second to last token will be different to the first token)
// use the second to last token end position as the AST node end position.
positions.astPosEnd = getTokenStartCursorPosition(positions.lastTokenIndex, cursor);
if (positions.lastTokenIndex != positions.firstTokenIndex)
positions.astPosEnd = getTokenEndCursorPosition(positions.secondToLastTokenIndex, cursor);
if (debug) {
qDebug() << "Token positions start and end:"
<< positions.astPosStart << positions.astPosEnd;
}
return positions;
}
void CppSelectionChanger::updateCursorSelection(
QTextCursor &cursorToModify,
ASTNodePositions positions)
{
m_workingCursor.setPosition(positions.astPosStart, QTextCursor::MoveAnchor);
m_workingCursor.setPosition(positions.astPosEnd, QTextCursor::KeepAnchor);
cursorToModify = m_workingCursor;
if (debug) {
printTokenDebugInfo(positions.firstTokenIndex, m_workingCursor,
QString::fromLatin1("First token:"));
printTokenDebugInfo(positions.lastTokenIndex, m_workingCursor,
QString::fromLatin1("Last token:"));
printTokenDebugInfo(positions.secondToLastTokenIndex, m_workingCursor,
QString::fromLatin1("Second to last:"));
qDebug() << "Anchor is now: " << m_workingCursor.anchor();
qDebug() << "Position is now: " << m_workingCursor.position();
}
}
int CppSelectionChanger::getFirstCurrentStepForASTNode(AST *ast) const
{
if (m_direction == ExpandSelection)
return 1;
else
return possibleASTStepCount(ast);
}
bool CppSelectionChanger::isLastPossibleStepForASTNode(AST *ast) const
{
if (m_direction == ExpandSelection)
return currentASTStep() == possibleASTStepCount(ast);
else
return currentASTStep() == 1;
}
ASTNodePositions CppSelectionChanger::getFineTunedASTPositions(AST *ast,
const QTextCursor &cursor) const
{
ASTNodePositions positions = getASTPositions(ast, cursor);
fineTuneASTNodePositions(positions);
return positions;
}
ASTNodePositions CppSelectionChanger::findRelevantASTPositionsFromCursor(
const QList<AST *> &astPath,
const QTextCursor &cursor,
int startingFromNodeIndex)
{
ASTNodePositions currentNodePositions;
const int size = astPath.size();
int currentAstIndex = m_direction == ExpandSelection ? size - 1 : 0;
// Adjust starting node index, if a valid value was passed.
if (startingFromNodeIndex != kChangeSelectionNodeIndexNotSet)
currentAstIndex = startingFromNodeIndex;
if (currentAstIndex < size && currentAstIndex >= 0) {
AST *ast = astPath.at(currentAstIndex);
m_changeSelectionNodeIndex = currentAstIndex;
m_nodeCurrentStep = getFirstCurrentStepForASTNode(ast);
currentNodePositions = getFineTunedASTPositions(ast, cursor);
if (debug && startingFromNodeIndex == kChangeSelectionNodeIndexNotSet)
qDebug() << "Setting AST index for the first time.";
}
if (!currentNodePositions.ast)
setNodeIndexAndStep(NodeIndexAndStepNotSet);
return currentNodePositions;
}
ASTNodePositions CppSelectionChanger::findRelevantASTPositionsFromCursorWhenNodeIndexNotSet(
const QList<AST *> astPath,
const QTextCursor &cursor)
{
// Find relevant AST node from cursor, when the user expands for the first time.
return findRelevantASTPositionsFromCursor(astPath, cursor);
}
ASTNodePositions CppSelectionChanger::findRelevantASTPositionsFromCursorWhenWholeDocumentSelected(
const QList<AST *> astPath,
const QTextCursor &cursor)
{
// Can't expand more, because whole document is selected.
if (m_direction == ExpandSelection)
return 0;
// In case of shrink, select the next smaller selection.
return findRelevantASTPositionsFromCursor(astPath, cursor);
}
ASTNodePositions CppSelectionChanger::findRelevantASTPositionsFromCursorFromPreviousNodeIndex(
const QList<AST *> astPath,
const QTextCursor &cursor)
{
ASTNodePositions nodePositions;
// This is not the first expansion, use the previous node index.
nodePositions.ast = astPath.at(m_changeSelectionNodeIndex);
// We reached the last possible step for the current AST node, so we move to the
// next / previous one depending on the direction.
if (isLastPossibleStepForASTNode(nodePositions.ast)) {
int newAstIndex = m_changeSelectionNodeIndex;
if (m_direction == ExpandSelection)
--newAstIndex;
else
++newAstIndex;
if (newAstIndex < 0 || newAstIndex >= astPath.count()) {
if (debug)
qDebug() << "Skipping expansion because there is no available next AST node.";
return 0;
}
// Switch to next AST and set the first step.
nodePositions = findRelevantASTPositionsFromCursor(astPath, cursor, newAstIndex);
if (!nodePositions)
return 0;
if (debug)
qDebug() << "Moved to next AST node.";
} else {
// There are possible steps available for current node, so move to the next / previous
// step.
if (m_direction == ExpandSelection)
++m_nodeCurrentStep;
else
--m_nodeCurrentStep;
nodePositions = getFineTunedASTPositions(nodePositions.ast, cursor);
if (debug)
qDebug() << "Moved to next AST step.";
}
return nodePositions;
}
ASTNodePositions CppSelectionChanger::findNextASTStepPositions(const QTextCursor &cursor)
{
// Find AST node path starting from the initial change selection cursor.
// The ASTPath class, only takes into consideration the position of the cursor, but not the
// anchor. We make up for that later in the code.
QTextCursor cursorToStartFrom(m_initialChangeSelectionCursor);
ASTPath astPathFinder(m_doc);
const QList<AST *> astPath = astPathFinder(cursorToStartFrom);
#ifdef WITH_AST_PATH_DUMP
if (debug)
ASTPath::dump(astPath);
#endif
if (astPath.size() == 0)
return 0;
ASTNodePositions currentNodePositions;
if (m_changeSelectionNodeIndex == kChangeSelectionNodeIndexNotSet) {
currentNodePositions = findRelevantASTPositionsFromCursorWhenNodeIndexNotSet(astPath,
cursor);
} else if (m_changeSelectionNodeIndex == kChangeSelectionNodeIndexWholeDocoument) {
currentNodePositions = findRelevantASTPositionsFromCursorWhenWholeDocumentSelected(astPath,
cursor);
} else {
currentNodePositions = findRelevantASTPositionsFromCursorFromPreviousNodeIndex(astPath,
cursor);
}
if (debug) {
qDebug() << "m_changeSelectionNodeIndex:" << m_changeSelectionNodeIndex
<< "possible step count:" << possibleASTStepCount(currentNodePositions.ast)
<< "current step:" << m_nodeCurrentStep;
}
QTC_ASSERT(m_nodeCurrentStep >= 1, return 0);
return currentNodePositions;
}
void CppSelectionChanger::fineTuneForStatementPositions(unsigned firstParenTokenIndex,
unsigned lastParenTokenIndex,
ASTNodePositions &positions) const
{
Token firstParenToken = m_unit->tokenAt(firstParenTokenIndex);
Token lastParenToken = m_unit->tokenAt(lastParenTokenIndex);
if (debug) {
qDebug() << "firstParenToken:" << firstParenToken.spell();
qDebug() << "lastParenToken:" << lastParenToken.spell();
}
int newPosStart = getTokenStartCursorPosition(firstParenTokenIndex, m_workingCursor);
int newPosEnd = getTokenEndCursorPosition(lastParenTokenIndex, m_workingCursor);
bool isOutsideParen =
m_initialChangeSelectionCursor.position() <= newPosStart;
if (currentASTStep() == 1 && !isOutsideParen) {
if (debug)
qDebug() << "Selecting Paren contents of for statement.";
positions.astPosStart = newPosStart + 1;
positions.astPosEnd = newPosEnd - 1;
}
if (currentASTStep() == 2 && !isOutsideParen) {
if (debug)
qDebug() << "Selecting Paren of for statement together with contents.";
positions.astPosStart = newPosStart;
positions.astPosEnd = newPosEnd;
}
}
void CppSelectionChanger::fineTuneASTNodePositions(ASTNodePositions &positions) const
{
AST *ast = positions.ast;
if (ast->asCompoundStatement()) {
// Allow first selecting the contents of the scope, without selecting the braces, and
// afterwards select the contents together with braces.
if (currentASTStep() == 1) {
if (debug)
qDebug() << "Selecting inner contents of compound statement.";
unsigned firstInnerTokenIndex = positions.firstTokenIndex + 1;
unsigned lastInnerTokenIndex = positions.lastTokenIndex - 2;
Token firstInnerToken = m_unit->tokenAt(firstInnerTokenIndex);
Token lastInnerToken = m_unit->tokenAt(lastInnerTokenIndex);
if (debug) {
qDebug() << "LastInnerToken:" << lastInnerToken.spell();
qDebug() << "FirstInnerToken:" << firstInnerToken.spell();
}
// Check if compound statement is empty, then select just the blank space inside it.
int newPosStart, newPosEnd;
if (positions.secondToLastTokenIndex - positions.firstTokenIndex <= 1) {
// TODO: If the empty space has a new tab character, or spaces, and the document is
// not saved, the last semantic info is not updated, and the selection is not
// properly computed. Figure out how to work around this.
newPosStart = getTokenEndCursorPosition(positions.firstTokenIndex, m_workingCursor);
newPosEnd = getTokenStartCursorPosition(positions.secondToLastTokenIndex,
m_workingCursor);
if (debug)
qDebug() << "Selecting inner contents of compound statement which is empty.";
} else {
// Select the inner contents of the scope, without the braces.
newPosStart = getTokenStartCursorPosition(firstInnerTokenIndex, m_workingCursor);
newPosEnd = getTokenEndCursorPosition(lastInnerTokenIndex, m_workingCursor);
}
if (debug) {
qDebug() << "New" << newPosStart << newPosEnd
<< "Old" << m_workingCursor.anchor() << m_workingCursor.position();
}
positions.astPosStart = newPosStart;
positions.astPosEnd = newPosEnd;
}
// Next time, we select the braces as well. Reverse for shrinking.
// The positions already have the correct selection, so no need to set them.
} else if (CallAST *callAST = ast->asCall()) {
unsigned firstParenTokenIndex = callAST->lparen_token;
unsigned lastParenTokenIndex = callAST->rparen_token;
Token firstParenToken = m_unit->tokenAt(firstParenTokenIndex);
Token lastParenToken = m_unit->tokenAt(lastParenTokenIndex);
if (debug) {
qDebug() << "firstParenToken:" << firstParenToken.spell();
qDebug() << "lastParenToken:" << lastParenToken.spell();
}
// Select the parenthesis of the call, and everything between.
int newPosStart = getTokenStartCursorPosition(firstParenTokenIndex, m_workingCursor);
int newPosEnd = getTokenEndCursorPosition(lastParenTokenIndex, m_workingCursor);
bool isInFunctionName =
m_initialChangeSelectionCursor.position() <= newPosStart;
// If cursor is inside the function name, select the name implicitly (because it's a
// different AST node), and then the whole call expression (so just one step).
// If cursor is inside parentheses, on first step select everything inside them,
// on second step select the everything inside parentheses including them,
// on third step select the whole call expression.
if (currentASTStep() == 1 && !isInFunctionName) {
if (debug)
qDebug() << "Selecting everything inside parentheses.";
positions.astPosStart = newPosStart + 1;
positions.astPosEnd = newPosEnd - 1;
}
if (currentASTStep() == 2 && !isInFunctionName) {
if (debug)
qDebug() << "Selecting everything inside and including "
"the parentheses of the function call.";
positions.astPosStart = newPosStart;
positions.astPosEnd = newPosEnd;
}
} else if (StringLiteralAST *stringLiteralAST = ast->asStringLiteral()) {
// Select literal without quotes on first step, and the whole literal on next step.
if (currentASTStep() == 1) {
Token firstToken = m_unit->tokenAt(stringLiteralAST->firstToken());
bool isRawLiteral = firstToken.f.kind >= T_FIRST_RAW_STRING_LITERAL
&& firstToken.f.kind <= T_RAW_UTF32_STRING_LITERAL;
if (debug && isRawLiteral)
qDebug() << "Is raw literal.";
// Start from positions that include quotes.
int newPosStart = positions.astPosStart;
int newPosEnd = positions.astPosEnd;
// Decrement last position to skip last quote.
--newPosEnd;
// If raw literal also skip parenthesis.
if (isRawLiteral)
--newPosEnd;
// Start position will be the end position minus the size of the actual contents of the
// literal.
newPosStart = newPosEnd - static_cast<int>(firstToken.string->size());
// Skip raw literal parentheses.
if (isRawLiteral)
newPosStart += 2;
positions.astPosStart = newPosStart;
positions.astPosEnd = newPosEnd;
if (debug)
qDebug() << "Selecting inner contents of string literal.";
}
} else if (NumericLiteralAST *numericLiteralAST = ast->asNumericLiteral()) {
Token firstToken = m_unit->tokenAt(numericLiteralAST->firstToken());
// If char literal, select it without quotes on first step.
if (firstToken.isCharLiteral()) {
if (currentASTStep() == 1) {
if (debug)
qDebug() << "Selecting inner contents of char literal.";
int newPosStart = positions.astPosStart;
int newPosEnd = positions.astPosEnd;
newPosEnd = newPosEnd - 1;
newPosStart = newPosEnd - static_cast<int>(firstToken.literal->size());
positions.astPosStart = newPosStart;
positions.astPosEnd = newPosEnd;