Skip to content

Commit 2d60cf7

Browse files
Added support for more generic uris (#239)
* Added support for more generic uris * Reacted to feedback
1 parent 61d029a commit 2d60cf7

File tree

2 files changed

+97
-21
lines changed

2 files changed

+97
-21
lines changed

src/Protocol/DocumentUri.cs

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Runtime.InteropServices;
55
using System.Runtime.Versioning;
6+
using System.Text.RegularExpressions;
67
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
78
using static System.IO.Path;
89

@@ -14,17 +15,23 @@ namespace OmniSharp.Extensions.LanguageServer.Protocol
1415
/// <remarks>This exists because of some non-standard serialization in vscode around uris and .NET's behavior when deserializing those uris</remarks>
1516
public class DocumentUri : IEquatable<DocumentUri>
1617
{
18+
private static readonly Regex WindowsPath =
19+
new Regex(@"^\w(?:\:|%3a)[\\|\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
20+
21+
private readonly string _delimiter = SchemeDelimiter;
22+
1723
/// <summary>
1824
/// Create a new document uri
1925
/// </summary>
2026
/// <param name="url"></param>
2127
public DocumentUri(string url)
2228
{
29+
var uncMatch = false;
2330
var delimiterIndex = url.IndexOf(SchemeDelimiter, StringComparison.Ordinal);
24-
if (delimiterIndex == -1)
31+
if ((uncMatch = url.StartsWith(@"\\")) || (url.StartsWith("/")) || (WindowsPath.IsMatch(url)))
2532
{
2633
// Unc path
27-
if (url.StartsWith("\\\\"))
34+
if (uncMatch)
2835
{
2936
var authorityEndIndex = url.IndexOf('\\', 2);
3037
Authority = url.Substring(2, authorityEndIndex - 2);
@@ -42,25 +49,36 @@ public DocumentUri(string url)
4249
Query = string.Empty;
4350
Fragment = string.Empty;
4451
Path = Uri.UnescapeDataString(url.StartsWith("/") ? url : "/" + url);
45-
4652
return;
4753
}
4854

49-
Scheme = url.Substring(0, delimiterIndex);
50-
51-
var authorityIndex = url.IndexOf('/', delimiterIndex + SchemeDelimiter.Length);
52-
Authority = url.Substring(delimiterIndex + SchemeDelimiter.Length,
53-
authorityIndex - (delimiterIndex + SchemeDelimiter.Length));
54-
55-
// this is a possible windows path without the proper tripple slash
56-
// file://c:/some/path.file.cs
57-
// We need deal with this case.
58-
if (Authority.IndexOf(':') > -1 || Authority.IndexOf("%3a", StringComparison.OrdinalIgnoreCase) > -1)
55+
int authorityIndex;
56+
if (delimiterIndex == -1)
5957
{
58+
delimiterIndex = url.IndexOf(':');
59+
authorityIndex = delimiterIndex + 1;
6060
Authority = string.Empty;
61-
authorityIndex = delimiterIndex + SchemeDelimiter.Length;
61+
_delimiter = ":";
62+
}
63+
else
64+
{
65+
var delimiterSize = SchemeDelimiter.Length;
66+
authorityIndex = url.IndexOf('/', delimiterIndex + delimiterSize);
67+
Authority = url.Substring(delimiterIndex + delimiterSize,
68+
authorityIndex - (delimiterIndex + delimiterSize));
69+
70+
// this is a possible windows path without the proper tripple slash
71+
// file://c:/some/path.file.cs
72+
// We need deal with this case.
73+
if (Authority.IndexOf(':') > -1 || Authority.IndexOf("%3a", StringComparison.OrdinalIgnoreCase) > -1)
74+
{
75+
Authority = string.Empty;
76+
authorityIndex = delimiterIndex + delimiterSize;
77+
}
6278
}
6379

80+
Scheme = url.Substring(0, delimiterIndex);
81+
6482
var fragmentIndex = url.IndexOf('#');
6583
if (fragmentIndex > -1)
6684
{
@@ -152,8 +170,16 @@ public Uri ToUri()
152170
/// </summary>
153171
/// <returns></returns>
154172
/// <remarks>This will not a uri encode asian and cyrillic characters</remarks>
155-
public override string ToString() =>
156-
$"{Scheme}{SchemeDelimiter}{Authority}{Path}{(string.IsNullOrWhiteSpace(Query) ? "" : "?" + Query)}{(string.IsNullOrWhiteSpace(Fragment) ? "" : "#" + Fragment)}";
173+
public override string ToString()
174+
{
175+
if (string.IsNullOrWhiteSpace(_stringValue))
176+
{
177+
_stringValue =
178+
$"{Scheme}{_delimiter}{Authority}{Path}{(string.IsNullOrWhiteSpace(Query) ? "" : "?" + Query)}{(string.IsNullOrWhiteSpace(Fragment) ? "" : "#" + Fragment)}";
179+
}
180+
181+
return _stringValue;
182+
}
157183

158184
/// <summary>
159185
/// Gets the file system path prefixed with / for unix platforms
@@ -166,7 +192,7 @@ public string GetFileSystemPath()
166192
if (Path.IndexOf(':') == -1 && !(Scheme == UriSchemeFile && !string.IsNullOrWhiteSpace(Authority)))
167193
return Path;
168194
if (!string.IsNullOrWhiteSpace(Authority))
169-
return $"\\\\{Authority}{Path}".Replace('/', '\\');
195+
return $@"\\{Authority}{Path}".Replace('/', '\\');
170196
return Path.TrimStart('/').Replace('/', '\\');
171197
}
172198

@@ -299,6 +325,8 @@ public static DocumentUri From(Uri uri)
299325
/// </summary>
300326
public static readonly string SchemeDelimiter = Uri.SchemeDelimiter;
301327

328+
private string _stringValue;
329+
302330
/// <summary>
303331
/// Get the local file-system path for the specified document URI.
304332
/// </summary>

test/Lsp.Tests/DocumentUriTests.cs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,52 @@ public override IEnumerator<object[]> GetEnumerator()
219219
}
220220
}
221221

222+
[Theory]
223+
[ClassData(typeof(ResourceStringUris))]
224+
public void Should_Handle_Resource_String_Uris(string uri, string expected)
225+
{
226+
_testOutputHelper.WriteLine($"Given: {uri}");
227+
_testOutputHelper.WriteLine($"Expected: {expected}");
228+
new DocumentUri(uri).ToString().Should().Be(expected);
229+
}
230+
231+
public class ResourceStringUris : IEnumerable<object[]>
232+
{
233+
private const string ResourcePath = "untitled:Untitled-1";
234+
private const string ResourcePathWithPath = "untitled:Untitled-1/some/path";
235+
236+
protected IEnumerable<(string, string)> AddPaths(params string[] paths)
237+
{
238+
foreach (var path in paths)
239+
{
240+
yield return (path.Replace("c:", "c%3A"), path);
241+
yield return (path.Replace("c:", "c%3a"), path);
242+
yield return (path, path);
243+
}
244+
}
245+
246+
public IEnumerator<object[]> GetEnumerator()
247+
{
248+
foreach (var (source, destination) in AddPaths(
249+
ResourcePath,
250+
ResourcePath.Replace("Untitled", "Пространствоимен"),
251+
ResourcePath.Replace("Untitled", "汉字漢字"),
252+
ResourcePath.Replace("Untitled", "のはでした"),
253+
ResourcePath.Replace("Untitled", "コンサート"),
254+
ResourcePathWithPath,
255+
ResourcePathWithPath.Replace("Untitled", "Пространствоимен"),
256+
ResourcePathWithPath.Replace("Untitled", "汉字漢字"),
257+
ResourcePathWithPath.Replace("Untitled", "のはでした"),
258+
ResourcePathWithPath.Replace("Untitled", "コンサート")
259+
))
260+
{
261+
yield return new object[] {source, destination};
262+
}
263+
}
264+
265+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
266+
}
267+
222268
[Theory]
223269
[ClassData(typeof(WindowsPathUris))]
224270
public void Should_Handle_Windows_Uris(Uri uri, DocumentUri expected)
@@ -535,11 +581,13 @@ public abstract class BaseSingle : IEnumerable<object[]>
535581
{
536582
protected IEnumerable<(string, DocumentUri)> AddPaths(params string[] paths)
537583
{
538-
foreach (var path in paths)
584+
foreach (var expectedPath in paths)
539585
{
540-
yield return (path.Replace("c:", "c%3A"), path);
541-
yield return (path.Replace("c:", "c%3a"), path);
542-
yield return (path, path);
586+
if (expectedPath.Replace("c:", "c%3A") != expectedPath)
587+
yield return (expectedPath.Replace("c:", "c%3A"), expectedPath);
588+
if (expectedPath.Replace("c:", "c%3a") != expectedPath)
589+
yield return (expectedPath.Replace("c:", "c%3a"), expectedPath);
590+
yield return (expectedPath, expectedPath);
543591
}
544592
}
545593

0 commit comments

Comments
 (0)