# Text.TrimStart, Text.TrimEnd, Text.PadStart, Text.PadEnd

> Trim whitespace from one end of a string, or pad it out to a width with a fill string. Trimming uses the full Unicode whitespace set. Padding never truncates — a string already at or over the width comes back unchanged — and the fill is a STRING, cut to fit the gap exactly. All run in memory.

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

## Summary        {#summary}
`Text.TrimStart(s)` and `Text.TrimEnd(s)` remove whitespace from one end of a string (`Text.Trim(s)` does
both). Give any of the three a **second argument** and it removes those CHARACTERS instead — C#'s
`TrimEnd(params char[])`, with the set written as a string because Osy# has no `char` type. `Text.PadStart(s, width, pad)` and `Text.PadEnd(s, width, pad)` grow a string to at least `width`
characters by adding a fill on the left or right. Padding **never shortens** a string, and the fill is a
**string**, not a single character.

## Signature      {#signature}
```osy syntax
Text.TrimStart(<string> s) -> string          // strip leading whitespace
Text.TrimEnd(<string> s)   -> string          // strip trailing whitespace
Text.TrimStart(<string> s, <string> chars) -> string   // …or strip any of THESE characters
Text.TrimEnd(<string> s, <string> chars)   -> string
Text.Trim(<string> s, <string> chars)      -> string   // both ends
Text.PadStart(<string> s, <int> width, <string> pad) -> string   // fill on the LEFT to `width`
Text.PadEnd(<string> s, <int> width, <string> pad)   -> string   // fill on the RIGHT to `width`
```

## Description    {#description}

### Trimming characters rather than whitespace   {#chars}
The second argument is a SET, not a suffix: every character in it is stripped from that end, repeatedly, exactly as
C#'s `char[]` overload does. So `TrimEnd("/")` removes as many trailing slashes as there are, and `TrimEnd("/\\")`
removes either kind.

```osy title="the path-joining line every app writes" test app=function-text-affix-chars
string JoinUrl(string baseUrl, string path) {
  return Text.TrimEnd(baseUrl, "/") + "/" + Text.TrimStart(path, "/");
}
```

⚠ It is a set of characters, so `TrimEnd(url, "/api")` strips any trailing `/`, `a`, `p` or `i` — not the word
"api". That is C#'s behaviour too, and it is the one thing about these overloads that surprises people.

### Trimming uses the full Unicode whitespace set   {#trimming}
`TrimStart`/`TrimEnd` strip every leading/trailing whitespace character, not just the ASCII space — a tab, a
newline, a no-break space (` `), an ideographic space (`　`) are all removed. Only the named end is touched:
`Text.TrimStart("  hi  ")` is `"hi  "` (trailing spaces survive).

### Padding never truncates, and takes a WIDTH not a count-to-add   {#padding-width}
`width` is the **target length**, not "how many characters to add". If the string is already at least that
long it comes back **unchanged**: `Text.PadStart("hello", 3, "0")` is `"hello"`. Otherwise the gap is filled
to reach exactly `width`.

### The pad is a STRING, cut to fit the gap   {#pad-string}
The fill is repeated and then **cut to exactly the deficit**, so a multi-character pad can end mid-repeat:
`Text.PadStart("7", 3, "ab")` fills two characters — `"ab7"` — and `Text.PadEnd("7", 4, "ab")` is `"7aba"`.
An **empty** pad is a no-op (it cannot fill anything), so `Text.PadStart("x", 5, "")` is `"x"` — it does not
loop forever. For zero-padding a *number*, a format specifier like `D5` or `"00000"` is usually clearer than
padding a string — see [format specifiers](https://osysharp.com/reference/function/format-specifiers/).

All four run **in memory** on a value already in hand.

## Examples       {#examples}
```osy title="normalise then right-justify an amount" test app=text-affix
// Trim stray whitespace, then right-justify into a fixed-width column with leading zeros.
string Ticket(string code) {
  return Text.PadStart(Text.TrimEnd(Text.TrimStart(code)), 6, "0");
}
```

```osy title="the exact answers, pinned" run app=text-affix
[Test]
void Text_affix_answers() {
  Assert.Equal("000042", Ticket("  42  "));

  Assert.Equal("hi  ", Text.TrimStart("  hi  "));   // only the leading end
  Assert.Equal("  hi", Text.TrimEnd("  hi  "));
  Assert.Equal("hi", Text.TrimStart("　hi"));        // an ideographic space is whitespace too

  Assert.Equal("007", Text.PadStart("7", 3, "0"));
  Assert.Equal("ab7", Text.PadStart("7", 3, "ab"));  // multi-char pad, cut to the 2-char gap
  Assert.Equal("hello", Text.PadStart("hello", 3, "0"));   // already long enough → unchanged
  Assert.Equal("x", Text.PadStart("x", 5, ""));      // empty pad → no-op, never an infinite loop
  Assert.Equal("700", Text.PadEnd("7", 3, "0"));
  Assert.Equal("7aba", Text.PadEnd("7", 4, "ab"));
}
```

## See also       {#see-also}
- [Text.Length, Text.IsEmpty, Text.IsBlank, Text.Contains, Text.StartsWith, Text.EndsWith](https://osysharp.com/reference/function/text-inspect/) — `Text.Length`, and the emptiness checks
- [format specifiers](https://osysharp.com/reference/function/format-specifiers/) — zero-padding a number (`D5`, `"00000"`), usually better than padding a string
- [Text.TitleCase](https://osysharp.com/reference/function/text-titlecase/) — the other in-memory string builtins
