diff options
Diffstat (limited to 'main/src')
142 files changed, 12876 insertions, 2684 deletions
diff --git a/main/src/addins/AspNet/Properties/MonoDevelop.AspNet.addin.xml b/main/src/addins/AspNet/Properties/MonoDevelop.AspNet.addin.xml index 2cc9d27589..eb60c3311a 100644 --- a/main/src/addins/AspNet/Properties/MonoDevelop.AspNet.addin.xml +++ b/main/src/addins/AspNet/Properties/MonoDevelop.AspNet.addin.xml @@ -342,7 +342,7 @@ <Extension path = "/MonoDevelop/Ide/TextEditorExtensions">
<Class fileExtensions=".aspx, .ascx, .master" class = "MonoDevelop.AspNet.WebForms.WebFormsEditorExtension" />
<Class mimeTypes="text/html,application/x-spark" class = "MonoDevelop.AspNet.Html.HtmlEditorExtension" />
- <Class fileExtensions=".cshtml" class = "MonoDevelop.AspNet.Razor.RazorCSharpEditorExtension" />
+ <Class mimeTypes="text/x-cshtml" class = "MonoDevelop.AspNet.Razor.RazorCSharpEditorExtension" />
</Extension>
<Extension path = "/MonoDevelop/Html/DocTypes">
@@ -378,7 +378,11 @@ <CommandItem id = "MonoDevelop.AspNet.Commands.AspNetCommands.AddViewFromController" />
<SeparatorItem id = "MvcSeparator1" />
</Condition>
- <Condition id="FileType" fileExtensions=".cshtml,.aspx">
+ <Condition id="FileType" fileExtensions=".aspx">
+ <CommandItem id = "MonoDevelop.AspNet.Commands.AspNetCommands.GoToController" insertbefore="MonoDevelop.Ide.Commands.EditCommands.Cut" />
+ <SeparatorItem id = "MvcSeparator2" />
+ </Condition>
+ <Condition id="FileType" mimeType="text/x-cshtml">
<CommandItem id = "MonoDevelop.AspNet.Commands.AspNetCommands.GoToController" insertbefore="MonoDevelop.Ide.Commands.EditCommands.Cut" />
<SeparatorItem id = "MvcSeparator2" />
</Condition>
diff --git a/main/src/addins/CSharpBinding/CSharpBinding.csproj b/main/src/addins/CSharpBinding/CSharpBinding.csproj index ee7265d81a..dedf92b2be 100644 --- a/main/src/addins/CSharpBinding/CSharpBinding.csproj +++ b/main/src/addins/CSharpBinding/CSharpBinding.csproj @@ -1,6 +1,7 @@ <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <Import Project="..\..\..\MonoDevelop.props" /> <Import Project="$(ReferencesVSEditor)" /> + <Import Project="$(ReferencesRoslyn)" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> @@ -144,30 +145,6 @@ <HintPath>..\..\..\build\bin\System.Collections.Immutable.dll</HintPath> <Private>False</Private> </Reference> - <Reference Include="Microsoft.CodeAnalysis.Workspaces"> - <HintPath>..\..\..\build\bin\Microsoft.CodeAnalysis.Workspaces.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.CodeAnalysis"> - <HintPath>..\..\..\build\bin\Microsoft.CodeAnalysis.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"> - <HintPath>..\..\..\build\bin\Microsoft.CodeAnalysis.CSharp.Workspaces.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.CodeAnalysis.CSharp"> - <HintPath>..\..\..\build\bin\Microsoft.CodeAnalysis.CSharp.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.CodeAnalysis.CSharp.Features"> - <HintPath>..\..\..\build\bin\Microsoft.CodeAnalysis.CSharp.Features.dll</HintPath> - <Private>False</Private> - </Reference> - <Reference Include="Microsoft.CodeAnalysis.Features"> - <HintPath>..\..\..\build\bin\Microsoft.CodeAnalysis.Features.dll</HintPath> - <Private>False</Private> - </Reference> <Reference Include="System.ValueTuple"> <HintPath>..\..\..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath> <Private>False</Private> @@ -324,6 +301,17 @@ <Compile Include="MonoDevelop.CSharp.Features\Formatter\Indent.cs" /> <Compile Include="MonoDevelop.CSharp\DeclaredSymbolInfo.cs" /> <Compile Include="MonoDevelop.CSharp\ProjectSearchCategory.cs" /> + <Compile Include="Razor\ContainedDocumentPreserveFormattingRule.cs" /> + <Compile Include="Razor\IMonoDevelopContainedLanguageHost.cs" /> + <Compile Include="Razor\IMyRoslynCompletionDataProvider.cs" /> + <Compile Include="Razor\MonoDevelopContainedDocument.cs" /> + <Compile Include="Razor\MonoDevelopFormattingRuleFactoryServiceFactory.cs" /> + <Compile Include="Razor\MyCSharpCompletionData.cs" /> + <Compile Include="Razor\MyCSharpCompletionDataProvider.cs" /> + <Compile Include="Razor\MyRoslynCompletionData.cs" /> + <Compile Include="Razor\RoslynCommandTarget.cs" /> + <Compile Include="Razor\RoslynCompletionPresenter.cs" /> + <Compile Include="Razor\RoslynCompletionPresenterSession.cs" /> <Compile Include="Util\7BitEncodedInts.cs" /> <Compile Include="Util\ArgumentSyntaxExtensions.cs" /> <Compile Include="Util\CloneableStack.cs" /> @@ -416,6 +404,7 @@ <InternalsVisibleTo Include="MonoDevelop.Unity"> <PublicKey>0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9</PublicKey> </InternalsVisibleTo> + <InternalsVisibleTo Include="WebToolingAddin" /> </ItemGroup> <ItemGroup> <Folder Include="MonoDevelop.CSharp.Features\LineSeparators\" /> diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Parser/CSharpParsedDocument.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Parser/CSharpParsedDocument.cs index 6be03cc78c..faff647bc7 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Parser/CSharpParsedDocument.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Parser/CSharpParsedDocument.cs @@ -517,8 +517,8 @@ namespace MonoDevelop.CSharp.Parser static readonly IReadOnlyList<Error> emptyErrors = Array.Empty<Error> (); public override async Task<IReadOnlyList<Error>> GetErrorsAsync (CancellationToken cancellationToken = default(CancellationToken)) { - //if (Ide.IdeApp.Preferences.EnableSourceAnalysis) - // return emptyErrors; + if (Ide.IdeApp.Preferences.EnableSourceAnalysis) + return emptyErrors; var model = GetAst<SemanticModel> (); if (model == null) diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp/SignatureMarkupCreator.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp/SignatureMarkupCreator.cs index 7e95e4e9ac..7e1fe63c05 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp/SignatureMarkupCreator.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp/SignatureMarkupCreator.cs @@ -51,7 +51,7 @@ using System.Threading; namespace MonoDevelop.CSharp { - class SignatureMarkupCreator + internal class SignatureMarkupCreator { const double optionalAlpha = 0.7; readonly DocumentContext ctx; diff --git a/main/src/addins/CSharpBinding/Razor/ContainedDocumentPreserveFormattingRule.cs b/main/src/addins/CSharpBinding/Razor/ContainedDocumentPreserveFormattingRule.cs new file mode 100644 index 0000000000..cf29b3d373 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/ContainedDocumentPreserveFormattingRule.cs @@ -0,0 +1,34 @@ +using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Formatting.Rules;
+using Microsoft.CodeAnalysis.Options;
+
+namespace Microsoft.VisualStudio.Platform
+{
+ internal class ContainedDocumentPreserveFormattingRule : AbstractFormattingRule
+ {
+ public static readonly IFormattingRule Instance = new ContainedDocumentPreserveFormattingRule ();
+
+ private static readonly AdjustSpacesOperation s_preserveSpace = FormattingOperations.CreateAdjustSpacesOperation (0, AdjustSpacesOption.PreserveSpaces);
+ private static readonly AdjustNewLinesOperation s_preserveLine = FormattingOperations.CreateAdjustNewLinesOperation (0, AdjustNewLinesOption.PreserveLines);
+
+ public override AdjustSpacesOperation GetAdjustSpacesOperation (SyntaxToken previousToken, SyntaxToken currentToken, OptionSet optionSet, NextOperation<AdjustSpacesOperation> nextOperation)
+ {
+ var operation = base.GetAdjustSpacesOperation (previousToken, currentToken, optionSet, nextOperation);
+ if (operation != null) {
+ return s_preserveSpace;
+ }
+
+ return operation;
+ }
+
+ public override AdjustNewLinesOperation GetAdjustNewLinesOperation (SyntaxToken previousToken, SyntaxToken currentToken, OptionSet optionSet, NextOperation<AdjustNewLinesOperation> nextOperation)
+ {
+ var operation = base.GetAdjustNewLinesOperation (previousToken, currentToken, optionSet, nextOperation);
+ if (operation != null) {
+ return s_preserveLine;
+ }
+
+ return operation;
+ }
+ }
+}
diff --git a/main/src/addins/CSharpBinding/Razor/IMonoDevelopContainedLanguageHost.cs b/main/src/addins/CSharpBinding/Razor/IMonoDevelopContainedLanguageHost.cs new file mode 100644 index 0000000000..3f2fbf6ba2 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/IMonoDevelopContainedLanguageHost.cs @@ -0,0 +1,7 @@ +namespace Microsoft.VisualStudio.Platform
+{
+ public interface IMonoDevelopContainedLanguageHost
+ {
+ void GetLineIndent (int lLineNumber, out string indentString, out int parentIndentLevel, out int indentSize, out bool useTabs, out int tabSize);
+ }
+}
diff --git a/main/src/addins/CSharpBinding/Razor/IMyRoslynCompletionDataProvider.cs b/main/src/addins/CSharpBinding/Razor/IMyRoslynCompletionDataProvider.cs new file mode 100644 index 0000000000..e9d26eb234 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/IMyRoslynCompletionDataProvider.cs @@ -0,0 +1,11 @@ +using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Completion;
+using Microsoft.VisualStudio.Text;
+
+namespace MonoDevelop.Ide.CodeCompletion
+{
+ public interface IMyRoslynCompletionDataProvider
+ {
+ MyRoslynCompletionData CreateCompletionData (Document document, ITextSnapshot textSnapshtot, CompletionService completionService, CompletionItem item);
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/Razor/MonoDevelopContainedDocument.cs b/main/src/addins/CSharpBinding/Razor/MonoDevelopContainedDocument.cs new file mode 100644 index 0000000000..00d8f1f685 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/MonoDevelopContainedDocument.cs @@ -0,0 +1,881 @@ +using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.Formatting.Rules;
+using Microsoft.CodeAnalysis.LanguageServices;
+using Microsoft.CodeAnalysis.Options;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Differencing;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
+using Microsoft.VisualStudio.Text.Projection;
+using MonoDevelop.Ide.Composition;
+using MonoDevelop.Ide.TypeSystem;
+using Roslyn.Utilities;
+
+namespace Microsoft.VisualStudio.Platform
+{
+ public class MonoDevelopContainedDocument : IMonoDevelopHostDocument
+ {
+ private const string ReturnReplacementString = @"{|r|}";
+ private const string NewLineReplacementString = @"{|n|}";
+
+ private const char RazorExplicit = '@';
+
+ private const string CSharpRazorBlock = "{";
+ private const string VBRazorBlock = "code";
+
+ private const string HelperRazor = "helper";
+ private const string FunctionsRazor = "functions";
+
+ private static readonly EditOptions s_venusEditOptions = new EditOptions (new StringDifferenceOptions {
+ DifferenceType = StringDifferenceTypes.Character,
+ IgnoreTrimWhiteSpace = false
+ });
+
+ public ITextBuffer LanguageBuffer { get; private set; }
+ public IProjectionBuffer DataBuffer { get; private set; }
+ public IMonoDevelopContainedLanguageHost ContainedLanguageHost { get; private set; }
+
+ // _workspace will only be set once the Workspace is available
+ private Workspace _workspace;
+
+ private readonly ITextDifferencingSelectorService _differenceSelectorService;
+
+ public DocumentId Id { get; private set; }
+
+ // Language will only be set once the Workspace is available
+ private string Language { get; set; }
+
+ public static MonoDevelopContainedDocument FromDocument(Document document)
+ {
+ return MonoDevelopHostDocumentRegistration.FromDocument(document) as MonoDevelopContainedDocument;
+ }
+
+ public static MonoDevelopContainedDocument AttachToBuffer(ITextBuffer languageBuffer, IProjectionBuffer dataBuffer, IMonoDevelopContainedLanguageHost containedLanguageHost)
+ {
+ return new MonoDevelopContainedDocument(languageBuffer, dataBuffer, containedLanguageHost);
+ }
+
+ public static void DetachFromBuffer(ITextBuffer languageBuffer)
+ {
+ var document = languageBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges ();
+ MonoDevelopHostDocumentRegistration.UnRegister (document);
+ }
+
+ private MonoDevelopContainedDocument (ITextBuffer languageBuffer, IProjectionBuffer dataBuffer, IMonoDevelopContainedLanguageHost containedLanguageHost)
+ {
+ LanguageBuffer = languageBuffer;
+ DataBuffer = dataBuffer;
+ ContainedLanguageHost = containedLanguageHost;
+
+ _differenceSelectorService = CompositionManager.GetExportedValue<ITextDifferencingSelectorService> ();
+
+ var container = languageBuffer.CurrentSnapshot.AsText ().Container;
+ var registration = Workspace.GetWorkspaceRegistration (container);
+
+ if (registration.Workspace == null)
+ {
+ registration.WorkspaceChanged += Registration_WorkspaceChanged;
+ }
+ else
+ {
+ FinishInitialization ();
+ }
+ }
+
+ private void Registration_WorkspaceChanged (object sender, EventArgs e)
+ {
+ var container = LanguageBuffer.CurrentSnapshot.AsText ().Container;
+ var registration = Workspace.GetWorkspaceRegistration (container);
+
+ registration.WorkspaceChanged -= Registration_WorkspaceChanged;
+
+ FinishInitialization ();
+ }
+
+ private void FinishInitialization ()
+ {
+ var document = LanguageBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges ();
+ var project = document.Project;
+
+ _workspace = project.Solution.Workspace;
+
+ Language = project.Language;
+ Id = document.Id;
+
+ MonoDevelopHostDocumentRegistration.Register (document, this);
+ }
+
+ public ITextBuffer GetOpenTextBuffer ()
+ {
+ return LanguageBuffer;
+ }
+
+ public void UpdateText (SourceText newText)
+ {
+ if (_workspace == null) {
+ return;
+ }
+
+ var subjectBuffer = (IProjectionBuffer)this.GetOpenTextBuffer ();
+ var originalSnapshot = LanguageBuffer.CurrentSnapshot;
+ var originalText = originalSnapshot.AsText ();
+
+ var changes = newText.GetTextChanges (originalText);
+
+ IEnumerable<int> affectedVisibleSpanIndices = null;
+ var editorVisibleSpansInOriginal = SharedPools.Default<List<TextSpan>> ().AllocateAndClear ();
+
+ try {
+ var originalDocument = _workspace.CurrentSolution.GetDocument (this.Id);
+
+ editorVisibleSpansInOriginal.AddRange (GetEditorVisibleSpans ());
+ var newChanges = FilterTextChanges (originalText, editorVisibleSpansInOriginal, changes).ToList ();
+ if (newChanges.Count == 0) {
+ // no change to apply
+ return;
+ }
+
+ ApplyChanges (subjectBuffer, newChanges, editorVisibleSpansInOriginal, out affectedVisibleSpanIndices);
+ AdjustIndentation (subjectBuffer, affectedVisibleSpanIndices);
+ }
+ finally {
+ SharedPools.Default<HashSet<int>> ().ClearAndFree ((HashSet<int>)affectedVisibleSpanIndices);
+ SharedPools.Default<List<TextSpan>> ().ClearAndFree (editorVisibleSpansInOriginal);
+ }
+ }
+
+ private IEnumerable<TextChange> FilterTextChanges (SourceText originalText, List<TextSpan> editorVisibleSpansInOriginal, IReadOnlyList<TextChange> changes)
+ {
+ // no visible spans or changes
+ if (editorVisibleSpansInOriginal.Count == 0 || changes.Count == 0) {
+ // return empty one
+ yield break;
+ }
+
+ using (var pooledObject = SharedPools.Default<List<TextChange>> ().GetPooledObject ()) {
+ var changeQueue = pooledObject.Object;
+ changeQueue.AddRange (changes);
+
+ var spanIndex = 0;
+ var changeIndex = 0;
+ for (; spanIndex < editorVisibleSpansInOriginal.Count; spanIndex++) {
+ var visibleSpan = editorVisibleSpansInOriginal[spanIndex];
+ var visibleTextSpan = GetVisibleTextSpan (originalText, visibleSpan, uptoFirstAndLastLine: true);
+
+ for (; changeIndex < changeQueue.Count; changeIndex++) {
+ var change = changeQueue[changeIndex];
+
+ // easy case first
+ if (change.Span.End < visibleSpan.Start) {
+ // move to next change
+ continue;
+ }
+
+ if (visibleSpan.End < change.Span.Start) {
+ // move to next visible span
+ break;
+ }
+
+ // make sure we are not replacing whitespace around start and at the end of visible span
+ if (WhitespaceOnEdges (originalText, visibleTextSpan, change)) {
+ continue;
+ }
+
+ if (visibleSpan.Contains (change.Span)) {
+ yield return change;
+ continue;
+ }
+
+ // now it is complex case where things are intersecting each other
+ var subChanges = GetSubTextChanges (originalText, change, visibleSpan).ToList ();
+ if (subChanges.Count > 0) {
+ if (subChanges.Count == 1 && subChanges[0] == change) {
+ // we can't break it. not much we can do here. just don't touch and ignore this change
+ continue;
+ }
+
+ changeQueue.InsertRange (changeIndex + 1, subChanges);
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+ private bool WhitespaceOnEdges (SourceText text, TextSpan visibleTextSpan, TextChange change)
+ {
+ if (!string.IsNullOrWhiteSpace (change.NewText)) {
+ return false;
+ }
+
+ if (change.Span.End <= visibleTextSpan.Start) {
+ return true;
+ }
+
+ if (visibleTextSpan.End <= change.Span.Start) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private IEnumerable<TextChange> GetSubTextChanges (SourceText originalText, TextChange changeInOriginalText, TextSpan visibleSpanInOriginalText)
+ {
+ using (var changes = SharedPools.Default<List<TextChange>> ().GetPooledObject ()) {
+ var leftText = originalText.ToString (changeInOriginalText.Span);
+ var rightText = changeInOriginalText.NewText;
+ var offsetInOriginalText = changeInOriginalText.Span.Start;
+
+ if (TryGetSubTextChanges (originalText, visibleSpanInOriginalText, leftText, rightText, offsetInOriginalText, changes.Object)) {
+ return changes.Object.ToList ();
+ }
+
+ return GetSubTextChanges (originalText, visibleSpanInOriginalText, leftText, rightText, offsetInOriginalText);
+ }
+ }
+
+ private bool TryGetSubTextChanges (
+ SourceText originalText, TextSpan visibleSpanInOriginalText, string leftText, string rightText, int offsetInOriginalText, List<TextChange> changes)
+ {
+ // these are expensive. but hopefully we don't hit this as much except the boundary cases.
+ using (var leftPool = SharedPools.Default<List<TextSpan>> ().GetPooledObject ())
+ using (var rightPool = SharedPools.Default<List<TextSpan>> ().GetPooledObject ()) {
+ var spansInLeftText = leftPool.Object;
+ var spansInRightText = rightPool.Object;
+
+ if (TryGetWhitespaceOnlyChanges (leftText, rightText, spansInLeftText, spansInRightText)) {
+ for (var i = 0; i < spansInLeftText.Count; i++) {
+ var spanInLeftText = spansInLeftText[i];
+ var spanInRightText = spansInRightText[i];
+ if (spanInLeftText.IsEmpty && spanInRightText.IsEmpty) {
+ continue;
+ }
+
+ var spanInOriginalText = new TextSpan (offsetInOriginalText + spanInLeftText.Start, spanInLeftText.Length);
+ if (TryGetSubTextChange (originalText, visibleSpanInOriginalText, rightText, spanInOriginalText, spanInRightText, out var textChange)) {
+ changes.Add (textChange);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ private IEnumerable<TextChange> GetSubTextChanges (
+ SourceText originalText, TextSpan visibleSpanInOriginalText, string leftText, string rightText, int offsetInOriginalText)
+ {
+ // these are expensive. but hopefully we don't hit this as much except the boundary cases.
+ using (var leftPool = SharedPools.Default<List<ValueTuple<int, int>>> ().GetPooledObject ())
+ using (var rightPool = SharedPools.Default<List<ValueTuple<int, int>>> ().GetPooledObject ()) {
+ var leftReplacementMap = leftPool.Object;
+ var rightReplacementMap = rightPool.Object;
+ GetTextWithReplacements (leftText, rightText, leftReplacementMap, rightReplacementMap, out var leftTextWithReplacement, out var rightTextWithReplacement);
+
+ var diffResult = DiffStrings (leftTextWithReplacement, rightTextWithReplacement);
+
+ foreach (var difference in diffResult) {
+ var spanInLeftText = AdjustSpan (diffResult.LeftDecomposition.GetSpanInOriginal (difference.Left), leftReplacementMap);
+ var spanInRightText = AdjustSpan (diffResult.RightDecomposition.GetSpanInOriginal (difference.Right), rightReplacementMap);
+
+ var spanInOriginalText = new TextSpan (offsetInOriginalText + spanInLeftText.Start, spanInLeftText.Length);
+ if (TryGetSubTextChange (originalText, visibleSpanInOriginalText, rightText, spanInOriginalText, spanInRightText.ToTextSpan (), out var textChange)) {
+ yield return textChange;
+ }
+ }
+ }
+ }
+
+ private bool TryGetWhitespaceOnlyChanges (string leftText, string rightText, List<TextSpan> spansInLeftText, List<TextSpan> spansInRightText)
+ {
+ return TryGetWhitespaceGroup (leftText, spansInLeftText) && TryGetWhitespaceGroup (rightText, spansInRightText) && spansInLeftText.Count == spansInRightText.Count;
+ }
+
+ private bool TryGetWhitespaceGroup (string text, List<TextSpan> groups)
+ {
+ if (text.Length == 0) {
+ groups.Add (new TextSpan (0, 0));
+ return true;
+ }
+
+ var start = 0;
+ for (var i = 0; i < text.Length; i++) {
+ var ch = text[i];
+ switch (ch) {
+ case ' ':
+ if (!TextAt (text, i - 1, ' ')) {
+ start = i;
+ }
+
+ break;
+
+ case '\r':
+ case '\n':
+ if (i == 0) {
+ groups.Add (TextSpan.FromBounds (0, 0));
+ }
+ else if (TextAt (text, i - 1, ' ')) {
+ groups.Add (TextSpan.FromBounds (start, i));
+ }
+ else if (TextAt (text, i - 1, '\n')) {
+ groups.Add (TextSpan.FromBounds (start, i));
+ }
+
+ start = i + 1;
+ break;
+
+ default:
+ return false;
+ }
+ }
+
+ if (start <= text.Length) {
+ groups.Add (TextSpan.FromBounds (start, text.Length));
+ }
+
+ return true;
+ }
+
+ private bool TextAt (string text, int index, char ch)
+ {
+ if (index < 0 || text.Length <= index) {
+ return false;
+ }
+
+ return text[index] == ch;
+ }
+
+ private bool TryGetSubTextChange (
+ SourceText originalText, TextSpan visibleSpanInOriginalText,
+ string rightText, TextSpan spanInOriginalText, TextSpan spanInRightText, out TextChange textChange)
+ {
+ textChange = default(TextChange);
+
+ var visibleFirstLineInOriginalText = originalText.Lines.GetLineFromPosition (visibleSpanInOriginalText.Start);
+ var visibleLastLineInOriginalText = originalText.Lines.GetLineFromPosition (visibleSpanInOriginalText.End);
+
+ // skip easy case
+ // 1. things are out of visible span
+ if (!visibleSpanInOriginalText.IntersectsWith (spanInOriginalText)) {
+ return false;
+ }
+
+ // 2. there are no intersects
+ var snippetInRightText = rightText.Substring (spanInRightText.Start, spanInRightText.Length);
+ if (visibleSpanInOriginalText.Contains (spanInOriginalText) && visibleSpanInOriginalText.End != spanInOriginalText.End) {
+ textChange = new TextChange (spanInOriginalText, snippetInRightText);
+ return true;
+ }
+
+ // okay, more complex case. things are intersecting boundaries.
+ var firstLineOfRightTextSnippet = snippetInRightText.GetFirstLineText ();
+ var lastLineOfRightTextSnippet = snippetInRightText.GetLastLineText ();
+
+ // there are 4 complex cases - these are all heuristic. not sure what better way I have. and the heuristic is heavily based on
+ // text differ's behavior.
+
+ // 1. it is a single line
+ if (visibleFirstLineInOriginalText.LineNumber == visibleLastLineInOriginalText.LineNumber) {
+ // don't do anything
+ return false;
+ }
+
+ // 2. replacement contains visible spans
+ if (spanInOriginalText.Contains (visibleSpanInOriginalText)) {
+ // header
+ // don't do anything
+
+ // body
+ textChange = new TextChange (
+ TextSpan.FromBounds (visibleFirstLineInOriginalText.EndIncludingLineBreak, visibleLastLineInOriginalText.Start),
+ snippetInRightText.Substring (firstLineOfRightTextSnippet.Length, snippetInRightText.Length - firstLineOfRightTextSnippet.Length - lastLineOfRightTextSnippet.Length));
+
+ // footer
+ // don't do anything
+
+ return true;
+ }
+
+ // 3. replacement intersects with start
+ if (spanInOriginalText.Start < visibleSpanInOriginalText.Start &&
+ visibleSpanInOriginalText.Start <= spanInOriginalText.End &&
+ spanInOriginalText.End < visibleSpanInOriginalText.End) {
+ // header
+ // don't do anything
+
+ // body
+ if (visibleFirstLineInOriginalText.EndIncludingLineBreak <= spanInOriginalText.End) {
+ textChange = new TextChange (
+ TextSpan.FromBounds (visibleFirstLineInOriginalText.EndIncludingLineBreak, spanInOriginalText.End),
+ snippetInRightText.Substring (firstLineOfRightTextSnippet.Length));
+ return true;
+ }
+
+ return false;
+ }
+
+ // 4. replacement intersects with end
+ if (visibleSpanInOriginalText.Start < spanInOriginalText.Start &&
+ spanInOriginalText.Start <= visibleSpanInOriginalText.End &&
+ visibleSpanInOriginalText.End <= spanInOriginalText.End) {
+ // body
+ if (spanInOriginalText.Start <= visibleLastLineInOriginalText.Start) {
+ textChange = new TextChange (
+ TextSpan.FromBounds (spanInOriginalText.Start, visibleLastLineInOriginalText.Start),
+ snippetInRightText.Substring (0, snippetInRightText.Length - lastLineOfRightTextSnippet.Length));
+ return true;
+ }
+
+ // footer
+ // don't do anything
+
+ return false;
+ }
+
+ // if it got hit, then it means there is a missing case
+ throw ExceptionUtilities.Unreachable;
+ }
+
+ private IHierarchicalDifferenceCollection DiffStrings (string leftTextWithReplacement, string rightTextWithReplacement)
+ {
+ var document = LanguageBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges (); ;
+
+ var diffService = _differenceSelectorService.GetTextDifferencingService (
+ _workspace.Services.GetLanguageServices (document.Project.Language).GetService<IContentTypeLanguageService> ().GetDefaultContentType ());
+
+ diffService = diffService ?? _differenceSelectorService.DefaultTextDifferencingService;
+ return diffService.DiffStrings (leftTextWithReplacement, rightTextWithReplacement, s_venusEditOptions.DifferenceOptions);
+ }
+
+ private void GetTextWithReplacements (
+ string leftText, string rightText,
+ List<ValueTuple<int, int>> leftReplacementMap, List<ValueTuple<int, int>> rightReplacementMap,
+ out string leftTextWithReplacement, out string rightTextWithReplacement)
+ {
+ // to make diff works better, we choose replacement strings that don't appear in both texts.
+ var returnReplacement = GetReplacementStrings (leftText, rightText, ReturnReplacementString);
+ var newLineReplacement = GetReplacementStrings (leftText, rightText, NewLineReplacementString);
+
+ leftTextWithReplacement = GetTextWithReplacementMap (leftText, returnReplacement, newLineReplacement, leftReplacementMap);
+ rightTextWithReplacement = GetTextWithReplacementMap (rightText, returnReplacement, newLineReplacement, rightReplacementMap);
+ }
+
+ private static string GetReplacementStrings (string leftText, string rightText, string initialReplacement)
+ {
+ if (leftText.IndexOf (initialReplacement, StringComparison.Ordinal) < 0 && rightText.IndexOf (initialReplacement, StringComparison.Ordinal) < 0) {
+ return initialReplacement;
+ }
+
+ // okay, there is already one in the given text.
+ const string format = "{{|{0}|{1}|{0}|}}";
+ for (var i = 0; true; i++) {
+ var replacement = string.Format (format, i.ToString (), initialReplacement);
+ if (leftText.IndexOf (replacement, StringComparison.Ordinal) < 0 && rightText.IndexOf (replacement, StringComparison.Ordinal) < 0) {
+ return replacement;
+ }
+ }
+ }
+
+ private string GetTextWithReplacementMap (string text, string returnReplacement, string newLineReplacement, List<ValueTuple<int, int>> replacementMap)
+ {
+ var delta = 0;
+ var returnLength = returnReplacement.Length;
+ var newLineLength = newLineReplacement.Length;
+
+ var sb = StringBuilderPool.Allocate ();
+ for (var i = 0; i < text.Length; i++) {
+ var ch = text[i];
+ if (ch == '\r') {
+ sb.Append (returnReplacement);
+ delta += returnLength - 1;
+ replacementMap.Add (ValueTuple.Create (i + delta, delta));
+ continue;
+ }
+ else if (ch == '\n') {
+ sb.Append (newLineReplacement);
+ delta += newLineLength - 1;
+ replacementMap.Add (ValueTuple.Create (i + delta, delta));
+ continue;
+ }
+
+ sb.Append (ch);
+ }
+
+ return StringBuilderPool.ReturnAndFree (sb);
+ }
+
+ private Span AdjustSpan (Span span, List<ValueTuple<int, int>> replacementMap)
+ {
+ var start = span.Start;
+ var end = span.End;
+
+ for (var i = 0; i < replacementMap.Count; i++) {
+ var positionAndDelta = replacementMap[i];
+ if (positionAndDelta.Item1 <= span.Start) {
+ start = span.Start - positionAndDelta.Item2;
+ }
+
+ if (positionAndDelta.Item1 <= span.End) {
+ end = span.End - positionAndDelta.Item2;
+ }
+
+ if (positionAndDelta.Item1 > span.End) {
+ break;
+ }
+ }
+
+ return Span.FromBounds (start, end);
+ }
+
+ public IEnumerable<TextSpan> GetEditorVisibleSpans ()
+ {
+ return DataBuffer.CurrentSnapshot
+ .GetSourceSpans ()
+ .Where (ss => ss.Snapshot.TextBuffer == LanguageBuffer)
+ .Select (s => s.Span.ToTextSpan ())
+ .OrderBy (s => s.Start);
+ }
+
+ private static void ApplyChanges (
+ IProjectionBuffer subjectBuffer,
+ IEnumerable<TextChange> changes,
+ IList<TextSpan> visibleSpansInOriginal,
+ out IEnumerable<int> affectedVisibleSpansInNew)
+ {
+ using (var edit = subjectBuffer.CreateEdit (s_venusEditOptions, reiteratedVersionNumber: null, editTag: null)) {
+ var affectedSpans = SharedPools.Default<HashSet<int>> ().AllocateAndClear ();
+ affectedVisibleSpansInNew = affectedSpans;
+
+ var currentVisibleSpanIndex = 0;
+ foreach (var change in changes) {
+ // Find the next visible span that either overlaps or intersects with
+ while (currentVisibleSpanIndex < visibleSpansInOriginal.Count &&
+ visibleSpansInOriginal[currentVisibleSpanIndex].End < change.Span.Start) {
+ currentVisibleSpanIndex++;
+ }
+
+ // no more place to apply text changes
+ if (currentVisibleSpanIndex >= visibleSpansInOriginal.Count) {
+ break;
+ }
+
+ var newText = change.NewText;
+ var span = new Span(change.Span.Start, change.Span.Length);
+
+ edit.Replace (span, newText);
+
+ affectedSpans.Add (currentVisibleSpanIndex);
+ }
+
+ edit.ApplyAndLogExceptions ();
+ }
+ }
+
+ private void AdjustIndentation (IProjectionBuffer subjectBuffer, IEnumerable<int> visibleSpanIndex)
+ {
+ if (!visibleSpanIndex.Any ()) {
+ return;
+ }
+
+ var snapshot = subjectBuffer.CurrentSnapshot;
+ var document = _workspace.CurrentSolution.GetDocument (this.Id);
+ if (!document.SupportsSyntaxTree) {
+ return;
+ }
+
+ var originalText = document.GetTextAsync (CancellationToken.None).WaitAndGetResult (CancellationToken.None);
+ Contract.Requires (object.ReferenceEquals (originalText, snapshot.AsText ()));
+
+ var root = document.GetSyntaxRootSynchronously (CancellationToken.None);
+
+ var editorOptionsFactory = CompositionManager.GetExportedValue<IEditorOptionsFactoryService> ();
+ var editorOptions = editorOptionsFactory.GetOptions (DataBuffer);
+ var options = _workspace.Options
+ .WithChangedOption (FormattingOptions.UseTabs, root.Language, !editorOptions.IsConvertTabsToSpacesEnabled ())
+ .WithChangedOption (FormattingOptions.TabSize, root.Language, editorOptions.GetTabSize ())
+ .WithChangedOption (FormattingOptions.IndentationSize, root.Language, editorOptions.GetIndentSize ());
+
+ using (var pooledObject = SharedPools.Default<List<TextSpan>> ().GetPooledObject ()) {
+ var spans = pooledObject.Object;
+
+ spans.AddRange (this.GetEditorVisibleSpans ());
+ using (var edit = subjectBuffer.CreateEdit (s_venusEditOptions, reiteratedVersionNumber: null, editTag: null)) {
+ foreach (var spanIndex in visibleSpanIndex) {
+ var rule = GetBaseIndentationRule (root, originalText, spans, spanIndex);
+
+ var visibleSpan = spans[spanIndex];
+ AdjustIndentationForSpan (document, edit, visibleSpan, rule, options);
+ }
+
+ edit.Apply ();
+ }
+ }
+ }
+
+ private void AdjustIndentationForSpan (
+ Document document, ITextEdit edit, TextSpan visibleSpan, IFormattingRule baseIndentationRule, OptionSet options)
+ {
+ var root = document.GetSyntaxRootSynchronously (CancellationToken.None);
+
+ using (var rulePool = SharedPools.Default<List<IFormattingRule>> ().GetPooledObject ())
+ using (var spanPool = SharedPools.Default<List<TextSpan>> ().GetPooledObject ()) {
+ var venusFormattingRules = rulePool.Object;
+ var visibleSpans = spanPool.Object;
+
+ venusFormattingRules.Add (baseIndentationRule);
+ venusFormattingRules.Add (ContainedDocumentPreserveFormattingRule.Instance);
+
+ var formattingRules = venusFormattingRules.Concat (Formatter.GetDefaultFormattingRules (document));
+
+ var workspace = document.Project.Solution.Workspace;
+ var changes = Formatter.GetFormattedTextChanges (
+ root, new TextSpan[] { CommonFormattingHelpers.GetFormattingSpan (root, visibleSpan) },
+ workspace, options, formattingRules, CancellationToken.None);
+
+ visibleSpans.Add (visibleSpan);
+ var newChanges = FilterTextChanges (document.GetTextAsync (CancellationToken.None).WaitAndGetResult (CancellationToken.None), visibleSpans, new ReadOnlyCollection<TextChange>(changes)).Where (t => visibleSpan.Contains (t.Span));
+
+ foreach (var change in newChanges) {
+ edit.Replace (new Span(change.Span.Start, change.Span.Length), change.NewText);
+ }
+ }
+ }
+
+ internal BaseIndentationFormattingRule GetBaseIndentationRule (SyntaxNode root, SourceText text, List<TextSpan> spans, int spanIndex)
+ {
+ var currentSpanIndex = spanIndex;
+ GetVisibleAndTextSpan (text, spans, currentSpanIndex, out var visibleSpan, out var visibleTextSpan);
+
+ var end = visibleSpan.End;
+ var current = root.FindToken (visibleTextSpan.Start).Parent;
+ while (current != null) {
+ if (current.Span.Start == visibleTextSpan.Start) {
+ var blockType = GetRazorCodeBlockType (visibleSpan.Start);
+ if (blockType == RazorCodeBlockType.Explicit) {
+ var baseIndentation = GetBaseIndentation (root, text, visibleSpan);
+ return new BaseIndentationFormattingRule (root, TextSpan.FromBounds (visibleSpan.Start, end), baseIndentation);
+ }
+ }
+
+ if (current.Span.Start < visibleSpan.Start) {
+ var blockType = GetRazorCodeBlockType (visibleSpan.Start);
+ if (blockType == RazorCodeBlockType.Block || blockType == RazorCodeBlockType.Helper) {
+ var baseIndentation = GetBaseIndentation (root, text, visibleSpan);
+ return new BaseIndentationFormattingRule (root, TextSpan.FromBounds (visibleSpan.Start, end), baseIndentation);
+ }
+
+ if (currentSpanIndex == 0) {
+ break;
+ }
+
+ GetVisibleAndTextSpan (text, spans, --currentSpanIndex, out visibleSpan, out visibleTextSpan);
+ continue;
+ }
+
+ current = current.Parent;
+ }
+
+ var span = spans[spanIndex];
+ var indentation = GetBaseIndentation (root, text, span);
+ return new BaseIndentationFormattingRule (root, span, indentation);
+ }
+
+ private void GetVisibleAndTextSpan (SourceText text, List<TextSpan> spans, int spanIndex, out TextSpan visibleSpan, out TextSpan visibleTextSpan)
+ {
+ visibleSpan = spans[spanIndex];
+
+ visibleTextSpan = GetVisibleTextSpan (text, visibleSpan);
+ if (visibleTextSpan.IsEmpty) {
+ // span has no text in them
+ visibleTextSpan = visibleSpan;
+ }
+ }
+
+ private int GetBaseIndentation (SyntaxNode root, SourceText text, TextSpan span)
+ {
+ // Is this right? We should probably get this from the IVsContainedLanguageHost instead.
+ var editorOptionsFactory = CompositionManager.GetExportedValue<IEditorOptionsFactoryService> ();
+ var editorOptions = editorOptionsFactory.GetOptions (DataBuffer);
+
+ var additionalIndentation = GetAdditionalIndentation (root, text, span);
+
+ // Skip over the first line, since it's in "Venus space" anyway.
+ var startingLine = text.Lines.GetLineFromPosition (span.Start);
+ for (var line = startingLine; line.Start < span.End; line = text.Lines[line.LineNumber + 1]) {
+ ContainedLanguageHost.GetLineIndent (
+ line.LineNumber,
+ out var baseIndentationString,
+ out var parent,
+ out var indentSize,
+ out var useTabs,
+ out var tabSize);
+
+ if (!string.IsNullOrEmpty (baseIndentationString)) {
+ return baseIndentationString.GetColumnFromLineOffset (baseIndentationString.Length, editorOptions.GetTabSize ()) + additionalIndentation;
+ }
+ }
+
+ return additionalIndentation;
+ }
+
+ private TextSpan GetVisibleTextSpan (SourceText text, TextSpan visibleSpan, bool uptoFirstAndLastLine = false)
+ {
+ var start = visibleSpan.Start;
+ for (; start < visibleSpan.End; start++) {
+ if (!char.IsWhiteSpace (text[start])) {
+ break;
+ }
+ }
+
+ var end = visibleSpan.End - 1;
+ if (start <= end) {
+ for (; start <= end; end--) {
+ if (!char.IsWhiteSpace (text[end])) {
+ break;
+ }
+ }
+ }
+
+ if (uptoFirstAndLastLine) {
+ var firstLine = text.Lines.GetLineFromPosition (visibleSpan.Start);
+ var lastLine = text.Lines.GetLineFromPosition (visibleSpan.End);
+
+ if (firstLine.LineNumber < lastLine.LineNumber) {
+ start = (start < firstLine.End) ? start : firstLine.End;
+ end = (lastLine.Start < end + 1) ? end : lastLine.Start - 1;
+ }
+ }
+
+ return (start <= end) ? TextSpan.FromBounds (start, end + 1) : default (TextSpan);
+ }
+
+ private int GetAdditionalIndentation (SyntaxNode root, SourceText text, TextSpan span)
+ {
+ var type = GetRazorCodeBlockType (span.Start);
+
+ // razor block
+ if (type == RazorCodeBlockType.Block) {
+ if (_workspace == null) {
+ // this can happen if the workspace creation isn't yet completed
+ return 0;
+ }
+
+ // more workaround for csharp razor case. when } for csharp razor code block is just typed, "}" exist
+ // in both subject and surface buffer and there is no easy way to figure out who owns } just typed.
+ // in this case, we let razor owns it. later razor will remove } from subject buffer if it is something
+ // razor owns.
+ var textSpan = GetVisibleTextSpan (text, span);
+ var end = textSpan.End - 1;
+ if (end >= 0 && text[end] == '}') {
+ var token = root.FindToken (end);
+ var syntaxFact = _workspace.Services.GetLanguageServices (Language).GetService<ISyntaxFactsService> ();
+ if (token.Span.Start == end && syntaxFact != null) {
+ if (syntaxFact.TryGetCorrespondingOpenBrace (token, out var openBrace) && !textSpan.Contains (openBrace.Span)) {
+ return 0;
+ }
+ }
+ }
+
+ return _workspace.Options.GetOption (FormattingOptions.IndentationSize, Language);
+ }
+
+ return 0;
+ }
+
+ private RazorCodeBlockType GetRazorCodeBlockType (int position)
+ {
+ var subjectBuffer = (IProjectionBuffer)LanguageBuffer;
+ var subjectSnapshot = subjectBuffer.CurrentSnapshot;
+ var surfaceSnapshot = DataBuffer.CurrentSnapshot;
+
+ var surfacePoint = surfaceSnapshot.MapFromSourceSnapshot (new SnapshotPoint (subjectSnapshot, position), PositionAffinity.Predecessor);
+ if (!surfacePoint.HasValue) {
+ // how this can happen?
+ return RazorCodeBlockType.Implicit;
+ }
+
+ var ch = char.ToLower (surfaceSnapshot[Math.Max (surfacePoint.Value - 1, 0)]);
+
+ // razor block
+ if (IsCodeBlock (surfaceSnapshot, surfacePoint.Value, ch)) {
+ return RazorCodeBlockType.Block;
+ }
+
+ if (ch == RazorExplicit) {
+ return RazorCodeBlockType.Explicit;
+ }
+
+ if (CheckCode (surfaceSnapshot, surfacePoint.Value, HelperRazor)) {
+ return RazorCodeBlockType.Helper;
+ }
+
+ return RazorCodeBlockType.Implicit;
+ }
+
+ private bool IsCodeBlock (ITextSnapshot surfaceSnapshot, int position, char ch)
+ {
+ return CheckCode (surfaceSnapshot, position, ch, CSharpRazorBlock) ||
+ CheckCode (surfaceSnapshot, position, ch, FunctionsRazor, CSharpRazorBlock);
+ }
+
+ private bool CheckCode (ITextSnapshot snapshot, int position, char ch, string tag, bool checkAt = true)
+ {
+ if (ch != tag[tag.Length - 1] || position < tag.Length) {
+ return false;
+ }
+
+ var start = position - tag.Length;
+ var razorTag = snapshot.GetText (start, tag.Length);
+ return string.Equals (razorTag, tag, StringComparison.OrdinalIgnoreCase) && (!checkAt || snapshot[start - 1] == RazorExplicit);
+ }
+
+ private bool CheckCode (ITextSnapshot snapshot, int position, string tag)
+ {
+ int i = position - 1;
+ if (i < 0) {
+ return false;
+ }
+
+ for (; i >= 0; i--) {
+ if (!char.IsWhiteSpace (snapshot[i])) {
+ break;
+ }
+ }
+
+ var ch = snapshot[i];
+ position = i + 1;
+
+ return CheckCode (snapshot, position, ch, tag);
+ }
+
+ private bool CheckCode (ITextSnapshot snapshot, int position, char ch, string tag1, string tag2)
+ {
+ if (!CheckCode (snapshot, position, ch, tag2, checkAt: false)) {
+ return false;
+ }
+
+ return CheckCode (snapshot, position - tag2.Length, tag1);
+ }
+
+ private enum RazorCodeBlockType
+ {
+ Block,
+ Explicit,
+ Implicit,
+ Helper
+ }
+ }
+}
diff --git a/main/src/addins/CSharpBinding/Razor/MonoDevelopFormattingRuleFactoryServiceFactory.cs b/main/src/addins/CSharpBinding/Razor/MonoDevelopFormattingRuleFactoryServiceFactory.cs new file mode 100644 index 0000000000..5c69695620 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/MonoDevelopFormattingRuleFactoryServiceFactory.cs @@ -0,0 +1,91 @@ +using System;
+using System.Collections.Generic;
+using System.Composition;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Formatting.Rules;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Host.Mef;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Microsoft.VisualStudio.Platform
+{
+ [ExportWorkspaceServiceFactory (typeof (IHostDependentFormattingRuleFactoryService), ServiceLayer.Host), Shared]
+ internal sealed class MonoDevelopFormattingRuleFactoryServiceFactory : IWorkspaceServiceFactory
+ {
+ public MonoDevelopFormattingRuleFactoryServiceFactory ()
+ {
+ }
+
+ public IWorkspaceService CreateService (HostWorkspaceServices workspaceServices)
+ {
+ return new Factory ();
+ }
+
+ private sealed class Factory : IHostDependentFormattingRuleFactoryService
+ {
+ private readonly IFormattingRule _noopRule = new NoOpFormattingRule ();
+
+ public bool ShouldUseBaseIndentation (Document document)
+ {
+ return IsContainedDocument (document);
+ }
+
+ public bool ShouldNotFormatOrCommitOnPaste (Document document)
+ {
+ return IsContainedDocument (document);
+ }
+
+ private bool IsContainedDocument (Document document)
+ {
+ MonoDevelopContainedDocument containedDocument = MonoDevelopContainedDocument.FromDocument (document);
+
+ return (containedDocument != null);
+ }
+
+ public IFormattingRule CreateRule (Document document, int position)
+ {
+ MonoDevelopContainedDocument containedDocument = MonoDevelopContainedDocument.FromDocument (document);
+ if (containedDocument == null)
+ {
+ return _noopRule;
+ }
+
+ List<TextSpan> spans = new List<TextSpan>();
+
+ var root = document.GetSyntaxRootSynchronously (CancellationToken.None);
+ var text = root.SyntaxTree.GetText (CancellationToken.None);
+
+ spans.AddRange (containedDocument.GetEditorVisibleSpans ());
+
+ for (var i = 0; i < spans.Count; i++) {
+ var visibleSpan = spans[i];
+ if (visibleSpan.IntersectsWith (position) || visibleSpan.End == position) {
+ return containedDocument.GetBaseIndentationRule (root, text, spans, i);
+ }
+ }
+
+ // in razor (especially in @helper tag), it is possible for us to be asked for next line of visible span
+ var line = text.Lines.GetLineFromPosition (position);
+ if (line.LineNumber > 0) {
+ line = text.Lines[line.LineNumber - 1];
+
+ // find one that intersects with previous line
+ for (var i = 0; i < spans.Count; i++) {
+ var visibleSpan = spans[i];
+ if (visibleSpan.IntersectsWith (line.Span)) {
+ return containedDocument.GetBaseIndentationRule (root, text, spans, i);
+ }
+ }
+ }
+
+ throw new InvalidOperationException ();
+ }
+
+ public IEnumerable<TextChange> FilterFormattedChanges (Document document, TextSpan span, IList<TextChange> changes)
+ {
+ return changes;
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/Razor/MyCSharpCompletionData.cs b/main/src/addins/CSharpBinding/Razor/MyCSharpCompletionData.cs new file mode 100644 index 0000000000..2687919e92 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/MyCSharpCompletionData.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Completion;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Utilities;
+using MonoDevelop.Core;
+using MonoDevelop.Ide.Editor;
+
+namespace MonoDevelop.Ide.CodeCompletion
+{
+ public class MyCSharpCompletionData : MyRoslynCompletionData
+ {
+ public MyCSharpCompletionData (Microsoft.CodeAnalysis.Document document, ITextSnapshot triggerSnapshot, CompletionService completionService, CompletionItem completionItem) :
+ base (document, triggerSnapshot, completionService, completionItem)
+ {
+ }
+
+ protected override string MimeType => "text/csharp";
+
+ protected override void Format (TextEditor editor, Gui.Document document, SnapshotPoint start, SnapshotPoint end)
+ {
+ return;
+ //MonoDevelop.CSharp.Formatting.OnTheFlyFormatter.Format (editor, document, start, end);
+ }
+
+ public override IconId Icon {
+ get {
+ IconId result = base.Icon;
+ if (!result.IsNull) {
+ return result;
+ }
+
+ var modifier = GetItemModifier ();
+ var type = GetItemType ();
+ return "md-" + modifier + type;
+ }
+ }
+
+ static Dictionary<string, string> roslynCompletionTypeTable = new Dictionary<string, string> {
+ { "Field", "field" },
+ { "Alias", "field" },
+ { "ArrayType", "field" },
+ { "Assembly", "field" },
+ { "DynamicType", "field" },
+ { "ErrorType", "field" },
+ { "Label", "field" },
+ { "NetModule", "field" },
+ { "PointerType", "field" },
+ { "RangeVariable", "field" },
+ { "TypeParameter", "field" },
+ { "Preprocessing", "field" },
+
+ { "Constant", "literal" },
+
+ { "Parameter", "variable" },
+ { "Local", "variable" },
+
+ { "Method", "method" },
+
+ { "Namespace", "name-space" },
+
+ { "Property", "property" },
+
+ { "Event", "event" },
+
+ { "Class", "class" },
+
+ { "Delegate", "delegate" },
+
+ { "Enum", "enum" },
+
+ { "Interface", "interface" },
+
+ { "Struct", "struct" },
+ { "Structure", "struct" },
+
+ { "Keyword", "keyword" },
+
+ { "Snippet", "template"},
+
+ { "EnumMember", "literal" },
+
+ { "NewMethod", "newmethod" }
+ };
+
+ string GetItemType ()
+ {
+ foreach (var tag in CompletionItem.Tags) {
+ if (roslynCompletionTypeTable.TryGetValue (tag, out string result))
+ return result;
+ }
+ LoggingService.LogWarning ("RoslynCompletionData: Can't find item type '" + string.Join (",", CompletionItem.Tags) + "'");
+ return "literal";
+ }
+
+ static Dictionary<string, string> modifierTypeTable = new Dictionary<string, string> {
+ { "Private", "private-" },
+ { "ProtectedAndInternal", "ProtectedOrInternal-" },
+ { "Protected", "protected-" },
+ { "Internal", "internal-" },
+ { "ProtectedOrInternal", "ProtectedOrInternal-" }
+ };
+
+ string GetItemModifier ()
+ {
+ foreach (var tag in CompletionItem.Tags) {
+ if (modifierTypeTable.TryGetValue (tag, out string result))
+ return result;
+ }
+ return "";
+ }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/Razor/MyCSharpCompletionDataProvider.cs b/main/src/addins/CSharpBinding/Razor/MyCSharpCompletionDataProvider.cs new file mode 100644 index 0000000000..19279eb342 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/MyCSharpCompletionDataProvider.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.Composition;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Completion;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Utilities;
+using MonoDevelop.Ide.Editor;
+
+namespace MonoDevelop.Ide.CodeCompletion
+{
+ [Export (typeof (IMyRoslynCompletionDataProvider))]
+ [ContentType ("CSharp")]
+ public class MyCSharpCompletionDataProvider : IMyRoslynCompletionDataProvider
+ {
+ public MyRoslynCompletionData CreateCompletionData (Document document, ITextSnapshot textSnapshtot, CompletionService completionService, CompletionItem item)
+ {
+ return new MyCSharpCompletionData (document, textSnapshtot, completionService, item);
+ }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/Razor/MyRoslynCompletionData.cs b/main/src/addins/CSharpBinding/Razor/MyRoslynCompletionData.cs new file mode 100644 index 0000000000..8a1a43cc88 --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/MyRoslynCompletionData.cs @@ -0,0 +1,188 @@ +//
+// RoslynCompletionData.cs
+//
+// Author:
+// Mike Krüger <mikkrg@microsoft.com>
+//
+// Copyright (c) 2017 Microsoft Corporation
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Text; +using MonoDevelop.Core; +using MonoDevelop.Ide.CodeCompletion; +using MonoDevelop.Ide.Editor; +using MonoDevelop.Ide.Editor.Extension; +using MonoDevelop.Ide; +using MonoDevelop.Ide.Gui; +using Microsoft.VisualStudio.Platform; +using Microsoft.VisualStudio.Text; +using MonoDevelop.Ide.CodeTemplates; +using System.Linq; +using System.Text; +using MonoDevelop.Ide.Editor.Highlighting; +using MonoDevelop.Ide.Fonts; + +namespace MonoDevelop.Ide.CodeCompletion +{ + public abstract class MyRoslynCompletionData : CompletionData + { + protected readonly Microsoft.CodeAnalysis.Document doc; + protected readonly ITextSnapshot triggerSnapshot; + protected readonly CompletionService completionService; + + public CompletionItem CompletionItem { get; private set; } + + public CompletionItemRules Rules { get { return CompletionItem.Rules; } } + + public override string DisplayText { + get { + return CompletionItem.DisplayText; + } + } + + public override string Description { + get { + var description = completionService.GetDescriptionAsync (doc, CompletionItem).Result; + return description.Text; + } + } + + public override string CompletionText { + get { + return CompletionItem.DisplayText; + } + } + + protected abstract string MimeType { get; } + + public override IconId Icon { + get { + if (CompletionItem.Tags.Contains ("Snippet")) { + var template = CodeTemplateService.GetCodeTemplates (MimeType).FirstOrDefault (t => t.Shortcut == CompletionItem.DisplayText); + if (template != null) + return template.Icon; + } + + return null; + } + } + + public MyRoslynCompletionData (Microsoft.CodeAnalysis.Document document, ITextSnapshot triggerSnapshot, CompletionService completionService, CompletionItem completionItem) + { + this.doc = document; + this.triggerSnapshot = triggerSnapshot; + this.completionService = completionService; + CompletionItem = completionItem; + } + + public override string GetDisplayDescription (bool isSelected) + { + if (CompletionItem.Properties.TryGetValue ("DescriptionMarkup", out string result)) + return result; + return base.GetDisplayDescription (isSelected); + } + + public override string GetRightSideDescription (bool isSelected) + { + if (CompletionItem.Properties.TryGetValue ("RightSideMarkup", out string result)) + return result; + return null; + } + + public override DisplayFlags DisplayFlags { + get { + DisplayFlags result = DisplayFlags.None; + + if (CompletionItem.Tags.Contains ("bold")) { + result = DisplayFlags.MarkedBold; + } + + return result; + } + } + + public override void InsertCompletionText (CompletionListWindow window, ref KeyActions ka, KeyDescriptor descriptor) + { + var document = IdeApp.Workbench.ActiveDocument; + var editor = document?.Editor; + if (editor == null || completionService == null) {
+ base.InsertCompletionText (window, ref ka, descriptor); + return; + } + var completionChange = completionService.GetChangeAsync (doc, CompletionItem, null, default (CancellationToken)).WaitAndGetResult (default (CancellationToken)); + + var currentBuffer = editor.GetPlatformTextBuffer (); + var textChange = completionChange.TextChange; + + var triggerSnapshotSpan = new SnapshotSpan (triggerSnapshot, new Span (textChange.Span.Start, textChange.Span.Length)); + + var mappedSpan = triggerSnapshotSpan.TranslateTo (triggerSnapshot.TextBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + + triggerSnapshot.TextBuffer.Replace (mappedSpan, completionChange.TextChange.NewText); +// editor.ReplaceText (mappedSpan.Start, mappedSpan.Length, completionChange.TextChange.NewText); + + if (completionChange.NewPosition.HasValue) + editor.CaretOffset = completionChange.NewPosition.Value; + + if (CompletionItem.Rules.FormatOnCommit) { + var endOffset = mappedSpan.Start + completionChange.TextChange.NewText.Length; + Format (editor, document, mappedSpan.Start, endOffset); + } + } + + protected abstract void Format (TextEditor editor, Gui.Document document, SnapshotPoint start, SnapshotPoint end); + + public override async Task<TooltipInformation> CreateTooltipInformation (bool smartWrap, CancellationToken cancelToken) + { + var description = await completionService.GetDescriptionAsync (doc, CompletionItem); + var markup = new StringBuilder (); + var theme = DefaultSourceEditorOptions.Instance.GetEditorTheme (); + var taggedParts = description.TaggedParts; + int i = 0; + while (i < taggedParts.Length) { + if (taggedParts[i].Tag == "LineBreak") + break; + i++; + } + if (i + 1 >= taggedParts.Length) { + markup.AppendTaggedText (theme, taggedParts); + }
+ else { + markup.AppendTaggedText (theme, taggedParts.Take (i)); + markup.Append ("<span font='" + FontService.SansFontName + "' size='small'>"); + markup.AppendLine (); + markup.AppendLine (); + markup.AppendTaggedText (theme, taggedParts.Skip (i + 1), 0, 50); + markup.Append ("</span>"); + } + return new TooltipInformation { + SignatureMarkup = markup.ToString () + }; + } + } + +} diff --git a/main/src/addins/CSharpBinding/Razor/RoslynCommandTarget.cs b/main/src/addins/CSharpBinding/Razor/RoslynCommandTarget.cs new file mode 100644 index 0000000000..b35d73456e --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/RoslynCommandTarget.cs @@ -0,0 +1,121 @@ +//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using System;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Editor.Commands;
+using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
+using Microsoft.CodeAnalysis.Utilities;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using MonoDevelop.Ide.Composition;
+
+namespace Microsoft.VisualStudio.Platform
+{
+ public class RoslynCommandTarget
+ {
+ internal ICommandHandlerService CurrentHandlers { get; set; }
+
+ internal ITextBuffer _languageBuffer;
+ internal ITextView _textView;
+
+ static RoslynCommandTarget()
+ {
+ var defaultForegroundThreadData = ForegroundThreadData.CreateDefault(
+ defaultKind: ForegroundThreadDataKind.ForcedByPackageInitialize);
+ ForegroundThreadAffinitizedObject.CurrentForegroundThreadData = defaultForegroundThreadData;
+ }
+
+ private RoslynCommandTarget(ITextView textView, ITextBuffer languageBuffer)
+ {
+ ICommandHandlerServiceFactory commandHandlerServiceFactory = CompositionManager.GetExportedValue<ICommandHandlerServiceFactory>();
+
+ if (commandHandlerServiceFactory != null)
+ {
+ commandHandlerServiceFactory.Initialize (languageBuffer.ContentType.TypeName);
+
+ CurrentHandlers = commandHandlerServiceFactory.GetService (languageBuffer);
+ }
+
+ _languageBuffer = languageBuffer;
+ _textView = textView;
+ }
+
+ public static RoslynCommandTarget FromViewAndBuffer(ITextView textView, ITextBuffer languageBuffer)
+ {
+ return languageBuffer.Properties.GetOrCreateSingletonProperty<RoslynCommandTarget>(() => new RoslynCommandTarget(textView, languageBuffer));
+ }
+
+ public void ExecuteTypeCharacter(char typedChar, Action lastHandler)
+ {
+ CurrentHandlers?.Execute (_languageBuffer.ContentType,
+ args: new TypeCharCommandArgs (_textView, _languageBuffer, typedChar),
+ lastHandler: lastHandler);
+ }
+
+ public void ExecuteTab (Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new TabKeyCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteBackspace(Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new BackspaceKeyCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteDelete(Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new DeleteKeyCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteReturn(Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new ReturnKeyCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteUp (Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new UpKeyCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteDown (Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new DownKeyCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteUncommentBlock(Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new UncommentSelectionCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteCommentBlock(Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new CommentSelectionCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+
+ public void ExecuteInvokeCompletionList (Action executeNextCommandTarget)
+ {
+ CurrentHandlers.Execute (_languageBuffer.ContentType,
+ args: new InvokeCompletionListCommandArgs (_textView, _languageBuffer),
+ lastHandler: executeNextCommandTarget);
+ }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/Razor/RoslynCompletionPresenter.cs b/main/src/addins/CSharpBinding/Razor/RoslynCompletionPresenter.cs new file mode 100644 index 0000000000..ba934127fa --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/RoslynCompletionPresenter.cs @@ -0,0 +1,81 @@ +//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Completion;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using MonoDevelop.Ide;
+using MonoDevelop.Ide.CodeCompletion;
+
+namespace MonoDevelop.Ide.CodeCompletion
+{
+ public interface IContentTypeMetadata
+ {
+ IEnumerable<string> ContentTypes { get; }
+ }
+
+
+ [Export (typeof (IIntelliSensePresenter<ICompletionPresenterSession, ICompletionSession>))]
+ [Name (PredefinedCompletionPresenterNames.RoslynCompletionPresenter)]
+ [ContentType (ContentTypeNames.RoslynContentType)]
+ internal sealed class RoslynCompletionPresenter : IIntelliSensePresenter<ICompletionPresenterSession, ICompletionSession>
+ {
+ [ImportMany (typeof (IMyRoslynCompletionDataProvider))]
+ internal List<Lazy<IMyRoslynCompletionDataProvider, IContentTypeMetadata>> _completionDataProviders { get; set; }
+
+ public ICompletionPresenterSession CreateSession (ITextView textView, ITextBuffer subjectBuffer, ICompletionSession sessionOpt)
+ {
+ foreach (var completionDataProviderHandle in _completionDataProviders) {
+ foreach (string contentTypeName in completionDataProviderHandle.Metadata.ContentTypes) {
+ if (string.Compare (subjectBuffer.ContentType.TypeName, contentTypeName, StringComparison.OrdinalIgnoreCase) == 0) {
+ string languageName;
+ if (TryGetLanguageNameFromContentType (subjectBuffer.ContentType, out languageName)) {
+ if (Workspace.TryGetWorkspace (subjectBuffer.AsTextContainer (), out var workspace)) {
+ CompletionService completionService = workspace.Services.GetLanguageServices (languageName).GetService<CompletionService> ();
+ return new RoslynCompletionPresenterSession (textView, subjectBuffer, completionDataProviderHandle.Value, completionService);
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // TODO: Remove this
+ private static bool TryGetLanguageNameFromContentType (IContentType contentType, out string languageName)
+ {
+ if (contentType.IsOfType ("htmlx")) {
+ languageName = "HTML";
+ }
+ else if (contentType.IsOfType ("css")) {
+ languageName = "CSS";
+ }
+ else if (contentType.IsOfType ("JSON")) {
+ languageName = "JSON";
+ }
+ else if (contentType.IsOfType ("CSharp")) {
+ languageName = LanguageNames.CSharp;
+ }
+ else if (contentType.IsOfType ("TypeScript")) {
+ languageName = "TypeScript";
+ }
+ else {
+ languageName = null;
+ }
+
+ return languageName != null;
+ }
+
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/Razor/RoslynCompletionPresenterSession.cs b/main/src/addins/CSharpBinding/Razor/RoslynCompletionPresenterSession.cs new file mode 100644 index 0000000000..7660ba024a --- /dev/null +++ b/main/src/addins/CSharpBinding/Razor/RoslynCompletionPresenterSession.cs @@ -0,0 +1,155 @@ +//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Completion;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+using MonoDevelop.Core;
+using MonoDevelop.Ide;
+using MonoDevelop.Ide.CodeCompletion;
+using MonoDevelop.Ide.CodeTemplates;
+using MonoDevelop.Ide.Editor;
+using MonoDevelop.Ide.Editor.Extension;
+using MonoDevelop.Ide.Editor.Highlighting;
+using MonoDevelop.Ide.Fonts;
+
+namespace MonoDevelop.Ide.CodeCompletion
+{
+ internal class RoslynCompletionPresenterSession : ICompletionPresenterSession
+ {
+ private ITextView _textView;
+ private ITextBuffer _subjectBuffer;
+ private Document _document;
+ private CompletionService _completionService;
+ private IMyRoslynCompletionDataProvider _completionDataProvider;
+ private bool _isAdvised = false;
+
+ public event EventHandler<CompletionItemEventArgs> ItemSelected;
+ public event EventHandler<CompletionItemEventArgs> ItemCommitted;
+ public event EventHandler<CompletionItemFilterStateChangedEventArgs> FilterStateChanged;
+ public event EventHandler<EventArgs> Dismissed;
+
+
+ public RoslynCompletionPresenterSession (ITextView textView, ITextBuffer subjectBuffer, IMyRoslynCompletionDataProvider completionDataProvider, CompletionService completionService)
+ {
+ _textView = textView;
+ _subjectBuffer = subjectBuffer;
+ _completionDataProvider = completionDataProvider;
+ _completionService = completionService;
+ }
+
+ public void Dismiss ()
+ {
+ CompletionWindowManager.HideWindow();
+ }
+
+ public void PresentItems (ITrackingSpan triggerSpan, IList<CompletionItem> items, CompletionItem selectedItem, CompletionItem suggestionModeItem, bool suggestionMode, bool isSoftSelected, ImmutableArray<CompletionItemFilter> completionItemFilters, string filterText)
+ {
+ var result = new CompletionDataList ();
+
+ foreach (var item in items) {
+ if (string.IsNullOrEmpty (item.DisplayText))
+ continue;
+ result.Add (WrapItem (item));
+ }
+
+ if (suggestionMode)
+ result.AutoSelect = false;
+ if (filterText != null)
+ result.DefaultCompletionString = filterText;
+ if (suggestionModeItem != null) {
+ result.DefaultCompletionString = suggestionModeItem.DisplayText;
+ result.AutoSelect = false;
+ }
+
+ if (selectedItem != null) {
+ result.DefaultCompletionString = selectedItem.DisplayText;
+ }
+
+ // TODO: isSoftSelected
+ // TODO: completionItemFilters
+ var editor = IdeApp.Workbench.ActiveDocument.Editor;
+ CompletionTextEditorExtension completionEditorExtension = editor.GetContent<CompletionTextEditorExtension> ();
+ completionEditorExtension.ShowCompletion (result);
+
+ if (!_isAdvised)
+ {
+ CompletionWindowManager.Wnd.SelectionChanged += OnSelectionChanged;
+ CompletionWindowManager.WordCompleted += OnWordCompleted;
+
+ CompletionWindowManager.WindowClosed += OnWindowClosed;
+
+ // TODO: Would be nice it we could better detect whether we've already advised on the completion window
+ _isAdvised = true;
+ }
+ }
+
+ private void OnWordCompleted (object sender, CodeCompletionContextEventArgs e)
+ {
+ MyRoslynCompletionData completionData = (MyRoslynCompletionData) CompletionWindowManager.Wnd.SelectedItem;
+ ItemCommitted?.Invoke (this, new CompletionItemEventArgs (completionData.CompletionItem));
+ }
+
+ private void OnWindowClosed (object sender, EventArgs e)
+ {
+ _isAdvised = false;
+
+ CompletionWindowManager.Wnd.SelectionChanged -= OnSelectionChanged;
+ CompletionWindowManager.WordCompleted -= OnWordCompleted;
+
+ CompletionWindowManager.WindowClosed -= OnWindowClosed;
+
+ Dismissed?.Invoke (this, EventArgs.Empty);
+ }
+
+ private void OnSelectionChanged (object sender, EventArgs e)
+ {
+ MyRoslynCompletionData completionData = (MyRoslynCompletionData) CompletionWindowManager.Wnd.SelectedItem;
+ ItemSelected?.Invoke (this, new CompletionItemEventArgs (completionData.CompletionItem));
+ }
+
+ public virtual MyRoslynCompletionData WrapItem (CompletionItem item)
+ {
+ Document document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
+ return _completionDataProvider.CreateCompletionData(document, _subjectBuffer.CurrentSnapshot, _completionService, item);
+ }
+
+ public void SelectPreviousItem ()
+ {
+ CompletionWindowManager.PreProcessKeyEvent (Editor.Extension.KeyDescriptor.Up);
+ }
+
+ public void SelectNextItem ()
+ {
+ CompletionWindowManager.PreProcessKeyEvent (Editor.Extension.KeyDescriptor.Down);
+ }
+
+ public void SelectPreviousPageItem ()
+ {
+ CompletionWindowManager.PreProcessKeyEvent (Editor.Extension.KeyDescriptor.PageUp);
+ }
+
+ public void SelectNextPageItem ()
+ {
+ CompletionWindowManager.PreProcessKeyEvent (Editor.Extension.KeyDescriptor.PageDown);
+ }
+
+ internal void OnCompletionItemCommitted(CompletionItem completionItem)
+ {
+ ItemCommitted?.Invoke(this, new CompletionItemEventArgs(completionItem));
+ }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj index bdfe3f4405..4a6c516637 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <Import Project="..\..\..\MonoDevelop.props" /> + <Import Project="$(ReferencesVSEditor)" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs index 789aaed73e..e62af36fe0 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs @@ -383,7 +383,7 @@ namespace MonoDevelop.Debugger void IClipboardHandler.Copy () { - editor.EditorActionHost.ClipboardCopy (); + editor.EditorOperations.CopySelection (); } void IClipboardHandler.Paste () @@ -398,7 +398,7 @@ namespace MonoDevelop.Debugger void IClipboardHandler.SelectAll () { - editor.EditorActionHost.SelectAll (); + editor.EditorOperations.SelectAll (); } bool IClipboardHandler.EnableCut { diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Refactoring/PackageCodeDiagnosticProvider.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Refactoring/PackageCodeDiagnosticProvider.cs index 8b87157e1a..6c0b36215b 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Refactoring/PackageCodeDiagnosticProvider.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Refactoring/PackageCodeDiagnosticProvider.cs @@ -1,113 +1,113 @@ -// -// PackageCodeDiagnosticProvider.cs -// -// Author: -// Mike Krüger <mkrueger@xamarin.com> -// -// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. +//// +//// PackageCodeDiagnosticProvider.cs +//// +//// Author: +//// Mike Krüger <mkrueger@xamarin.com> +//// +//// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +//// +//// Permission is hereby granted, free of charge, to any person obtaining a copy +//// of this software and associated documentation files (the "Software"), to deal +//// in the Software without restriction, including without limitation the rights +//// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//// copies of the Software, and to permit persons to whom the Software is +//// furnished to do so, subject to the following conditions: +//// +//// The above copyright notice and this permission notice shall be included in +//// all copies or substantial portions of the Software. +//// +//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +//// THE SOFTWARE. -using System; -using MonoDevelop.CodeIssues; -using MonoDevelop.Core; -using MonoDevelop.Projects; -using System.Linq; -using System.IO; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using MonoDevelop.Ide.Editor; -using MonoDevelop.Ide.TypeSystem; -using MonoDevelop.Ide; +//using System; +//using MonoDevelop.CodeIssues; +//using MonoDevelop.Core; +//using MonoDevelop.Projects; +//using System.Linq; +//using System.IO; +//using System.Collections.Generic; +//using System.Reflection; +//using System.Threading; +//using System.Threading.Tasks; +//using MonoDevelop.Ide.Editor; +//using MonoDevelop.Ide.TypeSystem; +//using MonoDevelop.Ide; -namespace MonoDevelop.PackageManagement.Refactoring -{ - sealed class PackageCodeDiagnosticProvider : CodeDiagnosticProvider - { - static readonly Dictionary<Project, AnalyzersFromAssembly> diagnosticCache = new Dictionary<Project, AnalyzersFromAssembly> (); +//namespace MonoDevelop.PackageManagement.Refactoring +//{ +// sealed class PackageCodeDiagnosticProvider : CodeDiagnosticProvider +// { +// static readonly Dictionary<Project, AnalyzersFromAssembly> diagnosticCache = new Dictionary<Project, AnalyzersFromAssembly> (); - static PackageCodeDiagnosticProvider () - { - IdeApp.Workspace.SolutionUnloaded += delegate { - diagnosticCache.Clear (); - }; - IdeApp.Workspace.ActiveConfigurationChanged += delegate { - diagnosticCache.Clear (); - }; - } +// static PackageCodeDiagnosticProvider () +// { +// IdeApp.Workspace.SolutionUnloaded += delegate { +// diagnosticCache.Clear (); +// }; +// IdeApp.Workspace.ActiveConfigurationChanged += delegate { +// diagnosticCache.Clear (); +// }; +// } - public async override Task<IEnumerable<CodeDiagnosticFixDescriptor>> GetCodeFixDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default(CancellationToken)) - { - var diags = await GetProjectDiagnosticsAsync (document.Project, language, cancellationToken).ConfigureAwait (false); - return diags.Fixes; - } +// public async override Task<IEnumerable<CodeDiagnosticFixDescriptor>> GetCodeFixDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default(CancellationToken)) +// { +// var diags = await GetProjectDiagnosticsAsync (document.Project, language, cancellationToken).ConfigureAwait (false); +// return diags.Fixes; +// } - public async override Task<IEnumerable<CodeDiagnosticDescriptor>> GetCodeDiagnosticDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default(CancellationToken)) - { - var diags = await GetProjectDiagnosticsAsync (document.Project, language, cancellationToken).ConfigureAwait (false); - return diags.Analyzers; - } +// public async override Task<IEnumerable<CodeDiagnosticDescriptor>> GetCodeDiagnosticDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default(CancellationToken)) +// { +// var diags = await GetProjectDiagnosticsAsync (document.Project, language, cancellationToken).ConfigureAwait (false); +// return diags.Analyzers; +// } - public async override Task<IEnumerable<MonoDevelop.CodeActions.CodeRefactoringDescriptor>> GetCodeRefactoringDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken) - { - var diags = await GetProjectDiagnosticsAsync (document.Project, language, cancellationToken).ConfigureAwait (false); - return diags.Refactorings; - } +// public async override Task<IEnumerable<MonoDevelop.CodeActions.CodeRefactoringDescriptor>> GetCodeRefactoringDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken) +// { +// var diags = await GetProjectDiagnosticsAsync (document.Project, language, cancellationToken).ConfigureAwait (false); +// return diags.Refactorings; +// } - static async Task<AnalyzersFromAssembly> GetProjectDiagnosticsAsync (Project project, string language, CancellationToken cancellationToken) - { - if (project == null) - return AnalyzersFromAssembly.Empty; - AnalyzersFromAssembly result; - if (diagnosticCache.TryGetValue(project, out result)) - return result; +// static async Task<AnalyzersFromAssembly> GetProjectDiagnosticsAsync (Project project, string language, CancellationToken cancellationToken) +// { +// if (project == null) +// return AnalyzersFromAssembly.Empty; +// AnalyzersFromAssembly result; +// if (diagnosticCache.TryGetValue(project, out result)) +// return result; - result = new AnalyzersFromAssembly (); +// result = new AnalyzersFromAssembly (); - var dotNetProject = project as DotNetProject; - if (dotNetProject != null) { - var proxy = new DotNetProjectProxy (dotNetProject); - if (proxy.HasPackages ()) { - var packagesPath = await GetPackagesPath (proxy); - foreach (var file in Directory.EnumerateFiles (packagesPath, "*.dll", SearchOption.AllDirectories)) { - cancellationToken.ThrowIfCancellationRequested (); - try { - var asm = Assembly.LoadFrom (file); - result.AddAssembly (asm); - } catch (Exception) { - } - } - } - } - diagnosticCache[project] = result; - return result; - } +// var dotNetProject = project as DotNetProject; +// if (dotNetProject != null) { +// var proxy = new DotNetProjectProxy (dotNetProject); +// if (proxy.HasPackages ()) { +// var packagesPath = await GetPackagesPath (proxy); +// foreach (var file in Directory.EnumerateFiles (packagesPath, "*.dll", SearchOption.AllDirectories)) { +// cancellationToken.ThrowIfCancellationRequested (); +// try { +// var asm = Assembly.LoadFrom (file); +// result.AddAssembly (asm); +// } catch (Exception) { +// } +// } +// } +// } +// diagnosticCache[project] = result; +// return result; +// } - static Task<FilePath> GetPackagesPath (IDotNetProject project) - { - return Runtime.RunInMainThread (() => { - var solutionManager = PackageManagementServices.Workspace.GetSolutionManager (project.ParentSolution); - var nugetProject = solutionManager.GetNuGetProject (project); - return nugetProject.GetPackagesFolderPath (solutionManager); - }); - } - } -}
\ No newline at end of file +// static Task<FilePath> GetPackagesPath (IDotNetProject project) +// { +// return Runtime.RunInMainThread (() => { +// var solutionManager = PackageManagementServices.Workspace.GetSolutionManager (project.ParentSolution); +// var nugetProject = solutionManager.GetNuGetProject (project); +// return nugetProject.GetPackagesFolderPath (solutionManager); +// }); +// } +// } +//}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/AnalysisCommands.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/AnalysisCommands.cs index 348cc789a9..3abed75c3d 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/AnalysisCommands.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/AnalysisCommands.cs @@ -44,6 +44,9 @@ namespace MonoDevelop.AnalysisCore class ExportRulesHandler : CommandHandler { + static MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable options = + ((MonoDevelopWorkspaceDiagnosticAnalyzerProviderService)Ide.Composition.CompositionManager.GetExportedValue<IWorkspaceDiagnosticAnalyzerProviderService> ()).Options; + protected override void Run () { var lang = "text/x-csharp"; @@ -55,7 +58,8 @@ namespace MonoDevelop.AnalysisCore Dictionary<CodeDiagnosticDescriptor, DiagnosticSeverity?> severities = new Dictionary<CodeDiagnosticDescriptor, DiagnosticSeverity?> (); - foreach (var node in BuiltInCodeDiagnosticProvider.GetBuiltInCodeDiagnosticDescriptorsAsync (CodeRefactoringService.MimeTypeToLanguage(lang), true).Result) { + var language = CodeRefactoringService.MimeTypeToLanguage (lang); + foreach (var node in options.AllDiagnostics.Where (x => x.Languages.Contains (language))) { severities [node] = node.DiagnosticSeverity; // if (node.GetProvider ().SupportedDiagnostics.Length > 1) { // foreach (var subIssue in node.GetProvider ().SupportedDiagnostics) { @@ -90,7 +94,7 @@ namespace MonoDevelop.AnalysisCore } var providerStates = new Dictionary<CodeRefactoringDescriptor, bool> (); - foreach (var node in BuiltInCodeDiagnosticProvider.GetBuiltInCodeRefactoringDescriptorsAsync (CodeRefactoringService.MimeTypeToLanguage(lang), true).Result) { + foreach (var node in options.AllRefactorings.Where (x => x.Language.Contains (language))) { providerStates [node] = node.IsEnabled; } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable.cs new file mode 100644 index 0000000000..667665a194 --- /dev/null +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable.cs @@ -0,0 +1,114 @@ +// +// MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Diagnostics; +using MonoDevelop.AnalysisCore.Gui; +using MonoDevelop.CodeActions; +using MonoDevelop.CodeIssues; +using MonoDevelop.Core; + +namespace MonoDevelop.AnalysisCore +{ + partial class MonoDevelopWorkspaceDiagnosticAnalyzerProviderService + { + internal class OptionsTable + { + Dictionary<string, CodeDiagnosticDescriptor> diagnosticTable = new Dictionary<string, CodeDiagnosticDescriptor> (); + Dictionary<Type, CodeRefactoringDescriptor> refactoringTable = new Dictionary<Type, CodeRefactoringDescriptor> (); + List<CodeDiagnosticDescriptor> diagnostics = new List<CodeDiagnosticDescriptor> (); + + public IEnumerable<CodeDiagnosticDescriptor> AllDiagnostics => diagnostics; + public IEnumerable<CodeRefactoringDescriptor> AllRefactorings => refactoringTable.Values; + + public bool TryGetDiagnosticDescriptor (string id, out CodeDiagnosticDescriptor desc) + { + return diagnosticTable.TryGetValue (id, out desc); + } + + public bool TryGetRefactoringDescriptor (Type t, out CodeRefactoringDescriptor desc) + { + return refactoringTable.TryGetValue (t, out desc); + } + + // Needed to support configuration of diagnostics and code fixes/refactorings. + public void ProcessAssembly (Assembly asm) + { + try { + foreach (var type in asm.GetTypes ()) { + + //HACK: Workaround for missing UI + if (type == typeof (Microsoft.CodeAnalysis.GenerateOverrides.GenerateOverridesCodeRefactoringProvider)) + continue; + if (type == typeof (Microsoft.CodeAnalysis.AddMissingReference.AbstractAddMissingReferenceCodeFixProvider)) + continue; + + var analyzerAttr = (DiagnosticAnalyzerAttribute)type.GetCustomAttributes (typeof (DiagnosticAnalyzerAttribute), false).FirstOrDefault (); + if (analyzerAttr != null) { + try { + var analyzer = (DiagnosticAnalyzer)Activator.CreateInstance (type); + var descriptor = new CodeDiagnosticDescriptor (analyzerAttr.Languages, type); + + foreach (var diag in analyzer.SupportedDiagnostics) { + if (!IsDiagnosticSupported (diag)) + continue; + + diagnosticTable[diag.Id] = descriptor; + } + diagnostics.Add (descriptor); + } catch (Exception e) { + LoggingService.LogError ($"error while adding diagnostic analyzer {type} from assembly {asm.FullName}", e); + } + } + + var exportAttr = type.GetCustomAttributes (typeof (ExportCodeRefactoringProviderAttribute), false).FirstOrDefault () as ExportCodeRefactoringProviderAttribute; + if (exportAttr != null) { + refactoringTable[type] = new CodeRefactoringDescriptor (type, exportAttr); + } + } + } catch (ReflectionTypeLoadException ex) { + foreach (var subException in ex.LoaderExceptions) { + LoggingService.LogError ("Error while loading diagnostics in " + asm.FullName, subException); + } + throw; + } + } + + static bool IsDiagnosticSupported (DiagnosticDescriptor diag) + { + //filter out E&C analyzers as we don't support E&C + return !diag.CustomTags.Contains (WellKnownDiagnosticTags.EditAndContinue); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.cs index 901d8b4565..5f3184e237 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.cs @@ -30,24 +30,37 @@ using System.ComponentModel.Composition; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Mono.Addins; using MonoDevelop.Core; +using MonoDevelop.Core.AddIns; namespace MonoDevelop.AnalysisCore { [Export(typeof(IWorkspaceDiagnosticAnalyzerProviderService))] partial class MonoDevelopWorkspaceDiagnosticAnalyzerProviderService : IWorkspaceDiagnosticAnalyzerProviderService { + static readonly AnalyzerAssemblyLoader analyzerAssemblyLoader = new AnalyzerAssemblyLoader (); readonly static string diagnosticAnalyzerAssembly = typeof (DiagnosticAnalyzerAttribute).Assembly.GetName ().Name; + const bool ClrHeapEnabled = false; - static readonly AnalyzerAssemblyLoader analyzerAssemblyLoader = new AnalyzerAssemblyLoader (); + internal OptionsTable Options = new OptionsTable (); readonly ImmutableArray<HostDiagnosticAnalyzerPackage> hostDiagnosticAnalyzerInfo; + const string extensionPath = "/MonoDevelop/Refactoring/AnalyzerAssemblies"; + string [] RuntimeEnabledAssemblies; public MonoDevelopWorkspaceDiagnosticAnalyzerProviderService () { + LoadAnalyzerAssemblies (); + RefactoringEssentials.NRefactory6Host.GetLocalizedString = GettextCatalog.GetString; hostDiagnosticAnalyzerInfo = CreateHostDiagnosticAnalyzerPackages (); } + void LoadAnalyzerAssemblies() + { + RuntimeEnabledAssemblies = AddinManager.GetExtensionNodes<AssemblyExtensionNode> (extensionPath).Select (b => b.FileName).ToArray (); + } + public IAnalyzerAssemblyLoader GetAnalyzerAssemblyLoader () { return analyzerAssemblyLoader; @@ -58,7 +71,7 @@ namespace MonoDevelop.AnalysisCore return hostDiagnosticAnalyzerInfo; } - static ImmutableArray<HostDiagnosticAnalyzerPackage> CreateHostDiagnosticAnalyzerPackages () + ImmutableArray<HostDiagnosticAnalyzerPackage> CreateHostDiagnosticAnalyzerPackages () { var builder = ImmutableArray.CreateBuilder<HostDiagnosticAnalyzerPackage> (); var assemblies = ImmutableArray.CreateBuilder<string> (); @@ -66,33 +79,27 @@ namespace MonoDevelop.AnalysisCore foreach (var asm in AppDomain.CurrentDomain.GetAssemblies ()) { try { var assemblyName = asm.GetName ().Name; - switch (assemblyName) { - //whitelist - case "RefactoringEssentials": - case "Refactoring Essentials": - case "Microsoft.CodeAnalysis.CSharp": - case "Microsoft.CodeAnalysis.CSharp.Features": - case "Microsoft.CodeAnalysis.Features": - case "Microsoft.CodeAnalysis.VisualBasic.Features": - case "Microsoft.CodeAnalysis.VisualBasic": - break; - case "ClrHeapAllocationAnalyzer": - if (!ClrHeapEnabled) - continue; - break; - //blacklist - case "FSharpBinding": - continue; - //addin assemblies that reference roslyn - default: - var refAsm = asm.GetReferencedAssemblies (); - if (refAsm.Any (a => a.Name == diagnosticAnalyzerAssembly) && refAsm.Any (a => a.Name == "MonoDevelop.Ide")) + if (Array.IndexOf (RuntimeEnabledAssemblies, assemblyName) == -1) { + switch (assemblyName) { + case "ClrHeapAllocationAnalyzer": + if (!ClrHeapEnabled) + continue; break; - continue; + //blacklist + case "FSharpBinding": + continue; + //addin assemblies that reference roslyn + default: + var refAsm = asm.GetReferencedAssemblies (); + if (refAsm.Any (a => a.Name == diagnosticAnalyzerAssembly) && refAsm.Any (a => a.Name == "MonoDevelop.Ide")) + break; + continue; + } } // Figure out a way to disable E&C analyzers. assemblies.Add (asm.Location); + Options.ProcessAssembly (asm); } catch (Exception e) { LoggingService.LogError ("Error while loading diagnostics in " + asm.FullName, e); } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionContainer.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionContainer.cs index f8522866e1..28fec6bcef 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionContainer.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionContainer.cs @@ -24,96 +24,50 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System.Collections.Generic; -using Microsoft.CodeAnalysis.CodeActions; using System; -using MonoDevelop.CodeIssues; using System.Linq; +using System.Collections.Generic; using Microsoft.CodeAnalysis; -using System.Collections; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeRefactorings; +using System.Collections.Immutable; namespace MonoDevelop.CodeActions { class CodeActionContainer { - public static readonly CodeActionContainer Empty = new CodeActionContainer(); + public static readonly CodeActionContainer Empty = new CodeActionContainer(ImmutableArray<CodeFixCollection>.Empty, ImmutableArray<CodeRefactoring>.Empty); public bool IsEmpty { get { - return CodeFixActions.Count + DiagnosticsAtCaret.Count + CodeRefactoringActions.Count == 0; + return CodeFixActions.Length + CodeRefactoringActions.Length == 0; } } - IReadOnlyList<ValidCodeDiagnosticAction> codeFixActions; - public IReadOnlyList<ValidCodeDiagnosticAction> CodeFixActions { + ImmutableArray<CodeFixCollection> codeFixActions; + public ImmutableArray<CodeFixCollection> CodeFixActions { get { - return codeFixActions ?? new ValidCodeDiagnosticAction[0]; + return codeFixActions; } private set { codeFixActions = value; } } - IReadOnlyList<ValidCodeAction> codeRefactoringActions; - - public IReadOnlyList<ValidCodeAction> CodeRefactoringActions { + ImmutableArray<CodeRefactoring> codeRefactoringActions; + public ImmutableArray<CodeRefactoring> CodeRefactoringActions { get { - return codeRefactoringActions ?? new ValidCodeAction[0]; + return codeRefactoringActions; } private set { codeRefactoringActions = value; } } - public IEnumerable<ValidCodeAction> AllValidCodeActions { - get { - return CodeRefactoringActions.Concat (CodeFixActions); - } - } - - IReadOnlyList<Diagnostic> diagnosticsAtCaret; - public IReadOnlyList<Diagnostic> DiagnosticsAtCaret { - get { - return diagnosticsAtCaret ?? new Diagnostic[0]; - } - private set { - diagnosticsAtCaret = value.Distinct (new DiagnosticComparer()).ToList (); - } - } - - class DiagnosticComparer : IEqualityComparer<Diagnostic> - { - bool IEqualityComparer<Diagnostic>.Equals (Diagnostic x, Diagnostic y) - { - if (x.Id != null && y.Id != null) - return x.Id == y.Id; - return x.Equals (y); - } - - int IEqualityComparer<Diagnostic>.GetHashCode (Diagnostic obj) - { - return obj.Id != null ? obj.Id.GetHashCode () : obj.GetHashCode (); - } - } - - CodeActionContainer () - { - CodeFixActions = new List<ValidCodeDiagnosticAction> (); - CodeRefactoringActions = new List<ValidCodeAction> (); - DiagnosticsAtCaret = new List<Diagnostic> (); - } - - internal CodeActionContainer (List<ValidCodeDiagnosticAction> codeDiagnosticActions, List<ValidCodeAction> codeRefactoringActions, List<Diagnostic> diagnosticsAtCaret) + internal CodeActionContainer (ImmutableArray<CodeFixCollection> codeDiagnosticActions, ImmutableArray<CodeRefactoring> codeRefactoringActions) { - if (codeDiagnosticActions == null) - throw new ArgumentNullException ("codeDiagnosticActions"); - if (codeRefactoringActions == null) - throw new ArgumentNullException ("codeRefactoringActions"); - if (diagnosticsAtCaret == null) - throw new ArgumentNullException ("diagnosticsAtCaret"); CodeFixActions = codeDiagnosticActions; CodeRefactoringActions = codeRefactoringActions; - DiagnosticsAtCaret = diagnosticsAtCaret; } } }
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs index 941aba52af..e8fa0110be 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionEditorExtension.cs @@ -35,6 +35,7 @@ using System.Threading.Tasks; using Gtk; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Text; using MonoDevelop.AnalysisCore; @@ -100,8 +101,6 @@ namespace MonoDevelop.CodeActions Task<CodeActionContainer> smartTagTask; CancellationTokenSource quickFixCancellationTokenSource = new CancellationTokenSource (); - List<CodeDiagnosticFixDescriptor> codeFixes; - void HandleCaretPositionChanged (object sender, EventArgs e) { if (Editor.IsInAtomicUndo) @@ -110,8 +109,8 @@ namespace MonoDevelop.CodeActions if (AnalysisOptions.EnableFancyFeatures && DocumentContext.ParsedDocument != null) { if (HasCurrentFixes) { var curOffset = Editor.CaretOffset; - foreach (var fix in smartTagTask.Result.AllValidCodeActions) { - if (!fix.ValidSegment.Contains (curOffset)) { + foreach (var fix in smartTagTask.Result.CodeFixActions) { + if (!fix.TextSpan.Contains (curOffset)) { RemoveWidget (); break; } @@ -124,6 +123,8 @@ namespace MonoDevelop.CodeActions } } + static ICodeFixService codeFixService = Ide.Composition.CompositionManager.GetExportedValue<ICodeFixService> (); + static ICodeRefactoringService codeRefactoringService = Ide.Composition.CompositionManager.GetExportedValue<ICodeRefactoringService> (); internal Task<CodeActionContainer> GetCurrentFixesAsync (CancellationToken cancellationToken) { var loc = Editor.CaretOffset; @@ -132,80 +133,19 @@ namespace MonoDevelop.CodeActions return Task.FromResult (CodeActionContainer.Empty); } TextSpan span; - if (Editor.IsSomethingSelected) { var selectionRange = Editor.SelectionRange; span = selectionRange.Offset >= 0 ? TextSpan.FromBounds (selectionRange.Offset, selectionRange.EndOffset) : TextSpan.FromBounds (loc, loc); } else { span = TextSpan.FromBounds (loc, loc); } - var rExt = Editor.GetContent<ResultsEditorExtension> (); - var errorList = Editor - .GetTextSegmentMarkersAt (Editor.CaretOffset) - .OfType<IErrorMarker> () - .Where (rm => !string.IsNullOrEmpty (rm.Error.Id)).ToList (); + return Task.Run (async delegate { try { - var result = await CodeDiagnosticRunner.Check (new AnalysisDocument (Editor, DocumentContext), cancellationToken).ConfigureAwait (false); - var diagnosticsAtCaret = result.OfType<DiagnosticResult> ().Where (d => d.Region.Contains (loc)).Select (d => d.Diagnostic).ToList (); + var fixes = await codeFixService.GetFixesAsync (ad, span, true, cancellationToken); + var refactorings = await codeRefactoringService.GetRefactoringsAsync (ad, span, cancellationToken); - var codeIssueFixes = new List<ValidCodeDiagnosticAction> (); - var diagnosticIds = diagnosticsAtCaret.Select (diagnostic => diagnostic.Id).Concat (errorList.Select (rm => rm.Error.Id)).ToList (); - if (codeFixes == null) { - codeFixes = (await CodeRefactoringService.GetCodeFixesAsync (DocumentContext, CodeRefactoringService.MimeTypeToLanguage (Editor.MimeType), cancellationToken).ConfigureAwait (false)).ToList (); - } - var root = await ad.GetSyntaxRootAsync (cancellationToken).ConfigureAwait (false); - foreach (var cfp in codeFixes) { - if (cancellationToken.IsCancellationRequested) - return CodeActionContainer.Empty; - var provider = cfp.GetCodeFixProvider (); - if (!provider.FixableDiagnosticIds.Any (diagnosticIds.Contains)) - continue; - - // These two delegates were factored out, as using them as lambdas in the inner loop creates more captures than declaring them here. - Func<Diagnostic, bool> providerIdsContain = d => provider.FixableDiagnosticIds.Contains (d.Id); - Action<Microsoft.CodeAnalysis.CodeActions.CodeAction, ImmutableArray<Diagnostic>> codeFixRegistration = (ca, d) => codeIssueFixes.Add (new ValidCodeDiagnosticAction (cfp, ca, d, d [0].Location.SourceSpan)); - try { - var groupedDiagnostics = diagnosticsAtCaret - .Concat (errorList.Select (em => em.Error.Tag) - .OfType<Diagnostic> ()) - .GroupBy (d => d.Location.SourceSpan); - foreach (var g in groupedDiagnostics) { - if (cancellationToken.IsCancellationRequested) - return CodeActionContainer.Empty; - var diagnosticSpan = g.Key; - var validDiagnostics = g.Where (providerIdsContain).ToImmutableArray (); - if (validDiagnostics.Length == 0) { - continue; - } - if (diagnosticSpan.Start < 0 || diagnosticSpan.End > root.Span.End) { - continue; - } - await provider.RegisterCodeFixesAsync (new CodeFixContext (ad, diagnosticSpan, validDiagnostics, codeFixRegistration, cancellationToken)).ConfigureAwait (false); - - // TODO: Is that right ? Currently it doesn't really make sense to run one code fix provider on several overlapping diagnostics at the same location - // However the generate constructor one has that case and if I run it twice the same code action is generated twice. So there is a dupe check problem there. - // Work around for now is to only take the first diagnostic batch. - break; - } - } catch (OperationCanceledException) { - return CodeActionContainer.Empty; - } catch (AggregateException ae) { - ae.Flatten ().Handle (aex => aex is OperationCanceledException); - return CodeActionContainer.Empty; - } catch (Exception ex) { - LoggingService.LogError ("Error while getting refactorings from code fix provider " + cfp.Name, ex); - continue; - } - } - var codeActions = new List<ValidCodeAction> (); - foreach (var action in await CodeRefactoringService.GetValidActionsAsync (Editor, DocumentContext, span, cancellationToken).ConfigureAwait (false)) { - codeActions.Add (action); - } - if (cancellationToken.IsCancellationRequested) - return CodeActionContainer.Empty; - - var codeActionContainer = new CodeActionContainer (codeIssueFixes, codeActions, diagnosticsAtCaret); + var codeActionContainer = new CodeActionContainer (fixes, refactorings); Application.Invoke ((o, args) => { if (cancellationToken.IsCancellationRequested) return; @@ -229,7 +169,6 @@ namespace MonoDevelop.CodeActions } }, cancellationToken); - } async void PopupQuickFixMenu (Gdk.EventButton evt, Action<CodeFixMenu> menuAction) @@ -335,8 +274,8 @@ namespace MonoDevelop.CodeActions bool first = true; var smartTagLocBegin = offset; - foreach (var fix in fixes.CodeFixActions.Concat (fixes.CodeRefactoringActions)) { - var textSpan = fix.ValidSegment; + foreach (var fix in fixes.CodeFixActions) { + var textSpan = fix.TextSpan; if (textSpan.IsEmpty) continue; if (first || offset < textSpan.Start) { @@ -356,7 +295,7 @@ namespace MonoDevelop.CodeActions currentSmartTag.CancelPopup += CurrentSmartTag_CancelPopup; currentSmartTag.ShowPopup += CurrentSmartTag_ShowPopup; currentSmartTag.Tag = fixes; - currentSmartTag.IsVisible = fixes.CodeFixActions.Count > 0; + currentSmartTag.IsVisible = fixes.CodeFixActions.Sum (x => x.Fixes.Length) > 0; editor.AddMarker (currentSmartTag); } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionPanelWidget.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionPanelWidget.cs index a2ba81c573..52368ba888 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionPanelWidget.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeActionPanelWidget.cs @@ -34,6 +34,8 @@ using MonoDevelop.Refactoring; using System.Collections.Generic; using GLib; using MonoDevelop.CodeIssues; +using MonoDevelop.AnalysisCore; +using Microsoft.CodeAnalysis.Diagnostics; namespace MonoDevelop.CodeActions { @@ -59,10 +61,13 @@ namespace MonoDevelop.CodeActions readonly TreeStore treeStore = new TreeStore (typeof(string), typeof(bool), typeof(CodeRefactoringDescriptor)); readonly Dictionary<CodeRefactoringDescriptor, bool> providerStates = new Dictionary<CodeRefactoringDescriptor, bool> (); + static MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable options = + ((MonoDevelopWorkspaceDiagnosticAnalyzerProviderService)Ide.Composition.CompositionManager.GetExportedValue<IWorkspaceDiagnosticAnalyzerProviderService> ()).Options; + void GetAllProviderStates () { var language = CodeRefactoringService.MimeTypeToLanguage (mimeType); - foreach (var node in BuiltInCodeDiagnosticProvider.GetBuiltInCodeRefactoringDescriptorsAsync (CodeRefactoringService.MimeTypeToLanguage(language), true).Result) { + foreach (var node in options.AllRefactorings.Where (x => x.Language.Contains (language))) { providerStates [node] = node.IsEnabled; } } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs index 3e1a3491c0..df1fb480aa 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs @@ -48,6 +48,7 @@ using MonoDevelop.Ide.Composition; using MonoDevelop.Ide.Editor; using MonoDevelop.Refactoring; using RefactoringEssentials; +using MonoDevelop.AnalysisCore; namespace MonoDevelop.CodeActions { @@ -76,6 +77,8 @@ namespace MonoDevelop.CodeActions usages.Set (id, CodeActionUsages [id]); } + static MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable options = + ((MonoDevelopWorkspaceDiagnosticAnalyzerProviderService)Ide.Composition.CompositionManager.GetExportedValue<IWorkspaceDiagnosticAnalyzerProviderService> ()).Options; public static async Task<CodeFixMenu> CreateFixMenu (TextEditor editor, CodeActionContainer fixes, CancellationToken cancellationToken = default(CancellationToken)) { var menu = new CodeFixMenu (); @@ -86,191 +89,101 @@ namespace MonoDevelop.CodeActions int mnemonic = 1; - foreach (var fix in fixes.CodeFixActions.OrderByDescending (i => GetUsage (i.CodeAction.EquivalenceKey))) { - AddFixMenuItem (editor, menu, ref mnemonic, fix.CodeAction); - } + var suppressLabel = GettextCatalog.GetString ("_Suppress"); + var suppressMenu = new CodeFixMenu (suppressLabel); - bool first = true; - foreach (var fix in fixes.CodeRefactoringActions) { - if (first) { - if (menu.Items.Count > 0) - menu.Add (CodeFixMenuEntry.Separator); - first = false; - } - - AddFixMenuItem (editor, menu, ref mnemonic, fix.CodeAction); - } + var fixAllLabel = GettextCatalog.GetString ("_Fix all"); + var fixAllMenu = new CodeFixMenu (fixAllLabel); - var warningsAtCaret = (await editor.DocumentContext.AnalysisDocument.GetSemanticModelAsync (cancellationToken)) - .GetDiagnostics (new TextSpan (editor.CaretOffset, 0)) - .Where (diag => diag.Severity == DiagnosticSeverity.Warning).ToList (); + var configureLabel = GettextCatalog.GetString ("_Options"); + var configureMenu = new CodeFixMenu (configureLabel); - var caretSpan = new TextSpan (editor.CaretOffset, 0); + foreach (var cfa in fixes.CodeFixActions.OrderByDescending (x => x.Fixes.Max(y => GetUsage (y.Action.EquivalenceKey)))) { + var state = cfa.FixAllState; + var scopes = cfa.SupportedScopes; - first = true; - foreach (var warning in warningsAtCaret) { - if (string.IsNullOrWhiteSpace (warning.Descriptor.Title.ToString ())) - continue; - var label = GettextCatalog.GetString ("_Options for \u2018{0}\u2019", warning.Descriptor.Title); - var subMenu = new CodeFixMenu (label); + foreach (var fix in cfa.Fixes) { + var diag = fix.PrimaryDiagnostic; - await AddSuppressionMenuItems (subMenu, editor, warning, caretSpan); + if (options.TryGetDiagnosticDescriptor (diag.Id, out var descriptor) && !descriptor.GetIsEnabled (diag.Descriptor)) + continue; + + bool isSuppress = fix.Action is TopLevelSuppressionCodeAction; - if (subMenu.Items.Count > 0) { + if (isSuppress) { + AddFixMenuItem (editor, suppressMenu, ref mnemonic, fix.Action); + continue; + } - if (first) { - menu.Add (CodeFixMenuEntry.Separator); - first = false; + AddFixMenuItem (editor, menu, ref mnemonic, fix.Action); + + var configurable = !DescriptorHasTag (diag.Descriptor, WellKnownDiagnosticTags.NotConfigurable); + if (descriptor != null && configurable) { + var optionsMenuItem = new CodeFixMenuEntry (GettextCatalog.GetString ("_Configure Rule \u2018{0}\u2019", diag.Descriptor.Title), + delegate { + IdeApp.Workbench.ShowGlobalPreferencesDialog (null, "C#", dialog => { + var panel = dialog.GetPanel<CodeIssuePanel> ("C#"); + if (panel == null) + return; + panel.Widget.SelectCodeIssue (fix.PrimaryDiagnostic.Descriptor.Id); + }); + }); + configureMenu.Add (optionsMenuItem); } - menu.Add (subMenu); + if (!scopes.Contains (FixAllScope.Document)) + continue; + + // FIXME: No global undo yet to support fixall in project/solution + var fixState = state.WithScopeAndEquivalenceKey (FixAllScope.Document, fix.Action.EquivalenceKey); + + var provider = state.FixAllProvider; + if (provider == null) + continue; + + // FIXME: Use a real progress tracker. + var fixAll = await provider.GetFixAsync (fixState.CreateFixAllContext (new RoslynProgressTracker (), cancellationToken)); + AddFixMenuItem (editor, fixAllMenu, ref mnemonic, fixAll); } } - first = true; - foreach (var diag in fixes.DiagnosticsAtCaret) { - if (string.IsNullOrWhiteSpace (diag.Descriptor.Title.ToString ())) + bool first = true; + foreach (var refactoring in fixes.CodeRefactoringActions) { + if (options.TryGetRefactoringDescriptor (refactoring.GetType (), out var descriptor) && !descriptor.IsEnabled) continue; - var notConfigurable = DescriptorHasTag (diag.Descriptor, WellKnownDiagnosticTags.NotConfigurable); - - var label = GettextCatalog.GetString ("_Options for \u2018{0}\u2019", diag.Descriptor.Title); - var subMenu = new CodeFixMenu (label); - if (first) { - menu.Add (CodeFixMenuEntry.Separator); + if (menu.Items.Count > 0) + menu.Add (CodeFixMenuEntry.Separator); first = false; } - await AddSuppressionMenuItems (subMenu, editor, diag, caretSpan); - - var descriptor = BuiltInCodeDiagnosticProvider.GetCodeDiagnosticDescriptor (diag.Id); - - if (descriptor != null && IsConfigurable (diag.Descriptor)) { - var optionsMenuItem = new CodeFixMenuEntry (GettextCatalog.GetString ("_Configure Rule"), - delegate { - IdeApp.Workbench.ShowGlobalPreferencesDialog (null, "C#", dialog => { - var panel = dialog.GetPanel<CodeIssuePanel> ("C#"); - if (panel == null) - return; - panel.Widget.SelectCodeIssue (diag.Descriptor.Id); - }); - }); - subMenu.Add (optionsMenuItem); + foreach (var action in refactoring.Actions) { + AddFixMenuItem (editor, menu, ref mnemonic, action); } + } - foreach (var fix in fixes.CodeFixActions.OrderByDescending (i => GetUsage (i.CodeAction.EquivalenceKey))) { - if (cancellationToken.IsCancellationRequested) - return null; - var provider = fix.Diagnostic.GetCodeFixProvider ().GetFixAllProvider (); - if (provider == null) - continue; - - if (!provider.GetSupportedFixAllScopes ().Contains (FixAllScope.Document)) - continue; - - var language = editor.DocumentContext.AnalysisDocument.Project.Language; - var diagnosticdDescriptor = fix.Diagnostic?.GetCodeDiagnosticDescriptor (language); - if (diagnosticdDescriptor == null) - continue; - - var subMenu2 = new CodeFixMenu (GettextCatalog.GetString ("Fix all")); - - var diagnosticAnalyzer = diagnosticdDescriptor.GetProvider (); - if (!diagnosticAnalyzer.SupportedDiagnostics.Contains (diag.Descriptor)) - continue; - - var menuItem = new CodeFixMenuEntry ( - GettextCatalog.GetString ("In _Document"), - async delegate { await FixAll (editor, fix, provider, diagnosticAnalyzer); } - ); - subMenu2.Add (menuItem); - subMenu.Add (CodeFixMenuEntry.Separator); - subMenu.Add (subMenu2); - } + first = true; - menu.Add (subMenu); + if (fixAllMenu.Items.Count != 0) { + if (first) + menu.Add (CodeFixMenuEntry.Separator); + menu.Add (fixAllMenu); + first = false; } - return menu; - } - - static async Task FixAll (TextEditor editor, ValidCodeDiagnosticAction fix, FixAllProvider provider, DiagnosticAnalyzer diagnosticAnalyzer) - { - var diagnosticIds = diagnosticAnalyzer.SupportedDiagnostics.Select (d => d.Id).ToImmutableHashSet (); - - var analyzers = new [] { diagnosticAnalyzer }.ToImmutableArray (); - - var dict = ImmutableDictionary<Document, ImmutableArray<Diagnostic>>.Empty; - var doc = editor.DocumentContext.AnalysisDocument; - var diagnostics = await GetDiagnosticsForDocument (analyzers, doc, diagnosticIds, CancellationToken.None); - - dict = dict.Add (doc, diagnostics); - var fixAllDiagnosticProvider = new FixAllState.FixMultipleDiagnosticProvider (dict); - - var ctx = new FixAllContext ( - editor.DocumentContext.AnalysisDocument, - fix.Diagnostic.GetCodeFixProvider (), - FixAllScope.Document, - fix.CodeAction.EquivalenceKey, - diagnosticIds, - fixAllDiagnosticProvider, - default (CancellationToken) - ); - - var fixAll = await provider.GetFixAsync (ctx); - using (var undo = editor.OpenUndoGroup ()) { - await CodeDiagnosticDescriptor.RunAction (editor.DocumentContext, fixAll, default (CancellationToken)); + if (suppressMenu.Items.Count != 0) { + if (first) + menu.Add (CodeFixMenuEntry.Separator); + menu.Add (suppressMenu); + first = false; } - } - - static async Task<ImmutableArray<Diagnostic>> GetDiagnosticsForDocument (ImmutableArray<DiagnosticAnalyzer> analyzers, Microsoft.CodeAnalysis.Document doc, ImmutableHashSet<string> diagnostics, CancellationToken token) - { - var sol = doc.Project.Solution; - var options = new CompilationWithAnalyzersOptions ( - new WorkspaceAnalyzerOptions ( - new AnalyzerOptions (ImmutableArray<AdditionalText>.Empty), - sol.Options, - sol), - delegate (Exception exception, DiagnosticAnalyzer analyzer, Diagnostic diag) { - LoggingService.LogError ("Exception in diagnostic analyzer " + diag.Id + ":" + diag.GetMessage (), exception); - }, - true, - false - ); - - var model = await doc.GetSemanticModelAsync (token).ConfigureAwait (false); - var compilationWithAnalyzer = model.Compilation.WithAnalyzers (analyzers, options); - - var diagnosticList = new List<Diagnostic> (); - diagnosticList.AddRange (await compilationWithAnalyzer.GetAnalyzerSemanticDiagnosticsAsync (model, null, token).ConfigureAwait (false)); - diagnosticList.AddRange (await compilationWithAnalyzer.GetAnalyzerSemanticDiagnosticsAsync (model, null, token).ConfigureAwait (false)); - - return diagnosticList.ToImmutableArray (); - } - - static async Task AddSuppressionMenuItems (CodeFixMenu menu, TextEditor editor, Diagnostic diag, TextSpan span) - { - var workspace = editor.DocumentContext.AnalysisDocument.Project.Solution.Workspace; - var language = editor.DocumentContext.AnalysisDocument.Project.Language; - var mefExporter = (IMefHostExportProvider)workspace.Services.HostServices; - - //TODO: cache this - var suppressionProviders = mefExporter.GetExports<ISuppressionFixProvider, CodeChangeProviderMetadata> () - .ToPerLanguageMapWithMultipleLanguages(); - - foreach (var suppressionProvider in suppressionProviders[LanguageNames.CSharp].Select (lz => lz.Value)) { - if (!suppressionProvider.CanBeSuppressedOrUnsuppressed (diag)) { - continue; - } - try { - var fixes = await suppressionProvider.GetSuppressionsAsync (editor.DocumentContext.AnalysisDocument, span, new [] { diag }, default (CancellationToken)).ConfigureAwait (false); - foreach (var fix in fixes) { - AddFixMenuItem (editor, menu, fix.Action); - } - } catch (Exception e) { - LoggingService.LogError ("Error while adding fixes", e); - } + if (configureMenu.Items.Count != 0) { + if (first) + menu.Add (CodeFixMenuEntry.Separator); + menu.Add (configureMenu); + first = false; } + return menu; } static bool DescriptorHasTag (DiagnosticDescriptor desc, string tag) @@ -278,11 +191,6 @@ namespace MonoDevelop.CodeActions return desc.CustomTags.Any (c => CultureInfo.InvariantCulture.CompareInfo.Compare (c, tag) == 0); } - static bool IsConfigurable (DiagnosticDescriptor desc) - { - return !DescriptorHasTag (desc, WellKnownDiagnosticTags.NotConfigurable); - } - static CodeFixMenuEntry CreateFixMenuEntry (TextEditor editor, CodeAction fix) { int mnemonic = -1; diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs index cb53d0a708..8f480dc917 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeRefactoringService.cs @@ -43,117 +43,6 @@ namespace MonoDevelop.CodeActions { static class CodeRefactoringService { - readonly static List<CodeDiagnosticProvider> providers = new List<CodeDiagnosticProvider> (); - - static CodeRefactoringService () - { - providers.Add (new BuiltInCodeDiagnosticProvider ()); - - AddinManager.AddExtensionNodeHandler ("/MonoDevelop/Refactoring/CodeDiagnosticProvider", delegate (object sender, ExtensionNodeEventArgs args) { - var node = (TypeExtensionNode)args.ExtensionNode; - switch (args.Change) { - case ExtensionChange.Add: - providers.Add ((CodeDiagnosticProvider)node.CreateInstance ()); - break; - } - }); - } - - public async static Task<IEnumerable<CodeDiagnosticDescriptor>> GetCodeDiagnosticsAsync (DocumentContext documentContext, string language, CancellationToken cancellationToken = default (CancellationToken)) - { - var result = new List<CodeDiagnosticDescriptor> (); - - foreach (var provider in providers) { - if (cancellationToken.IsCancellationRequested) - return Enumerable.Empty<CodeDiagnosticDescriptor> (); - result.AddRange (await provider.GetCodeDiagnosticDescriptorsAsync (documentContext, language, cancellationToken).ConfigureAwait (false)); - } - return result; - } - - public async static Task<IEnumerable<CodeDiagnosticFixDescriptor>> GetCodeFixesAsync (DocumentContext documentContext, string language, CancellationToken cancellationToken = default (CancellationToken)) - { - var result = new List<CodeDiagnosticFixDescriptor> (); - foreach (var provider in providers) { - result.AddRange (await provider.GetCodeFixDescriptorsAsync (documentContext, language, cancellationToken).ConfigureAwait (false)); - } - return result; - } - - public async static Task<IEnumerable<CodeRefactoringDescriptor>> GetCodeRefactoringsAsync (DocumentContext documentContext, string language, CancellationToken cancellationToken = default (CancellationToken)) - { - var result = new List<CodeRefactoringDescriptor> (); - foreach (var provider in providers) { - result.AddRange (await provider.GetCodeRefactoringDescriptorsAsync (documentContext, language, cancellationToken).ConfigureAwait (false)); - } - return result; - } - - static List<CodeRefactoringDescriptor> codeRefactoringCache; - public static async Task<IEnumerable<ValidCodeAction>> GetValidActionsAsync (TextEditor editor, DocumentContext doc, TextSpan span, CancellationToken cancellationToken = default (CancellationToken)) - { - if (editor == null) - throw new ArgumentNullException ("editor"); - if (doc == null) - throw new ArgumentNullException ("doc"); - var parsedDocument = doc.ParsedDocument; - var actions = new List<ValidCodeAction> (); - if (parsedDocument == null) - return actions;
- var analysisDocument = doc.AnalysisDocument; - if (analysisDocument == null) - return actions; - - var model = await analysisDocument.GetSemanticModelAsync (cancellationToken); - if (model == null) - return actions; - var root = await model.SyntaxTree.GetRootAsync (cancellationToken).ConfigureAwait (false); - if (span.End > root.Span.End) - return actions; - TextSpan tokenSegment = span; - var token = root.FindToken (span.Start); - if (!token.IsMissing) - tokenSegment = token.Span; - try { - if (codeRefactoringCache == null) { - codeRefactoringCache = (await GetCodeRefactoringsAsync (doc, MimeTypeToLanguage (editor.MimeType), cancellationToken).ConfigureAwait (false)).ToList (); - } - foreach (var descriptor in codeRefactoringCache) { - if (!descriptor.IsEnabled) - continue; - if (cancellationToken.IsCancellationRequested || analysisDocument == null) - return Enumerable.Empty<ValidCodeAction> (); - try { - await descriptor.GetProvider ().ComputeRefactoringsAsync ( - new CodeRefactoringContext (analysisDocument, span, delegate (CodeAction ca) { - var nrca = ca as NRefactoryCodeAction; - var validSegment = tokenSegment; - if (nrca != null) - validSegment = nrca.TextSpan; - actions.Add (new ValidCodeAction (ca, validSegment)); - }, cancellationToken) - ).ConfigureAwait (false); - } catch (OperationCanceledException) { - if (cancellationToken.IsCancellationRequested) - return Enumerable.Empty<ValidCodeAction> (); - } catch (AggregateException) { - if (cancellationToken.IsCancellationRequested) - return Enumerable.Empty<ValidCodeAction> (); - } catch (Exception e) { - LoggingService.LogError ("Error while getting refactorings from " + descriptor.IdString, e); - continue; - } - } - } catch (OperationCanceledException) { - if (cancellationToken.IsCancellationRequested) - return Enumerable.Empty<ValidCodeAction> (); - } catch (AggregateException) { - if (cancellationToken.IsCancellationRequested) - return Enumerable.Empty<ValidCodeAction> (); - } - return actions; - } - public static string MimeTypeToLanguage (string mimeType) { switch (mimeType) { diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/AnalyzersFromAssembly.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/AnalyzersFromAssembly.cs deleted file mode 100644 index 9799766e12..0000000000 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/AnalyzersFromAssembly.cs +++ /dev/null @@ -1,137 +0,0 @@ -// -// AnalyzersFromAssembly.cs -// -// Author: -// Mike Krüger <mkrueger@xamarin.com> -// -// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System; -using System.Linq; -using System.Collections.Generic; -using System.Reflection; -using System.Threading; -using MonoDevelop.CodeIssues; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.CodeFixes; -using RefactoringEssentials; -using MonoDevelop.Core; -using System.Threading.Tasks; -using MonoDevelop.Ide.Editor; -using MonoDevelop.CodeActions; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis; - -namespace MonoDevelop.CodeIssues -{ - - class AnalyzersFromAssembly - { - public List<CodeDiagnosticDescriptor> Analyzers; - public List<CodeDiagnosticFixDescriptor> Fixes; - public List<CodeRefactoringDescriptor> Refactorings; - - public readonly static AnalyzersFromAssembly Empty = new AnalyzersFromAssembly (); - - public AnalyzersFromAssembly () - { - Analyzers = new List<CodeDiagnosticDescriptor> (); - Fixes = new List<CodeDiagnosticFixDescriptor> (); - Refactorings = new List<CodeRefactoringDescriptor> (); - } - - readonly static string diagnosticAnalyzerAssembly = typeof (DiagnosticAnalyzerAttribute).Assembly.GetName ().Name; - - const bool ClrHeapEnabled = false; - internal void AddAssembly (System.Reflection.Assembly asm, bool force = false) - { - //FIXME; this is a really hacky arbitrary heuristic - //we should be using proper MEF composition APIs as part of the addin scan - if (!force) { - var assemblyName = asm.GetName ().Name; - switch (assemblyName) { - //whitelist - case "RefactoringEssentials": - case "Refactoring Essentials": - case "Microsoft.CodeAnalysis.Features": - case "Microsoft.CodeAnalysis.VisualBasic.Features": - case "Microsoft.CodeAnalysis.CSharp.Features": - break; - case "ClrHeapAllocationAnalyzer": - if (!ClrHeapEnabled) - return; - break; - //blacklist - case "FSharpBinding": - return; - //addin assemblies that reference roslyn - default: - var refAsm = asm.GetReferencedAssemblies (); - if (refAsm.Any (a => a.Name == diagnosticAnalyzerAssembly) && refAsm.Any (a => a.Name == "MonoDevelop.Ide")) - break; - return; - } - } - - try { - foreach (var type in asm.GetTypes ()) { - var analyzerAttr = (DiagnosticAnalyzerAttribute)type.GetCustomAttributes (typeof (DiagnosticAnalyzerAttribute), false).FirstOrDefault (); - if (analyzerAttr != null) { - try { - var analyzer = (DiagnosticAnalyzer)Activator.CreateInstance (type); - - if (analyzer.SupportedDiagnostics.Any (IsDiagnosticSupported)) { - Analyzers.Add (new CodeDiagnosticDescriptor (analyzerAttr.Languages, type)); - } - } catch (Exception e) { - LoggingService.LogError ($"error while adding diagnostic analyzer {type} from assembly {asm.FullName}", e); - } - } - - var codeFixAttr = (ExportCodeFixProviderAttribute)type.GetCustomAttributes (typeof(ExportCodeFixProviderAttribute), false).FirstOrDefault (); - if (codeFixAttr != null) { - Fixes.Add (new CodeDiagnosticFixDescriptor (type, codeFixAttr)); - } - - var exportAttr = type.GetCustomAttributes (typeof(ExportCodeRefactoringProviderAttribute), false).FirstOrDefault () as ExportCodeRefactoringProviderAttribute; - if (exportAttr != null) { - Refactorings.Add (new CodeRefactoringDescriptor (type, exportAttr)); - } - } - } catch (ReflectionTypeLoadException ex) { - foreach (var subException in ex.LoaderExceptions) { - LoggingService.LogError ("Error while loading diagnostics in " + asm.FullName, subException); - } - throw; - } - } - - static bool IsDiagnosticSupported (DiagnosticDescriptor diag) - { - //filter out E&C analyzers as we don't support E&C - if (diag.CustomTags.Contains (WellKnownDiagnosticTags.EditAndContinue)) { - return false; - } - - return true; - } - } -} diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/BuiltInCodeDiagnosticProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/BuiltInCodeDiagnosticProvider.cs deleted file mode 100644 index 08eff6b5d7..0000000000 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/BuiltInCodeDiagnosticProvider.cs +++ /dev/null @@ -1,112 +0,0 @@ -// -// BuiltInCodeDiagnosticProvider.cs -// -// Author: -// Mike Krüger <mkrueger@xamarin.com> -// -// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading; -using MonoDevelop.CodeIssues; -using MonoDevelop.Core; -using System.Threading.Tasks; -using MonoDevelop.Ide.Editor; -using MonoDevelop.CodeActions; - -namespace MonoDevelop.CodeIssues -{ - - /// <summary> - /// Provides all IDE code diagnostics and fix provider. - /// (Scans the app domain for providers) - /// </summary> - class BuiltInCodeDiagnosticProvider : CodeDiagnosticProvider - { - readonly static Task<AnalyzersFromAssembly> builtInDiagnostics; - - static BuiltInCodeDiagnosticProvider () - { - RefactoringEssentials.NRefactory6Host.GetLocalizedString = GettextCatalog.GetString; - builtInDiagnostics = Task.Run (() => { - var result = new AnalyzersFromAssembly (); - foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { - try { - result.AddAssembly (asm); - } catch (Exception e) { - LoggingService.LogError ("Error while loading diagnostics in " + asm.FullName, e); - } - } - return result; - }); - } - - internal static CodeDiagnosticDescriptor GetCodeDiagnosticDescriptor (string diagnosticId) - { - foreach (var builtInDescriptor in GetBuiltInCodeDiagnosticDescriptorsAsync (null).Result) { - if (builtInDescriptor.GetProvider ().SupportedDiagnostics.Any (diagnostic => diagnosticId == diagnostic.Id)) - return builtInDescriptor; - } - return null; - } - - internal async static Task<IEnumerable<CodeDiagnosticDescriptor>> GetBuiltInCodeDiagnosticDescriptorsAsync (string language, bool includeDisabledNodes = false, CancellationToken cancellationToken = default (CancellationToken)) - { - builtInDiagnostics.Wait (cancellationToken); - var diags = await builtInDiagnostics; - var builtInCodeDiagnostics = diags.Analyzers; - if (string.IsNullOrEmpty (language)) - return includeDisabledNodes ? builtInCodeDiagnostics : builtInCodeDiagnostics.Where (act => act.IsEnabled); - return includeDisabledNodes ? builtInCodeDiagnostics.Where (ca => ca.Languages.Contains (language)) : builtInCodeDiagnostics.Where (ca => ca.Languages.Contains (language) && ca.IsEnabled); - } - - public async static Task<IEnumerable<CodeDiagnosticFixDescriptor>> GetBuiltInCodeFixDescriptorsAsync (string language, CancellationToken cancellationToken = default (CancellationToken)) - { - var diags = await builtInDiagnostics.ConfigureAwait (false); - var builtInCodeFixes = diags.Fixes; - return string.IsNullOrEmpty (language) ? builtInCodeFixes : builtInCodeFixes.Where (cfp => cfp.Languages.Contains (language)); - } - - public async static Task<IEnumerable<CodeRefactoringDescriptor>> GetBuiltInCodeRefactoringDescriptorsAsync (string language, bool includeDisabledNodes = false, CancellationToken cancellationToken = default (CancellationToken)) - { - var diags = await builtInDiagnostics.ConfigureAwait (false); - var builtInCodeFixes = diags.Refactorings; - return string.IsNullOrEmpty (language) ? builtInCodeFixes : builtInCodeFixes.Where (cfp => cfp.Language.Contains (language)); - } - - public override Task<IEnumerable<CodeDiagnosticFixDescriptor>> GetCodeFixDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken) - { - return GetBuiltInCodeFixDescriptorsAsync (language, cancellationToken); - } - - public override Task<IEnumerable<CodeDiagnosticDescriptor>> GetCodeDiagnosticDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken) - { - return GetBuiltInCodeDiagnosticDescriptorsAsync (language, false, cancellationToken); - } - - public override Task<IEnumerable<CodeRefactoringDescriptor>> GetCodeRefactoringDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default (CancellationToken)) - { - return GetBuiltInCodeRefactoringDescriptorsAsync (language, false, cancellationToken); - } - } -}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticDescriptor.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticDescriptor.cs index 114a56ff2d..c0a9daca33 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticDescriptor.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticDescriptor.cs @@ -124,6 +124,11 @@ namespace MonoDevelop.CodeIssues return PropertyService.Get ("CodeIssues." + Languages + "." + IdString + "." + diagnostic.Id + ".enabled", true); } + internal bool GetIsEnabled (string diagnosticId) + { + return PropertyService.Get ("CodeIssues." + Languages + "." + IdString + "." + diagnosticId + ".enabled", true); + } + internal void SetIsEnabled (DiagnosticDescriptor diagnostic, bool value) { PropertyService.Set ("CodeIssues." + Languages + "." + IdString + "." + diagnostic.Id + ".enabled", value); diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticFixDescriptor.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticFixDescriptor.cs deleted file mode 100644 index b70fd9e0e2..0000000000 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticFixDescriptor.cs +++ /dev/null @@ -1,89 +0,0 @@ -// -// CodeFixDescriptor.cs -// -// Author: -// Mike Krüger <mkrueger@xamarin.com> -// -// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -using System; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using System.Linq; -using MonoDevelop.Core; - -namespace MonoDevelop.CodeIssues -{ - class CodeDiagnosticFixDescriptor - { - readonly Type codeFixProviderType; - readonly ExportCodeFixProviderAttribute attribute; - CodeFixProvider instance; - - public string Name { - get { - return attribute.Name; - } - } - - public string[] Languages { - get { - return attribute.Languages; - } - } - - internal CodeDiagnosticFixDescriptor (Type codeFixProviderType, ExportCodeFixProviderAttribute attribute) - { - if (codeFixProviderType == null) - throw new ArgumentNullException ("codeFixProviderType"); - if (attribute == null) - throw new ArgumentNullException ("attribute"); - this.codeFixProviderType = codeFixProviderType; - this.attribute = attribute; - } - - public CodeFixProvider GetCodeFixProvider () - { - if (instance == null) { - try { - instance = (CodeFixProvider)Activator.CreateInstance (codeFixProviderType); - } catch (InvalidCastException) { - LoggingService.LogError (codeFixProviderType + " can't be cast to CodeFixProvider."); - throw; - } - } - - return instance; - } - - public CodeDiagnosticDescriptor GetCodeDiagnosticDescriptor (string language) - { - var fixableIds = GetCodeFixProvider ().FixableDiagnosticIds.ToList (); - - foreach (var descriptor in BuiltInCodeDiagnosticProvider.GetBuiltInCodeDiagnosticDescriptorsAsync (language).Result) { - if (descriptor.GetProvider ().SupportedDiagnostics.Any (diagnostic => fixableIds.Contains (diagnostic.Id))) - return descriptor; - - } - return null; - - } - } -} diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticProvider.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticProvider.cs deleted file mode 100644 index 479d24dffb..0000000000 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -// CodeDiagnosticProvider.cs -// -// Author: -// Mike Krüger <mkrueger@xamarin.com> -// -// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System.Collections.Generic; -using System.Threading; -using MonoDevelop.CodeIssues; -using System.Threading.Tasks; -using MonoDevelop.Ide.Editor; -using MonoDevelop.CodeActions; - -namespace MonoDevelop.CodeIssues -{ - /// <summary> - /// The code diagnostic provider gives a list of code diagnostic and fix providers from an arbitrary source. - /// </summary> - abstract class CodeDiagnosticProvider - { - public abstract Task<IEnumerable<CodeDiagnosticDescriptor>> GetCodeDiagnosticDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default (CancellationToken)); - public abstract Task<IEnumerable<CodeDiagnosticFixDescriptor>> GetCodeFixDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default (CancellationToken)); - public abstract Task<IEnumerable<CodeRefactoringDescriptor>> GetCodeRefactoringDescriptorsAsync (DocumentContext document, string language, CancellationToken cancellationToken = default (CancellationToken)); - } -}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticRunner.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticRunner.cs index fda8a10732..6da380030f 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticRunner.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeDiagnosticRunner.cs @@ -48,131 +48,11 @@ namespace MonoDevelop.CodeIssues { static class CodeDiagnosticRunner { - static List<DiagnosticAnalyzer> providers = new List<DiagnosticAnalyzer> (); - static IEnumerable<CodeDiagnosticDescriptor> diagnostics; - static Dictionary<string, CodeDiagnosticDescriptor> diagnosticTable; - static SemaphoreSlim diagnosticLock = new SemaphoreSlim (1, 1); + static MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable options = + ((MonoDevelopWorkspaceDiagnosticAnalyzerProviderService)Ide.Composition.CompositionManager.GetExportedValue<IWorkspaceDiagnosticAnalyzerProviderService> ()).Options; + static TraceListener consoleTraceListener = new ConsoleTraceListener (); - static bool SkipContext (DocumentContext ctx) - { - return (ctx.IsAdHocProject || !(ctx.Project is MonoDevelop.Projects.DotNetProject)); - } - - static async Task GetDescriptorTable (AnalysisDocument analysisDocument, CancellationToken cancellationToken) - { - if (diagnosticTable != null) - return; - - bool locked = await diagnosticLock.WaitAsync (Timeout.Infinite, cancellationToken).ConfigureAwait (false); - if (diagnosticTable != null) - return; - - try { - var language = CodeRefactoringService.MimeTypeToLanguage (analysisDocument.Editor.MimeType); - var alreadyAdded = new HashSet<Type> (); - - var table = new Dictionary<string, CodeDiagnosticDescriptor> (); - - diagnostics = await CodeRefactoringService.GetCodeDiagnosticsAsync (analysisDocument.DocumentContext, language, cancellationToken); - foreach (var diagnostic in diagnostics) { - if (!alreadyAdded.Add (diagnostic.DiagnosticAnalyzerType)) - continue; - var provider = diagnostic.GetProvider (); - if (provider == null) - continue; - foreach (var diag in provider.SupportedDiagnostics) - table [diag.Id] = diagnostic; - - providers.Add (provider); - } - - diagnosticTable = table; - } finally { - if (locked) - diagnosticLock.Release (); - } - } - - // Old code, until we get EditorFeatures into composition so we can switch code fix service. - public static async Task<IEnumerable<Result>> Check (AnalysisDocument analysisDocument, CancellationToken cancellationToken) - { - var input = analysisDocument.DocumentContext; - if (!AnalysisOptions.EnableFancyFeatures || input.Project == null || !input.IsCompileableInProject || input.AnalysisDocument == null) - return Enumerable.Empty<Result> (); - if (SkipContext (input)) - return Enumerable.Empty<Result> (); - try { - var model = await analysisDocument.DocumentContext.AnalysisDocument.GetSemanticModelAsync (cancellationToken); - if (model == null) - return Enumerable.Empty<Result> (); - var compilation = model.Compilation; - var language = CodeRefactoringService.MimeTypeToLanguage (analysisDocument.Editor.MimeType); - - await GetDescriptorTable (analysisDocument, cancellationToken); - - if (providers.Count == 0 || cancellationToken.IsCancellationRequested) - return Enumerable.Empty<Result> (); - #if DEBUG - Debug.Listeners.Add (consoleTraceListener); - #endif - - CompilationWithAnalyzers compilationWithAnalyzer; - var analyzers = ImmutableArray<DiagnosticAnalyzer>.Empty.AddRange (providers); - var diagnosticList = new List<Diagnostic> (); - try { - var sol = analysisDocument.DocumentContext.AnalysisDocument.Project.Solution; - var options = new CompilationWithAnalyzersOptions ( - new WorkspaceAnalyzerOptions ( - new AnalyzerOptions (ImmutableArray<AdditionalText>.Empty), - sol.Options, - sol), - delegate (Exception exception, DiagnosticAnalyzer analyzer, Diagnostic diag) { - LoggingService.LogError ("Exception in diagnostic analyzer " + diag.Id + ":" + diag.GetMessage (), exception); - }, - false, - false - ); - - compilationWithAnalyzer = compilation.WithAnalyzers (analyzers, options); - if (input.ParsedDocument == null || cancellationToken.IsCancellationRequested) - return Enumerable.Empty<Result> (); - - diagnosticList.AddRange (await compilationWithAnalyzer.GetAnalyzerSemanticDiagnosticsAsync (model, null, cancellationToken).ConfigureAwait (false)); - diagnosticList.AddRange (await compilationWithAnalyzer.GetAnalyzerSyntaxDiagnosticsAsync (model.SyntaxTree, cancellationToken).ConfigureAwait (false)); - } catch (OperationCanceledException) { - } catch (AggregateException ae) { - ae.Flatten ().Handle (ix => ix is OperationCanceledException); - } catch (Exception ex) { - LoggingService.LogError ("Error creating analyzer compilation", ex); - return Enumerable.Empty<Result> (); - } finally { - #if DEBUG - Debug.Listeners.Remove (consoleTraceListener); - #endif - CompilationWithAnalyzers.ClearAnalyzerState (analyzers); - } - - return diagnosticList - .Where (d => !d.Id.StartsWith("CS", StringComparison.Ordinal)) - .Where (d => !diagnosticTable.TryGetValue (d.Id, out var desc) || desc.GetIsEnabled (d.Descriptor)) - .Select (diagnostic => { - var res = new DiagnosticResult(diagnostic); - // var line = analysisDocument.Editor.GetLineByOffset (res.Region.Start); - // Console.WriteLine (diagnostic.Id + "/" + res.Region +"/" + analysisDocument.Editor.GetTextAt (line)); - return res; - }); - } catch (OperationCanceledException) { - return Enumerable.Empty<Result> (); - } catch (AggregateException ae) { - ae.Flatten ().Handle (ix => ix is OperationCanceledException); - return Enumerable.Empty<Result> (); - } catch (Exception e) { - LoggingService.LogError ("Error while running diagnostics.", e); - return Enumerable.Empty<Result> (); - } - } - public static async Task<IEnumerable<Result>> Check (AnalysisDocument analysisDocument, CancellationToken cancellationToken, ImmutableArray<DiagnosticData> results) { var input = analysisDocument.DocumentContext; @@ -183,20 +63,21 @@ namespace MonoDevelop.CodeIssues Debug.Listeners.Add (consoleTraceListener); #endif - await GetDescriptorTable (analysisDocument, cancellationToken); - var resultList = new List<Result> (results.Length); foreach (var data in results) { if (input.IsAdHocProject && SkipError (data.Id)) continue; + if (data.IsSuppressed) + continue; + if (DataHasTag (data, WellKnownDiagnosticTags.EditAndContinue)) continue; - if (diagnosticTable.TryGetValue (data.Id, out var descriptor) && !descriptor.IsEnabled) + if (options.TryGetDiagnosticDescriptor (data.Id, out var desc) && !desc.GetIsEnabled (data.Id)) continue; - - var diagnostic = await data.ToDiagnosticAsync (analysisDocument, cancellationToken); + + var diagnostic = await data.ToDiagnosticAsync (analysisDocument, cancellationToken, desc); resultList.Add (new DiagnosticResult (diagnostic)); } return resultList; @@ -241,17 +122,13 @@ namespace MonoDevelop.CodeIssues return !lexicalError.Contains (errorId); } - static async Task<Diagnostic> ToDiagnosticAsync (this DiagnosticData data, AnalysisDocument analysisDocument, CancellationToken cancellationToken) + static async Task<Diagnostic> ToDiagnosticAsync (this DiagnosticData data, AnalysisDocument analysisDocument, CancellationToken cancellationToken, CodeDiagnosticDescriptor desc) { var project = analysisDocument.DocumentContext.AnalysisDocument.Project; var location = await data.DataLocation.ConvertLocationAsync (project, cancellationToken).ConfigureAwait (false); var additionalLocations = await data.AdditionalLocations.ConvertLocationsAsync (project, cancellationToken).ConfigureAwait (false); - DiagnosticSeverity severity; - if (diagnosticTable.TryGetValue (data.Id, out var desc)) - severity = diagnosticTable [data.Id].GetSeverity (data.Id, data.Severity); - else - severity = data.Severity; + DiagnosticSeverity severity = desc != null ? desc.GetSeverity (data.Id, data.Severity) : data.Severity; return Diagnostic.Create ( data.Id, data.Category, data.Message, severity, data.DefaultSeverity, diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeIssuePanelWidget.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeIssuePanelWidget.cs index 59a26bc5ca..db0004a6c8 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeIssuePanelWidget.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeIssues/CodeIssuePanelWidget.cs @@ -32,6 +32,8 @@ using Gdk; using GLib; using Gtk; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using MonoDevelop.AnalysisCore; using MonoDevelop.CodeActions; using MonoDevelop.Components; using MonoDevelop.Core; @@ -79,9 +81,16 @@ namespace MonoDevelop.CodeIssues readonly Dictionary<Tuple<CodeDiagnosticDescriptor, DiagnosticDescriptor>, DiagnosticSeverity?> severities = new Dictionary<Tuple<CodeDiagnosticDescriptor, DiagnosticDescriptor>, DiagnosticSeverity?> (); readonly Dictionary<Tuple<CodeDiagnosticDescriptor, DiagnosticDescriptor>, bool> enableState = new Dictionary<Tuple<CodeDiagnosticDescriptor, DiagnosticDescriptor>, bool> (); + static MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable options = + ((MonoDevelopWorkspaceDiagnosticAnalyzerProviderService)Ide.Composition.CompositionManager.GetExportedValue<IWorkspaceDiagnosticAnalyzerProviderService> ()).Options; + void GetAllSeverities () { - foreach (var node in BuiltInCodeDiagnosticProvider.GetBuiltInCodeDiagnosticDescriptorsAsync (CodeRefactoringService.MimeTypeToLanguage (mimeType), true).Result) { + var language = CodeRefactoringService.MimeTypeToLanguage (mimeType); + foreach (var node in options.AllDiagnostics) { + if (!node.Languages.Contains (language)) + continue; + foreach (var subIssue in node.GetProvider ().SupportedDiagnostics.Where (IsConfigurable).ToList ()) { var sub = new Tuple<CodeDiagnosticDescriptor, DiagnosticDescriptor> (node, subIssue); severities [sub] = node.GetSeverity (subIssue); diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.addin.xml b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.addin.xml index d0bb933641..1b2f6f142a 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.addin.xml +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.addin.xml @@ -150,4 +150,20 @@ <Assembly file="RefactoringEssentials.dll"/> </Extension> + <ExtensionPoint path="/MonoDevelop/Refactoring/AnalyzerAssemblies"> + <ExtensionNode name = "Assembly" type = "MonoDevelop.Core.AddIns.AssemblyExtensionNode" /> + </ExtensionPoint> + + <Extension path="/MonoDevelop/Refactoring/AnalyzerAssemblies"> + <Assembly file="RefactoringEssentials" /> + <Assembly file="Refactoring Essentials" /> + <Assembly file="Microsoft.CodeAnalysis.CSharp" /> + <Assembly file="Microsoft.CodeAnalysis.CSharp.Features" /> + <Assembly file="Microsoft.CodeAnalysis.EditorFeatures" /> + <Assembly file="Microsoft.CodeAnalysis.EditorFeatures.CSharp" /> + <Assembly file="Microsoft.CodeAnalysis.Features" /> + <Assembly file="Microsoft.CodeAnalysis.VisualBasic" /> + <Assembly file="Microsoft.CodeAnalysis.VisualBasic.Features" /> + </Extension> + </ExtensionModel> diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj index 7c2533e29e..565f8e0bb4 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring.csproj @@ -174,12 +174,6 @@ <Compile Include="MonoDevelop.CodeIssues\CodeDiagnosticRunner.cs" /> <Compile Include="MonoDevelop.CodeActions\CodeActionContainer.cs" /> <Compile Include="MonoDevelop.AnalysisCore\IssueMarker.cs" /> - <Compile Include="MonoDevelop.CodeIssues\CodeDiagnosticProvider.cs" /> - <Compile Include="MonoDevelop.CodeIssues\BuiltInCodeDiagnosticProvider.cs" /> - <Compile Include="MonoDevelop.CodeIssues\AnalyzersFromAssembly.cs" /> - <Compile Include="MonoDevelop.CodeActions\ValidCodeAction.cs" /> - <Compile Include="MonoDevelop.CodeActions\ValidCodeDiagnosticAction.cs" /> - <Compile Include="MonoDevelop.CodeIssues\CodeDiagnosticFixDescriptor.cs" /> <Compile Include="MonoDevelop.Refactoring\Commands.cs" /> <Compile Include="MonoDevelop.Refactoring\RefactoringSymbolInfo.cs" /> <Compile Include="MonoDevelop.CodeIssues\CodeFix.cs" /> @@ -205,6 +199,7 @@ <Compile Include="MonoDevelop.AnalysisCore\MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.AnalyzerAssemblyLoader.cs" /> <Compile Include="MonoDevelop.Refactoring.PickMembersService\PickMembersService.cs" /> <Compile Include="MonoDevelop.Refactoring.PickMembersService\PickMembersDialog.cs" /> + <Compile Include="MonoDevelop.AnalysisCore\MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable.cs" /> </ItemGroup> <ItemGroup> <Folder Include="MonoDevelop.Refactoring\" /> @@ -353,6 +348,7 @@ <InternalsVisibleTo Include="MonoDevelop.CSharpBinding" /> <InternalsVisibleTo Include="MonoDevelop.PackageManagement" /> <InternalsVisibleTo Include="MonoDevelop.MonoDroid" /> + <InternalsVisibleTo Include="MonoDevelop.Refactoring.Tests" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <ItemGroup> diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/AnalyzeWholeSolutionHandler.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/AnalyzeWholeSolutionHandler.cs index 8fe1b65c0e..8994b736d3 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/AnalyzeWholeSolutionHandler.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/AnalyzeWholeSolutionHandler.cs @@ -28,11 +28,11 @@ using MonoDevelop.Components.Commands; using MonoDevelop.Ide.TypeSystem; using System.Threading.Tasks; using System.Threading; -using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Generic; using MonoDevelop.CodeActions; using MonoDevelop.Ide.Editor; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; using MonoDevelop.CodeIssues; using MonoDevelop.Core; @@ -42,23 +42,26 @@ using MonoDevelop.Ide.Gui.Pads; using System.Collections.Immutable; using System.Linq; using MonoDevelop.Ide.Gui.Components; +using MonoDevelop.AnalysisCore; namespace MonoDevelop.Refactoring { class AnalyzeWholeSolutionHandler : CommandHandler { + static MonoDevelopWorkspaceDiagnosticAnalyzerProviderService.OptionsTable options = + ((MonoDevelopWorkspaceDiagnosticAnalyzerProviderService)Ide.Composition.CompositionManager.GetExportedValue<IWorkspaceDiagnosticAnalyzerProviderService> ()).Options; + internal static async Task<List<DiagnosticAnalyzer>> GetProviders (Project project) {
var providers = new List<DiagnosticAnalyzer> (); var alreadyAdded = new HashSet<Type> (); - var diagnostics = await CodeRefactoringService.GetCodeDiagnosticsAsync (new EmptyDocumentContext (project), LanguageNames.CSharp, default (CancellationToken)); + var diagnostics = options.AllDiagnostics.Where (x => x.Languages.Contains (LanguageNames.CSharp)); var diagnosticTable = new Dictionary<string, CodeDiagnosticDescriptor> (); foreach (var diagnostic in diagnostics) { - if (alreadyAdded.Contains (diagnostic.DiagnosticAnalyzerType)) - continue; if (!diagnostic.IsEnabled) continue; - alreadyAdded.Add (diagnostic.DiagnosticAnalyzerType); + if (!alreadyAdded.Add (diagnostic.DiagnosticAnalyzerType)) + continue; var provider = diagnostic.GetProvider (); if (provider == null) continue; diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.MdTextViewLine.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.MdTextViewLine.cs new file mode 100644 index 0000000000..46749c13e5 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.MdTextViewLine.cs @@ -0,0 +1,226 @@ +// +// MdTextViewLineCollection.MdTextViewLine.cs +// +// Author: +// Mike Krüger <mikkrg@microsoft.com> +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Formatting;
+using Microsoft.VisualStudio.Text.Implementation; +
+namespace Mono.TextEditor
+{
+ partial class MdTextViewLineCollection + { + sealed class MdTextViewLine : ITextViewLine + { + readonly DocumentLine line; + readonly TextViewMargin.LayoutWrapper layoutWrapper; + readonly MdTextViewLineCollection collection; + MonoTextEditor textEditor; + SnapshotSpan lineSpan; + int lineBreakLength; + + public MdTextViewLine(MdTextViewLineCollection collection, MonoTextEditor textEditor, DocumentLine line, TextViewMargin.LayoutWrapper layoutWrapper) + { + this.collection = collection; + this.layoutWrapper = layoutWrapper; + this.textEditor = textEditor; + this.line = line; + this.lineSpan = new SnapshotSpan(textEditor.VisualSnapshot, line.Offset, line.LengthIncludingDelimiter); + this.lineBreakLength = line.DelimiterLength; + } + + object indentityTag = new object (); + public object IdentityTag => indentityTag; + + public ITextSnapshot Snapshot => lineSpan.Snapshot; + + public bool IsFirstTextViewLineForSnapshotLine => collection[0] == this; + + public bool IsLastTextViewLineForSnapshotLine => collection[collection.Count - 1] == this; + + public double Baseline => throw new System.NotImplementedException(); + + public SnapshotSpan Extent => new SnapshotSpan (Snapshot, line.Offset, line.Length); + + public IMappingSpan ExtentAsMappingSpan => new MappingSpan (Extent, SpanTrackingMode.EdgeInclusive, null); + + public SnapshotSpan ExtentIncludingLineBreak => new SnapshotSpan (Snapshot, line.Offset, line.LengthIncludingDelimiter); + + public IMappingSpan ExtentIncludingLineBreakAsMappingSpan => new MappingSpan (ExtentIncludingLineBreak, SpanTrackingMode.EdgeInclusive, null); + + public SnapshotPoint Start => lineSpan.Start; + + public int Length => Length - LineBreakLength; + + public int LengthIncludingLineBreak => lineSpan.Length; + + public SnapshotPoint End => EndIncludingLineBreak - LineBreakLength; + + public SnapshotPoint EndIncludingLineBreak => lineSpan.End; + + public int LineBreakLength => lineBreakLength; + + public double Left => textEditor.VAdjustment.Value; + + public double Top => textEditor.LocationToPoint (line.LineNumber, 0).Y; + + public double Height => layoutWrapper.Height; + + public double TextTop => Top; + + public double TextBottom => Top + TextHeight; + + public double TextHeight => textEditor.LineHeight; + + public double TextLeft => Left; + + public double TextRight => layoutWrapper.Width; + + public double TextWidth => layoutWrapper.Width; + + public double Width => textEditor.TextViewMargin.RectInParent.Width; + + public double Bottom => TextBottom; + + public double Right => layoutWrapper.Width; + + public double EndOfLineWidth => 0; + + public double VirtualSpaceWidth => 0; + + public bool IsValid => true; + + public LineTransform LineTransform => DefaultLineTransform; + + public LineTransform DefaultLineTransform => new LineTransform (); + + public VisibilityState VisibilityState => VisibilityState.FullyVisible; + + public double DeltaY => 0; + + public TextViewLineChange Change => TextViewLineChange.None; + + SnapshotPoint FixBufferPosition(SnapshotPoint bufferPosition) + { + if (bufferPosition.Snapshot != this.lineSpan.Snapshot) + throw new ArgumentException("The specified SnapshotPoint is on a different ITextSnapshot than this SnapshotPoint."); + + return bufferPosition; + } + + public bool ContainsBufferPosition(SnapshotPoint bufferPosition) + { + bufferPosition = this.FixBufferPosition(bufferPosition); + + return ((bufferPosition >= lineSpan.Start) && + ((bufferPosition < lineSpan.End) || + ((bufferPosition == lineSpan.End) && + (lineBreakLength == 0) && (lineSpan.End == lineSpan.Snapshot.Length)))); + } + + public TextBounds? GetAdornmentBounds(object identityTag) + { + throw new System.NotImplementedException(); + } + + public ReadOnlyCollection<object> GetAdornmentTags(object providerTag) + { + throw new System.NotImplementedException(); + } + + public SnapshotPoint? GetBufferPositionFromXCoordinate(double xCoordinate, bool textOnly) + { + var y = textEditor.LocationToPoint(textEditor.OffsetToLocation(lineSpan.Start)).Y; + var loc = textEditor.PointToLocation(xCoordinate, y); + var pos = textEditor.LocationToOffset(loc); + return new SnapshotPoint(Snapshot, pos); + } + + public SnapshotPoint? GetBufferPositionFromXCoordinate(double xCoordinate) + { + return GetBufferPositionFromXCoordinate(xCoordinate, true); + } + + public TextBounds GetCharacterBounds(SnapshotPoint bufferPosition) + { + var point = textEditor.LocationToPoint(textEditor.OffsetToLocation(bufferPosition.Position)); + return new TextBounds(point.X, point.Y, textEditor.TextViewMargin.CharWidth, textEditor.LineHeight, TextTop, textEditor.LineHeight); + } + + public TextBounds GetCharacterBounds(VirtualSnapshotPoint bufferPosition) + { + throw new System.NotImplementedException(); + } + + public TextBounds GetExtendedCharacterBounds(SnapshotPoint bufferPosition) + { + return new TextBounds(textEditor.LocationToPoint(textEditor.OffsetToLocation(bufferPosition.Position)).X, Top, Width, TextHeight, TextTop, TextHeight); + } + + public TextBounds GetExtendedCharacterBounds(VirtualSnapshotPoint bufferPosition) + { + throw new System.NotImplementedException(); + } + + public VirtualSnapshotPoint GetInsertionBufferPositionFromXCoordinate(double xCoordinate) + { + throw new System.NotImplementedException(); + } + + public Collection<TextBounds> GetNormalizedTextBounds(SnapshotSpan bufferSpan) + { + if (bufferSpan.OverlapsWith(lineSpan)) + { + double leading = 0; + if (lineSpan.Contains(bufferSpan.Start)) + leading = textEditor.LocationToPoint(textEditor.OffsetToLocation(bufferSpan.Start)).X; + var endLoc = textEditor.OffsetToLocation(lineSpan.Contains(bufferSpan.End) ? bufferSpan.End : lineSpan.End); + double endPos = textEditor.LocationToPoint(endLoc).X; + return new Collection<TextBounds>(new List<TextBounds>() { new TextBounds(leading, Top, endPos - leading, TextHeight, TextTop, TextHeight) }); + } + else + return new Collection<TextBounds>(); + } + + public SnapshotSpan GetTextElementSpan(SnapshotPoint bufferPosition) + { + throw new System.NotImplementedException(); + } + + public VirtualSnapshotPoint GetVirtualBufferPositionFromXCoordinate(double xCoordinate) + { + throw new System.NotImplementedException(); + } + + public bool IntersectsBufferSpan(SnapshotSpan bufferSpan) + { + return lineSpan.IntersectsWith (bufferSpan); + } + }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.cs new file mode 100644 index 0000000000..789dde16ad --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.cs @@ -0,0 +1,145 @@ +// +// MdTextViewLineCollection.cs +// +// Author: +// Mike Krüger <mikkrg@microsoft.com> +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Formatting;
+using MonoDevelop.Core.Text; + +namespace Mono.TextEditor
+{
+ partial class MdTextViewLineCollection : List<ITextViewLine>, ITextViewLineCollection
+ {
+ readonly MonoTextEditor textView; + readonly ITextSourceVersion version; + + public MdTextViewLineCollection (MonoTextEditor textEditor) : base (64)
+ {
+ this.textView = textEditor;
+ this.version = this.textView.Document.Version;
+
+ int startLine = textEditor.TextArea.YToLine (textEditor.VAdjustment.Value);
+ double startY = textEditor.TextArea.LineToY (startLine);
+ double curY = startY;
+ double lastY = textEditor.VAdjustment.Value + textEditor.Allocation.Height;
+
+ for (int visualLineNumber = textEditor.GetTextEditorData ().LogicalToVisualLine (startLine); ; visualLineNumber++) {
+ int logicalLineNumber = textEditor.GetTextEditorData ().VisualToLogicalLine (visualLineNumber);
+ var line = textEditor.GetLine (logicalLineNumber);
+
+ if (line == null)
+ break;
+
+ Add (new MdTextViewLine (this, textEditor, line, textEditor.TextViewMargin.GetLayout (line)));
+
+ curY += textEditor.TextArea.GetLineHeight (line);
+ if (curY >= lastY)
+ break;
+ }
+ }
+
+ public ITextViewLine FirstVisibleLine => this.FirstOrDefault ();
+
+ public ITextViewLine LastVisibleLine => this.LastOrDefault ();
+
+ public SnapshotSpan FormattedSpan => new SnapshotSpan (this [0].Start, this.Last ().EndIncludingLineBreak);
+
+ public bool IsValid => version.CompareAge (textView.Document.Version) == 0;
+
+ public bool ContainsBufferPosition (SnapshotPoint bufferPosition)
+ {
+ foreach (var line in this) {
+ if (line.Start <= bufferPosition && line.End < bufferPosition)
+ return true;
+ }
+ return false;
+ }
+
+ public TextBounds GetCharacterBounds (SnapshotPoint bufferPosition)
+ {
+ foreach (var line in this) {
+ if (line.Start <= bufferPosition && line.End < bufferPosition) {
+ // TODO: correct both 0 parameters
+ return new TextBounds (0, line.Top, 0, line.Height, line.TextTop, line.TextHeight);
+ }
+ }
+
+ return new TextBounds ();
+ }
+
+ public int GetIndexOfTextLine (ITextViewLine textLine)
+ {
+ return IndexOf (textLine);
+ }
+
+ public Collection<TextBounds> GetNormalizedTextBounds (SnapshotSpan bufferSpan)
+ {
+ var bounds = new Collection<TextBounds> ();
+ foreach (var line in this)
+ foreach (var bound in line.GetNormalizedTextBounds (bufferSpan))
+ bounds.Add (bound);
+ return bounds;
+ }
+
+ public SnapshotSpan GetTextElementSpan (SnapshotPoint bufferPosition)
+ {
+ return new SnapshotSpan (bufferPosition, 1);
+ }
+
+ public ITextViewLine GetTextViewLineContainingBufferPosition (SnapshotPoint bufferPosition)
+ {
+ return this.FirstOrDefault (l => l.ContainsBufferPosition (bufferPosition));
+ }
+
+ public ITextViewLine GetTextViewLineContainingYCoordinate (double y)
+ {
+ return this.FirstOrDefault (l => l.Top <= y && l.Top + l.Height >= y);
+ }
+
+ public Collection<ITextViewLine> GetTextViewLinesIntersectingSpan (SnapshotSpan bufferSpan)
+ {
+ var result = new Collection<ITextViewLine> ();
+ foreach (var line in this)
+ if (line.IntersectsBufferSpan (bufferSpan))
+ result.Add (line);
+ return result;
+ }
+
+ public bool IntersectsBufferSpan (SnapshotSpan bufferSpan)
+ {
+ foreach (var line in this)
+ if (line.IntersectsBufferSpan (bufferSpan))
+ return true;
+ return false;
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.ITextView.MouseHover.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.ITextView.MouseHover.cs new file mode 100644 index 0000000000..eb5a29d6cb --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.ITextView.MouseHover.cs @@ -0,0 +1,226 @@ +using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Timers;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Xwt;
+
+namespace Mono.TextEditor
+{
+ partial class MonoTextEditor
+ {
+ internal IList<MouseHoverEventData> _mouseHoverEvents = new List<MouseHoverEventData> ();
+
+ internal Timer _mouseHoverTimer;
+ internal int _millisecondsSinceMouseMove = 0;
+
+ internal int? _lastHoverPosition = null;
+
+ private void InitializeMouse ()
+ {
+ if (_mouseHoverTimer != null)
+ return;
+ _mouseHoverTimer = new Timer ();
+ _mouseHoverTimer.Elapsed += delegate {
+ MonoDevelop.Core.Runtime.RunInMainThread (() => {
+ this.OnHoverTimer ();
+ });
+ };
+ }
+
+ internal void OnHoverTimer ()
+ {
+ if (!isClosed) {
+ _millisecondsSinceMouseMove += (int)_mouseHoverTimer.Interval;
+
+ if (Visible && _lastHoverPosition.HasValue)
+ this.RaiseHoverEvents ();
+ }
+ } + + /// <summary> + /// Event raised whenever the mouse has hovered over the same character + /// for 150 ms. + /// </summary> + /// <remarks>No hover events will be generated when the mouse is not over text in the buffer.</remarks> + public event EventHandler<MouseHoverEventArgs> MouseHover {
+ add {
+ lock (_mouseHoverEvents) {
+ InitializeMouse ();
+ if (_mouseHoverEvents.Count == 0) {
+ MotionNotifyEvent += OnMouseMove;
+ ButtonPressEvent += this.OnMouseDown;
+ }
+
+ _mouseHoverEvents.Add (new MouseHoverEventData (value));
+ }
+ }
+
+ remove {
+ lock (_mouseHoverEvents) {
+ for (int i = _mouseHoverEvents.Count - 1; (i >= 0); --i) {
+ if (_mouseHoverEvents [i].EventHandler == value) {
+ _mouseHoverEvents.RemoveAt (i);
+
+ if (_mouseHoverEvents.Count == 0) {
+ MotionNotifyEvent -= this.OnMouseMove;
+ ButtonPressEvent -= this.OnMouseDown;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ internal void OnMouseMove (object o, Gtk.MotionNotifyEventArgs args)
+ {
+ if (!isClosed) {
+ MonoDevelop.Core.Runtime.AssertMainThread ();
+ var but1 = (args.Event.State & Gdk.ModifierType.Button1Mask) == Gdk.ModifierType.Button1Mask;
+ var but2 = (args.Event.State & Gdk.ModifierType.Button2Mask) == Gdk.ModifierType.Button2Mask;
+ var but3 = (args.Event.State & Gdk.ModifierType.Button3Mask) == Gdk.ModifierType.Button3Mask;
+ if (!but1 && !but2 && !but3) {
+ this.HandleMouseMove (new Point (args.Event.X, args.Event.Y));
+ }
+ }
+ }
+
+ void OnMouseDown (object o, Gtk.ButtonPressEventArgs args)
+ {
+ if (!isClosed) {
+ _mouseHoverTimer.Stop ();
+ }
+ } + + + #region Mouse Related Helpers + + internal class MouseHoverEventData
+ {
+ public readonly MouseHoverAttribute Attribute;
+ public readonly EventHandler<MouseHoverEventArgs> EventHandler;
+ public bool Fired;
+
+ public MouseHoverEventData (EventHandler<MouseHoverEventArgs> eventHandler)
+ {
+ this.Attribute = GetMouseHoverAttribute (eventHandler);
+ this.EventHandler = eventHandler;
+ this.Fired = false;
+ }
+
+ private static MouseHoverAttribute GetMouseHoverAttribute (EventHandler<MouseHoverEventArgs> client)
+ {
+ object [] attributes = client.Method.GetCustomAttributes (typeof (MouseHoverAttribute), false);
+ foreach (object attribute in attributes) {
+ MouseHoverAttribute mouseHoverAttribute = attribute as MouseHoverAttribute;
+ if (mouseHoverAttribute != null) {
+ return mouseHoverAttribute;
+ }
+ }
+
+ return new MouseHoverAttribute (150);
+ }
+ } + + // internal for exposure to unit tests + internal void RaiseHoverEvents ()
+ {
+ MonoDevelop.Core.Runtime.AssertMainThread (); + + //See if there are any unfired events that are ready to fire. + MouseHoverEventData nextEvent = null;
+ IList<MouseHoverEventData> eventsToFire = new List<MouseHoverEventData> ();
+ lock (_mouseHoverEvents) {
+ foreach (var eventData in _mouseHoverEvents) {
+ if (!eventData.Fired) {
+ if (eventData.Attribute.Delay <= _millisecondsSinceMouseMove) {
+ eventsToFire.Add (eventData);
+ } else if ((nextEvent == null) || (eventData.Attribute.Delay < nextEvent.Attribute.Delay))
+ nextEvent = eventData;
+ }
+ }
+ }
+
+ if (eventsToFire.Count > 0) {
+ MouseHoverEventArgs args = new MouseHoverEventArgs (this, _lastHoverPosition.Value,
+ bufferGraph.CreateMappingPoint (new SnapshotPoint (Document.TextBuffer.CurrentSnapshot, _lastHoverPosition.Value), PointTrackingMode.Positive));
+ foreach (var eventData in eventsToFire) {
+ eventData.Fired = true;
+
+ try {
+ eventData.EventHandler (this, args);
+ } catch (Exception e) {
+ factoryService.GuardedOperations.HandleException (eventData.EventHandler, e);
+ }
+ }
+ }
+
+ if (nextEvent == null) { + //No more events to fire ... stop the timer. + _mouseHoverTimer.Stop ();
+ } else { + //Set the timer interval to match the delay to the next event. + int newDelay = Math.Max (50, (nextEvent.Attribute.Delay - _millisecondsSinceMouseMove));
+ _mouseHoverTimer.Interval = newDelay;
+ }
+ }
+
+ internal void HandleMouseMove (Point pt)
+ {
+ if (_mouseHoverEvents.Count > 0) {
+ int? newPosition = null;
+
+ if ((pt.X >= 0.0) && (pt.X < this.ViewportWidth) &&
+ (pt.Y >= 0.0) && (pt.Y < this.ViewportHeight)) {
+ double y = pt.Y + this.ViewportTop;
+
+ var line = TextViewLines.GetTextViewLineContainingYCoordinate (y);
+ if ((line != null) && (y >= line.TextTop) && (y <= line.TextBottom)) {
+ double x = pt.X + this.ViewportLeft;
+ newPosition = line.GetBufferPositionFromXCoordinate (x, true);
+ if ((!newPosition.HasValue) && (line.LineBreakLength == 0) && line.IsLastTextViewLineForSnapshotLine) { + //For purposes of hover events, pretend the last line in the buffer + //actually is padded by the EndOfLineWidth (even though it is not). + if ((line.TextRight <= x) && (x < line.TextRight + line.EndOfLineWidth))
+ newPosition = line.End;
+ }
+ }
+ }
+
+ if (newPosition != _lastHoverPosition) {
+ _lastHoverPosition = newPosition; + + //The mouse moved to a different character, reset the timer. + _mouseHoverTimer.Stop (); + + //If the mouse is over a character, reset the events & restart the timer. + if (newPosition.HasValue) {
+ int delay = int.MaxValue;
+ lock (_mouseHoverEvents) {
+ foreach (var eventData in _mouseHoverEvents) {
+ eventData.Fired = false;
+ if (eventData.Attribute.Delay < delay)
+ delay = eventData.Attribute.Delay;
+ }
+ } + + //In theory the last event could have been removed on a background thread after we checked the count. + if (delay != int.MaxValue) {
+ _millisecondsSinceMouseMove = 0;
+ _mouseHoverTimer.Interval = Math.Max (50, delay);
+ _mouseHoverTimer.Start ();
+ }
+ }
+ }
+ }
+ } + + #endregion //Mouse Related Helpers
+ }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.ITextView.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.ITextView.cs new file mode 100644 index 0000000000..636046e7d8 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.ITextView.cs @@ -0,0 +1,510 @@ +// +// MonoTextEditor.ITextView.cs +// +// Author: +// Mike Krüger <mikkrg@microsoft.com> +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Formatting;
+using Microsoft.VisualStudio.Text.Projection;
+using Microsoft.VisualStudio.Utilities;
+using Microsoft.VisualStudio.Text.Editor.Implementation;
+using Microsoft.VisualStudio.Text.Utilities;
+using System.Diagnostics;
+using MonoDevelop.Ide;
+using Microsoft.VisualStudio.Text.Classification;
+using System.Threading;
+
+namespace Mono.TextEditor
+{ + partial class MonoTextEditor : IMdTextView + { + #region Private Members
+ + public MonoTextEditor VisualElement { get => this; }
+
+ ITextBuffer textBuffer;
+
+ IBufferGraph bufferGraph;
+ ITextViewRoleSet roles;
+
+ ConnectionManager connectionManager;
+
+ TextEditorFactoryService factoryService;
+ int queuedSpaceReservationStackRefresh = 0; //int so that it can be set via Interlocked.CompareExchange() + + // IEditorFormatMap _editorFormatMap; + + bool hasInitializeBeenCalled = false;
+
+ ITextSelection selection;
+
+ bool hasAggregateFocus;
+
+ IEditorOptions editorOptions;
+
+ List<Lazy<ITextViewCreationListener, IDeferrableContentTypeAndTextViewRoleMetadata>> deferredTextViewListeners;
+
+ bool isClosed = false;
+
+ private PropertyCollection properties = new PropertyCollection (); + + //Only one view at a time will have aggregate focus, so keep track of it so that (when sending aggregate focus changed events) + //we give a view that had focus the chance to send its lost focus message before we claim aggregate focus. + [ThreadStatic]
+ static MonoTextEditor ViewWithAggregateFocus = null; +#if DEBUG
+ [ThreadStatic]
+ static bool SettingAggregateFocus = false; +#endif
+ + #endregion // Private Members
+ + /// <summary>
+ /// Text View constructor.
+ /// </summary>
+ /// <param name="textViewModel">The text view model that provides the text to visualize.</param>
+ /// <param name="roles">Roles for this view.</param>
+ /// <param name="parentOptions">Parent options for this view.</param>
+ /// <param name="factoryService">Our handy text editor factory service.</param>
+ internal void Initialize (ITextViewModel textViewModel, ITextViewRoleSet roles, IEditorOptions parentOptions, TextEditorFactoryService factoryService, bool initialize = true)
+ {
+ this.roles = roles;
+
+ this.factoryService = factoryService; + GuardedOperations = this.factoryService.GuardedOperations; + _spaceReservationStack = new SpaceReservationStack(this.factoryService.OrderedSpaceReservationManagerDefinitions, this);
+
+ this.TextDataModel = textViewModel.DataModel;
+ this.TextViewModel = textViewModel;
+
+ textBuffer = textViewModel.EditBuffer; + // _visualBuffer = textViewModel.VisualBuffer; + + // _textSnapshot = _textBuffer.CurrentSnapshot; + // _visualSnapshot = _visualBuffer.CurrentSnapshot; + + editorOptions = this.factoryService.EditorOptionsFactoryService.GetOptions (this);
+ editorOptions.Parent = parentOptions;
+
+ if (initialize)
+ this.Initialize ();
+ }
+
+ internal bool IsTextViewInitialized { get { return hasInitializeBeenCalled; } } + + // This method should only be called once (it is normally called from the ctor unless we're using + // ITextEditorFactoryService2.CreateTextViewWithoutInitialization on the factory to delay initialization). + internal void Initialize ()
+ {
+ if (hasInitializeBeenCalled)
+ throw new InvalidOperationException ("Attempted to Initialize a WpfTextView twice");
+
+ bufferGraph = factoryService.BufferGraphFactoryService.CreateBufferGraph (this.TextViewModel.VisualBuffer); + + //_editorFormatMap = _factoryService.EditorFormatMapService.GetEditorFormatMap(this); + + selection = new TextSelection (this); + + // this.Loaded += OnLoaded; + + // TODO: *Someone* needs to call this to execute UndoHistoryRegistry.RegisterHistory -- VS does this via the ShimCompletionControllerFactory. + factoryService.EditorOperationsProvider.GetEditorOperations (this);
+
+ connectionManager = new ConnectionManager (this, factoryService.TextViewConnectionListeners, factoryService.GuardedOperations);
+
+ SubscribeToEvents (); + + // Binding content type specific assets includes calling out to content-type + // specific view creation listeners. We need to do this as late as possible. + this.BindContentTypeSpecificAssets (null, TextViewModel.DataModel.ContentType); + + //Subscribe now so that there is no chance that a layout could be forced by a text change. + //_visualBuffer.ChangedLowPriority += OnVisualBufferChanged; + //_visualBuffer.ContentTypeChanged += OnVisualBufferContentTypeChanged; + + hasInitializeBeenCalled = true;
+ }
+
+ ITextCaret ITextView.Caret {
+ get {
+ return Caret;
+ }
+ }
+
+ public bool HasAggregateFocus {
+ get {
+ return hasAggregateFocus;
+ }
+ }
+
+ public bool InLayout {
+ get {
+ return false;
+ }
+ }
+
+ public bool IsClosed { get { return isClosed; } }
+
+ public bool IsMouseOverViewOrAdornments {
+ get {
+ return textArea.IsMouseTrapped;
+ }
+ }
+
+ public double MaxTextRightCoordinate {
+ get {
+ return Allocation.Width - TextViewMargin.XOffset;
+ }
+ }
+
+ IEditorOptions ITextView.Options {
+ get { return editorOptions; }
+ }
+
+ public PropertyCollection Properties {
+ get {
+ return properties;
+ }
+ }
+
+ public ITrackingSpan ProvisionalTextHighlight {
+ get {
+ throw new NotImplementedException ();
+ }
+
+ set {
+ throw new NotImplementedException ();
+ }
+ }
+
+ public ITextSelection Selection {
+ get {
+ return selection;
+ }
+ }
+
+ public ITextViewRoleSet Roles {
+ get {
+ return roles;
+ }
+ } + + /// <summary>
+ /// Gets the text buffer whose text, this text editor renders
+ /// </summary>
+ public ITextBuffer TextBuffer {
+ get {
+ return textBuffer;
+ }
+ }
+
+ public IBufferGraph BufferGraph {
+ get {
+ return bufferGraph;
+ }
+ }
+
+ public ITextSnapshot TextSnapshot {
+ get { + // TODO: MONO: WpfTextView has a much more complex calculation of this + return TextBuffer.CurrentSnapshot; + // return _textSnapshot; + }
+ }
+
+ public ITextSnapshot VisualSnapshot {
+ get {
+ return TextBuffer.CurrentSnapshot; + // return _visualSnapshot; + }
+ }
+
+ public ITextDataModel TextDataModel { get; private set; }
+ public ITextViewModel TextViewModel { get; private set; } + + public ITextViewLineCollection TextViewLines { get => new MdTextViewLineCollection (this); }
+
+ public double ViewportBottom {
+ get {
+ return TextViewMargin.RectInParent.Bottom;
+ }
+ }
+
+ public double ViewportHeight {
+ get {
+ return TextViewMargin.RectInParent.Height;
+ }
+ }
+
+ public double ViewportLeft {
+ get {
+ return 0;
+ }
+ set {
+ throw new NotImplementedException ();
+ }
+ }
+
+ public double ViewportRight {
+ get {
+ return TextViewMargin.RectInParent.Right + HAdjustment.Value;
+ }
+ }
+
+ public double ViewportTop {
+ get {
+ return TextViewMargin.RectInParent.Top;// + VAdjustment.Value; + }
+ }
+
+ public double ViewportWidth {
+ get {
+ return TextViewMargin.RectInParent.Width;
+ }
+ }
+
+ public IViewScroller ViewScroller {
+ get {
+ return this;
+ }
+ } + + public event EventHandler Closed;
+ public event EventHandler GotAggregateFocus;
+ public event EventHandler LostAggregateFocus; +#pragma warning disable CS0067
+ public event EventHandler<TextViewLayoutChangedEventArgs> LayoutChanged;
+ public event EventHandler ViewportLeftChanged;
+ public event EventHandler ViewportHeightChanged;
+ public event EventHandler ViewportWidthChanged; +#pragma warning restore CS0067
+ + public void Close ()
+ {
+ if (isClosed)
+ throw new InvalidOperationException ();//Strings.TextViewClosed); + isClosed = true; + + factoryService.GuardedOperations.RaiseEvent (this, this.Closed); + + if (hasAggregateFocus) { + //Silently lose aggregate focus (to preserve Dev11 compatibility which did not raise a focus changed event when the view was closed). + Debug.Assert (ViewWithAggregateFocus == this);
+ ViewWithAggregateFocus = null;
+ hasAggregateFocus = false;
+ }
+
+ UnsubscribeFromEvents ();
+
+ connectionManager.Close ();
+
+ TextViewModel.Dispose ();
+ TextViewModel = null;
+ }
+
+ public void DisplayTextLineContainingBufferPosition (SnapshotPoint bufferPosition, double verticalDistance, ViewRelativePosition relativeTo)
+ { + this.textArea.ScrollTo (bufferPosition.Position); + }
+
+ public void DisplayTextLineContainingBufferPosition (SnapshotPoint bufferPosition, double verticalDistance, ViewRelativePosition relativeTo, double? viewportWidthOverride, double? viewportHeightOverride)
+ {
+ this.textArea.ScrollTo (bufferPosition.Position); + }
+
+ public SnapshotSpan GetTextElementSpan (SnapshotPoint point)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public ITextViewLine GetTextViewLineContainingBufferPosition (SnapshotPoint bufferPosition)
+ {
+ return TextViewLines.GetTextViewLineContainingBufferPosition (bufferPosition);
+ }
+
+ public void QueueSpaceReservationStackRefresh ()
+ {
+ if (Interlocked.CompareExchange (ref queuedSpaceReservationStackRefresh, 1, 0) == 0) {
+ MonoDevelop.Core.Runtime.RunInMainThread (new Action (delegate {
+ Interlocked.Exchange (ref queuedSpaceReservationStackRefresh, 0);
+
+ if (!isClosed) {
+ _spaceReservationStack.Refresh ();
+ }
+ }));
+ }
+ } + + /// <remarks>
+ /// If you add an event subscription to this method, be sure to add the corresponding unsubscription to
+ /// UnsubscribeFromEvents()
+ /// </remarks>
+ private void SubscribeToEvents ()
+ {
+ if (IdeApp.IsInitialized)
+ IdeApp.Workbench.ActiveDocumentChanged += Workbench_ActiveDocumentChanged;
+ }
+
+ void Workbench_ActiveDocumentChanged (object sender, EventArgs e)
+ {
+ QueueAggregateFocusCheck ();
+ }
+
+ private void UnsubscribeFromEvents ()
+ {
+ if (IdeApp.IsInitialized)
+ IdeApp.Workbench.ActiveDocumentChanged -= Workbench_ActiveDocumentChanged;
+ }
+
+ private void BindContentTypeSpecificAssets (IContentType beforeContentType, IContentType afterContentType)
+ { + // Notify the Text view creation listeners + var extensions = UIExtensionSelector.SelectMatchingExtensions (factoryService.TextViewCreationListeners, afterContentType, beforeContentType, roles);
+ foreach (var extension in extensions) {
+ string deferOptionName = extension.Metadata.OptionName;
+ if (!string.IsNullOrEmpty (deferOptionName) && ((ITextView)this).Options.IsOptionDefined (deferOptionName, false)) {
+ object value = ((ITextView)this).Options.GetOptionValue (deferOptionName);
+ if (value is bool) {
+ if (!(bool)value) {
+ if (deferredTextViewListeners == null) {
+ deferredTextViewListeners = new List<Lazy<ITextViewCreationListener, IDeferrableContentTypeAndTextViewRoleMetadata>> ();
+ }
+ deferredTextViewListeners.Add (extension);
+ continue;
+ }
+ }
+ }
+
+ var instantiatedExtension = factoryService.GuardedOperations.InstantiateExtension (extension, extension);
+ if (instantiatedExtension != null) {
+ factoryService.GuardedOperations.CallExtensionPoint (instantiatedExtension,
+ () => instantiatedExtension.TextViewCreated (this));
+ }
+ }
+ } + + /// <summary>
+ /// Handles the Classification changed event that comes from the Classifier aggregator
+ /// </summary>
+ void OnClassificationChanged (object sender, ClassificationChangedEventArgs e)
+ {
+ if (!isClosed) { + // When classifications change, we just invalidate the lines. That invalidation will + // create new lines based on the new classifications. + + // Map the classification change (from the edit buffer) to the visual buffer + Span span = Span.FromBounds (
+ TextViewModel.GetNearestPointInVisualSnapshot (e.ChangeSpan.Start, VisualSnapshot, PointTrackingMode.Negative),
+ TextViewModel.GetNearestPointInVisualSnapshot (e.ChangeSpan.End, VisualSnapshot, PointTrackingMode.Positive)); + + //Classifications changes invalidate only the characters contained in the span so a zero length change + //will have no effect. + if (span.Length > 0) { + //IsLineInvalid will invalidate a line if it intersects the end. The result is that any call to InvalidateSpan() implicitly + //invalidates any line that starts at the end of the invalidated span, which we do not want here. Reduce the length of the classification + //change span one so -- if someone invalidated an entire line including the line break -- the next line will not be invalidated. + span = new Span (span.Start, span.Length - 1); + + // MONO: TODO: this + + //lock (_invalidatedSpans) + //{ + // if ((_attachedLineCache.Count > 0) || (_unattachedLineCache.Count > 0)) + // { + // _reclassifiedSpans.Add(span); + // this.QueueLayout(); + // } + //} + }
+ }
+ }
+
+ internal void QueueAggregateFocusCheck (bool checkForFocus = true)
+ { +#if DEBUG
+ if (SettingAggregateFocus) {
+ Debug.Fail ("WpfTextView.SettingAggregateFocus");
+ } +#endif
+ + if (!isClosed) {
+ bool newHasAggregateFocus = ((IdeApp.Workbench.ActiveDocument?.Editor?.Implementation as MonoDevelop.SourceEditor.SourceEditorView)?.TextEditor == this);
+ if (newHasAggregateFocus != hasAggregateFocus) {
+ hasAggregateFocus = newHasAggregateFocus;
+
+ if (hasAggregateFocus) { + //Got focus so make sure that the view that had focus (which wasn't us since we didn't have focus before) raises its + //lost focus event before we raise our got focus event. This will potentially do bad things if someone changes focus + //if the lost aggregate focus handler. + Debug.Assert (ViewWithAggregateFocus != this);
+ if (ViewWithAggregateFocus != null) {
+ ViewWithAggregateFocus.QueueAggregateFocusCheck (checkForFocus: false);
+ }
+ Debug.Assert (ViewWithAggregateFocus == null);
+ ViewWithAggregateFocus = this;
+ } else { + //Lost focus (which means we were the view with focus). + Debug.Assert (ViewWithAggregateFocus == this);
+ ViewWithAggregateFocus = null;
+ }
+
+ EventHandler handler = hasAggregateFocus ? this.GotAggregateFocus : this.LostAggregateFocus; + +#if DEBUG
+ try {
+ SettingAggregateFocus = true; +#endif
+ factoryService.GuardedOperations.RaiseEvent (this, handler); +#if DEBUG
+ } finally {
+ SettingAggregateFocus = false;
+ } +#endif
+ }
+ }
+ }
+
+ public IGuardedOperations GuardedOperations;
+ internal SpaceReservationStack _spaceReservationStack;
+
+ public ISpaceReservationManager GetSpaceReservationManager (string name)
+ {
+ if (name == null)
+ throw new ArgumentNullException ("name");
+
+ return _spaceReservationStack.GetOrCreateManager (name);
+ }
+
+ internal TextEditorFactoryService ComponentContext {
+ get { return factoryService; }
+ }
+
+ }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.IViewScroller.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.IViewScroller.cs new file mode 100644 index 0000000000..480d9b26a5 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.IViewScroller.cs @@ -0,0 +1,109 @@ +// +// MonoTextEditor.IViewScroller.cs +// +// Author: +// Mike Krüger <mikkrg@microsoft.com> +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Mono.TextEditor
+{
+ partial class MonoTextEditor : IViewScroller
+ { + void IViewScroller.EnsureSpanVisible(SnapshotSpan span) + { + ((IViewScroller)this).EnsureSpanVisible (new VirtualSnapshotSpan (span), EnsureSpanVisibleOptions.None); + } + + void IViewScroller.EnsureSpanVisible(SnapshotSpan span, EnsureSpanVisibleOptions options) + { + ((IViewScroller)this).EnsureSpanVisible (new VirtualSnapshotSpan (span), options); + } + + void IViewScroller.EnsureSpanVisible (VirtualSnapshotSpan span, EnsureSpanVisibleOptions options)
+ {
+ // If the textview is closed, this should be a no-op
+ if (!IsClosed) {
+ if ((options & ~(EnsureSpanVisibleOptions.ShowStart | EnsureSpanVisibleOptions.MinimumScroll | EnsureSpanVisibleOptions.AlwaysCenter)) != 0x00)
+ throw new ArgumentOutOfRangeException ("options");
+
+ //It is possible that this call is a result of an action that was defered until the view was loaded (& if so, it is possible that the
+ //snapshot changed inbetween).
+ span = span.TranslateTo (TextSnapshot);
+
+ // TODO: handle the various options for scrolling
+ ScrollTo (span.Start.Position);
+ }
+ }
+
+ void IViewScroller.ScrollViewportHorizontallyByPixels (double distanceToScroll)
+ {
+ HAdjustment.Value += distanceToScroll;
+ }
+
+ void IViewScroller.ScrollViewportVerticallyByLine (ScrollDirection direction)
+ { + ((IViewScroller)this).ScrollViewportVerticallyByLines (direction, 1); + }
+
+ void IViewScroller.ScrollViewportVerticallyByLines (ScrollDirection direction, int count)
+ {
+ switch (direction) { + case ScrollDirection.Up: + VAdjustment.Value -= LineHeight * count; + break; + case ScrollDirection.Down: + VAdjustment.Value += LineHeight * count; + break; + } + }
+
+ bool IViewScroller.ScrollViewportVerticallyByPage (ScrollDirection direction)
+ {
+ switch (direction) { + case ScrollDirection.Up: + if (VAdjustment.Value == 0) + return false; + VAdjustment.Value -= VAdjustment.PageSize; + return true; + case ScrollDirection.Down: + if (VAdjustment.Value + VAdjustment.PageSize > VAdjustment.Upper) + return false; + VAdjustment.Value += VAdjustment.PageSize; + return true; + } + return false; + } + + void IViewScroller.ScrollViewportVerticallyByPixels(double distanceToScroll) + { + VAdjustment.Value += distanceToScroll; + } + }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.cs index 162cc2814f..66c4cafa34 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MonoTextEditor.cs @@ -46,7 +46,7 @@ namespace Mono.TextEditor { [System.ComponentModel.Category("Mono.TextEditor")] [System.ComponentModel.ToolboxItem(true)] - class MonoTextEditor : Container + partial class MonoTextEditor : Container { readonly TextArea textArea; @@ -912,8 +912,7 @@ namespace Mono.TextEditor } catch (Exception e) { if (Debugger.IsAttached) Debugger.Break (); - //TODO: we should really find a way to log this properly - Console.WriteLine ("Error while executing " + action + " :" + e); + LoggingService.LogError ("Error while executing " + action, e); } } diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs index 10a9f4bb61..bb24a36718 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs @@ -53,7 +53,7 @@ using MonoDevelop.Ide.Editor.Highlighting; namespace Mono.TextEditor { - class TextArea : Container, ITextEditorDataProvider + partial class TextArea : Container, ITextEditorDataProvider { TextEditorData textEditorData; @@ -235,7 +235,7 @@ namespace Mono.TextEditor value = System.Math.Round (value); this.textEditorData.VAdjustment.Value = value; } - if (isMouseTrapped) + if (IsMouseTrapped) FireMotionEvent (mx + textViewMargin.XOffset, my, lastState); double delta = value - this.oldVadjustment; @@ -1497,17 +1497,17 @@ namespace Mono.TextEditor customText = null; } #endregion - bool isMouseTrapped = false; + internal bool IsMouseTrapped { get; set; } = false; protected override bool OnEnterNotifyEvent (EventCrossing evnt) { - isMouseTrapped = true; + IsMouseTrapped = true; return base.OnEnterNotifyEvent (evnt); } protected override bool OnLeaveNotifyEvent (Gdk.EventCrossing e) { - isMouseTrapped = false; + IsMouseTrapped = false; if (tipWindow != null && currentTooltipProvider != null) { if (!currentTooltipProvider.IsInteractive (textEditorData.Parent, tipWindow)) DelayedHideTooltip (); @@ -1831,7 +1831,7 @@ namespace Mono.TextEditor Options.ZoomOut (); this.QueueDraw (); - if (isMouseTrapped) + if (IsMouseTrapped) FireMotionEvent (mx + textViewMargin.XOffset, my, lastState); return true; } diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor.csproj b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor.csproj index b42d4007d5..31ce9d4e2e 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor.csproj +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <Import Project="..\..\..\MonoDevelop.props" /> <Import Project="$(ReferencesVSEditor)" /> @@ -123,6 +123,16 @@ </ProjectReference> </ItemGroup> <ItemGroup> + <Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\..\build\bin\Microsoft.VisualStudio.Threading.dll</HintPath> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.VisualStudio.Text.Implementation"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\..\build\bin\Microsoft.VisualStudio.Text.Implementation.dll</HintPath> + <Private>False</Private> + </Reference> <Reference Include="System" /> <Reference Include="System.Xml" /> <Reference Include="Mono.Posix" /> @@ -175,6 +185,11 @@ </None> <Compile Include="AssemblyInfo.cs" /> <Compile Include="gtk-gui\generated.cs" /> + <Compile Include="Mono.TextEditor\Gui\MdTextViewLineCollection.MdTextViewLine.cs" /> + <Compile Include="Mono.TextEditor\Gui\MdTextViewLineCollection.cs" /> + <Compile Include="Mono.TextEditor\Gui\MonoTextEditor.IViewScroller.cs" /> + <Compile Include="Mono.TextEditor\Gui\MonoTextEditor.ITextView.cs" /> + <Compile Include="Mono.TextEditor\Gui\MonoTextEditor.ITextView.MouseHover.cs" /> <Compile Include="MonoDevelop.SourceEditor\EditActions.cs" /> <Compile Include="MonoDevelop.SourceEditor\ExtensibleTextEditor.cs" /> <Compile Include="MonoDevelop.SourceEditor\LanguageItemWindow.cs" /> @@ -272,21 +287,76 @@ <Compile Include="Mono.TextEditor.Theatrics\SingleActorStage.cs" /> <Compile Include="Mono.TextEditor.Theatrics\SmartScrolledWindow.cs" /> <Compile Include="Mono.TextEditor.Theatrics\Stage.cs" /> + <None Include="packages.config" /> + <Compile Include="MonoDevelop.SourceEditor\TextMarker\LineSeparatorMarker.cs" /> + <Compile Include="MonoDevelop.SourceEditor.QuickTasks\QuickTaskOverviewMode.IndicatorDrawingCache.cs" /> + <Compile Include="MonoDevelop.SourceEditor.QuickTasks\QuickTaskOverviewMode.IdleUpdater.cs" /> + <Compile Include="MonoDevelop.SourceEditor\SourceEditorView.IMonoDevelopEditorOperations.cs" /> <Compile Include="VSEditor\ConnectionManager.cs" /> + <Compile Include="VSEditor\IMdTextView.cs" /> <Compile Include="VSEditor\ITagBasedSyntaxHighlightingFactory.cs" /> + <Compile Include="VSEditor\MDUtils.cs" /> <Compile Include="VSEditor\SmartIndentationService.cs" /> <Compile Include="VSEditor\TagBasedSyntaxHighlighting.cs" /> - <Compile Include="VSEditor\TextCaret.cs" /> <Compile Include="VSEditor\TextEditorFactoryService.cs" /> <Compile Include="VSEditor\TextSelection.cs" /> - <Compile Include="VSEditor\TextView.cs" /> <Compile Include="VSEditor\TextViewRoleSet.cs" /> <Compile Include="VSEditor\VacuousTextDataModel.cs" /> - <None Include="packages.config" /> - <Compile Include="MonoDevelop.SourceEditor\TextMarker\LineSeparatorMarker.cs" /> - <Compile Include="MonoDevelop.SourceEditor.QuickTasks\QuickTaskOverviewMode.IndicatorDrawingCache.cs" /> - <Compile Include="MonoDevelop.SourceEditor.QuickTasks\QuickTaskOverviewMode.IdleUpdater.cs" /> <Compile Include="MonoDevelop.SourceEditor\TextEditorKeyPressTimings.cs" /> + <Compile Include="VSEditor\FakeWpf\Geometry.cs" /> + <Compile Include="VSEditor\FakeWpf\Mouse.cs" /> + <Compile Include="VSEditor\FakeWpf\SimulatingExtensions.cs" /> + <Compile Include="VSEditor\Language\Def\Intellisense\IPopupIntellisensePresenter.cs" /> + <Compile Include="VSEditor\Language\Impl\Helpers.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\BaseIntellisenseSession.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\CurrentLineSpaceReservationAgent.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\IMultiSessionIntellisensePresenter.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\IntellisenseManager.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\IntellisenseSession.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\IntellisenseSessionStack.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\IntellisenseSessionStackMapService.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\IntellisenseSpaceReservationManagers.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\MultiSessionIntellisensePresenterProvider.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\SignatureHelp\DefaultSignatureHelpPresenter.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\SignatureHelp\DefaultSignatureHelpPresenterProvider.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\SignatureHelp\DefaultSignatureHelpPresenterSurfaceElement.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\SignatureHelp\SignatureHelpBroker.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\SignatureHelp\SignatureHelpParameterBoldingClassfier.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\SignatureHelp\SignatureHelpSession.cs" /> + <Compile Include="VSEditor\Language\Impl\Intellisense\SignatureHelp\SignatureHelpSessionView.cs" /> + <Compile Include="VSEditor\Language\Impl\QuickInfo\AsyncQuickInfoBroker.cs" /> + <Compile Include="VSEditor\Language\Impl\QuickInfo\AsyncQuickInfoSession.cs" /> + <Compile Include="VSEditor\Language\Impl\QuickInfo\QuickInfoController.cs" /> + <Compile Include="VSEditor\Language\Impl\QuickInfo\QuickInfoTextViewCreationListener.cs" /> + <Compile Include="VSEditor\Language\Impl\QuickInfo\SquiggleQuickInfoSource.cs" /> + <Compile Include="VSEditor\Language\Impl\QuickInfo\SquiggleQuickInfoSourceProvider.cs" /> + <Compile Include="VSEditor\Language\Util\LanguageUtil\IntellisenseSourceCache.cs" /> + <Compile Include="VSEditor\Language\Util\LanguageUtil\IntellisenseUtilities.cs" /> + <Compile Include="VSEditor\Text\Def\TextUIWpf\Adornments\ToolTipPresenterStyle.cs" /> + <Compile Include="VSEditor\Text\Def\TextUIWpf\Editor\ISpaceReservationAgent.cs" /> + <Compile Include="VSEditor\Text\Def\TextUIWpf\Editor\ISpaceReservationManager.cs" /> + <Compile Include="VSEditor\Text\Def\TextUIWpf\Editor\SpaceReservationAgentChangedEventArgs.cs" /> + <Compile Include="VSEditor\Text\Def\TextUIWpf\Editor\SpaceReservationManagerDefinition.cs" /> + <Compile Include="VSEditor\Text\Impl\ToolTipAdornment\GuardedToolTipPresenter.cs" /> + <Compile Include="VSEditor\Text\Impl\ToolTipAdornment\IViewElementFactoryMetadata.cs" /> + <Compile Include="VSEditor\Text\Impl\ToolTipAdornment\ToolTipService.cs" /> + <Compile Include="VSEditor\Text\Impl\ToolTipAdornment\ViewElementFactoryService.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\BaseWpfToolTipPresenter.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\DefaultToolTipPresenterStyle.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\MouseTrackingWpfToolTipPresenter.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\SpanTrackingWpfToolTipPresenter.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\ToolTipStyleFactory.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\WpfToolTipPresenterProvider.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\Legacy\ToolTipProvider.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\Legacy\ToolTipProviderFactory.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\ViewElementFactories\WpfClassifiedTextElementViewElementFactory.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\ViewElementFactories\WpfContainerElementViewElementFactory.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfToolTipAdornment\ViewElementFactories\WpfImageElementViewElementFactory.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfView\PopupAgent.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfView\SpaceReservationManager.cs" /> + <Compile Include="VSEditor\Text\Impl\WpfView\SpaceReservationStack.cs" /> + <Compile Include="VSEditor\Text\Util\TextDataUtil\MappingHelper.cs" /> + <Compile Include="VSEditor\VisualStudio\Impl\ViewAdapter\TipManager.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="gtk-gui\gui.stetic"> @@ -540,13 +610,5 @@ </ItemGroup> <Import Project="..\..\core\Mono.TextEditor.Shared\Mono.TextEditor.Shared.projitems" Label="Shared" Condition="Exists('..\..\core\Mono.TextEditor.Shared\Mono.TextEditor.Shared.projitems')" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> - <ItemGroup> - <Folder Include="MonoDevelop.SourceEditor\" /> - <Folder Include="MonoDevelop.SourceEditor.QuickTasks\" /> - <Folder Include="MonoDevelop.SourceEditor.Wrappers\" /> - <Folder Include="MonoDevelop.SourceEditor\TextMarker\" /> - <Folder Include="Mono.TextEditor\" /> - <Folder Include="Mono.TextEditor.PopupWindow\" /> - <Folder Include="Mono.TextEditor.Theatrics\" /> - </ItemGroup> + <ItemGroup /> </Project>
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/EditorFactory.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/EditorFactory.cs index 38fd7ada85..d0b2e80407 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/EditorFactory.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/EditorFactory.cs @@ -49,20 +49,16 @@ namespace MonoDevelop.SourceEditor ITextDocument ITextEditorFactory.CreateNewDocument (MonoDevelop.Core.Text.ITextSource textSource, string fileName, string mimeType) { - return new TextDocument (textSource.Text) { + return new TextDocument (textSource.Text, fileName, mimeType) { Encoding = textSource.Encoding, - MimeType = mimeType, - FileName = fileName }; } IReadonlyTextDocument ITextEditorFactory.CreateNewReadonlyDocument (MonoDevelop.Core.Text.ITextSource textSource, string fileName, string mimeType) { - return new TextDocument (textSource.Text) { + return new TextDocument (textSource.Text, fileName, mimeType) { Encoding = textSource.Encoding, IsReadOnly = true, - MimeType = mimeType, - FileName = fileName }; } diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.IMonoDevelopEditorOperations.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.IMonoDevelopEditorOperations.cs new file mode 100644 index 0000000000..35b08fd1db --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.IMonoDevelopEditorOperations.cs @@ -0,0 +1,642 @@ +// +// SourceEditorView.IEditorOperations.cs +// +// Author: +// Mike Krüger <mikkrg@microsoft.com> +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Text.Operations; +using Mono.TextEditor; +using MonoDevelop.Ide.Editor; + +namespace MonoDevelop.SourceEditor +{ + partial class SourceEditorView : IMonoDevelopEditorOperations + { + bool IEditorOperations.CanPaste => this.EnablePaste; + + bool IEditorOperations.CanDelete => this.EnableDelete; + + bool IEditorOperations.CanCut => this.EnableCut; + + public ITextView TextView { get; set; } + + IEditorOptions IEditorOperations.Options => throw new NotImplementedException (); + + ITrackingSpan IEditorOperations.ProvisionalCompositionSpan => throw new NotImplementedException (); + + string IEditorOperations.SelectedText => TextEditor.SelectedText; + + void IEditorOperations.MoveLineDown (bool extendSelection) + { + if (extendSelection) { + SelectionActions.MoveDown (TextEditor.GetTextEditorData ()); + } else { + CaretMoveActions.Down (TextEditor.GetTextEditorData ()); + } + } + + void IEditorOperations.MoveLineUp (bool extendSelection) + { + if (extendSelection) { + SelectionActions.MoveUp (TextEditor.GetTextEditorData ()); + } else { + CaretMoveActions.Up (TextEditor.GetTextEditorData ()); + } + } + + void IEditorOperations.MoveToPreviousCharacter (bool extendSelection) + { + if (extendSelection) { + SelectionActions.MoveLeft (TextEditor.GetTextEditorData ()); + } else { + CaretMoveActions.Left (TextEditor.GetTextEditorData ()); + } + } + + void IEditorOperations.MoveToNextCharacter (bool extendSelection) + { + if (extendSelection) { + SelectionActions.MoveLeft (TextEditor.GetTextEditorData ()); + } else { + CaretMoveActions.Left (TextEditor.GetTextEditorData ()); + } + } + + void IEditorOperations.MoveToEndOfLine (bool extendSelection) + { + if (extendSelection) { + SelectionActions.MoveLineEnd (TextEditor.GetTextEditorData ()); + } else { + CaretMoveActions.LineEnd (TextEditor.GetTextEditorData ()); + } + } + + void IEditorOperations.MoveToStartOfLine (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MoveLineHome); + } else { + TextEditor.RunAction (CaretMoveActions.LineHome); + } + } + + void IEditorOperations.MoveToStartOfDocument (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MoveToDocumentStart); + } else { + TextEditor.RunAction (CaretMoveActions.ToDocumentStart); + } + } + + void IEditorOperations.MoveToEndOfDocument (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MoveToDocumentEnd); + } else { + TextEditor.RunAction (CaretMoveActions.ToDocumentEnd); + } + } + + bool IEditorOperations.Backspace () + { + TextEditor.RunAction (DeleteActions.Backspace); + return true; + } + + bool IEditorOperations.CopySelection () + { + TextEditor.RunAction (ClipboardActions.Copy); + return true; + } + + bool IEditorOperations.CutSelection () + { + ClipboardActions.Cut (TextEditor.GetTextEditorData ()); + return true; + } + + bool IEditorOperations.Paste () + { + return ClipboardActions.PasteWithResult (TextEditor.GetTextEditorData ()); + } + + bool IEditorOperations.InsertNewLine () + { + TextEditor.RunAction (MiscActions.InsertNewLine); + return true; + } + + bool IEditorOperations.Tabify () + { + TextEditor.RunAction (MiscActions.InsertTab); + return true; + } + + bool IEditorOperations.Untabify () + { + TextEditor.RunAction (MiscActions.RemoveTab); + return true; + } + + bool IEditorOperations.DeleteWordToLeft () + { + TextEditor.RunAction (DeleteActions.PreviousWord); + return true; + } + + bool IEditorOperations.DeleteWordToRight () + { + TextEditor.RunAction (DeleteActions.NextWord); + return true; + } + + void IEditorOperations.ScrollLineCenter () + { + TextEditor.RunAction (MiscActions.RecenterEditor); + } + + + void IEditorOperations.MoveToNextWord (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MoveNextWord); + } else { + TextEditor.RunAction (CaretMoveActions.NextWord); + } + } + + void IEditorOperations.MoveToPreviousWord (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MovePreviousWord); + } else { + TextEditor.RunAction (CaretMoveActions.PreviousWord); + } + } + + void IEditorOperations.PageUp (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MovePageUp); + } else { + TextEditor.RunAction (CaretMoveActions.PageUp); + } + } + + void IEditorOperations.PageDown (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MovePageDown); + } else { + TextEditor.RunAction (CaretMoveActions.PageDown); + } + } + + bool IEditorOperations.DeleteFullLine () + { + TextEditor.RunAction (DeleteActions.CaretLine); + return true; + } + + bool IEditorOperations.DeleteToEndOfLine () + { + TextEditor.RunAction (DeleteActions.CaretLineToEnd); + return true; + } + + void IEditorOperations.ScrollLineTop () + { + TextEditor.RunAction (ScrollActions.Up); + } + + void IEditorOperations.ScrollLineBottom () + { + TextEditor.RunAction (ScrollActions.Down); + } + + void IEditorOperations.ScrollPageUp () + { + TextEditor.RunAction (ScrollActions.PageUp); + } + + void IEditorOperations.ScrollPageDown () + { + TextEditor.RunAction (ScrollActions.PageDown); + } + + bool IEditorOperations.Indent () + { + if (widget.TextEditor.IsSomethingSelected) { + MiscActions.IndentSelection (widget.TextEditor.GetTextEditorData ()); + } else { + int offset = widget.TextEditor.LocationToOffset (widget.TextEditor.Caret.Line, 1); + widget.TextEditor.Insert (offset, widget.TextEditor.Options.IndentationString); + } + return true; + } + + bool IEditorOperations.Unindent () + { + MiscActions.RemoveTab (widget.TextEditor.GetTextEditorData ()); + return true; + } + + void IEditorOperations.SelectAndMoveCaret (VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint) + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectAndMoveCaret (VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint, TextSelectionMode selectionMode) + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectAndMoveCaret (VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint, TextSelectionMode selectionMode, EnsureSpanVisibleOptions? scrollOptions) + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveToHome (bool extendSelection) + { + if (extendSelection) { + TextEditor.RunAction (SelectionActions.MoveLineHome); + } else { + TextEditor.RunAction (CaretMoveActions.LineHome); + } + } + + void IEditorOperations.GotoLine (int lineNumber) + { + TextEditor.Caret.Line = lineNumber; + TextEditor.ScrollToCaret (); + } + + void IEditorOperations.MoveCurrentLineToTop () + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveCurrentLineToBottom () + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveToStartOfLineAfterWhiteSpace (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveToStartOfNextLineAfterWhiteSpace (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveToStartOfPreviousLineAfterWhiteSpace (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveToLastNonWhiteSpaceCharacter (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveToTopOfView (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveToBottomOfView (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.SwapCaretAndAnchor () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.DeleteToBeginningOfLine () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.DeleteBlankLines () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.DeleteHorizontalWhiteSpace () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.OpenLineAbove () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.OpenLineBelow () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.IncreaseLineIndent () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.DecreaseLineIndent () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.InsertText (string text) + { + TextEditor.InsertAtCaret (text); + return true; + } + + bool IEditorOperations.InsertTextAsBox (string text, out VirtualSnapshotPoint boxStart, out VirtualSnapshotPoint boxEnd) + { + throw new NotImplementedException (); + } + + bool IEditorOperations.InsertProvisionalText (string text) + { + throw new NotImplementedException (); + } + + bool IEditorOperations.Delete () + { + TextEditor.RunAction (DeleteActions.Delete); + return true; + } + + bool IEditorOperations.ReplaceSelection (string text) + { + throw new NotImplementedException (); + } + + bool IEditorOperations.TransposeCharacter () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.TransposeLine () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.TransposeWord () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.MakeLowercase () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.MakeUppercase () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.ToggleCase () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.Capitalize () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.ReplaceText (Span replaceSpan, string text) + { + throw new NotImplementedException (); + } + + int IEditorOperations.ReplaceAllMatches (string searchText, string replaceText, bool matchCase, bool matchWholeWord, bool useRegularExpressions) + { + throw new NotImplementedException (); + } + + bool IEditorOperations.InsertFile (string filePath) + { + throw new NotImplementedException (); + } + + bool IEditorOperations.ConvertSpacesToTabs () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.ConvertTabsToSpaces () + { + throw new NotImplementedException (); + } + + bool IEditorOperations.NormalizeLineEndings (string replacement) + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectCurrentWord () + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectEnclosing () + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectFirstChild () + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectNextSibling (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectPreviousSibling (bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectLine (ITextViewLine viewLine, bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.SelectAll () + { + TextEditor.RunAction (SelectionActions.SelectAll); + } + + void IEditorOperations.ExtendSelection (int newEnd) + { + throw new NotImplementedException (); + } + + void IEditorOperations.MoveCaret (ITextViewLine textLine, double horizontalOffset, bool extendSelection) + { + throw new NotImplementedException (); + } + + void IEditorOperations.ResetSelection () + { + TextEditor.RunAction (SelectionActions.ClearSelection); + } + + bool IEditorOperations.CutFullLine () + { + throw new NotImplementedException (); + } + + void IEditorOperations.ScrollUpAndMoveCaretIfNecessary () + { + throw new NotImplementedException (); + } + + void IEditorOperations.ScrollDownAndMoveCaretIfNecessary () + { + throw new NotImplementedException (); + } + + void IEditorOperations.ScrollColumnLeft () + { + throw new NotImplementedException (); + } + + void IEditorOperations.ScrollColumnRight () + { + throw new NotImplementedException (); + } + + void IEditorOperations.AddBeforeTextBufferChangePrimitive () + { + throw new NotImplementedException (); + } + + void IEditorOperations.AddAfterTextBufferChangePrimitive () + { + throw new NotImplementedException (); + } + + void IEditorOperations.ZoomIn () + { + TextEditor.Options.ZoomIn (); + } + + void IEditorOperations.ZoomOut () + { + TextEditor.Options.ZoomOut (); + } + + void IEditorOperations.ZoomTo (double zoomLevel) + { + TextEditor.Options.Zoom = zoomLevel; + } + + string IEditorOperations.GetWhitespaceForVirtualSpace (VirtualSnapshotPoint point) + { + throw new NotImplementedException (); + } + + #region IMonoDevelopEditorOperations members + void IMonoDevelopEditorOperations.SwitchCaretMode () + { + TextEditor.RunAction (MiscActions.SwitchCaretMode); + } + + void IMonoDevelopEditorOperations.DeletePreviousSubword () + { + TextEditor.RunAction (DeleteActions.PreviousSubword); + } + + void IMonoDevelopEditorOperations.DeleteNextSubword () + { + TextEditor.RunAction (DeleteActions.NextSubword); + } + + void IMonoDevelopEditorOperations.StartCaretPulseAnimation () + { + TextEditor.StartCaretPulseAnimation (); + } + + void IMonoDevelopEditorOperations.JoinLines () + { + using (var undo = Document.OpenUndoGroup ()) { + TextEditor.RunAction (Mono.TextEditor.Vi.ViActions.Join); + } + } + + void IMonoDevelopEditorOperations.MoveToNextSubWord () + { + TextEditor.RunAction (SelectionActions.MoveNextSubword); + } + + void IMonoDevelopEditorOperations.MoveToPrevSubWord () + { + TextEditor.RunAction (SelectionActions.MovePreviousSubword); + } + + void IMonoDevelopEditorOperations.ShowQuickInfo () + { + widget.TextEditor.TextArea.ShowQuickInfo (); + } + + void IMonoDevelopEditorOperations.MoveBlockUp () + { + using (var undo = TextEditor.OpenUndoGroup ()) { + TextEditor.RunAction (MiscActions.MoveBlockUp); + CorrectIndenting (); + } + } + + void IMonoDevelopEditorOperations.MoveBlockDown () + { + using (var undo = TextEditor.OpenUndoGroup ()) { + TextEditor.RunAction (MiscActions.MoveBlockDown); + CorrectIndenting (); + } + } + + void IMonoDevelopEditorOperations.ToggleBlockSelectionMode () + { + TextEditor.SelectionMode = TextEditor.SelectionMode == MonoDevelop.Ide.Editor.SelectionMode.Normal ? MonoDevelop.Ide.Editor.SelectionMode.Block : MonoDevelop.Ide.Editor.SelectionMode.Normal; + TextEditor.QueueDraw (); + } + #endregion + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs index 4fca04c857..63c7f25158 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs @@ -1,4 +1,4 @@ -// SourceEditorView.cs +// SourceEditorView.cs // // Author: // Mike Krüger <mkrueger@novell.com> @@ -69,7 +69,7 @@ namespace MonoDevelop.SourceEditor ICompletionWidget, ISplittable, IFoldable, IToolboxDynamicProvider, ICustomFilteringToolboxConsumer, IZoomable, ITextEditorResolver, ITextEditorDataProvider, ICodeTemplateHandler, ICodeTemplateContextProvider, IPrintable, - ITextEditorImpl, IEditorActionHost, ITextMarkerFactory, IUndoHandler + ITextEditorImpl, ITextMarkerFactory, IUndoHandler { readonly SourceEditorWidget widget; bool isDisposed = false; @@ -267,13 +267,15 @@ namespace MonoDevelop.SourceEditor public DocumentAndLoaded (string fileName, string mimeType) {
if (AutoSave.AutoSaveExists(fileName)) {
// Don't load the document now, let Load() handle it
- this.Document = new TextDocument();
- this.Document.MimeType = mimeType;
- this.Document.FileName = fileName;
+ this.Document = new TextDocument(string.Empty, fileName, mimeType);
this.Loaded = false;
} else {
- this.Document = new TextDocument(fileName, mimeType);
+ // HACK we really need to be told explicitly that the document is for an existing file (that we should load) or a new file (which will have the given name/mime type)
+ // so we don't need to use File.Exists().
+ this.Document = File.Exists(fileName)
+ ? new TextDocument(fileName, mimeType)
+ : new TextDocument(string.Empty, fileName, mimeType);
this.Loaded = true;
}
@@ -2918,7 +2920,7 @@ namespace MonoDevelop.SourceEditor } } - IEditorActionHost ITextEditorImpl.Actions { + IMonoDevelopEditorOperations ITextEditorImpl.Actions { get { return this; } @@ -3237,236 +3239,6 @@ namespace MonoDevelop.SourceEditor - - #region IEditorActionHost implementation - - void IEditorActionHost.MoveCaretDown () - { - CaretMoveActions.Down (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.MoveCaretUp () - { - CaretMoveActions.Up (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.MoveCaretRight () - { - CaretMoveActions.Right (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.MoveCaretLeft () - { - CaretMoveActions.Left (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.MoveCaretToLineEnd () - { - CaretMoveActions.LineEnd (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.MoveCaretToLineStart () - { - CaretMoveActions.LineHome (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.MoveCaretToDocumentStart () - { - CaretMoveActions.ToDocumentStart (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.MoveCaretToDocumentEnd () - { - CaretMoveActions.ToDocumentEnd (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.Backspace () - { - DeleteActions.Backspace (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.ClipboardCopy () - { - ClipboardActions.Copy (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.ClipboardCut () - { - ClipboardActions.Cut (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.ClipboardPaste () - { - ClipboardActions.Paste (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.NewLine () - { - MiscActions.InsertNewLine (TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.SwitchCaretMode () - { - TextEditor.RunAction (MiscActions.SwitchCaretMode); - } - - void IEditorActionHost.InsertTab () - { - TextEditor.RunAction (MiscActions.InsertTab); - } - - void IEditorActionHost.RemoveTab () - { - TextEditor.RunAction (MiscActions.RemoveTab); - } - - void IEditorActionHost.InsertNewLine () - { - TextEditor.RunAction (MiscActions.InsertNewLine); - } - - void IEditorActionHost.DeletePreviousWord () - { - TextEditor.RunAction (DeleteActions.PreviousWord); - } - - void IEditorActionHost.DeleteNextWord () - { - TextEditor.RunAction (DeleteActions.NextWord); - } - - void IEditorActionHost.DeletePreviousSubword () - { - TextEditor.RunAction (DeleteActions.PreviousSubword); - } - - void IEditorActionHost.DeleteNextSubword () - { - TextEditor.RunAction (DeleteActions.NextSubword); - } - - void IEditorActionHost.StartCaretPulseAnimation () - { - TextEditor.StartCaretPulseAnimation (); - } - - void IEditorActionHost.RecenterEditor () - { - TextEditor.RunAction (MiscActions.RecenterEditor); - } - - void IEditorActionHost.JoinLines () - { - using (var undo = Document.OpenUndoGroup ()) { - TextEditor.RunAction (Mono.TextEditor.Vi.ViActions.Join); - } - } - - void IEditorActionHost.MoveNextSubWord () - { - TextEditor.RunAction (SelectionActions.MoveNextSubword); - } - - void IEditorActionHost.MovePrevSubWord () - { - TextEditor.RunAction (SelectionActions.MovePreviousSubword); - } - - void IEditorActionHost.MoveNextWord () - { - TextEditor.RunAction (CaretMoveActions.NextWord); - } - - void IEditorActionHost.MovePrevWord () - { - TextEditor.RunAction (CaretMoveActions.PreviousWord); - } - - void IEditorActionHost.PageUp () - { - TextEditor.RunAction (CaretMoveActions.PageUp); - } - - void IEditorActionHost.PageDown () - { - TextEditor.RunAction (CaretMoveActions.PageDown); - } - - void IEditorActionHost.DeleteCurrentLine () - { - TextEditor.RunAction (DeleteActions.CaretLine); - } - - void IEditorActionHost.DeleteCurrentLineToEnd () - { - TextEditor.RunAction (DeleteActions.CaretLineToEnd); - } - - void IEditorActionHost.ScrollLineUp () - { - TextEditor.RunAction (ScrollActions.Up); - } - - void IEditorActionHost.ScrollLineDown () - { - TextEditor.RunAction (ScrollActions.Down); - } - - void IEditorActionHost.ScrollPageUp () - { - TextEditor.RunAction (ScrollActions.PageUp); - } - - void IEditorActionHost.ScrollPageDown () - { - TextEditor.RunAction (ScrollActions.PageDown); - } - - void IEditorActionHost.MoveBlockUp () - { - using (var undo = TextEditor.OpenUndoGroup ()) { - TextEditor.RunAction (MiscActions.MoveBlockUp); - CorrectIndenting (); - } - } - - void IEditorActionHost.MoveBlockDown () - { - using (var undo = TextEditor.OpenUndoGroup ()) { - TextEditor.RunAction (MiscActions.MoveBlockDown); - CorrectIndenting (); - } - } - - void IEditorActionHost.ToggleBlockSelectionMode () - { - TextEditor.SelectionMode = TextEditor.SelectionMode == MonoDevelop.Ide.Editor.SelectionMode.Normal ? MonoDevelop.Ide.Editor.SelectionMode.Block : MonoDevelop.Ide.Editor.SelectionMode.Normal; - TextEditor.QueueDraw (); - } - - void IEditorActionHost.IndentSelection () - { - if (widget.TextEditor.IsSomethingSelected) { - MiscActions.IndentSelection (widget.TextEditor.GetTextEditorData ()); - } else { - int offset = widget.TextEditor.LocationToOffset (widget.TextEditor.Caret.Line, 1); - widget.TextEditor.Insert (offset, widget.TextEditor.Options.IndentationString); - } - } - - void IEditorActionHost.UnIndentSelection () - { - MiscActions.RemoveTab (widget.TextEditor.GetTextEditorData ()); - } - - void IEditorActionHost.ShowQuickInfo () - { - widget.TextEditor.TextArea.ShowQuickInfo (); - } - - #endregion - - #region ISegmentMarkerHost implementation ITextSegmentMarker ITextMarkerFactory.CreateUsageMarker (MonoDevelop.Ide.Editor.TextEditor editor, Usage usage) @@ -3508,11 +3280,22 @@ namespace MonoDevelop.SourceEditor { switch (effect) { case TextSegmentMarkerEffect.DottedLine: - case TextSegmentMarkerEffect.WavedLine: - var result = new GenericUnderlineMarker (new TextSegment (offset, length), effect); - if (color.HasValue) - result.Color = color.Value; - return result; + case TextSegmentMarkerEffect.WavedLine:
+ {
+ var result = new GenericUnderlineMarker (new TextSegment (offset, length), effect);
+ if (color.HasValue)
+ result.Color = color.Value;
+ return result;
+ }
+ case TextSegmentMarkerEffect.Underline:
+ {
+ var result = new GenericUnderlineMarker (new TextSegment (offset, length), effect);
+ if (color.HasValue)
+ result.Color = color.Value;
+ result.Wave = false;
+ return result;
+ }
+
case TextSegmentMarkerEffect.GrayOut: return new GrayOutMarker (new TextSegment (offset, length)); default: diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/TextMarker/WavedLineMarker.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/TextMarker/WavedLineMarker.cs index 843713c7a6..5db970acc4 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/TextMarker/WavedLineMarker.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/TextMarker/WavedLineMarker.cs @@ -1,4 +1,4 @@ -// +// // WavedLineMarker.cs // // Author: @@ -34,14 +34,12 @@ namespace MonoDevelop.SourceEditor { class GenericUnderlineMarker : UnderlineTextSegmentMarker, MonoDevelop.Ide.Editor.IGenericTextSegmentMarker { - HslColor color; - HslColor MonoDevelop.Ide.Editor.IGenericTextSegmentMarker.Color { get { - return color; + return Color; } set { - color = value; + Color = value; } } @@ -95,7 +93,7 @@ namespace MonoDevelop.SourceEditor return; double height = editor.LineHeight / 5; - cr.SetSourceColor (color); + cr.SetSourceColor (Color); if (effect == MonoDevelop.Ide.Editor.TextSegmentMarkerEffect.WavedLine) { Pango.CairoHelper.ShowErrorUnderline (cr, drawFrom, y + editor.LineHeight - height, drawTo - drawFrom, height); } else if (effect == MonoDevelop.Ide.Editor.TextSegmentMarkerEffect.DottedLine) { diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/Geometry.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/Geometry.cs new file mode 100644 index 0000000000..cea8d67b83 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/Geometry.cs @@ -0,0 +1,61 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xwt;
+
+namespace System.Windows.Media
+{
+ public abstract class Geometry
+ {
+ public static Geometry Empty { get; } = new EmptyGeometry ();
+ public abstract Rectangle Bounds { get; }
+
+ public abstract bool IsEmpty ();
+ }
+
+ class EmptyGeometry : Geometry
+ {
+ public override Rectangle Bounds => Rectangle.Zero;
+
+ public override bool IsEmpty ()
+ {
+ return true;
+ }
+ }
+
+ public class GeometryGroup : Geometry
+ {
+ public List<Geometry> Children { get; } = new List<Geometry> ();
+
+ public override Rectangle Bounds {
+ get {
+ Rectangle union = new Rectangle ();
+ foreach (var c in Children)
+ union = union.Union (c.Bounds);
+ return union;
+ }
+ }
+ public override bool IsEmpty ()
+ {
+ return !Children.Any (c => !c.IsEmpty ());
+ }
+ }
+
+ public class RectangleGeometry : Geometry
+ {
+ public Rectangle Rectangle;
+ public RectangleGeometry (Rectangle rectangle)
+ {
+ this.Rectangle = rectangle;
+ }
+
+ public override Rectangle Bounds => Rectangle;
+
+ public override bool IsEmpty ()
+ {
+ return Rectangle.IsEmpty;
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/Mouse.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/Mouse.cs new file mode 100644 index 0000000000..d854ea3934 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/Mouse.cs @@ -0,0 +1,19 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Mono.TextEditor;
+using Xwt;
+
+namespace System.Windows.Input
+{
+ class Mouse
+ {
+ internal static Point GetPosition (Gtk.Widget widget)
+ {
+ widget.GetPointer (out int x, out int y);
+ return new Point (x, y);
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/SimulatingExtensions.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/SimulatingExtensions.cs new file mode 100644 index 0000000000..42917e3d29 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/FakeWpf/SimulatingExtensions.cs @@ -0,0 +1,29 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Windows.Input
+{
+ static class MouseSimulatingExtensions
+ {
+ public static bool IsMouseOver (this Gtk.Widget widget)
+ {
+ widget.GetPointer (out int x, out int y);
+ return x >= 0 && y >= 0 && x <= widget.Allocation.Width && y <= widget.Allocation.Height;
+ }
+
+ public static bool IsMouseOver (this Xwt.Widget widget)
+ {
+ var mousePosition = Xwt.Desktop.MouseLocation;
+ return widget.ScreenBounds.Contains (mousePosition);
+ }
+
+ public static bool IsMouseOver (this Xwt.Window widget)
+ {
+ var mousePosition = Xwt.Desktop.MouseLocation;
+ return widget.ScreenBounds.Contains (mousePosition);
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/IMdTextView.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/IMdTextView.cs new file mode 100644 index 0000000000..353f69933d --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/IMdTextView.cs @@ -0,0 +1,23 @@ +using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Text.Editor
+{
+ interface IMdTextView : ITextView
+ {
+ /// <summary> + /// Gets a named <see cref="ISpaceReservationManager"/>. + /// </summary> + /// <param name="name">The name of the manager.</param> + /// <returns>An instance of the manager in this view. Not null.</returns> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="name"/> is not registered via an <see cref="SpaceReservationManagerDefinition"/>.</exception> + /// <remarks> + /// <para>Managers must be exported using <see cref="SpaceReservationManagerDefinition"/> component parts.</para> + /// </remarks> + ISpaceReservationManager GetSpaceReservationManager (string name);
+
+ Mono.TextEditor.MonoTextEditor VisualElement + { + get; + }
+}
+}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Def/Intellisense/IPopupIntellisensePresenter.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Def/Intellisense/IPopupIntellisensePresenter.cs new file mode 100644 index 0000000000..88ff524590 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Def/Intellisense/IPopupIntellisensePresenter.cs @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Adornments; + +namespace Microsoft.VisualStudio.Language.Intellisense +{ + /// <summary> + /// Defines an IntelliSense presenter that is rendered as a popup within an + /// <see cref="Microsoft.VisualStudio.Text.Editor.ITextView"/>. + /// </summary> + public interface IPopupIntellisensePresenter : IIntellisensePresenter + { + /// <summary> + /// Gets the WPF <see cref="UIElement"/> that the presenter wants to be displayed inside a + /// <see cref="Microsoft.VisualStudio.Text.Editor.ITextView"/> popup. + /// </summary> + Xwt.Widget SurfaceElement { get; } + + /// <summary> + /// Occurs when the WPF SurfaceElement is changed. + /// </summary> + event EventHandler SurfaceElementChanged; + + /// <summary> + /// Gets the <see cref="ITrackingSpan"/> to which this presenter is related. + /// </summary> + /// <remarks> + /// This property is used to determine where to + /// place the <see cref="Microsoft.VisualStudio.Text.Editor.ITextView"/> popup inside of which the presenter's + /// SurfaceElement is hosted. + /// </remarks> + ITrackingSpan PresentationSpan { get; } + + /// <summary> + /// Occurs when the PresentationSpan property changes. + /// </summary> + /// <remarks> + /// This is the way popup presenters signal that they should be moved. + /// </remarks> + event EventHandler PresentationSpanChanged; + + /// <summary> + /// Gets a set of flags that determine the popup style. + /// </summary> + PopupStyles PopupStyles { get; } + + /// <summary> + /// Occurs when the PopupStyles property changes. + /// </summary> + event EventHandler<ValueChangedEventArgs<PopupStyles>> PopupStylesChanged; + + /// <summary> + /// Gets the name of the space reservation manager that should be used to create popups for this presenter. + /// </summary> + /// <remarks> + /// Space reservation + /// managers can be ordered, thus ensuring predictable popup placement. + /// </remarks> + string SpaceReservationManagerName { get; } + + /// <summary> + /// Gets or sets the opacity of this popup presenter. + /// </summary> + /// <remarks> + /// The presenter should use this property to set the + /// opacity of its surface element and of any other text-obscuring UI elements it has provided. + /// </remarks> + double Opacity { get; set; } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Helpers.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Helpers.cs new file mode 100644 index 0000000000..c747a2cee0 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Helpers.cs @@ -0,0 +1,232 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Utilities; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudio.Language.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal static class Helpers + { + private static string GetAllTextDisplayedByCompletion(Completion c) + { + string displayText = c.DisplayText; + string suffixText = null; + + var c4 = c as Completion4; + if (c4 != null) + { + suffixText = c4.Suffix; + } + + return string.IsNullOrEmpty(suffixText) ? displayText : (displayText + suffixText); + } + +#if DEBUG + internal static void TrackObject(List<Lazy<IObjectTracker>> objectTrackers, string bucketName, object value) + { + foreach (Lazy<IObjectTracker> trackerExport in objectTrackers) + { + IObjectTracker objectTracker = trackerExport.Value; + if (objectTracker != null) + { + objectTracker.TrackObject(value, bucketName); + } + } + } +#endif + + internal static ITrackingSpan GetEncapsulatingSpan(ITextView textView, ITrackingSpan span1, ITrackingSpan span2) + { + // If either of the spans is null, return the other one. If they're both null, we'll end up returning null + // (as it should be). + if (span1 == null) + { + var spans = textView.BufferGraph.MapUpToBuffer + ( + span2.GetSpan(span2.TextBuffer.CurrentSnapshot), + span2.TrackingMode, + textView.TextBuffer + ); + Debug.Assert(spans.Count == 1); + return spans[0].Snapshot.CreateTrackingSpan(spans[0], span2.TrackingMode); + } + if (span2 == null) + { + var spans = textView.BufferGraph.MapUpToBuffer + ( + span1.GetSpan(span2.TextBuffer.CurrentSnapshot), + span1.TrackingMode, + textView.TextBuffer + ); + Debug.Assert(spans.Count == 1); + return spans[0].Snapshot.CreateTrackingSpan(spans[0], span1.TrackingMode); + } + + List<SnapshotSpan> surfaceSpans = new List<SnapshotSpan>(); + surfaceSpans.AddRange + ( + textView.BufferGraph.MapUpToBuffer + ( + span1.GetSpan(span1.TextBuffer.CurrentSnapshot), + span1.TrackingMode, + textView.TextBuffer + ) + ); + surfaceSpans.AddRange + ( + textView.BufferGraph.MapUpToBuffer + ( + span2.GetSpan(span2.TextBuffer.CurrentSnapshot), + span2.TrackingMode, + textView.TextBuffer + ) + ); + + ITrackingSpan encapsulatingSpan = null; + foreach (var span in surfaceSpans) + { + encapsulatingSpan = Helpers.GetEncapsulatingSpan + ( + encapsulatingSpan, + span.Snapshot.CreateTrackingSpan(span, span1.TrackingMode) + ); + } + + return encapsulatingSpan; + } + + internal static ITrackingSpan GetEncapsulatingSpan(ITrackingSpan span1, ITrackingSpan span2) + { + // If either of the spans is null, return the other one. If they're both null, we'll end up returning null + // (as it should be). + if (span1 == null) + { + return span2; + } + if (span2 == null) + { + return span1; + } + + // In the case where the two spans exist over different buffers, just use the first span. + if (span1.TextBuffer != span2.TextBuffer) + { + return span1; + } + + ITextSnapshot snapshot = span1.TextBuffer.CurrentSnapshot; + SnapshotSpan snapSpan1 = span1.GetSpan(snapshot); + SnapshotSpan snapSpan2 = span2.GetSpan(snapshot); + + return snapshot.CreateTrackingSpan + (Span.FromBounds + (Math.Min(snapSpan1.Start.Position, snapSpan2.Start.Position), + Math.Max(snapSpan1.End.Position, snapSpan2.End.Position)), + span1.TrackingMode); + } + + internal static IEnumerable<TStyle> GetMatchingPresenterStyles<TSession, TStyle> + (TSession session, + IList<Lazy<TStyle, IOrderableContentTypeMetadata>> orderedPresenterStyles, + GuardedOperations guardedOperations) + where TSession : IIntellisenseSession + { + List<TStyle> styles = new List<TStyle>(); + + ITextView textView = session.TextView; + SnapshotPoint? surfaceBufferPoint = session.GetTriggerPoint(textView.TextSnapshot); + if (surfaceBufferPoint == null) + { + return styles; + } + + var buffers = Helpers.GetBuffersForTriggerPoint(session).ToList(); + + foreach (var styleExport in orderedPresenterStyles) + { + bool usedThisProviderAlready = false; + foreach (var buffer in buffers) + { + foreach (string contentType in styleExport.Metadata.ContentTypes) + { + if (buffer.ContentType.IsOfType(contentType)) + { + var style = guardedOperations.InstantiateExtension(styleExport, styleExport); + if (!Object.Equals(style, default(TStyle))) + { + styles.Add(style); + } + usedThisProviderAlready = true; + break; + } + } + if (usedThisProviderAlready) + { + break; + } + } + } + + return styles; + } + + internal static Xwt.Rectangle GetScreenRect(IIntellisenseSession session) + { + return Xwt.MessageDialog.RootWindow.Screen.VisibleBounds; + //TODO + //if ((session != null) && (session.TextView != null)) { + // Visual sessionViewVisual = ((IWpfTextView)session.TextView).VisualElement; + // if ((sessionViewVisual != null) && PresentationSource.FromVisual (sessionViewVisual) != null) { + // Rect nativeScreenRect = WpfHelper.GetScreenRect (sessionViewVisual.PointToScreen (new Point (0, 0))); + // return new Rect + // (0, + // 0, + // nativeScreenRect.Width * WpfHelper.DeviceScaleX, + // nativeScreenRect.Height * WpfHelper.DeviceScaleY); + // } + //} + + //return new Rect + // (0, + // 0, + // SystemParameters.PrimaryScreenWidth * WpfHelper.DeviceScaleX, + // SystemParameters.PrimaryScreenHeight * WpfHelper.DeviceScaleY); + } + + internal static Collection<ITextBuffer> GetBuffersForTriggerPoint(IIntellisenseSession session) + { + return session.TextView.BufferGraph.GetTextBuffers( + buffer => session.GetTriggerPoint(buffer) != null); + } + + public static bool CanMapDownToBuffer(ITextView textView, ITextBuffer textBuffer, ITrackingPoint triggerPoint) + { + SnapshotPoint triggerSnapshotPoint = triggerPoint.GetPoint(textView.TextSnapshot); + var triggerSpan = new SnapshotSpan(triggerSnapshotPoint, 0); + + var mappedSpans = new FrugalList<SnapshotSpan>(); + MappingHelper.MapDownToBufferNoTrack(triggerSpan, textBuffer, mappedSpans); + return mappedSpans.Count > 0; + } + + public static IEnumerable<TSource> GetSources<TSource>( + IIntellisenseSession session, + Func<ITextBuffer, IEnumerable<TSource>> sourceCreator) where TSource : IDisposable + { + return IntellisenseSourceCache.GetSources( + session.TextView, + GetBuffersForTriggerPoint(session), + sourceCreator); + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/BaseIntellisenseSession.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/BaseIntellisenseSession.cs new file mode 100644 index 0000000000..c8d2849a4d --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/BaseIntellisenseSession.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Utilities; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal abstract class BaseIntellisenseSession : IIntellisenseSession + { + private PropertyCollection properties = new PropertyCollection(); + protected readonly ITextView textView; + protected IIntellisensePresenter presenter; + + protected BaseIntellisenseSession(ITextView textView) + { + if (textView == null) + { + throw new ArgumentNullException("textView"); + } + + this.textView = textView; + } + + public event EventHandler PresenterChanged; + public event EventHandler Recalculated; + public event EventHandler Dismissed; + + public ITextView TextView + { + get { return this.textView; } + } + + public PropertyCollection Properties + { + get { return this.properties; } + } + + public IIntellisensePresenter Presenter + { + get { return this.presenter; } + } + + public virtual void Dismiss() + { + this.properties = null; + this.presenter = null; + } + + public abstract void Start(); + public abstract void Recalculate(); + public abstract bool Match(); + public abstract void Collapse(); + public abstract bool IsDismissed { get; } + public abstract ITrackingPoint TriggerPoint { get; } + + public virtual ITrackingPoint GetTriggerPoint(ITextBuffer textBuffer) + { + var mappedTriggerPoint = GetTriggerPoint(textBuffer.CurrentSnapshot); + + if (!mappedTriggerPoint.HasValue) + { + return null; + } + + return mappedTriggerPoint.Value.Snapshot.CreateTrackingPoint(mappedTriggerPoint.Value, PointTrackingMode.Negative); + } + + public SnapshotPoint? GetTriggerPoint(ITextSnapshot textSnapshot) + { + var triggerSnapshotPoint = this.TriggerPoint.GetPoint(this.textView.TextSnapshot); + var triggerSpan = new SnapshotSpan(triggerSnapshotPoint, 0); + + var mappedSpans = new FrugalList<SnapshotSpan>(); + MappingHelper.MapDownToBufferNoTrack(triggerSpan, textSnapshot.TextBuffer, mappedSpans); + + if (mappedSpans.Count == 0) + { + return null; + } + else + { + return mappedSpans[0].Start; + } + } + + protected IIntellisensePresenter FindPresenter + (IIntellisenseSession session, + IList<Lazy<IIntellisensePresenterProvider, IOrderableContentTypeMetadata>> orderedPresenterProviders, + GuardedOperations guardedOperations) + { + var buffers = Helpers.GetBuffersForTriggerPoint(session); + + foreach (var presenterProviderExport in orderedPresenterProviders) + { + foreach (var buffer in buffers) + { + foreach (var contentType in presenterProviderExport.Metadata.ContentTypes) + { + if (buffer.ContentType.IsOfType(contentType)) + { + IIntellisensePresenter presenter = guardedOperations.InstantiateExtension( + session, presenterProviderExport, + factory => factory.TryCreateIntellisensePresenter(session)); + if (presenter != null) + { + return presenter; + } + } + } + } + } + + return null; + } + + + + protected void RaisePresenterChanged() + { + EventHandler tempHandler = this.PresenterChanged; + if (tempHandler != null) + { + tempHandler(this, EventArgs.Empty); + } + } + + protected void RaiseRecalculated() + { + EventHandler tempHandler = this.Recalculated; + if (tempHandler != null) + { + tempHandler(this, EventArgs.Empty); + } + } + + protected void RaiseDismissed() + { + EventHandler tempHandler = this.Dismissed; + if (tempHandler != null) + { + tempHandler(this, EventArgs.Empty); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/CurrentLineSpaceReservationAgent.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/CurrentLineSpaceReservationAgent.cs new file mode 100644 index 0000000000..f61e991312 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/CurrentLineSpaceReservationAgent.cs @@ -0,0 +1,220 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Windows; +using System.Windows.Media; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Text.Utilities; +using Microsoft.VisualStudio.Utilities; +using MonoDevelop.Components; +using Rect = Xwt.Rectangle; +using Point = Xwt.Point; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal class CurrentLineSpaceReservationAgent : ISpaceReservationAgent + { + internal const string CurrentLineSRManagerName = "currentline"; + + private ITextView _textView; + private IIntellisenseSessionStack _sessionStack; + private bool _isAttached = false; + + [Export(typeof(ITextViewCreationListener))] + [ContentType("Text")] + [TextViewRole(PredefinedTextViewRoles.Editable)] + internal class CurrentLineSpaceReservationAgent_ViewCreationListener : ITextViewCreationListener + { + [Import] + internal IIntellisenseSessionStackMapService IntellisenseSessionStackMapService { get; set; } + + public void TextViewCreated(ITextView textView) + { + var sessionStack = this.IntellisenseSessionStackMapService.GetStackForTextView(textView); + if (sessionStack != null) + { + var currentLineReservationAgent = new CurrentLineSpaceReservationAgent(textView, sessionStack); + textView.Properties.AddProperty(typeof(CurrentLineSpaceReservationAgent), currentLineReservationAgent); + } + } + } + + internal CurrentLineSpaceReservationAgent(ITextView textView, IIntellisenseSessionStack sessionStack) + { + _textView = textView; + _sessionStack = sessionStack; + + // We'll need to know when the collection of sessions changes. + ((INotifyCollectionChanged)_sessionStack.Sessions).CollectionChanged += this.OnIntellisenseSessions_CollectionChanged; + + // Make sure to unsubscribe when the view closes. + EventHandler closedHandler = null; + closedHandler = delegate + { + this.Detach(); + + ((INotifyCollectionChanged)_sessionStack.Sessions).CollectionChanged -= this.OnIntellisenseSessions_CollectionChanged; + _textView.Closed -= closedHandler; + }; + _textView.Closed += closedHandler; + } + + public Geometry PositionAndDisplay(Geometry reservedSpace) + { + return this.CurrentLineGeometry; + } + + public void Hide() + { + } + + public bool IsMouseOver + { + get { return false; } + } + + public bool HasFocus + { + get { return false; } + } + +#pragma warning disable 67 + public event EventHandler LostFocus; + public event EventHandler GotFocus; +#pragma warning restore 67 + + private void OnIntellisenseSessions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + IEnumerable<IIntellisenseSession> sessions = (IEnumerable<IIntellisenseSession>)sender; + + bool shouldBeAttached = false; + foreach (var session in sessions) + { + if ((session is ICompletionSession) || (session is ISignatureHelpSession)) + { + shouldBeAttached = true; + break; + } + } + + if (_isAttached && !shouldBeAttached) + { + this.Detach(); + } + else if (!_isAttached && shouldBeAttached) + { + this.Attach(); + } + } + + private void OnCaret_PositionChanged(object sender, CaretPositionChangedEventArgs e) + { + if (_isAttached) + { + _textView.QueueSpaceReservationStackRefresh(); + } + } + + private void OnSRManager_AgentChanged(object sender, SpaceReservationAgentChangedEventArgs e) + { + if (_isAttached && (e.OldAgent == this)) + { + // When our space reservation agent is removed from the view, wait for the next layout before re-adding the agent. + EventHandler<TextViewLayoutChangedEventArgs> layoutChangedHandler = null; + layoutChangedHandler = delegate(object layoutSender, TextViewLayoutChangedEventArgs layoutArgs) + { + // Make sure to always unsubscribe from the event. Do this first so we're sure not to leak this ref. + ITextView textView = (ITextView)layoutSender; + textView.LayoutChanged -= layoutChangedHandler; + + if ((_textView != null) && (this.CurrentLineSRManager != null)) + { + this.CurrentLineSRManager.AddAgent(this); + } + }; + _textView.LayoutChanged += layoutChangedHandler; + } + } + + private void Attach() + { + _isAttached = true; + + this.CurrentLineSRManager.AgentChanged += this.OnSRManager_AgentChanged; + _textView.Caret.PositionChanged += this.OnCaret_PositionChanged; + + if (this.CurrentLineSRManager != null) + { + this.CurrentLineSRManager.AddAgent(this); + } + } + + private void Detach() + { + _isAttached = false; + + this.CurrentLineSRManager.AgentChanged -= this.OnSRManager_AgentChanged; + _textView.Caret.PositionChanged -= this.OnCaret_PositionChanged; + + if (this.CurrentLineSRManager != null) + { + this.CurrentLineSRManager.RemoveAgent(this); + } + } + + private Geometry CurrentLineGeometry + { + get + { + Debug.Assert(_textView != null); + + if (!_isAttached || (_textView == null)) + { + return Geometry.Empty; + } + + var caretLine = _textView.Caret.ContainingTextViewLine; + + if ((caretLine != null) && + (caretLine.VisibilityState != VisibilityState.Unattached) && + (caretLine.VisibilityState != VisibilityState.Hidden)) + {
+ var topLeft = ((IMdTextView)_textView).VisualElement.GetScreenCoordinates
+ (new Gdk.Point((int)_textView.ViewportLeft, (int)(caretLine.TextTop - _textView.ViewportTop)));
+ Rect screenRect = new Rect
+ (topLeft.X,
+ topLeft.Y,
+ _textView.ViewportWidth,
+ (caretLine.TextHeight) + 3); // Add some buffer to allow for the possibility of a smart
+ // tag tickler below the line.
+ return new RectangleGeometry(screenRect); + } + else + { + return Geometry.Empty; + } + } + } + + private ISpaceReservationManager _currentLineSRManager; + private ISpaceReservationManager CurrentLineSRManager + { + get + { + if (_currentLineSRManager == null) + { + _currentLineSRManager = ((IMdTextView)_textView).GetSpaceReservationManager(CurrentLineSRManagerName); + } + + return _currentLineSRManager; + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IMultiSessionIntellisensePresenter.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IMultiSessionIntellisensePresenter.cs new file mode 100644 index 0000000000..581b307066 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IMultiSessionIntellisensePresenter.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows; +using Rect = Xwt.Rectangle; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + /// <summary> + /// An internal interface defining the basic operations for an <see cref="IIntellisensePresenter"/> that can be associated with more + /// than one <see cref="IIntellisenseSession"/>. + /// </summary> + internal interface IMultiSessionIntellisensePresenter<TSession> : IDisposable + where TSession : IIntellisenseSession + { + bool IsAttachedToSession { get; } + + void AttachToSession(TSession session); + void DetachFromSession(); + + Rect? AllowableScreenSize { get; } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseManager.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseManager.cs new file mode 100644 index 0000000000..c9b1847b37 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseManager.cs @@ -0,0 +1,194 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Projection; +using Microsoft.VisualStudio.Text.Utilities; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + [PartCreationPolicy(CreationPolicy.NonShared)] + [Export(typeof(ITextViewConnectionListener))] + [ContentType("text")] + [TextViewRole(PredefinedTextViewRoles.Editable)] + [TextViewRole(PredefinedTextViewRoles.EmbeddedPeekTextView)] + [TextViewRole(PredefinedTextViewRoles.CodeDefinitionView)] + internal class IntellisenseManagerConnectionListener : ITextViewConnectionListener + { + [ImportMany] + internal List<Lazy<IIntellisenseControllerProvider, IContentTypeMetadata>> IntellisenseControllerFactories { get; set; } + + [Import] + internal IGuardedOperations GuardedOperations { get; set; } + + public void SubjectBuffersConnected( + ITextView textView, + ConnectionReason reason, + IReadOnlyCollection<ITextBuffer> subjectBuffers) + { + IntellisenseManager manager = textView.Properties.GetOrCreateSingletonProperty( + delegate { return new IntellisenseManager(this, textView); }); + + // Create the appropriate Intellisense controllers for the content types in the buffer graph. It's important that we do + // this after creating the brokers, as the controllers will most likely start using the brokers immediately. + + for (int f = 0; f < this.IntellisenseControllerFactories.Count; ++f) + { + var factory = this.IntellisenseControllerFactories[f]; + + // filter subject buffers to get the ones that match the factory content types + FrugalList<ITextBuffer> matchingSubjectBuffers = new FrugalList<ITextBuffer>(); + foreach (string factoryContentType in factory.Metadata.ContentTypes) + { + foreach (ITextBuffer subjectBuffer in subjectBuffers) + { + if (subjectBuffer.ContentType.IsOfType(factoryContentType) && + !matchingSubjectBuffers.Contains(subjectBuffer)) + { + matchingSubjectBuffers.Add(subjectBuffer); + } + } + } + + if (matchingSubjectBuffers.Count > 0) + { + // This controller factory is registered for the content type we understand. Go ahead and create + // one. Note that this won't give us a handle to a controller object. We wouldn't be able to do anything + // with such a reference anyway. + + if (manager.Controllers[f] == null) + { + manager.Controllers[f] = this.GuardedOperations.InstantiateExtension + (factory, factory, + provider => provider.TryCreateIntellisenseController(textView, matchingSubjectBuffers)); + } + else + { + foreach (ITextBuffer matchingSubjectBuffer in matchingSubjectBuffers) + { + manager.Controllers[f].ConnectSubjectBuffer(matchingSubjectBuffer); + } + } + } + } + } + + public void SubjectBuffersDisconnected( + ITextView textView, + ConnectionReason reason, + IReadOnlyCollection<ITextBuffer> subjectBuffers) + { + // Notify controllers that subject buffer is no longer interesting. We let the controller figure out if the + // buffer was interesting in the first place. + IntellisenseManager manager = textView.Properties.GetProperty<IntellisenseManager>(typeof(IntellisenseManager)); + + for (int f = 0; f < manager.Controllers.Length; ++f) + { + if (manager.Controllers[f] != null) + { + foreach (ITextBuffer subjectBuffer in subjectBuffers) + { + manager.Controllers[f].DisconnectSubjectBuffer(subjectBuffer); + } + } + } + } + } + + internal class IntellisenseManager + { + private readonly IntellisenseManagerConnectionListener _componentContext; + private readonly ITextView _associatedTextView; + public IIntellisenseController[] Controllers { get; private set; } + + internal IntellisenseManager(IntellisenseManagerConnectionListener componentContext, ITextView associatedTextView) + { + _componentContext = componentContext; + this.Controllers = new IIntellisenseController[_componentContext.IntellisenseControllerFactories.Count]; + _associatedTextView = associatedTextView; + + _associatedTextView.Closed += this.OnViewClosed; + _associatedTextView.BufferGraph.GraphBufferContentTypeChanged += this.OnGraphBufferContentTypeChange; + } + + private void OnViewClosed(object sender, EventArgs e) + { + // Detach each of the Intellisense controllers on the associated view. We won't need them anymore + foreach (var controller in this.Controllers) + { + if (controller != null) + { + controller.Detach(_associatedTextView); + } + } + + // Stop listening to events + _associatedTextView.Closed -= this.OnViewClosed; + _associatedTextView.BufferGraph.GraphBufferContentTypeChanged -= this.OnGraphBufferContentTypeChange; + } + + private void OnGraphBufferContentTypeChange(object sender, GraphBufferContentTypeChangedEventArgs args) + { + if (args.BeforeContentType.IsOfType("text") && args.AfterContentType.IsOfType("text")) + { + // We won't get subject buffers connected/disconnected calls when both the before & after content + // types are "text", but we still need to manage intellisense controllers in this situation. + // The broker associated with the subjectBuffer in question remains the same. + ITextBuffer subjectBuffer = args.TextBuffer; + + for (int f = 0; f < _componentContext.IntellisenseControllerFactories.Count; ++f) + { + var factory = _componentContext.IntellisenseControllerFactories[f]; + bool beforeMatch = false; + bool afterMatch = false; + foreach (string factoryContentType in factory.Metadata.ContentTypes) + { + if (args.BeforeContentType.IsOfType(factoryContentType)) + { + beforeMatch = true; + } + if (args.AfterContentType.IsOfType(factoryContentType)) + { + afterMatch = true; + } + } + if (beforeMatch != afterMatch) + { + if (beforeMatch) + { + if (this.Controllers[f] != null) + { + // the controller will be null if its creation failed + this.Controllers[f].DisconnectSubjectBuffer(subjectBuffer); + // should we destroy the controller if it has no more buffers? + } + } + + if (afterMatch) + { + if (this.Controllers[f] != null) + { + this.Controllers[f].ConnectSubjectBuffer(subjectBuffer); + } + else + { + this.Controllers[f] = + this._componentContext.GuardedOperations.InstantiateExtension( + factory, + factory, + provider => provider.TryCreateIntellisenseController + (_associatedTextView, new FrugalList<ITextBuffer>() { subjectBuffer })); + } + } + } + } + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSession.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSession.cs new file mode 100644 index 0000000000..756775d29c --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSession.cs @@ -0,0 +1,30 @@ +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using System; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal abstract class IntellisenseSession : BaseIntellisenseSession + { + protected readonly ITrackingPoint triggerPoint; + + protected IntellisenseSession(ITextView textView, ITrackingPoint triggerPoint) + : base(textView) + { + if (triggerPoint == null) + { + throw new ArgumentNullException("triggerPoint"); + } + + this.triggerPoint = triggerPoint; + } + + public override ITrackingPoint TriggerPoint + { + get + { + return this.triggerPoint; + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSessionStack.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSessionStack.cs new file mode 100644 index 0000000000..f53c921949 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSessionStack.cs @@ -0,0 +1,550 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Windows.Input; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal class StackList<T> : ObservableCollection<T> + { + internal void Push(T item) + { + this.Insert(0, item); + } + + internal T Pop() + { + if (this.Count > 0) + { + T item = this[0]; + this.RemoveAt(0); + return (item); + } + else + { + return (default(T)); + } + } + + internal T Peek() + { + if (this.Count > 0) + { + return (this[0]); + } + else + { + return (default(T)); + } + } + } + + internal sealed class IntellisenseSessionStack : IIntellisenseSessionStack, IIntellisenseCommandTarget + { + private readonly IObscuringTipManager _tipManager; + private IMdTextView _textView; + private StackList<IIntellisenseSession> _sessions = new StackList<IIntellisenseSession>(); + private ReadOnlyObservableCollection<IIntellisenseSession> _readOnlySessions; + private Dictionary<IIntellisenseSession, ISpaceReservationAgent> _reservationAgentIndex = + new Dictionary<IIntellisenseSession, ISpaceReservationAgent>(); + private Dictionary<ISpaceReservationAgent, ISpaceReservationManager> _reservationManagerIndex = + new Dictionary<ISpaceReservationAgent, ISpaceReservationManager>(); + private IIntellisenseSession _keyboardSession; + private IIntellisenseSession _sessionBeingRehosted; + + public IntellisenseSessionStack(IMdTextView textView, IObscuringTipManager tipManager) + { + _textView = textView; + _tipManager = tipManager; // This can be null + } + + public IIntellisenseSession PopSession() + { + // First, remove the session at the top of the stack. + + IIntellisenseSession session = _sessions.Pop(); + if (session != null) + { + this.ReleaseKeyboard(); + this.RemoveSession(session); + } + + // Now, figure out once again who deserves to own the keyboard. + + this.DoleOutKeyboard(); + + return (session); + } + + public void PushSession(IIntellisenseSession session) + { + // make sure the session is not already dismissed + if (session.IsDismissed) + { + throw new ArgumentException("We cannot push a dismissed session on to the stack."); + } + + // Tell whatever session has the keyboard to release it. After we push, we'll figure out who deserves it. + + this.ReleaseKeyboard(); + + // Add the session to our list. + + _sessions.Push(session); + + // Subscribe to events on this new session + + session.Dismissed += this.OnSessionDismissed; + session.PresenterChanged += this.OnSessionPresenterChanged; + IPopupIntellisensePresenter popupPresenter = session.Presenter as IPopupIntellisensePresenter; + if (popupPresenter != null) + { + popupPresenter.SurfaceElementChanged += this.OnPresenterSurfaceElementChanged; + popupPresenter.PresentationSpanChanged += this.OnPresenterPresentationSpanChanged; + popupPresenter.PopupStylesChanged += this.OnPresenterPopupStylesChanged; + + // Since this is a popup presenter, we're responsible for drawing it. Therefore, here we'll create a popup agent + // that will take care of rendering this session's presenter. + + this.HostSession(session, popupPresenter); + } + else + { + ICustomIntellisensePresenter customPresenter = session.Presenter as ICustomIntellisensePresenter; + if (customPresenter != null) + { + customPresenter.Render(); + } + } + + // Now, figure out once again who deserves to own the keyboard. + + this.DoleOutKeyboard(); + } + + public void MoveSessionToTop(IIntellisenseSession session) + { + // Make sure this session is actually in the stack. + if (session == null) + { + throw new ArgumentNullException("session"); + } + int sessionIndex = _sessions.IndexOf(session); + if (sessionIndex == -1) + { + throw new ArgumentException + ("IIntellisenseSessionStack.MoveSessionToTop() must be called with a session already in the stack.", + "session"); + } + + // Release the keyboard. We'll give it back in a minute. + this.ReleaseKeyboard(); + + // Remove the session from the stack at its old position and re-add it at the top. + _sessions.RemoveAt(sessionIndex); + _sessions.Push(session); + + // Dole out the keyboard once again. + this.DoleOutKeyboard(); + } + + public ReadOnlyObservableCollection<IIntellisenseSession> Sessions + { + get + { + if (_readOnlySessions == null) + { + _readOnlySessions = new ReadOnlyObservableCollection<IIntellisenseSession>(_sessions); + } + + return _readOnlySessions; + } + } + + public IIntellisenseSession TopSession + { + get { return (_sessions.Peek()); } + } + + public void CollapseAllSessions() + { + List<IIntellisenseSession> sessions = new List<IIntellisenseSession>(_sessions); + foreach (var session in sessions) + { + session.Collapse(); + } + } + + /// <summary> + /// Calls each of the stack's session presenters, in order, to see if they want to handle the keyboard command + /// </summary> + public bool ExecuteKeyboardCommand(IntellisenseKeyboardCommand command) + { + // We don't care if there's a keyboard session here or not. If someone has captured the keyboard, this should only get + // called if the capturer has decided not to handle the command. + + // Run through the sessions from the topmost to the bottom-most. + foreach (IIntellisenseSession session in _sessions) + { + IIntellisenseCommandTarget commandTarget = session.Presenter as IIntellisenseCommandTarget; + if (commandTarget != null) + { + if (commandTarget.ExecuteKeyboardCommand(command)) + { + return true; + } + } + } + + return false; + } + + private void OnSpaceReservationManager_AgentChanged(object sender, SpaceReservationAgentChangedEventArgs e) + { + if (e.NewAgent == null) + { + List<IIntellisenseSession> sessionsToDismiss = new List<IIntellisenseSession>(); + + foreach (IIntellisenseSession session in _reservationAgentIndex.Keys) + { + // Skip sessions that are being re-hosted. + + if (session == _sessionBeingRehosted) + { continue; } + + if (_reservationAgentIndex[session] == e.OldAgent) + { + // We've just noticed that one of the agents we've put up on-screen has gone away. We need to dismiss + // the session being displayed with this agent. + + if (!session.IsDismissed) + { + sessionsToDismiss.Add(session); + } + } + } + + // We need to dismiss these outside of the main loop, as dismissing will change the _reservationAgentIndex, which + // will fail the enumeration above. + + foreach (IIntellisenseSession session in sessionsToDismiss) + { + session.Dismiss(); + } + } + } + + private void OnSessionDismissed(object sender, EventArgs e) + { + // Whenever a session is dismissed, we'll want to remove it from the stack, as it's no longer a "valid" session. + + IIntellisenseSession session = sender as IIntellisenseSession; + if (session == null) + { + throw new ArgumentException("Expected 'sender' to be of type IIntellisenseSession", "sender"); + } + + if (!_sessions.Contains(session)) + { + throw new ArgumentException("Expected session that is already on the stack", "sender"); + } + + // If it's the top session that was dismissed, our job is easy, we just have to pop() it off. + + if ((session.TextView != null) && (_tipManager != null)) + { + var tip = session.Presenter as IObscuringTip; + if (tip != null) + { + _tipManager.RemoveTip(session.TextView, tip); + } + } + + if (session == this.TopSession) + { + this.PopSession(); + return; + } + + // Must not have been the top session. We'll have to remove it from the stack manually, then remove all of our + // event handlers, etc. + + _sessions.Remove(session); + this.RemoveSession(session); + + // If the session being removed is the one that's holding-on to the keyboard, we need to figure out who owns the + // keyboard once again. + + if (session == _keyboardSession) + { + this.DoleOutKeyboard(); + } + } + + private void OnPresenterPresentationSpanChanged(object sender, EventArgs e) + { + IPopupIntellisensePresenter presenter = sender as IPopupIntellisensePresenter; + if (presenter == null) + { + throw new ArgumentException("Expected 'sender' to be of type IPopupIntellisensePresenter", "sender"); + } + + this.RehostPresenter(presenter); + } + + private void OnPresenterSurfaceElementChanged(object sender, EventArgs e) + { + IPopupIntellisensePresenter presenter = sender as IPopupIntellisensePresenter; + if (presenter == null) + { + throw new ArgumentException("Expected 'sender' to be of type IPopupIntellisensePresenter", "sender"); + } + + this.RehostPresenter(presenter); + } + + private void OnPresenterPopupStylesChanged(object sender, ValueChangedEventArgs<Text.Adornments.PopupStyles> e) + { + IPopupIntellisensePresenter presenter = sender as IPopupIntellisensePresenter; + if (presenter == null) + { + throw new ArgumentException("Expected 'sender' to be of type IPopupIntellisensePresenter", "sender"); + } + + this.RehostPresenter(presenter); + } + + private void OnSessionPresenterChanged(object sender, EventArgs e) + { + // It could have been any of our sessions that fired this event. That means we've first got to determine which one it + // was. + + IIntellisenseSession session = sender as IIntellisenseSession; + if (session == null) + { + throw new ArgumentException("Expected 'sender' to be of type IIntellisenseSession", "sender"); + } + + // Since the presenter changed, we could have a new owner for the keyboard. Figure it out. + + this.ReleaseKeyboard(); + + // We should make sure to re-subscribe to events on this new presenter. + + IPopupIntellisensePresenter popupPresenter = session.Presenter as IPopupIntellisensePresenter; + if (popupPresenter != null) + { + popupPresenter.PresentationSpanChanged += this.OnPresenterPresentationSpanChanged; + popupPresenter.SurfaceElementChanged += this.OnPresenterSurfaceElementChanged; + popupPresenter.PopupStylesChanged += this.OnPresenterPopupStylesChanged; + + this.RehostSession(session); + } + else + { + ICustomIntellisensePresenter customPresenter = session.Presenter as ICustomIntellisensePresenter; + if (customPresenter != null) + { + customPresenter.Render(); + } + } + + if ((session.TextView != null) && (_tipManager != null)) + { + var tip = session.Presenter as IObscuringTip; + if (tip != null) + { + _tipManager.PushTip(session.TextView, tip); + } + } + + this.DoleOutKeyboard(); + } + + private void ReleaseKeyboard() + { + if (_keyboardSession != null) + { + ICustomKeyboardHandler keyboardHandler = _keyboardSession.Presenter as ICustomKeyboardHandler; + if (keyboardHandler != null) + { + keyboardHandler.ReleaseKeyboard(); + } + } + + _keyboardSession = null; + } + + private void DoleOutKeyboard() + { + if (_keyboardSession != null) + { + this.ReleaseKeyboard(); + } + + // The idea is to walk down the stack from top to bottom, looking for the first session that has a presenter. When we + // find it, give it the keyboard, unless it already has it. + + foreach (IIntellisenseSession session in _sessions) + { + ICustomKeyboardHandler keyboardHandler = session.Presenter as ICustomKeyboardHandler; + if (keyboardHandler != null) + { + if (keyboardHandler.CaptureKeyboard()) + { + _keyboardSession = session; + break; + } + } + } + } + + private void HostSession(IIntellisenseSession session, IPopupIntellisensePresenter popupPresenter) + { + // If the Popup presenter doesn't have anything to draw, don't even bother. + + if (popupPresenter.SurfaceElement == null) + { return; } + + ISpaceReservationManager manager = _textView.GetSpaceReservationManager(popupPresenter.SpaceReservationManagerName); + if (manager != null) + { + // If this is the first time we've seen this manager, subscribe to its AgentChanged event. + + if (!_reservationManagerIndex.ContainsValue(manager)) + { + manager.AgentChanged += this.OnSpaceReservationManager_AgentChanged; + } + + ISpaceReservationAgent agent = manager.CreatePopupAgent(popupPresenter.PresentationSpan, + popupPresenter.PopupStyles, + popupPresenter.SurfaceElement); + + // We'll need to hold-on to the manager and agent so that later, when we want to hide this popup, we can clear + // the agent. + + _reservationManagerIndex[agent] = manager; + _reservationAgentIndex[session] = agent; + + // When we add this agent to the manager's collection, the popup will become visible. + + manager.AddAgent(agent); + } + } + + private void RehostSession(IIntellisenseSession session) + { + IPopupIntellisensePresenter popupPresenter = session.Presenter as IPopupIntellisensePresenter; + if (popupPresenter == null) + { + throw new ArgumentException("Expected to rehost a session with presenter of type IPopupIntellisensePresenter", + "session"); + } + + // If the Popup presenter doesn't have anything to draw, don't even bother. + + if (popupPresenter.PresentationSpan == null || popupPresenter.SurfaceElement == null) + { + return; + } + + // We need to re-draw this popup. This involves tearing down the old agent and re-creating it. First, we've + // got to find it. We'll also want to save-off a reference to it so that we know we're re-hosting this session. The + // process of tearing-down and re-adding a space reservation agent is loud and messy. We don't want to accidentally + // dismiss this session, thinking that it went out-of-focus. + + _sessionBeingRehosted = session; + + try + { + ISpaceReservationAgent oldAgent = null; + if ((_reservationAgentIndex.TryGetValue(session, out oldAgent)) && (oldAgent != null)) + { + ISpaceReservationManager manager = null; + if ((_reservationManagerIndex.TryGetValue(oldAgent, out manager)) && (manager != null)) + { + manager.UpdatePopupAgent(oldAgent, popupPresenter.PresentationSpan, popupPresenter.PopupStyles); + } + } + else + { + // This presenter hasn't yet been hosted in a popup. Host it for the first time. + + this.HostSession(session, popupPresenter); + } + } + finally + { + // Clear out our state. We're no longer re-hosting this session. + + _sessionBeingRehosted = null; + } + } + + private void RemoveSession(IIntellisenseSession session) + { + // A session can be added to the stack in only one way. It must be pushed onto the top of the stack. A session can + // come off the stack in many ways, however. It can be popped, or the session can be dismissed, etc. We'll assume that + // the session passed-in has already been removed from the stack. We're just interested in removing it from our indices + // and getting ourselves unhooked from its delegates. + + // First, unsubscribe from events to which we previously subscribed + + session.Dismissed -= this.OnSessionDismissed; + session.PresenterChanged -= this.OnSessionPresenterChanged; + + IPopupIntellisensePresenter popupPresenter = session.Presenter as IPopupIntellisensePresenter; + if (popupPresenter != null) + { + popupPresenter.SurfaceElementChanged -= this.OnPresenterSurfaceElementChanged; + popupPresenter.PresentationSpanChanged -= this.OnPresenterPresentationSpanChanged; + popupPresenter.PopupStylesChanged -= this.OnPresenterPopupStylesChanged; + + // Now, see if we have a popup agent for this presenter. If so, let's get rid of it. This is what will "hide" the + // popup. + + ISpaceReservationAgent agent; + if ((_reservationAgentIndex.TryGetValue(session, out agent)) && (agent != null)) + { + ISpaceReservationManager manager; + if ((_reservationManagerIndex.TryGetValue(agent, out manager)) && (manager != null)) + { + manager.RemoveAgent(agent); + _reservationManagerIndex.Remove(agent); + + // If this was the last reference to this particular manager, stop listening to its AgentChanged event. + + if (!_reservationManagerIndex.ContainsValue(manager)) + { + manager.AgentChanged -= this.OnSpaceReservationManager_AgentChanged; + } + } + _reservationAgentIndex.Remove(session); + } + } + } + + private void RehostPresenter(IPopupIntellisensePresenter presenter) + { + IIntellisenseSession session = presenter.Session; + if (session != null) + { + // Technically, there's probably no reason to release the keyboard here, but maybe there's a presenter that + // conditionally holds-on to the keyboard depending on its presentation. In that case, we'll give them another + // chance to own the keyboard here. + + this.ReleaseKeyboard(); + + RehostSession(session); + + this.DoleOutKeyboard(); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSessionStackMapService.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSessionStackMapService.cs new file mode 100644 index 0000000000..f110d2ae4b --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSessionStackMapService.cs @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + [Export(typeof(IIntellisenseSessionStackMapService))] + internal sealed class IntellisenseSessionStackMapService : IIntellisenseSessionStackMapService + { + [Import] + internal IToolTipProviderFactory ToolTipProviderFactory { get; set; } + + [Import(AllowDefault = true)] + internal IObscuringTipManager TipManager { get; set; } + +#if DEBUG + [ImportMany] + internal List<Lazy<IObjectTracker>> ObjectTrackers { get; set; } +#endif + + public IIntellisenseSessionStack GetStackForTextView(ITextView textView) + { + if (textView == null) + { + return (null); + } + + IIntellisenseSessionStack stack = null; + if (!textView.Properties.TryGetProperty<IIntellisenseSessionStack>(typeof(IIntellisenseSessionStack), out stack)) + { + var wpfTextView = textView as IMdTextView; + if (wpfTextView != null) + { + stack = new IntellisenseSessionStack(wpfTextView, this.TipManager); + +#if DEBUG + Helpers.TrackObject(this.ObjectTrackers, "Intellisense Session Stacks", stack); +#endif + + wpfTextView.Properties.AddProperty(typeof(IIntellisenseSessionStack), stack); + wpfTextView.Closed += this.OnTextViewClosed; + } + } + + return (stack); + } + + void OnTextViewClosed(object sender, EventArgs e) + { + ITextView textView = sender as ITextView; + if (textView != null) + { + textView.Properties.RemoveProperty(typeof(IIntellisenseSessionStack)); + textView.Closed -= this.OnTextViewClosed; + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSpaceReservationManagers.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSpaceReservationManagers.cs new file mode 100644 index 0000000000..462b256e00 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/IntellisenseSpaceReservationManagers.cs @@ -0,0 +1,38 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal class IntellisenseSpaceReservationManagers + { + [Export] + [Name(CurrentLineSpaceReservationAgent.CurrentLineSRManagerName)] + [Order(Before = IntellisenseSpaceReservationManagerNames.SmartTagSpaceReservationManagerName)] + internal SpaceReservationManagerDefinition currentLineManager; + + [Export] + [Name(IntellisenseSpaceReservationManagerNames.SmartTagSpaceReservationManagerName)] + [Order(Before = IntellisenseSpaceReservationManagerNames.QuickInfoSpaceReservationManagerName)] + internal SpaceReservationManagerDefinition smartTagManager; + + [Export] + [Name(IntellisenseSpaceReservationManagerNames.QuickInfoSpaceReservationManagerName)] + [Order(Before = IntellisenseSpaceReservationManagerNames.SignatureHelpSpaceReservationManagerName)] + internal SpaceReservationManagerDefinition quickInfoManager; + + [Export] + [Name(IntellisenseSpaceReservationManagerNames.SignatureHelpSpaceReservationManagerName)] + [Order(Before = IntellisenseSpaceReservationManagerNames.CompletionSpaceReservationManagerName)] + internal SpaceReservationManagerDefinition signatureHelpManager; + + [Export] + [Name(IntellisenseSpaceReservationManagerNames.CompletionSpaceReservationManagerName)] + [Order()] + internal SpaceReservationManagerDefinition completionManager; + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/MultiSessionIntellisensePresenterProvider.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/MultiSessionIntellisensePresenterProvider.cs new file mode 100644 index 0000000000..06c3acba0e --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/MultiSessionIntellisensePresenterProvider.cs @@ -0,0 +1,101 @@ +using System; + +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + /// <summary> + /// An intellisense presenter provider that caches the presenters it provides in the session's text view's + /// property bag. + /// </summary> + internal class MultiSessionIntellisensePresenterProvider<TSession, TPresenter> + where TSession : IIntellisenseSession + where TPresenter : IMultiSessionIntellisensePresenter<TSession> + { + /// <summary> + /// Tries to obtain a presenter from the cache (session's view's property bag). If a valid presenter was found + /// in the cache returns true, otherwise returns false. It always constructs a new presenter using the + /// provided factory. + /// </summary> + /// <returns><c>true</c> if the instance was retrieved from a cache; <c>false</c> if a new instance was created.</returns> + public bool GetOrCreatePresenter(TSession session, Func<TPresenter> factory, out TPresenter presenter) + { + bool addPresenterToCache = true; + + // check view's property bag for a cached version of the presenter + if (session.TextView.Properties.TryGetProperty<TPresenter>(typeof(TPresenter), out presenter)) + { + // if there is a cached presenter, make sure it's not already in a relationship with a session + if (!presenter.IsAttachedToSession) + { + // if there is a usable cached presenter, ensure that its screen size is compatible with the current screen size + // (ensure compatibility of screen resolution) + if (presenter.AllowableScreenSize == Helpers.GetScreenRect(session)) + { + // we have a valid cached presenter, attach it to the session and return + presenter.AttachToSession(session); + return true; + } + else + { + // This one won't work. Go ahead and remove it from the cache, assuming it won't ever work again. + this.RemovePresenterFromCache(session); + } + } + else + { + // don't add the new presenter in the cache since the cache already contains a valid presenter + // (that happens to be attached to another session now, but we still want to re-use it for future sessions) + addPresenterToCache = false; + } + } + + // we don't have a valid presenter or the cache is empty + presenter = factory.Invoke(); + + if (addPresenterToCache) + { + this.AddPresenterToCache(session, presenter); + } + + // Attach our newly-created presenter to the session + presenter.AttachToSession(session); + + // return false because we couldn't find a valid object from the cache + return false; + } + + protected void AddPresenterToCache(TSession session, TPresenter presenter) + { + session.TextView.Properties.AddProperty(typeof(TPresenter), presenter); + session.TextView.Closed += this.OnTextViewClosed; + } + + protected void RemovePresenterFromCache(TSession session) + { + session.TextView.Properties.RemoveProperty(typeof(TPresenter)); + } + + protected virtual void OnCacheContainerClosing(IPropertyOwner cacheContainer) + { + TPresenter presenter; + + if (cacheContainer.Properties.TryGetProperty<TPresenter>(typeof(TPresenter), out presenter)) + { + presenter.Dispose(); + + cacheContainer.Properties.RemoveProperty(typeof(TPresenter)); + } + } + + private void OnTextViewClosed(object sender, EventArgs args) + { + ITextView textView = (ITextView)sender; + + this.OnCacheContainerClosing(textView); + + textView.Closed -= this.OnTextViewClosed; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenter.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenter.cs new file mode 100644 index 0000000000..c153df47d9 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenter.cs @@ -0,0 +1,389 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Specialized; +using System.Windows; +using Microsoft.VisualStudio.Language.Utilities; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Editor; +using MonoDevelop.Components;
+using Xwt;
+using Xwt.Backends; +using Rect = Xwt.Rectangle;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal sealed class DefaultSignatureHelpPresenter : IPopupIntellisensePresenter, IObscuringTip, IIntellisenseCommandTarget, IMultiSessionIntellisensePresenter<ISignatureHelpSession> + { + private ISignatureHelpSession _session; + private ITrackingSpan _presentationSpan = null; + private DefaultSignatureHelpPresenterSurfaceElement _surfaceElement; + private Widget wrappedXwtWidget; + private EventHandler _surfaceElementChangedEvent; + private bool _isDisposed = false; + private SignatureHelpSessionView _sessionView; + private PopupStyles _popupStyles = PopupStyles.None; + + public DefaultSignatureHelpPresenter(DefaultSignatureHelpPresenterProvider componentContext) + {
+ _surfaceElement = new DefaultSignatureHelpPresenterSurfaceElement();
+ wrappedXwtWidget = _surfaceElement.Content;
+ //TODO: _surfaceElement.MouseLeftButtonDown += this.OnSigHelpLayoutGrid_MouseLeftButtonDown;
+ _surfaceElement.UpButtonClick += this.OnSurfaceElementUpButtonClick; + _surfaceElement.DownButtonClick += this.OnSurfaceElementDownButtonClick; + _sessionView = new SignatureHelpSessionView(componentContext); + } + + public IIntellisenseSession Session + { + get { return (_session); } + } + + public bool ExecuteKeyboardCommand(IntellisenseKeyboardCommand command) + { + // Certain keys are important to us. We'll want to trap things like up/down/escape and make them mean something + // special to us. + + switch (command) + { + case IntellisenseKeyboardCommand.Up: + return this.PreviousSignature(); + case IntellisenseKeyboardCommand.Down: + return this.NextSignature(); + default: + break; + } + + return false; + } + + public Xwt.Widget SurfaceElement + { + get + {
+ return wrappedXwtWidget; + } + } + + public event EventHandler SurfaceElementChanged + { + add { _surfaceElementChangedEvent += value; } + remove { _surfaceElementChangedEvent -= value; } + } + + public ITrackingSpan PresentationSpan + { + get + { + return _presentationSpan; + } + } + + public PopupStyles PopupStyles + { + get + { + return _popupStyles; + } + private set + { + if (value == _popupStyles) + { + return; + } + + PopupStyles oldPopupStyles = _popupStyles; + _popupStyles = value; + + // Raise the 'PopupStylesChanged' event. + EventHandler<ValueChangedEventArgs<PopupStyles>> tempHandler = this.PopupStylesChanged; + if (tempHandler != null) + { + tempHandler(this, new ValueChangedEventArgs<PopupStyles>(oldPopupStyles, _popupStyles)); + } + } + } + + public event EventHandler<ValueChangedEventArgs<PopupStyles>> PopupStylesChanged; + + public string SpaceReservationManagerName + { + get { return IntellisenseSpaceReservationManagerNames.SignatureHelpSpaceReservationManagerName; } + } + + public event EventHandler PresentationSpanChanged; + + public double Opacity { + get; set;
+ //get { return (_surfaceElement.Opacity); }
+ //set { _surfaceElement.Opacity = value; }
+ } + + public void Dispose() + { + // Make sure we don't double-dispose. + if (_isDisposed) + { + return; + } + _isDisposed = true; + + _sessionView.Dispose(); + } + + public bool IsAttachedToSession + { + get { return _session != null; } + } + + public void AttachToSession(ISignatureHelpSession session) + { + // If we're being attached to a session for the first time, set the allowable screen size for this presenter. This + // presenter instance should only be used for sessions on screens with this resolution. + if (this.AllowableScreenSize == null) + { + this.AllowableScreenSize = Helpers.GetScreenRect(session); + } + + // If we're re-attaching, make sure to unsubscribe from events on the old session. + if (this.IsAttachedToSession) + { + this.DetachFromSession(); + } + + _session = session; + _session.Dismissed += this.OnSessionDismissed; + ((INotifyCollectionChanged)_session.Signatures).CollectionChanged += this.OnSessionSignaturesChanged; + _session.TextView.TextBuffer.Changed += this.OnSessionViewBuffer_Changed; + + this.ComputePresentationSpan(); + + // Bind the view model to the session; + _sessionView.Session = session; + + // Use the view model as the data context for our presenter surface element. + _surfaceElement.DataContext = _sessionView; + + // Make sure we start off fully-opaque + this.Opacity = 1.0; + } + + public void DetachFromSession() + { + // Don't use the view model as the data context anymore. + _surfaceElement.DataContext = null; + + // Unbind the view model. + _sessionView.Session = null; + + // Stop listening to all of the session/surface element events. + _session.Dismissed -= this.OnSessionDismissed; + ((INotifyCollectionChanged)_session.Signatures).CollectionChanged -= this.OnSessionSignaturesChanged; + _session.TextView.TextBuffer.Changed -= this.OnSessionViewBuffer_Changed; + + _session = null; + + // Reset out popup styles for the next session to which we get attached. + _popupStyles = PopupStyles.None; + + _surfaceElement.Hide(); + _presentationSpan = null; + } + + public Rect? AllowableScreenSize { get; private set; } + + bool IObscuringTip.Dismiss() + { + if ((_session != null) && !(_session.IsDismissed)) + { + _session.Dismiss(); + return true; + } + + return false; + } + + void IObscuringTip.SetOpacity(double opacity) + { + this.Opacity = opacity; + } + + private void OnSessionDismissed(object sender, EventArgs e) + { + this.DetachFromSession(); + } + + private void OnSurfaceElementDownButtonClick(object sender, EventArgs e) + { + this.NextSignature(); + + // We don't want the focus to remain with this control. We want to give it back ASAP. + this.FocusTheSessionView(); + } + + private void OnSurfaceElementUpButtonClick(object sender, EventArgs e) + { + this.PreviousSignature(); + + // We don't want the focus to remain with this control. We want to give it back ASAP. + this.FocusTheSessionView(); + } + + private void OnSigHelpLayoutGrid_MouseLeftButtonDown() + { + this.NextSignature(); + + // We don't want the focus to remain with this control. We want to give it back ASAP. + this.FocusTheSessionView(); + } + + private void OnSessionSignaturesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + this.ComputePresentationSpan(); + } + + private void OnSessionViewBuffer_Changed(object sender, TextContentChangedEventArgs e) + { + // When the view buffer changes, it's possible that we could want to display the presenter on-top of the signature + // instead-of below it. + if ((_session != null) && (!_session.IsDismissed)) + { + foreach (var signature in _session.Signatures) + { + SnapshotSpan applicabilitySpan = + signature.ApplicableToSpan.GetSpan + (signature.ApplicableToSpan.TextBuffer.CurrentSnapshot); + if (applicabilitySpan.Start.GetContainingLine().LineNumber != + applicabilitySpan.End.GetContainingLine().LineNumber) + { + this.PopupStyles |= PopupStyles.PreferLeftOrTopPosition; + } + } + } + } + + private void FocusTheSessionView() + { + if (_session == null) + { + return; + } + + var textView = _session.TextView as IMdTextView; + if (textView != null) + {
+ textView.VisualElement.GrabFocus (); + } + } + + private void FirePresentationSpanChanged() + { + EventHandler tempHandler = this.PresentationSpanChanged; + if (tempHandler != null) + { + tempHandler(this, new EventArgs()); + } + } + + private bool NextSignature() + { + // If there are fewer than two signatures, it doesn't make any sense to go to the next one. We shouldn't be handling + // keyboard events in this case. + if ((_session == null) || (_session.Signatures.Count <= 1)) + { + return false; + } + + if (_session.SelectedSignature == null) + { + if (_session.Signatures.Count > 0) + { + _session.SelectedSignature = _session.Signatures[0]; + } + } + else + { + int currentIndex = _session.Signatures.IndexOf(_session.SelectedSignature); + if (currentIndex < (_session.Signatures.Count - 1)) + { + _session.SelectedSignature = _session.Signatures[++currentIndex]; + } + else if (currentIndex == (_session.Signatures.Count - 1)) + { + _session.SelectedSignature = _session.Signatures[0]; + } + } + + return true; + } + + private bool PreviousSignature() + { + // If there are fewer than two signatures, it doesn't make any sense to go to the next one. We shouldn't be handling + // keyboard events in this case. + if ((_session == null) || (_session.Signatures.Count <= 1)) + { + return false; + } + + if (_session.SelectedSignature == null) + { + if (_session.Signatures.Count > 0) + { + _session.SelectedSignature = _session.Signatures[0]; + } + } + else + { + int currentIndex = _session.Signatures.IndexOf(_session.SelectedSignature); + if (currentIndex > 0) + { + _session.SelectedSignature = _session.Signatures[--currentIndex]; + } + else if (currentIndex == 0) + { + _session.SelectedSignature = _session.Signatures[_session.Signatures.Count - 1]; + } + } + + return true; + } + + private void ComputePresentationSpan() + { + if ((_session == null) || (_session.IsDismissed)) + { return; } + + // Save off the old presentation span so we can tell if it changes. + ITrackingSpan oldPresentationSpan = _presentationSpan; + + if (_session.Signatures.Count > 0) + { + _presentationSpan = null; + foreach (ISignature signature in _session.Signatures) + { + _presentationSpan = IntellisenseUtilities.GetEncapsulatingSpan(_session.TextView, _presentationSpan, signature.ApplicableToSpan); + } + } + + // For now, we're only interested in letting consumers know about presentation span changes if the START of our + // presentation span changed. That's where our display position gets calculated. + // TODO : Develop a better story around presentation span changes in popup presenters. If the presentation location + // (coordinates) doesn't really change, we shouldn't have a flicker... + ITextSnapshot viewSnapshot = _session.TextView.TextSnapshot; + SnapshotSpan newSpan = _presentationSpan.GetSpan(viewSnapshot); + SnapshotSpan? oldSpan = null; + if (oldPresentationSpan != null) + { + oldSpan = oldPresentationSpan.GetSpan(viewSnapshot); + } + if (!oldSpan.HasValue || (newSpan.Start != oldSpan.Value.Start)) + { + this.FirePresentationSpanChanged(); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenterProvider.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenterProvider.cs new file mode 100644 index 0000000000..5bb292ab30 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenterProvider.cs @@ -0,0 +1,91 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Utilities; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + [Export(typeof(IIntellisensePresenterProvider))] + [Name("Default Signature Help Presenter")] + [ContentType("text")] + [Order] + internal sealed class DefaultSignatureHelpPresenterProvider : + MultiSessionIntellisensePresenterProvider<ISignatureHelpSession, DefaultSignatureHelpPresenter>, + IIntellisensePresenterProvider + { + [Export] + [Name("intellisense")] + [BaseDefinition("text")] + internal ContentTypeDefinition intellisenseContentType; + + [Export] + [Name("sighelp-doc")] + [BaseDefinition("intellisense")] + internal ContentTypeDefinition sigHelpDocumentationContentType; + + [Export] + [Name("sighelp-documentation")] + [BaseDefinition("text")] + internal ClassificationTypeDefinition sigHelpDocClassificationType; + + [Export] + [Name("sighelp")] + [BaseDefinition("intellisense")] + internal ContentTypeDefinition sigHelpContentType; + + [Export] + [Name("currentParam")] + [BaseDefinition("text")] + internal ClassificationTypeDefinition currentParamClassificationType; + + [Import] + internal IContentTypeRegistryService ContentTypeRegistryService { get; set; } + + [Import] + internal ITextBufferFactoryService TextBufferFactoryService { get; set; } + + [Import] + internal ITextEditorFactoryService TextEditorFactoryService { get; set; } + + [Import] + internal IEditorOptionsFactoryService EditorOptionsFactoryService { get; set; } + + [Import] + internal GuardedOperations GuardedOperations { get; set; } + +#if DEBUG + [ImportMany] + internal List<Lazy<IObjectTracker>> ObjectTrackers { get; set; } +#endif + + public IIntellisensePresenter TryCreateIntellisensePresenter(IIntellisenseSession session) + { + DefaultSignatureHelpPresenter presenter = null; + + // We only support signature help sessions with this presenter, and we only support sessions that actually have + // signatures + ISignatureHelpSession signatureHelpSession = session as ISignatureHelpSession; + if ((signatureHelpSession != null) && + (signatureHelpSession.Signatures != null) && + (signatureHelpSession.Signatures.Count > 0)) + { + if (!this.GetOrCreatePresenter(signatureHelpSession, () => new DefaultSignatureHelpPresenter(this), out presenter)) + { +#if DEBUG + Helpers.TrackObject(this.ObjectTrackers, "Default Signature Help Presenters", presenter); +#endif + } + } + + return presenter; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenterSurfaceElement.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenterSurfaceElement.cs new file mode 100644 index 0000000000..6a55978934 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/DefaultSignatureHelpPresenterSurfaceElement.cs @@ -0,0 +1,181 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Gtk;
+using Microsoft.VisualStudio.Platform;
+using MonoDevelop.Components;
+using MonoDevelop.Core;
+using MonoDevelop.Ide;
+using MonoDevelop.Ide.CodeCompletion;
+using MonoDevelop.Ide.Editor.Highlighting;
+using MonoDevelop.Ide.Fonts;
+using MonoDevelop.Ide.Gui;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+{
+ class DefaultSignatureHelpPresenterSurfaceElement
+ {
+ SignatureHelpSessionView dataContext;
+ public SignatureHelpSessionView DataContext
+ {
+ get
+ {
+ return dataContext;
+ }
+ set
+ {
+ if (value == dataContext)
+ return;
+ if (dataContext != null)
+ dataContext.PropertyChanged -= DataContext_PropertyChanged;
+ dataContext = value;
+ if (dataContext != null)
+ dataContext.PropertyChanged += DataContext_PropertyChanged;
+ DataContext_PropertyChanged (this, new System.ComponentModel.PropertyChangedEventArgs (""));
+ }
+ }
+
+ private void DataContext_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ while (vb.Children.Length > 0)
+ vb.Remove(vb.Children[0]);
+ if (DataContext != null)
+ {
+ vb.PackStart(DataContext.SignatureWpfViewVisualElement, true, true, 0);
+ vb.PackStart(descriptionBox, true, true, 0);
+ }
+ ShowTooltipInfo();
+ }
+
+ VBox descriptionBox = new VBox(false, 0);
+ VBox vb = new VBox(false, 0);
+ VBox vb2 = new VBox(false, 0);
+ Cairo.Color foreColor;
+ public Xwt.Widget Content;
+ private XwtPopupWindowTheme Theme { get => ((XwtThemedPopup)Content.ParentWindow).Theme; }
+ public DefaultSignatureHelpPresenterSurfaceElement()
+ {
+ descriptionBox.Spacing = 4;
+
+
+ HBox hb = new HBox(false, 0);
+ hb.PackStart(vb, true, true, 0);
+
+ vb2.Spacing = 4;
+ vb2.PackStart(hb, true, true, 0);
+
+ vb2.ShowAll();
+ vb2.ParentSet += Vb2_ParentSet;
+ Content = Xwt.Toolkit.CurrentEngine.WrapWidget(vb2, Xwt.NativeWidgetSizing.DefaultPreferredSize);
+
+ Styles.Changed += HandleThemeChanged;
+ IdeApp.Preferences.ColorScheme.Changed += HandleThemeChanged;
+ Content.Disposed += Content_Disposed;
+ }
+
+ private void Vb2_ParentSet(object o, ParentSetArgs args)
+ {
+ ShowTooltipInfo();
+ }
+
+ void UpdateStyle()
+ {
+ var scheme = SyntaxHighlightingService.GetEditorTheme(IdeApp.Preferences.ColorScheme);
+ if (!scheme.FitsIdeTheme(IdeApp.Preferences.UserInterfaceTheme))
+ scheme = SyntaxHighlightingService.GetDefaultColorStyle(IdeApp.Preferences.UserInterfaceTheme);
+ Theme.SetSchemeColors(scheme);
+ Theme.Font = FontService.SansFont.CopyModified(Styles.FontScale11).ToXwtFont();
+ Theme.ShadowColor = Styles.PopoverWindow.ShadowColor;
+ foreColor = Styles.PopoverWindow.DefaultTextColor.ToCairoColor();
+
+ if (DataContext != null)
+ {
+ DataContext.SignatureWpfViewVisualElement.ModifyFg(StateType.Normal, foreColor.ToGdkColor());
+ DataContext.SignatureWpfViewVisualElement.FontDescription = FontService.GetFontDescription("Editor").CopyModified(Styles.FontScale11);
+ }
+ //if (this.Visible)
+ // QueueDraw ();
+ }
+
+ void HandleThemeChanged(object sender, EventArgs e)
+ {
+ UpdateStyle();
+ }
+
+
+ private void Content_Disposed(object sender, EventArgs e)
+ {
+ if (Content == null)
+ return;
+ Styles.Changed -= HandleThemeChanged;
+ IdeApp.Preferences.ColorScheme.Changed -= HandleThemeChanged;
+ Content.Disposed -= Content_Disposed;
+ ((XwtThemedPopup)Content.ParentWindow).PagerLeftClicked -= DownButtonClick;
+ ((XwtThemedPopup)Content.ParentWindow).PagerRightClicked -= UpButtonClick;
+ Content = null;
+ }
+ bool arrowEventsRegistered;
+ void ShowTooltipInfo()
+ {
+ if (Content.ParentWindow == null)
+ return;
+ if (DataContext == null)
+ return;
+ if (!arrowEventsRegistered)
+ {
+ UpdateStyle();
+ arrowEventsRegistered = true;
+ ((XwtThemedPopup)Content.ParentWindow).PagerLeftClicked += DownButtonClick;
+ ((XwtThemedPopup)Content.ParentWindow).PagerRightClicked += UpButtonClick;
+ }
+ Theme.NumPages = DataContext.Session.Signatures.Count;
+ Theme.CurrentPage = DataContext.Session.Signatures.IndexOf(dataContext.Session.SelectedSignature);
+
+ if (Theme.NumPages > 1)
+ {
+ Theme.DrawPager = true;
+ Theme.PagerVertical = true;
+ }
+ ClearDescriptions();
+ if (Theme.DrawPager)
+ DataContext.SignatureWpfViewVisualElement.WidthRequest = DataContext.SignatureWpfViewVisualElement.RealWidth + 70;
+
+ if (!string.IsNullOrEmpty(dataContext.Session.SelectedSignature.Documentation))
+ descriptionBox.PackStart(CreateCategory(TooltipInformationWindow.GetHeaderMarkup(GettextCatalog.GetString("Summary")), dataContext.Session.SelectedSignature.Documentation), true, true, 4);
+
+ descriptionBox.ShowAll();
+ Content.QueueForReallocate();
+ }
+
+ public event EventHandler MouseLeftButtonDown;
+ public event EventHandler UpButtonClick;
+ public event EventHandler DownButtonClick;
+
+ void CurrentTooltipInformation_Changed(object sender, EventArgs e)
+ {
+ ShowTooltipInfo();
+ }
+
+ void ClearDescriptions()
+ {
+ while (descriptionBox.Children.Length > 0)
+ {
+ var child = descriptionBox.Children[0];
+ descriptionBox.Remove(child);
+ child.Destroy();
+ }
+ }
+
+ VBox CreateCategory(string categoryName, string categoryContentMarkup)
+ {
+ return TooltipInformationWindow.CreateCategory(categoryName, categoryContentMarkup, foreColor, Theme.Font.ToPangoFont());
+ }
+
+ internal void Hide()
+ {
+ //Content.Hide ();
+ }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpBroker.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpBroker.cs new file mode 100644 index 0000000000..7b42f3cdee --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpBroker.cs @@ -0,0 +1,184 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Utilities; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + [Export(typeof(ISignatureHelpBroker))] + internal class SignatureHelpBroker : ISignatureHelpBroker + { + [ImportMany] + internal List<Lazy<ISignatureHelpSourceProvider, IOrderableContentTypeMetadata>> UnOrderedSignatureHelpSourceProviderExports + { get; set; } + + [ImportMany] + private List<Lazy<IIntellisensePresenterProvider, IOrderableContentTypeMetadata>> UnOrderedIntellisensePresenterProviderExports + { get; set; } + + private IList<Lazy<IIntellisensePresenterProvider, IOrderableContentTypeMetadata>> _orderedIntellisensePresenterProviderExports; + internal IList<Lazy<IIntellisensePresenterProvider, IOrderableContentTypeMetadata>> OrderedIntellisensePresenterProviderExports + { + get + { + if (_orderedIntellisensePresenterProviderExports == null) + { + _orderedIntellisensePresenterProviderExports = Orderer.Order(this.UnOrderedIntellisensePresenterProviderExports); + } + + return _orderedIntellisensePresenterProviderExports; + } + } + + [Import] + internal IIntellisenseSessionStackMapService IntellisenseSessionStackMap { get; set; } + + [Import] + internal GuardedOperations GuardedOperations { get; set; } + +#if DEBUG + [ImportMany] + internal List<Lazy<IObjectTracker>> ObjectTrackers { get; set; } +#endif + + public void DismissAllSessions(ITextView textView) + { + foreach (var session in this.GetSessions(textView)) + { + session.Dismiss(); + } + } + + public bool IsSignatureHelpActive(ITextView textView) + { + return this.GetSessions(textView).Count > 0; + } + + public ReadOnlyCollection<ISignatureHelpSession> GetSessions(ITextView textView) + { + FrugalList<ISignatureHelpSession> tempSessionList = new FrugalList<ISignatureHelpSession>(); + + IIntellisenseSessionStack sessionStack = this.IntellisenseSessionStackMap.GetStackForTextView(textView); + if (sessionStack == null) + { + return tempSessionList.AsReadOnly(); + } + + foreach (var session in sessionStack.Sessions) + { + ISignatureHelpSession sigHelpSession = session as ISignatureHelpSession; + if (sigHelpSession != null) + { + tempSessionList.Add(sigHelpSession); + } + } + + return tempSessionList.AsReadOnly(); + } + + public ISignatureHelpSession TriggerSignatureHelp(ITextView textView) + { + var caretPosition = textView.Caret.Position.BufferPosition; + return this.TriggerSignatureHelp + (textView, + caretPosition.Snapshot.CreateTrackingPoint + (caretPosition.Position, PointTrackingMode.Negative), + true); + } + + public ISignatureHelpSession TriggerSignatureHelp(ITextView textView, ITrackingPoint triggerPoint, bool trackCaret) + { + ISignatureHelpSession session = this.CreateSignatureHelpSession(textView, triggerPoint, trackCaret); + session.Start(); + + if (session.IsDismissed) + { + return (null); + } + + return session; + } + + public ISignatureHelpSession CreateSignatureHelpSession(ITextView textView, ITrackingPoint triggerPoint, bool trackCaret) + { + if (triggerPoint.TextBuffer != textView.TextBuffer) + { + // Map the trigger point up to the surface buffer. That's where the session will live. + SnapshotPoint? surfaceTriggerSnapPoint = textView.BufferGraph.MapUpToBuffer + (triggerPoint.GetPoint(triggerPoint.TextBuffer.CurrentSnapshot), + PointTrackingMode.Negative, + PositionAffinity.Successor, + textView.TextBuffer); + if (!surfaceTriggerSnapPoint.HasValue) + { + // We can't trigger a session when the trigger point doesn't map to the surface buffer. + return null; + } + triggerPoint = surfaceTriggerSnapPoint.Value.Snapshot.CreateTrackingPoint + (surfaceTriggerSnapPoint.Value.Position, + PointTrackingMode.Negative); + } + + // Make sure we can get ahold of a session stack. + IIntellisenseSessionStack sessionStack = this.IntellisenseSessionStackMap.GetStackForTextView(textView); + if (sessionStack == null) + { + throw new InvalidOperationException("Couldn't retrieve Intellisense Session Stack for the specified ITextView."); + } + + SignatureHelpSession session = new SignatureHelpSession(this, textView, triggerPoint, trackCaret); + +#if DEBUG + Helpers.TrackObject(this.ObjectTrackers, "Signature Help Sessions", session); +#endif + + sessionStack.PushSession(session); + return session; + } + + internal IEnumerable<ISignatureHelpSource> GetSignatureHelpSources(IIntellisenseSession session) + { + return Helpers.GetSources(session, this.CreateSourcesForBuffer); + } + + private IList<Lazy<ISignatureHelpSourceProvider, IOrderableContentTypeMetadata>> _orderedSignatureHelpSourceProviderExports; + private IList<Lazy<ISignatureHelpSourceProvider, IOrderableContentTypeMetadata>> OrderedSignatureHelpSourceProviderExports + { + get + { + if (_orderedSignatureHelpSourceProviderExports == null) + { + _orderedSignatureHelpSourceProviderExports = Orderer.Order(this.UnOrderedSignatureHelpSourceProviderExports); + } + + return _orderedSignatureHelpSourceProviderExports; + } + } + + private IEnumerable<ISignatureHelpSource> CreateSourcesForBuffer(ITextBuffer buffer) + { + FrugalList<ISignatureHelpSource> sources = new FrugalList<ISignatureHelpSource>(); + foreach(var source in this.GuardedOperations.InvokeMatchingFactories + (this.OrderedSignatureHelpSourceProviderExports, + provider => provider.TryCreateSignatureHelpSource(buffer), + buffer.ContentType, + this)) + { +#if DEBUG + Helpers.TrackObject(this.ObjectTrackers, "SignatureHelp Sources", source); +#endif + sources.Add(source); + } + + return sources; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpParameterBoldingClassfier.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpParameterBoldingClassfier.cs new file mode 100644 index 0000000000..1e1b606f15 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpParameterBoldingClassfier.cs @@ -0,0 +1,146 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Utilities; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal class SignatureHelpParameterBoldingClassfier : IClassifier + { + private ITextBuffer _textBuffer; + private event EventHandler<ClassificationChangedEventArgs> _classificationChanged; + private ISignatureHelpSession _session; + private IClassificationTypeRegistryService _classificationTypeRegistry; + + public const string UsePrettyPrintedContentKey = "UsePrettyPrintedContent"; + + public SignatureHelpParameterBoldingClassfier(ITextBuffer textBuffer, + IClassificationTypeRegistryService classificationTypeRegistry) + { + _textBuffer = textBuffer; + _classificationTypeRegistry = classificationTypeRegistry; + + // Find the signature help session that created this text buffer. + + if ((!_textBuffer.Properties.TryGetProperty<ISignatureHelpSession>(typeof(ISignatureHelpSession), out _session)) || + (_session == null)) + { + throw new ArgumentException + ("Invalid text buffer. The specified buffer wasn't created by the default signature help presenter", + "textBuffer"); + } + + _session.Dismissed += this.OnSession_Dismissed; + ((INotifyCollectionChanged)_session.Signatures).CollectionChanged += this.OnSignaturesChanged; + _session.SelectedSignatureChanged += this.OnSession_SelectedSignatureChanged; + + if (_session.SelectedSignature != null) + { + _session.SelectedSignature.CurrentParameterChanged += this.OnSelectedSignature_CurrentParameterChanged; + } + } + + public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged + { + add { _classificationChanged += value; } + remove { _classificationChanged -= value; } + } + + public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan trackingSpan) + { + IClassificationType currentParamType = _classificationTypeRegistry.GetClassificationType("currentParam"); + if (currentParamType == null) + { + throw (new InvalidOperationException("Unable to retrieve 'current parameter' classification type")); + } + + bool usePrettyPrintedContent; + if (!_textBuffer.Properties.TryGetProperty<bool>(SignatureHelpParameterBoldingClassfier.UsePrettyPrintedContentKey, out usePrettyPrintedContent)) + { + usePrettyPrintedContent = false; + } + + FrugalList<ClassificationSpan> classSpans = new FrugalList<ClassificationSpan>(); + if ((_session != null) && + (_session.SelectedSignature != null) && + (_session.SelectedSignature.CurrentParameter != null)) + { + Span span = usePrettyPrintedContent ? + _session.SelectedSignature.CurrentParameter.PrettyPrintedLocus : + _session.SelectedSignature.CurrentParameter.Locus; + + if ((trackingSpan.IntersectsWith(span)) && + (span.End <= _textBuffer.CurrentSnapshot.Length)) + { + // We only have one classification span to add, and that's the classification span for the current parameter. + + classSpans.Add + (new ClassificationSpan + (new SnapshotSpan(_textBuffer.CurrentSnapshot, span), + currentParamType)); + } + } + + return classSpans; + } + + private void OnSignaturesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + this.FireClassificationChanged + (new SnapshotSpan(_textBuffer.CurrentSnapshot, 0, _textBuffer.CurrentSnapshot.Length)); + } + + private void OnSession_SelectedSignatureChanged(object sender, SelectedSignatureChangedEventArgs e) + { + // If the selected signature changed, we should subscribe to current parameter change notification on this new + // selected signature. + + if (e.PreviousSelectedSignature != null) + { + e.PreviousSelectedSignature.CurrentParameterChanged -= this.OnSelectedSignature_CurrentParameterChanged; + } + + if (e.NewSelectedSignature != null) + { + e.NewSelectedSignature.CurrentParameterChanged += this.OnSelectedSignature_CurrentParameterChanged; + } + + this.FireClassificationChanged + (new SnapshotSpan(_textBuffer.CurrentSnapshot, 0, _textBuffer.CurrentSnapshot.Length)); + } + + private void OnSelectedSignature_CurrentParameterChanged(object sender, CurrentParameterChangedEventArgs e) + { + this.FireClassificationChanged + (new SnapshotSpan(_textBuffer.CurrentSnapshot, 0, _textBuffer.CurrentSnapshot.Length)); + } + + private void OnSession_Dismissed(object sender, EventArgs e) + { + _session.Dismissed -= this.OnSession_Dismissed; + ((INotifyCollectionChanged)_session.Signatures).CollectionChanged -= this.OnSignaturesChanged; + _session.SelectedSignatureChanged -= this.OnSession_SelectedSignatureChanged; + + if (_session.SelectedSignature != null) + { + _session.SelectedSignature.CurrentParameterChanged -= this.OnSelectedSignature_CurrentParameterChanged; + } + _session = null; + } + + private void FireClassificationChanged(SnapshotSpan changeSpan) + { + EventHandler<ClassificationChangedEventArgs> tempHandler = _classificationChanged; + if (tempHandler != null) + { + tempHandler(this, new ClassificationChangedEventArgs(changeSpan)); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpSession.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpSession.cs new file mode 100644 index 0000000000..57f305af61 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpSession.cs @@ -0,0 +1,255 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal class SignatureHelpSession : IntellisenseSession, ISignatureHelpSession + { + private SignatureHelpBroker _componentContext; + private bool _trackCaret; + + private BulkObservableCollection<ISignature> _activeSignatures = new BulkObservableCollection<ISignature>(); + private ReadOnlyObservableCollection<ISignature> _readOnlySignatures; + private bool _isDismissed = false; + private ISignature _selectedSignature; + + + public SignatureHelpSession(SignatureHelpBroker componentContext, + ITextView textView, + ITrackingPoint triggerPoint, + bool trackCaret) + : base(textView, triggerPoint) + { + _componentContext = componentContext; + + // If we were asked to track the caret, go ahead and subscribe to the event that tracks its movement. + + _trackCaret = trackCaret; + if (_trackCaret) + { + base.textView.Caret.PositionChanged += new EventHandler<CaretPositionChangedEventArgs>(OnCaretPositionChanged); + } + } + + public ReadOnlyObservableCollection<ISignature> Signatures + { + get + { + if (_readOnlySignatures == null) + { + _readOnlySignatures = new ReadOnlyObservableCollection<ISignature>(_activeSignatures); + } + + return _readOnlySignatures; + } + } + + public ISignature SelectedSignature + { + get + { + return (_selectedSignature); + } + set + { + if (value != _selectedSignature) + { + if (_activeSignatures.Contains(value)) + { + ISignature prevSelectedSignature = _selectedSignature; + _selectedSignature = value; + this.RaiseSelectedSignatureChanged(prevSelectedSignature, _selectedSignature); + return; + } + + throw (new ArgumentException("Invalid signature. Cannot set selection to signature that doesn't exist in the set of signatures for this session.")); + } + } + } + + public event EventHandler<SelectedSignatureChangedEventArgs> SelectedSignatureChanged; + + public override bool Match() + { + foreach (ISignatureHelpSource provider in _componentContext.GetSignatureHelpSources(this)) + { + ISignature bestMatch = provider.GetBestMatch(this); + if (bestMatch != null) + { + this.SelectedSignature = bestMatch; + return (true); + } + } + + return (false); + } + + public override void Start() + { + this.Recalculate(); + } + + public override void Recalculate() + { + if (_isDismissed) + { + throw (new InvalidOperationException("Cannot recalculate. The session is dismissed.")); + } + + // Clear out the active set of signatures. + _activeSignatures.Clear(); + + // In order to find the signatures to be displayed in this session, we'll need to go through each of the signature help + // providers and ask them for signatures at the trigger point. + + List<ISignature> signatures = new List<ISignature>(); + foreach (ISignatureHelpSource provider in _componentContext.GetSignatureHelpSources(this)) + { + provider.AugmentSignatureHelpSession(this, signatures); + + // The source could have done anything to the session. Let's verify that we're still alive and well. + if (this.IsDismissed) + { + return; + } + } + + if (signatures != null) + { + _activeSignatures.AddRange(signatures); + } + + // If we ended-up with zero signatures, that's it. We need to dismiss and get out of here. + if (_activeSignatures.Count == 0) + { + this.Dismiss(); + return; + } + + // Let's determine which signature should be "selected" before we find a presenter. + if (!_activeSignatures.Contains(_selectedSignature)) + { + // The old selection is no longer valid (or we never had one). Let's select the first signature. + this.SelectedSignature = _activeSignatures[0]; + } + + // If we don't already have a presenter for this session, find one. + if (base.presenter == null) + { + base.presenter = FindPresenter(this, _componentContext.OrderedIntellisensePresenterProviderExports, _componentContext.GuardedOperations); + if (base.presenter != null) + { + this.RaisePresenterChanged(); + } + else + { + this.Dismiss(); + return; + } + } + + base.RaiseRecalculated(); + } + + public override void Dismiss() + { + if (!_isDismissed) + { + // Stop listening to the caret movement event + + if (_trackCaret) + { + base.textView.Caret.PositionChanged -= new EventHandler<CaretPositionChangedEventArgs>(OnCaretPositionChanged); + } + + _isDismissed = true; + + // Fire the "Dismissed" event. This will signal consumers (namely, the session stack) to release pointers to this + // session and let us die a peaceful death of garbage collection + + base.RaiseDismissed(); + + // Now that we're dismissed, there's no reason to hang-on to the rest of this stuff. + _componentContext = null; + _activeSignatures = null; + _readOnlySignatures = null; + _selectedSignature = null; + + base.Dismiss(); + } + } + + public override bool IsDismissed + { + get { return (_isDismissed); } + } + + public override void Collapse() + { + this.Dismiss(); + } + + void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e) + { + // It is possible that one of the previous Caret.PositionChanged event handlers dismissed + // the session and removed this handler (and null'd all data) from the event. In that case + // we should simply ignore this call. + if (_isDismissed) + { + return; + } + + // Let's make sure that the caret is still inside the applicable-to for each of the signatures. We'll want to remove + // any completions that aren't applicable. + + ITextSnapshot snapshot = this.TextView.TextBuffer.CurrentSnapshot; + SnapshotSpan caretSurfaceSpan = new SnapshotSpan(snapshot, this.TextView.Caret.Position.BufferPosition.Position, 0); + + List<ISignature> signaturesToRemove = new List<ISignature>(); + if (null != this.Signatures) + { + foreach (ISignature signature in this.Signatures) + { + var signatureSurfaceSpans = this.TextView.BufferGraph.MapUpToBuffer + (signature.ApplicableToSpan.GetSpan(signature.ApplicableToSpan.TextBuffer.CurrentSnapshot), + signature.ApplicableToSpan.TrackingMode, + this.TextView.TextBuffer); + foreach (var signatureSurfaceSpan in signatureSurfaceSpans) + { + if (!signatureSurfaceSpan.IntersectsWith(caretSurfaceSpan)) + { + signaturesToRemove.Add(signature); + } + } + } + } + + foreach (ISignature sigToRemove in signaturesToRemove) + { + _activeSignatures.Remove(sigToRemove); + } + + // If we rejected all of the signatures, we'll want to make sure to dismiss the session + if (_activeSignatures.Count == 0) + { + this.Dismiss(); + } + } + + private void RaiseSelectedSignatureChanged(ISignature oldSelectedSignature, ISignature newSelectedSignature) + { + EventHandler<SelectedSignatureChangedEventArgs> tempHandler = this.SelectedSignatureChanged; + if (tempHandler != null) + { + tempHandler(this, new SelectedSignatureChangedEventArgs(oldSelectedSignature, newSelectedSignature)); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpSessionView.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpSessionView.cs new file mode 100644 index 0000000000..b0a4de6cab --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/Intellisense/SignatureHelp/SignatureHelpSessionView.cs @@ -0,0 +1,473 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Microsoft Corporation. All rights reserved. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using Gtk; +using Microsoft.VisualStudio.Platform; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using MonoDevelop.Components; +using MonoDevelop.Core; + +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + internal sealed class SignatureHelpSessionView : IDisposable, INotifyPropertyChanged + { + private DefaultSignatureHelpPresenterProvider _componentContext; + private ISignatureHelpSession _session; + private string _pagerText; + private bool _pagerVisibility = false; + private ITextBuffer _signatureTextBuffer; + private MonoDevelop.Components.FixedWidthWrapLabel _signatureWpfTextView; + private string _signatureDocumentation; + private bool _signatureDocumentationVisibility; + private string _currentParameterName; + private string _currentParameterDocumentation; + private bool _currentParameterVisibility; + private IContentType _textContentType; + private double _pagerWidth; + private bool _isAutoSizePending = false; + + internal SignatureHelpSessionView(DefaultSignatureHelpPresenterProvider componentContext) + { + _componentContext = componentContext; + } + + public ISignatureHelpSession Session + { + get { return _session; } + set + { + this.UnBindFromSession(); + _session = value; + this.BindToSession(); + } + } + + public string PagerText + { + get { return _pagerText; } + private set + { + _pagerText = value; + this.RaisePropertyChanged("PagerText"); + } + } + + public bool PagerVisibility + { + get { return _pagerVisibility; } + private set + { + _pagerVisibility = value; + this.RaisePropertyChanged("PagerVisibility"); + } + } + + public FixedWidthWrapLabel SignatureWpfViewVisualElement { get { return _signatureWpfTextView; } } + + public string SignatureDocumentation + { + get { return _signatureDocumentation; } + private set + { + _signatureDocumentation = value; + this.RaisePropertyChanged("SignatureDocumentation"); + } + } + + public bool SignatureDocumentationVisibility + { + get { return _signatureDocumentationVisibility; } + private set + { + _signatureDocumentationVisibility = value; + this.RaisePropertyChanged("SignatureDocumentationVisibility"); + } + } + + public string CurrentParameterName + { + get { return _currentParameterName; } + private set + { + _currentParameterName = value; + this.RaisePropertyChanged("CurrentParameterName"); + } + } + + public string CurrentParameterDocumentation + { + get { return _currentParameterDocumentation; } + private set + { + _currentParameterDocumentation = value; + this.RaisePropertyChanged("CurrentParameterDocumentation"); + } + } + + public bool CurrentParameterVisibility + { + get { return _currentParameterVisibility; } + private set + { + _currentParameterVisibility = value; + this.RaisePropertyChanged("CurrentParameterVisibility"); + } + } + + public double ContainerMaxWidth + { + get + { + return Helpers.GetScreenRect(this.Session).Width * 0.9; + } + } + + public double ContainerMaxHeight + { + get + { + return Helpers.GetScreenRect(this.Session).Height * 0.5; + } + } + + public double CurrentParameterNameMaxWidth + { + get + { + return Helpers.GetScreenRect(this.Session).Width * 0.2; + } + } + + public double PagerWidth + { + get + { + return _pagerWidth; + } + set + { + _pagerWidth = value; + + this.AutoSizeSignatureTextView(); + } + } + + public void Dispose() + { + this.UnBindFromSession(); + _signatureWpfTextView.Dispose(); + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void OnSession_SelectedSignatureChanged(object sender, SelectedSignatureChangedEventArgs e) + { + // Stop listening to CurrentParameterChanged on the old selected signature, and start listening on the new selected + // signature. + if (e.PreviousSelectedSignature != null) + { + e.PreviousSelectedSignature.CurrentParameterChanged -= this.OnSelectedSignature_CurrentParameterChanged; + } + if (e.NewSelectedSignature != null) + { + e.NewSelectedSignature.CurrentParameterChanged += this.OnSelectedSignature_CurrentParameterChanged; + } + + // Since the selected signature changed, we need to update the signature info being displayed. + this.UpdateSignatureInfo(); + } + + private void OnSelectedSignature_CurrentParameterChanged(object sender, CurrentParameterChangedEventArgs e) + { + this.UpdateParameterInfo(); + } + + private void OnSignatures_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + this.UpdateSignatureInfo(); + } + + private void CreateSignatureTextView() + { + IEditorOptions options = _componentContext.EditorOptionsFactoryService.CreateOptions(); + _signatureTextBuffer = _componentContext.TextBufferFactoryService.CreateTextBuffer(); + _signatureWpfTextView = new MonoDevelop.Components.FixedWidthWrapLabel(); + _signatureWpfTextView.Indent = -20; + + _signatureWpfTextView.Wrap = Pango.WrapMode.WordChar; + _signatureWpfTextView.BreakOnCamelCasing = false; + _signatureWpfTextView.BreakOnPunctuation = false; + } + + private void BindToSession() + { + // No work to do if we don't have a session + if ((_session == null) || (_session.IsDismissed)) + { + return; + } + + // Create the signature text view (if it hasn't already been created) + if (_signatureWpfTextView == null) + { + this.CreateSignatureTextView(); + } + + // Subscribe to a few events + _session.SelectedSignatureChanged += this.OnSession_SelectedSignatureChanged; + if (_session.SelectedSignature != null) + { + _session.SelectedSignature.CurrentParameterChanged += this.OnSelectedSignature_CurrentParameterChanged; + } + ((INotifyCollectionChanged)_session.Signatures).CollectionChanged += this.OnSignatures_CollectionChanged; + + // Ensure we watch the view for layouts and resize it to ensure its contents fit + _signatureWpfTextView.SizeAllocated += this.OnTextView_LayoutChanged; + + // Add the session as a property to the signature help text buffer. This will be used by the current parameter bolding + // classifier. + _signatureTextBuffer.Properties.AddProperty(typeof(ISignatureHelpSession), _session); + + // Start the process of updating our view model to match the selected signature for this newly-bound session. + this.UpdateSignatureInfo(); + } + + private void OnTextView_LayoutChanged(object o, SizeAllocatedArgs args) + { + AutoSizeSignatureTextView(); + } + + private void UnBindFromSession() + { + // Don't do anything with an invalid session (dismissed session is ok). + if (_session == null) + { + return; + } + + // Unsubscribe from some events. + _session.SelectedSignatureChanged -= this.OnSession_SelectedSignatureChanged; + if (_session.SelectedSignature != null) + { + _session.SelectedSignature.CurrentParameterChanged -= this.OnSelectedSignature_CurrentParameterChanged; + } + ((INotifyCollectionChanged)_session.Signatures).CollectionChanged -= this.OnSignatures_CollectionChanged; + + _signatureWpfTextView.SizeAllocated -= this.OnTextView_LayoutChanged; + + this.PagerVisibility = false; + this.SignatureDocumentationVisibility = false; + this.CurrentParameterVisibility = false; + + // Make sure that our property added to the signature text buffer is released. Also, the buffer should be empty. + _signatureTextBuffer.Replace(new Span(0, _signatureTextBuffer.CurrentSnapshot.Length), string.Empty); + _signatureTextBuffer.Properties.RemoveProperty(typeof(ISignatureHelpSession)); + + // Change the content-type of our signature text buffer back to text. This is how we'll ensure that our classifiers + // always get run. When we change the content type when binding to a session, our classifiers will be re-run. + _signatureTextBuffer.ChangeContentType(this.TextContentType, null); + + // Null-out our ref to the session. + _session = null; + } + + private void RaisePropertyChanged(string propertyName) + { + PropertyChangedEventHandler tempHandler = this.PropertyChanged; + if (tempHandler != null) + { + tempHandler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + private void UpdateSignatureInfo() + { + // If the session is in a bad state, just don't update. + Debug.Assert(_session != null, "How'd we get a null session?"); + if ((_session == null) || _session.IsDismissed || (_session.Signatures.Count < 1)) + { + return; + } + + // Determine which signature we should be rendering. This is usually the selected signature, but if we run up against + // a session with no selected signature, just use the first one. + ISignature sigToRender = _session.SelectedSignature != null ? _session.SelectedSignature : _session.Signatures[0]; + + // Make sure the content type of the signature text buffer is correct. + IContentType desiredContentType = this.GetSignatureHelpContentType(sigToRender); + if (_signatureTextBuffer.ContentType != desiredContentType) + { + _signatureTextBuffer.ChangeContentType(desiredContentType, null); + } + + // Update the text in the signature text buffer to match the new signature being displayed. + this.DisplayContent(sigToRender); + + // If all the content didn't fit in one line, then try the pretty printed content unless it's null (see Dev11 #376399) + if (_signatureWpfTextView.RealWidth > this.ContainerMaxWidth - this.PagerWidth - 4 && + sigToRender.PrettyPrintedContent != null) + { + this.DisplayPrettyPrintedContent(sigToRender); + + // Ensure that the pretty printed content fits, otherwise, switch back to the regular content as a last resort + if (_signatureWpfTextView.RealWidth > this.ContainerMaxWidth - this.PagerWidth - 4) + { + this.DisplayContent(sigToRender); + } + } + + // Update the documentation of the signature to match. + if (!string.IsNullOrEmpty(sigToRender.Documentation)) + { + this.SignatureDocumentation = sigToRender.Documentation; + this.SignatureDocumentationVisibility = true; + } + else + { + this.SignatureDocumentationVisibility = false; + this.SignatureDocumentation = string.Empty; + } + + // See if the pager should be enabled. It should show up only if there are at least 2 signatures. + if (this.Session.Signatures.Count > 1) + { + // The pager should be enabled. + int selectionIndex = this.Session.Signatures.IndexOf(sigToRender); + this.PagerText = GettextCatalog.GetString("{0} of {1}", + selectionIndex + 1, + this.Session.Signatures.Count); + this.PagerVisibility = true; + } + else + { + // The pager should not be enabled. + this.PagerVisibility = false; + this.PagerText = string.Empty; + } + + // Whenever the signature information is being updated, chances are we have to update the parameter info as well. + this.UpdateParameterInfo(); + } + + private void DisplayContent(ISignature sigToRender) + { + // Ensure word-wrap is off initially. We only turn word-wrap on if we are forced to due to lack of + // space on the screen + _signatureTextBuffer.Properties[SignatureHelpParameterBoldingClassfier.UsePrettyPrintedContentKey] = false; + _signatureTextBuffer.Replace(new Span(0, _signatureTextBuffer.CurrentSnapshot.Length), sigToRender.Content); + var classifierSpans = PlatformCatalog.Instance.ClassifierAggregatorService.GetClassifier(_signatureTextBuffer)?.GetClassificationSpans(new SnapshotSpan(_signatureTextBuffer.CurrentSnapshot, 0, _signatureTextBuffer.CurrentSnapshot.Length)); + if (classifierSpans != null && classifierSpans.Count > 0) + _signatureWpfTextView.Markup = MDUtils.ClassificationsToMarkup(_signatureTextBuffer.CurrentSnapshot, classifierSpans, sigToRender.CurrentParameter?.Locus); + else + { + _signatureWpfTextView.Markup = MonoDevelop.Ide.TypeSystem.Ambience.EscapeText(_signatureTextBuffer.CurrentSnapshot.GetText()); + LoggingService.LogWarning("No classification spans found for signature helper:" + _signatureTextBuffer.ContentType); + } + _signatureWpfTextView.Visible = true; + } + + private void DisplayPrettyPrintedContent(ISignature sigToRender) + { + Debug.Assert(sigToRender.PrettyPrintedContent != null, "We shouldn't try to display null PrettyPrintedContent."); + // Ensure word-wrap is off initially. We only turn word-wrap on if we are forced to due to lack of + // space on the screen + _signatureTextBuffer.Properties[SignatureHelpParameterBoldingClassfier.UsePrettyPrintedContentKey] = true; + _signatureTextBuffer.Replace(new Span(0, _signatureTextBuffer.CurrentSnapshot.Length), sigToRender.PrettyPrintedContent); + var classifierSpans = PlatformCatalog.Instance.ClassifierAggregatorService.GetClassifier(_signatureTextBuffer)?.GetClassificationSpans(new SnapshotSpan(_signatureTextBuffer.CurrentSnapshot, 0, _signatureTextBuffer.CurrentSnapshot.Length)); + if (classifierSpans != null && classifierSpans.Count > 0) + _signatureWpfTextView.Markup = MDUtils.ClassificationsToMarkup(_signatureTextBuffer.CurrentSnapshot, classifierSpans, sigToRender.CurrentParameter?.Locus); + else + { + _signatureWpfTextView.Markup = MonoDevelop.Ide.TypeSystem.Ambience.EscapeText(_signatureTextBuffer.CurrentSnapshot.GetText()); + LoggingService.LogWarning("No classification spans found for signature helper:" + _signatureTextBuffer.ContentType); + } + _signatureWpfTextView.Visible = true; + } + + private void UpdateParameterInfo() + { + // If the session is in a bad state, just don't update. + Debug.Assert(_session != null, "How'd we get a null session?"); + if ((_session == null) || _session.IsDismissed || (_session.Signatures.Count < 1)) + { + return; + } + + // Determine which parameter is the current one. + IParameter currentParam = _session.SelectedSignature != null + ? _session.SelectedSignature.CurrentParameter + : _session.Signatures[0].CurrentParameter; + + // Should the "current parameter" section even be enabled? Not if either the name or documentation is empty. + if ((currentParam == null) || + string.IsNullOrEmpty(currentParam.Name) || + string.IsNullOrEmpty(currentParam.Documentation)) + { + this.CurrentParameterVisibility = false; + this.CurrentParameterName = string.Empty; + this.CurrentParameterDocumentation = string.Empty; + } + else + { + this.CurrentParameterName = GettextCatalog.GetString("{0}:", + currentParam.Name); + this.CurrentParameterDocumentation = currentParam.Documentation; + this.CurrentParameterVisibility = true; + } + } + + private IContentType TextContentType + { + get + { + if (_textContentType == null) + { + _textContentType = _componentContext.ContentTypeRegistryService.GetContentType("text"); + } + + return _textContentType; + } + } + + private IContentType GetSignatureHelpContentType(ISignature signature) + { + if ((_session == null) || (_session.IsDismissed)) + { + throw new InvalidOperationException("We can't determine the signature help content type without a session"); + } + + IContentType baseContentType = signature.ApplicableToSpan.TextBuffer.ContentType; + string newContentTypeName = string.Format(CultureInfo.InvariantCulture, "{0} Signature Help", baseContentType.TypeName); + //TODO: Workaround VS bug + if (newContentTypeName == "HTMLXProjection Signature Help") + newContentTypeName = "CSharp Signature Help"; + IContentType sigHelpContentType = _componentContext.ContentTypeRegistryService.GetContentType(newContentTypeName); + + if (sigHelpContentType == null) + { + string[] baseContentTypeNames = new string[] { "sighelp" }; + + sigHelpContentType = _componentContext.ContentTypeRegistryService.AddContentType + (newContentTypeName, + baseContentTypeNames); + } + + return sigHelpContentType; + } + + private void AutoSizeSignatureTextView() + { + //TODO + //Helpers.AutoSizeTextView(_signatureWpfTextView, this.ContainerMaxWidth - this.PagerWidth - 4 /* 4px of Margin */, this.ContainerMaxHeight); + _isAutoSizePending = false; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/AsyncQuickInfoBroker.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/AsyncQuickInfoBroker.cs new file mode 100644 index 0000000000..0896238889 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/AsyncQuickInfoBroker.cs @@ -0,0 +1,148 @@ +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.Composition; + using System.Diagnostics; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Internal.VisualStudio.Language.Intellisense; + using Microsoft.VisualStudio.Language.Utilities; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Utilities; + using Microsoft.VisualStudio.Threading; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(IAsyncQuickInfoBroker))] + internal sealed class AsyncQuickInfoBroker : IAsyncQuickInfoBroker + { + private readonly IEnumerable<Lazy<IAsyncQuickInfoSourceProvider, IOrderableContentTypeMetadata>> unorderedSourceProviders; + private readonly IGuardedOperations guardedOperations; + private readonly IToolTipService toolTipService; + private readonly JoinableTaskContext joinableTaskContext; + private IEnumerable<Lazy<IAsyncQuickInfoSourceProvider, IOrderableContentTypeMetadata>> orderedSourceProviders; + + [ImportingConstructor] + public AsyncQuickInfoBroker( + [ImportMany]IEnumerable<Lazy<IAsyncQuickInfoSourceProvider, IOrderableContentTypeMetadata>> unorderedSourceProviders, + IGuardedOperations guardedOperations, + IToolTipService toolTipService, + JoinableTaskContext joinableTaskContext) + { + // Bug #512117: Remove compatibility shims for 2nd gen. Quick Info APIs. + // Combines new + legacy providers into a single series for relative ordering. + var combinedProviders = unorderedSourceProviders ?? throw new ArgumentNullException(nameof(unorderedSourceProviders)); + + this.unorderedSourceProviders = combinedProviders; +#pragma warning restore 618 + this.guardedOperations = guardedOperations ?? throw new ArgumentNullException(nameof(guardedOperations)); + this.joinableTaskContext = joinableTaskContext ?? throw new ArgumentNullException(nameof(joinableTaskContext)); + this.toolTipService = toolTipService; + } + + #region IAsyncQuickInfoBroker + + public IAsyncQuickInfoSession GetSession(ITextView textView) + { + if (textView.Properties.TryGetProperty(typeof(AsyncQuickInfoSession), out AsyncQuickInfoSession property)) + { + return property; + } + + return null; + } + + public bool IsQuickInfoActive(ITextView textView) => GetSession(textView) != null; + + public async Task<IAsyncQuickInfoSession> TriggerQuickInfoAsync( + ITextView textView, + ITrackingPoint triggerPoint, + QuickInfoSessionOptions options, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Caret element requires UI thread. + await this.joinableTaskContext.Factory.SwitchToMainThreadAsync(); + + // We switched threads and there is some latency, so ensure that we're still not canceled. + cancellationToken.ThrowIfCancellationRequested(); + + // Dismiss any currently open session. + var currentSession = this.GetSession(textView); + if (currentSession != null) + { + await currentSession.DismissAsync(); + } + + // Get the trigger point from the caret if none is provided. + triggerPoint = triggerPoint ?? textView.TextSnapshot.CreateTrackingPoint( + textView.Caret.Position.BufferPosition, + PointTrackingMode.Negative); + + var newSession = new AsyncQuickInfoSession( + this.OrderedSourceProviders, + this.guardedOperations, + this.joinableTaskContext, + this.toolTipService, + textView, + triggerPoint, + options, + null); + + // StartAsync() is responsible for dispatching a StateChange + // event if canceled so no need to clean these up on cancellation. + newSession.StateChanged += this.OnStateChanged; + textView.Properties.AddProperty(typeof(AsyncQuickInfoSession), newSession); + + try + { + await newSession.StartAsync(cancellationToken); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + // Don't throw OperationCanceledException unless the caller canceled us. + // This can happen if computation was canceled by a quick info source + // dismissing the session during computation, which we want to consider + // more of a 'N/A' than an error. + return null; + } + + return newSession; + } + + #endregion + + #region Private Impl + + // Bug #512117: Remove compatibility shims for 2nd gen. Quick Info APIs. + // This interface exists only to expose additional functionality required by the shims. +#pragma warning disable 618 + private IEnumerable<Lazy<IAsyncQuickInfoSourceProvider, IOrderableContentTypeMetadata>> OrderedSourceProviders + => this.orderedSourceProviders ?? (this.orderedSourceProviders = Orderer.Order(this.unorderedSourceProviders)); +#pragma warning restore 618 + + // Listens for the session being dismissed so that we can remove it from the view's property bag. + private void OnStateChanged(object sender, QuickInfoSessionStateChangedEventArgs e) + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + if (e.NewState == QuickInfoSessionState.Dismissed) + { + if (sender is AsyncQuickInfoSession session) + { + session.TextView.Properties.RemoveProperty(typeof(AsyncQuickInfoSession)); + session.StateChanged -= this.OnStateChanged; + return; + } + + Debug.Fail("Unexpected sender type"); + } + } + + #endregion + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/AsyncQuickInfoSession.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/AsyncQuickInfoSession.cs new file mode 100644 index 0000000000..da67867c20 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/AsyncQuickInfoSession.cs @@ -0,0 +1,535 @@ +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Internal.VisualStudio.Language.Intellisense; + using Microsoft.VisualStudio.Language.Intellisense; + using Microsoft.VisualStudio.Language.Utilities; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Utilities; + using Microsoft.VisualStudio.Threading; + using Microsoft.VisualStudio.Utilities; + + internal sealed class AsyncQuickInfoSession : IAsyncQuickInfoSession + { + private readonly IEnumerable<Lazy<IAsyncQuickInfoSourceProvider, IOrderableContentTypeMetadata>> orderedSourceProviders; + + private readonly IGuardedOperations guardedOperations; + private readonly IToolTipService toolTipService; + private readonly JoinableTaskContext joinableTaskContext; + private readonly ITrackingPoint triggerPoint; + + // For the purposes of synchronization, state updates are non-atomic and 'Calculating' + // state is considered to be transient. The properties of the object can be updated + // individually and are immediately visible to all threads. External extenders are + // essentially not impacted by this lack of atomicity because they only have a reference + // to the session from the broker after it is finished calculating, and the non-atomic + // updating of the properties happens after all IAsyncQuickInfoSources have returned. + // This class avoids its own races by marshalling all calls into the class and all state + // changes through the UI thread and by only allowing one invocation of 'StartAsync()'. + + #region Cross Thread Readable, Modifiable + + // All state in this region can be read or modified from any thread and must + // be accessed with VOLATILE.READ() + VOLATILE.WRITE(). + private ImmutableList<object> content = ImmutableList<object>.Empty; + private ITrackingSpan applicableToSpan; + + #endregion + + #region Cross Thread Readable, Modifiable Only Via UI Thread + + // State in this region can be read from any thread at any time (often via properties) + // but writes are synchronized via the UI thread. All readers should use VOLATILE.READ() + // all writers should use VOLATILE.WRITE(). + private bool uiThreadWritableHasInteractiveContent; + private int uiThreadWritableState = (int)QuickInfoSessionState.Created; + + #endregion + + #region Reable/Writeable via UI Thread Only + + private CancellationTokenSource uiThreadOnlyLinkedCancellationTokenSource; + internal IToolTipPresenter uiThreadOnlyPresenter; + + #endregion + + public AsyncQuickInfoSession (
+ IEnumerable<Lazy<IAsyncQuickInfoSourceProvider, IOrderableContentTypeMetadata>> orderedSourceProviders,
+ IGuardedOperations guardedOperations, + JoinableTaskContext joinableTaskContext, + IToolTipService toolTipService, + ITextView textView, + ITrackingPoint triggerPoint, + QuickInfoSessionOptions options, + PropertyCollection propertyCollection) + { + this.orderedSourceProviders = orderedSourceProviders ?? throw new ArgumentNullException(nameof(orderedSourceProviders)); + this.guardedOperations = guardedOperations ?? throw new ArgumentNullException(nameof(guardedOperations)); + this.joinableTaskContext = joinableTaskContext ?? throw new ArgumentNullException(nameof(joinableTaskContext)); + this.toolTipService = toolTipService ?? throw new ArgumentNullException(nameof(toolTipService)); + this.TextView = textView ?? throw new ArgumentNullException(nameof(textView)); + this.triggerPoint = triggerPoint ?? throw new ArgumentNullException(nameof(triggerPoint)); + this.Options = options; + + // Bug #512117: Remove compatibility shims for 2nd gen. Quick Info APIs. + // We can remove this null check once we remove the legacy APIs. + this.Properties = propertyCollection ?? new PropertyCollection(); + + // Trigger point must be a tracking point on the view's buffer. + if (triggerPoint.TextBuffer != textView.TextBuffer) + { + throw new ArgumentException("The specified ITextSnapshot doesn't belong to the correct TextBuffer"); + } + } + + #region IAsyncQuickInfoSession + + // All state changes are dispatched on the UI thread via TransitionState(). + public event EventHandler<QuickInfoSessionStateChangedEventArgs> StateChanged; + + public bool HasInteractiveContent => Volatile.Read(ref this.uiThreadWritableHasInteractiveContent); + + public ITrackingSpan ApplicableToSpan => Volatile.Read(ref this.applicableToSpan); + + public QuickInfoSessionOptions Options { get; } + + public PropertyCollection Properties { get; } + + public QuickInfoSessionState State => (QuickInfoSessionState)Volatile.Read(ref this.uiThreadWritableState); + + public ITextView TextView { get; } + + public IEnumerable<object> Content => Volatile.Read(ref this.content); + + public async Task DismissAsync() + { + // Ensure that we have the UI thread. To avoid races, the rest of this method must be sync. + await this.joinableTaskContext.Factory.SwitchToMainThreadAsync(); + + var currentState = this.State; + if (currentState != QuickInfoSessionState.Dismissed) + { + // Cancel any running computations. + this.uiThreadOnlyLinkedCancellationTokenSource?.Cancel(); + this.uiThreadOnlyLinkedCancellationTokenSource = null; + + // Dismiss presenter. + var presenter = this.uiThreadOnlyPresenter; + if (presenter != null) + { + presenter.Dismissed -= this.OnDismissed; + this.uiThreadOnlyPresenter.Dismiss(); + + this.uiThreadOnlyPresenter = null; + } + + // Update object state. + Volatile.Write(ref this.content, ImmutableList<object>.Empty); + Volatile.Write(ref this.applicableToSpan, null); + + // Alert subscribers on the UI thread. + this.TransitionTo(QuickInfoSessionState.Dismissed); + } + } + + public ITrackingPoint GetTriggerPoint(ITextBuffer textBuffer) + { + var mappedTriggerPoint = GetTriggerPoint(textBuffer.CurrentSnapshot); + + if (!mappedTriggerPoint.HasValue) + { + return null; + } + + return mappedTriggerPoint.Value.Snapshot.CreateTrackingPoint(mappedTriggerPoint.Value, PointTrackingMode.Negative); + } + + public SnapshotPoint? GetTriggerPoint(ITextSnapshot textSnapshot) + { + var triggerSnapshotPoint = this.triggerPoint.GetPoint(this.TextView.TextSnapshot); + var triggerSpan = new SnapshotSpan(triggerSnapshotPoint, 0); + + var mappedSpans = new FrugalList<SnapshotSpan>(); + MappingHelper.MapDownToBufferNoTrack(triggerSpan, textSnapshot.TextBuffer, mappedSpans); + + if (mappedSpans.Count == 0) + { + return null; + } + else + { + return mappedSpans[0].Start; + } + } + + #endregion + + #region Internal Impl + + internal async Task StartAsync(CancellationToken cancellationToken) + { + if (this.State != QuickInfoSessionState.Created) + { + throw new InvalidOperationException($"Session must be in the {QuickInfoSessionState.Created} state to be started"); + } + + // Ensure we have the UI thread. + await this.joinableTaskContext.Factory.SwitchToMainThreadAsync(); + + cancellationToken.ThrowIfCancellationRequested(); + + // Create a linked cancellation token and store this in the class so we can be canceled by calls to DismissAsync() + // without impacting the caller's cancellation token. + using (var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + try + { + this.uiThreadOnlyLinkedCancellationTokenSource = linkedCancellationTokenSource; + var failures = await this.ComputeContentAndStartPresenterAsync(this.uiThreadOnlyLinkedCancellationTokenSource.Token); + + if (failures?.Any() ?? false) + { + // Guarded operations is not safe to use from the background so + // we catch all exceptions and post them here on the UI thread. + this.guardedOperations.HandleException(this, new AggregateException(failures)); + } + } + finally + { + this.uiThreadOnlyLinkedCancellationTokenSource = null; + + // If we were unable to await some computation task and don't end up with + // a visible presenter + content, ensure that we cleanup and change our state appropriately. + if (Volatile.Read(ref this.uiThreadOnlyPresenter) == null) + { + await this.DismissAsync().ConfigureAwait(false); + } + } + } + } + + #endregion + + #region Private Impl + + private async Task<IList<Exception>> ComputeContentAndStartPresenterAsync(CancellationToken cancellationToken) + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + // Read current state. + var originalState = this.State; + (IList<object> items, IList<ITrackingSpan> applicableToSpans, IList<Exception> failures)? results = null; + + // Alert subscribers on the UI thread. + this.TransitionTo(QuickInfoSessionState.Calculating); + + cancellationToken.ThrowIfCancellationRequested(); + + // Find and create the sources. Sources cache is smart enough to + // invalidate on content-type changed and free on view close. + var sources = IntellisenseSourceCache.GetSources( + this.TextView, + GetBuffersForTriggerPoint().ToList(), + this.CreateSources); + + // Compute quick info items. This method switches off the UI thread. + // From here on out we're on an arbitrary thread. + results = await ComputeContentAsync(sources, cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + // Update our content, or put the empty list if there is none. + Volatile.Write( + ref this.content, + results != null ? ImmutableList.CreateRange(results.Value.items) : ImmutableList<object>.Empty); + + await StartUIThreadEpilogueAsync(originalState, results?.applicableToSpans, cancellationToken).ConfigureAwait(false); + + return results?.failures; + } + + private async Task StartUIThreadEpilogueAsync( + QuickInfoSessionState originalState, + IList<ITrackingSpan> applicableToSpans, + CancellationToken cancellationToken) + { + // Ensure we're back on the UI thread. + await this.joinableTaskContext.Factory.SwitchToMainThreadAsync(); + + if (applicableToSpans != null) + { + // Update the applicable-to span. + this.ComputeApplicableToSpan(applicableToSpans); + } + + // Check if any of our content is interactive and cache that so it's not done on mouse move. + this.ComputeHasInteractiveContent(); + + // If we have results and a span for which to show them and we aren't cancelled update the tip. + if ((originalState == QuickInfoSessionState.Created) + && this.Content.Any() + && (this.ApplicableToSpan != null) + && !cancellationToken.IsCancellationRequested) + { + this.CreateAndStartPresenter(); + } + } + + private void CreateAndStartPresenter() + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + Debug.Assert(this.uiThreadOnlyPresenter == null); + + // Configure presenter behavior. + var parameters = new ToolTipParameters( + this.Options.HasFlag(QuickInfoSessionOptions.TrackMouse), + keepOpenFunc: this.ContentRequestsKeepOpen); + + // Create and show presenter. + this.uiThreadOnlyPresenter = this.toolTipService.CreatePresenter(this.TextView, parameters); + this.uiThreadOnlyPresenter.Dismissed += this.OnDismissed; + this.uiThreadOnlyPresenter.StartOrUpdate(this.ApplicableToSpan, this.Content); + + // Ensure that the presenter didn't dismiss the session. + if (this.State != QuickInfoSessionState.Dismissed) + { + // Update state and alert subscribers on the UI thread. + this.TransitionTo(QuickInfoSessionState.Visible); + } + } + + private bool ContentRequestsKeepOpen() + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + if (this.HasInteractiveContent) + { + foreach (var content in this.Content) + { + if ((content is IInteractiveQuickInfoContent interactiveContent) + && ((interactiveContent.KeepQuickInfoOpen || interactiveContent.IsMouseOverAggregated))) + { + return true; + } + } + } + + return false; + } + + private void OnDismissed(object sender, EventArgs e) + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + this.joinableTaskContext.Factory.RunAsync(async delegate + { + await this.DismissAsync().ConfigureAwait(false); + }); + } + + private async Task<(IList<object> items, IList<ITrackingSpan> applicableToSpans, IList<Exception> failures)> ComputeContentAsync( + IEnumerable<OrderedSource> unorderedSources, + CancellationToken cancellationToken) + { + // Ensure we're off the UI thread. + await TaskScheduler.Default; + + cancellationToken.ThrowIfCancellationRequested(); + + var items = new FrugalList<object>(); + var applicableToSpans = new FrugalList<ITrackingSpan>(); + var failures = new FrugalList<Exception>(); + + // Sources from the cache are from the flattened projection buffer graph + // so they're initially out of order. We recorded their MEF ordering in + // a property though so we can reorder them now. + foreach (var source in unorderedSources.OrderBy(source => source.Order)) + { + // This code is sequential to enable back-compat with the IQuickInfo* APIs, + // but when the shims are removed, consider parallelizing as a potential optimization. + await this.ComputeSourceContentAsync( + source.Source, + items, + applicableToSpans, + failures, + cancellationToken); + } + + return (items, applicableToSpans, failures); + } + + private void ComputeHasInteractiveContent() + { + foreach (var result in this.Content) + { + if (result is IInteractiveQuickInfoContent) + { + Volatile.Write(ref this.uiThreadWritableHasInteractiveContent, true); + break; + } + } + } + + private async Task ComputeSourceContentAsync( + IAsyncQuickInfoSource source, + IList<object> items, + IList<ITrackingSpan> applicableToSpans, + IList<Exception> failures, + CancellationToken cancellationToken) + { + Debug.Assert(!this.joinableTaskContext.IsOnMainThread); + + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var result = await source.GetQuickInfoItemAsync(this, cancellationToken).ConfigureAwait(false); + if (result != null) + { + items.Add(result.Item); + if (result.ApplicableToSpan != null) + { + applicableToSpans.Add(result.ApplicableToSpan); + } + } + } + catch (Exception ex) when (ex.GetType() != typeof(OperationCanceledException)) + { + failures.Add(ex); + } + } + + private void ComputeApplicableToSpan(IEnumerable<ITrackingSpan> applicableToSpans) + { + // Requires UI thread for access to BufferGraph. + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + ITrackingSpan newApplicableToSpan = Volatile.Read(ref this.applicableToSpan); + + foreach (var result in applicableToSpans) + { + var applicableToSpan = result; + + if (applicableToSpan != null) + { + SnapshotSpan subjectAppSnapSpan = applicableToSpan.GetSpan(applicableToSpan.TextBuffer.CurrentSnapshot); + + var surfaceAppSpans = this.TextView.BufferGraph.MapUpToBuffer( + subjectAppSnapSpan, + applicableToSpan.TrackingMode, + this.TextView.TextBuffer); + + if (surfaceAppSpans.Count >= 1) + { + applicableToSpan = surfaceAppSpans[0].Snapshot.CreateTrackingSpan(surfaceAppSpans[0], applicableToSpan.TrackingMode); + + newApplicableToSpan = IntellisenseUtilities.GetEncapsulatingSpan( + this.TextView, + newApplicableToSpan, + applicableToSpan); + } + } + } + + Volatile.Write(ref this.applicableToSpan, newApplicableToSpan); + } + + private IEnumerable<OrderedSource> CreateSources(ITextBuffer textBuffer) + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + int i = 0; + + foreach (var sourceProvider in this.orderedSourceProviders) + { + foreach (var contentType in sourceProvider.Metadata.ContentTypes) + { + if (textBuffer.ContentType.IsOfType(contentType)) + { + var source = this.guardedOperations.InstantiateExtension( + this, + sourceProvider, + provider => provider.TryCreateQuickInfoSource(textBuffer)); + + if (source != null) + { + yield return new OrderedSource(i, source); + } + } + } + + ++i; + } + } + + private Collection<ITextBuffer> GetBuffersForTriggerPoint() + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + return this.TextView.BufferGraph.GetTextBuffers( + buffer => this.GetTriggerPoint(buffer.CurrentSnapshot) != null); + } + + private void TransitionTo(QuickInfoSessionState newState) + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + var oldState = this.State; + bool isValid = false; + + switch (newState) + { + case QuickInfoSessionState.Created: + isValid = false; + break; + case QuickInfoSessionState.Calculating: + isValid = oldState == QuickInfoSessionState.Created || oldState == QuickInfoSessionState.Visible; + break; + case QuickInfoSessionState.Dismissed: + isValid = oldState == QuickInfoSessionState.Visible + || oldState == QuickInfoSessionState.Calculating; + break; + case QuickInfoSessionState.Visible: + isValid = oldState == QuickInfoSessionState.Calculating; + break; + } + + if (!isValid) + { + throw new InvalidOperationException($"Invalid {nameof(IAsyncQuickInfoSession)} state transition from {oldState} to {newState}"); + } + + Volatile.Write(ref this.uiThreadWritableState, (int)newState); + this.StateChanged?.Invoke(this, new QuickInfoSessionStateChangedEventArgs(oldState, newState)); + } + + private sealed class OrderedSource : IDisposable + { + public OrderedSource(int order, IAsyncQuickInfoSource source) + { + this.Order = order; + this.Source = source ?? throw new ArgumentNullException(nameof(source)); + } + + public IAsyncQuickInfoSource Source { get; } + + public int Order { get; } + + public void Dispose() + { + this.Source.Dispose(); + } + } + + #endregion + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/QuickInfoController.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/QuickInfoController.cs new file mode 100644 index 0000000000..ce7fea00e3 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/QuickInfoController.cs @@ -0,0 +1,165 @@ +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + using System; + using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.VisualStudio.Language.Utilities; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Threading; + + internal sealed class QuickInfoController + { + private readonly IAsyncQuickInfoBroker quickInfoBroker; + private readonly JoinableTaskContext joinableTaskContext; + private readonly ITextView textView; + private CancellationTokenSource cancellationTokenSource; + + internal QuickInfoController( + IAsyncQuickInfoBroker quickInfoBroker, + JoinableTaskContext joinableTaskContext, + ITextView textView) + { + this.quickInfoBroker = quickInfoBroker ?? throw new ArgumentNullException(nameof(quickInfoBroker)); + this.joinableTaskContext = joinableTaskContext ?? throw new ArgumentNullException(nameof(joinableTaskContext)); + this.textView = textView ?? throw new ArgumentNullException(nameof(textView)); + + IntellisenseUtilities.ThrowIfNotOnMainThread(joinableTaskContext); + + this.textView.MouseHover += this.OnMouseHover; + this.textView.Closed += this.OnTextViewClosed; + } + + // Internal for unit test. + internal void OnTextViewClosed(object sender, EventArgs e) + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + this.textView.Closed -= this.OnTextViewClosed; + + // Cancel any calculating sessions and dispose the token. + this.CancelAndDisposeToken(); + + // Terminate any open quick info sessions. + this.joinableTaskContext.Factory.RunAsync(async delegate + { + var session = this.quickInfoBroker.GetSession(this.textView); + if (session != null) + { + await session.DismissAsync(); + } + }); + + this.textView.MouseHover -= this.OnMouseHover; + } + + private void OnMouseHover(object sender, MouseHoverEventArgs e) + { + IntellisenseUtilities.ThrowIfNotOnMainThread(this.joinableTaskContext); + + SnapshotPoint? surfaceHoverPointNullable = e.TextPosition.GetPoint( + this.textView.TextViewModel.DataBuffer, + PositionAffinity.Predecessor); + + // Does hover correspond to actual position in document or + // is there already a session around that is valid? + if (!surfaceHoverPointNullable.HasValue || this.IsSessionStillValid(surfaceHoverPointNullable.Value)) + { + return; + } + + // Cancel last queued quick info update, if there is one. + CancelAndDisposeToken(); + + this.cancellationTokenSource = new CancellationTokenSource(); + + // Start quick info session async on the UI thread. + this.joinableTaskContext.Factory.RunAsync(async delegate + { + await UpdateSessionStateAsync(surfaceHoverPointNullable.Value, this.cancellationTokenSource.Token); + + // Clean up the cancellation token source. + Debug.Assert(this.joinableTaskContext.IsOnMainThread); + this.cancellationTokenSource.Dispose(); + this.cancellationTokenSource = null; + }); + } + + private async Task UpdateSessionStateAsync(SnapshotPoint surfaceHoverPoint, CancellationToken cancellationToken) + { + // If we were cancelled while queued, do nothing. + if (cancellationToken.IsCancellationRequested) + { + return; + } + + ITrackingPoint triggerPoint = surfaceHoverPoint.Snapshot.CreateTrackingPoint( + surfaceHoverPoint.Position, + PointTrackingMode.Negative); + + try + { + await this.quickInfoBroker.TriggerQuickInfoAsync( + this.textView, + triggerPoint, + QuickInfoSessionOptions.TrackMouse, + cancellationToken); + } + catch (OperationCanceledException) { /* swallow exception */ } + } + + /// <summary> + /// Ensures that the specified session is still valid given the specified point. If the point is within the applicability + /// span of the session, the session will be left alone and the method will return true. If the point is outside of the + /// sessions applicability span, the session will be dismissed and the method will return false. + /// </summary> + private bool IsSessionStillValid(SnapshotPoint point) + { + // Make sure we're being called with a surface snapshot point. + Debug.Assert(point.Snapshot.TextBuffer == this.textView.TextViewModel.DataBuffer); + + var session = this.quickInfoBroker.GetSession(this.textView); + + if (session != null) + { + // First check that the point and applicable span are from the same subject buffer, + // and then that they intersect. + if ((session.ApplicableToSpan != null) && + (session.ApplicableToSpan.TextBuffer == point.Snapshot.TextBuffer) && + (session.ApplicableToSpan.GetSpan(point.Snapshot).IntersectsWith(new Span(point.Position, 0)))) + { + return true; + } + + // If this session has an interactive content give it a chance to keep the session alive. + if (session.HasInteractiveContent) + { + foreach (var content in session.Content) + { + foreach (var result in session.Content) + { + if (result is IInteractiveQuickInfoContent interactiveContent + && (interactiveContent.KeepQuickInfoOpen || interactiveContent.IsMouseOverAggregated)) + { + return true; + } + } + } + } + } + + return false; + } + + private void CancelAndDisposeToken() + { + if (this.cancellationTokenSource != null) + { + this.cancellationTokenSource.Cancel(); + this.cancellationTokenSource.Dispose(); + this.cancellationTokenSource = null; + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/QuickInfoTextViewCreationListener.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/QuickInfoTextViewCreationListener.cs new file mode 100644 index 0000000000..0df39fdac1 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/QuickInfoTextViewCreationListener.cs @@ -0,0 +1,40 @@ +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + using System; + using System.ComponentModel.Composition; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Threading; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(ITextViewCreationListener))] + [ContentType("any")] + [TextViewRole(PredefinedTextViewRoles.Editable)] + [TextViewRole(PredefinedTextViewRoles.EmbeddedPeekTextView)] + [TextViewRole(PredefinedTextViewRoles.CodeDefinitionView)] + internal sealed class QuickInfoTextViewCreationListener : ITextViewCreationListener + { + private readonly IAsyncQuickInfoBroker quickInfoBroker; + private readonly JoinableTaskContext joinableTaskContext; + + [ImportingConstructor] + public QuickInfoTextViewCreationListener( + IAsyncQuickInfoBroker quickInfoBroker, + JoinableTaskContext joinableTaskContext) + { + this.quickInfoBroker = quickInfoBroker + ?? throw new ArgumentNullException(nameof(quickInfoBroker)); + this.joinableTaskContext = joinableTaskContext + ?? throw new ArgumentNullException(nameof(joinableTaskContext)); + } + + public void TextViewCreated(ITextView textView) + { + // No need to do anything further, this type hooks up events to the + // text view and tracks its own life cycle. + new QuickInfoController( + this.quickInfoBroker, + this.joinableTaskContext, + textView); + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/SquiggleQuickInfoSource.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/SquiggleQuickInfoSource.cs new file mode 100644 index 0000000000..c92dfb3570 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/SquiggleQuickInfoSource.cs @@ -0,0 +1,139 @@ +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.VisualStudio.Language.Utilities; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Tagging; + + internal sealed class SquiggleQuickInfoSource : IAsyncQuickInfoSource + { + private bool disposed = false; + private SquiggleQuickInfoSourceProvider componentContext; + private ITextBuffer textBuffer; + private ITextView tagAggregatorTextView; + private ITagAggregator<IErrorTag> tagAggregator; + + public SquiggleQuickInfoSource( + SquiggleQuickInfoSourceProvider componentContext, + ITextBuffer textBuffer) + { + this.componentContext = componentContext + ?? throw new ArgumentNullException(nameof(componentContext)); + this.textBuffer = textBuffer + ?? throw new ArgumentNullException(nameof(textBuffer)); + } + + public async Task<QuickInfoItem> GetQuickInfoItemAsync( + IAsyncQuickInfoSession session, + CancellationToken cancellationToken) + { + if (this.disposed) + { + throw new ObjectDisposedException("SquiggleQuickInfoSource"); + } + + if (session.TextView.TextBuffer != this.textBuffer) + { + return null; + } + + // TagAggregators must be used exclusively on the UI thread. + await this.componentContext.JoinableTaskContext.Factory.SwitchToMainThreadAsync(); + + ITrackingSpan applicableToSpan = null; + object quickInfoContent = null; + ITagAggregator<IErrorTag> tagAggregator = GetTagAggregator(session.TextView); + + Debug.Assert(tagAggregator != null, "Couldn't create a tag aggregator for error tags"); + if (tagAggregator != null) + { + // Put together the span over which tags are to be discovered. This will be the zero-length span at the trigger + // point of the session. + SnapshotPoint? subjectTriggerPoint = session.GetTriggerPoint(this.textBuffer.CurrentSnapshot); + if (!subjectTriggerPoint.HasValue) + { + Debug.Fail("The squiggle QuickInfo source is being called when it shouldn't be."); + return null; + } + + ITextSnapshot currentSnapshot = subjectTriggerPoint.Value.Snapshot; + var querySpan = new SnapshotSpan(subjectTriggerPoint.Value, 0); + + // Ask for all of the error tags that intersect our query span. We'll get back a list of mapping tag spans. + // The first of these is what we'll use for our quick info. + IEnumerable<IMappingTagSpan<IErrorTag>> tags = tagAggregator.GetTags(querySpan); + ITrackingSpan appToSpan = null; + foreach (MappingTagSpan<IErrorTag> tag in tags) + { + NormalizedSnapshotSpanCollection applicableToSpans = tag.Span.GetSpans(currentSnapshot); + if (applicableToSpans.Count > 0) + { + // We've found a error tag at the right location with a tag span that maps to our subject buffer. + // Return the applicability span as well as the tooltip content. + appToSpan = IntellisenseUtilities.GetEncapsulatingSpan( + session.TextView, + appToSpan, + currentSnapshot.CreateTrackingSpan( + applicableToSpans[0].Span, + SpanTrackingMode.EdgeInclusive)); + + quickInfoContent = tag.Tag.ToolTipContent; + } + } + + if (quickInfoContent != null) + { + applicableToSpan = appToSpan; + return new QuickInfoItem( + applicableToSpan, + quickInfoContent); + } + } + + return null; + } + + private ITagAggregator<IErrorTag> GetTagAggregator(ITextView textView) + { + if (this.tagAggregator == null) + { + this.tagAggregatorTextView = textView; + this.tagAggregator = this.componentContext.TagAggregatorFactoryService.CreateTagAggregator<IErrorTag>(textView); + } + else if (this.tagAggregatorTextView != textView) + { + throw new ArgumentException ("The SquiggleQuickInfoSource cannot be shared between TextViews."); + } + + return this.tagAggregator; + } + + public void Dispose() + { + if (this.disposed) + { + return; + } + + this.disposed = true; + + Debug.Assert(this.componentContext.JoinableTaskContext.IsOnMainThread); + + // Get rid of the tag aggregator, if we created it. + if (this.tagAggregator != null) + { + this.tagAggregator.Dispose(); + this.tagAggregator = null; + this.tagAggregatorTextView = null; + } + + this.componentContext = null; + this.textBuffer = null; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/SquiggleQuickInfoSourceProvider.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/SquiggleQuickInfoSourceProvider.cs new file mode 100644 index 0000000000..668c0379be --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Impl/QuickInfo/SquiggleQuickInfoSourceProvider.cs @@ -0,0 +1,26 @@ +namespace Microsoft.VisualStudio.Language.Intellisense.Implementation +{ + using System.ComponentModel.Composition; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Tagging; + using Microsoft.VisualStudio.Threading; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(IAsyncQuickInfoSourceProvider))] + [Name("squiggle")] + [Order] + [ContentType("any")] + internal sealed class SquiggleQuickInfoSourceProvider : IAsyncQuickInfoSourceProvider + { + [Import] + internal IViewTagAggregatorFactoryService TagAggregatorFactoryService { get; set; } + + [Import] + internal JoinableTaskContext JoinableTaskContext { get; set; } + + public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) + { + return new SquiggleQuickInfoSource(this, textBuffer); + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Util/LanguageUtil/IntellisenseSourceCache.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Util/LanguageUtil/IntellisenseSourceCache.cs new file mode 100644 index 0000000000..6be0e1eb98 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Util/LanguageUtil/IntellisenseSourceCache.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation +// All rights reserved + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Utilities; + +namespace Microsoft.VisualStudio.Language.Utilities +{ + internal static class IntellisenseSourceCache + { + public static IEnumerable<TSource> GetSources<TSource>(ITextView textView, ITextBuffer buffer, Func<ITextBuffer, IEnumerable<TSource>> sourceCreator) where TSource : IDisposable + { + var viewCache = textView.Properties.GetOrCreateSingletonProperty(() => new ViewSourceCache<TSource>(textView, sourceCreator)); + return viewCache.GetSources(new ITextBuffer[] { buffer }); + } + + public static IEnumerable<TSource> GetSources<TSource>( + ITextView textView, + IReadOnlyCollection<ITextBuffer> buffers, + Func<ITextBuffer, IEnumerable<TSource>> sourceCreator) where TSource : IDisposable + { + var viewCache = textView.Properties.GetOrCreateSingletonProperty(() => new ViewSourceCache<TSource>(textView, sourceCreator)); + return viewCache.GetSources(buffers); + } + + private class ViewSourceCache<TSource> where TSource : IDisposable + { + private readonly ITextView _textView; + private readonly Func<ITextBuffer, IEnumerable<TSource>> _sourceCreator; + private readonly HybridDictionary _bufferCache = new HybridDictionary(); + + public ViewSourceCache(ITextView textView, Func<ITextBuffer, IEnumerable<TSource>> sourceCreator) + { + _textView = textView; + _sourceCreator = sourceCreator; + + _textView.Closed += (sender, args) => this.HandleViewClosed(); + } + + public IEnumerable<TSource> GetSources(IEnumerable<ITextBuffer> buffers) + { + if (_textView.IsClosed) + { + Debug.Fail("Intellisense is trying to operate after its view has been closed."); + return Enumerable.Empty<TSource>(); + } + + // For each buffer that should be involved, either return the cached source for that buffer, or create a source and + // cache it for later use. + IList<TSource> sources = new FrugalList<TSource>(); + foreach (var buffer in buffers) + { + var bufferSources = (IEnumerable<TSource>) _bufferCache[buffer]; + + if (bufferSources == null) + { + bufferSources = _sourceCreator(buffer); + _bufferCache.Add(buffer, bufferSources); + + buffer.ContentTypeChanged += OnContentTypeChanged; + } + + foreach (var source in bufferSources) + { + sources.Add(source); + } + } + + return sources; + } + + private void OnContentTypeChanged(object sender, ContentTypeChangedEventArgs e) + { + var buffer = (ITextBuffer)sender; + buffer.ContentTypeChanged -= OnContentTypeChanged; + + var bufferSources = (IEnumerable<TSource>)_bufferCache[buffer]; + + if (bufferSources != null) + { + foreach (TSource source in bufferSources) + { + source.Dispose(); + } + } + + _bufferCache.Remove(buffer); + } + + public void HandleViewClosed() + { + foreach (ITextBuffer buffer in _bufferCache.Keys) + { + buffer.ContentTypeChanged -= OnContentTypeChanged; + } + + foreach (IEnumerable<TSource> sourcesList in _bufferCache.Values) + { + foreach (var source in sourcesList) + { + source.Dispose(); + } + } + + _bufferCache.Clear(); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Util/LanguageUtil/IntellisenseUtilities.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Util/LanguageUtil/IntellisenseUtilities.cs new file mode 100644 index 0000000000..574ac2e8a9 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Language/Util/LanguageUtil/IntellisenseUtilities.cs @@ -0,0 +1,100 @@ +namespace Microsoft.VisualStudio.Language.Utilities +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Threading; + + internal static class IntellisenseUtilities + { + internal static void ThrowIfNotOnMainThread(JoinableTaskContext joinableTaskContext) + { + if (!joinableTaskContext.IsOnMainThread) + { + throw new InvalidOperationException("Operation must occur on main thread."); + } + } + + internal static ITrackingSpan GetEncapsulatingSpan(ITextView textView, ITrackingSpan span1, ITrackingSpan span2) + { + // If either of the spans is null, return the other one. If they're both null, we'll end up returning null + // (as it should be). + if (span1 == null) + { + return MapTrackingSpanToBuffer(textView, span2); + } + + if (span2 == null) + { + return MapTrackingSpanToBuffer(textView, span1); + } + + var surfaceSpans = new List<SnapshotSpan>(); + surfaceSpans.AddRange(MapTrackingSpanToSpansOnBuffer(textView, span1)); + surfaceSpans.AddRange(MapTrackingSpanToSpansOnBuffer(textView, span2)); + + ITrackingSpan encapsulatingSpan = null; + foreach (var span in surfaceSpans) + { + encapsulatingSpan = GetEncapsulatingSpan( + encapsulatingSpan, + span.Snapshot.CreateTrackingSpan(span, span1.TrackingMode)); + } + + return encapsulatingSpan; + } + + private static ITrackingSpan GetEncapsulatingSpan(ITrackingSpan span1, ITrackingSpan span2) + { + // If either of the spans is null, return the other one. If they're both null, we'll end up returning null + // (as it should be). + if (span1 == null) + { + return span2; + } + + if (span2 == null) + { + return span1; + } + + // Use the other overload if you need mapping between buffers. + if (span1.TextBuffer != span2.TextBuffer) + { + throw new ArgumentException("Spans must be from the same textBuffer"); + } + + var snapshot = span1.TextBuffer.CurrentSnapshot; + var snapSpan1 = span1.GetSpan(snapshot); + var snapSpan2 = span2.GetSpan(snapshot); + + return snapshot.CreateTrackingSpan( + Span.FromBounds( + Math.Min(snapSpan1.Start.Position, snapSpan2.Start.Position), + Math.Max(snapSpan1.End.Position, snapSpan2.End.Position)), + span1.TrackingMode); + } + + private static ITrackingSpan MapTrackingSpanToBuffer(ITextView textView, ITrackingSpan trackingSpan) + { + if (trackingSpan == null) + { + return null; + } + + var spans = MapTrackingSpanToSpansOnBuffer(textView, trackingSpan); + Debug.Assert(spans.Count == 1); + return spans[0].Snapshot.CreateTrackingSpan(spans[0], trackingSpan.TrackingMode); + } + + private static NormalizedSnapshotSpanCollection MapTrackingSpanToSpansOnBuffer(ITextView textView, ITrackingSpan trackingSpan) + { + return textView.BufferGraph.MapUpToBuffer( + trackingSpan.GetSpan(trackingSpan.TextBuffer.CurrentSnapshot), + trackingSpan.TrackingMode, + textView.TextBuffer); + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/MDUtils.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/MDUtils.cs new file mode 100644 index 0000000000..10e31d09b3 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/MDUtils.cs @@ -0,0 +1,165 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Classification;
+using MonoDevelop.Core;
+using MonoDevelop.Ide.Editor;
+using MonoDevelop.Ide.Editor.Highlighting;
+using MonoDevelop.Ide.TypeSystem;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+{
+ static class MDUtils
+ {
+ public static string ClassificationsToMarkup (ITextSnapshot snapshot, IList<ClassificationSpan> classifications, Span? locus)
+ {
+ var markup = new StringBuilder ();
+ var theme = DefaultSourceEditorOptions.Instance.GetEditorTheme (); + foreach (var part in classifications) { + //if () { TODO: Check if we hanle new lines + // markup.AppendLine (); + // continue; + //} + markup.Append ("<span foreground=\"");
+ markup.Append (GetThemeColor (theme, GetThemeColor (part.ClassificationType.Classification)));
+ markup.Append ("\">");
+ if (locus is Span locusSpan && part.Span.Intersection (locusSpan) is SnapshotSpan intersection) {
+ if (intersection.Start == part.Span.Start) {
+ if (intersection.End == part.Span.End) {
+ markup.Append ("<b>");
+ markup.Append (Ambience.EscapeText (snapshot.GetText (part.Span)));
+ markup.Append ("</b>");
+ } else {
+ markup.Append ("<b>");
+ markup.Append (Ambience.EscapeText (snapshot.GetText (intersection)));
+ markup.Append ("</b>");
+ markup.Append (Ambience.EscapeText (snapshot.GetText (intersection.End, part.Span.End - intersection.End)));
+ }
+ } else {
+ if (intersection.End == part.Span.End) {
+ markup.Append (Ambience.EscapeText (snapshot.GetText (part.Span.Start, intersection.Start - part.Span.Start)));
+ markup.Append ("<b>");
+ markup.Append (Ambience.EscapeText (snapshot.GetText (intersection)));
+ markup.Append ("</b>");
+ } else {
+ markup.Append (Ambience.EscapeText (snapshot.GetText (part.Span.Start, intersection.Start - part.Span.Start)));
+ markup.Append ("<b>");
+ markup.Append (Ambience.EscapeText (snapshot.GetText (intersection)));
+ markup.Append ("</b>");
+ markup.Append (Ambience.EscapeText (snapshot.GetText (intersection.End, part.Span.End - intersection.End)));
+ }
+ }
+ } else {
+ markup.Append (Ambience.EscapeText (snapshot.GetText (part.Span)));
+ }
+ markup.Append ("</span>"); + }
+ return markup.ToString ();
+ }
+
+ private static void AppendSpan (ITextSnapshot snapshot, StringBuilder markup, EditorTheme theme, ClassificationSpan part)
+ {
+ markup.Append ("<span foreground=\"");
+ markup.Append (GetThemeColor (theme, GetThemeColor (part.ClassificationType.Classification)));
+ markup.Append ("\">");
+ markup.Append (Ambience.EscapeText (snapshot.GetText (part.Span)));
+ markup.Append ("</span>");
+ }
+
+ static string GetThemeColor (EditorTheme theme, string scope) + { + return SyntaxHighlightingService.GetColorFromScope (theme, scope, EditorThemeColors.Foreground).ToPangoString (); + } + + static string GetThemeColor (string type) + { + switch (type) { + case ClassificationTypeNames.Keyword: + return "keyword"; + case ClassificationTypeNames.ClassName: + return EditorThemeColors.UserTypes; + case ClassificationTypeNames.DelegateName: + return EditorThemeColors.UserTypesDelegates; + case ClassificationTypeNames.EnumName: + return EditorThemeColors.UserTypesEnums; + case ClassificationTypeNames.InterfaceName: + return EditorThemeColors.UserTypesInterfaces; + case ClassificationTypeNames.ModuleName: + return EditorThemeColors.UserTypes; + case ClassificationTypeNames.StructName: + return EditorThemeColors.UserTypesValueTypes; + case ClassificationTypeNames.TypeParameterName: + return EditorThemeColors.UserTypesTypeParameters; + case ClassificationTypeNames.Identifier: + return "source.cs";//TODO: This things say .cs, C# + case ClassificationTypeNames.NumericLiteral: + return "constant.numeric"; + case ClassificationTypeNames.StringLiteral: + return "string.quoted"; + case ClassificationTypeNames.WhiteSpace: + return "source.cs"; + case ClassificationTypeNames.Operator: + return "keyword.source"; + case ClassificationTypeNames.Punctuation: + return "punctuation"; + case ClassificationTypeNames.Text: + return "source.cs"; + default: + LoggingService.LogWarning ("Warning unexpected classification type: " + type); + return "source.cs"; + } + }
+
+ public static class ClassificationTypeNames
+ {
+ public const string Comment = "comment";
+ public const string ExcludedCode = "excluded code";
+ public const string Identifier = "identifier";
+ public const string Keyword = "keyword";
+ public const string NumericLiteral = "number";
+ public const string Operator = "operator";
+ public const string PreprocessorKeyword = "preprocessor keyword";
+ public const string StringLiteral = "string";
+ public const string WhiteSpace = "whitespace";
+ public const string Text = "text";
+
+ public const string PreprocessorText = "preprocessor text";
+ public const string Punctuation = "punctuation";
+ public const string VerbatimStringLiteral = "string - verbatim";
+
+ public const string ClassName = "class name";
+ public const string DelegateName = "delegate name";
+ public const string EnumName = "enum name";
+ public const string InterfaceName = "interface name";
+ public const string ModuleName = "module name";
+ public const string StructName = "struct name";
+ public const string TypeParameterName = "type parameter name";
+
+ public const string XmlDocCommentAttributeName = "xml doc comment - attribute name";
+ public const string XmlDocCommentAttributeQuotes = "xml doc comment - attribute quotes";
+ public const string XmlDocCommentAttributeValue = "xml doc comment - attribute value";
+ public const string XmlDocCommentCDataSection = "xml doc comment - cdata section";
+ public const string XmlDocCommentComment = "xml doc comment - comment";
+ public const string XmlDocCommentDelimiter = "xml doc comment - delimiter";
+ public const string XmlDocCommentEntityReference = "xml doc comment - entity reference";
+ public const string XmlDocCommentName = "xml doc comment - name";
+ public const string XmlDocCommentProcessingInstruction = "xml doc comment - processing instruction";
+ public const string XmlDocCommentText = "xml doc comment - text";
+
+ public const string XmlLiteralAttributeName = "xml literal - attribute name";
+ public const string XmlLiteralAttributeQuotes = "xml literal - attribute quotes";
+ public const string XmlLiteralAttributeValue = "xml literal - attribute value";
+ public const string XmlLiteralCDataSection = "xml literal - cdata section";
+ public const string XmlLiteralComment = "xml literal - comment";
+ public const string XmlLiteralDelimiter = "xml literal - delimiter";
+ public const string XmlLiteralEmbeddedExpression = "xml literal - embedded expression";
+ public const string XmlLiteralEntityReference = "xml literal - entity reference";
+ public const string XmlLiteralName = "xml literal - name";
+ public const string XmlLiteralProcessingInstruction = "xml literal - processing instruction";
+ public const string XmlLiteralText = "xml literal - text";
+ }
+ }
+}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs index 0fa65b18b8..08e7984999 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs @@ -1,4 +1,4 @@ -//
+//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
@@ -48,6 +48,9 @@ namespace Microsoft.VisualStudio.Platform public Task<HighlightedLine> GetHighlightedLineAsync(IDocumentLine line, CancellationToken cancellationToken)
{
+ //TODO verify that the snapshot line from this.textBuffer is equivalent to the document line converted to a snapshotline.
+ //Possibly take in a TextDataModel as a parameter and verify the buffers are appropriate.
+ //ITextSnapshotLine snapshotLine = (line as Mono.TextEditor.TextDocument.DocumentLineFromTextSnapshotLine)?.Line;
ITextSnapshotLine snapshotLine = textBuffer.CurrentSnapshot.GetLineFromLineNumber (line.LineNumber - 1);
if ((this.classifier == null) || (snapshotLine == null))
{
@@ -194,6 +197,9 @@ namespace Microsoft.VisualStudio.Platform styleName = "punctuation.separator.key-value.html";
break;
case "HTML Server-Side Script":
+ //styleName = "punctuation.section.embedded.begin"; // suggested by mike, does nothing
+ //styleName = "punctuation.section.embedded.begin.cs"; // suggested by mike, does nothing
+ styleName = "meta.preprocessor.source.cs"; // TODO: Find a name to use here
//styleName = style.HtmlServerSideScript.Name;
break;
case "HTML Tag Delimiter":
@@ -257,7 +263,12 @@ namespace Microsoft.VisualStudio.Platform styleName = "variable.other.less";
break;
default:
- styleName = EditorThemeColors.Foreground;
+ // If the stylename looks like a textmate style, just use it
+ if (classificationType.Classification.IndexOf('.') >= 0)
+ {
+ styleName = classificationType.Classification;
+ }
+
break;
}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Adornments/ToolTipPresenterStyle.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Adornments/ToolTipPresenterStyle.cs new file mode 100644 index 0000000000..655524eae3 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Adornments/ToolTipPresenterStyle.cs @@ -0,0 +1,35 @@ +namespace Microsoft.VisualStudio.Text.Adornments +{ + using Xwt.Drawing; + using Microsoft.VisualStudio.Text.Editor; + + ///<summary> + /// Defines a set of properties that will be used to style the default WPF ToolTip presenter. + ///</summary> + /// <remarks> + /// This is a MEF component part, and should be exported with the following attributes: + /// [Export(typeof(ToolTipPresenterStyle))] + /// [Name] + /// [Order] + /// All exports of this component part should be ordered before the "default" ToolTip presenter style. At a minimum, this + /// means adding [Order(Before="default")] to the export metadata. + /// </remarks> + public class ToolTipPresenterStyle + { + /// <summary> + /// Gets a string that identifies the appearance category for the <see cref="ITextView"/> + /// displayed in the default ToolTip presenter. + /// </summary> + public virtual string AppearanceCategory { get; protected set; } + + /// <summary> + /// Gets a <see cref="Brush"/> that will be used to paint the borders in the ToolTip presenter. + /// </summary> + public virtual Color BorderBrush { get; protected set; } + + /// <summary> + /// Gets a <see cref="Brush"/> that will be used to paint the background of the ToolTip presenter. + /// </summary> + public virtual Color BackgroundBrush { get; protected set; } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/ISpaceReservationAgent.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/ISpaceReservationAgent.cs new file mode 100644 index 0000000000..7fd2dd12a8 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/ISpaceReservationAgent.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Editor +{ + using System.Windows.Media; + using System; + + /// <summary> + /// Handles the display of space reservation adornments. + /// </summary> + public interface ISpaceReservationAgent + { + /// <summary> + /// Positions and displays the contents of the the <see cref="ISpaceReservationAgent"/>. + /// </summary> + /// <param name="reservedSpace">Currently reserved space.</param> + /// <returns>The space. If null is returned, the <see cref="ISpaceReservationManager"/> will remove the agent.</returns> + /// <remarks>If an agent does not want to be removed, but also does not wish to request any additional space, it can return a non-null but empty Geometry.</remarks> + Geometry PositionAndDisplay(Geometry reservedSpace); + + /// <summary> + /// Called whenever the content of the space reservation agent should be hidden. + /// </summary> + /// <remarks>This method is called by the manager to hide the content of the space reservation agent.</remarks> + void Hide(); + + /// <summary> + /// Determines whether the mouse is over this agent or anything it contains. + /// </summary> + bool IsMouseOver { get; } + + /// <summary> + /// Determines whether the adornment created by the space reservation agent has keyboard focus. + /// </summary> + bool HasFocus { get; } + + /// <summary> + /// Occurs when the adornment created by the ISpaceReservationAgent loses focus. + /// </summary> + event EventHandler LostFocus; + + /// <summary> + /// Occurs when the adornment created by the ISpaceReservationAgent gets focus. + /// </summary> + event EventHandler GotFocus; + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/ISpaceReservationManager.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/ISpaceReservationManager.cs new file mode 100644 index 0000000000..b64d4ab8bb --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/ISpaceReservationManager.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Editor +{ + using System.Collections.ObjectModel; + using Microsoft.VisualStudio.Text.Adornments; + using System;
+ using MonoDevelop.Components;
+
+ /// <summary> + /// Manages space reservation adornments. + /// </summary> + public interface ISpaceReservationManager + { + /// <summary> + /// Creates a default implementation of an <see cref="ISpaceReservationAgent"/> that displays <paramref name="content"/> in a popup window. + /// </summary> + /// <param name="visualSpan">The span of text associated with the tip.</param> + /// <param name="style">The style options for displaying the tip.</param> + /// <param name="content">The UI element to be displayed in the tip.</param> + /// <returns>An <see cref="ISpaceReservationAgent"/> that will display the desired content in a popup window.</returns> + ISpaceReservationAgent CreatePopupAgent(ITrackingSpan visualSpan, PopupStyles style, Xwt.Widget content); + + /// <summary> + /// Updates <paramref name="agent"/> with the <paramref name="visualSpan"/>. + /// This only works for PopupAgents and returns for other agents. + /// </summary> + /// <param name="agent">The agent to add.</param> + /// <param name="visualSpan">The agent's new visual span.</param> + void UpdatePopupAgent(ISpaceReservationAgent agent, ITrackingSpan visualSpan, PopupStyles styles); + + /// <summary> + /// Adds <paramref name="agent"/> to the list of agents managed by this manager. + /// </summary> + /// <param name="agent">The agent to add.</param> + void AddAgent(ISpaceReservationAgent agent); + + /// <summary> + /// Removes <paramref name="agent"/> from the list of agents managed by this manager. + /// </summary> + /// <param name="agent">The agent to remove.</param> + /// <returns><c>true</c> if the agent was in the list of agents to remove.</returns> + bool RemoveAgent(ISpaceReservationAgent agent); + + /// <summary> + /// Gets the list of agents managed by this manager. + /// </summary> + /// <remarks>Any implementation of aa <see cref="ISpaceReservationAgent"/> can be used for this method.</remarks> + ReadOnlyCollection<ISpaceReservationAgent> Agents { get; } + + /// <summary> + /// Occurs when the agent is changed. + /// </summary> + /// <remarks></remarks> + event EventHandler<SpaceReservationAgentChangedEventArgs> AgentChanged; + + /// <summary> + /// Determines whether the mouse is over an agent managed by this manager. + /// </summary> + bool IsMouseOver { get; } + + /// <summary> + /// Determines whether the adornment created by the space reservation agent has keyboard focus. + /// </summary> + bool HasAggregateFocus { get; } + + /// <summary> + /// Occurs when keyboard focus is lost by any of the managed adornments. + /// </summary> + event EventHandler LostAggregateFocus; + + /// <summary> + /// Occurs when any of the managed adornments gets keyboard focus. + /// </summary> + event EventHandler GotAggregateFocus; + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/SpaceReservationAgentChangedEventArgs.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/SpaceReservationAgentChangedEventArgs.cs new file mode 100644 index 0000000000..55aea71068 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/SpaceReservationAgentChangedEventArgs.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Editor +{ + using System; + + /// <summary> + /// Provides information when an <see cref="ISpaceReservationAgent"/> is changed in an <see cref="ISpaceReservationManager"/>. + /// </summary> + + public class SpaceReservationAgentChangedEventArgs : EventArgs + { + private readonly ISpaceReservationAgent _newAgent; + private readonly ISpaceReservationAgent _oldAgent; + + /// <summary> + /// Initializes a new instance of <see cref="SpaceReservationAgentChangedEventArgs"/>. + /// </summary> + /// <param name="oldAgent">The <see cref="ISpaceReservationAgent "/> associated with the previous value.</param> + /// <param name="newAgent">The <see cref="ISpaceReservationAgent "/> associated with the new value.</param> + public SpaceReservationAgentChangedEventArgs(ISpaceReservationAgent oldAgent, ISpaceReservationAgent newAgent) + { + _oldAgent = oldAgent; + _newAgent = newAgent; + } + + /// <summary> + /// Gets the old agent. + /// </summary> + public ISpaceReservationAgent OldAgent { get { return _oldAgent; } } + + /// <summary> + /// Gets the new agent. + /// </summary> + public ISpaceReservationAgent NewAgent { get { return _newAgent; } } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/SpaceReservationManagerDefinition.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/SpaceReservationManagerDefinition.cs new file mode 100644 index 0000000000..4691cf03c0 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Def/TextUIWpf/Editor/SpaceReservationManagerDefinition.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Editor +{ + /// <summary> + /// Represents metadata for an <see cref="ISpaceReservationManager"/>. + /// </summary> + /// <remarks> + /// Because you cannot subclass this type, you can simply use the [Export] attribute. + /// </remarks> + /// <example> + /// internal sealed class Components + /// { + /// [Export] + /// [Name("SampleSpaceReservationManager")] + /// [Order(After = "Selection", Before = "Text")] + /// internal SpaceReservationManagerDefinition sampleManagerDefinition; + /// + /// { other components } + /// } + /// </example> + public sealed class SpaceReservationManagerDefinition + { + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/GuardedToolTipPresenter.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/GuardedToolTipPresenter.cs new file mode 100644 index 0000000000..937358b5b8 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/GuardedToolTipPresenter.cs @@ -0,0 +1,56 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Utilities; + + internal sealed class GuardedToolTipPresenter : IToolTipPresenter + { + private readonly IGuardedOperations guardedOperations; + private bool isDismissed = false; + + public GuardedToolTipPresenter( + IGuardedOperations guardedOperations, + IToolTipPresenter presenter) + { + this.guardedOperations = guardedOperations ?? throw new ArgumentNullException(nameof(guardedOperations)); + this.Presenter = presenter ?? throw new ArgumentNullException(nameof(presenter)); + + presenter.Dismissed += this.OnDismissed; + } + + public event EventHandler Dismissed; + + internal IToolTipPresenter Presenter { get; } + + public void Dismiss() + { + if (!this.isDismissed) + { + this.isDismissed = true; + this.guardedOperations.CallExtensionPoint(() => + { + this.Presenter.Dismissed -= this.Dismissed; + this.Presenter.Dismiss(); + }); + + this.Dismissed?.Invoke(this, EventArgs.Empty); + } + } + + public void StartOrUpdate(ITrackingSpan applicableToSpan, IEnumerable<object> content) + { + if (this.isDismissed) + { + throw new InvalidOperationException($"{nameof(IToolTipPresenter)} is dismissed"); + } + + this.guardedOperations.CallExtensionPoint( + () => this.Presenter.StartOrUpdate(applicableToSpan, content)); + } + + private void OnDismissed(object sender, EventArgs e) => this.Dismiss(); + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/IViewElementFactoryMetadata.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/IViewElementFactoryMetadata.cs new file mode 100644 index 0000000000..69f4889335 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/IViewElementFactoryMetadata.cs @@ -0,0 +1,11 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using Microsoft.VisualStudio.Utilities; + + public interface IViewElementFactoryMetadata : IOrderable + { + string FromFullName { get; } + + string ToFullName { get; } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/ToolTipService.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/ToolTipService.cs new file mode 100644 index 0000000000..079bebbe98 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/ToolTipService.cs @@ -0,0 +1,61 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.Composition; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Threading; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(IToolTipService))] + internal sealed class ToolTipService : IToolTipService + { + private readonly IEnumerable<Lazy<IToolTipPresenterFactory, IOrderable>> unorderedPresenterProviders; + private readonly IGuardedOperations guardedOperations; + private readonly JoinableTaskContext joinableTaskContext; + + // Lazily initialized. + private IEnumerable<Lazy<IToolTipPresenterFactory, IOrderable>> orderedPresenterProviders; + + [ImportingConstructor] + public ToolTipService( + [ImportMany]IEnumerable<Lazy<IToolTipPresenterFactory, IOrderable>> unorderedPresenterProviders, + IGuardedOperations guardedOperations, + JoinableTaskContext joinableTaskContext) + { + this.unorderedPresenterProviders = unorderedPresenterProviders + ?? throw new ArgumentNullException(nameof(unorderedPresenterProviders)); + this.guardedOperations = guardedOperations + ?? throw new ArgumentNullException(nameof(guardedOperations)); + this.joinableTaskContext = joinableTaskContext + ?? throw new ArgumentNullException(nameof(joinableTaskContext)); + } + + private IEnumerable<Lazy<IToolTipPresenterFactory, IOrderable>> OrderedPresenterProviders + => this.orderedPresenterProviders ?? (this.orderedPresenterProviders = Orderer.Order(this.unorderedPresenterProviders)); + + public IToolTipPresenter CreatePresenter(ITextView textView, ToolTipParameters parameters) + { + if (!this.joinableTaskContext.IsOnMainThread) + { + throw new InvalidOperationException("Must be called from UI thread"); + } + + foreach (var provider in this.OrderedPresenterProviders) + { + var presenter = this.guardedOperations.CallExtensionPoint( + () => provider.Value.Create(textView, parameters ?? ToolTipParameters.Default), + valueOnThrow: null); + + if (presenter != null) + { + // Wrap in a presenter that wraps all calls in a guarded operation. + return new GuardedToolTipPresenter(this.guardedOperations, presenter); + } + } + + throw new InvalidOperationException($"No applicable {nameof(IToolTipPresenterFactory)}"); + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/ViewElementFactoryService.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/ViewElementFactoryService.cs new file mode 100644 index 0000000000..258763c0a0 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/ToolTipAdornment/ViewElementFactoryService.cs @@ -0,0 +1,143 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.ComponentModel.Composition; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Threading; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(IViewElementFactoryService))] + internal sealed class ViewElementFactoryService : IViewElementFactoryService + { + private readonly IEnumerable<Lazy<IViewElementFactory, IViewElementFactoryMetadata>> unorderedViewFactories; + private readonly IGuardedOperations guardedOperations; + private readonly JoinableTaskContext joinableTaskContext; + + // Lazily computed. + private ImmutableDictionary<(Type, Type), Lazy<IViewElementFactory, IViewElementFactoryMetadata>> factoryMap + = ImmutableDictionary <(Type, Type), Lazy<IViewElementFactory, IViewElementFactoryMetadata>>.Empty; + private IEnumerable<Lazy<IViewElementFactory, IViewElementFactoryMetadata>> orderedFactories; + + [ImportingConstructor] + public ViewElementFactoryService( + [ImportMany]IEnumerable<Lazy<IViewElementFactory, IViewElementFactoryMetadata>> unorderedViewFactories, + IGuardedOperations guardedOperations, + JoinableTaskContext joinableTaskContext) + { + this.joinableTaskContext = joinableTaskContext + ?? throw new ArgumentNullException(nameof(joinableTaskContext)); + this.guardedOperations = guardedOperations + ?? throw new ArgumentNullException(nameof(guardedOperations)); + this.unorderedViewFactories = unorderedViewFactories + ?? throw new ArgumentNullException(nameof(unorderedViewFactories)); + } + + public TView CreateViewElement<TView>(ITextView textView, object model) where TView : class + { + if (!this.joinableTaskContext.IsOnMainThread) + { + throw new InvalidOperationException("Must be called on UI thread"); + } + + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + var lazyFactory = this.FindFactory<TView>(model.GetType()); + + if (lazyFactory != null) + { + var factory = this.guardedOperations + .InstantiateExtension(this, lazyFactory); + + return this.guardedOperations.CallExtensionPoint( + () => factory.CreateViewElement<TView>(textView, model), + valueOnThrow: default(TView)); + } + + return null; + } + + private IEnumerable<Lazy<IViewElementFactory, IViewElementFactoryMetadata>> OrderedFactories + => this.orderedFactories ?? (this.orderedFactories ?? Orderer.Order(this.unorderedViewFactories)); + + private Lazy<IViewElementFactory, IViewElementFactoryMetadata> FindFactory<TView>(Type modelType) + { + // Do we have this conversion cached? + if (this.factoryMap.TryGetValue((modelType, typeof(TView)), out var lazyFactory)) + { + return lazyFactory; + } + + // Nope, try and find a suitable converter that matches this type exactly. + var exactMatch = this.FindExactMatchingFactory<TView>(modelType); + if (exactMatch != null) + { + return exactMatch; + } + else + { + // Try and find a suitable match from our type and interface hierarchy. + return this.FindInheritanceMatchingFactory<TView>(modelType); + } + } + + private Lazy<IViewElementFactory, IViewElementFactoryMetadata> FindExactMatchingFactory<TView>(Type modelType) + { + var candidate = this.FindMatchingFactory<TView>(modelType, (item) => item.Metadata.FromFullName == modelType.AssemblyQualifiedName); + if (candidate != null) + { + return candidate; + } + + // Unknown conversion. + return null; + } + + private Lazy<IViewElementFactory, IViewElementFactoryMetadata> FindInheritanceMatchingFactory<TView>(Type modelType) + { + // Try and find a suitable match from the interfaces on this type. + foreach (var iface in modelType.GetInterfaces()) + { + var candidate = this.FindMatchingFactory<TView>(modelType, (item) => item.Metadata.FromFullName == iface.AssemblyQualifiedName); + if (candidate != null) + { + return candidate; + } + } + + // Still no, try and find a suitable converter from among the base types for this class. + for (var t = modelType.BaseType; t != null; t = t.BaseType) + { + var candidate = this.FindMatchingFactory<TView>(modelType, (item) => item.Metadata.FromFullName == t.AssemblyQualifiedName); + if (candidate != null) + { + return candidate; + } + } + + // Unknown conversion. + return null; + } + + private Lazy<IViewElementFactory, IViewElementFactoryMetadata> FindMatchingFactory<TView>( + Type modelType, + Predicate<Lazy<IViewElementFactory, IViewElementFactoryMetadata>> fromSelector) + { + foreach (var candidate in this.OrderedFactories) + { + if (candidate.Metadata.ToFullName == typeof(TView).AssemblyQualifiedName && fromSelector.Invoke(candidate)) + { + this.factoryMap = this.factoryMap.Add((modelType, typeof(TView)), candidate); + return candidate; + } + } + + return null; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/BaseWpfToolTipPresenter.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/BaseWpfToolTipPresenter.cs new file mode 100644 index 0000000000..9ece9899b9 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/BaseWpfToolTipPresenter.cs @@ -0,0 +1,176 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Windows; + using System.Windows.Media; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor;
+ using MonoDevelop.Components;
+ using Xwt;
+
+ internal abstract class BaseWpfToolTipPresenter : IToolTipPresenter, IObscuringTip + { + protected readonly IViewElementFactoryService viewElementFactoryService; + protected readonly IObscuringTipManager obscuringTipManager; + protected readonly ITextView textView; + protected readonly ToolTipParameters parameters; + protected readonly ToolTipPresenterStyle presenterStyle; + + protected readonly MonoDevelop.Components.XwtPopup popup = new MonoDevelop.Components.XwtPopup (Xwt.PopupWindow.PopupType.Tooltip); + protected ITrackingSpan applicableToSpan; + protected bool isDismissed; + + public BaseWpfToolTipPresenter( + IViewElementFactoryService viewElementFactoryService, + IObscuringTipManager obscuringTipManager, + ITextView textView, + ToolTipParameters parameters, + ToolTipPresenterStyle presenterStyle) + { + this.viewElementFactoryService = viewElementFactoryService + ?? throw new ArgumentNullException(nameof(viewElementFactoryService)); + this.obscuringTipManager = obscuringTipManager + ?? throw new ArgumentNullException(nameof(obscuringTipManager)); + this.textView = textView ?? throw new ArgumentNullException(nameof(textView)); + this.parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); + this.presenterStyle = presenterStyle ?? throw new ArgumentNullException(nameof(presenterStyle)); + } + + #region IObscuringTip + + public double Opacity => this.popup.Content.Opacity; + + bool IObscuringTip.Dismiss() + { + bool isDismissed = this.isDismissed; + this.Dismiss(); + + return isDismissed; + } + + public void SetOpacity(double opacity) + { + var child = this.popup.Content; + if (child != null) + { + child.Opacity = opacity; + } + } + + #endregion + + #region IToolTipPresenter + + public event EventHandler Dismissed; + + public virtual void Dismiss() + { + if (this.popup != null) + { + this.popup.Closed -= this.OnPopupClosed;
+ this.popup.Visible = false; + //this.popup.Content = null; + this.isDismissed = true; + this.obscuringTipManager.RemoveTip(this.textView, this); + } + + this.Dismissed?.Invoke(this, EventArgs.Empty); + } + + public virtual void StartOrUpdate(ITrackingSpan applicableToSpan, IEnumerable<object> content) + { + Debug.Assert(!this.isDismissed); + + if (!this.popup.Visible) + { + this.Start(content); + } + else + { + this.Update(content); + } + } + + #endregion + + #region Impl + + private void Start(IEnumerable<object> content) + { + Debug.Assert(!this.popup.Visible && !this.isDismissed); + + if (this.PresentationSpan == null) + { + this.Dismiss(); + return; + } + + + this.Update(content); + + this.popup.Closed += this.OnPopupClosed; + + this.popup.Visible = true; + //todo this.popup.BringIntoView(); + this.obscuringTipManager.PushTip(this.textView, this); + } + + public void Update(IEnumerable<object> content) + { + // Translate intermediate objects to UIElements. + var contentViewElements = content.Select( + item => this.viewElementFactoryService.CreateViewElement<Xwt.Widget>( + this.textView, item)) + .Where(item => item != null);
+
+ //var control = new WpfToolTipControl (this.WpfTextView) {
+ // // Translate intermediate to UI.
+ // DataContext = new WpfToolTipViewModel (
+ // this.parameters,
+ // contentViewElements,
+ // this.presenterStyle)
+ //};
+ var vbox = new Xwt.VBox ();
+ foreach (var view in contentViewElements) {
+ vbox.PackStart (view);
+ }
+ this.popup.Content = vbox; + } + + protected ITrackingSpan PresentationSpan + { + get + { + if (this.applicableToSpan == null) + { + return null; + } + + SpanTrackingMode mode = this.applicableToSpan.TrackingMode; + NormalizedSnapshotSpanCollection viewSpans = this.textView.BufferGraph.MapUpToBuffer( + this.applicableToSpan.GetSpan(this.applicableToSpan.TextBuffer.CurrentSnapshot), + mode, + this.textView.TextBuffer); + return viewSpans.Count > 0 ? viewSpans[0].Snapshot.CreateTrackingSpan(viewSpans[0], mode) : null; + } + } + + protected IMdTextView WpfTextView => this.textView as IMdTextView; + + protected Point GetScreenPointFromTextXY(double x, double y) + { + var view = WpfTextView; + Debug.Assert(view != null); + + return view.VisualElement.GetScreenCoordinates(new Gdk.Point((int)(x - view.ViewportLeft), (int)(y - view.ViewportTop))).ToXwtPoint(); + } + + private void OnPopupClosed(object sender, EventArgs e) => this.Dismiss(); + + #endregion + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/DefaultToolTipPresenterStyle.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/DefaultToolTipPresenterStyle.cs new file mode 100644 index 0000000000..71fcd35f9e --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/DefaultToolTipPresenterStyle.cs @@ -0,0 +1,18 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System.ComponentModel.Composition;
+ using Xwt.Drawing;
+ using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Utilities;
+
+ [Export(typeof(ToolTipPresenterStyle))] + [Name("default")] + internal sealed class DefaultToolTipPresenterStyle : ToolTipPresenterStyle + { + public DefaultToolTipPresenterStyle() + {
+ this.BorderBrush = Colors.Black;
+ this.BackgroundBrush = Colors.LightGray; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/Legacy/ToolTipProvider.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/Legacy/ToolTipProvider.cs new file mode 100644 index 0000000000..5d8a94a48a --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/Legacy/ToolTipProvider.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This file contain implementations details that are subject to change without notice. +// Use at your own risk. +// +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Windows; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor;
+ using MonoDevelop.Components;
+
+ /// <summary> + /// An adornment provider that can create and display ToolTips taking an arbitrary object as content. + /// </summary> + internal class ToolTipProvider : IToolTipProvider + { + #region Private Members + private readonly IMdTextView _textView; + internal readonly ISpaceReservationManager _spaceReservationManager; + internal ISpaceReservationAgent _agent; + #endregion + + internal ToolTipProvider(IMdTextView textView) + { + _textView = textView; + _spaceReservationManager = _textView.GetSpaceReservationManager("ToolTip"); + _spaceReservationManager.AgentChanged += OnAgentChanged; + } + + void OnAgentChanged(object sender, SpaceReservationAgentChangedEventArgs e) + { + if (_agent == e.OldAgent) + _agent = null; + } + + #region IToolTipProvider Members + public void ClearToolTip() + { + if (_agent != null) + { + _spaceReservationManager.RemoveAgent(_agent); + //_agent should be null (cleared by OnAgentChanged) + } + } + + public void ShowToolTip(ITrackingSpan span, object toolTipContent, PopupStyles style) + { + if (span == null) + throw new ArgumentNullException("span"); + if (span.TextBuffer != _textView.TextBuffer) + throw new ArgumentException("Invalid span"); + if (toolTipContent == null) + throw new ArgumentNullException("toolTipContent"); + + var element = toolTipContent as Control; + if (element == null) + { + string toolTipContentAsString = toolTipContent as string; + if (toolTipContentAsString != null) + { + element = BuildTooltipUIElement(toolTipContentAsString); + } + else + { + throw new ArgumentException("Invalid contnet", nameof(toolTipContent)); + } + } + + this.ClearToolTip(); + _agent = _spaceReservationManager.CreatePopupAgent(span, style, element); + _spaceReservationManager.AddAgent(_agent); + } + + public void ShowToolTip(ITrackingSpan span, object toolTipContent) + { + this.ShowToolTip(span, toolTipContent, PopupStyles.None); + } + #endregion + + internal static Control BuildTooltipUIElement(string toolTipText) + { + // Make a pretty ToolTip-looking thing, using a border and a TextBlock. + //TextBlock txt = new TextBlock(); + //txt.Text = toolTipText; + //txt.Background = SystemColors.InfoBrush; + //txt.Foreground = SystemColors.InfoTextBrush; + //txt.Padding = new Thickness(1.0); + + //Border border = new Border(); + //border.BorderBrush = SystemColors.WindowFrameBrush; + //border.BorderThickness = new Thickness(1.0); + //border.Child = txt; + //return border; + return new XwtControl(new Xwt.Label(toolTipText)); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/Legacy/ToolTipProviderFactory.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/Legacy/ToolTipProviderFactory.cs new file mode 100644 index 0000000000..231eb1e2d4 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/Legacy/ToolTipProviderFactory.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This file contain implementations details that are subject to change without notice. +// Use at your own risk. +// +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Utilities; + using System.ComponentModel.Composition; + + [Export(typeof(IToolTipProviderFactory))] + internal sealed class ToolTipProviderFactory : IToolTipProviderFactory + { + //Specify the view layer definitions for the view. + [Export] + [Name("ToolTip")] + [Order()] + internal SpaceReservationManagerDefinition tooltipManager; + + #region IToolTipProviderFactory Members + public IToolTipProvider GetToolTipProvider(ITextView textView) + { + var wpfTextView = textView as IMdTextView; + if (wpfTextView == null) + throw new ArgumentException("Invalid TextView"); + + return CreateToolTipProviderInternal(wpfTextView); + } + #endregion + + internal static ToolTipProvider CreateToolTipProviderInternal(IMdTextView view) + { + ToolTipProvider toolTipAdornmentProvider = view.Properties.GetOrCreateSingletonProperty<ToolTipProvider>(delegate + { + return new ToolTipProvider(view); + }); + + return toolTipAdornmentProvider; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/MouseTrackingWpfToolTipPresenter.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/MouseTrackingWpfToolTipPresenter.cs new file mode 100644 index 0000000000..ff69fdf9ba --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/MouseTrackingWpfToolTipPresenter.cs @@ -0,0 +1,327 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Formatting; + using UIElement = Xwt.Widget; + using MouseEventArgs = Xwt.MouseMovedEventArgs;
+ using Xwt;
+ using Rect = Xwt.Rectangle;
+ using System.Windows.Input;
+
+ internal sealed class MouseTrackingWpfToolTipPresenter : BaseWpfToolTipPresenter + { + // The tooltip can cause flickering issues if shown on the pixel row immediately below text. This + // offset is used to move all tooltips down in order to eliminate the flicker. + private const int ToolTipVerticalOffset = 1; + private UIElement mouseContainer; + + public MouseTrackingWpfToolTipPresenter( + IViewElementFactoryService viewElementFactoryService, + IObscuringTipManager obscuringTipManager, + ITextView textView, + ToolTipParameters parameters, + ToolTipPresenterStyle presenterStyle) + : base(viewElementFactoryService, obscuringTipManager, textView, parameters, presenterStyle) + { + } + + public override void Dismiss() + { + if (this.mouseContainer != null) + { + this.mouseContainer.MouseExited -= this.OnMouseLeaveContainer; + this.mouseContainer.MouseMoved -= this.OnMouseMoveContainer; + this.mouseContainer = null; + } + + base.Dismiss(); + } + + public override void StartOrUpdate(ITrackingSpan applicableToSpan, IEnumerable<object> content) + { + Debug.Assert(this.parameters.TrackMouse, + "This tooltip presenter is only valid when TrackMouse is true."); + + this.applicableToSpan = applicableToSpan; + + if (DismissIfMouseOutsideOfVisualSpan()) + { + this.Dismiss(); + return; + } + + if (!this.popup.Visible) + { + this.SubscribeToContainerUnderMouse(); + + Rect? presentationSpanRect = this.GetViewSpanRect(this.PresentationSpan); + if (!presentationSpanRect.HasValue) + { + this.Dismiss(); + return; + }
+
+ this.popup.Location = Xwt.Desktop.MouseLocation; + } + + base.StartOrUpdate(applicableToSpan, content); + } + + private void SubscribeToContainerUnderMouse() + {
+ if (!this.isDismissed
+ && (this.mouseContainer == null)
+ && (((IMdTextView)textView).VisualElement.IsMouseOver () || (popup.IsMouseOver () && popup.Content != null))) { + mouseContainer = popup.IsMouseOver () ? popup.Content : Xwt.Toolkit.CurrentEngine.WrapWidget (((IMdTextView)textView).VisualElement);
+ mouseContainer.MouseExited += this.OnMouseLeaveContainer;
+ mouseContainer.MouseMoved += this.OnMouseMoveContainer;
+ } + } + + private void UnsubscribeFromMouseContainer() + { + if (this.mouseContainer != null) + { + this.mouseContainer.MouseExited -= this.OnMouseLeaveContainer; + this.mouseContainer.MouseMoved -= this.OnMouseMoveContainer; + this.mouseContainer = null; + } + } + + private void OnMouseLeaveContainer(object sender, EventArgs e) + { + Debug.Assert(sender == this.mouseContainer); + + UnsubscribeFromMouseContainer(); + this.SubscribeToContainerUnderMouse(); + + if (this.mouseContainer == null) + { + this.Dismiss(); + } + else + { + DismissIfMouseOutsideOfVisualSpan(); + } + } + + private void OnMouseMoveContainer(object sender, EventArgs e) + => this.DismissIfMouseOutsideOfVisualSpan(); + + private bool DismissIfMouseOutsideOfVisualSpan() + { + var wpfTextView = this.WpfTextView; + + if (wpfTextView == null || this.ShouldClearToolTipOnMouseMove()) + { + this.Dismiss(); + return true; + } + + return false; + } + + private Point GetMousePosRelativeToView() + { + var view = WpfTextView; + if (view == null) + { + throw new InvalidOperationException("Can't determine the relative position of the mouse without a WpfTextView."); + } + + Point mousePoint; + //if (e != null) + //{ + // mousePoint = e.GetPosition(view.VisualElement); + //} + //else + //{ + mousePoint = Mouse.GetPosition(view.VisualElement); + //} + + mousePoint.X += view.ViewportLeft; + mousePoint.Y += view.ViewportTop; + + return mousePoint; + } + + //private CustomPopupPlacement[] PlacePopup(Size popupSize, Size targetSize, Point offset) + //{ + // Debug.Assert(!this.isDismissed); + + // if (WpfTextView.VisualElement == null || PresentationSource.FromVisual(WpfTextView.VisualElement) == null) + // { + // return new CustomPopupPlacement[] { }; + // } + + // double zoom = this.WpfTextView.ZoomLevel / 100.0; + + // // We need to provide a placement point relative to the top left corner of the popup placement rectangle. + // // X would be the distance from mouse point to the left side of the placement rectangle so that the popup + // // is positioned horizontally at mouse position. + // Point mouseRelativeToView = GetMousePosRelativeToView(); + // Point popupTargetOrigin = new Point(mouseRelativeToView.X - this.WpfTextView.ViewportLeft - this.WpfTextView.VisualElement.PointFromScreen(this.popup.PlacementRectangle.BottomLeft).X, 0); + // Matrix transformToDevice = PresentationSource.FromVisual(WpfTextView.VisualElement).CompositionTarget.TransformToDevice; + // popupTargetOrigin = transformToDevice.Transform(popupTargetOrigin); + // popupTargetOrigin.X *= zoom; + + // // Y would be the height of the placement rectangle - so the popup is right below it + // popupTargetOrigin.Y = targetSize.Height; + + // return new CustomPopupPlacement[] { new CustomPopupPlacement(popupTargetOrigin, PopupPrimaryAxis.Horizontal) }; + //} + + private bool ShouldClearToolTipOnMouseMove() + { + if (this.popup != null) + { + if (this.popup.IsMouseOver() || this.parameters.KeepOpen) + { + return false; + } + else if (this.WpfTextView != null && !this.WpfTextView.VisualElement.IsMouseOver()) + { + // The mouse is not over any interactive content and not over the text view either + return true; + } + } + + return ShouldClearToolTipOnMouseMove(GetMousePosRelativeToView()); + } + + private bool ShouldClearToolTipOnMouseMove(Point pointRelativeToView) + { + var view = WpfTextView; + if (view == null || view.TextViewLines == null) + { + return true; + } + + // If the point isn't over a valid line, dismiss. + ITextViewLine line = view.TextViewLines.GetTextViewLineContainingYCoordinate(pointRelativeToView.Y); + if (line == null) + { + return true; + } + + if (this.PresentationSpan == null) + + { + return false; + } + + SnapshotSpan span = this.PresentationSpan.GetSpan(view.TextSnapshot); + SnapshotPoint? position = line.GetBufferPositionFromXCoordinate(pointRelativeToView.X, true); + + //Special case handling for the last line on the buffer: we want to treat the mouse hovering near the end + //of the line as a "hit" + if ((!position.HasValue) && (line.End == line.Snapshot.Length)) + { + if ((pointRelativeToView.X >= line.TextLeft) && (pointRelativeToView.X < line.TextRight + line.EndOfLineWidth)) + { + position = line.End; + } + } + + // If the position is valid and within the start/end of the visual span, inclusive, we + // don't need to dismiss. + if (position.HasValue && (span.Contains(position.Value) || span.End == position.Value)) + { + return false; + } + + // No match, dismiss the tooltip. + return true; + } + + private Rect? GetViewSpanRect(ITrackingSpan viewSpan) + { + var view = WpfTextView; + if (view == null || view.TextViewLines == null || view.IsClosed) + { + return null; + } + + SnapshotSpan visualSpan = viewSpan.GetSpan(view.TextSnapshot); + + Rect? spanRectangle = null; + if (visualSpan.Length > 0) + { + double left = double.MaxValue; + double top = double.MaxValue; + double right = double.MinValue; + double bottom = double.MinValue; + + var bounds = view.TextViewLines.GetNormalizedTextBounds(visualSpan); + foreach (var bound in bounds) + { + left = Math.Min(left, bound.Left); + top = Math.Min(top, bound.TextTop); + right = Math.Max(right, bound.Right); + bottom = Math.Max(bottom, bound.TextBottom + ToolTipVerticalOffset); + } + + // If the start of the span lies within the view, use that instead of the left-bound of the span as a whole. + // This will cause popups to be left-aligned with the start of their span, if at all possible. + var startLine = view.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start); + if (startLine != null) + { + var startPointBounds = startLine.GetExtendedCharacterBounds(visualSpan.Start); + if ((startPointBounds.Left < right) && + (startPointBounds.Left >= view.ViewportLeft) && + (startPointBounds.Left < view.ViewportRight)) + { + left = startPointBounds.Left; + } + } + + //Special case handling for when the end of the span is at the start of a line. + ITextViewLine line = view.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.End); + if ((line != null) && (line.Start == visualSpan.End)) + { + bottom = Math.Max(bottom, line.TextBottom + ToolTipVerticalOffset); + } + + if (left < right) + { + spanRectangle = new Rect(left, top, right - left, bottom - top); + } + } + else + { + // visualSpan is zero length so the default MarkerGeometry will be null. Create a custom marker geometry based on + // the location. + ITextViewLine line = view.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start); + if (line != null) + { + TextBounds bounds = line.GetCharacterBounds(visualSpan.Start); + spanRectangle = new Rect(bounds.Left, bounds.TextTop, 0.0, bounds.TextHeight + ToolTipVerticalOffset); + } + } + + if (spanRectangle.HasValue && !spanRectangle.Value.IsEmpty) + { + //Get the portion of the span geometry that is inside the view. + Rect viewRect = new Rect(view.ViewportLeft, + view.ViewportTop, + view.ViewportWidth, + view.ViewportHeight); + + Rect spanRect = spanRectangle.Value; + spanRect.Intersect(viewRect); + + Rect spanRectInScreenCoordinates = + new Rect(this.GetScreenPointFromTextXY(spanRect.Left, spanRect.Top), + this.GetScreenPointFromTextXY(spanRect.Right, spanRect.Bottom)); + + return spanRectInScreenCoordinates; + } + + return null; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/SpanTrackingWpfToolTipPresenter.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/SpanTrackingWpfToolTipPresenter.cs new file mode 100644 index 0000000000..b0fb9d3911 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/SpanTrackingWpfToolTipPresenter.cs @@ -0,0 +1,115 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.Generic; + using System.Windows; + using System.Windows.Input; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor;
+ using MonoDevelop.Components;
+
+ internal sealed class SpanTrackingWpfToolTipPresenter : BaseWpfToolTipPresenter + { + public SpanTrackingWpfToolTipPresenter( + IViewElementFactoryService viewElementFactoryService, + IObscuringTipManager obscuringTipManager, + ITextView textView, + ToolTipParameters parameters, + ToolTipPresenterStyle presenterStyle) + : base(viewElementFactoryService, obscuringTipManager, textView, parameters, presenterStyle) + { + } + + public override void Dismiss() + { + if (!this.isDismissed) + { + this.popup.BoundsChanged -= this.OnLayoutUpdated; + this.textView.LayoutChanged -= this.OnTextViewLayoutChanged; + this.WpfTextView.Caret.PositionChanged -= this.OnCaretPositionChanged; + //this.WpfTextView.ZoomLevelChanged -= this.OnZoomLevelChanged; + this.WpfTextView.VisualElement.FocusOutEvent -= this.OnLostKeyboardFocus; + } + + base.Dismiss(); + } + + public override void StartOrUpdate(ITrackingSpan applicableToSpan, IEnumerable<object> content) + { + this.applicableToSpan = applicableToSpan;
+ this.popup.TransientFor = Xwt.Toolkit.CurrentEngine.WrapWidget (this.WpfTextView.VisualElement).ParentWindow; + //this.popup.HorizontalAlignment = HorizontalAlignment.Left; + //this.popup.VerticalAlignment = VerticalAlignment.Top; + + if (!this.popup.Visible) + { + this.popup.BoundsChanged += this.OnLayoutUpdated; + this.textView.LayoutChanged += this.OnTextViewLayoutChanged; + this.WpfTextView.Caret.PositionChanged += this.OnCaretPositionChanged; + //this.WpfTextView.ZoomLevelChanged += this.OnZoomLevelChanged; + this.WpfTextView.VisualElement.FocusOutEvent += this.OnLostKeyboardFocus; + } + + base.StartOrUpdate(applicableToSpan, content); + } + + private void OnLostKeyboardFocus(object sender, Gtk.FocusOutEventArgs e) => this.Dismiss(); + + private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e) => this.Dismiss(); + + // Refreshes and async addition of controls like those used by Roslyn cause the tooltip + // to change size. Recompute position when this happens. + private void OnLayoutUpdated(object sender, EventArgs e) => this.UpdatePopupPosition(); + + //private void OnZoomLevelChanged(object sender, ZoomLevelChangedEventArgs e) => this.UpdatePopupPosition(); + + private void OnTextViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) + { + if (e.NewViewState.EditSnapshot == e.OldViewState.EditSnapshot || this.parameters.IgnoreBufferChange) + { + this.UpdatePopupPosition(); + } + else + { + this.Dismiss(); + } + } + + private void UpdatePopupPosition() + { + var startPoint = this.PresentationSpan.GetStartPoint(this.textView.TextSnapshot); + var endPoint = this.PresentationSpan.GetEndPoint(this.textView.TextSnapshot); + + if (!this.textView.IsClosed + && this.textView.TextViewLines.FormattedSpan.Contains(endPoint)) + { + var startLine = this.WpfTextView.GetTextViewLineContainingBufferPosition(startPoint); + if (startLine != null) + { + double verticalOffset; + + var endLine = this.WpfTextView.GetTextViewLineContainingBufferPosition(endPoint); + if (endLine != null) + { + verticalOffset = (endLine.Bottom - this.textView.ViewportTop); + } + else + { + verticalOffset = (startLine.Bottom - this.textView.ViewportTop); + }
+
+ // WPF seems to have a bug where if the offsets are the same, the control doesn't move, even
+ // if its relative target did. Force the popup to move by toggling the offsets.
+ this.popup.Location = ((IMdTextView)textView).VisualElement.GetScreenCoordinates (new Gdk.Point ((int)(startLine.GetCharacterBounds (startPoint).Leading - this.textView.ViewportLeft), (int)verticalOffset)).ToXwtPoint (); + + return; + }
+ }
+ else {
+ Console.WriteLine ();
+ } + + this.Dismiss(); + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ToolTipStyleFactory.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ToolTipStyleFactory.cs new file mode 100644 index 0000000000..0a35b76c54 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ToolTipStyleFactory.cs @@ -0,0 +1,36 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.Composition; + using System.Linq; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Utilities; + + [Export] + internal sealed class ToolTipStyleFactory + { + private ToolTipPresenterStyle style; + + [ImportMany] + private List<Lazy<ToolTipPresenterStyle, IOrderable>> unorderedPresenterStyles; + + public ToolTipPresenterStyle Style + { + get + { + if (this.style == null) + { + this.style = Orderer.Order(this.unorderedPresenterStyles).FirstOrDefault()?.Value; + + if (this.style == null) + { + throw new ArgumentNullException($"No exports of type {nameof(ToolTipPresenterStyle)}"); + } + } + + return this.style; + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfClassifiedTextElementViewElementFactory.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfClassifiedTextElementViewElementFactory.cs new file mode 100644 index 0000000000..70964cc113 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfClassifiedTextElementViewElementFactory.cs @@ -0,0 +1,74 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.ComponentModel.Composition; + using System.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Classification; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Utilities; + using UIElement = Xwt.Widget; + + [Export(typeof(IViewElementFactory))] + [Name("default ClassifiedTextElement to UIElement")] + [TypeConversion(from: typeof(ClassifiedTextElement), to: typeof(UIElement))] + [Order] + internal sealed class WpfClassifiedTextElementViewElementFactory : IViewElementFactory + { + private readonly IClassificationTypeRegistryService classificationTypeRegistryService; + private readonly ToolTipStyleFactory styleFactory; + + [ImportingConstructor] + public WpfClassifiedTextElementViewElementFactory( + IClassificationTypeRegistryService classificationTypeRegistryService, + ToolTipStyleFactory styleFactory) + { + this.classificationTypeRegistryService = classificationTypeRegistryService + ?? throw new ArgumentNullException(nameof(classificationTypeRegistryService)); + this.styleFactory = styleFactory + ?? throw new ArgumentNullException(nameof(styleFactory)); + } + + public TView CreateViewElement<TView>(ITextView textView, object model) where TView : class + { + // Should never happen if the service's code is correct, but it's good to be paranoid. + if (typeof(TView) != typeof(UIElement) || !(model is ClassifiedTextElement element)) + { + throw new ArgumentException($"Invalid type conversion. Unsupported {nameof(model)} or {nameof(TView)} type"); + } + + + var textBlock = new Xwt.Label(); + StringBuilder markup = new StringBuilder ();
+ foreach (var run in element.Runs) + { + var textRunClassification = this.classificationTypeRegistryService.GetClassificationType(run.ClassificationTypeName); + + //var wpfRun = new Run() + //{ + // // Set colors from the specific classification type's text run properties. + // Background = viewTextRunProperties.BackgroundBrush, + // BaselineAlignment = viewTextRunProperties.BaselineAlignment, + // Foreground = viewTextRunProperties.ForegroundBrush, + // Text = run.Text, + // TextDecorations = viewTextRunProperties.TextDecorations, + // TextEffects = viewTextRunProperties.TextEffects, + + // // Set font properties from Editor Tooltips category so we match other tooltips. + // FontSize = tooltipTextRunProperties.FontRenderingEmSize, + // FontFamily = tooltipTextRunProperties.Typeface.FontFamily, + // FontStretch = tooltipTextRunProperties.Typeface.Stretch, + // FontStyle = tooltipTextRunProperties.Typeface.Style, + // FontWeight = tooltipTextRunProperties.Typeface.Weight + //};
+ var classy = textRunClassification.Classification;
+ var color = classy.GetHashCode ().ToString ("X");
+ color = color.Substring (2);
+ markup.AppendLine ($"<span foreground=\"#{color}\">{run.Text}</span>");
+ } + textBlock.Markup = markup.ToString (); +
+ return textBlock as TView; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfContainerElementViewElementFactory.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfContainerElementViewElementFactory.cs new file mode 100644 index 0000000000..759d3ac921 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfContainerElementViewElementFactory.cs @@ -0,0 +1,58 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.ComponentModel.Composition; + using System.Text;
+ using UIElement = Xwt.Widget; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Utilities;
+ using Xwt;
+
+ [Export(typeof(IViewElementFactory))] + [Name("default ContainerElement to UIElement")] + [TypeConversion(from: typeof(ContainerElement), to: typeof(UIElement))] + [Order] + internal sealed class WpfContainerElementViewElementFactory : IViewElementFactory + { + [Import] + internal IViewElementFactoryService viewElementFactoryService; + + public TView CreateViewElement<TView>(ITextView textView, object model) where TView : class + { + // Should never happen if the service's code is correct, but it's good to be paranoid. + if (typeof(TView) != typeof(UIElement) || !(model is ContainerElement container)) + { + throw new ArgumentException($"Invalid type conversion. Unsupported {nameof(model)} or {nameof(TView)} type"); + } + + VBox containerControl; + + if (container.Style == ContainerElementStyle.Stacked) + { + containerControl = new VBox(); + } + else + { + containerControl = new VBox ();//TODO + }
+
+ containerControl.HorizontalPlacement = WidgetPlacement.Start;
+ containerControl.VerticalPlacement = WidgetPlacement.Start; + + var automationNameBuffer = new StringBuilder(); + + foreach (var element in container.Elements) + { + var convertedElement = this.viewElementFactoryService.CreateViewElement<UIElement>(textView, element); + + if (convertedElement != null) + { + containerControl.PackStart(convertedElement); + } + } + + return containerControl as TView; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfImageElementViewElementFactory.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfImageElementViewElementFactory.cs new file mode 100644 index 0000000000..56b4876a43 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/ViewElementFactories/WpfImageElementViewElementFactory.cs @@ -0,0 +1,39 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System; + using System.ComponentModel.Composition; + using System.Windows; + using Microsoft.VisualStudio.Core.Imaging; + using Microsoft.VisualStudio.Imaging; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Utilities; + using UIElement = Xwt.Widget;
+
+ [Export(typeof(IViewElementFactory))] + [Name("default ImageElement to UIElement")] + [TypeConversion(from: typeof(ImageElement), to: typeof(UIElement))] + [Order] + internal sealed class WpfImageElementViewElementFactory : IViewElementFactory + { + public TView CreateViewElement<TView>(ITextView textView, object model) where TView : class + { + // Should never happen if the service's code is correct, but it's good to be paranoid. + if (typeof(TView) != typeof(UIElement) || !(model is ImageElement element)) + { + throw new ArgumentException($"Invalid type conversion. Unsupported {nameof(model)} or {nameof(TView)} type"); + }
+
+ var imageElement = new Xwt.ImageView ();
+
+ imageElement.Image = MonoDevelop.Ide.ImageService.GetIcon ("md-monodevelop");
+
+ // Add a slight margin so we don't contact any ClassifiedTextElements directly following us.
+ imageElement.Margin = new Xwt.WidgetSpacing (4, 4, 4, 4);
+ imageElement.HorizontalPlacement = Xwt.WidgetPlacement.Start;
+ imageElement.HorizontalPlacement = Xwt.WidgetPlacement.Start;
+
+ return imageElement as TView; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/WpfToolTipPresenterProvider.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/WpfToolTipPresenterProvider.cs new file mode 100644 index 0000000000..1e8bdf160b --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfToolTipAdornment/WpfToolTipPresenterProvider.cs @@ -0,0 +1,45 @@ +namespace Microsoft.VisualStudio.Text.AdornmentLibrary.ToolTip.Implementation +{ + using System.ComponentModel.Composition; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(IToolTipPresenterFactory))] + [Name("default")] + [ContentType("text")] + [Order] + internal sealed class WpfToolTipPresenterProvider : IToolTipPresenterFactory + { + [Import] + internal IViewElementFactoryService viewElementFactoryService; + + [Import] + internal IObscuringTipManager obscuringTipManager; + + [Import] + internal ToolTipStyleFactory styleFactory; + + public IToolTipPresenter Create(ITextView textView, ToolTipParameters parameters) + { + if (parameters.TrackMouse) + { + return new MouseTrackingWpfToolTipPresenter( + this.viewElementFactoryService, + this.obscuringTipManager, + textView, + parameters, + this.styleFactory.Style); + } + else + { + return new SpanTrackingWpfToolTipPresenter( + this.viewElementFactoryService, + this.obscuringTipManager, + textView, + parameters, + this.styleFactory.Style); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/PopupAgent.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/PopupAgent.cs new file mode 100644 index 0000000000..e6ad239284 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/PopupAgent.cs @@ -0,0 +1,794 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This file contain implementations details that are subject to change without notice. +// Use at your own risk. +// +namespace Microsoft.VisualStudio.Text.Editor.Implementation +{ + using System; + using System.Windows; + using System.Windows.Media; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Formatting; + using Microsoft.VisualStudio.Text.Outlining; + using Microsoft.VisualStudio.Text.Utilities; + using System.Windows.Input; + using System.Collections.Generic;
+ using Xwt;
+ using Mono.TextEditor;
+ using MonoDevelop.Components; + using Rect = Xwt.Rectangle;
+
+ class PopupAgent : ISpaceReservationAgent + { + internal readonly Mono.TextEditor.MonoTextEditor _textView; + internal readonly ISpaceReservationManager _manager; + internal ITrackingSpan _visualSpan; + internal PopupStyles _style; + internal Widget _mouseContainer; + internal readonly PopupOrWindowContainer _popup; + private const int MaxPopupCacheSize = 10; + private const double BelowTheLineBufferHint = 3.0; + + public PopupAgent(Mono.TextEditor.MonoTextEditor textView, ISpaceReservationManager manager, ITrackingSpan visualSpan, PopupStyles style, Widget content) + { + if (textView == null) + throw new ArgumentNullException("textView"); + if (manager == null) + throw new ArgumentNullException("manager"); + if (visualSpan == null) + throw new ArgumentNullException("visualSpan"); + if (((int)style & ~(0xff)) != 0) //Union of all the legal style bits. + throw new ArgumentOutOfRangeException("style"); + if (content == null) + throw new ArgumentNullException("content"); + if ((style & PopupStyles.DismissOnMouseLeaveText) != 0 && (style & PopupStyles.DismissOnMouseLeaveTextOrContent) != 0) + throw new ArgumentException("Can't specify both PopupStyles.DismissOnMouseLeaveText and PopupStyles.DismissOnMouseLeaveTextOrContent", "style"); + + _textView = textView; + _manager = manager; + _visualSpan = visualSpan; + _style = style; + + var popupCache = textView.Properties.GetOrCreateSingletonProperty<Dictionary<WeakReferenceForDictionaryKey, PopupOrWindowContainer>>( + () => new Dictionary<WeakReferenceForDictionaryKey, PopupOrWindowContainer>(MaxPopupCacheSize)); + + if (!popupCache.TryGetValue(new WeakReferenceForDictionaryKey(content), out _popup)) + { + _popup = PopupOrWindowContainer.Create(content, _textView.VisualElement); + + if (popupCache.Count == MaxPopupCacheSize) + { + popupCache.Clear(); + } + popupCache.Add(new WeakReferenceForDictionaryKey(content), _popup); + } + } + + public void SetVisualSpan(ITrackingSpan visualSpan) + { + _visualSpan = visualSpan; + } + + #region ISpaceReservationAgent Members + public Geometry PositionAndDisplay(Geometry reservedSpace) + { + //This method should only be called from the popup manager (which should never call it when the containing + //view is hidden or in the middle of a layout). + + //This method does not support virtual whitespace positioning. An attempt to support it by using caret position was introduced, but + //regressed the behavior that popup should stay in place as the caret moves. If in the future we need to support virtual whitespace, + //consider using virtual span instead. + + //Update the visual span to the current snapshot. + SnapshotSpan visualSpan = _visualSpan.GetSpan(_textView.TextSnapshot); + + // If the style indicates that we should dismiss when the mouse leaves the span of the text, then we should make an + // initial check to make sure the mouse starts-off in the span. If not, we should fail to position. + if ((_style & (PopupStyles.DismissOnMouseLeaveText | PopupStyles.DismissOnMouseLeaveTextOrContent)) != 0) + { + _textView.VisualElement.GetPointer(out int x, out int y); + if (this.ShouldClearToolTipOnMouseMove(new Point(x, y))) + return null; + } + + Rect? spanRectangle = null; + if (visualSpan.Length > 0) + { + double left = double.MaxValue; + double top = double.MaxValue; + double right = double.MinValue; + double bottom = double.MinValue; + + var bounds = _textView.TextViewLines.GetNormalizedTextBounds(visualSpan); + foreach (var bound in bounds) + { + left = Math.Min(left, bound.Left); + top = Math.Min(top, bound.TextTop); + right = Math.Max(right, bound.Right); + bottom = Math.Max(bottom, bound.TextBottom); + } + + // If the start of the span lies within the view, use that instead of the left-bound of the span as a whole. + // This will cause popups to be left-aligned with the start of their span, if at all possible. + var startLine = _textView.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start); + if (startLine != null) + { + var startPointBounds = startLine.GetExtendedCharacterBounds(visualSpan.Start); + if ((startPointBounds.Left < right) && (startPointBounds.Left >= _textView.ViewportLeft) && (startPointBounds.Left < _textView.ViewportRight)) + { + left = startPointBounds.Left; + } + } + + //Special case handling for when the end of the span is at the start of a line. + ITextViewLine line = _textView.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.End); + if ((line != null) && (line.Start == visualSpan.End)) + { + bottom = Math.Max(bottom, line.TextBottom); + } + + if (left < right) + { + spanRectangle = new Rect(left, top, right - left, bottom - top); + } + } + else + { + //visualSpan is zero length so the default MarkerGeometry will be null. Create a custom marker geometry based on the location. + ITextViewLine line = _textView.TextViewLines.GetTextViewLineContainingBufferPosition(visualSpan.Start); + if (line != null) + { + TextBounds bounds = line.GetCharacterBounds(visualSpan.Start); + spanRectangle = new Rect(bounds.Left, bounds.TextTop, 0.0, bounds.TextHeight); + } + } + + if (spanRectangle.HasValue) + { + //Get the portion of the span geometry that is inside the view. + Rect viewRect = new Rect(_textView.ViewportLeft, _textView.ViewportTop, _textView.ViewportWidth, _textView.ViewportHeight); + + Rect spanRect = spanRectangle.Value; + spanRect = spanRect.Intersect(viewRect); + + if (spanRect != default(Rect)) + { + // Determine two different rectangles for the span. One is the span in its raw form. The other is a "guess" at + // what the already-reserved space around the span will be. We have a very-prevalent space reservation agent (the + // current line agent) that reserves the current line plus a 3-pixel buffer below the line. We'll optimistically + // guess that this agent might be in-play and attempt to avoid it. + Rect spanRectWithBuffer = new Rect(spanRect.Left, spanRect.Top, spanRect.Right - spanRect.Left, spanRect.Bottom - spanRect.Top + BelowTheLineBufferHint); + + //Some of the text associated with the popup is visible, show the popup. + Rect spanRectInScreenCoordinates = new Rect(this.GetScreenPointFromTextXY(spanRect.Left, spanRect.Top), + this.GetScreenPointFromTextXY(spanRect.Right, spanRect.Bottom)); + Rect spanRectWithBufferInScreenCoordinates = new Rect(this.GetScreenPointFromTextXY(spanRectWithBuffer.Left, spanRectWithBuffer.Top), + this.GetScreenPointFromTextXY(spanRectWithBuffer.Right, spanRectWithBuffer.Bottom)); + Rect screenRect = Xwt.Desktop.GetScreenAtLocation(spanRectInScreenCoordinates.TopLeft).Bounds;//TODO: Check if we should use VisualBounds + + Size desiredSize = _popup.Size; + //The popup size specified in deivice pixels. Convert these to logical + //pixels for purposes of calculating the actual size of the popup. + //TODO desiredSize = new Size (desiredSize.Width / WpfHelper.DeviceScaleX, desiredSize.Height / WpfHelper.DeviceScaleY); + desiredSize = new Size(desiredSize.Width, desiredSize.Height); + + PopupStyles alternateStyle = _style ^ PopupStyles.PreferLeftOrTopPosition; + + Rect reservedRect = reservedSpace.Bounds; + Point topLeft = new Point(Math.Min(spanRectInScreenCoordinates.Left, reservedRect.Left), + Math.Min(spanRectInScreenCoordinates.Top, reservedRect.Top)); + Point bottomRight = new Point(Math.Max(spanRectInScreenCoordinates.Right, reservedRect.Right), + Math.Max(spanRectInScreenCoordinates.Bottom, reservedRect.Bottom)); + reservedRect = new Rect(topLeft, bottomRight); + + //There are 6 possible locations for the popup. The order of preference is determined by the presence of the + //'PositionClosest' PopupStyle. + // + // Without 'PositionClosest': + // 1 .. On the desired side of the span. + // 2 .. On the desired side of the span with a bit of buffer. + // 3 .. on the desired side of the reserved rectangle. + // 4 .. On the alternate side of the span. + // 5 .. On the alternate side of the span with a bit of buffer. + // 6 .. on the alternate side of the reserved rectangle. + // + // With 'PositionClosest': + // 1 .. On the desired side of the span. + // 2 .. On the desired side of the span with a bit of buffer. + // 3 .. On the alternate side of the span. + // 4 .. On the alternate side of the span with a bit of buffer. + // 5 .. on the desired side of the reserved rectangle. + // 6 .. on the alternate side of the reserved rectangle. + // + //Try each location till we get a winner. + // A location is a winner if it is disjoint from the original reserved rect and + // the edges of the screen. + + Tuple<PopupStyles, Rect>[] positionChoices; + if (reservedRect != default(Rect)) + { + if ((_style & PopupStyles.PositionClosest) == 0) + { + positionChoices = new Tuple<PopupStyles, Rect>[] + { + new Tuple<PopupStyles,Rect>(_style, spanRectInScreenCoordinates), + new Tuple<PopupStyles,Rect>(_style, spanRectWithBufferInScreenCoordinates), + new Tuple<PopupStyles,Rect>(_style, reservedRect), + new Tuple<PopupStyles,Rect>(alternateStyle, spanRectInScreenCoordinates), + new Tuple<PopupStyles,Rect>(alternateStyle, spanRectWithBufferInScreenCoordinates), + new Tuple<PopupStyles,Rect>(alternateStyle, reservedRect), + }; + } + else + { + positionChoices = new Tuple<PopupStyles, Rect>[] + { + new Tuple<PopupStyles,Rect>(_style, spanRectInScreenCoordinates), + new Tuple<PopupStyles,Rect>(_style, spanRectWithBufferInScreenCoordinates), + new Tuple<PopupStyles,Rect>(alternateStyle, spanRectInScreenCoordinates), + new Tuple<PopupStyles,Rect>(alternateStyle, spanRectWithBufferInScreenCoordinates), + new Tuple<PopupStyles,Rect>(_style, reservedRect), + new Tuple<PopupStyles,Rect>(alternateStyle, reservedRect), + }; + } + } + else + { + positionChoices = new Tuple<PopupStyles, Rect>[] + { + new Tuple<PopupStyles,Rect>(_style, spanRectInScreenCoordinates), + new Tuple<PopupStyles,Rect>(_style, spanRectWithBufferInScreenCoordinates), + new Tuple<PopupStyles,Rect>(alternateStyle, spanRectInScreenCoordinates), + new Tuple<PopupStyles,Rect>(alternateStyle, spanRectWithBufferInScreenCoordinates), + }; + } + + Rect location = Rect.Zero; + foreach (var choice in positionChoices) + { + Rect locationToTry = GetLocation(choice.Item1, desiredSize, spanRectInScreenCoordinates, choice.Item2, screenRect); + if (DisjointWithPadding(reservedSpace, locationToTry) && ContainsWithPadding(screenRect, locationToTry)) + { + location = locationToTry; + _style = choice.Item1; + break; + } + } + + // If we couldn't locate a place to live, tell the manager we want to go away. + if (location == Rect.Zero) + return null; + + if (!_popup.IsVisible) + this.RegisterForEvents(); + + _popup.DisplayAt(location.TopLeft); + + GeometryGroup requestedSpace = new GeometryGroup(); + requestedSpace.Children.Add(new RectangleGeometry(spanRectInScreenCoordinates)); + requestedSpace.Children.Add(new RectangleGeometry(location)); + + return requestedSpace; + } + } + + //The text associated with the popup visualSpan is not visible: tell the manager we want to go away. + return null; + } + + public bool IsMouseOver + { + get + { + Gdk.Display.Default.GetPointer(out int x, out int y); + return _popup.IsVisible ? _popup.Content.ScreenBounds.Contains(x, y) : false; + } + } + + public void Hide() + { + if (_popup.IsVisible) + { + _popup.Hide(); + + this.UnregisterForEvents(); + } + } + + public bool HasFocus + { + get + { + return _popup.IsVisible && _popup.IsKeyboardFocusWithin; + } + } + + public event EventHandler LostFocus; + public event EventHandler GotFocus; + + #endregion + + private void RegisterForEvents() + { + // For tooltips with style DismissOnMouseLeave + if ((_style & (PopupStyles.DismissOnMouseLeaveText | PopupStyles.DismissOnMouseLeaveTextOrContent)) != 0) + { + _textView.VisualElement.MotionNotifyEvent += this.OnMouseMove; + + //TODO: This used to be Mouse.DirectlyOver, is it ok just use popup.Content? + _mouseContainer = _popup.Content; + if (_mouseContainer != null) + { + _mouseContainer.MouseExited += this.OnMouseLeave; + } + } + + _textView.LostAggregateFocus += this.OnViewFocusLost; + _popup.Content.LostFocus += this.OnContentLostFocus; + _popup.Content.GotFocus += this.OnContentGotFocus; + + var content = _popup.Content as Widget; + if (content != null) + { + content.BoundsChanged += this.OnContentSizeChanged; + } + + // So we can dismiss the tooltip on window move / sizing. + var hostWindow = MonoDevelop.Ide.IdeApp.Workbench.RootWindow; + if (hostWindow != null) // for tests + { + hostWindow.ConfigureEvent += OnLocationChanged; + } + + // Register to be notified when outlining regions are collapsed. + if (this.OutliningManager != null) + { + this.OutliningManager.RegionsCollapsed += this.OnOutliningManager_RegionsCollapsed; + } + } + + private void UnregisterForEvents() + { + // For tooltips with style DismissOnMouseLeave + if ((_style & (PopupStyles.DismissOnMouseLeaveText | PopupStyles.DismissOnMouseLeaveTextOrContent)) != 0) + { + _textView.VisualElement.MotionNotifyEvent -= this.OnMouseMove; + if (_mouseContainer != null) + { + _mouseContainer.MouseExited -= this.OnMouseLeave; + } + } + + _textView.LostAggregateFocus -= this.OnViewFocusLost; + _popup.Content.LostFocus -= this.OnContentLostFocus; + _popup.Content.GotFocus -= this.OnContentGotFocus; + + var content = _popup.Content as Xwt.Widget; + if (content != null) + { + content.BoundsChanged -= this.OnContentSizeChanged; + } + + var hostWindow = MonoDevelop.Ide.IdeApp.Workbench.RootWindow; + if (hostWindow != null) // for tests + { + hostWindow.ConfigureEvent -= OnLocationChanged; + } + + if (this.OutliningManager != null) + { + this.OutliningManager.RegionsCollapsed -= this.OnOutliningManager_RegionsCollapsed; + } + } + + private IOutliningManager OutliningManager + { + get + { + if ((_textView.TextViewModel != null) && (_textView.ComponentContext.OutliningManagerService != null)) + { + return _textView.ComponentContext.OutliningManagerService.GetOutliningManager(_textView); + } + + return null; + } + } + + #region Event handlers + void OnMouseMove(object sender, Gtk.MotionNotifyEventArgs e) + { + if (_popup.IsVisible) + { + Point mousePt = new Point(e.Event.X, e.Event.Y); //TODO: Check if we have to move to screen cordinate system + + if (this.ShouldClearToolTipOnMouseMove(mousePt)) + { + _manager.RemoveAgent(this); + } + } + } + + void OnMouseLeave(object sender, EventArgs e) + { + //TODO: This method, well whole MouseLeave logic is much simplefied in our case + //We just support on Popup mouse leave while WPF supports also DismissOnMouseLeaveText + if (_mouseContainer != null) + { + _mouseContainer.MouseExited -= this.OnMouseLeave; + _mouseContainer = null; + } + + Widget newContainer = null;//e.MouseDevice.Target; + bool shouldRemoveAgent = false; + + // First, check to see if the mouse left the view entirely. + if ((newContainer == null) || !_textView.IsMouseOverViewOrAdornments) + { + //Mouse moved over a non-WPF element or a WPF element not associated with the view. + //Remove the agent. + shouldRemoveAgent = true; + } + else if ((_style & PopupStyles.DismissOnMouseLeaveText) != 0) + { + // The mouse left the element over which it was originally positioned. This may or + // may not mean that the mouse left the span of text to which the popup is bound. + _textView.VisualElement.GetPointer (out int x, out int y); + if (this.ShouldClearToolTipOnMouseMove(new Point(x,y))) + { + shouldRemoveAgent = true; + } + } + + // If we determined that we should remove the popup agent for any reason, do so. + // Otherwise, re-subscribe to MouseLeave events on the new mouse-over target. + if (shouldRemoveAgent) + { + _manager.RemoveAgent(this); + } + else + { + _mouseContainer = newContainer; + _mouseContainer.MouseExited += this.OnMouseLeave; + } + } + + void OnContentLostFocus(object sender, EventArgs e) + { + EventHandler lostFocus = this.LostFocus; + if (lostFocus != null) + lostFocus(sender, e); + } + + void OnContentGotFocus(object sender, EventArgs e) + { + EventHandler gotFocus = this.GotFocus; + if (gotFocus != null) + gotFocus(sender, e); + } + + void OnContentSizeChanged(object sender, EventArgs e) + { + _textView.QueueSpaceReservationStackRefresh(); + } + + /// <summary> + /// Handle focus change events for TextView and tooltip popup window. If they both lose + /// focus, then dismiss the tooltip. + /// </summary> + void OnViewFocusLost(object sender, EventArgs e) + { + if (_popup.IsVisible) + { + _manager.RemoveAgent(this); + } + } + + /// <summary> + /// Handle the LocationChanged event for the window hosting the view and dismiss the + /// tooltip. + /// </summary> + void OnLocationChanged(object sender, EventArgs e) + { + _manager.RemoveAgent(this); + } + + void OnOutliningManager_RegionsCollapsed(object sender, RegionsCollapsedEventArgs e) + { + if (_popup.IsVisible) + { + foreach (ICollapsed collapsed in e.CollapsedRegions) + { + if (_visualSpan.TextBuffer != collapsed.Extent.TextBuffer) + continue; + + ITextSnapshot snapshot = _visualSpan.TextBuffer.CurrentSnapshot; + SnapshotSpan visualSnapSpan = _visualSpan.GetSpan(snapshot); + SnapshotSpan collapsedSnapSpan = collapsed.Extent.GetSpan(snapshot); + if (visualSnapSpan.IntersectsWith(collapsedSnapSpan)) + { + _manager.RemoveAgent(this); + } + } + } + } + + #endregion + + internal bool ShouldClearToolTipOnMouseMove(Point mousePt) + { + _textView.VisualElement.GetPointer(out int x, out int y); + var topLeft = _textView.VisualElement.GetScreenCoordinates(new Gdk.Point(0, 0)); + var bottomRight = _textView.VisualElement.GetScreenCoordinates(new Gdk.Point(_textView.VisualElement.WidthRequest, _textView.VisualElement.HeightRequest)); + //TODO: Test if this is correct + if (!new Rect(topLeft.ToXwtPoint(), bottomRight.ToXwtPoint()).Contains(x, y)) + return true; + + return this.InnerShouldClearToolTipOnMouseMove(mousePt); + } + + internal bool InnerShouldClearToolTipOnMouseMove(Point mousePt) + { + if ((mousePt.X >= 0.0) && (mousePt.X < _textView.ViewportWidth) && + (mousePt.Y >= 0.0) && (mousePt.Y < _textView.ViewportHeight)) + { + ITextViewLine line = _textView.TextViewLines.GetTextViewLineContainingYCoordinate(mousePt.Y + _textView.ViewportTop); + if (line != null) + { + SnapshotSpan span = _visualSpan.GetSpan(_textView.TextSnapshot); + if (span.IntersectsWith(line.ExtentIncludingLineBreak)) + { + double x = mousePt.X + _textView.ViewportLeft; + + //The mouse could be over text in the line ... see if it is over the tip + //This code essentially duplicates the logic in WpfTextView with respect + //to determining the logical position of the hover event. + int? position = line.GetBufferPositionFromXCoordinate(x, true); + if ((!position.HasValue) && (line.LineBreakLength == 0) && line.IsLastTextViewLineForSnapshotLine) + { + //For purposes of clearing tips, pretend the last line in the buffer + //actually is padded by the EndOfLineWidth (even though it is not). + if ((line.TextRight <= x) && (x < line.TextRight + line.EndOfLineWidth)) + { + //position is at the end of the buffer. Return true if the span + //doesn't extend to the end of the buffer. + return (span.End < _textView.TextSnapshot.Length); + } + } + + if (position.HasValue) + { + //A span that ends at the end of the buffer contains a position at the end of the buffer. + return !span.Contains(position.Value); + } + } + } + } + + return true; + } + + internal Point GetScreenPointFromTextXY(double x, double y) + { + return _textView.VisualElement.GetScreenCoordinates(new Gdk.Point((int)(x - _textView.ViewportLeft), (int)(y - _textView.ViewportTop))).ToXwtPoint(); + } + + #region Static positioning helpers + internal static Rect GetLocation(PopupStyles style, Size desiredSize, Rect spanRectInScreenCoordinates, Rect reservedRect, Rect screenRect) + { + if ((style & PopupStyles.PositionLeftOrRight) != 0) + { + //Position left or right + double xPosition = ((style & PopupStyles.PreferLeftOrTopPosition) != 0) + ? (reservedRect.Left - desiredSize.Width) //To the left + : reservedRect.Right; //To the right + + double yPosition = ((style & PopupStyles.RightOrBottomJustify) != 0) + ? (spanRectInScreenCoordinates.Bottom - desiredSize.Height) //Bottom justified + : spanRectInScreenCoordinates.Top; //Top justified + + return ShiftVerticallyToFitScreen(xPosition, yPosition, + desiredSize, screenRect); + } + else + { + //Position above or below. + double xPosition = ((style & PopupStyles.RightOrBottomJustify) != 0) + ? (spanRectInScreenCoordinates.Right - desiredSize.Width) //Right justified + : spanRectInScreenCoordinates.Left; //Left justified + + double yPosition = ((style & PopupStyles.PreferLeftOrTopPosition) != 0) + ? (reservedRect.Top - desiredSize.Height) //Above + : reservedRect.Bottom; //Below + + return ShiftHorizontallyToFitScreen(xPosition, yPosition, + desiredSize, screenRect); + } + } + + internal static Rect ShiftHorizontallyToFitScreen(double x, double y, Size desiredSize, Rect screenRect) + { + if ((x + desiredSize.Width) > screenRect.Right) + x = screenRect.Right - desiredSize.Width; + + if (x < screenRect.Left) + x = screenRect.Left; + + return new Rect(x, y, desiredSize.Width, desiredSize.Height); + } + + internal static Rect ShiftVerticallyToFitScreen(double x, double y, Size desiredSize, Rect screenRect) + { + if ((y + desiredSize.Height) > screenRect.Bottom) + y = screenRect.Bottom - desiredSize.Height; + + if (y < screenRect.Top) + y = screenRect.Top; + + return new Rect(x, y, desiredSize.Width, desiredSize.Height); + } + + internal static bool DisjointWithPadding(Geometry reserved, Rect location) + { + double left = location.Left + 0.1; + double top = location.Top + 0.1; + double width = location.Right - 0.1 - left; + double height = location.Bottom - 0.1 - top; + + if ((width > 0.0) && (height > 0.0)) + { + Geometry insetLocation = new RectangleGeometry(new Rect(left, top, width, height)); + return reserved.Bounds.IntersectsWith(insetLocation.Bounds);//TODO: This was simpliefied + } + else + return true; + } + + internal static bool ContainsWithPadding(Rect outer, Rect inner) + { + return (outer.Left - 0.01 <= inner.Left) && (inner.Right <= outer.Right + 0.01) && + (outer.Top - 0.01 <= inner.Top) && (inner.Bottom <= outer.Bottom + 0.01); + } + #endregion + + internal abstract class PopupOrWindowContainer + { + private Widget _content; + protected Mono.TextEditor.MonoTextEditor _placementTarget; + + public static PopupOrWindowContainer Create(Widget content, Mono.TextEditor.MonoTextEditor placementTarget) + { + return new PopUpContainer(content, placementTarget); + } + + public PopupOrWindowContainer(Widget content, Mono.TextEditor.MonoTextEditor placementTarget) + { + _content = content; + _placementTarget = placementTarget; + } + + public abstract bool IsVisible { get; } + + public Widget Content { get { return _content; } } + + public abstract bool IsKeyboardFocusWithin { get; } + + public abstract void DisplayAt(Point point); + public abstract void Hide(); + public abstract Size Size { get; } + } + + private class PopUpContainer : PopupOrWindowContainer + { +#if WINDOWS
+ private class NoTopmostPopup : XwtThemedPopup
+ {
+ protected override void OnShown ()
+ { + WpfHelper.SetNoTopmost(this.Child);
+ base.OnShown ();
+ }
+ } + + public static void SetNoTopmost(Visual visual) + { + if (visual != null) + { + HwndSource source = PresentationSource.FromVisual(visual) as HwndSource; + if (source != null) + { + const int SWP_NOMOVE = 0x02; + const int SWP_NOSIZE = 0x01; + const int SWP_NOACTIVATE = 0x10; + const int HWND_NOTOPMOST = -2; + NativeMethods.SetWindowPos(source.Handle, (IntPtr)HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + } + } + }
+ XwtThemedPopup _popup = new NoTopmostPopup ();
+#else
+ XwtThemedPopup _popup = new XwtThemedPopup(); +#endif
+ + // WPF popup doesn't detach its child from the visual tree when the popup is not open, + // even if we assign Child property to null. That prevents reusing of the popup content. + // And assiging popup's Child to null when the popup is still open is expensive + // as it requires full subtree traversal to update relevant properties, see + // FrameworkElement.ChangeLogicalParent() sources. + // The solution is to have a neutral control as the popup's child and attach the real content + // to that control instead of popup itself. Then we can safely (and more effectively) + // detach popup content when popup has been closed and so significantly speed up + // popup closing. + FrameBox _popupContentContainer = new FrameBox(); + + public PopUpContainer(Widget content, Mono.TextEditor.MonoTextEditor placementTarget) + : base(content, placementTarget) + { + WindowTransparencyDecorator.Attach(_popup);//TODO: not sure we want this on all popus? + _popup.Content = _popupContentContainer; + _popup.Hidden += OnPopupClosed; + } + + private void OnPopupClosed(object sender, EventArgs e) + { + _popupContentContainer.Content = null; + } + + public override void DisplayAt(Point point) + { + //The horizontal and verical offsets are specified in terms of device pixels + //so convert logical pixel position in point to device pixels. + //_popup.HorizontalOffset = point.X * WpfHelper.DeviceScaleX; + //_popup.VerticalOffset = point.Y * WpfHelper.DeviceScaleY; + _popup.Location = point; + + if (base.Content != _popupContentContainer.Content) + { + if (base.Content.Parent == null) + { + _popupContentContainer.Content = base.Content; + _popup.Show(); + } + else + { + //The intended content still has a parent. Clear out the old content and hope things are better next time around. + _popupContentContainer.Content = null; + } + } + } + + public override void Hide() + { + _popup.Hide(); + } + + public override bool IsVisible + { + get + { + return _popupContentContainer.Content != null; + } + } + + public override Size Size + { + get + { + return ((IWidgetSurface)base.Content).GetPreferredSize(); + } + } + + public override bool IsKeyboardFocusWithin + { + get + { + return _popup.HasFocus; + } + } + } + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/SpaceReservationManager.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/SpaceReservationManager.cs new file mode 100644 index 0000000000..49dee36724 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/SpaceReservationManager.cs @@ -0,0 +1,244 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This file contain implementations details that are subject to change without notice. +// Use at your own risk. +// +namespace Microsoft.VisualStudio.Text.Editor.Implementation +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Windows; + using System.Windows.Media; + using Microsoft.VisualStudio.Text; + using Microsoft.VisualStudio.Text.Adornments; + using Microsoft.VisualStudio.Text.Editor;
+ using MonoDevelop.Components;
+
+ internal class SpaceReservationManager : ISpaceReservationManager + { + public readonly string Name; + public readonly int Rank; + private readonly Mono.TextEditor.MonoTextEditor _view; + private bool _hasAggregateFocus; + internal IList<ISpaceReservationAgent> _agents = new List<ISpaceReservationAgent>(); + + public SpaceReservationManager(string name, int rank, Mono.TextEditor.MonoTextEditor view) + { + this.Name = name; + this.Rank = rank; + _view = view; + _view.Closed += this.OnViewClosed; + } + + #region ISpaceReservationManager Members + public ISpaceReservationAgent CreatePopupAgent(ITrackingSpan visualSpan, PopupStyles styles, Xwt.Widget content) + { + return new PopupAgent(_view, this, visualSpan, styles, content); + } + + public void UpdatePopupAgent(ISpaceReservationAgent agent, ITrackingSpan visualSpan, PopupStyles styles) + { + if (agent == null) + throw new ArgumentNullException("agent"); + if (visualSpan == null) + throw new ArgumentNullException("visualSpan"); + + PopupAgent popupAgent = agent as PopupAgent; + if (popupAgent == null) + throw new ArgumentException("The agent is not a PopupAgent", "agent"); + + popupAgent.SetVisualSpan(visualSpan); + popupAgent._style = styles; + this.CheckFocusChange(); + _view.QueueSpaceReservationStackRefresh(); + } + + public ReadOnlyCollection<ISpaceReservationAgent> Agents + { + get { return new ReadOnlyCollection<ISpaceReservationAgent>(_agents); } + } + + public void AddAgent(ISpaceReservationAgent agent) + { + if (agent == null) + throw new ArgumentNullException("agent"); + + _agents.Add(agent); + this.ChangeAgents(null, agent); + this.CheckFocusChange(); + _view.QueueSpaceReservationStackRefresh(); + } + + public bool RemoveAgent(ISpaceReservationAgent agent) + { + if (agent == null) + throw new ArgumentNullException("agent"); + + if (_agents.Remove(agent)) + { + this.ChangeAgents(agent, null); + this.CheckFocusChange(); + _view.QueueSpaceReservationStackRefresh(); + + return true; + } + + return false; + } + + public event EventHandler<SpaceReservationAgentChangedEventArgs> AgentChanged; + + public bool IsMouseOver + { + get + { + foreach (var agent in _agents) + { + if (agent.IsMouseOver) + return true; + } + + return false; + } + } + + public bool HasAggregateFocus + { + get + { + //We can't uses _hasAggregateFocus (the got focus event may not have reached the agent yet) + foreach (var agent in _agents) + { + if (agent.HasFocus) + return true; + } + + return false; + } + } + + public event EventHandler LostAggregateFocus; + public event EventHandler GotAggregateFocus; + #endregion + + internal void ChangeAgents(ISpaceReservationAgent oldAgent, ISpaceReservationAgent newAgent) + { + if (oldAgent != null) + { + oldAgent.LostFocus -= OnAgentLostFocus; + oldAgent.GotFocus -= OnAgentGotFocus; + oldAgent.Hide(); + } + + EventHandler<SpaceReservationAgentChangedEventArgs> agentChanged = this.AgentChanged; + if (agentChanged != null) + agentChanged(this, new SpaceReservationAgentChangedEventArgs(oldAgent, newAgent)); + + if (newAgent != null) + { + newAgent.LostFocus += OnAgentLostFocus; + newAgent.GotFocus += OnAgentGotFocus; + } + + _view.QueueSpaceReservationStackRefresh(); + } + + void OnAgentLostFocus(object sender, EventArgs e) + { + if (_hasAggregateFocus) + { + foreach (var agent in _agents) + { + if (agent.HasFocus) + return; + } + + _hasAggregateFocus = false; + EventHandler lostAggregateFocus = this.LostAggregateFocus; + if (lostAggregateFocus != null) + lostAggregateFocus(sender, e); + } + } + + void OnAgentGotFocus(object sender, EventArgs e) + { + if (!_hasAggregateFocus) + { + _hasAggregateFocus = true; + EventHandler gotAggregateFocus = this.GotAggregateFocus; + if (gotAggregateFocus != null) + gotAggregateFocus(sender, e); + } + } + + /// <summary> + /// Handle the close event for TextView. If the view is closed, then dismiss all agents. + /// </summary> + void OnViewClosed(object sender, EventArgs e) + { + List<ISpaceReservationAgent> agentsToRemove = new List<ISpaceReservationAgent>(); + agentsToRemove.AddRange (_agents); + + foreach (ISpaceReservationAgent agent in agentsToRemove) + { + this.RemoveAgent (agent); + } + + _view.Closed -= this.OnViewClosed; + } + + internal void PositionAndDisplay(GeometryGroup reservedGeometry) + { + _view.GuardedOperations.CallExtensionPoint(this, + () => + { + if (_agents.Count != 0) + {
+ if (_view.Visible) + { + for (int i = _agents.Count - 1; (i >= 0); --i) + { + ISpaceReservationAgent agent = _agents[i]; + + Geometry requestedGeometry = agent.PositionAndDisplay(reservedGeometry); + if (requestedGeometry == null) + { + _agents.RemoveAt(i); + this.ChangeAgents(agent, null); + } + else if (!requestedGeometry.IsEmpty()) + reservedGeometry.Children.Add(requestedGeometry); + } + } + else + { + for (int i = _agents.Count - 1; (i >= 0); --i) + { + ISpaceReservationAgent agent = _agents[i]; + _agents.RemoveAt(i); + this.ChangeAgents(agent, null); + } + } + + this.CheckFocusChange(); + } + }); + } + + private void CheckFocusChange() + { + bool newFocus = this.HasAggregateFocus; + if (_hasAggregateFocus != newFocus) + { + _hasAggregateFocus = newFocus; + EventHandler focusChangeHandler = _hasAggregateFocus ? this.GotAggregateFocus : this.LostAggregateFocus; + + if (focusChangeHandler != null) + focusChangeHandler(this, new EventArgs()); + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/SpaceReservationStack.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/SpaceReservationStack.cs new file mode 100644 index 0000000000..fa403acdfe --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Impl/WpfView/SpaceReservationStack.cs @@ -0,0 +1,129 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This file contain implementations details that are subject to change without notice. +// Use at your own risk. +// +namespace Microsoft.VisualStudio.Text.Editor.Implementation +{ + using System; + using System.Collections.Generic; + using System.Windows.Media; + using Microsoft.VisualStudio.Text.Editor; + + internal class SpaceReservationStack + { + #region Private Members + internal Dictionary<string, int> _orderedManagerDefinitions; + + internal readonly Mono.TextEditor.MonoTextEditor _view; + internal readonly List<SpaceReservationManager> _managers = new List<SpaceReservationManager>(); + bool _hasAggregateFocus; + + void OnManagerLostFocus(object sender, EventArgs e) + { + if (_hasAggregateFocus) + { + foreach (var manager in _managers) + { + if (manager.HasAggregateFocus) + return; + } + + _hasAggregateFocus = false; + _view.QueueAggregateFocusCheck(); + } + } + + void OnManagerGotFocus(object sender, EventArgs e) + { + _hasAggregateFocus = true; + _view.QueueAggregateFocusCheck(); + } + + #endregion // Private Members + + public SpaceReservationStack(Dictionary<string, int> orderedManagerDefinitions, Mono.TextEditor.MonoTextEditor view) + { + _orderedManagerDefinitions = orderedManagerDefinitions; + _view = view; + } + + public ISpaceReservationManager GetOrCreateManager(string name) + { + foreach (SpaceReservationManager manager in _managers) + { + if (manager.Name == name) + return manager; + } + + int rank; + if (_orderedManagerDefinitions.TryGetValue(name, out rank)) + { + SpaceReservationManager manager = new SpaceReservationManager(name, rank, _view); + + int position = 0; + while (position < _managers.Count) + { + SpaceReservationManager existing = _managers[position]; + if (existing.Rank > rank) + break; + + ++position; + } + + _managers.Insert(position, manager); + manager.LostAggregateFocus += OnManagerLostFocus; + manager.GotAggregateFocus += OnManagerGotFocus; + + return manager; + } + + return null; + } + + public void Refresh() + { + GeometryGroup reservedGeometry = new GeometryGroup(); + + //Make a copy just in case some one in PositionAndDisplay attempts to create a new manager. + //We don't need to queue a new refresh because adding an empty manager won't affect what is displayed + //(& adding a agent to a manager will queue a refresh). + List<SpaceReservationManager> managers = new List<SpaceReservationManager>(_managers); + foreach (SpaceReservationManager manager in managers) + { + manager.PositionAndDisplay(reservedGeometry); + } + } + + public bool IsMouseOver + { + get + { + foreach (var manager in _managers) + { + if (manager.IsMouseOver) + return true; + } + + return false; + } + } + + public bool HasAggregateFocus + { + get + { + //We can't uses _hasAggregateFocus (the got focus event may not have reached the manager yet) + foreach (var manager in _managers) + { + if (manager.HasAggregateFocus) + return true; + } + + return false; + } + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Util/TextDataUtil/MappingHelper.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Util/TextDataUtil/MappingHelper.cs new file mode 100644 index 0000000000..add43f2550 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/Text/Util/TextDataUtil/MappingHelper.cs @@ -0,0 +1,275 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +namespace Microsoft.VisualStudio.Text.Utilities +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.Text.Projection; + + internal static class MappingHelper + { + //These two methods are nearly duplicates of one another but delegates can be expensive and this is inner loop code. + internal static ITextSnapshot FindCorrespondingSnapshot(ITextSnapshot sourceSnapshot, ITextBuffer targetBuffer) + { + if (sourceSnapshot.TextBuffer == targetBuffer) + { + // simple case: single buffer + return sourceSnapshot; + } + else + { + IProjectionSnapshot2 projSnap = sourceSnapshot as IProjectionSnapshot2; + if (projSnap != null) + { + return projSnap.GetMatchingSnapshotInClosure(targetBuffer); + } + else + { + return null; + } + } + } + + internal static ITextSnapshot FindCorrespondingSnapshot(ITextSnapshot sourceSnapshot, Predicate<ITextBuffer> match) + { + if (match(sourceSnapshot.TextBuffer)) + { + // simple case: single buffer + return sourceSnapshot; + } + else + { + IProjectionSnapshot2 projSnap = sourceSnapshot as IProjectionSnapshot2; + if (projSnap != null) + { + return projSnap.GetMatchingSnapshotInClosure(match); + } + else + { + return null; + } + } + } + + internal static NormalizedSnapshotSpanCollection MapDownToBufferNoTrack(SnapshotSpan sourceSpan, ITextBuffer targetBuffer, bool mapByContentType = false) + { + FrugalList<SnapshotSpan> mappedSpans = new FrugalList<SnapshotSpan>(); + + MapDownToFirstMatchNoTrack(sourceSpan, (ITextBuffer b) => { return b == targetBuffer; }, mappedSpans, mapByContentType); + + return new NormalizedSnapshotSpanCollection(mappedSpans); + } + + internal static void MapDownToBufferNoTrack(SnapshotSpan sourceSpan, ITextBuffer targetBuffer, IList<SnapshotSpan> mappedSpans, bool mapByContentType = false) + { + // Most of the time, the sourceSpan will map to the targetBuffer as a single span, rather than being split. + // Since this method is called a lot, we'll assume first that we'll get a single span and don't need to + // allocate a stack to keep track of unmapped spans. If that fails we'll fall back on the more expensive approach. + // Scroll around for a while and this saves a bunch of allocations. + SnapshotSpan mappedSpan = sourceSpan; + while (true) + { + if (mappedSpan.Snapshot.TextBuffer == targetBuffer) + { + mappedSpans.Add(mappedSpan); + return; + } + else + { + IProjectionSnapshot mappedSpanProjectionSnapshot = mappedSpan.Snapshot as IProjectionSnapshot; + if (mappedSpanProjectionSnapshot != null && + (!mapByContentType || mappedSpanProjectionSnapshot.ContentType.IsOfType("projection"))) + { + var mappedDownSpans = mappedSpanProjectionSnapshot.MapToSourceSnapshots(mappedSpan); + if (mappedDownSpans.Count == 1) + { + mappedSpan = mappedDownSpans[0]; + continue; + } + else if (mappedDownSpans.Count == 0) + { + return; + } + else + { + // the projection mapping resulted in more than one span + FrugalList<SnapshotSpan> unmappedSpans = new FrugalList<SnapshotSpan>(mappedDownSpans); + SplitMapDownToBufferNoTrack(unmappedSpans, targetBuffer, mappedSpans, mapByContentType); + return; + } + } + else + { + // either it's a projection buffer we can't look through, or it's + // an ordinary buffer that didn't match + return; + } + } + } + } + + private static void SplitMapDownToBufferNoTrack(FrugalList<SnapshotSpan> unmappedSpans, ITextBuffer targetBuffer, IList<SnapshotSpan> mappedSpans, bool mapByContentType) + { + while (unmappedSpans.Count > 0) + { + SnapshotSpan span = unmappedSpans[unmappedSpans.Count - 1]; + unmappedSpans.RemoveAt(unmappedSpans.Count - 1); + + if (span.Snapshot.TextBuffer == targetBuffer) + { + mappedSpans.Add(span); + } + else + { + IProjectionSnapshot spanSnapshotAsProjection = span.Snapshot as IProjectionSnapshot; + if (spanSnapshotAsProjection != null && + (!mapByContentType || span.Snapshot.TextBuffer.ContentType.IsOfType("projection"))) + { + unmappedSpans.AddRange(spanSnapshotAsProjection.MapToSourceSnapshots(span)); + } + } + } + } + + internal static void MapDownToFirstMatchNoTrack(SnapshotSpan sourceSpan, Predicate<ITextBuffer> match, IList<SnapshotSpan> mappedSpans, bool mapByContentType = false) + { + // Most of the time, the sourceSpan will map to the targetBuffer as a single span, rather than being split. + // Since this method is called a lot, we'll assume first that we'll get a single span and don't need to + // allocate a stack to keep track of unmapped spans. If that fails we'll fall back on the more expensive approach. + // Scroll around for a while and this saves a bunch of allocations. + SnapshotSpan mappedSpan = sourceSpan; + while (true) + { + if (match(mappedSpan.Snapshot.TextBuffer)) + { + mappedSpans.Add(mappedSpan); + return; + } + else + { + IProjectionSnapshot mappedSpanProjectionSnapshot = mappedSpan.Snapshot as IProjectionSnapshot; + if (mappedSpanProjectionSnapshot != null && + (!mapByContentType || mappedSpanProjectionSnapshot.ContentType.IsOfType("projection"))) + { + var mappedDownSpans = mappedSpanProjectionSnapshot.MapToSourceSnapshots(mappedSpan); + if (mappedDownSpans.Count == 1) + { + mappedSpan = mappedDownSpans[0]; + continue; + } + else if (mappedDownSpans.Count == 0) + { + return; + } + else + { + // the projection mapping resulted in more than one span + FrugalList<SnapshotSpan> unmappedSpans = new FrugalList<SnapshotSpan>(mappedDownSpans); + SplitMapDownToFirstMatchNoTrack(unmappedSpans, match, mappedSpans, mapByContentType); + return; + } + } + else + { + // either it's a projection buffer we can't look through, or it's + // an ordinary buffer that didn't match + return; + } + } + } + } + + private static void SplitMapDownToFirstMatchNoTrack(FrugalList<SnapshotSpan> unmappedSpans, Predicate<ITextBuffer> match, IList<SnapshotSpan> mappedSpans, bool mapByContentType) + { + ITextSnapshot matchingSnapshot = null; + + while (unmappedSpans.Count > 0) + { + SnapshotSpan span = unmappedSpans[unmappedSpans.Count - 1]; + unmappedSpans.RemoveAt(unmappedSpans.Count - 1); + + if (span.Snapshot == matchingSnapshot) + { + mappedSpans.Add(span); + } + else if (match(span.Snapshot.TextBuffer)) + { + mappedSpans.Add(span); + matchingSnapshot = span.Snapshot; + } + else + { + IProjectionSnapshot spanSnapshotAsProjection = span.Snapshot as IProjectionSnapshot; + if (spanSnapshotAsProjection != null && + (!mapByContentType || span.Snapshot.TextBuffer.ContentType.IsOfType("projection"))) + { + unmappedSpans.AddRange(spanSnapshotAsProjection.MapToSourceSnapshots(span)); + } + } + } + } + + internal static SnapshotPoint? MapDownToBufferNoTrack(SnapshotPoint position, ITextBuffer targetBuffer, PositionAffinity affinity) + { + while (position.Snapshot.TextBuffer != targetBuffer) + { + IProjectionSnapshot projSnap = position.Snapshot as IProjectionSnapshot; + if ((projSnap == null) || (projSnap.SourceSnapshots.Count == 0)) + { + return null; + } + + position = projSnap.MapToSourceSnapshot(position, affinity); + } + return position; + } + + internal static SnapshotPoint? MapDownToFirstMatchNoTrack(SnapshotPoint position, Predicate<ITextBuffer> match, PositionAffinity affinity) + { + while (!match(position.Snapshot.TextBuffer)) + { + IProjectionSnapshot projSnap = position.Snapshot as IProjectionSnapshot; + if ((projSnap == null) || (projSnap.SourceSnapshots.Count == 0)) + { + return null; + } + + position = projSnap.MapToSourceSnapshot(position, affinity); + } + return position; + } + + internal static SnapshotPoint? MapDownToBufferNoTrack(SnapshotPoint position, ITextBuffer targetBuffer) + { + while (position.Snapshot.TextBuffer != targetBuffer) + { + IProjectionSnapshot projSnap = position.Snapshot as IProjectionSnapshot; + if ((projSnap == null) || (projSnap.SourceSnapshots.Count == 0)) + { + return null; + } + + position = projSnap.MapToSourceSnapshot(position); + } + + return position; + } + + internal static SnapshotPoint? MapDownToFirstMatchNoTrack(SnapshotPoint position, Predicate<ITextBuffer> match) + { + while (!match(position.Snapshot.TextBuffer)) + { + IProjectionSnapshot projSnap = position.Snapshot as IProjectionSnapshot; + if ((projSnap == null) || (projSnap.SourceSnapshots.Count == 0)) + { + return null; + } + + position = projSnap.MapToSourceSnapshot(position); + } + return position; + } + } +} diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextCaret.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextCaret.cs deleted file mode 100644 index 14d856875b..0000000000 --- a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextCaret.cs +++ /dev/null @@ -1,256 +0,0 @@ -//
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-//
-// This file contain implementations details that are subject to change without notice.
-// Use at your own risk.
-//
-using System;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.Formatting;
-using Mono.TextEditor;
-using MonoDevelop.Ide.Editor;
-
-namespace Microsoft.VisualStudio.Text.Editor.Implementation
-{
- internal class TextCaret : ITextCaret
- {
- private TextEditor _textEditor;
- private ITextView _textView;
-
- private VirtualSnapshotPoint _insertionPoint;
- private PositionAffinity _caretAffinity;
-
- public TextCaret(TextEditor textEditor, ITextView textView)
- {
- _textEditor = textEditor;
- _textView = textView;
-
- // Set up initial values
- _caretAffinity = PositionAffinity.Successor;
- _insertionPoint = new VirtualSnapshotPoint(new SnapshotPoint(_textView.TextSnapshot, 0));
-
- textEditor.Carets[0].PositionChanged += ImmediateCaretPositionChanged;
- }
-
- void ImmediateCaretPositionChanged(object sender, CaretLocationEventArgs args)
- {
- // MD doesn't fire textEditor.CaretPositionChanged until after the command has gone completely through the command chain.
- // Too much VS stuff depends on it getting updated earlier, so we'll use this event which fires earlier.
- int position = _textEditor.CaretOffset;
- VirtualSnapshotPoint vsp = new VirtualSnapshotPoint(_textView.TextSnapshot, position);
-
- _insertionPoint = vsp;
- if (args.CaretChangeReason == CaretChangeReason.Movement)
- {
- SnapshotPoint snapshotPoint = new SnapshotPoint(_textView.TextSnapshot, position);
- IMappingPoint mappingPoint = _textView.BufferGraph.CreateMappingPoint(snapshotPoint, PointTrackingMode.Positive);
- CaretPosition newCaretPosition = new CaretPosition(vsp, mappingPoint, _caretAffinity);
- CaretPositionChangedEventArgs eventArgs = new CaretPositionChangedEventArgs(_textView, Position, newCaretPosition);
-
- PositionChanged?.Invoke(this, eventArgs);
- }
- }
-
- public double Bottom
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public ITextViewLine ContainingTextViewLine
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double Height
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public bool InVirtualSpace
- {
- get
- {
- ITextSnapshotLine snapshotLine = _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(Position.BufferPosition);
-
- return _textEditor.CaretColumn > snapshotLine.Length + 1;
- }
- }
-
- public bool IsHidden
- {
- get
- {
- throw new NotImplementedException();
- }
-
- set
- {
- throw new NotImplementedException();
- }
- }
-
- public double Left
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public bool OverwriteMode
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public CaretPosition Position
- {
- get
- {
-#if TARGET_VS
- //In theory the _insertion point is always at the same snapshot at the _wpfTextView but there could be cases
- //where someone is using the position in a classifier that is using the caret position in the classificaiton changed event.
- //In that case return the old insertion point.
- return new CaretPosition(_insertionPoint,
- _textView.BufferGraph.CreateMappingPoint(_insertionPoint.Position, PointTrackingMode.Positive),
- _caretAffinity);
-#else
- // MD doesn't update the caret location until after the command has gone completely through the command chain. Too much VS stuff depends on it getting updated earlier.
- // Thus, I'm going to ensure this is returning a position based on the current snapshot, ignoring/breaking the scenario outlined in the above comment.
- VirtualSnapshotPoint insertionPointInLatestSnapshot = _insertionPoint.TranslateTo(_insertionPoint.Position.Snapshot.TextBuffer.CurrentSnapshot, PointTrackingMode.Positive);
- return new CaretPosition(insertionPointInLatestSnapshot,
- _textView.BufferGraph.CreateMappingPoint(insertionPointInLatestSnapshot.Position, PointTrackingMode.Positive),
- _caretAffinity);
-#endif
- }
- }
-
- public double Right
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double Top
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double Width
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public event EventHandler<CaretPositionChangedEventArgs> PositionChanged;
-
- public void EnsureVisible()
- {
- _textEditor.ScrollTo(_textEditor.CaretLocation);
- }
-
- public CaretPosition MoveTo(SnapshotPoint bufferPosition)
- {
- this.InternalMoveTo(new VirtualSnapshotPoint(bufferPosition), PositionAffinity.Successor, true, true, true);
-
- return this.Position;
- }
-
- public CaretPosition MoveTo(VirtualSnapshotPoint bufferPosition)
- {
- this.InternalMoveTo(bufferPosition, PositionAffinity.Successor, true, true, true);
-
- return this.Position;
- }
-
- public CaretPosition MoveTo(ITextViewLine textLine)
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveTo(SnapshotPoint bufferPosition, PositionAffinity caretAffinity)
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveTo(VirtualSnapshotPoint bufferPosition, PositionAffinity caretAffinity)
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveTo(ITextViewLine textLine, double xCoordinate)
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveTo(VirtualSnapshotPoint bufferPosition, PositionAffinity caretAffinity, bool captureHorizontalPosition)
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveTo(SnapshotPoint bufferPosition, PositionAffinity caretAffinity, bool captureHorizontalPosition)
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveTo(ITextViewLine textLine, double xCoordinate, bool captureHorizontalPosition)
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveToNextCaretPosition()
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveToPreferredCoordinates()
- {
- throw new NotImplementedException();
- }
-
- public CaretPosition MoveToPreviousCaretPosition()
- {
- throw new NotImplementedException();
- }
-
- private void InternalMoveTo(VirtualSnapshotPoint bufferPosition, PositionAffinity caretAffinity, bool captureHorizontalPosition, bool captureVerticalPosition, bool raiseEvent)
- {
- int requestedPosition = bufferPosition.Position;
- ITextSnapshotLine snapshotLine = _textView.TextSnapshot.GetLineFromPosition(requestedPosition);
- int line = snapshotLine.LineNumber + 1;
-
- int col;
- if (bufferPosition.IsInVirtualSpace)
- {
- col = bufferPosition.VirtualSpaces;
- }
- else
- {
- col = requestedPosition - snapshotLine.Start + 1;
- }
-
- _textEditor.SetCaretLocation(line, col, false, false);
- }
- }
-}
-
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextEditorFactoryService.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextEditorFactoryService.cs index eaa19073d2..f72a7c18db 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextEditorFactoryService.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextEditorFactoryService.cs @@ -31,7 +31,7 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation /// Provides a VisualStudio Service that aids in creation of Editor Views
/// </summary>
[Export(typeof(ITextEditorFactoryService))]
- internal sealed class TextEditorFactoryService : ITextEditorFactoryService
+ internal sealed class TextEditorFactoryService : ITextEditorFactoryService, IPartImportsSatisfiedNotification
{
[Import]
internal GuardedOperations GuardedOperations { get; set; }
@@ -100,6 +100,14 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation PredefinedTextViewRoles.Structured,
PredefinedTextViewRoles.Zoomable);
+ public ITextView CreateTextView (ITextBuffer textBuffer)
+ {
+ MonoDevelop.Ide.Editor.ITextDocument textDocument = textBuffer.GetTextEditor();
+ TextEditor textEditor = textDocument as TextEditor;
+
+ return CreateTextView(textEditor);
+ }
+
public ITextView CreateTextView (MonoDevelop.Ide.Editor.TextEditor textEditor, ITextViewRoleSet roles = null, IEditorOptions parentOptions = null)
{
if (textEditor == null)
@@ -123,7 +131,8 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation this.GuardedOperations,
this) ?? new VacuousTextViewModel(dataModel);
- TextView view = new TextView(textEditor, viewModel, roles ?? this.DefaultRoles, parentOptions ?? this.EditorOptionsFactoryService.GlobalOptions, this);
+ var view = ((MonoDevelop.SourceEditor.SourceEditorView)textEditor.Implementation).TextEditor;
+ view.Initialize(viewModel, roles, parentOptions ?? this.EditorOptionsFactoryService.GlobalOptions, this);
view.Properties.AddProperty(typeof(MonoDevelop.Ide.Editor.TextEditor), textEditor);
this.TextViewCreated?.Invoke(this, new TextViewCreatedEventArgs(view));
@@ -164,5 +173,18 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation {
return new TextViewRoleSet(roles);
}
+
+ [ImportMany]
+ private List<Lazy<SpaceReservationManagerDefinition, IOrderable>> _spaceReservationManagerDefinitions = null;
+ internal Dictionary<string, int> OrderedSpaceReservationManagerDefinitions = new Dictionary<string, int>();
+
+ public void OnImportsSatisfied()
+ {
+ IList<Lazy<SpaceReservationManagerDefinition, IOrderable>> orderedManagers = Orderer.Order(_spaceReservationManagerDefinitions);
+ for (int i = 0; (i < orderedManagers.Count); ++i)
+ {
+ this.OrderedSpaceReservationManagerDefinitions.Add(orderedManagers[i].Metadata.Name, i);
+ }
+ }
}
}
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextSelection.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextSelection.cs index 1814107000..47dc46170c 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextSelection.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextSelection.cs @@ -17,13 +17,13 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation {
internal class TextSelection : ITextSelection
{
- private TextEditor _textEditor;
+ private Mono.TextEditor.MonoTextEditor _textEditor;
private ITextView _textView;
- public TextSelection(TextEditor textEditor, ITextView textView)
+ public TextSelection(Mono.TextEditor.MonoTextEditor textArea)
{
- _textEditor = textEditor;
- _textView = textView;
+ _textEditor = textArea;
+ _textView = textArea;
_textEditor.SelectionChanged += OnSelectionChanged;
}
@@ -51,7 +51,7 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation {
get
{
- int offset = _textEditor.SelectionLeadOffset;
+ int offset = _textEditor.SelectionLead;
if (offset == -1)
offset = _textEditor.SelectionRange.Offset; // Selection is empty
@@ -66,7 +66,7 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation {
get
{
- int offset = _textEditor.SelectionAnchorOffset;
+ int offset = _textEditor.SelectionAnchor;
if (offset == -1)
offset = _textEditor.SelectionRange.Offset; // Selection is empty
@@ -147,7 +147,7 @@ namespace Microsoft.VisualStudio.Text.Editor.Implementation else
{
IList<SnapshotSpan> spans = new List<SnapshotSpan>();
- foreach (MonoDevelop.Ide.Editor.Selection curSelection in _textEditor.Selections)
+ foreach (MonoDevelop.Ide.Editor.Selection curSelection in new MonoDevelop.Ide.Editor.Selection[] { _textEditor.MainSelection })
{
for (int curLineIndex = curSelection.MinLine; curLineIndex <= curSelection.MaxLine; curLineIndex++)
{
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextView.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextView.cs deleted file mode 100644 index ae3e4740d2..0000000000 --- a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TextView.cs +++ /dev/null @@ -1,562 +0,0 @@ -//
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-//
-// This file contain implementations details that are subject to change without notice.
-// Use at your own risk.
-//
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Classification;
-using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.Editor.Implementation;
-using Microsoft.VisualStudio.Text.Formatting;
-using Microsoft.VisualStudio.Text.Projection;
-using Microsoft.VisualStudio.Text.Utilities;
-using Microsoft.VisualStudio.Utilities;
-using MonoDevelop.Ide;
-using MonoDevelop.Ide.Editor;
-using Microsoft.VisualStudio.Platform;
-
-namespace Microsoft.VisualStudio.Text.Editor.Implementation
-{
- internal class TextView : ITextView
- {
- #region Private Members
- private TextEditor _textEditor;
-
- ITextBuffer _textBuffer;
- // ITextSnapshot _textSnapshot;
-
- // ITextBuffer _visualBuffer;
- // ITextSnapshot _visualSnapshot;
-
- IBufferGraph _bufferGraph;
- ITextViewRoleSet _roles;
-
- ConnectionManager _connectionManager;
-
- TextEditorFactoryService _factoryService;
-
- // IEditorFormatMap _editorFormatMap;
-
- private bool _hasInitializeBeenCalled = false;
-
- private ITextSelection _selection;
-
- private bool _hasAggregateFocus;
-
- private IEditorOptions _editorOptions;
-
- private List<Lazy<ITextViewCreationListener, IDeferrableContentTypeAndTextViewRoleMetadata>> _deferredTextViewListeners;
-
- private ITextCaret _caret;
-
- bool _isClosed = false;
-
- private PropertyCollection _properties = new PropertyCollection();
-
- //Only one view at a time will have aggregate focus, so keep track of it so that (when sending aggregate focus changed events)
- //we give a view that had focus the chance to send its lost focus message before we claim aggregate focus.
- [ThreadStatic]
- static TextView ViewWithAggregateFocus = null;
-#if DEBUG
- [ThreadStatic]
- static bool SettingAggregateFocus = false;
-#endif
-
-#endregion // Private Members
-
- /// <summary>
- /// Text View constructor.
- /// </summary>
- /// <param name="textViewModel">The text view model that provides the text to visualize.</param>
- /// <param name="roles">Roles for this view.</param>
- /// <param name="parentOptions">Parent options for this view.</param>
- /// <param name="factoryService">Our handy text editor factory service.</param>
- internal TextView(TextEditor textEditor, ITextViewModel textViewModel, ITextViewRoleSet roles, IEditorOptions parentOptions, TextEditorFactoryService factoryService, bool initialize = true)
- {
- _textEditor = textEditor;
-
- _roles = roles;
-
- _factoryService = factoryService;
-
- this.TextDataModel = textViewModel.DataModel;
- this.TextViewModel = textViewModel;
-
- _textBuffer = textViewModel.EditBuffer;
- // _visualBuffer = textViewModel.VisualBuffer;
-
- // _textSnapshot = _textBuffer.CurrentSnapshot;
- // _visualSnapshot = _visualBuffer.CurrentSnapshot;
-
- _editorOptions = _factoryService.EditorOptionsFactoryService.GetOptions(this);
- _editorOptions.Parent = parentOptions;
-
- if (initialize)
- this.Initialize();
- }
-
- internal bool IsTextViewInitialized { get { return _hasInitializeBeenCalled; } }
-
- // This method should only be called once (it is normally called from the ctor unless we're using
- // ITextEditorFactoryService2.CreateTextViewWithoutInitialization on the factory to delay initialization).
- internal void Initialize()
- {
- if (_hasInitializeBeenCalled)
- throw new InvalidOperationException("Attempted to Initialize a WpfTextView twice");
-
- _bufferGraph = _factoryService.BufferGraphFactoryService.CreateBufferGraph(this.TextViewModel.VisualBuffer);
-
- //_editorFormatMap = _factoryService.EditorFormatMapService.GetEditorFormatMap(this);
-
- _selection = new TextSelection(_textEditor, this);
-
- // Create caret
- _caret = new TextCaret(_textEditor, this);
-
- // this.Loaded += OnLoaded;
-
- // TODO: *Someone* needs to call this to execute UndoHistoryRegistry.RegisterHistory -- VS does this via the ShimCompletionControllerFactory.
- _factoryService.EditorOperationsProvider.GetEditorOperations (this);
-
- _connectionManager = new ConnectionManager(this, _factoryService.TextViewConnectionListeners, _factoryService.GuardedOperations);
-
- SubscribeToEvents();
-
- // Binding content type specific assets includes calling out to content-type
- // specific view creation listeners. We need to do this as late as possible.
- this.BindContentTypeSpecificAssets(null, TextViewModel.DataModel.ContentType);
-
- //Subscribe now so that there is no chance that a layout could be forced by a text change.
- //_visualBuffer.ChangedLowPriority += OnVisualBufferChanged;
- //_visualBuffer.ContentTypeChanged += OnVisualBufferContentTypeChanged;
-
- _hasInitializeBeenCalled = true;
- }
-
- public ITextCaret Caret
- {
- get
- {
- return _caret;
- }
- }
-
- public bool HasAggregateFocus
- {
- get
- {
- return _hasAggregateFocus;
- }
- }
-
- public bool InLayout
- {
- get
- {
- return false;
- }
- }
-
- public bool IsClosed { get { return _isClosed; } }
-
- public bool IsMouseOverViewOrAdornments
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double LineHeight
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double MaxTextRightCoordinate
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public IEditorOptions Options
- {
- get { return _editorOptions; }
- }
-
- public PropertyCollection Properties
- {
- get
- {
- return _properties;
- }
- }
-
- public ITrackingSpan ProvisionalTextHighlight
- {
- get
- {
- throw new NotImplementedException();
- }
-
- set
- {
- throw new NotImplementedException();
- }
- }
-
- public ITextSelection Selection
- {
- get
- {
- return _selection;
- }
- }
-
- public ITextViewRoleSet Roles
- {
- get
- {
- return _roles;
- }
- }
-
- /// <summary>
- /// Gets the text buffer whose text, this text editor renders
- /// </summary>
- public ITextBuffer TextBuffer
- {
- get
- {
- return _textBuffer;
- }
- }
-
- public IBufferGraph BufferGraph
- {
- get
- {
- return _bufferGraph;
- }
- }
-
- public ITextSnapshot TextSnapshot
- {
- get
- {
- // TODO: MONO: WpfTextView has a much more complex calculation of this
- return TextBuffer.CurrentSnapshot;
- // return _textSnapshot;
- }
- }
-
- public ITextSnapshot VisualSnapshot
- {
- get
- {
- return TextBuffer.CurrentSnapshot;
- // return _visualSnapshot;
- }
- }
-
- public ITextDataModel TextDataModel { get; private set; }
- public ITextViewModel TextViewModel { get; private set; }
-
-#if TARGET_VS
- public ITextViewLineCollection TextViewLines
- {
- get
- {
- return _textViewLinesCollection;
- }
- }
-#endif
-
- public double ViewportBottom
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double ViewportHeight
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double ViewportLeft
- {
- get
- {
- throw new NotImplementedException();
- }
-
- set
- {
- throw new NotImplementedException();
- }
- }
-
- public double ViewportRight
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double ViewportTop
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public double ViewportWidth
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public IViewScroller ViewScroller
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public ITextViewLineCollection TextViewLines
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public event EventHandler Closed;
- public event EventHandler GotAggregateFocus;
- public event EventHandler LostAggregateFocus;
-#pragma warning disable CS0067
- public event EventHandler<TextViewLayoutChangedEventArgs> LayoutChanged;
- public event EventHandler ViewportLeftChanged;
- public event EventHandler ViewportHeightChanged;
- public event EventHandler ViewportWidthChanged;
- public event EventHandler<MouseHoverEventArgs> MouseHover;
-#pragma warning restore CS0067
-
- public void Close()
- {
- if (_isClosed)
- throw new InvalidOperationException();//Strings.TextViewClosed);
-
- if (_hasAggregateFocus)
- {
- //Silently lose aggregate focus (to preserve Dev11 compatibility which did not raise a focus changed event when the view was closed).
- Debug.Assert(TextView.ViewWithAggregateFocus == this);
- TextView.ViewWithAggregateFocus = null;
- _hasAggregateFocus = false;
- }
-
- UnsubscribeFromEvents();
-
- _connectionManager.Close();
-
- TextViewModel.Dispose();
- TextViewModel = null;
-
- _isClosed = true;
-
- _factoryService.GuardedOperations.RaiseEvent(this, this.Closed);
- }
-
- public void DisplayTextLineContainingBufferPosition(SnapshotPoint bufferPosition, double verticalDistance, ViewRelativePosition relativeTo)
- {
- throw new NotImplementedException();
- }
-
- public void DisplayTextLineContainingBufferPosition(SnapshotPoint bufferPosition, double verticalDistance, ViewRelativePosition relativeTo, double? viewportWidthOverride, double? viewportHeightOverride)
- {
- throw new NotImplementedException();
- }
-
- public SnapshotSpan GetTextElementSpan(SnapshotPoint point)
- {
- throw new NotImplementedException();
- }
-
- public ITextViewLine GetTextViewLineContainingBufferPosition(SnapshotPoint bufferPosition)
- {
- throw new NotImplementedException();
- }
-
- public void QueueSpaceReservationStackRefresh()
- {
- throw new NotImplementedException();
- }
-
- /// <remarks>
- /// If you add an event subscription to this method, be sure to add the corresponding unsubscription to
- /// UnsubscribeFromEvents()
- /// </remarks>
- private void SubscribeToEvents()
- {
- if (IdeApp.IsInitialized)
- IdeApp.Workbench.ActiveDocumentChanged += Workbench_ActiveDocumentChanged;
- }
-
- void Workbench_ActiveDocumentChanged(object sender, EventArgs e)
- {
- QueueAggregateFocusCheck();
- }
-
- private void UnsubscribeFromEvents()
- {
- if (IdeApp.IsInitialized)
- IdeApp.Workbench.ActiveDocumentChanged -= Workbench_ActiveDocumentChanged;
- }
-
- private void BindContentTypeSpecificAssets(IContentType beforeContentType, IContentType afterContentType)
- {
- // Notify the Text view creation listeners
- var extensions = UIExtensionSelector.SelectMatchingExtensions(_factoryService.TextViewCreationListeners, afterContentType, beforeContentType, _roles);
- foreach (var extension in extensions)
- {
- string deferOptionName = extension.Metadata.OptionName;
- if (!string.IsNullOrEmpty(deferOptionName) && Options.IsOptionDefined(deferOptionName, false))
- {
- object value = Options.GetOptionValue(deferOptionName);
- if (value is bool)
- {
- if (!(bool)value)
- {
- if (_deferredTextViewListeners == null)
- {
- _deferredTextViewListeners = new List<Lazy<ITextViewCreationListener, IDeferrableContentTypeAndTextViewRoleMetadata>>();
- }
- _deferredTextViewListeners.Add(extension);
- continue;
- }
- }
- }
-
- var instantiatedExtension = _factoryService.GuardedOperations.InstantiateExtension(extension, extension);
- if (instantiatedExtension != null)
- {
- _factoryService.GuardedOperations.CallExtensionPoint(instantiatedExtension,
- () => instantiatedExtension.TextViewCreated(this));
- }
- }
- }
-
- /// <summary>
- /// Handles the Classification changed event that comes from the Classifier aggregator
- /// </summary>
- void OnClassificationChanged(object sender, ClassificationChangedEventArgs e)
- {
- if (!_isClosed)
- {
- // When classifications change, we just invalidate the lines. That invalidation will
- // create new lines based on the new classifications.
-
- // Map the classification change (from the edit buffer) to the visual buffer
- Span span = Span.FromBounds(
- TextViewModel.GetNearestPointInVisualSnapshot(e.ChangeSpan.Start, VisualSnapshot, PointTrackingMode.Negative),
- TextViewModel.GetNearestPointInVisualSnapshot(e.ChangeSpan.End, VisualSnapshot, PointTrackingMode.Positive));
-
- //Classifications changes invalidate only the characters contained in the span so a zero length change
- //will have no effect.
- if (span.Length > 0)
- {
- //IsLineInvalid will invalidate a line if it intersects the end. The result is that any call to InvalidateSpan() implicitly
- //invalidates any line that starts at the end of the invalidated span, which we do not want here. Reduce the length of the classification
- //change span one so -- if someone invalidated an entire line including the line break -- the next line will not be invalidated.
- span = new Span(span.Start, span.Length - 1);
-
-// MONO: TODO: this
-
- //lock (_invalidatedSpans)
- //{
- // if ((_attachedLineCache.Count > 0) || (_unattachedLineCache.Count > 0))
- // {
- // _reclassifiedSpans.Add(span);
- // this.QueueLayout();
- // }
- //}
- }
- }
- }
-
- internal void QueueAggregateFocusCheck(bool checkForFocus = true)
- {
-#if DEBUG
- if (TextView.SettingAggregateFocus)
- {
- Debug.Fail("WpfTextView.SettingAggregateFocus");
- }
-#endif
-
- if (!_isClosed)
- {
- bool newHasAggregateFocus = (IdeApp.Workbench.ActiveDocument?.Editor == _textEditor);
- if (newHasAggregateFocus != _hasAggregateFocus)
- {
- _hasAggregateFocus = newHasAggregateFocus;
-
- if (_hasAggregateFocus)
- {
- //Got focus so make sure that the view that had focus (which wasn't us since we didn't have focus before) raises its
- //lost focus event before we raise our got focus event. This will potentially do bad things if someone changes focus
- //if the lost aggregate focus handler.
- Debug.Assert(TextView.ViewWithAggregateFocus != this);
- if (TextView.ViewWithAggregateFocus != null)
- {
- TextView.ViewWithAggregateFocus.QueueAggregateFocusCheck(checkForFocus: false);
- }
- Debug.Assert(TextView.ViewWithAggregateFocus == null);
- TextView.ViewWithAggregateFocus = this;
- }
- else
- {
- //Lost focus (which means we were the view with focus).
- Debug.Assert(TextView.ViewWithAggregateFocus == this);
- TextView.ViewWithAggregateFocus = null;
- }
-
- EventHandler handler = _hasAggregateFocus ? this.GotAggregateFocus : this.LostAggregateFocus;
-
-#if DEBUG
- try
- {
- TextView.SettingAggregateFocus = true;
-#endif
- _factoryService.GuardedOperations.RaiseEvent(this, handler);
-#if DEBUG
- }
- finally
- {
- TextView.SettingAggregateFocus = false;
- }
-#endif
- }
- }
- }
- }
-}
-
diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/VisualStudio/Impl/ViewAdapter/TipManager.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/VisualStudio/Impl/ViewAdapter/TipManager.cs new file mode 100644 index 0000000000..6d733fd1c5 --- /dev/null +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/VisualStudio/Impl/ViewAdapter/TipManager.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.Editor.Implementation +{
+ [Export (typeof (IObscuringTipManager))]
+ public class TipManager : IObscuringTipManager
+ {
+ public void PushTip (ITextView view, IObscuringTip tip)
+ {
+ }
+
+ public void RemoveTip (ITextView view, IObscuringTip tip)
+ {
+ }
+ }
+}
\ No newline at end of file diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj index 0e03990c59..c81c5529ac 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj @@ -118,6 +118,10 @@ <HintPath>..\..\..\..\build\bin\Microsoft.CodeAnalysis.dll</HintPath> <Private>False</Private> </Reference> + <Reference Include="Microsoft.VisualStudio.Text.UI"> + <HintPath>..\..\..\..\build\bin\Microsoft.VisualStudio.Text.UI.dll</HintPath> + <Private>False</Private> + </Reference> </ItemGroup> <ItemGroup> <EmbeddedResource Include="icons\added-overlay-16.png"> diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Shared.projitems b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Shared.projitems index 957dd9b2eb..003ef36856 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Shared.projitems +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Shared.projitems @@ -79,6 +79,7 @@ <Compile Include="$(MSBuildThisFileDirectory)Mono.TextEditor\UrlMarker.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Mono.TextEditor\TextEditorData.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Mono.TextEditor\DefaultIndentationTracker.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Mono.TextEditor\CaretImpl.ITextCaret.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Mono.TextEditor\" />
diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/ClipboardActions.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/ClipboardActions.cs index 5aeeb1eb38..c0894e600b 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/ClipboardActions.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/ClipboardActions.cs @@ -435,12 +435,17 @@ namespace Mono.TextEditor data.Document.CommitLineUpdate (data.GetLineByOffset (insertionOffset)); return result; } - + public static void Paste (TextEditorData data) { + PasteWithResult (data); + } + + public static bool PasteWithResult (TextEditorData data) + { if (!data.CanEditSelection) - return; - PasteFrom (Clipboard.Get (CopyOperation.CLIPBOARD_ATOM), data, false, data.IsSomethingSelected ? data.SelectionRange.Offset : data.Caret.Offset); + return false; + return PasteFrom (Clipboard.Get (CopyOperation.CLIPBOARD_ATOM), data, false, data.IsSomethingSelected ? data.SelectionRange.Offset : data.Caret.Offset) > 0; } public static string GetClipboardContent() diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/MiscActions.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/MiscActions.cs index 5bf609cd3a..ce678e062b 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/MiscActions.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Actions/MiscActions.cs @@ -242,9 +242,16 @@ namespace Mono.TextEditor { using (var undo = data.OpenUndoGroup ()) { data.EnsureCaretIsNotVirtual (); + + var oldCaretLine = data.Caret.Location.Line; + string indentString = data.GetIndentationString (data.Caret.Location); data.InsertAtCaret (data.EolMarker); - data.InsertAtCaret (indentString); + + // Don't insert the indent string if the EOL insertion modified the caret location in an unexpected fashion + // (This likely means someone has custom logic regarding insertion of the EOL) + if (data.Caret.Location.Line == oldCaretLine + 1 && data.Caret.Location.Column == 1) + data.InsertAtCaret (indentString); } } diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.ITextCaret.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.ITextCaret.cs new file mode 100644 index 0000000000..386a85f6c1 --- /dev/null +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.ITextCaret.cs @@ -0,0 +1,236 @@ +// +// CaretImpl.ITextCaret.cs +// +// Author: +// Mike Krüger <mikkrg@microsoft.com> +// +// Copyright (c) 2018 Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using MonoDevelop.Core.Text; +using MonoDevelop.Ide.Editor; +using System; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Mono.TextEditor; +using MonoDevelop.Ide.Editor; + +namespace Mono.TextEditor +{ + partial class CaretImpl : ITextCaret + { + MonoTextEditor TextEditor => TextEditorData.Parent; + + VirtualSnapshotPoint insertionPoint; + PositionAffinity _caretAffinity; + + ITextViewLine ITextCaret.ContainingTextViewLine => TextEditor.GetTextViewLineContainingBufferPosition (((ITextCaret)this).Position.VirtualBufferPosition.Position); + + double ITextCaret.Left => TextEditor.TextArea.TextViewMargin.caretX; + + double ITextCaret.Width => TextEditor.TextArea.TextViewMargin.charWidth; + + double ITextCaret.Right => TextEditor.TextArea.TextViewMargin.caretX + TextEditor.TextArea.TextViewMargin.charWidth; + + double ITextCaret.Top => TextEditor.TextArea.TextViewMargin.caretY; + + double ITextCaret.Height => TextEditor.LineHeight; + + double ITextCaret.Bottom => TextEditor.TextArea.TextViewMargin.caretY + TextEditor.LineHeight; + + CaretPosition ITextCaret.Position { + get { +#if TARGET_VS + //In theory the _insertion point is always at the same snapshot at the _wpfTextView but there could be cases + //where someone is using the position in a classifier that is using the caret position in the classificaiton changed event. + //In that case return the old insertion point. + return new CaretPosition(_insertionPoint, + _textView.BufferGraph.CreateMappingPoint(_insertionPoint.Position, PointTrackingMode.Positive), + _caretAffinity); +#else + // MD doesn't update the caret location until after the command has gone completely through the command chain. Too much VS stuff depends on it getting updated earlier. + // Thus, I'm going to ensure this is returning a position based on the current snapshot, ignoring/breaking the scenario outlined in the above comment. + VirtualSnapshotPoint insertionPointInLatestSnapshot = insertionPoint.TranslateTo (insertionPoint.Position.Snapshot.TextBuffer.CurrentSnapshot, PointTrackingMode.Positive); + return new CaretPosition (insertionPointInLatestSnapshot, + TextEditor.BufferGraph.CreateMappingPoint (insertionPointInLatestSnapshot.Position, PointTrackingMode.Positive), + _caretAffinity); +#endif + } + } + + + bool ITextCaret.OverwriteMode => !IsInInsertMode; + + bool ITextCaret.InVirtualSpace { + get { + var snapshotLine = TextEditor.TextBuffer.CurrentSnapshot.GetLineFromPosition (((ITextCaret)this).Position.BufferPosition); + + return TextEditor.Caret.Column > snapshotLine.Length + 1; + } + } + + bool ITextCaret.IsHidden { + get { + return !IsVisible; + } + set { + IsVisible = !value; + } + } + + DocumentLocation oldCaretLocation; + void PositionChanged_ITextCaret (CaretLocationEventArgs args) + { + //Some unit tests don't initialize full UI representation of MonoTextEditor + //which means they don't depend on ITextCaret implementation, so we can return here + //If something is using MonoTextEditor directly(e.g. DiffView) and is not initializing ITextView + //TextBuffer is null, in that case don't depend on ITextCaret implementation, so we can return here + if (TextEditor?.TextBuffer == null) + return; + // MD doesn't fire textEditor.CaretPositionChanged until after the command has gone completely through the command chain. + // Too much VS stuff depends on it getting updated earlier, so we'll use this event which fires earlier. + int position = TextEditor.Caret.Offset; + VirtualSnapshotPoint vsp = new VirtualSnapshotPoint (TextEditor.TextSnapshot, position); + + insertionPoint = vsp; + if (args.CaretChangeReason == CaretChangeReason.Movement) { + oldCaretLocation = args.Location; + var oldOffset = TextEditor.LocationToOffset (args.Location); + var snapshotPoint = new SnapshotPoint (TextEditor.TextSnapshot, oldOffset); + var mappingPoint = TextEditor.BufferGraph.CreateMappingPoint (snapshotPoint, PointTrackingMode.Positive); + var oldCaretPosition = new CaretPosition (vsp, mappingPoint, _caretAffinity); + var eventArgs = new CaretPositionChangedEventArgs (TextEditor, oldCaretPosition, ((ITextCaret)this).Position); + + ITextCaret_PositionChanged?.Invoke (this, eventArgs); + } + } + + event EventHandler<CaretPositionChangedEventArgs> ITextCaret_PositionChanged; + event EventHandler<CaretPositionChangedEventArgs> ITextCaret.PositionChanged { + add { ITextCaret_PositionChanged += value; } + remove { ITextCaret_PositionChanged -= value; } + } + + void ITextCaret.EnsureVisible () + { + TextEditor.ScrollToCaret (); + } + + + CaretPosition ITextCaret.MoveTo (ITextViewLine textLine, double xCoordinate) + { + Line = textLine.Start; + // TODO: xCoordinate - is that just visual or does it have an impact on the column ? + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (ITextViewLine textLine, double xCoordinate, bool captureHorizontalPosition) + { + Line = textLine.Start; + // TODO: xCoordinate - is that just visual or does it have an impact on the column ? + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (ITextViewLine textLine) + { + Line = textLine.Start; + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (SnapshotPoint bufferPosition) + { + this.InternalMoveTo (new VirtualSnapshotPoint (bufferPosition), PositionAffinity.Successor, true, true, true); + + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (SnapshotPoint bufferPosition, PositionAffinity caretAffinity) + { + this.InternalMoveTo (new VirtualSnapshotPoint (bufferPosition), caretAffinity, true, true, true); + + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (SnapshotPoint bufferPosition, PositionAffinity caretAffinity, bool captureHorizontalPosition) + { + this.InternalMoveTo (new VirtualSnapshotPoint (bufferPosition), caretAffinity, captureHorizontalPosition, true, true); + + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (VirtualSnapshotPoint bufferPosition) + { + this.InternalMoveTo (bufferPosition, PositionAffinity.Successor, true, true, true); + + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (VirtualSnapshotPoint bufferPosition, PositionAffinity caretAffinity) + { + this.InternalMoveTo (bufferPosition, caretAffinity, true, true, true); + + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveTo (VirtualSnapshotPoint bufferPosition, PositionAffinity caretAffinity, bool captureHorizontalPosition) + { + this.InternalMoveTo (bufferPosition, caretAffinity, captureHorizontalPosition, true, true); + + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveToNextCaretPosition () + { + // TODO: Implement me - not sure if we've a 'next' position. What should this be ? + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveToPreferredCoordinates () + { + TextEditor.GetTextEditorData ().FixVirtualIndentation (); + return ((ITextCaret)this).Position; + } + + CaretPosition ITextCaret.MoveToPreviousCaretPosition () + { + Location = oldCaretLocation; + return ((ITextCaret)this).Position; + } + + void InternalMoveTo (VirtualSnapshotPoint bufferPosition, PositionAffinity caretAffinity, bool captureHorizontalPosition, bool captureVerticalPosition, bool raiseEvent) + { + int requestedPosition = bufferPosition.Position; + ITextSnapshotLine snapshotLine = TextEditor.TextSnapshot.GetLineFromPosition (requestedPosition); + int line = snapshotLine.LineNumber + 1; + + int col; + if (bufferPosition.IsInVirtualSpace) { + col = bufferPosition.VirtualSpaces; + } else { + col = requestedPosition - snapshotLine.Start + 1; + } + + TextEditor.SetCaretTo (line, col, false, false); + } + } +}
\ No newline at end of file diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs index d27baf33ae..9822b5a151 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs @@ -27,12 +27,13 @@ using System; using System.Linq; +using Microsoft.VisualStudio.Text; using MonoDevelop.Core.Text; using MonoDevelop.Ide.Editor; namespace Mono.TextEditor { - class CaretImpl : MonoDevelop.Ide.Editor.Caret + partial class CaretImpl : MonoDevelop.Ide.Editor.Caret { bool isInInsertMode = true; bool autoScrollToCaret = true; @@ -196,6 +197,10 @@ namespace Mono.TextEditor AllowCaretBehindLineEnd = false; DesiredColumn = DocumentLocation.MinColumn; AutoUpdatePosition = true; + + // Set up initial values + _caretAffinity = PositionAffinity.Successor; + insertionPoint = new VirtualSnapshotPoint (new SnapshotPoint (editor.Document.TextBuffer.CurrentSnapshot, 0)); } /// <summary> @@ -324,6 +329,7 @@ namespace Mono.TextEditor { TextEditorData.Document.EnsureOffsetIsUnfolded (Offset); base.OnPositionChanged (args); + PositionChanged_ITextCaret (args); } protected virtual void OnModeChanged () diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs index 3d39f9b743..05a1c1db71 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs @@ -35,6 +35,7 @@ using System.ComponentModel; using System.Text; using System.Threading.Tasks; using System.Threading; +using MonoDevelop.Ide.Composition;
using MonoDevelop.Core.Text; using MonoDevelop.Ide.Editor; using MonoDevelop.Core; @@ -42,6 +43,7 @@ using System.IO; using MonoDevelop.Ide.Editor.Highlighting; using Microsoft.VisualStudio.Platform; using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities;
namespace Mono.TextEditor {
@@ -69,7 +71,7 @@ namespace Mono.TextEditor return PlatformCatalog.Instance.MimeToContentTypeRegistryService.GetMimeType(snapshot.ContentType) ?? snapshot.ContentType.TypeName;
}
set {
- var newContentType = value != null ? GetContentTypeFromMimeType(value) : PlatformCatalog.Instance.ContentTypeRegistryService.UnknownContentType; + var newContentType = value != null ? GetContentTypeFromMimeType(null, value) : PlatformCatalog.Instance.ContentTypeRegistryService.UnknownContentType; if (this.TextBuffer.CurrentSnapshot.ContentType != newContentType) {
this.TextBuffer.ChangeContentType(newContentType, null);
@@ -77,9 +79,20 @@ namespace Mono.TextEditor }
}
- private static Microsoft.VisualStudio.Utilities.IContentType GetContentTypeFromMimeType(string mimeType)
+ private static Microsoft.VisualStudio.Utilities.IContentType GetContentTypeFromMimeType(string filePath, string mimeType)
{
- Microsoft.VisualStudio.Utilities.IContentType contentType = PlatformCatalog.Instance.MimeToContentTypeRegistryService.GetContentType(mimeType);
+ if (filePath != null) + {
+ IFilePathRegistryService filePathRegistryService = CompositionManager.GetExportedValue<IFilePathRegistryService> ();
+
+ IContentType contentTypeFromPath = filePathRegistryService.GetContentTypeForPath (filePath);
+ if (contentTypeFromPath != PlatformCatalog.Instance.ContentTypeRegistryService.UnknownContentType) + {
+ return contentTypeFromPath;
+ }
+ }
+
+ IContentType contentType = PlatformCatalog.Instance.MimeToContentTypeRegistryService.GetContentType (mimeType); if (contentType == null)
{
// fallback 1: see if there is a content tyhpe with the same name
@@ -147,7 +160,7 @@ namespace Mono.TextEditor void SyntaxMode_HighlightingStateChanged (object sender, MonoDevelop.Ide.Editor.LineEventArgs e)
{
- CommitDocumentUpdate ();
+ CommitMultipleLineUpdate (e.Line.LineNumber, e.Line.LineNumber);
}
void OnSyntaxModeChanged (SyntaxModeChangeEventArgs e)
@@ -213,6 +226,7 @@ namespace Mono.TextEditor this.TextBuffer.Properties.AddProperty(typeof(ITextDocument), this);
this.TextBuffer.Changed += this.OnTextBufferChanged;
+ (this.TextBuffer as Microsoft.VisualStudio.Text.Implementation.BaseBuffer).ChangedImmediate += OnTextBufferChangedImmediate;
this.TextBuffer.ContentTypeChanged += this.OnTextBufferContentTypeChanged;
this.VsTextDocument.FileActionOccurred += this.OnTextDocumentFileActionOccured;
@@ -230,7 +244,7 @@ namespace Mono.TextEditor SyntaxMode = null;
}
- void OnTextBufferChanged(object sender, Microsoft.VisualStudio.Text.TextContentChangedEventArgs args) + private void OnTextBufferChangedImmediate (object sender, Microsoft.VisualStudio.Text.TextContentChangedEventArgs args) { if (args.Changes == null) return;
@@ -239,14 +253,25 @@ namespace Mono.TextEditor changes.Add (new TextChange (change.OldPosition, change.NewPosition, change.OldText, change.NewText)); EnsureSegmentIsUnfolded(change.OldPosition, change.NewLength); }
- bool endUndo = false;
- UndoOperation operation = null;
var textChange = new TextChangeEventArgs(changes); InterruptFoldWorker(); TextChanging?.Invoke(this, textChange); // After TextChanging notification has been sent, we can update the cached snapshot
this.currentSnapshot = args.After; + }
+
+ void OnTextBufferChanged(object sender, Microsoft.VisualStudio.Text.TextContentChangedEventArgs args)
+ {
+ if (args.Changes == null)
+ return;
+ var changes = new List<TextChange> ();
+ foreach (var change in args.Changes) {
+ changes.Add (new TextChange (change.OldPosition, change.NewPosition, change.OldText, change.NewText));
+ }
+ bool endUndo = false;
+ UndoOperation operation = null;
+ var textChange = new TextChangeEventArgs(changes);
if (!isInUndo) {
operation = new UndoOperation(args);
@@ -294,11 +319,11 @@ namespace Mono.TextEditor public TextDocument (string fileName, string mimeType)
{
- var contentType = GetContentTypeFromMimeType (mimeType); + var contentType = (mimeType == null) ? PlatformCatalog.Instance.TextBufferFactoryService.InertContentType : GetContentTypeFromMimeType(fileName, mimeType);
Encoding enc;
var text = TextFileUtility.GetText (fileName, out enc);
var buffer = PlatformCatalog.Instance.TextBufferFactoryService.CreateTextBuffer (text ?? string.Empty, - PlatformCatalog.Instance.TextBufferFactoryService.InertContentType); + contentType); this.VsTextDocument = PlatformCatalog.Instance.TextDocumentFactoryService.CreateTextDocument (buffer, fileName); this.VsTextDocument.Encoding = enc; @@ -306,13 +331,14 @@ namespace Mono.TextEditor this.Initialize();
}
- public TextDocument (string text = null)
+ public TextDocument (string text = null, string fileName = null, string mimeType = null)
{
- var buffer = PlatformCatalog.Instance.TextBufferFactoryService.CreateTextBuffer(text ?? string.Empty,
- PlatformCatalog.Instance.TextBufferFactoryService.InertContentType);
+ var contentType = (mimeType == null) ? PlatformCatalog.Instance.TextBufferFactoryService.InertContentType : GetContentTypeFromMimeType(fileName, mimeType);
+ var buffer = PlatformCatalog.Instance.TextBufferFactoryService.CreateTextBuffer (text ?? string.Empty,
+ contentType);
- this.VsTextDocument = PlatformCatalog.Instance.TextDocumentFactoryService.CreateTextDocument(buffer, string.Empty);
- this.VsTextDocument.Encoding = MonoDevelop.Core.Text.TextFileUtility.DefaultEncoding;
+ this.VsTextDocument = PlatformCatalog.Instance.TextDocumentFactoryService.CreateTextDocument(buffer, fileName ?? string.Empty);
+ this.VsTextDocument.Encoding = TextFileUtility.DefaultEncoding;
this.Initialize();
}
diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs index dfd3a1e2c6..1609832a40 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs @@ -206,12 +206,13 @@ namespace Mono.TextEditor { LineHeight = 16; - caret = new CaretImpl (this); - caret.PositionChanged += CaretPositionChanged; - options = TextEditorOptions.DefaultOptions; document = doc; AttachDocument (); + + caret = new CaretImpl (this); + caret.PositionChanged += CaretPositionChanged; + SearchEngine = new BasicSearchEngine (); HeightTree = new HeightTree (this); diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs index ea8f0b4273..c9373afb86 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs @@ -1,4 +1,4 @@ -// +// // RemoteBuildEngineManager.cs // // Author: @@ -609,12 +609,12 @@ namespace MonoDevelop.Projects.MSBuild spid = spid.Substring (0, i); int pid; if (int.TryParse (Path.GetFileName (spid), out pid)) { - try { - // If there is a process running with this id it means the builder is still being used - if (Process.GetProcessById (pid) != null) + try {
+ // If there is a process running with this id it means the builder is still being used
+ if (Process.GetProcessById (pid) != null)
continue; } catch { - // Ignore + // Ignore
} // No process for this id, it should be safe to delete the folder try { diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs index 46768a95f0..6b0b36c660 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs @@ -115,7 +115,7 @@ namespace MonoDevelop.Projects.MSBuild resolvers.AddRange (assembly.ExportedTypes .Select (type => new { type, info = type.GetTypeInfo () }) - .Where (t => t.info.IsClass && t.info.IsPublic && typeof (SdkResolver).IsAssignableFrom (t.type)) + .Where (t => t.info.IsClass && t.info.IsPublic && !t.info.IsAbstract && typeof (SdkResolver).IsAssignableFrom (t.type)) .Select (t => (SdkResolver)Activator.CreateInstance (t.type))); } catch (Exception e) { logger.LogWarning (e.Message); diff --git a/main/src/core/MonoDevelop.Core/packages.config b/main/src/core/MonoDevelop.Core/packages.config index b71392f474..e32f14c0a0 100644 --- a/main/src/core/MonoDevelop.Core/packages.config +++ b/main/src/core/MonoDevelop.Core/packages.config @@ -1,90 +1,91 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="Humanizer.Core" version="2.2.0" targetFramework="net461" /> - <package id="ManagedEsent" version="1.9.4" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.Analyzers" version="1.2.0-beta2" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.Common" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.CSharp" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.CSharp.Features" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.EditorFeatures" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.EditorFeatures.Text" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.Elfie" version="1.0.0-rc9" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.Features" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.VisualBasic" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.VisualBasic.Features" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.VisualBasic.Workspaces" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.CodeAnalysis.Workspaces.Common" version="2.7.0-beta3-62509-03" targetFramework="net461" /> - <package id="Microsoft.Composition" version="1.0.30" targetFramework="net461" /> - <package id="Microsoft.VisualStudio.Composition" version="15.3.38" targetFramework="net461" /> - <package id="Microsoft.VisualStudio.CoreUtility" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Language.Intellisense" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Language.StandardClassification" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Text.Data" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Text.Internal" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Text.Logic" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Text.UI" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="15.6.161-preview" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Text.Implementation" version="15.0.7-pre" targetFramework="net45" /> - <package id="Microsoft.VisualStudio.Threading" version="15.4.4" targetFramework="net461" /> - <package id="Microsoft.VisualStudio.Validation" version="15.3.15" targetFramework="net461" /> - <package id="Mono.Cecil" version="0.10.0-beta6" targetFramework="net45" /> - <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" /> - <package id="SharpZipLib" version="0.86.0" targetFramework="net45" /> - <package id="SQLitePCLRaw.bundle_e_sqlite3" version="1.1.6" targetFramework="net461" /> - <package id="SQLitePCLRaw.core" version="1.1.6" targetFramework="net461" /> - <package id="SQLitePCLRaw.lib.e_sqlite3.linux" version="1.1.6" targetFramework="net461" /> - <package id="SQLitePCLRaw.lib.e_sqlite3.osx" version="1.1.6" targetFramework="net461" /> - <package id="SQLitePCLRaw.lib.e_sqlite3.v110_xp" version="1.1.6" targetFramework="net461" /> - <package id="SQLitePCLRaw.provider.e_sqlite3.net45" version="1.1.6" targetFramework="net461" /> - <package id="System.AppContext" version="4.3.0" targetFramework="net461" /> - <package id="System.Collections" version="4.3.0" targetFramework="net461" /> - <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net461" /> - <package id="System.Collections.Immutable" version="1.3.1" targetFramework="net461" /> - <package id="System.Composition.AttributedModel" version="1.0.31" targetFramework="net461" /> - <package id="System.Composition.Hosting" version="1.0.31" targetFramework="net461" /> - <package id="System.Composition.Runtime" version="1.0.31" targetFramework="net461" /> - <package id="System.Composition.TypedParts" version="1.0.31" targetFramework="net461" /> - <package id="System.Console" version="4.3.0" targetFramework="net461" /> - <package id="System.Diagnostics.Contracts" version="4.3.0" targetFramework="net461" /> - <package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net461" /> - <package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net461" /> - <package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net461" /> - <package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net461" /> - <package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net461" /> - <package id="System.Globalization" version="4.3.0" targetFramework="net461" /> - <package id="System.IO.Compression" version="4.3.0" targetFramework="net461" /> - <package id="System.IO.FileSystem" version="4.3.0" targetFramework="net461" /> - <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net461" /> - <package id="System.Linq" version="4.3.0" targetFramework="net461" /> - <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net461" /> - <package id="System.Linq.Parallel" version="4.3.0" targetFramework="net461" /> - <package id="System.ObjectModel" version="4.3.0" targetFramework="net461" /> - <package id="System.Reflection" version="4.3.0" targetFramework="net461" /> - <package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net461" /> - <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net461" /> - <package id="System.Runtime" version="4.3.0" targetFramework="net461" /> - <package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net461" /> - <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net461" /> - <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net461" /> - <package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net461" /> - <package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net461" /> - <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net461" /> - <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net461" /> - <package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net461" /> - <package id="System.Text.Encoding" version="4.3.0" targetFramework="net461" /> - <package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net461" /> - <package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net461" /> - <package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net461" /> - <package id="System.Threading" version="4.3.0" targetFramework="net461" /> - <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net461" /> - <package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net461" /> - <package id="System.Threading.Thread" version="4.3.0" targetFramework="net461" /> - <package id="System.ValueTuple" version="4.4.0" targetFramework="net461" /> - <package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net461" /> - <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net461" /> - <package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net461" /> - <package id="System.Xml.XPath" version="4.3.0" targetFramework="net461" /> - <package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net461" /> -</packages> +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Humanizer.Core" version="2.2.0" targetFramework="net461" />
+ <package id="ManagedEsent" version="1.9.4" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.Analyzers" version="1.2.0-beta2" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.Common" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.CSharp" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.CSharp.Features" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.CSharp.Workspaces" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.EditorFeatures" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.EditorFeatures.Text" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.Elfie" version="1.0.0-rc9" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.Features" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.VisualBasic" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.VisualBasic.Features" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.VisualBasic.Workspaces" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.CodeAnalysis.Workspaces.Common" version="2.7.0-beta3-62509-03" targetFramework="net461" />
+ <package id="Microsoft.Composition" version="1.0.30" targetFramework="net461" />
+ <package id="Microsoft.VisualStudio.Composition" version="15.3.38" targetFramework="net461" />
+ <package id="Microsoft.VisualStudio.CoreUtility" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Language" version="15.6.241-preview" targetFramework="net461" />
+ <package id="Microsoft.VisualStudio.Language.Intellisense" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Language.StandardClassification" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Text.Data" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Text.Internal" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Text.Logic" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Text.UI" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Text.UI.Wpf" version="15.6.241-preview" targetFramework="net45" />
+ <package id="Microsoft.VisualStudio.Text.Implementation" version="15.1.0-pre" targetFramework="net461" />
+ <package id="Microsoft.VisualStudio.Threading" version="15.4.4" targetFramework="net461" />
+ <package id="Microsoft.VisualStudio.Validation" version="15.3.15" targetFramework="net461" />
+ <package id="Mono.Cecil" version="0.10.0-beta6" targetFramework="net45" />
+ <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
+ <package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
+ <package id="SQLitePCLRaw.bundle_e_sqlite3" version="1.1.6" targetFramework="net461" />
+ <package id="SQLitePCLRaw.core" version="1.1.6" targetFramework="net461" />
+ <package id="SQLitePCLRaw.lib.e_sqlite3.linux" version="1.1.6" targetFramework="net461" />
+ <package id="SQLitePCLRaw.lib.e_sqlite3.osx" version="1.1.6" targetFramework="net461" />
+ <package id="SQLitePCLRaw.lib.e_sqlite3.v110_xp" version="1.1.6" targetFramework="net461" />
+ <package id="SQLitePCLRaw.provider.e_sqlite3.net45" version="1.1.6" targetFramework="net461" />
+ <package id="System.AppContext" version="4.3.0" targetFramework="net461" />
+ <package id="System.Collections" version="4.3.0" targetFramework="net461" />
+ <package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net461" />
+ <package id="System.Collections.Immutable" version="1.3.1" targetFramework="net461" />
+ <package id="System.Composition.AttributedModel" version="1.0.31" targetFramework="net461" />
+ <package id="System.Composition.Hosting" version="1.0.31" targetFramework="net461" />
+ <package id="System.Composition.Runtime" version="1.0.31" targetFramework="net461" />
+ <package id="System.Composition.TypedParts" version="1.0.31" targetFramework="net461" />
+ <package id="System.Console" version="4.3.0" targetFramework="net461" />
+ <package id="System.Diagnostics.Contracts" version="4.3.0" targetFramework="net461" />
+ <package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net461" />
+ <package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net461" />
+ <package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net461" />
+ <package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net461" />
+ <package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net461" />
+ <package id="System.Globalization" version="4.3.0" targetFramework="net461" />
+ <package id="System.IO.Compression" version="4.3.0" targetFramework="net461" />
+ <package id="System.IO.FileSystem" version="4.3.0" targetFramework="net461" />
+ <package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net461" />
+ <package id="System.Linq" version="4.3.0" targetFramework="net461" />
+ <package id="System.Linq.Expressions" version="4.3.0" targetFramework="net461" />
+ <package id="System.Linq.Parallel" version="4.3.0" targetFramework="net461" />
+ <package id="System.ObjectModel" version="4.3.0" targetFramework="net461" />
+ <package id="System.Reflection" version="4.3.0" targetFramework="net461" />
+ <package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net461" />
+ <package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net461" />
+ <package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net461" />
+ <package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net461" />
+ <package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net461" />
+ <package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net461" />
+ <package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net461" />
+ <package id="System.Text.Encoding" version="4.3.0" targetFramework="net461" />
+ <package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net461" />
+ <package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net461" />
+ <package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net461" />
+ <package id="System.Threading" version="4.3.0" targetFramework="net461" />
+ <package id="System.Threading.Tasks" version="4.3.0" targetFramework="net461" />
+ <package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net461" />
+ <package id="System.Threading.Thread" version="4.3.0" targetFramework="net461" />
+ <package id="System.ValueTuple" version="4.4.0" targetFramework="net461" />
+ <package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net461" />
+ <package id="System.Xml.XDocument" version="4.3.0" targetFramework="net461" />
+ <package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net461" />
+ <package id="System.Xml.XPath" version="4.3.0" targetFramework="net461" />
+ <package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net461" />
+</packages>
diff --git a/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml b/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml index 4423f6f512..e9da2928c4 100644 --- a/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml +++ b/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml @@ -401,6 +401,8 @@ </Extension> <Extension path="/MonoDevelop/Ide/Composition"> + <Assembly file="Microsoft.CodeAnalysis.CSharp.EditorFeatures.dll"/> + <Assembly file="Microsoft.CodeAnalysis.EditorFeatures.dll"/> <Assembly file="Microsoft.CodeAnalysis.Features.dll" /> <Assembly file="Microsoft.CodeAnalysis.Workspaces.dll" /> <Assembly file="Microsoft.CodeAnalysis.Workspaces.Desktop.dll" /> @@ -410,6 +412,8 @@ <Assembly file="Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll" /> <Assembly file="MonoDevelop.Ide.dll"/> <Assembly file="Microsoft.VisualStudio.Text.Implementation.dll"/> + <Assembly file="Microsoft.VisualStudio.Text.Logic.dll"/> + <Assembly file="Microsoft.VisualStudio.Text.UI.dll"/> <Assembly file="Microsoft.VisualStudio.Language.StandardClassification.dll"/> <Assembly file="Microsoft.CodeAnalysis.CSharp.EditorFeatures.dll" /> </Extension> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ContextMenuExtensionsMac.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ContextMenuExtensionsMac.cs index 30706dedee..34f8345a9c 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ContextMenuExtensionsMac.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ContextMenuExtensionsMac.cs @@ -157,7 +157,8 @@ namespace MonoDevelop.Components public NSContextMenuItem (string label, ContextMenuItem item) : base (label) { contextMenu = new WeakReference<ContextMenuItem> (item); - this.Activated += OnActivated; + if (item.SubMenu == null || item.SubMenu.Items.Count == 0) + this.Activated += OnActivated; } static void OnActivated (object sender, EventArgs args) @@ -195,6 +196,7 @@ namespace MonoDevelop.Components public NSLocationAwareMenu (ContextMenu menu, Action closeHandler, NSLocationAwareMenu parent) { WeakDelegate = new ContextMenuDelegate (menu) { CloseHandler = closeHandler }; + Parent = parent != null ? new WeakReference<NSLocationAwareMenu> (parent) : null; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionController.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionController.cs index 1263f0f5a7..00e5de7e0c 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionController.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionController.cs @@ -1,4 +1,4 @@ -// +// // CodeCompletionSession.cs // // Author: @@ -32,6 +32,7 @@ using Gtk; using MonoDevelop.Ide.Editor.Extension; using Xwt.Drawing; using MonoDevelop.Core; +using MonoDevelop.Ide.Editor; namespace MonoDevelop.Ide.CodeCompletion { @@ -205,7 +206,6 @@ namespace MonoDevelop.Ide.CodeCompletion initialWordLength = CompletionWidget.SelectedLength > 0 ? 0 : text.Length; StartOffset = CompletionWidget.CaretOffset - initialWordLength; - HideWhenWordDeleted = initialWordLength != 0; ResetSizes (); UpdateWordSelection (); @@ -259,7 +259,6 @@ namespace MonoDevelop.Ide.CodeCompletion { usingPreviewEntry = false; previewCompletionEntryText = ""; - HideWhenWordDeleted = false; SelectedItemCompletionText = null; ResetViewState(); } @@ -505,10 +504,6 @@ namespace MonoDevelop.Ide.CodeCompletion get { return initialWordLength; } } - bool HideWhenWordDeleted { - get; set; - } - public CompletionTextEditorExtension Extension { get; set; @@ -710,15 +705,7 @@ namespace MonoDevelop.Ide.CodeCompletion set; } - int startOffset; - internal int StartOffset { - get { - return startOffset; - } - set { - startOffset = value; - } - } + internal int StartOffset { get; set; } public int EndOffset { get; @@ -1058,13 +1045,10 @@ namespace MonoDevelop.Ide.CodeCompletion public KeyActions PostProcessKey (KeyDescriptor descriptor) { - if (CompletionWidget == null || StartOffset > CompletionWidget.CaretOffset) {// CompletionWidget == null may happen in unit tests. + if (CompletionWidget == null) {// CompletionWidget == null may happen in unit tests. return KeyActions.CloseWindow | KeyActions.Process; } - if (HideWhenWordDeleted && StartOffset >= CompletionWidget.CaretOffset) { - return KeyActions.CloseWindow | KeyActions.Process; - } switch (descriptor.SpecialKey) { case SpecialKey.BackSpace: ResetSizes (); @@ -1166,7 +1150,7 @@ namespace MonoDevelop.Ide.CodeCompletion void UpdateLastWordChar () { if (CompletionWidget != null) - EndOffset = CompletionWidget.CaretOffset; + EndOffset = Math.Max (StartOffset, CompletionWidget.CaretOffset); } void SelectEntry (string s) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionWindowManager.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionWindowManager.cs index df602402bb..eac7135294 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionWindowManager.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionWindowManager.cs @@ -28,6 +28,7 @@ using System; using MonoDevelop.Core; using MonoDevelop.Ide.Gui.Content; using MonoDevelop.Ide.Editor.Extension; +using MonoDevelop.Ide.Editor; namespace MonoDevelop.Ide.CodeCompletion { @@ -176,15 +177,26 @@ namespace MonoDevelop.Ide.CodeCompletion return wnd.PreProcessKeyEvent (descriptor); } + static bool isInUpdate; public static void UpdateCursorPosition () { if (!IsVisible) return; - if (wnd.IsInCompletion || isShowing) + if (wnd.IsInCompletion || isShowing || isInUpdate) return; - var caretOffset = wnd.CompletionWidget.CaretOffset; - if (caretOffset < wnd.StartOffset || caretOffset > wnd.EndOffset + 1) { - HideWindow (); + isInUpdate = true; + try { + ITextEditorImpl impl = wnd.CompletionWidget as ITextEditorImpl; + if (impl != null) + impl.EnsureCaretIsNotVirtual (); + var caretOffset = wnd.CompletionWidget.CaretOffset; + if (caretOffset < wnd.StartOffset || caretOffset > wnd.EndOffset + 1) { + HideWindow (); + } + if (impl != null) + impl.FixVirtualIndentation (); + } finally { + isInUpdate = false; } } @@ -213,8 +225,9 @@ namespace MonoDevelop.Ide.CodeCompletion public static void HideWindow () { isShowing = false; - if (IsVisible) + if (IsVisible) { wnd.HideWindow (); + } } static void HandleWndVisibleChanged (object sender, EventArgs args) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/InlineRenameService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/InlineRenameService.cs new file mode 100644 index 0000000000..a13d665518 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/InlineRenameService.cs @@ -0,0 +1,24 @@ +using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Editor;
+using Microsoft.CodeAnalysis.Text;
+
+namespace MonoDevelop.Ide.Composition
+{
+ [Export(typeof(IInlineRenameService))]
+ internal class InlineRenameService : IInlineRenameService
+ {
+ public IInlineRenameSession ActiveSession => null;
+
+ public InlineRenameSessionInfo StartInlineSession (Document document, TextSpan triggerSpan, CancellationToken cancellationToken = default (CancellationToken))
+ {
+ throw new NotImplementedException ();
+ }
+ }
+}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/PlatformCatalog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/PlatformCatalog.cs index c0148e3a65..2e8163e388 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/PlatformCatalog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/PlatformCatalog.cs @@ -94,7 +94,7 @@ namespace Microsoft.VisualStudio.Platform return mimeType;
}
- return null;
+ return (ContentTypeRegistryService as IContentTypeRegistryService2).GetMimeType (type);
}
public IContentType GetContentType(string type)
@@ -105,7 +105,7 @@ namespace Microsoft.VisualStudio.Platform return contentType;
}
- return null;
+ return (ContentTypeRegistryService as IContentTypeRegistryService2).GetContentTypeForMimeType (type);
}
public void LinkTypes(string mimeType, IContentType contentType)
@@ -136,14 +136,6 @@ namespace Microsoft.VisualStudio.Platform {
LinkTypes ("text/plain", "text");
LinkTypes ("text/x-csharp", "csharp");
-
- if (this.ContentTypeRegistryService.GetContentType ("css") != null) {
- LinkTypes ("text/x-css", "css");
- LinkTypes ("text/x-less-web", "LESS");
- LinkTypes ("text/x-scss-web", "SCSS");
- LinkTypes ("text/x-html", "htmlx");
- LinkTypes ("text/x-json", "JSON");
- }
}
Tuple<ImmutableDictionary<string, IContentType>, ImmutableDictionary<IContentType, string>> maps = Tuple.Create(ImmutableDictionary<string, IContentType>.Empty, ImmutableDictionary<IContentType, string>.Empty);
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs index 486c780048..3355502f17 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs @@ -34,6 +34,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text.RegularExpressions; +using Microsoft.VisualStudio.Platform;
+using Microsoft.VisualStudio.Utilities; + using Mono.Addins; using MonoDevelop.Core; using Mono.Unix; @@ -41,8 +44,8 @@ using MonoDevelop.Ide.Extensions; using MonoDevelop.Core.Execution; using MonoDevelop.Components; using MonoDevelop.Components.MainToolbar; - - +using MonoDevelop.Ide.Composition;
+
namespace MonoDevelop.Ide.Desktop { public abstract class PlatformService @@ -296,6 +299,19 @@ namespace MonoDevelop.Ide.Desktop MimeTypeNode FindMimeTypeForFile (string fileName) { + IFilePathRegistryService filePathRegistryService = CompositionManager.GetExportedValue<IFilePathRegistryService> ();
+
+ IContentType contentType = filePathRegistryService.GetContentTypeForPath (fileName);
+ if (contentType != PlatformCatalog.Instance.ContentTypeRegistryService.UnknownContentType) {
+ string mimeType = PlatformCatalog.Instance.MimeToContentTypeRegistryService.GetMimeType(contentType);
+ if (mimeType != null) {
+ MimeTypeNode mt = FindMimeType(mimeType);
+ if (mt != null) {
+ return mt;
+ }
+ }
+ }
+ foreach (MimeTypeNode mt in mimeTypeNodes) { if (mt.SupportsFile (fileName)) return mt; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/EditActions.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/EditActions.cs index 9fdeca3319..ea6fe11b15 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/EditActions.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/EditActions.cs @@ -37,129 +37,129 @@ namespace MonoDevelop.Ide.Editor { public static void MoveCaretDown (TextEditor editor) { - editor.EditorActionHost.MoveCaretDown (); + editor.EditorOperations.MoveLineDown (false); } public static void MoveCaretUp (TextEditor editor) { - editor.EditorActionHost.MoveCaretUp (); + editor.EditorOperations.MoveLineUp (false); } public static void MoveCaretRight (TextEditor editor) { - editor.EditorActionHost.MoveCaretRight (); + editor.EditorOperations.MoveToPreviousCharacter (false); } public static void MoveCaretLeft (TextEditor editor) { - editor.EditorActionHost.MoveCaretLeft (); + editor.EditorOperations.MoveToNextCharacter (false); } public static void MoveCaretToLineEnd (TextEditor editor) { - editor.EditorActionHost.MoveCaretToLineEnd (); + editor.EditorOperations.MoveToEndOfLine (false); } public static void MoveCaretToLineStart (TextEditor editor) { - editor.EditorActionHost.MoveCaretToLineStart (); + editor.EditorOperations.MoveToStartOfLine (false); } public static void MoveCaretToDocumentStart (TextEditor editor) { - editor.EditorActionHost.MoveCaretToDocumentStart (); + editor.EditorOperations.MoveToStartOfDocument (false); } public static void MoveCaretToDocumentEnd (TextEditor editor) { - editor.EditorActionHost.MoveCaretToDocumentEnd (); + editor.EditorOperations.MoveToEndOfDocument (false); } public static void Backspace (TextEditor editor) { - editor.EditorActionHost.Backspace (); + editor.EditorOperations.Backspace (); } public static void Delete (TextEditor editor) { - editor.EditorActionHost.Delete (); + editor.EditorOperations.Delete (); } public static void ClipboardCopy (TextEditor editor) { - editor.EditorActionHost.ClipboardCopy (); + editor.EditorOperations.CopySelection (); } public static void ClipboardCut (TextEditor editor) { - editor.EditorActionHost.ClipboardCut (); + editor.EditorOperations.CutSelection (); } public static void ClipboardPaste (TextEditor editor) { - editor.EditorActionHost.ClipboardPaste (); + editor.EditorOperations.Paste (); } public static void SelectAll (TextEditor editor) { - editor.EditorActionHost.SelectAll (); + editor.EditorOperations.SelectAll (); } public static void NewLine (TextEditor editor) { - editor.EditorActionHost.NewLine (); + editor.EditorOperations.InsertNewLine (); } public static void PageUp (TextEditor textEditor) { - textEditor.EditorActionHost.PageUp (); + textEditor.EditorOperations.PageUp (false); } public static void PageDown (TextEditor textEditor) { - textEditor.EditorActionHost.PageDown (); + textEditor.EditorOperations.PageDown (false); } public static void Undo (TextEditor editor) { - editor.EditorActionHost.Undo (); + ((IMonoDevelopEditorOperations)editor.EditorOperations).Undo (); } public static void Redo (TextEditor editor) { - editor.EditorActionHost.Redo (); + ((IMonoDevelopEditorOperations)editor.EditorOperations).Redo (); } public static void DeleteCurrentLine (TextEditor textEditor) { - textEditor.EditorActionHost.DeleteCurrentLine (); + textEditor.EditorOperations.DeleteFullLine (); } public static void DeleteCurrentLineToEnd (TextEditor textEditor) { - textEditor.EditorActionHost.DeleteCurrentLineToEnd (); + textEditor.EditorOperations.DeleteToEndOfLine (); } public static void ScrollLineUp (TextEditor textEditor) { - textEditor.EditorActionHost.ScrollLineUp (); + textEditor.EditorOperations.ScrollLineTop (); } public static void ScrollLineDown (TextEditor textEditor) { - textEditor.EditorActionHost.ScrollLineDown (); + textEditor.EditorOperations.ScrollLineBottom (); } public static void ScrollPageUp (TextEditor textEditor) { - textEditor.EditorActionHost.ScrollPageUp (); + textEditor.EditorOperations.ScrollPageUp (); } public static void ScrollPageDown (TextEditor textEditor) { - textEditor.EditorActionHost.ScrollPageDown (); + textEditor.EditorOperations.ScrollPageDown (); } public static void GotoMatchingBrace (TextEditor textEditor) @@ -171,27 +171,27 @@ namespace MonoDevelop.Ide.Editor public static void MovePrevWord (TextEditor textEditor) { - textEditor.EditorActionHost.MovePrevWord (); + textEditor.EditorOperations.MoveToPreviousWord (false); } public static void MoveNextWord (TextEditor textEditor) { - textEditor.EditorActionHost.MoveNextWord (); + textEditor.EditorOperations.MoveToNextWord (false); } public static void MovePrevSubWord (TextEditor textEditor) { - textEditor.EditorActionHost.MovePrevSubWord (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).MoveToPrevSubWord (); } public static void MoveNextSubWord (TextEditor textEditor) { - textEditor.EditorActionHost.MoveNextSubWord (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).MoveToNextSubWord (); } public static void ShowQuickInfo (TextEditor textEditor) { - textEditor.EditorActionHost.ShowQuickInfo (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).ShowQuickInfo (); } @@ -268,37 +268,37 @@ namespace MonoDevelop.Ide.Editor public static void JoinLines (TextEditor textEditor) { - textEditor.EditorActionHost.JoinLines (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).JoinLines (); } public static void RecenterEditor (TextEditor textEditor) { - textEditor.EditorActionHost.RecenterEditor (); + textEditor.EditorOperations.ScrollLineCenter (); } public static void StartCaretPulseAnimation (TextEditor textEditor) { - textEditor.EditorActionHost.StartCaretPulseAnimation (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).StartCaretPulseAnimation (); } public static void DeleteNextSubword (TextEditor textEditor) { - textEditor.EditorActionHost.DeleteNextSubword (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).DeleteNextSubword (); } public static void DeletePreviousSubword (TextEditor textEditor) { - textEditor.EditorActionHost.DeletePreviousSubword (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).DeletePreviousSubword (); } public static void DeleteNextWord (TextEditor textEditor) { - textEditor.EditorActionHost.DeleteNextWord (); + textEditor.EditorOperations.DeleteWordToRight (); } public static void DeletePreviousWord (TextEditor textEditor) { - textEditor.EditorActionHost.DeletePreviousWord (); + textEditor.EditorOperations.DeleteWordToLeft (); } public static void InsertNewLinePreserveCaretPosition (TextEditor textEditor) @@ -324,47 +324,47 @@ namespace MonoDevelop.Ide.Editor public static void InsertNewLine (TextEditor textEditor) { - textEditor.EditorActionHost.InsertNewLine (); + textEditor.EditorOperations.InsertNewLine (); } public static void RemoveTab (TextEditor textEditor) { - textEditor.EditorActionHost.RemoveTab (); + textEditor.EditorOperations.Untabify (); } public static void InsertTab (TextEditor textEditor) { - textEditor.EditorActionHost.InsertTab (); + textEditor.EditorOperations.Tabify (); } public static void SwitchCaretMode (TextEditor textEditor) { - textEditor.EditorActionHost.SwitchCaretMode (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).SwitchCaretMode (); } public static void MoveBlockUp (TextEditor textEditor) { - textEditor.EditorActionHost.MoveBlockUp (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).MoveBlockUp (); } public static void MoveBlockDown (TextEditor textEditor) { - textEditor.EditorActionHost.MoveBlockDown (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).MoveBlockDown (); } public static void ToggleBlockSelectionMode (TextEditor textEditor) { - textEditor.EditorActionHost.ToggleBlockSelectionMode (); + ((IMonoDevelopEditorOperations)textEditor.EditorOperations).ToggleBlockSelectionMode (); } public static void IndentSelection (TextEditor editor) { - editor.EditorActionHost.IndentSelection (); + editor.EditorOperations.Indent (); } public static void UnIndentSelection (TextEditor editor) { - editor.EditorActionHost.UnIndentSelection (); + editor.EditorOperations.Unindent (); } #region SelectionActions diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IEditorActionHost.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IEditorActionHost.cs deleted file mode 100644 index 81fccd8303..0000000000 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IEditorActionHost.cs +++ /dev/null @@ -1,125 +0,0 @@ -// -// ITextEditor.cs -// -// Author: -// Mike Krüger <mkrueger@xamarin.com> -// -// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -using System; -using MonoDevelop.Core.Text; - -namespace MonoDevelop.Ide.Editor -{ - interface IEditorActionHost - { - void SwitchCaretMode (); - - void InsertTab (); - - void RemoveTab (); - - void InsertNewLine (); - - void DeletePreviousWord (); - - void DeleteNextWord (); - - void DeletePreviousSubword (); - - void DeleteNextSubword (); - - void StartCaretPulseAnimation (); - - void RecenterEditor (); - - void JoinLines (); - - void MoveNextSubWord (); - - void MovePrevSubWord (); - - void MoveNextWord (); - - void MovePrevWord (); - - void PageUp (); - - void PageDown (); - - void MoveCaretDown (); - - void MoveCaretUp (); - - void MoveCaretRight (); - - void MoveCaretLeft (); - - void MoveCaretToLineEnd (); - - void MoveCaretToLineStart (); - - void MoveCaretToDocumentStart (); - - void MoveCaretToDocumentEnd (); - - void Backspace (); - - void Delete (); - - void ClipboardCopy (); - - void ClipboardCut (); - - void ClipboardPaste (); - - void SelectAll (); - - void NewLine (); - - void Undo (); - - void Redo (); - - void DeleteCurrentLine (); - - void DeleteCurrentLineToEnd (); - - void ScrollLineUp (); - - void ScrollLineDown (); - - void ScrollPageUp (); - - void ScrollPageDown (); - - void MoveBlockUp (); - - void MoveBlockDown (); - - void ToggleBlockSelectionMode (); - - void IndentSelection (); - - void UnIndentSelection (); - - void ShowQuickInfo (); - } -}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/ValidCodeAction.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IMonoDevelopEditorOperations.cs index 803cd429be..3819dc85ab 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/ValidCodeAction.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/IMonoDevelopEditorOperations.cs @@ -1,10 +1,10 @@ -// -// ValidCodeAction.cs +// +// ITextEditor.cs // // Author: // Mike Krüger <mkrueger@xamarin.com> // -// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// Copyright (c) 2014 Xamarin Inc. (http://xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,25 +23,37 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System; +using MonoDevelop.Core.Text; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.Text; - -namespace MonoDevelop.CodeActions +namespace MonoDevelop.Ide.Editor { - /// <summary> - /// Represents a code action that's valid at a specific segment. - /// </summary> - class ValidCodeAction + interface IMonoDevelopEditorOperations : Microsoft.VisualStudio.Text.Operations.IEditorOperations { - public CodeAction CodeAction { get; private set; } + void SwitchCaretMode (); + + void DeletePreviousSubword (); + + void DeleteNextSubword (); + + void StartCaretPulseAnimation (); + + void JoinLines (); + + void MoveToNextSubWord (); + + void MoveToPrevSubWord (); + + void Undo (); + + void Redo (); + + void MoveBlockUp (); + + void MoveBlockDown (); - public TextSpan ValidSegment { get; private set; } + void ToggleBlockSelectionMode (); - public ValidCodeAction (CodeAction codeAction, TextSpan validSegment) - { - this.CodeAction = codeAction; - this.ValidSegment = validSegment; - } + void ShowQuickInfo (); } }
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs index e0d1a9d7b3..36eae1181f 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs @@ -46,6 +46,8 @@ namespace MonoDevelop.Ide.Editor interface ITextEditorImpl : IDisposable { + Microsoft.VisualStudio.Text.Editor.ITextView TextView { get; set; } + ViewContent ViewContent { get; } string ContentName { get; set; } @@ -98,7 +100,7 @@ namespace MonoDevelop.Ide.Editor void FixVirtualIndentation (); - IEditorActionHost Actions { get; } + IMonoDevelopEditorOperations Actions { get; } ITextMarkerFactory TextMarkerFactory { get; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs index 3e67d61419..01df4804d9 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs @@ -50,7 +50,7 @@ namespace MonoDevelop.Ide.Editor public sealed class TextEditor : Control, ITextDocument, IDisposable { readonly ITextEditorImpl textEditorImpl; - public Microsoft.VisualStudio.Text.Editor.ITextView TextView { get; } + public Microsoft.VisualStudio.Text.Editor.ITextView TextView { get => textEditorImpl.TextView; set => textEditorImpl.TextView = value; } IReadonlyTextDocument ReadOnlyTextDocument { get { return textEditorImpl.Document; } } @@ -970,6 +970,8 @@ namespace MonoDevelop.Ide.Editor if (isDisposed) return; Runtime.AssertMainThread (); + this.TextView.Close (); + // Break fileTypeCondition circular event handling reference. fileTypeCondition = null; isDisposed = true; @@ -980,8 +982,6 @@ namespace MonoDevelop.Ide.Editor provider.Dispose (); textEditorImpl.Dispose (); - this.TextView.Close();
- base.Dispose (disposing); } @@ -1002,7 +1002,7 @@ namespace MonoDevelop.Ide.Editor } } - internal IEditorActionHost EditorActionHost { + internal Microsoft.VisualStudio.Text.Operations.IEditorOperations EditorOperations { get { return textEditorImpl.Actions; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorDisplayBinding.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorDisplayBinding.cs index ba49053020..f3f90d19b6 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorDisplayBinding.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorDisplayBinding.cs @@ -97,18 +97,12 @@ namespace MonoDevelop.Ide.Editor {
TextEditor editor;
- // HACK: this is a very poor test for whether to load the document. Maybe add an IsPlaceholder property to FilePath.
- // Another alternative would be to add a parameter to CreateContent.
- if (File.Exists(fileName))
- {
- editor = TextEditorFactory.CreateNewEditor(fileName, mimeType);
- }
- else
- {
- editor = TextEditorFactory.CreateNewEditor();
- editor.FileName = fileName; - editor.MimeType = mimeType;
- }
+ // HACK: CreateNewEditor really needs to know whether the document exists (& should be loaded)
+ // or we're creating an empty document with the given file name & mime type.
+ //
+ // That information could be added to FilePath but fileName is converted to a string below
+ // which means the information is lost.
+ editor = TextEditorFactory.CreateNewEditor(fileName, mimeType);
editor.GetViewContent ().Project = ownerProject; return editor.GetViewContent (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs index 3b2d881171..58f710ad14 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs @@ -500,20 +500,7 @@ namespace MonoDevelop.Ide.FindInFiles Visible = true, Ready = true, }; - - var checkMenuItem = searchentryFileMask.AddFilterOption (0, GettextCatalog.GetString ("Include binary files")); - checkMenuItem.DrawAsRadio = false; - checkMenuItem.Active = properties.Get ("IncludeBinaryFiles", false); - checkMenuItem.Toggled += delegate { - properties.Set ("IncludeBinaryFiles", checkMenuItem.Active); - }; - - var checkMenuItem1 = searchentryFileMask.AddFilterOption (1, GettextCatalog.GetString ("Include hidden files and directories")); - checkMenuItem1.DrawAsRadio = false; - checkMenuItem1.Active = properties.Get ("IncludeHiddenFiles", false); - checkMenuItem1.Toggled += delegate { - properties.Set ("IncludeHiddenFiles", checkMenuItem1.Active); - }; + searchentryFileMask.Query = properties.Get ("MonoDevelop.FindReplaceDialogs.FileMask", ""); @@ -793,15 +780,12 @@ namespace MonoDevelop.Ide.FindInFiles return null; } - scope = new DirectoryScope (comboboxentryPath.Entry.Text, checkbuttonRecursively.Active) { - IncludeHiddenFiles = properties.Get ("IncludeHiddenFiles", false) - }; + scope = new DirectoryScope (comboboxentryPath.Entry.Text, checkbuttonRecursively.Active); break; default: throw new ApplicationException ("Unknown scope:" + comboboxScope.Active); } - scope.IncludeBinaryFiles = properties.Get ("IncludeBinaryFiles", false); return scope; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/Scope.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/Scope.cs index 9da97c4c0b..19a08d442f 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/Scope.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/Scope.cs @@ -39,6 +39,7 @@ namespace MonoDevelop.Ide.FindInFiles { public abstract class Scope { + [Obsolete ("Unused - will be removed")] public bool IncludeBinaryFiles { get; set; @@ -145,7 +146,7 @@ namespace MonoDevelop.Ide.FindInFiles () => new List<FileProvider> (), (folder, loop, providers) => { foreach (var file in folder.Files.Where (f => filterOptions.NameMatches (f.FileName) && File.Exists (f.FullPath))) { - if (!IncludeBinaryFiles && !DesktopService.GetFileIsText (file.FullPath)) + if (!DesktopService.GetFileIsText (file.FullPath)) continue; lock (alreadyVisited) { if (alreadyVisited.Contains (file.FullPath)) @@ -171,7 +172,7 @@ namespace MonoDevelop.Ide.FindInFiles foreach (ProjectFile file in project.GetSourceFilesAsync (conf).Result.Where (f => filterOptions.NameMatches (f.Name) && File.Exists (f.Name))) { if ((file.Flags & ProjectItemFlags.Hidden) == ProjectItemFlags.Hidden) continue; - if (!IncludeBinaryFiles && !DesktopService.GetFileIsText (file.FilePath)) + if (!DesktopService.GetFileIsText (file.FilePath)) continue; lock (alreadyVisited) { @@ -227,7 +228,7 @@ namespace MonoDevelop.Ide.FindInFiles foreach (ProjectFile file in project.GetSourceFilesAsync (conf).Result.Where (f => filterOptions.NameMatches (f.Name) && File.Exists (f.Name))) { if ((file.Flags & ProjectItemFlags.Hidden) == ProjectItemFlags.Hidden) continue; - if (!IncludeBinaryFiles && !DesktopService.GetFileIsText (file.Name)) + if (!DesktopService.GetFileIsText (file.Name)) continue; if (alreadyVisited.Contains (file.FilePath.FullPath)) continue; @@ -280,6 +281,7 @@ namespace MonoDevelop.Ide.FindInFiles get { return PathMode.Absolute; } } + [Obsolete ("Unused - will be removed")] public bool IncludeHiddenFiles { get; set; @@ -315,33 +317,29 @@ namespace MonoDevelop.Ide.FindInFiles } foreach (string fileName in Directory.EnumerateFiles (curPath, "*")) { - if (!IncludeHiddenFiles) { - if (Platform.IsWindows) { - var attr = File.GetAttributes (fileName); - if (attr.HasFlag (FileAttributes.Hidden)) - continue; - } - if (Path.GetFileName (fileName).StartsWith (".", StringComparison.Ordinal)) + if (Platform.IsWindows) { + var attr = File.GetAttributes (fileName); + if (attr.HasFlag (FileAttributes.Hidden)) continue; } + if (Path.GetFileName (fileName).StartsWith (".", StringComparison.Ordinal)) + continue; if (!filterOptions.NameMatches (fileName)) continue; - if (!IncludeBinaryFiles && !DesktopService.GetFileIsText (fileName)) + if (!DesktopService.GetFileIsText (fileName)) continue; yield return fileName; } if (recurse) { foreach (string directoryName in Directory.EnumerateDirectories (curPath)) { - if (!IncludeHiddenFiles) { - if (Platform.IsWindows) { - var attr = File.GetAttributes (directoryName); - if (attr.HasFlag (FileAttributes.Hidden)) - continue; - } - if (Path.GetFileName (directoryName).StartsWith (".", StringComparison.Ordinal)) + if (Platform.IsWindows) { + var attr = File.GetAttributes (directoryName); + if (attr.HasFlag (FileAttributes.Hidden)) continue; } + if (Path.GetFileName (directoryName).StartsWith (".", StringComparison.Ordinal)) + continue; directoryStack.Push (directoryName); } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Document.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Document.cs index 6abe1511a5..c7f81d654c 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Document.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Document.cs @@ -862,7 +862,7 @@ namespace MonoDevelop.Ide.Gui return Task.CompletedTask; SubscribeRoslynWorkspace (); analysisDocument = FileName != null ? TypeSystemService.GetDocumentId (this.Project, this.FileName) : null; - if (analysisDocument != null) { + if (analysisDocument != null && !RoslynWorkspace.IsDocumentOpen(analysisDocument)) { TypeSystemService.InformDocumentOpen (analysisDocument, Editor); OnAnalysisDocumentChanged (EventArgs.Empty); return Task.CompletedTask; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/ViewCommandHandlers.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/ViewCommandHandlers.cs index fd2177cd6f..864ca29f0e 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/ViewCommandHandlers.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/ViewCommandHandlers.cs @@ -359,61 +359,61 @@ namespace MonoDevelop.Ide.Gui [CommandHandler (TextEditorCommands.LineEnd)] protected void OnLineEnd () { - doc.Editor.EditorActionHost.MoveCaretToLineEnd (); + doc.Editor.EditorOperations.MoveToEndOfLine (false); } [CommandHandler (TextEditorCommands.LineStart)] protected void OnLineStart () { - doc.Editor.EditorActionHost.MoveCaretToLineStart (); + doc.Editor.EditorOperations.MoveToStartOfLine (false); } [CommandHandler (TextEditorCommands.DeleteLeftChar)] protected void OnDeleteLeftChar () { - doc.Editor.EditorActionHost.Backspace (); + doc.Editor.EditorOperations.Backspace (); } [CommandHandler (TextEditorCommands.DeleteRightChar)] protected void OnDeleteRightChar () { - doc.Editor.EditorActionHost.Delete (); + doc.Editor.EditorOperations.Delete (); } [CommandHandler (TextEditorCommands.CharLeft)] protected void OnCharLeft () { - doc.Editor.EditorActionHost.MoveCaretLeft (); + doc.Editor.EditorOperations.MoveToPreviousCharacter (false); } [CommandHandler (TextEditorCommands.CharRight)] protected void OnCharRight () { - doc.Editor.EditorActionHost.MoveCaretRight (); + doc.Editor.EditorOperations.MoveToNextCharacter (false); } [CommandHandler (TextEditorCommands.LineUp)] protected void OnLineUp () { - doc.Editor.EditorActionHost.MoveCaretUp (); + doc.Editor.EditorOperations.MoveLineUp (false); } [CommandHandler (TextEditorCommands.LineDown)] protected void OnLineDown () { - doc.Editor.EditorActionHost.MoveCaretDown (); + doc.Editor.EditorOperations.MoveLineDown (false); } [CommandHandler (TextEditorCommands.DocumentStart)] protected void OnDocumentStart () { - doc.Editor.EditorActionHost.MoveCaretToDocumentStart (); + doc.Editor.EditorOperations.MoveToStartOfDocument (false); } [CommandHandler (TextEditorCommands.DocumentEnd)] protected void OnDocumentEnd () { - doc.Editor.EditorActionHost.MoveCaretToDocumentEnd (); + doc.Editor.EditorOperations.MoveToEndOfDocument (false); } [CommandHandler (TextEditorCommands.DeleteLine)] diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/IMonoDevelopHostDocument.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/IMonoDevelopHostDocument.cs new file mode 100644 index 0000000000..4c4871116b --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/IMonoDevelopHostDocument.cs @@ -0,0 +1,52 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.VisualStudio.Text;
+
+namespace MonoDevelop.Ide.TypeSystem
+{
+ interface IMonoDevelopHostDocument
+ {
+ /// <summary>
+ /// Updates the text of the document.
+ /// </summary>
+ void UpdateText (SourceText newText);
+ }
+
+ static class MonoDevelopHostDocumentRegistration
+ {
+ internal static void Register (Document document, IMonoDevelopHostDocument hostDocument)
+ {
+ if (document.TryGetText (out SourceText sourceText)) {
+ ITextBuffer textBuffer = sourceText.Container.TryGetTextBuffer ();
+
+ textBuffer?.Properties.AddProperty (typeof (IMonoDevelopHostDocument), hostDocument);
+ }
+ }
+
+ internal static void UnRegister(Document document)
+ {
+ if (document.TryGetText (out SourceText sourceText)) {
+ ITextBuffer textBuffer = sourceText.Container.TryGetTextBuffer ();
+
+ textBuffer?.Properties.RemoveProperty (typeof (IMonoDevelopHostDocument));
+ }
+ }
+
+ internal static IMonoDevelopHostDocument FromDocument(Document document)
+ {
+ IMonoDevelopHostDocument containedDocument = null;
+ if (document.TryGetText (out SourceText sourceText)) {
+ ITextBuffer textBuffer = sourceText.Container.TryGetTextBuffer ();
+
+ containedDocument = textBuffer?.Properties.GetProperty<IMonoDevelopHostDocument> (typeof (IMonoDevelopHostDocument));
+ }
+
+ return containedDocument;
+ }
+ }
+}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs index 15c3947bbb..1a604658fc 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs @@ -103,8 +103,8 @@ namespace MonoDevelop.Ide.TypeSystem IdeApp.Workspace.ActiveConfigurationChanged += HandleActiveConfigurationChanged;
}
ISolutionCrawlerRegistrationService solutionCrawler = Services.GetService<ISolutionCrawlerRegistrationService> ();
- //Options = Options.WithChangedOption (Microsoft.CodeAnalysis.Diagnostics.InternalRuntimeDiagnosticOptions.Syntax, true)
- // .WithChangedOption (Microsoft.CodeAnalysis.Diagnostics.InternalRuntimeDiagnosticOptions.Semantic, true);
+ Options = Options.WithChangedOption (Microsoft.CodeAnalysis.Diagnostics.InternalRuntimeDiagnosticOptions.Syntax, true)
+ .WithChangedOption (Microsoft.CodeAnalysis.Diagnostics.InternalRuntimeDiagnosticOptions.Semantic, true);
if (IdeApp.Preferences.EnableSourceAnalysis) {
solutionCrawler.Register (this);
@@ -755,6 +755,16 @@ namespace MonoDevelop.Ide.TypeSystem IdeApp.Workbench.OpenDocument (doc.FilePath, mdProject, activate);
}
}
+ } + + internal void InternalOnDocumentOpened (DocumentId documentId, SourceTextContainer textContainer, bool isCurrentContext = true) + { + OnDocumentOpened (documentId, textContainer, isCurrentContext);
+ }
+
+ internal void InternalOnDocumentClosed (DocumentId documentId, TextLoader reloader, bool updateActiveContext = false)
+ {
+ OnDocumentClosed (documentId, reloader, updateActiveContext);
}
List<MonoDevelopSourceTextContainer> openDocuments = new List<MonoDevelopSourceTextContainer>();
@@ -822,6 +832,10 @@ namespace MonoDevelop.Ide.TypeSystem if (openDoc != null) {
openDoc.Dispose ();
openDocuments.Remove (openDoc);
+ } else {
+ //Apparently something else opened this file via InternalOnDocumentOpened(e.g. .cshtml)
+ //it's job of whatever opened to also call InternalOnDocumentClosed
+ return;
}
}
if (!CurrentSolution.ContainsDocument (analysisDocument))
@@ -865,6 +879,13 @@ namespace MonoDevelop.Ide.TypeSystem var document = GetDocument (id);
if (document == null)
return;
+
+ var hostDocument = MonoDevelopHostDocumentRegistration.FromDocument (document);
+ if (hostDocument != null) {
+ hostDocument.UpdateText (text);
+ return;
+ }
+
bool isOpen;
var filePath = document.FilePath;
Projection projection = null;
@@ -1204,6 +1225,11 @@ namespace MonoDevelop.Ide.TypeSystem }
var path = DetermineFilePath (info.Id, info.Name, info.FilePath, info.Folders, mdProject?.FileName.ParentDirectory, true);
+ // If file is already part of project don't re-add it, example of this is .cshtml
+ if (mdProject?.IsFileInProject (path) == true) {
+ this.OnDocumentAdded (info);
+ return;
+ }
info = info.WithFilePath (path).WithTextLoader (new MonoDevelopTextLoader (path));
string formattedText;
@@ -1483,7 +1509,6 @@ namespace MonoDevelop.Ide.TypeSystem originalOffset = offset;
return false;
}
-
}
// static class MonoDevelopWorkspaceFeatures
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService_WorkspaceHandling.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService_WorkspaceHandling.cs index ec571ac1af..7d8da6d18a 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService_WorkspaceHandling.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService_WorkspaceHandling.cs @@ -41,7 +41,8 @@ namespace MonoDevelop.Ide.TypeSystem { public static partial class TypeSystemService { - static readonly MonoDevelopWorkspace emptyWorkspace; + //Internal for unit test + internal static readonly MonoDevelopWorkspace emptyWorkspace; static object workspaceLock = new object(); static ImmutableList<MonoDevelopWorkspace> workspaces = ImmutableList<MonoDevelopWorkspace>.Empty; @@ -116,42 +117,47 @@ namespace MonoDevelop.Ide.TypeSystem internal static async Task<List<MonoDevelopWorkspace>> Load (WorkspaceItem item, ProgressMonitor progressMonitor, CancellationToken cancellationToken = default (CancellationToken), bool showStatusIcon = true) { using (Counters.ParserService.WorkspaceItemLoaded.BeginTiming ()) { - var wsList = new List<MonoDevelopWorkspace> (); + var wsList = CreateWorkspaces (item).ToList(); //If we want BeginTiming to work correctly we need to `await` - return await InternalLoad (wsList, item, progressMonitor, cancellationToken, showStatusIcon).ContinueWith (t => { t.Wait (); return wsList; }); + await InternalLoad (wsList, progressMonitor, cancellationToken, showStatusIcon).ConfigureAwait (false); + return wsList.ToList (); } } - static Task InternalLoad (List<MonoDevelopWorkspace> list, MonoDevelop.Projects.WorkspaceItem item, ProgressMonitor progressMonitor, CancellationToken cancellationToken = default(CancellationToken), bool showStatusIcon = true) + static IEnumerable<MonoDevelopWorkspace> CreateWorkspaces (WorkspaceItem item) { - return Task.Run (async () => { - var ws = item as MonoDevelop.Projects.Workspace; - if (ws != null) { - foreach (var it in ws.Items) { - await InternalLoad (list, it, progressMonitor, cancellationToken).ConfigureAwait (false); - } - ws.ItemAdded += OnWorkspaceItemAdded; - ws.ItemRemoved += OnWorkspaceItemRemoved; - } else { - var solution = item as MonoDevelop.Projects.Solution; - if (solution != null) { - var workspace = new MonoDevelopWorkspace (solution); - lock (workspaceLock) - workspaces = workspaces.Add (workspace); - list.Add (workspace); - if (showStatusIcon) - workspace.ShowStatusIcon (); - await workspace.TryLoadSolution (cancellationToken).ConfigureAwait (false); - solution.SolutionItemAdded += OnSolutionItemAdded; - solution.SolutionItemRemoved += OnSolutionItemRemoved; - TaskCompletionSource<MonoDevelopWorkspace> request; - if (workspaceRequests.TryGetValue (solution, out request)) - request.TrySetResult (workspace); - if (showStatusIcon) - workspace.HideStatusIcon (); + if (item is MonoDevelop.Projects.Workspace ws) { + foreach (var wsItem in ws.Items) { + foreach (var mdWorkspace in CreateWorkspaces (wsItem)) { + yield return mdWorkspace; } } - }); + ws.ItemAdded += OnWorkspaceItemAdded; + ws.ItemRemoved += OnWorkspaceItemRemoved; + } else if (item is MonoDevelop.Projects.Solution solution) { + var workspace = new MonoDevelopWorkspace (solution); + lock (workspaceLock) + workspaces = workspaces.Add (workspace); + solution.SolutionItemAdded += OnSolutionItemAdded; + solution.SolutionItemRemoved += OnSolutionItemRemoved; + yield return workspace; + } + } + + static async Task InternalLoad (List<MonoDevelopWorkspace> mdWorkspaces, ProgressMonitor progressMonitor, CancellationToken cancellationToken = default (CancellationToken), bool showStatusIcon = true) + { + foreach (var workspace in mdWorkspaces) { + if (showStatusIcon) + workspace.ShowStatusIcon (); + + await workspace.TryLoadSolution (cancellationToken).ConfigureAwait (false); + TaskCompletionSource<MonoDevelopWorkspace> request; + if (workspaceRequests.TryGetValue (workspace.MonoDevelopSolution, out request)) + request.TrySetResult (workspace); + if (showStatusIcon) + workspace.HideStatusIcon (); + + } } internal static void Unload (MonoDevelop.Projects.WorkspaceItem item) @@ -256,7 +262,29 @@ namespace MonoDevelop.Ide.TypeSystem var parentSolution = project.ParentSolution; var workspace = await GetWorkspaceAsync (parentSolution, cancellationToken); var projectId = workspace.GetProjectId (project); - return projectId == null ? null : workspace.CurrentSolution.GetProject (projectId); + if (projectId == null) + throw new Exception ("Project not part of workspace"); + var proj = workspace.CurrentSolution.GetProject (projectId); + if (proj != null) + return proj; + //We assume that since we have projectId and project is not found in solution + //project is being loaded(waiting MSBuild to return list of source files) + var taskSource = new TaskCompletionSource<Microsoft.CodeAnalysis.Project> (); + EventHandler<WorkspaceChangeEventArgs> del = (s, e) => { + if (e.Kind == WorkspaceChangeKind.SolutionAdded || e.Kind == WorkspaceChangeKind.SolutionReloaded) { + proj = workspace.CurrentSolution.GetProject (projectId); + if (proj != null) + taskSource.SetResult (proj); + } + }; + cancellationToken.Register (taskSource.SetCanceled); + workspace.WorkspaceChanged += del; + try { + proj = await taskSource.Task; + } finally { + workspace.WorkspaceChanged -= del; + } + return proj; } public static Task<Compilation> GetCompilationAsync (MonoDevelop.Projects.Project project, CancellationToken cancellationToken = default(CancellationToken)) @@ -277,7 +305,7 @@ namespace MonoDevelop.Ide.TypeSystem static void OnWorkspaceItemAdded (object s, MonoDevelop.Projects.WorkspaceItemEventArgs args) { - Task.Run (() => TypeSystemService.Load (args.Item, null)); + TypeSystemService.Load (args.Item, null).Ignore (); } static void OnWorkspaceItemRemoved (object s, MonoDevelop.Projects.WorkspaceItemEventArgs args) @@ -285,12 +313,12 @@ namespace MonoDevelop.Ide.TypeSystem Unload (args.Item); } - static async void OnSolutionItemAdded (object sender, MonoDevelop.Projects.SolutionItemChangeEventArgs args) + static void OnSolutionItemAdded (object sender, MonoDevelop.Projects.SolutionItemChangeEventArgs args) { var project = args.SolutionItem as MonoDevelop.Projects.Project; if (project != null) { Unload (project.ParentSolution); - await Load (project.ParentSolution, new ProgressMonitor()); + Load (project.ParentSolution, null).Ignore (); } } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/ValidCodeDiagnosticAction.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/WorkspaceExtensions.cs index 5c946ac714..580af7969a 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/ValidCodeDiagnosticAction.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/WorkspaceExtensions.cs @@ -1,10 +1,10 @@ // -// ValidCodeDiagnosticAction.cs +// WorkspaceExtensions.cs // // Author: -// Mike Krüger <mkrueger@xamarin.com> +// Kirill Osenkov <https://github.com/KirillOsenkov> // -// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// Copyright (c) 2018 Microsoft // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,39 +24,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; -using Microsoft.CodeAnalysis.CodeActions; -using MonoDevelop.Core.Text; -using MonoDevelop.CodeActions; using Microsoft.CodeAnalysis; -using MonoDevelop.CodeIssues; -using Microsoft.CodeAnalysis.Text; -using System.Collections.Immutable; +using Microsoft.CodeAnalysis.SolutionCrawler; -namespace MonoDevelop.CodeActions +namespace MonoDevelop.Ide.TypeSystem { - /// <summary> - /// Represents a code action that's valid at a specific segment that was created as a action for a specific code diagnostic. - /// </summary> - class ValidCodeDiagnosticAction : ValidCodeAction + static class WorkspaceExtensions { - ImmutableArray<Diagnostic> validDiagnostics; - - public CodeDiagnosticFixDescriptor Diagnostic { - get; - private set; - } - - public ImmutableArray<Diagnostic> ValidDiagnostics { - get { - return validDiagnostics; - } + internal static void RegisterSolutionCrawler (Workspace workspace) + { + var solutionCrawlerRegistrationService = workspace.Services.GetService<ISolutionCrawlerRegistrationService> (); + solutionCrawlerRegistrationService.Register (workspace); } - public ValidCodeDiagnosticAction (CodeDiagnosticFixDescriptor diagnostic, CodeAction codeAction, ImmutableArray<Diagnostic> validDiagnostics, TextSpan validSegment) : base (codeAction, validSegment) + internal static void UnregisterSolutionCrawler (Workspace workspace) { - this.Diagnostic = diagnostic; - this.validDiagnostics = validDiagnostics; + var solutionCrawlerRegistrationService = workspace.Services.GetService<ISolutionCrawlerRegistrationService> (); + solutionCrawlerRegistrationService.Unregister (workspace); } } }
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj index f73ac34f27..704f980a31 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj @@ -8562,9 +8562,13 @@ <Compile Include="MonoDevelop.Ide.Commands\ViewCommands.cs" /> <Compile Include="MonoDevelop.Ide.Commands\WindowCommands.cs" /> <Compile Include="MonoDevelop.Ide.Composition\CompositionManager.cs" /> + <Compile Include="MonoDevelop.Ide.Composition\InlineRenameService.cs" /> <Compile Include="MonoDevelop.Ide.Composition\JoinableTaskContextHost.cs" /> <Compile Include="MonoDevelop.Ide.Composition\PlatformCatalog.cs" /> <Compile Include="MonoDevelop.Ide.Composition\PlatformExtensions.cs" /> + <Compile Include="MonoDevelop.Ide.Composition\PreviewFactoryService.cs" /> + <Compile Include="MonoDevelop.Ide.Composition\RoslynWaitIndicator.cs" /> + <Compile Include="MonoDevelop.Ide.Composition\SuggestedActionCategoryRegistryService.cs" /> <Compile Include="MonoDevelop.Ide.Editor\ITextEditorFactoryService.cs" /> <Compile Include="MonoDevelop.Ide.Gui\DisplayBindingService.cs" /> <Compile Include="MonoDevelop.Ide.Gui\BackgroundProgressMonitor.cs" /> @@ -8643,6 +8647,7 @@ <Compile Include="MonoDevelop.Ide.Gui\Workbench.cs" /> <Compile Include="MonoDevelop.Ide.Gui\StartupInfo.cs" /> <Compile Include="MonoDevelop.Ide.Gui\ProgressMonitors.cs" /> + <Compile Include="MonoDevelop.Ide.TypeSystem\IMonoDevelopHostDocument.cs" /> <Compile Include="MonoDevelop.Ide.TypeSystem\MonoDevelopPersistentStorageLocationService.cs" /> <Compile Include="MonoDevelop.Ide\RoslynLogger.cs" /> <Compile Include="MonoDevelop.Ide\Services.cs" /> @@ -9274,6 +9279,7 @@ <Compile Include="MonoDevelop.Components\ImageLoader.cs" /> <Compile Include="MonoDevelop.Ide.CodeCompletion\ParameterHintingData.cs" /> <Compile Include="MonoDevelop.Ide.TypeSystem\MonoDevelopWorkspace.cs" /> + <Compile Include="MonoDevelop.Ide.TypeSystem\WorkspaceExtensions.cs" /> <Compile Include="MonoDevelop.Ide.Editor\IDocumentLine.cs" /> <Compile Include="MonoDevelop.Ide.CodeTemplates\IListDataProvider.cs" /> <Compile Include="MonoDevelop.Ide.Editor\ITextEditorOptions.cs" /> @@ -9343,7 +9349,7 @@ <Compile Include="MonoDevelop.Ide.Editor\TextMarkerFactory.cs" /> <Compile Include="MonoDevelop.Ide.Editor\InternalExtensionAPI\ITextEditorImpl.cs" /> <Compile Include="MonoDevelop.Ide.Editor\InternalExtensionAPI\ITextMarkerFactory.cs" /> - <Compile Include="MonoDevelop.Ide.Editor\InternalExtensionAPI\IEditorActionHost.cs" /> + <Compile Include="MonoDevelop.Ide.Editor\InternalExtensionAPI\IMonoDevelopEditorOperations.cs" /> <Compile Include="MonoDevelop.Ide.Editor\EditActions.cs" /> <Compile Include="MonoDevelop.Ide.Editor\DocumentContext.cs" /> <Compile Include="MonoDevelop.Ide.Editor\InternalExtensionAPI\ITextEditorFactory.cs" /> @@ -9728,6 +9734,7 @@ <InternalsVisibleTo Include="MonoDevelop.Refactoring.Tests" /> <InternalsVisibleTo Include="Xamarin.Forms.Addin" /> <InternalsVisibleTo Include="MonoDevelop.TextEditor.Tests" /> + <InternalsVisibleTo Include="WebToolingAddin" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Target Name="AfterBuild"> |