diff --git a/src/Protocol/DocumentUri.cs b/src/Protocol/DocumentUri.cs index c00f18f3b..d5e5b8be2 100644 --- a/src/Protocol/DocumentUri.cs +++ b/src/Protocol/DocumentUri.cs @@ -23,13 +23,25 @@ public DocumentUri(string url) var delimiterIndex = url.IndexOf(SchemeDelimiter, StringComparison.Ordinal); if (delimiterIndex == -1) { - url = Uri.UnescapeDataString(url).Replace('\\', '/'); + // Unc path + if (url.StartsWith("\\\\")) + { + var authorityEndIndex = url.IndexOf('\\', 2); + Authority = url.Substring(2, authorityEndIndex - 2); + url = url.Substring(authorityEndIndex); + // Path = Uri.UnescapeDataString(url); + } + else + { + Authority = string.Empty; + } + + url = url.Replace('\\', '/'); Scheme = UriSchemeFile; - Authority = string.Empty; Query = string.Empty; Fragment = string.Empty; - Path = Uri.UnescapeDataString(url).TrimStart('/'); + Path = Uri.UnescapeDataString(url.StartsWith("/") ? url : "/" + url); return; } @@ -40,6 +52,15 @@ public DocumentUri(string url) Authority = url.Substring(delimiterIndex + SchemeDelimiter.Length, authorityIndex - (delimiterIndex + SchemeDelimiter.Length)); + // this is a possible windows path without the proper tripple slash + // file://c:/some/path.file.cs + // We need deal with this case. + if (Authority.IndexOf(':') > -1 || Authority.IndexOf("%3a", StringComparison.OrdinalIgnoreCase) > -1) + { + Authority = string.Empty; + authorityIndex = delimiterIndex + SchemeDelimiter.Length; + } + var fragmentIndex = url.IndexOf('#'); if (fragmentIndex > -1) { @@ -62,8 +83,7 @@ public DocumentUri(string url) queryIndex = fragmentIndex; } - Path = Uri.UnescapeDataString(url.Substring(authorityIndex + 1, queryIndex - (authorityIndex))) - .TrimStart('/'); + Path = Uri.UnescapeDataString(url.Substring(authorityIndex, queryIndex - (authorityIndex) + 1)); } /// @@ -133,7 +153,7 @@ public Uri ToUri() /// /// This will not a uri encode asian and cyrillic characters public override string ToString() => - $"{Scheme}{SchemeDelimiter}{Authority}/{Path}{(string.IsNullOrWhiteSpace(Query) ? "" : "?" + Query)}{(string.IsNullOrWhiteSpace(Fragment) ? "" : "#" + Fragment)}"; + $"{Scheme}{SchemeDelimiter}{Authority}{Path}{(string.IsNullOrWhiteSpace(Query) ? "" : "?" + Query)}{(string.IsNullOrWhiteSpace(Fragment) ? "" : "#" + Fragment)}"; /// /// Gets the file system path prefixed with / for unix platforms @@ -143,7 +163,11 @@ public override string ToString() => public string GetFileSystemPath() { // The language server protocol represents "C:\Foo\Bar" as "file:///c:/foo/bar". - return Path.IndexOf(':') == -1 ? "/" + Path : Path.Replace('/', '\\'); + if (Path.IndexOf(':') == -1 && !(Scheme == UriSchemeFile && !string.IsNullOrWhiteSpace(Authority))) + return Path; + if (!string.IsNullOrWhiteSpace(Authority)) + return $"\\\\{Authority}{Path}".Replace('/', '\\'); + return Path.TrimStart('/').Replace('/', '\\'); } /// diff --git a/src/Protocol/Models/DocumentFilter.cs b/src/Protocol/Models/DocumentFilter.cs index 9fe6f2b58..30f8ddd1f 100644 --- a/src/Protocol/Models/DocumentFilter.cs +++ b/src/Protocol/Models/DocumentFilter.cs @@ -78,11 +78,11 @@ public bool IsMatch(TextDocumentAttributes attributes) { if (HasLanguage && HasPattern && HasScheme) { - return Language == attributes.LanguageId && Scheme == attributes.Scheme && _minimatcher.IsMatch(attributes.Uri.Path); + return Language == attributes.LanguageId && Scheme == attributes.Scheme && _minimatcher.IsMatch(attributes.Uri.ToString()); } if (HasLanguage && HasPattern) { - return Language == attributes.LanguageId && _minimatcher.IsMatch(attributes.Uri.Path); + return Language == attributes.LanguageId && _minimatcher.IsMatch(attributes.Uri.ToString()); } if (HasLanguage && HasScheme) { @@ -90,7 +90,7 @@ public bool IsMatch(TextDocumentAttributes attributes) } if (HasPattern && HasScheme) { - return Scheme == attributes.Scheme && _minimatcher.IsMatch(attributes.Uri.Path); + return Scheme == attributes.Scheme && _minimatcher.IsMatch(attributes.Uri.ToString()); } if (HasLanguage) { @@ -102,7 +102,7 @@ public bool IsMatch(TextDocumentAttributes attributes) } if (HasPattern) { - return _minimatcher.IsMatch(attributes.Uri.Path); + return _minimatcher.IsMatch(attributes.Uri.ToString()); } return false; diff --git a/test/Lsp.Tests/AbsoluteUriConverterTests.cs b/test/Lsp.Tests/AbsoluteUriConverterTests.cs index 2e2e79a4b..b72be073c 100644 --- a/test/Lsp.Tests/AbsoluteUriConverterTests.cs +++ b/test/Lsp.Tests/AbsoluteUriConverterTests.cs @@ -17,7 +17,10 @@ public AbsoluteUriConverterTests(ITestOutputHelper testOutputHelper) } [Theory] - [ClassData(typeof(DocumentUriTestData.StringUris))] + [ClassData(typeof(DocumentUriTests.WindowsPathStringUris))] + [ClassData(typeof(DocumentUriTests.WindowsPathAltStringUris))] + [ClassData(typeof(DocumentUriTests.UncPathStringUris))] + [ClassData(typeof(DocumentUriTests.UnixPathStringUris))] public void Should_Deserialize_VSCode_Style_Uris(string uri, DocumentUri expected) { _testOutputHelper.WriteLine($"Given: {uri}"); @@ -29,7 +32,10 @@ public void Should_Deserialize_VSCode_Style_Uris(string uri, DocumentUri expecte } [Theory] - [ClassData(typeof(DocumentUriTestData.StringUris))] + [ClassData(typeof(DocumentUriTests.WindowsPathStringUris))] + [ClassData(typeof(DocumentUriTests.WindowsPathAltStringUris))] + [ClassData(typeof(DocumentUriTests.UncPathStringUris))] + [ClassData(typeof(DocumentUriTests.UnixPathStringUris))] public void Should_Serialize_VSCode_Style_Uris(string uri, DocumentUri expected) { _testOutputHelper.WriteLine($"Given: {uri}"); diff --git a/test/Lsp.Tests/DocumentUriTestData.cs b/test/Lsp.Tests/DocumentUriTestData.cs deleted file mode 100644 index 95c2c523a..000000000 --- a/test/Lsp.Tests/DocumentUriTestData.cs +++ /dev/null @@ -1,286 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using OmniSharp.Extensions.LanguageServer.Protocol; - -namespace Lsp.Tests -{ - public static class DocumentUriTestData - { - public class StringUris : IEnumerable - { - private const string WindowsPath = "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - private const string UnixPath = "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - private IEnumerable<(string, DocumentUri)> AddPaths(string path) - { - yield return (path.Replace("c:", "c%3A"), path); - yield return (path.Replace("c:", "c%3a"), path); - yield return (path, path); - } - - public IEnumerator GetEnumerator() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - foreach (var (source, destination) in AddPaths(WindowsPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsPath.Replace("Namespace", "Пространствоимен"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - - foreach (var (source, destination) in AddPaths(UnixPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixPath.Replace("Namespace", "Пространствоимен"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - public class Uris : IEnumerable - { - private const string WindowsPath = "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - private const string UnixPath = "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - private IEnumerable<(Uri, DocumentUri)> AddPaths(string path) - { - yield return (new Uri(path.Replace("c:", "c%3A")), path); - yield return (new Uri(path.Replace("c:", "c%3a")), path); - yield return (new Uri(path), path); - } - - public IEnumerator GetEnumerator() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - foreach (var (source, destination) in AddPaths(WindowsPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "Пространствоимен")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - - foreach (var (source, destination) in AddPaths(UnixPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "Пространствоимен"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - public class FileSystemPaths : IEnumerable - { - private const string WindowsPath = "c:\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; - private const string UnixPath = "/usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - private IEnumerable<(string, DocumentUri)> AddPaths(string path) - { - yield return (path.Replace("c:", "c%3A"), path); - yield return (path.Replace("c:", "c%3a"), path); - yield return (path, path); - } - - public IEnumerator GetEnumerator() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - foreach (var (source, destination) in AddPaths(WindowsPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "Пространствоимен")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(WindowsPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - else - { - foreach (var (source, destination) in AddPaths(UnixPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "Пространствоимен"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths(UnixPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - public class FileSystemToFileUri : IEnumerable - { - private const string WindowsSourcePath = "c:\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; - private const string UnixSourcePath = "/usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - private const string WindowsDestinationPath = - "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - private const string UnixDestinationPath = "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - public IEnumerator GetEnumerator() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - foreach (var (source, destination) in AddPaths( - WindowsSourcePath, - WindowsDestinationPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "Пространствоимен"), - WindowsDestinationPath.Replace("Namespace", "Пространствоимен")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "汉字漢字"), - WindowsDestinationPath.Replace("Namespace", "汉字漢字")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "のはでした"), - WindowsDestinationPath.Replace("Namespace", "のはでした")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "コンサート"), - WindowsDestinationPath.Replace("Namespace", "コンサート")) - ) - yield return new object[] {source, destination}; - } - - foreach (var (source, destination) in AddPaths(UnixSourcePath, UnixDestinationPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "Пространствоимен"), - UnixDestinationPath.Replace("Namespace", "Пространствоимен"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "汉字漢字"), - UnixDestinationPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "のはでした"), - UnixDestinationPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "コンサート"), - UnixDestinationPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - - private IEnumerable<(string, DocumentUri)> AddPaths(string source, DocumentUri destination) - { - yield return (source.Replace("c:", "c%3A"), destination); - yield return (source.Replace("c:", "c%3a"), destination); - yield return (source, destination); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - public class FileUriToFileSystem : IEnumerable - { - private const string WindowsSourcePath = "c:\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; - private const string UnixSourcePath = "/usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - private const string WindowsDestinationPath = - "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - private const string UnixDestinationPath = "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; - - public IEnumerator GetEnumerator() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - foreach (var (source, destination) in AddPaths( - WindowsSourcePath, - WindowsDestinationPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "Пространствоимен"), - WindowsDestinationPath.Replace("Namespace", "Пространствоимен")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "汉字漢字"), - WindowsDestinationPath.Replace("Namespace", "汉字漢字")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "のはでした"), - WindowsDestinationPath.Replace("Namespace", "のはでした")) - ) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - WindowsSourcePath.Replace("Namespace", "コンサート"), - WindowsDestinationPath.Replace("Namespace", "コンサート")) - ) - yield return new object[] {source, destination}; - } - else - { - foreach (var (source, destination) in AddPaths(UnixSourcePath, UnixDestinationPath)) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "Пространствоимен"), - UnixDestinationPath.Replace("Namespace", "Пространствоимен"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "汉字漢字"), - UnixDestinationPath.Replace("Namespace", "汉字漢字"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "のはでした"), - UnixDestinationPath.Replace("Namespace", "のはでした"))) - yield return new object[] {source, destination}; - foreach (var (source, destination) in AddPaths( - UnixSourcePath.Replace("Namespace", "コンサート"), - UnixDestinationPath.Replace("Namespace", "コンサート"))) - yield return new object[] {source, destination}; - } - } - - private IEnumerable<(Uri, DocumentUri)> AddPaths(string source, DocumentUri destination) - { - yield return (new Uri(source), destination); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/test/Lsp.Tests/DocumentUriTests.cs b/test/Lsp.Tests/DocumentUriTests.cs index ed03d4a1f..a46405c28 100644 --- a/test/Lsp.Tests/DocumentUriTests.cs +++ b/test/Lsp.Tests/DocumentUriTests.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using FluentAssertions; using OmniSharp.Extensions.LanguageServer.Protocol; using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities; @@ -18,48 +21,544 @@ public DocumentUriTests(ITestOutputHelper testOutputHelper) } [Theory] - [ClassData(typeof(DocumentUriTestData.FileSystemPaths))] - public void Should_Handle_VSCode_Style_File_System_Paths(string uri, DocumentUri expected) + [ClassData(typeof(WindowsFileSystemPaths))] + public void Should_Handle_Windows_File_System_Paths(string uri, DocumentUri expected) { _testOutputHelper.WriteLine($"Given: {uri}"); _testOutputHelper.WriteLine($"Expected: {expected}"); new DocumentUri(uri).Should().Be(expected); } + public class WindowsFileSystemPaths : BaseSingle + { + private const string WindowsPath = "c:\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + WindowsPath, + WindowsPath.Replace("Namespace", "Пространствоимен"), + WindowsPath.Replace("Namespace", "汉字漢字"), + WindowsPath.Replace("Namespace", "のはでした"), + WindowsPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(UncFileSystemPaths))] + public void Should_Handle_Unc_File_System_Paths(string uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + new DocumentUri(uri).Should().Be(expected); + } + + public class UncFileSystemPaths : BaseSingle + { + private const string UncPath = "\\\\myserver\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + UncPath, + UncPath.Replace("Namespace", "Пространствоимен"), + UncPath.Replace("Namespace", "汉字漢字"), + UncPath.Replace("Namespace", "のはでした"), + UncPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(UnixFileSystemPaths))] + public void Should_Handle_Unix_File_System_Paths(string uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + new DocumentUri(uri).Should().Be(expected); + } + + public class UnixFileSystemPaths : BaseSingle + { + private const string UnixPath = "/usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + UnixPath, + UnixPath.Replace("Namespace", "Пространствоимен"), + UnixPath.Replace("Namespace", "汉字漢字"), + UnixPath.Replace("Namespace", "のはでした"), + UnixPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {source, destination}; + } + } + } + [Theory] - [ClassData(typeof(DocumentUriTestData.StringUris))] - public void Should_Handle_VSCode_Style_String_Uris(string uri, DocumentUri expected) + [ClassData(typeof(WindowsPathStringUris))] + public void Should_Handle_Windows_String_Uris(string uri, DocumentUri expected) { _testOutputHelper.WriteLine($"Given: {uri}"); _testOutputHelper.WriteLine($"Expected: {expected}"); new DocumentUri(uri).Should().Be(expected); } + public class WindowsPathStringUris : BaseSingle + { + private const string WindowsPath = "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + if (true) + { + foreach (var (source, destination) in AddPaths( + WindowsPath, + WindowsPath.Replace("Namespace", "Пространствоимен"), + WindowsPath.Replace("Namespace", "汉字漢字"), + WindowsPath.Replace("Namespace", "のはでした"), + WindowsPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {source, destination}; + } + } + } + } + [Theory] - [ClassData(typeof(DocumentUriTestData.Uris))] - public void Should_Handle_VSCode_Style_Uris(Uri uri, DocumentUri expected) + [ClassData(typeof(WindowsPathAltStringUris))] + public void Should_Handle_Windows_Alt_String_Uris(string uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + new DocumentUri(uri).Should().Be(expected); + } + + public class WindowsPathAltStringUris : BaseSingle + { + private const string WindowsPathAlt = "file://c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + WindowsPathAlt, + WindowsPathAlt.Replace("Namespace", "Пространствоимен"), + WindowsPathAlt.Replace("Namespace", "汉字漢字"), + WindowsPathAlt.Replace("Namespace", "のはでした"), + WindowsPathAlt.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(UncPathStringUris))] + public void Should_Handle_Unc_String_Uris(string uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + new DocumentUri(uri).Should().Be(expected); + } + + public class UncPathStringUris : BaseSingle + { + private const string UncPath = "file://myserver/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + UncPath, + UncPath.Replace("Namespace", "Пространствоимен"), + UncPath.Replace("Namespace", "汉字漢字"), + UncPath.Replace("Namespace", "のはでした"), + UncPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(UnixPathStringUris))] + public void Should_Handle_Unix_String_Uris(string uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + new DocumentUri(uri).Should().Be(expected); + } + + public class UnixPathStringUris : BaseSingle + { + private const string UnixPath = "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + UnixPath, + UnixPath.Replace("Namespace", "Пространствоимен"), + UnixPath.Replace("Namespace", "汉字漢字"), + UnixPath.Replace("Namespace", "のはでした"), + UnixPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(WindowsPathUris))] + public void Should_Handle_Windows_Uris(Uri uri, DocumentUri expected) { _testOutputHelper.WriteLine($"Given: {uri}"); _testOutputHelper.WriteLine($"Expected: {expected}"); DocumentUri.From(uri).Should().Be(expected); } + public class WindowsPathUris : BaseSingle + { + private const string WindowsPath = "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + WindowsPath, + WindowsPath.Replace("Namespace", "Пространствоимен"), + WindowsPath.Replace("Namespace", "汉字漢字"), + WindowsPath.Replace("Namespace", "のはでした"), + WindowsPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {new Uri(source), destination}; + } + } + } + [Theory] - [ClassData(typeof(DocumentUriTestData.FileSystemToFileUri))] - public void Should_Normalize_VSCode_Style_FileSystem_Paths(string uri, DocumentUri expected) + [ClassData(typeof(UncPathUris))] + public void Should_Handle_Unc_Uris(Uri uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + DocumentUri.From(uri).Should().Be(expected); + } + + public class UncPathUris : BaseSingle + { + private const string UncPath = "file://myserver/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + UncPath, + UncPath.Replace("Namespace", "Пространствоимен"), + UncPath.Replace("Namespace", "汉字漢字"), + UncPath.Replace("Namespace", "のはでした"), + UncPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {new Uri(source), destination}; + } + } + } + + [Theory] + [ClassData(typeof(UnixPathUris))] + public void Should_Handle_Unix_Uris(Uri uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + DocumentUri.From(uri).Should().Be(expected); + } + + public class UnixPathUris : BaseSingle + { + private const string UnixPath = "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in AddPaths( + UnixPath, + UnixPath.Replace("Namespace", "Пространствоимен"), + UnixPath.Replace("Namespace", "汉字漢字"), + UnixPath.Replace("Namespace", "のはでした"), + UnixPath.Replace("Namespace", "コンサート") + )) + { + yield return new object[] {new Uri(source), destination}; + } + } + } + + [Theory] + [ClassData(typeof(WindowsFileSystemToFileUri))] + public void Should_Normalize_Windows_FileSystem_Paths(string uri, DocumentUri expected) { _testOutputHelper.WriteLine($"Given: {uri}"); _testOutputHelper.WriteLine($"Expected: {expected}"); DocumentUri.FromFileSystemPath(uri).Should().Be(expected); } + public class WindowsFileSystemToFileUri : BaseSourceDestination + { + private const string WindowsSourcePath = "c:\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; + + private const string WindowsDestinationPath = + "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in + AddPaths(WindowsSourcePath, WindowsDestinationPath) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "Пространствоимен"), + WindowsDestinationPath.Replace("Namespace", "Пространствоимен"))) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "汉字漢字"), + WindowsDestinationPath.Replace("Namespace", "汉字漢字"))) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "のはでした"), + WindowsDestinationPath.Replace("Namespace", "のはでした"))) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "コンサート"), + WindowsDestinationPath.Replace("Namespace", "コンサート"))) + ) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(UncFileSystemToFileUri))] + public void Should_Normalize_Unc_FileSystem_Paths(string uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + DocumentUri.FromFileSystemPath(uri).Should().Be(expected); + } + + public class UncFileSystemToFileUri : BaseSourceDestination + { + private const string UncSourcePath = + "\\\\myserver\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; + + private const string UncDestinationPath = + "file://myserver/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in + AddPaths(UncSourcePath, UncDestinationPath) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "Пространствоимен"), + UncDestinationPath.Replace("Namespace", "Пространствоимен"))) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "汉字漢字"), + UncDestinationPath.Replace("Namespace", "汉字漢字"))) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "のはでした"), + UncDestinationPath.Replace("Namespace", "のはでした"))) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "コンサート"), + UncDestinationPath.Replace("Namespace", "コンサート"))) + ) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(UnixFileSystemToFileUri))] + public void Should_Normalize_Unix_FileSystem_Paths(string uri, DocumentUri expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + DocumentUri.FromFileSystemPath(uri).Should().Be(expected); + } + + public class UnixFileSystemToFileUri : BaseSourceDestination + { + private const string UnixSourcePath = "/usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + private const string UnixDestinationPath = "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in + AddPaths(UnixSourcePath, UnixDestinationPath) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "Пространствоимен"), + UnixDestinationPath.Replace("Namespace", "Пространствоимен"))) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "汉字漢字"), + UnixDestinationPath.Replace("Namespace", "汉字漢字"))) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "のはでした"), + UnixDestinationPath.Replace("Namespace", "のはでした"))) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "コンサート"), + UnixDestinationPath.Replace("Namespace", "コンサート"))) + ) + { + yield return new object[] {source, destination}; + } + } + } + + [Theory] + [ClassData(typeof(WindowsFileUriToFileSystem))] + public void Should_Normalize_Windows_Uris(Uri uri, string expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + DocumentUri.GetFileSystemPath(uri).Should().Be(expected); + } + + public class WindowsFileUriToFileSystem : BaseSourceDestination + { + private const string WindowsSourcePath = + "file:///c:/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + private const string WindowsDestinationPath = + "c:\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in + AddPaths(WindowsSourcePath, WindowsDestinationPath) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "Пространствоимен"), + WindowsDestinationPath.Replace("Namespace", "Пространствоимен"))) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "汉字漢字"), + WindowsDestinationPath.Replace("Namespace", "汉字漢字"))) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "のはでした"), + WindowsDestinationPath.Replace("Namespace", "のはでした"))) + .Concat(AddPaths(WindowsSourcePath.Replace("Namespace", "コンサート"), + WindowsDestinationPath.Replace("Namespace", "コンサート"))) + ) + { + yield return new object[] {source, destination}; + } + } + + private IEnumerable<(Uri, string)> AddPaths(string source, string destination) + { + yield return (new Uri(source), destination); + } + } + [Theory] - [ClassData(typeof(DocumentUriTestData.FileUriToFileSystem))] - public void Should_Normalize_VSCode_Style_Uris(Uri uri, DocumentUri expected) + [ClassData(typeof(UncFileUriToFileSystem))] + public void Should_Normalize_Unc_Uris(Uri uri, string expected) { _testOutputHelper.WriteLine($"Given: {uri}"); _testOutputHelper.WriteLine($"Expected: {expected}"); - DocumentUri.GetFileSystemPath(uri).Should().Be(expected.GetFileSystemPath()); + DocumentUri.GetFileSystemPath(uri).Should().Be(expected); + } + + public class UncFileUriToFileSystem : BaseSourceDestination + { + private const string UncSourcePath = + "file://myserver/Users/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + private const string UncDestinationPath = + "\\\\myserver\\Users\\mb\\src\\gh\\Cake.Json\\src\\Cake.Json\\Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in + AddPaths(UncSourcePath, UncDestinationPath) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "Пространствоимен"), + UncDestinationPath.Replace("Namespace", "Пространствоимен"))) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "汉字漢字"), + UncDestinationPath.Replace("Namespace", "汉字漢字"))) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "のはでした"), + UncDestinationPath.Replace("Namespace", "のはでした"))) + .Concat(AddPaths(UncSourcePath.Replace("Namespace", "コンサート"), + UncDestinationPath.Replace("Namespace", "コンサート"))) + ) + { + yield return new object[] {source, destination}; + } + } + + private IEnumerable<(Uri, string)> AddPaths(string source, string destination) + { + yield return (new Uri(source), destination); + } + } + + [Theory] + [ClassData(typeof(UnixFileUriToFileSystem))] + public void Should_Normalize_Unix_Uris(Uri uri, string expected) + { + _testOutputHelper.WriteLine($"Given: {uri}"); + _testOutputHelper.WriteLine($"Expected: {expected}"); + DocumentUri.GetFileSystemPath(uri).Should().Be(expected); + } + + public class UnixFileUriToFileSystem : BaseSourceDestination + { + private const string UnixSourcePath = + "file:///usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + private const string UnixDestinationPath = + "/usr/mb/src/gh/Cake.Json/src/Cake.Json/Namespaces.cs"; + + public override IEnumerator GetEnumerator() + { + foreach (var (source, destination) in + AddPaths(UnixSourcePath, UnixDestinationPath) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "Пространствоимен"), + UnixDestinationPath.Replace("Namespace", "Пространствоимен"))) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "汉字漢字"), + UnixDestinationPath.Replace("Namespace", "汉字漢字"))) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "のはでした"), + UnixDestinationPath.Replace("Namespace", "のはでした"))) + .Concat(AddPaths(UnixSourcePath.Replace("Namespace", "コンサート"), + UnixDestinationPath.Replace("Namespace", "コンサート"))) + ) + { + yield return new object[] {source, destination}; + } + } + + private IEnumerable<(Uri, string)> AddPaths(string source, string destination) + { + yield return (new Uri(source), destination); + } + } + + + public abstract class BaseSingle : IEnumerable + { + protected IEnumerable<(string, DocumentUri)> AddPaths(params string[] paths) + { + foreach (var path in paths) + { + yield return (path.Replace("c:", "c%3A"), path); + yield return (path.Replace("c:", "c%3a"), path); + yield return (path, path); + } + } + + public abstract IEnumerator GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public abstract class BaseSourceDestination : IEnumerable + { + public abstract IEnumerator GetEnumerator(); + + protected IEnumerable<(string, DocumentUri)> AddPaths(string source, DocumentUri destination) + { + yield return (source.Replace("c:", "c%3A"), destination); + yield return (source.Replace("c:", "c%3a"), destination); + yield return (source, destination); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } } diff --git a/test/Lsp.Tests/Models/CodeLensParamsTests.cs b/test/Lsp.Tests/Models/CodeLensParamsTests.cs index e5b1d2372..d5262d488 100644 --- a/test/Lsp.Tests/Models/CodeLensParamsTests.cs +++ b/test/Lsp.Tests/Models/CodeLensParamsTests.cs @@ -30,7 +30,7 @@ public void NonStandardCharactersTest(string expected) { var model = new CodeLensParams() { // UNC path with Chinese character for tree. - TextDocument = new TextDocumentIdentifier(new Uri("\\\\abc\\123\\树.cs")), + TextDocument = new TextDocumentIdentifier(DocumentUri.FromFileSystemPath("\\\\abc\\123\\树.cs")), }; var result = Fixture.SerializeObject(model); diff --git a/test/Lsp.Tests/Models/CodeLensParamsTests_$NonStandardCharactersTest.json b/test/Lsp.Tests/Models/CodeLensParamsTests_$NonStandardCharactersTest.json index 6445f1036..2d1e4dbbe 100644 --- a/test/Lsp.Tests/Models/CodeLensParamsTests_$NonStandardCharactersTest.json +++ b/test/Lsp.Tests/Models/CodeLensParamsTests_$NonStandardCharactersTest.json @@ -1,5 +1,5 @@ { "textDocument": { - "uri": "file:///abc/123/树.cs" + "uri": "file://abc/123/树.cs" } } diff --git a/test/Lsp.Tests/Models/DidChangeTextDocumentParamsTests.cs b/test/Lsp.Tests/Models/DidChangeTextDocumentParamsTests.cs index 691a76114..0c20b3c28 100644 --- a/test/Lsp.Tests/Models/DidChangeTextDocumentParamsTests.cs +++ b/test/Lsp.Tests/Models/DidChangeTextDocumentParamsTests.cs @@ -47,7 +47,7 @@ public void NonStandardCharactersTest(string expected) } }, TextDocument = new VersionedTextDocumentIdentifier() { - Uri = new Uri("C:\\abc\\Mörkö.cs") + Uri = DocumentUri.FromFileSystemPath("C:\\abc\\Mörkö.cs") } }; var result = Fixture.SerializeObject(model); diff --git a/test/Lsp.Tests/Models/LocationOrLocationLinksTests.cs b/test/Lsp.Tests/Models/LocationOrLocationLinksTests.cs index 2a0a71d6e..6e273d244 100644 --- a/test/Lsp.Tests/Models/LocationOrLocationLinksTests.cs +++ b/test/Lsp.Tests/Models/LocationOrLocationLinksTests.cs @@ -67,7 +67,7 @@ public void LocationLinkTest(string expected) { TargetSelectionRange = new Range(new Position(1, 1), new Position(3, 3)), TargetRange = new Range(new Position(1, 1), new Position(3, 3)), - TargetUri = new Uri("file://asdfasdf/a.tst"), + TargetUri = new Uri("file:///asdfasdf/a.tst"), OriginSelectionRange = new Range(new Position(1, 1), new Position(3, 3)), }); var result = Fixture.SerializeObject(model); @@ -87,7 +87,7 @@ public void LocationLinksTest(string expected) { TargetSelectionRange = new Range(new Position(1, 1), new Position(3, 3)), TargetRange = new Range(new Position(1, 1), new Position(3, 3)), - TargetUri = new Uri("file://asdfasdf/a.tst"), + TargetUri = new Uri("file:///asdfasdf/a.tst"), OriginSelectionRange = new Range(new Position(1, 1), new Position(3, 3)), }, @@ -95,7 +95,7 @@ public void LocationLinksTest(string expected) { TargetSelectionRange = new Range(new Position(1, 1), new Position(3, 3)), TargetRange = new Range(new Position(1, 1), new Position(3, 3)), - TargetUri = new Uri("file://asdfasdf/a.tst"), + TargetUri = new Uri("file:///asdfasdf/a.tst"), OriginSelectionRange = new Range(new Position(1, 1), new Position(3, 3)), });