add syntax highlighting (WIP)

This commit is contained in:
MihailRis 2024-12-04 22:13:17 +03:00
parent ce1e9f76cf
commit ed3865964b
16 changed files with 168 additions and 43 deletions

View File

@ -32,10 +32,9 @@
autoresize='true' autoresize='true'
margin='0' margin='0'
padding='5' padding='5'
editable='false'
multiline='true' multiline='true'
line-numbers='true' line-numbers='true'
text-color="#FFFFFFA0" syntax='lua'
size-func="gui.get_viewport()[1]-350,40" size-func="gui.get_viewport()[1]-350,40"
gravity="top-left" gravity="top-left"
text-wrap='false' text-wrap='false'

View File

@ -214,10 +214,14 @@ std::string_view BasicParser::readUntil(char c) {
return source.substr(start, pos - start); return source.substr(start, pos - start);
} }
std::string_view BasicParser::readUntil(std::string_view s) { std::string_view BasicParser::readUntil(std::string_view s, bool nothrow) {
int start = pos; int start = pos;
size_t found = source.find(s, pos); size_t found = source.find(s, pos);
if (found == std::string::npos) { if (found == std::string::npos) {
if (nothrow) {
pos = source.size();
return source.substr(start);
}
throw error(util::quote(std::string(s))+" expected"); throw error(util::quote(std::string(s))+" expected");
} }
skip(found - pos); skip(found - pos);

View File

@ -105,7 +105,7 @@ protected:
parsing_error error(const std::string& message); parsing_error error(const std::string& message);
public: public:
std::string_view readUntil(char c); std::string_view readUntil(char c);
std::string_view readUntil(std::string_view s); std::string_view readUntil(std::string_view s, bool nothrow);
std::string_view readUntilWhitespace(); std::string_view readUntilWhitespace();
std::string_view readUntilEOL(); std::string_view readUntilEOL();
std::string parseName(); std::string parseName();

View File

@ -119,24 +119,28 @@ public:
); );
continue; continue;
} else if (is_digit(c)) { } else if (is_digit(c)) {
auto value = parseNumber(1); dv::value value;
auto tag = TokenTag::UNEXPECTED;
try {
value = parseNumber(1);
tag = value.isInteger() ? TokenTag::INTEGER
: TokenTag::NUMBER;
} catch (const parsing_error& err) {}
auto literal = source.substr(start.pos, pos - start.pos); auto literal = source.substr(start.pos, pos - start.pos);
emitToken( emitToken(tag, std::string(literal), start);
value.isInteger() ? TokenTag::INTEGER : TokenTag::NUMBER,
std::string(literal),
start
);
continue; continue;
} }
switch (c) { switch (c) {
case '(': case '[': case '{': case '(': case '[': case '{':
if (isNext("[==[")) { if (isNext("[==[")) {
readUntil("]==]"); auto string = readUntil("]==]", true);
skip(4); skip(4);
emitToken(TokenTag::COMMENT, std::string(string)+"]==]", start);
continue; continue;
} else if (isNext("[[")) { } else if (isNext("[[")) {
skip(2); skip(2);
auto string = readUntil("]]"); auto string = readUntil("]]", true);
skip(2); skip(2);
emitToken(TokenTag::STRING, std::string(string), start); emitToken(TokenTag::STRING, std::string(string), start);
continue; continue;
@ -154,7 +158,7 @@ public:
continue; continue;
case '\'': case '"': { case '\'': case '"': {
skip(1); skip(1);
auto string = parseString(c); auto string = parseString(c, false);
emitToken(TokenTag::STRING, std::move(string), start); emitToken(TokenTag::STRING, std::move(string), start);
continue; continue;
} }
@ -163,6 +167,8 @@ public:
if (is_lua_operator_start(c)) { if (is_lua_operator_start(c)) {
auto text = parseOperator(); auto text = parseOperator();
if (text == "--") { if (text == "--") {
auto string = readUntilEOL();
emitToken(TokenTag::COMMENT, std::string(string), start);
skipLine(); skipLine();
continue; continue;
} }

View File

@ -12,7 +12,7 @@ namespace lua {
enum class TokenTag { enum class TokenTag {
KEYWORD, NAME, INTEGER, NUMBER, OPEN_BRACKET, CLOSE_BRACKET, STRING, KEYWORD, NAME, INTEGER, NUMBER, OPEN_BRACKET, CLOSE_BRACKET, STRING,
OPERATOR, COMMA, SEMICOLON, UNEXPECTED OPERATOR, COMMA, SEMICOLON, UNEXPECTED, COMMENT
}; };
struct Token { struct Token {

View File

@ -0,0 +1,72 @@
#include "syntax_highlighting.hpp"
#include "coders/commons.hpp"
#include "coders/lua_parsing.hpp"
#include "graphics/core/Font.hpp"
using namespace devtools;
static std::unique_ptr<FontStylesScheme> build_styles(
const std::vector<lua::Token>& tokens
) {
FontStylesScheme styles {
{
{false, false, glm::vec4(0.8f, 0.8f, 0.8f, 1)}, // default
{true, false, glm::vec4(0.9, 0.6f, 0.4f, 1)}, // keyword
{false, false, glm::vec4(0.4, 0.8f, 0.5f, 1)}, // string
{false, false, glm::vec4(0.3, 0.3f, 0.3f, 1)}, // comment
{false, false, glm::vec4(0.4, 0.45f, 0.5f, 1)}, // self
{true, false, glm::vec4(1.0f, 0.2f, 0.1f, 1)}, // unexpected
},
{}
};
size_t offset = 0;
for (int i = 0; i < tokens.size(); i++) {
const auto& token = tokens.at(i);
if (token.tag != lua::TokenTag::KEYWORD &&
token.tag != lua::TokenTag::STRING &&
token.tag != lua::TokenTag::INTEGER &&
token.tag != lua::TokenTag::NUMBER &&
token.tag != lua::TokenTag::COMMENT &&
token.tag != lua::TokenTag::UNEXPECTED) {
continue;
}
if (token.start.pos > offset) {
int n = token.start.pos - offset;
styles.map.insert(styles.map.end(), token.start.pos - offset, 0);
}
offset = token.end.pos;
int styleIndex;
switch (token.tag) {
case lua::TokenTag::KEYWORD: styleIndex = 1; break;
case lua::TokenTag::STRING:
case lua::TokenTag::INTEGER:
case lua::TokenTag::NUMBER: styleIndex = 2; break;
case lua::TokenTag::COMMENT: styleIndex = 3; break;
case lua::TokenTag::UNEXPECTED: styleIndex = 5; break;
default:
styleIndex = 0;
break;
}
styles.map.insert(
styles.map.end(), token.end.pos - token.start.pos, styleIndex
);
}
styles.map.push_back(0);
return std::make_unique<FontStylesScheme>(std::move(styles));
}
std::unique_ptr<FontStylesScheme> devtools::syntax_highlight(
const std::string& lang, std::string_view source
) {
try {
if (lang == "lua") {
auto tokens = lua::tokenize("<string>", source);
return build_styles(tokens);
} else {
return nullptr;
}
} catch (const parsing_error& err) {
return nullptr;
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <memory>
struct FontStylesScheme;
namespace devtools {
std::unique_ptr<FontStylesScheme> syntax_highlight(
const std::string& lang, std::string_view source
);
}

View File

@ -62,7 +62,7 @@ static inline void draw_glyph(
pos.y + offset.y * right.y, pos.y + offset.y * right.y,
right.x / glyphInterval, right.x / glyphInterval,
up.y, up.y,
-0.2f * style.italic, -0.15f * style.italic,
16, 16,
c, c,
batch.getColor() * style.color batch.getColor() * style.color
@ -102,11 +102,11 @@ static inline void draw_text(
const glm::vec3& right, const glm::vec3& right,
const glm::vec3& up, const glm::vec3& up,
float glyphInterval, float glyphInterval,
const FontStylesScheme* styles const FontStylesScheme* styles,
size_t styleMapOffset
) { ) {
static FontStylesScheme defStyles { static FontStylesScheme defStyles {{{}}, {0}};
{{std::numeric_limits<size_t>::max()}},
};
if (styles == nullptr) { if (styles == nullptr) {
styles = &defStyles; styles = &defStyles;
} }
@ -117,17 +117,12 @@ static inline void draw_text(
int y = 0; int y = 0;
do { do {
size_t entryIndex = 0; for (size_t i = 0; i < text.length(); i++) {
int styleCharsCounter = -1; uint c = text[i];
const FontStyle* style = &styles->palette.at(entryIndex); size_t styleIndex = styles->map.at(
std::min(styles->map.size() - 1, i + styleMapOffset)
for (uint c : text) { );
styleCharsCounter++; const FontStyle& style = styles->palette.at(styleIndex);
if (styleCharsCounter > style->n &&
entryIndex + 1 < styles->palette.size()) {
style = &styles->palette.at(++entryIndex);
styleCharsCounter = -1;
}
if (!font.isPrintableChar(c)) { if (!font.isPrintableChar(c)) {
x++; x++;
continue; continue;
@ -143,7 +138,7 @@ static inline void draw_text(
right, right,
up, up,
glyphInterval, glyphInterval,
*style style
); );
} }
else if (charpage > page && charpage < next){ else if (charpage > page && charpage < next){
@ -174,6 +169,7 @@ void Font::draw(
int x, int x,
int y, int y,
const FontStylesScheme* styles, const FontStylesScheme* styles,
size_t styleMapOffset,
float scale float scale
) const { ) const {
draw_text( draw_text(
@ -182,7 +178,8 @@ void Font::draw(
glm::vec3(glyphInterval*scale, 0, 0), glm::vec3(glyphInterval*scale, 0, 0),
glm::vec3(0, lineHeight*scale, 0), glm::vec3(0, lineHeight*scale, 0),
glyphInterval/static_cast<float>(lineHeight), glyphInterval/static_cast<float>(lineHeight),
styles styles,
styleMapOffset
); );
} }
@ -190,6 +187,7 @@ void Font::draw(
Batch3D& batch, Batch3D& batch,
std::wstring_view text, std::wstring_view text,
const FontStylesScheme* styles, const FontStylesScheme* styles,
size_t styleMapOffset,
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& right, const glm::vec3& right,
const glm::vec3& up const glm::vec3& up
@ -199,6 +197,7 @@ void Font::draw(
right * static_cast<float>(glyphInterval), right * static_cast<float>(glyphInterval),
up * static_cast<float>(lineHeight), up * static_cast<float>(lineHeight),
glyphInterval/static_cast<float>(lineHeight), glyphInterval/static_cast<float>(lineHeight),
styles styles,
styleMapOffset
); );
} }

View File

@ -12,7 +12,6 @@ class Batch3D;
class Camera; class Camera;
struct FontStyle { struct FontStyle {
size_t n = -1;
bool bold = false; bool bold = false;
bool italic = false; bool italic = false;
glm::vec4 color {1, 1, 1, 1}; glm::vec4 color {1, 1, 1, 1};
@ -20,6 +19,7 @@ struct FontStyle {
struct FontStylesScheme { struct FontStylesScheme {
std::vector<FontStyle> palette; std::vector<FontStyle> palette;
std::vector<ubyte> map;
}; };
class Font { class Font {
@ -57,6 +57,7 @@ public:
int x, int x,
int y, int y,
const FontStylesScheme* styles, const FontStylesScheme* styles,
size_t styleMapOffset,
float scale = 1 float scale = 1
) const; ) const;
@ -64,6 +65,7 @@ public:
Batch3D& batch, Batch3D& batch,
std::wstring_view text, std::wstring_view text,
const FontStylesScheme* styles, const FontStylesScheme* styles,
size_t styleMapOffset,
const glm::vec3& pos, const glm::vec3& pos,
const glm::vec3& right={1, 0, 0}, const glm::vec3& right={1, 0, 0},
const glm::vec3& up={0, 1, 0} const glm::vec3& up={0, 1, 0}

View File

@ -98,13 +98,13 @@ void TextsRenderer::renderNote(
pos + xvec * (width * 0.5f * preset.scale))) { pos + xvec * (width * 0.5f * preset.scale))) {
return; return;
} }
static FontStylesScheme styles {};
auto color = preset.color; auto color = preset.color;
batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity)); batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity));
font.draw( font.draw(
batch, batch,
text, text,
&styles, nullptr,
0,
pos - xvec * (width * 0.5f) * preset.scale, pos - xvec * (width * 0.5f) * preset.scale,
xvec * preset.scale, xvec * preset.scale,
yvec * preset.scale yvec * preset.scale

View File

@ -194,9 +194,9 @@ void SlotView::draw(const DrawContext* pctx, Assets* assets) {
int y = pos.y+slotSize-16; int y = pos.y+slotSize-16;
batch->setColor({0, 0, 0, 1.0f}); batch->setColor({0, 0, 0, 1.0f});
font->draw(*batch, text, x+1, y+1, nullptr); font->draw(*batch, text, x+1, y+1, nullptr, 0);
batch->setColor(glm::vec4(1.0f)); batch->setColor(glm::vec4(1.0f));
font->draw(*batch, text, x, y, nullptr); font->draw(*batch, text, x, y, nullptr, 0);
} }
} }

View File

@ -203,10 +203,10 @@ void Label::draw(const DrawContext* pctx, Assets* assets) {
if (i < cache.lines.size()-1) { if (i < cache.lines.size()-1) {
view = std::wstring_view(text.c_str()+offset, cache.lines.at(i+1).offset-offset); view = std::wstring_view(text.c_str()+offset, cache.lines.at(i+1).offset-offset);
} }
font->draw(*batch, view, pos.x, pos.y + i * totalLineHeight, styles.get()); font->draw(*batch, view, pos.x, pos.y + i * totalLineHeight, styles.get(), offset);
} }
} else { } else {
font->draw(*batch, text, pos.x, pos.y, styles.get()); font->draw(*batch, text, pos.x, pos.y, styles.get(), 0);
} }
} }

View File

@ -52,7 +52,8 @@ void Plotter::draw(const DrawContext* pctx, Assets* assets) {
string, string,
pos.x + dmwidth + 2, pos.x + dmwidth + 2,
pos.y + dmheight - y - labelsInterval, pos.y + dmheight - y - labelsInterval,
nullptr nullptr,
0
); );
} }
} }

View File

@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include "Label.hpp" #include "Label.hpp"
#include "devtools/syntax_highlighting.hpp"
#include "graphics/core/DrawContext.hpp" #include "graphics/core/DrawContext.hpp"
#include "graphics/core/Batch2D.hpp" #include "graphics/core/Batch2D.hpp"
#include "graphics/core/Font.hpp" #include "graphics/core/Font.hpp"
@ -65,11 +66,10 @@ void TextBox::draw(const DrawContext* pctx, Assets* assets) {
lcoord.y -= 2; lcoord.y -= 2;
auto batch = pctx->getBatch2D(); auto batch = pctx->getBatch2D();
batch->texture(nullptr); batch->texture(nullptr);
batch->setColor(glm::vec4(1.0f));
if (editable && int((Window::time() - caretLastMove) * 2) % 2 == 0) { if (editable && int((Window::time() - caretLastMove) * 2) % 2 == 0) {
uint line = label->getLineByTextIndex(caret); uint line = label->getLineByTextIndex(caret);
uint lcaret = caret - label->getTextLineOffset(line); uint lcaret = caret - label->getTextLineOffset(line);
batch->setColor(glm::vec4(1.0f));
int width = font->calcWidth(input, lcaret); int width = font->calcWidth(input, lcaret);
batch->rect(lcoord.x + width, lcoord.y+label->getLineYOffset(line), 2, lineHeight); batch->rect(lcoord.x + width, lcoord.y+label->getLineYOffset(line), 2, lineHeight);
} }
@ -529,10 +529,21 @@ void TextBox::stepDefaultUp(bool shiftPressed, bool breakSelection) {
} }
} }
void TextBox::refreshSyntax() {
if (!syntax.empty()) {
if (auto styles = devtools::syntax_highlight(
syntax, util::wstr2str_utf8(input)
)) {
label->setStyles(std::move(styles));
}
}
}
void TextBox::onInput() { void TextBox::onInput() {
if (subconsumer) { if (subconsumer) {
subconsumer(input); subconsumer(input);
} }
refreshSyntax();
} }
void TextBox::performEditingKeyboardEvents(keycode key) { void TextBox::performEditingKeyboardEvents(keycode key) {
@ -710,6 +721,7 @@ const std::wstring& TextBox::getText() const {
void TextBox::setText(const std::wstring& value) { void TextBox::setText(const std::wstring& value) {
this->input = value; this->input = value;
input.erase(std::remove(input.begin(), input.end(), '\r'), input.end()); input.erase(std::remove(input.begin(), input.end(), '\r'), input.end());
refreshSyntax();
} }
const std::wstring& TextBox::getPlaceholder() const { const std::wstring& TextBox::getPlaceholder() const {
@ -789,3 +801,12 @@ void TextBox::setShowLineNumbers(bool flag) {
bool TextBox::isShowLineNumbers() const { bool TextBox::isShowLineNumbers() const {
return showLineNumbers; return showLineNumbers;
} }
void TextBox::setSyntax(const std::string& lang) {
syntax = lang;
if (syntax.empty()) {
label->setStyles(nullptr);
} else {
refreshSyntax();
}
}

View File

@ -57,6 +57,8 @@ namespace gui {
bool autoresize = false; bool autoresize = false;
bool showLineNumbers = false; bool showLineNumbers = false;
std::string syntax;
void stepLeft(bool shiftPressed, bool breakSelection); void stepLeft(bool shiftPressed, bool breakSelection);
void stepRight(bool shiftPressed, bool breakSelection); void stepRight(bool shiftPressed, bool breakSelection);
void stepDefaultDown(bool shiftPressed, bool breakSelection); void stepDefaultDown(bool shiftPressed, bool breakSelection);
@ -84,6 +86,8 @@ namespace gui {
void refreshLabel(); void refreshLabel();
void onInput(); void onInput();
void refreshSyntax();
public: public:
TextBox( TextBox(
std::wstring placeholder, std::wstring placeholder,
@ -219,5 +223,7 @@ namespace gui {
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override; virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
virtual void setOnUpPressed(const runnable &callback); virtual void setOnUpPressed(const runnable &callback);
virtual void setOnDownPressed(const runnable &callback); virtual void setOnDownPressed(const runnable &callback);
virtual void setSyntax(const std::string& lang);
}; };
} }

View File

@ -357,6 +357,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
} }
textbox->setText(text); textbox->setText(text);
if (element->has("syntax")) {
textbox->setSyntax(element->attr("syntax").getText());
}
if (element->has("multiline")) { if (element->has("multiline")) {
textbox->setMultiline(element->attr("multiline").asBool()); textbox->setMultiline(element->attr("multiline").asBool());
} }