-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the Atlassian Jira linkifier extension sample
Reviving an old extension of mine.
- Loading branch information
1 parent
2b4b951
commit 5080c1d
Showing
7 changed files
with
252 additions
and
3 deletions.
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
.vscode/extensions/atlassian-jira-ticket-code-linker/.gitignore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
bun.lockb |
164 changes: 164 additions & 0 deletions
164
.vscode/extensions/atlassian-jira-ticket-code-linker/extension.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
40
.vscode/extensions/atlassian-jira-ticket-code-linker/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"atlassianJiraTicketCodeLinker.mapping": [ | ||
{ | ||
"code": "TICKET", | ||
"url": "https://jira.atlassian.com/browse/" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters