Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions src/FsAutoComplete.Core/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -614,17 +614,22 @@ type Path with
uri.Append('/')
length <- length + 1
else
uri.Append('%')
let buffer = SpanOwner<char>.Allocate 2
let mutable out = 0

try
(int c).TryFormat(buffer.Span, &out, "X2") |> ignore
uri.Append(buffer.Span)
finally
buffer.Dispose()

length <- length + 2
// Encode using UTF-8 bytes so that non-ASCII chars (e.g. accented letters)
// produce correct multi-byte sequences like %C3%B3 rather than %F3.
let bytes = Text.Encoding.UTF8.GetBytes(string c)

for b in bytes do
uri.Append('%')
let buffer = SpanOwner<char>.Allocate 2
let mutable out = 0

try
(int b).TryFormat(buffer.Span, &out, "X2") |> ignore
uri.Append(buffer.Span)
finally
buffer.Dispose()

length <- length + (bytes.Length * 3)

let file =
if uri.Length >= 2 && uri.[0] = '/' && uri.[1] = '/' then // UNC path
Expand Down
15 changes: 13 additions & 2 deletions test/FsAutoComplete.Tests.Lsp/ExtensionsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,26 @@ let uriTests =
"file:///Users/carlyrae/oss/f%23test", "/Users/carlyrae/oss/f#test" // escaped chars, unix-root
"file:///c%3A/carly rae/oss/f%23test", "c:/carly rae/oss/f#test" // spaces and escaped chars, windows-root
"file:///Users/carly rae/oss/f%23test", "/Users/carly rae/oss/f#test" // spaces and escaped chars, unix-root
"file:///d%3A/code/Saturn/src/Saturn/Utils.fs", "d:/code/Saturn/src/Saturn/Utils.fs" ]
"file:///d%3A/code/Saturn/src/Saturn/Utils.fs", "d:/code/Saturn/src/Saturn/Utils.fs"
// unicode (accented chars) in path — .NET Uri normalises %C3%B3 back to ó in its string representation
"file:///home/user/c\u00F3digo/Program.fs", "/home/user/c\u00F3digo/Program.fs" ]

// Separate tests that verify FilePathToUri emits correct UTF-8 percent-encoded sequences
// without the normalisation that Uri.ToString() applies.
let unicodeEncodingTests =
[ test "FilePathToUri encodes accented chars as UTF-8 percent sequences" {
let uri = Path.FilePathToUri "/home/user/c\u00F3digo/Program.fs"
Expect.equal uri "file:///home/user/c%C3%B3digo/Program.fs" "ó (U+00F3) must encode as %C3%B3 (UTF-8), not %F3"
} ]

testList
"Uri tests"
[ testList "roundtrip tests" (samples |> List.map (fun (uriForm, filePath) -> verifyUri uriForm filePath))
testList
"fileName to uri tests"
(samples
|> List.map (fun (uriForm, filePath) -> convertRawPathToUri filePath uriForm)) ]
|> List.map (fun (uriForm, filePath) -> convertRawPathToUri filePath uriForm))
testList "unicode encoding tests" unicodeEncodingTests ]

/// Tests for linter
let linterTests state =
Expand Down