diff options
Diffstat (limited to 'src/Editor/Text/Util/TextUIUtil')
23 files changed, 2309 insertions, 8 deletions
diff --git a/src/Editor/Text/Util/TextUIUtil/AssemblyInfo.cs b/src/Editor/Text/Util/TextUIUtil/AssemblyInfo.cs new file mode 100644 index 0000000..4567dde --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/AssemblyInfo.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; +using System.Security.Permissions; + +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.View.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.Input.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.Classification.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.DragDrop.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.AdornmentLibrary.TextMarker.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.AdornmentLibrary.ToolTip.Wpf.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.TextMarkerAdornment.UnitTests, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.CurrentLineHighlighter.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.GlyphMargin.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.DifferenceViewer.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.OverviewMargin.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.OverviewMargin.UnitTests, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.UI.Utilities.UnitTests, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.TextViewUnitTestHelper, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.View.UnitTests, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Wpf.Input.UnitTests, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.Differencing.DifferenceViewer.UnitTests, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Language.Intellisense.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Commanding.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Commanding.Implementation.UnitTests, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Text.Editor.PrintingService.Implementation, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("EditorTestApp, PublicKey=" + ThisAssembly.PublicKey)] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.UI.Text.Cocoa.View.Implementation, PublicKey=" + ThisAssembly.PublicKey)] + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: SecurityPermission(SecurityAction.RequestMinimum, Flags = SecurityPermissionFlag.Execution)] +[assembly: ReliabilityContract(Consistency.MayCorruptProcess, Cer.MayFail)] diff --git a/src/Editor/Text/Util/TextUIUtil/BaseProxyService.cs b/src/Editor/Text/Util/TextUIUtil/BaseProxyService.cs index 0062990..c2467a4 100644 --- a/src/Editor/Text/Util/TextUIUtil/BaseProxyService.cs +++ b/src/Editor/Text/Util/TextUIUtil/BaseProxyService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.Composition; @@ -32,4 +32,4 @@ namespace Microsoft.VisualStudio.Utilities } } } -}
\ No newline at end of file +} diff --git a/src/Editor/Text/Util/TextUIUtil/ChangeBrushes.cs b/src/Editor/Text/Util/TextUIUtil/ChangeBrushes.cs new file mode 100644 index 0000000..e527d11 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/ChangeBrushes.cs @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Utilities +{ + using System.Collections.Generic; + using Microsoft.VisualStudio.Text.Document; + using Microsoft.VisualStudio.Text.Tagging; + + internal static class ChangeBrushes + { + public static NormalizedSnapshotSpanCollection[] GetUnifiedChanges(ITextSnapshot snapshot, IEnumerable<IMappingTagSpan<ChangeTag>> tags) + { + List<Span>[] unnormalizedChanges = new List<Span>[4] { null, + new List<Span>(), + new List<Span>(), + new List<Span>() + }; + foreach (IMappingTagSpan<ChangeTag> change in tags) + { + int type = (int)(change.Tag.ChangeTypes & (ChangeTypes.ChangedSinceOpened | ChangeTypes.ChangedSinceSaved)); + if (type != 0) + unnormalizedChanges[type].AddRange((NormalizedSpanCollection)(change.Span.GetSpans(snapshot))); + } + + NormalizedSnapshotSpanCollection[] changes = new NormalizedSnapshotSpanCollection[4]; + for (int i = 1; (i <= 3); ++i) + changes[i] = new NormalizedSnapshotSpanCollection(snapshot, unnormalizedChanges[i]); + + return changes; + } + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/DefaultStatusBarService.cs b/src/Editor/Text/Util/TextUIUtil/DefaultStatusBarService.cs new file mode 100644 index 0000000..f604b55 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/DefaultStatusBarService.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Text.Utilities +{ + [ExportImplementation(typeof(IStatusBarService))] + [Name("default")] + internal class DefaultStatusBarService : IStatusBarService + { + public Task SetTextAsync(string text) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/DefaultUIThreadOperationExecutor.cs b/src/Editor/Text/Util/TextUIUtil/DefaultUIThreadOperationExecutor.cs index c6c349d..4067a15 100644 --- a/src/Editor/Text/Util/TextUIUtil/DefaultUIThreadOperationExecutor.cs +++ b/src/Editor/Text/Util/TextUIUtil/DefaultUIThreadOperationExecutor.cs @@ -37,4 +37,4 @@ namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation { } } -}
\ No newline at end of file +} diff --git a/src/Editor/Text/Util/TextUIUtil/DifferenceBrushManager.cs b/src/Editor/Text/Util/TextUIUtil/DifferenceBrushManager.cs new file mode 100644 index 0000000..8773c20 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/DifferenceBrushManager.cs @@ -0,0 +1,119 @@ +// +// 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. +// +using System; +using System.Linq; +using System.Windows.Media; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.Text.Utilities +{ + class DifferenceBrushManager + { + public static DifferenceBrushManager GetBrushManager(ITextView3 view, IEditorFormatMapService formatMapService) + { + return view.Properties.GetOrCreateSingletonProperty(() => new DifferenceBrushManager(view, formatMapService.GetEditorFormatMap(view))); + } + + public static DifferenceBrushManager GetBrushManager(ITextView3 view, IEditorFormatMap formatMap) + { + return view.Properties.GetOrCreateSingletonProperty(() => new DifferenceBrushManager(view, formatMap)); + } + + // internal for unit testing + internal static readonly SolidColorBrush _defaultRemovedLineBrush = Brushes.PaleVioletRed; + internal static readonly SolidColorBrush _defaultAddedLineBrush = Brushes.LightYellow; + internal static readonly SolidColorBrush _defaultRemovedWordBrush = Brushes.Red; + internal static readonly SolidColorBrush _defaultAddedWordBrush = Brushes.Yellow; + + IEditorFormatMap _formatMap; + + #region Public properties (brushes) and changed event + + public Brush RemovedLineBrush { get; private set; } + public Brush AddedLineBrush { get; private set; } + + public Brush RemovedWordBrush { get; private set; } + public Brush RemovedWordForegroundBrush { get; private set; } + public Pen RemovedWordForegroundPen { get; private set; } + public Brush AddedWordBrush { get; private set; } + public Brush AddedWordForegroundBrush { get; private set; } + public Pen AddedWordForegroundPen { get; private set; } + + public Brush ViewportBrush { get; private set; } + public Pen ViewportPen { get; private set; } + public Brush OverviewBrush { get; private set; } + + public event EventHandler<EventArgs> BrushesChanged; + + #endregion + + internal DifferenceBrushManager(ITextView3 view, IEditorFormatMap formatMap) + { + _formatMap = formatMap; + + InitializeBrushes(); + + _formatMap.FormatMappingChanged += FormatMapChanged; + view.Closed += (s,a) => { _formatMap.FormatMappingChanged -= FormatMapChanged; }; + } + + void InitializeBrushes() + { + RemovedLineBrush = GetBrushValue("deltadiff.remove.line", _defaultRemovedLineBrush); + RemovedWordBrush = GetBrushValue("deltadiff.remove.word", _defaultRemovedWordBrush); + RemovedWordForegroundBrush = GetBrushValue("deltadiff.remove.word", _defaultRemovedWordBrush, EditorFormatDefinition.ForegroundBrushId); + RemovedWordForegroundPen = new Pen(RemovedWordForegroundBrush, 2.0); + RemovedWordForegroundPen.Freeze(); + + AddedLineBrush = GetBrushValue("deltadiff.add.line", _defaultAddedLineBrush); + AddedWordBrush = GetBrushValue("deltadiff.add.word", _defaultAddedWordBrush); + AddedWordForegroundBrush = GetBrushValue("deltadiff.add.word", _defaultAddedWordBrush, EditorFormatDefinition.ForegroundBrushId); + AddedWordForegroundPen = new Pen(AddedWordForegroundBrush, 2.0); + AddedWordForegroundPen.Freeze(); + + ViewportBrush = GetBrushValue("deltadiff.overview.color", Brushes.DarkGray, EditorFormatDefinition.ForegroundBrushId); + ViewportPen = new Pen(ViewportBrush, 2.0); + ViewportPen.Freeze(); + + OverviewBrush = GetBrushValue("deltadiff.overview.color", Brushes.Gray); + + var temp = BrushesChanged; + if (temp != null) + temp(this, EventArgs.Empty); + } + + Brush GetBrushValue(string formatName, Brush defaultValue, string resource = EditorFormatDefinition.BackgroundBrushId) + { + var formatProperties = _formatMap.GetProperties(formatName); + if (formatProperties != null && formatProperties.Contains(resource)) + { + var brushValue = formatProperties[resource] as Brush; + if (brushValue != null) + return brushValue; + } + + return defaultValue; + } + + void FormatMapChanged(object sender, FormatItemsEventArgs e) + { + bool updateRequired = e.ChangedItems.Any(item => + string.Equals(item, "deltadiff.add.word", System.StringComparison.OrdinalIgnoreCase) || + string.Equals(item, "deltadiff.add.line", System.StringComparison.OrdinalIgnoreCase) || + string.Equals(item, "deltadiff.remove.word", System.StringComparison.OrdinalIgnoreCase) || + string.Equals(item, "deltadiff.remove.line", System.StringComparison.OrdinalIgnoreCase) || + string.Equals(item, "deltadiff.overview.color", System.StringComparison.OrdinalIgnoreCase)); + + if (updateRequired) + { + InitializeBrushes(); + } + } + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/ExtensionMethods.cs b/src/Editor/Text/Util/TextUIUtil/ExtensionMethods.cs index 4d93bce..9c1ba00 100644 --- a/src/Editor/Text/Util/TextUIUtil/ExtensionMethods.cs +++ b/src/Editor/Text/Util/TextUIUtil/ExtensionMethods.cs @@ -81,7 +81,7 @@ namespace Microsoft.VisualStudio.Text.MultiSelection //The indentation specified by the smart indent service is desired column position of the caret. Find out how much virtual space //need to be at the end of the line to satisfy that. // TODO: need a way to determine column width in xplat scenarios, bug https://devdiv.visualstudio.com/DevDiv/_workitems/edit/637741 - double columnWidth = 7; + double columnWidth = (textView is ITextView3 textView3) ? textView3.FormattedLineSource.ColumnWidth : throw new NotSupportedException(); indentationWidth = Math.Max(0.0, (((double)indentation.Value) * columnWidth - textLine.TextWidth)); // if the coordinate is specified by the user and the user has selected a coordinate to the left diff --git a/src/Editor/Text/Util/TextUIUtil/IDragDropMouseProcessor.cs b/src/Editor/Text/Util/TextUIUtil/IDragDropMouseProcessor.cs new file mode 100644 index 0000000..185f0a8 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/IDragDropMouseProcessor.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Utilities +{ + using System.Windows; + using System.Windows.Input; + + /// <summary> + /// This interface is a used as an abstraction of the DragDropMouseProcessor so that it can be called from the left margin + /// to handle drag/drop. + /// </summary> + public interface IDragDropMouseProcessor + { + void DoPreprocessMouseLeftButtonDown(MouseButtonEventArgs e, Point position); + void DoPreprocessMouseLeftButtonUp(MouseButtonEventArgs e, Point position); + void DoPostprocessMouseLeftButtonUp(MouseButtonEventArgs e, Point position); + void DoPreprocessMouseMove(MouseEventArgs e, Point position); + void DoPreprocessDrop(DragEventArgs e, Point position); + void DoPreprocessDragEnter(DragEventArgs e, Point position); + void DoPreprocessDragLeave(DragEventArgs e); + void DoPreprocessDragOver(DragEventArgs e, Point position); + void DoPreprocessQueryContinueDrag(QueryContinueDragEventArgs e); + void DoPostprocessMouseLeave(MouseEventArgs e); + } +}
\ No newline at end of file diff --git a/src/Editor/Text/Util/TextUIUtil/IOrderableContentTypeAndTextViewRoleMetadata.cs b/src/Editor/Text/Util/TextUIUtil/IOrderableContentTypeAndTextViewRoleMetadata.cs new file mode 100644 index 0000000..8f24867 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/IOrderableContentTypeAndTextViewRoleMetadata.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Text.Utilities +{ + /// <summary> + /// Metadata which includes Ordering, Content Types and Text View Roles + /// </summary> + public interface IOrderableContentTypeAndTextViewRoleMetadata : IContentTypeAndTextViewRoleMetadata, IOrderable + { + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/IScrollMap2.cs b/src/Editor/Text/Util/TextUIUtil/IScrollMap2.cs new file mode 100644 index 0000000..3f02d06 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/IScrollMap2.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Utilities +{ + using Microsoft.VisualStudio.Text.Editor; + + /// <summary> + /// <para>Defines the mapping between character positions and scrollmap coordinates. This is not + /// the same as the coordinate system in which the scrollbar is rendered.</para> + /// </summary> + /// <remarks> + /// <para>Valid text positions range are [0...TextView.TextSnapshot.Length].</para> + /// <para>Corresponding scrollmap coordinates are [0.0 ... CoordinateOfBufferEnd].</para> + /// <para>Not every buffer position will have a distinct scrollmap coordinate. For example, every character on the same line of text will, + /// generally, have the same scrollmap coordinate.</para> + /// <para>Different scrollmap coordinates may map to the same buffer position. For example, scrollmap coordinates in the range [0.0, 1.0) will, generally, + /// map to the first character of the buffer.</para> + /// </remarks> + public interface IScrollMap2 : IScrollMap + { + void GetThumbTopAndBottom(out double thumbTop, out double thumbBottom); + + void ScrollToCoordinate(double coordinate); + void CenterOnCoordinate(double coordinate); + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/Markers.cs b/src/Editor/Text/Util/TextUIUtil/Markers.cs new file mode 100644 index 0000000..186c0a3 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/Markers.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Windows; + using System.Windows.Media; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Formatting; + + internal static class Markers + { + // pad the bottom part by 1 pixel to take advantage of the extra 1 pixel that's available in the default + // line transform at the bottom of each line + public readonly static Thickness SingleLinePadding = new Thickness(0.0, 0.0, 0.0, 1.0); + public readonly static Thickness MultiLinePadding = new Thickness(0.0); + + public static bool MarkerGeometrySpansMultipleLines(ITextViewLineCollection collection, SnapshotSpan bufferSpan) + { + ITextViewLine start = collection.GetTextViewLineContainingBufferPosition(bufferSpan.Start); + + return (start == null || bufferSpan.End > start.EndIncludingLineBreak); + } + + + //use double.MinValue/double.MaxValue for leftClip & rightClip to avoid clipping. + public static IList<Rect> GetRectanglesFromBounds(IList<TextBounds> bounds, Thickness padding, double leftClip, double rightClip, bool useTextBounds) + { + Debug.Assert(bounds != null); + + List<Rect> newBounds = new List<Rect>(bounds.Count); + foreach (var b in bounds) + { + double x1 = Math.Max(leftClip, b.Left - padding.Left); + double x2 = Math.Min(rightClip, b.Right + padding.Right); + if (x1 < x2) + { + double y1 = (useTextBounds ? b.TextTop : b.Top) - padding.Top; + double y2 = (useTextBounds ? b.TextBottom : b.Bottom) + padding.Bottom; + + newBounds.Add(new Rect(x1, y1, x2 - x1, y2 - y1)); + } + } + + return newBounds; + } + + public static Geometry GetMarkerGeometryFromRectangles(IList<Rect> rectangles) + { + Debug.Assert(rectangles != null); + + if (rectangles.Count == 0) + return null; + + // Set up the initial geometry + PathGeometry geometry = new PathGeometry(); + geometry.FillRule = FillRule.Nonzero; + + foreach (var rectangle in rectangles) + { + geometry.AddGeometry(new RectangleGeometry(rectangle)); + } + geometry.Freeze(); + + if (rectangles.Count > 1) + { + geometry = geometry.GetOutlinedPathGeometry(); + geometry.Freeze(); + } + return geometry; + } + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/MultiSelectionMouseState.cs b/src/Editor/Text/Util/TextUIUtil/MultiSelectionMouseState.cs new file mode 100644 index 0000000..a18587c --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/MultiSelectionMouseState.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.MultiSelection; + +namespace Microsoft.VisualStudio.Text.UI.Utilities +{ + public class MultiSelectionMouseState + { + public static MultiSelectionMouseState GetStateForView(ITextView textView) + { + return textView.Properties.GetOrCreateSingletonProperty(() => + { + return new MultiSelectionMouseState(textView); + }); + } + + private MultiSelectionMouseState(ITextView textView) + { + _textView = textView; + textView.LayoutChanged += OnLayoutChanged; + } + + private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) + { + if (_provisionalSelection != Selection.Invalid) + { + _provisionalSelection = _provisionalSelection.MapToSnapshot(e.NewSnapshot, _textView); + } + } + + private Selection _provisionalSelection = Selection.Invalid; + private ITextView _textView; + + public Selection ProvisionalSelection + { + get + { + return _provisionalSelection; + } + set + { + if (_provisionalSelection != value) + { + _provisionalSelection = value; + FireProvisionalSelectionChanged(); + } + } + } + + public event EventHandler ProvisionalSelectionChanged; + + private void FireProvisionalSelectionChanged() + { + ProvisionalSelectionChanged?.Invoke(this, EventArgs.Empty); + } + + public bool UserIsDraggingSelection { get; set; } = false; + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/PerformanceBlockMarker.cs b/src/Editor/Text/Util/TextUIUtil/PerformanceBlockMarker.cs new file mode 100644 index 0000000..13dd4fc --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/PerformanceBlockMarker.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; + +namespace Microsoft.VisualStudio.Text.Utilities +{ + [Export] + [PartCreationPolicy(CreationPolicy.Shared)] + internal sealed class PerformanceBlockMarker + { + [ImportMany] + private List<Lazy<IPerformanceMarkerBlockProvider>> _performanceMarkerBlockProviders = null; + + internal IDisposable CreateBlock(string blockName) + { + // Unit tests case + if (_performanceMarkerBlockProviders == null || _performanceMarkerBlockProviders.Count == 0) + { + return new Block(); + } + + // Optimize for the most common case + if (_performanceMarkerBlockProviders.Count == 1) + { + IDisposable block = _performanceMarkerBlockProviders[0].Value?.CreateBlock(blockName); + if (block != null) + { + return block; + } + } + + var providedBlocks = new FrugalList<IDisposable>(); + for (int i = 0; i < _performanceMarkerBlockProviders.Count; i++) + { + providedBlocks.Add(_performanceMarkerBlockProviders[i].Value?.CreateBlock(blockName)); + } + + return new Block(providedBlocks); + } + + private class Block : IDisposable + { + private readonly FrugalList<IDisposable> _markers; + + public Block(FrugalList<IDisposable> markers) + { + _markers = markers; + } + + public Block() + { + } + + public void Dispose() + { + if (_markers == null) + { + return; + } + + foreach (var marker in _markers) + { + marker?.Dispose(); + } + } + } + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/StatusBarService.cs b/src/Editor/Text/Util/TextUIUtil/StatusBarService.cs new file mode 100644 index 0000000..77b6b23 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/StatusBarService.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Text.UI.Utilities +{ + [Export(typeof(IStatusBarService))] + internal class StatusBarService : BaseProxyService<IStatusBarService>, IStatusBarService + { + [ImportImplementations(typeof(IStatusBarService))] + protected override IEnumerable<Lazy<IStatusBarService, IOrderable>> UnorderedImplementations { get; set; } + + public Task SetTextAsync(string text) + { + return BestImplementation.SetTextAsync(text); + } + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/Strings.Designer.cs b/src/Editor/Text/Util/TextUIUtil/Strings.Designer.cs new file mode 100644 index 0000000..938895a --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/Strings.Designer.cs @@ -0,0 +1,279 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace Microsoft.VisualStudio.Text.Utilities { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.Text.UI.Utilities.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to backslash. + /// </summary> + public static string Backslash { + get { + return ResourceManager.GetString("Backslash", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to capital. + /// </summary> + public static string Capital { + get { + return ResourceManager.GetString("Capital", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to We don't support child elements in Text yet.. + /// </summary> + public static string ChildElementsNotSupported { + get { + return ResourceManager.GetString("ChildElementsNotSupported", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to colon. + /// </summary> + public static string Colon { + get { + return ResourceManager.GetString("Colon", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to comma. + /// </summary> + public static string Comma { + get { + return ResourceManager.GetString("Comma", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to double quote. + /// </summary> + public static string DoubleQuote { + get { + return ResourceManager.GetString("DoubleQuote", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to empty line. + /// </summary> + public static string EmptyLine { + get { + return ResourceManager.GetString("EmptyLine", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The Visual Studio text editor only supports movements by word, character, document and line.. + /// </summary> + public static string InvalidTextMovementUnit { + get { + return ResourceManager.GetString("InvalidTextMovementUnit", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to left angled bracket. + /// </summary> + public static string LeftAngledBracket { + get { + return ResourceManager.GetString("LeftAngledBracket", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to left curly brace. + /// </summary> + public static string LeftCurlyBrace { + get { + return ResourceManager.GetString("LeftCurlyBrace", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to left parenthesis. + /// </summary> + public static string LeftParenthesis { + get { + return ResourceManager.GetString("LeftParenthesis", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to left square bracket. + /// </summary> + public static string LeftSquareBracket { + get { + return ResourceManager.GetString("LeftSquareBracket", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to period. + /// </summary> + public static string Period { + get { + return ResourceManager.GetString("Period", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to question mark. + /// </summary> + public static string QuestionMark { + get { + return ResourceManager.GetString("QuestionMark", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Supplied range is not valid.. + /// </summary> + public static string RangeNotValid { + get { + return ResourceManager.GetString("RangeNotValid", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to right angled bracket. + /// </summary> + public static string RightAngledBracket { + get { + return ResourceManager.GetString("RightAngledBracket", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to right curly brace. + /// </summary> + public static string RightCurlyBrace { + get { + return ResourceManager.GetString("RightCurlyBrace", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to right parenthesis. + /// </summary> + public static string RightParenthesis { + get { + return ResourceManager.GetString("RightParenthesis", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to right square bracket. + /// </summary> + public static string RightSquareBracket { + get { + return ResourceManager.GetString("RightSquareBracket", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to semicolon. + /// </summary> + public static string Semicolon { + get { + return ResourceManager.GetString("Semicolon", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to single quote. + /// </summary> + public static string SingleQuote { + get { + return ResourceManager.GetString("SingleQuote", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to slash. + /// </summary> + public static string Slash { + get { + return ResourceManager.GetString("Slash", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Supplied target range is not valid.. + /// </summary> + public static string TargetRangeNotValid { + get { + return ResourceManager.GetString("TargetRangeNotValid", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The Visual Studio text editor does not support search based on text formatting attributes.. + /// </summary> + public static string UnsupportedSearchBasedOnTextFormatted { + get { + return ResourceManager.GetString("UnsupportedSearchBasedOnTextFormatted", resourceCulture); + } + } + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/Strings.resx b/src/Editor/Text/Util/TextUIUtil/Strings.resx new file mode 100644 index 0000000..1811dbb --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/Strings.resx @@ -0,0 +1,213 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Backslash" xml:space="preserve"> + <value>backslash</value> + <comment>Used to represent \ in code for accessibility readers</comment> + </data> + <data name="Capital" xml:space="preserve"> + <value>capital</value> + <comment>Used to signify an upper case letter, e.g. C as capital c, used for accessibility readers</comment> + </data> + <data name="ChildElementsNotSupported" xml:space="preserve"> + <value>We don't support child elements in Text yet.</value> + </data> + <data name="Colon" xml:space="preserve"> + <value>colon</value> + <comment>Used to represent : in code for accessibility readers</comment> + </data> + <data name="Comma" xml:space="preserve"> + <value>comma</value> + <comment>Used to represent , in code for accessibility readers</comment> + </data> + <data name="DoubleQuote" xml:space="preserve"> + <value>double quote</value> + <comment>Used to represent " in code for accessibility readers</comment> + </data> + <data name="EmptyLine" xml:space="preserve"> + <value>empty line</value> + <comment>Used for screen readers to read "empty line" when the line is empty or contains white spaces only</comment> + </data> + <data name="InvalidTextMovementUnit" xml:space="preserve"> + <value>The Visual Studio text editor only supports movements by word, character, document and line.</value> + <comment>Error message shown to the user when they try to move the caret by a text unit other than word, line, document or character using automation clients.</comment> + </data> + <data name="LeftAngledBracket" xml:space="preserve"> + <value>left angled bracket</value> + <comment>Used to represent < in code for accessibility readers</comment> + </data> + <data name="LeftCurlyBrace" xml:space="preserve"> + <value>left curly brace</value> + <comment>Used to represent { in code for accessibility readers</comment> + </data> + <data name="LeftParenthesis" xml:space="preserve"> + <value>left parenthesis</value> + <comment>Used to represent ( in code for accessibility readers</comment> + </data> + <data name="LeftSquareBracket" xml:space="preserve"> + <value>left square bracket</value> + <comment>Used to represent [ in code for accessibility readers</comment> + </data> + <data name="Period" xml:space="preserve"> + <value>dot</value> + <comment>Used to represent . in code for accessibility readers</comment> + </data> + <data name="QuestionMark" xml:space="preserve"> + <value>question mark</value> + <comment>Used to represent ? in code for accessibility readers</comment> + </data> + <data name="RangeNotValid" xml:space="preserve"> + <value>Supplied range is not valid.</value> + </data> + <data name="RightAngledBracket" xml:space="preserve"> + <value>right angled bracket</value> + <comment>Used to represent > in code for accessibility readers</comment> + </data> + <data name="RightCurlyBrace" xml:space="preserve"> + <value>right curly brace</value> + <comment>Used to represent } in code for accessibility readers</comment> + </data> + <data name="RightParenthesis" xml:space="preserve"> + <value>right parenthesis</value> + <comment>Used to represent ) in code for accessibility readers</comment> + </data> + <data name="RightSquareBracket" xml:space="preserve"> + <value>right square bracket</value> + <comment>Used to represent ] in code for accessibility readers</comment> + </data> + <data name="Semicolon" xml:space="preserve"> + <value>semicolon</value> + <comment>Used to represent ; in code for accessibility readers</comment> + </data> + <data name="SingleQuote" xml:space="preserve"> + <value>single quote</value> + <comment>Used to represent ' in code for accessibility readers</comment> + </data> + <data name="Slash" xml:space="preserve"> + <value>slash</value> + <comment>Used to represent / in code for accessibility readers</comment> + </data> + <data name="TargetRangeNotValid" xml:space="preserve"> + <value>Supplied target range is not valid.</value> + </data> + <data name="UnsupportedSearchBasedOnTextFormatted" xml:space="preserve"> + <value>The Visual Studio text editor does not support search based on text formatting attributes.</value> + <comment>Error message shown to the user when they try to search for some text based on its formatting attributes through an automation client.</comment> + </data> +</root>
\ No newline at end of file diff --git a/src/Editor/Text/Util/TextUIUtil/TelemetryLogger.cs b/src/Editor/Text/Util/TextUIUtil/TelemetryLogger.cs new file mode 100644 index 0000000..55871e4 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/TelemetryLogger.cs @@ -0,0 +1,126 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +using System; +using System.ComponentModel.Composition; +using System.Windows.Threading; + +namespace Microsoft.VisualStudio.Text.Utilities +{ + [Export] + [PartCreationPolicy(CreationPolicy.Shared)] + internal sealed class TelemetryLogger + { + // This import may fail if we are running outside of VS, in scenarios such as CodeFlow. That is ok + // and this logging code should gracefully no-op in that case. + [Import(AllowDefault = true)] + private ILoggingServiceInternal LoggingService { get; set; } + + public const string VSEditorKey = "VS/Editor"; + + DispatcherTimer _touchZoomTimer = null; + DispatcherTimer _touchScrollTimer = null; + DispatcherTimer _zoomTimer = null; + DispatcherTimer _scrollTimer = null; + + uint _lastZoomLevel = 0; + readonly TimeSpan _timeout = TimeSpan.FromMilliseconds(1000.0); + + public void LogTouchZoom() + { + if (LoggingService != null) + { + if (_touchZoomTimer == null) + { + _touchZoomTimer = new DispatcherTimer(); + _touchZoomTimer.Interval = _timeout; + _touchZoomTimer.Tick += (s, e) => + { + _touchZoomTimer.Stop(); + LoggingService.AdjustCounter(TelemetryLogger.VSEditorKey, "TouchZoom", delta: 1); + }; + } + + // Restart timer + _touchZoomTimer.Stop(); + _touchZoomTimer.Start(); + } + } + + public void LogZoom(uint finalZoomLevel) + { + if (LoggingService != null) + { + if (_zoomTimer == null) + { + _zoomTimer = new DispatcherTimer(); + _zoomTimer.Interval = _timeout; + _zoomTimer.Tick += (s, e) => + { + _zoomTimer.Stop(); + LoggingService.PostEvent("VS/Editor/Zoom", "VS.Editor.Zoom.LastZoomLevel", _lastZoomLevel); + }; + } + + // Restart timer + _zoomTimer.Stop(); + + // Set _lastZoomLevel between stop and start out of paranoia regarding race conditions that shouldn't + // actually occur while using DispatcherTimer, since it runs all on the same thread. However, if the + // underlying timer get's changed, and this set were above the stop, there's a chance that we could + // occasionally log incorrect data if the set happened, and then tick occurred before the stop. + _lastZoomLevel = finalZoomLevel; + + _zoomTimer.Start(); + } + } + + public void LogTouchScroll() + { + if (LoggingService != null) + { + if (_touchScrollTimer == null) + { + _touchScrollTimer = new DispatcherTimer(); + _touchScrollTimer.Interval = _timeout; + _touchScrollTimer.Tick += (s, e) => + { + _touchScrollTimer.Stop(); + LoggingService.AdjustCounter(TelemetryLogger.VSEditorKey, "TouchScroll", delta: 1); + }; + } + + // Restart timer + _touchScrollTimer.Stop(); + _touchScrollTimer.Start(); + } + } + + public void LogScroll() + { + if (LoggingService != null) + { + if (_scrollTimer == null) + { + _scrollTimer = new DispatcherTimer(); + _scrollTimer.Interval = _timeout; + _scrollTimer.Tick += (s, e) => + { + _scrollTimer.Stop(); + LoggingService.AdjustCounter(TelemetryLogger.VSEditorKey, "Scroll", delta: 1); + }; + } + + // Restart timer + _scrollTimer.Stop(); + _scrollTimer.Start(); + } + } + + public void PostCounters() + { + LoggingService.PostCounters(); + } + } +}
\ No newline at end of file diff --git a/src/Editor/Text/Util/TextUIUtil/TextUIUtil.csproj b/src/Editor/Text/Util/TextUIUtil/TextUIUtil.csproj new file mode 100644 index 0000000..91594fb --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/TextUIUtil.csproj @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <AssemblyName>Microsoft.VisualStudio.Text.UI.Utilities</AssemblyName> + <RootNamespace>$(AssemblyName)</RootNamespace> + <NoWarn>649;436;618;8073;$(NoWarn)</NoWarn> + <AssemblyAttributeClsCompliant>true</AssemblyAttributeClsCompliant> + <TargetFramework>$(TargetFramework)</TargetFramework> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.Composition" /> + <Reference Include="System.Core" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Runtime" /> + <Reference Include="UIAutomationClient" /> + <Reference Include="UIAutomationProvider" /> + <Reference Include="UIAutomationTypes" /> + <Reference Include="System.Xaml" /> + <Reference Include="WindowsBase" /> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + </ItemGroup> + <ItemGroup> + <PackageReference Include="System.Collections.Immutable" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\Core\Def\CoreUtility.csproj" /> + <ProjectReference Include="..\..\Def\TextData\TextData.csproj" /> + <ProjectReference Include="..\..\Def\TextLogic\TextLogic.csproj" /> + <ProjectReference Include="..\..\Def\TextUI\TextUI.csproj" /> + <ProjectReference Include="..\..\Def\TextUIWpf\TextUIWpf.csproj" /> + <ProjectReference Include="..\..\Def\Internal\Internal.csproj" /> + <ProjectReference Include="..\..\Util\TextDataUtil\TextDataUtil.csproj" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Update="Strings.resx"> + <Generator>PublicResXFileCodeGenerator</Generator> + <LastGenOutput>Strings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> + <Compile Update="Strings.Designer.cs"> + <DesignTime>true</DesignTime> + <AutoGen>true</AutoGen> + <DependentUpon>Strings.resx</DependentUpon> + </Compile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/src/Editor/Text/Util/TextUIUtil/TransformedDispatcherCollection.cs b/src/Editor/Text/Util/TextUIUtil/TransformedDispatcherCollection.cs new file mode 100644 index 0000000..e6cebc6 --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/TransformedDispatcherCollection.cs @@ -0,0 +1,268 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Threading; + +namespace Microsoft.VisualStudio.Text.Utilities +{ + internal class TransformedDispatcherCollection<TSourceCollection, TSourceElement, TTargetElement> : ReadOnlyCollection<TTargetElement>, INotifyCollectionChanged, INotifyPropertyChanged, IWeakEventListener, IDisposable + where TSourceCollection : class, IEnumerable<TSourceElement>, INotifyCollectionChanged + { + #region Fields + private readonly Dispatcher dispatcher; + private readonly TSourceCollection sourceCollection; + private readonly Func<TSourceElement, TTargetElement> setup; + private readonly Action<TTargetElement> teardown; + private bool disposed; + #endregion + + #region Constructors + /// <summary> + /// Creates a new transformed collection wrapping a source collection. + /// </summary> + /// <param name="sourceCollection">The source collection that this collection wraps.</param> + /// <param name="setup">The logic for creating a transformed element from a source element.</param> + /// <param name="teardown">The logic for destroying a transformed element when removed from the transformed collection.</param> + public TransformedDispatcherCollection(Dispatcher dispatcher, TSourceCollection sourceCollection, Func<TSourceElement, TTargetElement> setup, Action<TTargetElement> teardown = null) : + base(new List<TTargetElement>(sourceCollection.Select(setup))) + { + ArgumentValidation.NotNull(dispatcher, "dispatcher"); + ArgumentValidation.NotNull(sourceCollection, "sourceCollection"); + ArgumentValidation.NotNull(setup, "setup"); + + this.setup = setup; + this.teardown = teardown; + + this.dispatcher = dispatcher; + this.sourceCollection = sourceCollection; + CollectionChangedEventManager.AddListener(this.sourceCollection, this); + } + #endregion + + #region Events + /// <summary> + /// Occurs when the collection changes. + /// </summary> + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// <summary> + /// Occurs when a property changes. + /// </summary> + public event PropertyChangedEventHandler PropertyChanged; + #endregion + + #region Public Methods + /// <summary> + /// Tears down all elements of the transformed collection, clears the transformed collection, and stops listening to change events on the source collection. + /// </summary> + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (!this.ReceiveWeakEvent(managerType, sender, e)) + { + Debug.Fail("Weak event was not handled"); + return false; + } + + return true; + } + #endregion + + #region Protected Methods + protected TSourceCollection SourceCollection + { + get + { + return this.sourceCollection; + } + } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + // Cleanup managed resources + CollectionChangedEventManager.RemoveListener(this.sourceCollection, this); + + if (this.teardown != null) + { + foreach (var target in this.Items) + { + this.teardown(target); + } + } + + this.Items.Clear(); + } + + // Cleanup unmanaged resources + + // Mark the object as disposed + this.disposed = true; + } + } + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (this.CollectionChanged != null) + { + this.CollectionChanged(this, e); + } + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + if (this.PropertyChanged != null) + { + this.PropertyChanged(this, e); + } + } + + protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (object.ReferenceEquals(sender, this.sourceCollection)) + { + var collectionChangedEventArgs = e as NotifyCollectionChangedEventArgs; + if (collectionChangedEventArgs != null) + { + this.OnSourceCollectionChanged(sender, collectionChangedEventArgs); + return true; + } + } + + return false; + } + #endregion + + #region Private Methods + + private async void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + TSourceElement[] snapshot = null; + if (e.Action == NotifyCollectionChangedAction.Reset) + { + snapshot = this.sourceCollection.ToArray(); + } + + await CheckAccessInvokeAsync(() => UpdateCollectionOnDispatcherThread(e, snapshot)).ConfigureAwait(false); + } + + private void UpdateCollectionOnDispatcherThread(NotifyCollectionChangedEventArgs e, TSourceElement[] newElementsSnapshot) + { + NotifyCollectionChangedEventArgs collectionChangedEventArgs = null; + + if (e.Action == NotifyCollectionChangedAction.Reset) + { + if (this.teardown != null) + { + foreach (var target in this.Items) + { + this.teardown(target); + } + } + + this.Items.Clear(); + + foreach (var source in newElementsSnapshot) + { + this.Items.Add(this.setup(source)); + } + + collectionChangedEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); + } + else + { + List<object> oldItems = null; + if (e.OldItems != null) + { + oldItems = new List<object>(); + for (int i = 0; i < e.OldItems.Count; i++) + { + TTargetElement target = this.Items[e.OldStartingIndex]; + oldItems.Add(target); + + if (this.teardown != null) + { + this.teardown(target); + } + + this.Items.RemoveAt(e.OldStartingIndex); + } + } + + List<object> newItems = null; + if (e.NewItems != null) + { + newItems = new List<object>(); + for (int i = 0; i < e.NewItems.Count; i++) + { + TTargetElement target = this.setup((TSourceElement)e.NewItems[i]); + newItems.Add(target); + this.Items.Insert(i + e.NewStartingIndex, target); + } + } + + if (e.Action == NotifyCollectionChangedAction.Remove) + { + collectionChangedEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, e.OldStartingIndex); + } + else if (e.Action == NotifyCollectionChangedAction.Add) + { + collectionChangedEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, e.NewStartingIndex); + } + else if (e.Action == NotifyCollectionChangedAction.Move) + { + collectionChangedEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, newItems, e.NewStartingIndex, e.OldStartingIndex); + } + else if (e.Action == NotifyCollectionChangedAction.Replace) + { + collectionChangedEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, e.NewStartingIndex); + } + } + + this.OnPropertyChanged(new PropertyChangedEventArgs("Count")); + this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + + this.OnCollectionChanged(collectionChangedEventArgs); + } + + /// <summary> + /// Executes the specified action on a thread associated with object's dispatcher. + /// This invokes a InvokeAsync on the Dispatcher, does not wait for the action + /// to complete -- returns immediately. + /// </summary> + /// <param name="action">An action to execute.</param> + /// <returns>A task that completes when action has completed.</returns> + private async Task CheckAccessInvokeAsync(Action action) + { + ArgumentValidation.NotNull(action, "action"); + + if (this.dispatcher.CheckAccess()) + { + action(); + } + else + { + await this.dispatcher.InvokeAsync(action, DispatcherPriority.Normal); + } + } + #endregion + } +} diff --git a/src/Editor/Text/Util/TextUIUtil/UIExtensionSelector.cs b/src/Editor/Text/Util/TextUIUtil/UIExtensionSelector.cs index 124c4e8..db336d1 100644 --- a/src/Editor/Text/Util/TextUIUtil/UIExtensionSelector.cs +++ b/src/Editor/Text/Util/TextUIUtil/UIExtensionSelector.cs @@ -12,7 +12,7 @@ namespace Microsoft.VisualStudio.Text.Utilities /// <summary> /// Helper class to perform ContentType and TextViewRole match against a set of extensions. /// </summary> - public static class UIExtensionSelector + internal static class UIExtensionSelector { /// <summary> /// Given a list of extensions that provide text view roles, filter the list and return that @@ -70,7 +70,7 @@ namespace Microsoft.VisualStudio.Text.Utilities ITextViewRoleSet viewRoles, Func<TExtensionFactory, TExtensionInstance> getter, IContentTypeRegistryService contentTypeRegistryService, - GuardedOperations guardedOperations, + IGuardedOperations guardedOperations, object errorSource) where TMetadataView : IContentTypeAndTextViewRoleMetadata // both content type and text view role are required where TExtensionFactory : class diff --git a/src/Editor/Text/Util/TextUIUtil/UIThreadOperationExecutor.cs b/src/Editor/Text/Util/TextUIUtil/UIThreadOperationExecutor.cs index 592c814..3e068fd 100644 --- a/src/Editor/Text/Util/TextUIUtil/UIThreadOperationExecutor.cs +++ b/src/Editor/Text/Util/TextUIUtil/UIThreadOperationExecutor.cs @@ -31,4 +31,4 @@ namespace Microsoft.VisualStudio.Text.Utilities return BestImplementation.Execute(executionOptions, action); } } -}
\ No newline at end of file +} diff --git a/src/Editor/Text/Util/TextUIUtil/VacuousTextViewModel.cs b/src/Editor/Text/Util/TextUIUtil/VacuousTextViewModel.cs index baf84fa..f7ea58e 100644 --- a/src/Editor/Text/Util/TextUIUtil/VacuousTextViewModel.cs +++ b/src/Editor/Text/Util/TextUIUtil/VacuousTextViewModel.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Text.Utilities /// is the same as the edit buffer, which is in turn the same as the data buffer if no edit buffer is specified. /// This is the default if no view model provider is specified or if the specified one declines to build a model. /// </summary> - public class VacuousTextViewModel : ITextViewModel + internal class VacuousTextViewModel : ITextViewModel { private ITextDataModel dataModel; private ITextBuffer editBuffer; diff --git a/src/Editor/Text/Util/TextUIUtil/WpfHelper.cs b/src/Editor/Text/Util/TextUIUtil/WpfHelper.cs new file mode 100644 index 0000000..65219ac --- /dev/null +++ b/src/Editor/Text/Util/TextUIUtil/WpfHelper.cs @@ -0,0 +1,853 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +#pragma warning disable 1634, 1691 + +namespace Microsoft.VisualStudio.Text.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Runtime.InteropServices; + using System.Security; + using System.Text; + using System.Windows; + using System.Windows.Input; + using System.Windows.Interop; + using System.Windows.Media; + + /// <summary> + /// Helpful utilities related to WPF and the windows platform, including input method editor support. + /// </summary> + public static class WpfHelper + { + public const int WM_IME_STARTCOMPOSITION = 0x010D; + public const int WM_IME_ENDCOMPOSITION = 0x010E; + public const int WM_IME_COMPOSITION = 0x010F; + public const int WM_IME_SETCONTEXT = 0x0281; + public const int WM_IME_NOTIFY = 0x0282; + public const int WM_IME_CONTROL = 0x0283; + public const int WM_IME_COMPOSITIONFULL = 0x0284; + public const int WM_IME_SELECT = 0x0285; + public const int WM_IME_CHAR = 0x0286; + public const int WM_IME_REQUEST = 0x0288; + public const int WM_IME_KEYDOWN = 0x0290; + + public const int WM_KEYDOWN = 0x0100; + + public const int GCS_COMPSTR = 0x0008; + public const int GCS_RESULTSTR = 0x0800; + + public const int VK_HANJA = 0x19; + + public const int IMR_RECONVERTSTRING = 0x0004; + public const int IMR_CONFIRMRECONVERTSTRING = 0x0005; + + public const int LCID_KOREAN = 1042; + + [ThreadStatic] //Unit tests run each test in its own thread and we can't reused thread managers across threads. + static NativeMethods.ITfThreadMgr _threadMgr; + + [ThreadStatic] + static bool _threadMgrFailed = false; + + public static readonly double DeviceScaleX; + public static readonly double DeviceScaleY; + +#pragma warning disable CA1810 // Initialize reference type static fields inline + static WpfHelper() +#pragma warning restore CA1810 // Initialize reference type static fields inline + { + //Get the device DPI (which can only be changed via a restart so we + //can save the result in a static). + IntPtr dc = NativeMethods.GetDC(IntPtr.Zero); + if (dc != IntPtr.Zero) + { + const double LogicalDpi = 96.0; + DeviceScaleX = LogicalDpi / NativeMethods.GetDeviceCaps(dc, NativeMethods.LOGPIXELSX); + DeviceScaleY = LogicalDpi / NativeMethods.GetDeviceCaps(dc, NativeMethods.LOGPIXELSY); + + NativeMethods.ReleaseDC(IntPtr.Zero, dc); + } + else + { + DeviceScaleX = 1.0; + DeviceScaleY = 1.0; + } + } + +#if false + /// <summary> + /// Given a point relative to a visual, gets the Screen co-ordinates + /// </summary> + public static Point GetScreenCoordinates(Point point, Visual relativeTo) + { + // Validate + if (relativeTo == null) + throw new ArgumentNullException("relativeTo"); + + Visual root = GetRootVisual(relativeTo); + Point rootTranslatedPoint = relativeTo.TransformToAncestor(root).Transform(point); + + // Get the Hwnd for this visual + HwndSource hwndSource = GetHwndSource(relativeTo); + if (hwndSource != null) + { + NativeMethods.POINT pt = new NativeMethods.POINT(); + pt.x = (int)rootTranslatedPoint.X; + pt.y = (int)rootTranslatedPoint.Y; + NativeMethods.ClientToScreen(hwndSource.Handle, ref pt); + return new Point(pt.x, pt.y); + } + + return rootTranslatedPoint; + } + + /// <summary> + /// Gets the screen rectangle that contains the point relative to the given visual + /// </summary> + public static Rect GetScreenRect(Point pt, Visual relativeTo) + { + // Validate + if (relativeTo == null) + throw new ArgumentNullException("relativeTo"); + + Point screenCoordinates = GetScreenCoordinates(pt, relativeTo); + NativeMethods.POINT screenPoint = new NativeMethods.POINT(); + screenPoint.x = (int)screenCoordinates.X; + screenPoint.y = (int)screenCoordinates.Y; + IntPtr monitor = NativeMethods.MonitorFromPoint(screenPoint, NativeMethods.MONITOR_DEFAULTTONEAREST); + + NativeMethods.MONITORINFO monitorInfo = new NativeMethods.MONITORINFO(); + monitorInfo.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(monitorInfo); + if (NativeMethods.GetMonitorInfo(monitor, ref monitorInfo)) + return new Rect(new Point(monitorInfo.rcWork.left, monitorInfo.rcWork.top), new Point(monitorInfo.rcWork.right, monitorInfo.rcWork.bottom)); + else + return SystemParameters.WorkArea; + } +#endif + /// <summary> + /// Gets the screen rectangle that contains given screen point. + /// </summary> + public static Rect GetScreenRect(Point screenCoordinates) + { + NativeMethods.POINT screenPoint = new NativeMethods.POINT(); + screenPoint.x = (int)screenCoordinates.X; + screenPoint.y = (int)screenCoordinates.Y; + IntPtr monitor = NativeMethods.MonitorFromPoint(screenPoint, NativeMethods.MONITOR_DEFAULTTONEAREST); + + NativeMethods.MONITORINFO monitorInfo = new NativeMethods.MONITORINFO(); + monitorInfo.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(monitorInfo); + if (NativeMethods.GetMonitorInfo(monitor, ref monitorInfo)) + return new Rect(new Point(monitorInfo.rcWork.left, monitorInfo.rcWork.top), new Point(monitorInfo.rcWork.right, monitorInfo.rcWork.bottom)); + else + return SystemParameters.WorkArea; + } + + /// <summary> + /// Determine whether two brushes are equal. + /// </summary> + /// <param name="brush">The first brush.</param> + /// <param name="other">The second brush.</param> + /// <returns><c>true</c> if the two are equal, <c>false</c> otherwise.</returns> + /// <remarks>internal for testability</remarks> + public static bool BrushesEqual(Brush brush, Brush other) + { + if (brush == null || other == null) + { + return object.ReferenceEquals(brush, other); + } + else + { + if (brush.Opacity == 0 && other.Opacity == 0) + return true; + + SolidColorBrush colorBrush1 = brush as SolidColorBrush; + SolidColorBrush colorBrush2 = other as SolidColorBrush; + + // If both brushes are SolidColorBrushes check the color of each + if (colorBrush1 != null && colorBrush2 != null) + { + if (colorBrush1.Color.A == 0 && colorBrush2.Color.A == 0) + return true; + + return colorBrush1.Color == colorBrush2.Color && + Math.Abs(colorBrush1.Opacity - colorBrush2.Opacity) < 0.01; + } + + // as a last resort try brush.Equals (which pretty much is the equivalent of returning false here since + // it doesn't compare any of the core properties of brushes) + return brush.Equals(other); + } + } + + public static string GetImmCompositionString(IntPtr immContext, int dwIndex) + { + if (immContext == IntPtr.Zero) + return null; + + // get buffer size + int size = NativeMethods.ImmGetCompositionStringW(immContext, dwIndex, null, 0); + if (size <= 0) + { + //If there is no composition string we return + return null; + } + + //Get the string in an appropriately sized buffer + StringBuilder result = new StringBuilder(size / 2); //We get the size in bytes. + size = NativeMethods.ImmGetCompositionStringW(immContext, dwIndex, result, size); + if (size <= 0) + { + Debug.Assert(false); //This should never happen? why did we succeed the first time? + + //But handle it gracefully. + return null; + } + + return result.ToString().Substring(0, size / 2); + } + + public static bool ImmNotifyIME(IntPtr immContext, int dwAction, int dwIndex, int dwValue) + { + return NativeMethods.ImmNotifyIME(immContext, dwAction, dwIndex, dwValue); + } + + public static bool HanjaConversion(IntPtr context, IntPtr keyboardLayout, char selection) + { + const int IME_ESC_HANJA_MODE = 0x1008; + + if (context != IntPtr.Zero) + { + IntPtr charsPtr = Marshal.StringToHGlobalUni(new string(selection, 1)); + + IntPtr hr = NativeMethods.ImmEscapeW(keyboardLayout, context, IME_ESC_HANJA_MODE, charsPtr); + + // Free the allocated memory + Marshal.FreeHGlobal(charsPtr); + + if (hr != IntPtr.Zero) + return true; + } + + return false; + } + + private static SnapshotSpan GetSelectionContext(SnapshotSpan selection) + { + const int padding = 20; //Consistent with Win7 code. + SnapshotPoint start = new SnapshotPoint(selection.Snapshot, + Math.Max(0, selection.Start.Position - padding)); + SnapshotPoint end = new SnapshotPoint(selection.Snapshot, + Math.Min(selection.Snapshot.Length, selection.End.Position + padding)); + return new SnapshotSpan(start, end); + + } + + /// <summary> + /// Size or fill-in a RECONVERTSTRING structure that contains the selection (with padding) + /// </summary> + public static IntPtr ReconvertString(IntPtr lParam, SnapshotSpan selection) + { + SnapshotSpan selectionContext = GetSelectionContext(selection); + int sizeofRCS = Marshal.SizeOf(typeof(NativeMethods.RECONVERTSTRING)); + + if (lParam != IntPtr.Zero) + { + NativeMethods.RECONVERTSTRING reconvertString = (NativeMethods.RECONVERTSTRING)(Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING))); + + if (selection.Length >= ((reconvertString.dwSize - sizeofRCS) / 2)) + { + //We didn't get space for all the characters we requested. + return IntPtr.Zero; + } + + Marshal.WriteInt32((IntPtr)((long)lParam + (long)Marshal.OffsetOf(typeof(NativeMethods.RECONVERTSTRING), "dwStrLen")), selectionContext.Length); + Marshal.WriteInt32((IntPtr)((long)lParam + (long)Marshal.OffsetOf(typeof(NativeMethods.RECONVERTSTRING), "dwStrOffset")), sizeofRCS); + + Marshal.WriteInt32((IntPtr)((long)lParam + (long)Marshal.OffsetOf(typeof(NativeMethods.RECONVERTSTRING), "dwCompStrLen")), selection.Length); + Marshal.WriteInt32((IntPtr)((long)lParam + (long)Marshal.OffsetOf(typeof(NativeMethods.RECONVERTSTRING), "dwCompStrOffset")), (selection.Start.Position - selectionContext.Start.Position) * 2); + + Marshal.WriteInt32((IntPtr)((long)lParam + (long)Marshal.OffsetOf(typeof(NativeMethods.RECONVERTSTRING), "dwTargetStrLen")), selection.Length); + Marshal.WriteInt32((IntPtr)((long)lParam + (long)Marshal.OffsetOf(typeof(NativeMethods.RECONVERTSTRING), "dwTargetStrOffset")), (selection.Start.Position - selectionContext.Start.Position) * 2); + + Marshal.Copy(selection.Snapshot.GetText(selectionContext).ToCharArray(), 0, (IntPtr)((long)lParam + (long)sizeofRCS), selectionContext.Length); + Marshal.WriteInt16((IntPtr)((long)lParam + (long)(sizeofRCS + (selectionContext.Length * 2))), 0); + } + + return new IntPtr(sizeofRCS + ((selectionContext.Length + 1) * 2)); + } + + /// <summary> + /// Generate a selection from a RECONVERTSTRING block. + /// </summary> + public static SnapshotSpan ConfirmReconvertString(IntPtr lParam, SnapshotSpan selection) + { + if (lParam != IntPtr.Zero) + { + SnapshotSpan selectionContext = GetSelectionContext(selection); + + NativeMethods.RECONVERTSTRING reconvertString = (NativeMethods.RECONVERTSTRING)(Marshal.PtrToStructure(lParam, typeof(NativeMethods.RECONVERTSTRING))); + + return new SnapshotSpan(selectionContext.Start + (reconvertString.dwCompStrOffset / 2), reconvertString.dwCompStrLen); + } + + return new SnapshotSpan(selection.Snapshot, 0, 0); + } + + private static class CompositionFontMapper + { + private static IDictionary<int, LanguageFontMapping> _languageMap = new Dictionary<int, LanguageFontMapping>(9); + private static IDictionary<string, FontSizeMapping> _fontMap = new Dictionary<string, FontSizeMapping>(25); + private static IDictionary<string, FontSizeMapping> _consolasFontMap = new Dictionary<string, FontSizeMapping>(6); + private static IDictionary<string, FontSizeMapping> _courierNewFontMap = new Dictionary<string, FontSizeMapping>(6); + + static CompositionFontMapper() + { + LanguageFontMapping simplifiedChinese = new LanguageFontMapping("SimSun", "Microsoft YaHei"); + LanguageFontMapping traditionalChinese = new LanguageFontMapping("MingLiU", "Microsoft JhengHei"); + LanguageFontMapping japanese = new LanguageFontMapping("MS Gothic", "Meiryo"); + + _languageMap.Add(0x0004, simplifiedChinese); //zh-CHS Chinese-China (Simplified) + _languageMap.Add(0x0804, simplifiedChinese); //zh-CN Chinese-China + _languageMap.Add(0x1004, simplifiedChinese); //zh-SG Chinese-Singapore + + _languageMap.Add(0x7c04, traditionalChinese); //zh-CHT Chinese-China (Traditional) + _languageMap.Add(0x0c04, traditionalChinese); //zh-HK Chinese-Hong Kong SAR + _languageMap.Add(0x1404, traditionalChinese); //zh-MO Chinese-Macau + _languageMap.Add(0x0404, traditionalChinese); //zh-TW Chinese-Taiwan + + _languageMap.Add(0x0011, japanese); //ja Japanese + _languageMap.Add(0x0411, japanese); //ja-JP Japanese - Japan + + //This is a map of conversions from a Consolas base font to the specified composition font used in the composition window + _consolasFontMap.Add("SimSun", new FontSizeMapping(-2.0, 2.0, -2.0)); + _consolasFontMap.Add("SimSun-ExtB", new FontSizeMapping(-2.0, 2.0, -2.0)); + _consolasFontMap.Add("Microsoft YaHei", new FontSizeMapping(1.0, 2.0, 2.0)); + _consolasFontMap.Add("MingLiU", new FontSizeMapping(-2.0, 2.0, -3.0)); + _consolasFontMap.Add("Microsoft JhengHei", new FontSizeMapping(2.0, 2.0, 3.0)); + _consolasFontMap.Add("MS Gothic", new FontSizeMapping(-1.0, 2.0, -2.0)); + _consolasFontMap.Add("Meiryo", new FontSizeMapping(1.0, 2.0, 4.0)); + + //This is a map of conversions from a Courier New base font to the specified composition font used in the composition window + _courierNewFontMap.Add("SimSun", new FontSizeMapping(-2.0, 2.0, -3.0)); + _courierNewFontMap.Add("SimSun-ExtB", new FontSizeMapping(-2.0, 2.0, -3.0)); + _courierNewFontMap.Add("Microsoft YaHei", new FontSizeMapping(1.0, 2.0, 2.0)); + _courierNewFontMap.Add("MingLiU", new FontSizeMapping(-2.0, 2.0, -4.0)); + _courierNewFontMap.Add("Microsoft JhengHei", new FontSizeMapping(2.0, 2.0, 2.0)); + _courierNewFontMap.Add("MS Gothic", new FontSizeMapping(-1.0, 2.0, -3.0)); + _courierNewFontMap.Add("Meiryo", new FontSizeMapping(2.0, 2.0, 4.0)); + + //This is a map of conversion factors intended for use when the same font is used as the base font and in the composition window + //but we'll use it whenever the base font isn't Consola or Courier New. + _fontMap.Add("MS Gothic", new FontSizeMapping(0.0, 2.0, 0.0)); + _fontMap.Add("MS PGothic", new FontSizeMapping(0.0, 2.0, 0.0)); + _fontMap.Add("MS UI Gothic", new FontSizeMapping(0.0, 2.0, 0.0)); + _fontMap.Add("Meiryo", new FontSizeMapping(0.0, 2.0, 0.0)); + _fontMap.Add("Arial Unicode MS", new FontSizeMapping(0.0, 2.0, 0.0)); + _fontMap.Add("MS Mincho", new FontSizeMapping(0.0, 2.0, 0.0)); + _fontMap.Add("MS PMincho", new FontSizeMapping(0.0, 2.0, 0.0)); + + _fontMap.Add("Dotum", new FontSizeMapping(0.0, 2.0, 1.0)); + _fontMap.Add("DotumChe", new FontSizeMapping(0.0, 2.0, 1.0)); + _fontMap.Add("Malgun Gothic", new FontSizeMapping(1.0, 2.0, 0.0)); + _fontMap.Add("Batang", new FontSizeMapping(0.0, 2.0, -1.0)); + _fontMap.Add("BatangChe", new FontSizeMapping(0.0, 2.0, -1.0)); + _fontMap.Add("Gulim", new FontSizeMapping(-1.0, 2.0, -1.0)); + _fontMap.Add("GulimChe", new FontSizeMapping(-1.0, 2.0, -1.0)); + _fontMap.Add("Gungsuh", new FontSizeMapping(-1.0, 2.0, -1.0)); + _fontMap.Add("GungsuhChe", new FontSizeMapping(-1.0, 2.0, -1.0)); + + _fontMap.Add("SimSun", new FontSizeMapping(-1.0, 2.0, -2.0)); + _fontMap.Add("SimSun-ExtB", new FontSizeMapping(-1.0, 2.0, -2.0)); + _fontMap.Add("NSimSun", new FontSizeMapping(-1.0, 2.0, -2.0)); + _fontMap.Add("Microsoft YaHei", new FontSizeMapping(-1.0, 2.0, 0.0)); + _fontMap.Add("SimHei", new FontSizeMapping(-1.0, 2.0, -2.0)); + _fontMap.Add("KaiTi", new FontSizeMapping(-1.0, 2.0, -1.0)); + _fontMap.Add("FangSong", new FontSizeMapping(-1.0, 2.0, -2.0)); + + _fontMap.Add("MingLiU", new FontSizeMapping(-2.0, 1.0, -3.0)); + _fontMap.Add("PMingLiU", new FontSizeMapping(-2.0, 1.0, -3.0)); + _fontMap.Add("Microsoft JhengHei", new FontSizeMapping(-1.0, 2.0, 0.0)); + } + + public static void GetSizeAdjustments(string baseFont, string compositionFont, out double topPadding, out double bottomPadding, out double heightPadding) + { + IDictionary<string, FontSizeMapping> map; + if (string.Equals(baseFont, "Consolas", StringComparison.Ordinal)) + map = _consolasFontMap; + else if (string.Equals(baseFont, "Courier New", StringComparison.Ordinal)) + map = _courierNewFontMap; + else + map = _fontMap; + + FontSizeMapping mapping; + if (map.TryGetValue(compositionFont, out mapping)) + { + topPadding = mapping.TopPadding; + bottomPadding = mapping.BottomPadding; + heightPadding = mapping.HeightPadding; + } + else + { + topPadding = 0.0; + bottomPadding = 2.0; + heightPadding = -2.0; + } + } + + private struct FontSizeMapping + { + public readonly double TopPadding; + public readonly double BottomPadding; + public readonly double HeightPadding; + + public FontSizeMapping(double topPadding, double bottomPadding, double unadjustedHeightPadding) + { + this.TopPadding = topPadding; + this.BottomPadding = bottomPadding; + this.HeightPadding = unadjustedHeightPadding - (topPadding + bottomPadding); + } + } + + private class LanguageFontMapping + { + public readonly string OldFallbackFont; + public readonly string NewFallbackFont; + + public LanguageFontMapping(string oldFallbackFont, string newFallbackFont) + { + this.OldFallbackFont = oldFallbackFont; + this.NewFallbackFont = newFallbackFont; + } + + public string GetCompositionFont(int majorVersion) + { + return (majorVersion >= 6) ? this.NewFallbackFont : this.OldFallbackFont; + } + } + } + + public static IntPtr GetDefaultIMEWnd() + { + return NativeMethods.ImmGetDefaultIMEWnd(IntPtr.Zero); + } + + public static IntPtr GetImmContext(IntPtr hwnd) + { + if (hwnd != IntPtr.Zero) + return NativeMethods.ImmGetContext(hwnd); + else + return IntPtr.Zero; + } + + /// <summary> + /// Release the IMM Context. + /// </summary> + public static bool ReleaseContext(IntPtr hwnd, IntPtr immContext) + { + if ((hwnd != IntPtr.Zero) && (immContext != IntPtr.Zero)) + return NativeMethods.ImmReleaseContext(hwnd, immContext); + else + return false; + } + + public static void EnableImmComposition() + { + + if (!_threadMgrFailed) + { + // Create a Thread manager if it doesn't exist + if (_threadMgr == null) + { +#pragma warning disable CA1806 // Do not ignore method results + NativeMethods.TF_CreateThreadMgr(out _threadMgr); +#pragma warning restore CA1806 // Do not ignore method results + if (_threadMgr == null) + { + _threadMgrFailed = true; + return; + } + } + + _threadMgr.SetFocus(IntPtr.Zero); + } + } + + public static IntPtr GetKeyboardLayout() + { + return NativeMethods.GetKeyboardLayout(0); + } + + public static bool ImmIsIME(IntPtr hkl) + { + return NativeMethods.ImmIsIME(hkl); + } + + /// <summary> + /// Generate a new FileStream with a unique random name. + /// </summary> + /// <param name="fileDirectory">Directory where the file will live.</param> + /// <param name="filePath">Path to the file created.</param> + /// <returns>A file stream with a random file name.</returns> + private static FileStream GetRandomFileNameStream(string fileDirectory, out string filePath) + { + int count = 0; + filePath = string.Empty; + while (count++ < 2) + { + string fileName = Path.GetRandomFileName(); + filePath = Path.Combine(fileDirectory, fileName + "~"); //The ~ suffix hides the temporary file from GIT. + if (!File.Exists(filePath)) + { + try + { + return new FileStream(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None); + } + catch (Exception) + { + Debug.Fail("Creating random file failed."); + } + } + } + + throw new IOException(filePath + " exists"); + } + } + + /// <summary> + /// SafeHandle wrapper for the cursor image + /// </summary> + internal sealed class SafeCursor : SafeHandle + { + public SafeCursor() + : base(IntPtr.Zero, true) + { + } + + public SafeCursor(IntPtr hCursor) + : base(hCursor, true) + { + } + + public override bool IsInvalid + { + get + { + return this.handle == IntPtr.Zero; + } + } + + protected override bool ReleaseHandle() + { + return NativeMethods.DestroyCursor(this.handle); + } + } + + + [SuppressUnmanagedCodeSecurity] + static class NativeMethods + { + public const int LOGPIXELSX = 88; + public const int LOGPIXELSY = 90; + + #region Win32 Interop + + /// <summary> + /// A point structure to match the Win32 POINT + /// </summary> + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int x; + public int y; + }; + + /// <summary> + /// A rect structure to match the Win32 RECT + /// </summary> + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + }; + + /// <summary> + /// Win32 MONITORINFO Struct + /// </summary> + [StructLayout(LayoutKind.Sequential)] + public struct MONITORINFO + { + public int cbSize; + public RECT rcMonitor; + public RECT rcWork; + public int dwFlags; + }; + + /// <summary> + /// Win32 COMPOSITIONFORM struct + /// </summary> + [StructLayout(LayoutKind.Sequential)] + public struct COMPOSITIONFORM + { + public int dwStyle; + public POINT ptCurrentPos; + public RECT rcArea; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECONVERTSTRING + { + public int dwSize; + public int dwVersion; + public int dwStrLen; + public int dwStrOffset; + public int dwCompStrLen; + public int dwCompStrOffset; + public int dwTargetStrLen; + public int dwTargetStrOffset; + } + + /// <summary> + /// Win32 LOGFONT struct + /// </summary> + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public class LOGFONT + { + public int lfHeight; + public int lfWidth; + public int lfEscapement; + public int lfOrientation; + public int lfWeight; + public byte lfItalic; + public byte lfUnderline; + public byte lfStrikeOut; + public byte lfCharSet; + public byte lfOutPrecision; + public byte lfClipPrecision; + public byte lfQuality; + public byte lfPitchAndFamily; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string lfFaceName; + } + + /// <summary> + /// Win32 WINDOWPOS struct + /// </summary> + [StructLayout(LayoutKind.Sequential)] + public class WINDOWPOS + { + public IntPtr hwnd; + public IntPtr hwndInsertAfter; + public int x; + public int y; + public int cx; + public int cy; + public uint flags; + } + + /// <summary></summary> + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("aa80e801-2021-11d2-93e0-0060b067b86e")] + internal interface ITfThreadMgr + { + // <summary></summary> + //HRESULT Activate([out] TfClientId *ptid); + /// <SecurityNote> + /// Critical: This code calls into an unmanaged COM function which is not + /// safe since it elevates + /// </SecurityNote> + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + void Activate(out int clientId); + + // <summary></summary> + //HRESULT Deactivate(); + /// <SecurityNote> + /// Critical: This code calls into an unmanaged COM function which is not + /// safe since it elevates + /// </SecurityNote> + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + void Deactivate(); + + // <summary></summary> + //HRESULT CreateDocumentMgr([out] ITfDocumentMgr **ppdim); + /// <SecurityNote> + /// Critical: This code calls into an unmanaged COM function which is not + /// safe since it elevates + /// </SecurityNote> + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + void CreateDocumentMgr(out object docMgr); + + /// <summary></summary> + //HRESULT EnumDocumentMgrs([out] IEnumTfDocumentMgrs **ppEnum); + void EnumDocumentMgrs(out object enumDocMgrs); + + /// <summary></summary> + //HRESULT GetFocus([out] ITfDocumentMgr **ppdimFocus); + void GetFocus(out IntPtr docMgr); + + // <summary></summary> + //HRESULT SetFocus([in] ITfDocumentMgr *pdimFocus); + /// <SecurityNote> + /// Critical: This code calls into an unmanaged COM function which is not + /// safe since it elevates + /// </SecurityNote> + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + void SetFocus(IntPtr docMgr); + + /// <summary></summary> + //HRESULT AssociateFocus([in] HWND hwnd, + // [in, unique] ITfDocumentMgr *pdimNew, + // [out] ITfDocumentMgr **ppdimPrev); + void AssociateFocus(IntPtr hwnd, object newDocMgr, out object prevDocMgr); + + /// <summary></summary> + //HRESULT IsThreadFocus([out] BOOL *pfThreadFocus); + void IsThreadFocus([MarshalAs(UnmanagedType.Bool)] out bool isFocus); + + //HRESULT GetFunctionProvider([in] REFCLSID clsid, + // [out] ITfFunctionProvider **ppFuncProv); + /// <summary></summary> + /// <SecurityNote> + /// Critical: This code calls into an unmanaged COM function which is not + /// safe since it elevates + /// </SecurityNote> + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [PreserveSig] + int GetFunctionProvider(ref Guid classId, out object funcProvider); + + /// <summary></summary> + //HRESULT EnumFunctionProviders([out] IEnumTfFunctionProviders **ppEnum); + void EnumFunctionProviders(out object enumProviders); + + //HRESULT GetGlobalCompartment([out] ITfCompartmentMgr **ppCompMgr); + /// <summary></summary> + /// <SecurityNote> + /// Critical: This code calls into an unmanaged COM function which is not + /// safe since it elevates + /// </SecurityNote> + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + void GetGlobalCompartment(out object compartmentMgr); + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DestroyCursor(IntPtr hCursor); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags); + + [DllImport("User32.dll")] + public static extern IntPtr GetDC(IntPtr hwnd); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ReleaseDC(IntPtr hWnd, IntPtr hdc); + + [DllImport("Gdi32.dll")] + public static extern int GetDeviceCaps(IntPtr hdc, int index); + + [DllImport("user32.dll")] + public extern static IntPtr GetKeyboardLayout(int dwThread); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public extern static bool ClientToScreen(IntPtr hWnd, ref POINT point); + + [DllImport("user32.dll")] + public extern static IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags); + + [DllImport("user32.dll")] + public extern static IntPtr MonitorFromPoint(POINT pt, int dwFlags); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public extern static bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr LoadImage(IntPtr hinst, string lpszName, IMAGE_TYPE uType, int cxDesired, int cyDesired, IMAGE_FORMAT_REQUEST fuLoad); + + [DllImport("msctf.dll")] + internal static extern int TF_CreateThreadMgr(out ITfThreadMgr threadMgr); + + [DllImport("imm32.dll")] + internal static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd); + + [DllImport("imm32.dll")] + internal static extern IntPtr ImmGetContext(IntPtr hWnd); + + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImmSetCompositionWindow(IntPtr hIMC, IntPtr ptr); + + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC); + + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImmSetCompositionFontW(IntPtr hIMC, IntPtr lplf); + + [DllImport("imm32.dll", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.I4)] + internal static extern int ImmGetCompositionStringW(IntPtr hIMC, int dwIndex, StringBuilder lpBuf, int dwBufLen); + + [DllImport("imm32.dll", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.I4)] + internal static extern int ImmSetCompositionStringW(IntPtr hIMC, int dwIndex, StringBuilder lpComp, int dwCompLen, StringBuilder lpBuf, int dwBufLen); + + [DllImport("imm32.dll")] + internal static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC); + + [DllImport("imm32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ImmNotifyIME(IntPtr immContext, int dwAction, int dwIndex, int dwValue); + + [DllImport("imm32.dll", CharSet = CharSet.Unicode)] + internal static extern IntPtr ImmEscapeW(IntPtr hkl, IntPtr himc, int esc, IntPtr lpBuf); + + [DllImport("imm32.dll", CharSet = CharSet.Unicode)] + internal static extern bool ImmIsIME(IntPtr hkl); + + public const int MONITOR_DEFAULTTONEAREST = 0x00000002; + public const int CFS_RECT = 0x0001; + + public enum IMAGE_TYPE + { + IMAGE_BITMAP = 0, + IMAGE_ICON = 1, + IMAGE_CURSOR = 2, + IMAGE_ENHMETAFILE = 3 + } + + [Flags] + public enum IMAGE_FORMAT_REQUEST + { + LR_DEFAULTCOLOR = 0x0000, + LR_MONOCHROME = 0x0001, + LR_COPYRETURNORG = 0x0004, + LR_COPYDELETEORG = 0x0008, + LR_LOADFROMFILE = 0x0010, + LR_DEFAULTSIZE = 0x0040, + LR_LOADMAP3DCOLORS = 0x1000, + LR_CREATEDIBSECTION = 0x2000, + LR_COPYFROMRESOURCE = 0x4000, + LR_SHARED = 0x8000 + } + + #endregion // Win32 Interop + } +} |