Skip to content

Extension Hands On#

@https://github.com/microsoft/vscode-extension-samples

Command#

Usage#

@https://code.visualstudio.com/api/references/vscode-api

API Desc
executeCommand(command: string, ...) Executes the command denoted by the given command identifier 透過給定的 command id 執行對應的 command
getCommands(filterInternal?: boolean) Retrieve the list of all available commands. Commands starting with an underscore are treated as internal commands 取得 command ,可以給個 boolean 值決定使否列出vscode內部使用的 command
registerCommand(command: string, callback) Registers a command that can be invoked via a keyboard shortcut, a menu item, an action, or directly 在 vscode 中註冊一個可以被 keyboard shortcut、extension 或其他 UI 元件呼叫的命令
registerTextEditorCommand(command: string, callback) Registers a text editor command that can be invoked via a keyboard shortcut, a menu item, an action, or directly 跟 register 相似,但只有在 vscode 開啟文件的 editor 時才會觸發

Code & Detail#

Command API
// toggle the panel
const execCmd = vscode.commands.registerCommand('testextension.executeCommand', async () => {
    await vscode.commands.executeCommand('workbench.action.togglePanel');
    vscode.window.showInformationMessage('Panel has been toggle');
});

// toggle the sidebar
const execCmd2 = vscode.commands.registerCommand('testextension.execCommand', async () => {
    await vscode.commands.executeCommand('workbench.action.toggleSidebarVisibility');
    vscode.window.showInformationMessage('Sidebar');
});

context.subscriptions.push(execCmd, execCmd2);

B4 executeCommand

After executeCommand

B4 exec

After exec

// showing internal commands
const getCmd = vscode.commands.registerCommand('testextension.getHi', () => {
    vscode.commands.getCommands(false).then((data) => {
    console.log('commands', data);
    });
});

// not showing internal commands
const getCmd2 = vscode.commands.registerCommand('testextension.getHi', () => {
    vscode.commands.getCommands(true).then((data) => {
    console.log('commands', data);
    });
});

context.subscriptions.push(getCmd, getCmd2);

B4 getCommands

Set false

Set true

const command = 'testextension.sayHi';

const commandHandler = (name: string = 'world') => {
    console.log(`Hello ${name}!!!`);
    vscode.window.showInformationMessage(`Hello ${name}!!!`);
};

context.subscriptions.push(vscode.commands.registerCommand(command, commandHandler));

/* ---------- 我是分隔線 ---------- */

let disposable = vscode.commands.registerCommand('testextension.helloWorld', () => {
    vscode.window.showInformationMessage('Hello World from testExtension!');
});

context.subscription.push(disposable);

Contributes

Register commands

1
2
3
4
5
6
// register text editor command
const regTxtCmd = vscode.commands.registerTextEditorCommand('testextension.textEditorCommand', () => {
    vscode.window.showInformationMessage('Show Message Only when there is an active editor')
});

context.subscription.push(regTxtCmd);

B4 open active editor

After open active editor

Context Menu#

Usage#

@https://code.visualstudio.com/api/ux-guidelines/context-menus
@https://code.visualstudio.com/api/references/contribution-points#contributes.menus

{
    "contributes": {
        "menus": {
            "${extension writers can contribute to}": [
                {
                    "alt": "${commandID}",
                    "command": "${commandID}",
                    "group": "${sorting group}",
                    "submenu": "${submentID}",
                    "when": "${boolean}",
                }
            ]
        }
    }
}
contributes desc
alt identifier of an alternative command to execute
command identifier of the command to execute
group group into which this item belongs
submenu identifier of the submenu to display in this item
when condition which must be true to show this item

Code & Detail#

Context Menu

"contributes": {
  "commands": [
    {
      "command": "context-menu.helloWorld",
      "title": "Hello World"
    },
    {
      "command": "context-menu.panelVisible",
      "title": "Hi Panel"
    },
    {
      "command": "context-menu.panelVisibleAlt",
      "title": "Hi PanelAlt"
    },
    {
      "command": "context-menu.panelInvisible",
      "title": "Bye Panel"
    }
  ],
  "menus": {
    "explorer/context": [
      {
        "command": "context-menu.panelVisible",
        "alt": "context-menu.panelVisibleAlt",
        "group": "navigation",
        "when": "!activePanel"
      },
      {
        "command": "context-menu.panelInvisible",
        "group": "navigation",
        "when": "activePanel"
      }
    ]
  }
}
group sorting Group sorting

// My context menu
const menu1 = vscode.commands.registerCommand('context-menu.panelVisible', async () => {
    await vscode.commands.executeCommand('workbench.action.togglePanel');
    vscode.window.showInformationMessage('Execute command when panel is visible');
});

const menu2 = vscode.commands.registerCommand('context-menu.panelVisibleAlt', async () => {
    await vscode.commands.executeCommand('workbench.action.toggleDevTools');
    vscode.window.showInformationMessage('Execute command when using alternative menu command');
});

const menu3 = vscode.commands.registerCommand('context-menu.panelInvisible', async () => {
    await vscode.commands.executeCommand('workbench.action.togglePanel');
    vscode.window.showInformationMessage('Execute command when panel is invisible');
});

context.subscriptions.push(menu1, menu2, menu3);

B4 Hi

After Hi

B4 Bye

After Bye

B4 Hi Alt

After Hi Alt

TreeView, DataProvider & View Container#

TreeView

View Container

Usage#

@https://code.visualstudio.com/api/extension-guides/tree-view

TreeView#

identifier desc
explorer explorer view in the side bar
debug run and debug view in the side bar
scm source control view in the side bar
test test explorer view in the side bar

TreeDataProvider#

TreeDataProvider desc
getTreeItem return the children for the given element or root (if no element is passed) 回傳TreeView呈現的TreeItem Element
getChildren return the UI representation (TreeItem) of the element that gets displayed in the view 回傳TreeView底下的treeItem們
Registering desc
vscode.window.registerTreeDataProvider register the tree data provider by providing registered view ID and data provider
vscode.window.createTreeView create the Tree View by broviding view ID and data provider
Updating content desc
onDidChangeTreeData if your tree data can change and you want to update the treeview 使用EventEmitter的event監聽方法,通知DataProvider的訂閱者資料已經發生變化

View Container#

fields
id The ID of the new view container
title The name that will show up the top of the veiw container
icon An image that will be displayed for the view container when in the Activity Bar

Code & Detail#

TreeView & DataProvider
"contributes": {
  "commands": [
    {
      "command": "treeview.helloWorld",
      "title": "Hello World"
    },
    {
      "command": "treeview.registerdataprovider",
      "title": "Resigter Data Provider"
    }
  ],
  "views": {
    "explorer": [
      {
        "id": "TreeView",
        "contextualTitle": "TreeViewTitle",
        "name": "TreeView"
      }
    ]
  },
  "viewsWelcome": [
    {
      "view": "TreeView",
      "contents": "Welcome to NewTreeView =]\n[Resigter Data Provider](command:treeview.registerdataprovider)"
    }
  ]
}

viewsWelcome

TreeView

vscode.window.registerTreeDataProvider('TreeView', new DataProvider());

vscode.window.showInformationMessage('Create TreeView!!');

/* -----  ----- */

class TreeViewItem extends vscode.TreeItem {
    constructor(label: string, collapsibleStates?: vscode.TreeItemCollapsibleState) {
        super(label, collapsibleStates);
    }
}

class DataProvider implements vscode.TreeDataProvider<TreeViewItem> {
    // Implement this if your tree data can change and your want to update the treeview
    onDidChangeTreeData?: vscode.Event<void | TreeViewItem | TreeViewItem[] | null | undefined> | undefined;

    // Implement this to return the UI representation of the element that gets displayed in the view
    getTreeItem(element: TreeViewItem): vscode.TreeItem | Thenable<vscode.TreeItem> {
        return element;
        throw new Error('Method not implemented.');
    }

    // Implement this to return the children for the given element or root (if no element is passed)
    getChildren(element?: TreeViewItem | undefined): vscode.ProviderResult<TreeViewItem[]> {
        return Promise.resolve([
            new TreeViewItem('TreeItem-01'),
            new TreeViewItem('TreeItem-02'),
            new TreeViewItem('TreeItem-03'),
        ]);
        throw new Error('Method not implemented.');
    }
    getParent?(element: TreeViewItem): vscode.ProviderResult<TreeViewItem> {
        throw new Error('Method not implemented.');
    }
    resolveTreeItem?(item: vscode.TreeItem, element: TreeViewItem, token: vscode.CancellationToken): vscode.ProviderResult<vscode.TreeItem> {
        throw new Error('Method not implemented.');
    }
}
View Container & DataProvider

"contributes": {
  "commands": [
    {
      "command": "dataprovider.helloWorld",
      "title": "Hello World"
    },
    {
      "command": "dataprovider.registerDataProvider",
      "title": "Register DataProvider"
    },
    {
      "command": "dataprovider.01_add",
      "title": "Add New Item"
    },
    {
      "command": "dataprovider.02_edit",
      "title": "Edit Exist Item",
      "icon": {
        "light": "./src/assets/edit.svg",
        "dark": "./src/assets/edit.svg"
      }
    },
    {
      "command": "dataprovider.03_delete",
      "title": "Delete Item",
      "icon": {
        "light": "./src/assets/trashcan.svg",
        "dark": "./src/assets/trashcan.svg"
      }
    }
  ],
  "viewsContainers": {
    "activitybar": [
      {
        "id": "treeview-explorer",
        "title": "TreeView Explorer",
        "icon": "./src/assets/storage.svg"
      }
    ]
  },
  "views": {
    "treeview-explorer": [
      {
        "id": "treeview",
        "name": "∀",
        "icon": "./src/assets/storage.svg",
        "contextualTitle": "TreeView Explorer ∀"
      }
    ]
  },
  "viewsWelcome": [
    {
      "view": "treeview",
      "contents": "Welcome to TreeView :D\n[Data Provider](command:dataprovider.registerDataProvider)"
    }
  ],
  "menus": {
    "view/title": [
      {
        "command": "dataprovider.01_add",
        "when": "view == treeview"
      }
    ],
    "view/item/context": [
      {
        "command": "dataprovider.02_edit",
        "when": "view == treeview && viewItem == treeviewitem",
        "group": "inline"
      },
      {
        "command": "dataprovider.03_delete",
        "when": "view == treeview && viewItem == treeviewitem",
        "group": "inline"
      },
      {
        "command": "dataprovider.03_delete",
        "when": "view == treeview && viewItem == treeviewitem"
      }
    ]
  }
}
View Container View Container TreeView

"commands": [
    {
      "command": "dataprovider.01_add",
      "title": "Add New Item"
    }
]

/* ----- */

"menus": {
    "view/title": [
      {
        "command": "dataprovider.01_add",
        "when": "view == treeview"
      }
    ]
}

view/title

add item

after add item

"commands": [
  {
    "command": "dataprovider.02_edit",
    "title": "Edit Exist Item",
    "icon": {
      "light": "./src/assets/edit.svg",
      "dark": "./src/assets/edit.svg"
    }
  }
]

/* ----- */

"menus": {
  "view/item/context": [
    {
      "command": "dataprovider.02_edit",
      "when": "view == treeview && viewItem == treeviewitem",
      "group": "inline"
    }
  ]
}

select file

type the file name

complete

    "commands": [
      {
        "command": "dataprovider.03_delete",
        "title": "Delete Item",
        "icon": {
          "light": "./src/assets/trashcan.svg",
          "dark": "./src/assets/trashcan.svg"
        }
      }
    ]

    /* ----- */

    "menus": {
      "view/item/context": [
        {
          "command": "dataprovider.03_delete",
          "when": "view == treeview && viewItem == treeviewitem",
          "group": "inline"
        },
        {
          "command": "dataprovider.03_delete",
          "when": "view == treeview && viewItem == treeviewitem"
        }
      ]
    }

select file

select the choice

complete

import * as vscode from 'vscode';

import { DataProvider, TreeViewItem } from './treeview-data-provider';

export function activate(context: vscode.ExtensionContext) {

      const dataProvider = new DataProvider();

      let initView = vscode.commands.registerCommand('dataprovider.registerDataProvider', () => {
            vscode.window.registerTreeDataProvider('treeview', dataProvider);

            vscode.window.showInformationMessage('Create treeview ∀');
      });

      let addItem = vscode.commands.registerCommand('dataprovider.01_add', async () => {
            const itemId = await vscode.window.showInputBox({
                  placeHolder: 'Your New TreeItem id'
            }) || '';

            if (itemId !== '') {
                  dataProvider.addItem(new TreeViewItem(itemId));
            }

            vscode.window.showInformationMessage('Add TreeViewItem ∀');
      });

      let editItem = vscode.commands.registerCommand('dataprovider.02_edit', async (item: TreeViewItem) => {
            const itemName = await vscode.window.showInputBox({
                  placeHolder: 'Your New TreeItem Name'
            }) || '';

            if (itemName !== '') {
                  dataProvider.editItem(item, itemName);
            };

            vscode.window.showInformationMessage('Re-name TreeViewItem ∀!');        
      });

      let deleteItem = vscode.commands.registerCommand('dataprovider.03_delete', async (item: TreeViewItem) => {

            const confirm = await vscode.window.showQuickPick(['delete', 'canel'], {
                  placeHolder: 'Do you  want to delete item?'
            });

            if(confirm === 'delete') {
                  dataProvider.deleteItem(item);
            }

            vscode.window.showInformationMessage('Delete TreeViewItem ∀!');
      });

      context.subscriptions.push(initView, addItem, editItem, deleteItem);
}
import * as vscode from 'vscode';

export class TreeViewItem extends vscode.TreeItem {
    constructor(label: string , collapsibleState?: vscode.TreeItemCollapsibleState) {
        super(label, collapsibleState);
        this.contextValue = 'treeviewitem';
    }
}

export class DataProvider implements vscode.TreeDataProvider<TreeViewItem> {
    private dataStorage = [
        new TreeViewItem('TreeItem_01'),
        new TreeViewItem('TreeItem_02'),
        new TreeViewItem('TreeItem_03'),
        new TreeViewItem('TreeItem_04'),
        new TreeViewItem('TreeItem_05'),
    ];

    private eventEmitter = new vscode.EventEmitter<TreeViewItem | undefined | void>();

    public get onDidChangeTreeData(): vscode.Event<TreeViewItem | undefined | void> {
        return this.eventEmitter.event;
    }

    public getTreeItem(element: TreeViewItem): vscode.TreeItem | Thenable<vscode.TreeItem> {
        return element;
    }

    public getChildren(element?: TreeViewItem | undefined): vscode.ProviderResult<TreeViewItem[]> {
        return Promise.resolve(this.dataStorage);
    }

    public addItem(newItem: TreeViewItem) {
        this.dataStorage.push(newItem);
        this.updateView();
    }

    public editItem(item: TreeViewItem, name: string) {
        const editItem = this.dataStorage.find(i => i.label === item.label);
        if (editItem) {
            editItem.label = name;
            this.updateView();
        }
    }

    public deleteItem(item: TreeViewItem) {
        this.dataStorage = this.dataStorage.filter(i => i.label !== item.label);
        this.updateView();
    }

    private updateView() {
        this.eventEmitter.fire();
    }
}

Output Channel#

Usage#

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

property desc
name: string The human-readable name of this output channel
methods desc
append A string, falsy values will not be printed
appendLine A string, false values will not be printed
clear Removes all output from the channel
dispose Dispose and free associated resources
hide Hide this channel from the UI
replace Replaces all output from the channel with the given value
show Reveal this channel in the UI

Code & Detail#

Output Channel
import * as vscode from 'vscode';

let outputchannel: vscode.OutputChannel;

export function activate(context: vscode.ExtensionContext) {

      outputchannel = vscode.window.createOutputChannel('∀∀∀∀∀');

      let disposable = vscode.commands.registerCommand('outputchannel.helloWorld', () => {
          outputchannel.show();
      });

      context.subscriptions.push(disposable);

      outputchannel.append('[ Text ]');
      outputchannel.appendLine('[ value and line ]');
      outputchannel.append('[ Text 2.0 ]');
      outputchannel.appendLine('[ value and line 2.0]');
}

B4 display

After display

channel

Workspace API#

Usage#

@https://code.visualstudio.com/api/references/contribution-points#contributes.configuration

Configuration schema desc
1⃣ title The heading used for that category
2⃣ properties Dictionary of configuration properties
3⃣ description/ markdownDescription After the title and before the input field, except for booleans, where the description is used as the label for the checkbox
4⃣ type Can be edited directly in the setting UI
5⃣ enum/ enumDescriptions/ enumItemLabels Provides descriptive text rendered at the bottom of the dropdown

workspace

Code & Detail#

Workspace API
"contributes": {
  "configuration":{
    "title": "myWorkspace",
      "properties": {
        "myWorkspace.input": {
          "type": "string",
          "default": "default",
          "description": "provide a input for accepting value"
        },
        "myWorkspace.inputNum": {
          "type": "number",
          "default": "1.011",
          "description": "provide a input for accepting number"
        },
        "myWorkspace.inputInt": {
          "type": "integer",
          "default": "1",
          "markdownDescription": "MarkdownDescription, provide a integer number"
        },
        "myWorkspace.inputBoolean": {
          "type": "boolean",
          "default": "true",
          "description": "click!!"
        },
        "myWorkspace.color.inputEnum": {
          "type": "string",
          "default": "Sapphire blue",
          "enum": ["Ruby red", "Sapphire blue", "Emerald green", "Onyx black", "Pearl white", "Topaz yellow", "Amethyst purple"],
          "enumDescriptions": [ "0", "1", "2", "3", "4", "5", "6" ]
        }
      }
    },
  "commands": [
    {
      "command": "workspace-api.helloWorld",
      "title": "Hello World"
    },
    {
      "command": "myWorkspace.getSettings",
      "title": "get WS"
    }
  ]
}

myWorkspace

Input Enum

Input

Boolean

Invalid input 1 Invalid input 2

Valid input

Invalid input

Valid input

const workspaceCmd = vscode.commands.registerCommand('myWorkspace.getSettings', async () => {
    const workspaceSettings = vscode.workspace.getConfiguration('myWorkspace');
    console.log(workspaceSettings);
});

context.subscriptions.push(workspaceCmd);

Extension Development Host get setting

Original Working File console.log