# Text.Truncate

> Shorten a string to at most maxLength characters — INCLUDING the ellipsis — cutting back to the last word boundary rather than mid-word. The bound covers the ellipsis on purpose: a caller truncating to a budget can add the result to its total without re-checking it.

<!-- id: function-text-truncate · area: function · stability: stable · html: https://osysharp.com/reference/function/text-truncate/ -->

## Summary        {#summary}
`Text.Truncate(s, maxLength)` returns `s` unchanged when it already fits, and otherwise shortens it to
**at most `maxLength` characters, ellipsis included**, backing off to the last word boundary so a word is
never cut in half.

## Signature      {#signature}
```osy syntax
Text.Truncate(<string> s, <int> maxLength) -> string
```

## Description    {#description}
The length bound **includes the ellipsis** (`…`, a single character). That is the point of the method: the
usual reason to truncate is that you are spending a budget — a token budget in a prompt, a column width in
a table — and a helper that can overshoot its own limit forces the caller to measure the result again.

Behaviour at the edges, each of which a hand-rolled version tends to get wrong:

- **A single long word** has no boundary to back off to, so it is cut hard: `Text.Truncate("supercalifragilistic", 10)`
  is `"supercali…"`, not `""`.
- **A leading space** does not empty the string — the back-off only applies to a boundary found *past* the
  start.
- **`maxLength` of 1** leaves room for the ellipsis alone; **0 or less** returns an empty string rather than
  faulting.

Trailing whitespace is trimmed before the ellipsis is appended, so you never get `"the quick …"`.

`Text.Truncate` runs **in memory** — call it on locals inside a function body, not inside a query
predicate.

## Examples       {#examples}
```osy title="assembling context under a budget" test app=text-search
string Excerpt(string body, int budget) {
  return Text.Truncate(body, budget);
}
// Excerpt("the quick brown fox jumps", 12)  ->  "the quick…"   (10 chars — inside the budget)
// Excerpt("hello", 20)                      ->  "hello"        (already fits, untouched)
```

Because the result is bounded, a budget loop can trust it:

```osy title="a budget loop that can trust the bounded result" syntax
var line = "- " + Text.Truncate(item.Content, 200) + "\n";
var cost = Text.Length(line) / 4;
if (used + cost > budget) { break; }
```

## See also       {#see-also}
- [Text.LastIndexOf](https://osysharp.com/reference/function/text-lastindexof/) — the backward search `Text.Truncate` is built on; reach for it directly
  only when you need the index itself rather than a shortened string
- [String interpolation & format specifiers](https://osysharp.com/reference/function/string-interpolation/) — building the strings you are truncating
