// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // // This file contain implementations details that are subject to change without notice. // Use at your own risk. // namespace Microsoft.VisualStudio.Text.Implementation { using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; using System.IO.MemoryMappedFiles; using System.Text; using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Document; using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.Text.Projection.Implementation; using Microsoft.VisualStudio.Text.Utilities; using Microsoft.VisualStudio.Utilities; /// /// Factory for TextBuffers and ProjectionBuffers. /// [Export(typeof(ITextImageFactoryService))] [Export(typeof(ITextImageFactoryService2))] [Export(typeof(ITextBufferFactoryService))] [Export(typeof(ITextBufferFactoryService2))] [Export(typeof(ITextBufferFactoryService3))] [Export(typeof(IProjectionBufferFactoryService))] internal partial class BufferFactoryService : ITextBufferFactoryService2, ITextBufferFactoryService3, IProjectionBufferFactoryService, IInternalTextBufferFactory, ITextImageFactoryService2 { #region Standard Content Type Definitions [Export] [Name("any")] public ContentTypeDefinition anyContentTypeDefinition; [Export] [Name("text")] [BaseDefinition("any")] public ContentTypeDefinition textContentTypeDefinition; [Export] [Name("projection")] [BaseDefinition("any")] public ContentTypeDefinition projectionContentTypeDefinition; [Export] [Name("plaintext")] [BaseDefinition("text")] public ContentTypeDefinition plaintextContentTypeDefinition; [Export] [Name("code")] [BaseDefinition("text")] public ContentTypeDefinition codeContentType; [Export] [Name("inert")] // N.B.: This ContentType does NOT inherit from anything public ContentTypeDefinition inertContentTypeDefinition; #endregion #region Service Consumptions [Import] internal IContentTypeRegistryService _contentTypeRegistryService { get; set; } [Import] internal IDifferenceService _differenceService { get; set; } [Import] internal ITextDifferencingSelectorService _textDifferencingSelectorService { get; set; } [Import] internal GuardedOperations _guardedOperations { get; set; } [Import] internal IWhitespaceManagerFactory _whitespaceManagerFactory { get; set; } #endregion #region Private state private IContentType textContentType; private IContentType plaintextContentType; private IContentType inertContentType; private IContentType projectionContentType; #endregion #region ContentType accessors public IContentType TextContentType { get { if (this.textContentType == null) { // it's OK to evaluate this more than once, and the assignment is atomic, so we don't protect this with a lock this.textContentType = _contentTypeRegistryService.GetContentType("text"); } return this.textContentType; } } public IContentType PlaintextContentType { get { if (this.plaintextContentType == null) { // it's OK to evaluate this more than once, and the assignment is atomic, so we don't protect this with a lock this.plaintextContentType = _contentTypeRegistryService.GetContentType("plaintext"); } return this.plaintextContentType; } } public IContentType InertContentType { get { if (this.inertContentType == null) { // it's OK to evaluate this more than once, and the assignment is atomic, so we don't protect this with a lock this.inertContentType = _contentTypeRegistryService.GetContentType("inert"); } return this.inertContentType; } } public IContentType ProjectionContentType { get { if (this.projectionContentType == null) { // it's OK to evaluate this more than once, and the assignment is atomic, so we don't protect this with a lock this.projectionContentType = _contentTypeRegistryService.GetContentType("projection"); } return this.projectionContentType; } } #endregion public ITextBuffer CreateTextBuffer() { return Make(TextContentType, StringRebuilder.Empty, false); } public ITextBuffer CreateTextBuffer(IContentType contentType) { if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } return Make(contentType, StringRebuilder.Empty, false); } public ITextBuffer CreateTextBuffer(string text, IContentType contentType) { return CreateTextBuffer(text, contentType, false); } public ITextBuffer CreateTextBuffer(SnapshotSpan span, IContentType contentType) { if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } StringRebuilder content = StringRebuilderFromSnapshotSpan(span); return Make(contentType, content, false); } public ITextBuffer CreateTextBuffer(ITextImage image, IContentType contentType) { if (image == null) { throw new ArgumentNullException(nameof(image)); } if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } StringRebuilder content = StringRebuilder.Create(image); return Make(contentType, content, false); } public ITextBuffer CreateTextBuffer(string text, IContentType contentType, bool spurnGroup) { if (text == null) { throw new ArgumentNullException(nameof(text)); } if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } return Make(contentType, StringRebuilder.Create(text), spurnGroup); } public ITextBuffer CreateTextBuffer(TextReader reader, IContentType contentType, long length, string traceId) { return this.CreateTextBuffer(reader, contentType, length, traceId, throwOnInvalidCharacters: false); } public ITextBuffer CreateTextBuffer(TextReader reader, IContentType contentType, long length, string traceId, bool throwOnInvalidCharacters) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } if (length > int.MaxValue) { throw new InvalidOperationException(Strings.FileTooLarge); } int longestLineLength; StringRebuilder content = TextImageLoader.Load( reader, length, out var newlineState, out var leadingWhitespaceState, out longestLineLength, throwOnInvalidCharacters: throwOnInvalidCharacters); ITextBuffer buffer = Make(contentType, content, false); // Make the call to GetWhitespaceManager to add the manager to the properties. We don't need the return value here. var _ = _whitespaceManagerFactory.GetOrCreateWhitespaceManager(buffer, newlineState, leadingWhitespaceState); // Leave a sign about the longest line in the buffer. This is rather nasty, but for now // we don't want to pollute the API with this factoid buffer.Properties["LongestLineLength"] = longestLineLength; return buffer; } public ITextBuffer CreateTextBuffer(TextReader reader, IContentType contentType) { return CreateTextBuffer(reader, contentType, -1, "legacy", throwOnInvalidCharacters: false); } internal static StringRebuilder StringRebuilderFromSnapshotAndSpan(ITextSnapshot snapshot, Span span) { return AppendStringRebuildersFromSnapshotAndSpan(StringRebuilder.Empty, snapshot, span); } internal static StringRebuilder StringRebuilderFromSnapshotSpan(SnapshotSpan span) { return StringRebuilderFromSnapshotAndSpan(span.Snapshot, span.Span); } internal static StringRebuilder StringRebuilderFromSnapshotSpans(IList sourceSpans, Span selectedSourceSpans) { StringRebuilder content = StringRebuilder.Empty; for (int i = 0; (i < selectedSourceSpans.Length); ++i) { var span = sourceSpans[selectedSourceSpans.Start + i]; content = AppendStringRebuildersFromSnapshotAndSpan(content, span.Snapshot, span.Span); } return content; } internal static StringRebuilder AppendStringRebuildersFromSnapshotAndSpan(StringRebuilder content, ITextSnapshot snapshot, Span span) { if (span.Length != 0) { var baseSnapshot = snapshot as BaseSnapshot; if (baseSnapshot != null) { content = content.Append(baseSnapshot.Content.GetSubText(span)); } else { // The we don't know what to do fallback. This should never be called unless someone provides a new snapshot // implementation. content = content.Append(snapshot.GetText(span)); } } return content; } #region ITextImageFactoryService members public ITextImage CreateTextImage(string text) { return CachingTextImage.Create(StringRebuilder.Create(text), null); } public ITextImage CreateTextImage(TextReader reader, long length) { return CachingTextImage.Create(TextImageLoader.Load(reader, length, out var _, out var _, out var _), null); } public ITextImage CreateTextImage(MemoryMappedFile source) { // Evil implementation (for now) that just reads the entire contents of the MMF. // Eventually to be replaced with something along the lines of a version of the StringRebuilderForCompressedChars that uses the MMF directly. using (var stream = source.CreateViewStream()) { using (var reader = new StreamReader(stream, Encoding.Unicode)) { return this.CreateTextImage(reader, -1); } } } #endregion private TextBuffer Make(IContentType contentType, StringRebuilder content, bool spurnGroup) { TextBuffer buffer = new TextBuffer(contentType, content, _textDifferencingSelectorService.DefaultTextDifferencingService, _guardedOperations, spurnGroup); RaiseTextBufferCreatedEvent(buffer); return buffer; } public IProjectionBuffer CreateProjectionBuffer(IProjectionEditResolver projectionEditResolver, IList trackingSpans, ProjectionBufferOptions options, IContentType contentType) { // projectionEditResolver is allowed to be null. if (trackingSpans == null) { throw new ArgumentNullException(nameof(trackingSpans)); } if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } IProjectionBuffer buffer = new ProjectionBuffer(this, projectionEditResolver, contentType, trackingSpans, _differenceService, _textDifferencingSelectorService.DefaultTextDifferencingService, options, _guardedOperations); RaiseProjectionBufferCreatedEvent(buffer); return buffer; } public IProjectionBuffer CreateProjectionBuffer(IProjectionEditResolver projectionEditResolver, IList trackingSpans, ProjectionBufferOptions options) { // projectionEditResolver is allowed to be null. if (trackingSpans == null) { throw new ArgumentNullException(nameof(trackingSpans)); } IProjectionBuffer buffer = new ProjectionBuffer(this, projectionEditResolver, ProjectionContentType, trackingSpans, _differenceService, _textDifferencingSelectorService.DefaultTextDifferencingService, options, _guardedOperations); RaiseProjectionBufferCreatedEvent(buffer); return buffer; } public IElisionBuffer CreateElisionBuffer(IProjectionEditResolver projectionEditResolver, NormalizedSnapshotSpanCollection exposedSpans, ElisionBufferOptions options, IContentType contentType) { // projectionEditResolver is allowed to be null. if (exposedSpans == null) { throw new ArgumentNullException(nameof(exposedSpans)); } if (exposedSpans.Count == 0) { throw new ArgumentOutOfRangeException(nameof(exposedSpans)); // really? } if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } if (exposedSpans[0].Snapshot != exposedSpans[0].Snapshot.TextBuffer.CurrentSnapshot) { // TODO: // build against given snapshot and then move forward if necessary? throw new ArgumentException("Elision buffer must be created against the current snapshot of its source buffer"); } IElisionBuffer buffer = new ElisionBuffer(projectionEditResolver, contentType, exposedSpans[0].Snapshot.TextBuffer, exposedSpans, options, _textDifferencingSelectorService.DefaultTextDifferencingService, _guardedOperations); RaiseProjectionBufferCreatedEvent(buffer); return buffer; } public IElisionBuffer CreateElisionBuffer(IProjectionEditResolver projectionEditResolver, NormalizedSnapshotSpanCollection exposedSpans, ElisionBufferOptions options) { return CreateElisionBuffer(projectionEditResolver, exposedSpans, options, ProjectionContentType); } public event EventHandler TextBufferCreated; public event EventHandler ProjectionBufferCreated; private void RaiseTextBufferCreatedEvent(ITextBuffer buffer) { EventHandler handler = TextBufferCreated; if (handler != null) { handler(this, new TextBufferCreatedEventArgs(buffer)); } } private void RaiseProjectionBufferCreatedEvent(IProjectionBufferBase buffer) { EventHandler handler = ProjectionBufferCreated; if (handler != null) { handler(this, new TextBufferCreatedEventArgs(buffer)); } } } }