Step 168: split oversized headers
This commit is contained in:
284
editor/src/CodeEditorRenderHelpers.h
Normal file
284
editor/src/CodeEditorRenderHelpers.h
Normal file
@@ -0,0 +1,284 @@
|
||||
#pragma once
|
||||
// Included inside CodeEditorWidget (render helpers).
|
||||
static ImU32 colorFor(TokenCategory cat) {
|
||||
ImU32 fallback = IM_COL32(220, 220, 220, 255);
|
||||
switch (cat) {
|
||||
case TokenCategory::Keyword: fallback = IM_COL32(196, 126, 220, 255); break;
|
||||
case TokenCategory::String: fallback = IM_COL32(207, 138, 94, 255); break;
|
||||
case TokenCategory::Comment: fallback = IM_COL32(107, 133, 89, 255); break;
|
||||
case TokenCategory::Number: fallback = IM_COL32(181, 204, 140, 255); break;
|
||||
case TokenCategory::Function: fallback = IM_COL32(220, 220, 140, 255); break;
|
||||
case TokenCategory::Parameter: fallback = IM_COL32(153, 199, 229, 255); break;
|
||||
case TokenCategory::Type: fallback = IM_COL32(77, 179, 174, 255); break;
|
||||
case TokenCategory::Operator: fallback = IM_COL32(220, 220, 220, 255); break;
|
||||
case TokenCategory::Punctuation: fallback = IM_COL32(160, 160, 160, 255); break;
|
||||
case TokenCategory::Builtin: fallback = IM_COL32(77, 179, 229, 255); break;
|
||||
case TokenCategory::Identifier: fallback = IM_COL32(160, 200, 230, 255); break;
|
||||
default: break;
|
||||
}
|
||||
return ThemeEngine::instance().syntaxColor(cat, fallback);
|
||||
}
|
||||
|
||||
static int lineFromPos(int pos, const std::vector<int>& lineStarts) {
|
||||
if (lineStarts.empty()) return 0;
|
||||
int line = 0;
|
||||
for (int i = 0; i < (int)lineStarts.size(); ++i) {
|
||||
if (lineStarts[i] <= pos) line = i;
|
||||
else break;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
static int lineFromMouseY(float mouseY, float baseY, float lineHeight, int lineCount) {
|
||||
int line = (int)((mouseY - baseY) / lineHeight);
|
||||
line = std::max(0, std::min(line, lineCount - 1));
|
||||
return line;
|
||||
}
|
||||
|
||||
static int positionFromMouse(const ImVec2& mouse,
|
||||
const ImVec2& base,
|
||||
const std::vector<int>& lineStarts,
|
||||
const std::string& text,
|
||||
float charAdvance,
|
||||
float lineHeight) {
|
||||
int line = (int)((mouse.y - base.y) / lineHeight);
|
||||
line = std::max(0, std::min(line, (int)lineStarts.size() - 1));
|
||||
|
||||
int start = lineStarts[line];
|
||||
int end = (line + 1 < (int)lineStarts.size()) ? lineStarts[line + 1] - 1 : (int)text.size();
|
||||
int len = std::max(0, end - start);
|
||||
|
||||
int col = (int)((mouse.x - base.x) / charAdvance);
|
||||
col = std::max(0, std::min(col, len));
|
||||
return start + col;
|
||||
}
|
||||
|
||||
static float calcGutterWidth(int lineCount, ImFont* font, float charAdvance) {
|
||||
int digits = 1;
|
||||
int n = std::max(1, lineCount);
|
||||
while (n >= 10) { n /= 10; ++digits; }
|
||||
const float pad = 6.0f;
|
||||
float digitsWidth = font->CalcTextSizeA(font->FontSize, FLT_MAX, -1.0f,
|
||||
std::string(digits, '0').c_str()).x;
|
||||
float width = digitsWidth + pad * 2.0f;
|
||||
return std::max(width, charAdvance * 2.0f + pad * 2.0f);
|
||||
}
|
||||
|
||||
const FoldRegion* findFoldAtLine(int line) const {
|
||||
for (const auto& f : folds_) {
|
||||
if (f.startLine == line) return &f;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void drawTriangle(ImDrawList* drawList, const ImVec2& center, ImU32 color, bool down) {
|
||||
float s = 4.0f;
|
||||
if (down) {
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(center.x - s, center.y - s * 0.6f),
|
||||
ImVec2(center.x + s, center.y - s * 0.6f),
|
||||
ImVec2(center.x, center.y + s),
|
||||
color);
|
||||
} else {
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(center.x - s, center.y - s),
|
||||
ImVec2(center.x - s, center.y + s),
|
||||
ImVec2(center.x + s, center.y),
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
static bool lineIn(const std::vector<int>* lines, int line) {
|
||||
if (!lines) return false;
|
||||
return std::find(lines->begin(), lines->end(), line) != lines->end();
|
||||
}
|
||||
|
||||
static bool annotationAtLine(const std::vector<AnnotationMarker>& markers,
|
||||
int line,
|
||||
AnnotationMarker& out) {
|
||||
for (const auto& m : markers) {
|
||||
if (m.line == line) {
|
||||
out = m;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool suggestionAtLine(const std::vector<SuggestionMarker>& markers,
|
||||
int line,
|
||||
SuggestionMarker& out) {
|
||||
for (const auto& m : markers) {
|
||||
if (m.line == line) {
|
||||
out = m;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool conflictAtLine(const std::vector<AnnotationConflictMarker>& markers,
|
||||
int line,
|
||||
AnnotationConflictMarker& out) {
|
||||
for (const auto& m : markers) {
|
||||
if (m.childLine == line || m.parentLine == line) {
|
||||
out = m;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string diagnosticMessageAtLine(const std::vector<DiagnosticRange>& diags, int line) {
|
||||
for (const auto& d : diags) {
|
||||
if (line >= d.startLine && line <= d.endLine) return d.message;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static void renderSquiggles(const std::vector<DiagnosticRange>& diags,
|
||||
int line,
|
||||
float y,
|
||||
float lineHeight,
|
||||
float charAdvance,
|
||||
float textBaseX,
|
||||
int lineLen,
|
||||
ImDrawList* drawList) {
|
||||
float baseY = y + lineHeight - 2.0f;
|
||||
for (const auto& d : diags) {
|
||||
if (line < d.startLine || line > d.endLine) continue;
|
||||
int startCol = (line == d.startLine) ? d.startCol : 0;
|
||||
int endCol = (line == d.endLine) ? d.endCol : lineLen;
|
||||
startCol = std::max(0, startCol);
|
||||
endCol = std::max(startCol, endCol);
|
||||
float xStart = textBaseX + startCol * charAdvance;
|
||||
float xEnd = textBaseX + endCol * charAdvance;
|
||||
ImU32 color = (d.severity == 1)
|
||||
? ThemeEngine::instance().editorColor("diag_error", IM_COL32(220, 80, 80, 255))
|
||||
: ThemeEngine::instance().editorColor("diag_warning", IM_COL32(220, 160, 60, 255));
|
||||
float x = xStart;
|
||||
float amp = 2.0f;
|
||||
bool up = true;
|
||||
while (x < xEnd) {
|
||||
float x2 = std::min(x + 4.0f, xEnd);
|
||||
float y1 = baseY + (up ? -amp : amp);
|
||||
float y2 = baseY + (up ? amp : -amp);
|
||||
drawList->AddLine(ImVec2(x, y1), ImVec2(x2, y2), color, 1.0f);
|
||||
up = !up;
|
||||
x = x2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string diagnosticMessageAtPoint(const std::vector<DiagnosticRange>& diags,
|
||||
int line,
|
||||
const ImVec2& mouse,
|
||||
float y,
|
||||
float lineHeight,
|
||||
float charAdvance,
|
||||
float textBaseX) {
|
||||
if (mouse.y < y || mouse.y > y + lineHeight) return "";
|
||||
for (const auto& d : diags) {
|
||||
if (line < d.startLine || line > d.endLine) continue;
|
||||
int startCol = (line == d.startLine) ? d.startCol : 0;
|
||||
int endCol = (line == d.endLine) ? d.endCol : startCol + 1;
|
||||
float xStart = textBaseX + startCol * charAdvance;
|
||||
float xEnd = textBaseX + endCol * charAdvance;
|
||||
if (mouse.x >= xStart && mouse.x <= xEnd) return d.message;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void updateFolds(const std::string& text, const std::string& language) {
|
||||
if (text == lastFoldText_ && language == lastFoldLang_) return;
|
||||
lastFoldText_ = text;
|
||||
lastFoldLang_ = language;
|
||||
|
||||
std::vector<FoldRegion> newFolds;
|
||||
if (text.empty()) { folds_.clear(); return; }
|
||||
|
||||
const TSLanguage* lang = nullptr;
|
||||
if (language == "python") lang = tree_sitter_python();
|
||||
else if (language == "cpp") lang = tree_sitter_cpp();
|
||||
else if (language == "elisp") lang = tree_sitter_elisp();
|
||||
if (!lang) { folds_.clear(); return; }
|
||||
|
||||
TSParser* parser = ts_parser_new();
|
||||
ts_parser_set_language(parser, lang);
|
||||
TSTree* tree = ts_parser_parse_string(parser, nullptr, text.c_str(), (uint32_t)text.size());
|
||||
TSNode root = ts_tree_root_node(tree);
|
||||
|
||||
collectFoldNodes(root, language, newFolds);
|
||||
|
||||
// Preserve folded state by startLine
|
||||
for (auto& f : newFolds) {
|
||||
for (const auto& old : folds_) {
|
||||
if (old.startLine == f.startLine && old.endLine == f.endLine) {
|
||||
f.folded = old.folded;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
folds_ = std::move(newFolds);
|
||||
if (applyDesiredFoldState_) {
|
||||
for (auto& f : folds_) {
|
||||
f.folded = std::find(desiredFoldedLines_.begin(),
|
||||
desiredFoldedLines_.end(),
|
||||
f.startLine) != desiredFoldedLines_.end();
|
||||
}
|
||||
applyDesiredFoldState_ = false;
|
||||
}
|
||||
|
||||
ts_tree_delete(tree);
|
||||
ts_parser_delete(parser);
|
||||
}
|
||||
|
||||
static bool isFoldNodeType(const std::string& language, const char* type) {
|
||||
if (language == "python") {
|
||||
return strcmp(type, "function_definition") == 0 ||
|
||||
strcmp(type, "class_definition") == 0 ||
|
||||
strcmp(type, "if_statement") == 0 ||
|
||||
strcmp(type, "for_statement") == 0 ||
|
||||
strcmp(type, "while_statement") == 0 ||
|
||||
strcmp(type, "with_statement") == 0 ||
|
||||
strcmp(type, "try_statement") == 0;
|
||||
} else if (language == "cpp") {
|
||||
return strcmp(type, "function_definition") == 0 ||
|
||||
strcmp(type, "class_specifier") == 0 ||
|
||||
strcmp(type, "struct_specifier") == 0 ||
|
||||
strcmp(type, "namespace_definition") == 0 ||
|
||||
strcmp(type, "if_statement") == 0 ||
|
||||
strcmp(type, "for_statement") == 0 ||
|
||||
strcmp(type, "while_statement") == 0 ||
|
||||
strcmp(type, "compound_statement") == 0;
|
||||
} else if (language == "elisp") {
|
||||
return strcmp(type, "function_definition") == 0 ||
|
||||
strcmp(type, "lambda_expression") == 0 ||
|
||||
strcmp(type, "if_expression") == 0 ||
|
||||
strcmp(type, "while_expression") == 0 ||
|
||||
strcmp(type, "cond_expression") == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void collectFoldNodes(TSNode node, const std::string& language, std::vector<FoldRegion>& out) {
|
||||
if (ts_node_is_null(node)) return;
|
||||
const char* type = ts_node_type(node);
|
||||
if (isFoldNodeType(language, type)) {
|
||||
TSPoint start = ts_node_start_point(node);
|
||||
TSPoint end = ts_node_end_point(node);
|
||||
if (end.row > start.row) {
|
||||
FoldRegion f;
|
||||
f.startLine = (int)start.row;
|
||||
f.endLine = (int)end.row;
|
||||
f.folded = false;
|
||||
out.push_back(f);
|
||||
}
|
||||
}
|
||||
uint32_t count = ts_node_child_count(node);
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
collectFoldNodes(ts_node_child(node, i), language, out);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user