diff --git a/uri.go b/uri.go index 7deaad1..d88ed5e 100644 --- a/uri.go +++ b/uri.go @@ -11,6 +11,8 @@ import ( "net/url" "path/filepath" "regexp" + "strings" + "unicode" "github.com/arduino/go-paths-helper" "go.bug.st/json" @@ -29,7 +31,11 @@ type DocumentURI struct { // NilURI is the empty DocumentURI var NilURI = DocumentURI{} -var expDriveID = regexp.MustCompile("^/[a-zA-Z]:") +// for example, `"/c:"` or `"/A:"` +var expDriveWithLeadingSlashID = regexp.MustCompile("^/[a-zA-Z]:") + +// for example, `"C:"` or `"A:"` +var expUppercaseDriveID = regexp.MustCompile("^[A-Z]:") // AsPath convert the DocumentURI to a paths.Path func (uri DocumentURI) AsPath() *paths.Path { @@ -39,12 +45,23 @@ func (uri DocumentURI) AsPath() *paths.Path { // unbox convert the DocumentURI to a file path string func (uri DocumentURI) unbox() string { path := uri.url.Path - if expDriveID.MatchString(path) { + if expDriveWithLeadingSlashID.MatchString(path) { return path[1:] } return path } +// Converts `"C:"` to `"c:"` to be compatible with VS Code URI's drive letter casing +// https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 +func lowercaseDriveSegment(pathSegment string) string { + if expUppercaseDriveID.MatchString(pathSegment) { + chars := []rune(pathSegment) + chars[0] = unicode.ToLower(chars[0]) + return string(chars) + } + return pathSegment +} + func (uri DocumentURI) String() string { return uri.url.String() } @@ -68,11 +85,23 @@ func NewDocumentURI(path string) DocumentURI { if len(path) == 0 || path[0] != '/' { path = "/" + path } - uri, err := NewDocumentURIFromURL("file://") + segments := strings.Split(path, "/") + encodedSegments := make([]string, len(segments)) + for i, segment := range segments { + if len(segment) == 0 { + encodedSegments[i] = segment + } else { + segment = lowercaseDriveSegment(segment) + segment = url.QueryEscape(segment) + // Spaces must be turned into `%20`. Otherwise, `url.QueryEscape`` encodes them to `+`. + encodedSegments[i] = strings.ReplaceAll(segment, "+", "%20") + } + } + urlPath := strings.Join(encodedSegments, "/") + uri, err := NewDocumentURIFromURL("file://" + urlPath) if err != nil { panic(err) } - uri.url.Path = path return uri } @@ -89,7 +118,7 @@ func NewDocumentURIFromURL(inURL string) (DocumentURI, error) { func (uri *DocumentURI) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { - return fmt.Errorf("expoected JSON string for DocumentURI: %s", err) + return fmt.Errorf("expected JSON string for DocumentURI: %s", err) } newDocURI, err := NewDocumentURIFromURL(s) diff --git a/uri_test.go b/uri_test.go index dd4f537..5503d96 100644 --- a/uri_test.go +++ b/uri_test.go @@ -48,7 +48,9 @@ func TestPathToUri(t *testing.T) { toSlash = windowsToSlash // Emulate windows cases d = NewDocumentURI("C:\\Users\\test\\Sketch.ino") - require.Equal(t, "file:///C:/Users/test/Sketch.ino", d.String()) + require.Equal(t, "file:///c%3A/Users/test/Sketch.ino", d.String()) // driver letter is converted to lower case https://github.com/Microsoft/vscode/issues/68325#issuecomment-462239992 + d = NewDocumentURI("c:\\Users\\test\\Sketch.ino") + require.Equal(t, "file:///c%3A/Users/test/Sketch.ino", d.String()) d = NewDocumentURI("/Users/test/Sketch.ino") require.Equal(t, "file:///Users/test/Sketch.ino", d.String()) d = NewDocumentURI("\U0001F61B") @@ -70,7 +72,7 @@ func TestJSONMarshalUnmarshal(t *testing.T) { d = NewDocumentURI("C:\\Users\\test\\Sketch.ino") data, err := json.Marshal(d) require.NoError(t, err) - require.Equal(t, `"file:///C:/Users/test/Sketch.ino"`, string(data)) + require.Equal(t, `"file:///c%3A/Users/test/Sketch.ino"`, string(data)) d = NewDocumentURI("/Users/test/Sketch.ino") data, err = json.Marshal(d)