sourcejig::Term.fan

//
// Copyright (c) 2026, Novant LLC
// Licensed under the MIT License
//
// History:
//   26 Oct 2026  Andy Frank  Creation
//

using [java] java.lang::System as JavaSystem
using concurrent

*************************************************************************
** Term
*************************************************************************

** Terminal and process utilities.
const class Term
{
  ** Is stdout an interactive terminal.
  static Bool isTty()
  {
    if (Env.cur.vars["CI"] != null) return false
    if (Env.cur.vars["TERM"] == "dumb") return false
    return JavaSystem.console != null
  }

  ** Terminal width in columns, or 80 if unknown.
  static Int width()
  {
    cols := Env.cur.vars["COLUMNS"]?.toInt(10, false)
    if (cols != null) return cols
    try
    {
      Str? size
      exec(["sh", "-c", "stty size < /dev/tty"], |s| { size = s }) |->Bool| { size != null }
      cols = size?.trim?.split(' ')?.last?.toInt(10, false)
    }
    catch (Err e) {}
    return cols != null && cols > 0 ? cols : 80
  }

  ** Run process and deliver each line of merged stdout/stderr to
  ** 'onLine'; returns exit code.  'Process.join' does not wait for
  ** the output pump thread, so after exit we wait until 'isDone'
  ** returns true, or output has been quiet for 250ms.
  static Int exec(Str[] cmd, |Str| onLine, |->Bool|? isDone := null)
  {
    out  := LineOutStream(onLine)
    proc := Process(cmd) { it.out = out; it.mergeErr = true }
    exit := proc.run.join
    while (isDone == null || !isDone())
    {
      if (Duration.nowTicks - out.lastWrite > 250ms.ticks) break
      Actor.sleep(5ms)
    }
    out.close
    return exit
  }

  ** Path to 'fant' launcher for current runtime.
  static Str[] fant()
  {
    path := (Env.cur.homeDir + `bin/fant`).osPath
    return Env.cur.os == "win32" ? ["cmd", "/C", path] : [path]
  }

  static const Str clearLine := "\r\u001b[2K"
  static const Str upClearLine := "\u001b[1A\u001b[2K"
  static const Str reset     := "\u001b[0m"
  static const Str bold      := "\u001b[1m"
  static const Str dim       := "\u001b[2m"
  static const Str red       := "\u001b[31m"
  static const Str green     := "\u001b[32m"
  static const Str magenta   := "\u001b[35m"
}