diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dae953f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${file}", + "output": "debug", + }, + { + "name": "Connect to server", + "type": "go", + "request": "launch", + "mode": "remote", + "remotePath": "${fileDirname}", + "port": 2345, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": {}, + "args": [] + } + { + "name": "Attach to Process", + "type": "go", + "request": "attach", + "mode": "local", + "processId": 20297 + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index e94f940..d63e17e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,24 +1,3 @@ { - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#37e119", - "activityBar.activeBorder": "#536aec", - "activityBar.background": "#37e119", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#536aec", - "activityBarBadge.foreground": "#e7e7e7", - "editorGroup.border": "#37e119", - "panel.border": "#37e119", - "sideBar.border": "#37e119", - "statusBar.background": "#2cb314", - "statusBar.border": "#2cb314", - "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#37e119", - "titleBar.activeBackground": "#2cb314", - "titleBar.activeForeground": "#e7e7e7", - "titleBar.border": "#2cb314", - "titleBar.inactiveBackground": "#2cb31499", - "titleBar.inactiveForeground": "#e7e7e799" - }, "peacock.color": "#2cb314" } \ No newline at end of file diff --git a/README.md b/README.md index ff73d0d..03ede5b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ go build -o out/ main.go Create a alias to the run script ``` -alias j='source /run' +alias j='source /alfred' ``` @@ -71,7 +71,7 @@ To add your commands edit the settings.json You can also run the add command ``` -run add +alfred add ``` Make sure that run is available in the PATH. diff --git a/run b/alfred similarity index 100% rename from run rename to alfred diff --git a/lib/cli/add/add.go b/lib/cli/add/add.go index 9a8f9ec..06dab6a 100644 --- a/lib/cli/add/add.go +++ b/lib/cli/add/add.go @@ -57,7 +57,7 @@ func Execute() { if commandAccepted { newCommand := types.Command{ - Context: contextInput, + Context: contextInput + ":", Program: commandInput, } configData.Commands = append(configData.Commands, newCommand) diff --git a/lib/cli/help/help.go b/lib/cli/help/help.go new file mode 100644 index 0000000..b4c2c97 --- /dev/null +++ b/lib/cli/help/help.go @@ -0,0 +1,12 @@ +package help + +import ( + "fmt" +) + +func Execute() { + fmt.Println("Shell Butler - Help") + fmt.Println(` + add - Add a new command + help - Shows the help screen`) +} diff --git a/lib/runtime/runtime.go b/lib/runtime/runtime.go index e540088..34b31f4 100644 --- a/lib/runtime/runtime.go +++ b/lib/runtime/runtime.go @@ -1,8 +1,14 @@ package runtime import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "path/filepath" "sync" + "github.com/vinodsr/shell-butler/lib/config" types "github.com/vinodsr/shell-butler/lib/types" ) @@ -46,9 +52,37 @@ func (rt *Runtime) GetContextDS() []string { } // Init initializes -func (rt *Runtime) Init(configData types.ConfigData, commandMap map[string]string, contextDataSource []string) { - rt.configData = configData - rt.commandMap = commandMap +func (rt *Runtime) Init() { + + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + log.Fatal(err) + } + + dir, _ = os.UserHomeDir() + configFile := dir + "/.butler/settings.json" + dat, err := ioutil.ReadFile(configFile) + if err != nil { + initStruct := types.ConfigData{ + Commands: []types.Command{{ + Context: "Help", + Program: "echo Edit " + configFile + " to add more commands", + }}, + } + //fmt.Printf("%+v\n", initStruct) + //os.Exit(0) + config.WriteConfig(initStruct) + rt.configData = initStruct + } + + json.Unmarshal([]byte(string(dat)), &rt.configData) + rt.initialized = true - rt.contextDataSource = contextDataSource + rt.commandMap = make(map[string]string) + rt.contextDataSource = nil + for _, command := range rt.configData.Commands { + // splits := strings.Split(command.Context, ":") + rt.contextDataSource = append(rt.contextDataSource, command.Context) + rt.commandMap[command.Context] = command.Program + } } diff --git a/lib/ui/confirmbox.go b/lib/ui/confirmbox.go new file mode 100644 index 0000000..32ca581 --- /dev/null +++ b/lib/ui/confirmbox.go @@ -0,0 +1,38 @@ +package ui + +import ( + ui "github.com/gizak/termui/v3" + "github.com/gizak/termui/v3/widgets" +) + +func ConfirmBox(msg string, uiEvents <-chan ui.Event) bool { + + alertBox := widgets.NewParagraph() + alertBox.Title = "Alert" + alertBox.Text = msg + " [y/n] " + termWidth, termHeight := ui.TerminalDimensions() + alertBox.SetRect((termWidth/2)-40, (termHeight/2)-2, (termWidth/2)+40, (termHeight/2)+2) + alertBox.TextStyle.Fg = ui.ColorRed + alertBox.BorderStyle.Fg = ui.ColorRed + ret := true + //fmt.Println(msg) + ui.Render(alertBox) + loop := true + for loop { + e := <-uiEvents + + switch e.ID { + case "n", "N": + loop = false + ret = false + break + case "y", "Y": + loop = false + ret = true + break + } + ui.Render(alertBox) + + } + return ret +} diff --git a/lib/ui/layout.go b/lib/ui/layout.go index 89cdad8..1ada34b 100644 --- a/lib/ui/layout.go +++ b/lib/ui/layout.go @@ -4,10 +4,14 @@ import ( "fmt" "log" "os" + "strconv" "strings" ui "github.com/gizak/termui/v3" + addcli "github.com/vinodsr/shell-butler/lib/cli/add" + "github.com/vinodsr/shell-butler/lib/config" runtime "github.com/vinodsr/shell-butler/lib/runtime" + lib "github.com/vinodsr/shell-butler/lib/types" "github.com/gizak/termui/v3/widgets" ) @@ -17,8 +21,6 @@ func Render() { var selectedContext []string rt := runtime.GetRunTime() - commandMap := rt.GetCommandMap() - contextDataSource := rt.GetContextDS() commandStr := "" var filteredDataSource []string var commandLevel int = 1 @@ -33,7 +35,7 @@ func Render() { // Load the widget components contextTextBox := widgets.NewParagraph() - contextTextBox.Title = "Command" + contextTextBox.Title = "Search" contextTextBox.Text = formatCommandString(selectedContext, commandStr) contextTextBox.SetRect(0, 0, termWidth, 3) contextTextBox.TextStyle.Fg = ui.ColorGreen @@ -42,7 +44,7 @@ func Render() { alertBox := widgets.NewParagraph() alertBox.Title = "Alert" alertBox.Text = "" - alertBox.SetRect(5, 5, termWidth-5, 8) + alertBox.SetRect((termWidth/2)-20, (termHeight/2)-2, (termWidth/2)+20, (termHeight/2)+2) alertBox.TextStyle.Fg = ui.ColorRed alertBox.BorderStyle.Fg = ui.ColorRed @@ -51,44 +53,78 @@ func Render() { debugBox := widgets.NewParagraph() debugBox.Title = "Debug" debugBox.Text = "" - debugBox.SetRect(0, 50, 50, 53) + debugBox.SetRect(0, termHeight-10, termWidth, termHeight) contextListBox := widgets.NewList() - filteredDataSource = displayContextatLevel(updateContextList(contextDataSource, ""), commandLevel) + filteredDataSource = displayContextatLevel(updateContextList(rt.GetContextDS(), ""), commandLevel) contextListBox.Rows = filteredDataSource + contextListBox.Title = "Commands" contextListBox.TextStyle = ui.NewStyle(ui.ColorWhite) contextListBox.SelectedRowStyle = ui.NewStyle(ui.ColorBlack, ui.ColorGreen, ui.ModifierBold) contextListBox.WrapText = false - contextListBox.SetRect(0, 3, termWidth, termHeight) + contextListBox.SetRect(0, 6, termWidth, termHeight) contextListBox.BorderStyle.Fg = ui.ColorGreen - ui.Render(contextTextBox, contextListBox) + addButton := widgets.NewParagraph() + addButton.SetRect(0, 3, 20, 6) + addButton.Text = "Ins - Add Command" + addButton.BorderStyle.Bg = ui.ColorBlue + + deleteButton := widgets.NewParagraph() + deleteButton.SetRect(24, 3, 40, 6) + deleteButton.Text = "Del - Delete Command" + deleteButton.BorderStyle.Bg = ui.ColorRed + + exitButton := widgets.NewParagraph() + exitButton.SetRect(44, 3, 60, 6) + exitButton.Text = "Esc - Quit" + exitButton.BorderStyle.Bg = ui.ColorCyan + + staicDisplayItems := []ui.Drawable{contextListBox, contextTextBox, addButton, deleteButton, exitButton} + + ui.Render(staicDisplayItems...) uiEvents := ui.PollEvents() for { e := <-uiEvents switch e.ID { - case "q", "": + case "q", "", "": ui.Close() os.Exit(1) return case "j", "": - contextListBox.ScrollDown() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollDown() + } case "k", "": - contextListBox.ScrollUp() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollUp() + } case "": - contextListBox.ScrollHalfPageDown() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollHalfPageDown() + } case "": - contextListBox.ScrollHalfPageUp() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollHalfPageUp() + } case "": - contextListBox.ScrollPageDown() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollPageDown() + } case "": - contextListBox.ScrollPageUp() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollPageUp() + } case "": - contextListBox.ScrollTop() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollTop() + } case "G", "": - contextListBox.ScrollBottom() + if len(contextListBox.Rows) > 0 { + contextListBox.ScrollBottom() + } case "": if len(commandStr) > 0 { commandStr = commandStr[0 : len(commandStr)-1] @@ -106,15 +142,51 @@ func Render() { case "": commandStr += " " + case "": + ui.Close() + addcli.Execute() + rt.Init() + ui.Init() + case "": + toDeleteContext := append(selectedContext, filteredDataSource[contextListBox.SelectedRow]) + _joinedContext := strings.Join(toDeleteContext, ":") + + shouldDelete := ConfirmBox(fmt.Sprintf("Are you sure to delete %s ?", _joinedContext), uiEvents) + if shouldDelete { + if contextListBox.SelectedRow >= 0 && len(filteredDataSource) > 0 { + _joinedContext += ":" + debug("JOined context = "+_joinedContext, debugBox) + var newCommandList []lib.Command + deleted := false + for _, configEntry := range rt.GetConfig().Commands { + debug(configEntry.Context, debugBox) + if strings.HasPrefix(configEntry.Context, _joinedContext) { + debug("Found Context", debugBox) + deleted = true + } else { + newCommandList = append(newCommandList, configEntry) + } + } + debug("Should replace ? "+strconv.FormatBool(deleted), debugBox) + if deleted { + newConfig := rt.GetConfig() + newConfig.Commands = newCommandList + config.WriteConfig(newConfig) + rt.Init() + } + + } + break + } case "": // Now take the context available in the select box if contextListBox.SelectedRow >= 0 && len(filteredDataSource) > 0 { selectedContext = append(selectedContext, filteredDataSource[contextListBox.SelectedRow]) - _joinedContext := strings.Join(selectedContext, ":") + _joinedContext := strings.Join(selectedContext, ":") + ":" debug("JOined context = "+_joinedContext, debugBox) - if commandMap[_joinedContext] != "" { - fmt.Println(commandMap[_joinedContext]) + if rt.GetCommandMap()[_joinedContext] != "" { + fmt.Println(rt.GetCommandMap()[_joinedContext]) ui.Clear() ui.Close() os.Exit(0) @@ -146,7 +218,11 @@ func Render() { filterText := "(?i)^" + strings.Join(selectedContext, ":") + contextSep + "[^:]*" + commandStr + ".*\\:?" //contextTextBox.Text = filterText - filteredDataSource = displayContextatLevel(updateContextList(contextDataSource, filterText), commandLevel) + for _, cc := range rt.GetContextDS() { + debug(" context main DS = "+cc, debugBox) + + } + filteredDataSource = displayContextatLevel(updateContextList(rt.GetContextDS(), filterText), commandLevel) contextListBox.Rows = filteredDataSource if alertText == "" && len(filteredDataSource) == 0 { alertText = "No match found for : " + commandStr @@ -156,11 +232,17 @@ func Render() { } debug(filterText, debugBox) - displayItems := []ui.Drawable{contextListBox, contextTextBox} + if len(rt.GetContextDS()) == 0 { + alertText = " No commands found. Please add one" + } + displayItems := staicDisplayItems if alertText != "" { alertBox.Text = alertText displayItems = append(displayItems, alertBox) } + //displayItems = append(displayItems, debugBox) + + ui.Clear() ui.Render(displayItems...) alertText = "" } diff --git a/lib/ui/utils.go b/lib/ui/utils.go index 263d16b..de00d5e 100644 --- a/lib/ui/utils.go +++ b/lib/ui/utils.go @@ -6,6 +6,7 @@ import ( "github.com/enescakir/emoji" "github.com/gizak/termui/v3/widgets" + lib "github.com/vinodsr/shell-butler/lib/types" ) func filter(ss []string, test func(string) bool) (ret []string) { @@ -55,3 +56,7 @@ func formatCommandString(selectedContext []string, commandStr string) string { } return formattedCommandStr + emoji.RightArrow.String() + " " + commandStr } + +func RemoveIndex(s []lib.Command, index int) []lib.Command { + return append(s[:index], s[index+1:]...) +} diff --git a/main.go b/main.go index 3dfccd0..9604a8a 100644 --- a/main.go +++ b/main.go @@ -1,71 +1,41 @@ package main import ( - "encoding/json" - "io/ioutil" - "log" "os" - "path/filepath" addcli "github.com/vinodsr/shell-butler/lib/cli/add" - config "github.com/vinodsr/shell-butler/lib/config" - runtime "github.com/vinodsr/shell-butler/lib/runtime" - types "github.com/vinodsr/shell-butler/lib/types" + "github.com/vinodsr/shell-butler/lib/cli/help" + "github.com/vinodsr/shell-butler/lib/runtime" layout "github.com/vinodsr/shell-butler/lib/ui" ) //Main program func main() { - - var contextDataSource []string - var commandMap = make(map[string]string) - var configData types.ConfigData - - dir, err := filepath.Abs(filepath.Dir(os.Args[0])) - if err != nil { - log.Fatal(err) - } - - dir, _ = os.UserHomeDir() - configFile := dir + "/.butler/settings.json" - dat, err := ioutil.ReadFile(configFile) - if err != nil { - initStruct := types.ConfigData{ - Commands: []types.Command{{ - Context: "Help", - Program: "echo Edit " + configFile + " to add more commands", - }}, - } - //fmt.Printf("%+v\n", initStruct) - //os.Exit(0) - config.WriteConfig(initStruct) - configData = initStruct - } - - json.Unmarshal([]byte(string(dat)), &configData) - //fmt.Printf("Data %+v\n", commands) - //fmt.Print(commands.Commands[0].Context) - - // initialise the list datasource . - for _, command := range configData.Commands { - // splits := strings.Split(command.Context, ":") - contextDataSource = append(contextDataSource, command.Context) - commandMap[command.Context] = command.Program - } - // Initialize the runtime var rt *runtime.Runtime = runtime.GetRunTime() - rt.Init(configData, commandMap, contextDataSource) + rt.Init() if len(os.Args) > 1 { argCommand := os.Args[1] if argCommand == "add" { addcli.Execute() os.Exit(1) + } else if argCommand == "help" || argCommand == "--help" { + help.Execute() + os.Exit(1) } } + // if len(rt.GetConfig().Commands) == 0 { + // fmt.Println("No commands to load. Please add commands.") + // os.Exit(1) + // } + //fmt.Printf("Data %+v\n", commands) + //fmt.Print(commands.Commands[0].Context) + + // initialise the list datasource . + layout.Render() //os.Exit(1) diff --git a/package/debian/shell-butler/opt/shell-butler/bin/shell-butler b/package/debian/shell-butler/opt/shell-butler/bin/shell-butler index 7357515..3002139 100755 Binary files a/package/debian/shell-butler/opt/shell-butler/bin/shell-butler and b/package/debian/shell-butler/opt/shell-butler/bin/shell-butler differ diff --git a/shell-butler b/shell-butler new file mode 100755 index 0000000..88e898b Binary files /dev/null and b/shell-butler differ diff --git a/start.debug b/start.debug new file mode 100755 index 0000000..4ee8ca1 --- /dev/null +++ b/start.debug @@ -0,0 +1 @@ + dlv debug --headless --listen=:2345 --api-version=2 \ No newline at end of file