Skip to content
This repository has been archived by the owner on May 22, 2023. It is now read-only.

Commit

Permalink
Feature/fuzzy goto (#50)
Browse files Browse the repository at this point in the history
* added command to goto contact with fuzzy matching

* change hotkey to

* log formatter fix
  • Loading branch information
derricw authored Dec 1, 2021
1 parent 4687fa8 commit 91e8ab8
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ If you are updating from a previous version, I recommend deleting your conversat
* `k` - Scroll Up
* `J` - Next Contact
* `K` - Previous Contact
* `t` - Use fzf to goto contact with fuzzy matching
* `a` - Attach file (sent with next message)
* `A` - Use fzf to attach a file
* `/` - Filter conversation by providing a pattern
Expand Down Expand Up @@ -134,7 +135,6 @@ Here is a list of features I'd like to add soonish.
* gui configuration
* colors and border styles
* let user re-sort contact list (for example alphabetically)
* command to go to contact with fuzzy matching
* use dbus to send instead of signal-cli, to avoid having to spin up the JVM
* there is still some data that I'm dropping on the floor (I believe it to be the "typing indicator" messages)
* weechat/BitlBee plugin that uses the siggo model without the UI
Expand Down
22 changes: 22 additions & 0 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,28 @@ func (cl ContactList) SortedByIndex() []*Contact {
return list
}

// StringSlice returns the contact list as a string slice
func (cl ContactList) StringSlice() []string {
list := cl.SortedByName()
s := []string{}
for _, contact := range list {
s = append(s, contact.String())
}
return s
}

// FindContact searches the contact list for the first contact whose name matches the
// supplied pattern. Returns nil if no match is found.
func (cl ContactList) FindContact(pattern string) *Contact {
for _, contact := range cl {
// TODO: replace with regex pattern matching
if pattern == contact.Name || pattern == contact.alias || pattern == contact.String() {
return contact
}
}
return nil
}

type Message struct {
Content string `json:"content"`
Timestamp int64 `json:"timestamp"`
Expand Down
32 changes: 0 additions & 32 deletions widgets/attach.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package widgets

import (
"bytes"
"fmt"
"io/ioutil"
"mime"
"net/http"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"

"github.com/atotto/clipboard"
Expand Down Expand Up @@ -70,33 +65,6 @@ func NewAttachInput(parent *ChatWindow) *CommandInput {
return ci
}

// FZFFile opens up FZF and fuzzy-searches for a file
func FZFFile() (string, error) {
cmd := exec.Command("fzf", "--prompt=attach: ", "--margin=10%,10%,10%,10%", "--border")
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
buf := bytes.NewBuffer([]byte{})
cmd.Stdout = buf
usr, err := user.Current()
if err != nil {
// if we can find home, we run fzf from there
return "", err
}
cmd.Dir = usr.HomeDir

if err = cmd.Run(); cmd.ProcessState.ExitCode() == 130 {
// exit code 130 is when we cancel FZF
// not an error
return "", nil
} else if err != nil {
return "", fmt.Errorf("failed to find a file: %s", err)
}

f := strings.TrimSpace(buf.String())
path := filepath.Join(usr.HomeDir, f)
return path, err
}

// AttachFromClipboard attaches a file directly from clipboard. Text is just pasted into the
// message.
func AttachFromClipboard(parent *ChatWindow) error {
Expand Down
43 changes: 42 additions & 1 deletion widgets/chatwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,17 @@ func (c *ChatWindow) SetCurrentContact(contact *model.Contact) error {
return nil
}

// SetCurrentContactByString sets the active contact to the first contact whose
// string representation matches
func (c *ChatWindow) SetCurrentContactByString(contactName string) error {
log.Debugf("setting current contact to: %v", contactName)
contact := c.siggo.Contacts().FindContact(contactName)
if contact != nil {
return c.SetCurrentContact(contact)
}
return fmt.Errorf("couldn't find contact: %s", contactName)
}

// NextUnreadMessage searches for the next conversation with unread messages and makes that the
// active conversation.
func (c *ChatWindow) NextUnreadMessage() error {
Expand Down Expand Up @@ -395,6 +406,33 @@ func (c *ChatWindow) FancyAttach() {
}
}

// FuzzyGoTo goes to a contact or group with fuzzy matching
func (c *ChatWindow) FuzzyGoTo() {
contactName := ""
var err error
_, err = exec.LookPath("fzf")
if err != nil {
c.SetErrorStatus(fmt.Errorf("failed to find in PATH: fzf"))
return
}
contactList := c.siggo.Contacts().StringSlice()
success := c.app.Suspend(func() {
contactName, err = FZFList(contactList)
})
time.Sleep(100 * time.Millisecond)
if !success {
c.SetErrorStatus(fmt.Errorf("failed to suspend siggo"))
return
}
if err != nil {
c.SetErrorStatus(err)
}
if contactName != "" {
log.Infof("going to contact: %s", contactName)
c.SetCurrentContactByString(contactName)
}
}

// ShowTempSentMsg shows a temporary message when a message is sent but before delivery.
// Only displayed for the second or two after a message is sent.
func (c *ChatWindow) ShowTempSentMsg(msg string) {
Expand Down Expand Up @@ -507,9 +545,12 @@ func NewChatWindow(siggo *model.Siggo, app *tview.Application) *ChatWindow {
case 47: // /
w.ShowFilterInput()
return nil
case 65: // a
case 65: // A
w.FancyAttach()
return nil
case 116: // t
w.FuzzyGoTo()
return nil
case 112: // p
w.Paste()
return nil
Expand Down
59 changes: 59 additions & 0 deletions widgets/fzf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package widgets

import (
"bytes"
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
)

// FZFFile opens up FZF and fuzzy-searches for a file
func FZFFile() (string, error) {
cmd := exec.Command("fzf", "--prompt=attach: ", "--margin=10%,10%,10%,10%", "--border")
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
buf := bytes.NewBuffer([]byte{})
cmd.Stdout = buf
usr, err := user.Current()
if err != nil {
// if we can find home, we run fzf from there
return "", err
}
cmd.Dir = usr.HomeDir

if err = cmd.Run(); cmd.ProcessState.ExitCode() == 130 {
// exit code 130 is when we cancel FZF
// not an error
return "", nil
} else if err != nil {
return "", fmt.Errorf("failed to find a file: %s", err)
}

f := strings.TrimSpace(buf.String())
path := filepath.Join(usr.HomeDir, f)
return path, err
}

// FZFList opens up FZF and fuzzy-searches from a list of strings
func FZFList(items []string) (string, error) {
cmd := exec.Command("fzf", "--prompt=contact: ", "--margin=10%,10%,10%,10%", "--border")
//cmd.Stdin = os.Stdin
inputBuffer := strings.NewReader(strings.Join(items, "\n"))
cmd.Stdin = inputBuffer
cmd.Stderr = os.Stderr
buf := bytes.NewBuffer([]byte{})
cmd.Stdout = buf
if err := cmd.Run(); cmd.ProcessState.ExitCode() == 130 {
// exit code 130 is when we cancel FZF
// not an error
return "", nil
} else if err != nil {
return "", fmt.Errorf("failed to call fzf: %s", err)
}

result := strings.TrimSpace(buf.String())
return result, nil
}

0 comments on commit 91e8ab8

Please sign in to comment.