#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& 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 bool isBracketChar(char c) { return c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}'; } 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& 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* lines, int line) { if (!lines) return false; return std::find(lines->begin(), lines->end(), line) != lines->end(); } static bool annotationAtLine(const std::vector& 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& 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& 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& diags, int line) { for (const auto& d : diags) { if (line >= d.startLine && line <= d.endLine) return d.message; } return ""; } static bool securityDiagnosticAtLine(const std::vector& diags, int line, DiagnosticRange& out) { for (const auto& d : diags) { if (line < d.startLine || line > d.endLine) continue; if (d.message.find("[Security]") != std::string::npos) { out = d; return true; } } return false; } static void renderSquiggles(const std::vector& 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)) : (d.severity == 2) ? ThemeEngine::instance().editorColor("diag_warning", IM_COL32(220, 160, 60, 255)) : ThemeEngine::instance().editorColor("diag_info", IM_COL32(90, 160, 220, 255)); float x = xStart; float amp = 2.0f; bool up = true; int segment = 0; bool dotted = (d.severity != 1 && d.severity != 2); while (x < xEnd) { if (dotted) { drawList->AddCircleFilled(ImVec2(x, baseY), 1.3f, color); x += 4.0f; continue; } float x2 = std::min(x + 4.0f, xEnd); float y1 = baseY + (up ? -amp : amp); float y2 = baseY + (up ? amp : -amp); bool drawSegment = true; if (d.severity == 2) { drawSegment = (segment % 2 == 0); } if (drawSegment) { drawList->AddLine(ImVec2(x, y1), ImVec2(x2, y2), color, 1.0f); } up = !up; x = x2; segment++; } } } static std::string diagnosticMessageAtPoint(const std::vector& 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 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& 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); } }