Code Block
Code set out to be read: in the theme's code type, a line to a line, each run of it in the colour of what it is, and ready to be copied.
import com.pantrixui.components.data.codeblock.PantrixCodeBlock
- Two forms. The form that takes
codeshows a text. The form that takeslinestakes the lines one by one: each line carries its own tokens and what is said about it.codeLinesgives the lines of a text; a screen starts from there and marks the lines it wants. - Tokens. There are eight kinds of token: plain text, comment, keyword, type, string, number, punctuation, and the names written before something (
@Composable). Their colours come from the theme's roles; a comment is slanted as well. Where two tokens lie over the same letters, the one given later colours them. - Header. Over the code a header stands while there is something to say:
title, mostly a file's name;language, the language's name;icon; what the screen puts at its start withleadingand at its end withtrailing. The copy button is at the header's end; when there is nothing else to say there is no header, and the button stands over the code, in a strip of its own. - Copying and selecting. The button copies all of the code, whatever part of it is on the screen. After a copy it shows a tick for two seconds and tells a screen reader too;
onCopyreports whether the code got to the clipboard. The lines on the screen can be selected as well, by dragging a mouse or by a long press.copyable = falsetakes the button away, andselectable = falseturns selection off. - Line numbers.
lineNumberswrites each line's number before it;firstLineNumbergives the first line's number: a part taken out of a file starts where it started there. The numbers are not selected and not read to a screen reader. - Scrolling sideways. A line that does not fit the block is not broken: the code scrolls sideways and the line numbers stay where they are. A line of code that has lost its indent cannot be read.
- Lines that go on under themselves. With
softWrapa long line goes on under itself: for a narrow screen where the words matter more than the columns. - Height. In a block given a height the code scrolls in that height, under a header that stays, and only the lines on the screen are built. A block given no height is, in a page that scrolls, as tall as its lines where it has no more than 300; with longer code it is no taller than the window, and scrolls in it.
- Scroll bars. Where it scrolls the block has a bar of its own: at its side, and along its foot for code that scrolls sideways. For a reader with a pointer it is there while there is something to scroll, and a mouse drags it; under a finger it shows only while the code moves.
- Marks on lines. A line's mark (
CodeMark) draws the soft fill of its tone's role behind the line and a bar in the role's colour down its left edge; with amessage, the message stands under the line. There are six tones:Primary,Info,Success,Warning,DangerandNeutral. All of a marked line is written in the one colour the theme pairs with that fill. - The code's type.
textStylesets the code in another style than the theme'scode: smaller on a phone's page. - Reading direction. Code is written from left to right in every language; the header and a mark's message follow the app's direction.
- Colour. On the theme's own grounds each kind of token has a colour of its own. On one of the theme's role colours all of the code is written in the one colour that reads there. A colour given with
CodeBlockColorsis used, and one that is not stays the theme's. - Platform. Lines of code stand close: nothing in them is pressed. A line of the theme's code type is 20dp tall; Android leaves 2dp over and under its words, iOS 1dp, and the desktop and the web none. The corners are the card's.
- Automation. With
tagKeythe block isCodeBlock_<tagKey>, a line of itCodeBlock_<tagKey>.Line_<number>, the message under a lineCodeBlock_<tagKey>.Note_<number>, the window a keyboard scrollsCodeBlock_<tagKey>.Lines_window, and the button that copiesCodeBlock_<tagKey>.Button_copy. A copy is reported ascopy, with whether it got to the clipboard. Nothing of the code is reported.
Examples
A snippet of code
At its plainest: a text of code, the tokens that colour it, and the language's name. The block knows no language; tokens says what is a keyword, what a string and what a comment. Here the app's own reader of Kotlin gives them.
Keywords are in the colour of the theme's primary role, types in info, strings in success, numbers in warning, and names that start with @ in danger; a comment is written in textSecondary, and slanted. The button in the header copies all of the code and shows a tick for two seconds.
PantrixCodeBlock(
code = GREETING,
tokens = remember { kotlinTokens(GREETING) },
language = "Kotlin",
tagKey = "snippet"
)
private val GREETING =
"""
@Composable
fun Greeting(name: String) {
// The greeting a new account is met with.
val unread = 3
PantrixText(text = "Hello, " + name + ": " + unread + " new messages")
}
""".trimIndent()
A file, in a height of its own
A file: title gives its name, icon the icon before it, lineNumbers the line numbers. The block was given a height: the code scrolls in it, under a header that stays, and only the lines on the screen are built.
A line that does not fit is not broken. Scroll the code sideways: the line numbers stay where they are, and the lines pass under them. On a screen with a pointer both of the block's scroll bars show, and a mouse drags them; once Tab has come to the lines, the arrow keys scroll the code.
PantrixCodeBlock(
code = REPOSITORY,
modifier = Modifier.height(220.dp),
tokens = remember { kotlinTokens(REPOSITORY) },
title = "OrderRepository.kt",
language = "Kotlin",
icon = PantrixIcons.Folder,
lineNumbers = true,
tagKey = "file"
)
private val REPOSITORY =
"""
package com.example.orders
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/** Reads the orders of an account, newest first. */
class OrderRepository(private val api: OrderApi, private val cache: OrderCache, private val clock: Clock) {
fun orders(account: String): Flow<List<Order>> = cache.observe(account).map { it.sortedBy(Order::placedAt) }
suspend fun refresh(account: String) {
val fresh = api.orders(account, since = cache.newest(account))
cache.put(account, fresh, at = clock.now())
}
suspend fun cancel(order: Order): Boolean {
if (order.shipped) return false
api.cancel(order.id)
cache.remove(order.id)
return true
}
}
""".trimIndent()
A finding, under the line it is about
A review: the finding stands under the line it is about. In the form that takes lines each line carries its own mark; codeLines gives the lines of the text, and the screen marks the ones it wants. A mark draws the soft fill of its tone's role behind the line and a bar in the role's colour down its left edge; message is written under the line, after the tone's icon, and source stands at its end.
A marked line is written in the one colour the theme pairs with that fill. firstLineNumber gives where the part started in its file: the finding's line carries its number in the file.
val lines = remember {
codeLines(REVIEWED, kotlinTokens(REVIEWED)).mapIndexed { index, line ->
when (index) {
1 -> {
line.copy(
mark = CodeMark(
tone = CodeTone.Danger,
message = "The token may be null here; `!!` brings the app down.",
source = "kotlin:S2259"
)
)
}
4 -> {
line.copy(mark = CodeMark(CodeTone.Warning, message = "The length of time belongs in a named constant."))
}
else -> {
line
}
}
}
}
PantrixCodeBlock(
lines = lines,
title = "AuthRepository.kt",
lineNumbers = true,
firstLineNumber = 41,
tagKey = "finding"
)
private val REVIEWED =
"""
suspend fun token(): String {
val cached = cache.token!!
if (cached.isFresh()) return cached.value
val fresh = api.refresh(cached.refreshToken)
cache.keep(fresh, seconds = 3600)
return fresh.value
}
""".trimIndent()
Lines added and taken out
A mark without a message is a band and nothing more: in the difference between two versions the added lines are in the Success tone and the ones taken out in Danger. Here the + and the - at the start of a line say what the band means too: a colour says nothing to someone who cannot see it.
val lines = remember {
codeLines(CHANGES, kotlinTokens(CHANGES)).map { line ->
when {
line.text.startsWith("+") -> line.copy(mark = CodeMark(CodeTone.Success))
line.text.startsWith("-") -> line.copy(mark = CodeMark(CodeTone.Danger))
else -> line
}
}
}
PantrixCodeBlock(lines = lines, title = "Greeting.kt", tagKey = "changes")
private val CHANGES =
"""
@Composable
fun Greeting(name: String) {
- PantrixText(text = "Hello")
+ PantrixText(text = "Hello, " + name)
+ PantrixDivider()
}
""".trimIndent()
The steps of a guide
Marks are the screen's data: when the step of a guide changes, other lines stand out. Here each step marks its own lines in the Primary tone; change the step, and the band moves to the other lines.
var step by remember { mutableIntStateOf(0) }
val lines = remember(step) {
codeLines(GREETING, kotlinTokens(GREETING)).mapIndexed { index, line ->
if (index in STEP_LINES[step]) line.copy(mark = CodeMark(CodeTone.Primary)) else line
}
}
PantrixSegmentButton(
options = STEPS,
selected = step,
onSelect = { step = it },
fullWidth = false,
tagKey = "step"
)
PantrixCodeBlock(lines = lines, lineNumbers = true, copyable = false, tagKey = "steps")
private val STEPS = listOf(
SegmentOption(value = 0, label = "1. Signature", tagKey = "signature"),
SegmentOption(value = 1, label = "2. State", tagKey = "state"),
SegmentOption(value = 2, label = "3. Drawing", tagKey = "drawing")
)
private val STEP_LINES = listOf(0..1, 3..3, 4..4)
private val GREETING =
"""
@Composable
fun Greeting(name: String) {
// The greeting a new account is met with.
val unread = 3
PantrixText(text = "Hello, " + name + ": " + unread + " new messages")
}
""".trimIndent()
Lines that go on under themselves
A long command. With softWrap the line goes on under itself where it does not fit the block, and the block does not scroll sideways; without it the command is one line, and the code scrolls sideways. Turn the switch and compare the two.
A line that goes on under itself loses its indent: softWrap suits a command or a line of a log, not code whose indent carries meaning.
var wraps by remember { mutableStateOf(true) }
PantrixSwitchButton(
checked = wraps,
onCheckedChange = { wraps = it },
label = "Let a long line go on under itself",
tagKey = "code_wraps"
)
PantrixCodeBlock(code = COMMAND, softWrap = wraps, language = "Shell", tagKey = "command")
private val COMMAND =
"./gradlew :ui:components:jvmTest --tests 'com.pantrixui.components.CodeBlockTest' --offline --console=plain"
Copying, and the end of the header
onCopy is called after the button was pressed, with whether the code got to the clipboard: a screen that says so in its own way listens here. trailing puts something of the screen's own at the end of the header, before the button that copies: here, the number of lines.
In the second block copyable and selectable are off and nothing else stands in the header: it has no header, and its code is not selected.
val report = LocalExampleEvents.current
PantrixCodeBlock(
code = DEPENDENCIES,
title = "build.gradle.kts",
onCopy = { copied -> report(if (copied) "Code copied to the clipboard" else "The code could not be copied") },
trailing = { PantrixText(text = "4 lines", style = PantrixTheme.typography.caption) },
tagKey = "copied"
)
PantrixCodeBlock(code = DEPENDENCIES, selectable = false, copyable = false, tagKey = "plain")
private val DEPENDENCIES =
"""
dependencies {
implementation("com.pantrixui:foundation:0.1.0")
implementation("com.pantrixui:components:0.1.0")
}
""".trimIndent()
Tabs in the header
leading puts the screen's own thing at the header's start, before the icon and the title: here two tabs that choose between two forms of the same line, as a documentation's setup step does. They are written in the header's text colour.
textStyle sets the code in another style than the theme's code: here at the size of caption, in the code's family, for a narrow column. The style's headerLineWidth is the width of the line under the header; headerHorizontalPadding keeps what the header holds nearer the side than the lines are.
var form by remember { mutableIntStateOf(0) }
val code = FORMS[form].second
val type = PantrixTheme.typography
PantrixCodeBlock(
code = code,
tokens = remember(code) { kotlinTokens(code) },
style = CodeBlockStyle(headerLineWidth = 2.dp, headerHorizontalPadding = PantrixTheme.spacing.xs),
leading = {
FORMS.forEachIndexed { index, (name, _) ->
PantrixButton(
text = name,
onClick = { form = index },
variant = if (index == form) ButtonVariant.Soft else ButtonVariant.Text,
size = ButtonSize.Small,
tagKey = "form_$index"
)
}
},
textStyle = type.caption.copy(fontFamily = type.code.fontFamily),
tagKey = "tabs"
)
private val FORMS = listOf(
"Kotlin" to """implementation("com.pantrixui:components:0.1.0")""",
"Groovy" to """implementation 'com.pantrixui:components:0.1.0'"""
)
In a card, in colours of its own
The block has a fill and an edge of its own: the fill is the theme's surface, the edge the theme's divider. In a card that has both, colors makes both transparent. A colour that is given is used, and one that is not stays the theme's.
style changes the block's geometry; only the fields it is given change. Here horizontalPadding is zero, so that the lines start where the card's own padding ends.
PantrixCard(modifier = Modifier.fillMaxWidth(), variant = CardVariant.Outlined) {
PantrixText(text = "Setup", style = PantrixTheme.typography.title)
PantrixCodeBlock(
code = DEPENDENCIES,
tokens = remember { kotlinTokens(DEPENDENCIES) },
style = CodeBlockStyle(horizontalPadding = 0.dp),
colors = CodeBlockColors(container = Color.Transparent, border = Color.Transparent),
tagKey = "in_a_card"
)
}
private val DEPENDENCIES =
"""
dependencies {
implementation("com.pantrixui:foundation:0.1.0")
implementation("com.pantrixui:components:0.1.0")
}
""".trimIndent()
Tokens written by hand
The block carries no reader of a language; the app gives the tokens. For a short text with no reader the tokens can be written by hand: each CodeToken is what a run of the line is (CodeTokenKind), from start up to end. Here a shell command's lines are built as CodeLines directly: the comment Comment, the command Keyword, the task StringLiteral, the option Type.
PantrixCodeBlock(lines = SHELL_LINES, language = "Shell", tagKey = "hand_tokens")
private val SHELL_LINES = listOf(
CodeLine(text = "# Run the desktop demo", tokens = listOf(CodeToken(0, 28, CodeTokenKind.Comment))),
CodeLine(
text = "./gradlew :app:desktopApp:run --offline",
tokens = listOf(
CodeToken(0, 9, CodeTokenKind.Keyword),
CodeToken(10, 29, CodeTokenKind.StringLiteral),
CodeToken(30, 39, CodeTokenKind.Type)
)
)
)
API
code
Code set out to be read: in the theme's code type, a line to a line, each run of it in the colour of what it is, and ready to be copied.
| Parameter | Type | Default | What it is |
|---|---|---|---|
code | String | the code. Its lines end at a line feed, at a carriage return, or at the two together, and the break that ends the text starts no line. | |
modifier | Modifier | Modifier | the block's modifier. A height given here is the room the code scrolls in. |
tokens | List<CodeToken> | emptyList() | the runs of code that have a colour, placed by the letters of the whole text. None by default. |
title | String? | null | what the header says first: a file's name, or what the code is. A title too long for the header is cut short. |
language | String? | null | the language's name, written after the title in the header, as it is to be read: Kotlin, TS. The block keeps no list of languages. |
icon | ImageVector? | null | an icon before the title. |
lineNumbers | Boolean | false | whether each line carries its number before the code. |
firstLineNumber | Int | 1 | the number of the first line. A part taken out of a file starts where it started there. |
softWrap | Boolean | false | whether a line too long for the block goes on under itself. By default no line does, and the code scrolls sideways. |
selectable | Boolean | true | whether the code can be selected, by a pointer drawn over it or a finger held on it, and copied by the system's own means. What is copied so is what the lines on the screen show, without their numbers and without what is said about them. Code that scrolls in its own height has built the lines on its screen and no others, and "select all" there selects those. The button copies all of it. |
copyable | Boolean | true | whether the block has the button that copies the code. |
onCopy | ((copied: Boolean) -> Unit)? | null | called after that button was pressed, with whether the code got to the clipboard: a screen that says so in its own way listens here. The button itself shows a tick for a moment and tells a screen reader, unless the system shows every copy itself (PantrixClipboard.traits): there nothing more is needed. |
trailing | (@Composable () -> Unit)? | null | what the screen puts at the header's end, before the button that copies: a count, a button of its own. It is drawn in the block's text colour. |
style | CodeBlockStyle? | null | this call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
colors | CodeBlockColors? | null | this block's own colours, where they are not the theme's: the ones set here, with the theme's for the rest. |
tagKey | String? | null | the block's automation key: the block is CodeBlock_<tagKey>, a line of it CodeBlock_<tagKey>.Line_<number>, the message under a line CodeBlock_<tagKey>.Note_<number>, the lines' window where a keyboard scrolls it CodeBlock_<tagKey>.Lines_window, and the button that copies CodeBlock_<tagKey>.Button_copy. A press on that button is reported as a CodeBlock copy event with whether the code got there. Nothing of the code is reported. |
leading | (@Composable RowScope.() -> Unit)? | null | what the screen puts at the header's start, before the icon and the title, written in the header's colour: the tabs of one file in two forms, as a page of documentation shows a setup for two repositories. |
textStyle | TextStyle? | null | what the code is set in: the theme's code where it is not given, a smaller one on a phone's page. |
lines
PantrixCodeBlock given its lines one by one, each with what is said about it: a band behind a line that is to be looked at, the lines that were added and taken out, a finding with its message under the line it is about.
| Parameter | Type | Default | What it is |
|---|---|---|---|
lines | List<CodeLine> | the lines, in their order. The button copies their texts, a line to a line. | |
modifier | Modifier | Modifier | the block's modifier. A height given here is the room the code scrolls in. |
title | String? | null | what the header says first: a file's name, or what the code is. |
language | String? | null | the language's name, written after the title in the header, as it is to be read. |
icon | ImageVector? | null | an icon before the title. |
lineNumbers | Boolean | false | whether each line carries its number before the code. |
firstLineNumber | Int | 1 | the number of the first line. A part taken out of a file starts where it started there. |
softWrap | Boolean | false | whether a line too long for the block goes on under itself. |
selectable | Boolean | true | whether the code can be selected and copied by the system's own means. |
copyable | Boolean | true | whether the block has the button that copies the code. |
onCopy | ((copied: Boolean) -> Unit)? | null | called after that button was pressed, with whether the code got to the clipboard. |
trailing | (@Composable () -> Unit)? | null | what the screen puts at the header's end, before the button that copies. |
style | CodeBlockStyle? | null | this call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
colors | CodeBlockColors? | null | this block's own colours, where they are not the theme's: the ones set here, with the theme's for the rest. |
tagKey | String? | null | the block's automation key, as the other overload has it. |
leading | (@Composable RowScope.() -> Unit)? | null | what the screen puts at the header's start, before the icon and the title: the tabs of a file in two forms, written in the header's colour. |
textStyle | TextStyle? | null | what the code is set in: the theme's code where it is not given. |
CodeBlockStyle
PantrixCodeBlock's geometry: the room around its lines, its header, the room before the code where the lines are numbered, and the edge.
| Parameter | Type | Default | What it is |
|---|---|---|---|
shape | ShapeSpec | ShapeSpec.Unspecified | The corners of the block: a step of the brand's shape scale, or a fixed shape. |
horizontalPadding | Dp | Dp.Unspecified | The room between the block's sides and what it holds: the header's words, the lines, a message under a line. A mark's band is drawn across it, to the sides. |
verticalPadding | Dp | Dp.Unspecified | The room over the first line and under the last, and under a message. A message has half of it over its words. |
linePadding | Dp | Dp.Unspecified | The room over and under the words of each line. |
headerHeight | Dp | Dp.Unspecified | The least the header is tall. It grows with what stands in it. A block that has nothing in its header but the button that copies has no header: the button's strip is as tall as the button. |
gutterGap | Dp | Dp.Unspecified | Between the line numbers and the code, where the lines are numbered. |
iconSize | Dp | Dp.Unspecified | The width and height of the header's icon, of the icon of the button that copies, and of the one before a message. |
borderWidth | Dp | Dp.Unspecified | The width of the edge around the block, and of the line under its header where headerLineWidth is not set. Zero draws none. |
barWidth | Dp | Dp.Unspecified | The width of the bar down the left edge of a line with a mark. Zero draws none. |
headerLineWidth | Dp | Dp.Unspecified | The width of the line under the header: borderWidth where it is not set, so that a block without an edge can still have its header ruled off. Zero draws none. |
headerHorizontalPadding | Dp | Dp.Unspecified | The room between the block's sides and what its header holds: horizontalPadding where it is not set. Tabs at the header's start stand nearer the side than the lines do. |
CodeBlockColors
What a PantrixCodeBlock is painted with, for a block that does not take the theme's colours: a colour that is set is used, and one that is left out stays the theme's.
| Parameter | Type | Default | What it is |
|---|---|---|---|
container | Color | Color.Unspecified | The fill behind the header and the lines: the theme's surface. Color.Transparent leaves what is behind the block to show. |
text | Color | Color.Unspecified | What no token colours, and the header's title. The theme's textPrimary. |
comment | Color | Color.Unspecified | A comment, which is slanted too. The theme's textSecondary. |
keyword | Color | Color.Unspecified | A word of the language. The theme's primary role. |
type | Color | Color.Unspecified | The name of a type. The theme's info role. |
string | Color | Color.Unspecified | A string. The theme's success role. |
number | Color | Color.Unspecified | A number. The theme's warning role. |
punctuation | Color | Color.Unspecified | Brackets, commas and semicolons. The theme's textSecondary. |
annotation | Color | Color.Unspecified | A name that says something about what follows it. The theme's danger role. |
lineNumber | Color | Color.Unspecified | The line numbers, the header's language and its icon, the icon of the button that copies, and the scroll bars. The theme's textSecondary. |
border | Color | Color.Unspecified | The edge around the block and the line under its header. The theme's divider on the theme's own grounds, and on a fill, where that line would not read, a share of what is written there. |
Accessibility
- Keyboard. Where there is a pointer and something to scroll, the lines are a stop for Tab. With the focus there the arrow keys scroll by a line and, sideways, by a step; Page Up and Page Down by a screen; Home and End go to the first line and to the last.
- Screen reader. The code is read a line at a time; the line numbers are passed over. A mark's message is read after its line, as one thing.