Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] - Table full width based on terminal size / Header / Row content auto positioning #328

Closed
ondrovic opened this issue Aug 21, 2024 · 9 comments · Fixed by #335
Labels
enhancement New feature or request

Comments

@ondrovic
Copy link

ondrovic commented Aug 21, 2024

Is your feature request related to a problem? Please describe.
Tables with and column spacing seem to be dependent on row content

Header / Row content alignment, I found in using the text.Hyperlink(text, link) it table formatting

Describe the solution you'd like
A config option allow the table to calculate no only the full table width, but also split the header / column locations based on the terminal size

Header / Row content alignment, I found in using the text.Hyperlink(text, link) seems to break the content alignment

Describe alternatives you've considered
Tried adjusting the .SetColumnConfigs( WidthMin, WidthMax)

Additional context

Examples:

using regular non hyperlink - works as expected, wish I could force it to be the full size of the terminal with everything aligned perfectly

image
image

using hyperlink - throws alignment off just a bit

image
image

####I was able to find fix the alignment issue by doing the following but it's very hacky and I haven't tested it enough, would be better if this was automagically calculated

image
image

// DirectoryResults struct for the results
type DirectoryResult struct {
	Directory string
	Count     int
}

// EntryResults struct for more in depth entry info
type EntryResults struct {
	Directory string
	FileName  string
	FileSize  string
}

// The `FormatPath` function converts file paths to either Windows or Unix style based on the operating.
// system specified.
func FormatPath(path, goos string) string {
	switch goos {
	case "windows":
		// Convert to Windows style paths (with backslashes)
		return filepath.FromSlash(path)
	case "linux", "darwin":
		// Convert to Unix style paths (with forward slashes)
		return filepath.ToSlash(path)
	default:
		// Default to Unix style paths
		return path
	}
}

// DetailedResults example
detailedResults := []EntryResults {
    {Directory: "S:\\testdata\\zips", FileName: "tamara.zip", FileSize: "32.37 MB"},
    {Directory: "S:\\testdata\\zips", FileName: "pop.7z", FileSize: "121.22 MB"},
    {Directory: "S:\\testdata\\One\\Videos\\One", FileName: "videofile.mkv", FileSize: "0 B"},
}

// Non Detailed example
nonDetailedResuls := []DirectoryResult {
  {Directory: "s:\\testdata\\One\\Videos\\One", Count: 1},
  {Directory: "s:\\testdata\\zips" , Count: 2},
}

// shared vars
totalCount := 3
totalFileSize := 161061273

// renderResults to table takes and interface and renders the table accordingly.
```go
func renderResultsToTable(results interface{}, totalCount int, totalFileSize int64, ff types.FileFinder) {
	t := table.Table{}

	// Determine header and footer based on the type of results
	var header table.Row
	var footer table.Row
	switch results.(type) {
	case []types.DirectoryResult:
		header = table.Row{"Directory", "\t\tCount"}
		footer = table.Row{"Total", pterm.Sprintf("\t\t  %v", totalCount)}
	case []types.EntryResult:
		header = table.Row{"Directory", "\t\tFileName", "\t\tFileSize"}
		footer = table.Row{"Total", pterm.Sprintf("\t\t  %v", totalCount), pterm.Sprintf("\t\t%v", commonFormatters.FormatSize(totalFileSize))}
	default:
		return // Exit if results type is not supported
	}

	t.AppendHeader(header)

	// Append rows based on the display mode
	switch results := results.(type) {
	case []types.DirectoryResult:
		for _, result := range results {
			t.AppendRow(table.Row{
				text.Hyperlink(commonFormatters.FormatPath(result.Directory, runtime.GOOS), result.Directory),
				pterm.Sprintf("\t   %v", result.Count),
			})
		}
	case []types.EntryResult:
		if ff.DisplayDetailedResults {
			for _, result := range results {
				t.AppendRow(table.Row{
					text.Hyperlink(commonFormatters.FormatPath(result.Directory, runtime.GOOS), result.Directory),
					"\t " + result.FileName,
					"\t " + result.FileSize,
				})
			}
		}
	}

	t.AppendFooter(footer)

	t.SetStyle(table.StyleColoredDark)
	t.SetOutputMirror(os.Stdout)
	t.Render()
}

another example using colors with hyperlink illustration how alignment is broken - illustration how my temp fix is garbage

func formatResultHyperLink(link, txt string) string {
	text.EnableColors()

	link = commonFormatters.FormatPath(link, runtime.GOOS)
	txt = text.FgGreen.Sprint(txt)
	
	return text.Hyperlink(link, txt)
}


func renderResultsToTable(results interface{}, totalCount int, totalFileSize int64, ff types.FileFinder) {
	t := table.Table{}

	// Determine header and footer based on the type of results
	var header table.Row
	var footer table.Row
	switch results.(type) {
	case []types.DirectoryResult:
		header = table.Row{"Directory", "Count"}
		footer = table.Row{"Total", pterm.Sprintf("%v", totalCount)}
	case []types.EntryResult:
		header = table.Row{"Directory", "FileName", "FileSize"}
		footer = table.Row{"Total", pterm.Sprintf("%v", totalCount), pterm.Sprintf("%v", commonFormatters.FormatSize(totalFileSize))}
	default:
		return // Exit if results type is not supported
	}

	t.AppendHeader(header)

	// Append rows based on the display mode
	switch results := results.(type) {
	case []types.DirectoryResult:
		for _, result := range results {
			t.AppendRow(table.Row{
				formatResultHyperLink(result.Directory, result.Directory),
				pterm.Sprintf("%v", result.Count),
			})
		}
	case []types.EntryResult:
		if ff.DisplayDetailedResults {
			for _, result := range results {
				newLink := pterm.Sprintf("%s/%s", result.Directory, result.FileName)
				t.AppendRow(table.Row{
					formatResultHyperLink(result.Directory, result.Directory),
					formatResultHyperLink(newLink, result.FileName),
					result.FileSize,
				})
			}
		}
	}

	t.AppendFooter(footer)

	t.SetStyle(table.StyleColoredDark)
	t.SetOutputMirror(os.Stdout)
	t.Render()
}

image
image

@jedib0t jedib0t added the enhancement New feature or request label Sep 1, 2024
@jedib0t
Copy link
Owner

jedib0t commented Sep 1, 2024

Auto-sizing tables has been something I wanted to implement, thanks for the reminder.

@jedib0t
Copy link
Owner

jedib0t commented Oct 5, 2024

Hey @ondrovic try the code in branch table-auto-width and see if that works for you?

Sample usage: https://github.com/jedib0t/go-pretty/blob/table-auto-width/table/render_test.go#L1356-L1533

@ondrovic
Copy link
Author

ondrovic commented Oct 5, 2024

Hey @ondrovic try the code in branch table-auto-width and see if that works for you?

Sample usage: https://github.com/jedib0t/go-pretty/blob/table-auto-width/table/render_test.go#L1356-L1533

I will give it a try later today and let you know

@ondrovic
Copy link
Author

ondrovic commented Oct 5, 2024

Hey @ondrovic try the code in branch table-auto-width and see if that works for you?

Sample usage: https://github.com/jedib0t/go-pretty/blob/table-auto-width/table/render_test.go#L1356-L1533

As a quick test I tried it like this

func renderResultsToTable(results interface{}, totalCount int, totalFileSize int64, ff types.FileFinder) {
	t := table.Table{}

	// Determine header and footer based on the type of results
	var header table.Row
	var footer table.Row
	switch results.(type) {
	case []types.DirectoryResult:
		header = table.Row{"Directory", "Count"}
		footer = table.Row{"Total", pterm.Sprintf("%v", totalCount)}
	case []types.EntryResult:
		header = table.Row{"Directory", "FileName", "FileSize"}
		footer = table.Row{"Total", pterm.Sprintf("%v", totalCount), pterm.Sprintf("%v", commonFormatters.FormatSize(totalFileSize))}
	default:
		return // Exit if results type is not supported
	}

	t.AppendHeader(header)

	// Append rows based on the display mode
	switch results := results.(type) {
	case []types.DirectoryResult:
		for _, result := range results {
			t.AppendRow(table.Row{
				formatResultHyperLink(result.Directory, result.Directory),
				pterm.Sprintf("%v", result.Count),
			})
		}
	case []types.EntryResult:
		if ff.DisplayDetailedResults {
			for _, result := range results {
				newLink := pterm.Sprintf("%s/%s", result.Directory, result.FileName)
				t.AppendRow(table.Row{
					formatResultHyperLink(result.Directory, result.Directory),
					formatResultHyperLink(newLink, result.FileName),
					result.FileSize,
				})
			}
		}
	}

	t.AppendFooter(footer)

	t.SetStyle(table.StyleColoredDark)
	t.SetStyle(table.Style{Size: table.SizeOptions{
		WidthMax: 50,
		WidthMin: 0,
	}})
	t.SetOutputMirror(os.Stdout)
	t.Render()
}

It didn't quite work as I expected

without size styles

image

With new size styles
image

It seems like it broke the headers / footers, it could have been that I wasn't using it like your example using the table writer, I will try that when I get back from errands this morning

But also I was hoping more for an automatic / dynamic min / max width where it would auto calculate the min / and the maximum based on the content so you don't have to hard code in the values. While I understand it may increase rendering times based on the the size of the data set.

@jedib0t
Copy link
Owner

jedib0t commented Oct 5, 2024

Your second SetStyle just overrode the first call's effect. Try it like this:

	t.SetStyle(table.StyleColoredDark)
	t.Style().Size = table.SizeOptions{
		WidthMax: 50,
		WidthMin: 0,
	}})

To auto-size for your terminal, you'd do something like this:

package main

import (
	"fmt"
	"os"

	"github.com/jedib0t/go-pretty/v6/table"
	"golang.org/x/term"
)

func main() {
	w, h, err := term.GetSize(int(os.Stdin.Fd()))
	if err != nil {
		panic(err.Error())
	}
	fmt.Println(w, h)

	tw := table.NewWriter()
	tw.SetTitle("Title")
	tw.AppendHeader(table.Row{"#", "First Name", "Last Name", "Salary"})
	tw.AppendRows([]table.Row{
		{1, "Arya", "Stark", 3000},
		{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
		{300, "Tyrion", "Lannister", 5000},
	})
	tw.AppendFooter(table.Row{"", "", "Total", 10000})
	tw.SetStyle(table.StyleLight)
	tw.Style().Size = table.SizeOptions{
		WidthMin: w,
	}
	fmt.Println(tw.Render())
}

Output:
image

Title sizing logic is broken now and I will fix it before merging this code.

@jedib0t
Copy link
Owner

jedib0t commented Oct 5, 2024

I've pushed a couple of commit just now. So try to use the latest to get the fix for the title rendering being broken.

image

@ondrovic
Copy link
Author

ondrovic commented Oct 5, 2024

I've pushed a couple of commit just now. So try to use the latest to get the fix for the title rendering being broken.

image

so based on this still using non table.NewWriter() method (going to do that next)

func renderResultsToTable(results interface{}, totalCount int, totalFileSize int64, ff types.FileFinder) {
	t := table.Table{}
	w, h, err := getTerminalSize()
    if err != nil {
        panic(err.Error())
    }
    fmt.Printf("Terminal size: Width = %d, Height = %d\n", w, h)
	// Determine header and footer based on the type of results
	var header table.Row
	var footer table.Row
	switch results.(type) {
	case []types.DirectoryResult:
		header = table.Row{"Directory", "Count"}
		footer = table.Row{"Total", pterm.Sprintf("%v", totalCount)}
	case []types.EntryResult:
		header = table.Row{"Directory", "FileName", "FileSize"}
		footer = table.Row{"Total", pterm.Sprintf("%v", totalCount), pterm.Sprintf("%v", commonFormatters.FormatSize(totalFileSize))}
	default:
		return // Exit if results type is not supported
	}

	t.AppendHeader(header)

	// Append rows based on the display mode
	switch results := results.(type) {
	case []types.DirectoryResult:
		for _, result := range results {
			t.AppendRow(table.Row{
				formatResultHyperLink(result.Directory, result.Directory),
				pterm.Sprintf("%v", result.Count),
			})
		}
	case []types.EntryResult:
		if ff.DisplayDetailedResults {
			for _, result := range results {
				newLink := pterm.Sprintf("%s/%s", result.Directory, result.FileName)
				t.AppendRow(table.Row{
					formatResultHyperLink(result.Directory, result.Directory),
					formatResultHyperLink(newLink, result.FileName),
					result.FileSize,
				})
			}
		}
	}

	t.AppendFooter(footer)

	t.SetStyle(table.StyleColoredDark)
	t.SetStyle(table.Style{Size: table.SizeOptions{
		WidthMin: w,
	}})
	t.SetOutputMirror(os.Stdout)
	t.Render()
}

it does seem to have fixed the formatting
image

The only thing is with really long filenames
Main monitor 5120x1440
image
Second monitor 1920x860
image

@jedib0t
Copy link
Owner

jedib0t commented Oct 5, 2024

Two things:

  1. You are still calling SetStyle() twice with the second call overriding everything except for Size; please use Style().Size for the second call.
  2. This logic does auto-expand, and not auto-contract; maybe I'll find a way to do it next.

@ondrovic
Copy link
Author

ondrovic commented Oct 5, 2024

Two things:

  1. You are still calling SetStyle() twice with the second call overriding everything except for Size; please use Style().Size for the second call.
  2. This logic does auto-expand, and not auto-contract; maybe I'll find a way to do it next.
  1. That worked
// just posting for anyone else who stumbles across this ;-)
t.Style().Size = table.SizeOptions{
		WidthMin: w,
	}

image

  1. Let me know if you end up doing it and need me to test it out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants