import ViE.State.Config
import ViE.Config
import ViE.Command.Impl
import ViE.Key.Map
import ViE.State.Edit
import ViE.State.Search
import ViE.Data.PieceTable.Tree
import ViE.UI
namespace ViE.Benchmark
open ViE
/-- Mock Config for Benchmarking -/
def makeBenchConfig (buildLeafBits : Option Bool := none) : Config :=
let base := ViE.defaultConfig
let settings :=
match buildLeafBits with
| some v => { base with searchBloomBuildLeafBits := v }
| none => base
{
settings := settings
commands := ViE.Command.defaultCommandMap
bindings := ViE.Key.makeKeyMap ViE.Command.defaultCommandMap
}
structure BenchOptions where
iterations : Nat := 1000
render : Bool := true
cases : List String := []
textLines : Nat := 200
lineLen : Nat := 80
warmup : Nat := 0
buildLeafBits : Option Bool := none
listOnly : Bool := false
def parseArgs (args : List String) : BenchOptions :=
let rec loop (opts : BenchOptions) (args : List String) : BenchOptions :=
match args with
| [] => opts
| "--no-render" :: rest => loop { opts with render := false } rest
| "--case" :: name :: rest => loop { opts with cases := opts.cases ++ [name] } rest
| "--lines" :: n :: rest =>
match n.toNat? with
| some v => loop { opts with textLines := v } rest
| none => loop opts rest
| "--line-len" :: n :: rest =>
match n.toNat? with
| some v => loop { opts with lineLen := v } rest
| none => loop opts rest
| "--warmup" :: n :: rest =>
match n.toNat? with
| some v => loop { opts with warmup := v } rest
| none => loop opts rest
| "--bloom-leaf-bits" :: rest => loop { opts with buildLeafBits := some true } rest
| "--no-bloom-leaf-bits" :: rest => loop { opts with buildLeafBits := some false } rest
| "--list" :: rest => loop { opts with listOnly := true } rest
| arg :: rest =>
match arg.toNat? with
| some v => loop { opts with iterations := v } rest
| none => loop opts rest
loop {} args
/-- Simple timer helper -/
def timeCase (label : String) (iterations : Nat) (f : IO Unit) : IO Unit := do
let t0 ← IO.monoMsNow
f
let t1 ← IO.monoMsNow
let ms := t1 - t0
let opsPerSec := if ms == 0 then 0 else (iterations * 1000) / ms
IO.println s!"[bench] {label}: {ms} ms ({opsPerSec} ops/s)"
/-- Build a simple multi-line ASCII buffer for search benchmarks. -/
def buildText (lines : Nat) (lineLen : Nat) : String :=
let line := String.ofList (List.replicate lineLen 'a')
String.intercalate "\n" (List.replicate lines line)
/-- Insert a needle in the middle of text for search benchmarks. -/
def buildSearchText (lines : Nat) (lineLen : Nat) (needle : String) : String :=
let baseLine := String.ofList (List.replicate lineLen 'a')
let mid := lines / 2
let leftLen := lineLen / 2
let rightLen := if lineLen > leftLen + needle.length then lineLen - leftLen - needle.length else 0
let needleLine :=
(String.ofList (List.replicate leftLen 'a')) ++
needle ++
(String.ofList (List.replicate rightLen 'a'))
let linesArr := Id.run do
let mut arr := Array.replicate lines baseLine
if mid < arr.size then
arr := arr.set! mid needleLine
return arr
String.intercalate "\n" linesArr.toList
/-- Case: Large insert workload (EditorState). -/
def benchInsert (iterations : Nat) : IO Unit := do
let mut s := ViE.initialState
for i in [0:iterations] do
s := s.insertChar 'a'
if i % 100 == 0 then
s := s.commitEdit
/-- Case: Mixed small edits/movements. -/
def benchEditMix (iterations : Nat) : IO Unit := do
let mut s := ViE.initialState
for _ in [0:iterations] do
s := s.insertChar 'a'
s := s.moveCursorLeft
s := s.insertChar 'b'
s := s.moveCursorRight
/-- Case: Clipboard (yank/paste). -/
def benchClipboard (iterations : Nat) : IO Unit := do
let mut s := ViE.initialState
s := s.insertChar 'x'
for _ in [0:iterations/50] do
s := s.yankCurrentLine
s := s.pasteBelow
/-- Case: Workgroup churn. -/
def benchWorkgroups (iterations : Nat) : IO Unit := do
let mut s := ViE.initialState
for i in [0:iterations/200] do
s := ← ViE.Command.cmdWg ["new", s!"BenchGroup {i}"] s
s := s.insertChar 'w'
for _ in [0:iterations/200] do
s := ← ViE.Command.cmdWg ["close"] s
/-- Case: Window splits/cycles. -/
def benchWindows (iterations : Nat) : IO Unit := do
let mut s := ViE.initialState
for _ in [0:iterations/500] do
s := ViE.Window.splitWindow s true
s := ViE.Window.splitWindow s false
s := s.insertChar 'v'
s := ViE.Window.cycleWindow s
for _ in [0:iterations/200] do
s := ViE.Window.closeActiveWindow s
/-- Case: Undo/Redo stress. -/
def benchUndoRedo (iterations : Nat) : IO Unit := do
let mut s := ViE.initialState
for i in [0:iterations] do
s := s.insertChar 'a'
if i % 50 == 0 then
s := s.commitEdit
for _ in [0:iterations/10] do
s := s.undo
for _ in [0:iterations/10] do
s := s.redo
/-- Case: Search workload (PieceTable). -/
def benchSearch (iterations : Nat) (useBloom : Bool) (lines lineLen : Nat) (buildLeafBits : Bool) (cacheMax : Nat) : IO Unit := do
let needle := "needle"
let text := buildSearchText lines lineLen needle
let pt := PieceTable.fromString text buildLeafBits
let pattern := needle.toUTF8
let mut offset := 0
let mut cache : Lean.RBMap Nat ByteArray compare := Lean.RBMap.empty
let mut order : Array Nat := #[]
for _ in [0:iterations] do
let (res, cache', order') := PieceTree.searchNext pt.tree pt pattern offset searchChunkSize useBloom cache order cacheMax
cache := cache'
order := order'
offset := match res with
| some r => r + 1
| none => 0
/-- Case: Render workload. -/
def benchRender (iterations : Nat) : IO Unit := do
let mut s := ViE.initialState
for _ in [0:iterations/20] do
s := s.insertChar 'a'
let _ ← ViE.UI.render s
def availableCases : List String :=
[ "insert", "edit", "clipboard", "workgroups", "windows", "undo", "search-bloom", "search-linear", "render" ]
/-- Run benchmark cases. -/
def runBenchmark (opts : BenchOptions) : IO Unit := do
let buildLeafBits := opts.buildLeafBits.getD ViE.defaultConfig.searchBloomBuildLeafBits
let config := makeBenchConfig (some buildLeafBits)
let cacheMax := config.settings.searchBloomCacheMax
let cases := if opts.cases.isEmpty then availableCases else opts.cases
IO.println s!"Starting benchmark: iter={opts.iterations}, render={opts.render}"
if opts.warmup > 0 then
IO.println s!"Warmup: {opts.warmup} iterations"
for _ in [0:opts.warmup] do
let mut s := ViE.initialState
s := s.insertChar 'a'
let _ ← pure s
for c in cases do
match c with
| "insert" => timeCase "insert" opts.iterations (benchInsert opts.iterations)
| "edit" => timeCase "edit" opts.iterations (benchEditMix opts.iterations)
| "clipboard" => timeCase "clipboard" opts.iterations (benchClipboard opts.iterations)
| "workgroups" => timeCase "workgroups" opts.iterations (benchWorkgroups opts.iterations)
| "windows" => timeCase "windows" opts.iterations (benchWindows opts.iterations)
| "undo" => timeCase "undo/redo" opts.iterations (benchUndoRedo opts.iterations)
| "search-bloom" =>
timeCase "search-bloom" opts.iterations (benchSearch opts.iterations true opts.textLines opts.lineLen buildLeafBits cacheMax)
| "search-linear" =>
timeCase "search-linear" opts.iterations (benchSearch opts.iterations false opts.textLines opts.lineLen buildLeafBits cacheMax)
| "render" =>
if opts.render then
timeCase "render" opts.iterations (benchRender opts.iterations)
else
IO.println "[bench] render skipped (--no-render)"
| other =>
IO.println s!"[bench] Unknown case: {other}"
IO.println "Benchmark completed."
end ViE.Benchmark
/-- CLI entrypoint. -/
def main (args : List String) : IO Unit := do
let opts := ViE.Benchmark.parseArgs args
if opts.listOnly then
IO.println "Available cases:"
IO.println (String.intercalate ", " ViE.Benchmark.availableCases)
else
ViE.Benchmark.runBenchmark opts