All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.vim.Vim.kt Maven / Gradle / Ivy

There is a newer version: 0.0.87
Show newest version
package pl.mareklangiewicz.kommand.vim

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.toList
import okio.Path
import pl.mareklangiewicz.annotations.DelicateApi
import pl.mareklangiewicz.annotations.NotPortableApi
import pl.mareklangiewicz.bad.chk
import pl.mareklangiewicz.kground.*
import pl.mareklangiewicz.kground.io.pth
import pl.mareklangiewicz.kommand.*
import pl.mareklangiewicz.kommand.vim.XVim.Option.*
import pl.mareklangiewicz.kommand.vim.XVim.Option.Companion.KeysScriptStdInForVim
import pl.mareklangiewicz.kommand.vim.XVim.Option.Companion.VimRcNONE
import pl.mareklangiewicz.udata.strf

/**
 * When opening stdin content, vim expects commands from stderr.
 *
 * BTW: When launching vim from terminal, all (0, 1, 2) streams are connected to the same tty device by default,
 * so in that case, it's great default behavior, that vim tries to use stderr as input when stdin is used for content.
 */
@DelicateApi("When opening stdin content, vim expects commands from (redirected) stderr!")
fun vimStdIn(init: XVim.() -> Unit = {}): XVim = vim("-".pth, init = init)

fun gvimStdIn(init: XVim.() -> Unit = {}): XVim = gvim("-".pth, init = init)

fun nvimStdIn(init: XVim.() -> Unit = {}): XVim = nvim("-".pth, init = init)

fun nvimMan(manpage: String, section: ManSection? = null): XVim = nvim {
  val cmd = section?.number?.let { "hid Man $it $manpage" } ?: "hid Man $manpage"
  -ExCmd(cmd)
}

/** GVim can display 'reading from stdin...' until it reads full [inLineS] flow and only then show the full content */
fun gvimLineS(inLineS: Flow, init: XVim.() -> Unit = {}) = ReducedScript {
  gvimStdIn(init).ax(inLineS = inLineS)
}

fun gvimLines(inLines: List, init: XVim.() -> Unit = {}) = gvimLineS(inLines.asFlow(), init)

fun gvimContent(inContent: String, init: XVim.() -> Unit = {}) = gvimLines(inContent.lines(), init)


fun vim(vararg files: Path, init: XVim.() -> Unit = {}) = XVim(XVim.Type.Vim, files.toMutableList()).apply(init)

fun nvim(vararg files: Path, init: XVim.() -> Unit = {}) = XVim(XVim.Type.NVim, files.toMutableList()).apply(init)

fun gvim(vararg files: Path, init: XVim.() -> Unit = {}) = XVim(XVim.Type.GVim, files.toMutableList()).apply(init)
// TODO NOW: nvim, opening specific lines, opening in existing editor (is servername same as in vim?),
//  combine with Ide as in kolib openInIdeOrGVim but better selecting (with nvim too)

/**
 * Useful for playing with ex-mode interactively before writing script for one of:
 * [vimExScriptStdIn] or [vimExScriptContent] or [vimExScriptFile]
 * Note: there is :vi (:visual) command available to switch to normal (visual) mode.
 */
fun vimEx(vararg files: Path, init: XVim.() -> Unit = {}): XVim = vim(*files) { -ExMode; init() }

/**
 * Useful for playing with ex-mode interactively before writing script for one of:
 * [vimExScriptStdIn] or [vimExScriptContent] or [vimExScriptFile]
 * This "Improved" flavor differs mostly (only??) in interactive features, so it's nicer,
 * but still can be useful playground before creating ex-mode script.
 * Note: there is :vi (:visual) command available to switch to normal (visual) mode.
 */
fun vimExIm(vararg files: Path, init: XVim.() -> Unit = {}): XVim = vim(*files) { -ExImMode; init() }

fun vimExScriptStdIn(
  vararg files: Path,
  isViCompat: Boolean = false,
  isCleanMode: Boolean = true,
): XVim = vim(*files) {
  -ViCompat(isViCompat) // setting explicitly in scripts (not rely on .vimrc presence). BTW nvim is always nocompatible
  if (isCleanMode) -CleanMode
  -ExScriptMode // BTW initialization should be skipped in this mode
}

/**
 * Normally should not be needed, but in case of problems it can be useful to experiment with these settings
 * Proposed settings are inspired by answers/flags from SO (which can be incorrect or redundant):
 * https://stackoverflow.com/questions/18860020/executing-vim-commands-in-a-shell-script
 */
@OptIn(NotPortableApi::class)
fun vimExScriptStdInWithExplicitSettings(
  vararg files: Path,
  isViCompat: Boolean = false,
  isDebugMode: Boolean = false,
  isCleanMode: Boolean = true,
  isNoPluginMode: Boolean = false,
  isVimRcNONE: Boolean = true,
  isSwapNONE: Boolean = false, // in nvim swap is disabled in ExScriptMode anyway (not sure about original vim)
  isSetNoMore: Boolean = false, // I guess it shouldn't be needed because ExScriptMode is not TUI, but I'm not sure.
  isTermDumb: Boolean = false, // I guess it shouldn't be needed because ExScriptMode is not TUI, but I'm not sure.
): XVim = vim(*files) {
  -ViCompat(isViCompat) // setting explicitly in scripts (not rely on .vimrc presence). BTW nvim is always nocompatible
  if (isDebugMode) -DebugMode
  if (isCleanMode) -CleanMode
  if (isNoPluginMode) -NoPluginMode
  if (isVimRcNONE) -VimRcNONE // although initialization should be skipped anyway in ExScriptMode
  if (isSwapNONE) -SwapNONE
  if (isSetNoMore) -ExCmd("set nomore") // avoids blocking/pausing when some output/listing fills whole screen
  if (isTermDumb) -TermName("dumb") // BTW nvim does not support it
  -ExScriptMode
}

fun vimExScriptContent(
  exScriptContent: String,
  vararg files: Path,
  isViCompat: Boolean = false,
  isCleanMode: Boolean = true,
): ReducedKommand> = vimExScriptStdIn(files = files, isViCompat = isViCompat, isCleanMode = isCleanMode)
  .reducedManually {
    stdin.collect(exScriptContent.lineSequence().asFlow())
    val out = stdout.toList() // ex-commands can print stuff (like :list, :number, :print, :set, also see :verbose)
    awaitAndChkExit(firstCollectErr = true)
    out
  }

/**
 * This version uses [XVim.Option.Session] for [exScriptFile].
 * @param exScriptFile can not start with "-" (or be empty).
 */
fun vimExScriptFile(
  exScriptFile: String = "Session.vim",
  vararg files: Path,
  isViCompat: Boolean = false,
  isCleanMode: Boolean = true,
): XVim = vim(*files) {
  -ViCompat(isViCompat) // setting explicitly in scripts (not rely on .vimrc presence). BTW nvim is always nocompatible
  if (isCleanMode) -CleanMode
  -ExScriptMode // still needed (even though Session below) because we want silent mode prepared for usage without TTY
  -Session(exScriptFile)
} // BTW ex-commands can print stuff (like :list, :number, :print, :set, also see :verbose)


@OptIn(NotPortableApi::class)
fun vimKeysScriptFile(
  keysScriptFile: Path,
  vararg files: Path,
  isViCompat: Boolean = false,
  isCleanMode: Boolean = true,
  isSwapNONE: Boolean = true,
  isSetNoMore: Boolean = true,
  isTermDumb: Boolean = true,
): XVim = vim(*files) {
  -ViCompat(isViCompat) // setting explicitly in scripts (not rely on .vimrc presence). BTW nvim is always nocompatible
  if (isCleanMode) -CleanMode
  if (isSwapNONE) -SwapNONE
  if (isSetNoMore) -ExCmd("set nomore") // avoids blocking/pausing when some output/listing fills whole screen
  if (isTermDumb) -TermName("dumb") // BTW nvim does not support it
  -KeysScriptIn(keysScriptFile)
} // BTW stdout should not be used as vim will unfortunately print screen content there

/**
 * Note: current impl adds \n at the end of the keys script.
 * It's because internal details of [StdinCollector.collect], [Kommand.ax], etc..
 * generally KommandLine currently treats input as flow of lines.
 */
@OptIn(NotPortableApi::class, DelicateApi::class)
fun vimKeysScriptStdIn(
  vararg files: Path,
  isViCompat: Boolean = false,
  isCleanMode: Boolean = true,
  isSwapNONE: Boolean = true,
  isSetNoMore: Boolean = true,
  isTermDumb: Boolean = true,
): XVim = vim(*files) {
  -ViCompat(isViCompat) // setting explicitly in scripts (not rely on .vimrc presence). BTW nvim is always nocompatible
  if (isCleanMode) -CleanMode
  if (isSwapNONE) -SwapNONE
  if (isSetNoMore) -ExCmd("set nomore") // avoids blocking/pausing when some output/listing fills whole screen
  if (isTermDumb) -TermName("dumb") // BTW nvim does not support it
  -KeysScriptStdInForVim // BTW waiting for answer if it's a good approach: https://github.com/vim/vim/discussions/15315
} // BTW stdout should not be used as vim will unfortunately print screen content there

/**
 * Note: current impl adds \n at the end of the [keysScriptContent].
 * It's because internal details of [StdinCollector.collect], [Kommand.ax], etc..
 * generally KommandLine currently treats input as flow of lines.
 */
@OptIn(NotPortableApi::class, DelicateApi::class)
fun vimKeysScriptContent(
  keysScriptContent: String,
  vararg files: Path,
  isViCompat: Boolean = false,
  isCleanMode: Boolean = true,
  isSwapNONE: Boolean = true,
  isSetNoMore: Boolean = true,
  isTermDumb: Boolean = true,
): ReducedKommand = vimKeysScriptStdIn(
  files = files,
  isViCompat = isViCompat,
  isCleanMode = isCleanMode,
  isSwapNONE = isSwapNONE,
  isSetNoMore = isSetNoMore,
  isTermDumb = isTermDumb,
).reducedManually {
  stdin.collect(keysScriptContent.lineSequence().asFlow())
  awaitAndChkExit(firstCollectErr = true)
}



// Note: I could add all script flavored wrappers here for nvim as well, but let's not do it.
// Vim is more portable (github actions etc), and user can wrap nvim himself if needed.


@Suppress("unused")
data class XVim(
  val type: Type = Type.Vim,
  val files: MutableList = mutableListOf(),
  val options: MutableList




© 2015 - 2024 Weber Informatics LLC | Privacy Policy