1
/****************************************************************************
3
** Copyright (C) 2013 Petar Perisin <petar.perisin@gmail.com>
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of Qt Creator.
8
** Commercial License Usage
9
** Licensees holding valid commercial Qt licenses may use this file in
10
** accordance with the commercial license agreement provided with the
11
** Software or, alternatively, in accordance with the terms contained in
12
** a written agreement between you and Digia. For licensing terms and
13
** conditions see http://qt.digia.com/licensing. For further information
14
** use the contact form at http://qt.digia.com/contact-us.
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file. Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
** In addition, as a special exception, Digia gives you certain additional
25
** rights. These rights are described in the Digia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28
****************************************************************************/
30
#include "ansiescapecodehandler.h"
31
#include <utils/qtcassert.h>
36
\class Utils::AnsiEscapeCodeHandler
38
\brief The AnsiEscapeCodeHandler class parses text and extracts ANSI escape codes from it.
40
In order to preserve color information across text segments, an instance of this class
41
must be stored for the lifetime of a stream.
42
Also, one instance of this class should not handle multiple streams (at least not
45
Its main function is parseText(), which accepts text and default QTextCharFormat.
46
This function is designed to parse text and split colored text to smaller strings,
47
with their appropriate formatting information set inside QTextCharFormat.
51
\li Create new instance of AnsiEscapeCodeHandler for a stream.
52
\li To add new text, call parseText() with the text and a default QTextCharFormat.
53
The result of this function is a list of strings with formats set in appropriate
58
AnsiEscapeCodeHandler::AnsiEscapeCodeHandler() :
59
m_previousFormatClosed(true)
63
static QColor ansiColor(uint code)
65
QTC_ASSERT(code < 8, return QColor());
67
const int red = code & 1 ? 170 : 0;
68
const int green = code & 2 ? 170 : 0;
69
const int blue = code & 4 ? 170 : 0;
70
return QColor(red, green, blue);
73
QList<StringFormatPair> AnsiEscapeCodeHandler::parseText(const QString &text,
74
const QTextCharFormat &defaultFormat)
76
QList<StringFormatPair> outputData;
78
QTextCharFormat charFormat = m_previousFormatClosed ? defaultFormat : m_previousFormat;
80
const QString escape = QLatin1String("\x1b[");
81
if (!text.contains(escape)) {
82
outputData << StringFormatPair(text, charFormat);
84
} else if (!text.startsWith(escape)) {
85
outputData << StringFormatPair(text.left(text.indexOf(escape)), charFormat);
88
const QChar semicolon = QLatin1Char(';');
89
const QChar colorTerminator = QLatin1Char('m');
90
// strippedText always starts with "\e["
91
QString strippedText = text.mid(text.indexOf(escape));
92
while (!strippedText.isEmpty()) {
93
while (strippedText.startsWith(escape)) {
94
strippedText.remove(0, 2);
99
while (strippedText.at(0).isDigit() || strippedText.at(0) == semicolon) {
100
if (strippedText.at(0).isDigit()) {
101
strNumber += strippedText.at(0);
103
numbers << strNumber;
106
strippedText.remove(0, 1);
108
if (!strNumber.isEmpty())
109
numbers << strNumber;
111
// remove terminating char
112
if (!strippedText.startsWith(colorTerminator)) {
113
strippedText.remove(0, 1);
116
strippedText.remove(0, 1);
118
if (numbers.isEmpty()) {
119
charFormat = defaultFormat;
123
for (int i = 0; i < numbers.size(); ++i) {
124
const int code = numbers.at(i).toInt();
126
if (code >= TextColorStart && code <= TextColorEnd) {
127
charFormat.setForeground(ansiColor(code - TextColorStart));
128
setFormatScope(charFormat);
129
} else if (code >= BackgroundColorStart && code <= BackgroundColorEnd) {
130
charFormat.setBackground(ansiColor(code - BackgroundColorStart));
131
setFormatScope(charFormat);
135
charFormat = defaultFormat;
139
charFormat.setFontWeight(QFont::Bold);
140
setFormatScope(charFormat);
142
case DefaultTextColor:
143
charFormat.setForeground(defaultFormat.foreground());
144
setFormatScope(charFormat);
146
case DefaultBackgroundColor:
147
charFormat.setBackground(defaultFormat.background());
148
setFormatScope(charFormat);
151
case RgbBackgroundColor:
152
if (++i >= numbers.size())
154
if (numbers.at(i).toInt() == 2) {
155
// RGB set with format: 38;2;<r>;<g>;<b>
156
if ((i + 3) < numbers.size()) {
157
(code == RgbTextColor) ?
158
charFormat.setForeground(QColor(numbers.at(i + 1).toInt(),
159
numbers.at(i + 2).toInt(),
160
numbers.at(i + 3).toInt())) :
161
charFormat.setBackground(QColor(numbers.at(i + 1).toInt(),
162
numbers.at(i + 2).toInt(),
163
numbers.at(i + 3).toInt()));
164
setFormatScope(charFormat);
167
} else if (numbers.at(i).toInt() == 5) {
168
// rgb set with format: 38;5;<i>
169
// unsupported because of unclear documentation, so we just skip <i>
180
if (strippedText.isEmpty())
182
int index = strippedText.indexOf(escape);
184
outputData << StringFormatPair(strippedText.left(index), charFormat);
185
strippedText.remove(0, index);
186
} else if (index == -1) {
187
outputData << StringFormatPair(strippedText, charFormat);
194
void AnsiEscapeCodeHandler::endFormatScope()
196
m_previousFormatClosed = true;
199
void AnsiEscapeCodeHandler::setFormatScope(const QTextCharFormat &charFormat)
201
m_previousFormat = charFormat;
202
m_previousFormatClosed = false;