/ ink / tabstops.ts
tabstops.ts
 1  // Tab expansion, inspired by Ghostty's Tabstops.zig
 2  // Uses 8-column intervals (POSIX default, hardcoded in terminals like Ghostty)
 3  
 4  import { stringWidth } from './stringWidth.js'
 5  import { createTokenizer } from './termio/tokenize.js'
 6  
 7  const DEFAULT_TAB_INTERVAL = 8
 8  
 9  export function expandTabs(
10    text: string,
11    interval = DEFAULT_TAB_INTERVAL,
12  ): string {
13    if (!text.includes('\t')) {
14      return text
15    }
16  
17    const tokenizer = createTokenizer()
18    const tokens = tokenizer.feed(text)
19    tokens.push(...tokenizer.flush())
20  
21    let result = ''
22    let column = 0
23  
24    for (const token of tokens) {
25      if (token.type === 'sequence') {
26        result += token.value
27      } else {
28        const parts = token.value.split(/(\t|\n)/)
29        for (const part of parts) {
30          if (part === '\t') {
31            const spaces = interval - (column % interval)
32            result += ' '.repeat(spaces)
33            column += spaces
34          } else if (part === '\n') {
35            result += part
36            column = 0
37          } else {
38            result += part
39            column += stringWidth(part)
40          }
41        }
42      }
43    }
44  
45    return result
46  }