mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-30 06:37:14 +09:00
Allows opening files using full path on Windows (#1126)
* 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
This commit is contained in:
committed by
Zachary Yedidia
parent
d7f7d845b9
commit
efe343b37c
@@ -77,12 +77,12 @@ type SerializedBuffer struct {
|
|||||||
ModTime time.Time
|
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 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
|
// and an error if the file is a directory
|
||||||
func NewBufferFromFile(path string) (*Buffer, error) {
|
func NewBufferFromFile(path string) (*Buffer, error) {
|
||||||
filename := GetPath(path)
|
filename, cursorPosition := GetPathAndCursorPosition(path)
|
||||||
filename = ReplaceHome(filename)
|
filename = ReplaceHome(filename)
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
fileInfo, _ := os.Stat(filename)
|
fileInfo, _ := os.Stat(filename)
|
||||||
@@ -96,41 +96,22 @@ func NewBufferFromFile(path string) (*Buffer, error) {
|
|||||||
var buf *Buffer
|
var buf *Buffer
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// File does not exist -- create an empty buffer with that name
|
// File does not exist -- create an empty buffer with that name
|
||||||
buf = NewBufferFromString("", path)
|
buf = NewBufferFromString("", filename)
|
||||||
} else {
|
} else {
|
||||||
buf = NewBuffer(file, FSize(file), path)
|
buf = NewBuffer(file, FSize(file), filename, cursorPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBufferFromString creates a new buffer containing the given
|
// NewBufferFromString creates a new buffer containing the given string
|
||||||
// string
|
|
||||||
func NewBufferFromString(text, path string) *Buffer {
|
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
|
// NewBuffer creates a new buffer from a given reader with a given path
|
||||||
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
|
||||||
startpos := Loc{0, 0}
|
cursorLocation, cursorLocationError := ParseCursorLocation(cursorPosition)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if path != "" {
|
if path != "" {
|
||||||
for _, tab := range tabs {
|
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
|
// Put the cursor at the first spot
|
||||||
cursorStartX := 0
|
cursorStartX := 0
|
||||||
cursorStartY := 0
|
cursorStartY := 0
|
||||||
// If -startpos LINE,COL was passed, use start position LINE,COL
|
// If -cursorLocation LINE,COL was passed, use start position LINE,COL
|
||||||
if len(*flagStartPos) > 0 || !startposErr {
|
if len(*flagStartPos) > 0 || cursorLocationError == nil {
|
||||||
positions := strings.Split(*flagStartPos, ",")
|
positions := strings.Split(*flagStartPos, ",")
|
||||||
if len(positions) == 2 || !startposErr {
|
if len(positions) == 2 || cursorLocationError == nil {
|
||||||
var lineNum, colNum int
|
var lineNum, colNum int
|
||||||
var errPos1, errPos2 error
|
var errPos1, errPos2 error
|
||||||
if !startposErr {
|
if cursorLocationError == nil {
|
||||||
lineNum = startpos.Y
|
lineNum = cursorLocation.Y
|
||||||
colNum = startpos.X
|
colNum = cursorLocation.X
|
||||||
} else {
|
} else {
|
||||||
lineNum, errPos1 = strconv.Atoi(positions[0])
|
lineNum, errPos1 = strconv.Atoi(positions[0])
|
||||||
colNum, errPos2 = strconv.Atoi(positions[1])
|
colNum, errPos2 = strconv.Atoi(positions[1])
|
||||||
@@ -219,7 +200,7 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
|
|||||||
|
|
||||||
InitLocalSettings(b)
|
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
|
// If either savecursor or saveundo is turned on, we need to load the serialized information
|
||||||
// from ~/.config/micro/buffers
|
// from ~/.config/micro/buffers
|
||||||
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
|
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Util.go is a collection of utility functions that are used throughout
|
// 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)
|
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
|
// This is used for opening files like util.go:10:5 to specify a line and column
|
||||||
func GetPath(path string) string {
|
// Special cases like Windows Absolute path (C:\myfile.txt:10:5) are handled correctly.
|
||||||
if strings.Contains(path, ":") {
|
func GetPathAndCursorPosition(path string) (string, []string) {
|
||||||
path = strings.Split(path, ":")[0]
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,3 +103,239 @@ func TestWidthOfLargeRunes(t *testing.T) {
|
|||||||
t.Error("WidthOfLargeRunes 5 Failed. Got", w)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user