add markdown dialect (WIP) & add strikethrough and underline font styles

This commit is contained in:
MihailRis 2024-12-06 17:35:03 +03:00
parent 605d7e7897
commit 80e809f97f
15 changed files with 296 additions and 58 deletions

View File

@ -22,6 +22,7 @@
multiline='true'
size-func="gui.get_viewport()[1],40"
gravity="bottom-left"
markdown="true"
></textbox>
</container>
<container id="editorContainer" pos="0,60" color="#00000080"
@ -54,6 +55,7 @@
<textbox id='prompt'
consumer='submit'
margin='0'
markdown="true"
gravity='bottom-left'
size-func="gui.get_viewport()[1],40"
onup="on_history_up()"

View File

@ -5,6 +5,7 @@
#include "commons.hpp"
using namespace lua;
using namespace devtools;
static std::set<std::string_view> keywords {
"and", "break", "do", "else", "elseif", "end", "false", "for", "function",

View File

@ -3,33 +3,12 @@
#include <string>
#include <vector>
#include "devtools/syntax.hpp"
namespace lua {
struct Location {
int pos;
int lineStart;
int line;
};
enum class TokenTag {
KEYWORD, NAME, INTEGER, NUMBER, OPEN_BRACKET, CLOSE_BRACKET, STRING,
OPERATOR, COMMA, SEMICOLON, UNEXPECTED, COMMENT
};
struct Token {
TokenTag tag;
std::string text;
Location start;
Location end;
Token(TokenTag tag, std::string text, Location start, Location end)
: tag(tag),
text(std::move(text)),
start(std::move(start)),
end(std::move(end)) {
}
};
bool is_lua_keyword(std::string_view view);
std::vector<Token> tokenize(std::string_view file, std::string_view source);
std::vector<devtools::Token> tokenize(
std::string_view file, std::string_view source
);
}

30
src/devtools/syntax.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <string>
namespace devtools {
struct Location {
int pos;
int lineStart;
int line;
};
enum class TokenTag {
KEYWORD, NAME, INTEGER, NUMBER, OPEN_BRACKET, CLOSE_BRACKET, STRING,
OPERATOR, COMMA, SEMICOLON, UNEXPECTED, COMMENT
};
struct Token {
TokenTag tag;
std::string text;
Location start;
Location end;
Token(TokenTag tag, std::string text, Location start, Location end)
: tag(tag),
text(std::move(text)),
start(std::move(start)),
end(std::move(end)) {
}
};
}

View File

@ -7,28 +7,28 @@
using namespace devtools;
static std::unique_ptr<FontStylesScheme> build_styles(
const std::vector<lua::Token>& tokens
const std::vector<devtools::Token>& tokens
) {
using devtools::TokenTag;
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
{false, false, false, false, glm::vec4(0.8f, 0.8f, 0.8f, 1)}, // default
{true, false, false, false, glm::vec4(0.9, 0.6f, 0.4f, 1)}, // keyword
{false, false, false, false, glm::vec4(0.4, 0.8f, 0.5f, 1)}, // string
{false, false, false, false, glm::vec4(0.3, 0.3f, 0.3f, 1)}, // comment
{true, false, false, 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) {
if (token.tag != TokenTag::KEYWORD &&
token.tag != TokenTag::STRING &&
token.tag != TokenTag::INTEGER &&
token.tag != TokenTag::NUMBER &&
token.tag != TokenTag::COMMENT &&
token.tag != TokenTag::UNEXPECTED) {
continue;
}
if (token.start.pos > offset) {
@ -38,12 +38,12 @@ static std::unique_ptr<FontStylesScheme> build_styles(
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;
case TokenTag::KEYWORD: styleIndex = SyntaxStyles::KEYWORD; break;
case TokenTag::STRING:
case TokenTag::INTEGER:
case TokenTag::NUMBER: styleIndex = SyntaxStyles::LITERAL; break;
case TokenTag::COMMENT: styleIndex = SyntaxStyles::COMMENT; break;
case TokenTag::UNEXPECTED: styleIndex = SyntaxStyles::ERROR; break;
default:
styleIndex = 0;
break;

View File

@ -6,6 +6,10 @@
struct FontStylesScheme;
namespace devtools {
enum SyntaxStyles {
DEFAULT, KEYWORD, LITERAL, COMMENT, ERROR
};
std::unique_ptr<FontStylesScheme> syntax_highlight(
const std::string& lang, std::string_view source
);

View File

@ -101,7 +101,7 @@ static inline void draw_text(
const glm::vec3& pos,
const glm::vec3& right,
const glm::vec3& up,
float glyphInterval,
float interval,
const FontStylesScheme* styles,
size_t styleMapOffset
) {
@ -115,6 +115,7 @@ static inline void draw_text(
uint next = MAX_CODEPAGES;
int x = 0;
int y = 0;
bool hasLines = false;
do {
for (size_t i = 0; i < text.length(); i++) {
@ -123,6 +124,9 @@ static inline void draw_text(
std::min(styles->map.size() - 1, i + styleMapOffset)
);
const FontStyle& style = styles->palette.at(styleIndex);
hasLines |= style.strikethrough;
hasLines |= style.underline;
if (!font.isPrintableChar(c)) {
x++;
continue;
@ -131,14 +135,7 @@ static inline void draw_text(
if (charpage == page){
batch.texture(font.getPage(charpage));
draw_glyph(
batch,
pos,
glm::vec2(x, y),
c,
right,
up,
glyphInterval,
style
batch, pos, glm::vec2(x, y), c, right, up, interval, style
);
}
else if (charpage > page && charpage < next){
@ -150,6 +147,31 @@ static inline void draw_text(
next = MAX_CODEPAGES;
x = 0;
} while (page < MAX_CODEPAGES);
if (!hasLines) {
return;
}
batch.texture(font.getPage(0));
for (size_t i = 0; i < text.length(); i++) {
uint c = text[i];
size_t styleIndex = styles->map.at(
std::min(styles->map.size() - 1, i + styleMapOffset)
);
const FontStyle& style = styles->palette.at(styleIndex);
FontStyle lineStyle = style;
lineStyle.bold = true;
if (style.strikethrough) {
draw_glyph(
batch, pos, glm::vec2(x, y), '-', right, up, interval, lineStyle
);
}
if (style.underline) {
draw_glyph(
batch, pos, glm::vec2(x, y), '_', right, up, interval, lineStyle
);
}
x++;
}
}
const Texture* Font::getPage(int charpage) const {

View File

@ -14,7 +14,25 @@ class Camera;
struct FontStyle {
bool bold = false;
bool italic = false;
bool strikethrough = false;
bool underline = false;
glm::vec4 color {1, 1, 1, 1};
FontStyle() = default;
FontStyle(
bool bold,
bool italic,
bool strikethrough,
bool underline,
glm::vec4 color
)
: bold(bold),
italic(italic),
strikethrough(strikethrough),
underline(underline),
color(std::move(color)) {
}
};
struct FontStylesScheme {

View File

@ -6,6 +6,7 @@
#include "graphics/core/Font.hpp"
#include "assets/Assets.hpp"
#include "util/stringutil.hpp"
#include "../markdown.hpp"
using namespace gui;
@ -80,11 +81,16 @@ glm::vec2 Label::calcSize() {
);
}
void Label::setText(const std::wstring& text) {
void Label::setText(std::wstring text) {
if (isMarkdown()) {
auto [processedText, styles] = markdown::process(text, true);
text = std::move(processedText);
setStyles(std::move(styles));
}
if (text == this->text && !cache.resetFlag) {
return;
}
this->text = text;
this->text = std::move(text);
cache.update(this->text, multiline, textWrap);
if (cache.font && autoresize) {
@ -242,6 +248,15 @@ bool Label::isTextWrapping() const {
return textWrap;
}
void Label::setMarkdown(bool flag) {
markdown = flag;
setText(text);
}
bool Label::isMarkdown() const {
return markdown;
}
void Label::setStyles(std::unique_ptr<FontStylesScheme> styles) {
this->styles = std::move(styles);
}

View File

@ -53,6 +53,9 @@ namespace gui {
/// @brief Auto resize label to fit text
bool autoresize = false;
/// @brief Enable text markdown
bool markdown = false;
std::unique_ptr<FontStylesScheme> styles;
public:
Label(const std::string& text, std::string fontName="normal");
@ -60,7 +63,7 @@ namespace gui {
virtual ~Label();
virtual void setText(const std::wstring& text);
virtual void setText(std::wstring text);
const std::wstring& getText() const;
virtual void setFontName(std::string name);
@ -113,6 +116,9 @@ namespace gui {
virtual void setTextWrapping(bool flag);
virtual bool isTextWrapping() const;
virtual void setMarkdown(bool flag);
virtual bool isMarkdown() const;
virtual void setStyles(std::unique_ptr<FontStylesScheme> styles);
};
}

View File

@ -13,6 +13,7 @@
#include "util/stringutil.hpp"
#include "window/Events.hpp"
#include "window/Window.hpp"
#include "../markdown.hpp"
using namespace gui;
@ -191,7 +192,18 @@ void TextBox::drawBackground(const DrawContext& pctx, const Assets&) {
void TextBox::refreshLabel() {
label->setColor(textColor * glm::vec4(input.empty() ? 0.5f : 1.0f));
label->setText(input.empty() && !hint.empty() ? hint : getText());
const auto& displayText = input.empty() && !hint.empty() ? hint : getText();
if (markdown) {
auto [processedText, styles] = markdown::process(displayText, !focused);
label->setText(std::move(processedText));
label->setStyles(std::move(styles));
} else {
label->setText(displayText);
if (syntax.empty()) {
label->setStyles(nullptr);
}
}
if (showLineNumbers) {
if (lineNumbersLabel->getLinesNumber() != label->getLinesNumber()) {
@ -846,3 +858,11 @@ void TextBox::setSyntax(const std::string& lang) {
refreshSyntax();
}
}
void TextBox::setMarkdown(bool flag) {
markdown = flag;
}
bool TextBox::isMarkdown() const {
return markdown;
}

View File

@ -56,6 +56,7 @@ namespace gui {
bool editable = true;
bool autoresize = false;
bool showLineNumbers = false;
bool markdown = false;
std::string syntax;
@ -227,5 +228,8 @@ namespace gui {
virtual void setOnDownPressed(const runnable &callback);
virtual void setSyntax(const std::string& lang);
virtual void setMarkdown(bool flag);
virtual bool isMarkdown() const;
};
}

View File

@ -278,6 +278,9 @@ static std::shared_ptr<UINode> readLabel(
if (element.has("text-wrap")) {
label->setTextWrapping(element.attr("text-wrap").asBool());
}
if (element.has("markdown")) {
label->setMarkdown(element.attr("markdown").asBool());
}
return label;
}
@ -381,6 +384,9 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, const xml::xmlel
if (element.has("line-numbers")) {
textbox->setShowLineNumbers(element.attr("line-numbers").asBool());
}
if (element.has("markdown")) {
textbox->setMarkdown(element.attr("markdown").asBool());
}
if (element.has("consumer")) {
textbox->setTextConsumer(scripting::create_wstring_consumer(
reader.getEnvironment(),

View File

@ -0,0 +1,109 @@
#include "markdown.hpp"
#include <sstream>
#include "graphics/core/Font.hpp"
using namespace markdown;
template <typename CharT>
static inline void emit(
CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss
) {
ss << c;
styles.map.emplace_back(styles.palette.size()-1);
}
template <typename CharT>
static inline void emit_md(
CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss
) {
ss << c;
styles.map.emplace_back(0);
}
template <typename CharT>
static inline void restyle(
CharT c,
FontStyle& style,
FontStylesScheme& styles,
std::basic_stringstream<CharT>& ss,
int& pos,
bool eraseMarkdown
) {
styles.palette.push_back(style);
if (!eraseMarkdown) {
emit_md(c, styles, ss);
}
pos++;
}
template <typename CharT>
Result<CharT> process_markdown(
std::basic_string_view<CharT> source, bool eraseMarkdown
) {
std::basic_stringstream<CharT> ss;
FontStylesScheme styles {
// markdown default
{{false, false, false, false, glm::vec4(1,1,1,0.5f)}, {}},
{}
};
FontStyle style;
int pos = 0;
while (pos < source.size()) {
CharT first = source[pos];
if (first == '\\') {
if (pos + 1 < source.size()) {
CharT second = source[++pos];
switch (second) {
case '*':
case '_':
case '~':
if (!eraseMarkdown) {
emit_md(first, styles, ss);
}
emit(second, styles, ss);
pos++;
continue;
}
}
} else if (first == '*') {
if (pos + 1 < source.size() && source[pos+1] == '*') {
pos++;
if (!eraseMarkdown)
emit_md(first, styles, ss);
style.bold = !style.bold;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
}
style.italic = !style.italic;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
} else if (first == '_' && pos + 1 < source.size() && source[pos+1] == '_') {
pos++;
if (!eraseMarkdown)
emit_md(first, styles, ss);
style.underline = !style.underline;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
} else if (first == '~' && pos + 1 < source.size() && source[pos+1] == '~') {
pos++;
if (!eraseMarkdown)
emit_md(first, styles, ss);
style.strikethrough = !style.strikethrough;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
}
emit(first, styles, ss);
pos++;
}
return {ss.str(), std::make_unique<FontStylesScheme>(std::move(styles))};
}
Result<char> markdown::process(std::string_view source, bool eraseMarkdown) {
return process_markdown(source, eraseMarkdown);
}
Result<wchar_t> markdown::process(std::wstring_view source, bool eraseMarkdown) {
return process_markdown(source, eraseMarkdown);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <string>
#include <memory>
struct FontStylesScheme;
// VoxelCore Markdown dialect
namespace markdown {
template <typename CharT>
struct Result {
/// @brief Text with erased markdown
std::basic_string<CharT> text;
/// @brief Text styles scheme
std::unique_ptr<FontStylesScheme> styles;
};
Result<char> process(std::string_view source, bool eraseMarkdown);
Result<wchar_t> process(std::wstring_view source, bool eraseMarkdown);
}