protected static object CreateToolTipContent(Workspace workspace, DiagnosticData diagnostic) { Action?navigationAction = null; string?tooltip = null; if (workspace != null) { var helpLinkUri = diagnostic.GetValidHelpLinkUri(); if (helpLinkUri != null) { navigationAction = new QuickInfoHyperLink(workspace, helpLinkUri).NavigationAction; tooltip = diagnostic.HelpLink; } } var diagnosticIdTextRun = navigationAction is null ? new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Id) : new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Id, navigationAction, tooltip); return(new ContainerElement( ContainerElementStyle.Wrapped, new ClassifiedTextElement( diagnosticIdTextRun, new ClassifiedTextRun(ClassificationTypeNames.Punctuation, ":"), new ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Message)))); }
protected static object CreateToolTipContent(Workspace workspace, DiagnosticData diagnostic) { Action?navigationAction = null; string?tooltip = null; if (workspace is object && diagnostic.HelpLink is { } helpLink && Uri.TryCreate(helpLink, UriKind.Absolute, out var helpLinkUri)) { navigationAction = new QuickInfoHyperLink(workspace, helpLinkUri).NavigationAction; tooltip = helpLink; } var diagnosticIdTextRun = navigationAction is null ? new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Id) : new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Id, navigationAction, tooltip); return(new ContainerElement( ContainerElementStyle.Wrapped, new ClassifiedTextElement( diagnosticIdTextRun, new ClassifiedTextRun(ClassificationTypeNames.Punctuation, ":"), new ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Message)))); }
private static IReadOnlyCollection <object> BuildInteractiveTextElements( ImmutableArray <TaggedText> taggedTexts, ref int index, IntellisenseQuickInfoBuilderContext?context) { // This method produces a sequence of zero or more paragraphs var paragraphs = new List <object>(); // Each paragraph is constructed from one or more lines var currentParagraph = new List <object>(); // Each line is constructed from one or more inline elements var currentRuns = new List <ClassifiedTextRun>(); while (index < taggedTexts.Length) { var part = taggedTexts[index]; // These tags can be ignored - they are for markdown formatting only. if (part.Tag is TextTags.CodeBlockStart or TextTags.CodeBlockEnd) { index++; continue; } if (part.Tag == TextTags.ContainerStart) { if (currentRuns.Count > 0) { // This line break means the end of a line within a paragraph. currentParagraph.Add(new ClassifiedTextElement(currentRuns)); currentRuns.Clear(); } index++; var nestedElements = BuildInteractiveTextElements(taggedTexts, ref index, context); if (nestedElements.Count <= 1) { currentParagraph.Add(new ContainerElement( ContainerElementStyle.Wrapped, new ClassifiedTextElement(new ClassifiedTextRun(ClassificationTypeNames.Text, part.Text)), new ContainerElement(ContainerElementStyle.Stacked, nestedElements))); } else { currentParagraph.Add(new ContainerElement( ContainerElementStyle.Wrapped, new ClassifiedTextElement(new ClassifiedTextRun(ClassificationTypeNames.Text, part.Text)), new ContainerElement( ContainerElementStyle.Stacked, nestedElements.First(), new ContainerElement( ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding, nestedElements.Skip(1))))); } index++; continue; } else if (part.Tag == TextTags.ContainerEnd) { // Return the current result and let the caller continue break; } if (part.Tag is TextTags.ContainerStart or TextTags.ContainerEnd) { index++; continue; } if (part.Tag == TextTags.LineBreak) { if (currentRuns.Count > 0) { // This line break means the end of a line within a paragraph. currentParagraph.Add(new ClassifiedTextElement(currentRuns)); currentRuns.Clear(); } else { // This line break means the end of a paragraph. Empty paragraphs are ignored, but could appear // in the input to this method: // // * Empty <para> elements // * Explicit line breaks at the start of a comment // * Multiple line breaks between paragraphs if (currentParagraph.Count > 0) { // The current paragraph is not empty, so add it to the result collection paragraphs.Add(CreateParagraphFromLines(currentParagraph)); currentParagraph.Clear(); } else { // The current paragraph is empty, so we simply ignore it. } } } else { // This is tagged text getting added to the current line we are building. var style = GetClassifiedTextRunStyle(part.Style); if (part.NavigationTarget is not null && context?.ThreadingContext is { } threadingContext&& context?.OperationExecutor is { } operationExecutor&& context?.AsynchronousOperationListener is { } asyncListener&& context?.StreamingPresenter is { } streamingPresenter) { var document = context.Document; if (Uri.TryCreate(part.NavigationTarget, UriKind.Absolute, out var absoluteUri)) { var target = new QuickInfoHyperLink(document.Project.Solution.Workspace, absoluteUri); var tooltip = part.NavigationHint; currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, target.NavigationAction, tooltip, style)); } else { // ⚠ PERF: avoid capturing Solution (including indirectly through Project or Document // instances) as part of the navigationAction delegate. var target = part.NavigationTarget; var tooltip = part.NavigationHint; var documentId = document.Id; var workspace = document.Project.Solution.Workspace; currentRuns.Add(new ClassifiedTextRun( part.Tag.ToClassificationTypeName(), part.Text, () => _ = NavigateToQuickInfoTargetAsync(target, workspace, documentId, threadingContext, operationExecutor, asyncListener, streamingPresenter.Value), tooltip, style)); } }
private static IReadOnlyCollection <object> BuildInteractiveTextElements( ImmutableArray <TaggedText> taggedTexts, ref int index, Document document, IThreadingContext?threadingContext, Lazy <IStreamingFindUsagesPresenter>?streamingPresenter) { // This method produces a sequence of zero or more paragraphs var paragraphs = new List <object>(); // Each paragraph is constructed from one or more lines var currentParagraph = new List <object>(); // Each line is constructed from one or more inline elements var currentRuns = new List <ClassifiedTextRun>(); while (index < taggedTexts.Length) { var part = taggedTexts[index]; if (part.Tag == TextTags.ContainerStart) { if (currentRuns.Count > 0) { // This line break means the end of a line within a paragraph. currentParagraph.Add(new ClassifiedTextElement(currentRuns)); currentRuns.Clear(); } index++; var nestedElements = BuildInteractiveTextElements(taggedTexts, ref index, document, threadingContext, streamingPresenter); if (nestedElements.Count <= 1) { currentParagraph.Add(new ContainerElement( ContainerElementStyle.Wrapped, new ClassifiedTextElement(new ClassifiedTextRun(ClassificationTypeNames.Text, part.Text)), new ContainerElement(ContainerElementStyle.Stacked, nestedElements))); } else { currentParagraph.Add(new ContainerElement( ContainerElementStyle.Wrapped, new ClassifiedTextElement(new ClassifiedTextRun(ClassificationTypeNames.Text, part.Text)), new ContainerElement( ContainerElementStyle.Stacked, nestedElements.First(), new ContainerElement( ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding, nestedElements.Skip(1))))); } index++; continue; } else if (part.Tag == TextTags.ContainerEnd) { // Return the current result and let the caller continue break; } if (part.Tag == TextTags.ContainerStart || part.Tag == TextTags.ContainerEnd) { index++; continue; } if (part.Tag == TextTags.LineBreak) { if (currentRuns.Count > 0) { // This line break means the end of a line within a paragraph. currentParagraph.Add(new ClassifiedTextElement(currentRuns)); currentRuns.Clear(); } else { // This line break means the end of a paragraph. Empty paragraphs are ignored, but could appear // in the input to this method: // // * Empty <para> elements // * Explicit line breaks at the start of a comment // * Multiple line breaks between paragraphs if (currentParagraph.Count > 0) { // The current paragraph is not empty, so add it to the result collection paragraphs.Add(CreateParagraphFromLines(currentParagraph)); currentParagraph.Clear(); } else { // The current paragraph is empty, so we simply ignore it. } } } else { // This is tagged text getting added to the current line we are building. var style = GetClassifiedTextRunStyle(part.Style); if (part.NavigationTarget is object && streamingPresenter != null && threadingContext != null) { if (Uri.TryCreate(part.NavigationTarget, UriKind.Absolute, out var absoluteUri)) { var target = new QuickInfoHyperLink(document.Project.Solution.Workspace, absoluteUri); var tooltip = part.NavigationHint; currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, target.NavigationAction, tooltip, style)); } else { var target = part.NavigationTarget; var tooltip = part.NavigationHint; currentRuns.Add(new ClassifiedTextRun( part.Tag.ToClassificationTypeName(), part.Text, () => NavigateToQuickInfoTarget(target, document, threadingContext, streamingPresenter.Value), tooltip, style)); } } else { currentRuns.Add(new ClassifiedTextRun(part.Tag.ToClassificationTypeName(), part.Text, style)); } } index++; } if (currentRuns.Count > 0) { // Add the final line to the final paragraph. currentParagraph.Add(new ClassifiedTextElement(currentRuns)); } if (currentParagraph.Count > 0) { // Add the final paragraph to the result. paragraphs.Add(CreateParagraphFromLines(currentParagraph)); } return(paragraphs); }