SEIYD7YXZ5XI4W7UVF5O2UFLA6OCZZCOLV2XB46RKJ5EN6UQC4UAC KJLCU4X5Z3CZM4AORMWIKOPBWPVTDMLK4LYH7UXBJFUGL7AUQCSQC 6UFPT4GPGYM3ORW35EIY5A7Z34WCTXRSWW45T625F2WPMLGR3NHAC CNJGJCJZ6LRHHIRSRATSE3D3Z5OQGSSTDMXCPVXSKRQLYJFME6IAC 6W6OJAUT3KLOXXKYR4GA3MG4ACLO2T4KX3SGD2KZDX6MYEZYTPHAC WRBKZMYVNHRWT7TTUGTDJ3TMWZB32QYW5PCLKTTVAJ2YF6OI3LTAC XCVPXP4UBFU25NF6VQC3MEYHHH45RL2UXM5TA6O23NA64FPCCCJQC NBG3GUEJJMWS2FRJDU26KRQLA6O2YK77PDAPBMD3SKWLDTK23BVAC U45XPF3YKPXXRJ4MN24T2RV7GOL4FZKQSWX5P5I7WT4HC5XF4FHAC YILSJ2SNU6DNZVSFL4MD6QSNLQHUNSBSTGGEQCFTWFTRR62IXBWAC 5SFTBD4FW6GQPKRUJBAII5R2EZPDYG6CH57BIJD4IVZBE6JZB2ZQC O763V2GX3Z2JT5EOWNAVVGJ2SDDJ3ZMB63D267P2YXG5SIC6RGGAC def activeMatchRange (hitRanges : Array (Nat × Nat)) (cursorByte : Option Nat) : Option (Nat × Nat) :=match cursorByte with| none => none| some cb =>let rec loop (i : Nat) : Option (Nat × Nat) :=if i >= hitRanges.size thennoneelselet m := hitRanges[i]!let (s, e) := mif s <= cb && cb < e thensome melseloop (i + 1)loop 0
searchMatches.any (fun (s, e) => byteStart < e && byteEnd > s)let isCursorMatch :=match cursorByte with| some cb => searchMatches.any (fun (s, e) => s <= cb && cb < e)
searchMatches.any (fun m => overlapsByteRange m byteStart byteEnd)let isActiveMatched :=match activeMatch with| some m => overlapsByteRange m byteStart byteEnd
let cursor := (r.toNat!, c.toNat!)parseFiles rest (fname :: accFiles) (cursor :: accCursors)
match r.toNat?, c.toNat? with| some row, some col =>let cursor := (row, col)parseFiles rest (fname :: accFiles) (cursor :: accCursors)| _, _ => none
def clampCursorInBuffer (buffer : FileBuffer) (row col : Nat) : Point :=let lineCount := buffer.lineCountlet clampedRow := if lineCount == 0 then 0 else min row (lineCount - 1)let lineLen := ViE.getLineLengthFromBuffer buffer ⟨clampedRow⟩ |>.getD 0let clampedCol := if lineLen == 0 then 0 else min col (lineLen - 1){ row := ⟨clampedRow⟩, col := ⟨clampedCol⟩ }/-- Build startup workspace from a restored checkpoint session. -/def buildRestoredWorkspace (settings : EditorConfig) (workspacePath : Option String)(files : List String) (activeIdx : Nat) (cursors : List (Nat × Nat)) : IO WorkspaceState := dolet mut loaded : Array FileBuffer := #[]let mut nextId := 0for fname in files dolet buf ← loadBufferByteArrayWithConfig fname settingsloaded := loaded.push {buf withid := nextIdtable := { buf.table with undoLimit := settings.historyLimit }}nextId := nextId + 1
let wsName := match workspacePath with| some path => (System.FilePath.fileName path).getD defaultWorkspaceName| none => defaultWorkspaceNameif loaded.isEmpty thenlet empty : FileBuffer := {id := 0filename := nonedirty := falsetable := PieceTable.fromString "" settings.searchBloomBuildLeafBitsmissingEol := falsecache := { lineMap := Lean.RBMap.empty, rawLineMap := Lean.RBMap.empty, lineIndexMap := Lean.RBMap.empty }}return {name := wsNamerootPath := workspacePathbuffers := [empty]nextBufferId := 1layout := .window 0 { initialView with bufferId := 0 }activeWindowId := 0nextWindowId := 1}let activeIdx := if activeIdx < loaded.size then activeIdx else 0let activeBuf := loaded[activeIdx]!let (row, col) := List.getD cursors activeIdx (0, 0)let cursor := clampCursorInBuffer activeBuf row colreturn {name := wsNamerootPath := workspacePathbuffers := loaded.toListnextBufferId := loaded.sizelayout := .window 0 { initialView with bufferId := activeBuf.id, cursor := cursor }activeWindowId := 0nextWindowId := 1}
-- Load buffer if file existslet initialBuffer ← match actualFilename with| some fname => loadBufferByteArrayWithConfig fname config.settings| none => pure {id := 0filename := actualFilenamedirty := falsetable := PieceTable.fromString "" config.settings.searchBloomBuildLeafBitsmissingEol := falsecache := { lineMap := Lean.RBMap.empty, rawLineMap := Lean.RBMap.empty, lineIndexMap := Lean.RBMap.empty }}
let restoreFromCheckpoint := args.isEmpty && restoreMeta.isSome && !startArgs.isEmptylet state ←if restoreFromCheckpoint thenmatch restoreMeta with| some (activeIdx, cursors) => dolet restored ← buildRestoredWorkspace config.settings workspacePath startArgs activeIdx cursorslet s := { ViE.initialState withconfig := config.settingsmessage := s!"Restored {restored.buffers.length} file(s)"}pure <| s.updateCurrentWorkspace (fun _ => restored)| none =>pure { ViE.initialState with config := config.settings }else do-- Load buffer if file existslet initialBuffer ← match actualFilename with| some fname => loadBufferByteArrayWithConfig fname config.settings| none => pure {id := 0filename := actualFilenamedirty := falsetable := PieceTable.fromString "" config.settings.searchBloomBuildLeafBitsmissingEol := falsecache := { lineMap := Lean.RBMap.empty, rawLineMap := Lean.RBMap.empty, lineIndexMap := Lean.RBMap.empty }}
-- Check if initial load had an errorlet firstLine := getLineFromBuffer initialBuffer 0 |>.getD ""let hasError := firstLine.startsWith "Error loading file:"
-- Check if initial load had an errorlet firstLine := getLineFromBuffer initialBuffer 0 |>.getD ""let hasError := firstLine.startsWith "Error loading file:"
let state := { ViE.initialState withconfig := config.settings,message := if hasError then firstLineelse match actualFilename with| some f => s!"\"{f}\" [Read]"| none => match workspacePath with| some ws => s!"Workspace: {ws}"| none => "New File"}
let s := { ViE.initialState withconfig := config.settings,message := if hasError then firstLineelse match actualFilename with| some f => s!"\"{f}\" [Read]"| none => match workspacePath with| some ws => s!"Workspace: {ws}"| none => "New File"}
-- Update the first workspace with the initial buffer and root pathlet state := state.updateCurrentWorkspace fun ws =>let wsName := match workspacePath with| some path => (System.FilePath.fileName path).getD ws.name| none => ws.name{ ws withname := wsName,rootPath := workspacePath,buffers := [initialBuffer],nextBufferId := 1}
-- Update the first workspace with the initial buffer and root pathpure <| s.updateCurrentWorkspace fun ws =>let wsName := match workspacePath with| some path => (System.FilePath.fileName path).getD ws.name| none => ws.name{ ws withname := wsName,rootPath := workspacePath,buffers := [initialBuffer],nextBufferId := 1}
import ViE.UIimport Test.Utilsnamespace Test.SearchHighlightopen Test.Utilsdef test : IO Unit := doIO.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 => falselet activeBody :=match active with| some m => ViE.UI.overlapsByteRange m 11 12| none => falseassertEqual "Active candidate does not color other matches" false activeHeadassertEqual "Active candidate colors only its own range" true activeBodyend Test.SearchHighlight
import ViE.Appimport ViE.Checkpointimport ViE.State.Configimport Test.Utilsnamespace Test.Checkpointopen Test.Utilsopen ViEdef testLoadSessionInvalid : IO Unit := doIO.println "Starting Checkpoint Parse Invalid Test..."IO.FS.writeFile ViE.Checkpoint.sessionFile "--ACTIVE--\nnot-a-number\n"let loaded <- ViE.Checkpoint.loadSessionassertEqual "Invalid checkpoint parses as none" none loadeddef testLoadSessionValid : IO Unit := doIO.println "Starting Checkpoint Parse Valid Test..."let content :="/tmp/file-a.txt\n" ++"0 0\n" ++"/tmp/file-b.txt\n" ++"3 4\n" ++"--ACTIVE--\n" ++"1\n"IO.FS.writeFile ViE.Checkpoint.sessionFile contentlet loaded <- ViE.Checkpoint.loadSessionlet expected : Option (List String × Nat × List (Nat × Nat)) :=some (["/tmp/file-a.txt", "/tmp/file-b.txt"], 1, [(0, 0), (3, 4)])assertEqual "Valid checkpoint parsing" expected loadeddef testBuildRestoredWorkspace : IO Unit := doIO.println "Starting Restored Workspace Build Test..."let stamp <- IO.monoMsNowlet root := s!"/tmp/vie-checkpoint-{stamp}"IO.FS.createDirAll rootlet f1 := s!"{root}/a.txt"let f2 := s!"{root}/b.txt"IO.FS.writeFile f1 "alpha\nbeta\n"IO.FS.writeFile f2 "line0\nline1\nline2\n"let ws <- ViE.buildRestoredWorkspace ViE.defaultConfig (some root) [f1, f2] 1 [(0, 0), (1, 2)]assertEqual "Restored workspace buffer count" 2 ws.buffers.lengthassertEqual "Restored workspace nextBufferId" 2 ws.nextBufferIdlet active := ws.layout.findView ws.activeWindowId |>.getD ViE.initialViewassertEqual "Restored active buffer id" 1 active.bufferIdassertEqual "Restored cursor row" 1 active.cursor.row.valassertEqual "Restored cursor col" 2 active.cursor.col.valmatch ws.buffers with| b1 :: b2 :: _ =>assertEqual "Restored first file path" (some f1) b1.filenameassertEqual "Restored second file path" (some f2) b2.filename| _ =>throw (IO.userError "Expected two restored buffers")def test : IO Unit := dotestLoadSessionInvalidtestLoadSessionValidtestBuildRestoredWorkspaceend Test.Checkpoint
- `:vs`, `:hs` vertical and horizontal window splitting- `:wincmd h/j/k/l` change focus select window- `:wh`, `:wj`, `:wk`, `:wl` change focus select window (alias)- `:wincmd w`, `:wc` cyclic selection- `:wg <n>` switch to workgroup <n>
- `:sp`, `:split`, `:hs`, `:hsplit`: Horizontal split- `:vs`, `:vsplit`: Vertical split- `:wincmd h/j/k/l`: Switch focused window- `:wincmd s/v`: Split from `wincmd`- `:wincmd w`, `:wc`: Cycle focused window- `:wh`, `:wj`, `:wk`, `:wl`: Window focus aliases- `:cd [path]`: Set/Clear workspace root- `:pwd`: Show current workspace root- `:workspace open <path>`: Set workspace root- `:workspace rename <name>`: Rename current workspace- `:ws list`: Open workspace explorer- `:ws open [--name <name>] <path>`: Create and switch workspace- `:ws new [name] [path]`: Create and switch workspace- `:ws close`: Close current workspace- `:ws rename <name>`: Rename current workspace- `:ws next`, `:ws prev`, `:ws <index>`: Switch workspace- `:wg list`: Open workgroup explorer- `:wg new [name]`: Create and switch workgroup- `:wg close`: Close current workgroup- `:wg rename <name>`: Rename current workgroup- `:wg next`, `:wg prev`, `:wg <index>`: Switch workgroup- `:ee [path]`: Open file explorer- `:wgex`: Open workgroup explorer- `:undo`, `:u`: Undo- `:redo`: Redo
- `:refresh`: alias for `:reload`- `:bloom /pattern`, `:bloom ?pattern`: Bloom-assisted search- `:noh`, `:nohl`: Clear search highlight- `:s/old/new/[g]`: Substitute on current line- `:%s/old/new/[g]`: Substitute on all lines- `:g/pat/ d`: Delete matching lines- `:g/pat/ s/old/new/[g]`: Substitute on matching lines- `:v/pat/ d`: Delete non-matching lines