56N3C3V6RMJUSTM462TLRWMZE5MKCML6UQT7K42Z3WSD3PNFDZEAC KK6O2B6FHJHMRVSE3N54S47OY4TWV2UGI53CYLYSWAL6WSACFEBAC 6W6OJAUT3KLOXXKYR4GA3MG4ACLO2T4KX3SGD2KZDX6MYEZYTPHAC TKAAHADV62OQORAO7MYRRRHTGPMG3GKXU4Z4MFNLAO6DLTSDU3CAC WBKXA5RVL5DSEJZ4OBDBCTNRT3Q4VFEVQNA344CET3BHY7CY7H5AC U3IMOYALG24OXCZQNPCZJIFKFOMDDREEQY4GNZWAPKQCTZ66H6PAC A6E5SE5JPWUJJYIUS4CF7NMB5GM2SIIUXUZASSOVD467YWI5SCQQC BVKFLEK2IK7AOIXLU3KGN7XNWP7KSJLGOPJKJIR7MU4LGN4J5EGQC ZL7ZSOEOGP2E24UEFC5VVPN5GTL3EUL34FA7F47WWWFPHJH2RS2AC 4GUZZYJSWKA44VKYZ2U3G7SQ32JHHHPBSYUWWNNWWBOEDUN7RLKAC GSOBCC5UYHCUY53WEYJ4WG5RUQACOQSBCLEGA5W6GRPHARU4ZWEAC import ViE.BlikuAdapter
-- SPDX-FileCopyrightText: 2026 Yuki Otsuka---- SPDX-License-Identifier: BSD-3import ViE.UI.Primitivesimport ViE.UI.Searchimport ViE.Terminalnamespace ViE.UIopen ViEdef renderWindow (state : EditorState) (windowId : Nat) (view : ViewState) (rect : Rect) : (Array String × EditorState) := Id.run dolet r := rect.rowlet c := rect.collet h := rect.heightlet w := rect.widthlet workH := if h > 0 then h - 1 else 0let buf0 := getWorkspaceBuffer state view.bufferIdlet mut currentSt := statelet mut winBuf : Array String := #[]let prefetchMargin := 20let totalLines := FileBuffer.lineCount buf0let startRow := if view.scrollRow.val > prefetchMargin then view.scrollRow.val - prefetchMargin else 0let endRow := min totalLines (view.scrollRow.val + workH + prefetchMargin)currentSt := prefetchSearchLineMatches currentSt buf0 startRow endRowfor i in [0:workH] dolet lineIdx : Row := ⟨view.scrollRow.val + i⟩let screenRow := r + iwinBuf := winBuf.push (Terminal.moveCursorStr screenRow c)let buf := getWorkspaceBuffer currentSt view.bufferIdif lineIdx.val < FileBuffer.lineCount buf thenlet lnWidth := if state.config.showLineNumbers then 4 else 0let availableWidth := if w > lnWidth then w - lnWidth else 0let cachedLine := buf.cache.find lineIdxlet cachedRaw := buf.cache.findRaw lineIdxlet cachedIdx := buf.cache.findIndex lineIdxlet lineStr := cachedRaw.getD (getLineFromBuffer buf lineIdx |>.getD "")let lineIndex := cachedIdx.getD (ViE.Unicode.buildDisplayByteIndexWithTabStop lineStr state.config.tabStop)let (displayLine, nextSt) :=match cachedLine with| some (s, cachedScrollCol, cachedWidth) =>if cachedScrollCol == view.scrollCol.val && cachedWidth == availableWidth then(s, currentSt)elsematch getLineFromBuffer buf lineIdx with| some lineStr =>let sub := ViE.Unicode.dropByDisplayWidthWithTabStop lineStr.toRawSubstring state.config.tabStop view.scrollCol.vallet dl := ViE.Unicode.takeByDisplayWidthWithTabStop sub state.config.tabStop availableWidthlet cache := match cachedRaw, cachedIdx with| some raw, some _ =>if raw == lineStr thenbuf.cacheelse(buf.cache.updateIndex lineIdx (ViE.Unicode.buildDisplayByteIndexWithTabStop lineStr state.config.tabStop))| _, _ =>(buf.cache.updateIndex lineIdx (ViE.Unicode.buildDisplayByteIndexWithTabStop lineStr state.config.tabStop))let cache := (cache.update lineIdx dl view.scrollCol.val availableWidth).updateRaw lineIdx lineStrlet updatedBuf := { buf with cache := cache }let s' := currentSt.updateCurrentWorkspace fun ws =>{ ws with buffers := ws.buffers.map (fun (b : FileBuffer) => if b.id == buf.id then updatedBuf else b) }(dl, s')| none => ("", currentSt)| none =>if let some lineStr := getLineFromBuffer buf lineIdx thenlet sub := ViE.Unicode.dropByDisplayWidthWithTabStop lineStr.toRawSubstring state.config.tabStop view.scrollCol.vallet dl := ViE.Unicode.takeByDisplayWidthWithTabStop sub state.config.tabStop availableWidthlet cache := match cachedRaw, cachedIdx with| some raw, some _ =>if raw == lineStr thenbuf.cacheelse(buf.cache.updateIndex lineIdx (ViE.Unicode.buildDisplayByteIndexWithTabStop lineStr state.config.tabStop))| _, _ =>(buf.cache.updateIndex lineIdx (ViE.Unicode.buildDisplayByteIndexWithTabStop lineStr state.config.tabStop))let cache := (cache.update lineIdx dl view.scrollCol.val availableWidth).updateRaw lineIdx lineStrlet updatedBuf := { buf with cache := cache }let s' := currentSt.updateCurrentWorkspace fun ws =>{ ws with buffers := ws.buffers.map (fun (b : FileBuffer) => if b.id == buf.id then updatedBuf else b) }(dl, s')else("", currentSt)currentSt := nextStif state.config.showLineNumbers thenwinBuf := winBuf.push s!"{leftPad (toString (lineIdx.val + 1)) 3} "let isVisual := state.mode == Mode.visual || state.mode == Mode.visualLine || state.mode == Mode.visualBlocklet selRange := if isVisual then state.selectionStart else nonelet (searchMatches, searchSt) := getLineSearchMatches currentSt buf.id lineIdx lineStrcurrentSt := searchStlet needsStyled := selRange.isSome || !searchMatches.isEmpty || !syntaxSpans.isEmptyif !needsStyled thenwinBuf := winBuf.push displayLineelselet mut activeStyle : Option String := nonelet chars := displayLine.toListlet mut dispIdx := 0let cursorByte :=if lineIdx == view.cursor.row thensome (ViE.Unicode.displayColToByteOffsetFromIndex lineIndex view.cursor.col.val)elsenonelet activeMatch := activeMatchRange searchMatches cursorBytefor ch in chars dolet chW := Unicode.charWidth chlet colVal := view.scrollCol.val + dispIdxlet byteStart := ViE.Unicode.displayColToByteOffsetFromIndex lineIndex colVallet byteEnd := ViE.Unicode.displayColToByteOffsetFromIndex lineIndex (colVal + chW)let isSelected :=state.isInSelection lineIdx ⟨colVal⟩ ||(chW > 1 && state.isInSelection lineIdx ⟨colVal + 1⟩)let isMatched :=searchMatches.any (fun m => overlapsByteRange m byteStart byteEnd)let isActiveMatched :=match activeMatch with| some m => overlapsByteRange m byteStart byteEnd| none => falselet desiredStyle : Option String :=if isSelected thensome "\x1b[7m"else if isMatched thenif isActiveMatched thensome state.config.searchHighlightCursorStyleelsesome state.config.searchHighlightStyleelse(Bliku.Tui.Syntax.faceForByteRange Bliku.Tui.Syntax.defaultPalette syntaxSpans byteStart byteEnd).map(fun face => face.toAnsi)if desiredStyle != activeStyle thenmatch activeStyle with| some _ => winBuf := winBuf.push "\x1b[0m"| none => pure ()match desiredStyle with| some stl => winBuf := winBuf.push stl| none => pure ()activeStyle := desiredStylewinBuf := winBuf.push ch.toStringdispIdx := dispIdx + chWif activeStyle.isSome thenwinBuf := winBuf.push "\x1b[0m"elsewinBuf := winBuf.push state.config.emptyLineMarkerwinBuf := winBuf.push Terminal.clearLineStrlet statusRow := r + workHlet statusBuf := getWorkspaceBuffer currentSt view.bufferIdwinBuf := winBuf.push (Terminal.moveCursorStr statusRow c)let fileName := statusBuf.filename.getD "[No Name]"let modeStr := if windowId == currentSt.getCurrentWorkspace.activeWindowId then s!"-- {state.mode} --" else "--"let eolMark := if statusBuf.missingEol then " [noeol]" else ""let statusStr := s!"{modeStr} {fileName}{eolMark} [W:{windowId} B:{view.bufferId}] [{state.getCurrentWorkgroup.name}] {state.getCurrentWorkspace.name}"winBuf := winBuf.push state.config.statusBarStylewinBuf := winBuf.push statusStr.trimAscii.toStringwinBuf := winBuf.push Terminal.clearLineStrwinBuf := winBuf.push state.config.resetStyle(winBuf, currentSt)def renderFloatingWindow(st : EditorState)(view : ViewState)(rect : Rect)(borderFlags : FloatingWindowBorderFlags): (Array String × Option (Nat × Nat)) := Id.run dolet top := rect.rowlet left := rect.collet h := rect.heightlet w := rect.widthlet topBorderH := if borderFlags.top then 1 else 0let rightBorderW := if borderFlags.right then 1 else 0let bottomBorderH := if borderFlags.bottom then 1 else 0let leftBorderW := if borderFlags.left then 1 else 0let topBottomBorderH := topBorderH + bottomBorderHlet sideBorderW := leftBorderW + rightBorderWif h < topBottomBorderH + 1 || w < sideBorderW + 1 thenreturn (#[], none)let buf := getWorkspaceBuffer st view.bufferIdlet innerW := if w > sideBorderW then w - sideBorderW else 1let innerH := if h > topBottomBorderH then h - topBottomBorderH else 1let lnWidth := if st.config.showLineNumbers then 4 else 0let textW := if innerW > lnWidth then innerW - lnWidth else 1let repeatToken (token : String) (n : Nat) : String :=String.intercalate "" (List.replicate n token)let hBorder := if st.config.hSplitStr.isEmpty then "-" else st.config.hSplitStrlet vBorder := if st.config.vSplitStr.isEmpty then "|" else st.config.vSplitStrlet leftBorder := if borderFlags.left then vBorder else ""let rightBorder := if borderFlags.right then vBorder else ""let border := repeatToken hBorder wlet mut out : Array String := #[]out := out.push st.config.resetStyleif borderFlags.top thenout := out.push (Terminal.moveCursorStr top left)out := out.push borderlet isVisual := st.mode == Mode.visual || st.mode == Mode.visualLine || st.mode == Mode.visualBlocklet hasSelection := isVisual && st.selectionStart.isSomefor i in [0:innerH] dolet lineIdx : Row := ⟨view.scrollRow.val + i⟩let raw := if lineIdx.val < FileBuffer.lineCount buf then getLineFromBuffer buf lineIdx |>.getD "" else ""let sub := ViE.Unicode.dropByDisplayWidthWithTabStop raw.toRawSubstring st.config.tabStop view.scrollCol.vallet plainShown := ViE.Unicode.takeByDisplayWidthWithTabStop sub st.config.tabStop textWlet shownW := Unicode.stringWidthWithTabStop plainShown st.config.tabStoplet shown :=if hasSelection || !syntaxSpans.isEmpty thenId.run dolet mut styled : Array String := #[]let mut activeStyle : Option String := nonelet mut dispOffset := 0for ch in plainShown.toList dolet chW := Unicode.charWidth chlet selected :=st.isInSelection lineIdx ⟨dispCol⟩ ||(chW > 1 && st.isInSelection lineIdx ⟨dispCol + 1⟩)let desiredStyle :=if selected thensome "\x1b[7m"else(Bliku.Tui.Syntax.faceForByteRange Bliku.Tui.Syntax.defaultPalette syntaxSpans byteStart byteEnd).map(fun face => face.toAnsi)if desiredStyle != activeStyle thenmatch activeStyle with| some _ => styled := styled.push st.config.resetStyle| none => pure ()match desiredStyle with| some stl => styled := styled.push stl| none => pure ()activeStyle := desiredStylestyled := styled.push ch.toStringdispOffset := dispOffset + chWif activeStyle.isSome thenstyled := styled.push st.config.resetStylereturn String.intercalate "" styled.toListelseplainShownlet padW := if shownW < textW then textW - shownW else 0let pad := "".pushn ' ' padWlet withNo :=if st.config.showLineNumbers thens!"{leftPad (toString (lineIdx.val + 1)) 3} {shown}{pad}"elses!"{shown}{pad}"out := out.push (Terminal.moveCursorStr (top + topBorderH + i) left)out := out.push s!"{leftBorder}{withNo}{rightBorder}"if borderFlags.bottom thenout := out.push (Terminal.moveCursorStr (top + h - 1) left)out := out.push borderout := out.push st.config.resetStylelet cursorPos : Option (Nat × Nat) :=if view.cursor.row.val < view.scrollRow.val thennoneelselet visRow := view.cursor.row.val - view.scrollRow.valif visRow < innerH thenlet visCol := if view.cursor.col.val >= view.scrollCol.val then view.cursor.col.val - view.scrollCol.val else 0let colOff := if st.config.showLineNumbers then 4 else 0let c := min (innerW - 1) (colOff + visCol)some (top + topBorderH + visRow, left + leftBorderW + c)elsenonereturn (out, cursorPos)end ViE.UIlet dispCol := view.scrollCol.val + dispOffsetlet byteStart := ViE.Unicode.displayColToByteOffsetFromIndex lineIndex dispCollet byteEnd := ViE.Unicode.displayColToByteOffsetFromIndex lineIndex (dispCol + chW)let lineIndex := ViE.Unicode.buildDisplayByteIndexWithTabStop raw st.config.tabStoplet syntaxSpans := Bliku.Tui.Syntax.highlightLine buf.filename rawlet syntaxSpans := Bliku.Tui.Syntax.highlightLine buf.filename lineStrimport Bliku.Tui.Syntax
import ViE.UI.Windowimport ViE.UI.Overlayimport ViE.Terminalnamespace ViE.UIopen ViE/-- Render the current editor state to the terminal. -/def render (state : EditorState) : IO EditorState := doif !state.dirty then return state-- Start building the frame bufferlet mut buffer : Array String := #[]buffer := buffer.push Terminal.hideCursorStrlet (rows, cols) ← Terminal.getWindowSizelet ws := state.getCurrentWorkspacelet rec renderLayout (l : Layout) (st : EditorState) (r c h w : Nat) : (Array String × EditorState) := Id.run domatch l with| Layout.window id view =>if st.getCurrentWorkspace.isFloatingWindow id thenlet mut blankBuf : Array String := #[]for i in [0:h] doblankBuf := blankBuf.push (Terminal.moveCursorStr (r + i) c)blankBuf := blankBuf.push ("".pushn ' ' w)return (blankBuf, st)renderWindow st id view { row := r, col := c, height := h, width := w }| Layout.hsplit left right ratio =>let leftW := (Float.ofNat w * ratio).toUInt64.toNatlet (leftStr, st') := renderLayout left st r c h leftW-- Draw vertical separatorlet mut sepStr : Array String := #[]if w > leftW thenlet sepCol := c + leftWfor i in [0:h] dosepStr := sepStr.push (Terminal.moveCursorStr (r + i) sepCol)sepStr := sepStr.push st.config.vSplitStrlet (rightStr, st'') := renderLayout right st' r (c + leftW + 1) h (if w > leftW then w - leftW - 1 else 0)(leftStr ++ sepStr ++ rightStr, st'')| Layout.vsplit top bottom ratio =>let topH := (Float.ofNat h * ratio).toUInt64.toNatlet (topStr, st') := renderLayout top st r c topH w-- Draw horizontal separatorlet mut sepStr : Array String := #[]if h > topH thenlet sepRow := r + topHsepStr := sepStr.push (Terminal.moveCursorStr sepRow c)for _ in [0:w] dosepStr := sepStr.push st.config.hSplitStrlet (bottomStr, st'') := renderLayout bottom st' (r + topH + 1) c (if h > topH then h - topH - 1 else 0) w(topStr ++ sepStr ++ bottomStr, st'')let layoutH := rows - 1let baseLayout :=ws.getFloatingWindowIds.foldl (fun acc wid =>match acc with| some l => l.remove wid| none => none) (some ws.layout)if baseLayout.isNone thenbuffer := buffer.push Terminal.clearScreenStrbuffer := buffer.push Terminal.homeCursorStrlet (layoutStr, stateAfterLayout) :=match baseLayout with| some l => renderLayout l state 0 0 layoutH cols| none => (#[], state)buffer := buffer ++ layoutStrlet wsAfterLayout := stateAfterLayout.getCurrentWorkspacelet floatingViews := wsAfterLayout.getFloatingWindowIds.foldl (fun acc wid =>match wsAfterLayout.layout.findView wid with| some v => acc.push (wid, v)| none => acc) #[]let mut activeFloatingCursorPos : Option (Nat × Nat) := nonefor i in [0:floatingViews.size] dolet (wid, view) := floatingViews[i]!match stateAfterLayout.getFloatingWindowBounds wid with| some (top, left, h, w) =>let borderFlags := stateAfterLayout.getFloatingWindowBorderFlags widlet (winBuf, cursorPos) := renderFloatingWindow stateAfterLayout view { row := top, col := left, height := h, width := w } borderFlagsbuffer := buffer ++ winBufif wid == wsAfterLayout.activeWindowId thenactiveFloatingCursorPos := cursorPos| none => pure ()-- Global Status / Command Linebuffer := buffer.push (Terminal.moveCursorStr (rows - 1) 0)let statusRight := renderStatusBar stateAfterLayoutbuffer := buffer.push statusRightbuffer := buffer.push Terminal.clearLineStrlet overlayToRender :=stateAfterLayout.floatingOverlay.orElse (fun _ => messageOverlayForState stateAfterLayout)if let some overlay := overlayToRender thenbuffer := buffer ++ renderFloatingOverlay rows cols stateAfterLayout.config.tabStop overlay-- Set Physical Cursorlet rec getCursorPos (l : Layout) (r c h w : Nat) : Option (Nat × Nat) :=match l with| Layout.window id view =>if id == wsAfterLayout.activeWindowId thenlet workH := if h > 0 then h - 1 else 0let colOffset := if stateAfterLayout.config.showLineNumbers then 4 else 0let visualRow := view.cursor.row.val - view.scrollRow.valif visualRow >= 0 && visualRow < workH thenif view.cursor.col.val < view.scrollCol.val thennoneelselet visualCol := view.cursor.col.val - view.scrollCol.valif (visualCol + colOffset) < w thensome (r + visualRow, c + visualCol + colOffset)else noneelse noneelse none| Layout.hsplit left right ratio =>let leftW := (Float.ofNat w * ratio).toUInt64.toNat(getCursorPos left r c h leftW).orElse (fun _ => getCursorPos right r (c + leftW + 1) h (if w > leftW then w - leftW - 1 else 0))| Layout.vsplit top bottom ratio =>let topH := (Float.ofNat h * ratio).toUInt64.toNat(getCursorPos top r c topH w).orElse (fun _ => getCursorPos bottom (r + topH + 1) c (if h > topH then h - topH - 1 else 0) w)let overlayCursorPos : Option (Nat × Nat) :=match stateAfterLayout.floatingOverlay with| none => none| some overlay =>match computeFloatingOverlayLayout rows cols stateAfterLayout.config.tabStop overlay with| none => none| some layout =>let lines := if overlay.lines.isEmpty then #[""] else overlay.lineslet maxRow := lines.size - 1let rowIdx := min overlay.cursorRow maxRowlet line := lines[rowIdx]!let lineW := Unicode.stringWidthWithTabStop line stateAfterLayout.config.tabStoplet colIdx := min overlay.cursorCol lineWlet visRow := min rowIdx (layout.contentRows - 1)let visCol := min colIdx layout.innerWidthsome (layout.top + 1 + layout.titleRows + visRow, layout.left + 2 + visCol)if stateAfterLayout.floatingOverlay.isSome && stateAfterLayout.mode != .command &&stateAfterLayout.mode != .searchForward && stateAfterLayout.mode != .searchBackward thenbuffer := buffer.push (match overlayCursorPos with| some (pr, pc) => Terminal.moveCursorStr pr pc| none => "")else if wsAfterLayout.isFloatingWindow wsAfterLayout.activeWindowId &&stateAfterLayout.mode != .command && stateAfterLayout.mode != .searchForward && stateAfterLayout.mode != .searchBackward thenbuffer := buffer.push (match activeFloatingCursorPos with| some (pr, pc) => Terminal.moveCursorStr pr pc| none => "")elsebuffer := buffer.push (match activeCursorPos with| some (pr, pc) => Terminal.moveCursorStr pr pc| none => "")if stateAfterLayout.mode == .command thenbuffer := buffer.push (Terminal.moveCursorStr (rows - 1) (1 + stateAfterLayout.inputState.commandBuffer.length))if stateAfterLayout.mode == .searchForward || stateAfterLayout.mode == .searchBackward thenbuffer := buffer.push (Terminal.moveCursorStr (rows - 1) (1 + stateAfterLayout.inputState.commandBuffer.length))if stateAfterLayout.mode == .visual || stateAfterLayout.mode == .visualLine || stateAfterLayout.mode == .visualBlock thenbuffer := buffer.push Terminal.hideCursorStrelsebuffer := buffer.push Terminal.showCursorStr-- Output everythinglet finalStr := String.intercalate "" buffer.toListIO.print finalStr(← IO.getStdout).flushreturn stateAfterLayoutend ViE.UIlet activeCursorPos := getCursorPos (baseLayout.getD wsAfterLayout.layout) 0 0 layoutH colsbuffer := buffer ++ renderCompletionPopup rows cols stateAfterLayout activeCursorPos-- SPDX-FileCopyrightText: 2026 Yuki Otsuka---- SPDX-License-Identifier: BSD-3
import ViE.UI.Primitivesimport ViE.Terminalimport ViE.Colornamespace ViE.UIopen ViEdef shouldRenderMessageAsFloat (msg : String) : Bool :=let m := msg.trimAscii.toStringif m.isEmpty thenfalseelsem.startsWith "Error" ||m.startsWith "Cannot" ||m.startsWith "Invalid" ||m.startsWith "Unknown" ||m.startsWith "No " ||m.startsWith "Empty " ||m.startsWith "Usage:" ||m.startsWith "failed" ||m.startsWith "Failed" ||m.contains "not found"def renderStatusBar (state : EditorState) : String :=let plainMessage := state.message.trimAscii.toStringlet floatMessage := shouldRenderMessageAsFloat plainMessageif state.mode == .command thens!":{state.inputState.commandBuffer}"else if state.mode == .searchForward thens!"/{state.inputState.commandBuffer}"else if state.mode == .searchBackward thens!"?{state.inputState.commandBuffer}"else if floatMessage then""elseplainMessagedef messageOverlayForState (state : EditorState) : Option FloatingOverlay :=let plainMessage := state.message.trimAscii.toStringlet floatMessage := shouldRenderMessageAsFloat plainMessageif state.floatingOverlay.isNone &&state.mode != .command &&state.mode != .searchForward &&state.mode != .searchBackward &&floatMessage thenif plainMessage.isEmpty thennoneelsesome {title := "Message"lines := (plainMessage.splitOn "\n").toArraymaxWidth := 0cursorRow := 0cursorCol := 0}elsenonestructure FloatingOverlayLayout wheretop : Natleft : NatinnerWidth : NattitleRows : NatcontentRows : Natderiving Inhabiteddef computeFloatingOverlayLayout (rows cols : Nat) (tabStop : Nat) (overlay : FloatingOverlay) : Option FloatingOverlayLayout := Id.run dolet availableRows := if rows > 1 then rows - 1 else rowsif cols < 8 || availableRows < 4 thenreturn nonelet lines := if overlay.lines.isEmpty then #[""] else overlay.lineslet titleText := if overlay.title.isEmpty then "" else s!"[{overlay.title}]"let titleRows := if titleText.isEmpty then 0 else 1let naturalWidthContent := lines.foldl (fun acc ln => max acc (Unicode.stringWidthWithTabStop ln tabStop)) 0let naturalWidth := max naturalWidthContent (Unicode.stringWidthWithTabStop titleText tabStop)let maxInnerWidth := if cols > 8 then cols - 8 else 1let targetWidth :=if overlay.maxWidth > 0 thenoverlay.maxWidthelsenaturalWidthlet innerWidth := max 1 (min targetWidth maxInnerWidth)let maxContentRows :=if availableRows > titleRows + 2 thenavailableRows - titleRows - 2else0if maxContentRows == 0 thenreturn nonelet naturalRows := lines.sizelet targetRows := naturalRowslet contentRows := max 1 (min targetRows maxContentRows)let boxHeight := contentRows + titleRows + 2let boxWidth := innerWidth + 4let top := (availableRows - boxHeight) / 2let left := (cols - boxWidth) / 2return some {top := topleft := leftinnerWidth := innerWidthtitleRows := titleRowscontentRows := contentRows}def renderFloatingOverlay (rows cols : Nat) (tabStop : Nat) (overlay : FloatingOverlay) : Array String := Id.run dolet some layout := computeFloatingOverlayLayout rows cols tabStop overlay | return #[]let lines := if overlay.lines.isEmpty then #[""] else overlay.lineslet titleText := if overlay.title.isEmpty then "" else s!"[{overlay.title}]"let top := layout.toplet left := layout.leftlet innerWidth := layout.innerWidthlet titleRows := layout.titleRowslet contentRows := layout.contentRowslet border := "+" ++ "".pushn '-' (innerWidth + 2) ++ "+"let style := (ViE.Color.toBg .brightBlack) ++ (ViE.Color.toFg .white)let mut out : Array String := #[]out := out.push (Terminal.moveCursorStr top left)out := out.push styleout := out.push borderif titleRows == 1 thenout := out.push (Terminal.moveCursorStr (top + 1) left)let clippedTitle := Unicode.takeByDisplayWidthWithTabStop titleText.toRawSubstring tabStop innerWidthlet titleW := Unicode.stringWidthWithTabStop clippedTitle tabStoplet titlePad := if titleW < innerWidth then "".pushn ' ' (innerWidth - titleW) else ""out := out.push s!"| {clippedTitle}{titlePad} |"for i in [0:contentRows] dolet raw := lines[i]?.getD ""let clipped := Unicode.takeByDisplayWidthWithTabStop raw.toRawSubstring tabStop innerWidthlet clippedW := Unicode.stringWidthWithTabStop clipped tabStoplet pad := if clippedW < innerWidth then "".pushn ' ' (innerWidth - clippedW) else ""let row := top + 1 + titleRows + iout := out.push (Terminal.moveCursorStr row left)out := out.push s!"| {clipped}{pad} |"let bottom := top + contentRows + titleRows + 1out := out.push (Terminal.moveCursorStr bottom left)out := out.push borderout := out.push ViE.Color.resetreturn outend ViE.UIprivate def repeatStr (s : String) (n : Nat) : String :=Id.run dolet mut out := ""for _ in [0:n] doout := out ++ sreturn outdef renderCompletionPopup (rows cols : Nat) (state : EditorState) (cursorPos : Option (Nat × Nat)) : Array String := Id.run dolet some popup := state.completionPopup | return #[]let some (cursorRow, cursorCol) := cursorPos | return #[]if popup.items.isEmpty thenreturn #[]let availableRows := if rows > 1 then rows - 1 else rowsif availableRows < 4 || cols < 8 thenreturn #[]let visibleMax := min 8 popup.items.sizelet labels := popup.items.extract 0 visibleMax |>.map (fun it => it.label)let naturalW := labels.foldl (fun acc s => max acc (ViE.Unicode.stringWidthWithTabStop s state.config.tabStop)) 0let innerWidth := max 8 (min 48 naturalW)let boxWidth := innerWidth + 2let boxHeight := visibleMax + 2let placeBelow := cursorRow + 1 + boxHeight <= availableRowslet top :=if placeBelow thencursorRow + 1else if cursorRow >= boxHeight thencursorRow - boxHeightelse0let left0 := cursorCollet left := if left0 + boxWidth < cols then left0 else cols - boxWidthlet hline := repeatStr state.config.hSplitStr innerWidthlet borderTop := state.config.vSplitStr ++ hline ++ state.config.vSplitStrlet borderBottom := borderToplet mut out : Array String := #[]out := out.push (Terminal.moveCursorStr top left)out := out.push borderToplet selected := if popup.selected < visibleMax then popup.selected else 0for i in [0:visibleMax] dolet label := labels[i]!let clipped := ViE.Unicode.takeByDisplayWidthWithTabStop label.toRawSubstring state.config.tabStop innerWidthlet clippedW := ViE.Unicode.stringWidthWithTabStop clipped state.config.tabStoplet pad := if clippedW < innerWidth then "".pushn ' ' (innerWidth - clippedW) else ""let row := top + 1 + iout := out.push (Terminal.moveCursorStr row left)out := out.push state.config.vSplitStrif i == selected thenout := out.push state.config.searchHighlightCursorStyleout := out.push clippedout := out.push padout := out.push state.config.resetStyleelseout := out.push clippedout := out.push padout := out.push state.config.vSplitStrout := out.push (Terminal.moveCursorStr (top + boxHeight - 1) left)out := out.push borderBottomreturn out-- SPDX-FileCopyrightText: 2026 Yuki Otsuka---- SPDX-License-Identifier: BSD-3