Initial commit
This commit is contained in:
commit
6cfa55c26e
42
Makefile
Normal file
42
Makefile
Normal file
@ -0,0 +1,42 @@
|
||||
default: test
|
||||
|
||||
# -timeout timout in seconds
|
||||
# -v verbose output
|
||||
test:
|
||||
go test -timeout=5s -v
|
||||
|
||||
# `CGO_ENABLED=0`
|
||||
# Because of dynamically linked libraries, this will statically compile the
|
||||
# app with all libraries built in. You won't be able to cross-compile if CGO
|
||||
# is enabled. This is because Go binary is looking for libraries on the
|
||||
# operating system it’s running in. We compiled our app, but it still is
|
||||
# dynamically linked to the libraries it needs to run
|
||||
# (i.e., all the C libraries it binds to). When using a minimal docker image
|
||||
# the operating system doesn't have these libraries.
|
||||
#
|
||||
# `GOOS=linux`
|
||||
# We're setting the OS to linux (in case someone builds the binary on Mac or
|
||||
# Windows)
|
||||
#
|
||||
# `-a`
|
||||
# Force rebuilding of package, all import will be rebuilt with cgo disabled,
|
||||
# which means all the imports will be rebuilt with cgo disabled.
|
||||
#
|
||||
# `-installsuffix cgo`
|
||||
# A suffix to use in the name of the package installation directory
|
||||
#
|
||||
# `-o`
|
||||
# Output
|
||||
#
|
||||
# `./bin/slack-term`
|
||||
# Placement of the binary
|
||||
#
|
||||
# `./src/`
|
||||
# Location of the source files
|
||||
build:
|
||||
CGO_ENABLED=0 go build -a -installsuffix cgo -o ./bin/slack-term ./src/
|
||||
|
||||
run: build
|
||||
./bin/slack-term
|
||||
|
||||
.PHONY: default test build run
|
21
TODO.md
Normal file
21
TODO.md
Normal file
@ -0,0 +1,21 @@
|
||||
Sources:
|
||||
|
||||
Slack:
|
||||
- https://api.slack.com/rtm
|
||||
- https://github.com/evanyeung/terminal-slack
|
||||
- https://github.com/nlopes/slack
|
||||
|
||||
UI
|
||||
- https://github.com/jroimartin/gocui
|
||||
- https://github.com/fatih/color
|
||||
- https://github.com/nsf/termbox-go
|
||||
- https://github.com/gizak/termui
|
||||
|
||||
Examples:
|
||||
- https://github.com/nsf/godit
|
||||
- https://github.com/moncho/dry
|
||||
- https://github.com/mikepea/go-jira-ui
|
||||
|
||||
- [ ] show cursor, see if you can alter Par
|
||||
- [ ] input overflow
|
||||
- [ ] chat
|
3
src/components/chat.go
Normal file
3
src/components/chat.go
Normal file
@ -0,0 +1,3 @@
|
||||
package components
|
||||
|
||||
type Chat struct{}
|
45
src/components/components.go
Normal file
45
src/components/components.go
Normal file
@ -0,0 +1,45 @@
|
||||
package components
|
||||
|
||||
import "github.com/gizak/termui"
|
||||
|
||||
func CreateInputComponent() *termui.Par {
|
||||
compInput := termui.NewPar("")
|
||||
compInput.Height = 3
|
||||
return compInput
|
||||
}
|
||||
|
||||
func CreateChannelsComponent(inputHeight int) *termui.List {
|
||||
channels := []string{
|
||||
"general",
|
||||
"random",
|
||||
}
|
||||
|
||||
compChannels := termui.NewList()
|
||||
compChannels.Items = channels
|
||||
compChannels.BorderLabel = "Channels"
|
||||
compChannels.Height = termui.TermHeight() - inputHeight
|
||||
compChannels.Overflow = "wrap"
|
||||
|
||||
return compChannels
|
||||
}
|
||||
|
||||
func CreateChatComponent(inputHeight int) *termui.List {
|
||||
messages := []string{
|
||||
"[jp] hello world",
|
||||
"[erroneousboat] foo bar",
|
||||
}
|
||||
|
||||
compChat := termui.NewList()
|
||||
compChat.Items = messages
|
||||
compChat.BorderLabel = "Channel01"
|
||||
compChat.Height = termui.TermHeight() - inputHeight
|
||||
compChat.Overflow = "wrap"
|
||||
|
||||
return compChat
|
||||
}
|
||||
|
||||
func CreateModeComponent() *termui.Par {
|
||||
compMode := termui.NewPar("NORMAL")
|
||||
compMode.Height = 3
|
||||
return compMode
|
||||
}
|
95
src/components/input.go
Normal file
95
src/components/input.go
Normal file
@ -0,0 +1,95 @@
|
||||
package components
|
||||
|
||||
import "github.com/gizak/termui"
|
||||
|
||||
type Input struct {
|
||||
Block *termui.Par
|
||||
CursorPosition int
|
||||
CursorFgColor termui.Attribute
|
||||
CursorBgColor termui.Attribute
|
||||
}
|
||||
|
||||
func CreateInput() *Input {
|
||||
input := &Input{
|
||||
Block: termui.NewPar(""),
|
||||
CursorPosition: 0,
|
||||
CursorBgColor: termui.ColorBlack,
|
||||
CursorFgColor: termui.ColorWhite,
|
||||
}
|
||||
|
||||
input.Block.Height = 3
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
// implements interface termui.Bufferer
|
||||
func (i *Input) Buffer() termui.Buffer {
|
||||
return i.Block.Buffer()
|
||||
}
|
||||
|
||||
// implements interface termui.GridBufferer
|
||||
func (i *Input) GetHeight() int {
|
||||
return i.Block.GetHeight()
|
||||
}
|
||||
|
||||
// implements interface termui.GridBufferer
|
||||
func (i *Input) SetWidth(w int) {
|
||||
i.Block.SetWidth(w)
|
||||
}
|
||||
|
||||
// implements interface termui.GridBufferer
|
||||
func (i *Input) SetX(x int) {
|
||||
i.Block.SetX(x)
|
||||
}
|
||||
|
||||
// implements interface termui.GridBufferer
|
||||
func (i *Input) SetY(y int) {
|
||||
i.Block.SetY(y)
|
||||
}
|
||||
|
||||
func (i *Input) Insert(key string) {
|
||||
i.Block.Text = i.Block.Text + key
|
||||
i.Block.TextBgColor = termui.ColorWhite
|
||||
i.Block.TextFgColor = termui.ColorBlack
|
||||
i.CursorPosition++
|
||||
}
|
||||
|
||||
func (i *Input) Remove() {
|
||||
if i.CursorPosition > 0 {
|
||||
|
||||
i.Block.Text = i.Block.Text[0:i.CursorPosition-1] + i.Block.Text[i.CursorPosition:len(i.Block.Text)]
|
||||
|
||||
i.Block.TextBgColor = termui.ColorBlack
|
||||
i.Block.TextFgColor = termui.ColorWhite
|
||||
|
||||
i.CursorPosition--
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Input) MoveCursorRight() {
|
||||
if i.CursorPosition < len(i.Block.Text) {
|
||||
i.CursorPosition++
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Input) MoveCursorLeft() {
|
||||
if i.CursorPosition > 0 {
|
||||
i.CursorPosition--
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Input) IsEmpty() bool {
|
||||
if i.Block.Text == "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *Input) Clear() {
|
||||
i.Block.Text = ""
|
||||
i.CursorPosition = 0
|
||||
}
|
||||
|
||||
func (i *Input) Text() string {
|
||||
return i.Block.Text
|
||||
}
|
27
src/context/context.go
Normal file
27
src/context/context.go
Normal file
@ -0,0 +1,27 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/slack-term/src/views"
|
||||
"github.com/gizak/termui"
|
||||
)
|
||||
|
||||
const (
|
||||
CommandMode = "command"
|
||||
InsertMode = "insert"
|
||||
)
|
||||
|
||||
type AppContext struct {
|
||||
Body *termui.Grid
|
||||
View *views.View
|
||||
Mode string
|
||||
}
|
||||
|
||||
// TODO: arg Config
|
||||
func CreateAppContext() *AppContext {
|
||||
view := views.CreateChatView()
|
||||
|
||||
return &AppContext{
|
||||
View: view,
|
||||
Mode: CommandMode,
|
||||
}
|
||||
}
|
114
src/handlers/event.go
Normal file
114
src/handlers/event.go
Normal file
@ -0,0 +1,114 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gizak/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/src/context"
|
||||
"github.com/erroneousboat/slack-term/src/views"
|
||||
)
|
||||
|
||||
func RegisterEventHandlers(ctx *context.AppContext) {
|
||||
termui.Handle("/sys/kbd/", anyKeyHandler(ctx))
|
||||
termui.Handle("/sys/wnd/resize", resizeHandler(ctx))
|
||||
}
|
||||
|
||||
func anyKeyHandler(ctx *context.AppContext) func(termui.Event) {
|
||||
return func(e termui.Event) {
|
||||
key := e.Data.(termui.EvtKbd).KeyStr
|
||||
|
||||
if ctx.Mode == context.CommandMode {
|
||||
switch key {
|
||||
case "q":
|
||||
actionQuit()
|
||||
return
|
||||
case "i":
|
||||
actionInsertMode(ctx)
|
||||
return
|
||||
}
|
||||
} else if ctx.Mode == context.InsertMode {
|
||||
switch key {
|
||||
case "<escape>":
|
||||
actionCommandMode(ctx)
|
||||
return
|
||||
case "<enter>":
|
||||
actionSend(ctx)
|
||||
return
|
||||
case "<space>":
|
||||
actionInput(ctx.View, " ")
|
||||
return
|
||||
case "<backspace>":
|
||||
actionBackSpace(ctx.View)
|
||||
case "C-8":
|
||||
actionBackSpace(ctx.View)
|
||||
case "<right>":
|
||||
actionMoveCursorRight(ctx.View)
|
||||
case "<left>":
|
||||
actionMoveCursorLeft(ctx.View)
|
||||
default:
|
||||
actionInput(ctx.View, key)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func resizeHandler(ctx *context.AppContext) func(termui.Event) {
|
||||
return func(e termui.Event) {
|
||||
actionResize(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: resize only seems to work for width and resizing it too small
|
||||
// will cause termui to panic
|
||||
func actionResize(ctx *context.AppContext) {
|
||||
termui.Body.Width = termui.TermWidth()
|
||||
termui.Body.Align()
|
||||
termui.Render(termui.Body)
|
||||
}
|
||||
|
||||
func actionInput(view *views.View, key string) {
|
||||
view.Input.Insert(key)
|
||||
termui.Render(view.Input)
|
||||
}
|
||||
|
||||
func actionBackSpace(view *views.View) {
|
||||
view.Input.Remove()
|
||||
termui.Render(view.Input)
|
||||
}
|
||||
|
||||
func actionMoveCursorRight(view *views.View) {
|
||||
view.Input.MoveCursorRight()
|
||||
termui.Render(view.Input)
|
||||
}
|
||||
|
||||
func actionMoveCursorLeft(view *views.View) {
|
||||
view.Input.MoveCursorLeft()
|
||||
termui.Render(view.Input)
|
||||
}
|
||||
|
||||
func actionSend(ctx *context.AppContext) {
|
||||
if !ctx.View.Input.IsEmpty() {
|
||||
// FIXME
|
||||
ctx.View.Chat.Items = append(ctx.View.Chat.Items, ctx.View.Input.Text())
|
||||
ctx.View.Input.Clear()
|
||||
ctx.View.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func actionQuit() {
|
||||
termui.StopLoop()
|
||||
}
|
||||
|
||||
func actionInsertMode(ctx *context.AppContext) {
|
||||
ctx.Mode = context.InsertMode
|
||||
ctx.View.Mode.Text = "INSERT"
|
||||
termui.Render(ctx.View.Mode)
|
||||
}
|
||||
|
||||
func actionCommandMode(ctx *context.AppContext) {
|
||||
ctx.Mode = context.CommandMode
|
||||
ctx.View.Mode.Text = "NORMAL"
|
||||
termui.Render(ctx.View.Mode)
|
||||
}
|
40
src/main.go
Normal file
40
src/main.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/slack-term/src/context"
|
||||
"github.com/erroneousboat/slack-term/src/handlers"
|
||||
|
||||
"github.com/gizak/termui"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := termui.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer termui.Close()
|
||||
|
||||
// create context
|
||||
ctx := context.CreateAppContext()
|
||||
|
||||
// setup view
|
||||
termui.Body.AddRows(
|
||||
termui.NewRow(
|
||||
termui.NewCol(1, 0, ctx.View.Channels),
|
||||
termui.NewCol(11, 0, ctx.View.Chat),
|
||||
),
|
||||
termui.NewRow(
|
||||
termui.NewCol(1, 0, ctx.View.Mode),
|
||||
termui.NewCol(11, 0, ctx.View.Input),
|
||||
),
|
||||
)
|
||||
termui.Body.Align()
|
||||
termui.Render(termui.Body)
|
||||
|
||||
ctx.Body = termui.Body
|
||||
|
||||
// register handlers
|
||||
handlers.RegisterEventHandlers(ctx)
|
||||
|
||||
termui.Loop()
|
||||
}
|
6
src/vendor/vendor.json
vendored
Normal file
6
src/vendor/vendor.json
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"comment": "",
|
||||
"ignore": "test",
|
||||
"package": [],
|
||||
"rootPath": "github.com/erroneousboat/slack-term/src"
|
||||
}
|
39
src/views/chat.go
Normal file
39
src/views/chat.go
Normal file
@ -0,0 +1,39 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/slack-term/src/components"
|
||||
|
||||
"github.com/gizak/termui"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
Input *components.Input
|
||||
Chat *termui.List
|
||||
Channels *termui.List
|
||||
Mode *termui.Par
|
||||
}
|
||||
|
||||
func CreateChatView() *View {
|
||||
input := components.CreateInput()
|
||||
channels := components.CreateChannelsComponent(input.Block.Height)
|
||||
chat := components.CreateChatComponent(input.Block.Height)
|
||||
mode := components.CreateModeComponent()
|
||||
|
||||
view := &View{
|
||||
Input: input,
|
||||
Channels: channels,
|
||||
Chat: chat,
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func (v *View) Refresh() {
|
||||
termui.Render(
|
||||
v.Input,
|
||||
v.Chat,
|
||||
v.Channels,
|
||||
v.Mode,
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user