mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-30 06:37:14 +09:00
allow command parameters to be quoted
this way FileCompletions could contain space runes without breaking the parameter.
This commit is contained in:
@@ -716,6 +716,8 @@ func (v *View) Save(usePlugin bool) bool {
|
|||||||
if v.Buf.Path == "" {
|
if v.Buf.Path == "" {
|
||||||
filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
|
filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
|
||||||
if !canceled {
|
if !canceled {
|
||||||
|
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||||
|
filename = strings.Join(SplitCommandArgs(filename), " ")
|
||||||
v.Buf.Path = filename
|
v.Buf.Path = filename
|
||||||
v.Buf.Name = filename
|
v.Buf.Name = filename
|
||||||
} else {
|
} else {
|
||||||
@@ -1003,6 +1005,9 @@ func (v *View) OpenFile(usePlugin bool) bool {
|
|||||||
if canceled {
|
if canceled {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// the filename might or might not be quoted, so unquote first then join the strings.
|
||||||
|
filename = strings.Join(SplitCommandArgs(filename), " ")
|
||||||
|
|
||||||
home, _ := homedir.Dir()
|
home, _ := homedir.Dir()
|
||||||
filename = strings.Replace(filename, "~", home, 1)
|
filename = strings.Replace(filename, "~", home, 1)
|
||||||
file, err := ioutil.ReadFile(filename)
|
file, err := ioutil.ReadFile(filename)
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ func Bind(args []string) {
|
|||||||
// Run runs a shell command in the background
|
// Run runs a shell command in the background
|
||||||
func Run(args []string) {
|
func Run(args []string) {
|
||||||
// Run a shell command in the background (openTerm is false)
|
// Run a shell command in the background (openTerm is false)
|
||||||
HandleShellCommand(strings.Join(args, " "), false)
|
HandleShellCommand(JoinCommandArgs(args...), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit closes the main view
|
// Quit closes the main view
|
||||||
@@ -340,8 +340,8 @@ func Replace(args []string) {
|
|||||||
|
|
||||||
// RunShellCommand executes a shell command and returns the output/error
|
// RunShellCommand executes a shell command and returns the output/error
|
||||||
func RunShellCommand(input string) (string, error) {
|
func RunShellCommand(input string) (string, error) {
|
||||||
inputCmd := strings.Split(input, " ")[0]
|
inputCmd := SplitCommandArgs(input)[0]
|
||||||
args := strings.Split(input, " ")[1:]
|
args := SplitCommandArgs(input)[1:]
|
||||||
|
|
||||||
cmd := exec.Command(inputCmd, args...)
|
cmd := exec.Command(inputCmd, args...)
|
||||||
outputBytes := &bytes.Buffer{}
|
outputBytes := &bytes.Buffer{}
|
||||||
@@ -357,7 +357,7 @@ func RunShellCommand(input string) (string, error) {
|
|||||||
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
// The openTerm argument specifies whether a terminal should be opened (for viewing output
|
||||||
// or interacting with stdin)
|
// or interacting with stdin)
|
||||||
func HandleShellCommand(input string, openTerm bool) {
|
func HandleShellCommand(input string, openTerm bool) {
|
||||||
inputCmd := strings.Split(input, " ")[0]
|
inputCmd := SplitCommandArgs(input)[0]
|
||||||
if !openTerm {
|
if !openTerm {
|
||||||
// Simply run the command in the background and notify the user when it's done
|
// Simply run the command in the background and notify the user when it's done
|
||||||
messenger.Message("Running...")
|
messenger.Message("Running...")
|
||||||
@@ -382,7 +382,7 @@ func HandleShellCommand(input string, openTerm bool) {
|
|||||||
screen.Fini()
|
screen.Fini()
|
||||||
screen = nil
|
screen = nil
|
||||||
|
|
||||||
args := strings.Split(input, " ")[1:]
|
args := SplitCommandArgs(input)[1:]
|
||||||
|
|
||||||
// Set up everything for the command
|
// Set up everything for the command
|
||||||
cmd := exec.Command(inputCmd, args...)
|
cmd := exec.Command(inputCmd, args...)
|
||||||
@@ -414,12 +414,12 @@ func HandleShellCommand(input string, openTerm bool) {
|
|||||||
|
|
||||||
// HandleCommand handles input from the user
|
// HandleCommand handles input from the user
|
||||||
func HandleCommand(input string) {
|
func HandleCommand(input string) {
|
||||||
inputCmd := strings.Split(input, " ")[0]
|
args := SplitCommandArgs(input)
|
||||||
args := strings.Split(input, " ")[1:]
|
inputCmd := args[0]
|
||||||
|
|
||||||
if _, ok := commands[inputCmd]; !ok {
|
if _, ok := commands[inputCmd]; !ok {
|
||||||
messenger.Error("Unknown command ", inputCmd)
|
messenger.Error("Unknown command ", inputCmd)
|
||||||
} else {
|
} else {
|
||||||
commands[inputCmd].action(args)
|
commands[inputCmd].action(args[1:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/zyedidia/clipboard"
|
"github.com/zyedidia/clipboard"
|
||||||
"github.com/zyedidia/tcell"
|
"github.com/zyedidia/tcell"
|
||||||
@@ -196,7 +195,7 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
|
|||||||
response, canceled = m.response, false
|
response, canceled = m.response, false
|
||||||
m.history[historyType][len(m.history[historyType])-1] = response
|
m.history[historyType][len(m.history[historyType])-1] = response
|
||||||
case tcell.KeyTab:
|
case tcell.KeyTab:
|
||||||
args := strings.Split(m.response, " ")
|
args := SplitCommandArgs(m.response)
|
||||||
currentArgNum := len(args) - 1
|
currentArgNum := len(args) - 1
|
||||||
currentArg := args[currentArgNum]
|
currentArg := args[currentArgNum]
|
||||||
var completionType Completion
|
var completionType Completion
|
||||||
@@ -229,10 +228,7 @@ func (m *Messenger) Prompt(prompt, historyType string, completionTypes ...Comple
|
|||||||
}
|
}
|
||||||
|
|
||||||
if chosen != "" {
|
if chosen != "" {
|
||||||
if len(args) > 1 {
|
m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
|
||||||
chosen = " " + chosen
|
|
||||||
}
|
|
||||||
m.response = strings.Join(args[:len(args)-1], " ") + chosen
|
|
||||||
m.cursorx = Count(m.response)
|
m.cursorx = Count(m.response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -217,3 +218,62 @@ func Abs(n int) int {
|
|||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitCommandArgs seperates multiple command arguments which may be quoted.
|
||||||
|
// The returned slice contains at least one string
|
||||||
|
func SplitCommandArgs(input string) []string {
|
||||||
|
var result []string
|
||||||
|
|
||||||
|
inQuote := false
|
||||||
|
escape := false
|
||||||
|
curArg := new(bytes.Buffer)
|
||||||
|
for _, r := range input {
|
||||||
|
if !escape {
|
||||||
|
switch {
|
||||||
|
case r == '\\' && inQuote:
|
||||||
|
escape = true
|
||||||
|
continue
|
||||||
|
case r == '"' && inQuote:
|
||||||
|
inQuote = false
|
||||||
|
continue
|
||||||
|
case r == '"' && !inQuote && curArg.Len() == 0:
|
||||||
|
inQuote = true
|
||||||
|
continue
|
||||||
|
case r == ' ' && !inQuote:
|
||||||
|
result = append(result, curArg.String())
|
||||||
|
curArg.Reset()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escape = false
|
||||||
|
curArg.WriteRune(r)
|
||||||
|
}
|
||||||
|
if curArg.Len() > 0 || len(result) == 0 {
|
||||||
|
result = append(result, curArg.String())
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
|
||||||
|
func JoinCommandArgs(args ...string) string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for _, arg := range args {
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
buf.WriteRune(' ')
|
||||||
|
}
|
||||||
|
if !strings.Contains(arg, " ") {
|
||||||
|
buf.WriteString(arg)
|
||||||
|
} else {
|
||||||
|
buf.WriteRune('"')
|
||||||
|
for _, r := range arg {
|
||||||
|
if r == '"' || r == '\\' {
|
||||||
|
buf.WriteRune('\\')
|
||||||
|
}
|
||||||
|
buf.WriteRune(r)
|
||||||
|
}
|
||||||
|
buf.WriteRune('"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestNumOccurences(t *testing.T) {
|
func TestNumOccurences(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
@@ -63,3 +66,28 @@ func TestIsWordChar(t *testing.T) {
|
|||||||
t.Errorf("IsWordChar(\n)) = true")
|
t.Errorf("IsWordChar(\n)) = true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJoinAndSplitCommandArgs(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Query []string
|
||||||
|
Wanted string
|
||||||
|
}{
|
||||||
|
{[]string{`test case`}, `"test case"`},
|
||||||
|
{[]string{`quote "test"`}, `"quote \"test\""`},
|
||||||
|
{[]string{`slash\\\ test`}, `"slash\\\\\\ test"`},
|
||||||
|
{[]string{`path 1`, `path\" 2`}, `"path 1" "path\\\" 2"`},
|
||||||
|
{[]string{`foo`}, `foo`},
|
||||||
|
{[]string{`foo\"bar`}, `foo\"bar`},
|
||||||
|
{[]string{``}, ``},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
if result := JoinCommandArgs(test.Query...); test.Wanted != result {
|
||||||
|
t.Errorf("JoinCommandArgs failed at Test %d\nGot: %v", i, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := SplitCommandArgs(test.Wanted); !reflect.DeepEqual(test.Query, result) {
|
||||||
|
t.Errorf("SplitCommandArgs failed at Test %d\nGot: %v", i, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user