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

⚗️ tinkering with viewport hotkeys #113

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 44 additions & 51 deletions pkg/cmd/index/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
)

func Command() *cobra.Command {

cmd := &cobra.Command{
Use: "index <command>",
Short: i18n.C.IndexShort,
Expand All @@ -34,31 +33,7 @@ func Command() *cobra.Command {
Use: "list <index>",
Short: i18n.C.IndexListShort,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return ui.Error(i18n.C.IndexErrorRequired)
}

// Create a new IndexQueryParameters struct and set the values from the flags
queryParameters := sdk.IndexQueryParameters{}
for i := 0; i < keys.NumField(); i++ {
flag := utils.NormalizeString(keys.Field(i).Name)
if cmd.Flag(flag).Value.String() != "" {
field := reflect.ValueOf(&queryParameters).Elem().Field(i)
switch field.Kind() {
case reflect.String:
field.SetString(cmd.Flag(flag).Value.String())
case reflect.Int:
intValue, err := strconv.Atoi(cmd.Flag(flag).Value.String())
if err != nil {
fmt.Println(err)
continue
}
field.SetInt(int64(intValue))
}
}
}

response, err := session.Connect(config.Token()).GetIndex(args[0], queryParameters)
response, err := getIndexResponse(cmd, args)
if err != nil {
return err
}
Expand All @@ -71,35 +46,18 @@ func Command() *cobra.Command {
Use: "browse <index>",
Short: i18n.C.IndexBrowseShort,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return ui.Error(i18n.C.IndexErrorRequired)
response, err := getIndexResponse(cmd, args)
if err != nil {
return err
}

// Create a new IndexQueryParameters struct and set the values from the flags
queryParameters := sdk.IndexQueryParameters{}
for i := 0; i < keys.NumField(); i++ {
flag := utils.NormalizeString(keys.Field(i).Name)
if cmd.Flag(flag).Value.String() != "" {
field := reflect.ValueOf(&queryParameters).Elem().Field(i)
switch field.Kind() {
case reflect.String:
field.SetString(cmd.Flag(flag).Value.String())
case reflect.Int:
intValue, err := strconv.Atoi(cmd.Flag(flag).Value.String())
if err != nil {
fmt.Println(err)
continue
}
field.SetInt(int64(intValue))
}
}
loadPageFunc := func(index string, page int) (*sdk.IndexResponse, error) {
queryParams := buildQueryParameters(cmd)
queryParams.Page = page
return session.Connect(config.Token()).GetIndex(index, queryParams)
}

response, err := session.Connect(config.Token()).GetIndex(args[0], queryParameters)
if err != nil {
return err
}
ui.Viewport(args[0], response.GetData())
ui.ViewportPaginated(args[0], response.GetData(), response.Meta, loadPageFunc)
return nil
},
}
Expand All @@ -109,3 +67,38 @@ func Command() *cobra.Command {

return cmd
}

func getIndexResponse(cmd *cobra.Command, args []string) (*sdk.IndexResponse, error) {
if len(args) != 1 {
return nil, ui.Error(i18n.C.IndexErrorRequired)
}

queryParameters := buildQueryParameters(cmd)

return session.Connect(config.Token()).GetIndex(args[0], queryParameters)
}

func buildQueryParameters(cmd *cobra.Command) sdk.IndexQueryParameters {
queryParameters := sdk.IndexQueryParameters{}
keys := reflect.TypeOf(sdk.IndexQueryParameters{})

for i := 0; i < keys.NumField(); i++ {
flag := utils.NormalizeString(keys.Field(i).Name)
if cmd.Flag(flag).Value.String() != "" {
field := reflect.ValueOf(&queryParameters).Elem().Field(i)
switch field.Kind() {
case reflect.String:
field.SetString(cmd.Flag(flag).Value.String())
case reflect.Int:
intValue, err := strconv.Atoi(cmd.Flag(flag).Value.String())
if err != nil {
fmt.Println(err)
continue
}
field.SetInt(int64(intValue))
}
}
}

return queryParameters
}
157 changes: 151 additions & 6 deletions pkg/ui/viewport.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/vulncheck-oss/sdk"
"strings"
)

const useHighPerformanceRenderer = false

var subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}

Check failure on line 16 in pkg/ui/viewport.go

View workflow job for this annotation

GitHub Actions / lint

var `subtle` is unused (unused)

Check failure on line 16 in pkg/ui/viewport.go

View workflow job for this annotation

GitHub Actions / lint

var `subtle` is unused (unused)

var (
titleStyle = func() lipgloss.Style {
b := lipgloss.RoundedBorder()
Expand All @@ -27,12 +30,25 @@
)

type model struct {
index string
content string
ready bool
viewport viewport.Model
index string
content string
ready bool
viewport viewport.Model
paginated bool
page int
totalPages int
showHelp bool
loadPage func(index string, page int) (*sdk.IndexResponse, error)
}

type newPageMsg struct {
content string
page int
totalPages int
}

type errMsg struct{ error }

func (m model) Init() tea.Cmd {
return nil
}
Expand All @@ -45,10 +61,40 @@

switch msg := msg.(type) {
case tea.KeyMsg:
if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" {
switch msg.String() {
case "ctrl+c", "q", "esc":
if m.showHelp {
m.showHelp = false
return m, nil
}
return m, tea.Quit
case "?":
m.showHelp = !m.showHelp
return m, nil
}

if m.paginated {
switch msg.String() {
case "left", "[":
if m.page > 1 {
m.page--
return m, loadPage(m)
}
case "right", "]":
if m.page < m.totalPages {
m.page++
return m, loadPage(m)
}
}
}

case newPageMsg:
m.content = msg.content
m.page = msg.page
m.totalPages = msg.totalPages
m.viewport.SetContent(m.content)
return m, nil

case tea.WindowSizeMsg:
headerHeight := lipgloss.Height(m.headerView())
footerHeight := lipgloss.Height(m.footerView())
Expand Down Expand Up @@ -92,10 +138,41 @@
return m, tea.Batch(cmds...)
}

func loadPage(m model) tea.Cmd {
return func() tea.Msg {
response, err := m.loadPage(m.index, m.page)
if err != nil {
return errMsg{err}
}

marshaled, err := json.MarshalIndent(response.GetData(), "", " ")
if err != nil {
return errMsg{err}
}

var buf strings.Builder
err = quick.Highlight(&buf, string(marshaled), "json", "terminal256", "nord")
if err != nil {
return errMsg{err}
}

return newPageMsg{
content: buf.String(),
page: response.Meta.Page,
totalPages: response.Meta.TotalPages,
}
}
}

func (m model) View() string {
if !m.ready {
return "\n Initializing..."
}

if m.showHelp {
return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.helpView(), m.footerView())
}

return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView())
}

Expand All @@ -106,11 +183,47 @@
}

func (m model) footerView() string {
info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
var info string
if m.paginated {
info = infoStyle.Render(fmt.Sprintf("%3.f%% | Page %d of %d | ? for help", m.viewport.ScrollPercent()*100, m.page, m.totalPages))
} else {
info = infoStyle.Render(fmt.Sprintf("%3.f%% | ? for help", m.viewport.ScrollPercent()*100))
}
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info)))
return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
}

func (m model) helpView() string {
helpContent := `
Hot Keys:

q, esc : Close help / Quit
? : Toggle help
left, [ : Previous page
right, ] : Next page
`

// Create a style for the help content
helpStyle := lipgloss.NewStyle().
Width(42). // Increased width to accommodate border
Align(lipgloss.Left).
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("240")).
Padding(1, 2)

// Render the help content
renderedHelp := helpStyle.Render(helpContent)

// Center the rendered help in the viewport
return lipgloss.Place(
m.viewport.Width,
m.viewport.Height,
lipgloss.Center,
lipgloss.Center,
renderedHelp,
)
}

func max(a, b int) int {
if a > b {
return a
Expand Down Expand Up @@ -143,3 +256,35 @@
fmt.Println("could not run program:", err)
}
}

func ViewportPaginated(index string, initialData interface{}, initialMeta sdk.IndexMeta, loadPageFunc func(index string, page int) (*sdk.IndexResponse, error)) {
marshaled, err := json.MarshalIndent(initialData, "", " ")
if err != nil {
panic(err)
}

var buf strings.Builder
err = quick.Highlight(&buf, string(marshaled), "json", "terminal256", "nord")
if err != nil {
panic(err)
}

m := model{
index: index,
content: buf.String(),
paginated: true,
page: initialMeta.Page,
totalPages: initialMeta.TotalPages,
loadPage: loadPageFunc,
}

p := tea.NewProgram(
m,
tea.WithAltScreen(),
tea.WithMouseCellMotion(),
)

if _, err := p.Run(); err != nil {
fmt.Println("could not run program:", err)
}
}
Loading