1 #include "asserts.hpp"
2 #include "code_editor_widget.hpp"
3 #include "decimal.hpp"
4 #include "formatter.hpp"
5 #include "formula_tokenizer.hpp"
6 #include "json_parser.hpp"
7 #include "label.hpp"
8 #include "string_utils.hpp"
9 #include "utility_query.hpp"
10
11 #include <boost/bind.hpp>
12 #include <boost/regex.hpp>
13
14 #include <stack>
15
16 namespace gui
17 {
18
code_editor_widget(int width,int height)19 code_editor_widget::code_editor_widget(int width, int height)
20 : text_editor_widget(width, height),
21 row_slider_(0), begin_col_slider_(0), end_col_slider_(0),
22 slider_decimal_(false), slider_magnitude_(0), is_formula_(false)
23 {
24 set_environment();
25 }
26
code_editor_widget(const variant & v,game_logic::formula_callable * e)27 code_editor_widget::code_editor_widget(const variant& v, game_logic::formula_callable* e)
28 : text_editor_widget(v,e), row_slider_(0), begin_col_slider_(0),
29 end_col_slider_(0), slider_decimal_(false), slider_magnitude_(0), is_formula_(false)
30 {
31 }
32
on_move_cursor(bool auto_shift)33 void code_editor_widget::on_move_cursor(bool auto_shift)
34 {
35 text_editor_widget::on_move_cursor(auto_shift);
36
37 ObjectInfo info = get_current_object();
38 }
39
on_change()40 void code_editor_widget::on_change()
41 {
42 generate_tokens();
43
44 bracket_match_.clear();
45 colors_.clear();
46 colors_.resize(colors_.size()+1);
47 const std::string s = (is_formula_ ? "\"" : "") + text() + (is_formula_ ? "\"" : "");
48 std::string::const_iterator i = s.begin();
49 while(i != s.end()) {
50 if(*i == '"') {
51 std::vector<std::vector<std::pair<int, int> > > opening_brackets;
52
53 if(!is_formula_) {
54 colors_.back().push_back(graphics::color(196, 196, 196));
55 }
56 ++i;
57 std::string::const_iterator end = i;
58 while(end != s.end() && *end != '"') {
59 if(*end == '\\') {
60 ++end;
61 }
62
63 ++end;
64 }
65
66 if(end != s.end()) {
67 while(i != end) {
68 std::string::const_iterator begin = i;
69 try {
70 formula_tokenizer::token t = formula_tokenizer::get_token(i, end);
71
72 bool error_color = false;
73 switch(t.type) {
74 case formula_tokenizer::TOKEN_LPARENS:
75 case formula_tokenizer::TOKEN_LSQUARE:
76 case formula_tokenizer::TOKEN_LBRACKET:
77 opening_brackets.resize(opening_brackets.size()+1);
78 opening_brackets.back().push_back(std::pair<int,int>(colors_.size()-1, colors_.back().size()));
79 break;
80 case formula_tokenizer::TOKEN_RPARENS:
81 case formula_tokenizer::TOKEN_RSQUARE:
82 case formula_tokenizer::TOKEN_RBRACKET:
83 if(opening_brackets.empty()) {
84 error_color = true;
85 } else {
86 opening_brackets.back().push_back(std::pair<int,int>(colors_.size()-1, colors_.back().size()));
87 std::pair<int,int> key(colors_.size()-1, colors_.back().size());
88 for(int n = 0; n != opening_brackets.back().size(); ++n) {
89 bracket_match_[opening_brackets.back()[n]] = opening_brackets.back();
90 }
91 opening_brackets.pop_back();
92 }
93 break;
94 case formula_tokenizer::TOKEN_COMMA:
95 if(opening_brackets.empty() == false) {
96 opening_brackets.back().push_back(std::pair<int,int>(colors_.size()-1, colors_.back().size()));
97 }
98 break;
99 default:
100 break;
101 }
102
103 if(t.type == formula_tokenizer::TOKEN_OPERATOR && util::c_isalpha(*t.begin)) {
104 t.type = formula_tokenizer::TOKEN_KEYWORD;
105
106 }
107
108 while(begin != i) {
109 if(*begin == '\n') {
110 colors_.resize(colors_.size()+1);
111 } else {
112 static const graphics::color TokenColors[] = {
113 graphics::color(128, 128, 255), //operator
114 graphics::color(64, 255, 64), //string literal
115 graphics::color(196, 196, 196), //const identifier
116 graphics::color(255, 255, 255), //identifier
117 graphics::color(255, 196, 196), //integer
118 graphics::color(255, 196, 196), //decimal
119 graphics::color(128, 128, 255), //lparens
120 graphics::color(128, 128, 255), //rparens
121 graphics::color(128, 128, 255), //lsquare
122 graphics::color(128, 128, 255), //rsquare
123 graphics::color(128, 128, 255), //lbracket
124 graphics::color(128, 128, 255), //rbracket
125 graphics::color(128, 128, 255), //comma
126 graphics::color(128, 128, 255), //semi
127 graphics::color(128, 128, 255), //colon
128 graphics::color(255, 255, 255), //whitespace
129 graphics::color(64, 255, 64), //keyword
130 graphics::color(64, 255, 64), //comment
131 graphics::color(255, 255, 255), //pointer
132 };
133 graphics::color col(255, 255, 255);
134 if(t.type >= 0 && t.type < sizeof(TokenColors)/sizeof(TokenColors[0])) {
135 col = TokenColors[t.type];
136 }
137
138 if(error_color) {
139 col = graphics::color(255, 0, 0);
140 }
141
142 colors_.back().push_back(col);
143 }
144 ++begin;
145 }
146
147 } catch(formula_tokenizer::token_error&) {
148 i = begin;
149 break;
150 }
151 }
152
153 for(int n = 0; n != opening_brackets.size(); ++n) {
154 //any remaining brackets that weren't matched can be marked as errors.
155 colors_[opening_brackets[n].front().first][opening_brackets[n].front().second] = graphics::color(255, 0, 0);
156 }
157
158 if(i == end) {
159 colors_.back().push_back(graphics::color(196, 196, 196));
160 i = end + 1;
161 }
162 }
163
164
165 } else if(*i == '\n') {
166 colors_.resize(colors_.size()+1);
167 ++i;
168 } else {
169 colors_.back().push_back(graphics::color(255, 255, 255));
170 ++i;
171 }
172 }
173
174 text_editor_widget::on_change();
175 }
176
get_character_color(int row,int col) const177 graphics::color code_editor_widget::get_character_color(int row, int col) const
178 {
179 std::map<std::pair<int, int>, std::vector<std::pair<int, int> > >::const_iterator itor = bracket_match_.find(std::pair<int,int>(row,col));
180 if(itor != bracket_match_.end()) {
181 for(int n = 0; n != itor->second.size(); ++n) {
182 const int match_row = itor->second[n].first;
183 const int match_col = itor->second[n].second;
184 if(cursor_row() == match_row) {
185 if(cursor_col() == match_col+1 || colors_[match_row].size() == match_col+1 && cursor_col() > match_col+1) {
186 return graphics::color(255, 0, 0);
187 }
188 }
189 }
190 }
191
192 ASSERT_LOG(row >= 0 && row < colors_.size(), "Invalid row: " << row << " /" << colors_.size());
193 ASSERT_LOG(col >= 0 && col < colors_[row].size(), "Invalid col: " << col << " /" << colors_[row].size());
194 return colors_[row][col];
195 }
196
select_token(const std::string & row,int & begin_row,int & end_row,int & begin_col,int & end_col)197 void code_editor_widget::select_token(const std::string& row, int& begin_row, int& end_row, int& begin_col, int& end_col)
198 {
199 std::pair<int,int> key(begin_row, begin_col);
200 if(bracket_match_.count(key)) {
201 begin_row = bracket_match_.find(key)->second.front().first;
202 begin_col = bracket_match_.find(key)->second.front().second;
203 end_row = bracket_match_.find(key)->second.back().first;
204 end_col = bracket_match_.find(key)->second.back().second+1;
205 return;
206 }
207
208 text_editor_widget::select_token(row, begin_row, end_row, begin_col, end_col);
209
210 std::string token(row.begin() + begin_col, row.begin() + end_col);
211
212 boost::regex numeric_regex("-?\\d+(\\.\\d+)?", boost::regex::perl);
213 std::cerr << "token: (" << token << ")\n";
214 if(boost::regex_match(token.c_str(), numeric_regex)) {
215
216 const decimal current_value(decimal::from_string(token));
217 if(current_value <= 10000000 && current_value >= -10000000) {
218 slider_.reset(new slider(200, boost::bind(&code_editor_widget::on_slider_move, this, _1)));
219 slider_decimal_ = std::count(token.begin(), token.end(), '.') ? true : false;
220 slider_magnitude_ = (abs(current_value.as_int())+1) * 5;
221
222 const decimal slider_value = (current_value - decimal::from_int(-slider_magnitude_)) / decimal::from_int(slider_magnitude_*2);
223 slider_->set_position(slider_value.as_float());
224
225 slider_range_.clear();
226 slider_labels_.clear();
227 if(current_value > 0) {
228 slider_range_.push_back(slider_range(0.0, 0.1, -current_value*5, -current_value));
229 slider_range_.push_back(slider_range(0.1, 0.2, -current_value, decimal(0)));
230 slider_range_.push_back(slider_range(0.2, 0.3, decimal(0), current_value));
231 slider_range_.push_back(slider_range(0.3, 0.5, decimal(0), current_value));
232 slider_range_.push_back(slider_range(0.5, 0.7, current_value, 2*current_value));
233 slider_range_.push_back(slider_range(0.7, 0.9, 2*current_value, 5*current_value));
234 slider_range_.push_back(slider_range(0.9, 1.0, 5*current_value, 10*current_value));
235 slider_range_.push_back(slider_range(1.0, 2.0, 10*current_value, 20*current_value));
236 slider_->set_position(0.5);
237 } else {
238 slider_range_.push_back(slider_range(0.0, 0.5, current_value*2, decimal(0)));
239 slider_range_.push_back(slider_range(0.5, 1.0, decimal(0), -current_value*2));
240 slider_range_.push_back(slider_range(1.0, 2.0, -current_value*2, -current_value*4));
241 slider_->set_position(0.25);
242 }
243
244 std::pair<int,int> pos = char_position_on_screen(begin_row, (begin_col+end_col)/2);
245
246 row_slider_ = begin_row;
247 begin_col_slider_ = begin_col;
248 end_col_slider_ = end_col;
249
250 int x = pos.second - slider_->width()/2;
251 int y = pos.first + 20 - slider_->height();
252 if(x < 10) {
253 x = 10;
254 }
255
256 if(x > width() - slider_->width()) {
257 x = width() - slider_->width();
258 }
259
260 if(y < 20) {
261 y += 60;
262 }
263
264 if(y > height() - slider_->height()) {
265 y = height() - slider_->height();
266 }
267
268 slider_->set_loc(x, y);
269
270 foreach(slider_range& r, slider_range_) {
271 slider_labels_.push_back(widget_ptr(new gui::label(formatter() << r.target_begin, 10)));
272 slider_labels_.back()->set_loc(x + slider_->width()*r.begin - slider_labels_.back()->width()/2, y);
273 }
274 }
275 }
276 }
277
on_slider_move(double value)278 void code_editor_widget::on_slider_move(double value)
279 {
280 if(record_op("slider")) {
281 save_undo_state();
282 }
283
284 std::ostringstream s;
285
286 decimal new_value;
287 foreach(const slider_range& r, slider_range_) {
288 if(value <= r.end) {
289 const float pos = (value - r.begin)/(r.end - r.begin);
290 new_value = decimal(r.target_begin.as_float() + (r.target_end.as_float() - r.target_begin.as_float())*pos);
291 break;
292 }
293 }
294
295 if(slider_decimal_) {
296 s << new_value;
297 } else {
298 s << new_value.as_int();
299 }
300
301 std::string new_string = s.str();
302
303 ASSERT_LOG(row_slider_ >= 0 && row_slider_ < get_data().size(), "Illegal row value for slider: " << row_slider_ << " / " << get_data().size());
304 std::string row = get_data()[row_slider_];
305
306 row.erase(row.begin() + begin_col_slider_, row.begin() + end_col_slider_);
307 row.insert(row.begin() + begin_col_slider_, new_string.begin(), new_string.end());
308
309 const int old_end = end_col_slider_;
310 end_col_slider_ = begin_col_slider_ + new_string.size();
311
312 if(cursor_row() == row_slider_ && cursor_col() == old_end) {
313 set_cursor(cursor_row(), end_col_slider_);
314 }
315
316 set_row_contents(row_slider_, row);
317 }
318
handle_draw() const319 void code_editor_widget::handle_draw() const
320 {
321 text_editor_widget::handle_draw();
322
323 if(slider_) {
324 slider_->draw();
325 foreach(widget_ptr w, slider_labels_) {
326 w->draw();
327 }
328 }
329 }
330
handle_event(const SDL_Event & event,bool claimed)331 bool code_editor_widget::handle_event(const SDL_Event& event, bool claimed)
332 {
333 if(slider_) {
334 if(slider_->process_event(event, claimed)) {
335 return true;
336 }
337 }
338
339 if(event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_KEYDOWN) {
340 slider_.reset();
341 }
342
343 return text_editor_widget::handle_event(event, claimed) || claimed;
344 }
345
generate_tokens()346 void code_editor_widget::generate_tokens()
347 {
348 current_text_ = text();
349
350 try {
351 current_obj_ = json::parse(current_text_);
352 } catch(...) {
353 }
354
355 tokens_.clear();
356 const char* begin = current_text_.c_str();
357 const char* end = begin + current_text_.size();
358
359 try {
360 json::Token token = json::get_token(begin, end);
361 while(token.type != json::Token::NUM_TYPES) {
362 tokens_.push_back(token);
363 token = json::get_token(begin, end);
364 }
365 } catch(json::TokenizerError& e) {
366 std::cerr << "Tokenizer error: " << e.msg << "\n";
367 }
368 }
369
370 namespace {
get_map_editing(int row,int col,variant item)371 variant get_map_editing(int row, int col, variant item)
372 {
373 if(!item.get_debug_info()) {
374 return variant();
375 }
376
377 const int begin_row = item.get_debug_info()->line;
378 const int begin_col = item.get_debug_info()->column;
379 const int end_row = item.get_debug_info()->end_line;
380 const int end_col = item.get_debug_info()->end_column;
381
382 typedef text_editor_widget::Loc Loc;
383
384 if(Loc(row,col) < Loc(begin_row,begin_col) ||
385 Loc(row,col) > Loc(end_row,end_col)) {
386 return variant();
387 }
388
389 if(item.is_list()) {
390 foreach(variant v, item.as_list()) {
391 variant result = get_map_editing(row, col, v);
392 if(result.is_null() == false) {
393 return result;
394 }
395 }
396 } else if(item.is_map()) {
397 foreach(const variant_pair& p, item.as_map()) {
398 variant result = get_map_editing(row, col, p.second);
399 if(result.is_null() == false) {
400 return result;
401 }
402 }
403
404 return item;
405 }
406
407 return variant();
408 }
409
410 }
411
get_object_at(int row,int col) const412 code_editor_widget::ObjectInfo code_editor_widget::get_object_at(int row, int col) const
413 {
414 const int pos = row_col_to_text_pos(row, col);
415 const char* ptr = current_text_.c_str() + pos;
416 ASSERT_LOG(pos >= 0 && pos <= current_text_.size(), "Unexpected position in code editor widget: " << pos << " / " << current_text_.size());
417 const json::Token* begin_token = NULL;
418 const json::Token* end_token = NULL;
419 std::stack<const json::Token*> begin_stack;
420 int nbracket = 0;
421 foreach(const json::Token& token, tokens_) {
422 if(token.type == json::Token::TYPE_LCURLY) {
423 begin_stack.push(&token);
424 }
425
426 if(token.type == json::Token::TYPE_RCURLY) {
427 if(begin_stack.empty()) {
428 return ObjectInfo();
429 }
430
431 if(begin_stack.top()->begin <= ptr && token.begin >= ptr) {
432 begin_token = begin_stack.top();
433 end_token = &token;
434 break;
435 } else {
436 begin_stack.pop();
437 }
438 }
439 }
440
441 if(!begin_token || !end_token) {
442 return ObjectInfo();
443 }
444
445 ObjectInfo result;
446 result.begin = begin_token->begin - current_text_.c_str();
447 result.end = end_token->end - current_text_.c_str();
448 result.tokens = std::vector<json::Token>(begin_token, end_token+1);
449 try {
450 result.obj = get_map_editing(row, col, current_obj_);
451 } catch(json::parse_error&) {
452 std::cerr << "json parse error: " << std::string(begin_token->begin, end_token->end) << "\n";
453 return result;
454 }
455
456 return result;
457 }
458
get_current_object() const459 code_editor_widget::ObjectInfo code_editor_widget::get_current_object() const
460 {
461 return get_object_at(cursor_row(), cursor_col());
462 }
463
set_highlight_current_object(bool value)464 void code_editor_widget::set_highlight_current_object(bool value)
465 {
466 if(!value) {
467 clear_highlight_lines();
468 return;
469 }
470
471 ObjectInfo info = get_current_object();
472 if(info.obj.is_null() == false) {
473 set_highlight_lines(text_pos_to_row_col(info.begin).first,
474 text_pos_to_row_col(info.end).first);
475 } else {
476 clear_highlight_lines();
477 }
478 }
479
modify_current_object(variant new_obj)480 void code_editor_widget::modify_current_object(variant new_obj)
481 {
482 ObjectInfo info = get_current_object();
483 if(info.obj.is_null() || info.tokens.empty()) {
484 return;
485 }
486
487 save_undo_state();
488
489
490 const std::string str(current_text_.begin() + info.begin, current_text_.begin() + info.end);
491
492 //calculate the indentation this object has based on the first attribute.
493 std::string indent;
494 std::string::const_iterator end_line = std::find(str.begin(), str.end(), '\n');
495 if(end_line != str.end()) {
496 ++end_line;
497 std::string::const_iterator end_indent = end_line;
498 while(end_indent != str.end() && util::c_isspace(*end_indent)) {
499 if(*end_indent == '\n') {
500 end_line = end_indent+1;
501 }
502 ++end_indent;
503 }
504
505 indent = std::string(end_line, end_indent);
506 }
507
508 const std::string new_str = modify_variant_text(str, info.obj, new_obj, info.obj.get_debug_info()->line, info.obj.get_debug_info()->column, indent);
509 current_text_ = std::string(current_text_.begin(), current_text_.begin() + info.begin) + new_str + std::string(current_text_.begin() + info.end, current_text_.end());
510 set_text(current_text_, false /*don't move cursor*/);
511 }
512
513
514 }
515