Initial commit

This commit is contained in:
erroneousboat 2016-09-11 17:55:19 +02:00
commit 6cfa55c26e
11 changed files with 434 additions and 0 deletions

42
Makefile Normal file
View 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 its 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

2
README.md Normal file
View File

@ -0,0 +1,2 @@
Slack-Term
==========

21
TODO.md Normal file
View 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
View File

@ -0,0 +1,3 @@
package components
type Chat struct{}

View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
{
"comment": "",
"ignore": "test",
"package": [],
"rootPath": "github.com/erroneousboat/slack-term/src"
}

39
src/views/chat.go Normal file
View 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,
)
}