[Add] Lua syntax highlighting to the code editor.
This commit is contained in:
		
							parent
							
								
									cb7c5d8a95
								
							
						
					
					
						commit
						420b570902
					
				@ -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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
#include "editor.h"
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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<MenuBar> _menu_bar;
 | 
			
		||||
  bool _should_close;
 | 
			
		||||
  WindowAction _pending_action;
 | 
			
		||||
  SyntaxTheme _theme;
 | 
			
		||||
  std::string _filename;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -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<Token> TextView::_tokenize_line(const std::string& line) {
 | 
			
		||||
  std::vector<Token> 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<Token> 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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,29 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <SDL3/SDL_events.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <unordered_set>
 | 
			
		||||
 | 
			
		||||
#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<Token> _tokenize_line(const std::string& line);
 | 
			
		||||
 | 
			
		||||
  TextBuffer* _buffer;
 | 
			
		||||
  int _scroll_offset;
 | 
			
		||||
  bool _handle_ret;
 | 
			
		||||
  bool _show_line_numbers;
 | 
			
		||||
  std::unordered_set<std::string> _lua_keywords;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user