File: tabstop.go

package info (click to toggle)
golang-github-charmbracelet-x 0.0~git20251028.0cf22f8%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,940 kB
  • sloc: sh: 124; makefile: 5
file content (137 lines) | stat: -rw-r--r-- 2,938 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package cellbuf

// DefaultTabInterval is the default tab interval.
const DefaultTabInterval = 8

// TabStops represents horizontal line tab stops.
type TabStops struct {
	stops    []int
	interval int
	width    int
}

// NewTabStops creates a new set of tab stops from a number of columns and an
// interval.
func NewTabStops(width, interval int) *TabStops {
	ts := new(TabStops)
	ts.interval = interval
	ts.width = width
	ts.stops = make([]int, (width+(interval-1))/interval)
	ts.init(0, width)
	return ts
}

// DefaultTabStops creates a new set of tab stops with the default interval.
func DefaultTabStops(cols int) *TabStops {
	return NewTabStops(cols, DefaultTabInterval)
}

// Resize resizes the tab stops to the given width.
func (ts *TabStops) Resize(width int) {
	if width == ts.width {
		return
	}

	if width < ts.width {
		size := (width + (ts.interval - 1)) / ts.interval
		ts.stops = ts.stops[:size]
	} else {
		size := (width - ts.width + (ts.interval - 1)) / ts.interval
		ts.stops = append(ts.stops, make([]int, size)...)
	}

	ts.init(ts.width, width)
	ts.width = width
}

// IsStop returns true if the given column is a tab stop.
func (ts TabStops) IsStop(col int) bool {
	mask := ts.mask(col)
	i := col >> 3
	if i < 0 || i >= len(ts.stops) {
		return false
	}
	return ts.stops[i]&mask != 0
}

// Next returns the next tab stop after the given column.
func (ts TabStops) Next(col int) int {
	return ts.Find(col, 1)
}

// Prev returns the previous tab stop before the given column.
func (ts TabStops) Prev(col int) int {
	return ts.Find(col, -1)
}

// Find returns the prev/next tab stop before/after the given column and delta.
// If delta is positive, it returns the next tab stop after the given column.
// If delta is negative, it returns the previous tab stop before the given column.
// If delta is zero, it returns the given column.
func (ts TabStops) Find(col, delta int) int {
	if delta == 0 {
		return col
	}

	var prev bool
	count := delta
	if count < 0 {
		count = -count
		prev = true
	}

	for count > 0 {
		if !prev {
			if col >= ts.width-1 {
				return col
			}

			col++
		} else {
			if col < 1 {
				return col
			}

			col--
		}

		if ts.IsStop(col) {
			count--
		}
	}

	return col
}

// Set adds a tab stop at the given column.
func (ts *TabStops) Set(col int) {
	mask := ts.mask(col)
	ts.stops[col>>3] |= mask
}

// Reset removes the tab stop at the given column.
func (ts *TabStops) Reset(col int) {
	mask := ts.mask(col)
	ts.stops[col>>3] &= ^mask
}

// Clear removes all tab stops.
func (ts *TabStops) Clear() {
	ts.stops = make([]int, len(ts.stops))
}

// mask returns the mask for the given column.
func (ts *TabStops) mask(col int) int {
	return 1 << (col & (ts.interval - 1))
}

// init initializes the tab stops starting from col until width.
func (ts *TabStops) init(col, width int) {
	for x := col; x < width; x++ {
		if x%ts.interval == 0 {
			ts.Set(x)
		} else {
			ts.Reset(x)
		}
	}
}