Thực hành: viết extension đầu tiên

← Về MOC · Trước: Built-in Extensions – extension tích hợp sẵn

Nguồn chính thức: Your First Extension và Extension Anatomy trên code.visualstudio.com/api.

Không cần build repo vscode

Viết extension chỉ cần VS Code bản thường + Node.js. Build repo vscode (Build & Run từ mã nguồn) chỉ cần khi bạn muốn sửa chính VS Code.

1. Ba khái niệm cốt lõi (Extension Anatomy)

Khái niệmGiải thíchVí dụ
Activation Event (sự kiện kích hoạt)Sự kiện khiến extension được nạp và chạy activate()onLanguage:sql, onCommand:..., onStartupFinished
Contribution Point (điểm đóng góp)Khai báo tĩnh trong package.json để mở rộng VS Codecontributes.commands, contributes.menus, contributes.configuration
VS Code APICác hàm JavaScript extension gọi lúc chạyvscode.commands.registerCommand, vscode.window.showInformationMessage

Các trường quan trọng trong package.json:

  • main: file vào (ví dụ ./out/extension.js).
  • engines.vscode: phiên bản API tối thiểu, đồng thời quyết định bản @types/vscode dùng được.
  • activationEvents: từ VS Code 1.74.0, lệnh khai báo trong contributes.commands tự động kích hoạt extension, không cần ghi onCommand nữa.
  • contributes: các contribution point.

2. Tạo khung dự án (scaffold)

Yêu cầu: cài Node.js và Git.

# Cách 1: không cần cài Yeoman toàn cục
npx --package yo --package generator-code -- yo code
 
# Cách 2: cài toàn cục để dùng nhiều lần
npm install --global yo generator-code
yo code

Khi được hỏi, chọn New Extension (TypeScript), đặt tên HelloWorld, identifier helloworld, khởi tạo Git, chọn bundler unbundled, trình quản lý gói npm (theo hướng dẫn chính thức).

Yeoman là gì?

Yeoman (yo) là công cụ sinh khung dự án (scaffolding tool). generator-code là “bộ sinh” do đội VS Code cung cấp.

3. Chạy và debug

  1. Mở src/extension.ts.
  2. Nhấn F5 (hoặc lệnh Debug: Start Debugging).
  3. VS Code biên dịch và mở cửa sổ mới tên Extension Development Host (cửa sổ chạy thử extension).
  4. Trong cửa sổ đó, Ctrl+Shift+P → chạy lệnh Hello World → thấy thông báo “Hello World from HelloWorld!”.

Nếu không thấy lệnh: kiểm tra engines.vscode có tương thích với VS Code đang cài không.

Mã mẫu chính thức (dịch chú thích)

// Module 'vscode' chứa Extension API
import * as vscode from 'vscode';
 
// Gọi khi extension được kích hoạt (lần đầu lệnh được chạy)
export function activate(context: vscode.ExtensionContext) {
	console.log('Congratulations, your extension "helloworld-sample" is now active!');
 
	// commandId phải khớp với trường "command" trong package.json
	let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
		vscode.window.showInformationMessage('Hello World!');
	});
 
	// Đưa vào subscriptions để VS Code tự dispose khi extension bị tắt
	context.subscriptions.push(disposable);
}
 
// Gọi trước khi extension bị tắt – nhiều extension không cần
export function deactivate() {}

Lưu ý context.subscriptions.push(...) – chính là mô hình Disposable mà lõi VS Code dùng.

4. Bài thực hành nâng cao: “SQL Helper” (tự viết)

Mục tiêu: trong file .sql, thêm lệnh đếm số batch (khối lệnh được tách bởi GO) và hiện kết quả trên status bar.

4.1 package.json (phần quan trọng)

{
  "name": "sql-helper",
  "displayName": "SQL Helper",
  "version": "0.0.1",
  "engines": { "vscode": "^1.90.0" },
  "main": "./out/extension.js",
  "activationEvents": ["onLanguage:sql"],
  "contributes": {
    "commands": [
      { "command": "sqlHelper.countBatches", "title": "SQL Helper: Đếm số batch" }
    ],
    "menus": {
      "editor/context": [
        { "command": "sqlHelper.countBatches", "when": "editorLangId == sql", "group": "navigation" }
      ]
    },
    "configuration": {
      "title": "SQL Helper",
      "properties": {
        "sqlHelper.batchSeparator": {
          "type": "string",
          "default": "GO",
          "description": "Từ khóa tách batch (T-SQL dùng GO)"
        }
      }
    }
  }
}

4.2 src/extension.ts

import * as vscode from 'vscode';
 
export function activate(context: vscode.ExtensionContext) {
	// Mục trên status bar, căn trái
	const statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
	context.subscriptions.push(statusItem);
 
	// Hàm đếm số batch dựa trên từ khóa tách (mặc định GO)
	const countBatches = (doc: vscode.TextDocument): number => {
		const sep = vscode.workspace.getConfiguration('sqlHelper').get<string>('batchSeparator', 'GO');
		// Dòng chỉ chứa từ khóa tách (không phân biệt hoa thường)
		const regex = new RegExp(`^\\s*${sep}\\s*$`, 'gim');
		const matches = doc.getText().match(regex);
		return (matches?.length ?? 0) + 1;
	};
 
	// Cập nhật status bar theo editor đang mở
	const refresh = () => {
		const editor = vscode.window.activeTextEditor;
		if (editor && editor.document.languageId === 'sql') {
			statusItem.text = `$(database) ${countBatches(editor.document)} batch`;
			statusItem.show();
		} else {
			statusItem.hide();
		}
	};
 
	context.subscriptions.push(
		vscode.commands.registerCommand('sqlHelper.countBatches', () => {
			const editor = vscode.window.activeTextEditor;
			if (!editor) {
				return;
			}
			vscode.window.showInformationMessage(`File có ${countBatches(editor.document)} batch.`);
		}),
		// Nghe sự kiện: đổi editor, sửa nội dung → cập nhật lại
		vscode.window.onDidChangeActiveTextEditor(refresh),
		vscode.workspace.onDidChangeTextDocument(e => {
			if (e.document === vscode.window.activeTextEditor?.document) {
				refresh();
			}
		})
	);
 
	refresh();
}
 
export function deactivate() {}

Liên hệ với lõi VS Code

  • $(database) là cú pháp chèn codicon (bộ icon của VS Code) vào chuỗi.
  • Khi bạn gán statusItem.text, phía extension host gọi RPC $setEntry sang workbench – đúng luồng đã phân tích ở Extension Host & RPC.
  • onDidChange… trả về Disposable – mô hình Event.

4.3 Thử nghiệm

  1. F5 → mở một file .sql trong Extension Development Host:
-- Tạo bảng tạm danh mục tài khoản
CREATE TABLE #DM_TK (TK NVARCHAR(20), TEN_TK NVARCHAR(200));
GO
-- Thêm dữ liệu mẫu
INSERT INTO #DM_TK VALUES (N'111', N'Tiền mặt'), (N'112', N'Tiền gửi ngân hàng');
GO
SELECT * FROM #DM_TK;
  1. Status bar hiện 3 batch. Chuột phải → SQL Helper: Đếm số batch.
  2. Vào Settings, đổi sqlHelper.batchSeparator thành ; để thử với PostgreSQL (lưu ý: đây chỉ là bài tập; regex hiện tại yêu cầu từ khóa nằm một mình trên dòng).

5. Bước tiếp theo

  • Đóng gói .vsix: công cụ @vscode/vsce (tài liệu Publishing Extensions).
  • Ví dụ chính thức: repo microsoft/vscode-extension-samples.
  • Muốn đóng góp API mới cho VS Code: Quy trình đóng góp (Issue, PR, API).