読者です 読者をやめる 読者になる 読者になる

みかづきメモ

学習したことのメモとか、日記とか、備忘録。

Visual Studio の拡張機能を作成する - 番外編 - ツールチップ

本筋からはちょっとズレますが、あると嬉しい機能である、ツールチップ表示を実装します。
画像のように、テキスト上にカーソルを持って行くと表示されるあれです。

f:id:MikazukiFuyuno:20160502155716p:plain:w400


ツールチップを実装するには、とりあえず継承しただけである AuthoringScope クラス内の、
GetDataTipText を使用します。
GetDataTipText には、 Ln, Col が渡されるので、該当位置にあるトークンを取得し、
それにあった結果を返します。

しかしながら、コードの解析を行う IScanner には行番号が降ってこないため、
行番号が渡されるよう、次のように変更します。

まず、 Microsoft.VisualStudio.Package.Colorizer を継承したクラスを作成します。
次に、 ColorizeLine 及び GetLineInfo をオーバーライドし、Scanner へと
行番号を渡すように変更します。
これらのメソッドは、内部で IScannerSetSource 及び ScanToken~ を呼び出しており、
また、 SetSource 及び ScanToken~ はこれらのメソッド経由で呼び出されています。
そのため、その処理の直前で行番号を渡します。
これらを実装したものが下のコード。

using System;
using System.Runtime.InteropServices;

using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;

using VSLanguageService = Microsoft.VisualStudio.Package.LanguageService;

namespace HSPToolsVS.LanguageService
{
    [ComVisible(true)]
    internal class HSPColorizer : Colorizer
    {
        public HSPColorizer(VSLanguageService svc, IVsTextLines buffer, IScanner scanner) : base(svc, buffer, scanner)
        {

        }

        #region Overrides of Colorizer

        public override int ColorizeLine(int line, int length, IntPtr ptr, int state, uint[] attrs)
        {
            (Scanner as HSPScanner)?.SetLineNumber(line);
            return base.ColorizeLine(line, length, ptr, state, attrs);
        }

        public override TokenInfo[] GetLineInfo(IVsTextLines buffer, int line, IVsTextColorState colorState)
        {
            (Scanner as HSPScanner)?.SetLineNumber(line);
            return base.GetLineInfo(buffer, line, colorState);
        }

        #endregion
    }
}


次に、上の Colorizer を使うように、 HSPLanguageService を変更します。

public override Colorizer GetColorizer(IVsTextLines buffer)
{
    return new HSPColorizer(this, buffer, GetScanner(buffer));
}

なお、 GetColorizer はキャッシュ機構を持っているため、
本来ならばそれも実装すべきかもしれません。
あとは、 AuthoringScope にて、トークンを元にツールチップテキストを返します。

using System.Collections.Generic;
using System.Linq;

using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;

namespace HSPToolsVS.LanguageService
{
    internal class HSPAuthoringScope : AuthoringScope
    {
        private readonly IEnumerable<Token> _tokens;

        public HSPAuthoringScope(IEnumerable<Token> tokens)
        {
            _tokens = tokens;
        }

        public override string GetDataTipText(int line, int col, out TextSpan span)
        {
            span = new TextSpan();
            var token = GetTokenFromLineAndCol(line, col);
            if (token == null || token.Type == HSPTokenType.Comment || token.Type == HSPTokenType.Numeric)
                return null;

            span.iStartLine = line;
            span.iEndLine = line;
            span.iStartIndex = token.StartIndex;
            span.iEndIndex = token.EndIndex;
            return $"{token.Type} : {token.Text}";
        }

        // 略

        private Token GetTokenFromLineAndCol(int line, int col)
        {
            return _tokens.SingleOrDefault(w => w.Line == line && w.StartIndex <= col && col <= w.EndIndex);
        }
    }
}

参考: