]> git.lizzy.rs Git - micro.git/commitdiff
Allows opening files using full path on Windows (#1126)
authorDimitar Borislavov Tasev <dimtasev@gmail.com>
Sun, 3 Jun 2018 21:13:03 +0000 (22:13 +0100)
committerZachary Yedidia <zyedidia@gmail.com>
Sun, 3 Jun 2018 21:13:03 +0000 (17:13 -0400)
* Now can open Windows full-path from command line arg

Example that now works: micro.exe D:\myfile.txt

* Now correctly retrieves the path from the input path string. Except for single-letter filenames

* Fixed line/cols, need to make the code prettier

* Fixed path matching with regex by @Pariador

* Fixed not stripping the line/col args from file path

* Added tests for ParseCursorLocation

cmd/micro/buffer.go
cmd/micro/util.go
cmd/micro/util_test.go

index a9de2a7a3152dbc54ee0d0d91c6cced109f55855..8b95109e595dd277c04da00bc2e4ade8bcd78100 100644 (file)
@@ -77,12 +77,12 @@ type SerializedBuffer struct {
        ModTime      time.Time
 }
 
-// NewBufferFromFile opens a new buffer using the given filepath
+// NewBufferFromFile opens a new buffer using the given path
 // It will also automatically handle `~`, and line/column with filename:l:c
-// It will return an empty buffer if the filepath does not exist
+// It will return an empty buffer if the path does not exist
 // and an error if the file is a directory
 func NewBufferFromFile(path string) (*Buffer, error) {
-       filename := GetPath(path)
+       filename, cursorPosition := GetPathAndCursorPosition(path)
        filename = ReplaceHome(filename)
        file, err := os.Open(filename)
        fileInfo, _ := os.Stat(filename)
@@ -96,41 +96,22 @@ func NewBufferFromFile(path string) (*Buffer, error) {
        var buf *Buffer
        if err != nil {
                // File does not exist -- create an empty buffer with that name
-               buf = NewBufferFromString("", path)
+               buf = NewBufferFromString("", filename)
        } else {
-               buf = NewBuffer(file, FSize(file), path)
+               buf = NewBuffer(file, FSize(file), filename, cursorPosition)
        }
 
        return buf, nil
 }
 
-// NewBufferFromString creates a new buffer containing the given
-// string
+// NewBufferFromString creates a new buffer containing the given string
 func NewBufferFromString(text, path string) *Buffer {
-       return NewBuffer(strings.NewReader(text), int64(len(text)), path)
+       return NewBuffer(strings.NewReader(text), int64(len(text)), path, []string{"0", "0"})
 }
 
 // NewBuffer creates a new buffer from a given reader with a given path
-func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
-       startpos := Loc{0, 0}
-       startposErr := true
-       if strings.Contains(path, ":") {
-               var err error
-               split := strings.Split(path, ":")
-               path = split[0]
-               startpos.Y, err = strconv.Atoi(split[1])
-               if err != nil {
-                       messenger.Error("Error opening file: ", err)
-               } else {
-                       startposErr = false
-                       if len(split) > 2 {
-                               startpos.X, err = strconv.Atoi(split[2])
-                               if err != nil {
-                                       messenger.Error("Error opening file: ", err)
-                               }
-                       }
-               }
-       }
+func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
+       cursorLocation, cursorLocationError := ParseCursorLocation(cursorPosition)
 
        if path != "" {
                for _, tab := range tabs {
@@ -178,15 +159,15 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
        // Put the cursor at the first spot
        cursorStartX := 0
        cursorStartY := 0
-       // If -startpos LINE,COL was passed, use start position LINE,COL
-       if len(*flagStartPos) > 0 || !startposErr {
+       // If -cursorLocation LINE,COL was passed, use start position LINE,COL
+       if len(*flagStartPos) > 0 || cursorLocationError == nil {
                positions := strings.Split(*flagStartPos, ",")
-               if len(positions) == 2 || !startposErr {
+               if len(positions) == 2 || cursorLocationError == nil {
                        var lineNum, colNum int
                        var errPos1, errPos2 error
-                       if !startposErr {
-                               lineNum = startpos.Y
-                               colNum = startpos.X
+                       if cursorLocationError == nil {
+                               lineNum = cursorLocation.Y
+                               colNum = cursorLocation.X
                        } else {
                                lineNum, errPos1 = strconv.Atoi(positions[0])
                                colNum, errPos2 = strconv.Atoi(positions[1])
@@ -219,7 +200,7 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
 
        InitLocalSettings(b)
 
-       if startposErr && len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
+       if cursorLocationError != nil && len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
                // If either savecursor or saveundo is turned on, we need to load the serialized information
                // from ~/.config/micro/buffers
                file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
index eb8a1977607df70a0291d012345c82480dc2ce38..09d116a23a3b4d164d705079fb45b9abea95bf4d 100644 (file)
@@ -12,6 +12,7 @@ import (
        "unicode/utf8"
 
        "github.com/mattn/go-runewidth"
+       "regexp"
 )
 
 // Util.go is a collection of utility functions that are used throughout
@@ -356,11 +357,43 @@ func ReplaceHome(path string) string {
        return strings.Replace(path, homeString, home, 1)
 }
 
-// GetPath returns a filename without everything following a `:`
+// GetPathAndCursorPosition returns a filename without everything following a `:`
 // This is used for opening files like util.go:10:5 to specify a line and column
-func GetPath(path string) string {
-       if strings.Contains(path, ":") {
-               path = strings.Split(path, ":")[0]
+// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
+func GetPathAndCursorPosition(path string) (string, []string) {
+       re := regexp.MustCompile(`([\s\S]+?)(?::(\d+))(?::(\d+))?`)
+       match := re.FindStringSubmatch(path)
+       // no lines/columns were specified in the path, return just the path with cursor at 0, 0
+       if len(match) == 0 {
+               return path, []string{"0", "0"}
+       } else if match[len(match)-1] != "" {
+               // if the last capture group match isn't empty then both line and column were provided
+               return match[1], match[2:]
        }
-       return path
+       // if it was empty, then only a line was provided, so default to column 0
+       return match[1], []string{match[2], "0"}
+}
+
+func ParseCursorLocation(cursorPositions []string) (Loc, error) {
+       startpos := Loc{0, 0}
+       var err error
+
+       // if no positions are available exit early
+       if len(cursorPositions) == 0 {
+               return startpos, err
+       }
+
+       startpos.Y, err = strconv.Atoi(cursorPositions[0])
+       if err != nil {
+               messenger.Error("Error parsing cursor position: ", err)
+       } else {
+               if len(cursorPositions) > 1 {
+                       startpos.X, err = strconv.Atoi(cursorPositions[1])
+                       if err != nil {
+                               messenger.Error("Error parsing cursor position: ", err)
+                       }
+               }
+       }
+
+       return startpos, err
 }
index 51a24e024e79d93c7a63e537799f3ec7bc13fc66..e62fee5fc87cc737dd96cd53f57027f4267f9918 100644 (file)
@@ -103,3 +103,239 @@ func TestWidthOfLargeRunes(t *testing.T) {
                t.Error("WidthOfLargeRunes 5 Failed. Got", w)
        }
 }
+
+func assertEqual(t *testing.T, expected interface{}, result interface{}) {
+       if expected != result {
+               t.Fatalf("Expected: %d != Got: %d", expected, result)
+       }
+}
+
+func assertTrue(t *testing.T, condition bool) {
+       if !condition {
+               t.Fatalf("Condition was not true. Got false")
+       }
+}
+
+func TestGetPathRelativeWithDot(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("./myfile:10:5")
+
+       assertEqual(t, path, "./myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, "5", cursorPosition[1])
+}
+func TestGetPathRelativeWithDotWindows(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10:5")
+
+       assertEqual(t, path, ".\\myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, cursorPosition[1], "5")
+}
+func TestGetPathRelativeNoDot(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("myfile:10:5")
+
+       assertEqual(t, path, "myfile")
+       assertEqual(t, "10", cursorPosition[0])
+
+       assertEqual(t, cursorPosition[1], "5")
+}
+func TestGetPathAbsoluteWindows(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10:5")
+
+       assertEqual(t, path, "C:\\myfile")
+       assertEqual(t, "10", cursorPosition[0])
+
+       assertEqual(t, cursorPosition[1], "5")
+
+       path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10:5")
+
+       assertEqual(t, path, "C:/myfile")
+       assertEqual(t, "10", cursorPosition[0])
+
+       assertEqual(t, cursorPosition[1], "5")
+}
+func TestGetPathAbsoluteUnix(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10:5")
+
+       assertEqual(t, path, "/home/user/myfile")
+       assertEqual(t, "10", cursorPosition[0])
+
+       assertEqual(t, cursorPosition[1], "5")
+}
+
+func TestGetPathRelativeWithDotWithoutLineAndColumn(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("./myfile")
+
+       assertEqual(t, path, "./myfile")
+       assertEqual(t, "0", cursorPosition[0])
+
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathRelativeWithDotWindowsWithoutLineAndColumn(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition(".\\myfile")
+
+       assertEqual(t, path, ".\\myfile")
+       assertEqual(t, "0", cursorPosition[0])
+
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathRelativeNoDotWithoutLineAndColumn(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("myfile")
+
+       assertEqual(t, path, "myfile")
+       assertEqual(t, "0", cursorPosition[0])
+
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathAbsoluteWindowsWithoutLineAndColumn(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("C:\\myfile")
+
+       assertEqual(t, path, "C:\\myfile")
+       assertEqual(t, "0", cursorPosition[0])
+
+       assertEqual(t, "0", cursorPosition[1])
+
+       path, cursorPosition = GetPathAndCursorPosition("C:/myfile")
+
+       assertEqual(t, path, "C:/myfile")
+       assertEqual(t, "0", cursorPosition[0])
+
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathAbsoluteUnixWithoutLineAndColumn(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile")
+
+       assertEqual(t, path, "/home/user/myfile")
+       assertEqual(t, "0", cursorPosition[0])
+
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathSingleLetterFileRelativePath(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("a:5:6")
+
+       assertEqual(t, path, "a")
+       assertEqual(t, "5", cursorPosition[0])
+       assertEqual(t, "6", cursorPosition[1])
+}
+func TestGetPathSingleLetterFileAbsolutePathWindows(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("C:\\a:5:6")
+
+       assertEqual(t, path, "C:\\a")
+       assertEqual(t, "5", cursorPosition[0])
+       assertEqual(t, "6", cursorPosition[1])
+
+       path, cursorPosition = GetPathAndCursorPosition("C:/a:5:6")
+
+       assertEqual(t, path, "C:/a")
+       assertEqual(t, "5", cursorPosition[0])
+       assertEqual(t, "6", cursorPosition[1])
+}
+func TestGetPathSingleLetterFileAbsolutePathUnix(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("/home/user/a:5:6")
+
+       assertEqual(t, path, "/home/user/a")
+       assertEqual(t, "5", cursorPosition[0])
+       assertEqual(t, "6", cursorPosition[1])
+}
+func TestGetPathSingleLetterFileAbsolutePathWindowsWithoutLineAndColumn(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("C:\\a")
+
+       assertEqual(t, path, "C:\\a")
+       assertEqual(t, "0", cursorPosition[0])
+
+       assertEqual(t, "0", cursorPosition[1])
+
+       path, cursorPosition = GetPathAndCursorPosition("C:/a")
+
+       assertEqual(t, path, "C:/a")
+       assertEqual(t, "0", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathSingleLetterFileAbsolutePathUnixWithoutLineAndColumn(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("/home/user/a")
+
+       assertEqual(t, path, "/home/user/a")
+       assertEqual(t, "0", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+}
+
+// TODO test for only line without a column
+func TestGetPathRelativeWithDotOnlyLine(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("./myfile:10")
+
+       assertEqual(t, path, "./myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathRelativeWithDotWindowsOnlyLine(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition(".\\myfile:10")
+
+       assertEqual(t, path, ".\\myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathRelativeNoDotOnlyLine(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("myfile:10")
+
+       assertEqual(t, path, "myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathAbsoluteWindowsOnlyLine(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("C:\\myfile:10")
+
+       assertEqual(t, path, "C:\\myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+
+       path, cursorPosition = GetPathAndCursorPosition("C:/myfile:10")
+
+       assertEqual(t, path, "C:/myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestGetPathAbsoluteUnixOnlyLine(t *testing.T) {
+       path, cursorPosition := GetPathAndCursorPosition("/home/user/myfile:10")
+
+       assertEqual(t, path, "/home/user/myfile")
+       assertEqual(t, "10", cursorPosition[0])
+       assertEqual(t, "0", cursorPosition[1])
+}
+func TestParseCursorLocationOneArg(t *testing.T) {
+       location, err := ParseCursorLocation([]string{"3"})
+
+       assertEqual(t, 3, location.Y)
+       assertEqual(t, 0, location.X)
+       assertEqual(t, nil, err)
+}
+func TestParseCursorLocationTwoArgs(t *testing.T) {
+       location, err := ParseCursorLocation([]string{"3", "15"})
+
+       assertEqual(t, 3, location.Y)
+       assertEqual(t, 15, location.X)
+       assertEqual(t, nil, err)
+}
+func TestParseCursorLocationNoArgs(t *testing.T) {
+       location, err := ParseCursorLocation([]string{})
+       // the expected result is the start position - 0, 0
+       assertEqual(t, 0, location.Y)
+       assertEqual(t, 0, location.X)
+       assertEqual(t, nil, err)
+}
+func TestParseCursorLocationFirstArgNotValidNumber(t *testing.T) {
+       // the messenger is necessary as ParseCursorLocation
+       // puts a message in it on error
+       messenger = new(Messenger)
+       _, err := ParseCursorLocation([]string{"apples", "1"})
+       // the expected result is the start position - 0, 0
+       assertTrue(t, messenger.hasMessage)
+       assertTrue(t, err != nil)
+}
+func TestParseCursorLocationSecondArgNotValidNumber(t *testing.T) {
+       // the messenger is necessary as ParseCursorLocation
+       // puts a message in it on error
+       messenger = new(Messenger)
+       _, err := ParseCursorLocation([]string{"1", "apples"})
+       // the expected result is the start position - 0, 0
+       assertTrue(t, messenger.hasMessage)
+       assertTrue(t, err != nil)
+}