diff options
author | Oleg Tkachenko <olegtk@microsoft.com> | 2018-01-17 09:46:41 +0300 |
---|---|---|
committer | Oleg Tkachenko <olegtk@microsoft.com> | 2018-01-17 09:46:41 +0300 |
commit | 82d657798f3873d1fbb9090a062327059544a8b0 (patch) | |
tree | 63cbd81b0081c897d11d39614d7dbb2369ccbd40 /src | |
parent | c60cb8991cf77e0de575f4cf15b1bb74a8091762 (diff) |
Sync with 15.6.253, add commanding
Diffstat (limited to 'src')
111 files changed, 3275 insertions, 4 deletions
diff --git a/src/Core/Def/BaseUtility/ExportImplementationAttribute.cs b/src/Core/Def/BaseUtility/ExportImplementationAttribute.cs new file mode 100644 index 0000000..0f98f43 --- /dev/null +++ b/src/Core/Def/BaseUtility/ExportImplementationAttribute.cs @@ -0,0 +1,64 @@ +using System; +using System.ComponentModel.Composition; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Along with <see cref="ImportImplementationsAttribute"/> enables MEF proxy pattern where a single component export serves + /// as a proxy for the best implementation selected at run time. This pattern allows component consumers to just [Import] it, + /// hiding the complexity of selecting one of implementations. + /// </summary> + /// <example> + /// A typical sample: + /// + /// A component contract definition: + /// + /// interface IService { + /// void Foo(); + /// } + /// + /// Default implementation: + /// + /// [ExportImplementation(typeof(IService))] + /// [Name("default")] + /// class DefaultService : IService {...} + /// + /// Another implementation: + /// + /// [ExportImplementation(typeof(IService))] + /// [Name("A better implementation")] + /// [Order(Before = "default")] + /// class AdvancedService : IService {...} + /// + /// A proxy: + /// + /// [Export(typeof(IService))] + /// class ProxyService : IService { + /// [ImportImplementations(typeof(IService))] + /// IEnumerable<Lazy<IService, IOrderable>> _unorderedImplementations; + /// + /// public void Foo() { + /// Orderer.Order(_unorderedImplementations).FirstOrDefault()?.Value.Foo(); + /// } + /// } + /// + /// Consuming IService: + /// + /// [Import] + /// IService service = null; + /// </example> + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public sealed class ExportImplementationAttribute : ExportAttribute + { + internal const string ImplementationContractName = "Microsoft.VisualStudio.Utilities.Export.Implementation"; + + /// <summary> + /// Creates new <see cref="ExportImplementationAttribute"/> instance. + /// </summary> + /// <param name="contractType">A contract type.</param> + public ExportImplementationAttribute(Type contractType) + : base(ImplementationContractName, contractType) + { + } + } +} diff --git a/src/Core/Def/BaseUtility/INamed.cs b/src/Core/Def/BaseUtility/INamed.cs new file mode 100644 index 0000000..122b247 --- /dev/null +++ b/src/Core/Def/BaseUtility/INamed.cs @@ -0,0 +1,17 @@ +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Represents an object that provides a localized display name + /// to be used when it's being represented to the user, for + /// example when blaming for delays. + /// </summary> + public interface INamed + { + /// <summary> + /// Gets display name of an instance used to represent it to the user, for + /// example when blaming it for delays. + /// </summary> + string DisplayName { get; } + } +} + diff --git a/src/Core/Def/BaseUtility/ImportImplementationsAttribute.cs b/src/Core/Def/BaseUtility/ImportImplementationsAttribute.cs new file mode 100644 index 0000000..4849cfc --- /dev/null +++ b/src/Core/Def/BaseUtility/ImportImplementationsAttribute.cs @@ -0,0 +1,62 @@ +using System; +using System.ComponentModel.Composition; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Along with <see cref="ExportImplementationAttribute"/> enables MEF proxy pattern where a single component export serves + /// as a proxy for the best implementation selected at run time. This pattern allows component consumers to just [Import] it, + /// hiding the complexity of selecting one of implementations. + /// </summary> + /// <example> + /// A typical sample: + /// + /// A component contract definition: + /// + /// interface IService { + /// void Foo(); + /// } + /// + /// Default implementation: + /// + /// [ExportImplementation(typeof(IService))] + /// [Name("default")] + /// class DefaultService : IService {...} + /// + /// Another implementation: + /// + /// [ExportImplementation(typeof(IService))] + /// [Name("A better implementation")] + /// [Order(Before = "default")] + /// class AdvancedService : IService {...} + /// + /// A proxy: + /// + /// [Export(typeof(IService))] + /// class ProxyService : IService { + /// [ImportImplementations(typeof(IService))] + /// IEnumerable<Lazy<IService, IOrderable>> _unorderedImplementations; + /// + /// public void Foo() { + /// Orderer.Order(_unorderedImplementations).FirstOrDefault()?.Value.Foo(); + /// } + /// } + /// + /// Consuming IService: + /// + /// [Import] + /// IService service = null; + /// </example> + + public class ImportImplementationsAttribute : ImportManyAttribute + { + /// <summary> + /// Creates new <see cref="ImportImplementationsAttribute"/> instance. + /// </summary> + /// <param name="contractType">A contract type.</param> + public ImportImplementationsAttribute(Type contractType) + : base(ExportImplementationAttribute.ImplementationContractName, contractType) + { + } + } +} diff --git a/src/Core/Impl/ContentType/Strings.Designer.cs b/src/Core/Impl/ContentType/Strings.Designer.cs index 9572258..48b56f5 100644 --- a/src/Core/Impl/ContentType/Strings.Designer.cs +++ b/src/Core/Impl/ContentType/Strings.Designer.cs @@ -115,7 +115,7 @@ namespace Microsoft.VisualStudio.Utilities.Implementation { } /// <summary> - /// Looks up a localized string similar to The content type {0} leads to a cycle in its base types.. + /// Looks up a localized string similar to The content type {0} cannot be used as base for content type {1} because it would create a derivation cycle.. /// </summary> internal static string ContentTypeRegistry_CausesCycles { get { diff --git a/src/Microsoft.VisualStudio.Text.Implementation.csproj b/src/Microsoft.VisualStudio.Text.Implementation.csproj index 728f071..4c4478e 100644 --- a/src/Microsoft.VisualStudio.Text.Implementation.csproj +++ b/src/Microsoft.VisualStudio.Text.Implementation.csproj @@ -5,9 +5,9 @@ <SignAssembly>true</SignAssembly> <AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile> <DelaySign>false</DelaySign> - <Version>15.0.7-pre</Version> + <Version>15.0.8-pre</Version> <AssemblyVersion>15.0.0.0</AssemblyVersion> - <NuGetVersionEditor>15.6.241-preview</NuGetVersionEditor> + <NuGetVersionEditor>15.6.253-preview</NuGetVersionEditor> </PropertyGroup> <PropertyGroup> @@ -56,6 +56,11 @@ <DesignTime>True</DesignTime> <DependentUpon>Strings.resx</DependentUpon> </Compile> + <Compile Update="Text\Impl\Commanding\CommandingStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>CommandingStrings.resx</DependentUpon> + </Compile> </ItemGroup> <ItemGroup> @@ -89,6 +94,11 @@ <LastGenOutput>Strings.Designer.cs</LastGenOutput> <CustomToolNamespace>Microsoft.VisualStudio.Text.Operations.Implementation</CustomToolNamespace> </EmbeddedResource> + <EmbeddedResource Update="Text\Impl\Commanding\CommandingStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>CommandingStrings.Designer.cs</LastGenOutput> + <CustomToolNamespace>Microsoft.VisualStudio.Text.Commanding.Implementation</CustomToolNamespace> + </EmbeddedResource> </ItemGroup> <ItemGroup> @@ -102,6 +112,7 @@ <ItemGroup> <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> + <PackageReference Include="System.ValueTuple" Version="4.3.0" /> <PackageReference Include="Microsoft.VisualStudio.CoreUtility" Version="$(NuGetVersionEditor)" /> <PackageReference Include="Microsoft.VisualStudio.Language.StandardClassification" Version="$(NuGetVersionEditor)" /> <PackageReference Include="Microsoft.VisualStudio.Text.Data" Version="$(NuGetVersionEditor)" /> diff --git a/src/Microsoft.VisualStudio.Text.Implementation.nuspec b/src/Microsoft.VisualStudio.Text.Implementation.nuspec index 365a828..9301476 100644 --- a/src/Microsoft.VisualStudio.Text.Implementation.nuspec +++ b/src/Microsoft.VisualStudio.Text.Implementation.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> <metadata> <id>Microsoft.VisualStudio.Text.Implementation</id> - <version>15.0.7-pre</version> + <version>15.0.8-pre</version> <authors>Microsoft</authors> <owners>Microsoft</owners> <requireLicenseAcceptance>false</requireLicenseAcceptance> diff --git a/src/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs b/src/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs new file mode 100644 index 0000000..27e6e2a --- /dev/null +++ b/src/Text/Def/TextLogic/PatternMatching/IPatternMatcher.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Text.PatternMatching +{ + /// <summary> + /// Defines a pattern matcher that can compare a candidate string against a search pattern to identify relevance. <see cref="IPatternMatcherFactory"/> defines + /// the way to obtain an <see cref="IPatternMatcher"/> given a search pattern and options. + /// </summary> + public interface IPatternMatcher + { + /// <summary> + /// Determines if, and how well a candidate string matches a search pattern and a set of <see cref="PatternMatcherCreationOptions"/>. + /// </summary> + /// <param name="candidate">The string to evaluate for relevancy.</param> + /// <returns>A <see cref="PatternMatch"/> object describing how well the candidate matched the pattern. If no match is found, this returns <see langword="null"/> instead.</returns> + /// <remarks> + /// This pattern matcher uses the concepts of a 'Pattern' and a 'Candidate' to to differentiate between what the user types to search + /// and what the system compares against. The pattern and some <see cref="PatternMatcherCreationOptions"/> are specified in <see cref="IPatternMatcherFactory"/> in order to obtain an <see cref="IPatternMatcher"/>. + /// + /// The user can then call this method repeatedly with multiple candidates to filter out non-matches, and obtain sortable <see cref="PatternMatch"/> objects to help decide + /// what the user actually wanted. + /// + /// A few examples are useful here. Suppose the user obtains an IPatternMatcher using the following: + /// Pattern = "PatMat" + /// + /// The following calls to TryMatch could expect these results: + /// Candidate = "PatternMatcher" + /// Returns a match containing <see cref="PatternMatchKind.CamelCaseExact"/>. + /// + /// Candidate = "IPatternMatcher" + /// Returns a match containing <see cref="PatternMatchKind.CamelCaseSubstring"/> + /// + /// Candidate = "patmat" + /// Returns a match containing <see cref="PatternMatchKind.Exact"/>, but <see cref="PatternMatch.IsCaseSensitive"/> will be false. + /// + /// Candidate = "Not A Match" + /// Returns <see langword="null"/>. + /// + /// To determine sort order, call <see cref="PatternMatch.CompareTo(PatternMatch)"/>. + /// </remarks> + PatternMatch? TryMatch(string candidate); + } +} diff --git a/src/Text/Def/TextLogic/PatternMatching/IPatternMatcherFactory.cs b/src/Text/Def/TextLogic/PatternMatching/IPatternMatcherFactory.cs new file mode 100644 index 0000000..86857e9 --- /dev/null +++ b/src/Text/Def/TextLogic/PatternMatching/IPatternMatcherFactory.cs @@ -0,0 +1,45 @@ +namespace Microsoft.VisualStudio.Text.PatternMatching +{ + /// <summary> + /// Provides instances of a <see cref="IPatternMatcher"/> for a given + /// search string and creation options. + /// </summary> + /// <remarks>This is a MEF component part, and should be imported as follows: + /// [Import] + /// IPatternMatcherFactory factory = null; + /// </remarks> + public interface IPatternMatcherFactory + { + /// <summary> + /// Gets an <see cref="IPatternMatcher"/> given a search pattern and search options. + /// </summary> + /// <param name="pattern">Describes the search pattern that candidate strings will be compared against for relevancy.</param> + /// <param name="creationOptions">Defines parameters for what should be considered relevant in a match.</param> + /// <remarks> + /// This pattern matcher uses the concepts of a 'Pattern' and a 'Candidate' to to differentiate between what the user types to search + /// and what the system compares against. The pattern and some <see cref="PatternMatcherCreationOptions"/> are specified in here in order to obtain an <see cref="IPatternMatcher"/>. + /// + /// The user can then call <see cref="IPatternMatcher.TryMatch(string)"/> repeatedly with multiple candidates to filter out non-matches, and obtain sortable <see cref="PatternMatch"/> objects to help decide + /// what the user actually wanted. + /// + /// A few examples are useful here. Suppose the user obtains an IPatternMatcher using the following: + /// Pattern = "PatMat" + /// + /// The following calls to TryMatch could expect these results: + /// Candidate = "PatternMatcher" + /// Returns a match containing <see cref="PatternMatchKind.CamelCaseExact"/>. + /// + /// Candidate = "IPatternMatcher" + /// Returns a match containing <see cref="PatternMatchKind.CamelCaseSubstring"/> + /// + /// Candidate = "patmat" + /// Returns a match containing <see cref="PatternMatchKind.Exact"/>, but <see cref="PatternMatch.IsCaseSensitive"/> will be false. + /// + /// Candidate = "Not A Match" + /// Returns <see langword="null"/>. + /// + /// To determine sort order, call <see cref="PatternMatch.CompareTo(PatternMatch)"/>. + /// </remarks> + IPatternMatcher CreatePatternMatcher(string pattern, PatternMatcherCreationOptions creationOptions); + } +} diff --git a/src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs b/src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs new file mode 100644 index 0000000..dd907d0 --- /dev/null +++ b/src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Immutable; +using System.Linq; + +namespace Microsoft.VisualStudio.Text.PatternMatching +{ + public struct PatternMatch : IComparable<PatternMatch> + { + /// <summary> + /// True if this was a case sensitive match. + /// </summary> + public bool IsCaseSensitive { get; } + + /// <summary> + /// The type of match that occurred. + /// </summary> + public PatternMatchKind Kind { get; } + + /// <summary> + /// The spans in the original text that were matched. Only returned if the + /// pattern matcher is asked to collect these spans. + /// </summary> + public ImmutableArray<Span> MatchedSpans { get; } + + /// <summary> + /// True if punctuation was removed for this match. + /// </summary> + public bool IsPunctuationStripped { get; } + + /// <summary> + /// Creates a PatternMatch object with an optional single span. + /// </summary> + /// <param name="resultType">How is this match categorized?</param> + /// <param name="punctuationStripped">Was punctuation removed?</param> + /// <param name="isCaseSensitive">Was this a case sensitive match?</param> + public PatternMatch( + PatternMatchKind resultType, + bool punctuationStripped, + bool isCaseSensitive) + : this(resultType, punctuationStripped, isCaseSensitive, ImmutableArray<Span>.Empty) + { + } + + /// <summary> + /// Creates a PatternMatch object with a set of spans + /// </summary> + /// <param name="resultType">How is this match categorized?</param> + /// <param name="punctuationStripped">Was punctuation removed?</param> + /// <param name="isCaseSensitive">Was this a case sensitive match?</param> + /// <param name="matchedSpans">What spans of the candidate were matched? An empty array signifies no span information is given.</param> + public PatternMatch( + PatternMatchKind resultType, + bool punctuationStripped, + bool isCaseSensitive, + ImmutableArray<Span> matchedSpans) + : this() + { + this.Kind = resultType; + this.IsCaseSensitive = isCaseSensitive; + this.MatchedSpans = matchedSpans; + this.IsPunctuationStripped = punctuationStripped; + } + + /// <summary> + /// Get a PatternMatch object with additional spans added to it. This is an optimization to avoid having to call the whole constructor. + /// </summary> + /// <param name="matchedSpans">Spans to associate with this PatternMatch.</param> + /// <returns>A new instance of a PatternMatch with the specified spans.</returns> + public PatternMatch WithMatchedSpans(ImmutableArray<Span> matchedSpans) + => new PatternMatch(Kind, IsPunctuationStripped, IsCaseSensitive, matchedSpans); + + /// <summary> + /// Compares two PatternMatch objects. + /// </summary> + public int CompareTo(PatternMatch other) + => CompareTo(other, ignoreCase: false); + + /// <summary> + /// Compares two PatternMatch objects with the specified behavior for ignoring capitalization. + /// </summary> + /// <param name="ignoreCase">Should case be ignored?</param> + public int CompareTo(PatternMatch other, bool ignoreCase) + { + int diff; + if ((diff = CompareType(this, other)) != 0 || + (diff = CompareCase(this, other, ignoreCase)) != 0 || + (diff = ComparePunctuation(this, other)) != 0) + { + return diff; + } + + return 0; + } + + private static int ComparePunctuation(PatternMatch result1, PatternMatch result2) + { + // Consider a match to be better if it was successful without stripping punctuation + // versus a match that had to strip punctuation to succeed. + if (result1.IsPunctuationStripped != result2.IsPunctuationStripped) + { + return result1.IsPunctuationStripped ? 1 : -1; + } + + return 0; + } + + private static int CompareCase(PatternMatch result1, PatternMatch result2, bool ignoreCase) + { + if (!ignoreCase) + { + if (result1.IsCaseSensitive != result2.IsCaseSensitive) + { + return result1.IsCaseSensitive ? -1 : 1; + } + } + + return 0; + } + + private static int CompareType(PatternMatch result1, PatternMatch result2) + => result1.Kind.CompareTo(result2.Kind); + } +} diff --git a/src/Text/Def/TextLogic/PatternMatching/PatternMatchKind.cs b/src/Text/Def/TextLogic/PatternMatching/PatternMatchKind.cs new file mode 100644 index 0000000..88be244 --- /dev/null +++ b/src/Text/Def/TextLogic/PatternMatching/PatternMatchKind.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.Text.PatternMatching +{ + /// <summary> + /// This enum is NOT ordered. Sorting based on the PatternMatchKind is performed in <see cref="PatternMatch"/> and is subject to change. + /// Additional entries can be added at the bottom of this enumeration. + /// </summary> + public enum PatternMatchKind + { + /// <summary> + /// The candidate string matched the pattern exactly. + /// </summary> + Exact, + + /// <summary> + /// The pattern was a prefix of the candidate string. + /// </summary> + Prefix, + + /// <summary> + /// The pattern was a substring of the candidate string, but in a way that wasn't a CamelCase match. + /// </summary> + Substring, + + // Note: CamelCased matches are ordered from best to worst. + + /// <summary> + /// All camel-humps in the pattern matched a camel-hump in the candidate. All camel-humps + /// in the candidate were matched by a camel-hump in the pattern. + /// + /// Example: "CFPS" matching "CodeFixProviderService" + /// Example: "cfps" matching "CodeFixProviderService" + /// Example: "CoFiPrSe" matching "CodeFixProviderService" + /// </summary> + CamelCaseExact, + + /// <summary> + /// All camel-humps in the pattern matched a camel-hump in the candidate. The first camel-hump + /// in the pattern matched the first camel-hump in the candidate. There was no gap in the camel- + /// humps in the candidate that were matched. + /// + /// Example: "CFP" matching "CodeFixProviderService" + /// Example: "cfp" matching "CodeFixProviderService" + /// Example: "CoFiPRo" matching "CodeFixProviderService" + /// </summary> + CamelCasePrefix, + + /// <summary> + /// All camel-humps in the pattern matched a camel-hump in the candidate. The first camel-hump + /// in the pattern matched the first camel-hump in the candidate. There was at least one gap in + /// the camel-humps in the candidate that were matched. + /// + /// Example: "CP" matching "CodeFixProviderService" + /// Example: "cp" matching "CodeFixProviderService" + /// Example: "CoProv" matching "CodeFixProviderService" + /// </summary> + CamelCaseNonContiguousPrefix, + + /// <summary> + /// All camel-humps in the pattern matched a camel-hump in the candidate. The first camel-hump + /// in the pattern did not match the first camel-hump in the pattern. There was no gap in the camel- + /// humps in the candidate that were matched. + /// + /// Example: "FP" matching "CodeFixProviderService" + /// Example: "fp" matching "CodeFixProviderService" + /// Example: "FixPro" matching "CodeFixProviderService" + /// </summary> + CamelCaseSubstring, + + /// <summary> + /// All camel-humps in the pattern matched a camel-hump in the candidate. The first camel-hump + /// in the pattern did not match the first camel-hump in the pattern. There was at least one gap in + /// the camel-humps in the candidate that were matched. + /// + /// Example: "FS" matching "CodeFixProviderService" + /// Example: "fs" matching "CodeFixProviderService" + /// Example: "FixSer" matching "CodeFixProviderService" + /// </summary> + CamelCaseNonContiguousSubstring, + + /// <summary> + /// The pattern matches the candidate in a fuzzy manner. Fuzzy matching allows for + /// a certain amount of misspellings, missing words, etc. + /// </summary> + Fuzzy + } +} diff --git a/src/Text/Def/TextLogic/PatternMatching/PatternMatchKindExtensions.cs b/src/Text/Def/TextLogic/PatternMatching/PatternMatchKindExtensions.cs new file mode 100644 index 0000000..fda778f --- /dev/null +++ b/src/Text/Def/TextLogic/PatternMatching/PatternMatchKindExtensions.cs @@ -0,0 +1,16 @@ +namespace Microsoft.VisualStudio.Text.PatternMatching +{ + public static class PatternMatchKindExtensions + { + /// <summary> + /// Compares two <see cref="PatternMatchKind"/> values, suggesting which one is more likely to be what the user was searching for. + /// </summary> + /// <param name="kind1">Item to be compared.</param> + /// <param name="kind2">Item to be compared.</param> + /// <returns>A negative value means kind1 is preferable, positive means kind2 is preferable. Zero means they are equivalent.</returns> + public static int CompareTo(this PatternMatchKind kind1, PatternMatchKind kind2) + { + return kind1 - kind2; + } + } +} diff --git a/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationFlags.cs b/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationFlags.cs new file mode 100644 index 0000000..ad21476 --- /dev/null +++ b/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationFlags.cs @@ -0,0 +1,33 @@ +using System; + +namespace Microsoft.VisualStudio.Text.PatternMatching +{ + /// <summary> + /// Specifies flags that control optional behavior of the pattern matching. + /// </summary> + [Flags] + public enum PatternMatcherCreationFlags + { + /// <summary> + /// No options selected. + /// </summary> + None = 0, + + /// <summary> + /// Signifies that strings differing from the initial pattern by minor spelling changes should be considered a match. + /// </summary> + AllowFuzzyMatching = 1, + + /// <summary> + /// Signifies that spans indicating matched segments in candidate strings should be returned. + /// </summary> + IncludeMatchedSpans = 2, + + /// <summary> + /// Signifies that a case insensitive substring match, but not a prefix should be considered a match. + /// This covers the case of non camel case naming conventions, for example matching + /// 'afxsettingsstore.h' when user types 'store.h' + /// </summary> + AllowSimpleSubstringMatching = 4 + } +} diff --git a/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs b/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs new file mode 100644 index 0000000..5693a3f --- /dev/null +++ b/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Globalization; + +namespace Microsoft.VisualStudio.Text.PatternMatching +{ + /// <summary> + /// Defines context for what should be considered relevant in a pattern match. + /// </summary> + public sealed class PatternMatcherCreationOptions + { + /// <summary> + /// Used to tailor character comparisons to the correct culture. + /// </summary> + public readonly CultureInfo CultureInfo; + + /// <summary> + /// A set of biniary options, used to control options like case-sensitivity. + /// </summary> + public readonly PatternMatcherCreationFlags Flags; + + /// <summary> + /// Characters that should be considered as describing a container/contained boundary. When matching types, this can be the '.' character + /// e.g. Namespace.Class.Property, so that the search can tailor behavior to better match Property first, then Class, then Namespace. + /// This also can work with directory separators in filenames and any other logical container/contained pattern in candidate strings. + /// + /// <see langword="null"/> signifies no characters are container boundaries. + /// </summary> + public readonly IReadOnlyCollection<char> ContainerSplitCharacters; + + /// <summary> + /// Creates an instance of <see cref="PatternMatcherCreationOptions"/>. + /// </summary> + /// <param name="cultureInfo">Used to tailor character comparisons to the correct culture.</param> + /// <param name="flags">A set of biniary options, used to control options like case-sensitivity.</param> + /// <param name="containerSplitCharacters"> + /// Characters that should be considered as describing a container/contained boundary. When matching types, this can be the '.' character + /// e.g. Namespace.Class.Property, so that the search can tailor behavior to better match Property first, then Class, then Namespace. + /// This also can work with directory separators in filenames and any other logical container/contained pattern in candidate strings. + /// + /// <see langword="null"/> signifies no characters are container boundaries. + /// </param> + public PatternMatcherCreationOptions(CultureInfo cultureInfo, PatternMatcherCreationFlags flags, IReadOnlyCollection<char> containerSplitCharacters = null) + { + CultureInfo = cultureInfo; + Flags = flags; + ContainerSplitCharacters = containerSplitCharacters; + } + } +} diff --git a/src/Text/Def/TextLogic/TextMate/CommonEditorConstants.cs b/src/Text/Def/TextLogic/TextMate/CommonEditorConstants.cs new file mode 100644 index 0000000..d2eaf68 --- /dev/null +++ b/src/Text/Def/TextLogic/TextMate/CommonEditorConstants.cs @@ -0,0 +1,23 @@ +namespace Microsoft.VisualStudio.Editor +{ + /// <summary> + /// Constants for interacting with <see cref="ICommonEditorAssetService"/> and Common Editor languages. + /// </summary> + public static class CommonEditorConstants + { + /// <summary> + /// Name used to identify all Common Editor assets. + /// </summary> + public const string AssetName = "TextMate"; + + /// <summary> + /// Name of the content type under from which all TextMate based languages are derived. + /// </summary> + public const string ContentTypeName = "code++"; + + /// <summary> + /// Name of the registry key under which new repositories for TextMate grammars can be defined. + /// </summary> + public const string TextMateRepositoryKey = @"TextMate\Repositories"; + } +} diff --git a/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetMetadata.cs b/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetMetadata.cs new file mode 100644 index 0000000..e2da623 --- /dev/null +++ b/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetMetadata.cs @@ -0,0 +1,19 @@ +namespace Microsoft.VisualStudio.Editor +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using Microsoft.VisualStudio.Utilities; + + /// <summary> + /// Common Editor asset metadata. + /// </summary> + public interface ICommonEditorAssetMetadata : IOrderable + { + /// <summary> + /// The type of tags supported by the Common Editor asset. + /// </summary> + [DefaultValue(null)] + IEnumerable<Type> TagTypes { get; } + } +} diff --git a/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetService.cs b/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetService.cs new file mode 100644 index 0000000..11d0d96 --- /dev/null +++ b/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetService.cs @@ -0,0 +1,27 @@ +namespace Microsoft.VisualStudio.Editor +{ + using System; + + /// <summary> + /// Service produced by <see cref="ICommonEditorAssetServiceFactory"/> that provides common language service assets. + /// </summary> + /// <remarks>This class supports the Visual Studio + /// infrastructure and in general is not intended to be used directly from your code.</remarks> + public interface ICommonEditorAssetService + { + /// <summary> + /// Produces common language service asset. + /// </summary> + /// <typeparam name="T"> + /// The type of language service asset to produce. Can be ITaggerProvider, IViewTaggerProvider, + /// or ICompletionSource. Use <paramref name="isMatch"/> to find a tagger of the desired type. + /// </typeparam> + /// <param name="isMatch">Returns true if the <see cref="ICommonEditorAssetMetadata"/> matches the desired feature.</param> + /// <remarks> + /// This method supports the Visual Studio infrastructure and in + /// general is not intended to be used directly from your code. + /// </remarks> + /// <returns>A feature of <typeparamref name="T"/> or null if unknown.</returns> + T FindAsset<T>(Predicate<ICommonEditorAssetMetadata> isMatch = null) where T : class; + } +} diff --git a/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetServiceFactory.cs b/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetServiceFactory.cs new file mode 100644 index 0000000..36b618b --- /dev/null +++ b/src/Text/Def/TextLogic/TextMate/ICommonEditorAssetServiceFactory.cs @@ -0,0 +1,40 @@ +namespace Microsoft.VisualStudio.Editor +{ + using Microsoft.VisualStudio.Text; + + /// <summary> + /// Service for getting a service that provides common language service elements. + /// </summary> + /// <remarks>This class supports the Visual Studio + /// infrastructure and in general is not intended to be used directly from your code.</remarks> + /// <example> + /// This is a MEF component part. Use the code below in your MEF exported class to import an + /// instance of the service factory. + /// <code> + /// [Import] + /// private ICommonEditorAssetServiceFactory assetServiceFactory = null; + /// </code> + /// Then, you can use the code below to get the ITaggerProvider for the Common Editor's + /// IClassificationTagger. Modify as needed to get the desired asset. + /// <code> + /// var factory = this.assetServiceFactory.GetOrCreate(buffer); + /// var tagger = factory.FindAsset<ITaggerProvider>( + /// (metadata) => metadata.TagTypes.Any(tagType => typeof(IClassificationTagger).IsAssignableFrom(tagType))) + /// ?.CreateTagger<T>(buffer); + /// </code> + /// </example> + public interface ICommonEditorAssetServiceFactory + { + /// <summary> + /// Gets a service that provides common language service elements. + /// </summary> + /// </summary> + /// <param name="textBuffer">The <see cref="ITextBuffer"/> for which to initialize TextMate.</param> + /// <remarks> + /// This method supports the Visual Studio infrastructure and in + /// general is not intended to be used directly from your code. + /// </remarks> + /// <returns>An instance of <see cref="ITextMateAssetService"/>.</returns> + ICommonEditorAssetService GetOrCreate(ITextBuffer textBuffer); + } +} diff --git a/src/Text/Def/TextUI/Commanding/CommandArgs.cs b/src/Text/Def/TextUI/Commanding/CommandArgs.cs new file mode 100644 index 0000000..ea4b4e2 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/CommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Commanding +{ + /// <summary> + /// A base class for all command arguments. + /// </summary> + public abstract class CommandArgs + { + } +} diff --git a/src/Text/Def/TextUI/Commanding/CommandExecutionContext.cs b/src/Text/Def/TextUI/Commanding/CommandExecutionContext.cs new file mode 100644 index 0000000..f39cd55 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/CommandExecutionContext.cs @@ -0,0 +1,27 @@ +using System; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Commanding +{ + /// <summary> + /// Represents a command execution context, which is set up by a command handler service + /// and provided to each command handler. + /// </summary> + public sealed class CommandExecutionContext + { + /// <summary> + /// Creates new instance of the <see cref="CommandExecutionContext"/>. + /// </summary> + public CommandExecutionContext(IUIThreadOperationContext waitContext) + { + this.WaitContext = waitContext ?? throw new ArgumentNullException(nameof(waitContext)); + } + + /// <summary> + /// Provides a context of executing a command handler on the UI thread, which + /// enables two way shared cancellability and wait indication. + /// </summary> + public IUIThreadOperationContext WaitContext { get; } + } +} + diff --git a/src/Text/Def/TextUI/Commanding/CommandHandlerExtensions.cs b/src/Text/Def/TextUI/Commanding/CommandHandlerExtensions.cs new file mode 100644 index 0000000..a4afd58 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/CommandHandlerExtensions.cs @@ -0,0 +1,123 @@ +using System; + +namespace Microsoft.VisualStudio.Commanding +{ + /// <summary> + /// Contains command handler utility extension methods. + /// </summary> + public static class CommandHandlerExtensions + { + /// <summary> + /// Called to determine the state of the command. + /// </summary> + /// <remarks> + /// <para> + /// A command handler can implement <see cref="ICommandHandler{T}"/> or + /// <see cref="IChainedCommandHandler{T}"/>, but either way this method returns + /// the final state of the command as returned by either this or next + /// command handler. + /// </para> + /// <para>If <paramref name="commandHandler"/> implements <see cref="ICommandHandler{T}"/>, + /// its <see cref="ICommandHandler{T}.GetCommandState(T)"/> method is called. If it returns + /// <see cref="CommandState.Unspecified"/>, <paramref name="nextCommandHandler"/> is invoked. + ///</para> + ///<para> + /// If <paramref name="commandHandler"/> implements <see cref="IChainedCommandHandler{T}"/>, + /// its <see cref="IChainedCommandHandler{T}.GetCommandState(T, Func{CommandState})"/> method is invoked with + /// <paramref name="nextCommandHandler"/> passed as an argument. + ///</para> + /// </remarks> + /// <param name="args">The <see cref="CommandArgs"/> arguments for the command.</param> + /// <param name="nextCommandHandler">The next command handler in the command execution chain.</param> + /// <param name="commandHandler">A command handler to query the state of the command.</param> + /// <returns>A <see cref="CommandState"/> instance that contains information on the availability of the command.</returns> + public static CommandState GetCommandState<T>(this ICommandHandler commandHandler, T args, Func<CommandState> nextCommandHandler) where T : CommandArgs + { + if (commandHandler == null) + { + throw new ArgumentNullException(nameof(commandHandler)); + } + + if (nextCommandHandler == null) + { + throw new ArgumentNullException(nameof(nextCommandHandler)); + } + + if (commandHandler is ICommandHandler<T> simpleCommandHandler) + { + var commandState = simpleCommandHandler.GetCommandState(args); + if (commandState.IsUnspecified) + { + return nextCommandHandler(); + } + + return commandState; + } + + if (commandHandler is IChainedCommandHandler<T> chainedCommandHandler) + { + return chainedCommandHandler.GetCommandState(args, nextCommandHandler); + } + + throw new ArgumentException($"Unsupported CommandHandler type: {commandHandler.GetType()}"); + } + + /// <summary> + /// Called to execute the command. + /// </summary> + /// <remarks> + /// <para> + /// A command handler can implement <see cref="ICommandHandler{T}"/> or + /// <see cref="IChainedCommandHandler{T}"/>, but either way this method executes + /// the command by either this or next command handler. + /// </para> + /// <para>If <paramref name="commandHandler"/> implements <see cref="ICommandHandler{T}"/>, + /// its <see cref="ICommandHandler{T}.ExecuteCommand(T, CommandExecutionContext)"/> method is called. If it returns + /// <c>false</c>, <paramref name="nextCommandHandler"/> is invoked. + ///</para> + ///<para> + /// If <paramref name="commandHandler"/> implements <see cref="IChainedCommandHandler{T}"/>, + /// its <see cref="IChainedCommandHandler{T}.ExecuteCommand(T, Action, CommandExecutionContext)"/> method is invoked with + /// <paramref name="nextCommandHandler"/> passed as an argument. + ///</para> + /// </remarks> + /// <param name="args">The <see cref="CommandArgs"/> arguments for the command.</param> + /// <param name="nextCommandHandler">The next command handler in the command execution chain.</param> + /// <param name="commandHandler">A command handler to execute the command.</param> + /// <param name="executionContext">Current command execution context.</param> + public static void ExecuteCommand<T>(this ICommandHandler commandHandler, T args, Action nextCommandHandler, CommandExecutionContext executionContext) where T : CommandArgs + { + if (commandHandler == null) + { + throw new ArgumentNullException(nameof(commandHandler)); + } + + if (nextCommandHandler == null) + { + throw new ArgumentNullException(nameof(nextCommandHandler)); + } + + if (commandHandler is ICommandHandler<T> simpleCommandHandler) + { + if (simpleCommandHandler.ExecuteCommand(args, executionContext)) + { + return; + } + else + { + nextCommandHandler(); + return; + } + } + + if (commandHandler is IChainedCommandHandler<T> chainedCommandHandler) + { + chainedCommandHandler.ExecuteCommand(args, nextCommandHandler, executionContext); + return; + } + + throw new ArgumentException($"Unsupported CommandHandler type: {commandHandler.GetType()}"); + } + } +} + diff --git a/src/Text/Def/TextUI/Commanding/CommandState.cs b/src/Text/Def/TextUI/Commanding/CommandState.cs new file mode 100644 index 0000000..93680c1 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/CommandState.cs @@ -0,0 +1,55 @@ +using System; + +namespace Microsoft.VisualStudio.Commanding +{ + public struct CommandState + { + /// <summary> + /// If true, the command state is unspecified and should not be taken into account. + /// </summary> + public bool IsUnspecified { get; } + + /// <summary> + /// If true, the command should be visible and enabled in the UI. + /// </summary> + public bool IsAvailable { get; } + + /// <summary> + /// If true, the command should appear as checked (i.e. toggled) in the UI. + /// </summary> + public bool IsChecked { get; } + + /// <summary> + /// If specified, returns the custom text that should be displayed in the UI. + /// </summary> + public string DisplayText { get; } + + public CommandState(bool isAvailable = false, bool isChecked = false, string displayText = null, bool isUnspecified = false) + { + if (isUnspecified && (isAvailable || isChecked || displayText != null)) + { + throw new ArgumentException("Unspecified command state cannot be combined with other states or command text."); + } + + this.IsAvailable = isAvailable; + this.IsChecked = isChecked; + this.IsUnspecified = isUnspecified; + this.DisplayText = displayText; + } + + /// <summary> + /// A helper singleton representing an available command state. + /// </summary> + public static CommandState Available { get; } = new CommandState(isAvailable: true); + + /// <summary> + /// A helper singleton representing an unavailable command state. + /// </summary> + public static CommandState Unavailable { get; } = new CommandState(isAvailable: false); + + /// <summary> + /// A helper singleton representing an unspecified command state. + /// </summary> + public static CommandState Unspecified { get; } = new CommandState(isUnspecified: true); + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/AutomaticLineEnderCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/AutomaticLineEnderCommandArgs.cs new file mode 100644 index 0000000..dca0d0c --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/AutomaticLineEnderCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class AutomaticLineEnderCommandArgs : EditorCommandArgs + { + public AutomaticLineEnderCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/BackTabKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/BackTabKeyCommandArgs.cs new file mode 100644 index 0000000..eeea83d --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/BackTabKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class BackTabKeyCommandArgs : EditorCommandArgs + { + public BackTabKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/BackspaceKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/BackspaceKeyCommandArgs.cs new file mode 100644 index 0000000..5808047 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/BackspaceKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class BackspaceKeyCommandArgs : EditorCommandArgs + { + public BackspaceKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/CommentSelectionCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/CommentSelectionCommandArgs.cs new file mode 100644 index 0000000..5072200 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/CommentSelectionCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class CommentSelectionCommandArgs : EditorCommandArgs + { + public CommentSelectionCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/CommitUniqueCompletionListItemCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/CommitUniqueCompletionListItemCommandArgs.cs new file mode 100644 index 0000000..48c34f5 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/CommitUniqueCompletionListItemCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class CommitUniqueCompletionListItemCommandArgs : EditorCommandArgs + { + public CommitUniqueCompletionListItemCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ContractSelectionCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ContractSelectionCommandArgs.cs new file mode 100644 index 0000000..038e9e3 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ContractSelectionCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ContractSelectionCommandArgs: EditorCommandArgs + { + public ContractSelectionCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/CopyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/CopyCommandArgs.cs new file mode 100644 index 0000000..47037f1 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/CopyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class CopyCommandArgs : EditorCommandArgs + { + public CopyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/CopyToInteractiveCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/CopyToInteractiveCommandArgs.cs new file mode 100644 index 0000000..4351246 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/CopyToInteractiveCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class CopyToInteractiveCommandArgs : EditorCommandArgs + { + public CopyToInteractiveCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/CutCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/CutCommandArgs.cs new file mode 100644 index 0000000..fb60598 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/CutCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class CutCommandArgs : EditorCommandArgs + { + public CutCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/DeleteKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/DeleteKeyCommandArgs.cs new file mode 100644 index 0000000..434ab6f --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/DeleteKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class DeleteKeyCommandArgs : EditorCommandArgs + { + public DeleteKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/DocumentEndCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/DocumentEndCommandArgs.cs new file mode 100644 index 0000000..657e6ae --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/DocumentEndCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class DocumentEndCommandArgs : EditorCommandArgs + { + public DocumentEndCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/DocumentStartCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/DocumentStartCommandArgs.cs new file mode 100644 index 0000000..b8bc5d4 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/DocumentStartCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class DocumentStartCommandArgs : EditorCommandArgs + { + public DocumentStartCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/DownKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/DownKeyCommandArgs.cs new file mode 100644 index 0000000..7f2ed81 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/DownKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class DownKeyCommandArgs : EditorCommandArgs + { + public DownKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/DuplicateSelectionCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/DuplicateSelectionCommandArgs.cs new file mode 100644 index 0000000..12b6628 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/DuplicateSelectionCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class DuplicateSelectionCommandArgs: EditorCommandArgs + { + public DuplicateSelectionCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/EncapsulateFieldCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/EncapsulateFieldCommandArgs.cs new file mode 100644 index 0000000..13786b1 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/EncapsulateFieldCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class EncapsulateFieldCommandArgs : EditorCommandArgs + { + public EncapsulateFieldCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/EscapeKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/EscapeKeyCommandArgs.cs new file mode 100644 index 0000000..7f7e30c --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/EscapeKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class EscapeKeyCommandArgs : EditorCommandArgs + { + public EscapeKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ExecuteInInteractiveCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ExecuteInInteractiveCommandArgs.cs new file mode 100644 index 0000000..8ea34f7 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ExecuteInInteractiveCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ExecuteInInteractiveCommandArgs : EditorCommandArgs + { + public ExecuteInInteractiveCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ExpandSelectionCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ExpandSelectionCommandArgs.cs new file mode 100644 index 0000000..7526f19 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ExpandSelectionCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ExpandSelectionCommandArgs: EditorCommandArgs + { + public ExpandSelectionCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ExtractInterfaceCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ExtractInterfaceCommandArgs.cs new file mode 100644 index 0000000..0c48f8f --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ExtractInterfaceCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ExtractInterfaceCommandArgs : EditorCommandArgs + { + public ExtractInterfaceCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ExtractMethodCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ExtractMethodCommandArgs.cs new file mode 100644 index 0000000..3e8694f --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ExtractMethodCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ExtractMethodCommandArgs : EditorCommandArgs + { + public ExtractMethodCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/FindReferencesCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/FindReferencesCommandArgs.cs new file mode 100644 index 0000000..32aadd1 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/FindReferencesCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class FindReferencesCommandArgs : EditorCommandArgs + { + public FindReferencesCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/FormatDocumentCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/FormatDocumentCommandArgs.cs new file mode 100644 index 0000000..7ba8eeb --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/FormatDocumentCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class FormatDocumentCommandArgs : EditorCommandArgs + { + public FormatDocumentCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/FormatSelectionCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/FormatSelectionCommandArgs.cs new file mode 100644 index 0000000..57f821e --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/FormatSelectionCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class FormatSelectionCommandArgs : EditorCommandArgs + { + public FormatSelectionCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/GoToDefinitionCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/GoToDefinitionCommandArgs.cs new file mode 100644 index 0000000..e5df4e9 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/GoToDefinitionCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class GoToDefinitionCommandArgs : EditorCommandArgs + { + public GoToDefinitionCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/GoToNextMemberCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/GoToNextMemberCommandArgs.cs new file mode 100644 index 0000000..8f74c77 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/GoToNextMemberCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class GoToNextMemberCommandArgs : EditorCommandArgs + { + public GoToNextMemberCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/GoToPreviousMemberCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/GoToPreviousMemberCommandArgs.cs new file mode 100644 index 0000000..af8d3c8 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/GoToPreviousMemberCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class GoToPreviousMemberCommandArgs : EditorCommandArgs + { + public GoToPreviousMemberCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/InsertCommentCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/InsertCommentCommandArgs.cs new file mode 100644 index 0000000..e7e3bd3 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/InsertCommentCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class InsertCommentCommandArgs : EditorCommandArgs + { + public InsertCommentCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/InsertSnippetCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/InsertSnippetCommandArgs.cs new file mode 100644 index 0000000..cba1f12 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/InsertSnippetCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class InsertSnippetCommandArgs : EditorCommandArgs + { + public InsertSnippetCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/InvokeCompletionListCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/InvokeCompletionListCommandArgs.cs new file mode 100644 index 0000000..8cd8bc3 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/InvokeCompletionListCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class InvokeCompletionListCommandArgs : EditorCommandArgs + { + public InvokeCompletionListCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/InvokeQuickInfoCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/InvokeQuickInfoCommandArgs.cs new file mode 100644 index 0000000..4e317b5 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/InvokeQuickInfoCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class InvokeQuickInfoCommandArgs : EditorCommandArgs + { + public InvokeQuickInfoCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/InvokeSignatureHelpCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/InvokeSignatureHelpCommandArgs.cs new file mode 100644 index 0000000..09a2b52 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/InvokeSignatureHelpCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class InvokeSignatureHelpCommandArgs : EditorCommandArgs + { + public InvokeSignatureHelpCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/LeftKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/LeftKeyCommandArgs.cs new file mode 100644 index 0000000..5bdc8b7 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/LeftKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class LeftKeyCommandArgs : EditorCommandArgs + { + public LeftKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/LineEndCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/LineEndCommandArgs.cs new file mode 100644 index 0000000..320c096 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/LineEndCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class LineEndCommandArgs : EditorCommandArgs + { + public LineEndCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/LineEndExtendCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/LineEndExtendCommandArgs.cs new file mode 100644 index 0000000..ab66007 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/LineEndExtendCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class LineEndExtendCommandArgs : EditorCommandArgs + { + public LineEndExtendCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/LineStartCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/LineStartCommandArgs.cs new file mode 100644 index 0000000..decb21d --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/LineStartCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class LineStartCommandArgs : EditorCommandArgs + { + public LineStartCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/LineStartExtendCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/LineStartExtendCommandArgs.cs new file mode 100644 index 0000000..1282c85 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/LineStartExtendCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class LineStartExtendCommandArgs : EditorCommandArgs + { + public LineStartExtendCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/MoveSelectedLinesDownCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/MoveSelectedLinesDownCommandArgs.cs new file mode 100644 index 0000000..2c3d701 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/MoveSelectedLinesDownCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class MoveSelectedLinesDownCommandArgs : EditorCommandArgs + { + public MoveSelectedLinesDownCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/MoveSelectedLinesUpCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/MoveSelectedLinesUpCommandArgs.cs new file mode 100644 index 0000000..c92378a --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/MoveSelectedLinesUpCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class MoveSelectedLinesUpCommandArgs : EditorCommandArgs + { + public MoveSelectedLinesUpCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/NavigateToNextHighlightedReferenceCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/NavigateToNextHighlightedReferenceCommandArgs.cs new file mode 100644 index 0000000..d8353b9 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/NavigateToNextHighlightedReferenceCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class NavigateToNextHighlightedReferenceCommandArgs : EditorCommandArgs + { + public NavigateToNextHighlightedReferenceCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/NavigateToPreviousHighlightedReferenceCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/NavigateToPreviousHighlightedReferenceCommandArgs.cs new file mode 100644 index 0000000..86cbf14 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/NavigateToPreviousHighlightedReferenceCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class NavigateToPreviousHighlightedReferenceCommandArgs : EditorCommandArgs + { + public NavigateToPreviousHighlightedReferenceCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/OpenLineAboveCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/OpenLineAboveCommandArgs.cs new file mode 100644 index 0000000..5a2f736 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/OpenLineAboveCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class OpenLineAboveCommandArgs : EditorCommandArgs + { + public OpenLineAboveCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/OpenLineBelowCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/OpenLineBelowCommandArgs.cs new file mode 100644 index 0000000..808bbac --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/OpenLineBelowCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class OpenLineBelowCommandArgs : EditorCommandArgs + { + public OpenLineBelowCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/PageDownKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/PageDownKeyCommandArgs.cs new file mode 100644 index 0000000..f54a5df --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/PageDownKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class PageDownKeyCommandArgs : EditorCommandArgs + { + public PageDownKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/PageUpKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/PageUpKeyCommandArgs.cs new file mode 100644 index 0000000..0887797 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/PageUpKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class PageUpKeyCommandArgs : EditorCommandArgs + { + public PageUpKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/PasteCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/PasteCommandArgs.cs new file mode 100644 index 0000000..d307787 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/PasteCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class PasteCommandArgs : EditorCommandArgs + { + public PasteCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/RedoCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/RedoCommandArgs.cs new file mode 100644 index 0000000..5e606b8 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/RedoCommandArgs.cs @@ -0,0 +1,12 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class RedoCommandArgs : EditorCommandArgs + { + public readonly int Count; + + public RedoCommandArgs(ITextView textView, ITextBuffer subjectBuffer, int count = 1) : base(textView, subjectBuffer) + { + this.Count = count; + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/RemoveParametersCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/RemoveParametersCommandArgs.cs new file mode 100644 index 0000000..d34e1ae --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/RemoveParametersCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class RemoveParametersCommandArgs : EditorCommandArgs + { + public RemoveParametersCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/RenameCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/RenameCommandArgs.cs new file mode 100644 index 0000000..6439a59 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/RenameCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class RenameCommandArgs : EditorCommandArgs + { + public RenameCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ReorderParametersCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ReorderParametersCommandArgs.cs new file mode 100644 index 0000000..b582981 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ReorderParametersCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ReorderParametersCommandArgs : EditorCommandArgs + { + public ReorderParametersCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ReturnKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ReturnKeyCommandArgs.cs new file mode 100644 index 0000000..d2744ce --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ReturnKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ReturnKeyCommandArgs : EditorCommandArgs + { + public ReturnKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/RightKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/RightKeyCommandArgs.cs new file mode 100644 index 0000000..45b2b8c --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/RightKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class RightKeyCommandArgs : EditorCommandArgs + { + public RightKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/SaveCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/SaveCommandArgs.cs new file mode 100644 index 0000000..1a45c29 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/SaveCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class SaveCommandArgs : EditorCommandArgs + { + public SaveCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/SelectAllCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/SelectAllCommandArgs.cs new file mode 100644 index 0000000..829b978 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/SelectAllCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class SelectAllCommandArgs : EditorCommandArgs + { + public SelectAllCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/StartAutomaticOutliningCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/StartAutomaticOutliningCommandArgs.cs new file mode 100644 index 0000000..ad097e5 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/StartAutomaticOutliningCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class StartAutomaticOutliningCommandArgs : EditorCommandArgs + { + public StartAutomaticOutliningCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/SurroundWithCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/SurroundWithCommandArgs.cs new file mode 100644 index 0000000..844451e --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/SurroundWithCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class SurroundWithCommandArgs : EditorCommandArgs + { + public SurroundWithCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/SyncClassViewCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/SyncClassViewCommandArgs.cs new file mode 100644 index 0000000..3ecd832 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/SyncClassViewCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class SyncClassViewCommandArgs : EditorCommandArgs + { + public SyncClassViewCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/TabKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/TabKeyCommandArgs.cs new file mode 100644 index 0000000..24a9c25 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/TabKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class TabKeyCommandArgs : EditorCommandArgs + { + public TabKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ToggleCompletionModeCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ToggleCompletionModeCommandArgs.cs new file mode 100644 index 0000000..ddbfa6b --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ToggleCompletionModeCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ToggleCompletionModeCommandArgs : EditorCommandArgs + { + public ToggleCompletionModeCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/TypeCharCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/TypeCharCommandArgs.cs new file mode 100644 index 0000000..9ff72dc --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/TypeCharCommandArgs.cs @@ -0,0 +1,11 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class TypeCharCommandArgs : EditorCommandArgs + { + public char TypedChar { get; } + public TypeCharCommandArgs(ITextView textView, ITextBuffer subjectBuffer, char typedChar) : base(textView, subjectBuffer) + { + TypedChar = typedChar; + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/UncommentSelectionCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/UncommentSelectionCommandArgs.cs new file mode 100644 index 0000000..9c46e39 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/UncommentSelectionCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class UncommentSelectionCommandArgs : EditorCommandArgs + { + public UncommentSelectionCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/UndoCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/UndoCommandArgs.cs new file mode 100644 index 0000000..ce0df8b --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/UndoCommandArgs.cs @@ -0,0 +1,12 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class UndoCommandArgs : EditorCommandArgs + { + public readonly int Count; + + public UndoCommandArgs(ITextView textView, ITextBuffer subjectBuffer, int count = 1) : base(textView, subjectBuffer) + { + this.Count = count; + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/UpKeyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/UpKeyCommandArgs.cs new file mode 100644 index 0000000..29b6bb2 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/UpKeyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class UpKeyCommandArgs : EditorCommandArgs + { + public UpKeyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/ViewCallHierarchyCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/ViewCallHierarchyCommandArgs.cs new file mode 100644 index 0000000..3eea996 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/ViewCallHierarchyCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class ViewCallHierarchyCommandArgs : EditorCommandArgs + { + public ViewCallHierarchyCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/WordDeleteToEndCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/WordDeleteToEndCommandArgs.cs new file mode 100644 index 0000000..eeeddc5 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/WordDeleteToEndCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class WordDeleteToEndCommandArgs : EditorCommandArgs + { + public WordDeleteToEndCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/Commands/WordDeleteToStartCommandArgs.cs b/src/Text/Def/TextUI/Commanding/Commands/WordDeleteToStartCommandArgs.cs new file mode 100644 index 0000000..5d8c532 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/Commands/WordDeleteToStartCommandArgs.cs @@ -0,0 +1,9 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding.Commands +{ + public sealed class WordDeleteToStartCommandArgs : EditorCommandArgs + { + public WordDeleteToStartCommandArgs(ITextView textView, ITextBuffer subjectBuffer) : base(textView, subjectBuffer) + { + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/EditorCommandArgs.cs b/src/Text/Def/TextUI/Commanding/EditorCommandArgs.cs new file mode 100644 index 0000000..deaf9cb --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/EditorCommandArgs.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.VisualStudio.Commanding; + +namespace Microsoft.VisualStudio.Text.Editor.Commanding +{ + /// <summary> + /// A base class for all editor command arguments. + /// </summary> + public abstract class EditorCommandArgs : CommandArgs + { + /// <summary> + /// A subject buffer to execute a command on. + /// </summary> + public ITextBuffer SubjectBuffer { get; } + + /// <summary> + /// An <see cref="ITextView"/> to execute a command on. + /// </summary> + public ITextView TextView { get; } + + /// <summary> + /// Creates new instance of the <see cref="EditorCommandArgs"/> with given + /// <see cref="ITextView"/> and <see cref="ITextBuffer"/>. + /// </summary> + /// <param name="textView">A <see cref="ITextView"/> to execute a command on.</param> + /// <param name="subjectBuffer">A <see cref="ITextBuffer"/> to execute command on.</param> + public EditorCommandArgs(ITextView textView, ITextBuffer subjectBuffer) + { + this.TextView = textView ?? throw new ArgumentNullException(nameof(textView)); + this.SubjectBuffer = subjectBuffer ?? throw new ArgumentNullException(nameof(subjectBuffer)); + } + } +} diff --git a/src/Text/Def/TextUI/Commanding/IChainedCommandHandler.cs b/src/Text/Def/TextUI/Commanding/IChainedCommandHandler.cs new file mode 100644 index 0000000..7dab7e3 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/IChainedCommandHandler.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Commanding +{ + /// <summary> + /// Represents a command handler that depends on behavior of following command handlers in the command execution chain + /// formed from same strongly-typed <see cref="ICommandHandler"/>s ordered according to their [Order] attributes. + /// </summary> + /// <remarks> + /// This is a MEF component part and should be exported as the non-generic <see cref="ICommandHandler"/> with required + /// [Name], [ContentType] attributes and optional [Order] and [TextViewRole] attributes. + /// </remarks> + /// <example> + /// [Export(typeof(ICommandHandler))] + /// [Name(nameof(MyCommandHandler))] + /// [ContentType("text")] + /// [Order(Before ="OtherCommandHandler")] + /// [TextViewRole(PredefinedTextViewRoles.Editable)] + /// internal class MyCommandHandler : IChainedCommandHandler<MyCommandArgs> + /// </example> + public interface IChainedCommandHandler<T> : ICommandHandler, INamed where T : CommandArgs + { + /// <summary> + /// Called to determine the state of the command. + /// </summary> + /// <param name="args">The <see cref="CommandArgs"/> arguments for the command.</param> + /// <param name="nextCommandHandler">The next command handler in the command execution chain.</param> + /// <returns>A <see cref="CommandState"/> instance that contains information on the availability of the command.</returns> + CommandState GetCommandState(T args, Func<CommandState> nextCommandHandler); + + /// <summary> + /// Called to execute the command. + /// </summary> + /// <param name="args">The <see cref="CommandArgs"/> arguments for the command.</param> + /// <param name="nextCommandHandler">The next command handler in the command execution chain.</param> + void ExecuteCommand(T args, Action nextCommandHandler, CommandExecutionContext executionContext); + } +} diff --git a/src/Text/Def/TextUI/Commanding/ICommandHandler.cs b/src/Text/Def/TextUI/Commanding/ICommandHandler.cs new file mode 100644 index 0000000..c999be2 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/ICommandHandler.cs @@ -0,0 +1,56 @@ +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Commanding +{ + /// <summary> + /// This interface marks a class that implements at least one strongly-typed + /// <see cref="ICommandHandler{T}"/> or <see cref="IChainedCommandHandler{T}"/>. + /// </summary> + /// <remarks> + /// This is a MEF component part and should be exported as the non-generic <see cref="ICommandHandler"/> with required + /// [Name], [ContentType] attributes and optional [Order] and [TextViewRole] attributes. + /// </remarks> + /// <example> + /// [Export(typeof(ICommandHandler))] + /// [Name(nameof(MyCommandHandler))] + /// [ContentType("text")] + /// [Order(Before ="OtherCommandHandler")] + /// [TextViewRole(PredefinedTextViewRoles.Editable)] + /// internal class MyCommandHandler : ICommandHandler<MyCommandArgs> + /// </example> + public interface ICommandHandler + { + } + + /// <summary> + /// Represents a handler for a command associated with specific <see cref="CommandArgs"/>. + /// </summary> + /// <remarks> + /// This is a MEF component part and should be exported as the non-generic <see cref="ICommandHandler"/> with required + /// [Name], [ContentType] attributes and optional [Order] and [TextViewRole] attributes. + /// </remarks> + /// <example> + /// [Export(typeof(ICommandHandler))] + /// [Name(nameof(MyCommandHandler))] + /// [ContentType("text")] + /// [Order(Before ="OtherCommandHandler")] + /// [TextViewRole(PredefinedTextViewRoles.Editable)] + /// internal class MyCommandHandler : ICommandHandler<MyCommandArgs> + /// </example> + public interface ICommandHandler<T> : ICommandHandler, INamed where T : CommandArgs + { + /// <summary> + /// Called to determine the state of the command. + /// </summary> + /// <param name="args">The <see cref="CommandArgs"/> arguments for the command.</param> + /// <returns>A <see cref="CommandState"/> instance that contains information on the availability of the command.</returns> + CommandState GetCommandState(T args); + + /// <summary> + /// Called to execute the command. + /// </summary> + /// <param name="args">The <see cref="CommandArgs"/> arguments for the command.</param> + /// <returns>Returns <c>true</c> if the command was handled, <c>false</c> otherwise.</returns> + bool ExecuteCommand(T args, CommandExecutionContext executionContext); + } +} diff --git a/src/Text/Def/TextUI/Commanding/ICommandingTextBufferResolver.cs b/src/Text/Def/TextUI/Commanding/ICommandingTextBufferResolver.cs new file mode 100644 index 0000000..4cf5a12 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/ICommandingTextBufferResolver.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Microsoft.VisualStudio.Text.Editor.Commanding +{ + /// <summary> + /// Given a text view and a command type, resolves a list of text buffers on which a command should be executed. + /// Default implementation of this service returns a list of buffers in the text view which can be mapped + /// to the caret position. Other implementations might take into acount text selection in addition to the caret position, + /// for example when executing Format Document command in a projection scenario. + /// </summary> + public interface ICommandingTextBufferResolver + { + /// <summary> + /// Given a command type, resolves a list of text buffers on which a command should be executed. + /// </summary> + /// <typeparam name="TArgs">Command type.</typeparam> + /// <returns>A list of text buffers on which a command should be executed.</returns> + IEnumerable<ITextBuffer> ResolveBuffersForCommand<TArgs>() where TArgs : EditorCommandArgs; + } +} diff --git a/src/Text/Def/TextUI/Commanding/ICommandingTextBufferResolverProvider.cs b/src/Text/Def/TextUI/Commanding/ICommandingTextBufferResolverProvider.cs new file mode 100644 index 0000000..eef5914 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/ICommandingTextBufferResolverProvider.cs @@ -0,0 +1,23 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding +{ + /// <summary> + /// Provides a <see cref="ICommandingTextBufferResolver"/> for a given + /// <see cref="ITextView"/> and content type. + /// </summary> + /// <remarks>This is a MEF component and should be exported as + /// + /// Export(typeof(ICommandingTextBufferResolverProvider))] + /// [ContentType("MyContentType")] + /// internal class MyBufferResolverProvider : ICommandingTextBufferResolverProvider + /// </remarks> + public interface ICommandingTextBufferResolverProvider + { + /// <summary> + /// Creates a <see cref="ICommandingTextBufferResolver"/> for a given + /// <see cref="ITextView"/>. + /// </summary> + /// <param name="textView">A <see cref="ITextView"/> to create a text buffer resolver for.</param> + /// <returns>A new instance of <see cref="ICommandingTextBufferResolver"/>.</returns> + ICommandingTextBufferResolver CreateResolver(ITextView textView); + } +} diff --git a/src/Text/Def/TextUI/Commanding/IEditorCommandHandlerService.cs b/src/Text/Def/TextUI/Commanding/IEditorCommandHandlerService.cs new file mode 100644 index 0000000..2516bd7 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/IEditorCommandHandlerService.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.Commanding; +using System; + +namespace Microsoft.VisualStudio.Text.Editor.Commanding +{ + /// <summary> + /// A service to execute commands on a text view. + /// </summary> + /// <remarks> + /// Instance of this service are created by <see cref="IEditorCommandHandlerServiceFactory"/>. + /// </remarks> + public interface IEditorCommandHandlerService + { + /// <summary> + /// Get the <see cref="CommandState"/> for command handlers of a given command. + /// </summary> + /// <param name="argsFactory">A factory of <see cref="EditorCommandArgs"/> that specifies what kind of command is being queried.</param> + /// <param name="nextCommandHandler">A next command handler to be called if no command handlers were + /// able to determine a command state.</param> + /// <typeparam name="T">Tehe </typeparam> + /// <returns>The command state of a given command.</returns> + CommandState GetCommandState<T>(Func<ITextView, ITextBuffer, T> argsFactory, Func<CommandState> nextCommandHandler) where T : EditorCommandArgs; + + /// <summary> + /// Execute a given command on the <see cref="ITextView"/> associated with this <see cref="IEditorCommandHandlerService"/> instance. + /// </summary> + /// <param name="argsFactory">A factory of <see cref="EditorCommandArgs"/> that specifies what kind of command is being executed. + /// <paramref name="nextCommandHandler">>A next command handler to be called if no command handlers were + /// able to handle a command.</paramref> + void Execute<T>(Func<ITextView, ITextBuffer, T> argsFactory, Action nextCommandHandler) where T : EditorCommandArgs; + } +} diff --git a/src/Text/Def/TextUI/Commanding/IEditorCommandHandlerServiceFactory.cs b/src/Text/Def/TextUI/Commanding/IEditorCommandHandlerServiceFactory.cs new file mode 100644 index 0000000..226deb2 --- /dev/null +++ b/src/Text/Def/TextUI/Commanding/IEditorCommandHandlerServiceFactory.cs @@ -0,0 +1,27 @@ +namespace Microsoft.VisualStudio.Text.Editor.Commanding +{ + /// <summary> + /// A factory producing <see cref="IEditorCommandHandlerService"/> used to execute commands in a given text view. + /// </summary> + /// <remarks> + /// This is a MEF component and should be imported as + /// + /// [Import] + /// private IEditorCommandHandlerServiceFactory factory; + /// </remarks> + public interface IEditorCommandHandlerServiceFactory + { + /// <summary> + /// Gets or creates a <see cref="IEditorCommandHandlerService"/> for a given <see cref="ITextView"/>. + /// </summary> + /// <param name="textView">A text view to get or create <see cref="IEditorCommandHandlerService"/> for.</param> + IEditorCommandHandlerService GetService(ITextView textView); + + /// <summary> + /// Gets or creates a <see cref="IEditorCommandHandlerService"/> for a given <see cref="ITextView"/> and <see cref="ITextBuffer"/>. + /// </summary> + /// <param name="textView">A text view to get or create <see cref="IEditorCommandHandlerService"/> for.</param> + /// <param name="subjectBuffer">A text buffer to get or create <see cref="IEditorCommandHandlerService"/> for.</param> + IEditorCommandHandlerService GetService(ITextView textView, ITextBuffer subjectBuffer); + } +} diff --git a/src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs b/src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs new file mode 100644 index 0000000..c7d8279 --- /dev/null +++ b/src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Abstract base implementation of the <see cref="IUIThreadOperationContext"/> interface. + /// </summary> + public abstract class AbstractUIThreadOperationContext : IUIThreadOperationContext + { + private List<IUIThreadOperationScope> _scopes; + private bool _allowCancellation; + private PropertyCollection _properties; + private readonly string _contextDescription; + private int _completedItems; + private int _totalItems; + + /// <summary> + /// Creates a new instance of the <see cref="AbstractUIThreadOperationContext"/>. + /// </summary> + /// <param name="allowCancellation">Initial value of the <see cref="IUIThreadOperationContext.AllowCancellation"/> + /// property, which can change as new scopes are added to the context.</param> + /// <param name="description">Initial value of the <see cref="IUIThreadOperationContext.Description"/> + /// property, which can change as new scopes are added to the context.</param> + public AbstractUIThreadOperationContext(bool allowCancellation, string description) + { + _contextDescription = description ?? throw new ArgumentNullException(nameof(description)); + _allowCancellation = allowCancellation; + } + + /// <summary> + /// Cancellation token for cancelling the operation. + /// </summary> + public virtual CancellationToken UserCancellationToken => CancellationToken.None; + + /// <summary> + /// Gets whether the operation can be cancelled. + /// </summary> + /// <remarks>This value is composed of initial AllowCancellation value and + /// <see cref="IUIThreadOperationScope.AllowCancellation"/> values of all currently added scopes. + /// The value composition logic takes into acount disposed scopes too - if any of added scopes + /// were disposed while its <see cref="IUIThreadOperationScope.AllowCancellation"/> was false, + /// this property will stay false regardless of all other scopes' <see cref="IUIThreadOperationScope.AllowCancellation"/> + /// values. + /// </remarks> + public virtual bool AllowCancellation + { + get + { + if (!_allowCancellation) + { + return false; + } + + if (_scopes == null || _scopes.Count == 0) + { + return _allowCancellation; + } + + return _scopes.All((s) => s.AllowCancellation); + } + } + + /// <summary> + /// Gets user readable operation description, composed of initial context description and + /// descriptions of all currently added scopes. + /// </summary> + public virtual string Description + { + get + { + if (_scopes == null || _scopes.Count == 0) + { + return _contextDescription; + } + + // Combine context description with descriptions of all current scopes + return _contextDescription + Environment.NewLine + string.Join(Environment.NewLine, _scopes.Select((s) => s.Description)); + } + } + + protected int CompletedItems => _completedItems; + protected int TotalItems => _totalItems; + + private IList<IUIThreadOperationScope> LazyScopes => _scopes ?? (_scopes = new List<IUIThreadOperationScope>()); + + /// <summary> + /// Gets current list of <see cref="IUIThreadOperationScope"/>s in this context. + /// </summary> + public virtual IEnumerable<IUIThreadOperationScope> Scopes => this.LazyScopes; + + /// <summary> + /// A collection of properties. + /// </summary> + public virtual PropertyCollection Properties => _properties ?? (_properties = new PropertyCollection()); + + /// <summary> + /// Adds an UI thread operation scope with its own cancellability, description and progress tracker. + /// The scope is removed from the context on dispose. + /// </summary> + public virtual IUIThreadOperationScope AddScope(bool allowCancellation, string description) + { + var scope = new UIThreadOperationScope(allowCancellation, description, this); + this.LazyScopes.Add(scope); + this.OnScopesChanged(); + return scope; + } + + protected virtual void OnScopeProgressChanged(IUIThreadOperationScope changedScope) + { + int completed = 0; + int total = 0; + foreach (UIThreadOperationScope scope in this.LazyScopes) + { + completed += scope.CompletedItems; + total += scope.TotalItems; + } + + Interlocked.Exchange(ref _completedItems, completed); + Interlocked.Exchange(ref _totalItems, total); + } + + /// <summary> + /// Invoked when new <see cref="IUIThreadOperationScope"/>s are added or disposed. + /// </summary> + protected virtual void OnScopesChanged() { } + + protected virtual void OnScopeChanged(IUIThreadOperationScope uiThreadOperationScope) + { + } + + /// <summary> + /// Disposes this instance. + /// </summary> + public virtual void Dispose() + { + } + + /// <summary> + /// Allows a component to take full ownership over this UI thread operation, for example + /// when it shows its own modal UI dialog and handles cancellability through that dialog instead. + /// </summary> + public virtual void TakeOwnership() + { + } + + protected virtual void OnScopeDisposed(IUIThreadOperationScope scope) + { + _allowCancellation &= scope.AllowCancellation; + _scopes.Remove(scope); + OnScopesChanged(); + } + + private class UIThreadOperationScope : IUIThreadOperationScope + { + private bool _allowCancellation; + private string _description; + private IProgress<ProgressInfo> _progress; + private readonly AbstractUIThreadOperationContext _context; + private int _completedItems; + private int _totalItems; + + public UIThreadOperationScope(bool allowCancellation, string description, AbstractUIThreadOperationContext context) + { + _context = context ?? throw new ArgumentNullException(nameof(context)); + this.AllowCancellation = allowCancellation; + this.Description = description ?? ""; + } + + public bool AllowCancellation + { + get { return _allowCancellation; } + set + { + if (_allowCancellation != value) + { + _allowCancellation = value; + _context.OnScopeChanged(this); + } + } + } + + public string Description + { + get { return _description; } + set + { + if (_description != value) + { + _description = value; + _context.OnScopeChanged(this); + } + } + } + + public IUIThreadOperationContext Context => _context; + + public IProgress<ProgressInfo> Progress => _progress ?? (_progress = new Progress<ProgressInfo>((progressInfo) => OnProgressChanged(progressInfo))); + + public int CompletedItems => _completedItems; + + public int TotalItems => _totalItems; + + private void OnProgressChanged(ProgressInfo progressInfo) + { + Interlocked.Exchange(ref _completedItems, progressInfo.CompletedItems); + Interlocked.Exchange(ref _totalItems, progressInfo.TotalItems); + _context.OnScopeProgressChanged(this); + } + + public void Dispose() + { + _context.OnScopeDisposed(this); + } + } + } +} diff --git a/src/Text/Def/TextUI/Utilities/IUIThreadOperationContext.cs b/src/Text/Def/TextUI/Utilities/IUIThreadOperationContext.cs new file mode 100644 index 0000000..fce50d4 --- /dev/null +++ b/src/Text/Def/TextUI/Utilities/IUIThreadOperationContext.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Represents a context of executing potentially long running operation on the UI thread, which + /// enables shared two way cancellability and wait indication. + /// </summary> + /// <remarks> + /// Instances implementing this interface are produced by <see cref="IUIThreadOperationExecutor"/> + /// MEF component. + /// </remarks> + public interface IUIThreadOperationContext : IPropertyOwner, IDisposable + { + /// <summary> + /// Cancellation token that allows user to cancel the operation unless the operation + /// is not cancellable. + /// </summary> + CancellationToken UserCancellationToken { get; } + + /// <summary> + /// Gets whether the operation can be cancelled. + /// </summary> + /// <remarks>This value is composed of initial AllowCancellation value and + /// <see cref="IUIThreadOperationScope.AllowCancellation"/> values of all currently added scopes. + /// The value composition logic takes into acount disposed scopes too - if any of added scopes + /// were disposed while its <see cref="IUIThreadOperationScope.AllowCancellation"/> was false, + /// this property will stay false regardless of all other scopes' <see cref="IUIThreadOperationScope.AllowCancellation"/> + /// values. + /// </remarks> + bool AllowCancellation { get; } + + /// <summary> + /// Gets user readable operation description, composed of initial context description and + /// descriptions of all currently added scopes. + /// </summary> + string Description { get; } + + /// <summary> + /// Gets current list of <see cref="IUIThreadOperationScope"/>s in this context. + /// </summary> + IEnumerable<IUIThreadOperationScope> Scopes { get; } + + /// <summary> + /// Adds a UI thread operation scope with its own two way cancellability, description and progress tracker. + /// The scope is removed from the context on dispose. + /// </summary> + IUIThreadOperationScope AddScope(bool allowCancellation, string description); + + /// <summary> + /// Allows a component to take full ownership over this UI thread operation, for example + /// when it shows its own modal UI dialog and handles cancellability through that dialog instead. + /// </summary> + void TakeOwnership(); + } +} diff --git a/src/Text/Def/TextUI/Utilities/IUIThreadOperationExecutor.cs b/src/Text/Def/TextUI/Utilities/IUIThreadOperationExecutor.cs new file mode 100644 index 0000000..3daf364 --- /dev/null +++ b/src/Text/Def/TextUI/Utilities/IUIThreadOperationExecutor.cs @@ -0,0 +1,89 @@ +using System; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Executes potentially long running operation on the UI thread and provides shared two way cancellability + /// and wait indication. + /// </summary> + /// <remarks> + /// <para> + /// Visual Studio implementation of this service measures operation execution duration + /// and displays a modal wait dialog if it takes too long. The wait dialog describes operation to the user, + /// optionally provides progress information and allows user to cancel the operation if + /// it can be cancelled. + /// Components running in the operation can affect the wait dialog via <see cref="IUIThreadOperationContext"/> + /// provided by this service. + /// </para> + /// <para> + /// This is a MEF component and should be imported for consumptions as follows: + /// + /// [Import] + /// private IUIThreadOperationExecutor uiThreadOperationExecutor = null; + /// </para> + /// <para> + /// Host specific implementations of this service should be exported as follows: + /// + /// [ExportImplementation(typeof(IUIThreadOperationExecutor))] + /// [Name("Visual Studio UI thread operation executor")] + /// [Order(Before = "default")] + /// internal sealed class VSUIThreadOperationExecutor : IUIThreadOperationExecutor + /// </para> + /// <para> + /// All methods of this interface should only be called on the UI thread. + /// </para> + /// </remarks> + /// <example> + /// A typical usage of <see cref="IUIThreadOperationExecutor"/> to execute a potentially + /// long running operation on the UI thread is as follows: + /// + /// [Import] + /// private IUIThreadOperationExecutor uiThreadOperationExecutor = null; + /// ... + /// UIThreadOperationStatus status = _uiThreadOperationExecutor.Execute("Format document", + /// "Please wait for document formatting...", allowCancel: true, showProgress: false, + /// action: (context) => Format(context.UserCancellationToken)); + /// if (status == UIThreadOperationStatus.Completed)... + /// + /// Or alternatively + /// + /// using (var context = _uiThreadOperationExecutor.BeginExecute("Format document", + /// "Please wait for document formatting...", allowCancel: true, showProgress: false)) + /// { + /// Format(context); + /// } + /// + /// private void Format(IUIThreadOperationContext context) + /// { + /// using (context.AddScope(allowCancellation: true, description: "Acquiring user preferences...")) + /// {...} + /// } + /// </example> + public interface IUIThreadOperationExecutor + { + /// <summary> + /// Executes the action synchronously and waits for it to complete. + /// </summary> + /// <param name="title">Operation's title.</param> + /// <param name="description">Initial operation's description.</param> + /// <param name="allowCancel">Whether to allow cancellability.</param> + /// <param name="showProgress">Whether to show progress indication.</param> + /// <param name="action">An action to execute.</param> + /// <returns>A status of action execution.</returns> + UIThreadOperationStatus Execute(string title, string description, bool allowCancel, bool showProgress, + Action<IUIThreadOperationContext> action); + + /// <summary> + /// Begins executing potentially long running operation on the caller thread and provides a context object that provides access to shared + /// cancellability and wait indication. + /// </summary> + /// <param name="title">Operation's title.</param> + /// <param name="description">Initial operation's description.</param> + /// <param name="allowCancel">Whether to allow cancellability.</param> + /// <param name="showProgress">Whether to show progress indication.</param> + /// <returns><see cref="IUIThreadOperationContext"/> instance that provides access to shared two way + /// cancellability and wait indication for the given operation. The operation is considered executed + /// when this <see cref="IUIThreadOperationContext"/> instance is disposed.</returns> + IUIThreadOperationContext BeginExecute(string title, string description, bool allowCancel, bool showProgress); + } +} diff --git a/src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs b/src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs new file mode 100644 index 0000000..8f1d05f --- /dev/null +++ b/src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs @@ -0,0 +1,58 @@ +using System; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Represents a single scope of a context of executing potentially long running operation on the UI thread. + /// Scopes allow multiple components running within an operation to share the same context. + /// </summary> + public interface IUIThreadOperationScope : IDisposable + { + /// <summary> + /// Gets or sets whether the operation can be cancelled. + /// </summary> + bool AllowCancellation { get; set; } + + /// <summary> + /// Gets user readable operation description. + /// </summary> + string Description { get; set; } + + /// <summary> + /// The <see cref="IUIThreadOperationContext" /> owning this scope instance. + /// </summary> + IUIThreadOperationContext Context { get; } + + /// <summary> + /// Progress tracker instance to report operation progress. + /// </summary> + IProgress<ProgressInfo> Progress { get; } + } + + /// <summary> + /// Represents an update of a progress. + /// </summary> + public struct ProgressInfo + { + /// <summary> + /// A number of already completed items. + /// </summary> + public int CompletedItems { get; } + + /// <summary> + /// A total number if items. + /// </summary> + public int TotalItems { get; } + + /// <summary> + /// Creates a new instance of the <see cref="ProgressInfo"/> struct. + /// </summary> + /// <param name="completedItems">A number of already completed items.</param> + /// <param name="totalItems">A total number if items.</param> + public ProgressInfo(int completedItems, int totalItems) + { + this.CompletedItems = completedItems; + this.TotalItems = totalItems; + } + } +} diff --git a/src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs b/src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs new file mode 100644 index 0000000..b7e47ae --- /dev/null +++ b/src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs @@ -0,0 +1,18 @@ +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Represents a status of executing a potentially long running operation on the UI thread. + /// </summary> + public enum UIThreadOperationStatus + { + /// <summary> + /// An operation was successfully completed. + /// </summary> + Completed, + + /// <summary> + /// An operation was cancelled. + /// </summary> + Canceled, + } +} diff --git a/src/Text/Impl/Commanding/CommandHandlerBucket.cs b/src/Text/Impl/Commanding/CommandHandlerBucket.cs new file mode 100644 index 0000000..2374c76 --- /dev/null +++ b/src/Text/Impl/Commanding/CommandHandlerBucket.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Commanding; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + /// <summary> + /// Lightweight stack-like view over a readonly ordered list of command handlers. + /// </summary> + internal class CommandHandlerBucket + { + private int _currentCommandHandlerIndex; + private readonly IReadOnlyList<Lazy<ICommandHandler, ICommandHandlerMetadata>> _commandHandlers; + + public CommandHandlerBucket(IReadOnlyList<Lazy<ICommandHandler, ICommandHandlerMetadata>> commandHandlers) + { + _commandHandlers = commandHandlers ?? throw new ArgumentNullException(nameof(commandHandlers)); + } + + public bool IsEmpty => _currentCommandHandlerIndex >= _commandHandlers.Count; + + public Lazy<ICommandHandler, ICommandHandlerMetadata> Peek() + { + if (!IsEmpty) + { + return _commandHandlers[_currentCommandHandlerIndex]; + } + + throw new InvalidOperationException($"{nameof(CommandHandlerBucket)} is empty."); + } + + internal Lazy<ICommandHandler, ICommandHandlerMetadata> Pop() + { + if (!IsEmpty) + { + return _commandHandlers[_currentCommandHandlerIndex++]; + } + + throw new InvalidOperationException($"{nameof(CommandHandlerBucket)} is empty."); + } + } +} diff --git a/src/Text/Impl/Commanding/CommandingStrings.Designer.cs b/src/Text/Impl/Commanding/CommandingStrings.Designer.cs new file mode 100644 index 0000000..cfaadfd --- /dev/null +++ b/src/Text/Impl/Commanding/CommandingStrings.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation { + 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", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CommandingStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CommandingStrings() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal 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.Implementation.Text.Impl.Commanding.CommandingStrings", typeof(CommandingStrings).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)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to Executing a command. + /// </summary> + internal static string ExecutingCommand { + get { + return ResourceManager.GetString("ExecutingCommand", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Please wait for command execution to finish.... + /// </summary> + internal static string WaitForCommandExecution { + get { + return ResourceManager.GetString("WaitForCommandExecution", resourceCulture); + } + } + } +} diff --git a/src/Text/Impl/Commanding/CommandingStrings.resx b/src/Text/Impl/Commanding/CommandingStrings.resx new file mode 100644 index 0000000..72d604d --- /dev/null +++ b/src/Text/Impl/Commanding/CommandingStrings.resx @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 1.3 + + 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">1.3</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1">this is my long string</data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + [base64 mime encoded serialized .NET Framework object] + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + [base64 mime encoded string representing a byte array form of the .NET Framework object] + </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.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:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <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" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + </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>1.3</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="ExecutingCommand" xml:space="preserve"> + <value>Executing a command</value> + </data> + <data name="WaitForCommandExecution" xml:space="preserve"> + <value>Please wait for command execution to finish...</value> + </data> +</root>
\ No newline at end of file diff --git a/src/Text/Impl/Commanding/DefaultBufferResolver.cs b/src/Text/Impl/Commanding/DefaultBufferResolver.cs new file mode 100644 index 0000000..71c93d8 --- /dev/null +++ b/src/Text/Impl/Commanding/DefaultBufferResolver.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Editor.Commanding; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + [Export(typeof(ICommandingTextBufferResolverProvider))] + [ContentType("any")] + internal class DefaultBufferResolverProvider : ICommandingTextBufferResolverProvider + { + public ICommandingTextBufferResolver CreateResolver(ITextView textView) + { + return new DefaultBufferResolver(textView); + } + } + + internal class DefaultBufferResolver : ICommandingTextBufferResolver + { + private readonly ITextView _textView; + + public DefaultBufferResolver(ITextView textView) + { + _textView = textView ?? throw new ArgumentNullException(nameof(textView)); + } + + public IEnumerable<ITextBuffer> ResolveBuffersForCommand<TArgs>() where TArgs : EditorCommandArgs + { + var mappingPoint = _textView.BufferGraph.CreateMappingPoint(_textView.Caret.Position.BufferPosition, PointTrackingMode.Negative); + return _textView.BufferGraph.GetTextBuffers((b) => mappingPoint.GetPoint(b, PositionAffinity.Successor) != null); + } + } +} diff --git a/src/Text/Impl/Commanding/EditorCommandHandlerService.cs b/src/Text/Impl/Commanding/EditorCommandHandlerService.cs new file mode 100644 index 0000000..3bd08c2 --- /dev/null +++ b/src/Text/Impl/Commanding/EditorCommandHandlerService.cs @@ -0,0 +1,373 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Text.Utilities; +using Microsoft.VisualStudio.Text.Editor.Commanding; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Threading; +using ICommandHandlerAndMetadata = System.Lazy<Microsoft.VisualStudio.Commanding.ICommandHandler, Microsoft.VisualStudio.UI.Text.Commanding.Implementation.ICommandHandlerMetadata>; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + internal class EditorCommandHandlerService : IEditorCommandHandlerService + { + private readonly IEnumerable<ICommandHandlerAndMetadata> _commandHandlers; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; + private readonly JoinableTaskContext _joinableTaskContext; + private readonly ITextView _textView; + private readonly IComparer<IEnumerable<string>> _contentTypesComparer; + private readonly ICommandingTextBufferResolver _bufferResolver; + private readonly IGuardedOperations _guardedOperations; + + private readonly static IReadOnlyList<ICommandHandlerAndMetadata> EmptyHandlerList = new List<ICommandHandlerAndMetadata>(0); + private readonly static Action EmptyAction = delegate { }; + private readonly static Func<CommandState> UnavalableCommandFunc = new Func<CommandState>(() => CommandState.Unavailable); + + /// This dictionary acts as a cache so we can avoid having to look through the full list of + /// handlers every time we need handlers of a specific type, for a given content type. + private readonly Dictionary<(Type commandArgType, IContentType contentType), IReadOnlyList<ICommandHandlerAndMetadata>> _commandHandlersByTypeAndContentType; + + public EditorCommandHandlerService(ITextView textView, + IEnumerable<ICommandHandlerAndMetadata> commandHandlers, + IUIThreadOperationExecutor uiThreadOperationExecutor, JoinableTaskContext joinableTaskContext, + IComparer<IEnumerable<string>> contentTypesComparer, + ICommandingTextBufferResolver bufferResolver, + IGuardedOperations guardedOperations) + { + _commandHandlers = commandHandlers ?? throw new ArgumentNullException(nameof(commandHandlers)); + _textView = textView ?? throw new ArgumentNullException(nameof(textView)); + _uiThreadOperationExecutor = uiThreadOperationExecutor ?? throw new ArgumentNullException(nameof(uiThreadOperationExecutor)); + _joinableTaskContext = joinableTaskContext ?? throw new ArgumentNullException(nameof(joinableTaskContext)); + _contentTypesComparer = contentTypesComparer ?? throw new ArgumentNullException(nameof(contentTypesComparer)); + _commandHandlersByTypeAndContentType = new Dictionary<(Type commandArgType, IContentType contentType), IReadOnlyList<ICommandHandlerAndMetadata>>(); + _bufferResolver = bufferResolver ?? throw new ArgumentNullException(nameof(bufferResolver)); + _guardedOperations = guardedOperations ?? throw new ArgumentNullException(nameof(guardedOperations)); + } + + public CommandState GetCommandState<T>(Func<ITextView, ITextBuffer, T> argsFactory, Func<CommandState> nextCommandHandler) where T : EditorCommandArgs + { + if (!_joinableTaskContext.IsOnMainThread) + { + throw new InvalidOperationException($"{nameof(IEditorCommandHandlerService.GetCommandState)} method shoudl only be called on the UI thread."); + } + + // In Razor scenario it's possible that EditorCommandHandlerService is called re-entrantly, + // first by contained language command filter and then by editor command chain. + // To preserve Razor commanding semantics, only execute handlers once. + if (IsReentrantCall()) + { + return nextCommandHandler?.Invoke() ?? CommandState.Unavailable; + } + + using (var reentrancyGuard = new ReentrancyGuard(_textView)) + { + // Build up chain of handlers per buffer + Func<CommandState> handlerChain = nextCommandHandler ?? UnavalableCommandFunc; + foreach (var bufferAndHandler in GetOrderedBuffersAndCommandHandlers<T>().Reverse()) + { + T args = null; + // Declare locals to ensure that we don't end up capturing the wrong thing + var nextHandler = handlerChain; + var handler = bufferAndHandler.handler; + args = args ?? (args = argsFactory(_textView, bufferAndHandler.buffer)); + if (args == null) + { + // Args factory failed, skip command handlers and just call next + return handlerChain(); + } + + handlerChain = () => handler.GetCommandState(args, nextHandler); + } + + // Kick off the first command handler + return handlerChain(); + } + } + + public void Execute<T>(Func<ITextView, ITextBuffer, T> argsFactory, Action nextCommandHandler) where T : EditorCommandArgs + { + if (!_joinableTaskContext.IsOnMainThread) + { + throw new InvalidOperationException($"{nameof(IEditorCommandHandlerService.Execute)} method shoudl only be called on the UI thread."); + } + + // In Razor scenario it's possible that EditorCommandHandlerService is called re-entrantly, + // first by contained language command filter and then by editor command chain. + // To preserve Razor commanding semantics, only execute handlers once. + if (IsReentrantCall()) + { + nextCommandHandler?.Invoke(); + return; + } + + using (var reentrancyGuard = new ReentrancyGuard(_textView)) + { + CommandExecutionContext commandExecutionContext = null; + + // Build up chain of handlers per buffer + Action handlerChain = nextCommandHandler ?? EmptyAction; + // TODO: realize the chain dynamically and without Reverse() + foreach (var bufferAndHandler in GetOrderedBuffersAndCommandHandlers<T>().Reverse()) + { + T args = null; + // Declare locals to ensure that we don't end up capturing the wrong thing + var nextHandler = handlerChain; + var handler = bufferAndHandler.handler; + args = args ?? (args = argsFactory(_textView, bufferAndHandler.buffer)); + if (args == null) + { + // Args factory failed, skip command handlers and just call next + handlerChain(); + } + + if (commandExecutionContext == null) + { + commandExecutionContext = CreateCommandExecutionContext(); + } + + handlerChain = () => handler.ExecuteCommand(args, nextHandler, commandExecutionContext); + } + + ExecuteCommandHandlerChain(commandExecutionContext, handlerChain, nextCommandHandler); + } + } + + private void ExecuteCommandHandlerChain(CommandExecutionContext commandExecutionContext, + Action handlerChain, Action nextCommandHandler) + { + try + { + // Kick off the first command handler. + handlerChain(); + } + catch (OperationCanceledException) + { + nextCommandHandler?.Invoke(); + } + catch (AggregateException aggregate) when (aggregate.InnerExceptions.All(e => e is OperationCanceledException)) + { + nextCommandHandler?.Invoke(); + } + finally + { + commandExecutionContext?.WaitContext?.Dispose(); + } + } + + private class ReentrancyGuard : IDisposable + { + private readonly IPropertyOwner _owner; + + public ReentrancyGuard(IPropertyOwner owner) + { + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); + _owner.Properties[typeof(ReentrancyGuard)] = this; + } + + public void Dispose() + { + _owner.Properties.RemoveProperty(typeof(ReentrancyGuard)); + } + } + + private bool IsReentrantCall() + { + return _textView.Properties.ContainsProperty(typeof(ReentrancyGuard)); + } + + private CommandExecutionContext CreateCommandExecutionContext() + { + CommandExecutionContext commandExecutionContext; + var uiThreadOperationContext = _uiThreadOperationExecutor.BeginExecute(CommandingStrings.ExecutingCommand, + CommandingStrings.WaitForCommandExecution, allowCancel: true, showProgress: true); + commandExecutionContext = new CommandExecutionContext(uiThreadOperationContext); + return commandExecutionContext; + } + + //internal for unit tests + internal IEnumerable<(ITextBuffer buffer, ICommandHandler handler)> GetOrderedBuffersAndCommandHandlers<T>() where T : EditorCommandArgs + { + // This method creates an ordered sequence of (buffer, handler) pairs that define proper order of + // command handling that takes into account the buffer graph and command handlers matching buffers in the graph by + // content types. + + // Currently this method discovers affected buffers based on caret mapping only. + // TODO: this should be an extensibility point as in some scenarios we might want to consider selection too for example. + + // A general idea is that command handlers matching more specifically content type of buffers higher in the buffer + // graph should be executed before those matching buffers lower in the graph or less specific content types. + + // So for example in a projection scenario (projection buffer containing C# buffer), 3 command handlers + // matching "projection", "CSharp" and "any" content types will be ordered like this: + // 1. command handler matching "projection" content type is executed on the projection buffer + // 2. command handler matching "CSharp" content type is executed on the C# buffer + // 3. command handler matching "any" content type is executed on the projection buffer + + // The ordering algorithm is as follows: + // 1. Create an ordered list of all affected buffers in the buffer graph + // by mapping caret position down and up the buffer graph. In a typical projection scenario + // (projection buffer containing C# buffer) that will produce (projection buffer, C# buffer) sequence. + // 2. For each affected buffer get or create a bucket of matching command handlers, + // ordered by [Order] and content type specificity. + // 3. Pick best command handler in all buckets in terms of content type specificity (e.g. + // if one command handler can handle "text" content type, but another can + // handle "CSharp" content type, we pick the latter one: + // 3. Start with top command handler in first non empty bucket. + // 4. Compare it with top command handlers in all other buckets in terms of content type specificity. + // 5. yield return current handler or better one if found, pop it from its bucket + // 6. Repeat starting with #3 utill all buckets are empty. + // In the projection scenario that will result in the following + // list of (buffer, handler) pairs: (projection buffer, projection handler), (C# buffer, C# handler), + // (projection buffer, any handler). + + IReadOnlyList<ITextBuffer> buffers = _bufferResolver.ResolveBuffersForCommand<T>().ToArray(); + if (buffers == null || buffers.Count == 0) + { + yield break; + } + + // An array of per-buffer buckets, each containing cached list of matching command handlers, + // ordered by [Order] and content type specificity + var handlerBuckets = new CommandHandlerBucket[buffers.Count]; + for (int i = 0; i < buffers.Count; i++) + { + handlerBuckets[i] = new CommandHandlerBucket(GetOrCreateOrderedHandlers<T>(buffers[i].ContentType, _textView.Roles)); + } + + while (true) + { + ICommandHandlerAndMetadata currentHandler = null; + int currentHandlerBufferIndex = 0; + + for (int i = 0; i < handlerBuckets.Length; i++) + { + if (!handlerBuckets[i].IsEmpty) + { + currentHandler = handlerBuckets[i].Peek(); + currentHandlerBufferIndex = i; + break; + } + } + + if (currentHandler == null) + { + // All buckets are empty, all done + break; + } + + // Check if any other bucket has a better handler (i.e. can handle more specific content type). + var foundBetterHandler = false; + for (int i = 0; i < buffers.Count; i++) + { + // Search in other buckets only + if (i != currentHandlerBufferIndex) + { + if (!handlerBuckets[i].IsEmpty) + { + var handler = handlerBuckets[i].Peek(); + // Can this handler handle content type more specific than top handler in firstNonEmptyBucket? + if (_contentTypesComparer.Compare(handler.Metadata.ContentTypes, currentHandler.Metadata.ContentTypes) < 0) + { + foundBetterHandler = true; + handlerBuckets[i].Pop(); + yield return (buffers[i], handler.Value); + break; + } + } + } + } + + if (!foundBetterHandler) + { + yield return (buffers[currentHandlerBufferIndex], currentHandler.Value); + handlerBuckets[currentHandlerBufferIndex].Pop(); + } + } + } + + private IReadOnlyList<ICommandHandlerAndMetadata> GetOrCreateOrderedHandlers<T>(IContentType contentType, ITextViewRoleSet textViewRoles) where T : EditorCommandArgs + { + var cacheKey = (commandArgsType: typeof(T), contentType: contentType); + if (!_commandHandlersByTypeAndContentType.TryGetValue(cacheKey, out var commandHandlerList)) + { + IList<ICommandHandlerAndMetadata> newCommandHandlerList = null; + foreach (var lazyCommandHandler in SelectMatchingCommandHandlers(_commandHandlers, contentType, textViewRoles)) + { + var commandHandler = _guardedOperations.InstantiateExtension<ICommandHandler>(this, lazyCommandHandler); + if (commandHandler is ICommandHandler<T> || commandHandler is IChainedCommandHandler<T>) + { + if (newCommandHandlerList == null) + { + newCommandHandlerList = new FrugalList<ICommandHandlerAndMetadata>(); + } + + newCommandHandlerList.Add(lazyCommandHandler); + } + } + + if (newCommandHandlerList?.Count > 1) + { + // Order handlers by [Order] across content types, but preserve sort order otherwise + newCommandHandlerList = StableOrderer.Order(newCommandHandlerList).ToArray(); + } + + commandHandlerList = newCommandHandlerList?.ToArray() ?? EmptyHandlerList; + _commandHandlersByTypeAndContentType[cacheKey] = commandHandlerList; + } + + return commandHandlerList; + } + + /// <summary> + /// Selects matching command handlers without allocating a new list. + /// </summary> + private static IEnumerable<ICommandHandlerAndMetadata> SelectMatchingCommandHandlers( + IEnumerable<ICommandHandlerAndMetadata> commandHandlers, + IContentType contentType, ITextViewRoleSet textViewRoles) + { + foreach (var handler in commandHandlers) + { + if (MatchesContentType(handler.Metadata, contentType) && + MatchesTextViewRoles(handler.Metadata, textViewRoles)) + { + yield return handler; + } + } + } + + private static bool MatchesContentType(ICommandHandlerMetadata handlerMetadata, IContentType contentType) + { + foreach (var handlerContentType in handlerMetadata.ContentTypes) + { + if (contentType.IsOfType(handlerContentType)) + { + return true; + } + } + + return false; + } + + private static bool MatchesTextViewRoles(ICommandHandlerMetadata handlerMetadata, ITextViewRoleSet roles) + { + // Text view roles are optional + if (handlerMetadata.TextViewRoles == null) + { + return true; + } + + foreach (var handlerRole in handlerMetadata.TextViewRoles) + { + if (roles.Contains(handlerRole)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Text/Impl/Commanding/EditorCommandHandlerServiceFactory.cs b/src/Text/Impl/Commanding/EditorCommandHandlerServiceFactory.cs new file mode 100644 index 0000000..241b3b6 --- /dev/null +++ b/src/Text/Impl/Commanding/EditorCommandHandlerServiceFactory.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Text.Editor; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Editor.Commanding; +using Microsoft.VisualStudio.Commanding; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Threading; +using System.Linq; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + [Export(typeof(IEditorCommandHandlerServiceFactory))] + internal class EditorCommandHandlerServiceFactory : IEditorCommandHandlerServiceFactory + { + private readonly IEnumerable<Lazy<ICommandHandler, ICommandHandlerMetadata>> _commandHandlers; + private readonly IList<Lazy<ICommandingTextBufferResolverProvider, IContentTypeMetadata>> _bufferResolverProviders; + private readonly IUIThreadOperationExecutor _uiThreadOperationExecutor; + private readonly JoinableTaskContext _joinableTaskContext; + private readonly IContentTypeRegistryService _contentTypeRegistryService; + private readonly IGuardedOperations _guardedOperations; + private readonly StableContentTypeComparer _contentTypeComparer; + + [ImportingConstructor] + public EditorCommandHandlerServiceFactory( + [ImportMany]IEnumerable<Lazy<ICommandHandler, ICommandHandlerMetadata>> commandHandlers, + [ImportMany]IEnumerable<Lazy<ICommandingTextBufferResolverProvider, IContentTypeMetadata>> bufferResolvers, + IUIThreadOperationExecutor uiThreadOperationExecutor, + JoinableTaskContext joinableTaskContext, + IContentTypeRegistryService contentTypeRegistryService, + IGuardedOperations guardedOperations) + { + _uiThreadOperationExecutor = uiThreadOperationExecutor; + _joinableTaskContext = joinableTaskContext; + _guardedOperations = guardedOperations; + _contentTypeRegistryService = contentTypeRegistryService; + _contentTypeComparer = new StableContentTypeComparer(_contentTypeRegistryService); + _commandHandlers = OrderCommandHandlers(commandHandlers); + if (!bufferResolvers.Any()) + { + throw new ImportCardinalityMismatchException($"Expected to import at least one {typeof(ICommandingTextBufferResolver).Name}"); + } + + _bufferResolverProviders = bufferResolvers.ToList(); + } + + public IEditorCommandHandlerService GetService(ITextView textView) + { + return textView.Properties.GetOrCreateSingletonProperty(() => + { + var bufferResolverProvider = _guardedOperations.InvokeBestMatchingFactory(_bufferResolverProviders, textView.TextBuffer.ContentType, _contentTypeRegistryService, errorSource: this); + ICommandingTextBufferResolver bufferResolver = null; + _guardedOperations.CallExtensionPoint(() => bufferResolver = bufferResolverProvider.CreateResolver(textView)); + bufferResolver = bufferResolver ?? new DefaultBufferResolver(textView); + return new EditorCommandHandlerService(textView, _commandHandlers, _uiThreadOperationExecutor, _joinableTaskContext, + _contentTypeComparer, bufferResolver, _guardedOperations); + }); + } + + public IEditorCommandHandlerService GetService(ITextView textView, ITextBuffer subjectBuffer) + { + if (subjectBuffer == null) + { + return GetService(textView); + } + + return subjectBuffer.Properties.GetOrCreateSingletonProperty(() => + { + return new EditorCommandHandlerService(textView, _commandHandlers, _uiThreadOperationExecutor, + _joinableTaskContext, _contentTypeComparer, + new SingleBufferResolver(subjectBuffer), _guardedOperations); + }); + } + + private IEnumerable<Lazy<ICommandHandler, ICommandHandlerMetadata>> OrderCommandHandlers(IEnumerable<Lazy<ICommandHandler, ICommandHandlerMetadata>> commandHandlers) + { + return commandHandlers.OrderBy((handler) => handler.Metadata.ContentTypes, _contentTypeComparer); + } + } +} diff --git a/src/Text/Impl/Commanding/ICommandHandlerMetadata.cs b/src/Text/Impl/Commanding/ICommandHandlerMetadata.cs new file mode 100644 index 0000000..49af645 --- /dev/null +++ b/src/Text/Impl/Commanding/ICommandHandlerMetadata.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.ComponentModel; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + public interface ICommandHandlerMetadata : IOrderable, IContentTypeMetadata + { + [DefaultValue(null)] // [TextViewRole] is optional + IEnumerable<string> TextViewRoles { get; } + } + +} diff --git a/src/Text/Impl/Commanding/SingleBufferResolver.cs b/src/Text/Impl/Commanding/SingleBufferResolver.cs new file mode 100644 index 0000000..6e7a20d --- /dev/null +++ b/src/Text/Impl/Commanding/SingleBufferResolver.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Editor.Commanding; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + internal class SingleBufferResolver : ICommandingTextBufferResolver + { + private readonly ITextBuffer[] _textBuffer; + + public SingleBufferResolver(ITextBuffer textBuffer) + { + if (textBuffer == null) + { + throw new ArgumentNullException(nameof(textBuffer)); + } + + _textBuffer = new ITextBuffer[] { textBuffer }; + } + + public IEnumerable<ITextBuffer> ResolveBuffersForCommand<TArgs>() where TArgs : EditorCommandArgs + { + return _textBuffer; + } + } +} diff --git a/src/Text/Util/TextDataUtil/StableContentTypeComparer.cs b/src/Text/Util/TextDataUtil/StableContentTypeComparer.cs new file mode 100644 index 0000000..19ed88d --- /dev/null +++ b/src/Text/Util/TextDataUtil/StableContentTypeComparer.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Custom comparer for sorting lists of content types that preserves original order of unrelated content types. + /// </summary> + internal class StableContentTypeComparer : IComparer<IEnumerable<string>> + { + private readonly IContentTypeRegistryService _contentTypeRegistryService; + + public StableContentTypeComparer(IContentTypeRegistryService contentTypeRegistryService) + { + _contentTypeRegistryService = contentTypeRegistryService ?? throw new ArgumentNullException(nameof(contentTypeRegistryService)); + } + + public int Compare(IEnumerable<string> x, IEnumerable<string> y) + { + if (x.SequenceEqual(y)) + { + return 0; + } + + foreach (var contentTypeXStr in x) + { + var contentTypeX = _contentTypeRegistryService.GetContentType(contentTypeXStr); + if (contentTypeX != null) + { + foreach (var contentTypeYStr in y) + { + if (contentTypeX.IsOfType(contentTypeYStr)) + { + return -1; + } + } + } + } + + foreach (var contentTypeYStr in y) + { + var contentTypeY = _contentTypeRegistryService.GetContentType(contentTypeYStr); + if (contentTypeY != null) + { + foreach (var contentTypeXStr in x) + { + if (contentTypeY.IsOfType(contentTypeXStr)) + { + return 1; + } + } + } + } + + // Content types are unrelated, there is no way resolve the tie so + // let's consider them equal to preserve original order. + return 0; + } + } +} diff --git a/src/Text/Util/TextDataUtil/StableOrderer.cs b/src/Text/Util/TextDataUtil/StableOrderer.cs new file mode 100644 index 0000000..ae9ee8f --- /dev/null +++ b/src/Text/Util/TextDataUtil/StableOrderer.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.VisualStudio.Utilities +{ + internal static class StableOrderer + { + private static bool OrderDependencyFunction<TValue, TMetadata>(Lazy<TValue, TMetadata> x, + Lazy<TValue, TMetadata> y) + where TValue : class + where TMetadata : IOrderable + { + if (y.Metadata.Before?.Contains(x.Metadata.Name) == true) + { + return true; + } + + if (x.Metadata.After?.Contains(y.Metadata.Name) == true) + { + return true; + } + + return false; + } + + public static IEnumerable<Lazy<TValue, TMetadata>> Order<TValue, TMetadata>(IEnumerable<Lazy<TValue, TMetadata>> itemsToOrder) + where TValue : class + where TMetadata : IOrderable + { + return StableTopologicalSort.Order(itemsToOrder, OrderDependencyFunction); + } + } +} diff --git a/src/Text/Util/TextDataUtil/StableTopologicalSort.cs b/src/Text/Util/TextDataUtil/StableTopologicalSort.cs new file mode 100644 index 0000000..04703d1 --- /dev/null +++ b/src/Text/Util/TextDataUtil/StableTopologicalSort.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.Text.Utilities; + +namespace Microsoft.VisualStudio.Utilities +{ + /// <summary> + /// Orders elements of a linearized directed graph in a dependency order, while preserving the original order + /// between elements with no dependencies. + /// </summary> + /// <remarks>This is an implementation of the Gapotchenko's stable topological sort algorithm, + /// https://blog.gapotchenko.com/stable-topological-sort. + /// </remarks> + internal static class StableTopologicalSort + { + /// <summary> + /// An element dependency function. + /// </summary> + /// <returns> + /// <c>true</c> if x depends on y, <c>false</c> otherwise. + /// </returns> + public delegate bool TopologicalDependencyFunction<in T>(T x, T y); + + /// <summary> + /// Orders elements of a linearized directed graph in a dependency order, while preserving the original order + /// between elements with no dependencies. + /// </summary> + public static IEnumerable<T> Order<T>( + IEnumerable<T> itemsToOrder, + TopologicalDependencyFunction<T> dependencyFunction) + { + if (itemsToOrder == null) + throw new ArgumentNullException(nameof(itemsToOrder)); + if (dependencyFunction == null) + throw new ArgumentNullException(nameof(dependencyFunction)); + + var itemsToOrderList = itemsToOrder.ToList(); + if (itemsToOrderList.Count < 2) + { + return itemsToOrder; + } + + int itemsToOrderCount = itemsToOrderList.Count; + + var graph = DependencyGraph<T>.TryCreate(itemsToOrderList, dependencyFunction, EqualityComparer<T>.Default); + if (graph == null) + { + return itemsToOrder; + } + + Restart: + for (int i = 0; i < itemsToOrderCount; ++i) + { + for (int j = 0; j < i; ++j) + { + if (graph.DoesXHaveDirectDependencyOnY(itemsToOrderList[j], itemsToOrderList[i])) + { + bool jDependsOnI = graph.DoesXHaveTransientDependencyOnY(itemsToOrderList[j], itemsToOrderList[i]); + bool iDependsOnJ = graph.DoesXHaveTransientDependencyOnY(itemsToOrderList[i], itemsToOrderList[j]); + + bool circularDependency = jDependsOnI && iDependsOnJ; + + if (!circularDependency) + { + var t = itemsToOrderList[i]; + itemsToOrderList.RemoveAt(i); + + itemsToOrderList.Insert(j, t); + goto Restart; + } + } + } + } + + return itemsToOrderList; + } + + private class DependencyGraph<T> + { + private IEqualityComparer<T> EqualityComparer { get; } + + public IDictionary<T, Node> Nodes { get; } + + public DependencyGraph(IEqualityComparer<T> equalityComparer, int n) + { + EqualityComparer = equalityComparer ?? throw new ArgumentNullException(nameof(equalityComparer)); + this.Nodes = new Dictionary<T, Node>(n, equalityComparer); + } + + public class Node + { + private IList<T> _children = new FrugalList<T>(); + + public IList<T> Children => _children ?? (_children = new FrugalList<T>()); + } + + public static DependencyGraph<T> TryCreate( + IList<T> items, + TopologicalDependencyFunction<T> dependencyFunction, + IEqualityComparer<T> equalityComparer) + { + var graph = new DependencyGraph<T>(equalityComparer, items.Count); + + bool hasDependencies = false; + + for (int position = 0; position < items.Count; ++position) + { + var element = items[position]; + + if (!graph.Nodes.TryGetValue(element, out Node node)) + { + node = new Node(); + graph.Nodes.Add(element, node); + } + + foreach (var anotherElement in items) + { + if (equalityComparer.Equals(element, anotherElement)) + { + continue; + } + + if (dependencyFunction(element, anotherElement)) + { + node.Children.Add(anotherElement); + hasDependencies = true; + } + } + } + + if (!hasDependencies) + { + return null; + } + + return graph; + } + + public bool DoesXHaveDirectDependencyOnY(T x, T y) + { + if (Nodes.TryGetValue(x, out Node node)) + { + if (node.Children.Contains(y, EqualityComparer)) + { + return true; + } + } + + return false; + } + + private class DependencyWalker + { + private readonly DependencyGraph<T> _graph; + private readonly HashSet<T> _visitedNodes; + + public DependencyWalker(DependencyGraph<T> graph) + { + _graph = graph; + _visitedNodes = new HashSet<T>(graph.EqualityComparer); + } + + public bool DoesXHaveTransientDependencyOnY(T x, T y) + { + if (!_visitedNodes.Add(x)) + { + return false; + } + + if (_graph.Nodes.TryGetValue(x, out Node node)) + { + if (node.Children.Contains(y, _graph.EqualityComparer)) + { + return true; + } + + foreach (var i in node.Children) + { + if (DoesXHaveTransientDependencyOnY(i, y)) + { + return true; + } + } + } + + return false; + } + } + + public bool DoesXHaveTransientDependencyOnY(T x, T y) + { + var traverser = new DependencyWalker(this); + return traverser.DoesXHaveTransientDependencyOnY(x, y); + } + } + } +} diff --git a/src/Text/Util/TextUIUtil/DefaultUIThreadOperationExecutor.cs b/src/Text/Util/TextUIUtil/DefaultUIThreadOperationExecutor.cs new file mode 100644 index 0000000..e022a1c --- /dev/null +++ b/src/Text/Util/TextUIUtil/DefaultUIThreadOperationExecutor.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + [ExportImplementation(typeof(IUIThreadOperationExecutor))] + [Name("default")] + internal class DefaultUIThreadOperationExecutor : IUIThreadOperationExecutor + { + public IUIThreadOperationContext BeginExecute(string title, string description, bool allowCancel, bool showProgress) + { + return new DefaultUIThreadOperationContext(allowCancel, description); + } + + public UIThreadOperationStatus Execute(string title, string description, bool allowCancel, bool showProgress, Action<IUIThreadOperationContext> action) + { + var context = new DefaultUIThreadOperationContext(allowCancel, description); + action(context); + return UIThreadOperationStatus.Completed; + } + } + + internal class DefaultUIThreadOperationContext : AbstractUIThreadOperationContext + { + public DefaultUIThreadOperationContext(bool allowCancellation, string description) + : base(allowCancellation, description) + { + } + } +} diff --git a/src/Text/Util/TextUIUtil/UIThreadOperationExecutor.cs b/src/Text/Util/TextUIUtil/UIThreadOperationExecutor.cs new file mode 100644 index 0000000..e1a92bf --- /dev/null +++ b/src/Text/Util/TextUIUtil/UIThreadOperationExecutor.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation +{ + [Export(typeof(IUIThreadOperationExecutor))] + internal class UIThreadOperationExecutor : IUIThreadOperationExecutor + { + [ImportImplementations(typeof(IUIThreadOperationExecutor))] + private IEnumerable<Lazy<IUIThreadOperationExecutor, IOrderable>> _unorderedImplementations; + + private IUIThreadOperationExecutor _bestImpl; + + private IUIThreadOperationExecutor BestImplementation + { + get + { + if (_bestImpl == null) + { + var orderedImpls = Orderer.Order(_unorderedImplementations); + if (orderedImpls.Count == 0) + { + throw new ImportCardinalityMismatchException($"Expected to import at least one export of {typeof(IUIThreadOperationExecutor).FullName}, but got none."); + } + + _bestImpl = orderedImpls[0].Value; + } + + return _bestImpl; + } + } + + public IUIThreadOperationContext BeginExecute(string title, string description, bool allowCancel, bool showProgress) + { + return BestImplementation.BeginExecute(title, description, allowCancel, showProgress); + } + + public UIThreadOperationStatus Execute(string title, string description, bool allowCancel, bool showProgress, Action<IUIThreadOperationContext> action) + { + return BestImplementation.Execute(title, description, allowCancel, showProgress, action); + } + } +} |