Skip to content

Commit

Permalink
Extract code from https://github.com/mtg4s/mtg4s
Browse files Browse the repository at this point in the history
  • Loading branch information
voidcontext committed Jul 11, 2020
1 parent 000a6d4 commit 13a4d43
Show file tree
Hide file tree
Showing 21 changed files with 1,372 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
target/
32 changes: 32 additions & 0 deletions .scalafix.conf
@@ -0,0 +1,32 @@
rules = [
DisableSyntax,
LeakingImplicitClassVal,
NoValInForComprehension,
ProcedureSyntax,
OrganizeImports
]

OrganizeImports {
coalesceToWildcardImportThreshold = 5
expandRelative = true
groupExplicitlyImportedImplicitsSeparately = false
groupedImports = Merge
groups = ["re:javax?\\.", "scala.", "*"]
importSelectorsOrder = Ascii
importsOrder = Ascii
removeUnused = true
}

DisableSyntax {
noVars = true
noThrows = true
noNulls = true
noReturns = true
noWhileLoops = true
noAsInstanceOf = true
noIsIstanceOf = true
noXml = true
noFinalize = true
noValPatterns = true
noUniversalEquality = true
}
15 changes: 15 additions & 0 deletions .scalafmt.conf
@@ -0,0 +1,15 @@
version = 2.4.1
maxColumn = 120
align.openParenCallSite = false
align.openParenDefnSite = false
align.tokens = [
{ code = "=>" , owner = "Case" },
{ code = "<-" , owner = "Enumerator.Generator" },
{ code = "%" },
{ code = "%%" }
]
danglingParentheses = true
continuationIndent.defnSite = 2
includeCurlyBraceInSelectChains = false
docstrings = JavaDoc
rewrite.rules = [RedundantBraces]
66 changes: 66 additions & 0 deletions build.sbt
@@ -0,0 +1,66 @@
import xerial.sbt.Sonatype._

ThisBuild / name := "console4s"
ThisBuild / scalaVersion := "2.13.1"
ThisBuild / organization := "com.gaborpihaj"
ThisBuild / dynverSonatypeSnapshots := true
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.3.1-RC2"

ThisBuild / publishTo := sonatypePublishToBundle.value

val catsVersion = "2.1.1"
val catsEffectVersion = "2.1.2"
val jlineVersion = "3.14.0"
val scalatestVersion = "3.1.1"

lazy val publishSettings = List(
licenses += ("MIT", url("http://opensource.org/licenses/MIT")),
publishMavenStyle := true,
sonatypeProjectHosting := Some(GitHubHosting("voidcontext", "console4s", "gabor.pihaj@gmail.com"))
)

lazy val defaultSettings = Seq(
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF"),
addCompilerPlugin(scalafixSemanticdb),
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full),
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
)

lazy val console4s = (project in file("console4s"))
.settings(
name := "console4s",
publishSettings,
defaultSettings,
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % catsVersion,
"org.typelevel" %% "cats-effect" % catsEffectVersion,
"org.jline" % "jline-terminal" % jlineVersion,
"org.jline" % "jline-terminal-jansi" % jlineVersion,
"org.jline" % "jline-reader" % jlineVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)

lazy val examples = (project in file("examples"))
.settings(
name := "examples",
publishSettings,
defaultSettings,
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % catsVersion,
"org.typelevel" %% "cats-effect" % catsEffectVersion
)
)
.dependsOn(console4s)

lazy val root = (project in file("."))
.settings(
skip in publish := true
)
.aggregate(
console4s,
examples
)

addCommandAlias("fmt", ";scalafix ;test:scalafix ;scalafmtAll ;scalafmtSbt")
addCommandAlias("prePush", ";fmt ;clean ;reload ;test")
53 changes: 53 additions & 0 deletions console4s/src/main/scala/com/gaborpihaj/console4s/Console.scala
@@ -0,0 +1,53 @@
package com.gaborpihaj.console4s

import cats.Show
import cats.effect.Sync
import cats.kernel.Eq
import cats.syntax.apply._
import com.gaborpihaj.console4s.{AutoCompletionConfig, AutoCompletionSource}

trait Console[F[_]] {
def putStr(text: String): F[Unit]
def putStrLn(): F[Unit]
def putStrLn(text: String): F[Unit]
def readLine(prompt: String): F[String]
def readLine[Repr: Show: Eq](prompt: String, autocomplete: AutoCompletionSource[Repr])(
implicit cfg: AutoCompletionConfig[Repr]
): F[(String, Option[Repr])]

def clearScreen(): F[Unit]
def moveToLastLine(): F[Unit]
}

object Console {
def apply[F[_]: Sync](terminal: Terminal, lineReader: LineReader[F]): Console[F] =
new Console[F] {
def putStr(text: String): F[Unit] =
write(text)

def putStrLn(): F[Unit] =
putStrLn("")

def putStrLn(text: String): F[Unit] =
write(s"$text\n")

def readLine(prompt: String): F[String] =
lineReader.readLine(prompt)

def readLine[Repr: Show: Eq](prompt: String, autocomplete: AutoCompletionSource[Repr])(
implicit cfg: AutoCompletionConfig[Repr]
): F[(String, Option[Repr])] =
lineReader.readLine(prompt, autocomplete)

def clearScreen(): F[Unit] =
write(TerminalControl.clearScreen()) *> write(TerminalControl.move(1, 1))

def moveToLastLine(): F[Unit] =
write(TerminalControl.move(terminal.getHeight() - 1, 1))

private def write(text: String): F[Unit] =
Sync[F].delay(terminal.writer().write(text))
}

def apply[F[_]](implicit ev: Console[F]) = ev
}
18 changes: 18 additions & 0 deletions console4s/src/main/scala/com/gaborpihaj/console4s/LineReader.scala
@@ -0,0 +1,18 @@
package com.gaborpihaj.console4s

import cats.effect.Sync
import cats.{Eq, Show}
import com.gaborpihaj.console4s.linereader.LineReaderImpl

trait InputReader

trait LineReader[F[_]] {
def readLine(prompt: String): F[String]
def readLine[Repr: Show: Eq](prompt: String, autocomplete: AutoCompletionSource[Repr])(
implicit cfg: AutoCompletionConfig[Repr]
): F[(String, Option[Repr])]
}

object LineReader {
def apply[F[_]: Sync](terminal: Terminal): LineReader[F] = LineReaderImpl(terminal)
}
72 changes: 72 additions & 0 deletions console4s/src/main/scala/com/gaborpihaj/console4s/Terminal.scala
@@ -0,0 +1,72 @@
package com.gaborpihaj.console4s

import cats.effect.{Resource, Sync}
import org.jline.keymap.BindingReader
import org.jline.terminal.TerminalBuilder

trait Terminal {
def writer(): Terminal.Writer
def reader(): Terminal.Reader
def flush(): Unit
def getCursorPosition(): (Terminal.Row, Terminal.Column)
def getHeight(): Int
}

object Terminal {
type Row = Int
type Column = Int
trait Writer {
def write(s: String): Unit
}

trait Reader {
def readchar(): Int
}

def apply[F[_]: Sync]: Resource[F, Terminal] =
Resource
.make(
Sync[F].delay {
val terminal = TerminalBuilder
.builder()
.system(true)
.jansi(true)
.build()
terminal.enterRawMode()
terminal.echo(false)
terminal
}
)(terminal =>
Sync[F].delay {
terminal.flush()
terminal.close()
}
)
.map { underlying =>
new Terminal {

def writer(): Writer = _writer

def reader(): Reader = _reader

def flush(): Unit = underlying.flush()

def getCursorPosition(): (Int, Int) = {
val pos = underlying.getCursorPosition(_ => ())

(pos.getY() + 1, pos.getX() + 1)
}

def getHeight(): Int = underlying.getHeight()

private val _writer = new Writer {
def write(s: String): Unit = underlying.writer().write(s)
}

private val _reader = new Reader {
def readchar(): Int = bindingReader.readCharacter()
private val bindingReader = new BindingReader(underlying.reader())
}
}
}
}
@@ -0,0 +1,32 @@
package com.gaborpihaj.console4s

object TerminalControl {
def up(n: Int = 1): String = csi(s"${n}A")

def down(n: Int = 1) = csi(s"${n}B")

def forward(n: Int = 1) = csi(s"${n}C")

def back(n: Int = 1) = csi(s"${n}D")

def clearScreen() = csi("2J")

def clearLine() = csi("0K")

def savePos() = esc("7")

def restorePos() = esc("8")

def sgrReset() = csi("0m")

def bold() = csi("1m")

@SuppressWarnings(Array("DisableSyntax.throw"))
def move(row: Int, column: Int) =
if (row < 0 || column < 0) throw new Exception(s"Row and column cannot be less than 0: ($row, $column) ")
else csi(s"${row};${column}H")

def csi(s: String): String = esc(s"[$s")

def esc(s: String): String = s"\u001b$s"
}
@@ -0,0 +1,33 @@
package com.gaborpihaj.console4s

import cats.Eq
import com.gaborpihaj.console4s.AutoCompletionConfig.Direction

trait AutoCompletionSource[Repr] {
def candidates(fragment: String): List[(String, Repr)]
}

case class AutoCompletionConfig[Repr](
maxCandidates: Int,
strict: Boolean,
direction: Direction,
onResultChange: (Option[Repr], String => Unit) => Unit
)

object AutoCompletionConfig {
sealed trait Direction
case object Up extends Direction
case object Down extends Direction

object Direction {
implicit val eq: Eq[Direction] = Eq.fromUniversalEquals
}

implicit def defaultAutoCompletionConfig[Repr]: AutoCompletionConfig[Repr] =
AutoCompletionConfig(
maxCandidates = 5,
strict = false,
direction = Up,
onResultChange = (_, _) => ()
)
}

0 comments on commit 13a4d43

Please sign in to comment.