VSCodeのツリービューでドラッグ&ドロップを実装する方法

# はじめに

ツリービューを実装する時、ドラッグ&ドロップを実装したいと思ったことはありませんか？

今回は、VSCodeの拡張機能にて、ツリービューを実装する際に、要素間のドラッグ&ドロップを実現する方法について紹介します。

# TreeDragAndDropControllerを実装する
VSCodeのツリービューにて、ドラッグ&ドロップを実装するには、`TreeDataProvider`を継承したクラスにて、新たに`TreeDragAndDropController`を継承することで実現させます。

```ts
export class SampleTreeProvider
implements
    vscode.TreeDataProvider<SampleTreeItem>,
    vscode.TreeDragAndDropController<SampleTreeItem>
{
    // 実際のコードがここに記述されます。
}
```

# handleDrago()メソッドを実装する

ドラッグの動作を実現するためには、`handleDrag()`を実装します。

```ts
handleDrag(
  source: readonly SampleTreeItem[],
  dataTransfer: vscode.DataTransfer,
  token: vscode.CancellationToken
): void | Thenable<void> {
  // 実際のコードがここに記述されます
}
```

`handleDrag()`は、3つの引数を受け取ります。
最初の引数は、`source`です。
`source`は、ドラッグされた要素の配列を表します。

次の引数は、`dataTransfer`です。`dataTransfer`は、ドラッグされた要素をドラッグイベントに渡すために使用されます。

最後の引数は、`token`です。この引数を使うことで、ドラッグイベントがキャンセルされたかどうかを確認することができます。

# handleDrop()メソッドを実装する

ドロップの動作を実現するためには、`handleDrop()`を実装します。

```ts
handleDrop(
  target: SampleTreeItem | undefined,
  dataTransfer: vscode.DataTransfer,
  token: vscode.CancellationToken
): void | Thenable<void> {
    // 実際のコードがここに記述されます。
}
```

`handleDrop()`は、3つの引数を受け取ります。
最初の引数は、`target`です。
`target`は、ドロップ先の要素を表します。

次の引数は、`dataTransfer`です。この引数を通して、`handleDrag()`にて渡された要素を取得することができます。

最後の引数は、`token`です。この引数を使うことで、ドラッグイベントがキャンセルされたかどうかを確認することができます。

# tokenの使い方

`token`を使って、ドラッグイベントがキャンセルされたかどうかを確認するには、次のようにします。

```ts
if (token.isCancellationRequested) {
  return;
}
```

# dataTransferの使い方

`dataTransfer`を使って、ドラッグイベントに要素を渡すには、次のようにします。

```ts
dataTransfer.set(
  "application/vnd.code.tree.sampleView",
  new vscode.DataTransferItem(source[0])
);
```

また、上記で渡された要素を`handleDrop()`内で受け取るには、次のようにします。

```ts
const transferItem = dataTransfer.get(
  "application/vnd.code.tree.sampleView"
);
```

取得された`transferItem`は、`SampleTreeItem`または`undefined`の可能性があるので、使用する前に必ずチェックします。

```ts
if (!transferItem) {
  return;
}
```

# dropMimeTypesとdragMimeTypes

`TreeDragAndDropController`を実装する場合、`dropMimeTypes`と`dragMimeTypes`プロパティを実装しなければなりません。

```ts
dropMimeTypes = ['application/vnd.code.tree.sampleView'];
dragMimeTypes = ['text/uri-list'];
```

## dragMimeTypesについて

`dragMimeTypes`に指定されるMIMEタイプは、`handleDrag()`メソッド内で、`dataTransfer`に対して指定することができるMIMEタイプを表します。
`'application/vnd.code.tree.ツリービューのID'`は自動的に追加されるので、ここでは任意で`'text/uri-list'`のみを追加しています。
この、`text/uri-list`というMIMEタイプは、一般的にドラッグ&ドロップの操作を実装する際に使用されます。ファイルやフォルダなどのオブジェクトの参照先を表すために使われるそうです。

## dropMimeTypesについて

`dropMimeTypes`に指定されるMIMEタイプは、`handleDrop()`メソッドにて受け入れることができるMIMEタイプを表します。
なので、特定のツリーからのドロップをサポートするには、そのツリーのMIMEタイプを指定する必要があります。
例えば、`handleDrag()`内にて、`dataTransfer`に対して`'application/vnd.code.tree.sampleView'`が指定されている場合は、そのMIMEタイプを指定しましょう。

```ts
dataTransfer.set(
  "application/vnd.code.tree.sampleView",
  new vscode.DataTransferItem(source[0])
);
```

ドキュメントでも、`application/vnd.code.tree.ツリービューのID`を指定することが推奨されています。

# ツリービューでドラッグ&ドロップを有効にする

ツリービューを作成するには、`createTreeView()`の呼び出しが欠かせません。

`createTreeView()`を呼び出す際、`dragAndDropController`フィールドの値として、`TreeDragAndDropController`を継承したクラスのインスタンスを指定します。

ツリービューにて、要素のドラッグ&ドロップができるようになります。`treeDataProvider`フィールドに指定するだけでは、この機能が有効にならないので、忘れずに設定を行いましょう。

```ts
vscode.window.createTreeView("sampleView", {
    treeDataProvider: sampleTreeProvider,
    showCollapseAll: true,
    canSelectMany: false,
    dragAndDropController: sampleTreeProvider,
})
```

# まとめ

実際に、`handleDrag()`や`handleDrop()`を実装する際には、渡したり、受け取ったりしたデータをもとに、TreeItem間の親子関係を調整する必要があります。

それらの具体的な方法については、いずれまたどこかで解説できると幸いです。

# 参考にした記事

https://code.visualstudio.com/api/references/vscode-api#TreeDragAndDropController

https://github.com/mre/vscode-snippet