v1
This commit is contained in:
parent
beb47854d6
commit
7f7ae26f4e
@ -1,3 +1,3 @@
|
||||
# Procurator
|
||||
|
||||
My Git project manager TUI
|
||||
My Git project manager TUI
|
||||
|
8
go.mod
8
go.mod
@ -3,12 +3,17 @@ module git.sual.in/casual/Procurator
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.26.1
|
||||
github.com/charmbracelet/lipgloss v0.10.0
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/projectdiscovery/goflags v0.1.50
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
|
||||
@ -27,7 +32,8 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
||||
github.com/projectdiscovery/utils v0.0.90 // indirect
|
||||
github.com/rivo/uniseg v0.4.6 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/tidwall/gjson v1.14.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
|
18
go.sum
18
go.sum
@ -1,19 +1,31 @@
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.26.1 h1:xujcQeF73rh4jwu3+zhfQsvV18x+7zIjlw7/CYbzGJ0=
|
||||
github.com/charmbracelet/bubbletea v0.26.1/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo=
|
||||
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
|
||||
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
@ -47,8 +59,10 @@ github.com/projectdiscovery/utils v0.0.90 h1:ZBClkrtC6U2xuT8AI6/MrYgm3cxQnwSZmM8
|
||||
github.com/projectdiscovery/utils v0.0.90/go.mod h1:laZCMcUTT1vTmclUCW+zpi/fjeYsgU1gMwgv2CxNcRk=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
|
34
main.go
34
main.go
@ -9,33 +9,25 @@ import (
|
||||
)
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
|
||||
var TUI *tea.Program
|
||||
|
||||
func main() {
|
||||
options, err := ParseOptions()
|
||||
_, err := ParseOptions()
|
||||
// options, err := ParseOptions()
|
||||
errHandler(err, "Error parsing options")
|
||||
|
||||
fmt.Println("hello world", options)
|
||||
|
||||
// // start workers in parallel
|
||||
// for i := 0; i < options.Threads; i++ {
|
||||
// waitGroup.Add(1)
|
||||
// go func() {
|
||||
// fmt.Println("do parallel stuff")
|
||||
//
|
||||
// defer waitGroup.Done()
|
||||
// }()
|
||||
// }
|
||||
// waitGroup.Wait()
|
||||
p := tea.NewProgram(initialModel())
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Printf("Alas, there's been an error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
model := initialModel()
|
||||
TUI = tea.NewProgram(
|
||||
&model,
|
||||
tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
|
||||
// tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel
|
||||
)
|
||||
|
||||
go watchFilesystem()
|
||||
|
||||
if _, err := TUI.Run(); err != nil {
|
||||
errHandler(err, "Tui error:")
|
||||
}
|
||||
}
|
||||
|
||||
func errHandler(err error, message string) {
|
||||
|
@ -24,9 +24,9 @@ func ParseOptions() (*Options, error) {
|
||||
flagSet.SetDescription("Procurator - your little Git TUI")
|
||||
|
||||
// flagSet.CreateGroup("input", "Input",
|
||||
// flagSet.StringVarP(&options.URL, "u", "url", "", "target's url"),
|
||||
// flagSet.IntVarP(&options.Threads, "t", "threads", 10, "threads to run"), //TODO add estimate counter to packets/s
|
||||
// flagSet.StringVarP(&options.URL, "u", "url", "", "verbose"),
|
||||
// flagSet.StringVarP(&options.URL, "u", "url", "", "target's url"),
|
||||
// flagSet.IntVarP(&options.Threads, "t", "threads", 10, "threads to run"), //TODO add estimate counter to packets/s
|
||||
// flagSet.StringVarP(&options.URL, "u", "url", "", "verbose"),
|
||||
// )
|
||||
_ = flagSet.Parse()
|
||||
|
||||
|
223
tui.go
223
tui.go
@ -2,104 +2,183 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
// "os"
|
||||
|
||||
// "path/filepath"
|
||||
// "github.com/kardianos/osext"
|
||||
// "unicode"
|
||||
|
||||
// "strings"
|
||||
// "time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
// "github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
var skipNextViewportUpdate bool
|
||||
|
||||
type model struct {
|
||||
choices []string // items on the to-do list
|
||||
cursor int // which to-do list item our cursor is pointing at
|
||||
selected map[int]struct{} // which to-do items are selected
|
||||
viewport viewport.Model
|
||||
ready bool
|
||||
|
||||
// choices []string // items on the to-do list
|
||||
list list.Model
|
||||
// choice string
|
||||
quitting bool
|
||||
// cursor int // which to-do list item our cursor is pointing at
|
||||
// selected map[int]struct{} // which to-do items are selected
|
||||
|
||||
}
|
||||
|
||||
func initialModel() model {
|
||||
items := []list.Item{
|
||||
item("fmt code"),
|
||||
item("git add ."),
|
||||
// item("git commit"), TODO
|
||||
item("git push"),
|
||||
// item("add to gitignore"), TODO
|
||||
// item("go mod init github_repo"), TODO
|
||||
// item("remove file from git history"), TODO
|
||||
}
|
||||
l := list.New(items, itemDelegate{}, defaultWidth, listHeight)
|
||||
// l.Title = "What do you want for dinner?"
|
||||
l.SetShowStatusBar(false)
|
||||
l.SetShowTitle(false)
|
||||
// l.SetShowStatusBar(true)
|
||||
l.SetFilteringEnabled(false)
|
||||
l.Styles.Title = titleStyle
|
||||
l.Styles.PaginationStyle = paginationStyle
|
||||
l.Styles.HelpStyle = helpStyle
|
||||
return model{
|
||||
// Our to-do list is a grocery list
|
||||
choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
|
||||
// choices: []string{"Buy carrots", "Buy celery", "Buy kohlrabi"},
|
||||
|
||||
// A map which indicates which choices are selected. We're using
|
||||
// the map like a mathematical set. The keys refer to the indexes
|
||||
// of the `choices` slice, above.
|
||||
selected: make(map[int]struct{}),
|
||||
// selected: make(map[int]struct{}),
|
||||
list: l,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
// Just return `nil`, which means "no I/O right now, please."
|
||||
return nil
|
||||
type dataMsg struct {
|
||||
// reflex string
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
|
||||
// Is it a key press?
|
||||
case tea.KeyMsg:
|
||||
|
||||
// Cool, what was the actual key pressed?
|
||||
switch msg.String() {
|
||||
|
||||
// These keys should exit the program.
|
||||
case "ctrl+c", "q":
|
||||
return m, tea.Quit
|
||||
|
||||
// The "up" and "k" keys move the cursor up
|
||||
case "up", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
|
||||
// The "down" and "j" keys move the cursor down
|
||||
case "down", "j":
|
||||
if m.cursor < len(m.choices)-1 {
|
||||
m.cursor++
|
||||
}
|
||||
|
||||
// The "enter" key and the spacebar (a literal space) toggle
|
||||
// the selected state for the item that the cursor is pointing at.
|
||||
case "enter", " ":
|
||||
_, ok := m.selected[m.cursor]
|
||||
if ok {
|
||||
delete(m.selected, m.cursor)
|
||||
} else {
|
||||
m.selected[m.cursor] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the updated model to the Bubble Tea runtime for processing.
|
||||
// Note that we're not returning a command.
|
||||
return m, nil
|
||||
func (m *model) Init() tea.Cmd {
|
||||
// Just return `nil`, which means "no I/O right now, please."
|
||||
// return m.reflex("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
// The header
|
||||
s := "What should we buy at the market?\n\n"
|
||||
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// var (
|
||||
// // cmd tea.Cmd
|
||||
// cmds []tea.Cmd
|
||||
// )
|
||||
|
||||
// Iterate over our choices
|
||||
for i, choice := range m.choices {
|
||||
switch msg := msg.(type) {
|
||||
|
||||
// Is the cursor pointing at this choice?
|
||||
cursor := " " // no cursor
|
||||
if m.cursor == i {
|
||||
cursor = ">" // cursor!
|
||||
}
|
||||
// Is it a key press?
|
||||
case tea.KeyMsg:
|
||||
|
||||
// Is this choice selected?
|
||||
checked := " " // not selected
|
||||
if _, ok := m.selected[i]; ok {
|
||||
checked = "x" // selected!
|
||||
}
|
||||
// Cool, what was the actual key pressed?
|
||||
switch msg.String() {
|
||||
|
||||
// Render the row
|
||||
s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
|
||||
}
|
||||
case "ctrl+c", "q":
|
||||
return m, tea.Quit
|
||||
|
||||
// The footer
|
||||
s += "\nPress q to quit.\n"
|
||||
// // The "up" and "k" keys move the cursor up
|
||||
// case "up", "k":
|
||||
// if m.cursor > 0 {
|
||||
// m.cursor--
|
||||
// }
|
||||
//
|
||||
// // The "down" and "j" keys move the cursor down
|
||||
// case "down", "j":
|
||||
// if m.cursor < len(m.choices)-1 {
|
||||
// m.cursor++
|
||||
// }
|
||||
|
||||
// The "enter" key and the spacebar (a literal space) toggle
|
||||
// the selected state for the item that the cursor is pointing at.
|
||||
case "enter", " ":
|
||||
i, ok := m.list.SelectedItem().(item)
|
||||
if ok {
|
||||
// m.choice = string(i)
|
||||
skipNextViewportUpdate = true
|
||||
m.viewport.SetContent("[Running...]")
|
||||
m.viewport.SetContent(choiceAction(string(i)))
|
||||
m.viewport.GotoBottom()
|
||||
|
||||
}
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
headerHeight := lipgloss.Height(m.headerView())
|
||||
footerHeight := lipgloss.Height(m.footerView())
|
||||
verticalMarginHeight := headerHeight + footerHeight
|
||||
m.list.SetWidth(msg.Width)
|
||||
m.list.SetHeight(msg.Height / 2)
|
||||
|
||||
if !m.ready {
|
||||
// Since this program is using the full size of the viewport we
|
||||
// need to wait until we've received the window dimensions before
|
||||
// we can initialize the viewport. The initial dimensions come in
|
||||
// quickly, though asynchronously, which is why we wait for them
|
||||
// here.
|
||||
m.viewport = viewport.New(msg.Width, msg.Height/2-verticalMarginHeight)
|
||||
m.viewport.YPosition = headerHeight
|
||||
m.viewport.HighPerformanceRendering = useHighPerformanceRenderer
|
||||
m.viewport.SetContent("[READY]")
|
||||
m.ready = true
|
||||
|
||||
// This is only necessary for high performance rendering, which in
|
||||
// most cases you won't need.
|
||||
//
|
||||
// Render the viewport one line below the header.
|
||||
m.viewport.YPosition = headerHeight + 1
|
||||
} else {
|
||||
m.viewport.Width = msg.Width
|
||||
m.viewport.Height = msg.Height/2 - verticalMarginHeight
|
||||
}
|
||||
|
||||
// if useHighPerformanceRenderer {
|
||||
// // Render (or re-render) the whole viewport. Necessary both to
|
||||
// // initialize the viewport and when the window is resized.
|
||||
// //
|
||||
// // This is needed for high-performance rendering only.
|
||||
// cmds = append(cmds, viewport.Sync(m.viewport))
|
||||
// }
|
||||
|
||||
case fileUpdated:
|
||||
if !skipNextViewportUpdate {
|
||||
m.viewport.SetContent("[Running...]")
|
||||
m.viewport.SetContent(watchRun())
|
||||
m.viewport.GotoBottom()
|
||||
return m, nil
|
||||
}
|
||||
skipNextViewportUpdate = false
|
||||
|
||||
}
|
||||
// Return the updated model to the Bubble Tea runtime for processing.
|
||||
// Note that we're not returning a command.
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
// return m, nil
|
||||
}
|
||||
|
||||
func (m *model) View() string {
|
||||
|
||||
if !m.ready {
|
||||
return "\n Initializing..."
|
||||
}
|
||||
|
||||
// return fmt.Sprintf("%s\n%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView(), m.choicesView())
|
||||
// TODO if window too small, hide list.View
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView(), m.list.View())
|
||||
|
||||
// Send the UI for rendering
|
||||
return s
|
||||
}
|
||||
|
126
tui_choices.go
Normal file
126
tui_choices.go
Normal file
@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
// https://github.com/charmbracelet/bubbletea/blob/master/examples/list-simple/main.go
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type item string
|
||||
|
||||
func (i item) FilterValue() string { return "" }
|
||||
|
||||
type itemDelegate struct{}
|
||||
|
||||
const listHeight = 14
|
||||
const defaultWidth = 20
|
||||
|
||||
func (d itemDelegate) Height() int { return 1 }
|
||||
func (d itemDelegate) Spacing() int { return 0 }
|
||||
func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
|
||||
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||
i, ok := listItem.(item)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("%d. %s", index+1, i)
|
||||
|
||||
fn := itemStyle.Render
|
||||
if index == m.Index() {
|
||||
fn = func(s ...string) string {
|
||||
return selectedItemStyle.Render("> " + strings.Join(s, " "))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(w, fn(str))
|
||||
}
|
||||
|
||||
var (
|
||||
// titleStyle = lipgloss.NewStyle().MarginLeft(2)
|
||||
itemStyle = lipgloss.NewStyle().PaddingLeft(4)
|
||||
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
|
||||
paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
|
||||
helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
|
||||
quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
||||
)
|
||||
|
||||
// func (m *model) choicesView() string {
|
||||
// // The header
|
||||
// s := "What should we buy at the market?\n\n"
|
||||
//
|
||||
// // Iterate over our choices
|
||||
// for i, choice := range m.choices {
|
||||
//
|
||||
// // Is the cursor pointing at this choice?
|
||||
// cursor := " " // no cursor
|
||||
// if m.cursor == i {
|
||||
// cursor = ">" // cursor!
|
||||
// }
|
||||
//
|
||||
// // Is this choice selected?
|
||||
// checked := " " // not selected
|
||||
// if _, ok := m.selected[i]; ok {
|
||||
// checked = "x" // selected!
|
||||
// }
|
||||
//
|
||||
// // Render the row
|
||||
// s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
|
||||
// }
|
||||
//
|
||||
// // The footer
|
||||
// s += "\nPress q to quit.\n"
|
||||
//
|
||||
// // Send the UI for rendering
|
||||
// // return s
|
||||
// return s
|
||||
// }
|
||||
//
|
||||
|
||||
// type errorRunningCommand string
|
||||
// const errorMsg=""
|
||||
|
||||
func choiceAction(input string) string {
|
||||
switch input {
|
||||
case "fmt code":
|
||||
// TODO if go
|
||||
output, err := exec.Command("go", "fmt").CombinedOutput()
|
||||
if err != nil {
|
||||
// var errorRnCmd errorRunningCommand
|
||||
// errorRnCmd = errorMsg+string(output)
|
||||
return "[ERROR] - executing command failed\n" + string(output)
|
||||
// tui.Send(errorRnCmd)
|
||||
}
|
||||
return "[OK]\n" + string(output)
|
||||
case "git add .":
|
||||
output, err := exec.Command("git", "add", ".").CombinedOutput()
|
||||
if err != nil {
|
||||
return "[ERROR] - executing command failed\n" + string(output)
|
||||
|
||||
}
|
||||
return "[OK]\n" + string(output)
|
||||
case "git commit":
|
||||
// TODO add input message box
|
||||
case "git push":
|
||||
output, err := exec.Command("git", "push").CombinedOutput()
|
||||
if err != nil {
|
||||
return "[ERROR] - executing command failed\n" + string(output)
|
||||
}
|
||||
return "[OK]\n" + string(output)
|
||||
case "add file to gitignore":
|
||||
// TODO add input message box with option to choose file or import filter
|
||||
case "go mod init github_repo":
|
||||
// TODO add input message box OR add fetching link via .git
|
||||
case "remove file from git history":
|
||||
// TODO add file chooser
|
||||
default:
|
||||
return "[ERROR] - unexisting option"
|
||||
}
|
||||
return "[ERROR] - unexisting option"
|
||||
}
|
68
tui_viewport.go
Normal file
68
tui_viewport.go
Normal file
@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
|
||||
// "os"
|
||||
// "path/filepath"
|
||||
"github.com/kardianos/osext"
|
||||
// "unicode"
|
||||
|
||||
"strings"
|
||||
// "time"
|
||||
|
||||
// tea "github.com/charmbracelet/bubbletea"
|
||||
// "github.com/charmbracelet/bubbles/viewport"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
// "github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
const useHighPerformanceRenderer = false
|
||||
|
||||
var (
|
||||
titleStyle = func() lipgloss.Style {
|
||||
b := lipgloss.RoundedBorder()
|
||||
b.Right = "├"
|
||||
return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1)
|
||||
}()
|
||||
|
||||
infoStyle = func() lipgloss.Style {
|
||||
b := lipgloss.RoundedBorder()
|
||||
b.Left = "┤"
|
||||
return titleStyle.Copy().BorderStyle(b)
|
||||
}()
|
||||
)
|
||||
|
||||
func (m *model) headerView() string {
|
||||
title := titleStyle.Render("Procurator - " + getCurrentFolder())
|
||||
|
||||
line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title)))
|
||||
return lipgloss.JoinHorizontal(lipgloss.Center, title, line)
|
||||
}
|
||||
|
||||
func (m *model) footerView() string {
|
||||
// info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100))
|
||||
line := strings.Repeat("─", max(0, m.viewport.Width))
|
||||
return lipgloss.JoinHorizontal(lipgloss.Center, line)
|
||||
}
|
||||
|
||||
func getCurrentFolder() string {
|
||||
folderPath, err := osext.ExecutableFolder()
|
||||
// folderPath, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
folders := strings.Split(folderPath, "/")
|
||||
if err != nil {
|
||||
errHandler(err, "Can't get current folder")
|
||||
}
|
||||
capitalizedCurFolder := folders[len(folders)-1]
|
||||
capitalizedCurFolder = strings.ToUpper(capitalizedCurFolder[:1]) + capitalizedCurFolder[1:]
|
||||
return capitalizedCurFolder
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
///////////
|
87
watchFilesystem.go
Normal file
87
watchFilesystem.go
Normal file
@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
// It's not an actual reflex (https://github.com/cespare/reflex). I just wrote some code as for reflex and couldn't manage to adapt it fast before moving to creating own solution
|
||||
|
||||
import (
|
||||
// "errors"
|
||||
// // "flag"
|
||||
// "fmt"
|
||||
// "io/ioutil"
|
||||
// "log"
|
||||
// "os"
|
||||
// "os/exec"
|
||||
// "path"
|
||||
// "reflect"
|
||||
// "regexp"
|
||||
// "strconv"
|
||||
// // "strings"
|
||||
// "syscall"
|
||||
// "time"
|
||||
// "bytes"
|
||||
"log"
|
||||
"os/exec"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
// tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type fileUpdated bool
|
||||
|
||||
// func watchFilesystem(tui *tea.Program) {
|
||||
func watchFilesystem() {
|
||||
// Create new watcher.
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
errHandler(err, "Can't setup filesystem watcher")
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
// Start listening for events.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// log.Println("event:", event)
|
||||
if event.Has(fsnotify.Write) {
|
||||
// log.Println("modified file:", event.Name)
|
||||
var fileUpdated1 fileUpdated
|
||||
TUI.Send(fileUpdated1)
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Add a current path.
|
||||
err = watcher.Add(".")
|
||||
if err != nil {
|
||||
errHandler(err, "Filesystem Watcher - Can't add current dir:")
|
||||
}
|
||||
|
||||
// Block main goroutine forever.
|
||||
<-make(chan struct{})
|
||||
}
|
||||
|
||||
func watchRun() string {
|
||||
//TODO if edited .go file
|
||||
return GoVet()
|
||||
}
|
||||
|
||||
func GoVet() string {
|
||||
out1, _ := exec.Command("go", "mod", "tidy").CombinedOutput()
|
||||
|
||||
out2, _ := exec.Command("go", "vet").CombinedOutput()
|
||||
|
||||
output := string(out1) + string(out2)
|
||||
if output == "" {
|
||||
output = "[BUILD OK]"
|
||||
}
|
||||
return output
|
||||
}
|
Loading…
Reference in New Issue
Block a user