forked from sloum/bombadillo
Some level of screen draw now works
This commit is contained in:
parent
b7d7d021ed
commit
bccca61ec2
49
bookmarks.go
49
bookmarks.go
|
@ -3,6 +3,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"tildegit.org/sloum/bombadillo/cui"
|
||||||
)
|
)
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
|
@ -81,16 +83,49 @@ func (b Bookmarks) List() []string {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Bookmarks) Render() ([]string, error) {
|
func (b Bookmarks) Render(termwidth, termheight int) []string {
|
||||||
// TODO Use b.List() to get the necessary
|
width := 40
|
||||||
// text and add on the correct border for
|
termheight -= 3
|
||||||
// rendering the focus. Use sprintf, left
|
var wall, ceil, tr, tl, br, bl string
|
||||||
// aligned: "| %-36.36s |" of the like.
|
if termwidth < 40 {
|
||||||
return []string{}, nil
|
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
|
// 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
|
// code for scroll
|
||||||
|
|
||||||
|
|
||||||
|
|
51
client.go
51
client.go
|
@ -15,7 +15,7 @@ import (
|
||||||
"tildegit.org/sloum/bombadillo/cmdparse"
|
"tildegit.org/sloum/bombadillo/cmdparse"
|
||||||
"tildegit.org/sloum/bombadillo/cui"
|
"tildegit.org/sloum/bombadillo/cui"
|
||||||
// "tildegit.org/sloum/bombadillo/gemini"
|
// "tildegit.org/sloum/bombadillo/gemini"
|
||||||
// "tildegit.org/sloum/bombadillo/gopher"
|
"tildegit.org/sloum/bombadillo/gopher"
|
||||||
"tildegit.org/sloum/bombadillo/http"
|
"tildegit.org/sloum/bombadillo/http"
|
||||||
"tildegit.org/sloum/bombadillo/telnet"
|
"tildegit.org/sloum/bombadillo/telnet"
|
||||||
)
|
)
|
||||||
|
@ -68,10 +68,30 @@ func (c *client) GetSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Draw() {
|
func (c *client) Draw() {
|
||||||
// TODO build this out.
|
var screen strings.Builder
|
||||||
// It should call all of the renders
|
screen.Grow(c.Height * c.Width)
|
||||||
// and add them to the a string buffer
|
screen.WriteString(c.TopBar.Render(c.Width, "This is a test"))
|
||||||
// It should then print the buffer
|
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() {
|
func (c *client) TakeControlInput() {
|
||||||
|
@ -131,6 +151,7 @@ func (c *client) TakeControlInput() {
|
||||||
// Process a command
|
// Process a command
|
||||||
c.ClearMessage()
|
c.ClearMessage()
|
||||||
c.ClearMessageLine()
|
c.ClearMessageLine()
|
||||||
|
cui.MoveCursorTo(c.Height-2, 0)
|
||||||
entry, err := cui.GetLine()
|
entry, err := cui.GetLine()
|
||||||
c.ClearMessageLine()
|
c.ClearMessageLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -460,9 +481,26 @@ func (c *client) Visit(url string) {
|
||||||
|
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "gopher":
|
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":
|
case "gemini":
|
||||||
// TODO send over to gemini request
|
// TODO send over to gemini request
|
||||||
|
c.SetMessage("Gemini is not currently supported", false)
|
||||||
|
c.DrawMessage()
|
||||||
case "telnet":
|
case "telnet":
|
||||||
c.SetMessage("Attempting to start telnet session", false)
|
c.SetMessage("Attempting to start telnet session", false)
|
||||||
c.DrawMessage()
|
c.DrawMessage()
|
||||||
|
@ -512,7 +550,6 @@ func MakeClient(name string) *client {
|
||||||
"configlocation": userinfo.HomeDir,
|
"configlocation": userinfo.HomeDir,
|
||||||
}
|
}
|
||||||
c := client{0, 0, options, "", MakePages(), MakeBookmarks(), MakeHeadbar(name), MakeFootbar()}
|
c := client{0, 0, options, "", MakePages(), MakeBookmarks(), MakeHeadbar(name), MakeFootbar()}
|
||||||
c.GetSize()
|
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"tildegit.org/sloum/bombadillo/gopher"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
|
@ -21,7 +20,10 @@ type Parser struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Bookmarks gopher.Bookmarks
|
// Bookmarks gopher.Bookmarks
|
||||||
|
Bookmarks struct {
|
||||||
|
Titles, Links []string
|
||||||
|
}
|
||||||
Colors []KeyValue
|
Colors []KeyValue
|
||||||
Settings []KeyValue
|
Settings []KeyValue
|
||||||
}
|
}
|
||||||
|
@ -86,10 +88,8 @@ func (p *Parser) Parse() (Config, error) {
|
||||||
}
|
}
|
||||||
switch section {
|
switch section {
|
||||||
case "BOOKMARKS":
|
case "BOOKMARKS":
|
||||||
err := c.Bookmarks.Add([]string{keyval.Value, keyval.Key})
|
c.Bookmarks.Titles = append(c.Bookmarks.Titles, keyval.Value)
|
||||||
if err != nil {
|
c.Bookmarks.Links = append(c.Bookmarks.Links, keyval.Key)
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
case "COLORS":
|
case "COLORS":
|
||||||
c.Colors = append(c.Colors, keyval)
|
c.Colors = append(c.Colors, keyval)
|
||||||
case "SETTINGS":
|
case "SETTINGS":
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shapes = map[string]string{
|
var Shapes = map[string]string{
|
||||||
"wall": "╵",
|
"wall": "╵",
|
||||||
"ceiling": "╴",
|
"ceiling": "╴",
|
||||||
"tl": "┌",
|
"tl": "┌",
|
||||||
|
@ -25,7 +25,7 @@ var shapes = map[string]string{
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawShape(shape string) {
|
func drawShape(shape string) {
|
||||||
if val, ok := shapes[shape]; ok {
|
if val, ok := Shapes[shape]; ok {
|
||||||
fmt.Printf("%s", val)
|
fmt.Printf("%s", val)
|
||||||
} else {
|
} else {
|
||||||
fmt.Print("x")
|
fmt.Print("x")
|
||||||
|
|
18
footbar.go
18
footbar.go
|
@ -9,9 +9,8 @@ import (
|
||||||
//--------------------------------------------------\\
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
type Footbar struct {
|
type Footbar struct {
|
||||||
PercentRead string
|
PercentRead int
|
||||||
PageType string
|
PageType string
|
||||||
Content string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ type Footbar struct {
|
||||||
//--------------------------------------------------\\
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
func (f *Footbar) SetPercentRead(p int) {
|
func (f *Footbar) SetPercentRead(p int) {
|
||||||
f.PercentRead = fmt.Sprintf("%d%%", p)
|
f.PercentRead = p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Footbar) SetPageType(t string) {
|
func (f *Footbar) SetPageType(t string) {
|
||||||
|
@ -32,15 +31,8 @@ func (f *Footbar) Draw() {
|
||||||
// without having to redraw everything else
|
// without having to redraw everything else
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Footbar) Build(width string) string {
|
func (f *Footbar) Render(termWidth int) string {
|
||||||
// TODO Build out header to specified width
|
return fmt.Sprintf("\033[7m%-*.*s\033[0m", termWidth, termWidth, "")
|
||||||
f.Content = "" // This is a temp value to show intention
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Footbar) Render() string {
|
|
||||||
// TODO returns a full line
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,6 +41,6 @@ func (f *Footbar) Render() string {
|
||||||
//--------------------------------------------------\\
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
func MakeFootbar() Footbar {
|
func MakeFootbar() Footbar {
|
||||||
return Footbar{"", "N/A", ""}
|
return Footbar{100, "N/A"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -81,15 +81,18 @@ func Retrieve(host, port, resource string) ([]byte, error) {
|
||||||
// the correct information to the client
|
// the correct information to the client
|
||||||
func Visit(gophertype, host, port, resource string) (string, []string, error) {
|
func Visit(gophertype, host, port, resource string) (string, []string, error) {
|
||||||
resp, err := Retrieve(host, port, resource)
|
resp, err := Retrieve(host, port, resource)
|
||||||
|
if err != nil {
|
||||||
|
return "", []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
text := string(resp)
|
text := string(resp)
|
||||||
links := []string{}
|
links := []string{}
|
||||||
|
|
||||||
if err != nil {
|
if IsDownloadOnly(gophertype) {
|
||||||
return "", []string{}, err
|
|
||||||
} else if IsDownloadOnly(gophertype) {
|
|
||||||
return text, []string{}, nil
|
return text, []string{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if gophertype == "1" {
|
if gophertype == "1" {
|
||||||
text, links = parseMap(text)
|
text, links = parseMap(text)
|
||||||
}
|
}
|
||||||
|
@ -113,8 +116,6 @@ func isWebLink(resource string) (string, bool) {
|
||||||
return "", false
|
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) {
|
func parseMap(text string) (string, []string) {
|
||||||
splitContent := strings.Split(text, "\n")
|
splitContent := strings.Split(text, "\n")
|
||||||
links := make([]string, 0, 10)
|
links := make([]string, 0, 10)
|
||||||
|
@ -128,23 +129,23 @@ func parseMap(text string) (string, []string) {
|
||||||
|
|
||||||
line := strings.Split(e, "\t")
|
line := strings.Split(e, "\t")
|
||||||
var title string
|
var title string
|
||||||
// TODO REFACTOR LINE == HERE
|
|
||||||
// - - - - - - - - - - - - - -
|
|
||||||
if len(line[0]) > 1 {
|
if len(line[0]) > 1 {
|
||||||
title = line[0][1:]
|
title = line[0][1:]
|
||||||
} else {
|
} else {
|
||||||
title = ""
|
title = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(line) > 1 && len(line[0]) > 0 && string(line[0][0]) == "i" {
|
if len(line) > 1 && len(line[0]) > 0 && string(line[0][0]) == "i" {
|
||||||
splitContent[i] = " " + string(title)
|
splitContent[i] = " " + string(title)
|
||||||
} else if len(line) >= 4 {
|
} else if len(line) >= 4 {
|
||||||
fulllink := fmt.Sprintf("%s://%s:%s/%s%s", "protocol" ,line[2], line[3], string(line[0][0]), line[1])
|
link := buildLink(line[2], line[3], string(line[0][0]), line[1])
|
||||||
links = append(links, fulllink)
|
links = append(links, link)
|
||||||
linktext := fmt.Sprintf("(%s) %2d %s", getType(string(line[0][0])), len(links), title)
|
linktext := fmt.Sprintf("(%s) %2d %s", getType(string(line[0][0])), len(links), title)
|
||||||
splitContent[i] = linktext
|
splitContent[i] = linktext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", links
|
return strings.Join(splitContent, "\n"), links
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns false for all text formats (including html
|
// Returns false for all text formats (including html
|
||||||
|
@ -160,3 +161,24 @@ func IsDownloadOnly(gophertype string) bool {
|
||||||
return true
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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{}}
|
|
||||||
}
|
|
|
@ -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(`^((?P<scheme>gopher|http|https|ftp|telnet):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`)
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
18
headbar.go
18
headbar.go
|
@ -1,5 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
// + + + T Y P E S + + + \\
|
// + + + T Y P E S + + + \\
|
||||||
|
@ -7,8 +10,6 @@ package main
|
||||||
|
|
||||||
type Headbar struct {
|
type Headbar struct {
|
||||||
title string
|
title string
|
||||||
url string
|
|
||||||
content string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,13 +17,8 @@ type Headbar struct {
|
||||||
// + + + R E C E I V E R S + + + \\
|
// + + + 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 {
|
func (h *Headbar) Build(width string) string {
|
||||||
// TODO Build out header to specified width
|
// TODO Build out header to specified width
|
||||||
h.content = "" // This is a temp value to show intention
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,9 +27,9 @@ func (h *Headbar) Draw() {
|
||||||
// without having to redraw everything else
|
// without having to redraw everything else
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headbar) Render() string {
|
func (h *Headbar) Render(width int, message string) string {
|
||||||
// TODO returns the content value
|
maxMsgWidth := width - len([]rune(h.title))
|
||||||
return ""
|
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 {
|
func MakeHeadbar(title string) Headbar {
|
||||||
return Headbar{title, "", title}
|
return Headbar{title}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
main.go
8
main.go
|
@ -174,8 +174,8 @@ func initClient() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cui.HandleAlternateScreen("smcup")
|
// cui.HandleAlternateScreen("smcup")
|
||||||
defer cui.Exit()
|
// defer cui.Exit()
|
||||||
err := initClient()
|
err := initClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if we can't initialize we should bail out
|
// 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
|
// If a url was passed, move it down the line
|
||||||
// Goroutine so keypresses can be made during
|
// Goroutine so keypresses can be made during
|
||||||
// page load
|
// page load
|
||||||
go bombadillo.Visit(os.Args[1])
|
bombadillo.Visit(os.Args[1])
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, load the homeurl
|
// Otherwise, load the homeurl
|
||||||
// Goroutine so keypresses can be made during
|
// Goroutine so keypresses can be made during
|
||||||
// page load
|
// page load
|
||||||
go bombadillo.Visit(bombadillo.Options["homeurl"])
|
bombadillo.Visit(bombadillo.Options["homeurl"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop indefinitely on user input
|
// Loop indefinitely on user input
|
||||||
|
|
60
page.go
60
page.go
|
@ -1,12 +1,16 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
// + + + T Y P E S + + + \\
|
// + + + T Y P E S + + + \\
|
||||||
//--------------------------------------------------\\
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
WrappedContent string
|
WrappedContent []string
|
||||||
RawContent string
|
RawContent string
|
||||||
Links []string
|
Links []string
|
||||||
Location Url
|
Location Url
|
||||||
|
@ -17,14 +21,64 @@ type Page struct {
|
||||||
// + + + R E C E I V E R S + + + \\
|
// + + + 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 + + + \\
|
// + + + F U N C T I O N S + + + \\
|
||||||
//--------------------------------------------------\\
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
func MakePage(url Url, content string) Page {
|
func MakePage(url Url, content string, links []string) Page {
|
||||||
p := Page{"", content, make([]string, 0), url, 0}
|
p := Page{make([]string, 0), content, links, url, 0}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
pages.go
31
pages.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
|
@ -31,16 +32,30 @@ func (p *Pages) NavigateHistory(qty int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pages) Add(pg Page) error {
|
func (p *Pages) Add(pg Page) {
|
||||||
// TODO add the given page onto the pages struct
|
if p.Position == p.Length - 1 && p.Length < len(p.History) {
|
||||||
// handling truncation of the history as needed.
|
p.History[p.Length] = pg
|
||||||
return fmt.Errorf("")
|
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) {
|
func (p *Pages) Render(termHeight int) []string {
|
||||||
// TODO grab the current page as wrappedContent
|
if p.Length < 1 {
|
||||||
// May need to handle spacing at end of lines.
|
msg := "Welcome to Bombadillo,\nif this is your first time here\ntype:\n\n:help\n(and then press enter)"
|
||||||
return []string{}, fmt.Errorf("")
|
return strings.Split(msg, "\n")
|
||||||
|
}
|
||||||
|
beg, end := p.History[p.Position].ScrollPositionRange(termHeight)
|
||||||
|
return p.History[p.Position].WrappedContent[beg:end]
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
|
|
4
url.go
4
url.go
|
@ -37,11 +37,11 @@ type Url struct {
|
||||||
// an error (or nil).
|
// an error (or nil).
|
||||||
func MakeUrl(u string) (Url, error) {
|
func MakeUrl(u string) (Url, error) {
|
||||||
var out Url
|
var out Url
|
||||||
re := regexp.MustCompile(`^((?P<scheme>gopher|http|https|gemini):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`)
|
re := regexp.MustCompile(`^((?P<scheme>gopher|telnet|http|https|gemini):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`)
|
||||||
match := re.FindStringSubmatch(u)
|
match := re.FindStringSubmatch(u)
|
||||||
|
|
||||||
if valid := re.MatchString(u); !valid {
|
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() {
|
for i, name := range re.SubexpNames() {
|
||||||
|
|
Loading…
Reference in New Issue