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) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
#include "text_editor.h"
|
||||
|
||||
#include "TextEditor.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user