Skip to content

Instantly share code, notes, and snippets.

@MasayukiOzawa
Last active December 18, 2025 23:58
Show Gist options
  • Select an option

  • Save MasayukiOzawa/83feef36ccb361661a2fc9a13817779b to your computer and use it in GitHub Desktop.

Select an option

Save MasayukiOzawa/83feef36ccb361661a2fc9a13817779b to your computer and use it in GitHub Desktop.
AutoInsertIsolationLevel
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.TextManager.Interop;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace VSIXProject1
{
internal sealed class EditorMonitor : IVsRunningDocTableEvents, IDisposable
{
private const string InsertedFlagKey = "IsolationLevelAutoInsert.Inserted";
private const string InsertText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;\r\n";
private readonly AsyncPackage _package;
private readonly IVsRunningDocumentTable _rdt;
private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory;
private readonly IVsTextManager _textManager;
private uint _rdtCookie;
public EditorMonitor(AsyncPackage package, IVsRunningDocumentTable rdt, IVsEditorAdaptersFactoryService editorAdaptersFactory, IVsTextManager textManager)
{
ThreadHelper.ThrowIfNotOnUIThread();
_package = package ?? throw new ArgumentNullException(nameof(package));
_rdt = rdt ?? throw new ArgumentNullException(nameof(rdt));
_editorAdaptersFactory = editorAdaptersFactory ?? throw new ArgumentNullException(nameof(editorAdaptersFactory));
_textManager = textManager ?? throw new ArgumentNullException(nameof(textManager));
_rdt.AdviseRunningDocTableEvents(this, out _rdtCookie);
}
public void Dispose()
{
ThreadHelper.ThrowIfNotOnUIThread();
if (_rdtCookie != 0)
{
_rdt.UnadviseRunningDocTableEvents(_rdtCookie);
_rdtCookie = 0;
}
}
private void TryInsertText(uint docCookie)
{
ThreadHelper.ThrowIfNotOnUIThread();
_rdt.GetDocumentInfo(docCookie,
out _, out _, out _,
out string moniker, out _, out _, out IntPtr docData);
if (docData == IntPtr.Zero)
{
return;
}
try
{
// Allow empty moniker (= Untitled/New Query), otherwise require .sql.
if (!string.IsNullOrEmpty(moniker))
{
var ext = Path.GetExtension(moniker);
if (!string.Equals(ext, ".sql", StringComparison.OrdinalIgnoreCase))
{
return;
}
}
var vsTextBuffer = Marshal.GetObjectForIUnknown(docData) as IVsTextBuffer;
if (vsTextBuffer == null)
{
return;
}
var wpfTextBuffer = _editorAdaptersFactory.GetDataBuffer(vsTextBuffer);
if (wpfTextBuffer == null)
{
return;
}
if (wpfTextBuffer.Properties.ContainsProperty(InsertedFlagKey))
{
return;
}
if (wpfTextBuffer.CurrentSnapshot.Length != 0)
{
return;
}
wpfTextBuffer.Properties.AddProperty(InsertedFlagKey, true);
using (var edit = wpfTextBuffer.CreateEdit())
{
edit.Insert(0, InsertText);
edit.Apply();
}
_ = _package.JoinableTaskFactory.RunAsync(async () =>
{
await Task.Delay(100).ConfigureAwait(true);
await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
_textManager.GetActiveView(1, null, out IVsTextView textView);
textView?.SetCaretPos(1, 0);
});
}
finally
{
Marshal.Release(docData);
}
}
public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
TryInsertText(docCookie);
}
catch
{
// Avoid crashing SSMS.
}
return VSConstants.S_OK;
}
public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
if (fFirstShow != 0)
{
TryInsertText(docCookie);
}
}
catch
{
// Avoid crashing SSMS.
}
return VSConstants.S_OK;
}
public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK;
public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK;
public int OnAfterSave(uint docCookie) => VSConstants.S_OK;
public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK;
}
}

FEATURE001: ISOLATION LEVEL の自動挿入 (SPEC)

1. 対象範囲

本仕様は SSMS 拡張機能における「新規クエリウィンドウ作成時の SET TRANSACTION ISOLATION LEVEL 自動挿入」の挙動と、現行実装(src\\IsolationLevelAutoInsert)に基づく検知/挿入方法を定義する。

2. 用語

  • Auto Insert: 新規クエリウィンドウ作成時に、分離レベル設定ステートメントを自動で挿入する機能
  • Manual Insert: 既存のクエリエディターに対して手動で挿入する機能 (本仕様では補足のみ)
  • RDT: Running Document Table(Visual Studio/SSMS が開いているドキュメントを管理するテーブル)
  • docCookie: RDT がドキュメントを識別するための ID

3. UI 仕様 (英語)

トップレベルメニュー: SSMS Extensions

配下メニュー例:

  • Transaction Isolation Level
    • Auto Insert
      • Read Uncommitted
      • Read Committed
      • Snapshot
      • Repeatable Read
      • Serializable
      • None (Disable Auto-Insert)

3.1 選択状態

  • Auto Insert の現在選択中の項目にチェックマークを表示する。
  • None (Disable Auto-Insert) を選択した場合、他の分離レベルのチェックは外れる。

4. 設定永続化

  • Auto Insert の選択値はユーザー単位で永続化する。
  • 保存先は Visual Studio Shell の UserSettings(ShellSettingsManager / WritableSettingsStore)を使用する。
  • 既定値は NONE(UI 上は None (Disable Auto-Insert))とする。

4.1 参考ドキュメント

5. 実装概要

本機能の実装は、SSMS(Visual Studio Shell ベース)のエディター拡張ポイントを利用し、以下の主要コンポーネントで構成される。

  • IsolationLevelAutoInsertPackage
    • SSMS 起動時に自動ロードされるエントリポイント。
    • 設定(SettingsManager)と RDT 監視(EditorMonitor)を初期化する。
  • SettingsManager
    • Auto Insert の選択状態(IsolationLevel)を UserSettings に永続化する。
  • EditorMonitor
    • Running Document Table (RDT) を監視し、「新規に作成された(= 空の)SQL エディター」を検知して自動挿入を実行する。
    • docCookie → IVsTextBufferITextBuffer の変換を行い、バッファを直接編集する。
    • 重複挿入防止のため ITextBuffer.Properties にフラグを保持する。
    • 挿入後、アクティブビューのキャレットを 2 行目へ移動する。

5.1 参考ドキュメントの使い所(本実装での利用箇所)

本 SPEC では learn.microsoft.com の一次情報を「実装の根拠」として参照している。各ドキュメントは、主に以下の用途で利用している。

  • 設定永続化(4 章)
    • ShellSettingsManager / WritableSettingsStore を使ってユーザー設定を保存/読み込みするため。
    • 対応する参考ドキュメント: 4.1
  • パッケージのロード/初期化(6.1)
    • AsyncPackageProvideAutoLoad / PackageRegistration / ProvideMenuResource の役割を整理し、SSMS 起動時に AutoLoad される構成を説明するため。
    • 対応する参考ドキュメント: 6.5(AsyncPackage/ProvideAutoLoad/ProvideMenuResource)
  • 新規クエリエディターの検知(6.2〜6.4)
    • Running Document Table (RDT) と IVsRunningDocTableEvents を購読し、docCookie を起点にドキュメントを特定するため。
    • OnBeforeDocumentWindowShow の「ウィンドウ/フレーム」概念の背景理解のため。
    • 対応する参考ドキュメント: 6.5(RDT/Events/Document windows)
  • 文字挿入(7.1〜7.3)
    • IVsTextBuffer(legacy)から ITextBuffer(MEF/WPF)へ変換し、ITextEdit で先頭へ挿入する方法を説明するため。
    • 対応する参考ドキュメント: 7.6(EditorAdapters/ITextBuffer/ITextEdit)
  • キャレット移動(7.5)
    • IVsTextManager.GetActiveView / IVsTextView.SetCaretPos を使ったカーソル移動の根拠として。
    • 対応する参考ドキュメント: 7.6(IVsTextManager/IVsTextView)

5.2 処理フロー(概要)

本機能は「SSMS 起動後にパッケージがロードされ、RDT イベントをトリガーとして空の SQL エディターに 1 回だけ挿入する」という流れで動作する。 特に RDT イベントは複数回発火する可能性があるため、バッファ単位のフラグで冪等性(同じバッファに 2 回挿入しない)を担保している。

5.1.1 ステップ概要

  1. SSMS 起動 → IsolationLevelAutoInsertPackage が AutoLoad
  2. SettingsManager がユーザー設定から分離レベルをロード
  3. EditorMonitor が RDT(Running Document Table)イベントを購読
  4. 新規クエリ作成などでドキュメントが生成されると、RDT イベントが発火
  5. TryInsertText(docCookie) が docCookie から対象バッファを解決
  6. フィルタ(SQL 判定/空バッファ/未挿入/設定有効)をすべて満たす場合のみ、先頭へ SET TRANSACTION ISOLATION LEVEL ... を挿入
  7. 挿入済みフラグを ITextBuffer.Properties に保存
  8. ビュー生成タイミングの差を吸収するため、短い遅延後にキャレットを 2 行目へ移動
[SSMS 起動 / Package AutoLoad]
    ↓
IsolationLevelAutoInsertPackage.InitializeAsync
    ↓
SettingsManager.LoadSettings
    ↓
EditorMonitor(IVsRunningDocTableEvents を購読)
    ↓
RDT event (OnAfterFirstDocumentLock / OnBeforeDocumentWindowShow)
    ↓
TryInsertText(docCookie)
    ↓
GetDocumentInfo → docData → IVsTextBuffer → ITextBuffer
    ↓
(フィルタ: SQL/空/未挿入/設定有効)
    ↓
ITextEdit.Insert(0, "SET TRANSACTION ISOLATION LEVEL ...") → Apply
    ↓
ITextBuffer.Properties に挿入済みフラグ
    ↓
(遅延) IVsTextView.SetCaretPos(1,0)

6. 自動挿入の検知方法(新しいクエリエディターを開いたことをどう検知しているか)

6.1 初期化フロー

  • パッケージ IsolationLevelAutoInsertPackageProvideAutoLoad により SSMS 起動時(EmptySolution / NoSolution)にロードされる。
  • InitializeAsync(UI スレッド)で以下を行う。
    1. SettingsManager を初期化し、設定をロード
    2. Auto Insert 選択用コマンド(Select*)と手動挿入コマンド(Insert*)を初期化
    3. EditorMonitor を生成し、RDT イベント購読を開始

6.1.1 コード(現行実装抜粋)

対象: src\IsolationLevelAutoInsert\IsolationLevelAutoInsertPackage.cs

// パッケージは AsyncPackage としてロードされる
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.EmptySolution, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class IsolationLevelAutoInsertPackage : AsyncPackage
{
    private SettingsManager _settingsManager;
    private EditorMonitor _editorMonitor;

    protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
    {
        await base.InitializeAsync(cancellationToken, progress);
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

        _settingsManager = new SettingsManager(this);
        _settingsManager.LoadSettings();

        // ... Select* / Insert* コマンド初期化 ...

        // RDT 監視を開始
        _editorMonitor = new EditorMonitor(_settingsManager, this);
    }
}

6.2 RDT (Running Document Table) のイベント購読

  • EditorMonitorIVsRunningDocTableEvents を実装し、SVsRunningDocumentTableIVsRunningDocumentTable)へ購読する。

6.2.1 コード(現行実装抜粋)

対象: src\IsolationLevelAutoInsert\EditorMonitor.cs

// RunningDocumentTable を取得
var rdt = serviceProvider.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (rdt == null)
{
    DiagnosticLogger.Log("EditorMonitor: Failed to get IVsRunningDocumentTable");
    return;
}

_rdt = rdt;
_rdt.AdviseRunningDocTableEvents(this, out _rdtCookie);
  • 以降、SSMS がドキュメントをオープン/表示するタイミングで RDT からコールバックされる。

6.3 「新規クエリエディター作成」を拾うために使っているイベント

現行実装では、検知の取りこぼしを減らすために 2 つのイベントから同じ挿入処理を呼び出す。

  1. OnAfterFirstDocumentLock(docCookie, ...)
  • ドキュメントが初めてロックされたタイミングで呼ばれる。
  1. OnBeforeDocumentWindowShow(docCookie, fFirstShow, ...)
  • ドキュメントウィンドウが表示される直前に呼ばれる。
  • fFirstShow != 0(初回表示)の場合のみ処理する。

6.3.1 コード(現行実装抜粋)

対象: src\IsolationLevelAutoInsert\EditorMonitor.cs

public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
{
    try
    {
        ThreadHelper.ThrowIfNotOnUIThread();
        TryInsertText(docCookie);
    }
    catch (Exception ex)
    {
        DiagnosticLogger.LogError($"OnAfterFirstDocumentLock failed for docCookie={docCookie}", ex);
    }
    return 0;
}

public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
{
    try
    {
        ThreadHelper.ThrowIfNotOnUIThread();
        if (fFirstShow != 0)
        {
            TryInsertText(docCookie);
        }
    }
    catch (Exception ex)
    {
        DiagnosticLogger.LogError($"OnBeforeDocumentWindowShow failed for docCookie={docCookie}", ex);
    }
    return 0;
}

6.4 Auto Insert 対象と見なす条件(フィルタ)

TryInsertText(docCookie) の内部では、次の条件をすべて満たした場合のみ挿入を行う。

  • 設定が有効(SettingsManager.IsolationLevel != "NONE"
  • RDT から docData(ドキュメントデータの COM ポインタ)を取得できる
  • docDataIVsTextBuffer として扱える(テキストエディターである)
  • SQL ファイル判定
    • moniker(パス)が空でない場合: 拡張子が .sql であること
    • moniker が空/NULL の場合: 「無題(新規)」の可能性があるため許可(※この挙動は現行実装に合わせる)
  • バッファが空(ITextBuffer.CurrentSnapshot.Length == 0
    • 既存ファイルを開いた場合やテンプレート展開済みの場合は挿入しない
  • 既に挿入済みでない
    • ITextBuffer.Properties に挿入済みフラグがある場合はスキップ

6.5 参考ドキュメント

7. 自動挿入の実装(開いたエディターにどうやって文字を挿入しているか)

7.1 docCookie → TextBuffer への解決

TryInsertText(docCookie) は RDT からドキュメントの詳細を取得し、docData(IUnknown)から IVsTextBufferITextBuffer へ変換する。

7.1.1 コード(現行実装抜粋)

対象: src\IsolationLevelAutoInsert\EditorMonitor.cs

_rdt.GetDocumentInfo(docCookie,
    out uint flags, out uint readLocks, out uint editLocks,
    out string moniker, out IVsHierarchy hierarchy, out uint itemId, out IntPtr docData);

if (docData == IntPtr.Zero)
{
    DiagnosticLogger.Log("DocData is null");
    return;
}

// docData(IUnknown) → IVsTextBuffer
var textBuffer = Marshal.GetObjectForIUnknown(docData) as IVsTextBuffer;
Marshal.Release(docData);

if (textBuffer == null)
{
    DiagnosticLogger.Log("Not a text buffer");
    return;
}

// IVsTextBuffer → ITextBuffer(WPF/MEF 側)
var wpfTextBuffer = _editorAdaptersFactory?.GetDataBuffer(textBuffer);
if (wpfTextBuffer == null)
{
    DiagnosticLogger.Log("Failed to get WPF text buffer");
    return;
}

7.2 挿入処理(ITextBuffer / ITextEdit)

  • 挿入は ITextBuffer.CreateEdit() により ITextEdit を作成し、先頭へ Insert(0, text) を行う。

7.2.1 コード(現行実装抜粋)

対象: src\IsolationLevelAutoInsert\EditorMonitor.cs

// 挿入するテキストを設定に基づいて決定
string insertText = GetIsolationLevelStatement(isolationLevel);
if (string.IsNullOrEmpty(insertText))
{
    DiagnosticLogger.Log($"Unknown isolation level: {isolationLevel}. Skipping insertion.");
    return;
}

// テキストを挿入
using (var edit = wpfTextBuffer.CreateEdit())
{
    edit.Insert(0, insertText);
    edit.Apply();
}
  • これは「VS/SSMS のエディターが管理するバッファ」に対する編集であり、ユーザーの手編集と同じレイヤーで反映される(ロックや特殊なモードはかけない)。

7.3 挿入済みフラグ(重複挿入防止のキー)

  • 同一バッファに対して複数回挿入しないため、ITextBuffer.Properties にフラグを保存する。

  • Key: IsolationLevelAutoInsert.Inserted

7.3.1 コード(現行実装抜粋)

対象: src\IsolationLevelAutoInsert\EditorMonitor.cs

private const string InsertedFlagKey = "IsolationLevelAutoInsert.Inserted";

if (wpfTextBuffer.Properties.ContainsProperty(InsertedFlagKey))
{
    DiagnosticLogger.Log("Text already inserted. Skipping.");
    return;
}

wpfTextBuffer.Properties.AddProperty(InsertedFlagKey, true);

このため、RDT イベントが複数回発火しても「最初の 1 回だけ」挿入される。

7.4 挿入する SQL

選択された分離レベルに応じて、以下のいずれかを挿入する。

  • Read Uncommitted:
    • SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  • Read Committed:
    • SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • Snapshot:
    • SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
  • Repeatable Read:
    • SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  • Serializable:
    • SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

7.4.1 フォーマット(現行実装)

  • 現行実装の挿入文字列は末尾が改行 1 つ(\r\n)である。
SET TRANSACTION ISOLATION LEVEL <LEVEL>;
  • 大文字/小文字は上記の固定表記とする(ユーザー体験の一貫性のため)。

7.5 カーソル位置の調整(2行目へ移動)

  • 挿入後、カーソルを 2 行目先頭へ移動する。
  • ビューの生成タイミング差を吸収するため、100ms 遅延した非同期処理で実行する。
await Task.Delay(100);
textManager.GetActiveView(1, null, out IVsTextView textView);
textView.SetCaretPos(1, 0); // (line=1, col=0)

7.6 参考ドキュメント

8. 例外/エラー処理

  • RDT イベントハンドラおよび挿入処理は try/catch で囲み、例外が起きても SSMS 全体が落ちないようにする。
  • Auto Insert の実行失敗をユーザーに MessageBox で通知しない(体験阻害を避ける)。

9. 診断ログ(何をログに残すか)

DiagnosticLogger を用い、少なくとも以下をログへ出力する。

  • パッケージ初期化開始/完了
  • RDT イベント購読の成否
  • イベント発火(docCookie
  • 設定値(IsolationLevel
  • スキップ理由
    • 無効(NONE)
    • docData が取れない
    • SQL 以外
    • バッファ非空
    • 既に挿入済み
  • 例外情報(スタックトレース)

10. テスト観点 (手動)

  1. Auto Insert=Snapshot を選択し、「New Query」等で新規クエリウィンドウを開く。
    • 先頭に SET TRANSACTION ISOLATION LEVEL SNAPSHOT; が挿入される。
    • カーソルが 2 行目先頭へ移動する。
  2. Auto Insert=None を選択し、新規クエリウィンドウを開く。
    • 何も挿入されない。
  3. 既存の .sql ファイルを開く(内容あり)。
    • バッファが空でないため、自動挿入されない。
  4. SSMS 再起動後も Auto Insert の選択が保持される。

11. 既知の注意点

  • 分離レベルは接続単位の設定であり、実行対象 DB の設定(例: Snapshot の可否)によっては SQL 実行時にエラーとなる場合がある。
    • 本機能は「挿入」を行うのみで、DB 側の設定検証はしない。
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Task = System.Threading.Tasks.Task;
namespace VSIXProject1
{
/// <summary>
/// This is the class that implements the package exposed by this assembly.
/// </summary>
/// <remarks>
/// <para>
/// The minimum requirement for a class to be considered a valid package for Visual Studio
/// is to implement the IVsPackage interface and register itself with the shell.
/// This package uses the helper classes defined inside the Managed Package Framework (MPF)
/// to do it: it derives from the Package class that provides the implementation of the
/// IVsPackage interface and uses the registration attributes defined in the framework to
/// register itself and its components with the shell. These attributes tell the pkgdef creation
/// utility what data to put into .pkgdef file.
/// </para>
/// <para>
/// To get loaded into VS, the package must be referred by &lt;Asset Type="Microsoft.VisualStudio.VsPackage" ...&gt; in .vsixmanifest file.
/// </para>
/// </remarks>
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[ProvideAutoLoad(UIContextGuids.EmptySolution, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideAutoLoad(UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
[Guid(VSIXProject1Package.PackageGuidString)]
public sealed class VSIXProject1Package : AsyncPackage
{
private EditorMonitor _editorMonitor;
/// <summary>
/// VSIXProject1Package GUID string.
/// </summary>
public const string PackageGuidString = "f2f34884-fbb6-4178-b7a0-c831dbdaec9c";
#region Package Members
/// <summary>
/// Initialization of the package; this method is called right after the package is sited, so this is the place
/// where you can put all the initialization code that rely on services provided by VisualStudio.
/// </summary>
/// <param name="cancellationToken">A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.</param>
/// <param name="progress">A provider for progress updates.</param>
/// <returns>A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.</returns>
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
await base.InitializeAsync(cancellationToken, progress);
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var rdt = await GetServiceAsync(typeof(SVsRunningDocumentTable)).ConfigureAwait(true) as IVsRunningDocumentTable;
var textManager = await GetServiceAsync(typeof(SVsTextManager)).ConfigureAwait(true) as IVsTextManager;
var componentModel = await GetServiceAsync(typeof(SComponentModel)).ConfigureAwait(true) as IComponentModel;
var editorAdaptersFactory = componentModel?.GetService<IVsEditorAdaptersFactoryService>();
if (rdt != null && textManager != null && editorAdaptersFactory != null)
{
_editorMonitor = new EditorMonitor(this, rdt, editorAdaptersFactory, textManager);
}
}
protected override void Dispose(bool disposing)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (disposing)
{
_editorMonitor?.Dispose();
_editorMonitor = null;
}
base.Dispose(disposing);
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment