From 7f7ae26f4e1119531e3560a6e25ffadc74c962ba Mon Sep 17 00:00:00 2001 From: Casual Date: Sat, 11 May 2024 04:09:51 +0300 Subject: [PATCH] v1 --- README.md | 2 +- go.mod | 8 +- go.sum | 18 +++- main.go | 34 +++---- options.go | 6 +- tui.go | 223 ++++++++++++++++++++++++++++++--------------- tui_choices.go | 126 +++++++++++++++++++++++++ tui_viewport.go | 68 ++++++++++++++ watchFilesystem.go | 87 ++++++++++++++++++ 9 files changed, 472 insertions(+), 100 deletions(-) create mode 100644 tui_choices.go create mode 100644 tui_viewport.go create mode 100644 watchFilesystem.go diff --git a/README.md b/README.md index 0f83950..6bd8372 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Procurator -My Git project manager TUI \ No newline at end of file +My Git project manager TUI diff --git a/go.mod b/go.mod index 94dd5c3..d6ab405 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 0b12f86..1c4afe8 100644 --- a/go.sum +++ b/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= diff --git a/main.go b/main.go index 10c8a3c..2b05cd5 100644 --- a/main.go +++ b/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) { diff --git a/options.go b/options.go index 1431015..1f9621f 100644 --- a/options.go +++ b/options.go @@ -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() diff --git a/tui.go b/tui.go index e66d92b..83f9078 100644 --- a/tui.go +++ b/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 } diff --git a/tui_choices.go b/tui_choices.go new file mode 100644 index 0000000..4772f12 --- /dev/null +++ b/tui_choices.go @@ -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" +} diff --git a/tui_viewport.go b/tui_viewport.go new file mode 100644 index 0000000..76355be --- /dev/null +++ b/tui_viewport.go @@ -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 +} + +/////////// diff --git a/watchFilesystem.go b/watchFilesystem.go new file mode 100644 index 0000000..dc9d7f8 --- /dev/null +++ b/watchFilesystem.go @@ -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 +}