Upstream olekukonko/tablewriter state of the touched files before c715bafde1
This commit is contained in:
commit
2383574d62
2 files changed
+329
No files matched your search
@@ -0,0 +1,289 @@
|
|||||||
|
package twwidth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tab rune
|
||||||
|
|
||||||
|
const (
|
||||||
|
TabWidthDefault = 8
|
||||||
|
TabString Tab = '\t'
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTab returns true if t equals the default tab.
|
||||||
|
func (t Tab) IsTab() bool {
|
||||||
|
return t == TabString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tab) Byte() byte {
|
||||||
|
return byte(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tab) Rune() rune {
|
||||||
|
return rune(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tab) String() string {
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTab returns true if r is a tab rune.
|
||||||
|
func IsTab(r rune) bool {
|
||||||
|
return r == TabString.Rune()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tabinal struct {
|
||||||
|
once sync.Once
|
||||||
|
width int
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tabinal) String() string {
|
||||||
|
return TabString.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the current tab width, default if unset.
|
||||||
|
func (t *Tabinal) Size() int {
|
||||||
|
t.once.Do(t.init)
|
||||||
|
|
||||||
|
t.mu.RLock()
|
||||||
|
w := t.width
|
||||||
|
t.mu.RUnlock()
|
||||||
|
|
||||||
|
if w <= 0 {
|
||||||
|
return TabWidthDefault
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWidth sets the tab width if valid (1–32).
|
||||||
|
func (t *Tabinal) SetWidth(w int) {
|
||||||
|
if w <= 0 || w > 32 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.once.Do(t.init)
|
||||||
|
t.mu.Lock()
|
||||||
|
t.width = w
|
||||||
|
t.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tabinal) init() {
|
||||||
|
w := t.detect()
|
||||||
|
t.mu.Lock()
|
||||||
|
t.width = w
|
||||||
|
t.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// detect determines tab width using env, editorconfig, project, or term.
|
||||||
|
func (t *Tabinal) detect() int {
|
||||||
|
if w := envInt("TABWIDTH"); w > 0 {
|
||||||
|
return clamp(w)
|
||||||
|
}
|
||||||
|
if w := envInt("TS"); w > 0 {
|
||||||
|
return clamp(w)
|
||||||
|
}
|
||||||
|
if w := envInt("VIM_TABSTOP"); w > 0 {
|
||||||
|
return clamp(w)
|
||||||
|
}
|
||||||
|
if w := editorConfigTabWidth(); w > 0 {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
if w := projectHeuristic(); w > 0 {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
if w := termHeuristic(); w > 0 {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func editorConfigTabWidth() int {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for dir != "" && dir != "/" && dir != "." {
|
||||||
|
path := filepath.Join(dir, ".editorconfig")
|
||||||
|
if w := parseEditorConfig(path); w > 0 {
|
||||||
|
return clamp(w)
|
||||||
|
}
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
|
if parent == dir {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dir = parent
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEditorConfig reads tab_width or indent_size from a file.
|
||||||
|
func parseEditorConfig(path string) int {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
inMatch := false
|
||||||
|
globalWidth := 0
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||||
|
pattern := line[1 : len(line)-1]
|
||||||
|
inMatch = pattern == "*"
|
||||||
|
|
||||||
|
knownExts := []string{".go", ".py", ".js", ".ts", ".java", ".rs"}
|
||||||
|
for _, ext := range knownExts {
|
||||||
|
if strings.Contains(pattern, ext) {
|
||||||
|
inMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inMatch && globalWidth == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
val := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "tab_width":
|
||||||
|
if w, err := strconv.Atoi(val); err == nil && w > 0 {
|
||||||
|
if inMatch {
|
||||||
|
return clamp(w)
|
||||||
|
}
|
||||||
|
if globalWidth == 0 {
|
||||||
|
globalWidth = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "indent_size":
|
||||||
|
if val == "tab" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if w, err := strconv.Atoi(val); err == nil && w > 0 {
|
||||||
|
if inMatch {
|
||||||
|
return clamp(w)
|
||||||
|
}
|
||||||
|
if globalWidth == 0 {
|
||||||
|
globalWidth = w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return globalWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
// projectHeuristic returns 4 for known project types.
|
||||||
|
func projectHeuristic() int {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
indicators := []string{
|
||||||
|
"go.mod", "go.sum",
|
||||||
|
"package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
||||||
|
"setup.py", "pyproject.toml", "requirements.txt", "Pipfile",
|
||||||
|
"pom.xml", "build.gradle", "build.gradle.kts",
|
||||||
|
"Cargo.toml",
|
||||||
|
"composer.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, indicator := range indicators {
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, indicator)); err == nil {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns := []string{"*.go", "*.py", "*.js", "*.ts", "*.java", "*.rs"}
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
if matches, _ := filepath.Glob(filepath.Join(dir, pattern)); len(matches) > 0 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// termHeuristic returns a default width based on the TERM variable.
|
||||||
|
func termHeuristic() int {
|
||||||
|
termEnv := strings.ToLower(os.Getenv("TERM"))
|
||||||
|
if termEnv == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(termEnv, "vt52") {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(termEnv, "xterm") ||
|
||||||
|
strings.Contains(termEnv, "screen") ||
|
||||||
|
strings.Contains(termEnv, "tmux") ||
|
||||||
|
strings.Contains(termEnv, "linux") ||
|
||||||
|
strings.Contains(termEnv, "ansi") ||
|
||||||
|
strings.Contains(termEnv, "rxvt") {
|
||||||
|
return TabWidthDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func clamp(w int) int {
|
||||||
|
if w <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if w > 32 {
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalTab *Tabinal
|
||||||
|
globalTabOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// TabInstance returns the singleton Tabinal.
|
||||||
|
func TabInstance() *Tabinal {
|
||||||
|
globalTabOnce.Do(func() {
|
||||||
|
globalTab = &Tabinal{}
|
||||||
|
})
|
||||||
|
return globalTab
|
||||||
|
}
|
||||||
|
|
||||||
|
// TabWidth returns the detected global tab width.
|
||||||
|
func TabWidth() int {
|
||||||
|
return TabInstance().Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTabWidth sets the global tab width.
|
||||||
|
func SetTabWidth(w int) {
|
||||||
|
TabInstance().SetWidth(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envInt(k string) int {
|
||||||
|
v := os.Getenv(k)
|
||||||
|
w, _ := strconv.Atoi(v)
|
||||||
|
return w
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package twwidth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTabinalSetWidth(t *testing.T) {
|
||||||
|
t.Setenv("TABWIDTH", "3")
|
||||||
|
|
||||||
|
for _, initialized := range []bool{false, true} {
|
||||||
|
for _, width := range []int{1, 2, 32, 0, -1, 33} {
|
||||||
|
t.Run(fmt.Sprintf("initialized=%v/width=%d", initialized, width), func(t *testing.T) {
|
||||||
|
var tab twwidth.Tabinal
|
||||||
|
if initialized {
|
||||||
|
if got := tab.Size(); got != 3 {
|
||||||
|
t.Fatalf("initial Size() = %d, want 3", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.SetWidth(width)
|
||||||
|
want := width
|
||||||
|
if width <= 0 || width > 32 {
|
||||||
|
want = 3
|
||||||
|
}
|
||||||
|
if got := tab.Size(); got != want {
|
||||||
|
t.Fatalf("Size() after SetWidth(%d) = %d, want %d", width, got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.SetWidth(5)
|
||||||
|
tab.SetWidth(0)
|
||||||
|
if got := tab.Size(); got != 5 {
|
||||||
|
t.Fatalf("Size() after changing width = %d, want 5", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user