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