Initial v2 commit, deep in restructuring... maybe not for the better?
This commit is contained in:
parent
be34a9a809
commit
da45f627e0
|
@ -0,0 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + T Y P E S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
type Bookmarks struct {
|
||||
IsOpen bool
|
||||
IsFocused bool
|
||||
Position int
|
||||
Length int
|
||||
Titles []string
|
||||
Links []string
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + R E C E I V E R S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func (b *Bookmarks) Add([]string) error {
|
||||
// TODO add a bookmark
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
|
||||
func (b *Bookmarks) Delete(int) error {
|
||||
// TODO delete a bookmark
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
|
||||
func (b *Bookmarks) ToggleOpen() {
|
||||
b.IsOpen = !b.IsOpen
|
||||
if b.IsOpen {
|
||||
b.IsFocused = true
|
||||
} else {
|
||||
b.IsFocused = false
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bookmarks) ToggleFocused() {
|
||||
if b.IsOpen {
|
||||
b.IsFocused = !b.IsFocused
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bookmarks) IniDump() string {
|
||||
// TODO create dump of values for INI file
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bookmarks) Render() ([]string, error) {
|
||||
// TODO grab all of the bookmarks as a fixed
|
||||
// width string including border and spacing
|
||||
return []string{}, fmt.Errorf("")
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + F U N C T I O N S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func MakeBookmarks() Bookmarks {
|
||||
return Bookmarks{false, false, 0, 0, make([]string, 0), make([]string, 0)}
|
||||
}
|
||||
|
|
@ -0,0 +1,541 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tildegit.org/sloum/bombadillo/cmdparse"
|
||||
"tildegit.org/sloum/bombadillo/cui"
|
||||
"tildegit.org/sloum/bombadillo/gopher"
|
||||
)
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + T Y P E S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
type client struct {
|
||||
Height int
|
||||
Width int
|
||||
Options map[string]string
|
||||
Message string
|
||||
PageState Pages
|
||||
BookMarks Bookmarks
|
||||
TopBar Headbar
|
||||
FootBar Footbar
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + R E C E I V E R S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func (c *client) GetSize() {
|
||||
for {
|
||||
redraw := false
|
||||
cmd := exec.Command("stty", "size")
|
||||
cmd.Stdin = os.Stdin
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error: Unable to retrieve terminal size")
|
||||
os.Exit(5)
|
||||
}
|
||||
var h, w int
|
||||
fmt.Sscan(string(out), &h, &w)
|
||||
if h != c.Height || w != c.Width {
|
||||
redraw = true
|
||||
}
|
||||
|
||||
c.Height = h
|
||||
c.Width = w
|
||||
|
||||
if redraw {
|
||||
c.Draw()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (c *client) TakeControlInput() {
|
||||
input := cui.Getch()
|
||||
|
||||
switch input {
|
||||
case 'j', 'J':
|
||||
// scroll down one line
|
||||
c.Scroll(1)
|
||||
case 'k', 'K':
|
||||
// scroll up one line
|
||||
c.Scroll(-1)
|
||||
case 'q', 'Q':
|
||||
// quite bombadillo
|
||||
cui.Exit()
|
||||
case 'g':
|
||||
// scroll to top
|
||||
c.Scroll(-len(c.PageState.History[c.PageState.Position].WrappedContent))
|
||||
case 'G':
|
||||
// scroll to bottom
|
||||
c.Scroll(len(c.PageState.History[c.PageState.Position].WrappedContent))
|
||||
case 'd':
|
||||
// scroll down 75%
|
||||
distance := c.Height - c.Height / 4
|
||||
c.Scroll(distance)
|
||||
case 'u':
|
||||
// scroll up 75%
|
||||
distance := c.Height - c.Height / 4
|
||||
c.Scroll(-distance)
|
||||
case 'b':
|
||||
// go back
|
||||
err := c.PageState.NavigateHistory(-1)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), false)
|
||||
c.DrawMessage()
|
||||
} else {
|
||||
c.Draw()
|
||||
}
|
||||
case 'B':
|
||||
// open the bookmarks browser
|
||||
c.BookMarks.ToggleOpen()
|
||||
c.Draw()
|
||||
case 'f', 'F':
|
||||
// go forward
|
||||
err := c.PageState.NavigateHistory(1)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), false)
|
||||
c.DrawMessage()
|
||||
} else {
|
||||
c.Draw()
|
||||
}
|
||||
case '\t':
|
||||
// Toggle bookmark browser focus on/off
|
||||
c.BookMarks.ToggleFocused()
|
||||
c.Draw()
|
||||
case ':', ' ':
|
||||
// Process a command
|
||||
c.ClearMessage()
|
||||
c.ClearMessageLine()
|
||||
entry, err := cui.GetLine()
|
||||
c.ClearMessageLine()
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
break
|
||||
} else if strings.TrimSpace(entry) == "" {
|
||||
break
|
||||
}
|
||||
|
||||
parser := cmdparse.NewParser(strings.NewReader(entry))
|
||||
p, err := parser.Parse()
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
} else {
|
||||
err := c.routeCommandInput(p)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (c *client) routeCommandInput(com *cmdparse.Command) error {
|
||||
var err error
|
||||
switch com.Type {
|
||||
case cmdparse.SIMPLE:
|
||||
c.simpleCommand(com.Action)
|
||||
case cmdparse.GOURL:
|
||||
c.goToURL(com.Target)
|
||||
case cmdparse.GOLINK:
|
||||
c.goToLink(com.Target)
|
||||
case cmdparse.DO:
|
||||
c.doCommand(com.Action, com.Value)
|
||||
case cmdparse.DOLINK:
|
||||
// err = doLinkCommand(com.Action, com.Target)
|
||||
case cmdparse.DOAS:
|
||||
c.doCommandAs(com.Action, com.Value)
|
||||
case cmdparse.DOLINKAS:
|
||||
// err = doLinkCommandAs(com.Action, com.Target, com.Value)
|
||||
default:
|
||||
return fmt.Errorf("Unknown command entry!")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) simpleCommand(action string) {
|
||||
action = strings.ToUpper(action)
|
||||
switch action {
|
||||
case "Q", "QUIT":
|
||||
cui.Exit()
|
||||
case "H", "HOME":
|
||||
if c.Options["homeurl"] != "unset" {
|
||||
go c.Visit(c.Options["homeurl"])
|
||||
} else {
|
||||
c.SetMessage(fmt.Sprintf("No home address has been set"), false)
|
||||
c.DrawMessage()
|
||||
}
|
||||
case "B", "BOOKMARKS":
|
||||
c.BookMarks.ToggleOpen()
|
||||
case "SEARCH":
|
||||
c.search()
|
||||
case "HELP", "?":
|
||||
go c.Visit(helplocation)
|
||||
default:
|
||||
c.SetMessage(fmt.Sprintf("Unknown action %q", action), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) doCommand(action string, values []string) {
|
||||
if length := len(values); length != 1 {
|
||||
c.SetMessage(fmt.Sprintf("Expected 1 argument, received %d", len(values)), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "CHECK", "C":
|
||||
c.displayConfigValue(values[0])
|
||||
default:
|
||||
c.SetMessage(fmt.Sprintf("Unknown action %q", action), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) doCommandAs(action string, values []string) {
|
||||
if len(values) < 2 {
|
||||
c.SetMessage(fmt.Sprintf("Expected 1 argument, received %d", len(values)), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
|
||||
if values[0] == "." {
|
||||
values[0] = c.PageState.History[c.PageState.Position].Location.Full
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "ADD", "A":
|
||||
err := c.BookMarks.Add(values)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
|
||||
err = saveConfig()
|
||||
if err != nil {
|
||||
c.SetMessage("Error saving bookmark to file", true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
if c.BookMarks.IsOpen {
|
||||
c.Draw()
|
||||
}
|
||||
|
||||
case "WRITE", "W":
|
||||
// TODO figure out how best to handle file
|
||||
// writing... it will depend on request model
|
||||
// using fetch would be best
|
||||
// - - - - - - - - - - - - - - - - - - - - -
|
||||
// var data []byte
|
||||
// if values[0] == "." {
|
||||
// d, err := c.getCurrentPageRawData()
|
||||
// if err != nil {
|
||||
// c.SetMessage(err.Error(), true)
|
||||
// c.DrawMessage()
|
||||
// return
|
||||
// }
|
||||
// data = []byte(d)
|
||||
// }
|
||||
// fp, err := c.saveFile(data, strings.Join(values[1:], " "))
|
||||
// if err != nil {
|
||||
// c.SetMessage(err.Error(), true)
|
||||
// c.DrawMessage()
|
||||
// return
|
||||
// }
|
||||
// c.SetMessage(fmt.Sprintf("File saved to: %s", fp), false)
|
||||
// c.DrawMessage()
|
||||
|
||||
case "SET", "S":
|
||||
if _, ok := c.Options[values[0]]; ok {
|
||||
c.Options[values[0]] = strings.Join(values[1:], " ")
|
||||
err := saveConfig()
|
||||
if err != nil {
|
||||
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.DrawMessage()
|
||||
}
|
||||
return
|
||||
}
|
||||
c.SetMessage(fmt.Sprintf("Unable to set %s, it does not exist", values[0]), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
c.SetMessage(fmt.Sprintf("Unknown command structure"), true)
|
||||
}
|
||||
|
||||
func (c *client) getCurrentPageUrl() (string, error) {
|
||||
if c.PageState.Length < 1 {
|
||||
return "", fmt.Errorf("There are no pages in history")
|
||||
}
|
||||
return c.PageState.History[c.PageState.Position].Location.Full, nil
|
||||
}
|
||||
|
||||
func (c *client) getCurrentPageRawData() (string, error) {
|
||||
if c.PageState.Length < 1 {
|
||||
return "", fmt.Errorf("There are no pages in history")
|
||||
}
|
||||
return c.PageState.History[c.PageState.Position].RawContent, nil
|
||||
}
|
||||
|
||||
func (c *client) saveFile(data []byte, name string) (string, error) {
|
||||
savePath := c.Options["savelocation"] + name
|
||||
err := ioutil.WriteFile(savePath, data, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return savePath, nil
|
||||
}
|
||||
|
||||
func (c *client) search() {
|
||||
c.ClearMessage()
|
||||
c.ClearMessageLine()
|
||||
fmt.Print("?")
|
||||
entry, err := cui.GetLine()
|
||||
c.ClearMessageLine()
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
} else if strings.TrimSpace(entry) == "" {
|
||||
return
|
||||
}
|
||||
u, err := MakeUrl(c.Options["searchurl"])
|
||||
if err != nil {
|
||||
c.SetMessage("'searchurl' is not set to a valid url", true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "gopher":
|
||||
go c.Visit(fmt.Sprintf("%s\t%s",u.Full,entry))
|
||||
case "gemini":
|
||||
// TODO url escape the entry variable
|
||||
escapedEntry := entry
|
||||
go c.Visit(fmt.Sprintf("%s?%s",u.Full,escapedEntry))
|
||||
case "http", "https":
|
||||
c.SetMessage("Attempting to open in web browser", false)
|
||||
c.DrawMessage()
|
||||
err := gopher.OpenBrowser(u.Full)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
} else {
|
||||
c.SetMessage("Opened in web browser", false)
|
||||
}
|
||||
c.DrawMessage()
|
||||
default:
|
||||
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Scroll(amount int) {
|
||||
page := c.PageState.History[c.PageState.Position]
|
||||
bottom := len(page.WrappedContent) - c.Height
|
||||
if amount < 0 && page.ScrollPosition == 0 {
|
||||
c.SetMessage("You are already at the top", false)
|
||||
c.DrawMessage()
|
||||
fmt.Print("\a")
|
||||
return
|
||||
} else if amount > 0 && page.ScrollPosition == bottom || bottom < 0 {
|
||||
c.SetMessage("You are already at the bottom", false)
|
||||
c.DrawMessage()
|
||||
fmt.Print("\a")
|
||||
return
|
||||
}
|
||||
|
||||
newScrollPosition := page.ScrollPosition + amount
|
||||
if newScrollPosition < 0 {
|
||||
newScrollPosition = 0
|
||||
} else if newScrollPosition > bottom {
|
||||
newScrollPosition = bottom
|
||||
}
|
||||
|
||||
page.ScrollPosition = newScrollPosition
|
||||
c.Draw()
|
||||
}
|
||||
|
||||
func (c *client) displayConfigValue(setting string) {
|
||||
if val, ok := c.Options[setting]; ok {
|
||||
c.SetMessage(fmt.Sprintf("%s is set to: %q", setting, val), false)
|
||||
c.DrawMessage()
|
||||
} else {
|
||||
c.SetMessage(fmt.Sprintf("Invalid: %q does not exist", setting), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) SetMessage(msg string, isError bool) {
|
||||
leadIn, leadOut := "", ""
|
||||
if isError {
|
||||
leadIn = "\033[31m"
|
||||
leadOut = "\033[0m"
|
||||
}
|
||||
|
||||
c.Message = fmt.Sprintf("%s%s%s", leadIn, msg, leadOut)
|
||||
}
|
||||
|
||||
func (c *client) DrawMessage() {
|
||||
c.ClearMessageLine()
|
||||
cui.MoveCursorTo(c.Height-1, 0)
|
||||
fmt.Print(c.Message)
|
||||
}
|
||||
|
||||
func (c *client) ClearMessage() {
|
||||
c.Message = ""
|
||||
}
|
||||
|
||||
func (c *client) ClearMessageLine() {
|
||||
cui.MoveCursorTo(c.Height-1, 0)
|
||||
cui.Clear("line")
|
||||
}
|
||||
|
||||
func (c *client) goToURL(u string) {
|
||||
if num, _ := regexp.MatchString(`^-?\d+.?\d*$`, u); num {
|
||||
c.goToLink(u)
|
||||
return
|
||||
}
|
||||
|
||||
go c.Visit(u)
|
||||
}
|
||||
|
||||
func (c *client) goToLink(l string) {
|
||||
if num, _ := regexp.MatchString(`^-?\d+$`, l); num && c.PageState.Length > 0 {
|
||||
linkcount := len(c.PageState.History[c.PageState.Position].Links)
|
||||
item, err := strconv.Atoi(l)
|
||||
if err != nil {
|
||||
c.SetMessage(fmt.Sprintf("Invalid link id: %s", l), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
if item <= linkcount && item > 0 {
|
||||
linkurl := c.PageState.History[c.PageState.Position].Links[item-1]
|
||||
c.Visit(linkurl)
|
||||
} else {
|
||||
c.SetMessage(fmt.Sprintf("Invalid link id: %s", l), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.SetMessage(fmt.Sprintf("Invalid link id: %s", l), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
|
||||
func (c *client) Visit(url string) {
|
||||
u, err := MakeUrl(url)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "gopher":
|
||||
// TODO send over to gopher request
|
||||
case "gemini":
|
||||
// TODO send over to gemini request
|
||||
case "http", "https":
|
||||
c.SetMessage("Attempting to open in web browser", false)
|
||||
c.DrawMessage()
|
||||
if strings.ToUpper(c.Options["openhttp"]) == "TRUE" {
|
||||
err := gopher.OpenBrowser(u.Full)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
} else {
|
||||
c.SetMessage("Opened in web browser", false)
|
||||
}
|
||||
c.DrawMessage()
|
||||
} else {
|
||||
c.SetMessage("'openhttp' is not set to true, aborting opening web link", false)
|
||||
c.DrawMessage()
|
||||
}
|
||||
default:
|
||||
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + F U N C T I O N S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
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()}
|
||||
c.GetSize()
|
||||
return &c
|
||||
}
|
||||
|
||||
// Retrieve a byte slice of raw response dataa
|
||||
// from a url string
|
||||
func Fetch(url string) ([]byte, error) {
|
||||
u, err := MakeUrl(url)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
|
||||
timeOut := time.Duration(5) * time.Second
|
||||
|
||||
if u.Host == "" || u.Port == "" {
|
||||
return []byte(""), fmt.Errorf("Incomplete request url")
|
||||
}
|
||||
|
||||
addr := u.Host + ":" + u.Port
|
||||
|
||||
conn, err := net.DialTimeout("tcp", addr, timeOut)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
|
||||
send := u.Resource + "\n"
|
||||
|
||||
_, err = conn.Write([]byte(send))
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
|
||||
result, err := ioutil.ReadAll(conn)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + T Y P E S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
type Footbar struct {
|
||||
PercentRead string
|
||||
PageType string
|
||||
Content string
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + R E C E I V E R S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func (f *Footbar) SetPercentRead(p int) {
|
||||
f.PercentRead = fmt.Sprintf("%d%%", 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) 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 ""
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + F U N C T I O N S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func MakeFootbar() Footbar {
|
||||
return Footbar{"", "N/A", ""}
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ func Visit(addr, openhttp string) (View, error) {
|
|||
|
||||
if u.Gophertype == "h" {
|
||||
if res, tf := isWebLink(u.Resource); tf && strings.ToUpper(openhttp) == "TRUE" {
|
||||
err := openBrowser(res)
|
||||
err := OpenBrowser(res)
|
||||
if err != nil {
|
||||
return View{}, err
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ package gopher
|
|||
|
||||
import "os/exec"
|
||||
|
||||
func openBrowser(url string) error {
|
||||
func OpenBrowser(url string) error {
|
||||
return exec.Command("open", url).Start()
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ package gopher
|
|||
|
||||
import "os/exec"
|
||||
|
||||
func openBrowser(url string) error {
|
||||
func OpenBrowser(url string) error {
|
||||
return exec.Command("xdg-open", url).Start()
|
||||
}
|
||||
|
|
|
@ -6,6 +6,6 @@ package gopher
|
|||
|
||||
import "fmt"
|
||||
|
||||
func openBrowser(url string) error {
|
||||
func OpenBrowser(url string) error {
|
||||
return fmt.Errorf("Unsupported os for browser detection")
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ package gopher
|
|||
|
||||
import "os/exec"
|
||||
|
||||
func openBrowser(url string) error {
|
||||
func OpenBrowser(url string) error {
|
||||
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package main
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + T Y P E S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
type Headbar struct {
|
||||
title string
|
||||
url string
|
||||
content string
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + 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 ""
|
||||
}
|
||||
|
||||
func (h *Headbar) Draw() {
|
||||
// TODO this will actually draw the bar
|
||||
// without having to redraw everything else
|
||||
}
|
||||
|
||||
func (h *Headbar) Render() string {
|
||||
// TODO returns the content value
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + F U N C T I O N S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func MakeHeadbar(title string) Headbar {
|
||||
return Headbar{title, "", title}
|
||||
}
|
||||
|
606
main.go
606
main.go
|
@ -1,402 +1,143 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"regexp"
|
||||
"strconv"
|
||||
// "strconv"
|
||||
"strings"
|
||||
|
||||
"tildegit.org/sloum/bombadillo/cmdparse"
|
||||
"tildegit.org/sloum/bombadillo/config"
|
||||
"tildegit.org/sloum/bombadillo/cui"
|
||||
"tildegit.org/sloum/bombadillo/gopher"
|
||||
// "tildegit.org/sloum/bombadillo/gopher"
|
||||
)
|
||||
|
||||
var bombadillo *client
|
||||
var helplocation string = "gopher://colorfield.space:70/1/bombadillo-info"
|
||||
var history gopher.History = gopher.MakeHistory()
|
||||
var screen *cui.Screen
|
||||
var userinfo, _ = user.Current()
|
||||
var settings config.Config
|
||||
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",
|
||||
}
|
||||
|
||||
func saveFile(address, name string) error {
|
||||
quickMessage("Saving file...", false)
|
||||
|
||||
url, err := gopher.MakeUrl(address)
|
||||
if err != nil {
|
||||
quickMessage("Saving file...", true)
|
||||
return err
|
||||
}
|
||||
// func saveFileFromData(v gopher.View) error {
|
||||
// quickMessage("Saving file...", false)
|
||||
// urlsplit := strings.Split(v.Address.Full, "/")
|
||||
// filename := urlsplit[len(urlsplit)-1]
|
||||
// saveMsg := fmt.Sprintf("Saved file as %q", options["savelocation"]+filename)
|
||||
// err := ioutil.WriteFile(options["savelocation"]+filename, []byte(strings.Join(v.Content, "")), 0644)
|
||||
// if err != nil {
|
||||
// quickMessage("Saving file...", true)
|
||||
// return err
|
||||
// }
|
||||
|
||||
data, err := gopher.Retrieve(url)
|
||||
if err != nil {
|
||||
quickMessage("Saving file...", true)
|
||||
return err
|
||||
}
|
||||
// quickMessage(saveMsg, false)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
err = ioutil.WriteFile(options["savelocation"]+name, data, 0644)
|
||||
if err != nil {
|
||||
quickMessage("Saving file...", true)
|
||||
return err
|
||||
}
|
||||
|
||||
quickMessage(fmt.Sprintf("Saved file to %s%s", options["savelocation"], name), false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveFileFromData(v gopher.View) error {
|
||||
quickMessage("Saving file...", false)
|
||||
urlsplit := strings.Split(v.Address.Full, "/")
|
||||
filename := urlsplit[len(urlsplit)-1]
|
||||
saveMsg := fmt.Sprintf("Saved file as %q", options["savelocation"]+filename)
|
||||
err := ioutil.WriteFile(options["savelocation"]+filename, []byte(strings.Join(v.Content, "")), 0644)
|
||||
if err != nil {
|
||||
quickMessage("Saving file...", true)
|
||||
return err
|
||||
}
|
||||
// func doLinkCommand(action, target string) error {
|
||||
// num, err := strconv.Atoi(target)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("Expected number, got %q", target)
|
||||
// }
|
||||
|
||||
quickMessage(saveMsg, false)
|
||||
return nil
|
||||
}
|
||||
// switch action {
|
||||
// case "DELETE", "D":
|
||||
// err := settings.Bookmarks.Del(num)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
func search(u string) error {
|
||||
cui.MoveCursorTo(screen.Height-1, 0)
|
||||
cui.Clear("line")
|
||||
fmt.Print("Enter form input: ")
|
||||
cui.MoveCursorTo(screen.Height-1, 17)
|
||||
// screen.Windows[1].Content = settings.Bookmarks.List()
|
||||
// err = saveConfig()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
entry, err := cui.GetLine()
|
||||
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
|
||||
// }
|
||||
|
||||
quickMessage("Searching...", false)
|
||||
searchurl := fmt.Sprintf("%s\t%s", u, entry)
|
||||
sv, err := gopher.Visit(searchurl, options["openhttp"])
|
||||
if err != nil {
|
||||
quickMessage("Searching...", true)
|
||||
return err
|
||||
}
|
||||
history.Add(sv)
|
||||
quickMessage("Searching...", true)
|
||||
updateMainContent()
|
||||
screen.Windows[0].Scrollposition = 0
|
||||
screen.ReflashScreen(true)
|
||||
return nil
|
||||
}
|
||||
// return fmt.Errorf("This method has not been built")
|
||||
// }
|
||||
|
||||
func routeInput(com *cmdparse.Command) error {
|
||||
var err error
|
||||
switch com.Type {
|
||||
case cmdparse.SIMPLE:
|
||||
err = simpleCommand(com.Action)
|
||||
case cmdparse.GOURL:
|
||||
err = goToURL(com.Target)
|
||||
case cmdparse.GOLINK:
|
||||
err = goToLink(com.Target)
|
||||
case cmdparse.DO:
|
||||
err = doCommand(com.Action, com.Value)
|
||||
case cmdparse.DOLINK:
|
||||
err = doLinkCommand(com.Action, com.Target)
|
||||
case cmdparse.DOAS:
|
||||
err = doCommandAs(com.Action, com.Value)
|
||||
case cmdparse.DOLINKAS:
|
||||
err = doLinkCommandAs(com.Action, com.Target, com.Value)
|
||||
default:
|
||||
return fmt.Errorf("Unknown command entry!")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
// func doCommand(action string, values []string) error {
|
||||
// if length := len(values); length != 1 {
|
||||
// return fmt.Errorf("Expected 1 argument, received %d", length)
|
||||
// }
|
||||
|
||||
func toggleBookmarks() {
|
||||
bookmarks := screen.Windows[1]
|
||||
main := screen.Windows[0]
|
||||
if bookmarks.Show {
|
||||
bookmarks.Show = false
|
||||
screen.Activewindow = 0
|
||||
main.Active = true
|
||||
bookmarks.Active = false
|
||||
} else {
|
||||
bookmarks.Show = true
|
||||
screen.Activewindow = 1
|
||||
main.Active = false
|
||||
bookmarks.Active = true
|
||||
}
|
||||
// switch action {
|
||||
// case "CHECK", "C":
|
||||
// err := checkConfigValue(values[0])
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
// return fmt.Errorf("Unknown command structure")
|
||||
// }
|
||||
|
||||
screen.ReflashScreen(false)
|
||||
}
|
||||
// func doLinkCommandAs(action, target string, values []string) error {
|
||||
// num, err := strconv.Atoi(target)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("Expected number, got %q", target)
|
||||
// }
|
||||
|
||||
func simpleCommand(a string) error {
|
||||
a = strings.ToUpper(a)
|
||||
switch a {
|
||||
case "Q", "QUIT":
|
||||
cui.Exit()
|
||||
case "H", "HOME":
|
||||
return goHome()
|
||||
case "B", "BOOKMARKS":
|
||||
toggleBookmarks()
|
||||
case "SEARCH":
|
||||
return search(options["searchengine"])
|
||||
case "HELP", "?":
|
||||
return goToURL(helplocation)
|
||||
// links := history.Collection[history.Position].Links
|
||||
// if num >= len(links) {
|
||||
// return fmt.Errorf("Invalid link id: %s", target)
|
||||
// }
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Unknown action %q", a)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// switch action {
|
||||
// case "ADD", "A":
|
||||
// newBookmark := append([]string{links[num-1]}, values...)
|
||||
// err := settings.Bookmarks.Add(newBookmark)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
func goToURL(u string) error {
|
||||
if num, _ := regexp.MatchString(`^-?\d+.?\d*$`, u); num {
|
||||
return goToLink(u)
|
||||
}
|
||||
quickMessage("Loading...", false)
|
||||
v, err := gopher.Visit(u, options["openhttp"])
|
||||
if err != nil {
|
||||
quickMessage("Loading...", true)
|
||||
return err
|
||||
}
|
||||
quickMessage("Loading...", true)
|
||||
// screen.Windows[1].Content = settings.Bookmarks.List()
|
||||
|
||||
if v.Address.Gophertype == "7" {
|
||||
err := search(v.Address.Full)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if v.Address.IsBinary {
|
||||
return saveFileFromData(v)
|
||||
} else {
|
||||
history.Add(v)
|
||||
}
|
||||
updateMainContent()
|
||||
screen.Windows[0].Scrollposition = 0
|
||||
screen.ReflashScreen(true)
|
||||
return nil
|
||||
}
|
||||
// err = saveConfig()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
func goToLink(l string) error {
|
||||
if num, _ := regexp.MatchString(`^-?\d+$`, l); num && history.Length > 0 {
|
||||
linkcount := len(history.Collection[history.Position].Links)
|
||||
item, _ := strconv.Atoi(l)
|
||||
if item <= linkcount && item > 0 {
|
||||
linkurl := history.Collection[history.Position].Links[item-1]
|
||||
quickMessage("Loading...", false)
|
||||
v, err := gopher.Visit(linkurl, options["openhttp"])
|
||||
if err != nil {
|
||||
quickMessage("Loading...", true)
|
||||
return err
|
||||
}
|
||||
quickMessage("Loading...", true)
|
||||
// screen.ReflashScreen(false)
|
||||
// return nil
|
||||
// case "WRITE", "W":
|
||||
// return saveFile(links[num-1], strings.Join(values, " "))
|
||||
// }
|
||||
|
||||
if v.Address.Gophertype == "7" {
|
||||
err := search(linkurl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if v.Address.IsBinary {
|
||||
return saveFileFromData(v)
|
||||
} else {
|
||||
history.Add(v)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Invalid link id: %s", l)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Invalid link id: %s", l)
|
||||
}
|
||||
updateMainContent()
|
||||
screen.Windows[0].Scrollposition = 0
|
||||
screen.ReflashScreen(true)
|
||||
return nil
|
||||
}
|
||||
// return fmt.Errorf("This method has not been built")
|
||||
// }
|
||||
|
||||
func goHome() error {
|
||||
if options["homeurl"] != "unset" {
|
||||
return goToURL(options["homeurl"])
|
||||
}
|
||||
return fmt.Errorf("No home address has been set")
|
||||
}
|
||||
|
||||
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 doCommandAs(action string, values []string) error {
|
||||
if len(values) < 2 {
|
||||
return fmt.Errorf("%q", values)
|
||||
}
|
||||
|
||||
if values[0] == "." {
|
||||
values[0] = history.Collection[history.Position].Address.Full
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "ADD", "A":
|
||||
err := settings.Bookmarks.Add(values)
|
||||
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(values[0], strings.Join(values[1:], " "))
|
||||
case "SET", "S":
|
||||
if _, ok := options[values[0]]; ok {
|
||||
options[values[0]] = strings.Join(values[1:], " ")
|
||||
return saveConfig()
|
||||
}
|
||||
return fmt.Errorf("Unable to set %s, it does not exist", values[0])
|
||||
}
|
||||
return fmt.Errorf("Unknown command structure")
|
||||
}
|
||||
|
||||
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 checkConfigValue(setting string) error {
|
||||
if val, ok := options[setting]; ok {
|
||||
quickMessage(fmt.Sprintf("%s is set to: %q", setting, val), false)
|
||||
return nil
|
||||
|
||||
}
|
||||
return fmt.Errorf("Unable to check %q, it does not exist", setting)
|
||||
}
|
||||
|
||||
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 clearInput(incError bool) {
|
||||
cui.MoveCursorTo(screen.Height-1, 0)
|
||||
cui.Clear("line")
|
||||
if incError {
|
||||
cui.MoveCursorTo(screen.Height, 0)
|
||||
cui.Clear("line")
|
||||
}
|
||||
}
|
||||
|
||||
func quickMessage(msg string, clearMsg bool) {
|
||||
xPos := screen.Width - 2 - len(msg)
|
||||
if xPos < 2 {
|
||||
xPos = 2
|
||||
}
|
||||
cui.MoveCursorTo(screen.Height, xPos)
|
||||
if clearMsg {
|
||||
cui.Clear("right")
|
||||
} else {
|
||||
fmt.Print("\033[48;5;21m\033[38;5;15m", msg, "\033[0m")
|
||||
}
|
||||
}
|
||||
// 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 := settings.Bookmarks.IniDump()
|
||||
bkmrks := bombadillo.BookMarks.IniDump()
|
||||
// TODO opts becomes a string builder rather than concat
|
||||
opts := "\n[SETTINGS]\n"
|
||||
for k, v := range options {
|
||||
for k, v := range bombadillo.Options {
|
||||
opts += k
|
||||
opts += "="
|
||||
opts += v
|
||||
opts += "\n"
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(userinfo.HomeDir+"/.bombadillo.ini", []byte(bkmrks+opts), 0644)
|
||||
return ioutil.WriteFile(bombadillo.Options["configlocation"] + "/.bombadillo.ini", []byte(bkmrks+opts), 0644)
|
||||
}
|
||||
|
||||
func loadConfig() error {
|
||||
file, err := os.Open(userinfo.HomeDir + "/.bombadillo.ini")
|
||||
file, err := os.Open(bombadillo.Options["configlocation"] + "/.bombadillo.ini")
|
||||
if err != nil {
|
||||
err = saveConfig()
|
||||
if err != nil {
|
||||
|
@ -407,72 +148,29 @@ func loadConfig() error {
|
|||
confparser := config.NewParser(file)
|
||||
settings, _ = confparser.Parse()
|
||||
file.Close()
|
||||
screen.Windows[1].Content = settings.Bookmarks.List()
|
||||
for _, v := range settings.Settings {
|
||||
lowerkey := strings.ToLower(v.Key)
|
||||
if _, ok := options[lowerkey]; ok {
|
||||
options[lowerkey] = v.Value
|
||||
if lowerkey == "configlocation" {
|
||||
// The config should always be stored in home
|
||||
// folder. Users cannot really edit this value.
|
||||
// It is still stored in the ini and as a part
|
||||
// of the options map.
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := bombadillo.Options[lowerkey]; ok {
|
||||
bombadillo.Options[lowerkey] = v.Value
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toggleActiveWindow() {
|
||||
if screen.Windows[1].Show {
|
||||
if screen.Windows[0].Active {
|
||||
screen.Windows[0].Active = false
|
||||
screen.Windows[1].Active = true
|
||||
screen.Activewindow = 1
|
||||
} else {
|
||||
screen.Windows[0].Active = true
|
||||
screen.Windows[1].Active = false
|
||||
screen.Activewindow = 0
|
||||
}
|
||||
screen.Windows[1].DrawWindow()
|
||||
}
|
||||
}
|
||||
|
||||
func displayError(err error) {
|
||||
cui.MoveCursorTo(screen.Height, 0)
|
||||
fmt.Print("\033[41m\033[37m", err, "\033[0m")
|
||||
}
|
||||
|
||||
func initClient() error {
|
||||
history.Position = -1
|
||||
|
||||
screen = cui.NewScreen()
|
||||
bombadillo = MakeClient(" ((( Bombadillo ))) ")
|
||||
cui.SetCharMode()
|
||||
|
||||
screen.AddWindow(2, 1, screen.Height-2, screen.Width, false, false, true)
|
||||
screen.Windows[0].Active = true
|
||||
screen.AddMsgBar(1, " ((( Bombadillo ))) ", " A fun gopher client!", true)
|
||||
bookmarksWidth := 40
|
||||
if screen.Width < 40 {
|
||||
bookmarksWidth = screen.Width
|
||||
}
|
||||
screen.AddWindow(2, screen.Width-bookmarksWidth, screen.Height-2, screen.Width, false, true, false)
|
||||
return loadConfig()
|
||||
}
|
||||
|
||||
func handleResize() {
|
||||
oldh, oldw := screen.Height, screen.Width
|
||||
screen.GetSize()
|
||||
if screen.Height != oldh || screen.Width != oldw {
|
||||
screen.Windows[0].Box.Row2 = screen.Height - 2
|
||||
screen.Windows[0].Box.Col2 = screen.Width
|
||||
bookmarksWidth := 40
|
||||
if screen.Width < 40 {
|
||||
bookmarksWidth = screen.Width
|
||||
}
|
||||
screen.Windows[1].Box.Row2 = screen.Height - 2
|
||||
screen.Windows[1].Box.Col1 = screen.Width - bookmarksWidth
|
||||
screen.Windows[1].Box.Col2 = screen.Width
|
||||
|
||||
screen.DrawAllWindows()
|
||||
screen.DrawMsgBars()
|
||||
screen.ClearCommandArea()
|
||||
}
|
||||
err := loadConfig()
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -480,91 +178,27 @@ func main() {
|
|||
defer cui.Exit()
|
||||
err := initClient()
|
||||
if err != nil {
|
||||
// if we can't initialize the window,
|
||||
// we can't do anything!
|
||||
// if we can't initialize we should bail out
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mainWindow := screen.Windows[0]
|
||||
// Start polling for terminal size changes
|
||||
go bombadillo.GetSize()
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
err = goToURL(os.Args[1])
|
||||
// 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])
|
||||
} else {
|
||||
err = goHome()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
displayError(err)
|
||||
} else {
|
||||
updateMainContent()
|
||||
// Otherwise, load the homeurl
|
||||
// Goroutine so keypresses can be made during
|
||||
// page load
|
||||
go bombadillo.Visit(bombadillo.Options["homeurl"])
|
||||
}
|
||||
|
||||
// Loop indefinitely on user input
|
||||
for {
|
||||
c := cui.Getch()
|
||||
|
||||
handleResize()
|
||||
|
||||
switch c {
|
||||
case 'j', 'J':
|
||||
screen.Windows[screen.Activewindow].ScrollDown()
|
||||
screen.ReflashScreen(false)
|
||||
case 'k', 'K':
|
||||
screen.Windows[screen.Activewindow].ScrollUp()
|
||||
screen.ReflashScreen(false)
|
||||
case 'q', 'Q':
|
||||
cui.Exit()
|
||||
case 'g':
|
||||
screen.Windows[screen.Activewindow].ScrollHome()
|
||||
screen.ReflashScreen(false)
|
||||
case 'G':
|
||||
screen.Windows[screen.Activewindow].ScrollEnd()
|
||||
screen.ReflashScreen(false)
|
||||
case 'd':
|
||||
screen.Windows[screen.Activewindow].PageDown()
|
||||
screen.ReflashScreen(false)
|
||||
case 'u':
|
||||
screen.Windows[screen.Activewindow].PageUp()
|
||||
screen.ReflashScreen(false)
|
||||
case 'b':
|
||||
success := history.GoBack()
|
||||
if success {
|
||||
mainWindow.Scrollposition = 0
|
||||
updateMainContent()
|
||||
screen.ReflashScreen(true)
|
||||
}
|
||||
case 'B':
|
||||
toggleBookmarks()
|
||||
case 'f', 'F':
|
||||
success := history.GoForward()
|
||||
if success {
|
||||
mainWindow.Scrollposition = 0
|
||||
updateMainContent()
|
||||
screen.ReflashScreen(true)
|
||||
}
|
||||
case '\t':
|
||||
toggleActiveWindow()
|
||||
case ':', ' ':
|
||||
cui.MoveCursorTo(screen.Height-1, 0)
|
||||
entry, err := cui.GetLine()
|
||||
if err != nil {
|
||||
displayError(err)
|
||||
}
|
||||
|
||||
// Clear entry line and error line
|
||||
clearInput(true)
|
||||
if entry == "" {
|
||||
continue
|
||||
}
|
||||
parser := cmdparse.NewParser(strings.NewReader(entry))
|
||||
p, err := parser.Parse()
|
||||
if err != nil {
|
||||
displayError(err)
|
||||
} else {
|
||||
err := routeInput(p)
|
||||
if err != nil {
|
||||
displayError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
bombadillo.TakeControlInput()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package main
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + T Y P E S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
type Page struct {
|
||||
WrappedContent []string
|
||||
RawContent string
|
||||
Links []string
|
||||
Location Url
|
||||
ScrollPosition int
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + R E C E I V E R S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + F U N C T I O N S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func MakePage(url Url, content string) Page {
|
||||
p := Page{make([]string, 0), content, make([]string, 0), url, 0}
|
||||
return p
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + T Y P E S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
type Pages struct {
|
||||
Position int
|
||||
Length int
|
||||
History [20]Page
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + R E C E I V E R S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func (p *Pages) NavigateHistory(qty int) error {
|
||||
newPosition := p.Position + qty
|
||||
if newPosition < 0 {
|
||||
return fmt.Errorf("You are already at the beginning of history")
|
||||
} else if newPosition > p.Length - 1 {
|
||||
return fmt.Errorf("Your way is blocked by void, there is nothing forward")
|
||||
}
|
||||
|
||||
p.Position = newPosition
|
||||
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) Render() ([]string, error) {
|
||||
// TODO grab the current page as wrappedContent
|
||||
// May need to handle spacing at end of lines.
|
||||
return []string{}, fmt.Errorf("")
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + F U N C T I O N S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func MakePages() Pages {
|
||||
return Pages{-1, 0, [20]Page{}}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + T Y P E S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
type Url struct {
|
||||
Scheme string
|
||||
Host string
|
||||
Port string
|
||||
Resource string
|
||||
Full string
|
||||
Mime string
|
||||
DownloadOnly bool
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + R E C E I V E R S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
// There are currently no receivers for the Url struct
|
||||
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + 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|gemini):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`)
|
||||
match := re.FindStringSubmatch(u)
|
||||
|
||||
if valid := re.MatchString(u); !valid {
|
||||
return out, fmt.Errorf("Invalid url/unable to parse")
|
||||
}
|
||||
|
||||
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.Mime = match[i]
|
||||
case "resource":
|
||||
out.Resource = match[i]
|
||||
}
|
||||
}
|
||||
|
||||
if out.Scheme == "" {
|
||||
out.Scheme = "gopher"
|
||||
}
|
||||
|
||||
if out.Host == "" {
|
||||
return out, fmt.Errorf("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"
|
||||
} else if out.Scheme == "gemini" && out.Port == "" {
|
||||
out.Port = "1965"
|
||||
}
|
||||
|
||||
if out.Scheme == "gopher" && out.Mime == "" {
|
||||
out.Mime = "0"
|
||||
}
|
||||
|
||||
if out.Mime == "" && (out.Resource == "" || out.Resource == "/") && out.Scheme == "gopher" {
|
||||
out.Mime = "1"
|
||||
}
|
||||
|
||||
if out.Mime == "7" && strings.Contains(out.Resource, "\t") {
|
||||
out.Mime = "1"
|
||||
}
|
||||
|
||||
if out.Scheme == "gopher" {
|
||||
switch out.Mime {
|
||||
case "1", "0", "h", "7":
|
||||
out.DownloadOnly = false
|
||||
default:
|
||||
out.DownloadOnly = true
|
||||
}
|
||||
} else {
|
||||
out.Resource = fmt.Sprintf("%s%s", out.Mime, out.Resource)
|
||||
out.Mime = ""
|
||||
}
|
||||
|
||||
out.Full = out.Scheme + "://" + out.Host + ":" + out.Port + "/" + out.Mime + out.Resource
|
||||
|
||||
return out, nil
|
||||
}
|
Loading…
Reference in New Issue