diff --git a/bookmarks.go b/bookmarks.go index 3e08eab..a3eb82d 100644 --- a/bookmarks.go +++ b/bookmarks.go @@ -107,10 +107,10 @@ func (b Bookmarks) Render(termwidth, termheight int) []string { } out := make([]string, 0, 5) - top := fmt.Sprintf("%s%s%s", tl, strings.Repeat(ceil, width-2), tr) + contentWidth := width - 2 + top := fmt.Sprintf("%s%s%s", tl, strings.Repeat(ceil, contentWidth), tr) out = append(out, top) marks := b.List() - contentWidth := width - 2 for i := 0; i < termheight - 2; i++ { if i + b.Position >= b.Length { out = append(out, fmt.Sprintf("%s%-*.*s%s", wall, contentWidth, contentWidth, "", wall )) @@ -119,7 +119,7 @@ func (b Bookmarks) Render(termwidth, termheight int) []string { } } - bottom := fmt.Sprintf("%s%s%s", bl, strings.Repeat(ceil, width-2), br) + bottom := fmt.Sprintf("%s%s%s", bl, strings.Repeat(ceil, contentWidth), br) out = append(out, bottom) return out } diff --git a/client.go b/client.go index c59195b..016cf3f 100644 --- a/client.go +++ b/client.go @@ -6,7 +6,7 @@ import ( "net" "os" "os/exec" - "os/user" + // "os/user" "regexp" "strconv" "strings" @@ -41,6 +41,8 @@ type client struct { //--------------------------------------------------\\ func (c *client) GetSize() { + c.SetMessage("Initializing...", false) + c.DrawMessage() for { redraw := false cmd := exec.Command("stty", "size") @@ -71,28 +73,33 @@ func (c *client) GetSize() { func (c *client) Draw() { var screen strings.Builder screen.Grow(c.Height * c.Width) - screen.WriteString(c.TopBar.Render(c.Width)) + screen.WriteString(c.TopBar.Render(c.Width, c.Options["theme"])) screen.WriteString("\n") pageContent := c.PageState.Render(c.Height, c.Width) + if c.Options["theme"] == "inverse" { + screen.WriteString("\033[7m") + } if c.BookMarks.IsOpen { bm := c.BookMarks.Render(c.Width, c.Height) - bmWidth := 40 + // TODO remove this hard coded value + bmWidth := len([]rune(bm[0])) for i := 0; i < c.Height - 3; i++ { if c.Width > bmWidth { - contentWidth := c.Width - bmWidth - 1 - if i < len(pageContent) - 1 { + contentWidth := c.Width - bmWidth + if i < len(pageContent) { screen.WriteString(fmt.Sprintf("%-*.*s", contentWidth, contentWidth, pageContent[i])) } else { screen.WriteString(fmt.Sprintf("%-*.*s", contentWidth, contentWidth, " ")) } } + screen.WriteString(bm[i]) screen.WriteString("\n") } } else { for i := 0; i < c.Height - 3; i++ { if i < len(pageContent) - 1 { - screen.WriteString(pageContent[i]) + screen.WriteString(fmt.Sprintf("%-*.*s", c.Width, c.Width, pageContent[i])) screen.WriteString("\n") } else { screen.WriteString(fmt.Sprintf("%*s", c.Width, " ")) @@ -100,8 +107,9 @@ func (c *client) Draw() { } } } + screen.WriteString("\033[0m") screen.WriteString("\n") // for the input line - screen.WriteString(c.FootBar.Render(c.Width)) + screen.WriteString(c.FootBar.Render(c.Width, c.PageState.Position, c.Options["theme"])) cui.Clear("screen") cui.MoveCursorTo(0,0) fmt.Print(screen.String()) @@ -183,6 +191,9 @@ func (c *client) TakeControlInput() { // Process a command c.ClearMessage() c.ClearMessageLine() + if c.Options["theme"] == "normal" { + fmt.Printf("\033[7m%*.*s\r", c.Width, c.Width, "") + } entry, err := cui.GetLine() c.ClearMessageLine() if err != nil { @@ -190,6 +201,7 @@ func (c *client) TakeControlInput() { c.DrawMessage() break } else if strings.TrimSpace(entry) == "" { + c.DrawMessage() break } @@ -221,7 +233,7 @@ func (c *client) routeCommandInput(com *cmdparse.Command) error { case cmdparse.DO: c.doCommand(com.Action, com.Value) case cmdparse.DOLINK: - // err = doLinkCommand(com.Action, com.Target) + c.doLinkCommand(com.Action, com.Target) case cmdparse.DOAS: c.doCommandAs(com.Action, com.Value) case cmdparse.DOLINKAS: @@ -337,7 +349,8 @@ func (c *client) doCommandAs(action string, values []string) { c.SetMessage("Value set, but error saving config to file", true) c.DrawMessage() } else { - c.SetMessage(fmt.Sprintf("%s is now set to %q", values[0], c.Options[values[0]]), true) + c.Draw() + c.SetMessage(fmt.Sprintf("%s is now set to %q", values[0], c.Options[values[0]]), false) c.DrawMessage() } return @@ -373,6 +386,47 @@ func (c *client) saveFile(data []byte, name string) (string, error) { return savePath, nil } +func (c *client) doLinkCommand(action, target string) { + num, err := strconv.Atoi(target) + if err != nil { + c.SetMessage(fmt.Sprintf("Expected number, got %q", target), true) + c.DrawMessage() + } + + switch action { + case "DELETE", "D": + msg, err := c.BookMarks.Delete(num) + if err != nil { + c.SetMessage(err.Error(), true) + c.DrawMessage() + return + } else { + c.SetMessage(msg, false) + c.DrawMessage() + } + + err = saveConfig() + if err != nil { + c.SetMessage("Error saving bookmark deletion to file", true) + c.DrawMessage() + } + if c.BookMarks.IsOpen { + c.Draw() + } + case "BOOKMARKS", "B": + if num > len(c.BookMarks.Links)-1 { + c.SetMessage(fmt.Sprintf("There is no bookmark with ID %d", num), true) + c.DrawMessage() + return + } + c.Visit(c.BookMarks.Links[num]) + default: + c.SetMessage(fmt.Sprintf("Action %q does not exist for target %q", action, target), true) + c.DrawMessage() + } + +} + func (c *client) search() { c.ClearMessage() c.ClearMessageLine() @@ -408,6 +462,7 @@ func (c *client) search() { } func (c *client) Scroll(amount int) { + var percentRead int page := c.PageState.History[c.PageState.Position] bottom := len(page.WrappedContent) - c.Height + 3 // 3 for the three bars: top, msg, bottom if amount < 0 && page.ScrollPosition == 0 { @@ -416,6 +471,7 @@ func (c *client) Scroll(amount int) { fmt.Print("\a") return } else if (amount > 0 && page.ScrollPosition == bottom) || bottom < 0 { + c.FootBar.SetPercentRead(100) c.SetMessage("You are already at the bottom", false) c.DrawMessage() fmt.Print("\a") @@ -430,6 +486,14 @@ func (c *client) Scroll(amount int) { } c.PageState.History[c.PageState.Position].ScrollPosition = newScrollPosition + + if len(page.WrappedContent) < c.Height - 3 { + percentRead = 100 + } else { + percentRead = int(float32(newScrollPosition + c.Height - 3) / float32(len(page.WrappedContent)) * 100.0) + } + c.FootBar.SetPercentRead(percentRead) + c.Draw() } @@ -446,21 +510,30 @@ func (c *client) displayConfigValue(setting string) { func (c *client) SetMessage(msg string, isError bool) { leadIn, leadOut := "", "" if isError { - leadIn = "\033[31m" + leadIn = "\033[91m" + leadOut = "\033[0m" + + if c.Options["theme"] == "normal" { + leadIn = "\033[101;7m" + } + } + + if c.Options["theme"] == "normal" { + leadIn = "\033[7m" leadOut = "\033[0m" } - c.Message = fmt.Sprintf("%s%s%s", leadIn, msg, leadOut) + c.Message = fmt.Sprintf("%s%-*.*s%s", leadIn, c.Width, c.Width, msg, leadOut) } func (c *client) DrawMessage() { c.ClearMessageLine() cui.MoveCursorTo(c.Height-1, 0) - fmt.Print(c.Message) + fmt.Printf("%s", c.Message) } func (c *client) ClearMessage() { - c.Message = "" + c.SetMessage("", false) } func (c *client) ClearMessageLine() { @@ -518,12 +591,6 @@ func (c *client) Visit(url string) { switch u.Scheme { case "gopher": - u, err := MakeUrl(url) - if err != nil { - c.SetMessage(err.Error(), true) - c.DrawMessage() - return - } c.SetMessage("Loading...", false) c.DrawMessage() content, links, err := gopher.Visit(u.Mime, u.Host, u.Port, u.Resource) @@ -535,13 +602,14 @@ func (c *client) Visit(url string) { pg := MakePage(u, content, links) pg.WrapContent(c.Width) c.PageState.Add(pg) + c.Scroll(0) // to update percent read c.ClearMessage() c.ClearMessageLine() c.SetHeaderUrl() c.Draw() case "gemini": // TODO send over to gemini request - c.SetMessage("Gemini is not currently supported", false) + c.SetMessage("Bombadillo has not mastered Gemini yet, check back soon", false) c.DrawMessage() case "telnet": c.SetMessage("Attempting to start telnet session", false) @@ -582,16 +650,16 @@ func (c *client) Visit(url string) { //--------------------------------------------------\\ func MakeClient(name string) *client { - var userinfo, _ = user.Current() - var options = map[string]string{ - "homeurl": "gopher://colorfield.space:70/1/bombadillo-info", - "savelocation": userinfo.HomeDir, - "searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs", - "openhttp": "false", - "httpbrowser": "lynx", - "configlocation": userinfo.HomeDir, - } - c := client{0, 0, options, "", MakePages(), MakeBookmarks(), MakeHeadbar(name), MakeFootbar()} + // var userinfo, _ = user.Current() + // var options = map[string]string{ + // "homeurl": "gopher://colorfield.space:70/1/bombadillo-info", + // "savelocation": userinfo.HomeDir, + // "searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs", + // "openhttp": "false", + // "httpbrowser": "lynx", + // "configlocation": userinfo.HomeDir, + // } + c := client{0, 0, defaultOptions, "", MakePages(), MakeBookmarks(), MakeHeadbar(name), MakeFootbar()} return &c } diff --git a/defaults.go b/defaults.go new file mode 100644 index 0000000..87fa557 --- /dev/null +++ b/defaults.go @@ -0,0 +1,49 @@ +package main + +import ( + "os/user" +) + +var userinfo, _ = user.Current() +var defaultOptions = map[string]string{ + // + // General configuration options + // + "homeurl": "gopher://colorfield.space:70/1/bombadillo-info", + "savelocation": userinfo.HomeDir, + "searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs", + "openhttp": "false", + "httpbrowser": "lynx", + "telnetcommand": "telnet", + "configlocation": userinfo.HomeDir, + "theme": "normal", // "normal", "inverted" +} + +// TODO decide whether or not to institute a color theme +// system. Preliminary testing implies it should be very +// doable. +var theme = map[string]string{ + "topbar_title_bg": "", + "topbar_link_fg": "", + "body_bg": "237", + "body_fg": "", + "bookmarks_bg": "", + "bookmarks_fg": "", + "command_bg": "", + "message_fg": "", + "error_fg": "", + "bottombar_bg": "", + "bottombar_fg": "", + // + // text style options + // + "topbar_title_style": "bold", + "topbar_link_style": "plain", + "body_style": "plain", + "bookmark_body_style": "plain", + "bookmark_border_style": "plain", + "message_style": "italic", + "error_style": "bold", + "command_style": "plain", + "bottom_bar_style": "plain", +} diff --git a/footbar.go b/footbar.go index dab20bf..c2d2e27 100644 --- a/footbar.go +++ b/footbar.go @@ -2,14 +2,16 @@ package main import ( "fmt" + "strconv" ) + //------------------------------------------------\\ // + + + T Y P E S + + + \\ //--------------------------------------------------\\ type Footbar struct { - PercentRead int + PercentRead string PageType string } @@ -19,20 +21,25 @@ type Footbar struct { //--------------------------------------------------\\ func (f *Footbar) SetPercentRead(p int) { - f.PercentRead = p + if p > 100 { + p = 100 + } else if p < 0 { + p = 0 + } + f.PercentRead = strconv.Itoa(p) + "%" } func (f *Footbar) SetPageType(t string) { f.PageType = t } -func (f *Footbar) Draw() { - // TODO this will actually draw the bar - // without having to redraw everything else -} - -func (f *Footbar) Render(termWidth int) string { - return fmt.Sprintf("\033[7m%-*.*s\033[0m", termWidth, termWidth, "") +func (f *Footbar) Render(termWidth, position int, theme string) string { + pre := fmt.Sprintf("HST: (%2.2d) - - - %4s Read ", position + 1, f.PercentRead) + out := "\033[0m%*.*s " + if theme == "inverse" { + out = "\033[7m%*.*s \033[0m" + } + return fmt.Sprintf(out, termWidth - 1, termWidth - 1, pre) } @@ -41,6 +48,6 @@ func (f *Footbar) Render(termWidth int) string { //--------------------------------------------------\\ func MakeFootbar() Footbar { - return Footbar{100, "N/A"} + return Footbar{"---", "N/A"} } diff --git a/headbar.go b/headbar.go index 8782857..e1f1434 100644 --- a/headbar.go +++ b/headbar.go @@ -28,9 +28,13 @@ func (h *Headbar) Draw() { // without having to redraw everything else } -func (h *Headbar) Render(width int) string { - maxMsgWidth := width - len([]rune(h.title)) - return fmt.Sprintf("\033[7m%s%-*.*s\033[0m", h.title, maxMsgWidth, maxMsgWidth, h.url) +func (h *Headbar) Render(width int, theme string) string { + maxMsgWidth := width - len([]rune(h.title)) - 2 + if theme == "inverse" { + return fmt.Sprintf("\033[7m%s▟\033[27m %-*.*s\033[0m", h.title, maxMsgWidth, maxMsgWidth, h.url) + } else { + return fmt.Sprintf("%s▟\033[7m %-*.*s\033[0m", h.title, maxMsgWidth, maxMsgWidth, h.url) + } } diff --git a/main.go b/main.go index 780189f..e754b9e 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,10 @@ package main import ( "io/ioutil" "os" - // "strconv" "strings" "tildegit.org/sloum/bombadillo/config" "tildegit.org/sloum/bombadillo/cui" - // "tildegit.org/sloum/bombadillo/gopher" ) var bombadillo *client @@ -32,96 +30,6 @@ var settings config.Config // } - -// func doLinkCommand(action, target string) error { - // num, err := strconv.Atoi(target) - // if err != nil { - // return fmt.Errorf("Expected number, got %q", target) - // } - - // switch action { - // case "DELETE", "D": - // err := settings.Bookmarks.Del(num) - // if err != nil { - // return err - // } - - // screen.Windows[1].Content = settings.Bookmarks.List() - // err = saveConfig() - // if err != nil { - // return err - // } - - // screen.ReflashScreen(false) - // return nil - // case "BOOKMARKS", "B": - // if num > len(settings.Bookmarks.Links)-1 { - // return fmt.Errorf("There is no bookmark with ID %d", num) - // } - // err := goToURL(settings.Bookmarks.Links[num]) - // return err - // } - - // return fmt.Errorf("This method has not been built") -// } - - -// func doCommand(action string, values []string) error { - // if length := len(values); length != 1 { - // return fmt.Errorf("Expected 1 argument, received %d", length) - // } - - // switch action { - // case "CHECK", "C": - // err := checkConfigValue(values[0]) - // if err != nil { - // return err - // } - // return nil - // } - // return fmt.Errorf("Unknown command structure") -// } - -// func doLinkCommandAs(action, target string, values []string) error { - // num, err := strconv.Atoi(target) - // if err != nil { - // return fmt.Errorf("Expected number, got %q", target) - // } - - // links := history.Collection[history.Position].Links - // if num >= len(links) { - // return fmt.Errorf("Invalid link id: %s", target) - // } - - // switch action { - // case "ADD", "A": - // newBookmark := append([]string{links[num-1]}, values...) - // err := settings.Bookmarks.Add(newBookmark) - // if err != nil { - // return err - // } - - // screen.Windows[1].Content = settings.Bookmarks.List() - - // err = saveConfig() - // if err != nil { - // return err - // } - - // screen.ReflashScreen(false) - // return nil - // case "WRITE", "W": - // return saveFile(links[num-1], strings.Join(values, " ")) - // } - - // return fmt.Errorf("This method has not been built") -// } - -// func updateMainContent() { - // screen.Windows[0].Content = history.Collection[history.Position].Content - // screen.Bars[0].SetMessage(history.Collection[history.Position].Address.Full) -// } - func saveConfig() error { bkmrks := bombadillo.BookMarks.IniDump() // TODO opts becomes a string builder rather than concat @@ -186,6 +94,9 @@ func main() { panic(err) } + // TODO find out why the loading message + // has disappeared on initial load... + // Start polling for terminal size changes go bombadillo.GetSize() @@ -193,12 +104,12 @@ func main() { // If a url was passed, move it down the line // Goroutine so keypresses can be made during // page load - bombadillo.Visit(os.Args[1]) + go bombadillo.Visit(os.Args[1]) } else { // Otherwise, load the homeurl // Goroutine so keypresses can be made during // page load - bombadillo.Visit(bombadillo.Options["homeurl"]) + go bombadillo.Visit(bombadillo.Options["homeurl"]) } // Loop indefinitely on user input diff --git a/page.go b/page.go index 3fe0a0e..20514b6 100644 --- a/page.go +++ b/page.go @@ -38,9 +38,11 @@ func (p *Page) ScrollPositionRange(termHeight int) (int, int) { return p.ScrollPosition, end } +// Performs a hard wrap to the requested +// width and updates the WrappedContent +// of the Page struct width a string slice +// of the wrapped data func (p *Page) WrapContent(width int) { - // TODO this is a temporary wrapping function - // in order to test. Rebuild it. counter := 0 var content strings.Builder content.Grow(len(p.RawContent)) @@ -48,6 +50,18 @@ func (p *Page) WrapContent(width int) { if ch == '\n' { content.WriteRune(ch) counter = 0 + } else if ch == '\t' { + if counter + 4 < width { + content.WriteString(" ") + counter += 4 + } else { + content.WriteRune('\n') + counter = 0 + } + } else if ch == '\r' { + // This handles non-linux line endings... + // to some degree... + continue } else { if counter < width { content.WriteRune(ch) diff --git a/pages.go b/pages.go index 9fde2d2..08a403b 100644 --- a/pages.go +++ b/pages.go @@ -50,7 +50,7 @@ func (p *Pages) Add(pg Page) { func (p *Pages) Render(termHeight, termWidth int) []string { if p.Length < 1 { - return []string{""} + return make([]string, 0) } pos := p.History[p.Position].ScrollPosition prev := len(p.History[p.Position].WrappedContent)