RUDXLEMLJLTDEPQVWE72AQRMMMLUX33XOTUKZKQTUOM2QELLIQAQC lean_exe bliku_test whereroot := `BlikuTest
import Blikuopen Bliku.Tuiprivate def mkWorkspace : WorkspaceView :={name := "test"layout := .window 0 { bufferId := 0, cursor := { row := 0, col := 0 }, scrollRow := 0, scrollCol := 0 }activeWindowId := 0}private def mkModel (mode : Mode := .normal) (selectionStart : Option Cursor := none) : Model :={mode := modeworkgroupName := "wg"workspace := mkWorkspacebuffers := #[]selectionStart := selectionStartmessage := ""commandBuffer := ""config := {resetStyle := "[/]"visualSelectionStyle := "[SEL]"cursorCharStyle := "[CUR]"cursorSpaceStyle := "[CUR]"}}private def assertEq [ToString α] [BEq α] (name : String) (actual expected : α) : IO Unit := doif actual != expected thenthrow <| IO.userError s!"{name} failed\nexpected: {expected}\nactual: {actual}"private def utf8ByteOffsetTest : IO Unit := dolet buf : BufferState :={id := 0filename := nonelines := #["あdef"]syntaxState := {lineSpans := #[(0, #[{ startByte := 3, endByte := 6, kind := .custom "test" }])]paletteOverrides := { custom := [("test", { fg := some (.ansi 196) })] }}}let rendered := renderVisibleLine (mkModel) buf 0 0 0 4 (some { row := 1, col := 0 })assertEq "utf8-byte-offset" rendered "あ\x1b[38;5;196mdef[/]"private def builtinLeanHighlightTest : IO Unit := dolet buf : BufferState :={id := 0filename := some "Main.lean"lines := #["def あ := 1"]}let rendered := renderVisibleLine (mkModel) buf 0 0 0 20 noneassertEq "builtin-lean-highlight" rendered "\x1b[38;5;12mdef[/] あ := \x1b[38;5;13m1[/]"private def cursorSelectionPriorityTest : IO Unit := dolet model := mkModel .visual (some { row := 0, col := 0 })let buf : BufferState :={id := 0filename := nonelines := #["abc"]syntaxState := {lineSpans := #[(0, #[{ startByte := 0, endByte := 3, kind := .custom "test" }])]paletteOverrides := { custom := [("test", { fg := some (.ansi 46) })] }}}let rendered := renderVisibleLine model buf 0 0 0 3 (some { row := 0, col := 1 })assertEq "cursor-selection-priority" rendered "[SEL]a[/][CUR]b[/]\x1b[38;5;46mc[/]"def main : IO Unit := doutf8ByteOffsetTestbuiltinLeanHighlightTestcursorSelectionPriorityTestIO.println "bliku-test: ok"
inductive HighlightKind where| keyword| comment| stringLiteral| numberLiteral| heading| code| link| emphasis| custom (name : String)deriving Repr, BEq, Inhabitedstructure LanguageProfile wherelanguage : LanguagesupportedKinds : Array HighlightKind := #[]deriving Repr, BEq, Inhabited
style : String
kind : HighlightKindderiving Repr, BEq, Inhabitedstructure Palette wherekeyword : Option Face := nonecomment : Option Face := nonestringLiteral : Option Face := nonenumberLiteral : Option Face := noneheading : Option Face := nonecode : Option Face := nonelink : Option Face := noneemphasis : Option Face := nonecustom : List (String × Face) := []
-- ANSI bright colors.def leanKeywordStyle : String := "\x1b[94m"def leanCommentStyle : String := "\x1b[90m"def leanStringStyle : String := "\x1b[92m"def leanNumberStyle : String := "\x1b[95m"
private def colorToAnsi (channel : String) : Color → String| .ansi code => s!"\x1b[{channel};5;{code}m"| .rgb r g b => s!"\x1b[{channel};2;{r};{g};{b}m"
def markdownHeadingStyle : String := "\x1b[96m"def markdownCodeStyle : String := "\x1b[93m"def markdownLinkStyle : String := "\x1b[94m"def markdownEmphasisStyle : String := "\x1b[95m"
def Face.toAnsi (face : Face) : String := Id.run dolet mut parts : Array String := #[]match face.fg with| some color => parts := parts.push (colorToAnsi "38" color)| none => pure ()match face.bg with| some color => parts := parts.push (colorToAnsi "48" color)| none => pure ()if face.bold thenparts := parts.push "\x1b[1m"if face.italic thenparts := parts.push "\x1b[3m"if face.underline thenparts := parts.push "\x1b[4m"return String.intercalate "" parts.toListprivate def mergeFace (base : Option Face) (override : Option Face) : Option Face :=match base, override with| none, none => none| some face, none => some face| none, some face => some face| some baseFace, some overrideFace =>some {fg := overrideFace.fg.orElse (fun _ => baseFace.fg)bg := overrideFace.bg.orElse (fun _ => baseFace.bg)bold := baseFace.bold || overrideFace.bolditalic := baseFace.italic || overrideFace.italicunderline := baseFace.underline || overrideFace.underline}
def Palette.merge (base overrides : Palette) : Palette :={keyword := mergeFace base.keyword overrides.keywordcomment := mergeFace base.comment overrides.commentstringLiteral := mergeFace base.stringLiteral overrides.stringLiteralnumberLiteral := mergeFace base.numberLiteral overrides.numberLiteralheading := mergeFace base.heading overrides.headingcode := mergeFace base.code overrides.codelink := mergeFace base.link overrides.linkemphasis := mergeFace base.emphasis overrides.emphasiscustom := overrides.custom ++ base.custom}def defaultPalette : Palette :={keyword := some { fg := some (.ansi 12) }comment := some { fg := some (.ansi 8) }stringLiteral := some { fg := some (.ansi 10) }numberLiteral := some { fg := some (.ansi 13) }heading := some { fg := some (.ansi 14) }code := some { fg := some (.ansi 11) }link := some { fg := some (.ansi 12) }emphasis := some { fg := some (.ansi 13) }}def Palette.faceFor (palette : Palette) : HighlightKind → Option Face| .keyword => palette.keyword| .comment => palette.comment| .stringLiteral => palette.stringLiteral| .numberLiteral => palette.numberLiteral| .heading => palette.heading| .code => palette.code| .link => palette.link| .emphasis => palette.emphasis| .custom name => palette.custom.find? (fun (n, _) => n == name) |>.map Prod.snddef profileFor : Language → LanguageProfile| .plain => { language := .plain }| .lean => {language := .leansupportedKinds := #[.keyword, .comment, .stringLiteral, .numberLiteral]}| .markdown => {language := .markdownsupportedKinds := #[.heading, .code, .link, .emphasis]}
def styleForByteRange (spans : Array Span) (byteStart byteEnd : Nat) : Option String :=let rec loop (i : Nat) : Option String :=
def faceForByteRange (palette : Palette) (spans : Array Span) (byteStart byteEnd : Nat) : Option Face :=let rec loop (i : Nat) : Option Face :=
private def renderLineSlice (line : String) (scrollCol width : Nat) : String :=((line.drop scrollCol).take width).toString
private def collectVisibleCells (line : String) (scrollCol width : Nat) : Array VisibleCell := Id.run dolet mut out : Array VisibleCell := #[]let mut bytePos := 0let mut col := 0for ch in line.toList dolet nextByte := bytePos + ch.utf8Sizeif scrollCol <= col && col < scrollCol + width thenout := out.push {text := ch.toStringcol := colstartByte := bytePosendByte := nextByte}bytePos := nextBytecol := col + 1out
private def getExternalSyntaxLine (buf : BufferState) (lineIdx : Nat) : Array Syntax.Span :=match buf.syntaxState.lineSpans.find? (fun (row, _) => row == lineIdx) with| some (_, spans) => spans| none => #[]private def syntaxPaletteFor (m : Model) (buf : BufferState) : Syntax.Palette :=Syntax.Palette.merge m.config.syntaxPalette buf.syntaxState.paletteOverridesprivate def syntaxSpansForLine (buf : BufferState) (lineIdx : Nat) (line : String) : Array Syntax.Span :=let builtin := Syntax.highlightLine buf.filename linelet external := getExternalSyntaxLine buf lineIdxexternal ++ builtin
private def styleVisibleLine(m : Model) (windowId lineIdx scrollCol : Nat) (visible : String) (windowCursor : Option Cursor) : String := Id.run do
def renderVisibleLine(m : Model) (buf : BufferState) (windowId lineIdx scrollCol availableWidth : Nat)(windowCursor : Option Cursor) : String := Id.run do
if ch == ' ' then some m.config.cursorSpaceStyle else some m.config.cursorCharStyleelse if isInSelection m selectionCur lineIdx col then
if cell.text == " " then some m.config.cursorSpaceStyle else some m.config.cursorCharStyleelse if isInSelection m selectionCur lineIdx cell.col then
let visible := renderLineSlice (getBufferLine buf lineIdx) view.scrollCol availableWidthlet styled := styleVisibleLine m windowId lineIdx view.scrollCol visible windowCursorlet lineToDraw := appendCursorOnBlankCell m windowId lineIdx view.scrollCol availableWidth visible styled windowCursor
let line := getBufferLine buf lineIdxlet styled := renderVisibleLine m buf windowId lineIdx view.scrollCol availableWidth windowCursorlet lineToDraw := appendCursorOnBlankCell m windowId lineIdx view.scrollCol availableWidth line styled windowCursor