-
Notifications
You must be signed in to change notification settings - Fork 4.3k
preserve encoding over temporary storage #482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
65f2f9f
ab47cbd
e7719dc
23a1004
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,7 +64,29 @@ public void TestCreateFromTemporaryStorage() | |
| // Create a temporary storage location | ||
| using (var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(System.Threading.CancellationToken.None)) | ||
| { | ||
| // Write text into it | ||
| temporaryStorage.WriteTextAsync(text).Wait(); | ||
|
|
||
| // Read text back from it | ||
| var text2 = temporaryStorage.ReadTextAsync().Result; | ||
|
|
||
| Assert.NotSame(text, text2); | ||
| Assert.Equal(text.ToString(), text2.ToString()); | ||
| Assert.Equal(text2.Encoding, null); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add another test (or enhance this one) to test non-null encodings too.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please do that.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public void TestCreateFromTemporaryStorageWithEncoding() | ||
| { | ||
| var textFactory = CreateMockTextFactoryService(); | ||
| var temporaryStorageService = new TemporaryStorageServiceFactory.TemporaryStorageService(textFactory); | ||
|
|
||
| var text = Text.SourceText.From("Hello, World!", Encoding.ASCII); | ||
|
|
||
| // Create a temporary storage location | ||
| using (var temporaryStorage = temporaryStorageService.CreateTemporaryTextStorage(System.Threading.CancellationToken.None)) | ||
| { | ||
| // Write text into it | ||
| temporaryStorage.WriteTextAsync(text).Wait(); | ||
|
|
||
|
|
@@ -73,7 +95,7 @@ public void TestCreateFromTemporaryStorage() | |
|
|
||
| Assert.NotSame(text, text2); | ||
| Assert.Equal(text.ToString(), text2.ToString()); | ||
| Assert.Equal(text2.Encoding, Encoding.Unicode); | ||
| Assert.Equal(text2.Encoding, Encoding.ASCII); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,9 @@ | |
|
|
||
| using System; | ||
| using System.Composition; | ||
| using System.Diagnostics; | ||
| using System.IO; | ||
| using System.Runtime.InteropServices; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
@@ -83,8 +85,10 @@ public SourceText ReadText(CancellationToken cancellationToken) | |
| using (Logger.LogBlock(FunctionId.TemporaryStorageServiceFactory_ReadText, cancellationToken)) | ||
| { | ||
| using (var stream = _memoryMappedInfo.CreateReadableStream()) | ||
| using (var reader = CreateTextReaderFromTemporaryStorage((ISupportDirectMemoryAccess)stream, (int)stream.Length, cancellationToken)) | ||
| { | ||
| return _service._textFactory.CreateText(stream, _encoding, cancellationToken); | ||
| // we pass in encoding we got from original source text even if it is null. | ||
| return _service._textFactory.CreateText(reader, _encoding, cancellationToken); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -135,6 +139,69 @@ public void WriteText(SourceText text, CancellationToken cancellationToken) | |
| // See commentary in ReadTextAsync for why this is implemented this way. | ||
| return Task.Factory.StartNew(() => WriteText(text, cancellationToken), cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); | ||
| } | ||
|
|
||
| private unsafe TextReader CreateTextReaderFromTemporaryStorage(ISupportDirectMemoryAccess accessor, int streamLength, CancellationToken cancellationToken) | ||
| { | ||
| char* src = (char*)accessor.GetPointer(); | ||
|
|
||
| // BOM: Unicode, little endian | ||
| // Skip the BOM when creating the reader | ||
| Debug.Assert(*src == 0xFEFF); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How are you certain that this will be true?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because we are the one who write in and read it and no-one else has access to it. |
||
|
|
||
| return new DirectMemoryAccessStreamReader(src + 1, streamLength / sizeof(char) - 1); | ||
| } | ||
|
|
||
| private unsafe class DirectMemoryAccessStreamReader : TextReader | ||
| { | ||
| private char* _position; | ||
| private readonly char* _end; | ||
|
|
||
| public DirectMemoryAccessStreamReader(char* src, int length) | ||
| { | ||
| Debug.Assert(src != null); | ||
| Debug.Assert(length >= 0); | ||
|
|
||
| _position = src; | ||
| _end = _position + length; | ||
| } | ||
|
|
||
| public override int Read() | ||
| { | ||
| if (_position >= _end) | ||
| { | ||
| return -1; | ||
| } | ||
|
|
||
| return *_position++; | ||
| } | ||
|
|
||
| public override int Read(char[] buffer, int index, int count) | ||
| { | ||
| if (buffer == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(buffer)); | ||
| } | ||
|
|
||
| if (index < 0 || index >= buffer.Length) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(index)); | ||
| } | ||
|
|
||
| if (count < 0 || (index + count) > buffer.Length) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(count)); | ||
| } | ||
|
|
||
| count = Math.Min(count, (int)(_end - _position)); | ||
| if (count > 0) | ||
| { | ||
| Marshal.Copy((IntPtr)_position, buffer, index, count); | ||
| _position += count; | ||
| } | ||
|
|
||
| return count; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private class TemporaryStreamStorage : ITemporaryStreamStorage | ||
|
|
@@ -244,3 +311,4 @@ private async Task WriteStreamMaybeAsync(Stream stream, bool useAsync, Cancellat | |
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,5 +17,12 @@ internal class TextFactoryService : ITextFactoryService | |
| cancellationToken.ThrowIfCancellationRequested(); | ||
| return SourceText.From(stream, defaultEncoding); | ||
| } | ||
|
|
||
| public SourceText CreateText(TextReader reader, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken)) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need this token? Is it just coming as a part of an interface contract?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The text reader could represent a large file that get's read in a loop. We need ability to quickly abort work.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mattwar but that token isn't cancelling the read, it just cancels before we start the read. Unless I'm missing the calling context.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ya, but it is interface, there could be an implementer that read through chunk of string and check cancellation. |
||
| return SourceText.From(reader.ReadToEnd(), encoding); | ||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you comment this function? I find it confusing that an Encoding is passed in, but then the code calls "detectEncodingFromBytOrderMarks" and then calls AsRoslynText, not using the encoding passed in, but instead reader.CurrentEncoding or Encoding.UTF8.
It seems like someone can be explicit about the encoding, but still have that overridden by the system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default encoding is something it use if it can't figure out Encoding from stream. that is why it is called default encoding. I will add more comments.
But I agree it is confusing. I took me sometime to figure out what that actually mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New API I added. CreateText(TextReader reader ... ) is the API people should use if they want to set encoding explicitly. this API doesnt have ambiguity where given Encoding and encoding embedded in the stream is different.
the first API is intended to be used in a situation where you just open a file (FileStream) and want to create a SourceText. the default encoding given is basically saying, if you can't figure out Encoding from the stream I gave you, assume it is the default encoding.