2019-09-10 02:35:16 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2019-09-14 05:56:38 +00:00
|
|
|
// "os/user"
|
2019-09-10 02:35:16 +00:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"tildegit.org/sloum/bombadillo/cmdparse"
|
|
|
|
"tildegit.org/sloum/bombadillo/cui"
|
2019-09-11 03:13:30 +00:00
|
|
|
// "tildegit.org/sloum/bombadillo/gemini"
|
2019-09-12 05:53:36 +00:00
|
|
|
"tildegit.org/sloum/bombadillo/gopher"
|
2019-09-11 03:13:30 +00:00
|
|
|
"tildegit.org/sloum/bombadillo/http"
|
|
|
|
"tildegit.org/sloum/bombadillo/telnet"
|
2019-09-10 02:35:16 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
//------------------------------------------------\\
|
|
|
|
// + + + 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() {
|
2019-09-14 05:56:38 +00:00
|
|
|
c.SetMessage("Initializing...", false)
|
|
|
|
c.DrawMessage()
|
2019-09-10 02:35:16 +00:00
|
|
|
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
|
|
|
|
|
2019-09-13 03:57:48 +00:00
|
|
|
|
2019-09-10 02:35:16 +00:00
|
|
|
if redraw {
|
|
|
|
c.Draw()
|
|
|
|
}
|
|
|
|
|
2019-09-13 03:57:48 +00:00
|
|
|
time.Sleep(500 * time.Millisecond)
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) Draw() {
|
2019-09-12 05:53:36 +00:00
|
|
|
var screen strings.Builder
|
|
|
|
screen.Grow(c.Height * c.Width)
|
2019-09-14 05:56:38 +00:00
|
|
|
screen.WriteString(c.TopBar.Render(c.Width, c.Options["theme"]))
|
2019-09-12 05:53:36 +00:00
|
|
|
screen.WriteString("\n")
|
2019-09-13 03:57:48 +00:00
|
|
|
pageContent := c.PageState.Render(c.Height, c.Width)
|
2019-09-14 05:56:38 +00:00
|
|
|
if c.Options["theme"] == "inverse" {
|
|
|
|
screen.WriteString("\033[7m")
|
|
|
|
}
|
2019-09-12 05:53:36 +00:00
|
|
|
if c.BookMarks.IsOpen {
|
|
|
|
bm := c.BookMarks.Render(c.Width, c.Height)
|
2019-09-14 05:56:38 +00:00
|
|
|
// TODO remove this hard coded value
|
|
|
|
bmWidth := len([]rune(bm[0]))
|
2019-09-13 03:57:48 +00:00
|
|
|
for i := 0; i < c.Height - 3; i++ {
|
|
|
|
if c.Width > bmWidth {
|
2019-09-14 05:56:38 +00:00
|
|
|
contentWidth := c.Width - bmWidth
|
|
|
|
if i < len(pageContent) {
|
2019-09-13 03:57:48 +00:00
|
|
|
screen.WriteString(fmt.Sprintf("%-*.*s", contentWidth, contentWidth, pageContent[i]))
|
|
|
|
} else {
|
|
|
|
screen.WriteString(fmt.Sprintf("%-*.*s", contentWidth, contentWidth, " "))
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 05:56:38 +00:00
|
|
|
|
2019-09-12 05:53:36 +00:00
|
|
|
screen.WriteString(bm[i])
|
|
|
|
screen.WriteString("\n")
|
|
|
|
}
|
|
|
|
} else {
|
2019-09-13 03:57:48 +00:00
|
|
|
for i := 0; i < c.Height - 3; i++ {
|
|
|
|
if i < len(pageContent) - 1 {
|
2019-09-14 05:56:38 +00:00
|
|
|
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width, c.Width, pageContent[i]))
|
2019-09-13 03:57:48 +00:00
|
|
|
screen.WriteString("\n")
|
|
|
|
} else {
|
|
|
|
screen.WriteString(fmt.Sprintf("%*s", c.Width, " "))
|
|
|
|
screen.WriteString("\n")
|
|
|
|
}
|
2019-09-12 05:53:36 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-14 05:56:38 +00:00
|
|
|
screen.WriteString("\033[0m")
|
2019-09-12 05:53:36 +00:00
|
|
|
screen.WriteString("\n") // for the input line
|
2019-09-14 05:56:38 +00:00
|
|
|
screen.WriteString(c.FootBar.Render(c.Width, c.PageState.Position, c.Options["theme"]))
|
2019-09-12 05:53:36 +00:00
|
|
|
cui.Clear("screen")
|
|
|
|
cui.MoveCursorTo(0,0)
|
|
|
|
fmt.Print(screen.String())
|
2019-09-13 03:57:48 +00:00
|
|
|
c.DrawMessage()
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) TakeControlInput() {
|
|
|
|
input := cui.Getch()
|
|
|
|
|
|
|
|
switch input {
|
|
|
|
case 'j', 'J':
|
|
|
|
// scroll down one line
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
c.Scroll(1)
|
|
|
|
case 'k', 'K':
|
|
|
|
// scroll up one line
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
c.Scroll(-1)
|
|
|
|
case 'q', 'Q':
|
|
|
|
// quite bombadillo
|
|
|
|
cui.Exit()
|
|
|
|
case 'g':
|
|
|
|
// scroll to top
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
c.Scroll(-len(c.PageState.History[c.PageState.Position].WrappedContent))
|
|
|
|
case 'G':
|
|
|
|
// scroll to bottom
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
c.Scroll(len(c.PageState.History[c.PageState.Position].WrappedContent))
|
|
|
|
case 'd':
|
|
|
|
// scroll down 75%
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
distance := c.Height - c.Height / 4
|
|
|
|
c.Scroll(distance)
|
|
|
|
case 'u':
|
|
|
|
// scroll up 75%
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
distance := c.Height - c.Height / 4
|
|
|
|
c.Scroll(-distance)
|
|
|
|
case 'b':
|
|
|
|
// go back
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
err := c.PageState.NavigateHistory(-1)
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), false)
|
|
|
|
c.DrawMessage()
|
|
|
|
} else {
|
2019-09-13 03:57:48 +00:00
|
|
|
c.SetHeaderUrl()
|
2019-09-10 02:35:16 +00:00
|
|
|
c.Draw()
|
|
|
|
}
|
|
|
|
case 'B':
|
|
|
|
// open the bookmarks browser
|
|
|
|
c.BookMarks.ToggleOpen()
|
|
|
|
c.Draw()
|
|
|
|
case 'f', 'F':
|
|
|
|
// go forward
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-10 02:35:16 +00:00
|
|
|
err := c.PageState.NavigateHistory(1)
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), false)
|
|
|
|
c.DrawMessage()
|
|
|
|
} else {
|
2019-09-13 03:57:48 +00:00
|
|
|
c.SetHeaderUrl()
|
2019-09-10 02:35:16 +00:00
|
|
|
c.Draw()
|
|
|
|
}
|
|
|
|
case '\t':
|
|
|
|
// Toggle bookmark browser focus on/off
|
|
|
|
c.BookMarks.ToggleFocused()
|
|
|
|
c.Draw()
|
|
|
|
case ':', ' ':
|
|
|
|
// Process a command
|
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
2019-09-14 05:56:38 +00:00
|
|
|
if c.Options["theme"] == "normal" {
|
|
|
|
fmt.Printf("\033[7m%*.*s\r", c.Width, c.Width, "")
|
|
|
|
}
|
2019-09-10 02:35:16 +00:00
|
|
|
entry, err := cui.GetLine()
|
|
|
|
c.ClearMessageLine()
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
break
|
|
|
|
} else if strings.TrimSpace(entry) == "" {
|
2019-09-14 05:56:38 +00:00
|
|
|
c.DrawMessage()
|
2019-09-10 02:35:16 +00:00
|
|
|
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:
|
2019-09-14 05:56:38 +00:00
|
|
|
c.doLinkCommand(com.Action, com.Target)
|
2019-09-10 02:35:16 +00:00
|
|
|
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":
|
2019-09-11 03:13:30 +00:00
|
|
|
msg, err := c.BookMarks.Add(values)
|
2019-09-10 02:35:16 +00:00
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
return
|
2019-09-11 03:13:30 +00:00
|
|
|
} else {
|
|
|
|
c.SetMessage(msg, false)
|
|
|
|
c.DrawMessage()
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2019-09-14 05:56:38 +00:00
|
|
|
c.Draw()
|
|
|
|
c.SetMessage(fmt.Sprintf("%s is now set to %q", values[0], c.Options[values[0]]), false)
|
2019-09-10 02:35:16 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-09-14 05:56:38 +00:00
|
|
|
func (c *client) doLinkCommand(action, target string) {
|
|
|
|
num, err := strconv.Atoi(target)
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(fmt.Sprintf("Expected number, got %q", target), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
}
|
|
|
|
|
|
|
|
switch action {
|
|
|
|
case "DELETE", "D":
|
|
|
|
msg, err := c.BookMarks.Delete(num)
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
c.SetMessage(msg, false)
|
|
|
|
c.DrawMessage()
|
|
|
|
}
|
|
|
|
|
|
|
|
err = saveConfig()
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage("Error saving bookmark deletion to file", true)
|
|
|
|
c.DrawMessage()
|
|
|
|
}
|
|
|
|
if c.BookMarks.IsOpen {
|
|
|
|
c.Draw()
|
|
|
|
}
|
|
|
|
case "BOOKMARKS", "B":
|
|
|
|
if num > len(c.BookMarks.Links)-1 {
|
|
|
|
c.SetMessage(fmt.Sprintf("There is no bookmark with ID %d", num), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.Visit(c.BookMarks.Links[num])
|
|
|
|
default:
|
|
|
|
c.SetMessage(fmt.Sprintf("Action %q does not exist for target %q", action, target), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-09-10 02:35:16 +00:00
|
|
|
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":
|
2019-09-11 03:13:30 +00:00
|
|
|
c.Visit(u.Full)
|
2019-09-10 02:35:16 +00:00
|
|
|
default:
|
|
|
|
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) Scroll(amount int) {
|
2019-09-14 05:56:38 +00:00
|
|
|
var percentRead int
|
2019-09-10 02:35:16 +00:00
|
|
|
page := c.PageState.History[c.PageState.Position]
|
2019-09-13 03:57:48 +00:00
|
|
|
bottom := len(page.WrappedContent) - c.Height + 3 // 3 for the three bars: top, msg, bottom
|
2019-09-10 02:35:16 +00:00
|
|
|
if amount < 0 && page.ScrollPosition == 0 {
|
|
|
|
c.SetMessage("You are already at the top", false)
|
|
|
|
c.DrawMessage()
|
|
|
|
fmt.Print("\a")
|
|
|
|
return
|
2019-09-13 03:57:48 +00:00
|
|
|
} else if (amount > 0 && page.ScrollPosition == bottom) || bottom < 0 {
|
2019-09-14 05:56:38 +00:00
|
|
|
c.FootBar.SetPercentRead(100)
|
2019-09-10 02:35:16 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-09-13 03:57:48 +00:00
|
|
|
c.PageState.History[c.PageState.Position].ScrollPosition = newScrollPosition
|
2019-09-14 05:56:38 +00:00
|
|
|
|
|
|
|
if len(page.WrappedContent) < c.Height - 3 {
|
|
|
|
percentRead = 100
|
|
|
|
} else {
|
|
|
|
percentRead = int(float32(newScrollPosition + c.Height - 3) / float32(len(page.WrappedContent)) * 100.0)
|
|
|
|
}
|
|
|
|
c.FootBar.SetPercentRead(percentRead)
|
|
|
|
|
2019-09-10 02:35:16 +00:00
|
|
|
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 {
|
2019-09-14 05:56:38 +00:00
|
|
|
leadIn = "\033[91m"
|
|
|
|
leadOut = "\033[0m"
|
|
|
|
|
|
|
|
if c.Options["theme"] == "normal" {
|
|
|
|
leadIn = "\033[101;7m"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Options["theme"] == "normal" {
|
|
|
|
leadIn = "\033[7m"
|
2019-09-10 02:35:16 +00:00
|
|
|
leadOut = "\033[0m"
|
|
|
|
}
|
|
|
|
|
2019-09-14 05:56:38 +00:00
|
|
|
c.Message = fmt.Sprintf("%s%-*.*s%s", leadIn, c.Width, c.Width, msg, leadOut)
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) DrawMessage() {
|
|
|
|
c.ClearMessageLine()
|
|
|
|
cui.MoveCursorTo(c.Height-1, 0)
|
2019-09-14 05:56:38 +00:00
|
|
|
fmt.Printf("%s", c.Message)
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) ClearMessage() {
|
2019-09-14 05:56:38 +00:00
|
|
|
c.SetMessage("", false)
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2019-09-13 03:57:48 +00:00
|
|
|
}
|
2019-09-10 02:35:16 +00:00
|
|
|
|
2019-09-13 03:57:48 +00:00
|
|
|
func (c *client) SetHeaderUrl() {
|
|
|
|
if c.PageState.Length > 0 {
|
|
|
|
u := c.PageState.History[c.PageState.Position].Location.Full
|
|
|
|
c.TopBar.url = u
|
|
|
|
} else {
|
|
|
|
c.TopBar.url = ""
|
|
|
|
}
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) Visit(url string) {
|
2019-09-11 03:23:44 +00:00
|
|
|
// TODO both gemini and gopher should return a string
|
|
|
|
// The wrap lines function in cui needs to be rewritten
|
2019-09-10 02:35:16 +00:00
|
|
|
u, err := MakeUrl(url)
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch u.Scheme {
|
|
|
|
case "gopher":
|
2019-09-13 03:57:48 +00:00
|
|
|
c.SetMessage("Loading...", false)
|
|
|
|
c.DrawMessage()
|
2019-09-12 05:53:36 +00:00
|
|
|
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)
|
2019-09-14 05:56:38 +00:00
|
|
|
c.Scroll(0) // to update percent read
|
2019-09-13 03:57:48 +00:00
|
|
|
c.ClearMessage()
|
|
|
|
c.ClearMessageLine()
|
|
|
|
c.SetHeaderUrl()
|
2019-09-12 05:53:36 +00:00
|
|
|
c.Draw()
|
2019-09-10 02:35:16 +00:00
|
|
|
case "gemini":
|
|
|
|
// TODO send over to gemini request
|
2019-09-14 05:56:38 +00:00
|
|
|
c.SetMessage("Bombadillo has not mastered Gemini yet, check back soon", false)
|
2019-09-12 05:53:36 +00:00
|
|
|
c.DrawMessage()
|
2019-09-11 03:13:30 +00:00
|
|
|
case "telnet":
|
|
|
|
c.SetMessage("Attempting to start telnet session", false)
|
|
|
|
c.DrawMessage()
|
|
|
|
msg, err := telnet.StartSession(u.Host, u.Port)
|
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), true)
|
|
|
|
c.DrawMessage()
|
|
|
|
} else {
|
|
|
|
c.SetMessage(msg, true)
|
|
|
|
c.DrawMessage()
|
|
|
|
}
|
|
|
|
c.Draw()
|
2019-09-10 02:35:16 +00:00
|
|
|
case "http", "https":
|
|
|
|
c.SetMessage("Attempting to open in web browser", false)
|
|
|
|
c.DrawMessage()
|
|
|
|
if strings.ToUpper(c.Options["openhttp"]) == "TRUE" {
|
2019-09-11 03:13:30 +00:00
|
|
|
msg, err := http.OpenInBrowser(u.Full)
|
2019-09-10 02:35:16 +00:00
|
|
|
if err != nil {
|
|
|
|
c.SetMessage(err.Error(), true)
|
|
|
|
} else {
|
2019-09-11 03:13:30 +00:00
|
|
|
c.SetMessage(msg, false)
|
2019-09-10 02:35:16 +00:00
|
|
|
}
|
|
|
|
c.DrawMessage()
|
|
|
|
} else {
|
2019-09-11 03:13:30 +00:00
|
|
|
c.SetMessage("'openhttp' is not set to true, cannot open web link", false)
|
2019-09-10 02:35:16 +00:00
|
|
|
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 {
|
2019-09-14 05:56:38 +00:00
|
|
|
// var userinfo, _ = user.Current()
|
|
|
|
// var options = map[string]string{
|
|
|
|
// "homeurl": "gopher://colorfield.space:70/1/bombadillo-info",
|
|
|
|
// "savelocation": userinfo.HomeDir,
|
|
|
|
// "searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
|
|
|
|
// "openhttp": "false",
|
|
|
|
// "httpbrowser": "lynx",
|
|
|
|
// "configlocation": userinfo.HomeDir,
|
|
|
|
// }
|
|
|
|
c := client{0, 0, defaultOptions, "", MakePages(), MakeBookmarks(), MakeHeadbar(name), MakeFootbar()}
|
2019-09-10 02:35:16 +00:00
|
|
|
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
|
|
|
|
}
|