Skip to content

Commit 743f729

Browse files
Replace : with %3A after file:/// scheme in URIs to match VS Code's LSP client (#4345)
Co-authored-by: Stefan VanBuren <[email protected]>
1 parent 5781e7d commit 743f729

File tree

11 files changed

+100
-25
lines changed

11 files changed

+100
-25
lines changed

private/buf/buflsp/buflsp_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func setupLSPServer(
146146
})
147147

148148
testWorkspaceDir := filepath.Dir(testProtoPath)
149-
testURI := uri.New(testProtoPath)
149+
testURI := buflsp.FilePathToURI(testProtoPath)
150150
var initResult protocol.InitializeResult
151151
_, initErr := clientJSONConn.Call(ctx, protocol.MethodInitialize, &protocol.InitializeParams{
152152
RootURI: uri.New(testWorkspaceDir),

private/buf/buflsp/definition_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ package buflsp_test
1616

1717
import (
1818
"path/filepath"
19-
"strings"
2019
"testing"
2120

21+
"github.com/bufbuild/buf/private/buf/buflsp"
2222
"github.com/stretchr/testify/assert"
2323
"github.com/stretchr/testify/require"
2424
"go.lsp.dev/protocol"
25-
"go.lsp.dev/uri"
2625
)
2726

2827
func TestDefinition(t *testing.T) {
@@ -37,7 +36,7 @@ func TestDefinition(t *testing.T) {
3736
require.NoError(t, err)
3837

3938
clientJSONConn, testURI := setupLSPServer(t, testProtoPath)
40-
typesURI := uri.New(typesProtoPath)
39+
typesURI := buflsp.FilePathToURI(typesProtoPath)
4140

4241
tests := []struct {
4342
name string
@@ -242,9 +241,7 @@ func TestDefinitionURLEncoding(t *testing.T) {
242241
require.Len(t, locations, 1, "expected exactly one definition location")
243242
location := locations[0]
244243

245-
// Construct the expected URI with @ encoded as %40
246-
// Use uri.File() to get the correct URI format for the platform (e.g., file:/// on Windows)
247-
expectedURI := protocol.URI(strings.ReplaceAll(string(uri.File(testProtoPath)), "@", "%40"))
244+
expectedURI := buflsp.FilePathToURI(testProtoPath)
248245

249246
assert.Equal(t, expectedURI, location.URI, "returned URI should have @ encoded as %40")
250247

private/buf/buflsp/diagnostics_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func setupLSPServerWithDiagnostics(
141141
})
142142

143143
testWorkspaceDir := filepath.Dir(testProtoPath)
144-
testURI := uri.New(testProtoPath)
144+
testURI := buflsp.FilePathToURI(testProtoPath)
145145
var initResult protocol.InitializeResult
146146
_, initErr := clientJSONConn.Call(ctx, protocol.MethodInitialize, &protocol.InitializeParams{
147147
RootURI: uri.New(testWorkspaceDir),

private/buf/buflsp/document_link_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import (
1818
"path/filepath"
1919
"testing"
2020

21+
"github.com/bufbuild/buf/private/buf/buflsp"
2122
"github.com/stretchr/testify/assert"
2223
"github.com/stretchr/testify/require"
2324
"go.lsp.dev/protocol"
24-
"go.lsp.dev/uri"
2525
)
2626

2727
func TestDocumentLink(t *testing.T) {
@@ -126,7 +126,7 @@ func TestDocumentLink(t *testing.T) {
126126
case linkTargetTypeLocal:
127127
localPath, err := filepath.Abs(expected.localPath)
128128
require.NoError(t, err)
129-
expectedURI := uri.New(localPath)
129+
expectedURI := buflsp.FilePathToURI(localPath)
130130
assert.Equal(t, expectedURI, link.Target, "link %d (%s): wrong target", i, expected.description)
131131
case linkTargetTypeURL:
132132
assert.Equal(t, protocol.DocumentURI(expected.targetURL), link.Target, "link %d (%s): wrong target", i, expected.description)

private/buf/buflsp/references_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import (
1919
"slices"
2020
"testing"
2121

22+
"github.com/bufbuild/buf/private/buf/buflsp"
2223
"github.com/stretchr/testify/assert"
2324
"github.com/stretchr/testify/require"
2425
"go.lsp.dev/protocol"
25-
"go.lsp.dev/uri"
2626
)
2727

2828
func TestReferences(t *testing.T) {
@@ -37,7 +37,7 @@ func TestReferences(t *testing.T) {
3737
require.NoError(t, err)
3838

3939
clientJSONConn, testURI := setupLSPServer(t, testProtoPath)
40-
typesURI := uri.New(typesProtoPath)
40+
typesURI := buflsp.FilePathToURI(typesProtoPath)
4141

4242
type refLocation struct {
4343
uri protocol.URI

private/buf/buflsp/rename_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import (
2020
"slices"
2121
"testing"
2222

23+
"github.com/bufbuild/buf/private/buf/buflsp"
2324
"github.com/stretchr/testify/assert"
2425
"github.com/stretchr/testify/require"
2526
"go.lsp.dev/protocol"
26-
"go.lsp.dev/uri"
2727
)
2828

2929
func TestRename(t *testing.T) {
@@ -38,9 +38,9 @@ func TestRename(t *testing.T) {
3838
subpkgOptionsProtoPath, err := filepath.Abs("testdata/rename/subpkg/options.proto")
3939
require.NoError(t, err)
4040
clientJSONConn, testURI := setupLSPServer(t, testProtoPath)
41-
typesURI := uri.New(typesProtoPath)
42-
extensionsURI := uri.New(extensionsProtoPath)
43-
subpkgOptionsURI := uri.New(subpkgOptionsProtoPath)
41+
typesURI := buflsp.FilePathToURI(typesProtoPath)
42+
extensionsURI := buflsp.FilePathToURI(extensionsProtoPath)
43+
subpkgOptionsURI := buflsp.FilePathToURI(subpkgOptionsProtoPath)
4444

4545
type editLocation struct {
4646
uri protocol.URI
@@ -361,7 +361,7 @@ func TestPrepareRename(t *testing.T) {
361361
wktProtoPath, err := filepath.Abs("testdata/rename/wkt.proto")
362362
require.NoError(t, err)
363363
clientJSONConn, testURI := setupLSPServer(t, testProtoPath)
364-
wktURI := uri.New(wktProtoPath)
364+
wktURI := buflsp.FilePathToURI(wktProtoPath)
365365

366366
tests := []struct {
367367
name string

private/buf/buflsp/type_definition_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import (
1818
"path/filepath"
1919
"testing"
2020

21+
"github.com/bufbuild/buf/private/buf/buflsp"
2122
"github.com/stretchr/testify/assert"
2223
"github.com/stretchr/testify/require"
2324
"go.lsp.dev/protocol"
24-
"go.lsp.dev/uri"
2525
)
2626

2727
func TestTypeDefinition(t *testing.T) {
@@ -36,7 +36,7 @@ func TestTypeDefinition(t *testing.T) {
3636
require.NoError(t, err)
3737

3838
clientJSONConn, testURI := setupLSPServer(t, testProtoPath)
39-
typesURI := uri.New(typesProtoPath)
39+
typesURI := buflsp.FilePathToURI(typesProtoPath)
4040

4141
tests := []struct {
4242
name string

private/buf/buflsp/uri.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,22 @@ import (
2828
// microsoft/vscode-uri package which encodes '@' as '%40' everywhere to avoid ambiguity
2929
// with the authority component separator (user@host).
3030
//
31+
// Additionally, on Windows, the package also encodes ':' as '%3A' in drive letter paths
32+
// (e.g., 'file:///d:/path' becomes 'file:///d%3A/path').
33+
//
3134
// When URIs don't match exactly, LSP operations like go-to-definition fail because
3235
// the client's URI (with %40) doesn't match the server's URI (with @).
3336
func normalizeURI(u protocol.URI) protocol.URI {
34-
return protocol.URI(strings.ReplaceAll(string(u), "@", "%40"))
37+
normalized := strings.ReplaceAll(string(u), "@", "%40")
38+
39+
if after, found := strings.CutPrefix(normalized, "file:///"); found {
40+
normalized = "file:///" + strings.ReplaceAll(after, ":", "%3A")
41+
}
42+
43+
return protocol.URI(normalized)
3544
}
3645

37-
// filePathToURI converts a file path to a properly encoded URI.
38-
func filePathToURI(path string) protocol.URI {
46+
// FilePathToURI converts a file path to a properly encoded URI.
47+
func FilePathToURI(path string) protocol.URI {
3948
return normalizeURI(uri.File(path))
4049
}

private/buf/buflsp/uri_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2020-2025 Buf Technologies, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package buflsp
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
"go.lsp.dev/protocol"
22+
)
23+
24+
func TestNormalizeURI(t *testing.T) {
25+
t.Parallel()
26+
27+
tests := []struct {
28+
name string
29+
input protocol.URI
30+
expected protocol.URI
31+
}{
32+
{
33+
name: "unix-path-unchanged",
34+
input: "file:///home/user/project/foo.proto",
35+
expected: "file:///home/user/project/foo.proto",
36+
},
37+
{
38+
name: "at-sign-encoded",
39+
input: "file:///home/user@host/project/foo.proto",
40+
expected: "file:///home/user%40host/project/foo.proto",
41+
},
42+
{
43+
name: "windows-drive-letter-colon-encoded",
44+
input: "file:///C:/Users/project/foo.proto",
45+
expected: "file:///C%3A/Users/project/foo.proto",
46+
},
47+
{
48+
name: "windows-lowercase-drive-letter-colon-encoded",
49+
input: "file:///d:/Users/project/foo.proto",
50+
expected: "file:///d%3A/Users/project/foo.proto",
51+
},
52+
{
53+
name: "non-file-uri-colon-not-encoded",
54+
input: "untitled:Untitled-1",
55+
expected: "untitled:Untitled-1",
56+
},
57+
{
58+
name: "at-sign-and-windows-drive-letter-both-encoded",
59+
input: "file:///C:/Users/user@host/foo.proto",
60+
expected: "file:///C%3A/Users/user%40host/foo.proto",
61+
},
62+
}
63+
for _, test := range tests {
64+
t.Run(test.name, func(t *testing.T) {
65+
t.Parallel()
66+
assert.Equal(t, test.expected, normalizeURI(test.input))
67+
})
68+
}
69+
}

private/buf/buflsp/workspace.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func (w *workspace) indexFiles(ctx context.Context) {
247247
for fileInfo := range w.fileInfos(ctx) {
248248
file, ok := previous[fileInfo.Path()]
249249
if !ok {
250-
fileURI := filePathToURI(fileInfo.LocalPath())
250+
fileURI := FilePathToURI(fileInfo.LocalPath())
251251
file = w.lsp.fileManager.Track(fileURI)
252252
w.lsp.logger.Debug("workspace: index track file", slog.String("path", file.uri.Filename()))
253253
}

0 commit comments

Comments
 (0)