0

I know how to use VS Extensibility to get the entire active document's text. Unfortunately, that only gets me the text and doesn't give me the formatting, and I want that too.

I can, for example, get an IWpfTextView but once I get it, I'm not sure what to do with it. Are there examples of actually getting all the formatting from it? I'm only really interested in text foreground/background color, that's it.

Note: I need the formatted text on every edit, so unfortunately doing cut-and-paste using the clipboard is not an option.

Dmitri Nesteruk
  • 23,067
  • 22
  • 97
  • 166
  • Can you give more specific details about what you're trying to do with the text? There might be something else that would work better than colors... – Jimmy Apr 17 '17 at 21:17
  • @Jimmy I need to simply store the text. with formatting. on every key typed or every change (e.g., refactoring) made. I'm ok with *diff* data, too, if that's possible. – Dmitri Nesteruk Apr 17 '17 at 21:31
  • @DmitryNesteruk to put it another way, is *color* important, or would another indication of formatting boundaries work for you? – Jimmy Apr 18 '17 at 01:10
  • @Jimmy I do need the color eventually. having formatting boundaries is great so long as I can recover the colors used by VS for them, too – Dmitri Nesteruk Apr 18 '17 at 06:16

2 Answers2

1

Possibly the simplest method is to select all of the text and copy it to the clipboard. VS puts the rich text into the clipboard, so when you paste, elsewhere, you'll get the colors (assuming you handle rich text in your destination).

Jimmy
  • 27,142
  • 5
  • 87
  • 100
1

Here's my not-the-simplest solution. TL;DR: you can jump to the code at https://github.com/jimmylewis/GetVSTextViewFormattedTextSample.

The VS editor uses "classifications" to show segments of text which have special meaning. These classifications can then be formatted differently according to the language and user settings.

There's an API for getting the classifications in a document, but it didn't work for me. Or other people, apparently. But we can still get the classifications through an ITagAggregator<IClassificationTag>, as described in the preceding link, or right here:

[Import]
IViewTagAggregatorFactoryService tagAggregatorFactory = null;

// in some method...
var classificationAggregator = tagAggregatorFactory.CreateTagAggregator<IClassificationTag>(textView);
var wholeBufferSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, 0, textBuffer.CurrentSnapshot.Length);
var tags = classificationAggregator.GetTags(wholeBufferSpan);

Armed with these, we can rebuild the document. It's important to note that some text is not classified, so you have to piece everything together in chunks.

It's also notable that at this point, we have no idea how any of these tags are formatted - i.e. the colors used during rendering. If you want to, you can define your own mapping from IClassificationType to a color of your choice. Or, we can ask VS for what it would do using an IClassificationFormatMap. Again, remember, this is affected by user settings, Light vs. Dark theme, etc.

Either way, it could look something like this:

// Magic sauce pt1: See the example repo for an RTFStringBuilder I threw together.
RTFStringBuilder sb = new RTFStringBuilder();
var wholeBufferSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, 0, textBuffer.CurrentSnapshot.Length);
// Magic sauce pt2: see the example repo, but it's basically just 
// mapping the spans from the snippet above with the formatting settings 
// from the IClassificationFormatMap.
var textSpans = GetTextSpansWithFormatting(textBuffer);

int currentPos = 0;
var formattedSpanEnumerator = textSpans.GetEnumerator();
while (currentPos < wholeBufferSpan.Length && formattedSpanEnumerator.MoveNext())
{
    var spanToFormat = formattedSpanEnumerator.Current;
    if (currentPos < spanToFormat.Span.Start)
    {
        int unformattedLength = spanToFormat.Span.Start - currentPos;
        SnapshotSpan unformattedSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, currentPos, unformattedLength);
        sb.AppendText(unformattedSpan.GetText(), System.Drawing.Color.Black);
    }

    System.Drawing.Color textColor = GetTextColor(spanToFormat.Formatting.ForegroundBrush);
    sb.AppendText(spanToFormat.Span.GetText(), textColor);

    currentPos = spanToFormat.Span.End;
}

if (currentPos < wholeBufferSpan.Length)
{
    // append any remaining unformatted text
    SnapshotSpan unformattedSpan = new SnapshotSpan(textBuffer.CurrentSnapshot, currentPos, wholeBufferSpan.Length - currentPos);
    sb.AppendText(unformattedSpan.GetText(), System.Drawing.Color.Black);
}

return sb.ToString();

Hope this helps with whatever you're doing. The example repo will ask if you you want the formatted text in the clipboard after each edit, but that was just a dirty way that I could test and see that it worked. It's annoying, but it was just a PoC.

Community
  • 1
  • 1
Jimmy
  • 27,142
  • 5
  • 87
  • 100