Skip to content

Commit

Permalink
feat: convert tabs to spaces with Style.TabWidth(int) (#204)
Browse files Browse the repository at this point in the history
* feat: convert tabs to spaces with Style.TabWidth(int)

By default tabs will be converted to 4 spaces. To disable tab
conversion set Style.TabWidth(NoTabConversion).
  • Loading branch information
meowgorithm committed Jul 24, 2023
1 parent 233079e commit df8b3fa
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 4 deletions.
6 changes: 6 additions & 0 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ func (s Style) GetMaxHeight() int {
return s.getAsInt(maxHeightKey)
}

// GetTabWidth returns the style's tab width setting. If no value is set 4 is
// returned which is the implicit default.
func (s Style) GetTabWidth() int {
return s.getAsInt(tabWidthKey)
}

// GetUnderlineSpaces returns whether or not the style is set to underline
// spaces. If not value is set false is returned.
func (s Style) GetUnderlineSpaces() bool {
Expand Down
28 changes: 26 additions & 2 deletions set.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ func (s *Style) set(key propKey, value interface{}) {

switch v := value.(type) {
case int:
// We don't allow negative integers on any of our values, so just keep
// TabWidth is the only property that may have a negative value (and
// that negative value can be no less than -1).
if key == tabWidthKey {
s.rules[key] = v
break
}

// We don't allow negative integers on any of our other values, so just keep
// them at zero or above. We could use uints instead, but the
// conversions are a little tedious, so we're sticking with ints for
// sake of usability.
Expand Down Expand Up @@ -497,13 +504,30 @@ func (s Style) MaxWidth(n int) Style {
// styles.
//
// Because this in intended to be used at the time of render, this method will
// not mutate the style and instead return a copy.
// not mutate the style and instead returns a copy.
func (s Style) MaxHeight(n int) Style {
o := s.Copy()
o.set(maxHeightKey, n)
return o
}

// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement
// of tabs with spaces at render time.
const NoTabConversion = -1

// TabWidth sets the number of spaces that a tab (/t) should be rendered as.
// When set to 0, tabs will be removed. To disable the replacement of tabs with
// spaces entirely, set this to [NoTabConversion].
//
// By default, tabs will be replaced with 4 spaces.
func (s Style) TabWidth(n int) Style {
if n <= -1 {
n = -1
}
s.set(tabWidthKey, n)
return s
}

// UnderlineSpaces determines whether to underline spaces between words. By
// default, this is true. Spaces can also be underlined without underlining the
// text itself.
Expand Down
23 changes: 22 additions & 1 deletion style.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"github.com/muesli/termenv"
)

const tabWidthDefault = 4

// Property for a key.
type propKey int

Expand Down Expand Up @@ -68,6 +70,7 @@ const (
inlineKey
maxWidthKey
maxHeightKey
tabWidthKey
underlineSpacesKey
strikethroughSpacesKey
)
Expand Down Expand Up @@ -224,7 +227,7 @@ func (s Style) Render(strs ...string) string {
)

if len(s.rules) == 0 {
return str
return s.maybeConvertTabs(str)
}

// Enable support for ANSI on the legacy Windows cmd.exe console. This is a
Expand Down Expand Up @@ -287,6 +290,9 @@ func (s Style) Render(strs ...string) string {
teSpace = teSpace.CrossOut()
}

// Potentially convert tabs to spaces
str = s.maybeConvertTabs(str)

// Strip newlines in single line mode
if inline {
str = strings.ReplaceAll(str, "\n", "")
Expand Down Expand Up @@ -397,6 +403,21 @@ func (s Style) Render(strs ...string) string {
return str
}

func (s Style) maybeConvertTabs(str string) string {
tw := tabWidthDefault
if s.isSet(tabWidthKey) {
tw = s.getAsInt(tabWidthKey)
}
switch tw {
case -1:
return str
case 0:
return strings.ReplaceAll(str, "\t", "")
default:
return strings.ReplaceAll(str, "\t", strings.Repeat(" ", tw))
}
}

func (s Style) applyMargins(str string, inline bool) string {
var (
topMargin = s.getAsInt(marginTopKey)
Expand Down
20 changes: 19 additions & 1 deletion style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ func TestStyleCopy(t *testing.T) {
Foreground(Color("#ffffff")).
Background(Color("#111111")).
Margin(1, 1, 1, 1).
Padding(1, 1, 1, 1)
Padding(1, 1, 1, 1).
TabWidth(2)

i := s.Copy()

Expand All @@ -202,6 +203,7 @@ func TestStyleCopy(t *testing.T) {
requireEqual(t, s.GetPaddingRight(), i.GetPaddingRight())
requireEqual(t, s.GetPaddingTop(), i.GetPaddingTop())
requireEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom())
requireEqual(t, s.GetTabWidth(), i.GetTabWidth())
}

func TestStyleUnset(t *testing.T) {
Expand Down Expand Up @@ -312,6 +314,12 @@ func TestStyleUnset(t *testing.T) {
requireTrue(t, s.GetBorderLeft())
s.UnsetBorderLeft()
requireFalse(t, s.GetBorderLeft())

// tab width
s = NewStyle().TabWidth(2)
requireEqual(t, s.GetTabWidth(), 2)
s.UnsetTabWidth()
requireNotEqual(t, s.GetTabWidth(), 4)
}

func TestStyleValue(t *testing.T) {
Expand Down Expand Up @@ -352,7 +360,17 @@ func TestStyleValue(t *testing.T) {
res, formatEscapes(res))
}
}
}

func TestTabConversion(t *testing.T) {
s := NewStyle()
requireEqual(t, "[ ]", s.Render("[\t]"))
s = NewStyle().TabWidth(2)
requireEqual(t, "[ ]", s.Render("[\t]"))
s = NewStyle().TabWidth(0)
requireEqual(t, "[]", s.Render("[\t]"))
s = NewStyle().TabWidth(-1)
requireEqual(t, "[\t]", s.Render("[\t]"))
}

func BenchmarkStyleRender(b *testing.B) {
Expand Down
6 changes: 6 additions & 0 deletions unset.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,12 @@ func (s Style) UnsetMaxHeight() Style {
return s
}

// UnsetMaxHeight removes the max height style rule, if set.
func (s Style) UnsetTabWidth() Style {
delete(s.rules, tabWidthKey)
return s
}

// UnsetUnderlineSpaces removes the value set by UnderlineSpaces.
func (s Style) UnsetUnderlineSpaces() Style {
delete(s.rules, underlineSpacesKey)
Expand Down

0 comments on commit df8b3fa

Please sign in to comment.