109 lines
3.5 KiB
Python
109 lines
3.5 KiB
Python
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"))
|