v1
This commit is contained in:
parent
beb47854d6
commit
7f7ae26f4e
8
go.mod
8
go.mod
@ -3,12 +3,17 @@ module git.sual.in/casual/Procurator
|
|||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/charmbracelet/bubbles v0.18.0
|
||||||
github.com/charmbracelet/bubbletea v0.26.1
|
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
|
github.com/projectdiscovery/goflags v0.1.50
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
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/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // 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/pkg/errors v0.9.1 // indirect
|
||||||
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
||||||
github.com/projectdiscovery/utils v0.0.90 // 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/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||||
github.com/tidwall/gjson v1.14.3 // indirect
|
github.com/tidwall/gjson v1.14.3 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // 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 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
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 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
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 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
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 h1:xujcQeF73rh4jwu3+zhfQsvV18x+7zIjlw7/CYbzGJ0=
|
||||||
github.com/charmbracelet/bubbletea v0.26.1/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo=
|
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 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
|
||||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
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 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
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=
|
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/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.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.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.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
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 h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
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=
|
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 waitGroup sync.WaitGroup
|
||||||
|
var TUI *tea.Program
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
options, err := ParseOptions()
|
_, err := ParseOptions()
|
||||||
|
// options, err := ParseOptions()
|
||||||
errHandler(err, "Error parsing options")
|
errHandler(err, "Error parsing options")
|
||||||
|
|
||||||
fmt.Println("hello world", options)
|
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
|
||||||
|
)
|
||||||
|
|
||||||
// // start workers in parallel
|
go watchFilesystem()
|
||||||
// for i := 0; i < options.Threads; i++ {
|
|
||||||
// waitGroup.Add(1)
|
if _, err := TUI.Run(); err != nil {
|
||||||
// go func() {
|
errHandler(err, "Tui error:")
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func errHandler(err error, message string) {
|
func errHandler(err error, message string) {
|
||||||
|
179
tui.go
179
tui.go
@ -2,37 +2,84 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
// "os"
|
|
||||||
|
|
||||||
|
// "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"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
// "github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var skipNextViewportUpdate bool
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
choices []string // items on the to-do list
|
viewport viewport.Model
|
||||||
cursor int // which to-do list item our cursor is pointing at
|
ready bool
|
||||||
selected map[int]struct{} // which to-do items are selected
|
|
||||||
|
// 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 {
|
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{
|
return model{
|
||||||
// Our to-do list is a grocery list
|
// 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
|
// A map which indicates which choices are selected. We're using
|
||||||
// the map like a mathematical set. The keys refer to the indexes
|
// the map like a mathematical set. The keys refer to the indexes
|
||||||
// of the `choices` slice, above.
|
// of the `choices` slice, above.
|
||||||
selected: make(map[int]struct{}),
|
// selected: make(map[int]struct{}),
|
||||||
|
list: l,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dataMsg struct {
|
||||||
|
// reflex string
|
||||||
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
func (m *model) Init() tea.Cmd {
|
||||||
// Just return `nil`, which means "no I/O right now, please."
|
// Just return `nil`, which means "no I/O right now, please."
|
||||||
|
// return m.reflex("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
// var (
|
||||||
|
// // cmd tea.Cmd
|
||||||
|
// cmds []tea.Cmd
|
||||||
|
// )
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
|
||||||
// Is it a key press?
|
// Is it a key press?
|
||||||
@ -41,65 +88,97 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
// Cool, what was the actual key pressed?
|
// Cool, what was the actual key pressed?
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
|
|
||||||
// These keys should exit the program.
|
|
||||||
case "ctrl+c", "q":
|
case "ctrl+c", "q":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
|
||||||
// The "up" and "k" keys move the cursor up
|
// // The "up" and "k" keys move the cursor up
|
||||||
case "up", "k":
|
// case "up", "k":
|
||||||
if m.cursor > 0 {
|
// if m.cursor > 0 {
|
||||||
m.cursor--
|
// m.cursor--
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// The "down" and "j" keys move the cursor down
|
// // The "down" and "j" keys move the cursor down
|
||||||
case "down", "j":
|
// case "down", "j":
|
||||||
if m.cursor < len(m.choices)-1 {
|
// if m.cursor < len(m.choices)-1 {
|
||||||
m.cursor++
|
// m.cursor++
|
||||||
}
|
// }
|
||||||
|
|
||||||
// The "enter" key and the spacebar (a literal space) toggle
|
// The "enter" key and the spacebar (a literal space) toggle
|
||||||
// the selected state for the item that the cursor is pointing at.
|
// the selected state for the item that the cursor is pointing at.
|
||||||
case "enter", " ":
|
case "enter", " ":
|
||||||
_, ok := m.selected[m.cursor]
|
i, ok := m.list.SelectedItem().(item)
|
||||||
if ok {
|
if ok {
|
||||||
delete(m.selected, m.cursor)
|
// 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 {
|
} else {
|
||||||
m.selected[m.cursor] = struct{}{}
|
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.
|
// Return the updated model to the Bubble Tea runtime for processing.
|
||||||
// Note that we're not returning a command.
|
// Note that we're not returning a command.
|
||||||
return m, nil
|
var cmd tea.Cmd
|
||||||
|
m.list, cmd = m.list.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
// return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) View() string {
|
func (m *model) View() string {
|
||||||
// The header
|
|
||||||
s := "What should we buy at the market?\n\n"
|
|
||||||
|
|
||||||
// Iterate over our choices
|
if !m.ready {
|
||||||
for i, choice := range m.choices {
|
return "\n Initializing..."
|
||||||
|
|
||||||
// Is the cursor pointing at this choice?
|
|
||||||
cursor := " " // no cursor
|
|
||||||
if m.cursor == i {
|
|
||||||
cursor = ">" // cursor!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this choice selected?
|
// return fmt.Sprintf("%s\n%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView(), m.choicesView())
|
||||||
checked := " " // not selected
|
// TODO if window too small, hide list.View
|
||||||
if _, ok := m.selected[i]; ok {
|
return fmt.Sprintf("%s\n%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView(), m.list.View())
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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