From bccca61ec223a6b1cac875e9a634b863575fa420 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Wed, 11 Sep 2019 22:53:36 -0700 Subject: [PATCH] Some level of screen draw now works --- bookmarks.go | 49 +++++++++++++++++--- client.go | 51 ++++++++++++++++++--- config/parser.go | 12 ++--- cui/cui.go | 4 +- footbar.go | 18 ++------ gopher/bookmark.go | 65 -------------------------- gopher/gopher.go | 42 +++++++++++++---- gopher/history.go | 112 --------------------------------------------- gopher/url.go | 94 ------------------------------------- gopher/view.go | 83 --------------------------------- headbar.go | 18 +++----- main.go | 8 ++-- page.go | 60 ++++++++++++++++++++++-- pages.go | 31 +++++++++---- url.go | 4 +- 15 files changed, 224 insertions(+), 427 deletions(-) delete mode 100644 gopher/bookmark.go delete mode 100644 gopher/history.go delete mode 100644 gopher/url.go delete mode 100644 gopher/view.go diff --git a/bookmarks.go b/bookmarks.go index a4c0c84..503e648 100644 --- a/bookmarks.go +++ b/bookmarks.go @@ -3,6 +3,8 @@ package main import ( "fmt" "strings" + + "tildegit.org/sloum/bombadillo/cui" ) //------------------------------------------------\\ @@ -81,16 +83,49 @@ func (b Bookmarks) List() []string { return out } -func (b Bookmarks) Render() ([]string, error) { - // TODO Use b.List() to get the necessary - // text and add on the correct border for - // rendering the focus. Use sprintf, left - // aligned: "| %-36.36s |" of the like. - return []string{}, nil +func (b Bookmarks) Render(termwidth, termheight int) []string { + width := 40 + termheight -= 3 + var wall, ceil, tr, tl, br, bl string + if termwidth < 40 { + width = termwidth + } + if b.IsFocused { + wall = cui.Shapes["awall"] + ceil = cui.Shapes["aceiling"] + tr = cui.Shapes["atr"] + br = cui.Shapes["abr"] + tl = cui.Shapes["atl"] + bl = cui.Shapes["abl"] + } else { + wall = cui.Shapes["wall"] + ceil = cui.Shapes["ceiling"] + tr = cui.Shapes["tr"] + br = cui.Shapes["br"] + tl = cui.Shapes["tl"] + bl = cui.Shapes["bl"] + } + + out := make([]string, 5) + top := fmt.Sprintf("%s%s%s", tl, strings.Repeat(ceil, width-2), tr) + out = append(out, top) + marks := b.List() + contentWidth := termwidth - 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 )) + } else { + out = append(out, fmt.Sprintf("%s%-*.*s%s", wall, contentWidth, contentWidth, marks[i + b.Position], wall )) + } + } + + bottom := fmt.Sprintf("%s%s%s", bl, strings.Repeat(ceil, width-2), br) + out = append(out, bottom) + return out } // TODO handle scrolling of the bookmarks list -// either here widh a scroll up/down or in the client +// either here with a scroll up/down or in the client // code for scroll diff --git a/client.go b/client.go index 4773435..f326fbc 100644 --- a/client.go +++ b/client.go @@ -15,7 +15,7 @@ import ( "tildegit.org/sloum/bombadillo/cmdparse" "tildegit.org/sloum/bombadillo/cui" // "tildegit.org/sloum/bombadillo/gemini" - // "tildegit.org/sloum/bombadillo/gopher" + "tildegit.org/sloum/bombadillo/gopher" "tildegit.org/sloum/bombadillo/http" "tildegit.org/sloum/bombadillo/telnet" ) @@ -68,10 +68,30 @@ func (c *client) GetSize() { } func (c *client) Draw() { - // TODO build this out. - // It should call all of the renders - // and add them to the a string buffer - // It should then print the buffer + var screen strings.Builder + screen.Grow(c.Height * c.Width) + screen.WriteString(c.TopBar.Render(c.Width, "This is a test")) + screen.WriteString("\n") + pageContent := c.PageState.Render(c.Height) + if c.BookMarks.IsOpen { + bm := c.BookMarks.Render(c.Width, c.Height) + bmWidth := len([]rune(bm[0])) + for i, ln := range pageContent { + screen.WriteString(ln[:len(ln) - bmWidth]) + screen.WriteString(bm[i]) + screen.WriteString("\n") + } + } else { + for _, ln := range pageContent { + screen.WriteString(ln) + screen.WriteString("\n") + } + } + screen.WriteString("\n") // for the input line + screen.WriteString(c.FootBar.Render(c.Width)) + cui.Clear("screen") + cui.MoveCursorTo(0,0) + fmt.Print(screen.String()) } func (c *client) TakeControlInput() { @@ -131,6 +151,7 @@ func (c *client) TakeControlInput() { // Process a command c.ClearMessage() c.ClearMessageLine() + cui.MoveCursorTo(c.Height-2, 0) entry, err := cui.GetLine() c.ClearMessageLine() if err != nil { @@ -460,9 +481,26 @@ func (c *client) Visit(url string) { switch u.Scheme { case "gopher": - // TODO send over to gopher request + u, err := MakeUrl(url) + if err != nil { + c.SetMessage(err.Error(), true) + c.DrawMessage() + return + } + content, links, err := gopher.Visit(u.Mime, u.Host, u.Port, u.Resource) + if err != nil { + c.SetMessage(err.Error(), true) + c.DrawMessage() + return + } + pg := MakePage(u, content, links) + pg.WrapContent(c.Width) + c.PageState.Add(pg) + c.Draw() case "gemini": // TODO send over to gemini request + c.SetMessage("Gemini is not currently supported", false) + c.DrawMessage() case "telnet": c.SetMessage("Attempting to start telnet session", false) c.DrawMessage() @@ -512,7 +550,6 @@ func MakeClient(name string) *client { "configlocation": userinfo.HomeDir, } c := client{0, 0, options, "", MakePages(), MakeBookmarks(), MakeHeadbar(name), MakeFootbar()} - c.GetSize() return &c } diff --git a/config/parser.go b/config/parser.go index 4661ac6..038c889 100644 --- a/config/parser.go +++ b/config/parser.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "strings" - "tildegit.org/sloum/bombadillo/gopher" ) //------------------------------------------------\\ @@ -21,7 +20,10 @@ type Parser struct { } type Config struct { - Bookmarks gopher.Bookmarks + // Bookmarks gopher.Bookmarks + Bookmarks struct { + Titles, Links []string + } Colors []KeyValue Settings []KeyValue } @@ -86,10 +88,8 @@ func (p *Parser) Parse() (Config, error) { } switch section { case "BOOKMARKS": - err := c.Bookmarks.Add([]string{keyval.Value, keyval.Key}) - if err != nil { - return c, err - } + c.Bookmarks.Titles = append(c.Bookmarks.Titles, keyval.Value) + c.Bookmarks.Links = append(c.Bookmarks.Links, keyval.Key) case "COLORS": c.Colors = append(c.Colors, keyval) case "SETTINGS": diff --git a/cui/cui.go b/cui/cui.go index b00e07e..5cb5d79 100644 --- a/cui/cui.go +++ b/cui/cui.go @@ -9,7 +9,7 @@ import ( "strings" ) -var shapes = map[string]string{ +var Shapes = map[string]string{ "wall": "╵", "ceiling": "╴", "tl": "┌", @@ -25,7 +25,7 @@ var shapes = map[string]string{ } func drawShape(shape string) { - if val, ok := shapes[shape]; ok { + if val, ok := Shapes[shape]; ok { fmt.Printf("%s", val) } else { fmt.Print("x") diff --git a/footbar.go b/footbar.go index be1ea60..dab20bf 100644 --- a/footbar.go +++ b/footbar.go @@ -9,9 +9,8 @@ import ( //--------------------------------------------------\\ type Footbar struct { - PercentRead string + PercentRead int PageType string - Content string } @@ -20,7 +19,7 @@ type Footbar struct { //--------------------------------------------------\\ func (f *Footbar) SetPercentRead(p int) { - f.PercentRead = fmt.Sprintf("%d%%", p) + f.PercentRead = p } func (f *Footbar) SetPageType(t string) { @@ -32,15 +31,8 @@ func (f *Footbar) Draw() { // without having to redraw everything else } -func (f *Footbar) Build(width string) string { - // TODO Build out header to specified width - f.Content = "" // This is a temp value to show intention - return "" -} - -func (f *Footbar) Render() string { - // TODO returns a full line - return "" +func (f *Footbar) Render(termWidth int) string { + return fmt.Sprintf("\033[7m%-*.*s\033[0m", termWidth, termWidth, "") } @@ -49,6 +41,6 @@ func (f *Footbar) Render() string { //--------------------------------------------------\\ func MakeFootbar() Footbar { - return Footbar{"", "N/A", ""} + return Footbar{100, "N/A"} } diff --git a/gopher/bookmark.go b/gopher/bookmark.go deleted file mode 100644 index 24ac80f..0000000 --- a/gopher/bookmark.go +++ /dev/null @@ -1,65 +0,0 @@ -package gopher - -import ( - "fmt" - "strings" -) - -//------------------------------------------------\\ -// + + + T Y P E S + + + \\ -//--------------------------------------------------\\ - -//Bookmarks is a holder for titles and links that -//can be retrieved by index -type Bookmarks struct { - Titles []string - Links []string -} - -//------------------------------------------------\\ -// + + + R E C E I V E R S + + + \\ -//--------------------------------------------------\\ - -// Add adds a new title and link combination to the bookmarks -// struct. It takes as input a string slice in which the first -// element represents the link and all following items represent -// the title of the bookmark (they will be joined with spaces). -func (b *Bookmarks) Add(v []string) error { - if len(v) < 2 { - return fmt.Errorf("Received %d arguments, expected 2 or more", len(v)) - } - b.Titles = append(b.Titles, strings.Join(v[1:], " ")) - b.Links = append(b.Links, v[0]) - return nil -} - -func (b *Bookmarks) Del(i int) error { - if i < len(b.Titles) && i < len(b.Links) { - b.Titles = append(b.Titles[:i], b.Titles[i+1:]...) - b.Links = append(b.Links[:i], b.Links[i+1:]...) - return nil - } - return fmt.Errorf("Bookmark %d does not exist", i) -} - -func (b Bookmarks) List() []string { - var out []string - for i, t := range b.Titles { - out = append(out, fmt.Sprintf("[%d] %s", i, t)) - } - return out -} - -func (b Bookmarks) IniDump() string { - if len(b.Titles) < 0 { - return "" - } - out := "[BOOKMARKS]\n" - for i := 0; i < len(b.Titles); i++ { - out += b.Titles[i] - out += "=" - out += b.Links[i] - out += "\n" - } - return out -} diff --git a/gopher/gopher.go b/gopher/gopher.go index d32dc70..b990682 100644 --- a/gopher/gopher.go +++ b/gopher/gopher.go @@ -81,15 +81,18 @@ func Retrieve(host, port, resource string) ([]byte, error) { // the correct information to the client func Visit(gophertype, host, port, resource string) (string, []string, error) { resp, err := Retrieve(host, port, resource) + if err != nil { + return "", []string{}, err + } + text := string(resp) links := []string{} - if err != nil { - return "", []string{}, err - } else if IsDownloadOnly(gophertype) { + if IsDownloadOnly(gophertype) { return text, []string{}, nil } + if gophertype == "1" { text, links = parseMap(text) } @@ -113,8 +116,6 @@ func isWebLink(resource string) (string, bool) { return "", false } -// TODO Make sure when parsing maps that links have the correct -// protocol rather than 'gopher', where applicable (telnet, gemini, etc). func parseMap(text string) (string, []string) { splitContent := strings.Split(text, "\n") links := make([]string, 0, 10) @@ -128,23 +129,23 @@ func parseMap(text string) (string, []string) { line := strings.Split(e, "\t") var title string - // TODO REFACTOR LINE == HERE - // - - - - - - - - - - - - - - + if len(line[0]) > 1 { title = line[0][1:] } else { title = "" } + if len(line) > 1 && len(line[0]) > 0 && string(line[0][0]) == "i" { splitContent[i] = " " + string(title) } else if len(line) >= 4 { - fulllink := fmt.Sprintf("%s://%s:%s/%s%s", "protocol" ,line[2], line[3], string(line[0][0]), line[1]) - links = append(links, fulllink) + link := buildLink(line[2], line[3], string(line[0][0]), line[1]) + links = append(links, link) linktext := fmt.Sprintf("(%s) %2d %s", getType(string(line[0][0])), len(links), title) splitContent[i] = linktext } } - return "", links + return strings.Join(splitContent, "\n"), links } // Returns false for all text formats (including html @@ -160,3 +161,24 @@ func IsDownloadOnly(gophertype string) bool { return true } } + +func buildLink(host, port, gtype, resource string) string { + switch gtype { + case "8", "T": + return fmt.Sprintf("telnet://%s:%s", host, port) + case "G": + return fmt.Sprintf("gemini://%s:%s%s", host, port, resource) + case "h": + u, tf := isWebLink(resource) + if tf { + if len(u) > 4 && string(u[:5]) == "http" { + return u + } else { + return fmt.Sprintf("http://%s", u) + } + } + return fmt.Sprintf("gopher://%s:%s/h%s", host, port, resource) + default: + return fmt.Sprintf("gopher://%s:%s/%s%s", host, port, gtype, resource) + } +} diff --git a/gopher/history.go b/gopher/history.go deleted file mode 100644 index 5fdc439..0000000 --- a/gopher/history.go +++ /dev/null @@ -1,112 +0,0 @@ -package gopher - -import ( - "errors" - "fmt" -) - -//------------------------------------------------\\ -// + + + T Y P E S + + + \\ -//--------------------------------------------------\\ - -// The history struct represents the history of the browsing -// session. It contains the current history position, the -// length of the active history space (this can be different -// from the available capacity in the Collection), and a -// collection array containing View structs representing -// each page in the current history. In general usage this -// struct should be initialized via the MakeHistory function. -type History struct { - Position int - Length int - Collection [20]View -} - -//------------------------------------------------\\ -// + + + R E C E I V E R S + + + \\ -//--------------------------------------------------\\ - -// The "Add" receiver takes a view and adds it to -// the history struct that called it. "Add" returns -// nothing. "Add" will shift history down if the max -// history length would be exceeded, and will reset -// history length if something is added in the middle. -func (h *History) Add(v View) { - v.ParseMap() - if h.Position == h.Length-1 && h.Length < len(h.Collection) { - h.Collection[h.Length] = v - h.Length++ - h.Position++ - } else if h.Position == h.Length-1 && h.Length == 20 { - for x := 1; x < len(h.Collection); x++ { - h.Collection[x-1] = h.Collection[x] - } - h.Collection[len(h.Collection)-1] = v - } else { - h.Position += 1 - h.Length = h.Position + 1 - h.Collection[h.Position] = v - } -} - -// The "Get" receiver is called by a history struct -// and returns a View from the current position, will -// return an error if history is empty and there is -// nothing to get. -func (h History) Get() (*View, error) { - if h.Position < 0 { - return nil, errors.New("History is empty, cannot get item from empty history.") - } - - return &h.Collection[h.Position], nil -} - -// The "GoBack" receiver is called by a history struct. -// When called it decrements the current position and -// displays the content for the View in that position. -// If history is at position 0, no action is taken. -func (h *History) GoBack() bool { - if h.Position > 0 { - h.Position-- - return true - } - - fmt.Print("\a") - return false -} - -// The "GoForward" receiver is called by a history struct. -// When called it increments the current position and -// displays the content for the View in that position. -// If history is at position len - 1, no action is taken. -func (h *History) GoForward() bool { - if h.Position+1 < h.Length { - h.Position++ - return true - } - - fmt.Print("\a") - return false -} - -// The "DisplayCurrentView" receiver is called by a history -// struct. It calls the Display receiver for th view struct -// at the current history position. "DisplayCurrentView" does -// not return anything, and does nothing if position is less -// that 0. -func (h *History) DisplayCurrentView() { - h.Collection[h.Position].Display() -} - -//------------------------------------------------\\ -// + + + F U N C T I O N S + + + \\ -//--------------------------------------------------\\ - -// Constructor function for History struct. -// This is used to initialize history position -// as -1, which is needed. Returns a copy of -// initialized History struct (does NOT return -// a pointer to the struct). -func MakeHistory() History { - return History{-1, 0, [20]View{}} -} diff --git a/gopher/url.go b/gopher/url.go deleted file mode 100644 index c94f020..0000000 --- a/gopher/url.go +++ /dev/null @@ -1,94 +0,0 @@ -package gopher - -import ( - "errors" - "regexp" - "strings" -) - -//------------------------------------------------\\ -// + + + T Y P E S + + + \\ -//--------------------------------------------------\\ - -// The url struct represents a URL for the rest of the system. -// It includes component parts as well as a full URL string. -type Url struct { - Scheme string - Host string - Port string - Gophertype string - Resource string - Full string - IsBinary bool -} - -//------------------------------------------------\\ -// + + + F U N C T I O N S + + + \\ -//--------------------------------------------------\\ - -// MakeUrl is a Url constructor that takes in a string -// representation of a url and returns a Url struct and -// an error (or nil). -func MakeUrl(u string) (Url, error) { - var out Url - re := regexp.MustCompile(`^((?Pgopher|http|https|ftp|telnet):\/\/)?(?P[\w\-\.\d]+)(?::(?P\d+)?)?(?:/(?P[01345679gIhisp])?)?(?P.*)?$`) - match := re.FindStringSubmatch(u) - - if valid := re.MatchString(u); !valid { - return out, errors.New("Invalid URL or command character") - } - - for i, name := range re.SubexpNames() { - switch name { - case "scheme": - out.Scheme = match[i] - case "host": - out.Host = match[i] - case "port": - out.Port = match[i] - case "type": - out.Gophertype = match[i] - case "resource": - out.Resource = match[i] - } - } - - if out.Scheme == "" { - out.Scheme = "gopher" - } - - if out.Host == "" { - return out, errors.New("no host") - } - - if out.Scheme == "gopher" && out.Port == "" { - out.Port = "70" - } else if out.Scheme == "http" && out.Port == "" { - out.Port = "80" - } else if out.Scheme == "https" && out.Port == "" { - out.Port = "443" - } - - if out.Gophertype == "" && (out.Resource == "" || out.Resource == "/") { - out.Gophertype = "1" - } - - if out.Scheme == "gopher" && out.Gophertype == "" { - out.Gophertype = "0" - } - - if out.Gophertype == "7" && strings.Contains(out.Resource, "\t") { - out.Gophertype = "1" - } - - switch out.Gophertype { - case "1", "0", "h", "7": - out.IsBinary = false - default: - out.IsBinary = true - } - - out.Full = out.Scheme + "://" + out.Host + ":" + out.Port + "/" + out.Gophertype + out.Resource - - return out, nil -} diff --git a/gopher/view.go b/gopher/view.go deleted file mode 100644 index 813f4ca..0000000 --- a/gopher/view.go +++ /dev/null @@ -1,83 +0,0 @@ -package gopher - -import ( - "fmt" - "strings" -) - -//------------------------------------------------\\ -// + + + T Y P E S + + + \\ -//--------------------------------------------------\\ - -// View is a struct representing a gopher page. It contains -// the page content as a string slice, a list of link URLs -// as string slices, and the Url struct representing the page. -type View struct { - Content []string - Links []string - Address Url -} - -//------------------------------------------------\\ -// + + + R E C E I V E R S + + + \\ -//--------------------------------------------------\\ - -// ParseMap is called by a view struct to parse a gophermap. -// It checks if the view is for a gophermap. If not,it does -// nothing. If so, it parses the gophermap into comment lines -// and link lines. For link lines it adds a link to the links -// slice and changes the content value to just the printable -// string plus a gophertype indicator and a link number that -// relates to the link position in the links slice. This -// receiver does not return anything. -func (v *View) ParseMap() { - if v.Address.Gophertype == "1" || v.Address.Gophertype == "7" { - for i, e := range v.Content { - e = strings.Trim(e, "\r\n") - if e == "." { - v.Content[i] = " " - continue - } - - line := strings.Split(e, "\t") - var title string - if len(line[0]) > 1 { - title = line[0][1:] - } else { - title = "" - } - if len(line) > 1 && len(line[0]) > 0 && string(line[0][0]) == "i" { - v.Content[i] = " " + string(title) - } else if len(line) >= 4 { - fulllink := fmt.Sprintf("%s:%s/%s%s", line[2], line[3], string(line[0][0]), line[1]) - v.Links = append(v.Links, fulllink) - linktext := fmt.Sprintf("(%s) %2d %s", getType(string(line[0][0])), len(v.Links), title) - v.Content[i] = linktext - } - } - } -} - -// Display is called on a view struct to print the contents of the view. -// This receiver does not return anything. -func (v View) Display() { - fmt.Println() - for _, el := range v.Content { - fmt.Println(el) - } -} - -//------------------------------------------------\\ -// + + + F U N C T I O N S + + + \\ -//--------------------------------------------------\\ - -// MakeView creates and returns a new View struct from -// a Url and a string splice of content. This is used to -// initialize a View with a Url struct, links, and content. -// It takes a Url struct and a content []string and returns -// a View (NOT a pointer to a View). -func MakeView(url Url, content []string) View { - v := View{content, make([]string, 0), url} - v.ParseMap() - return v -} diff --git a/headbar.go b/headbar.go index 8aafc55..d467a43 100644 --- a/headbar.go +++ b/headbar.go @@ -1,5 +1,8 @@ package main +import ( + "fmt" +) //------------------------------------------------\\ // + + + T Y P E S + + + \\ @@ -7,8 +10,6 @@ package main type Headbar struct { title string - url string - content string } @@ -16,13 +17,8 @@ type Headbar struct { // + + + R E C E I V E R S + + + \\ //--------------------------------------------------\\ -func (h *Headbar) SetUrl(u string) { - h.url = u -} - func (h *Headbar) Build(width string) string { // TODO Build out header to specified width - h.content = "" // This is a temp value to show intention return "" } @@ -31,9 +27,9 @@ func (h *Headbar) Draw() { // without having to redraw everything else } -func (h *Headbar) Render() string { - // TODO returns the content value - return "" +func (h *Headbar) Render(width int, message string) string { + maxMsgWidth := width - len([]rune(h.title)) + return fmt.Sprintf("\033[7m%s%-*.*s\033[0m", h.title, maxMsgWidth, maxMsgWidth, message) } @@ -42,6 +38,6 @@ func (h *Headbar) Render() string { //--------------------------------------------------\\ func MakeHeadbar(title string) Headbar { - return Headbar{title, "", title} + return Headbar{title} } diff --git a/main.go b/main.go index ff354bb..9f2e642 100644 --- a/main.go +++ b/main.go @@ -174,8 +174,8 @@ func initClient() error { } func main() { - cui.HandleAlternateScreen("smcup") - defer cui.Exit() + // cui.HandleAlternateScreen("smcup") + // defer cui.Exit() err := initClient() if err != nil { // if we can't initialize we should bail out @@ -189,12 +189,12 @@ func main() { // If a url was passed, move it down the line // Goroutine so keypresses can be made during // page load - go bombadillo.Visit(os.Args[1]) + bombadillo.Visit(os.Args[1]) } else { // Otherwise, load the homeurl // Goroutine so keypresses can be made during // page load - go bombadillo.Visit(bombadillo.Options["homeurl"]) + bombadillo.Visit(bombadillo.Options["homeurl"]) } // Loop indefinitely on user input diff --git a/page.go b/page.go index 9fa0a0e..e67124d 100644 --- a/page.go +++ b/page.go @@ -1,12 +1,16 @@ package main +import ( + "strings" + "bytes" +) //------------------------------------------------\\ // + + + T Y P E S + + + \\ //--------------------------------------------------\\ type Page struct { - WrappedContent string + WrappedContent []string RawContent string Links []string Location Url @@ -17,14 +21,64 @@ type Page struct { // + + + R E C E I V E R S + + + \\ //--------------------------------------------------\\ +func (p *Page) ScrollPositionRange(termHeight int) (int, int) { + termHeight -= 3 + if len(p.WrappedContent) - p.ScrollPosition < termHeight { + p.ScrollPosition = len(p.WrappedContent) - termHeight + } + if p.ScrollPosition < 0 { + p.ScrollPosition = 0 + } + var end int + if len(p.WrappedContent) < termHeight { + end = len(p.WrappedContent) + } else { + end = p.ScrollPosition + termHeight + } + return p.ScrollPosition, end +} + +func (p *Page) WrapContent(width int) { + // TODO this is a temporary wrapping function + // in order to test. Rebuild it. + src := strings.Split(p.RawContent, "\n") + out := []string{} + for _, ln := range src { + if len([]rune(ln)) <= width { + out = append(out, ln) + } else { + words := strings.SplitAfter(ln, " ") + var subout bytes.Buffer + for i, wd := range words { + sublen := subout.Len() + wdlen := len([]rune(wd)) + if sublen+wdlen <= width { + subout.WriteString(wd) + if i == len(words)-1 { + out = append(out, subout.String()) + } + } else { + out = append(out, subout.String()) + subout.Reset() + subout.WriteString(wd) + if i == len(words)-1 { + out = append(out, subout.String()) + subout.Reset() + } + } + } + } + } + p.WrappedContent = out +} //------------------------------------------------\\ // + + + F U N C T I O N S + + + \\ //--------------------------------------------------\\ -func MakePage(url Url, content string) Page { - p := Page{"", content, make([]string, 0), url, 0} +func MakePage(url Url, content string, links []string) Page { + p := Page{make([]string, 0), content, links, url, 0} return p } diff --git a/pages.go b/pages.go index 5c844e8..a4f12c3 100644 --- a/pages.go +++ b/pages.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "strings" ) //------------------------------------------------\\ @@ -31,16 +32,30 @@ func (p *Pages) NavigateHistory(qty int) error { return nil } -func (p *Pages) Add(pg Page) error { - // TODO add the given page onto the pages struct - // handling truncation of the history as needed. - return fmt.Errorf("") +func (p *Pages) Add(pg Page) { + if p.Position == p.Length - 1 && p.Length < len(p.History) { + p.History[p.Length] = pg + p.Length++ + p.Position++ + } else if p.Position == p.Length - 1 && p.Length == 20 { + for x := 1; x < len(p.History); x++ { + p.History[x-1] = p.History[x] + } + p.History[len(p.History)-1] = pg + } else { + p.Position += 1 + p.Length = p.Position + 1 + p.History[p.Position] = pg + } } -func (p *Pages) Render() ([]string, error) { - // TODO grab the current page as wrappedContent - // May need to handle spacing at end of lines. - return []string{}, fmt.Errorf("") +func (p *Pages) Render(termHeight int) []string { + if p.Length < 1 { + msg := "Welcome to Bombadillo,\nif this is your first time here\ntype:\n\n:help\n(and then press enter)" + return strings.Split(msg, "\n") + } + beg, end := p.History[p.Position].ScrollPositionRange(termHeight) + return p.History[p.Position].WrappedContent[beg:end] } //------------------------------------------------\\ diff --git a/url.go b/url.go index b6dfebd..58673fd 100644 --- a/url.go +++ b/url.go @@ -37,11 +37,11 @@ type Url struct { // an error (or nil). func MakeUrl(u string) (Url, error) { var out Url - re := regexp.MustCompile(`^((?Pgopher|http|https|gemini):\/\/)?(?P[\w\-\.\d]+)(?::(?P\d+)?)?(?:/(?P[01345679gIhisp])?)?(?P.*)?$`) + re := regexp.MustCompile(`^((?Pgopher|telnet|http|https|gemini):\/\/)?(?P[\w\-\.\d]+)(?::(?P\d+)?)?(?:/(?P[01345679gIhisp])?)?(?P.*)?$`) match := re.FindStringSubmatch(u) if valid := re.MatchString(u); !valid { - return out, fmt.Errorf("Invalid url/unable to parse") + return out, fmt.Errorf("Invalid url, unable to parse") } for i, name := range re.SubexpNames() {