chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use jcode::id::new_id;
|
||||
use jcode::message::{Message, ToolDefinition};
|
||||
use jcode::provider::{EventStream, Provider};
|
||||
use jcode::tool::{Registry, ToolContext, ToolExecutionMode};
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "jcode-harness")]
|
||||
#[command(about = "Run a deterministic tool harness smoke test")]
|
||||
struct Args {
|
||||
/// Use an explicit working directory (defaults to a temp folder).
|
||||
#[arg(long)]
|
||||
cwd: Option<String>,
|
||||
|
||||
/// Include network-backed tools (webfetch/websearch).
|
||||
#[arg(long)]
|
||||
include_network: bool,
|
||||
}
|
||||
|
||||
struct NoopProvider;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Provider for NoopProvider {
|
||||
async fn complete(
|
||||
&self,
|
||||
_messages: &[Message],
|
||||
_tools: &[ToolDefinition],
|
||||
_system: &str,
|
||||
_resume_session_id: Option<&str>,
|
||||
) -> Result<EventStream> {
|
||||
anyhow::bail!("Noop provider - tool harness does not invoke models.")
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"noop"
|
||||
}
|
||||
|
||||
fn fork(&self) -> Arc<dyn Provider> {
|
||||
Arc::new(NoopProvider)
|
||||
}
|
||||
|
||||
fn available_models_display(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
async fn prefetch_models(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct ToolCase {
|
||||
name: &'static str,
|
||||
input: serde_json::Value,
|
||||
label: &'static str,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let workspace = if let Some(cwd) = args.cwd {
|
||||
PathBuf::from(cwd)
|
||||
} else {
|
||||
create_temp_workspace()?
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&workspace)?;
|
||||
std::env::set_current_dir(&workspace)?;
|
||||
eprintln!("Harness workspace: {}", workspace.display());
|
||||
|
||||
let provider: Arc<dyn Provider> = Arc::new(NoopProvider);
|
||||
let registry = Registry::new(provider).await;
|
||||
|
||||
let session_id = new_id("harness");
|
||||
let base_ctx = ToolContext {
|
||||
session_id: session_id.clone(),
|
||||
message_id: session_id.clone(),
|
||||
tool_call_id: String::new(),
|
||||
working_dir: Some(workspace.clone()),
|
||||
stdin_request_tx: None,
|
||||
graceful_shutdown_signal: None,
|
||||
execution_mode: ToolExecutionMode::Direct,
|
||||
};
|
||||
|
||||
let mut cases = Vec::new();
|
||||
cases.push(ToolCase {
|
||||
name: "write",
|
||||
label: "write sample.txt",
|
||||
input: json!({"file_path": "sample.txt", "content": "alpha\nbeta\n"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "read",
|
||||
label: "read sample.txt",
|
||||
input: json!({"file_path": "sample.txt"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "edit",
|
||||
label: "edit sample.txt (alpha -> alpha1)",
|
||||
input: json!({"file_path": "sample.txt", "old_string": "alpha", "new_string": "alpha1"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "multiedit",
|
||||
label: "multiedit sample.txt",
|
||||
input: json!({
|
||||
"file_path": "sample.txt",
|
||||
"edits": [
|
||||
{"old_string": "alpha1", "new_string": "alpha2"},
|
||||
{"old_string": "beta", "new_string": "beta1"}
|
||||
]
|
||||
}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "patch",
|
||||
label: "patch sample.txt",
|
||||
input: json!({"patch_text": "--- a/sample.txt\n+++ b/sample.txt\n@@ -1,2 +1,3 @@\n alpha2\n beta1\n+gamma\n"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "apply_patch",
|
||||
label: "apply_patch add file",
|
||||
input: json!({"patch_text": "*** Begin Patch\n*** Add File: added.txt\n+added\n*** End Patch\n"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "ls",
|
||||
label: "ls .",
|
||||
input: json!({"path": "."}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "bash",
|
||||
label: "bash pwd",
|
||||
input: json!({"command": "pwd"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "invalid",
|
||||
label: "invalid tool call",
|
||||
input: json!({"tool": "unknown", "error": "missing required field"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "todo",
|
||||
label: "todo write",
|
||||
input: json!({"todos": [{"content": "harness task", "status": "pending", "priority": "low", "id": "1"}]}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "todo",
|
||||
label: "todo read",
|
||||
input: json!({}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "batch",
|
||||
label: "batch ls + read",
|
||||
input: json!({
|
||||
"tool_calls": [
|
||||
{"tool": "ls", "parameters": {"path": "."}},
|
||||
{"tool": "read", "parameters": {"file_path": "sample.txt"}}
|
||||
]
|
||||
}),
|
||||
});
|
||||
|
||||
if args.include_network {
|
||||
cases.push(ToolCase {
|
||||
name: "webfetch",
|
||||
label: "webfetch example.com",
|
||||
input: json!({"url": "https://example.com", "format": "text"}),
|
||||
});
|
||||
cases.push(ToolCase {
|
||||
name: "websearch",
|
||||
label: "websearch rust async",
|
||||
input: json!({"query": "rust async await"}),
|
||||
});
|
||||
}
|
||||
|
||||
for (idx, case) in cases.iter().enumerate() {
|
||||
let ctx = ToolContext {
|
||||
tool_call_id: format!("harness-{}", idx + 1),
|
||||
..base_ctx.clone()
|
||||
};
|
||||
println!("\n== {} ({}) ==", case.name, case.label);
|
||||
match registry.execute(case.name, case.input.clone(), ctx).await {
|
||||
Ok(output) => {
|
||||
if let Some(title) = output.title {
|
||||
println!("[title] {}", title);
|
||||
}
|
||||
println!("{}", output.output);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("[error] {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_temp_workspace() -> Result<PathBuf> {
|
||||
let mut path = std::env::temp_dir();
|
||||
path.push(format!("jcode-harness-{}", new_id("run")));
|
||||
Ok(path)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
use std::env;
|
||||
|
||||
fn usage() -> &'static str {
|
||||
"usage: cargo run --features dev-bins --bin mermaid_side_panel_probe -- <mermaid-file> [--pane-width N] [--pane-height N] [--font-width N] [--font-height N] [--left]"
|
||||
}
|
||||
|
||||
fn parse_u16_arg(args: &mut std::vec::IntoIter<String>, flag: &str) -> Result<u16> {
|
||||
let value = args
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("missing value for {flag}"))?;
|
||||
value
|
||||
.parse::<u16>()
|
||||
.with_context(|| format!("invalid integer for {flag}: {value}"))
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut args = env::args().skip(1).collect::<Vec<_>>().into_iter();
|
||||
let mut path: Option<String> = None;
|
||||
let mut pane_width: u16 = 36;
|
||||
let mut pane_height: u16 = 30;
|
||||
let mut font_width: u16 = 8;
|
||||
let mut font_height: u16 = 16;
|
||||
let mut centered = true;
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--pane-width" => pane_width = parse_u16_arg(&mut args, "--pane-width")?,
|
||||
"--pane-height" => pane_height = parse_u16_arg(&mut args, "--pane-height")?,
|
||||
"--font-width" => font_width = parse_u16_arg(&mut args, "--font-width")?,
|
||||
"--font-height" => font_height = parse_u16_arg(&mut args, "--font-height")?,
|
||||
"--left" => centered = false,
|
||||
"--help" | "-h" => {
|
||||
println!("{}", usage());
|
||||
return Ok(());
|
||||
}
|
||||
value if value.starts_with('-') => {
|
||||
return Err(anyhow!("unknown flag: {value}\n{}", usage()));
|
||||
}
|
||||
value => {
|
||||
if path.is_some() {
|
||||
return Err(anyhow!(
|
||||
"only one mermaid file path is supported\n{}",
|
||||
usage()
|
||||
));
|
||||
}
|
||||
path = Some(value.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let path = path.ok_or_else(|| anyhow!("missing mermaid file path\n{}", usage()))?;
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.with_context(|| format!("failed to read mermaid file: {path}"))?;
|
||||
let probe = jcode::tui::debug_probe_side_panel_mermaid(
|
||||
&content,
|
||||
pane_width,
|
||||
pane_height,
|
||||
Some((font_width, font_height)),
|
||||
centered,
|
||||
)?;
|
||||
println!("{}", serde_json::to_string_pretty(&probe)?);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
use clap::{Parser, ValueEnum};
|
||||
use jcode::message::{ContentBlock, Role};
|
||||
use jcode::process_memory;
|
||||
use jcode::session::Session;
|
||||
use jcode::side_panel::{
|
||||
SidePanelPage, SidePanelPageFormat, SidePanelPageSource, SidePanelSnapshot,
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about = "Benchmark heavy-session memory attribution and process footprint")]
|
||||
struct Args {
|
||||
/// Scenario source
|
||||
#[arg(long, value_enum, default_value = "synthetic")]
|
||||
scenario: Scenario,
|
||||
|
||||
/// Saved session id or path (required for --scenario saved)
|
||||
#[arg(long)]
|
||||
session: Option<String>,
|
||||
|
||||
/// Memory mode to benchmark
|
||||
#[arg(long, value_enum, default_value = "local")]
|
||||
mode: BenchMode,
|
||||
|
||||
/// Synthetic turns to generate
|
||||
#[arg(long, default_value_t = 24)]
|
||||
turns: usize,
|
||||
|
||||
/// Synthetic tool input size in KiB per turn
|
||||
#[arg(long, default_value_t = 4)]
|
||||
tool_input_kib: usize,
|
||||
|
||||
/// Synthetic tool output size in KiB per turn
|
||||
#[arg(long, default_value_t = 48)]
|
||||
tool_output_kib: usize,
|
||||
|
||||
/// Synthetic side-panel page count
|
||||
#[arg(long, default_value_t = 0)]
|
||||
side_panel_pages: usize,
|
||||
|
||||
/// Synthetic side-panel content size in KiB per page
|
||||
#[arg(long, default_value_t = 32)]
|
||||
side_panel_page_kib: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, ValueEnum)]
|
||||
enum Scenario {
|
||||
Synthetic,
|
||||
Saved,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, ValueEnum)]
|
||||
enum BenchMode {
|
||||
/// Current local steady state: canonical session + display, provider view only transient
|
||||
Local,
|
||||
/// Simulated pre-refactor duplicate steady state: keep a resident provider copy too
|
||||
Duplicated,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let process_before = process_memory::snapshot_with_source("bench:session-memory:before");
|
||||
let session = load_or_build_session(&args)?;
|
||||
let display_messages = jcode::tui::display_messages_from_session(&session);
|
||||
let side_panel = build_side_panel(&args);
|
||||
|
||||
let resident_provider_messages = match args.mode {
|
||||
BenchMode::Local => Vec::new(),
|
||||
BenchMode::Duplicated => session.messages_for_provider_uncached(),
|
||||
};
|
||||
|
||||
let process_after_build =
|
||||
process_memory::snapshot_with_source("bench:session-memory:after-build");
|
||||
|
||||
let materialized_provider_messages = match args.mode {
|
||||
BenchMode::Local => session.messages_for_provider_uncached(),
|
||||
BenchMode::Duplicated => resident_provider_messages.clone(),
|
||||
};
|
||||
let provider_view_source = match args.mode {
|
||||
BenchMode::Local => "session_materialized",
|
||||
BenchMode::Duplicated => "resident_ui",
|
||||
};
|
||||
|
||||
let client_memory = jcode::tui::transcript_memory_profile(
|
||||
&session,
|
||||
&resident_provider_messages,
|
||||
&materialized_provider_messages,
|
||||
provider_view_source,
|
||||
&display_messages,
|
||||
&side_panel,
|
||||
);
|
||||
|
||||
let process_after_profile =
|
||||
process_memory::snapshot_with_source("bench:session-memory:after-profile");
|
||||
|
||||
drop(materialized_provider_messages);
|
||||
let process_after_drop_transient =
|
||||
process_memory::snapshot_with_source("bench:session-memory:after-drop-transient");
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"scenario": match args.scenario {
|
||||
Scenario::Synthetic => "synthetic",
|
||||
Scenario::Saved => "saved",
|
||||
},
|
||||
"mode": match args.mode {
|
||||
BenchMode::Local => "local",
|
||||
BenchMode::Duplicated => "duplicated",
|
||||
},
|
||||
"config": {
|
||||
"turns": args.turns,
|
||||
"tool_input_kib": args.tool_input_kib,
|
||||
"tool_output_kib": args.tool_output_kib,
|
||||
"side_panel_pages": args.side_panel_pages,
|
||||
"side_panel_page_kib": args.side_panel_page_kib,
|
||||
"session": args.session,
|
||||
},
|
||||
"counts": {
|
||||
"session_messages": session.messages.len(),
|
||||
"display_messages": display_messages.len(),
|
||||
"resident_provider_messages": resident_provider_messages.len(),
|
||||
"side_panel_pages": side_panel.pages.len(),
|
||||
},
|
||||
"process": {
|
||||
"before": process_before,
|
||||
"after_build": process_after_build,
|
||||
"after_profile": process_after_profile,
|
||||
"after_drop_transient": process_after_drop_transient,
|
||||
},
|
||||
"client_memory": client_memory,
|
||||
});
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&payload)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_or_build_session(args: &Args) -> anyhow::Result<Session> {
|
||||
match args.scenario {
|
||||
Scenario::Synthetic => Ok(build_synthetic_session(
|
||||
args.turns,
|
||||
args.tool_input_kib,
|
||||
args.tool_output_kib,
|
||||
)),
|
||||
Scenario::Saved => {
|
||||
let Some(value) = args.session.as_deref() else {
|
||||
anyhow::bail!("--session is required with --scenario saved");
|
||||
};
|
||||
let path = std::path::Path::new(value);
|
||||
if path.exists() {
|
||||
Session::load_from_path(path)
|
||||
} else {
|
||||
Session::load(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_synthetic_session(turns: usize, tool_input_kib: usize, tool_output_kib: usize) -> Session {
|
||||
let mut session = Session::create_with_id(
|
||||
format!("session_memory_bench_{}", std::process::id()),
|
||||
None,
|
||||
Some("session memory bench".to_string()),
|
||||
);
|
||||
let tool_input_bytes = tool_input_kib * 1024;
|
||||
let tool_output_bytes = tool_output_kib * 1024;
|
||||
|
||||
for idx in 0..turns {
|
||||
session.add_message(
|
||||
Role::User,
|
||||
vec![text_block(make_blob(
|
||||
&format!("user turn {idx} - "),
|
||||
768 + (idx % 5) * 64,
|
||||
))],
|
||||
);
|
||||
session.add_message(
|
||||
Role::Assistant,
|
||||
vec![
|
||||
text_block(make_blob(
|
||||
&format!("assistant summary {idx} - "),
|
||||
1024 + (idx % 7) * 96,
|
||||
)),
|
||||
ContentBlock::ToolUse {
|
||||
id: format!("tool_{idx}"),
|
||||
name: "bash".to_string(),
|
||||
input: serde_json::json!({
|
||||
"command": make_blob(&format!("printf 'turn {idx}' && # "), tool_input_bytes),
|
||||
"description": format!("Synthetic tool call {idx}"),
|
||||
}), thought_signature: None, },
|
||||
],
|
||||
);
|
||||
session.add_message(
|
||||
Role::User,
|
||||
vec![ContentBlock::ToolResult {
|
||||
tool_use_id: format!("tool_{idx}"),
|
||||
content: make_blob(&format!("tool output {idx} - "), tool_output_bytes),
|
||||
is_error: None,
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
||||
session
|
||||
}
|
||||
|
||||
fn build_side_panel(args: &Args) -> SidePanelSnapshot {
|
||||
if args.side_panel_pages == 0 {
|
||||
return SidePanelSnapshot::default();
|
||||
}
|
||||
|
||||
let mut pages = Vec::with_capacity(args.side_panel_pages);
|
||||
for idx in 0..args.side_panel_pages {
|
||||
pages.push(SidePanelPage {
|
||||
id: format!("bench_page_{idx}"),
|
||||
title: format!("Bench Page {idx}"),
|
||||
file_path: format!("/tmp/bench_page_{idx}.md"),
|
||||
format: SidePanelPageFormat::Markdown,
|
||||
source: SidePanelPageSource::Managed,
|
||||
content: make_blob(
|
||||
&format!("# Bench Page {idx}\n\n"),
|
||||
args.side_panel_page_kib * 1024,
|
||||
),
|
||||
updated_at_ms: idx as u64,
|
||||
});
|
||||
}
|
||||
|
||||
SidePanelSnapshot {
|
||||
focused_page_id: pages.first().map(|page| page.id.clone()),
|
||||
pages,
|
||||
}
|
||||
}
|
||||
|
||||
fn text_block(text: String) -> ContentBlock {
|
||||
ContentBlock::Text {
|
||||
text,
|
||||
cache_control: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_blob(prefix: &str, target_len: usize) -> String {
|
||||
if target_len <= prefix.len() {
|
||||
return prefix[..target_len].to_string();
|
||||
}
|
||||
let mut out = String::with_capacity(target_len);
|
||||
out.push_str(prefix);
|
||||
const CHUNK: &str = "abcdefghijklmnopqrstuvwxyz0123456789 ";
|
||||
while out.len() < target_len {
|
||||
out.push_str(CHUNK);
|
||||
}
|
||||
out.truncate(target_len);
|
||||
out
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use futures::StreamExt;
|
||||
use jcode::message::{ContentBlock, Message, ToolDefinition};
|
||||
use jcode::provider::Provider;
|
||||
use jcode_provider_claude_cli_runtime::ClaudeProvider;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
println!("Testing deprecated legacy Claude CLI provider...");
|
||||
let provider = ClaudeProvider::new();
|
||||
|
||||
let messages = vec![Message {
|
||||
role: jcode::message::Role::User,
|
||||
content: vec![ContentBlock::Text {
|
||||
text: "Say hello in exactly 5 words.".to_string(),
|
||||
cache_control: None,
|
||||
}],
|
||||
timestamp: None,
|
||||
tool_duration_ms: None,
|
||||
}];
|
||||
|
||||
let tools: Vec<ToolDefinition> = vec![];
|
||||
let system = "You are a helpful assistant.";
|
||||
|
||||
println!("Sending request...");
|
||||
let mut stream = provider.complete(&messages, &tools, system, None).await?;
|
||||
|
||||
println!("Response:");
|
||||
while let Some(event) = stream.next().await {
|
||||
match event {
|
||||
Ok(e) => print!("{:?} ", e),
|
||||
Err(e) => eprintln!("Error: {}", e),
|
||||
}
|
||||
}
|
||||
println!("\nDone!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
use super::{SidePanelSource, make_text};
|
||||
use anyhow::{Context, Result};
|
||||
use jcode::side_panel::{
|
||||
SidePanelPage, SidePanelPageFormat, SidePanelPageSource, SidePanelSnapshot,
|
||||
};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub(super) fn make_bench_file(idx: usize, approx_len: usize) -> Result<PathBuf> {
|
||||
let base_dir = std::env::temp_dir().join("jcode_tui_bench");
|
||||
fs::create_dir_all(&base_dir).with_context(|| {
|
||||
format!(
|
||||
"failed to create TUI bench directory {}",
|
||||
base_dir.display()
|
||||
)
|
||||
})?;
|
||||
let file_path = base_dir.join(format!("file_diff_{idx}.rs"));
|
||||
|
||||
let mut content = String::from("fn bench_file() {\n");
|
||||
let repeated = make_text(approx_len);
|
||||
for line_idx in 0..120 {
|
||||
if line_idx == idx % 120 {
|
||||
content.push_str(&format!(
|
||||
" let line_{line_idx} = \"target line {idx}\";\n"
|
||||
));
|
||||
} else {
|
||||
content.push_str(&format!(" let line_{line_idx} = \"{}\";\n", repeated));
|
||||
}
|
||||
}
|
||||
content.push_str("}\n");
|
||||
|
||||
fs::write(&file_path, content)
|
||||
.with_context(|| format!("failed to write bench file {}", file_path.display()))?;
|
||||
Ok(file_path)
|
||||
}
|
||||
|
||||
pub(super) fn make_bench_side_panel(
|
||||
approx_len: usize,
|
||||
source: SidePanelSource,
|
||||
mermaid_count: usize,
|
||||
bench_file_paths: &mut Vec<PathBuf>,
|
||||
) -> Result<SidePanelSnapshot> {
|
||||
let content = make_side_panel_content(approx_len, mermaid_count.max(1));
|
||||
let source_kind = match source {
|
||||
SidePanelSource::Managed => SidePanelPageSource::Managed,
|
||||
SidePanelSource::LinkedFile => SidePanelPageSource::LinkedFile,
|
||||
};
|
||||
|
||||
let file_path = match source {
|
||||
SidePanelSource::Managed => std::env::temp_dir()
|
||||
.join("jcode_tui_bench")
|
||||
.join("side_panel_managed.md"),
|
||||
SidePanelSource::LinkedFile => std::env::temp_dir()
|
||||
.join("jcode_tui_bench")
|
||||
.join("side_panel_linked.md"),
|
||||
};
|
||||
fs::create_dir_all(
|
||||
file_path
|
||||
.parent()
|
||||
.unwrap_or_else(|| std::path::Path::new(".")),
|
||||
)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to create side-panel bench directory for {}",
|
||||
file_path.display()
|
||||
)
|
||||
})?;
|
||||
fs::write(&file_path, &content).with_context(|| {
|
||||
format!(
|
||||
"failed to write side-panel bench file {}",
|
||||
file_path.display()
|
||||
)
|
||||
})?;
|
||||
bench_file_paths.push(file_path.clone());
|
||||
|
||||
Ok(SidePanelSnapshot {
|
||||
focused_page_id: Some("bench_side_panel".to_string()),
|
||||
pages: vec![SidePanelPage {
|
||||
id: "bench_side_panel".to_string(),
|
||||
title: format!(
|
||||
"Bench Side Panel ({})",
|
||||
match source {
|
||||
SidePanelSource::Managed => "managed",
|
||||
SidePanelSource::LinkedFile => "linked-file",
|
||||
}
|
||||
),
|
||||
file_path: file_path.display().to_string(),
|
||||
format: SidePanelPageFormat::Markdown,
|
||||
source: source_kind,
|
||||
content,
|
||||
updated_at_ms: 1,
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn make_side_panel_refresh_content(generation: usize) -> String {
|
||||
format!(
|
||||
"# Linked Refresh Benchmark\n\nGeneration: {generation}\n\n{}\n\n```mermaid\nflowchart TD\n A[Refresh {generation}] --> B[Read file]\n B --> C[Update snapshot]\n C --> D[Reuse width cache]\n```\n",
|
||||
make_text(360)
|
||||
)
|
||||
}
|
||||
|
||||
fn make_side_panel_content(approx_len: usize, mermaid_count: usize) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("# Side Panel Benchmark\n\n");
|
||||
for idx in 0..mermaid_count {
|
||||
out.push_str(&format!("## Section {}\n\n", idx + 1));
|
||||
out.push_str(&make_text(approx_len));
|
||||
out.push_str("\n\n");
|
||||
out.push_str("```mermaid\nflowchart TD\n");
|
||||
out.push_str(&format!(
|
||||
" A{idx}[Start {idx}] --> B{idx}[Load content]\n B{idx} --> C{idx}{{Scroll?}}\n C{idx} -- Yes --> D{idx}[Render viewport]\n C{idx} -- No --> E{idx}[Reuse cache]\n D{idx} --> F{idx}[Done]\n E{idx} --> F{idx}[Done]\n"
|
||||
));
|
||||
out.push_str("```\n\n");
|
||||
out.push_str("- scroll interaction\n- markdown wrapping\n- image viewport rendering\n\n");
|
||||
}
|
||||
out.push_str("## Final Notes\n\n");
|
||||
for idx in 0..24 {
|
||||
out.push_str(&format!("- Bench line {:02}: {}\n", idx + 1, make_text(64)));
|
||||
}
|
||||
out
|
||||
}
|
||||
Reference in New Issue
Block a user