From e2079ac88e5391d5ac9a0e3d4559ec43c3611cc8 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Thu, 9 Jun 2016 10:03:50 -0400 Subject: [PATCH] Add file completion to OpenFile (CtrlO) with tab --- cmd/micro/bindings.go | 10 +++++----- cmd/micro/messenger.go | 43 +++++++++++++++++++++++++++++++++++++++--- cmd/micro/view.go | 2 +- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/cmd/micro/bindings.go b/cmd/micro/bindings.go index 18e2d6a1..22a3f3b3 100644 --- a/cmd/micro/bindings.go +++ b/cmd/micro/bindings.go @@ -731,7 +731,7 @@ func (v *View) Save() bool { } // If this is an empty buffer, ask for a filename if v.Buf.Path == "" { - filename, canceled := messenger.Prompt("Filename: ", "Save") + filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion) if !canceled { v.Buf.Path = filename v.Buf.Name = filename @@ -906,7 +906,7 @@ func (v *View) SelectAll() bool { // OpenFile opens a new file in the buffer func (v *View) OpenFile() bool { if v.CanClose("Continue? (yes, no, save) ") { - filename, canceled := messenger.Prompt("File to open: ", "Open") + filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion) if canceled { return false } @@ -1018,7 +1018,7 @@ func (v *View) ToggleRuler() bool { // JumpLine jumps to a line and moves the view accordingly. func (v *View) JumpLine() bool { // Prompt for line number - linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber") + linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion) if canceled { return false } @@ -1061,7 +1061,7 @@ func (v *View) ToggleHelp() bool { // ShellMode opens a terminal to run a shell command func (v *View) ShellMode() bool { - input, canceled := messenger.Prompt("$ ", "Shell") + input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion) if !canceled { // The true here is for openTerm to make the command interactive HandleShellCommand(input, true) @@ -1071,7 +1071,7 @@ func (v *View) ShellMode() bool { // CommandMode lets the user enter a command func (v *View) CommandMode() bool { - input, canceled := messenger.Prompt("> ", "Command") + input, canceled := messenger.Prompt("> ", "Command", NoCompletion) if !canceled { HandleCommand(input) } diff --git a/cmd/micro/messenger.go b/cmd/micro/messenger.go index b75a779f..50100c40 100644 --- a/cmd/micro/messenger.go +++ b/cmd/micro/messenger.go @@ -4,8 +4,10 @@ import ( "bufio" "bytes" "fmt" + "io/ioutil" "os" "strconv" + "strings" "github.com/zyedidia/tcell" ) @@ -122,9 +124,16 @@ func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) { } } +type Completion int + +const ( + NoCompletion Completion = iota + FileCompletion +) + // Prompt sends the user a message and waits for a response to be typed in // This function blocks the main loop while waiting for input -func (m *Messenger) Prompt(prompt, historyType string) (string, bool) { +func (m *Messenger) Prompt(prompt, historyType string, completionType Completion) (string, bool) { m.hasPrompt = true m.Message(prompt) if _, ok := m.history[historyType]; !ok { @@ -153,6 +162,34 @@ func (m *Messenger) Prompt(prompt, historyType string) (string, bool) { m.hasPrompt = false response, canceled = m.response, false m.history[historyType][len(m.history[historyType])-1] = response + case tcell.KeyTab: + if completionType == FileCompletion { + dirs := strings.Split(m.response, "/") + var files []os.FileInfo + var err error + if len(dirs) > 1 { + files, err = ioutil.ReadDir(strings.Join(dirs[:len(dirs)-1], "/")) + } else { + files, err = ioutil.ReadDir(".") + } + if err != nil { + continue + } + var suggestions []string + for _, f := range files { + name := f.Name() + if f.IsDir() { + name += "/" + } + if strings.HasPrefix(name, dirs[len(dirs)-1]) { + suggestions = append(suggestions, name) + } + } + if len(suggestions) == 1 { + m.response = strings.Join(dirs[:len(dirs)-1], "/") + "/" + suggestions[0] + m.cursorx = Count(m.response) + } + } } } @@ -177,13 +214,13 @@ func (m *Messenger) HandleEvent(event tcell.Event, history []string) { if m.historyNum > 0 { m.historyNum-- m.response = history[m.historyNum] - m.cursorx = len(m.response) + m.cursorx = Count(m.response) } case tcell.KeyDown: if m.historyNum < len(history)-1 { m.historyNum++ m.response = history[m.historyNum] - m.cursorx = len(m.response) + m.cursorx = Count(m.response) } case tcell.KeyLeft: if m.cursorx > 0 { diff --git a/cmd/micro/view.go b/cmd/micro/view.go index c12891ab..8e60c636 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -165,7 +165,7 @@ func (v *View) ScrollDown(n int) { // The message is what to print after saying "You have unsaved changes. " func (v *View) CanClose(msg string) bool { if v.Buf.IsModified { - quit, canceled := messenger.Prompt("You have unsaved changes. "+msg, "Unsaved") + quit, canceled := messenger.Prompt("You have unsaved changes. "+msg, "Unsaved", NoCompletion) if !canceled { if strings.ToLower(quit) == "yes" || strings.ToLower(quit) == "y" { return true