From 3ad63f79dc4d56196d0d2edbe027fc59ba2ee4d5 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sat, 25 Apr 2026 21:00:31 +0200 Subject: [PATCH] feat(cpp/core): add text_editor_cpp_core component (PIMPL) Wrapper en namespace fn:: sobre ImGuiColorTextEdit. La API publica solo expone TextEditorState como tipo opaco; el TextEditor del vendor vive dentro del .cpp. Soporta CodeLang::{Generic, GLSL, SQL, Cpp} (highlighting via las LanguageDefinition del vendor). text_editor_render() devuelve true en el frame en que el contenido cambia; flag dirty manejado independientemente para casos "editado pero aun no guardado". text_editor_get_text() cachea el resultado en un std::string del state para mantener el const char* valido entre llamadas (el GetText() del vendor devuelve std::string por valor). Co-Authored-By: Claude Opus 4.7 (1M context) --- cpp/functions/core/text_editor.cpp | 68 +++++++++++++++++++ cpp/functions/core/text_editor.h | 50 ++++++++++++++ cpp/functions/core/text_editor.md | 101 +++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 cpp/functions/core/text_editor.cpp create mode 100644 cpp/functions/core/text_editor.h create mode 100644 cpp/functions/core/text_editor.md diff --git a/cpp/functions/core/text_editor.cpp b/cpp/functions/core/text_editor.cpp new file mode 100644 index 00000000..42657220 --- /dev/null +++ b/cpp/functions/core/text_editor.cpp @@ -0,0 +1,68 @@ +#include "text_editor.h" + +#include "TextEditor.h" + +#include + +namespace fn { + +struct TextEditorState { + ::TextEditor editor; // vendor type, oculto detras del PIMPL + std::string cached_text; // buffer estable para text_editor_get_text() + bool dirty = false; +}; + +static const ::TextEditor::LanguageDefinition& lang_def(CodeLang lang) { + switch (lang) { + case CodeLang::GLSL: return ::TextEditor::LanguageDefinition::GLSL(); + case CodeLang::SQL: return ::TextEditor::LanguageDefinition::SQL(); + case CodeLang::Cpp: return ::TextEditor::LanguageDefinition::CPlusPlus(); + case CodeLang::Generic: + default: + return ::TextEditor::LanguageDefinition::CPlusPlus(); + } +} + +TextEditorState* text_editor_create(CodeLang lang) { + auto* s = new TextEditorState(); + s->editor.SetLanguageDefinition(lang_def(lang)); + s->editor.SetShowWhitespaces(false); + return s; +} + +void text_editor_destroy(TextEditorState* state) { + delete state; +} + +void text_editor_set_text(TextEditorState* state, const char* text) { + if (!state || !text) return; + state->editor.SetText(text); + state->cached_text = text; + state->dirty = false; +} + +const char* text_editor_get_text(TextEditorState* state) { + if (!state) return ""; + state->cached_text = state->editor.GetText(); + // El editor anade siempre un '\n' final; lo dejamos para preservar + // la semantica del vendor (es lo que devuelve GetText()). + return state->cached_text.c_str(); +} + +bool text_editor_render(TextEditorState* state, const char* label, ImVec2 size) { + if (!state) return false; + state->editor.Render(label, size, true); + const bool changed = state->editor.IsTextChanged(); + if (changed) state->dirty = true; + return changed; +} + +bool text_editor_is_dirty(const TextEditorState* state) { + return state ? state->dirty : false; +} + +void text_editor_clear_dirty(TextEditorState* state) { + if (state) state->dirty = false; +} + +} // namespace fn diff --git a/cpp/functions/core/text_editor.h b/cpp/functions/core/text_editor.h new file mode 100644 index 00000000..a5f9f291 --- /dev/null +++ b/cpp/functions/core/text_editor.h @@ -0,0 +1,50 @@ +#pragma once + +// text_editor — wrapper PIMPL sobre ImGuiColorTextEdit (vendor MIT). +// API en namespace fn:: que oculta el tipo concreto del vendor. +// +// Uso tipico: +// auto* ed = fn::text_editor_create(fn::CodeLang::GLSL); +// fn::text_editor_set_text(ed, "void main() { gl_FragColor = vec4(1); }"); +// if (fn::text_editor_render(ed, "##ed", {600, 400})) { /* cambio */ } +// fn::text_editor_destroy(ed); +// +// El estado vive en TextEditorState (forward-declared), no se expone TextEditor del vendor. + +#include "imgui.h" + +namespace fn { + +enum class CodeLang { + Generic, + GLSL, + SQL, + Cpp, +}; + +// Forward declaration — definicion completa en text_editor.cpp (PIMPL). +struct TextEditorState; + +// Crea un editor con el lenguaje dado. El caller es dueno y debe llamar destroy. +TextEditorState* text_editor_create(CodeLang lang = CodeLang::Generic); + +// Libera el editor. Acepta nullptr (no-op). +void text_editor_destroy(TextEditorState* state); + +// Reemplaza el texto del editor. +void text_editor_set_text(TextEditorState* state, const char* text); + +// Devuelve el texto actual. El puntero es valido solo hasta la siguiente llamada +// a text_editor_set_text/get_text/render sobre el mismo editor. +const char* text_editor_get_text(TextEditorState* state); + +// Renderiza el editor. Devuelve true en el frame en que el contenido cambio. +bool text_editor_render(TextEditorState* state, const char* label, ImVec2 size); + +// True si el editor esta marcado como "dirty" (modificado desde el ultimo clear). +bool text_editor_is_dirty(const TextEditorState* state); + +// Limpia el flag dirty (tipicamente tras guardar a disco). +void text_editor_clear_dirty(TextEditorState* state); + +} // namespace fn diff --git a/cpp/functions/core/text_editor.md b/cpp/functions/core/text_editor.md new file mode 100644 index 00000000..3f620fa0 --- /dev/null +++ b/cpp/functions/core/text_editor.md @@ -0,0 +1,101 @@ +--- +name: text_editor +kind: component +lang: cpp +domain: core +version: "1.0.0" +purity: pure +signature: "fn::TextEditorState* fn::text_editor_create(fn::CodeLang); void fn::text_editor_destroy(fn::TextEditorState*); void fn::text_editor_set_text(fn::TextEditorState*, const char*); const char* fn::text_editor_get_text(fn::TextEditorState*); bool fn::text_editor_render(fn::TextEditorState*, const char* label, ImVec2 size); bool fn::text_editor_is_dirty(const fn::TextEditorState*); void fn::text_editor_clear_dirty(fn::TextEditorState*)" +description: "Editor de codigo embebido en ImGui con syntax highlighting (GLSL, SQL, C++, Generic). Wrapper PIMPL sobre ImGuiColorTextEdit (vendor MIT) — la API solo expone tipos opacos en namespace fn::." +tags: [imgui, editor, text, code, glsl, sql, syntax-highlighting, tokens] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [imgui, TextEditor] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/core/text_editor.cpp" +framework: imgui +source_repo: "https://github.com/BalazsJako/ImGuiColorTextEdit" +source_license: "MIT" +source_file: "TextEditor.h, TextEditor.cpp" +params: [] +output: "TextEditorState opaco — encapsula el editor del vendor + buffer de texto cacheado + flag dirty. Render devuelve true cuando el contenido cambio en el frame actual." +--- + +# text_editor + +Editor de codigo in-app con syntax highlighting. Resuelve el ciclo de edicion sin alt-tab a un editor externo (ej: editar GLSL en `shaders_lab` y recompilar al guardar; futuro `sql_workbench` con CTAS sobre `registry.db`). + +## API + +```cpp +namespace fn { + enum class CodeLang { Generic, GLSL, SQL, Cpp }; + struct TextEditorState; // PIMPL — tipo opaco + + TextEditorState* text_editor_create(CodeLang lang = CodeLang::Generic); + void text_editor_destroy(TextEditorState*); + + void text_editor_set_text(TextEditorState*, const char* text); + const char* text_editor_get_text(TextEditorState*); // valido hasta el siguiente call + bool text_editor_render(TextEditorState*, const char* label, ImVec2 size); // true si cambio + bool text_editor_is_dirty(const TextEditorState*); + void text_editor_clear_dirty(TextEditorState*); +} +``` + +## Ejemplo — editor GLSL con boton de guardado + +```cpp +#include "core/text_editor.h" +#include "core/button.h" + +static fn::TextEditorState* g_ed = nullptr; + +void render() { + if (!g_ed) { + g_ed = fn::text_editor_create(fn::CodeLang::GLSL); + fn::text_editor_set_text(g_ed, + "#version 330\nout vec4 c;\nvoid main() { c = vec4(1); }\n"); + } + + fn::text_editor_render(g_ed, "##editor", ImVec2(600, 400)); + + if (fn::text_editor_is_dirty(g_ed)) ImGui::TextUnformatted("(modificado)"); + + if (fn_ui::button("Save")) { + FILE* f = std::fopen("/tmp/shader.glsl", "w"); + std::fputs(fn::text_editor_get_text(g_ed), f); + std::fclose(f); + fn::text_editor_clear_dirty(g_ed); + } +} +``` + +## Decisiones de diseño + +- **PIMPL** — el tipo `TextEditor` del vendor no aparece en `text_editor.h`. El header publico solo importa `imgui.h` (por `ImVec2`). +- **`get_text` cachea** el resultado en un `std::string` propio del state para que el `const char*` siga siendo valido entre llamadas (el `GetText()` del vendor devuelve un `std::string` por valor). +- **Sin tokens custom** — el editor hereda el theme global de ImGui (paleta interna del vendor). Para integrar `fn_tokens` haria falta crear un `Palette` propio; lo dejamos para una v2 si pesa visualmente. +- **`Generic` reusa C++** como default: el vendor no tiene un "plain text" sin highlighting; C++ es el menos intrusivo. + +## Lenguajes soportados + +| `CodeLang` | Highlighting | Origen | +|-----------|--------------|--------| +| `GLSL` | Si | `LanguageDefinition::GLSL()` | +| `SQL` | Si | `LanguageDefinition::SQL()` | +| `Cpp` | Si | `LanguageDefinition::CPlusPlus()` | +| `Generic` | C++ por defecto | `LanguageDefinition::CPlusPlus()` | + +## Vendor + +`cpp/vendor/imgui_text_edit/` (commit pinneado en su `README.md`). MIT. + +## Combinacion sugerida + +`text_editor` + `file_watcher_cpp_core` = ciclo de edicion completo. Ver `apps/primitives_gallery/demos_text_editor.cpp` para la demo combinada.