How to Create VSCode Extension for Dynamsoft Barcode Reader Development
Tags: vscode
Published: 2022-05-20 00:30:04

[Dynamsoft Barcode Reader](https://www.dynamsoft.com/barcode-reader/overview/), as an enterprise-class barcode and QR code SDK, supports many platforms (Windows, Linux, macOS, Android, iOS, Raspberry Pi OS, and Web) and programming languages (Python, Java, Swift, C#, Cpp, and JavaScript). There are many sample projects distributed in Dynamsoft GitHub repositories and official website. To help developers get started with Dynamsoft Barcode Reader quickly, we created a VSCode extension that features project generation for different programming languages and frameworks, code snippet insertion, and template download.

## Install the VSCode Extension
[https://marketplace.visualstudio.com/items?itemName=yushulx.dbr-code-generator](https://marketplace.visualstudio.com/items?itemName=yushulx.dbr-code-generator)

## Creating Our First VSCode Extension
Let's follow the [official tutorial](https://code.visualstudio.com/api/get-started/your-first-extension) to create a basic VSCode extension project:
1. Install Yeoman and VS Code Extension Generator:
    
    ```bash
    npm install -g yo generator-code
    ```
2. Run the following command to scaffold a TypeScript or JavaScript project:
    
    ```bash
    yo code
    ``` 
3. Press `F5` to debug the project and then press `F1` to show the `Command Palette`. We can enter `Hello World` command to see the result.

## Making VSCode Extension for Dynamsoft Barcode Reader

The extension will support:
- Project generation 
    
    Create console, desktop GUI, web and mobile apps.

- Code snippet insertion 
   
    Add code snippets into corresponding programing language files.

- File download
    
    Download pre-set parameter template files for barcode and QR code recognition.

### Steps to Generate a DBR Project


#### Step1: List the commands for Command Palette
In `package.json` file, the commands for project generation are defined as follows:

```json
"activationEvents": [
    "onCommand:dbr.dotnet",
    "onCommand:dbr.cpp",
    "onCommand:dbr.web",
    "onCommand:dbr.python",
    "onCommand:dbr.android",
    "onCommand:dbr.ios"
  ],
"contributes": {
    "commands": [
      {
        "command": "dbr.dotnet",
        "title": "DBR: Create .NET Project"
      },
      {
        "command": "dbr.cpp",
        "title": "DBR: Create C/C++ Project"
      },
      {
        "command": "dbr.web",
        "title": "DBR: Create Web Project"
      },
      {
        "command": "dbr.python",
        "title": "DBR: Create Python Project"
      },
      {
        "command": "dbr.android",
        "title": "DBR: Create Android Project"
      },
      {
        "command": "dbr.ios",
        "title": "DBR: Create iOS Project"
      }
    ],
  },
```

To make the commands work, we need to add the following TypeScript code to `extension.ts` file:

```typescript
export function activate(context: vscode.ExtensionContext) {

	context.subscriptions.push(...[
		vscode.commands.registerCommand('dbr.dotnet', async () => {
		}),
		vscode.commands.registerCommand('dbr.cpp', async () => {
		}),
		vscode.commands.registerCommand('dbr.web', async () => {
		}),
		vscode.commands.registerCommand('dbr.python', async () => {
		}),
		vscode.commands.registerCommand('dbr.android', async () => {
		}),
		vscode.commands.registerCommand('dbr.ios', async () => {
		})
	]);
}
```

![vscode extension for Dynamsoft Barcode Reader](https://www.dynamsoft.com/codepool/img/2022/05/vscode-dbr-extension.png)

#### Step2: Prepare the project template
We put some project templates under the `res` folder and store their physical paths in TypeScript:

```typescript
private samples = {
        "python": path.join(__dirname, '../res/python/'),
        "dotnet": path.join(__dirname, '../res/dotnet/'),
        "cpp": path.join(__dirname, '../res/cpp/'),
        "web": path.join(__dirname, '../res/web/'),
        "android": path.join(__dirname, '../res/android/'),
        "ios": path.join(__dirname, '../res/ios/')
    };
``` 

According to the input command, we can return the corresponding project path:

```typescript
enum Type {
    CONSOLE = 'Console App',
    WINFORMS = 'Windows Forms App',
    FILE = 'File Reader',
    CAMERA = 'Camera Scanner',
    GUI = 'GUI App',
}

enum FolderName {
    FOLDER_NAME_CONSOLE = 'console',
    FOLDER_NAME_WINFORMS = 'winforms',
    FOLDER_NAME_FILE = 'file',
    FOLDER_NAME_CAMERA = 'camera',
    FOLDER_NAME_GUI = 'gui',
}

switch (option) {
    case "dotnet":
        root = this.samples.dotnet;
        projectType = await this.getProjectType([Type.CONSOLE, Type.WINFORMS]);
        break;
    case "cpp":
        root = this.samples.cpp;
        projectType = await this.getProjectType([Type.CONSOLE]);
        break;
    case "web":
        root = this.samples.web;
        projectType = await this.getProjectType([Type.FILE, Type.CAMERA]);
        break;
    case "python":
        root = this.samples.python;
        projectType = await this.getProjectType([Type.CONSOLE, Type.GUI]);
        break;
    case "android":
        root = this.samples.android;
        projectType = await this.getProjectType([Type.CAMERA]);
        break;
    case "ios":
        root = this.samples.ios;
        projectType = await this.getProjectType([Type.CAMERA]);
        break;
}
if (projectType === '') { return; }

switch (projectType) {
    case Type.CONSOLE:
        src = path.join(root, FolderName.FOLDER_NAME_CONSOLE);
        break;
    case Type.WINFORMS:
        src = path.join(root, FolderName.FOLDER_NAME_WINFORMS);
        break;
    case Type.FILE:
        src = path.join(root, FolderName.FOLDER_NAME_FILE);
        break;
    case Type.CAMERA:
        src = path.join(root, FolderName.FOLDER_NAME_CAMERA);
        break;
    case Type.GUI:
        src = path.join(root, FolderName.FOLDER_NAME_GUI);
        break;
}
```

#### Step3: Select and open the target project folder 
When creating a new project, we call `vscode.window.showQuickPick` to list two options: using current workspace folder or opening a new folder.

```typescript
const answer = await vscode.window.showQuickPick(['Yes', 'No'], { placeHolder: 'Do you want to create a new folder?' });
        if (!answer) { return; }
```

If the answer is `Yes`, we call `vscode.window.showOpenDialog` to pop up a window for opening a folder:

```typescript
const projectName = await vscode.window.showInputBox({
            prompt: 'Enter a name for the new project',
            validateInput: (value: string): string => {
                if (!value.length) {
                    return 'A project name is required';
                }
                return '';
            }
        });

let workspace = '';
const folderUris = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, canSelectMany: false, openLabel: 'Select folder' });
if (!folderUris) {
    return '';
}

let workspaceFolderUri = folderUris[0];
workspace = workspaceFolderUri.fsPath;
let des = path.join(workspace, projectName);
```

If the answer is `No`, we use the current workspace folder:

```typescript
let folders = vscode.workspace.workspaceFolders;
let des = folders[0].uri.fsPath;
```

#### Step4: Copy project files to the target folder
Once the target folder is determined, we can copy the project template files to the target folder:

```typescript
export function copyFolder(src: string, des: string) {
	fs.readdir(src, (err, files) => {
		if (err) {
			return;
		}

		files.forEach(file => {
			let srcFile = path.join(src, file);
			let desFile = path.join(des, file);
			if (fs.statSync(srcFile).isDirectory()) {
				fs.mkdirSync(desFile);
				copyFolder(srcFile, desFile);
			} else {
				fs.copyFileSync(srcFile, desFile);
			}
		});
	});
}

copyFolder(src, des);
```


### Code Snippet Insertion
The structure of the snippet files are as follows:

```json
{
    "prefix": "",
    "body": "",
    "description": ""
}
```

Create a folder named `snippets` and add some snippet files:

```bash
snippets
 - android.json
 - cpp.json
 - csharp.json
 - python.json
 - swift.jon
 - web.json
```

After that, open `package.json` to add the snippet file paths:

```json
"contributes": {
    "snippets": [
			{
				"language": "python",
				"path": "./snippets/python.json"
			},
      {
				"language": "csharp",
				"path": "./snippets/csharp.json"
			},
      {
				"language": "html",
				"path": "./snippets/web.json"
			},
      {
				"language": "cpp",
				"path": "./snippets/cpp.json"
			},
      {
				"language": "java",
				"path": "./snippets/android.json"
			},
      {
				"language": "swift",
				"path": "./snippets/swift.json"
			}
		]
}
```

As long as the extension is installed in Visual Studio Code, we can quickly add code snippet in corresponding language files to expedite the development process.

![vscode Dynamsoft Barcode Reader snippets](https://www.dynamsoft.com/codepool/img/2022/05/vscode-dbr-snippets.gif)

### File Download
To download files from specified URLs in VSCode extension, we invoke `http` or `https` module. The `vscode.window.withProgress` is used to visually show the progress status.

```typescript
import * as https from 'https';

export async function httpsDownload(url: string, filename: string) {
	await vscode.window.withProgress({
		location: vscode.ProgressLocation.Notification,
		cancellable: true
	}, (progress, token) => {
		token.onCancellationRequested(() => {
			console.log("User canceled the long running operation");
		});

		progress.report({ message: "Downloading " + filename });

		return new Promise((resolve, reject) => {
			const file = fs.createWriteStream(filename);
			https.get(url, function (response: any) {
				response.pipe(file);
				file.on("finish", () => {
					file.close();
					console.log("Download Completed");
					resolve("Download Completed");
				});
			});

		});
	});
}
```

We call the function defined above to download parameter template files of Dynamsoft Barcode Reader:

```typescript
import * as vscode from 'vscode';
import { httpsDownload } from './utils';
import * as path from 'path';

enum Template {
    URL = "https://raw.githubusercontent.com/yushulx/cmake-cpp-barcode-qrcode/main/templates/",
    SPEED = 'Speed',
    SPEED_FILE = 'speed.json',
    BALANCED = 'Balanced',
    BALANCED_FILE = 'balanced.json',
    COVERAGE = 'Coverage',
    COVERAGE_FILE = 'coverage.json',
    MORECOVERAGE = 'More Coverage',
    MORECOVERAGE_FILE = 'morecoverage.json',
    MOSTCOVERAGE = 'Most Coverage',
    MOSTCOVERAGE_FILE = 'mostcoverage.json',
}

export class ParameterManager {
    private templates: any = {};

    constructor() {
        this.templates[Template.SPEED] = Template.URL + Template.SPEED_FILE;
        this.templates[Template.BALANCED] = Template.URL + Template.BALANCED_FILE;
        this.templates[Template.COVERAGE] = Template.URL + Template.COVERAGE_FILE;
        this.templates[Template.MORECOVERAGE] = Template.URL + Template.MORECOVERAGE_FILE;
        this.templates[Template.MOSTCOVERAGE] = Template.URL + Template.MOSTCOVERAGE_FILE;
    }

    public async downloadParameterFile() {
        // Select a template file
        const answer = await vscode.window.showQuickPick([Template.SPEED, Template.BALANCED, Template.COVERAGE, Template.MORECOVERAGE, Template.MOSTCOVERAGE], { placeHolder: 'Select a template' });
        if (!answer) { return; }

        let folders = vscode.workspace.workspaceFolders;
        if (!folders) {
            vscode.window.showWarningMessage('No folder is opened.');
        }
        else {
            let des = folders[0].uri.fsPath;
            switch (answer) {
                case Template.SPEED:
                    httpsDownload(this.templates[Template.SPEED], path.join(des, Template.SPEED_FILE));
                    break;
                case Template.BALANCED:
                    httpsDownload(this.templates[Template.BALANCED], path.join(des, Template.BALANCED_FILE));
                    break;
                case Template.COVERAGE:
                    httpsDownload(this.templates[Template.COVERAGE], path.join(des, Template.COVERAGE_FILE));
                    break;
                case Template.MORECOVERAGE:
                    httpsDownload(this.templates[Template.MORECOVERAGE], path.join(des, Template.MORECOVERAGE_FILE));
                    break;
                case Template.MOSTCOVERAGE:
                    httpsDownload(this.templates[Template.MOSTCOVERAGE], path.join(des, Template.MOSTCOVERAGE_FILE));
                    break;
            }
        }
    }
}
```

## Source Code
[https://github.com/yushulx/vscode-dbr-code-generator](https://github.com/yushulx/vscode-dbr-code-generator)