Regex highlighting

This commit is contained in:
Mayuran Visakan 2023-06-29 20:32:29 +01:00
parent 3e79d42fd9
commit cda626c017
9 changed files with 114 additions and 55 deletions

View file

@ -114,14 +114,20 @@ Clicking on header
### Highlighting
To highlight lines, specify `hl:` followed by line numbers in the first line of the codeblock.
To highlight lines, specify `hl:` followed by line numbers, plain text or regular expressions in the first line of the codeblock.
- You can specify either single line numbers separated with a comma e.g.: `hl:1,3,5,7`.
- You can specify ranges e.g.: `hl:2-5` This would highlight lines from 2 to 5.
- You can also combine the methods e.g.: `hl:1,3,4-6` This would highlight lines 1, 3 and lines from 4 to 6.
You can specify any of the following highlight types separated with commas (**without spaces**) e.g.: `hl:1,3-4,foo,'bar baz',"bar and baz",/#\w{6}/`.
- Single numbers: `hl:1` would highlight the first line
- Number ranges: `hl:1-3` would highlight lines 1 through to 3
- Plain text: `hl:foo` would highlight lines with the word `foo` inside them
- Plain text in speech marks or quotation marks: `hl:'bar baz'` or `hl:"bar baz"` would highlight lines with the word `bar baz` inside them
- Regular Expressions: `hl:/#\w{6}/` would highlight lines which match this regular expression (tested by `regex.test(line)`) - for this example any lines with hexadecimal colors are highlighted
Combinations of these will highlight all relevant lines.
Example:
` ```cpp hl:1,3,4-6`
` ```cpp hl:1,3-4,foo,'bar baz',"bar and baz",/#\w{6}/`
![Default Highlight](images/DefaultHighlight.png)
@ -239,7 +245,6 @@ Take a look at the [changelog](CHANGELOG.md) to see what has changed in past ver
- Implement code wrapping options
- In reading mode, if wrapped, keep line numbers to the left when scrolling
- Add commands to fold all, unfold all and reset default fold for codeblocks
- Regex highlighting parameter
- Context Menu on right click
- Copy codeblock
- Copy line

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 102 KiB

73
main.js

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
import { LANGUAGE_NAMES, LANGUAGE_ICONS, CodeblockCustomizerThemeSettings } from "./Settings";
import { CodeblockParameters } from "./CodeblockParsing";
import { CodeblockParameters, Highlights } from "./CodeblockParsing";
export function createHeader(codeblockParameters: CodeblockParameters, themeSettings: CodeblockCustomizerThemeSettings): HTMLElement {
const headerContainer = createDiv({cls: `codeblock-customizer-header-container${(codeblockParameters.fold.enabled || codeblockParameters.title !== '')?'-specific':''}`});
@ -20,7 +20,10 @@ export function createHeader(codeblockParameters: CodeblockParameters, themeSett
if (codeblockParameters.title !== '')
headerText = codeblockParameters.title;
else if (codeblockParameters.fold.enabled)
headerText = codeblockParameters.fold.placeholder!==''?codeblockParameters.fold.placeholder:themeSettings.header.collapsePlaceholder!==''?themeSettings.header.collapsePlaceholder:'Collapsed Code';
if (codeblockParameters.fold.placeholder!=='')
headerText = codeblockParameters.fold.placeholder;
else
headerText = themeSettings.header.collapsePlaceholder!==''?themeSettings.header.collapsePlaceholder:'Collapsed Code';
headerContainer.appendChild(createDiv({cls: "codeblock-customizer-header-text", text: headerText}));
return headerContainer;
@ -40,13 +43,12 @@ function getLanguageIcon(language: string) {
return null;
}
export function getLineClass(codeblockParameters: CodeblockParameters, lineNumber: number): Array<string> {
//TODO (@mayurankv) Future: Speed this up by setting up a reverse dictionary for line number to highlights
export function getLineClass(codeblockParameters: CodeblockParameters, lineNumber: number, line: string): Array<string> {
let classList: Array<string> = [];
if (codeblockParameters.highlights.default.includes(lineNumber))
if (codeblockParameters.highlights.default.lineNumbers.includes(lineNumber) || codeblockParameters.highlights.default.plainText.some(text => line.indexOf(text) > -1) || codeblockParameters.highlights.default.regularExpressions.some(regExp => regExp.test(line)))
classList.push('codeblock-customizer-line-highlighted');
Object.entries(codeblockParameters.highlights.alternative).forEach(([alternativeHighlight,highlightedLineNumbers]: [string,Array<number>]) => {
if (highlightedLineNumbers.includes(lineNumber))
Object.entries(codeblockParameters.highlights.alternative).forEach(([alternativeHighlight,highlightedLines]: [string,Highlights]) => {
if (highlightedLines.lineNumbers.includes(lineNumber) || highlightedLines.plainText.some(text => line.indexOf(text) > -1) || highlightedLines.regularExpressions.some(regExp => regExp.test(line)))
classList.push(`codeblock-customizer-line-highlighted-${alternativeHighlight.replace(/\s+/g, '-').toLowerCase()}`);
})
if (classList.length === 0)

View file

@ -13,12 +13,18 @@ export interface CodeblockParameters {
offset: number;
}
highlights: {
default: Array<number>;
alternative: Record<string,Array<number>>
default: Highlights;
alternative: Record<string,Highlights>
},
ignore: boolean;
}
export interface Highlights {
lineNumbers: Array<number>;
plainText: Array<string>;
regularExpressions: Array<RegExp>;
}
export function parseCodeblockParameters(parameterLine: string, theme: CodeblockCustomizerTheme): CodeblockParameters {
let codeblockParameters: CodeblockParameters = {
language: '',
@ -33,7 +39,11 @@ export function parseCodeblockParameters(parameterLine: string, theme: Codeblock
offset: 0,
},
highlights: {
default: [],
default: {
lineNumbers: [],
plainText: [],
regularExpressions: [],
},
alternative: {},
},
ignore: false,
@ -82,7 +92,7 @@ function parseParameterString(parameterString: string, codeblockParameters: Code
codeblockParameters.lineNumbers.offset = 0;
}
} else {
let highlightMatch = /^(\w+):((?:\d+-\d+|\d+)(?:,\d+-\d+|,\d+)*)$/.exec(parameterString);
let highlightMatch = /^(\w+):(.+)$/.exec(parameterString);
if (highlightMatch) {
let highlights = parseHighlightedLines(highlightMatch[2]);
if (highlightMatch[1] === 'hl')
@ -93,18 +103,35 @@ function parseParameterString(parameterString: string, codeblockParameters: Code
}
}
}
function parseHighlightedLines(highlightedLinesString: string): Array<number> {
const lineSections = highlightedLinesString.split(',');
return lineSections.map(lineSection => {
if (lineSection.includes('-')) {
const [start,end] = lineSection.split('-').map(num => parseInt(num));
if (start && end)
return Array.from({length:end-start+1}, (_,num) => num + start);
} else {
return parseInt(lineSection);
function parseHighlightedLines(highlightedLinesString: string): Highlights {
const highlightRules = highlightedLinesString.split(',');
let lineNumbers: Set<number> = new Set();
let plainText: Set<string> = new Set();
let regularExpressions: Set<RegExp> = new Set();
highlightRules.forEach(highlightRule => {
if (/\d+-\d+/.test(highlightRule)) { // Number Range
const [start,end] = highlightRule.split('-').map(num => parseInt(num));
if (start && end && start <= end)
Array.from({length:end-start+1}, (_,num) => num + start).forEach(lineNumber => lineNumbers.add(lineNumber));
} else if (/^\/(.*)\/$/.test(highlightRule)) { // Regex
try {
regularExpressions.add(new RegExp(highlightRule.replace(/^\/(.*)\/$/,"$1")));
} catch { }
} else if (/".*"/.test(highlightRule)) { // Plain Text
plainText.add(highlightRule.substring(1,highlightRule.length-1));
} else if (/'.*'/.test(highlightRule)) { // Plain Text
plainText.add(highlightRule.substring(1,highlightRule.length-1));
} else if (/\D/.test(highlightRule)) { // Plain Text
plainText.add(highlightRule);
} else if (/\d+/.test(highlightRule)) { // Plain Number
lineNumbers.add(parseInt(highlightRule));
}
return [];
}).flat();
});
return {
lineNumbers: [...lineNumbers],
plainText: [...plainText],
regularExpressions: [...regularExpressions],
};
}
export function isLanguageExcluded(language: string, excludedLanguagesString: string): boolean {
return parseRegexExcludedLanguages(excludedLanguagesString).some(regexExcludedLanguage => {

View file

@ -100,7 +100,7 @@ export function createCodeMirrorExtensions(settings: CodeblockCustomizerSettings
if (excludedCodeblock)
return;
if (node.type.name.includes("HyperMD-codeblock")) {
decorations.push(Decoration.line({attributes: {class: (settings.specialLanguages.includes(codeblockParameters.language)||startLine||endLine?'codeblock-customizer-line':getLineClass(codeblockParameters,lineNumber).join(' '))+([''].concat(settings.specialLanguages).includes(codeblockParameters.language)?'':` language-${codeblockParameters.language}`)}}).range(node.from))
decorations.push(Decoration.line({attributes: {class: (settings.specialLanguages.includes(codeblockParameters.language)||startLine||endLine?'codeblock-customizer-line':getLineClass(codeblockParameters,lineNumber,line.text).join(' '))+([''].concat(settings.specialLanguages).includes(codeblockParameters.language)?'':` language-${codeblockParameters.language}`)}}).range(node.from))
decorations.push(Decoration.line({}).range(node.from));
decorations.push(Decoration.widget({widget: new LineNumberWidget(lineNumber,codeblockParameters,startLine||endLine)}).range(node.from))
lineNumber++;

View file

@ -74,7 +74,7 @@ async function remakeCodeblock(codeblockCodeElement: HTMLElement, codeblockPreEl
codeblockParameters.lineNumbers.offset = codePreviewParams.start - 1;
codeblockParameters.lineNumbers.alwaysEnabled = codePreviewParams.lineNumber;
}
codeblockParameters.highlights.default = [...new Set(codeblockParameters.highlights.default.concat(Array.from(plugins['obsidian-code-preview'].analyzeHighLightLines(codePreviewParams.lines,codePreviewParams.highlight),([num,_])=>(num))))];
codeblockParameters.highlights.default.lineNumbers = [...new Set(codeblockParameters.highlights.default.lineNumbers.concat(Array.from(plugins['obsidian-code-preview'].analyzeHighLightLines(codePreviewParams.lines,codePreviewParams.highlight),([num,_])=>(num))))];
if (codeblockParameters.title === '')
codeblockParameters.title = codePreviewParams.filePath;
codeblockParameters.language = codePreviewParams.language;
@ -112,7 +112,7 @@ function decorateCodeblock(codeblockCodeElement: HTMLElement, codeblockPreElemen
return;
const lineNumber = index + 1;
const lineWrapper = document.createElement("div");
getLineClass(codeblockParameters,lineNumber).forEach((lineClass) => {
getLineClass(codeblockParameters,lineNumber,line).forEach((lineClass) => {
lineWrapper.classList.add(lineClass);
});
codeblockCodeElement.appendChild(lineWrapper);