1 /**
2  * Copyright (c) 2013, Timothy Stack
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * * Redistributions of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  * * Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  * * Neither the name of Timothy Stack nor the names of its contributors
15  * may be used to endorse or promote products derived from this software
16  * without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * @file ansi_scrubber.cc
30  */
31 
32 #include "config.h"
33 
34 #include <algorithm>
35 
36 #include "base/opt_util.hh"
37 #include "view_curses.hh"
38 #include "pcrepp/pcrepp.hh"
39 #include "ansi_scrubber.hh"
40 
41 using namespace std;
42 
ansi_regex()43 static pcrepp &ansi_regex()
44 {
45     static pcrepp retval("\x1b\\[([\\d=;\\?]*)([a-zA-Z])");
46 
47     return retval;
48 }
49 
scrub_ansi_string(std::string & str,string_attrs_t & sa)50 void scrub_ansi_string(std::string &str, string_attrs_t &sa)
51 {
52     pcre_context_static<60> context;
53     pcrepp &   regex = ansi_regex();
54     pcre_input pi(str);
55 
56     replace(str.begin(), str.end(), '\0', ' ');
57     while (regex.match(context, pi)) {
58         pcre_context::capture_t *caps = context.all();
59         struct line_range        lr;
60         bool has_attrs = false;
61         attr_t attrs   = 0;
62         auto bg = nonstd::optional<int>();
63         auto fg = nonstd::optional<int>();
64         auto role = nonstd::optional<int>();
65         size_t lpc;
66 
67         switch (pi.get_substr_start(&caps[2])[0]) {
68             case 'm':
69                 for (lpc = caps[1].c_begin;
70                      lpc != string::npos && lpc < (size_t) caps[1].c_end;) {
71                     int ansi_code = 0;
72 
73                     if (sscanf(&(str[lpc]), "%d", &ansi_code) == 1) {
74                         if (90 <= ansi_code && ansi_code <= 97) {
75                             ansi_code -= 60;
76                             attrs |= A_STANDOUT;
77                         }
78                         if (30 <= ansi_code && ansi_code <= 37) {
79                             fg = ansi_code - 30;
80                         }
81                         if (40 <= ansi_code && ansi_code <= 47) {
82                             bg = ansi_code - 40;
83                         }
84                         switch (ansi_code) {
85                             case 1:
86                                 attrs |= A_BOLD;
87                                 break;
88 
89                             case 2:
90                                 attrs |= A_DIM;
91                                 break;
92 
93                             case 4:
94                                 attrs |= A_UNDERLINE;
95                                 break;
96 
97                             case 7:
98                                 attrs |= A_REVERSE;
99                                 break;
100                         }
101                     }
102                     lpc = str.find(';', lpc);
103                     if (lpc != string::npos) {
104                         lpc += 1;
105                     }
106                 }
107                 has_attrs = true;
108                 break;
109 
110             case 'C': {
111                 unsigned int spaces = 0;
112 
113                 if (sscanf(&(str[caps[1].c_begin]), "%u", &spaces) == 1 &&
114                     spaces > 0) {
115                     str.insert((unsigned long) caps[0].c_end, spaces, ' ');
116                 }
117                 break;
118             }
119 
120             case 'H': {
121                 unsigned int row = 0, spaces = 0;
122 
123                 if (sscanf(&(str[caps[1].c_begin]), "%u;%u", &row, &spaces) == 2 &&
124                     spaces > 1) {
125                     int ispaces = spaces - 1;
126                     if (ispaces > caps[0].c_begin) {
127                         str.insert((unsigned long) caps[0].c_end, ispaces - caps[0].c_begin, ' ');
128                     }
129                 }
130                 break;
131             }
132 
133             case 'O': {
134                 int role_int;
135 
136                 if (sscanf(&(str[caps[1].c_begin]), "%d", &role_int) == 1) {
137                     if (role_int >= 0 && role_int < view_colors::VCR__MAX) {
138                         role = role_int;
139                         has_attrs = true;
140                     }
141                 }
142                 break;
143             }
144         }
145         str.erase(str.begin() + caps[0].c_begin,
146                   str.begin() + caps[0].c_end);
147 
148         if (has_attrs) {
149             for (auto rit = sa.rbegin(); rit != sa.rend(); rit++) {
150                 if (rit->sa_range.lr_end != -1) {
151                     break;
152                 }
153                 rit->sa_range.lr_end = caps[0].c_begin;
154             }
155             lr.lr_start = caps[0].c_begin;
156             lr.lr_end   = -1;
157             if (attrs) {
158                 sa.emplace_back(lr, &view_curses::VC_STYLE, attrs);
159             }
160             role | [&lr, &sa](int r) {
161                 sa.emplace_back(lr, &view_curses::VC_ROLE, r);
162             };
163             fg | [&lr, &sa](int color) {
164                 sa.emplace_back(lr, &view_curses::VC_FOREGROUND, color);
165             };
166             bg | [&lr, &sa](int color) {
167                 sa.emplace_back(lr, &view_curses::VC_BACKGROUND, color);
168             };
169         }
170 
171         pi.reset(str);
172     }
173 }
174 
add_ansi_vars(std::map<std::string,std::string> & vars)175 void add_ansi_vars(std::map<std::string, std::string> &vars)
176 {
177     vars["ansi_csi"] = ANSI_CSI;
178     vars["ansi_norm"] = ANSI_NORM;
179     vars["ansi_bold"] = ANSI_BOLD_START;
180     vars["ansi_underline"] = ANSI_UNDERLINE_START;
181     vars["ansi_black"] = ANSI_COLOR(COLOR_BLACK);
182     vars["ansi_red"] = ANSI_COLOR(COLOR_RED);
183     vars["ansi_green"] = ANSI_COLOR(COLOR_GREEN);
184     vars["ansi_yellow"] = ANSI_COLOR(COLOR_YELLOW);
185     vars["ansi_blue"] = ANSI_COLOR(COLOR_BLUE);
186     vars["ansi_magenta"] = ANSI_COLOR(COLOR_MAGENTA);
187     vars["ansi_cyan"] = ANSI_COLOR(COLOR_CYAN);
188     vars["ansi_white"] = ANSI_COLOR(COLOR_WHITE);
189 }
190