qtoutputformatter.cpp 12 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** 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 Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29

dt's avatar
dt committed
30
#include "qtoutputformatter.h"
31

32
#include <coreplugin/editormanager/editormanager.h>
33
#include <projectexplorer/project.h>
34

35
#include <QPlainTextEdit>
36
#include <QTextCursor>
37
#include <QUrl>
38 39

using namespace ProjectExplorer;
40
using namespace QtSupport;
41

42 43 44 45 46
// "file" or "qrc", colon, optional '//', '/' and further characters
#define QML_URL_REGEXP \
    "(?:file|qrc):(?://)?/.+"


47
QtOutputFormatter::QtOutputFormatter(ProjectExplorer::Project *project)
dt's avatar
dt committed
48
    : OutputFormatter()
49
    , m_qmlError(QLatin1String("^(" QML_URL_REGEXP    // url
50 51
                               ":\\d+"           // colon, line
                               "(?::\\d+)?)"     // colon, column (optional)
52
                               "[: \t]"))        // colon, space or tab
dt's avatar
dt committed
53
    , m_qtError(QLatin1String("Object::.*in (.*:\\d+)"))
54
    , m_qtAssert(QLatin1String("ASSERT: .* in file (.+, line \\d+)"))
55
    , m_qtAssertX(QLatin1String("ASSERT failure in .*: \".*\", file (.+, line \\d+)"))
56
    , m_qtTestFail(QLatin1String("^   Loc: \\[(.*)\\]"))
dt's avatar
dt committed
57
    , m_project(project)
58
{
59
    if (project) {
60
        m_projectFinder.setProjectFiles(project->files(Project::ExcludeGeneratedFiles));
61
        m_projectFinder.setProjectDirectory(project->projectDirectory().toString());
62 63 64 65

        connect(project, SIGNAL(fileListChanged()),
                this, SLOT(updateProjectFileList()));
    }
66 67
}

68
LinkResult QtOutputFormatter::matchLine(const QString &line) const
69
{
70 71 72
    LinkResult lr;
    lr.start = -1;
    lr.end = -1;
73

74 75 76 77 78 79 80 81
    if (m_qmlError.indexIn(line) != -1) {
        lr.href = m_qmlError.cap(1);
        lr.start = m_qmlError.pos(1);
        lr.end = lr.start + lr.href.length();
    } else if (m_qtError.indexIn(line) != -1) {
        lr.href = m_qtError.cap(1);
        lr.start = m_qtError.pos(1);
        lr.end = lr.start + lr.href.length();
82 83 84 85
    } else if (m_qtAssert.indexIn(line) != -1) {
        lr.href = m_qtAssert.cap(1);
        lr.start = m_qtAssert.pos(1);
        lr.end = lr.start + lr.href.length();
86 87 88 89
    } else if (m_qtAssertX.indexIn(line) != -1) {
        lr.href = m_qtAssertX.cap(1);
        lr.start = m_qtAssertX.pos(1);
        lr.end = lr.start + lr.href.length();
90 91 92 93
    } else if (m_qtTestFail.indexIn(line) != -1) {
        lr.href = m_qtTestFail.cap(1);
        lr.start = m_qtTestFail.pos(1);
        lr.end = lr.start + lr.href.length();
94
    }
95 96 97
    return lr;
}

con's avatar
con committed
98
void QtOutputFormatter::appendMessage(const QString &txt, Utils::OutputFormat format)
99
{
100 101 102 103
    QTextCursor cursor(plainTextEdit()->document());
    cursor.movePosition(QTextCursor::End);
    cursor.beginEditBlock();

104
    QString deferredText;
105

106 107 108 109 110 111 112 113 114 115 116
    int start = 0;
    int pos = txt.indexOf(QLatin1Char('\n'));
    while (pos != -1) {
        // Line identified
        if (!m_lastLine.isEmpty()) {
            // Line continuation
            const QString newPart = txt.mid(start, pos - start + 1);
            const QString line = m_lastLine + newPart;
            LinkResult lr = matchLine(line);
            if (!lr.href.isEmpty()) {
                // Found something && line continuation
117 118
                cursor.insertText(deferredText, charFormat(format));
                deferredText.clear();
119
                clearLastLine();
120
                appendLine(cursor, lr, line, format);
121 122
            } else {
                // Found nothing, just emit the new part
123
                deferredText += newPart;
124 125 126 127 128 129 130
            }
            // Handled line continuation
            m_lastLine.clear();
        } else {
            const QString line = txt.mid(start, pos - start + 1);
            LinkResult lr = matchLine(line);
            if (!lr.href.isEmpty()) {
131 132
                cursor.insertText(deferredText, charFormat(format));
                deferredText.clear();
133
                appendLine(cursor, lr, line, format);
134
            } else {
135
                deferredText += line;
136 137 138 139 140 141 142 143 144 145 146
            }
        }
        start = pos + 1;
        pos = txt.indexOf(QLatin1Char('\n'), start);
    }

    // Handle left over stuff
    if (start < txt.length()) {
        if (!m_lastLine.isEmpty()) {
            // Line continuation
            const QString newPart = txt.mid(start);
147 148
            const QString line = m_lastLine + newPart;
            LinkResult lr = matchLine(line);
149 150
            if (!lr.href.isEmpty()) {
                // Found something && line continuation
151 152
                cursor.insertText(deferredText, charFormat(format));
                deferredText.clear();
153
                clearLastLine();
154
                appendLine(cursor, lr, line, format);
155 156
            } else {
                // Found nothing, just emit the new part
157
                deferredText += newPart;
158
            }
159
            m_lastLine = line;
160 161 162 163
        } else {
            m_lastLine = txt.mid(start);
            LinkResult lr = matchLine(m_lastLine);
            if (!lr.href.isEmpty()) {
164 165
                cursor.insertText(deferredText, charFormat(format));
                deferredText.clear();
166
                appendLine(cursor, lr, m_lastLine, format);
167
            } else {
168
                deferredText += m_lastLine;
169 170 171
            }
        }
    }
172
    cursor.insertText(deferredText, charFormat(format));
173
    cursor.endEditBlock();
174 175
}

176
void QtOutputFormatter::appendLine(QTextCursor &cursor, const LinkResult &lr,
con's avatar
con committed
177
    const QString &line, Utils::OutputFormat format)
178
{
179
    const QTextCharFormat normalFormat = charFormat(format);
180 181 182 183 184 185 186 187 188 189
    cursor.insertText(line.left(lr.start), normalFormat);

    QTextCharFormat linkFormat = normalFormat;
    const QColor textColor = plainTextEdit()->palette().color(QPalette::Text);
    linkFormat.setForeground(mixColors(textColor, QColor(Qt::blue)));
    linkFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
    linkFormat.setAnchor(true);
    linkFormat.setAnchorHref(lr.href);
    cursor.insertText(line.mid(lr.start, lr.end - lr.start), linkFormat);
    cursor.insertText(line.mid(lr.end), normalFormat);
dt's avatar
dt committed
190
}
191

dt's avatar
dt committed
192 193
void QtOutputFormatter::handleLink(const QString &href)
{
194
    if (!href.isEmpty()) {
195 196 197
        QRegExp qmlLineColumnLink(QLatin1String("^(" QML_URL_REGEXP ")" // url
                                                ":(\\d+)"               // line
                                                ":(\\d+)$"));           // column
198 199

        if (qmlLineColumnLink.indexIn(href) != -1) {
200
            const QUrl fileUrl = QUrl(qmlLineColumnLink.cap(1));
201 202
            const int line = qmlLineColumnLink.cap(2).toInt();
            const int column = qmlLineColumnLink.cap(3).toInt();
203

204
            openEditor(m_projectFinder.findFile(fileUrl), line, column - 1);
dt's avatar
dt committed
205

dt's avatar
dt committed
206
            return;
207 208
        }

209
        QRegExp qmlLineLink(QLatin1String("^(" QML_URL_REGEXP ")" // url
210
                                          ":(\\d+)$"));  // line
211 212

        if (qmlLineLink.indexIn(href) != -1) {
213
            const QUrl fileUrl = QUrl(qmlLineLink.cap(1));
214
            const int line = qmlLineLink.cap(2).toInt();
215
            openEditor(m_projectFinder.findFile(m_projectFinder.findFile(fileUrl)), line);
216 217 218
            return;
        }

219 220 221
        QString fileName;
        int line = -1;

dt's avatar
dt committed
222
        QRegExp qtErrorLink(QLatin1String("^(.*):(\\d+)$"));
223 224 225 226 227 228 229 230 231 232 233
        if (qtErrorLink.indexIn(href) != -1) {
            fileName = qtErrorLink.cap(1);
            line = qtErrorLink.cap(2).toInt();
        }

        QRegExp qtAssertLink(QLatin1String("^(.+), line (\\d+)$"));
        if (qtAssertLink.indexIn(href) != -1) {
            fileName = qtAssertLink.cap(1);
            line = qtAssertLink.cap(2).toInt();
        }

234 235 236 237 238 239
        QRegExp qtTestFailLink(QLatin1String("^(.*)\\((\\d+)\\)$"));
        if (qtTestFailLink.indexIn(href) != -1) {
            fileName = qtTestFailLink.cap(1);
            line = qtTestFailLink.cap(2).toInt();
        }

240
        if (!fileName.isEmpty()) {
241
            fileName = m_projectFinder.findFile(QUrl::fromLocalFile(fileName));
242
            openEditor(fileName, line);
dt's avatar
dt committed
243 244 245
            return;
        }
    }
246
}
247

248 249 250 251 252 253
void QtOutputFormatter::clearLastLine()
{
    OutputFormatter::clearLastLine();
    m_lastLine.clear();
}

254 255 256 257 258
void QtOutputFormatter::openEditor(const QString &fileName, int line, int column)
{
    Core::EditorManager::openEditorAt(fileName, line, column);
}

259 260 261
void QtOutputFormatter::updateProjectFileList()
{
    if (m_project)
262
        m_projectFinder.setProjectFiles(m_project.data()->files(Project::ExcludeGeneratedFiles));
263
}
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

// Unit tests:

#ifdef WITH_TESTS

#   include <QTest>

#   include "qtsupportplugin.h"

using namespace QtSupport::Internal;

class TestQtOutputFormatter : public QtOutputFormatter
{
public:
    TestQtOutputFormatter() :
        QtOutputFormatter(0),
        line(-1),
        column(-1)
    {
    }

    void openEditor(const QString &fileName, int line, int column = -1)
    {
        this->fileName = fileName;
        this->line = line;
        this->column = column;
    }

public:
    QString fileName;
    int line;
    int column;
};


void QtSupportPlugin::testQtOutputFormatter_data()
{
    QTest::addColumn<QString>("input");

    // matchLine results
    QTest::addColumn<int>("linkStart");
    QTest::addColumn<int>("linkEnd");
    QTest::addColumn<QString>("href");

    // handleLink results
    QTest::addColumn<QString>("file");
    QTest::addColumn<int>("line");
    QTest::addColumn<int>("column");

    QTest::newRow("pass through")
            << QString::fromLatin1("Pass through plain text.")
            << -1 << -1 << QString()
            << QString() << -1 << -1;

318 319 320 321 322
    QTest::newRow("qrc:/main.qml:20")
            << QString::fromLatin1("qrc:/main.qml:20 Unexpected token `identifier'")
            << 0 << 16 << QString::fromLatin1("qrc:/main.qml:20")
            << QString::fromLatin1("/main.qml") << 20 << -1;

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
    QTest::newRow("qrc:///main.qml:20")
            << QString::fromLatin1("qrc:///main.qml:20 Unexpected token `identifier'")
            << 0 << 18 << QString::fromLatin1("qrc:///main.qml:20")
            << QString::fromLatin1("/main.qml") << 20 << -1;

    QTest::newRow("file:///main.qml:20")
            << QString::fromLatin1("file:///main.qml:20 Unexpected token `identifier'")
            << 0 << 19 << QString::fromLatin1("file:///main.qml:20")
            << QString::fromLatin1("/main.qml") << 20 << -1;
}

void QtSupportPlugin::testQtOutputFormatter()
{
    QFETCH(QString, input);

    QFETCH(int, linkStart);
    QFETCH(int, linkEnd);
    QFETCH(QString, href);

    QFETCH(QString, file);
    QFETCH(int, line);
    QFETCH(int, column);

    TestQtOutputFormatter formatter;

    LinkResult result = formatter.matchLine(input);
    formatter.handleLink(result.href);

    QCOMPARE(result.start, linkStart);
    QCOMPARE(result.end, linkEnd);
    QCOMPARE(result.href, href);

    QCOMPARE(formatter.fileName, file);
    QCOMPARE(formatter.line, line);
    QCOMPARE(formatter.column, column);
}

#endif // WITH_TESTS