This guide explains how to add new functionality to the Cloudinary VS Code Extension.
Create a new file in src/commands/:
// src/commands/myNewCommand.ts
import * as vscode from "vscode";
import { CloudinaryTreeDataProvider } from "../tree/treeDataProvider";
function registerMyNewCommand(
context: vscode.ExtensionContext,
provider: CloudinaryTreeDataProvider
) {
context.subscriptions.push(
vscode.commands.registerCommand("cloudinary.myNewCommand", async () => {
// Implementation here
vscode.window.showInformationMessage("Command executed!");
})
);
}
export default registerMyNewCommand;Add to src/commands/registerCommands.ts:
import registerMyNewCommand from "./myNewCommand";
function registerAllCommands(
context: vscode.ExtensionContext,
provider: CloudinaryTreeDataProvider
) {
// ... existing registrations
registerMyNewCommand(context, provider);
}{
"contributes": {
"commands": [
{
"command": "cloudinary.myNewCommand",
"title": "My New Command",
"category": "Cloudinary",
"icon": "$(symbol-misc)"
}
]
}
}{
"contributes": {
"menus": {
"view/title": [
{
"command": "cloudinary.myNewCommand",
"when": "view == cloudinaryMediaLibrary",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "cloudinary.myNewCommand",
"when": "viewItem == asset",
"group": "inline"
}
]
}
}
}In src/tree/cloudinaryItem.ts:
export type CloudinaryItemType =
| 'asset'
| 'folder'
| 'loadMore'
| 'myNewType'; // Add your typeelse if (type === 'myNewType') {
this.contextValue = 'myNewType';
this.iconPath = new vscode.ThemeIcon('symbol-misc');
this.tooltip = 'My new item type';
this.command = {
command: 'cloudinary.handleMyNewType',
title: 'Handle',
arguments: [data],
};
}In src/tree/treeDataProvider.ts:
const myItem = new CloudinaryItem(
'Item Label',
vscode.TreeItemCollapsibleState.None,
'myNewType',
{ customData: 'value' },
this.cloudName!,
this.dynamicFolders
);
items.push(myItem);{
"contributes": {
"menus": {
"view/item/context": [
{
"command": "cloudinary.myCommand",
"when": "viewItem == myNewType"
}
]
}
}
}// src/commands/myWebview.ts
import * as vscode from "vscode";
import { createWebviewDocument, getScriptUri } from "../webview/webviewUtils";
import { escapeHtml } from "../webview/utils/helpers";
let panel: vscode.WebviewPanel | undefined;
function registerMyWebview(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("cloudinary.openMyWebview", () => {
if (panel) {
panel.reveal();
return;
}
panel = vscode.window.createWebviewPanel(
"cloudinaryMyWebview",
"My Webview",
vscode.ViewColumn.One,
{
enableScripts: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, "src", "webview", "media"),
],
}
);
panel.webview.html = createWebviewDocument({
title: "My Webview",
webview: panel.webview,
extensionUri: context.extensionUri,
bodyContent: getContent(),
inlineScript: "initCommon();",
});
panel.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case "doSomething":
// Handle message
break;
}
});
panel.onDidDispose(() => {
panel = undefined;
});
})
);
}
function getContent(): string {
return `
<div class="container">
<div class="card card--elevated">
<div class="card__body">
<h2>My Webview</h2>
<p>Content here</p>
<button class="btn btn--primary" id="myButton">Click Me</button>
</div>
</div>
</div>
`;
}
export default registerMyWebview;Create src/webview/media/scripts/my-webview.js:
/**
* My Webview functionality.
*/
function initMyWebview() {
const button = document.getElementById('myButton');
if (button) {
button.addEventListener('click', () => {
vscode.postMessage({ command: 'doSomething' });
});
}
}Update the webview to include it:
const myScriptUri = getScriptUri(panel.webview, context.extensionUri, "my-webview.js");
panel.webview.html = createWebviewDocument({
// ...
additionalScripts: [myScriptUri],
inlineScript: "initCommon(); initMyWebview();",
});Same as adding a command (see above).
In src/config/configUtils.ts:
export interface CloudinaryEnvironment {
apiKey: string;
apiSecret: string;
uploadPreset?: string;
myNewOption?: string; // Add your option
}const myOption = provider.getConfig().myNewOption || 'default';Update docs/configuration.md with the new option.
import { handleCloudinaryError } from '../utils/cloudinaryErrorHandler';
try {
const result = await cloudinary.api.someMethod();
} catch (err: any) {
handleCloudinaryError('Operation failed', err);
}// Input box
const query = await vscode.window.showInputBox({
placeHolder: 'Enter search term',
prompt: 'Search assets by public ID',
validateInput: (value) => value ? null : 'Cannot be empty'
});
if (!query) return; // User cancelled
// Quick pick
const selected = await vscode.window.showQuickPick(
['Option 1', 'Option 2', 'Option 3'],
{ placeHolder: 'Select an option' }
);await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Loading assets...',
cancellable: false,
},
async (progress) => {
progress.report({ increment: 0 });
// Do work
progress.report({ increment: 50 });
// More work
progress.report({ increment: 100 });
}
);// After modifying data
provider.refresh(); // Fires _onDidChangeTreeData- Compile:
npm run compile - Launch: Press
F5to open Extension Development Host - Test: Verify functionality works as expected
- Reload: Press
Ctrl+R/Cmd+Rafter code changes
- Feature works in light and dark themes
- Error cases show user-friendly messages
- Keyboard navigation works
- No console errors in Developer Tools
- TypeScript strict mode - Fix all type errors
- Use existing patterns - Follow conventions in similar files
- Escape HTML - Always use
escapeHtml()for dynamic content - Use design system - Use component classes from
components.css - Handle errors - Use
handleCloudinaryError()for API errors - Document - Add JSDoc comments for public functions