import Lean
import ViE.Types
import ViE.Basic
import ViE.State.Config
import ViE.Data.PieceTable
import ViE.Unicode
namespace ViE
/-- Get a line from FileBuffer (delegates to PieceTable) -/
def getLineFromBuffer (buffer : FileBuffer) (n : Row) : Option String :=
match buffer.cache.findRaw n with
| some s => some s
| none => buffer.table.getLine n.val
/-- Get byte offset from Row/Col (display column) with configurable tab stop. -/
def getOffsetFromPointInBufferWithTabStop (buffer : FileBuffer) (row : Row) (col : Col) (tabStop : Nat) : Option Nat :=
match buffer.table.getLineRange row.val with
| some (startOff, len) =>
let line := getLineFromBuffer buffer row |>.getD ""
let byteOff := ViE.Unicode.displayColToByteOffsetWithTabStop line tabStop col.val
let clamped := if byteOff <= len then byteOff else len
some (startOff + clamped)
| none => none
/-- Get line length from FileBuffer with configurable tab stop. -/
def getLineLengthFromBufferWithTabStop (buffer : FileBuffer) (n : Row) (tabStop : Nat) : Option Nat :=
match getLineFromBuffer buffer n with
| some line => some (ViE.Unicode.stringWidthWithTabStop line tabStop)
| none => none
/-- Convert byte offset to Row/Col (display column) with configurable tab stop. -/
def getPointFromOffsetInBufferWithTabStop (buffer : FileBuffer) (offset tabStop : Nat) : Point :=
let (rowNat, _) := buffer.table.getPointFromOffset offset
match buffer.table.getLineRange rowNat with
| some (startOff, len) =>
let rel := if offset >= startOff then min (offset - startOff) len else 0
let sub := ViE.PieceTree.getSubstring buffer.table.tree startOff rel buffer.table
let col := ViE.Unicode.stringWidthWithTabStop sub tabStop
Point.fromNat rowNat col
| none =>
Point.fromNat rowNat 0
/-- Get byte offset from Row/Col (display column) using default tab stop. -/
def getOffsetFromPointInBuffer (buffer : FileBuffer) (row : Row) (col : Col) : Option Nat :=
getOffsetFromPointInBufferWithTabStop buffer row col defaultConfig.tabStop
/-- Get line length from FileBuffer (delegates to PieceTable) using default tab stop. -/
def getLineLengthFromBuffer (buffer : FileBuffer) (n : Row) : Option Nat :=
getLineLengthFromBufferWithTabStop buffer n defaultConfig.tabStop
/-- Convert byte offset to Row/Col (display column) using default tab stop. -/
def getPointFromOffsetInBuffer (buffer : FileBuffer) (offset : Nat) : Point :=
getPointFromOffsetInBufferWithTabStop buffer offset defaultConfig.tabStop
namespace Buffer
/-- Convert FileBuffer to TextBuffer (compatibility function) -/
def fileBufferToTextBuffer (buf : FileBuffer) : TextBuffer :=
let lineCount := FileBuffer.lineCount buf
if lineCount > 0 then
-- Map each line index to its string content
(List.range lineCount).toArray.map fun i =>
match getLineFromBuffer buf ⟨i⟩ with
| some str => stringToLine str
| none => #[]
else
-- Empty buffer - return single empty line
#[#[]]
/-- Create/Update FileBuffer from TextBuffer (flattening is expensive, avoided if possible) -/
def fileBufferFromTextBuffer (id : Nat) (filename : Option String) (content : TextBuffer) : FileBuffer :=
let fullString := String.intercalate "\n" (content.toList.map lineToString)
let table := PieceTable.fromString fullString
{
id := id
filename := filename
dirty := true -- Assume dirty if created from manual content
table := table
missingEol := false -- Default
cache := { lineMap := Lean.RBMap.empty, rawLineMap := Lean.RBMap.empty, lineIndexMap := Lean.RBMap.empty }
}
/-- Create/Update FileBuffer from TextBuffer with bloom config. -/
def fileBufferFromTextBufferWithConfig (id : Nat) (filename : Option String) (content : TextBuffer) (buildLeafBits : Bool) : FileBuffer :=
let fullString := String.intercalate "\n" (content.toList.map lineToString)
let table := PieceTable.fromString fullString buildLeafBits
{
id := id
filename := filename
dirty := true -- Assume dirty if created from manual content
table := table
missingEol := false -- Default
cache := { lineMap := Lean.RBMap.empty, rawLineMap := Lean.RBMap.empty, lineIndexMap := Lean.RBMap.empty }
}
/-- Update FileBuffer from TextBuffer (compatibility function) -/
def fileBufferUpdateFromTextBuffer (buf : FileBuffer) (newContent : TextBuffer) : FileBuffer :=
let newBuf := fileBufferFromTextBuffer buf.id buf.filename newContent
{ newBuf with dirty := true }
end Buffer
/-- Modify a line in FileBuffer using PieceTable operations -/
def modifyLineInBuffer (buffer : FileBuffer) (row : Row) (f : String → String) : FileBuffer :=
match buffer.table.getLineRange row.val with
| some (startOffset, length) =>
match getLineFromBuffer buffer row with
| some oldLine =>
let newLine := f oldLine
-- Edit: Delete old line content, insert new content.
-- We preserve the newline character if it exists (getLineRange excludes it).
let table' := buffer.table.delete startOffset length startOffset
let table'' := table'.insert startOffset newLine startOffset
{ buffer with
table := table''
dirty := true
}
| none => buffer -- Should check range first, but safe fallback
| none => buffer -- Line not found
end ViE