Spoorthi Satheesha

Sporadic writer
Serial anthropomorphizer

Share: 

Writing your own Azure Data Studio extension, Part 2

In the following post, we will be building upon the changes done in the this post. So, I would recommend going back to that, if you haven’t already done so.

In this post, we will be making the changes to show intellisense regarding the database table and its columns on hover. For this purpose, we have created a documentation.json in the root of the DbScripts repository. We will be reading the documentation text from it and loading it into extension storage. And when a user hovers over a table name, we can fetch the data from the extension storage and create a markdown string and show that.

Documentation.json

Intellisense

You can find the implementation details and the source below.

column.ts

This class declares the database table column. It contains the column properties like datatype, nullable etc and a description about the column.


export default class Column {
    Name!: string;
    Description!: string;
    DataType!: string;
    DataLength!: number;
    Nullable!: boolean;
}

extension.ts


'use strict';
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

import ProBaseDefinitionProvider from './features/proBaseDefinitionProvider';
import ProBaseHoverProvider from './features/proBaseHoverProvider';
import Helper from './utils/helper';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

    //Enables Go to Definition and Peek Definition
    context.subscriptions.push(vscode.languages.registerDefinitionProvider({ language: "sql" }, new ProBaseDefinitionProvider()));

    //Enables intellisense on hover
    context.subscriptions.push(vscode.languages.registerHoverProvider({ language: "sql" }, new ProBaseHoverProvider(context.globalState)));

    //Updates documentation
    context.subscriptions.push(vscode.commands.registerCommand('extension.updateDocumentation', () => {
        Helper.LoadDocumentation(context.globalState);
    }));
    
    if (!context.globalState.get(Helper.IsDocumentationLoaded)) {
        Helper.LoadDocumentation(context.globalState);
    }
}

helper.ts

Here we implement the code to read the documentation.json file and load the data to the extension storage.


import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import ProBaseError from './probaseError';
import Table from './table';

export default class Helper {

    public static IsDocumentationLoaded : string = "IsDocumentationLoaded";

    public static GetDbScriptsPath(): string {
        return vscode.workspace.getConfiguration().get("code.dbscriptsFolderPath") as string;
    }

    public static IsRepositorySetup(): boolean {
        var dbScriptRepoFolder = this.GetDbScriptsPath();
        if (!dbScriptRepoFolder)
            return false;
        else if (dbScriptRepoFolder === "")
            return false;
        return true;
    }
    
    public static LoadDocumentation(state: vscode.Memento): void {

        if (this.IsRepositorySetup()) {
            var documentationFilePath = path.join(this.GetDbScriptsPath(), "documentation.json");
            fs.readFile(documentationFilePath, "utf-8", function (err, content) {

                if (err)
                    throw new ProBaseError(err.name, err.message);

                state.update(Helper.IsDocumentationLoaded, true);
                JSON.parse(content).forEach((element: any) => {
                    var table: Table = Object.assign(new Table(), element);
                    state.update(table.Name.toLowerCase(), table);
                });
            });
        }
    }
}

package.json

Declaration of any new command needs to be added in package.json under the contributes section.


{
    "name": "Name",
    "displayName": "Display Name",
    "description": "Extension description",
    "version": "1.0.0",
    "engines": {
        "vscode": "^1.33.0",
        "sqlops": "*"
    },
    "activationEvents": [
        "onLanguage:sql"
    ],
    "main": "./out/extension",
    "contributes": {
      "commands": [
            {
                "command": "extension.updateDocumentation",
                "title": "Probase: Update ProArc DB tables documentation"
            },
        ],
        "configuration": [
            {
                "title": "ProBase",
                "properties": {
                    "code.dbscriptsFolderPath": {
                        "type": "string",
                        "description": "The path to DbScripts repository",
                        "scope": "application"
                    },
                    "code.sqlSource": {
                        "type": "string",
                        "enum": [
                            "MS SQL",
                            "Oracle"
                        ],
                        "default": "MS SQL",
                        "description": "SQL source to fetch data for intellisense and other tools",
                        "scope": "application"
                    }
                }
            }
        ]
    },
}

probaseHoverProvider.ts

Here we implement the logic to read the documentation data and format it into a nice readable markdown. We need to implement the vscode.HoverProvider interface and the provideHover method needs to return the string which will be shown on hover.


import * as vscode from 'vscode'
import Table from '../utils/table';
import Column from '../utils/column';

export default class ProBaseHoverProvider implements vscode.HoverProvider {

    State: vscode.Memento;

    public constructor(state: vscode.Memento) {
        this.State = state;
    }

    provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult<vscode.Hover> {

        var selectedWord = document.getText(document.getWordRangeAtPosition(position));
        var table = this.State.get(selectedWord.toLowerCase()) as Table;
        if (table)
            return new vscode.Hover(this.buildIntellisenseMarkdown(table));
    }

    private buildIntellisenseMarkdown(table: Table): string | vscode.MarkdownString | { language: string; value: string; } {

        var tableDetails = this.buildTableMarkdown(table);
        var markdown = new vscode.MarkdownString(tableDetails);

        if (table.Columns) {

            markdown.appendMarkdown("\n***\n");

            for (var column of table.Columns) {
                var columnDetails = this.buildColumnMarkdown(column);
                markdown.appendMarkdown(columnDetails + ` - ${column.DataType}[${column.DataLength}]`);
            }
        }

        return markdown;
    }

    private buildColumnMarkdown(column: Column) {
        var columnDetails = `\n\n   \`${column.Name}\``;

        if (column.Nullable) {
            columnDetails += " *(nullable)*";
        }

        if (column.Description) {
            columnDetails += ` - ${column.Description}`;
        }

        return columnDetails;
    }

    private buildTableMarkdown(table: Table) {
        var tableDetails = `### ${table.Name.toUpperCase()}`;
        if (table.Description) {
            tableDetails += ` - ${table.Description}`;
        }
        return tableDetails;
    }
}

table.ts

This class contains the properties of the database table and contains the list of columns the table has.


import Column from "./column";

export default class Table {
    Name!: string;
    Description!: string;
    Columns!: Column[]
}