GSOBCC5UYHCUY53WEYJ4WG5RUQACOQSBCLEGA5W6GRPHARU4ZWEAC private 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
private def parseCompletionItem (j : Lean.Json) : Option CompletionItem := dolet label ← getObjStr? j "label"let insertText :=(getObjStr? j "insertText").getD <|match (j.getObjVal? "textEdit").toOption with| some te => (getObjStr? te "newText").getD label| none => labelsome { label := label, insertText := insertText }
private def parseCompletionItems (result : Lean.Json) : Array CompletionItem :=let rawItems : Array Lean.Json :=match result with| .arr xs => xs| .obj _ => (result.getObjValAs? (Array Lean.Json) "items").toOption.getD #[]| _ => #[]rawItems.foldl (fun acc j =>match parseCompletionItem j with| some item =>if item.insertText.isEmpty then acc else acc.push item| none => acc) #[]
private def handleCompletionResponse (reqId : Nat) (result : Lean.Json) : IO Unit := dolet st ← uiStateRef.getlet pending := st.pendingCompletionlet pending' := pending.erase reqIdmatch pending.find? reqId with| none =>uiStateRef.set { st with pendingCompletion := pending' }| some (uri, row, col) =>let items := parseCompletionItems resultlet completion :=if items.isEmpty thennoneelsesome (uri, { items := items.take 40, selected := 0, anchorRow := row, anchorCol := col })uiStateRef.set { st with pendingCompletion := pending', completion := completion }
private def requestCompletion (session : Session) (uri : String) (line lspCol row col : Nat) : IO Session := dolet reqId := session.nextIdlet params := Lean.Json.mkObj [("textDocument", Lean.Json.mkObj [("uri", .str uri)]),("position", Lean.Json.mkObj [("line", (line : Lean.Json)),("character", (lspCol : Lean.Json))]),("context", Lean.Json.mkObj [("triggerKind", (1 : Lean.Json))])]send session.child.stdin (mkRequest reqId "textDocument/completion" params)let st ← uiStateRef.getuiStateRef.set { st with pendingCompletion := st.pendingCompletion.insert reqId (uri, row, col) }pure { session with nextId := reqId + 1 }
private def completionIdentChar (c : Char) : Bool :=c.isAlphanum || c == '_' || c == '\''private def completionPrefixStartDisplayCol (line : String) (tabStop cursorCol : Nat) : Nat :=let charIdx := ViE.Unicode.displayColToCharIndexWithTabStop line tabStop cursorCollet prefixChars : Array Char := (line.toList.take charIdx).toArraylet startIdx := Id.run dolet mut i := prefixChars.sizewhile i > 0 dolet j := i - 1let c := prefixChars[j]!if completionIdentChar c theni := jelsereturn ireturn 0let before := String.ofList (prefixChars.toList.take startIdx)ViE.Unicode.stringWidthWithTabStop before tabStopdef clearCompletionPopup (state : EditorState) : EditorState :={ state with completionPopup := none }private def clearUiCompletion : IO Unit := dolet st ← uiStateRef.getuiStateRef.set { st with completion := none, pendingCompletion := Lean.RBMap.empty }def selectNextCompletion (state : EditorState) : EditorState :=match state.completionPopup with| none => state| some popup =>if popup.items.isEmpty then{ state with completionPopup := none }elselet next := (popup.selected + 1) % popup.items.size{ state with completionPopup := some { popup with selected := next } }def selectPrevCompletion (state : EditorState) : EditorState :=match state.completionPopup with| none => state| some popup =>if popup.items.isEmpty then{ state with completionPopup := none }elselet prev := if popup.selected == 0 then popup.items.size - 1 else popup.selected - 1{ state with completionPopup := some { popup with selected := prev } }
private def applySelectedCompletion (state : EditorState) : EditorState :=match state.completionPopup with| none => state| some popup =>if popup.items.isEmpty || popup.selected >= popup.items.size then{ state with completionPopup := none }elselet item := popup.items[popup.selected]!let cursor := state.getCursorlet line := ViE.getLineFromBuffer state.getActiveBuffer cursor.row |>.getD ""let startCol := completionPrefixStartDisplayCol line state.config.tabStop cursor.col.vallet deleteCount := cursor.col.val - startCollet sDeleted := Id.run dolet mut s := statefor _ in [0:deleteCount] dos := s.deleteBeforeCursorreturn slet sInserted := Id.run dolet mut s := sDeletedfor c in item.insertText.toList dos := s.insertChar creturn s{ sInserted with completionPopup := none }def acceptSelectedCompletion (state : EditorState) : IO EditorState := doclearUiCompletionpure (applySelectedCompletion state)def requestCompletionForActiveIfRunning (state : EditorState) : IO EditorState := dolet buf := state.getActiveBufferlet cursor := state.getCursormatch buf.filename with| none => pure state| some path =>if !isLeanPath path thenpure stateelsematch (← sessionRef.get) with| none => pure state| some s =>match (← s.child.tryWait) with| some _ =>sessionRef.set nonepure state| none =>clearUiCompletionlet uri := fileUri pathlet line := cursor.row.vallet lspCol := cursor.col.vallet s' ← requestCompletion s uri line lspCol cursor.row.val cursor.col.valsessionRef.set (some s')pure { state with completionPopup := none }private def syncCompletionPopupFromUi (state : EditorState) : IO EditorState := dolet st ← uiStateRef.getmatch st.completion with| none => pure { state with completionPopup := none }| some (uri, popup) =>let matchesActive :=match state.getActiveBuffer.filename with| some path => fileUri path == uri| none => falselet cursor := state.getCursorlet matchesCursor := cursor.row.val == popup.anchorRow && cursor.col.val == popup.anchorColif state.mode == .insert && matchesActive && matchesCursor thenpure { state with completionPopup := some popup }elsepure { state with completionPopup := none }
syncInfoViewWindow nextState
let cursorMoved :=let c0 := prevState.getCursorlet c1 := nextState.getCursorc0.row != c1.row || c0.col != c1.col ||prevState.getActiveBuffer.id != nextState.getActiveBuffer.idlet noCompletion :=if nextState.mode != .insert || cursorMoved thenclearCompletionPopup nextStateelsenextStatelet infoSynced ← syncInfoViewWindow noCompletionsyncCompletionPopupFromUi infoSynced
match k with| Key.esc => pure $ (s.commitEdit.moveCursorLeft).setMode Mode.normal| Key.backspace => pure $ s.deleteBeforeCursor| Key.char c => pure $ s.insertChar c| Key.enter => pure s.insertNewline| _ => pure s,
match s.completionPopup with| some _ =>match k with| Key.ctrl 'n' | Key.down => pure <| ViE.Lsp.Lean.selectNextCompletion s| Key.ctrl 'p' | Key.up => pure <| ViE.Lsp.Lean.selectPrevCompletion s| Key.enter | Key.char '\t' => ViE.Lsp.Lean.acceptSelectedCompletion s| Key.esc => pure <| ViE.Lsp.Lean.clearCompletionPopup ((s.commitEdit.moveCursorLeft).setMode Mode.normal)| Key.backspace => pure <| ViE.Lsp.Lean.clearCompletionPopup (s.deleteBeforeCursor)| Key.char c => pure <| ViE.Lsp.Lean.clearCompletionPopup (s.insertChar c)| _ => pure <| ViE.Lsp.Lean.clearCompletionPopup s| none =>match k with| Key.ctrl 'n' => ViE.Lsp.Lean.requestCompletionForActiveIfRunning s| Key.esc => pure $ (s.commitEdit.moveCursorLeft).setMode Mode.normal| Key.backspace => pure $ s.deleteBeforeCursor| Key.char c => pure $ s.insertChar c| Key.enter => pure s.insertNewline| _ => pure s,