public Task <QuickInfoItem> GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken) { QuickInfoItem quickInfoItem = null; if (session == null) { throw new ArgumentNullException("session"); } TemplateAnalysis analysis = this.analyzer.CurrentAnalysis; SnapshotPoint? triggerPoint = session.GetTriggerPoint(analysis.TextSnapshot); if (triggerPoint != null && analysis.Template != null) { string description; Span applicableTo; if (analysis.Template.TryGetDescription(triggerPoint.Value.Position, out description, out applicableTo)) { ITrackingSpan applicableToSpan = analysis.TextSnapshot.CreateTrackingSpan(applicableTo, SpanTrackingMode.EdgeExclusive); quickInfoItem = new QuickInfoItem(applicableToSpan, description); } } return(Task.FromResult(quickInfoItem)); }
public void PresentItem(ITrackingSpan triggerSpan, QuickInfoItem item, bool trackMouse) { AssertIsForeground(); _triggerSpan = triggerSpan; _item = item; // It's a new list of items. Either create the editor session if this is the first time, or ask the // editor session that we already have to recalculate. if (_editorSessionOpt == null || _editorSessionOpt.IsDismissed) { // We're tracking the caret. Don't have the editor do it. var triggerPoint = triggerSpan.GetStartTrackingPoint(PointTrackingMode.Negative); _editorSessionOpt = _quickInfoBroker.CreateQuickInfoSession(_textView, triggerPoint, trackMouse: trackMouse); _editorSessionOpt.Dismissed += (s, e) => OnEditorSessionDismissed(); } // So here's the deal. We cannot create the editor session and give it the right // signatures (even though we know what they are). Instead, the session with // call back into the ISignatureHelpSourceProvider (which is us) to get those // values. It will pass itself along with the calls back into // ISignatureHelpSourceProvider. So, in order to make that connection work, we // add properties to the session so that we can call back into ourselves, get // the signatures and add it to the session. if (!_editorSessionOpt.Properties.ContainsProperty(s_augmentSessionKey)) { _editorSessionOpt.Properties.AddProperty(s_augmentSessionKey, this); } _editorSessionOpt.Recalculate(); }
internal override void OnModelUpdated(Model modelOpt) { AssertIsForeground(); if (modelOpt == null || modelOpt.TextVersion != this.SubjectBuffer.CurrentSnapshot.Version) { this.StopModelComputation(); } else { // We want the span to actually only go up to the caret. So get the expected span // and then update its end point accordingly. ITrackingSpan trackingSpan; QuickInfoItem item = null; // Whether or not we have an item to show, we need to start the session. // If the user Edit.QuickInfo's on a squiggle, they want to see the // error text even if there's no symbol quickinfo. if (modelOpt.Item != null) { item = modelOpt.Item; var triggerSpan = modelOpt.GetCurrentSpanInSnapshot(item.TextSpan, this.SubjectBuffer.CurrentSnapshot); trackingSpan = triggerSpan.CreateTrackingSpan(SpanTrackingMode.EdgeInclusive); } else { var caret = this.TextView.GetCaretPoint(this.SubjectBuffer).Value; trackingSpan = caret.Snapshot.CreateTrackingSpan(caret.Position, 0, SpanTrackingMode.EdgeInclusive, TrackingFidelityMode.Forward); } sessionOpt.PresenterSession.PresentItem(trackingSpan, item, modelOpt.TrackMouse); } }
static async Task <Hover> GetHoverAsync(QuickInfoItem info, Document document, ClientCapabilities clientCapabilities, CancellationToken cancellationToken) { var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability(); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); if (supportsVSExtensions) { return(new VSHover { Range = ProtocolConversions.TextSpanToRange(info.Span, text), Contents = new SumType <SumType <string, MarkedString>, SumType <string, MarkedString>[], MarkupContent>(string.Empty), // Build the classified text without navigation actions - they are not serializable. // TODO - Switch to markup content once it supports classifications. // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138 RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, document, cancellationToken).ConfigureAwait(false) }); } else { return(new Hover { Range = ProtocolConversions.TextSpanToRange(info.Span, text), Contents = GetContents(info, document, clientCapabilities), }); } }
private static bool IsNullOrEmpty(QuickInfoItem info) { // Note that Sections.IsEmpty doesn't mean there is nothing // E.g. closing bracket `}` will have related open bracket // code in related spans. However this isn't supported yet. return(info == null || info.Sections.IsEmpty); }
private static QuickInfoItem?BuildQuickInfoDirectives(SyntaxToken token, CancellationToken cancellationToken) { if (token.Parent is DirectiveTriviaSyntax directiveTrivia) { if (directiveTrivia is EndRegionDirectiveTriviaSyntax) { var regionStart = directiveTrivia.GetMatchingDirective(cancellationToken); if (regionStart is not null) { return(QuickInfoItem.Create(token.Span, relatedSpans: ImmutableArray.Create(regionStart.Span))); } } else if (directiveTrivia is ElifDirectiveTriviaSyntax or ElseDirectiveTriviaSyntax or EndIfDirectiveTriviaSyntax) { var matchingDirectives = directiveTrivia.GetMatchingConditionalDirectives(cancellationToken); var matchesBefore = matchingDirectives .TakeWhile(d => d.SpanStart < directiveTrivia.SpanStart) .Select(d => d.Span) .ToImmutableArray(); if (matchesBefore.Length > 0) { return(QuickInfoItem.Create(token.Span, relatedSpans: matchesBefore)); } } } return(null); }
protected override async Task <QuickInfoItem?> BuildQuickInfoAsync( Document document, SyntaxToken token, CancellationToken cancellationToken ) { if (token.Kind() != SyntaxKind.CloseBraceToken) { return(null); } // Don't show for interpolations if ( token.Parent.IsKind( SyntaxKind.Interpolation, out InterpolationSyntax? interpolation ) && interpolation.CloseBraceToken == token ) { return(null); } // Now check if we can find an open brace. var parent = token.Parent !; var openBrace = parent .ChildNodesAndTokens() .FirstOrDefault(n => n.Kind() == SyntaxKind.OpenBraceToken) .AsToken(); if (openBrace.Kind() != SyntaxKind.OpenBraceToken) { return(null); } var spanStart = parent.SpanStart; var spanEnd = openBrace.Span.End; // If the parent is a scope block, check and include nearby comments around the open brace // LeadingTrivia is preferred if (IsScopeBlock(parent)) { MarkInterestedSpanNearbyScopeBlock(parent, openBrace, ref spanStart, ref spanEnd); } // If the parent is a child of a property/method declaration, object/array creation, or control flow node, // then walk up one higher so we can show more useful context else if (parent.GetFirstToken() == openBrace) { // parent.Parent must be non-null, because for GetFirstToken() to have returned something it would have had to walk up to its parent spanStart = parent.Parent !.SpanStart; } // encode document spans that correspond to the text to show var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var spans = ImmutableArray.Create(TextSpan.FromBounds(spanStart, spanEnd)); return(QuickInfoItem.Create(token.Span, relatedSpans: spans)); }
public async Task <QuickInfoItem> GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken) { await Task.Yield().ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { return(null); } var syntaxTreeAndSnapshot = ParserService.SyntaxTreeAndSnapshot; if (syntaxTreeAndSnapshot == null) { return(null); } // Map the trigger point down to our buffer. SnapshotPoint?triggerPoint = session.GetTriggerPoint(syntaxTreeAndSnapshot.Snapshot); if (triggerPoint == null) { return(null); } var triggerToken = syntaxTreeAndSnapshot.SyntaxTree.Root.FindToken(triggerPoint.Value.Position); if (triggerToken.IsMissing || triggerToken.Parent == null) { return(null); } var applicableToSpan = syntaxTreeAndSnapshot.Snapshot.CreateTrackingSpan( triggerToken.Extent.Start, triggerToken.Extent.Length, SpanTrackingMode.EdgeExclusive); var location = triggerToken.GetLocation(); var qiContent = $"{triggerToken.GetText()}\r\n{triggerToken.Kind} Ln {location?.StartLine + 1} Ch {location?.StartCharacter + 1}\r\n{triggerToken.Parent?.GetType().Name}"; await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var modifier = ModifierKeys.Control | ModifierKeys.Shift; if ((Keyboard.Modifiers & modifier) != modifier) { return(null); } var controlControl = new SymbolQuickInfoControl { CrispImage = { Moniker = ImageMonikers.StatusInformation }, TextContent = { Content = qiContent } }; var qiItem = new QuickInfoItem(applicableToSpan: applicableToSpan, item: controlControl ); return(qiItem); }
public Model(ITextVersion textVersion, QuickInfoItem item, bool trackMouse) { Contract.ThrowIfNull(item); this.TextVersion = textVersion; this.Item = item; this.TrackMouse = trackMouse; }
static MarkupContent GetContents(QuickInfoItem info, Document document, ClientCapabilities clientCapabilities) { var clientSupportsMarkdown = clientCapabilities?.TextDocument?.Hover?.ContentFormat.Contains(MarkupKind.Markdown) == true; // Insert line breaks in between sections to ensure we get double spacing between sections. var tags = info.Sections .SelectMany(section => section.TaggedParts.Add(new TaggedText(TextTags.LineBreak, Environment.NewLine))) .ToImmutableArray(); return(ProtocolConversions.GetDocumentationMarkupContent(tags, document, clientSupportsMarkdown)); }
public Model( ITextVersion textVersion, QuickInfoItem item, IQuickInfoProvider provider, bool trackMouse) { this.TextVersion = textVersion; this.Item = item; this.Provider = provider; this.TrackMouse = trackMouse; }
public Model( ITextVersion textVersion, QuickInfoItem item, IQuickInfoProvider provider, bool trackMouse) { Contract.ThrowIfNull(item); this.TextVersion = textVersion; this.Item = item; this.Provider = provider; this.TrackMouse = trackMouse; }
public async Task <QuickInfoItem> GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken) { await Task.Yield().ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { return(null); } var syntaxTreeAndSnapshot = ParserService.SyntaxTreeAndSnapshot; if (syntaxTreeAndSnapshot == null) { return(null); } // Map the trigger point down to our buffer. SnapshotPoint?triggerPoint = session.GetTriggerPoint(syntaxTreeAndSnapshot.Snapshot); if (triggerPoint == null) { return(null); } var qiInfo = QuickInfoProvider.GetQuickInfoDefinition(syntaxTreeAndSnapshot.SyntaxTree, triggerPoint.Value.Position); if (qiInfo == null) { return(null); } var applicableToSpan = syntaxTreeAndSnapshot.Snapshot.CreateTrackingSpan( qiInfo.ApplicableToExtent.Start, qiInfo.ApplicableToExtent.Length, SpanTrackingMode.EdgeExclusive); await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var qiContent = _textBlockBuilderService.ToTextBlock(qiInfo.Content); var controlControl = new SymbolQuickInfoControl { CrispImage = { Moniker = GdImageMonikers.GetMoniker(qiInfo.Glyph) }, TextContent = { Content = qiContent } }; var qiItem = new QuickInfoItem(applicableToSpan: applicableToSpan, item: controlControl ); return(qiItem); }
public IEnumerable<object> Create(QuickInfoItem item) { if (item == null) throw new ArgumentNullException(nameof(item)); switch (item.Content.Type) { case PredefinedQuickInfoContentTypes.Information: return Create((InformationQuickInfoContent)item.Content); case PredefinedQuickInfoContentTypes.CodeSpan: return Create((CodeSpanQuickInfoContent)item.Content); default: Debug.Fail($"Unknown QuickInfo content: {item.Content.Type}"); return Array.Empty<object>(); } }
// local functions // TODO - This should return correctly formatted markdown from quick info. // https://github.com/dotnet/roslyn/projects/45#card-20033878 static string GetMarkdownString(QuickInfoItem info) { var stringBuilder = new StringBuilder(); var description = info.Sections.FirstOrDefault(s => QuickInfoSectionKinds.Description.Equals(s.Kind))?.Text ?? string.Empty; var documentation = info.Sections.FirstOrDefault(s => QuickInfoSectionKinds.DocumentationComments.Equals(s.Kind))?.Text ?? string.Empty; if (!string.IsNullOrEmpty(description)) { stringBuilder.Append(description); if (!string.IsNullOrEmpty(documentation)) { stringBuilder.Append("\r\n> ").Append(documentation); } } return(stringBuilder.ToString()); }
private static async Task <Hover> GetHoverAsync( QuickInfoItem info, SourceText text, string language, Document?document, ClassificationOptions?classificationOptions, ClientCapabilities?clientCapabilities, CancellationToken cancellationToken) { Contract.ThrowIfFalse(document is null == (classificationOptions == null)); var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability(); if (supportsVSExtensions) { var context = document == null ? null : new IntellisenseQuickInfoBuilderContext( document, classificationOptions !.Value, threadingContext: null, operationExecutor: null, asynchronousOperationListener: null, streamingPresenter: null); return(new VSInternalHover { Range = ProtocolConversions.TextSpanToRange(info.Span, text), Contents = new SumType <SumType <string, MarkedString>, SumType <string, MarkedString>[], MarkupContent>(string.Empty), // Build the classified text without navigation actions - they are not serializable. // TODO - Switch to markup content once it supports classifications. // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/918138 RawContent = await IntellisenseQuickInfoBuilder.BuildContentWithoutNavigationActionsAsync(info, context, cancellationToken).ConfigureAwait(false) }); } else { return(new Hover { Range = ProtocolConversions.TextSpanToRange(info.Span, text), Contents = GetContents(info, language, clientCapabilities), }); }
public IEnumerable <object> Create(QuickInfoItem item) { if (item is null) { throw new ArgumentNullException(nameof(item)); } switch (item.Content.Type) { case PredefinedQuickInfoContentTypes.Information: return(Create((InformationQuickInfoContent)item.Content)); case PredefinedQuickInfoContentTypes.CodeSpan: return(Create((CodeSpanQuickInfoContent)item.Content)); default: Debug.Fail($"Unknown QuickInfo content: {item.Content.Type}"); return(Array.Empty <object>()); } }
private static QuickInfoItem CreateQuickInfo( TextSpan location, DiagnosticDescriptor descriptor, params TextSpan[] relatedSpans ) { var description = descriptor.Title.ToStringOrNull() ?? descriptor.Description.ToStringOrNull() ?? descriptor.MessageFormat.ToStringOrNull() ?? descriptor.Id; var idTag = !string.IsNullOrWhiteSpace(descriptor.HelpLinkUri) ? new TaggedText( TextTags.Text, descriptor.Id, TaggedTextStyle.None, descriptor.HelpLinkUri, descriptor.HelpLinkUri ) : new TaggedText(TextTags.Text, descriptor.Id); return(QuickInfoItem.Create( location, sections: new[] { QuickInfoSection.Create( QuickInfoSectionKinds.Description, new[] { idTag, new TaggedText(TextTags.Punctuation, ":"), new TaggedText(TextTags.Space, " "), new TaggedText(TextTags.Text, description) }.ToImmutableArray() ) }.ToImmutableArray(), relatedSpans: relatedSpans.ToImmutableArray() )); }
private Task SendInfoTipAsync(QuickInfoItem info, ICommandResultSender sender, CancellationToken cancellationToken) { var writer = sender.StartJsonMessage("infotip"); if (IsNullOrEmpty(info)) { return(sender.SendJsonMessageAsync(cancellationToken)); } writer.WriteTagsProperty("kinds", info.Tags); writer.WritePropertyStartArray("sections"); foreach (var section in info.Sections) { writer.WriteStartObject(); writer.WriteProperty("kind", FastConvert.StringToLowerInvariantString(section.Kind)); writer.WritePropertyStartArray("parts"); writer.WriteTaggedTexts(section.TaggedParts); writer.WriteEndArray(); writer.WriteEndObject(); } writer.WriteEndArray(); writer.WriteSpanProperty("span", info.Span); return(sender.SendJsonMessageAsync(cancellationToken)); }
static string GetMarkdownString(QuickInfoItem info) => string.Join("\r\n", info.Sections.Select(section => section.Text).Where(text => !string.IsNullOrEmpty(text)));