cchat-gtk/internal/ui/service/session/commander/commander.go

201 lines
4.2 KiB
Go

package commander
import (
"fmt"
"io"
"time"
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/autoscroll"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/scrollinput"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/pango"
"github.com/pkg/errors"
)
var monospace = primitives.PrepareCSS(`
* {
font-family: monospace;
border-radius: 0;
}
`)
var commandPadding = primitives.PrepareCSS(`
* { padding: 8px 12px; }
`)
type Session struct {
*gtk.Box
cmder cchat.Commander
buffer *Buffer
cmplt *completer
inputbuf *gtk.TextBuffer
// words []string
// index int
}
func SpawnDialog(buf *Buffer) {
s := NewSession(buf.cmder, buf)
s.Show()
h, _ := gtk.HeaderBarNew()
h.SetTitle(fmt.Sprintf(
"Commander: %s on %s",
buf.cmder.Name().Content, buf.svcname,
))
h.SetShowCloseButton(true)
h.Show()
d, _ := gts.NewEmptyModalDialog()
d.SetDefaultSize(450, 250)
d.SetTitlebar(h)
d.Add(s)
d.Show()
}
func NewSession(cmder cchat.Commander, buf *Buffer) *Session {
view, _ := gtk.TextViewNewWithBuffer(buf.TextBuffer)
view.SetEditable(false)
view.SetProperty("monospace", true)
view.SetPixelsAboveLines(1)
view.SetWrapMode(gtk.WRAP_WORD_CHAR)
view.Show()
scroll := autoscroll.NewScrolledWindow()
scroll.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scroll.Add(view)
scroll.Show()
input, _ := gtk.TextViewNew()
input.SetSizeRequest(-1, 35) // magic height 35px
primitives.AttachCSS(input, monospace)
input.Show()
inputbuf, _ := input.GetBuffer()
inputscroll := scrollinput.NewH(input)
inputscroll.Show()
sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_HORIZONTAL)
sep.Show()
b, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
b.PackStart(scroll, true, true, 0)
b.PackStart(sep, false, false, 0)
b.PackStart(inputscroll, false, false, 0)
session := &Session{
Box: b,
cmder: cmder,
buffer: buf,
cmplt: newCompleter(input, cmder),
inputbuf: inputbuf,
}
input.Connect("key-press-event", session.inputActivate)
input.GrabFocus()
primitives.AddClass(b, "commander")
primitives.AddClass(view, "command-buffer")
primitives.AddClass(input, "command-input")
primitives.AttachCSS(view, commandPadding)
primitives.AttachCSS(input, commandPadding)
return session
}
func (s *Session) inputActivate(v *gtk.TextView, ev *gdk.Event) bool {
// If the keypress is not enter, then ignore.
if kev := gdk.EventKeyNewFromEvent(ev); kev.KeyVal() != gdk.KEY_Return {
return false
}
// If the input is empty, then ignore.
if len(s.cmplt.Words) == 0 {
return true
}
r, err := s.cmder.RunCommand(s.cmplt.Words)
if err != nil {
s.buffer.WriteError(err)
return true
}
// Clear the entry.
s.inputbuf.Delete(s.inputbuf.GetBounds())
var then = time.Now()
s.buffer.Printlnf("%s: Running command...", then.Format(time.Kitchen))
go func() {
_, err := io.Copy(s.buffer, r)
r.Close()
gts.ExecAsync(func() {
if err != nil {
s.buffer.WriteError(errors.Wrap(err, "Internal error"))
}
var now = time.Now()
s.buffer.Printlnf(
"%s: Finished running command, took %s.",
now.Format(time.Kitchen),
now.Sub(then).String(),
)
})
}()
return true
}
type completer struct {
*completion.Completer
completer cchat.CommandCompleter
choices []string
}
func newCompleter(input *gtk.TextView, v cchat.Commander) *completer {
completer := &completer{}
completer.Completer = completion.NewCompleter(input, completer)
c, ok := v.(cchat.CommandCompleter)
if ok {
completer.completer = c
}
return completer
}
func (c *completer) Update(words []string, offset int) []gtk.IWidget {
if c.completer == nil {
return nil
}
c.choices = c.completer.CompleteCommand(words, offset)
var widgets = make([]gtk.IWidget, 0, len(c.choices))
for _, choice := range c.choices {
l, _ := gtk.LabelNew(choice)
l.SetXAlign(0)
l.SetEllipsize(pango.ELLIPSIZE_END)
primitives.AttachCSS(l, monospace)
l.Show()
widgets = append(widgets, l)
}
return widgets
}
func (c *completer) Word(i int) string {
return c.choices[i]
}