2020-06-30 07:20:13 +00:00
|
|
|
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"
|
2020-07-01 01:09:22 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/scrollinput"
|
|
|
|
"github.com/gotk3/gotk3/gdk"
|
2020-06-30 07:20:13 +00:00
|
|
|
"github.com/gotk3/gotk3/gtk"
|
2020-07-01 01:09:22 +00:00
|
|
|
"github.com/gotk3/gotk3/pango"
|
2020-06-30 07:20:13 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2020-07-01 01:09:22 +00:00
|
|
|
var monospace = primitives.PrepareCSS(`
|
2020-06-30 07:20:13 +00:00
|
|
|
* {
|
|
|
|
font-family: monospace;
|
|
|
|
border-radius: 0;
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
2020-07-04 04:41:12 +00:00
|
|
|
var commandPadding = primitives.PrepareCSS(`
|
|
|
|
* { padding: 8px 12px; }
|
|
|
|
`)
|
|
|
|
|
2020-06-30 07:20:13 +00:00
|
|
|
type Session struct {
|
|
|
|
*gtk.Box
|
2020-07-01 01:09:22 +00:00
|
|
|
|
2020-06-30 07:20:13 +00:00
|
|
|
cmder cchat.Commander
|
|
|
|
buffer *Buffer
|
2020-07-01 01:09:22 +00:00
|
|
|
cmplt *completer
|
|
|
|
|
|
|
|
inputbuf *gtk.TextBuffer
|
|
|
|
|
|
|
|
// words []string
|
|
|
|
// index int
|
2020-06-30 07:20:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2020-07-01 01:09:22 +00:00
|
|
|
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()
|
2020-06-30 07:20:13 +00:00
|
|
|
|
2020-07-01 01:09:22 +00:00
|
|
|
inputbuf, _ := input.GetBuffer()
|
|
|
|
|
|
|
|
inputscroll := scrollinput.NewH(input)
|
|
|
|
inputscroll.Show()
|
|
|
|
|
|
|
|
sep, _ := gtk.SeparatorNew(gtk.ORIENTATION_HORIZONTAL)
|
|
|
|
sep.Show()
|
2020-06-30 07:20:13 +00:00
|
|
|
|
|
|
|
b, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
2020-07-01 01:09:22 +00:00
|
|
|
b.PackStart(scroll, true, true, 0)
|
|
|
|
b.PackStart(sep, false, false, 0)
|
|
|
|
b.PackStart(inputscroll, false, false, 0)
|
2020-06-30 07:20:13 +00:00
|
|
|
|
|
|
|
session := &Session{
|
2020-07-01 01:09:22 +00:00
|
|
|
Box: b,
|
|
|
|
cmder: cmder,
|
|
|
|
buffer: buf,
|
|
|
|
cmplt: newCompleter(input, cmder),
|
|
|
|
inputbuf: inputbuf,
|
2020-06-30 07:20:13 +00:00
|
|
|
}
|
|
|
|
|
2020-07-01 01:09:22 +00:00
|
|
|
input.Connect("key-press-event", session.inputActivate)
|
|
|
|
input.GrabFocus()
|
|
|
|
|
|
|
|
primitives.AddClass(b, "commander")
|
|
|
|
primitives.AddClass(view, "command-buffer")
|
|
|
|
primitives.AddClass(input, "command-input")
|
2020-07-04 04:41:12 +00:00
|
|
|
primitives.AttachCSS(view, commandPadding)
|
|
|
|
primitives.AttachCSS(input, commandPadding)
|
2020-06-30 07:20:13 +00:00
|
|
|
|
|
|
|
return session
|
|
|
|
}
|
|
|
|
|
2020-07-01 01:09:22 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-06-30 07:20:13 +00:00
|
|
|
// If the input is empty, then ignore.
|
2020-07-01 01:09:22 +00:00
|
|
|
if len(s.cmplt.Words) == 0 {
|
|
|
|
return true
|
2020-06-30 07:20:13 +00:00
|
|
|
}
|
|
|
|
|
2020-07-01 01:09:22 +00:00
|
|
|
r, err := s.cmder.RunCommand(s.cmplt.Words)
|
2020-06-30 07:20:13 +00:00
|
|
|
if err != nil {
|
|
|
|
s.buffer.WriteError(err)
|
2020-07-01 01:09:22 +00:00
|
|
|
return true
|
2020-06-30 07:20:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the entry.
|
2020-07-01 01:09:22 +00:00
|
|
|
s.inputbuf.Delete(s.inputbuf.GetBounds())
|
2020-06-30 07:20:13 +00:00
|
|
|
|
|
|
|
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(),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}()
|
2020-07-01 01:09:22 +00:00
|
|
|
|
|
|
|
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]
|
2020-06-30 07:20:13 +00:00
|
|
|
}
|