@ -72,7 +72,7 @@ func layout(g *gocui.Gui) error {
feedView . Autoscroll = true
feedView . Autoscroll = true
feedView . Wrap = true
feedView . Wrap = true
feedView . Title = "Feed Window"
feedView . Title = "Feed Window"
fmt . Fprintln ( feedView , "Feed Window - If you are mentioned or receive a PM it will show here" )
printInfo ( "Feed Window - If you are mentioned or receive a PM it will show here" )
}
}
if chatView , err2 := g . SetView ( "Chat" , maxX / 2 - maxX / 3 , maxY / 5 + 1 , maxX - 1 , maxY - 5 , 0 ) ; err2 != nil {
if chatView , err2 := g . SetView ( "Chat" , maxX / 2 - maxX / 3 , maxY / 5 + 1 , maxX - 1 , maxY - 5 , 0 ) ; err2 != nil {
if ! gocui . IsUnknownView ( err2 ) {
if ! gocui . IsUnknownView ( err2 ) {
@ -80,7 +80,9 @@ func layout(g *gocui.Gui) error {
}
}
chatView . Autoscroll = true
chatView . Autoscroll = true
chatView . Wrap = true
chatView . Wrap = true
fmt . Fprintf ( chatView , "Welcome %s!\n\nYour chats will appear here.\nSupported commands are as follows:\n\n" , k . Username )
welcomeText := basicStyle . stylize ( "Welcome $USER!\n\nYour chats will appear here.\nSupported commands are as follows:\n" )
welcomeText = welcomeText . replace ( "$USER" , mentionColor . stylize ( k . Username ) )
fmt . Fprintln ( chatView , welcomeText . string ( ) )
RunCommand ( "help" )
RunCommand ( "help" )
}
}
if inputView , err3 := g . SetView ( "Input" , maxX / 2 - maxX / 3 , maxY - 4 , maxX - 1 , maxY - 1 , 0 ) ; err3 != nil {
if inputView , err3 := g . SetView ( "Input" , maxX / 2 - maxX / 3 , maxY - 4 , maxX - 1 , maxY - 1 , 0 ) ; err3 != nil {
@ -176,20 +178,19 @@ func getViewTitle(viewName string) string {
view , err := g . View ( viewName )
view , err := g . View ( viewName )
if err != nil {
if err != nil {
// in case there is active tab completion, filter that to just the view title and not the completion options.
// in case there is active tab completion, filter that to just the view title and not the completion options.
printToView ( "Feed" , fmt . Sprintf ( "Error getting view title: %s" , err ) )
printError ( fmt . Sprintf ( "Error getting view title: %s" , err ) )
return ""
return ""
}
}
return strings . Split ( view . Title , "||" ) [ 0 ]
return strings . Split ( view . Title , "||" ) [ 0 ]
}
}
func popupView ( viewName string ) {
func popupView ( viewName string ) {
_ , err := g . SetCurrentView ( viewName )
_ , err := g . SetCurrentView ( viewName )
if err != nil {
if err != nil {
printToView ( "Feed" , fmt . Sprintf ( "%+v" , err ) )
printError ( fmt . Sprintf ( "%+v" , err ) )
}
}
_ , err = g . SetViewOnTop ( viewName )
_ , err = g . SetViewOnTop ( viewName )
if err != nil {
if err != nil {
printToView ( "Feed" , fmt . Sprintf ( "%+v" , err ) )
printError ( fmt . Sprintf ( "%+v" , err ) )
}
}
g . Update ( func ( g * gocui . Gui ) error {
g . Update ( func ( g * gocui . Gui ) error {
updatingView , err := g . View ( viewName )
updatingView , err := g . View ( viewName )
@ -247,6 +248,24 @@ func writeToView(viewName string, message string) {
return nil
return nil
} )
} )
}
}
// this removes formatting
func printError ( message string ) {
printErrorF ( message )
}
func printErrorF ( message string , parts ... StyledString ) {
printToView ( "Feed" , errorColor . sprintf ( removeFormatting ( message ) , parts ... ) . string ( ) )
}
// this removes formatting
func printInfo ( message string ) {
printInfoF ( message )
}
// this removes formatting
func printInfoF ( message string , parts ... StyledString ) {
printToView ( "Feed" , feedColor . sprintf ( removeFormatting ( message ) , parts ... ) . string ( ) )
}
func printToView ( viewName string , message string ) {
func printToView ( viewName string , message string ) {
g . Update ( func ( g * gocui . Gui ) error {
g . Update ( func ( g * gocui . Gui ) error {
updatingView , err := g . View ( viewName )
updatingView , err := g . View ( viewName )
@ -287,13 +306,12 @@ func populateChat() {
chat = k . NewChat ( channel )
chat = k . NewChat ( channel )
_ , err2 := chat . Read ( 2 )
_ , err2 := chat . Read ( 2 )
if err2 != nil {
if err2 != nil {
printToView ( "Feed" , fmt . Sprintf ( "%+v" , err ) )
printError ( fmt . Sprintf ( "%+v" , err ) )
return
return
}
}
go populateChat ( )
go populateChat ( )
go generateChannelTabCompletionSlice ( )
go generateChannelTabCompletionSlice ( )
return
return
}
}
var printMe [ ] string
var printMe [ ] string
var actuallyPrintMe string
var actuallyPrintMe string
@ -325,75 +343,122 @@ func populateList() {
if testVar , err := k . ChatList ( ) ; err != nil {
if testVar , err := k . ChatList ( ) ; err != nil {
log . Printf ( "%+v" , err )
log . Printf ( "%+v" , err )
} else {
} else {
clearView ( "List" )
clearView ( "List" )
var recentPMs = fmt . Sprintf ( "%s---[PMs]---%s\n" , channelsHeaderColor , channelsColor )
var textBase = channelsColor . stylize ( "" )
var recentPMs = textBase . append ( channelsHeaderColor . stylize ( "---[PMs]---\n" ) )
var recentPMsCount = 0
var recentPMsCount = 0
var recentChannels = fmt . Sprintf ( "%s---[Teams]---%s\n" , channelsHeaderColor , channelsColor )
var recentChannels = textBase . append ( channelsHeaderColor . stylize ( "---[Teams]---\n" ) )
var recentChannelsCount = 0
var recentChannelsCount = 0
for _ , s := range testVar . Result . Conversations {
for _ , s := range testVar . Result . Conversations {
channels = append ( channels , s . Channel )
channels = append ( channels , s . Channel )
if s . Channel . MembersType == keybase . TEAM {
if s . Channel . MembersType == keybase . TEAM {
recentChannelsCount ++
recentChannelsCount ++
if recentChannelsCount <= ( ( maxY - 2 ) / 3 ) {
if recentChannelsCount <= ( ( maxY - 2 ) / 3 ) {
channel := fmt . Sprintf ( "%s\n\t#%s\n" , s . Channel . Name , s . Channel . TopicName )
if s . Unread {
if s . Unread {
recentChannels += fmt . Sprintf ( "%s*" , color ( 0 ) )
recentChannels = recentChannels . append ( channelUnreadColor . stylize ( "*" + channel ) )
} else {
recentChannels = recentChannels . appendString ( channel )
}
}
recentChannels += fmt . Sprintf ( "%s\n\t#%s\n%s" , s . Channel . Name , s . Channel . TopicName , channelsColor )
}
}
} else {
} else {
recentPMsCount ++
recentPMsCount ++
if recentPMsCount <= ( ( maxY - 2 ) / 3 ) {
if recentPMsCount <= ( ( maxY - 2 ) / 3 ) {
pmName := fmt . Sprintf ( "%s\n" , cleanChannelName ( s . Channel . Name ) )
if s . Unread {
if s . Unread {
recentChannels += fmt . Sprintf ( "%s*" , color ( 0 ) )
recentPMs = recentPMs . append ( channelUnreadColor . stylize ( "*" + pmName ) )
} else {
recentPMs = recentPMs . appendString ( pmName )
}
}
recentPMs += fmt . Sprintf ( "%s\n%s" , cleanChannelName ( s . Channel . Name ) , channelsColor )
}
}
}
}
}
}
time . Sleep ( 1 * time . Millisecond )
time . Sleep ( 1 * time . Millisecond )
printToView ( "List" , fmt . Sprintf ( "%s%s%s%s " , channelsColor , recentPMs , recentChannels , noColor ) )
printToView ( "List" , fmt . Sprintf ( "%s%s" , recentPMs . string ( ) , recentChannels . string ( ) ) )
go generateRecentTabCompletionSlice ( )
generateRecentTabCompletionSlice ( )
}
}
}
}
// End update/populate views automatically
// End update/populate views automatically
// Formatting
// Formatting
func formatMessageBody ( body string ) StyledString {
output := messageBodyColor . stylize ( body )
output = colorReplaceMentionMe ( output )
output = output . colorRegex ( ` _[^_]*_ ` , messageBodyColor . withItalic ( ) )
output = output . colorRegex ( ` ~[^~]*~ ` , messageBodyColor . withStrikethrough ( ) )
output = output . colorRegex ( ` @[\w_]*(\.[\w_]+)* ` , messageLinkKeybaseColor )
// TODO change how bold, italic etc works, so it uses boldOn boldOff ([1m and [22m)
output = output . colorRegex ( ` \*[^\*]*\* ` , messageBodyColor . withBold ( ) )
output = output . replaceString ( "```" , "<code>" )
// TODO make background color cover whole line
output = output . colorRegex ( "<code>(.*\n)*<code>" , messageCodeColor )
output = output . colorRegex ( "`[^`]*`" , messageCodeColor )
// mention URL
output = output . colorRegex ( ` (https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=] { 1,256}\.[a-zA-Z0-9()] { 1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)) ` , messageLinkURLColor )
return output
}
// TODO use this more
func formatChannel ( ch keybase . Channel ) StyledString {
return messageLinkKeybaseColor . stylize ( fmt . Sprintf ( "@%s#%s" , ch . Name , ch . TopicName ) )
}
func colorReplaceMentionMe ( msg StyledString ) StyledString {
return msg . colorRegex ( "(@?" + k . Username + ")" , mentionColor )
}
func colorUsername ( username string ) StyledString {
var color = messageSenderDefaultColor
if username == k . Username {
color = mentionColor
}
return color . stylize ( username )
}
func cleanChannelName ( c string ) string {
func cleanChannelName ( c string ) string {
newChannelName := strings . Replace ( c , fmt . Sprintf ( "%s," , k . Username ) , "" , 1 )
newChannelName := strings . Replace ( c , fmt . Sprintf ( "%s," , k . Username ) , "" , 1 )
return strings . Replace ( newChannelName , fmt . Sprintf ( ",%s" , k . Username ) , "" , 1 )
return strings . Replace ( newChannelName , fmt . Sprintf ( ",%s" , k . Username ) , "" , 1 )
}
}
func formatOutput ( api keybase . ChatAPI ) string {
ret := ""
func formatMessage ( api keybase . ChatAPI , formatString string ) string {
ret := messageHeaderColor . stylize ( "" )
msgType := api . Msg . Content . Type
msgType := api . Msg . Content . Type
switch msgType {
switch msgType {
case "text" , "attachment" :
case "text" , "attachment" :
var c = messageHeaderColor
ret = messageHeaderColor . stylize ( formatString )
ret = colorText ( outputFormat , c , noColor )
tm := time . Unix ( int64 ( api . Msg . SentAt ) , 0 )
tm := time . Unix ( int64 ( api . Msg . SentAt ) , 0 )
var msg = api . Msg . Content . Text . Body
var msg = formatMessageBody ( api . Msg . Content . Text . Body )
// mention teams or users
msg = colorRegex ( msg , ` (@\w*(\.\w+)*) ` , messageLinkColor , messageBodyColor )
// mention URL
msg = colorRegex ( msg , ` (https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=] { 1,256}\.[a-zA-Z0-9()] { 1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)) ` , messageLinkColor , messageBodyColor )
msg = colorText ( colorReplaceMentionMe ( msg , messageBodyColor ) , messageBodyColor , c )
if msgType == "attachment" {
if msgType == "attachment" {
msg = fmt . Sprintf ( "%s\n%s" , api . Msg . Content . Attachment . Object . Title , colorText ( fmt . Sprintf ( "[Attachment: %s]" , api . Msg . Content . Attachment . Object . Filename ) , messageAttachmentColor , c ) )
msg = messageBodyColor . stylize ( "$TITLE\n$FILE" )
attachment := api . Msg . Content . Attachment
msg = msg . replaceString ( "$TITLE" , attachment . Object . Title )
msg = msg . replace ( "$FILE" , messageAttachmentColor . stylize ( fmt . Sprintf ( "[Attachment: %s]" , attachment . Object . Filename ) ) )
}
}
user := colorUsername ( api . Msg . Sender . Username , c )
device := colorText ( api . Msg . Sender . DeviceName , messageSenderDeviceColor , c )
user := colorUsername ( api . Msg . Sender . Username )
msgID := colorText ( fmt . Sprintf ( "%d" , api . Msg . ID ) , messageIdColor , c )
device := messageSenderDeviceColor . stylize ( api . Msg . Sender . DeviceName )
ts := colorText ( tm . Format ( timeFormat ) , messageTimeColor , c )
msgID := messageIDColor . stylize ( fmt . Sprintf ( "%d" , api . Msg . ID ) )
ret = strings . Replace ( ret , "$MSG" , msg , 1 )
date := messageTimeColor . stylize ( tm . Format ( dateFormat ) )
ret = strings . Replace ( ret , "$USER" , user , 1 )
msgTime := messageTimeColor . stylize ( tm . Format ( timeFormat ) )
ret = strings . Replace ( ret , "$DEVICE" , device , 1 )
ret = strings . Replace ( ret , "$ID" , msgID , 1 )
channelName := messageIDColor . stylize ( fmt . Sprintf ( "@%s#%s" , api . Msg . Channel . Name , api . Msg . Channel . TopicName ) )
ret = strings . Replace ( ret , "$TIME" , ts , 1 )
ret = ret . replace ( "$MSG" , msg )
ret = strings . Replace ( ret , "$DATE" , colorText ( tm . Format ( dateFormat ) , messageTimeColor , c ) , 1 )
ret = ret . replace ( "$USER" , user )
ret = strings . Replace ( ret , "```" , fmt . Sprintf ( "\n<code>\n" ) , - 1 )
ret = ret . replace ( "$DEVICE" , device )
ret = ret . replace ( "$ID" , msgID )
ret = ret . replace ( "$TIME" , msgTime )
ret = ret . replace ( "$DATE" , date )
ret = ret . replace ( "$TEAM" , channelName )
}
return ret . string ( )
}
func formatOutput ( api keybase . ChatAPI ) string {
format := outputFormat
if stream {
format = outputStreamFormat
}
}
return ret
return formatMessage ( api , format )
}
}
// End formatting
// End formatting
@ -410,9 +475,7 @@ func handleMessage(api keybase.ChatAPI) {
}
}
if api . Msg . Content . Type == "text" || api . Msg . Content . Type == "attachment" {
if api . Msg . Content . Type == "text" || api . Msg . Content . Type == "attachment" {
go populateList ( )
go populateList ( )
msgBody := api . Msg . Content . Text . Body
msgSender := api . Msg . Sender . Username
msgSender := api . Msg . Sender . Username
channelName := api . Msg . Channel . Name
if ! stream {
if ! stream {
if msgSender != k . Username {
if msgSender != k . Username {
if api . Msg . Channel . MembersType == keybase . TEAM {
if api . Msg . Channel . MembersType == keybase . TEAM {
@ -421,7 +484,7 @@ func handleMessage(api keybase.ChatAPI) {
if m . Text == k . Username {
if m . Text == k . Username {
// We are in a team
// We are in a team
if topicName != channel . TopicName {
if topicName != channel . TopicName {
printToView ( "Feed" , fmt . Sprintf ( "[ %s#%s ] %s: %s" , channelName , topicName , msgSender , msgBody ) )
printInfo ( formatMessage ( api , mentionFormat ) )
fmt . Print ( "\a" )
fmt . Print ( "\a" )
}
}
@ -430,7 +493,7 @@ func handleMessage(api keybase.ChatAPI) {
}
}
} else {
} else {
if msgSender != channel . Name {
if msgSender != channel . Name {
printToView ( "Feed" , fmt . Sprintf ( "PM from @%s: %s" , cleanChannelName ( channelName ) , msgBody ) )
printInfo ( formatMessage ( api , pmFormat ) )
fmt . Print ( "\a" )
fmt . Print ( "\a" )
}
}
@ -446,10 +509,9 @@ func handleMessage(api keybase.ChatAPI) {
}
}
} else {
} else {
if api . Msg . Channel . MembersType == keybase . TEAM {
if api . Msg . Channel . MembersType == keybase . TEAM {
topicName := api . Msg . Channel . TopicName
printToView ( "Chat" , formatOutput ( api ) )
printToView ( "Chat" , fmt . Sprintf ( "@%s#%s [%s]: %s" , channelName , topicName , msgSender , msgBody ) )
} else {
} else {
printToView ( "Chat" , fmt . Sprintf ( "PM @%s [%s]: %s" , cleanChannelName ( channelName ) , msgSender , msgBody ) )
printToView ( "Chat" , formatMessage ( api , pmFormat ) )
}
}
}
}
} else {
} else {
@ -494,7 +556,7 @@ func handleInput(viewName string) error {
} else if cmd [ 0 ] == "q" || cmd [ 0 ] == "quit" {
} else if cmd [ 0 ] == "q" || cmd [ 0 ] == "quit" {
return gocui . ErrQuit
return gocui . ErrQuit
} else {
} else {
printToView ( "Feed" , fmt . Sprintf ( "Command '%s' not recognized" , cmd [ 0 ] ) )
printError ( fmt . Sprintf ( "Command '%s' not recognized" , cmd [ 0 ] ) )
return nil
return nil
}
}
}
}
@ -517,7 +579,7 @@ func sendChat(message string) {
chat := k . NewChat ( channel )
chat := k . NewChat ( channel )
_ , err := chat . Send ( message )
_ , err := chat . Send ( message )
if err != nil {
if err != nil {
printToView ( "Feed" , fmt . Sprintf ( "There was an error %+v" , err ) )
printError ( fmt . Sprintf ( "There was an error %+v" , err ) )
}
}
}
}