diff --git a/client/src/terminal.cpp b/client/src/terminal.cpp index f072486..5c7fe3b 100644 --- a/client/src/terminal.cpp +++ b/client/src/terminal.cpp @@ -8,6 +8,7 @@ #include "terminal.h" #include "game_state.h" #include "gfx/types.h" +#include "ui/editor.h" #include "ui/window_action.h" Terminal::Terminal(GameState* game_state) @@ -179,7 +180,8 @@ void Terminal::render(const RenderContext& context, int x, int y_screen, int y_g /* Render text view for the input right after prompt. */ float input_x_pos = x + padding + (prompt_str.length() * 8.5f); /* Estimate width */ float input_width = width - (input_x_pos-x); - _input_view->render_text_content(context.ui_renderer, input_x_pos, prompt_line_y, input_width, line_height); + SyntaxTheme theme; /* Terminal doesn't need highlighting, just a default theme. */ + _input_view->render_text_content(context.ui_renderer, theme, input_x_pos, prompt_line_y, input_width, line_height); context.ui_renderer->flush_text(); @@ -188,7 +190,7 @@ void Terminal::render(const RenderContext& context, int x, int y_screen, int y_g if(context.show_cursor) { context.ui_renderer->begin_shapes(); - _input_view->render_cursor(context.ui_renderer, input_x_pos, prompt_line_y, input_width, line_height); + _input_view->render_cursor(context.ui_renderer, theme, input_x_pos, prompt_line_y, input_width, line_height); context.ui_renderer->flush_shapes(); } } diff --git a/client/src/ui/editor.cpp b/client/src/ui/editor.cpp index 702ab8e..093beca 100644 --- a/client/src/ui/editor.cpp +++ b/client/src/ui/editor.cpp @@ -1,5 +1,7 @@ #include "editor.h" #include + +#include "text_view.h" #include "gfx/types.h" #include "ui/window_action.h" @@ -59,13 +61,13 @@ void Editor::render(const RenderContext& context, int x, int y_screen, int y_gl, /* Pass 2: Main text view. */ context.ui_renderer->begin_text(); - _view->render_text_content(context.ui_renderer, x, content_y, width, content_height); + _view->render_text_content(context.ui_renderer, _theme, x, content_y, width, content_height); context.ui_renderer->flush_text(); /* Pass 3: Editor cursor. */ if(context.show_cursor) { context.ui_renderer->begin_shapes(); - _view->render_cursor(context.ui_renderer, x, content_y, width, content_height); + _view->render_cursor(context.ui_renderer, _theme, x, content_y, width, content_height); context.ui_renderer->flush_shapes(); } diff --git a/client/src/ui/editor.h b/client/src/ui/editor.h index 1e32a2b..a7efd15 100644 --- a/client/src/ui/editor.h +++ b/client/src/ui/editor.h @@ -5,10 +5,20 @@ #include "gfx/types.h" #include "i_window_content.h" #include "ui/text_buffer.h" -#include "text_view.h" #include "ui/window_action.h" #include "ui/menu_bar.h" +class TextView; + +struct SyntaxTheme { + Color normal = { 1.0f, 1.0f, 1.0f }; + Color keyword = { 0.8f, 0.6f, 1.0f }; + Color string = { 1.0f, 0.8f, 0.6f }; + Color number = { 0.6f, 1.0f, 0.8f }; + Color comment = { 0.6f, 0.6f, 0.6f }; + Color line_num = { 0.5f, 0.6f, 0.7f }; +}; + class Editor : public IWindowContent { public: Editor(void); @@ -31,5 +41,6 @@ private: std::unique_ptr _menu_bar; bool _should_close; WindowAction _pending_action; + SyntaxTheme _theme; std::string _filename; }; diff --git a/client/src/ui/text_view.cpp b/client/src/ui/text_view.cpp index 0921ea4..a28fda8 100644 --- a/client/src/ui/text_view.cpp +++ b/client/src/ui/text_view.cpp @@ -3,6 +3,7 @@ #include "text_view.h" #include "gfx/types.h" +#include "ui/editor.h" #include "ui/text_buffer.h" #include "ui/ui_renderer.h" @@ -10,7 +11,14 @@ TextView::TextView(TextBuffer* buffer, bool handle_ret, bool show_line_numbers) _buffer(buffer), _scroll_offset(0), _handle_ret(handle_ret), - _show_line_numbers(show_line_numbers) {} + _show_line_numbers(show_line_numbers) { + + _lua_keywords = { + "and", "break", "do", "else", "elseif", "end", "false", "for", + "function", "if", "in", "local", "nil", "not", "or", "repeat", + "return", "then", "true", "until", "while" + }; +} TextView::~TextView(void) {} @@ -75,11 +83,77 @@ void TextView::scroll(int amount, int content_height) { } } -void TextView::render_text_content(UIRenderer* ui_renderer, int x, int y, int width, int height) { - if(!_buffer) return; +std::vector TextView::_tokenize_line(const std::string& line) { + std::vector tokens; + std::string current_token_text; + TokenType current_token_type = TokenType::NORMAL; - const Color text_color = { 1.0f, 1.0f, 1.0f }; - const Color line_num_color = { 0.5f, 0.6f, 0.7f }; + for(size_t i = 0; i < line.length(); ++i) { + char c = line[i]; + + /* Check for comments. */ + if(i + 1 < line.length() && c == '-' && line[i+1] == '-') { + if(!current_token_text.empty()) { + tokens.push_back({current_token_type, current_token_text}); + } + tokens.push_back({TokenType::COMMENT, line.substr(i)}); + return tokens; + } + + /* Check for strings. */ + if(c == '"') { + if(!current_token_text.empty()) { + tokens.push_back({current_token_type, current_token_text}); + } + size_t end_quote = line.find('"', i+1); + if(end_quote == std::string::npos) { + tokens.push_back({TokenType::STRING, line.substr(i)}); + return tokens; + } + tokens.push_back({TokenType::STRING, line.substr(i, end_quote - i+1)}); + i = end_quote; + current_token_text = ""; + continue; + } + + /* Check for numbers. */ + if(isdigit(c) && (current_token_text.empty() || current_token_type == TokenType::NUMBER)) { + current_token_type = TokenType::NUMBER; + current_token_text += c; + } else if(isalnum(c) || c == '_') { /* Keywords and identifiers. */ + if(current_token_type != TokenType::NORMAL) { + tokens.push_back({current_token_type, current_token_text}); + current_token_text = ""; + } + current_token_type = TokenType::NORMAL; + current_token_text += c; + } else { /* Delimiters. */ + if(!current_token_text.empty()) { + if(_lua_keywords.count(current_token_text)) { + tokens.push_back({TokenType::KEYWORD, current_token_text}); + } else { + tokens.push_back({current_token_type, current_token_text}); + } + current_token_text = ""; + } + tokens.push_back({TokenType::NORMAL, std::string(1, c)}); + current_token_type = TokenType::NORMAL; + } + } + + if(!current_token_text.empty()) { + if(_lua_keywords.count(current_token_text)) { + tokens.push_back({TokenType::KEYWORD, current_token_text}); + } else { + tokens.push_back({current_token_type, current_token_text}); + } + } + return tokens; +} + +void TextView::render_text_content(UIRenderer* ui_renderer, const SyntaxTheme& theme, + int x, int y, int width, int height) { + if(!_buffer) return; const float line_height = 20.0f; /* TODO: Get font metrics? */ const float padding = 5.0f; @@ -102,18 +176,29 @@ void TextView::render_text_content(UIRenderer* ui_renderer, int x, int y, int wi float line_num_text_width = ui_renderer->get_text_renderer()->get_text_width(line_num_str.c_str(), 1.0f); ui_renderer->render_text(line_num_str.c_str(), x+padding+(gutter_width-line_num_text_width-10), - current_y+18, line_num_color); + current_y+18, theme.line_num); } - /* Render line content. */ - std::string line = _buffer->get_line(i); - ui_renderer->render_text(line.c_str(), x+padding+gutter_width, current_y+18, text_color); + float current_x = x+padding + gutter_width; + std::vector tokens = _tokenize_line(_buffer->get_line(i)); + for(const auto& token : tokens) { + Color color = theme.normal; + switch(token.type) { + case TokenType::KEYWORD: color = theme.keyword; break; + case TokenType::STRING: color = theme.string; break; + case TokenType::NUMBER: color = theme.number; break; + case TokenType::COMMENT: color = theme.comment; break; + default: break; + } + ui_renderer->render_text(token.text.c_str(), current_x, current_y+18, color); + current_x += ui_renderer->get_text_renderer()->get_text_width(token.text.c_str(), 1.0f); + } current_y += line_height; } } -void TextView::render_cursor(UIRenderer* ui_renderer, int x, int y, int width, int height) { - const Color text_color = { 1.0f, 1.0f, 1.0f }; +void TextView::render_cursor(UIRenderer* ui_renderer, const SyntaxTheme& theme, + int x, int y, int width, int height) { const float line_height = 20.0f; /* TODO: Get font metrics? */ const float padding = 5.0f; const float gutter_width = _show_line_numbers ? 40.0f : 0.0f; @@ -124,6 +209,6 @@ void TextView::render_cursor(UIRenderer* ui_renderer, int x, int y, int width, i float cursor_x = x + padding + gutter_width + text_width; float cursor_y = y + (cursor_pos.row - _scroll_offset) * line_height; if(cursor_y >= y && cursor_y < y + height) { - ui_renderer->draw_rect(cursor_x, cursor_y, 2, line_height, text_color); + ui_renderer->draw_rect(cursor_x, cursor_y, 2, line_height, theme.normal); } } diff --git a/client/src/ui/text_view.h b/client/src/ui/text_view.h index 419b62a..09775d3 100644 --- a/client/src/ui/text_view.h +++ b/client/src/ui/text_view.h @@ -1,12 +1,29 @@ #pragma once #include +#include +#include +#include #include "ui/text_buffer.h" #include "ui/ui_renderer.h" +#include "ui/editor.h" /* For SyntaxTheme */ class TextRenderer; +enum class TokenType { + NORMAL, + KEYWORD, + STRING, + NUMBER, + COMMENT +}; + +struct Token { + TokenType type; + std::string text; +}; + class TextView { public: TextView(TextBuffer* buffer, bool handle_ret, bool show_line_numbers); @@ -14,12 +31,17 @@ public: bool handle_event(SDL_Event* event); void scroll(int amount, int content_height); - void render_text_content(UIRenderer* ui_renderer, int x, int y, int width, int height); - void render_cursor(UIRenderer* ui_renderer, int x, int y, int width, int height); + void render_text_content(UIRenderer* ui_renderer, const SyntaxTheme& theme, + int x, int y, int width, int height); + void render_cursor(UIRenderer* ui_renderer, const SyntaxTheme& theme, + int x, int y, int width, int height); private: + std::vector _tokenize_line(const std::string& line); + TextBuffer* _buffer; int _scroll_offset; bool _handle_ret; bool _show_line_numbers; + std::unordered_set _lua_keywords; };