2.1.0 Release - Merge branch 'develop' of sloum/bombadillo into master
This commit is contained in:
commit
b4e077869f
38
bombadillo.1
38
bombadillo.1
|
@ -4,8 +4,7 @@
|
|||
.SH SYNOPSIS
|
||||
.nf
|
||||
.fam C
|
||||
\fBbombadillo\fP [\fIurl\fP]
|
||||
\fBbombadillo\fP [\fBOPTION\fP]
|
||||
\fBbombadillo\fP [\fIoptions\fP] [\fIurl\fP]
|
||||
.fam T
|
||||
.fi
|
||||
.SH DESCRIPTION
|
||||
|
@ -15,12 +14,16 @@
|
|||
.SH OPTIONS
|
||||
.TP
|
||||
.B
|
||||
\fB-v\fP
|
||||
Display version information and exit.
|
||||
\fB-h\fP
|
||||
Display usage help and exit. Provides a list of all command line options with a short description and exits.
|
||||
.TP
|
||||
.B
|
||||
\fB-h\fP
|
||||
Usage help. Displays all command line options with a short description.
|
||||
\fB-t\fP
|
||||
Set the window title to 'Bombadillo'. Can be used in a GUI environment, however not all terminals support this feature.
|
||||
.TP
|
||||
.B
|
||||
\fB-v\fP
|
||||
Display version information and exit.
|
||||
.SH PROTOCOL SUPPORT
|
||||
All of the below protocols are supported. With the exception of gopher, the protocol name must be present as the scheme component of a url in the form of \fI[protocol]://[the rest of the url]\fP.
|
||||
.TP
|
||||
|
@ -38,7 +41,7 @@ Basic support is provided for the finger protocol. The format is: \fIfinger://[[
|
|||
.TP
|
||||
.B
|
||||
local
|
||||
Local is similar to the \fIfile\fP protocol used in web browsers or the like, with a smaller set of features. Users can use the local scheme to view files on their local system. Directories are supported as viewable text object as well as any files. Wildcards and globbing are not supported. Using \fI~\fP to represent a user's home directory, as well as relative paths, are supported.
|
||||
Local is similar to the \fIfile\fP protocol used in web browsers or the like, with a smaller set of features. Users can use the local scheme to view files on their local system. Directories are supported as viewable text object as well as any files. Wildcards and globbing are not supported. Using \fI~\fP to represent a user's home directory, as well as relative paths, are supported. The \fIcolor\fP theme has no effect on this protocol and all terminal escape sequences will be rendered to the screen literally.
|
||||
.TP
|
||||
.B
|
||||
telnet
|
||||
|
@ -88,6 +91,13 @@ k
|
|||
Scroll up a single line.
|
||||
.TP
|
||||
.B
|
||||
n
|
||||
Jump to next found text item.
|
||||
.TP
|
||||
.B
|
||||
Jump to previous found text item.
|
||||
.TP
|
||||
.B
|
||||
q
|
||||
Quit \fBbombadillo\fP.
|
||||
.TP
|
||||
|
@ -100,6 +110,10 @@ u
|
|||
Scroll up an amount corresponding to 75% of your terminal window height in the current document.
|
||||
.TP
|
||||
.B
|
||||
/
|
||||
Search for text within current document. / followed by a text query will highlight and allow navigation of found text. / with an empty query will clear the current query.
|
||||
.TP
|
||||
.B
|
||||
<tab>
|
||||
Toggle the scroll focus between the bookmarks panel and the document panel. Only has an effect if the bookmarks panel is open.
|
||||
.TP
|
||||
|
@ -150,7 +164,7 @@ check [setting name]
|
|||
Displays the current value for a given configuration setting. \fIc\fP can be used instead of the full \fIcheck\fP.
|
||||
.TP
|
||||
.B
|
||||
delete [bookmark id]]
|
||||
delete [bookmark id]
|
||||
Deletes the bookmark matching the bookmark id. \fId\fP can be used instead of the full \fIdelete\fP.
|
||||
.TP
|
||||
.B
|
||||
|
@ -186,7 +200,7 @@ search [keywords\.\.\.]
|
|||
Submits a search to the search engine set by the \fIsearchengine\fP setting, with the query being the provided keyword(s).
|
||||
.TP
|
||||
.B
|
||||
set [setting name]
|
||||
set [setting name] [value]
|
||||
Sets the value for a given configuration setting. \fIs\fP can be used instead of the full \fIset\fP.
|
||||
.TP
|
||||
.B
|
||||
|
@ -210,6 +224,10 @@ configlocation
|
|||
The path to the directory that the \fI.bombadillo.ini\fP configuration file is stored in. This is a \fBread only\fP setting and cannot be changed with the \fIset\fP command, but it can be read with the \fIcheck\fP command.
|
||||
.TP
|
||||
.B
|
||||
defaultscheme
|
||||
The scheme that should be used when no scheme is present in a given URL. \fIgopher\fP, \fIgemini\fP, \fIhttp\fP, and \fIhttps\fP are valid values.
|
||||
.TP
|
||||
.B
|
||||
homeurl
|
||||
The url that \fBbombadillo\fP navigates to when the program loads or when the \fIhome\fP or \fIh\fP LINE COMMAND is issued. This should be a valid url. If a scheme/protocol is not included, gopher will be assumed.
|
||||
.TP
|
||||
|
@ -227,7 +245,7 @@ Tells the browser what command to use to start a telnet session. Should be a val
|
|||
.TP
|
||||
.B
|
||||
theme
|
||||
Can toggle between visual modes. Valid values are \fInormal\fP and \fIinverse\fP. When set to inverse, the terminal color mode is inverted.
|
||||
Can toggle between visual modes. Valid values are \fInormal\fP, \fIcolor\fP, and \fIinverse\fP. When set to inverse, the normal mode colors are inverted. Both normal and inverse modes filter out terminal escape sequences. When set to color, Bombadillo will render terminal escape sequences representing colors when it finds them in documents.
|
||||
.TP
|
||||
.B
|
||||
tlscertificate
|
||||
|
|
|
@ -5,6 +5,6 @@ GenericName=Non-Web Browser
|
|||
Comment=View gopher, gemini, finger, telnet, http(s) sites over the internet
|
||||
Terminal=true
|
||||
Categories=Network;WebBrowser;ConsoleOnly;
|
||||
Exec=bombadillo %U
|
||||
Exec=bombadillo -t %u
|
||||
Icon=bombadillo-icon
|
||||
MimeType=x-scheme-handler/gopher;x-scheme-handler/gemini;x-scheme-handler/finger;
|
||||
|
|
165
client.go
165
client.go
|
@ -47,8 +47,7 @@ func (c *client) GetSizeOnce() {
|
|||
cmd.Stdin = os.Stdin
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error: Unable to retrieve terminal size")
|
||||
os.Exit(5)
|
||||
cui.Exit(5, "Fatal error: Unable to retrieve terminal size")
|
||||
}
|
||||
var h, w int
|
||||
_, _ = fmt.Sscan(string(out), &h, &w)
|
||||
|
@ -66,8 +65,7 @@ func (c *client) GetSize() {
|
|||
cmd.Stdin = os.Stdin
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Println("Fatal error: Unable to retrieve terminal size")
|
||||
os.Exit(5)
|
||||
cui.Exit(5, "Fatal error: Unable to retrieve terminal size")
|
||||
}
|
||||
var h, w int
|
||||
_, _ = fmt.Sscan(string(out), &h, &w)
|
||||
|
@ -88,10 +86,12 @@ func (c *client) Draw() {
|
|||
screen.WriteString("\033[0m")
|
||||
screen.WriteString(c.TopBar.Render(c.Width, c.Options["theme"]))
|
||||
screen.WriteString("\n")
|
||||
pageContent := c.PageState.Render(c.Height, c.Width-1)
|
||||
pageContent := c.PageState.Render(c.Height, c.Width-1, (c.Options["theme"] == "color"))
|
||||
var re *regexp.Regexp
|
||||
if c.Options["theme"] == "inverse" {
|
||||
screen.WriteString("\033[7m")
|
||||
}
|
||||
re = regexp.MustCompile(`\033\[(?:\d*;?)+[A-Za-z]`)
|
||||
if c.BookMarks.IsOpen {
|
||||
bm := c.BookMarks.Render(c.Width, c.Height)
|
||||
bmWidth := len([]rune(bm[0]))
|
||||
|
@ -99,7 +99,14 @@ func (c *client) Draw() {
|
|||
if c.Width > bmWidth {
|
||||
contentWidth := c.Width - bmWidth
|
||||
if i < len(pageContent) {
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", contentWidth, contentWidth, pageContent[i]))
|
||||
extra := 0
|
||||
if c.Options["theme"] == "color" {
|
||||
escapes := re.FindAllString(pageContent[i], -1)
|
||||
for _, esc := range escapes {
|
||||
extra += len(esc)
|
||||
}
|
||||
}
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", contentWidth+extra, contentWidth+extra, pageContent[i]))
|
||||
} else {
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", contentWidth, contentWidth, " "))
|
||||
}
|
||||
|
@ -112,6 +119,9 @@ func (c *client) Draw() {
|
|||
screen.WriteString("\033[2m")
|
||||
}
|
||||
|
||||
if c.Options["theme"] == "color" {
|
||||
screen.WriteString("\033[0m")
|
||||
}
|
||||
screen.WriteString(bm[i])
|
||||
|
||||
if c.Options["theme"] == "inverse" && !c.BookMarks.IsFocused {
|
||||
|
@ -125,7 +135,12 @@ func (c *client) Draw() {
|
|||
} else {
|
||||
for i := 0; i < c.Height-3; i++ {
|
||||
if i < len(pageContent) {
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width, c.Width, pageContent[i]))
|
||||
extra := 0
|
||||
escapes := re.FindAllString(pageContent[i], -1)
|
||||
for _, esc := range escapes {
|
||||
extra += len(esc)
|
||||
}
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width+extra, c.Width+extra, pageContent[i]))
|
||||
screen.WriteString("\n")
|
||||
} else {
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width, c.Width, " "))
|
||||
|
@ -157,7 +172,7 @@ func (c *client) TakeControlInput() {
|
|||
c.Scroll(-1)
|
||||
case 'q', 'Q':
|
||||
// quit bombadillo
|
||||
cui.Exit()
|
||||
cui.Exit(0, "")
|
||||
case 'g':
|
||||
// scroll to top
|
||||
c.ClearMessage()
|
||||
|
@ -217,14 +232,53 @@ func (c *client) TakeControlInput() {
|
|||
// Toggle bookmark browser focus on/off
|
||||
c.BookMarks.ToggleFocused()
|
||||
c.Draw()
|
||||
case 'n':
|
||||
// Next search item
|
||||
c.ClearMessage()
|
||||
err := c.NextSearchItem(1)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), false)
|
||||
c.DrawMessage()
|
||||
}
|
||||
case 'N':
|
||||
// Previous search item
|
||||
c.ClearMessage()
|
||||
err := c.NextSearchItem(-1)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), false)
|
||||
c.DrawMessage()
|
||||
}
|
||||
case '/':
|
||||
// Search for text
|
||||
c.ClearMessage()
|
||||
c.ClearMessageLine()
|
||||
if c.Options["theme"] == "normal" || c.Options["theme"] == "color" {
|
||||
fmt.Printf("\033[7m%*.*s\r", c.Width, c.Width, "")
|
||||
}
|
||||
entry, err := cui.GetLine("/")
|
||||
c.ClearMessageLine()
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
break
|
||||
}
|
||||
err = c.find(entry)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
}
|
||||
err = c.NextSearchItem(0)
|
||||
if err != nil {
|
||||
c.Draw()
|
||||
}
|
||||
case ':', ' ':
|
||||
// Process a command
|
||||
c.ClearMessage()
|
||||
c.ClearMessageLine()
|
||||
if c.Options["theme"] == "normal" {
|
||||
if c.Options["theme"] == "normal" || c.Options["theme"] == "color" {
|
||||
fmt.Printf("\033[7m%*.*s\r", c.Width, c.Width, "")
|
||||
}
|
||||
entry, err := cui.GetLine()
|
||||
entry, err := cui.GetLine(": ")
|
||||
c.ClearMessageLine()
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
|
@ -278,7 +332,7 @@ func (c *client) simpleCommand(action string) {
|
|||
action = strings.ToUpper(action)
|
||||
switch action {
|
||||
case "Q", "QUIT":
|
||||
cui.Exit()
|
||||
cui.Exit(0, "")
|
||||
case "H", "HOME":
|
||||
if c.Options["homeurl"] != "unset" {
|
||||
go c.Visit(c.Options["homeurl"])
|
||||
|
@ -622,11 +676,11 @@ func (c *client) search(query, url, question string) {
|
|||
if query == "" {
|
||||
c.ClearMessage()
|
||||
c.ClearMessageLine()
|
||||
if c.Options["theme"] == "normal" {
|
||||
if c.Options["theme"] == "normal" || c.Options["theme"] == "color" {
|
||||
fmt.Printf("\033[7m%*.*s\r", c.Width, c.Width, "")
|
||||
}
|
||||
fmt.Print(question)
|
||||
entry, err = cui.GetLine()
|
||||
entry, err = cui.GetLine("? ")
|
||||
c.ClearMessageLine()
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
|
@ -728,9 +782,13 @@ func (c *client) ReloadPage() error {
|
|||
return fmt.Errorf("There is no page to reload")
|
||||
}
|
||||
url := c.PageState.History[c.PageState.Position].Location.Full
|
||||
err := c.PageState.NavigateHistory(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
if c.PageState.Position == 0 {
|
||||
c.PageState.Position--
|
||||
} else {
|
||||
err := c.PageState.NavigateHistory(-1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
length := c.PageState.Length
|
||||
c.Visit(url)
|
||||
|
@ -771,7 +829,7 @@ func (c *client) DrawMessage() {
|
|||
|
||||
func (c *client) RenderMessage() string {
|
||||
leadIn, leadOut := "", ""
|
||||
if c.Options["theme"] == "normal" {
|
||||
if c.Options["theme"] == "normal" || c.Options["theme"] == "color" {
|
||||
leadIn = "\033[7m"
|
||||
leadOut = "\033[0m"
|
||||
}
|
||||
|
@ -780,7 +838,7 @@ func (c *client) RenderMessage() string {
|
|||
leadIn = "\033[31;1m"
|
||||
leadOut = "\033[0m"
|
||||
|
||||
if c.Options["theme"] == "normal" {
|
||||
if c.Options["theme"] == "normal" || c.Options["theme"] == "color" {
|
||||
leadIn = "\033[41;1;7m"
|
||||
}
|
||||
}
|
||||
|
@ -889,7 +947,7 @@ func (c *client) handleGopher(u Url) {
|
|||
return
|
||||
}
|
||||
pg := MakePage(u, content, links)
|
||||
pg.WrapContent(c.Width - 1)
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
c.ClearMessage()
|
||||
|
@ -912,7 +970,7 @@ func (c *client) handleGemini(u Url) {
|
|||
case 2:
|
||||
if capsule.MimeMaj == "text" {
|
||||
pg := MakePage(u, capsule.Content, capsule.Links)
|
||||
pg.WrapContent(c.Width - 1)
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
c.ClearMessage()
|
||||
|
@ -960,7 +1018,7 @@ func (c *client) handleLocal(u Url) {
|
|||
return
|
||||
}
|
||||
pg := MakePage(u, content, links)
|
||||
pg.WrapContent(c.Width - 1)
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
c.ClearMessage()
|
||||
|
@ -976,7 +1034,7 @@ func (c *client) handleFinger(u Url) {
|
|||
return
|
||||
}
|
||||
pg := MakePage(u, content, []string{})
|
||||
pg.WrapContent(c.Width - 1)
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
c.ClearMessage()
|
||||
|
@ -996,7 +1054,7 @@ func (c *client) handleWeb(u Url) {
|
|||
return
|
||||
}
|
||||
pg := MakePage(u, page.Content, page.Links)
|
||||
pg.WrapContent(c.Width - 1)
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
c.ClearMessage()
|
||||
|
@ -1029,6 +1087,67 @@ func (c *client) handleWeb(u Url) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *client) find(s string) error {
|
||||
c.PageState.History[c.PageState.Position].SearchTerm = s
|
||||
c.PageState.History[c.PageState.Position].FindText()
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
if len(c.PageState.History[c.PageState.Position].FoundLinkLines) == 0 {
|
||||
return fmt.Errorf("No text matching %q was found", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) NextSearchItem(dir int) error {
|
||||
page := c.PageState.History[c.PageState.Position]
|
||||
if len(page.FoundLinkLines) == 0 {
|
||||
return fmt.Errorf("The search is over before it has begun")
|
||||
}
|
||||
c.PageState.History[c.PageState.Position].SearchIndex += dir
|
||||
page.SearchIndex += dir
|
||||
if page.SearchIndex < 0 {
|
||||
c.PageState.History[c.PageState.Position].SearchIndex = 0
|
||||
page.SearchIndex = 0
|
||||
}
|
||||
|
||||
if page.SearchIndex >= len(page.FoundLinkLines) {
|
||||
c.PageState.History[c.PageState.Position].SearchIndex = len(page.FoundLinkLines) - 1
|
||||
return fmt.Errorf("The search path goes no further")
|
||||
} else if page.SearchIndex < 0 {
|
||||
c.PageState.History[c.PageState.Position].SearchIndex = 0
|
||||
return fmt.Errorf("You are at the beginning of the search path")
|
||||
}
|
||||
|
||||
diff := page.FoundLinkLines[page.SearchIndex] - page.ScrollPosition
|
||||
c.ScrollForSearch(diff)
|
||||
c.Draw()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) ScrollForSearch(amount int) {
|
||||
var percentRead int
|
||||
page := c.PageState.History[c.PageState.Position]
|
||||
bottom := len(page.WrappedContent) - c.Height + 3 // 3 for the three bars: top, msg, bottom
|
||||
|
||||
newScrollPosition := page.ScrollPosition + amount
|
||||
if newScrollPosition < 0 {
|
||||
newScrollPosition = 0
|
||||
} else if newScrollPosition > bottom {
|
||||
newScrollPosition = bottom
|
||||
}
|
||||
|
||||
c.PageState.History[c.PageState.Position].ScrollPosition = newScrollPosition
|
||||
|
||||
if len(page.WrappedContent) < c.Height-3 {
|
||||
percentRead = 100
|
||||
} else {
|
||||
percentRead = int(float32(newScrollPosition+c.Height-3) / float32(len(page.WrappedContent)) * 100.0)
|
||||
}
|
||||
c.FootBar.SetPercentRead(percentRead)
|
||||
c.Draw()
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + F U N C T I O N S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
|
12
cui/cui.go
12
cui/cui.go
|
@ -44,9 +44,13 @@ func moveCursorToward(dir string, amount int) {
|
|||
}
|
||||
|
||||
// Exit performs cleanup operations before exiting the application
|
||||
func Exit() {
|
||||
func Exit(exitCode int, msg string) {
|
||||
CleanupTerm()
|
||||
os.Exit(0)
|
||||
if msg != "" {
|
||||
fmt.Print(msg, "\n")
|
||||
}
|
||||
fmt.Print("\033[23;0t") // Restore window title from terminal stack
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
// InitTerm sets the terminal modes appropriate for Bombadillo
|
||||
|
@ -93,11 +97,11 @@ func Getch() rune {
|
|||
return char
|
||||
}
|
||||
|
||||
func GetLine() (string, error) {
|
||||
func GetLine(prefix string) (string, error) {
|
||||
SetLineMode()
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print(": ")
|
||||
fmt.Print(prefix)
|
||||
text, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -50,7 +50,8 @@ var defaultOptions = map[string]string{
|
|||
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
|
||||
"telnetcommand": "telnet",
|
||||
"configlocation": xdgConfigPath(),
|
||||
"theme": "normal", // "normal", "inverted"
|
||||
"defaultscheme": "gopher", // "gopher", "gemini", "http", "https"
|
||||
"theme": "normal", // "normal", "inverted", "color"
|
||||
"tlscertificate": "",
|
||||
"tlskey": "",
|
||||
"webmode": "none", // "none", "gui", "lynx", "w3m", "elinks"
|
||||
|
|
57
main.go
57
main.go
|
@ -47,10 +47,6 @@ func saveConfig() error {
|
|||
|
||||
opts.WriteString("\n[SETTINGS]\n")
|
||||
for k, v := range bombadillo.Options {
|
||||
if k == "theme" && v != "normal" && v != "inverse" {
|
||||
v = "normal"
|
||||
bombadillo.Options["theme"] = "normal"
|
||||
}
|
||||
opts.WriteString(k)
|
||||
opts.WriteRune('=')
|
||||
opts.WriteString(v)
|
||||
|
@ -66,8 +62,9 @@ func saveConfig() error {
|
|||
|
||||
func validateOpt(opt, val string) bool {
|
||||
var validOpts = map[string][]string{
|
||||
"webmode": []string{"none", "gui", "lynx", "w3m", "elinks"},
|
||||
"theme": []string{"normal", "inverse"},
|
||||
"webmode": []string{"none", "gui", "lynx", "w3m", "elinks"},
|
||||
"theme": []string{"normal", "inverse", "color"},
|
||||
"defaultscheme": []string{"gopher", "gemini", "http", "https"},
|
||||
}
|
||||
|
||||
opt = strings.ToLower(opt)
|
||||
|
@ -86,19 +83,27 @@ func validateOpt(opt, val string) bool {
|
|||
|
||||
func lowerCaseOpt(opt, val string) string {
|
||||
switch opt {
|
||||
case "webmode", "theme":
|
||||
case "webmode", "theme", "defaultscheme":
|
||||
return strings.ToLower(val)
|
||||
default:
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig() error {
|
||||
file, err := os.Open(bombadillo.Options["configlocation"] + "/.bombadillo.ini")
|
||||
func loadConfig() {
|
||||
err := os.MkdirAll(bombadillo.Options["configlocation"], 0755)
|
||||
if err != nil {
|
||||
exitMsg := fmt.Sprintf("Error creating 'configlocation' directory: %s", err.Error())
|
||||
cui.Exit(3, exitMsg)
|
||||
}
|
||||
|
||||
fp := filepath.Join(bombadillo.Options["configlocation"], ".bombadillo.ini")
|
||||
file, err := os.Open(fp)
|
||||
if err != nil {
|
||||
err = saveConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
exitMsg := fmt.Sprintf("Error writing config file during bootup: %s", err.Error())
|
||||
cui.Exit(4, exitMsg)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,11 +113,7 @@ func loadConfig() error {
|
|||
for _, v := range settings.Settings {
|
||||
lowerkey := strings.ToLower(v.Key)
|
||||
if lowerkey == "configlocation" {
|
||||
// The config defaults to the home folder.
|
||||
// Users cannot really edit this value. But
|
||||
// a compile time override is available.
|
||||
// It is still stored in the ini and as a part
|
||||
// of the options map.
|
||||
// Read only
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -132,17 +133,14 @@ func loadConfig() error {
|
|||
for _, v := range settings.Certs {
|
||||
bombadillo.Certs.Add(v.Key, v.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initClient() error {
|
||||
func initClient() {
|
||||
bombadillo = MakeClient(" ((( Bombadillo ))) ")
|
||||
err := loadConfig()
|
||||
loadConfig()
|
||||
if bombadillo.Options["tlscertificate"] != "" && bombadillo.Options["tlskey"] != "" {
|
||||
bombadillo.Certs.LoadCertificate(bombadillo.Options["tlscertificate"], bombadillo.Options["tlskey"])
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// In the event of specific signals, ensure the display is shown correctly.
|
||||
|
@ -159,7 +157,7 @@ func handleSignals(c <-chan os.Signal) {
|
|||
cui.InitTerm()
|
||||
bombadillo.Draw()
|
||||
case syscall.SIGINT:
|
||||
cui.Exit()
|
||||
cui.Exit(130, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,10 +166,10 @@ func handleSignals(c <-chan os.Signal) {
|
|||
func printHelp() {
|
||||
art := `Bombadillo - a non-web browser
|
||||
|
||||
Syntax: bombadillo [url]
|
||||
bombadillo [options...]
|
||||
Syntax: bombadillo [options] [url]
|
||||
|
||||
Examples: bombadillo gopher://bombadillo.colorfield.space
|
||||
bombadillo -t
|
||||
bombadillo -v
|
||||
|
||||
Options:
|
||||
|
@ -182,6 +180,7 @@ Options:
|
|||
|
||||
func main() {
|
||||
getVersion := flag.Bool("v", false, "Display version information and exit")
|
||||
addTitleToXWindow := flag.Bool("t", false, "Set the window title to 'Bombadillo'. Can be used in a GUI environment, however not all terminals support this feature.")
|
||||
flag.Usage = printHelp
|
||||
flag.Parse()
|
||||
if *getVersion {
|
||||
|
@ -191,13 +190,15 @@ func main() {
|
|||
args := flag.Args()
|
||||
|
||||
cui.InitTerm()
|
||||
defer cui.Exit()
|
||||
err := initClient()
|
||||
if err != nil {
|
||||
// if we can't initialize we should bail out
|
||||
panic(err)
|
||||
|
||||
if *addTitleToXWindow {
|
||||
fmt.Print("\033[22;0t") // Store window title on terminal stack
|
||||
fmt.Print("\033]0;Bombadillo\007") // Update window title
|
||||
}
|
||||
|
||||
defer cui.Exit(0, "")
|
||||
initClient()
|
||||
|
||||
// watch for signals, send them to be handled
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGTSTP, syscall.SIGCONT, syscall.SIGINT)
|
||||
|
|
69
page.go
69
page.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -17,6 +18,9 @@ type Page struct {
|
|||
Links []string
|
||||
Location Url
|
||||
ScrollPosition int
|
||||
FoundLinkLines []int
|
||||
SearchTerm string
|
||||
SearchIndex int
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
|
@ -47,15 +51,23 @@ func (p *Page) ScrollPositionRange(termHeight int) (int, int) {
|
|||
// width and updates the WrappedContent
|
||||
// of the Page struct width a string slice
|
||||
// of the wrapped data
|
||||
func (p *Page) WrapContent(width int) {
|
||||
func (p *Page) WrapContent(width int, color bool) {
|
||||
counter := 0
|
||||
var content strings.Builder
|
||||
var esc strings.Builder
|
||||
escape := false
|
||||
content.Grow(len(p.RawContent))
|
||||
for _, ch := range []rune(p.RawContent) {
|
||||
if escape {
|
||||
if color {
|
||||
esc.WriteRune(ch)
|
||||
}
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
|
||||
escape = false
|
||||
if ch == 'm' {
|
||||
content.WriteString(esc.String())
|
||||
esc.Reset()
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
@ -74,10 +86,20 @@ func (p *Page) WrapContent(width int) {
|
|||
// Get rid of control characters we dont want
|
||||
continue
|
||||
} else if ch == 27 {
|
||||
if p.Location.Scheme == "local" {
|
||||
if counter+4 >= width {
|
||||
content.WriteRune('\n')
|
||||
}
|
||||
content.WriteString("\\033")
|
||||
continue
|
||||
}
|
||||
escape = true
|
||||
if color {
|
||||
esc.WriteRune(ch)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
if counter < width {
|
||||
if counter <= width {
|
||||
content.WriteRune(ch)
|
||||
counter++
|
||||
} else {
|
||||
|
@ -94,6 +116,47 @@ func (p *Page) WrapContent(width int) {
|
|||
}
|
||||
|
||||
p.WrappedContent = strings.Split(content.String(), "\n")
|
||||
p.HighlightFoundText()
|
||||
}
|
||||
|
||||
func (p *Page) HighlightFoundText() {
|
||||
if p.SearchTerm == "" {
|
||||
return
|
||||
}
|
||||
for i, ln := range p.WrappedContent {
|
||||
found := strings.Index(ln, p.SearchTerm)
|
||||
if found < 0 {
|
||||
continue
|
||||
}
|
||||
format := "\033[7m%s\033[27m"
|
||||
if bombadillo.Options["theme"] == "inverse" {
|
||||
format = "\033[27m%s\033[7m"
|
||||
}
|
||||
ln = strings.ReplaceAll(ln, p.SearchTerm, fmt.Sprintf(format, p.SearchTerm))
|
||||
p.WrappedContent[i] = ln
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Page) FindText() {
|
||||
p.FoundLinkLines = make([]int, 0, 10)
|
||||
s := p.SearchTerm
|
||||
p.SearchIndex = 0
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
format := "\033[7m%s\033[27m"
|
||||
if bombadillo.Options["theme"] == "inverse" {
|
||||
format = "\033[27m%s\033[7m"
|
||||
}
|
||||
for i, ln := range p.WrappedContent {
|
||||
found := strings.Index(ln, s)
|
||||
if found < 0 {
|
||||
continue
|
||||
}
|
||||
ln = strings.ReplaceAll(ln, s, fmt.Sprintf(format, s))
|
||||
p.WrappedContent[i] = ln
|
||||
p.FoundLinkLines = append(p.FoundLinkLines, i)
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
|
@ -102,6 +165,6 @@ func (p *Page) WrapContent(width int) {
|
|||
|
||||
// MakePage returns a Page struct with default values
|
||||
func MakePage(url Url, content string, links []string) Page {
|
||||
p := Page{make([]string, 0), content, links, url, 0}
|
||||
p := Page{make([]string, 0), content, links, url, 0, make([]int, 0), "", 0}
|
||||
return p
|
||||
}
|
||||
|
|
4
pages.go
4
pages.go
|
@ -60,13 +60,13 @@ func (p *Pages) Add(pg Page) {
|
|||
|
||||
// Render wraps the content for the current page and returns
|
||||
// the page content as a string slice
|
||||
func (p *Pages) Render(termHeight, termWidth int) []string {
|
||||
func (p *Pages) Render(termHeight, termWidth int, color bool) []string {
|
||||
if p.Length < 1 {
|
||||
return make([]string, 0)
|
||||
}
|
||||
pos := p.History[p.Position].ScrollPosition
|
||||
prev := len(p.History[p.Position].WrappedContent)
|
||||
p.History[p.Position].WrapContent(termWidth)
|
||||
p.History[p.Position].WrapContent(termWidth, color)
|
||||
now := len(p.History[p.Position].WrappedContent)
|
||||
if prev > now {
|
||||
diff := prev - now
|
||||
|
|
3
url.go
3
url.go
|
@ -44,6 +44,7 @@ func MakeUrl(u string) (Url, error) {
|
|||
if len(u) < 1 {
|
||||
return Url{}, fmt.Errorf("Invalid url, unable to parse")
|
||||
}
|
||||
|
||||
if strings.HasPrefix(u, "finger://") {
|
||||
return parseFinger(u)
|
||||
}
|
||||
|
@ -103,7 +104,7 @@ func MakeUrl(u string) (Url, error) {
|
|||
out.Scheme = strings.ToLower(out.Scheme)
|
||||
|
||||
if out.Scheme == "" {
|
||||
out.Scheme = "gopher"
|
||||
out.Scheme = bombadillo.Options["defaultscheme"]
|
||||
}
|
||||
|
||||
if out.Scheme == "gopher" && out.Port == "" {
|
||||
|
|
Loading…
Reference in New Issue