Skip to content

Commit

Permalink
Add nonInteractiveWidth override (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajalt authored Sep 2, 2024
1 parent 9b36789 commit 941a636
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Added tvOS and watchOS native targets to all modules except the new `mordant-markdown` module.
- Added ability to control raw mode with the `TerminalRecorder`.
- Added support for unicode input in raw mode.
- Added `nonInteractiveWidth` and `nonInteractiveHeight` to `Terminal` terminal constructor to set a different width when the terminal is not interactive (e.g. when redirecting output to a file) [(#140)](https://github.com/ajalt/mordant/issues/140)

### Changed
- **Breaking Change** Moved `Terminal.info.width` and `height` to `Terminal.size.width` and `height`.
Expand Down
4 changes: 2 additions & 2 deletions mordant/api/mordant.api
Original file line number Diff line number Diff line change
Expand Up @@ -1186,8 +1186,8 @@ public final class com/github/ajalt/mordant/terminal/StringPrompt : com/github/a
}

public final class com/github/ajalt/mordant/terminal/Terminal {
public fun <init> (Lcom/github/ajalt/mordant/rendering/AnsiLevel;Lcom/github/ajalt/mordant/rendering/Theme;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;ILjava/lang/Boolean;Lcom/github/ajalt/mordant/terminal/TerminalInterface;)V
public synthetic fun <init> (Lcom/github/ajalt/mordant/rendering/AnsiLevel;Lcom/github/ajalt/mordant/rendering/Theme;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;ILjava/lang/Boolean;Lcom/github/ajalt/mordant/terminal/TerminalInterface;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lcom/github/ajalt/mordant/rendering/AnsiLevel;Lcom/github/ajalt/mordant/rendering/Theme;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;ILjava/lang/Boolean;Lcom/github/ajalt/mordant/terminal/TerminalInterface;)V
public synthetic fun <init> (Lcom/github/ajalt/mordant/rendering/AnsiLevel;Lcom/github/ajalt/mordant/rendering/Theme;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;ILjava/lang/Boolean;Lcom/github/ajalt/mordant/terminal/TerminalInterface;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun danger (Ljava/lang/Object;Lcom/github/ajalt/mordant/rendering/Whitespace;Lcom/github/ajalt/mordant/rendering/TextAlign;Lcom/github/ajalt/mordant/rendering/OverflowWrap;Ljava/lang/Integer;Z)V
public static synthetic fun danger$default (Lcom/github/ajalt/mordant/terminal/Terminal;Ljava/lang/Object;Lcom/github/ajalt/mordant/rendering/Whitespace;Lcom/github/ajalt/mordant/rendering/TextAlign;Lcom/github/ajalt/mordant/rendering/OverflowWrap;Ljava/lang/Integer;ZILjava/lang/Object;)V
public final fun getColors ()Lcom/github/ajalt/mordant/terminal/TerminalColors;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,27 @@ import com.github.ajalt.mordant.widgets.Text
* `interactive`.
*/
class Terminal private constructor(
/** The theme to use when rendering widgets */
val theme: Theme,
/** The number of spaces to use when printing tab characters. */
val tabWidth: Int,
/** The interface to use to interact with the system terminal. */
val terminalInterface: TerminalInterface,
private val forceWidth: Int?,
private val forceHeight: Int?,
private val nonInteractiveWidth: Int?,
private val nonInteractiveHeight: Int?,
/** The terminal capabilities that were detected or set in the constructor. */
val info: TerminalInfo,
) {

/**
* @param ansiLevel The level of color support to use, or `null` to detect the level of the current terminal
* @param theme The theme to use for widgets and styles like [success]
* @param width The width to render widget and wrap text, or `null` to detect the current width.
* @param height The height of the terminal to use when rendering widgets, or `null` to detect the current width.
* @param nonInteractiveWidth The width to use when the terminal is not interactive, or `null` to use the default of 79 columns.
* @param nonInteractiveHeight The height to use when the terminal is not interactive, or `null` to use the default of 24 rows.
* @param hyperlinks whether to render hyperlinks using ANSI codes, or `null` to detect the capability
* @param tabWidth The number of spaces to use for `\t` characters
* @param interactive Set to true to always use color codes, even if stdout is redirected to a
Expand All @@ -43,6 +51,8 @@ class Terminal private constructor(
theme: Theme = Theme.Default,
width: Int? = null,
height: Int? = null,
nonInteractiveWidth: Int? = null,
nonInteractiveHeight: Int? = null,
hyperlinks: Boolean? = null,
tabWidth: Int = 8,
interactive: Boolean? = null,
Expand All @@ -53,6 +63,8 @@ class Terminal private constructor(
terminalInterface = terminalInterface,
forceWidth = width,
forceHeight = height,
nonInteractiveWidth = nonInteractiveWidth,
nonInteractiveHeight = nonInteractiveHeight,
info = terminalInterface.info(
ansiLevel = ansiLevel,
hyperlinks = hyperlinks,
Expand All @@ -65,7 +77,15 @@ class Terminal private constructor(
MppAtomicRef(emptyList())

private val atomicSize: MppAtomicRef<Size> =
MppAtomicRef(terminalInterface.detectSize(forceWidth, forceHeight))
MppAtomicRef(
terminalInterface.detectSize(
info,
forceWidth,
forceHeight,
nonInteractiveWidth,
nonInteractiveHeight
)
)


/**
Expand All @@ -81,7 +101,15 @@ class Terminal private constructor(
* This is called automatically whenever you print to the terminal.
*/
fun updateSize(): Size {
return atomicSize.update { terminalInterface.detectSize(forceWidth, forceHeight) }.second
return atomicSize.update {
terminalInterface.detectSize(
info,
forceWidth,
forceHeight,
nonInteractiveWidth,
nonInteractiveHeight
)
}.second
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,23 @@ object TerminalDetection {
}
}

internal fun TerminalInterface.detectSize(width: Int?, height: Int?): Size {
private const val DEFAULT_WIDTH = 79
private const val DEFAULT_HEIGHT = 24
internal fun TerminalInterface.detectSize(
info: TerminalInfo,
width: Int?,
height: Int?,
nonInteractiveWidth: Int?,
nonInteractiveHeight: Int?,
): Size {
if (width != null && height != null) return Size(width, height)
if (!info.outputInteractive) return Size(
nonInteractiveWidth ?: width ?: DEFAULT_WIDTH,
nonInteractiveHeight ?: height ?: DEFAULT_HEIGHT
)
val detected = getTerminalSize() ?: Size(
width = (getEnv("COLUMNS")?.toIntOrNull() ?: 79),
height = (getEnv("LINES")?.toIntOrNull() ?: 24)
width = (getEnv("COLUMNS")?.toIntOrNull() ?: DEFAULT_WIDTH),
height = (getEnv("LINES")?.toIntOrNull() ?: DEFAULT_HEIGHT)
)
return Size(width = width ?: detected.width, height = height ?: detected.height)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.github.ajalt.mordant.terminal
import com.github.ajalt.mordant.rendering.TextAlign
import com.github.ajalt.mordant.rendering.TextColors.cyan
import com.github.ajalt.mordant.rendering.Whitespace
import io.kotest.data.blocking.forAll
import io.kotest.data.row
import io.kotest.matchers.shouldBe
import kotlin.js.JsName
import kotlin.test.Test
Expand Down Expand Up @@ -84,4 +86,23 @@ class TerminalTest {
|${cyan(" wrap")}
""".trimMargin()
}

@[Test JsName("width_override")]
fun `width override`() = forAll(
row(1, 2, true, 1),
row(1, 2, false, 2),
row(null, 2, true, 3),
row(null, 2, false, 2),
row(1, null, true, 1),
row(1, null, false, 1),
row(null, null, true, 3),
) { width, niWidth, interactive, expected ->
val vt = TerminalRecorder(width = 3, outputInteractive = interactive)
val t = Terminal(
terminalInterface = vt,
width = width,
nonInteractiveWidth = niWidth
)
t.size.width shouldBe expected
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ abstract class RenderingTest(
theme: Theme = Theme.Default,
transformActual: (String) -> String = { it },
) {
val t = Terminal(AnsiLevel.TRUECOLOR, theme, width, height, hyperlinks, tabWidth)
val t = Terminal(
ansiLevel = AnsiLevel.TRUECOLOR,
theme = theme,
width = width,
height = height,
hyperlinks = hyperlinks,
tabWidth = tabWidth
)
val actual = transformActual(t.render(widget))
actual.shouldMatchRender(expected, trimMargin)
}
Expand Down

0 comments on commit 941a636

Please sign in to comment.