How to add syntax highlighting to any source code in Bitbucket
My work Bitbucket doesn’t support proper syntax highlighting for kotlin
for unknown reasons. A quick search led me to this Atlassian public jira ticket from 2018.
They closed the ticket now - like last week! But the issue they linked is a ticket that adds syntax highlight to diffs not for viewing source files. Anyway, I still do not see syntax highlights on our repo for .kt
files.
And it is frustrating to stop and share multiple windows during Microsoft Teams/Slack sessions. I prefer to share one browser window so I can simply open another repo in another tab instead of doing a stop and start sharing routine after switching it to a new repo with Android Studio. Also I do not need to wait for Gradle sync or resolve errors.
I initially thought I could use highlight.js to add syntax highlighting. But digging a little deeper I found that Bitbucket uses Monaco-editor as their editor/viewer for source code.
After spending some time reviewing Monaco
documentation and monaco / monarch playgrounds. I was able to add syntax-highlighting to kotlin
relatively easily. Fortunately, I was able to find kotlin syntax in monaco-repo and most of my time spent on figuring out what goes into setMonarchTokensProvider
Now, I have a bookmarklet to add kotlin syntax highlight with one click.
The same can be used for adding support to any custom language that may not be supported by your Bitbucket instance.
Here is the before and after.
I pasted the screengrab of the post syntax highlight on the screenshot for easier comparison. The below script doesn’t do split screen :-)
JS
// source: https://github.com/microsoft/monaco-editor/blob/main/src/basic-languages/kotlin/kotlin.ts
monaco.languages.register({ id: "kotlin" });
monaco.languages.setLanguageConfiguration("kotlin", {
// the default separators except `@$`
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
{ open: '<', close: '>' }
],
folding: {
markers: {
start: new RegExp('^\\s*//\\s*(?:(?:#?region\\b)|(?:<editor-fold\\b))'),
end: new RegExp('^\\s*//\\s*(?:(?:#?endregion\\b)|(?:</editor-fold>))')
}
}
});
monaco.languages.setMonarchTokensProvider("kotlin", {
defaultToken: '',
tokenPostfix: '.kt',
keywords: [
'as',
'as?',
'break',
'class',
'continue',
'do',
'else',
'false',
'for',
'fun',
'if',
'in',
'!in',
'interface',
'is',
'!is',
'null',
'object',
'package',
'return',
'super',
'this',
'throw',
'true',
'try',
'typealias',
'val',
'var',
'when',
'while',
'by',
'catch',
'constructor',
'delegate',
'dynamic',
'field',
'file',
'finally',
'get',
'import',
'init',
'param',
'property',
'receiver',
'set',
'setparam',
'where',
'actual',
'abstract',
'annotation',
'companion',
'const',
'crossinline',
'data',
'enum',
'expect',
'external',
'final',
'infix',
'inline',
'inner',
'internal',
'lateinit',
'noinline',
'open',
'operator',
'out',
'override',
'private',
'protected',
'public',
'reified',
'sealed',
'suspend',
'tailrec',
'vararg',
'field',
'it'
],
operators: [
'+',
'-',
'*',
'/',
'%',
'=',
'+=',
'-=',
'*=',
'/=',
'%=',
'++',
'--',
'&&',
'||',
'!',
'==',
'!=',
'===',
'!==',
'>',
'<',
'<=',
'>=',
'[',
']',
'!!',
'?.',
'?:',
'::',
'..',
':',
'?',
'->',
'@',
';',
'$',
'_'
],
// we include these common regular expressions
symbols: /[=><!~?:&|+\-*\/\^%]+/,
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
digits: /\d+(_+\d+)*/,
octaldigits: /[0-7]+(_+[0-7]+)*/,
binarydigits: /[0-1]+(_+[0-1]+)*/,
hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
// The main tokenizer for our languages
tokenizer: {
root: [
// class name highlighting
[/[A-Z][\w\$]*/, 'type.identifier'],
// identifiers and keywords
[
/[a-zA-Z_$][\w$]*/,
{
cases: {
'@keywords': { token: 'keyword.$0' },
'@default': 'identifier'
}
}
],
// whitespace
{ include: '@whitespace' },
// delimiters and operators
[/[{}()\[\]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[
/@symbols/,
{
cases: {
'@operators': 'delimiter',
'@default': ''
}
}
],
// @ annotations.
[/@\s*[a-zA-Z_\$][\w\$]*/, 'annotation'],
// numbers
[/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/, 'number.float'],
[/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/, 'number.float'],
[/0[xX](@hexdigits)[Ll]?/, 'number.hex'],
[/0(@octaldigits)[Ll]?/, 'number.octal'],
[/0[bB](@binarydigits)[Ll]?/, 'number.binary'],
[/(@digits)[fFdD]/, 'number.float'],
[/(@digits)[lL]?/, 'number'],
// delimiter: after number because of .\d floats
[/[;,.]/, 'delimiter'],
// strings
[/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
[/"""/, 'string', '@multistring'],
[/"/, 'string', '@string'],
// characters
[/'[^\\']'/, 'string'],
[/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
[/'/, 'string.invalid']
],
whitespace: [
[/[ \t\r\n]+/, ''],
[/\/\*\*(?!\/)/, 'comment.doc', '@javadoc'],
[/\/\*/, 'comment', '@comment'],
[/\/\/.*$/, 'comment']
],
comment: [
[/[^\/*]+/, 'comment'],
[/\/\*/, 'comment', '@comment'],
[/\*\//, 'comment', '@pop'],
[/[\/*]/, 'comment']
],
//Identical copy of comment above, except for the addition of .doc
javadoc: [
[/[^\/*]+/, 'comment.doc'],
[/\/\*/, 'comment.doc', '@push'],
[/\/\*/, 'comment.doc.invalid'],
[/\*\//, 'comment.doc', '@pop'],
[/[\/*]/, 'comment.doc']
],
string: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, 'string', '@pop']
],
multistring: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"""/, 'string', '@pop'],
[/./, 'string']
]
}});
var ed = monaco.editor.getModels()[0]
monaco.editor.setModelLanguage(ed, 'kotlin')
Latest version of the script can be found at https://gist.github.com/palaniraja/c987ed0cc23b3cdbfaa329ce69ed888e