[go: up one dir, main page]

Skip to content

Commit

Permalink
Add the Atlassian Jira linkifier extension sample
Browse files Browse the repository at this point in the history
Reviving an old extension of mine.
  • Loading branch information
TomasHubelbauer committed Sep 17, 2024
1 parent 2b4b951 commit 5080c1d
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
bun.lockb
164 changes: 164 additions & 0 deletions .vscode/extensions/atlassian-jira-ticket-code-linker/extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Run `bun install` to install `@types/vscode`
// See https://code.visualstudio.com/api/references/vscode-api for the reference
const vscode = require('vscode');

/** @type {vscode.TextEditorDecorationType} */
let textEditorDecorationType;

async function activate(/** @type {vscode.ExtensionContext} */ context) {
// TODO: Listen for configuration changes instead of reloading in the provider
// See https://github.com/microsoft/vscode-extension-samples/blob/main/configuration-sample/src/extension.ts#L192
const configuration = await loadAndValidateConfiguration();

textEditorDecorationType = vscode.window.createTextEditorDecorationType({});

context.subscriptions.push(textEditorDecorationType);

const provider = new AtlassianJiraTicketLinkProvider();
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider({ scheme: 'file', language: 'markdown' }, provider));
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider({ scheme: 'untitled', language: 'markdown' }, provider));

if (!configuration) {
return;
}

//vscode.window.showInformationMessage(`Atlassian Jira Ticket Code Linker on for ${pluralize(configuration.length, 'code')} ${configuration.map(item => item.code).join(', ')}`);
}

function deactivate() {
// Do nothing
}

module.exports = {
activate,
deactivate
};

async function loadAndValidateConfiguration() {
const configuration = vscode.workspace.getConfiguration('atlassianJiraTicketCodeLinker');

/** @type {{ code: string; url: string; }[] | undefined} */
const mapping = configuration.get('mapping');
if (!mapping) {
/** @type {vscode.MessageItem} */
const openSettingsMessageItem = {
title: 'Open Settings'
};

const selectedItem = await vscode.window.showInformationMessage(
'No mapping configured',
{
modal: true,
detail: 'Please configure the mapping in the user or workspace settings'
},
openSettingsMessageItem
);

if (selectedItem === openSettingsMessageItem) {
// TODO: Figure out why this doesn't open JSON or focus the JSON key
// vscode.commands.executeCommand('workbench.action.openSettings', {
// // ISettingsEditorOptions
// target: vscode.ConfigurationTarget.WorkspaceFolder,
// revealSetting: {
// key: 'atlassianJiraTicketCodeLinker.mapping',
// edit: true,
// },

// // IOpenSettingsOptions
// jsonEditor: true,
// openToSide: true,
// });

vscode.commands.executeCommand('workbench.action.openWorkspaceSettings', 'atlassianJiraTicketCodeLinker.mapping');
}

return;
}

if (!Array.isArray(mapping)) {
vscode.window.showErrorMessage('Mapping must be an array of objects');
return;
}

for (const item of mapping) {
if (!('code' in item)) {
vscode.window.showErrorMessage('Mapping must have a "code" key');
return;
}

if (typeof item.code !== 'string') {
vscode.window.showErrorMessage('Mapping "code" key must be a string');
return;
}

const regex = /^[A-Z]+$/;
if (!regex.test(item.code)) {
vscode.window.showErrorMessage(`Mapping "code" key must be a \`${regex}\` string`);
return;
}

if (!('url' in item)) {
vscode.window.showErrorMessage('Mapping must have a "ticket" key');
return;
}

if (!URL.canParse(item.url) || new URL(item.url).pathname !== '/browse/') {
vscode.window.showErrorMessage('Mapping "ticket" key must be a valid `/browse` HTTP(S) URL');
return;
}

if (typeof item.url !== 'string') {
vscode.window.showErrorMessage('Mapping "ticket" key must be a string');
return;
}
}

return mapping;
}

function pluralize(count, singular, plural) {
return count === 1 ? singular : (plural || `${singular}s`);
}

/** @implements {vscode.DocumentLinkProvider} */
class AtlassianJiraTicketLinkProvider {
async provideDocumentLinks(
/** @type {vscode.TextDocument} */
document,
/** @type {vscode.CancellationToken} */
token) {
const configuration = await loadAndValidateConfiguration();
const text = document.getText();

/** @type {vscode.DocumentLink[]} */
const links = [];
for (const { code, url } of configuration) {
const regex = new RegExp(`(?<ticket>${code}-\\d+)`, 'g');

/** @type {RegExpExecArray | null} */
let match;
while (match = regex.exec(text)) {
if (!match.groups || !match.groups.ticket) {
throw new Error('Match does not have a ticket group');
}

const start = document.positionAt(match.index);
const end = document.positionAt(match.index + match[0].length);
const range = new vscode.Range(start, end);
const link = new vscode.DocumentLink(range, vscode.Uri.parse(url + match.groups.ticket));
links.push(link);
}

const editor = await vscode.window.showTextDocument(document);

/** @type {vscode.DecorationOptions[]} */
const decorations = links.map(link => ({
range: link.range,
hoverMessage: link.target.toString()
}));

editor.setDecorations(textEditorDecorationType, decorations);
return links;
}
}
}
40 changes: 40 additions & 0 deletions .vscode/extensions/atlassian-jira-ticket-code-linker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "atlassian-jira-ticket-code-linker",
"displayName": "Atlassian Jira Ticket Code Linker",
"description": "Linkifies Atlassian Jira ticket codes in MarkDown documents.",
"version": "0.0.0",
"publisher": "TomasHubelbauer",
"engines": {
"vscode": "^1.93.0"
},
"activationEvents": [
"onLanguage:markdown"
],
"contributes": {
"configuration": {
"title": "Atlassian Jira Ticket Code Linker",
"properties": {
"atlassianJiraTicketCodeLinker.mapping": {
"type": "array",
"items": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "The code of the ticket to linkify. E.g.: TICKET or TICKET-"
},
"url": {
"type": "string",
"description": "The ticket URL template. E.g.: https://jira.atlassian.com/browse/"
}
}
}
}
}
}
},
"main": "./extension.js",
"devDependencies": {
"@types/vscode": "^1.93.0"
}
}
3 changes: 2 additions & 1 deletion .vscode/extensions/my-extension/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
const vscode = require('vscode');

function activate(/** @type {vscode.ExtensionContext} */ _context) {
vscode.window.showInformationMessage('Hello World from my-extension!');
// Uncomment this to see the extension is activated upon VS Code startup
//vscode.window.showInformationMessage('Hello World from my-extension!');
}

function deactivate() {
Expand Down
5 changes: 3 additions & 2 deletions .vscode/extensions/my-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "my-extension",
"description": "My Extension",
"displayName": "My Extension",
"description": "A sample extension demonstrating VS Code local workspace extensions.",
"version": "0.0.0",
"publisher": "Tomas Hubelbauer",
"publisher": "TomasHubelbauer",
"engines": {
"vscode": "^1.93.0"
},
Expand Down
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"atlassianJiraTicketCodeLinker.mapping": [
{
"code": "TICKET",
"url": "https://jira.atlassian.com/browse/"
}
]
}
33 changes: 33 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,36 @@ will do the trick.

Use the Output pane in VS Code to check for extension host errors with loading
your extension if you're facing any.

## Extension samples

### My extension

I added the simplest possible extension that could be authored.
See `.vscode/extensions/my-extension/extension.js` and uncomment the live in
`activate`, then restart VS Code to see the extension come alive and display
the information popup.

### Atlassian Jira ticket code linker

Linkifiers Atlassian Jira ticket codes in MarkDown documents. E.g.: TICKET-1.

This is a resurrection of https://github.com/TomasHubelbauer/vscode-markdown-jira-links,
but in the form of a local workspace extension!

This extension contributes configuration to set up the ticket codes and the
corresponding URLs.
Use `@ext:TomasHubelbauer.atlassian-jira-ticket-code-linker` to filter the
extension settings in the VS Code settings editor or use `settings.json` either
at the user level or the workspace level:

```json
{
"atlassianJiraTicketCodeLinker": [
{
"code": "TICKET",
"url": "https://jira.atlassian.com/browse/"
}
]
}
```

0 comments on commit 5080c1d

Please sign in to comment.