ai-station/.venv/lib/python3.12/site-packages/textual/expand_tabs.py

109 lines
3.5 KiB
Python
Raw Normal View History

2025-12-25 14:54:33 +00:00
from __future__ import annotations
import re
from rich.cells import cell_len
from rich.text import Text
_TABS_SPLITTER_RE = re.compile(r"(.*?\t|.+?$)")
def get_tab_widths(line: str, tab_size: int = 4) -> list[tuple[str, int]]:
"""Splits a string line into tuples (str, int).
Each tuple represents a section of the line which precedes a tab character.
The string is the string text that appears before the tab character (excluding the tab).
The integer is the width that the tab character is expanded to.
Args:
line: The text to expand tabs in.
tab_size: Number of cells in a tab.
Returns:
A list of tuples representing the line split on tab characters,
and the widths of the tabs after tab expansion is applied.
"""
parts: list[tuple[str, int]] = []
add_part = parts.append
cell_position = 0
matches = _TABS_SPLITTER_RE.findall(line)
for match in matches:
expansion_width = 0
if match.endswith("\t"):
# Remove the tab, and check the width of the rest of the line.
match = match[:-1]
cell_position += cell_len(match)
# Now move along the line by the width of the tab.
tab_remainder = cell_position % tab_size
expansion_width = tab_size - tab_remainder
cell_position += expansion_width
add_part((match, expansion_width))
return parts
def expand_tabs_inline(line: str, tab_size: int = 4) -> str:
"""Expands tabs, taking into account double cell characters.
Args:
line: The text to expand tabs in.
tab_size: Number of cells in a tab.
Returns:
New string with tabs replaced with spaces.
"""
tab_widths = get_tab_widths(line, tab_size)
return "".join(
[part + expansion_width * " " for part, expansion_width in tab_widths]
)
def expand_text_tabs_from_widths(line: Text, tab_widths: list[int]) -> Text:
"""Expand tabs to the widths defined in the `tab_widths` list.
This will return a new Text instance with tab characters expanded into a
number of spaces. Each time a tab is encountered, it's expanded into the
next integer encountered in the `tab_widths` list. Consequently, the length
of `tab_widths` should match the number of tab characters in `line`.
Args:
line: The `Text` instance to expand tabs in.
tab_widths: The widths to expand tabs to.
Returns:
A new text instance with tab characters converted to spaces.
"""
if "\t" not in line.plain:
return line
parts = line.split("\t", include_separator=True)
tab_widths_iter = iter(tab_widths)
new_parts: list[Text] = []
append_part = new_parts.append
for part in parts:
if part.plain.endswith("\t"):
part._text[-1] = part._text[-1][:-1] + " "
spaces = next(tab_widths_iter)
part.extend_style(spaces - 1)
append_part(part)
return Text("", end="").join(new_parts)
if __name__ == "__main__":
print(expand_tabs_inline("\tbar"))
print(expand_tabs_inline("\tbar\t"))
print(expand_tabs_inline("1\tbar"))
print(expand_tabs_inline("12\tbar"))
print(expand_tabs_inline("123\tbar"))
print(expand_tabs_inline("1234\tbar"))
print(expand_tabs_inline("💩\tbar"))
print(expand_tabs_inline("💩💩\tbar"))
print(expand_tabs_inline("💩💩💩\tbar"))
print(expand_tabs_inline("F💩\tbar"))
print(expand_tabs_inline("F💩O\tbar"))