@@ 30,7 30,6 @@ const (
ModeNormal Mode = iota
ModeCommand
ModeInput
- ModeFind
ModeSave
ModeFollow
)
@@ 191,7 190,7 @@ func (b *Browser) Draw() {
b.statusView.DrawText(0, 0, b.message.text, style)
}
- case ModeCommand, ModeInput, ModeFind, ModeSave:
+ case ModeCommand, ModeInput, ModeSave:
b.input.Draw()
case ModeFollow:
@@ 267,6 266,9 @@ func (b *Browser) Event(event tcell.Event) {
return
}
b.RunCommand(strings.Fields(event.Text())...)
+
+ case *ui.EventTextEdit:
+ b.PreviewCommand(strings.Fields(event.Text())...)
}
case ModeInput:
@@ 286,31 288,6 @@ func (b *Browser) Event(event tcell.Event) {
tab.DoBackground(req)
}
- case ModeFind:
- switch event := event.(type) {
- case *ui.EventTextInput:
- b.mode = ModeNormal
- if event.NoInput() {
- return
- }
- text := tab.Text()
- if text == nil {
- return
- }
- if err := text.Find(event.Text()); err != nil {
- b.Error(err)
- return
- }
- b.view.Invalidate()
-
- case *ui.EventTextEdit:
- text := tab.Text()
- if text == nil {
- return
- }
- text.Find(event.Text())
- }
-
case ModeSave:
switch event := event.(type) {
case *ui.EventTextInput:
@@ 619,12 596,26 @@ func (b *Browser) RunCommand(args ...string) {
}
b.clearMessage()
c, ok := commands[args[0]]
- if ok {
- if err := c(b, args[1:]...); err != nil {
- b.Error(err)
- }
- } else {
+ if !ok {
b.Error(fmt.Errorf("%q is not a command", args[0]))
+ return
+ }
+ if err := c.Func(b, args[1:]...); err != nil {
+ b.Error(err)
+ }
+}
+
+func (b *Browser) PreviewCommand(args ...string) {
+ if len(args) == 0 {
+ return
+ }
+ b.clearMessage()
+ c, ok := commands[args[0]]
+ if !ok || !c.Preview {
+ return
+ }
+ if err := c.Func(b, args[1:]...); err != nil {
+ b.Error(err)
}
}
@@ 691,12 682,6 @@ func (b *Browser) InputMode(prompt string, sensitive bool) {
b.input.Sensitive(sensitive)
}
-func (b *Browser) FindMode() {
- b.mode = ModeFind
- b.input.Prompt("/")
- b.view.Invalidate()
-}
-
func (b *Browser) SaveMode(data io.ReadCloser, name string) {
b.mu.Lock()
defer b.mu.Unlock()
@@ 11,189 11,269 @@ import (
"git.sr.ht/~adnano/go-gemini"
)
-type Command func(b *Browser, args ...string) error
+type Command struct {
+ Func func(b *Browser, args ...string) error
+ Preview bool
+}
var commands = map[string]Command{
- "prompt": func(b *Browser, args ...string) error {
- b.CommandMode()
- prompt := strings.Join(args, " ")
- b.input.SetInput(prompt)
- return nil
+ "prompt": Command{
+ Func: cmdPrompt,
},
- "find": func(b *Browser, args ...string) error {
- if len(args) == 0 {
- b.FindMode()
- return nil
- }
-
- switch args[0] {
- case "prev":
- tab := b.tabs[b.tab]
- text := tab.Text()
- if text == nil {
- return nil
- }
- text.FindPrev()
- case "next":
- tab := b.tabs[b.tab]
- text := tab.Text()
- if text == nil {
- return nil
- }
- text.FindNext()
- default:
- return errors.New("usage: find [prev|next]")
- }
- return nil
+ "find": Command{
+ Func: cmdFind,
+ Preview: true,
},
- "follow": func(b *Browser, args ...string) error {
- return b.FollowMode()
+ "follow": Command{
+ Func: cmdFollow,
},
- "jump": func(b *Browser, args ...string) error {
- b.Jump()
- return nil
+ "jump": Command{
+ Func: cmdJump,
},
- "open": func(b *Browser, args ...string) error {
- if len(args) == 0 {
- return errors.New("usage: open <url>")
- }
-
- rawurl := args[0]
- u, err := url.Parse(rawurl)
- if err != nil {
- return err
- }
-
- if len(u.Scheme) == 0 && len(u.Host) == 0 {
- u2, err := url.Parse("gemini://" + rawurl)
- if err == nil {
- u = u2
- }
- }
- b.tabs[b.tab].DoBackground(&gemini.Request{URL: u})
- return nil
+ "open": Command{
+ Func: cmdOpen,
},
- "close": func(b *Browser, args ...string) error {
- b.CloseTab(b.tab)
- return nil
+ "close": Command{
+ Func: cmdClose,
},
- "tab": func(b *Browser, args ...string) error {
- if len(args) == 0 {
- return errors.New("usage: tab prev|next")
- }
- switch args[0] {
- case "prev":
- b.Previous()
- case "next":
- b.Next()
- }
- return nil
+ "tab": Command{
+ Func: cmdTab,
},
- "newtab": func(b *Browser, args ...string) error {
- tab := b.NewTab()
- tab.Do(&gemini.Request{
- URL: &url.URL{
- Scheme: "about",
- Host: "newtab",
- },
- })
- b.makeTabVisible()
- return nil
+ "newtab": Command{
+ Func: cmdNewtab,
},
- "clone": func(b *Browser, args ...string) error {
- tab := b.tabs[b.tab]
- clone := b.NewTab()
- clone.Clone(tab)
- clone.Reload()
- b.makeTabVisible()
- return nil
+ "clone": Command{
+ Func: cmdClone,
},
- "reload": func(b *Browser, args ...string) error {
- b.tabs[b.tab].Reload()
- return nil
+ "reload": Command{
+ Func: cmdReload,
},
- "cancel": func(b *Browser, args ...string) error {
- b.tabs[b.tab].Cancel()
- return nil
+ "cancel": Command{
+ Func: cmdCancel,
},
- "back": func(b *Browser, args ...string) error {
- tab := b.tabs[b.tab]
- if !tab.Back() {
- b.Message("Already at beginning of history")
- }
- return nil
+ "back": Command{
+ Func: cmdBack,
},
- "forward": func(b *Browser, args ...string) error {
- tab := b.tabs[b.tab]
- if !tab.Forward() {
- b.Message("Already at end of history")
- }
- return nil
+ "forward": Command{
+ Func: cmdForward,
},
- "quit": func(b *Browser, args ...string) error {
- b.view.Exit()
- return nil
+ "quit": Command{
+ Func: cmdQuit,
},
- "scroll": func(b *Browser, args ...string) error {
- const usage = "usage: scroll <n>[%]"
- if len(args) == 0 {
- return errors.New(usage)
- }
- amount := args[0]
- switch amount {
- case "top":
- b.tabView.Top()
- b.tabView.Invalidate()
- case "bottom":
- b.tabView.Bottom()
- b.tabView.Invalidate()
- default:
- var scroll int
- if strings.HasSuffix(amount, "%") {
- percent, err := strconv.Atoi(amount[:len(amount)-1])
- if err != nil {
- return errors.New(usage)
- }
- _, h := b.tabView.Size()
- scroll = h * percent / 100
- } else {
- var err error
- scroll, err = strconv.Atoi(amount)
- if err != nil {
- return errors.New(usage)
- }
- }
- b.tabView.ScrollDown(scroll)
- b.tabView.Invalidate()
- }
- return nil
+ "scroll": Command{
+ Func: cmdScroll,
},
- "save": func(b *Browser, args ...string) error {
- tab := b.tabs[b.tab]
- name := path.Base(tab.URL())
- b.saveMode(io.NopCloser(&tab.buf), name)
- return nil
+ "save": Command{
+ Func: cmdSave,
},
- "bookmark": func(b *Browser, args ...string) error {
- tab := b.tabs[b.tab]
- if b.bookmarks.Add(gemini.LineLink{
- URL: tab.URL(),
- Name: tab.Title(),
- }) {
- b.Message("Bookmarked")
- } else {
- b.Message("Already bookmarked")
- }
- return nil
+ "bookmark": Command{
+ Func: cmdBookmark,
},
- "search": func(b *Browser, args ...string) error {
- if len(args) == 0 {
- return errors.New("usage: search <query...>")
- }
- u := b.config.Search.URL.ResolveReference(&url.URL{
- RawQuery: gemini.QueryEscape(strings.Join(args, " ")),
- })
- b.tabs[b.tab].DoBackground(&gemini.Request{URL: u})
- return nil
+ "search": Command{
+ Func: cmdSearch,
},
}
+
+func cmdPrompt(b *Browser, args ...string) error {
+ b.CommandMode()
+ prompt := strings.Join(args, " ")
+ b.input.SetInput(prompt)
+ return nil
+}
+
+func cmdFind(b *Browser, args ...string) error {
+ tab := b.tabs[b.tab]
+ text := tab.Text()
+ if text == nil {
+ return nil
+ }
+
+ if len(args) == 0 {
+ return nil
+ }
+
+ // TODO: Dedicated commands for prev/next?
+ switch args[0] {
+ case "prev":
+ text.FindPrev()
+ b.view.Invalidate()
+ return nil
+ case "next":
+ text.FindNext()
+ b.view.Invalidate()
+ return nil
+ default:
+ // return errors.New("usage: find [prev|next]")
+ }
+
+ text.Find(strings.Join(args, " "))
+ b.view.Invalidate()
+
+ return nil
+}
+
+func cmdFollow(b *Browser, args ...string) error {
+ return b.FollowMode()
+}
+
+func cmdJump(b *Browser, args ...string) error {
+ b.Jump()
+ return nil
+}
+
+func cmdOpen(b *Browser, args ...string) error {
+ if len(args) == 0 {
+ return errors.New("usage: open <url>")
+ }
+
+ rawurl := args[0]
+ u, err := url.Parse(rawurl)
+ if err != nil {
+ return err
+ }
+
+ if len(u.Scheme) == 0 && len(u.Host) == 0 {
+ u2, err := url.Parse("gemini://" + rawurl)
+ if err == nil {
+ u = u2
+ }
+ }
+ b.tabs[b.tab].DoBackground(&gemini.Request{URL: u})
+ return nil
+}
+
+func cmdClose(b *Browser, args ...string) error {
+ b.CloseTab(b.tab)
+ return nil
+}
+
+func cmdTab(b *Browser, args ...string) error {
+ if len(args) == 0 {
+ return errors.New("usage: tab prev|next")
+ }
+ switch args[0] {
+ case "prev":
+ b.Previous()
+ case "next":
+ b.Next()
+ }
+ return nil
+}
+
+func cmdNewtab(b *Browser, args ...string) error {
+ tab := b.NewTab()
+ tab.Do(&gemini.Request{
+ URL: &url.URL{
+ Scheme: "about",
+ Host: "newtab",
+ },
+ })
+ b.makeTabVisible()
+ return nil
+}
+
+func cmdClone(b *Browser, args ...string) error {
+ tab := b.tabs[b.tab]
+ clone := b.NewTab()
+ clone.Clone(tab)
+ clone.Reload()
+ b.makeTabVisible()
+ return nil
+}
+
+func cmdReload(b *Browser, args ...string) error {
+ b.tabs[b.tab].Reload()
+ return nil
+}
+
+func cmdCancel(b *Browser, args ...string) error {
+ b.tabs[b.tab].Cancel()
+ return nil
+}
+
+func cmdBack(b *Browser, args ...string) error {
+ tab := b.tabs[b.tab]
+ if !tab.Back() {
+ b.Message("Already at beginning of history")
+ }
+ return nil
+}
+
+func cmdForward(b *Browser, args ...string) error {
+ tab := b.tabs[b.tab]
+ if !tab.Forward() {
+ b.Message("Already at end of history")
+ }
+ return nil
+}
+
+func cmdQuit(b *Browser, args ...string) error {
+ b.view.Exit()
+ return nil
+}
+
+func cmdScroll(b *Browser, args ...string) error {
+ const usage = "usage: scroll <n>[%]"
+ if len(args) == 0 {
+ return errors.New(usage)
+ }
+ amount := args[0]
+ switch amount {
+ case "top":
+ b.tabView.Top()
+ b.tabView.Invalidate()
+ case "bottom":
+ b.tabView.Bottom()
+ b.tabView.Invalidate()
+ default:
+ var scroll int
+ if strings.HasSuffix(amount, "%") {
+ percent, err := strconv.Atoi(amount[:len(amount)-1])
+ if err != nil {
+ return errors.New(usage)
+ }
+ _, h := b.tabView.Size()
+ scroll = h * percent / 100
+ } else {
+ var err error
+ scroll, err = strconv.Atoi(amount)
+ if err != nil {
+ return errors.New(usage)
+ }
+ }
+ b.tabView.ScrollDown(scroll)
+ b.tabView.Invalidate()
+ }
+ return nil
+}
+
+func cmdSave(b *Browser, args ...string) error {
+ tab := b.tabs[b.tab]
+ name := path.Base(tab.URL())
+ b.saveMode(io.NopCloser(&tab.buf), name)
+ return nil
+}
+
+func cmdBookmark(b *Browser, args ...string) error {
+ tab := b.tabs[b.tab]
+ if b.bookmarks.Add(gemini.LineLink{
+ URL: tab.URL(),
+ Name: tab.Title(),
+ }) {
+ b.Message("Bookmarked")
+ } else {
+ b.Message("Already bookmarked")
+ }
+ return nil
+}
+
+func cmdSearch(b *Browser, args ...string) error {
+ if len(args) == 0 {
+ return errors.New("usage: search <query...>")
+ }
+ u := b.config.Search.URL.ResolveReference(&url.URL{
+ RawQuery: gemini.QueryEscape(strings.Join(args, " ")),
+ })
+ b.tabs[b.tab].DoBackground(&gemini.Request{URL: u})
+ return nil
+}