text editor inspired vim and yi
import ViE.UI
import ViE.Config
import Test.Utils

namespace Test.SearchHighlight

open Test.Utils
open ViE


def test : IO Unit := do
  IO.println "Starting Search Highlight Test..."

  let hitRanges : Array (Nat × Nat) := #[(0, 4), (10, 14), (20, 24)]

  assertEqual "Active match picks cursor candidate" (some (10, 14)) (ViE.UI.activeMatchRange hitRanges (some 11))
  assertEqual "No active match when cursor is outside all matches" none (ViE.UI.activeMatchRange hitRanges (some 5))
  assertEqual "No active match when cursor is none" none (ViE.UI.activeMatchRange hitRanges none)

  let active := ViE.UI.activeMatchRange hitRanges (some 11)
  let activeHead :=
    match active with
    | some m => ViE.UI.overlapsByteRange m 0 1
    | none => false
  let activeBody :=
    match active with
    | some m => ViE.UI.overlapsByteRange m 11 12
    | none => false

  assertEqual "Active candidate does not color other matches" false activeHead
  assertEqual "Active candidate colors only its own range" true activeBody

  let s0 := ViE.startOrUpdateSearch ViE.initialState "abc" .forward false
  let (m1, s1) := ViE.UI.getLineSearchMatches s0 s0.getActiveBuffer.id 0 "abc abc"
  assertEqual "Line search cache computes matches" true (m1.size > 0)
  let cachedBeforeSwitch :=
    match s1.searchState with
    | some ss => ss.lineMatches.contains 0
    | none => false
  assertEqual "Line search cache populated" true cachedBeforeSwitch
  let s2 := ViE.UI.ensureSearchLineCacheForBuffer s1 9999
  let cacheClearedOnSwitch :=
    match s2.searchState with
    | some ss => ss.lineMatches.isEmpty && ss.lineCacheBufferId == some 9999
    | none => false
  assertEqual "Line search cache resets on buffer switch" true cacheClearedOnSwitch

  let sInc0 := ViE.startOrUpdateSearch ViE.initialState "abc" .forward false
  let sInc1 := {
    sInc0 with
      mode := .searchForward
      inputState := { sInc0.inputState with commandBuffer := "" }
  }
  let sInc2 := ViE.runIncrementalSearch sInc1
  let incCleared :=
    match sInc2.searchState with
    | none => true
    | some _ => false
  assertEqual "Incremental search with empty pattern clears highlight state" true incCleared

  let t0 := 1000
  let (p0, k0) := ViE.parseKey ViE.initialState '\x1b' t0
  let (p1, k1) := ViE.parseKey p0 '[' (t0 + 1)
  let (p2, k2) := ViE.parseKey p1 '1' (t0 + 2)
  let (p3, k3) := ViE.parseKey p2 ';' (t0 + 3)
  let (p4, k4) := ViE.parseKey p3 '2' (t0 + 4)
  let (p5, k5) := ViE.parseKey p4 'X' (t0 + 5)
  assertEqual "Unknown CSI emits no keys while pending" ([] : List Key) (k0 ++ k1 ++ k2 ++ k3 ++ k4)
  assertEqual "Unknown CSI final emits Key.unknown" [Key.unknown 'X'] k5
  assertEqual "Unknown CSI clears pending state" "" p5.inputState.pendingKeys

  let chars : List Char := ['\x1b', '[', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3']
  let (pLong, emitted) := chars.foldl
    (fun (acc : EditorState × List Key) ch =>
      let (st, out) := acc
      let (st', keys) := ViE.parseKey st ch (t0 + 20)
      (st', out ++ keys))
    (ViE.initialState, [])
  assertEqual "Overlong unknown CSI flushes emitted keys" true (!emitted.isEmpty)
  assertEqual "Overlong unknown CSI clears pending state" "" pLong.inputState.pendingKeys

  let (_, tabKeys) := ViE.parseKey ViE.initialState '\x09' (t0 + 30)
  assertEqual "Tab parses as char tab" [Key.char '\t'] tabKeys

end Test.SearchHighlight