mod support;
use macroquad::prelude::*;
use rae::{
materialize_output, truncate_str, CodeTokenKind, CommandItem, CommandPalette, KeyModifier,
OutputDocument, OutputOptions, OutputRow, Palette, Rect, Rgba, Shortcut, SnippetSet,
TextSnippet,
};
use support::*;
fn window_conf() -> Conf {
Conf {
window_title: "rae code editor".to_string(),
window_width: 1440,
window_height: 900,
high_dpi: true,
sample_count: 4,
..Default::default()
}
}
#[macroquad::main(window_conf)]
async fn main() {
let palette = Palette::midnight();
let stream = simulated_stream();
let stream_total = stream.chars().count();
let commands = command_palette();
let snippets = snippet_set();
let max_frames = max_frames_from_env();
let mut frames = 0u64;
loop {
frames = frames.saturating_add(1);
if should_stop(frames, max_frames) {
break;
}
let time = get_time() as f32;
let stream_chars = (720 + frames as usize * 150 + (time * 22.0) as usize).min(stream_total);
let visible = visible_prefix(&stream, stream_chars);
let doc = materialize_output(
&visible,
OutputOptions::for_width(58)
.with_code_line_numbers(true)
.with_max_rows(140),
);
let viewport = Rect::new(0.0, 0.0, screen_width(), screen_height());
draw_scene_backdrop(&palette, time);
draw_header(viewport, &palette, &doc, stream_chars, stream_total);
draw_editor_shell(
viewport,
&palette,
&doc,
&commands,
&snippets,
stream_chars,
stream_total,
time,
);
maybe_save_screenshot("demo_code_editor", frames);
next_frame().await;
}
}
fn simulated_stream() -> String {
[
"# Streaming Patch\n\n",
"\x1b[33mthinking\x1b[0m: inspect editor state, sanitize terminal control text, then render safe rows.\n\n",
"> Prompt expanded from `/fix @workspace !ship` with a zero-width marker\u{200b} removed before display.\n\n",
"## Plan\n",
"- Keep `rae` renderer-neutral; this example is just one macroquad shell.\n",
"- Materialize assistant Markdown while the response is still streaming.\n",
"- Preserve copy-safe fenced code and token classes.\n\n",
"```rust\n",
"pub fn apply_patch(input: &str) -> String {\n",
" let safe = rae::copy_safe_text(input);\n",
" format!(\"patched: {safe}\")\n",
"}\n",
"```\n\n",
"```diff\n",
"+ stream chunks through materialize_output\n",
"+ strip ANSI, OSC links, controls, and zero-width text\n",
"+ keep syntax token classes renderer-neutral\n",
"```\n\n",
"Result: the code editor can show transcript, file panes, prompt hints, command palette matches, sanitized metrics, and streaming assistant output without moving app orchestration into `rae`.\n",
"\x1b]8;;https://example.invalid\x1b\\hidden osc link\x1b]8;;\x1b\\\n",
]
.concat()
}
fn visible_prefix(input: &str, chars: usize) -> String {
input.chars().take(chars).collect()
}
fn command_palette() -> CommandPalette {
let mut commands = CommandPalette::new([
CommandItem::new("editor.apply", "Apply Streaming Patch")
.with_subtitle("materialize sanitized assistant output")
.with_keywords(["patch", "stream", "apply"])
.with_shortcut(Shortcut::new("Enter").with_modifiers([KeyModifier::Ctrl])),
CommandItem::new("editor.copy", "Copy Safe Markdown")
.with_subtitle("copy sanitized response text")
.with_keywords(["copy", "safe", "markdown"])
.with_shortcut(
Shortcut::new("C").with_modifiers([KeyModifier::Ctrl, KeyModifier::Shift]),
),
CommandItem::new("editor.palette", "Open Command Palette")
.with_subtitle("filter renderer-neutral commands")
.with_keywords(["palette", "commands"])
.with_shortcut(Shortcut::new("K").with_modifiers([KeyModifier::Ctrl])),
CommandItem::new("editor.tests", "Run Verification")
.with_subtitle("fmt, tests, examples, rendered smoke")
.with_keywords(["verify", "cargo", "smoke"]),
]);
commands.set_query("stream");
commands
}
fn snippet_set() -> SnippetSet {
SnippetSet::new([
TextSnippet::new("/fix", "inspect, patch, verify")
.with_description("minimal repair workflow")
.with_keywords(["patch", "repair"]),
TextSnippet::new("@workspace", "current crate modules and examples")
.with_description("workspace context")
.with_keywords(["context", "files"]),
TextSnippet::new("!ship", "run fmt, tests, examples, and rendered smoke")
.with_description("verification contract")
.with_keywords(["verify", "release"]),
])
}
fn draw_header(
viewport: Rect,
palette: &Palette,
doc: &OutputDocument,
stream_chars: usize,
stream_total: usize,
) {
let header = Rect::new(28.0, 24.0, viewport.width - 56.0, 74.0);
draw_glass_panel(header, 26.0, palette, palette.accent, true);
draw_text_line(
"Code Editor",
header.x + 26.0,
header.y + 42.0,
31.0,
to_mq(palette.text),
);
draw_text_line(
"streaming assistant output, sanitized Markdown, code panes, commands, and snippets",
header.x + 258.0,
header.y + 40.0,
17.0,
to_mq(palette.muted),
);
let progress = stream_chars as f32 / stream_total.max(1) as f32;
draw_pill(
Rect::new(header.right() - 470.0, header.y + 20.0, 142.0, 32.0),
if progress < 1.0 {
"streaming"
} else {
"complete"
},
if progress < 1.0 {
palette.warning
} else {
palette.success
},
palette.text,
);
draw_pill(
Rect::new(header.right() - 314.0, header.y + 20.0, 136.0, 32.0),
&format!("{} rows", doc.rows.len()),
palette.accent,
palette.text,
);
draw_pill(
Rect::new(header.right() - 164.0, header.y + 20.0, 142.0, 32.0),
&format!("{} escapes", doc.removed_escape_sequences),
palette.warning,
palette.text,
);
let rail = Rect::new(
header.x + 258.0,
header.y + 54.0,
(header.width - 790.0).max(96.0),
6.0,
);
draw_rounded_rect(rail, 3.0, rgba8(1, 5, 16, 150));
draw_rounded_rect(
Rect::new(
rail.x,
rail.y,
rail.width * progress.clamp(0.0, 1.0),
rail.height,
),
3.0,
to_mq(palette.accent.with_alpha(0.68)),
);
draw_text_line(
&format!("{:>3}% materialized", (progress * 100.0).round() as usize),
rail.right() + 14.0,
rail.y + 8.0,
11.0,
to_mq(palette.muted),
);
}
#[allow(clippy::too_many_arguments)]
fn draw_editor_shell(
viewport: Rect,
palette: &Palette,
doc: &OutputDocument,
commands: &CommandPalette,
snippets: &SnippetSet,
stream_chars: usize,
stream_total: usize,
time: f32,
) {
let shell = Rect::new(28.0, 126.0, viewport.width - 56.0, viewport.height - 154.0);
let gap = 18.0;
let bottom_h = 158.0;
let top_h = shell.height - bottom_h - gap;
let explorer = Rect::new(shell.x, shell.y, 220.0, top_h);
let assistant = Rect::new(shell.right() - 398.0, shell.y, 398.0, top_h);
let editor = Rect::new(
explorer.right() + gap,
shell.y,
assistant.x - explorer.right() - gap * 2.0,
top_h,
);
let bottom = Rect::new(shell.x, shell.y + top_h + gap, shell.width, bottom_h);
draw_file_explorer(explorer, palette, time);
draw_code_buffer(editor, palette, time);
draw_assistant_stream(assistant, palette, doc, stream_chars, stream_total, time);
draw_bottom_tools(bottom, palette, doc, commands, snippets, time);
}
fn draw_file_explorer(rect: Rect, palette: &Palette, time: f32) {
draw_glass_panel(rect, 28.0, palette, palette.accent, false);
draw_section_header(
"WORKSPACE",
Rect::new(rect.x + 20.0, rect.y + 32.0, rect.width - 40.0, 18.0),
palette.accent,
palette.muted,
);
let files = [
("src/", false, palette.muted),
(" lib.rs", false, palette.text),
(" output.rs", true, palette.accent),
(" sanitize.rs", false, palette.success),
("examples/", false, palette.muted),
(" demo_code_editor.rs", true, palette.warning),
(" demo_shader_catalog.rs", false, palette.text),
];
let mut y = rect.y + 76.0;
for (label, active, color) in files {
let row = Rect::new(rect.x + 16.0, y - 18.0, rect.width - 32.0, 30.0);
if active {
draw_rounded_rect(row, 12.0, to_mq(color.with_alpha(0.13)));
draw_rounded_rect_lines(row, 12.0, 1.0, to_mq(color.with_alpha(0.32)));
}
draw_text_line(label, rect.x + 26.0, y, 14.0, to_mq(color));
y += 36.0;
}
let minimap = Rect::new(
rect.x + 22.0,
rect.bottom() - 142.0,
rect.width - 44.0,
104.0,
);
draw_rounded_rect(minimap, 18.0, rgba8(1, 5, 16, 142));
draw_text_line(
"stream map",
minimap.x + 14.0,
minimap.y + 26.0,
13.0,
to_mq(palette.muted),
);
for index in 0..16 {
let t = index as f32 / 15.0;
let w = minimap.width * (0.18 + (time * 0.8 + t * 4.0).sin().abs() * 0.68);
draw_rounded_rect(
Rect::new(
minimap.x + 14.0,
minimap.y + 42.0 + index as f32 * 3.2,
w,
1.4,
),
1.0,
to_mq(palette.accent.with_alpha(0.18 + t * 0.18)),
);
}
}
fn draw_code_buffer(rect: Rect, palette: &Palette, time: f32) {
draw_glass_panel(rect, 28.0, palette, palette.success, true);
draw_text_line(
"output.rs",
rect.x + 24.0,
rect.y + 34.0,
20.0,
to_mq(palette.text),
);
draw_text_line(
"renderer-neutral rows feeding a desktop editor surface",
rect.x + 124.0,
rect.y + 33.0,
14.0,
to_mq(palette.muted),
);
let body = Rect::new(
rect.x + 22.0,
rect.y + 62.0,
rect.width - 44.0,
rect.height - 88.0,
);
draw_rounded_rect(body, 22.0, rgba8(1, 5, 16, 168));
draw_rounded_rect_lines(body, 22.0, 1.0, to_mq(palette.success.with_alpha(0.24)));
let code = [
"pub fn materialize_output(input: &str, options: OutputOptions) -> OutputDocument {",
" let sanitized = sanitize_with_policy(input, options.sanitize);",
" let mut rows = Vec::new();",
" let mut in_code = false;",
" for raw in sanitized.text.lines() {",
" if let Some(lang) = raw.strip_prefix(\"```\") {",
" in_code = !in_code;",
" rows.push(OutputRow::CodeFenceStart { language: lang.into() });",
" continue;",
" }",
" rows.push(tokenize_or_wrap(raw, in_code));",
" }",
" OutputDocument { rows, copy_text: sanitized.text, ..metrics }",
"}",
];
let mut y = body.y + 34.0;
for (index, line) in code.iter().enumerate() {
let line_rect = Rect::new(body.x + 12.0, y - 18.0, body.width - 24.0, 25.0);
if index == 4 || index == 10 {
draw_rounded_rect(line_rect, 8.0, to_mq(palette.accent.with_alpha(0.10)));
}
draw_code_text_line(
&format!("{:>2}", index + 162),
body.x + 20.0,
y,
13.0,
to_mq(palette.muted),
);
let visible_line = truncate_str(line, ((body.width - 82.0) / 8.2) as usize);
draw_code_text_line(
&visible_line,
body.x + 58.0,
y,
14.0,
to_mq(code_line_color(line, palette)),
);
y += 30.0;
}
let cursor_x = body.x + 58.0 + measure_code_text_width(" rows.push", 14.0);
let cursor_y = body.y + 34.0 + 10.0 * 30.0;
draw_rectangle(
cursor_x,
cursor_y - 17.0,
2.0,
22.0,
to_mq(palette.warning.with_alpha(0.55 + time.sin().abs() * 0.35)),
);
}
fn draw_assistant_stream(
rect: Rect,
palette: &Palette,
doc: &OutputDocument,
stream_chars: usize,
stream_total: usize,
time: f32,
) {
draw_glass_panel(rect, 28.0, palette, palette.warning, true);
draw_text_line(
"assistant stream",
rect.x + 24.0,
rect.y + 34.0,
20.0,
to_mq(palette.text),
);
draw_pill(
Rect::new(rect.right() - 132.0, rect.y + 16.0, 108.0, 28.0),
&format!("{} chars", stream_chars),
palette.warning,
palette.text,
);
let body = Rect::new(
rect.x + 20.0,
rect.y + 62.0,
rect.width - 40.0,
rect.height - 88.0,
);
draw_rounded_rect(body, 22.0, rgba8(1, 5, 16, 158));
draw_rounded_rect_lines(body, 22.0, 1.0, to_mq(palette.warning.with_alpha(0.26)));
draw_stream_status(body, palette, doc, stream_chars, stream_total, time);
draw_output_rows(
Rect::new(
body.x + 12.0,
body.y + 92.0,
body.width - 24.0,
body.height - 108.0,
),
palette,
doc,
time,
);
}
fn draw_stream_status(
rect: Rect,
palette: &Palette,
doc: &OutputDocument,
stream_chars: usize,
stream_total: usize,
time: f32,
) {
let band = Rect::new(rect.x + 14.0, rect.y + 14.0, rect.width - 28.0, 58.0);
let progress = stream_chars as f32 / stream_total.max(1) as f32;
let cards = [
(
"sanitize",
format!("{} esc", doc.removed_escape_sequences),
palette.warning,
),
("shape", format!("{} rows", doc.rows.len()), palette.accent),
("copy", format!("{}b", doc.copy_text.len()), palette.success),
];
let gap = 8.0;
let card_w = (band.width - gap * 2.0) / 3.0;
for (index, (label, value, color)) in cards.iter().enumerate() {
let card = Rect::new(band.x + index as f32 * (card_w + gap), band.y, card_w, 44.0);
draw_rounded_rect(card, 14.0, rgba8(0, 3, 12, 132));
draw_rounded_rect_lines(card, 14.0, 1.0, to_mq(color.with_alpha(0.30)));
draw_text_line(
label,
card.x + 10.0,
card.y + 17.0,
10.0,
to_mq(palette.muted),
);
draw_text_line(value, card.x + 10.0, card.y + 35.0, 14.0, to_mq(*color));
draw_circle(
card.right() - 13.0,
card.y + 14.0,
3.0 + (time * 1.7 + index as f32).sin().abs() * 2.0,
to_mq(color.with_alpha(0.58)),
);
}
let rail = Rect::new(band.x, band.bottom() - 6.0, band.width, 5.0);
draw_rounded_rect(rail, 2.5, rgba8(255, 255, 255, 18));
draw_rounded_rect(
Rect::new(
rail.x,
rail.y,
rail.width * progress.clamp(0.0, 1.0),
rail.height,
),
2.5,
to_mq(palette.warning.with_alpha(0.70)),
);
}
fn draw_output_rows(rect: Rect, palette: &Palette, doc: &OutputDocument, time: f32) {
let mut y = rect.y + 30.0;
let left = rect.x + 18.0;
let width = rect.width - 36.0;
for row in doc.visible_rows(0, 18) {
if y > rect.bottom() - 18.0 {
break;
}
match row {
OutputRow::Blank => y += 12.0,
OutputRow::Heading { level, text } => {
draw_text_line(&format!("H{level}"), left, y, 12.0, to_mq(palette.accent));
draw_wrapped_text(
text,
left + 36.0,
y,
width - 36.0,
17.0,
21.0,
to_mq(palette.text),
);
y += 34.0;
}
OutputRow::Paragraph { text, .. } => {
y = draw_wrapped_text(text, left, y, width, 14.0, 18.0, to_mq(palette.text));
}
OutputRow::Bullet { text, .. } => {
draw_text_line("+", left, y, 15.0, to_mq(palette.success));
y = draw_wrapped_text(
text,
left + 22.0,
y,
width - 22.0,
14.0,
18.0,
to_mq(palette.text),
);
}
OutputRow::Quote { text, .. } => {
draw_line(left, y - 14.0, left, y + 8.0, 2.0, to_mq(palette.warning));
y = draw_wrapped_text(
text,
left + 16.0,
y,
width - 16.0,
14.0,
18.0,
to_mq(palette.muted),
);
}
OutputRow::CodeFenceStart { language } => {
draw_pill(
Rect::new(left, y - 17.0, 96.0, 24.0),
language,
palette.accent,
palette.text,
);
y += 18.0;
}
OutputRow::Code { gutter, tokens, .. } => {
let line = Rect::new(left, y - 16.0, width, 24.0);
draw_rounded_rect(line, 7.0, rgba8(0, 3, 12, 132));
draw_code_text_line(
gutter.as_deref().unwrap_or(""),
left + 8.0,
y,
12.0,
to_mq(palette.muted),
);
let mut x = left + 58.0;
let max_x = line.right() - 10.0;
for token in tokens {
let color = token_color(token.kind, palette);
let token_w = measure_code_text_width(&token.text, 12.0);
if x + token_w > max_x {
let remaining = ((max_x - x) / 7.0).max(0.0) as usize;
if remaining > 1 {
let clipped = truncate_str(&token.text, remaining);
draw_code_text_line(&clipped, x, y, 12.0, to_mq(color));
}
break;
}
draw_code_text_line(&token.text, x, y, 12.0, to_mq(color));
x += token_w;
}
y += 26.0;
}
OutputRow::CodeFenceEnd => y += 8.0,
}
}
draw_circle(
rect.right() - 24.0,
rect.bottom() - 22.0,
4.0 + time.sin().abs() * 3.0,
to_mq(palette.warning.with_alpha(0.66)),
);
}
fn draw_bottom_tools(
rect: Rect,
palette: &Palette,
doc: &OutputDocument,
commands: &CommandPalette,
snippets: &SnippetSet,
time: f32,
) {
draw_glass_panel(rect, 26.0, palette, palette.accent, false);
let gap = 18.0;
let metric = Rect::new(rect.x + 22.0, rect.y + 24.0, 300.0, rect.height - 48.0);
let command = Rect::new(metric.right() + gap, metric.y, 430.0, metric.height);
let prompt = Rect::new(
command.right() + gap,
metric.y,
rect.right() - command.right() - gap - 22.0,
metric.height,
);
draw_sanitizer_metrics(metric, palette, doc);
draw_command_matches(command, palette, commands, snippets);
draw_prompt_bar(prompt, palette, time);
}
fn draw_sanitizer_metrics(rect: Rect, palette: &Palette, doc: &OutputDocument) {
draw_rounded_rect(rect, 20.0, rgba8(1, 5, 16, 132));
draw_text_line(
"sanitizer",
rect.x + 16.0,
rect.y + 26.0,
16.0,
to_mq(palette.text),
);
let w = (rect.width - 46.0) / 2.0;
draw_metric(
Rect::new(rect.x + 16.0, rect.y + 42.0, w, 48.0),
"esc",
&doc.removed_escape_sequences.to_string(),
palette.warning,
palette,
);
draw_metric(
Rect::new(rect.x + 30.0 + w, rect.y + 42.0, w, 48.0),
"zero",
&doc.removed_zero_width_chars.to_string(),
palette.success,
palette,
);
}
fn draw_command_matches(
rect: Rect,
palette: &Palette,
commands: &CommandPalette,
snippets: &SnippetSet,
) {
draw_rounded_rect(rect, 20.0, rgba8(1, 5, 16, 132));
draw_text_line(
"commands: stream",
rect.x + 16.0,
rect.y + 26.0,
16.0,
to_mq(palette.text),
);
let mut y = rect.y + 56.0;
for item in commands.filtered_items_limited(2) {
draw_text_line(&item.title, rect.x + 18.0, y, 13.0, to_mq(palette.accent));
if let Some(shortcut) = &item.shortcut {
draw_text_line(
&shortcut.label(),
rect.right() - 92.0,
y,
12.0,
to_mq(palette.warning),
);
}
y += 24.0;
}
for snippet in snippets.matches_limited("ship", 1) {
draw_pill(
Rect::new(rect.x + 16.0, rect.bottom() - 34.0, rect.width - 32.0, 26.0),
&format!("{} -> {}", snippet.trigger, snippet.body),
palette.success,
palette.text,
);
}
}
fn draw_prompt_bar(rect: Rect, palette: &Palette, time: f32) {
draw_rounded_rect(rect, 20.0, rgba8(1, 5, 16, 132));
draw_text_line(
"prompt",
rect.x + 16.0,
rect.y + 26.0,
16.0,
to_mq(palette.accent),
);
draw_text_line(
"/fix @workspace !ship",
rect.x + 18.0,
rect.y + 62.0,
20.0,
to_mq(palette.text),
);
let cursor_x = rect.x + 18.0 + measure_demo_prompt_width("/fix @workspace !ship", 20.0);
draw_rectangle(
cursor_x + 4.0,
rect.y + 43.0,
2.0,
26.0,
to_mq(palette.accent.with_alpha(0.42 + time.sin().abs() * 0.38)),
);
}
fn measure_demo_prompt_width(text: &str, size: f32) -> f32 {
measure_demo_text_width(text, size)
}
fn token_color(kind: CodeTokenKind, palette: &Palette) -> Rgba {
match kind {
CodeTokenKind::Text => palette.text,
CodeTokenKind::Keyword => palette.accent,
CodeTokenKind::String => palette.success,
CodeTokenKind::Number => palette.warning,
CodeTokenKind::Comment => palette.muted,
CodeTokenKind::Punctuation => Rgba::rgb8(205, 130, 255),
}
}
fn code_line_color(line: &str, palette: &Palette) -> Rgba {
if line.trim_start().starts_with("//") {
palette.muted
} else if line.contains("pub fn") || line.contains("let ") || line.contains("for ") {
palette.accent
} else if line.contains("Output") || line.contains("sanitize") {
palette.success
} else {
palette.text
}
}