-- SPDX-FileCopyrightText: 2026 Yuki Otsuka
--
-- SPDX-License-Identifier: BSD-3
import ViE.State
import ViE.Types
import ViE.Window.Actions
import ViE.Buffer.Manager
import ViE.Command.Explorer
import ViE.Loader
import ViE.Checkpoint
import ViE.Command.Parser
import ViE.Workspace
import ViE.Lsp.Lean
namespace ViE.Command
open ViE.Window
open ViE.Buffer
open ViE.Feature
def cmdEdit (args : List String) (state : EditorState) : IO EditorState := do
match args with
| [fname] =>
-- Resolve path relative to workspace
let resolvedPath := state.getCurrentWorkspace.resolvePath fname
let s ← ViE.Buffer.openBuffer state resolvedPath
ViE.Lsp.Lean.syncActiveBufferIfRunning s
return s
| [] => return { state with message := "No file name" }
| _ => return { state with message := "Too many arguments" }
def cmdWrite (args : List String) (state : EditorState) : IO EditorState :=
match ViE.Command.parseFilenameArg args state.getActiveBuffer.filename with
| .ok fname =>
do
let s ← ViE.saveBuffer state fname
ViE.Lsp.Lean.syncActiveBufferIfRunning s
return s
| .error msg =>
return { state with message := msg }
def cmdQuit (_ : List String) (state : EditorState) : IO EditorState := do
let s := ViE.Window.closeActiveWindow state
if s.shouldQuit then
ViE.Lsp.Lean.stopIfRunning
return s
def cmdQuitForce (_ : List String) (state : EditorState) : IO EditorState :=
do
ViE.Lsp.Lean.stopIfRunning
return { state with shouldQuit := true }
def cmdWriteQuit (args : List String) (state : EditorState) : IO EditorState := do
match ViE.Command.parseFilenameArg args state.getActiveBuffer.filename with
| .ok fname =>
let state' ← ViE.saveBuffer state fname
let s := ViE.Window.closeActiveWindow state'
if s.shouldQuit then
ViE.Lsp.Lean.stopIfRunning
return s
| .error msg =>
return { state with message := msg }
def cmdSplit (_ : List String) (state : EditorState) : IO EditorState :=
return ViE.Window.splitWindow state true
def cmdVSplit (_ : List String) (state : EditorState) : IO EditorState :=
return ViE.Window.splitWindow state false
def cmdSet (args : List String) (state : EditorState) : IO EditorState :=
match args with
| ["number"] => return { state with config := { state.config with showLineNumbers := true }, message := "number set" }
| ["nonumber"] => return { state with config := { state.config with showLineNumbers := false }, message := "number unset" }
| _ => return { state with message := s!"Unknown setting or args: {args}" }
def cmdBloom (args : List String) (state : EditorState) : IO EditorState := do
let (direction, pattern) :=
match args with
| [] => (SearchDirection.forward, "")
| [p] =>
if p.startsWith "/" then
(SearchDirection.forward, (p.drop 1).toString)
else if p.startsWith "?" then
(SearchDirection.backward, (p.drop 1).toString)
else
(SearchDirection.forward, p)
| p :: rest =>
let full := String.intercalate " " (p :: rest)
if full.startsWith "/" then
(SearchDirection.forward, (full.drop 1).toString)
else if full.startsWith "?" then
(SearchDirection.backward, (full.drop 1).toString)
else
(SearchDirection.forward, full)
if pattern.isEmpty then
return { state with message := "Empty search pattern" }
else
let s' := ViE.startOrUpdateSearch state pattern direction true
let s'' := ViE.findNextMatch s' (some direction)
return s''
def cmdNoHighlight (_ : List String) (state : EditorState) : IO EditorState :=
return { state with searchState := none, message := "search highlight cleared" }
def cmdRedraw (_ : List String) (state : EditorState) : IO EditorState :=
let s1 := state.updateCurrentWorkspace fun ws =>
{ ws with buffers := ws.buffers.map FileBuffer.clearCache }
let s2 :=
match s1.searchState with
| some ss =>
{ s1 with
searchState := some {
ss with
lineMatches := Lean.RBMap.empty
lineOrder := #[]
}
}
| none => s1
return { s2 with dirty := true, message := "redraw" }
def floatUsage : String :=
"Usage: :float [--title <title>|--title=<title>] [--width <cols>|--width=<cols>] <text> | :float clear"
def parsePositiveNat (s : String) : Option Nat :=
match s.toNat? with
| some n => if n > 0 then some n else none
| none => none
def parseFloatArgs
(args : List String)
(title : String)
(maxWidth : Nat)
(textRev : List String)
: Except String (String × Nat × List String) :=
match args with
| [] =>
.ok (title, maxWidth, textRev.reverse)
| "--" :: rest =>
.ok (title, maxWidth, textRev.reverse ++ rest)
| "--title" :: t :: rest =>
parseFloatArgs rest t maxWidth textRev
| "--title" :: [] =>
.error floatUsage
| "--width" :: w :: rest =>
match parsePositiveNat w with
| some n => parseFloatArgs rest title n textRev
| none => .error s!"Invalid float width: {w}"
| "--width" :: [] =>
.error floatUsage
| tok :: rest =>
if tok.startsWith "--title=" then
parseFloatArgs rest (tok.drop 8 |>.toString) maxWidth textRev
else if tok.startsWith "--width=" then
let w := (tok.drop 8).toString
match parsePositiveNat w with
| some n => parseFloatArgs rest title n textRev
| none => .error s!"Invalid float width: {w}"
else
parseFloatArgs rest title maxWidth (tok :: textRev)
def cmdFloat (args : List String) (state : EditorState) : IO EditorState := do
if args == ["clear"] then
return { state with floatingOverlay := none, floatingInputCommand := none, dirty := true, message := "floating overlay cleared" }
match parseFloatArgs args "Float" 0 [] with
| .error msg =>
return { state with message := msg }
| .ok (titleRaw, maxWidth, textTokens) =>
let raw := (String.intercalate " " textTokens).trimAscii.toString
if raw.isEmpty then
return { state with message := floatUsage }
else
let title := titleRaw.trimAscii.toString
let lines := (raw.splitOn "\\n").toArray
let lines := if lines.isEmpty then #[""] else lines
let lastRow := lines.size - 1
let lastCol := (lines[lastRow]!.toList.length)
let overlay : FloatingOverlay := {
title := if title.isEmpty then "Float" else title
lines := lines
maxWidth := maxWidth
cursorRow := lastRow
cursorCol := lastCol
}
return { state with floatingOverlay := some overlay, floatingInputCommand := none, dirty := true, message := "floating overlay shown" }
def cmdNoFloat (_ : List String) (state : EditorState) : IO EditorState :=
return { state with floatingOverlay := none, floatingInputCommand := none, dirty := true, message := "floating overlay cleared" }
def cmdFloatWin (args : List String) (state : EditorState) : IO EditorState := do
let usage := "Usage: :floatwin [toggle|on|off|clear]"
let activeId := state.getCurrentWorkspace.activeWindowId
let s' := state.updateCurrentWorkspace fun ws =>
let ws := ws.pruneFloatingWindows
match args with
| [] => ws.toggleWindowFloating activeId
| ["toggle"] => ws.toggleWindowFloating activeId
| ["on"] => ws.setWindowFloating activeId true
| ["off"] => ws.setWindowFloating activeId false
| ["clear"] => { ws with floatingWindows := [], floatingWindowOffsets := [] }
| _ => ws
let ws' := s'.getCurrentWorkspace
let isFloating := ws'.isFloatingWindow activeId
let msg :=
match args with
| _ :: _ =>
if args == ["clear"] then "floating windows cleared"
else if args == ["on"] then s!"window {activeId} floating on"
else if args == ["off"] then s!"window {activeId} floating off"
else if args == ["toggle"] then
if isFloating then s!"window {activeId} floating on" else s!"window {activeId} floating off"
else usage
| [] =>
if isFloating then s!"window {activeId} floating on" else s!"window {activeId} floating off"
return { s' with dirty := true, message := msg }
def cmdWincmd (args : List String) (state : EditorState) : IO EditorState :=
match args with
| ["w"] => return ViE.Window.cycleWindow state
| ["h"] => return ViE.Window.switchWindow state .left
| ["j"] => return ViE.Window.switchWindow state .down
| ["k"] => return ViE.Window.switchWindow state .up
| ["l"] => return ViE.Window.switchWindow state .right
| ["s"] => return ViE.Window.splitWindow state true
| ["v"] => return ViE.Window.splitWindow state false
| ["q"] => return (ViE.Window.closeActiveWindow state)
| _ => return { state with message := s!"Unknown wincmd: {args}" }
def cmdUndo (_ : List String) (state : EditorState) : IO EditorState :=
return ViE.EditorState.undo state
def cmdRedo (_ : List String) (state : EditorState) : IO EditorState :=
return ViE.EditorState.redo state
def cmdWinLeft (_ : List String) (state : EditorState) : IO EditorState := return ViE.Window.switchWindow state .left
def cmdWinDown (_ : List String) (state : EditorState) : IO EditorState := return ViE.Window.switchWindow state .down
def cmdWinUp (_ : List String) (state : EditorState) : IO EditorState := return ViE.Window.switchWindow state .up
def cmdWinRight (_ : List String) (state : EditorState) : IO EditorState := return ViE.Window.switchWindow state .right
def cmdWinCycle (_ : List String) (state : EditorState) : IO EditorState := return ViE.Window.cycleWindow state
def cmdCd (args : List String) (state : EditorState) : IO EditorState :=
match args with
| [path] => do
let ws := state.getCurrentWorkspace
let absPath ← ViE.resolveAbsolutePath ws.rootPath path
let s' := state.updateCurrentWorkspace fun ws =>
{ ws with rootPath := some absPath, name := ws.name }
return { s' with message := s!"Workspace path: {absPath}" }
| [] =>
let s' := state.updateCurrentWorkspace fun ws =>
{ ws with rootPath := none, name := ws.name }
return { s' with message := "Workspace cleared" }
| _ => return { state with message := "Usage: :cd [path]" }
def cmdWorkspace (args : List String) (state : EditorState) : IO EditorState :=
match args with
| ["open", path] => cmdCd [path] state
| ["rename", name] =>
let trimmed := name.trimAscii.toString
if trimmed.isEmpty then
return { state with message := "Workspace name cannot be empty" }
else
let wg := state.getCurrentWorkgroup
let currentName := state.getCurrentWorkspace.name
let duplicate := wg.workspaces.any (fun ws => ws.name == trimmed && ws.name != currentName)
if duplicate then
return { state with message := s!"Workspace name already exists: {trimmed}" }
else
let s' := state.updateCurrentWorkspace fun ws =>
{ ws with name := trimmed }
return { s' with message := s!"Workspace renamed to: {trimmed}" }
| _ => return { state with message := "Usage: :workspace <open|rename> <args>" }
def cmdPwd (_ : List String) (state : EditorState) : IO EditorState :=
match state.getCurrentWorkspace.rootPath with
| some path => return { state with message := path }
| none => return { state with message := "No workspace set" }
def cmdWg (args : List String) (state : EditorState) : IO EditorState :=
match args with
| ["list"] =>
ViE.Feature.openWorkgroupExplorer state
| ["new"] =>
if state.mode == .command then
return ViE.Feature.openNameInputFloat state "New Workgroup" "" "wg new "
else
let name := s!"Group {state.workgroups.size}"
let nextBufId := state.workgroups.foldl (fun m g =>
g.workspaces.foldl (fun m2 ws => max m2 ws.nextBufferId) m) 0
let newWg := createEmptyWorkgroup name nextBufId
let newState := { state with
workgroups := state.workgroups.push newWg,
currentGroup := state.workgroups.size -- switch to new one using the size before push
}
return { newState with message := s!"Created workgroup: {name}" }
| ["new", name] =>
let trimmed := name.trimAscii.toString
if trimmed.isEmpty then
return { state with message := "Workgroup name cannot be empty" }
else
let nextBufId := state.workgroups.foldl (fun m g =>
g.workspaces.foldl (fun m2 ws => max m2 ws.nextBufferId) m) 0
let newWg := createEmptyWorkgroup trimmed nextBufId
let newState := { state with
workgroups := state.workgroups.push newWg,
currentGroup := state.workgroups.size
}
return { newState with message := s!"Created workgroup: {trimmed}" }
| ["close"] =>
if state.workgroups.size <= 1 then
return { state with message := "Cannot close the last workgroup" }
else
if h_idx : state.currentGroup < state.workgroups.size then
let newGroups := state.workgroups.eraseIdx state.currentGroup h_idx
let newIdx := if state.currentGroup >= newGroups.size then newGroups.size - 1 else state.currentGroup
return { state with
workgroups := newGroups,
currentGroup := newIdx,
message := "Workgroup closed"
}
else
return { state with message := "Error: invalid workgroup index" }
| ["rename", name] =>
let trimmed := name.trimAscii.toString
if trimmed.isEmpty then
return { state with message := "Workgroup name cannot be empty" }
else
let currentName := state.getCurrentWorkgroup.name
let duplicate := state.workgroups.any (fun wg => wg.name == trimmed && wg.name != currentName)
if duplicate then
return { state with message := s!"Workgroup name already exists: {trimmed}" }
else
let current := state.workgroups[state.currentGroup]!
let updated := { current with name := trimmed }
return { state with
workgroups := state.workgroups.set! state.currentGroup updated,
message := s!"Workgroup renamed to: {trimmed}"
}
| ["next"] =>
let nextIdx := (state.currentGroup + 1) % state.workgroups.size
let wg := state.workgroups[nextIdx]!
return { state with currentGroup := nextIdx, message := s!"Switched to: {wg.name}" }
| ["prev"] =>
let prevIdx := if state.currentGroup == 0 then state.workgroups.size - 1 else state.currentGroup - 1
let wg := state.workgroups[prevIdx]!
return { state with currentGroup := prevIdx, message := s!"Switched to: {wg.name}" }
| [n] =>
match n.toNat? with
| some num =>
if num < state.workgroups.size then
let wg := state.workgroups[num]!
return { state with currentGroup := num, message := s!"Switched to: {wg.name}" }
else
return { state with message := s!"Workgroup index out of range (0-{state.workgroups.size - 1})" }
| none => return { state with message := "Invalid workgroup number" }
| [] =>
let wg := state.workgroups[state.currentGroup]!
return { state with message := s!"Current workgroup: {wg.name} (index {state.currentGroup})" }
| _ => return { state with message := "Usage: :wg <new|list|close|rename|next|prev|index> [args]" }
def cmdWs (args : List String) (state : EditorState) : IO EditorState :=
let wg := state.getCurrentWorkgroup
let maxBufId := wg.workspaces.foldl (fun m ws => max m ws.nextBufferId) 0
let parseOpenArgs (rest : List String) : Option (Option String × String) :=
match rest with
| ["--name", name, path] => some (some name, path)
| [path, "--name", name] => some (some name, path)
| [path] => some (none, path)
| [name, path] => some (some name, path)
| _ => none
match args with
| ["list"] =>
ViE.Feature.openWorkspaceExplorer state
| "open" :: rest => do
match parseOpenArgs rest with
| some (maybeName, path) =>
let absPath ← ViE.resolveAbsolutePath state.getCurrentWorkspace.rootPath path
let name := maybeName.getD ((System.FilePath.fileName absPath).getD "Workspace")
let newWs := makeWorkspaceState name (some absPath) maxBufId
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with
workspaces := wg.workspaces.push newWs,
currentWorkspace := wg.workspaces.size
}
return { newState with message := s!"Opened workspace: {name} ({absPath})" }
| none =>
return { state with message := "Usage: :ws open [--name <name>] <path>" }
| ["new"] =>
if state.mode == .command then
return ViE.Feature.openNameInputFloat state "New Workspace" "" "ws new "
else
let name := s!"Workspace {wg.workspaces.size}"
let newWs := makeWorkspaceState name none maxBufId
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with
workspaces := wg.workspaces.push newWs,
currentWorkspace := wg.workspaces.size
}
return { newState with message := s!"Created workspace: {name}" }
| ["new", name] =>
let trimmed := name.trimAscii.toString
if trimmed.isEmpty then
return { state with message := "Workspace name cannot be empty" }
else
let newWs := makeWorkspaceState trimmed none maxBufId
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with
workspaces := wg.workspaces.push newWs,
currentWorkspace := wg.workspaces.size
}
return { newState with message := s!"Created workspace: {trimmed}" }
| ["new", name, path] => do
let trimmed := name.trimAscii.toString
if trimmed.isEmpty then
return { state with message := "Workspace name cannot be empty" }
else do
let absPath ← ViE.resolveAbsolutePath state.getCurrentWorkspace.rootPath path
let newWs := makeWorkspaceState trimmed (some absPath) maxBufId
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with
workspaces := wg.workspaces.push newWs,
currentWorkspace := wg.workspaces.size
}
return { newState with message := s!"Created workspace: {trimmed} ({absPath})" }
| ["close"] =>
if wg.workspaces.size <= 1 then
return { state with message := "Cannot close the last workspace" }
else
if h_idx : wg.currentWorkspace < wg.workspaces.size then
let prevIdx := if wg.currentWorkspace == 0 then wg.workspaces.size - 1 else wg.currentWorkspace - 1
let newSpaces := wg.workspaces.eraseIdx wg.currentWorkspace h_idx
let newIdx := if prevIdx < newSpaces.size then prevIdx else newSpaces.size - 1
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with workspaces := newSpaces, currentWorkspace := newIdx }
let newWs := newSpaces[newIdx]!
return { newState with message := s!"Workspace closed. Switched to: {newWs.name}" }
else
return { state with message := "Error: invalid workspace index" }
| ["rename", name] =>
let trimmed := name.trimAscii.toString
if trimmed.isEmpty then
return { state with message := "Workspace name cannot be empty" }
else
let wg := state.getCurrentWorkgroup
let duplicate := wg.workspaces.any (fun ws => ws.name == trimmed)
if duplicate then
return { state with message := s!"Workspace name already exists: {trimmed}" }
else
let s' := state.updateCurrentWorkspace fun ws =>
{ ws with name := trimmed }
return { s' with message := s!"Workspace renamed to: {trimmed}" }
| ["next"] =>
let nextIdx := (wg.currentWorkspace + 1) % wg.workspaces.size
let ws := wg.workspaces[nextIdx]!
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with currentWorkspace := nextIdx }
return { newState with message := s!"Switched to: {ws.name}" }
| ["prev"] =>
let prevIdx := if wg.currentWorkspace == 0 then wg.workspaces.size - 1 else wg.currentWorkspace - 1
let ws := wg.workspaces[prevIdx]!
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with currentWorkspace := prevIdx }
return { newState with message := s!"Switched to: {ws.name}" }
| [n] =>
match n.toNat? with
| some num =>
if num < wg.workspaces.size then
let ws := wg.workspaces[num]!
let newState := state.updateCurrentWorkgroup fun wg =>
{ wg with currentWorkspace := num }
return { newState with message := s!"Switched to: {ws.name}" }
else
return { state with message := s!"Workspace index out of range (0-{wg.workspaces.size - 1})" }
| none => return { state with message := "Invalid workspace number" }
| [] =>
let ws := wg.workspaces[wg.currentWorkspace]!
return { state with message := s!"Current workspace: {ws.name} (index {wg.currentWorkspace})" }
| _ => return { state with message := "Usage: :ws <new|open|list|close|rename|next|prev|index> [args]" }
def refreshActiveFileExplorer (state : EditorState) : IO EditorState := do
let buf := state.getActiveBuffer
match state.explorers.find? (fun (id, _) => id == buf.id) with
| none => return state
| some (_, explorer) =>
if explorer.mode == .files then
ViE.Feature.openExplorerWithPreview state explorer.currentPath explorer.previewWindowId explorer.previewBufferId explorer.targetWindowId
else
return state
def resolveCommandPath (state : EditorState) (path : String) : String :=
if path.startsWith "/" then path else state.getCurrentWorkspace.resolvePath path
def cmdMkfile (args : List String) (state : EditorState) : IO EditorState := do
match args with
| [rawPath] =>
let path := (resolveCommandPath state rawPath).trimAscii.toString
let leaf := (System.FilePath.fileName path).getD ""
if path.isEmpty || leaf.isEmpty then
return { state with message := "File name cannot be empty" }
else
let fp := System.FilePath.mk path
try
if ← fp.pathExists then
return { state with message := s!"File already exists: {path}" }
else
match fp.parent with
| some p => IO.FS.createDirAll p
| none => pure ()
IO.FS.writeFile path ""
let refreshed ← refreshActiveFileExplorer state
return { refreshed with message := s!"Created file: {path}" }
catch e =>
return { state with message := s!"Error creating file: {e}" }
| _ =>
return { state with message := "Usage: :mkfile <path>" }
def cmdMkdir (args : List String) (state : EditorState) : IO EditorState := do
match args with
| [rawPath] =>
let path := (resolveCommandPath state rawPath).trimAscii.toString
let leaf := (System.FilePath.fileName path).getD ""
if path.isEmpty || leaf.isEmpty then
return { state with message := "Directory name cannot be empty" }
else
let fp := System.FilePath.mk path
try
if ← fp.pathExists then
if ← fp.isDir then
return { state with message := s!"Directory already exists: {path}" }
else
return { state with message := s!"File already exists: {path}" }
else
IO.FS.createDirAll path
let refreshed ← refreshActiveFileExplorer state
return { refreshed with message := s!"Created directory: {path}" }
catch e =>
return { state with message := s!"Error creating directory: {e}" }
| _ =>
return { state with message := "Usage: :mkdir <path>" }
def cmdEx (args : List String) (state : EditorState) : IO EditorState :=
match args with
| [] =>
ViE.Feature.openExplorer state (state.getCurrentWorkspace.rootPath.getD ".")
| ["list"] =>
ViE.Feature.openExplorer state (state.getCurrentWorkspace.rootPath.getD ".")
| ["list", path] =>
ViE.Feature.openExplorer state path
| [path] =>
ViE.Feature.openExplorer state path
| _ =>
return { state with message := "Usage: :ex [list [path]]" }
def cmdExplorer (args : List String) (state : EditorState) : IO EditorState :=
cmdEx args state
def cmdBuf (args : List String) (state : EditorState) : IO EditorState :=
match args with
| [] => ViE.Feature.openBufferExplorer state
| ["list"] => ViE.Feature.openBufferExplorer state
| ["ls"] => ViE.Feature.openBufferExplorer state
| _ => return { state with message := "Usage: :buf [list|ls]" }
def cmdBuffers (args : List String) (state : EditorState) : IO EditorState :=
match args with
| [] => ViE.Feature.openBufferExplorer state
| _ => return { state with message := "Usage: :buffers" }
def cmdWgExplorer (args : List String) (state : EditorState) : IO EditorState :=
match args with
| [] => cmdWg ["list"] state
| _ => return { state with message := "Usage: :wgex" }
def cmdReload (_ : List String) (state : EditorState) : IO EditorState := do
let success ← ViE.Loader.buildConfig
if success then
-- Save session
ViE.Checkpoint.saveSession state
-- Restart
ViE.Loader.restartEditor []
return { state with shouldQuit := true }
else
return { state with message := "Configuration build failed" }
def defaultCommandMap : CommandMap := [
("e", cmdEdit),
("w", cmdWrite),
("q", cmdQuit),
("q!", cmdQuitForce),
("qa", cmdQuitForce),
("qa!", cmdQuitForce),
("wq", cmdWriteQuit),
("sp", cmdSplit),
("split", cmdSplit),
("hs", cmdSplit),
("hsplit", cmdSplit),
("vs", cmdVSplit),
("vsplit", cmdVSplit),
("set", cmdSet),
("wincmd", cmdWincmd),
("wh", cmdWinLeft),
("wj", cmdWinDown),
("wk", cmdWinUp),
("wl", cmdWinRight),
("wc", cmdWinCycle),
("cd", cmdCd),
("workspace", cmdWorkspace),
("ws", cmdWs),
("pwd", cmdPwd),
("wg", cmdWg),
("ex", cmdEx),
("ee", cmdExplorer),
("buf", cmdBuf),
("buffers", cmdBuffers),
("ls", cmdBuffers),
("mkfile", cmdMkfile),
("mkdir", cmdMkdir),
("md", cmdMkdir),
("wgex", cmdWgExplorer),
("undo", cmdUndo),
("u", cmdUndo),
("redo", cmdRedo),
("redraw", cmdRedraw),
("redraw!", cmdRedraw),
("float", cmdFloat),
("nofloat", cmdNoFloat),
("floatwin", cmdFloatWin),
("winfloat", cmdFloatWin),
("wfloat", cmdFloatWin),
("reload", cmdReload),
("refresh", cmdReload),
("lsp", ViE.Lsp.Lean.cmdLsp),
("bloom", cmdBloom),
("nohl", cmdNoHighlight),
("noh", cmdNoHighlight)
]
end ViE.Command