Refactoring

This commit is contained in:
Mayuran Visakan 2023-08-10 14:45:16 +01:00
parent 8e9c62bec8
commit 00c1907a4e
12 changed files with 634 additions and 625 deletions

View file

@ -34,6 +34,8 @@ assignees: 'mayurankv'
- Obsidian Version: <!-- Obsidian Version -->
- Platform: <!-- Desktop or Mobile -->
- OS: <!-- OS -->
- Theme: <!-- Obsidian Theme -->
- CSS Snippets: <!-- Do you have any CSS Snippets enabled? -->
<!-- Other details that you think may affect this issue -->
### Screenshots

11
.github/TODO.md vendored
View file

@ -27,8 +27,7 @@
3. Add syntax highlighting for inline code to source view editing mode?
4. Add command to select all text inside current codeblock
5. Debug really long codeblocks not working
6. Debug live preview callouts not working
7. Add or update icons for:
6. Add or update icons for:
- `regex`
- `maxima`
- `zsh`
@ -38,9 +37,11 @@
- `ts`
- `julia`
- `java`
8. Changing line wrap options can be flaky
9. Make reset buttons only appear when value is not default value (in settings)
10. Implement Live preview re-rendering for relevant settings (search '//TODO (@mayurankv) Re-render' in `settingsTab.ts`)
7. Changing line wrap options can be flaky
8. Make reset buttons only appear when value is not default value (in settings)
9. Implement Live preview re-rendering for relevant settings (search '//TODO (@mayurankv) Re-render' in `settingsTab.ts`)
10. Reduce cognitive complexity (and other issues)`
11. Clean up reading view `remakeCodeblock`
See below:

552
main.js

File diff suppressed because one or more lines are too long

View file

@ -35,7 +35,7 @@ const THEME_STYLES: Record<string,ThemeStyle> = {
}
export function updateStyling(settings: CodeStylerSettings, app: App): void {
let currentTheme = getCurrentTheme(app);
const currentTheme = getCurrentTheme(app);
let styleTag = document.getElementById(STYLE_ID);
if (!styleTag) {
styleTag = document.createElement('style');
@ -157,20 +157,9 @@ function styleLanguageColours (themeSettings: CodeStylerThemeSettings, redirectL
}
function addThemeSettingsClasses (themeSettings: CodeStylerThemeSettings): void {
if (themeSettings.codeblock.lineNumbers)
document.body.classList.add("code-styler-show-line-numbers");
else
document.body.classList.remove("code-styler-show-line-numbers");
if (themeSettings.gutter.highlight)
document.body.classList.add('code-styler-gutter-highlight');
else
document.body.classList.remove('code-styler-gutter-highlight');
if (themeSettings.gutter.activeLine)
document.body.classList.add('code-styler-gutter-active-line');
else
document.body.classList.remove('code-styler-gutter-active-line');
themeSettings.codeblock.lineNumbers ? document.body.classList.add("code-styler-show-line-numbers") : document.body.classList.remove("code-styler-show-line-numbers");
themeSettings.gutter.highlight ? document.body.classList.add('code-styler-gutter-highlight') : document.body.classList.remove('code-styler-gutter-highlight');
themeSettings.gutter.activeLine ? document.body.classList.add('code-styler-gutter-active-line') : document.body.classList.remove('code-styler-gutter-active-line');
document.body.classList.remove("code-styler-active-line-highlight","code-styler-active-line-highlight-codeblock","code-styler-active-line-highlight-editor")
if (themeSettings.highlights.activeEditorLine && themeSettings.highlights.activeCodeblockLine) // Inside and outside of codeblocks with different colours

View file

@ -33,7 +33,7 @@ export function createInlineOpener(inlineCodeParameters: InlineCodeParameters, l
openerContainer.appendChild(createSpan({cls: `code-styler-inline-title`, text: inlineCodeParameters.title}));
return openerContainer;
}
function createImageWrapper(iconURL: string, imageWrapper: HTMLElement, imgClass: string = "code-styler-icon"): HTMLElement {
function createImageWrapper(iconURL: string, imageWrapper: HTMLElement, imgClass = "code-styler-icon"): HTMLElement {
const img = document.createElement("img");
img.classList.add(imgClass);
img.src = iconURL;
@ -67,3 +67,11 @@ export function getLineClass(codeblockParameters: CodeblockParameters, lineNumbe
classList = ['code-styler-line']
return classList;
}
export function getLineNumberDisplay(codeblockParameters: CodeblockParameters): string {
let lineNumberDisplay = '';
if (!codeblockParameters.lineNumbers.alwaysEnabled && codeblockParameters.lineNumbers.alwaysDisabled)
lineNumberDisplay = '-hide';
else if (codeblockParameters.lineNumbers.alwaysEnabled && !codeblockParameters.lineNumbers.alwaysDisabled)
lineNumberDisplay = '-specific';
return lineNumberDisplay;
}

View file

@ -42,19 +42,22 @@ export async function parseCodeblockSource(codeSection: Array<string>, sourcePat
//@ts-expect-error Undocumented Obsidian API
const plugins: Record<string,any> = plugin.app.plugins.plugins;
const admonitions: boolean = 'obsidian-admonition' in plugins;
let codeblocks: Array<Array<string>> = [];
let codeblocksParameters: Array<CodeblockParameters> = [];
const codeblocks: Array<Array<string>> = [];
const codeblocksParameters: Array<CodeblockParameters> = [];
function parseCodeblockSection(codeSection: Array<string>): void {
if (codeSection.length === 0)
return;
let openingCodeblockLine = getOpeningLine(codeSection);
const openingCodeblockLine = getOpeningLine(codeSection);
if (!openingCodeblockLine)
return;
let openDelimiter = /^\s*(?:>\s*)*((?:```+|~~~+)).*$/.exec(openingCodeblockLine)?.[1];
const openDelimiter = /^\s*(?:>\s*)*((?:```+|~~~+)).*$/.exec(openingCodeblockLine)?.[1];
if (!openDelimiter)
return;
let openDelimiterIndex = codeSection.indexOf(openingCodeblockLine);
let closeDelimiterIndex = codeSection.slice(openDelimiterIndex+1).findIndex((line)=>line.indexOf(openDelimiter as string)!==-1);
const openDelimiterIndex = codeSection.indexOf(openingCodeblockLine);
const closeDelimiterIndex = codeSection.slice(openDelimiterIndex+1).findIndex((line)=>line.indexOf(openDelimiter)!==-1);
if (!admonitions || !/^\s*(?:>\s*)*(?:```+|~~~+) *ad-.*$/.test(openingCodeblockLine))
codeblocks.push(codeSection.slice(0,openDelimiterIndex+2+closeDelimiterIndex))
else
@ -63,8 +66,8 @@ export async function parseCodeblockSource(codeSection: Array<string>, sourcePat
parseCodeblockSection(codeSection.slice(openDelimiterIndex+1+closeDelimiterIndex+1));
}
parseCodeblockSection(codeSection);
for (let codeblockLines of codeblocks) {
let parameterLine = getParameterLine(codeblockLines);
for (const codeblockLines of codeblocks) {
const parameterLine = getParameterLine(codeblockLines);
if (!parameterLine)
continue;
let codeblockParameters = parseCodeblockParameters(parameterLine,plugin.settings.currentTheme);
@ -79,7 +82,7 @@ export async function parseCodeblockSource(codeSection: Array<string>, sourcePat
}
export function parseInlineCode(codeText: string): {parameters: InlineCodeParameters | null, text: string} {
let match = /^({} ?)?{([^}]*)} ?(.*)$/.exec(codeText);
const match = /^({} ?)?{([^}]*)} ?(.*)$/.exec(codeText);
if (!match?.[1] && !(match?.[2] && match?.[3]))
return {parameters: null, text: ''};
@ -90,7 +93,7 @@ export function parseInlineCode(codeText: string): {parameters: InlineCodeParame
}
export function parseCodeblockParameters(parameterLine: string, theme: CodeStylerTheme): CodeblockParameters {
let codeblockParameters: CodeblockParameters = {
const codeblockParameters: CodeblockParameters = {
language: '',
title: '',
fold: {
@ -124,7 +127,7 @@ export function parseCodeblockParameters(parameterLine: string, theme: CodeStyle
} else {
return codeblockParameters;
}
let languageBreak = parameterLine.indexOf(' ');
const languageBreak = parameterLine.indexOf(' ');
codeblockParameters.language = parameterLine.slice(0,languageBreak !== -1?languageBreak:parameterLine.length).toLowerCase();
if (languageBreak === -1)
return codeblockParameters;
@ -137,7 +140,7 @@ export function parseCodeblockParameters(parameterLine: string, theme: CodeStyle
export async function pluginAdjustParameters(codeblockParameters: CodeblockParameters, plugins: Record<string,any>, codeblockLines: Array<string>, sourcePath: string): Promise<CodeblockParameters> {
if (codeblockParameters.language === 'preview') {
if ('obsidian-code-preview' in plugins) {
let codePreviewParams = await plugins['obsidian-code-preview'].code(codeblockLines.slice(1,-1).join('\n'),sourcePath);
const codePreviewParams = await plugins['obsidian-code-preview'].code(codeblockLines.slice(1,-1).join('\n'),sourcePath);
if (!codeblockParameters.lineNumbers.alwaysDisabled && !codeblockParameters.lineNumbers.alwaysEnabled) {
if (typeof codePreviewParams.start === 'number')
codeblockParameters.lineNumbers.offset = codePreviewParams.start - 1;
@ -158,12 +161,12 @@ export async function pluginAdjustParameters(codeblockParameters: CodeblockParam
return codeblockParameters
}
function parseInlineCodeParameters(parameterLine: string): InlineCodeParameters {
let inlineCodeParameters: InlineCodeParameters = {
const inlineCodeParameters: InlineCodeParameters = {
language: '',
title: '',
icon: false,
}
let languageBreak = parameterLine.indexOf(' ');
const languageBreak = parameterLine.indexOf(' ');
inlineCodeParameters.language = parameterLine.slice(0,languageBreak !== -1?languageBreak:parameterLine.length).toLowerCase();
if (languageBreak === -1)
return inlineCodeParameters;
@ -176,11 +179,11 @@ function parseInlineCodeParameters(parameterLine: string): InlineCodeParameters
function parseCodeblockParameterString(parameterString: string, codeblockParameters: CodeblockParameters, theme: CodeStylerTheme): void {
if (parameterString.startsWith('title:')) {
let titleMatch = /(["']?)([^\1]+)\1/.exec(parameterString.slice('title:'.length));
const titleMatch = /(["']?)([^\1]+)\1/.exec(parameterString.slice('title:'.length));
if (titleMatch)
codeblockParameters.title = titleMatch[2].trim();
} else if (parameterString.startsWith('fold:')) {
let foldPlaceholderMatch = /(["']?)([^\1]+)\1/.exec(parameterString.slice('fold:'.length));
const foldPlaceholderMatch = /(["']?)([^\1]+)\1/.exec(parameterString.slice('fold:'.length));
if (foldPlaceholderMatch) {
codeblockParameters.fold = {
enabled: true,
@ -249,9 +252,9 @@ function parseCodeblockParameterString(parameterString: string, codeblockParamet
}
}
} else {
let highlightMatch = /^(\w+):(.+)$/.exec(parameterString);
const highlightMatch = /^(\w+):(.+)$/.exec(parameterString);
if (highlightMatch) {
let highlights = parseHighlightedLines(highlightMatch[2]);
const highlights = parseHighlightedLines(highlightMatch[2]);
if (highlightMatch[1] === 'hl')
codeblockParameters.highlights.default = highlights;
else
@ -262,9 +265,9 @@ function parseCodeblockParameterString(parameterString: string, codeblockParamet
}
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();
const lineNumbers: Set<number> = new Set();
const plainText: Set<string> = new Set();
const regularExpressions: Set<RegExp> = new Set();
highlightRules.forEach(highlightRule => {
if (/\d+-\d+/.test(highlightRule)) { // Number Range
const [start,end] = highlightRule.split('-').map(num => parseInt(num));
@ -273,7 +276,9 @@ function parseHighlightedLines(highlightedLinesString: string): Highlights {
} else if (/^\/(.*)\/$/.test(highlightRule)) { // Regex
try {
regularExpressions.add(new RegExp(highlightRule.replace(/^\/(.*)\/$/,"$1")));
} catch { }
} catch {
//pass
}
} else if (/".*"/.test(highlightRule)) { // Plain Text
plainText.add(highlightRule.substring(1,highlightRule.length-1));
} else if (/'.*'/.test(highlightRule)) { // Plain Text
@ -301,7 +306,7 @@ function parseRegexExcludedLanguages(excludedLanguagesString: string): Array<Reg
}
function parseInlineCodeParameterString(parameterString: string, inlineCodeParameters: InlineCodeParameters): void {
if (parameterString.startsWith('title:')) {
let titleMatch = /(["']?)([^\1]+)\1/.exec(parameterString.slice('title:'.length));
const titleMatch = /(["']?)([^\1]+)\1/.exec(parameterString.slice('title:'.length));
if (titleMatch)
inlineCodeParameters.title = titleMatch[2].trim();
} else if (parameterString === 'icon' || (parameterString.startsWith('icon:') && parameterString.toLowerCase() === 'icon:true')) {
@ -311,7 +316,7 @@ function parseInlineCodeParameterString(parameterString: string, inlineCodeParam
export function getParameterLine(codeblockLines: Array<string>): string | undefined {
let openingCodeblockLine = getOpeningLine(codeblockLines);
if (openingCodeblockLine && (openingCodeblockLine !== codeblockLines[0] || />\s*(?:`|~)/.test(openingCodeblockLine)))
if (openingCodeblockLine && (openingCodeblockLine !== codeblockLines[0] || />\s*(?:[`~])/.test(openingCodeblockLine)))
openingCodeblockLine = cleanParameterLine(openingCodeblockLine);
return openingCodeblockLine
}
@ -319,7 +324,7 @@ function getOpeningLine(codeblockLines: Array<string>): string | undefined {
return codeblockLines.find((line: string)=>Boolean(testOpeningLine(line)));
}
export function testOpeningLine(codeblockLine: string): string {
let lineMatch = /^(\s*(?:>\s*)*)(```+|~~~+)/.exec(codeblockLine);
const lineMatch = /^(\s*(?:>\s*)*)(```+|~~~+)/.exec(codeblockLine);
if (!lineMatch)
return '';
if (codeblockLine.indexOf(lineMatch[2],lineMatch[1].length+lineMatch[2].length+1)===-1)
@ -348,6 +353,6 @@ export async function getFileContentLines(sourcePath: string, plugin: CodeStyler
return fileContent.split(/\n/g);
}
export function arraysEqual(array1: Array<any>,array2: Array<any>): boolean {
export function arraysEqual(array1: Array<unknown>,array2: Array<unknown>): boolean {
return array1.length === array2.length && array1.every((el) => array2.includes(el));
}

View file

@ -1,14 +1,16 @@
import { editorEditorField, editorInfoField, editorLivePreviewField } from "obsidian";
import { ViewPlugin, EditorView, ViewUpdate, Decoration, DecorationSet, WidgetType } from "@codemirror/view";
import { Extension, EditorState, StateField, StateEffect, StateEffectType, Range, RangeSetBuilder, Transaction, TransactionSpec, Line, SelectionRange, Annotation } from "@codemirror/state";
import { syntaxTree, tokenClassNodeProp, LanguageSupport } from "@codemirror/language";
import { syntaxTree, tokenClassNodeProp } from "@codemirror/language";
import { SyntaxNodeRef } from "@lezer/common";
import { highlightTree, classHighlighter } from "@lezer/highlight";
//NOSONAR
// import { LanguageSupport } from "@codemirror/language"; //NOTE: For future CM6 Compatibility
// import { highlightTree, classHighlighter } from "@lezer/highlight"; //NOTE: For future CM6 Compatibility
// import { languages } from "@codemirror/language-data"; //NOTE: For future CM6 Compatibility
import { CodeStylerSettings, CodeStylerThemeSettings } from "./Settings";
import { CodeblockParameters, parseCodeblockParameters, testOpeningLine, isExcluded, arraysEqual, trimParameterLine, InlineCodeParameters, parseInlineCode } from "./CodeblockParsing";
import { createHeader, createInlineOpener, getLanguageIcon, getLineClass } from "./CodeblockDecorating";
import { createHeader, createInlineOpener, getLanguageIcon, getLineClass, getLineNumberDisplay } from "./CodeblockDecorating";
export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings, languageIcons: Record<string,string>) {
const codeblockLineNumberCharWidth = StateField.define<number>({
@ -92,9 +94,9 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
const codeblocks = findUnduplicatedCodeblocks(view);
for (const codeblock of codeblocks) {
let codeblockParameters: CodeblockParameters;
let excludedCodeblock: boolean = false;
let lineNumber: number = 0;
let maxLineNum: number = 0;
let excludedCodeblock = false;
let lineNumber = 0;
let maxLineNum = 0;
let lineNumberMargin: number | undefined = 0;
syntaxTree(view.state).iterate({from: codeblock.from, to: codeblock.to,
enter: (syntaxNode)=>{
@ -107,7 +109,7 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
excludedCodeblock = isExcluded(codeblockParameters.language,[this.settings.excludedCodeblocks,this.settings.excludedLanguages].join(',')) || codeblockParameters.ignore;
lineNumber = 0;
let lineNumberCount = line.number + 1;
let startDelimiter = testOpeningLine(lineText);
const startDelimiter = testOpeningLine(lineText);
while (startDelimiter !== testOpeningLine(view.state.doc.line(lineNumberCount).text)) {
lineNumberCount += 1;
}
@ -149,12 +151,12 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
return Decoration.none;
const builder = new RangeSetBuilder<Decoration>();
let codeblockParameters: CodeblockParameters;
let startLine: boolean = true;
let startDelimiter: string = '```';
let startLine = true;
let startDelimiter = '```';
for (let i = 1; i < transaction.state.doc.lines; i++) {
const line = transaction.state.doc.line(i);
const lineText = line.text.toString();
let currentDelimiter = testOpeningLine(lineText);
const currentDelimiter = testOpeningLine(lineText);
if (currentDelimiter) {
if (startLine) {
startLine = false;
@ -185,12 +187,12 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
let codeblockParameters: CodeblockParameters;
let collapseStart: Line | null = null;
let collapseEnd: Line | null = null;
let startLine: boolean = true;
let startDelimiter: string = '```';
let startLine = true;
let startDelimiter = '```';
for (let i = 1; i < state.doc.lines; i++) {
const line = state.doc.line(i);
const lineText = line.text.toString();
let currentDelimiter = testOpeningLine(lineText);
const currentDelimiter = testOpeningLine(lineText);
if (currentDelimiter) {
if (startLine) {
startLine = false;
@ -252,6 +254,7 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
decorations: DecorationSet;
settings: CodeStylerSettings;
syntaxHighlight: boolean;
//NOSONAR
// loadedLanguages: Record<string,LanguageSupport>; //NOTE: For future CM6 Compatibility
constructor(view: EditorView) {
@ -259,17 +262,17 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
this.settings = settings;
this.syntaxHighlight = settings.currentTheme.settings.inline.syntaxHighlight;
this.buildDecorations(view);
//NOSONAR
// this.loadedLanguages = {}; //NOTE: For future CM6 Compatibility
}
update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged || update.selectionSet || update.state.field(editorLivePreviewField)!==update.startState.field(editorLivePreviewField) || this.settings.currentTheme.settings.inline.syntaxHighlight !== this.syntaxHighlight) {
if (this.settings.currentTheme.settings.inline.syntaxHighlight !== this.syntaxHighlight)
console.log(this.syntaxHighlight)
this.syntaxHighlight = this.settings.currentTheme.settings.inline.syntaxHighlight;
if (update.docChanged)
this.decorations = this.decorations.map(update.changes);
this.buildDecorations(update.view);
//NOSONAR
//NOTE: For future CM6 Compatibility
// const toHighlight = this.buildDecorations(update.view);
// toHighlight.forEach((highlightSet)=>{
@ -297,12 +300,14 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
}
}
buildDecorations(view: EditorView): void { //Array<{start: number, text: string, language: string}> //NOTE: For future CM6 Compatibility
buildDecorations(view: EditorView): void { //Array<{start: number, text: string, language: string}> //NOTE: For future CM6 Compatibility //NOSONAR
if (!view?.visibleRanges?.length || editingViewIgnore(view.state)) {
this.decorations = Decoration.none;
return;
//NOSONAR
// return [];//NOTE: For future CM6 Compatibility
}
//NOSONAR
// let toHighlight: Array<{start: number, text: string, language: string}> = []; //NOTE: For future CM6 Compatibility
for (const {from,to} of view.visibleRanges) {
syntaxTree(view.state).iterate({from: from, to: to,
@ -310,13 +315,13 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
const properties = new Set(syntaxNode.node.type.prop<string>(tokenClassNodeProp)?.split(" "));
if (!(properties.has("inline-code") && !properties.has("formatting")))
return;
let previousSibling = syntaxNode.node.prevSibling;
const previousSibling = syntaxNode.node.prevSibling;
if (!previousSibling)
return;
let delimiterSize = previousSibling.to-previousSibling.from;
let inlineCodeText = view.state.doc.sliceString(syntaxNode.from, syntaxNode.to);
let {parameters,text} = parseInlineCode(inlineCodeText);
let endOfParameters = inlineCodeText.lastIndexOf(text);
const delimiterSize = previousSibling.to-previousSibling.from;
const inlineCodeText = view.state.doc.sliceString(syntaxNode.from, syntaxNode.to);
const {parameters,text} = parseInlineCode(inlineCodeText);
const endOfParameters = inlineCodeText.lastIndexOf(text);
if (!parameters) {
if (text) {
if (view.state.selection.ranges.some((range: SelectionRange)=>range.to >= syntaxNode.from-delimiterSize && range.from <= syntaxNode.to+delimiterSize))
@ -346,11 +351,12 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
if (!settings.currentTheme.settings.inline.syntaxHighlight)
return;
this.decorations = this.decorations.update({add: modeHighlight({start: syntaxNode.from + endOfParameters, text: text, language: parameters.language})});
//NOSONAR
// toHighlight.push({start: syntaxNode.from + endOfParameters, text: text, language: parameters.language}); //NOTE: For future CM6 Compatibility
}
},
});
};
}
// return toHighlight; //NOTE: For future CM6 Compatibility
}
},
@ -360,9 +366,9 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
)
function cursorIntoCollapsedTransactionFilter() {
return EditorState.transactionFilter.of((transaction) => {
let extraTransactions: Array<TransactionSpec> = [];
let collapsedRangeSet = transaction.startState.field(codeblockCollapse,false)?.map(transaction.changes) ?? Decoration.none;
let temporarilyUncollapsedRangeSet = transaction.startState.field(temporarilyUncollapsed,false)?.map(transaction.changes) ?? Decoration.none;
const extraTransactions: Array<TransactionSpec> = [];
const collapsedRangeSet = transaction.startState.field(codeblockCollapse,false)?.map(transaction.changes) ?? Decoration.none;
const temporarilyUncollapsedRangeSet = transaction.startState.field(temporarilyUncollapsed,false)?.map(transaction.changes) ?? Decoration.none;
transaction.newSelection.ranges.forEach((range: SelectionRange)=>{
collapsedRangeSet.between(range.from, range.to, (collapseStartFrom, collapseEndTo, decorationValue) => {
if (collapseStartFrom <= range.head && range.head <= collapseEndTo)
@ -380,28 +386,28 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
}
//todo Finish this filter
function manageCollapsedTransactionFilter() {
return EditorState.transactionFilter.of((transaction) => {
let extraTransactions: Array<TransactionSpec> = [];
let collapsedRangeSet = transaction.startState.field(codeblockCollapse,false) ?? Decoration.none;
let temporarilyUncollapsedRangeSet = transaction.startState.field(temporarilyUncollapsed,false) ?? Decoration.none;
transaction.changes.iterChanges((fromA,toA,fromB,toB,inserted)=>{
if (fromB === toB && inserted.toString() === '') {
console.log(fromA,toA,fromB,toB,inserted)
collapsedRangeSet.between(fromA-100, toA+100, (collapseStartFrom, collapseEndTo, decorationValue) => {
console.log(fromA,toA,collapseStartFrom,collapseEndTo)
if ((fromA <= collapseStartFrom && collapseStartFrom <= toA) || (fromA <= collapseEndTo && collapseEndTo <= toA))
extraTransactions.push({effects: uncollapse.of({filter: (from,to) => (to <= collapseStartFrom || from >= collapseEndTo), filterFrom: collapseStartFrom, filterTo: collapseEndTo}), annotations: temporaryUncollapseAnnotation.of({decorationRange: {from: collapseStartFrom, to: collapseEndTo, value: decorationValue}, uncollapse: true})});
});
}
});
if (extraTransactions.length !== 0)
console.log('foo',extraTransactions)
if (extraTransactions.length !== 0)
return [transaction,...extraTransactions];
return transaction;
});
}
// function manageCollapsedTransactionFilter() {
// return EditorState.transactionFilter.of((transaction) => {
// const extraTransactions: Array<TransactionSpec> = [];
// const collapsedRangeSet = transaction.startState.field(codeblockCollapse,false) ?? Decoration.none;
// const temporarilyUncollapsedRangeSet = transaction.startState.field(temporarilyUncollapsed,false) ?? Decoration.none;
// transaction.changes.iterChanges((fromA,toA,fromB,toB,inserted)=>{
// if (fromB === toB && inserted.toString() === '') {
// console.log(fromA,toA,fromB,toB,inserted)
// collapsedRangeSet.between(fromA-100, toA+100, (collapseStartFrom, collapseEndTo, decorationValue) => {
// console.log(fromA,toA,collapseStartFrom,collapseEndTo)
// if ((fromA <= collapseStartFrom && collapseStartFrom <= toA) || (fromA <= collapseEndTo && collapseEndTo <= toA))
// extraTransactions.push({effects: uncollapse.of({filter: (from,to) => (to <= collapseStartFrom || from >= collapseEndTo), filterFrom: collapseStartFrom, filterTo: collapseEndTo}), annotations: temporaryUncollapseAnnotation.of({decorationRange: {from: collapseStartFrom, to: collapseEndTo, value: decorationValue}, uncollapse: true})});
// });
// }
// });
// if (extraTransactions.length !== 0)
// console.log('foo',extraTransactions)
// if (extraTransactions.length !== 0)
// return [transaction,...extraTransactions];
// return transaction;
// });
// }
class LineNumberWidget extends WidgetType {
lineNumber: number;
@ -422,12 +428,7 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
}
toDOM(view: EditorView): HTMLElement {
let lineNumberDisplay = '';
if (!this.codeblockParameters.lineNumbers.alwaysEnabled && this.codeblockParameters.lineNumbers.alwaysDisabled)
lineNumberDisplay = '-hide'
else if (this.codeblockParameters.lineNumbers.alwaysEnabled && !this.codeblockParameters.lineNumbers.alwaysDisabled)
lineNumberDisplay = '-specific'
return createSpan({attr: {style: this.maxLineNum.toString().length > (this.lineNumber + this.codeblockParameters.lineNumbers.offset).toString().length?'width: var(--line-number-gutter-width);':''}, cls: `code-styler-line-number${lineNumberDisplay}`, text: this.empty?'':(this.lineNumber + this.codeblockParameters.lineNumbers.offset).toString()});
return createSpan({attr: {style: this.maxLineNum.toString().length > (this.lineNumber + this.codeblockParameters.lineNumbers.offset).toString().length?'width: var(--line-number-gutter-width);':''}, cls: `code-styler-line-number${getLineNumberDisplay(this.codeblockParameters)}`, text: this.empty?'':(this.lineNumber + this.codeblockParameters.lineNumbers.offset).toString()});
}
}
class HeaderWidget extends WidgetType {
@ -517,12 +518,12 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
let collapseStart: Line | null = null;
let collapseEnd: Line | null = null;
let startLine: boolean = true;
let startDelimiter: string = '```';
let startLine = true;
let startDelimiter = '```';
for (let i = 1; i < view.state.doc.lines; i++) {
const line = view.state.doc.line(i);
const lineText = line.text.toString();
let currentDelimiter = testOpeningLine(lineText);
const currentDelimiter = testOpeningLine(lineText);
if (currentDelimiter) {
if (startLine) {
startLine = false;
@ -553,21 +554,21 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
}
const collapse: StateEffectType<Range<Decoration>> = StateEffect.define();
const uncollapse: StateEffectType<{filter: (from: any, to: any) => boolean, filterFrom: number, filterTo: number}> = StateEffect.define();
const uncollapse: StateEffectType<{filter: (from: number, to: number) => boolean, filterFrom: number, filterTo: number}> = StateEffect.define();
const temporaryUncollapseAnnotation = Annotation.define<{decorationRange: Range<Decoration>, uncollapse: boolean}>();
return [codeblockLineNumberCharWidth,codeblockLines,codeblockHeader,codeblockCollapse,temporarilyUncollapsed,inlineCodeDecorator,cursorIntoCollapsedTransactionFilter()];
}
function getCharWidth(state: EditorState, default_value: number): number {
let charWidths = Array.from(state.field(editorEditorField).contentDOM.querySelectorAll(".HyperMD-codeblock-end")).reduce((result: Array<number>,beginningElement: HTMLElement): Array<number> => {
let nextElement = beginningElement.previousElementSibling as HTMLElement;
const charWidths = Array.from(state.field(editorEditorField).contentDOM.querySelectorAll(".HyperMD-codeblock-end")).reduce((result: Array<number>,beginningElement: HTMLElement): Array<number> => {
const nextElement = beginningElement.previousElementSibling as HTMLElement;
if (!nextElement)
return result;
let lineNumberElement = nextElement.querySelector("[class^='code-styler-line-number']") as HTMLElement;
const lineNumberElement = nextElement.querySelector("[class^='code-styler-line-number']") as HTMLElement;
if (!lineNumberElement || lineNumberElement.innerText.length <= 2)
return result;
let computedStyles = window.getComputedStyle(lineNumberElement, null);
const computedStyles = window.getComputedStyle(lineNumberElement, null);
result.push((lineNumberElement.getBoundingClientRect().width - parseFloat(computedStyles.paddingLeft) - parseFloat(computedStyles.paddingRight)) / lineNumberElement.innerText.length)
return result;
},[])
@ -600,26 +601,27 @@ function findCodeblocks(view: EditorView): Array<SyntaxNodeRef> {
return codeblocks;
}
function languageHighlight({start,text,language}: {start: number, text: string, language: string},loadedLanguages: Record<string,LanguageSupport>): Array<Range<Decoration>> {
//NOTE: Uses codemirror 6 implementation which Obsidian does not currently use
let markDecorations: Array<Range<Decoration>> = [];
const tree = loadedLanguages[language].language.parser.parse(text);
highlightTree(tree,classHighlighter,(from,to,token) => { //todo (@mayurankv) Change this highlighter
if (token)
markDecorations.push({from: start+from, to: start+to, value: Decoration.mark({class: token})});
});
return markDecorations;
}
//NOSONAR
// function languageHighlight({start,text,language}: {start: number, text: string, language: string},loadedLanguages: Record<string,LanguageSupport>): Array<Range<Decoration>> {
// //NOTE: Uses codemirror 6 implementation which Obsidian does not currently use
// let markDecorations: Array<Range<Decoration>> = [];
// const tree = loadedLanguages[language].language.parser.parse(text);
// highlightTree(tree,classHighlighter,(from,to,token) => { //todo (@mayurankv) Change this highlighter
// if (token)
// markDecorations.push({from: start+from, to: start+to, value: Decoration.mark({class: token})});
// });
// return markDecorations;
// }
function modeHighlight ({start,text,language}: {start: number, text: string, language: string}): Array<Range<Decoration>> {
//NOTE: Uses CodeMirror 5 implementation which Obsidian currently uses
let markDecorations: Array<Range<Decoration>> = [];
const markDecorations: Array<Range<Decoration>> = [];
//@ts-expect-error Undocumented Obsidian API
const mode = window.CodeMirror.getMode(window.CodeMirror.defaults,window.CodeMirror.findModeByName(language)?.mime); // Alternatives: `text/x-${parameters.language}`, window.CodeMirror.findModeByName('js').mime
let state = window.CodeMirror.startState(mode);
const state = window.CodeMirror.startState(mode);
if (mode?.token) {
let stream = new window.CodeMirror.StringStream(text);
const stream = new window.CodeMirror.StringStream(text);
while (!stream.eol()) {
let style = mode.token(stream,state);
const style = mode.token(stream,state);
if (style)
markDecorations.push({from: start+stream.start, to: start+stream.pos, value: Decoration.mark({class: `cm-${style}`})})
stream.start = stream.pos;

View file

@ -5,144 +5,39 @@ import {visit} from "unist-util-visit";
import CodeStylerPlugin from "./main";
import { TRANSITION_LENGTH } from "./Settings";
import { CodeblockParameters, getFileContentLines, isExcluded, parseCodeblockSource, parseInlineCode } from "./CodeblockParsing";
import { createHeader, createInlineOpener, getLineClass } from "./CodeblockDecorating";
import { CodeblockParameters, InlineCodeParameters, getFileContentLines, isExcluded, parseCodeblockSource, parseInlineCode } from "./CodeblockParsing";
import { createHeader, createInlineOpener, getLineClass as getLineClasses, getLineNumberDisplay } from "./CodeblockDecorating";
export async function readingViewCodeblockDecoratingPostProcessor(element: HTMLElement, {sourcePath,getSectionInfo,frontmatter}: {sourcePath: string, getSectionInfo: (element: HTMLElement) => MarkdownSectionInformation | null, frontmatter: FrontMatterCache | undefined}, plugin: CodeStylerPlugin, editingEmbeds: boolean = false) {
export async function readingViewCodeblockDecoratingPostProcessor(element: HTMLElement, {sourcePath,getSectionInfo,frontmatter}: {sourcePath: string, getSectionInfo: (element: HTMLElement) => MarkdownSectionInformation | null, frontmatter: FrontMatterCache | undefined}, plugin: CodeStylerPlugin, editingEmbeds = false) {
const cache: CachedMetadata | null = plugin.app.metadataCache.getCache(sourcePath);
if (!sourcePath || !element || (frontmatter ?? cache?.frontmatter)?.['code-styler-ignore'] === true)
return;
let codeblockPreElements: Array<HTMLElement>;
editingEmbeds = editingEmbeds || Boolean(element.matchParent(".cm-embed-block"));
const specific = !Boolean(element.querySelector(".view-content > *"));
const specific = !element.querySelector(".view-content > *");
const printing = Boolean(element.querySelector("div.print > *"));
if (printing && !plugin.settings.decoratePrint)
return;
if (!editingEmbeds && !specific)
codeblockPreElements = Array.from(element.querySelectorAll('.markdown-reading-view pre:not(.frontmatter)'));
else if (!editingEmbeds && specific) {
codeblockPreElements = Array.from(element.querySelectorAll('pre:not(.frontmatter)'));
let admonitionCodeElement = codeblockPreElements?.[0]?.querySelector('pre:not([class]) > code[class*="language-ad-"]');
if (admonitionCodeElement) {
await sleep(50);
codeblockPreElements = Array.from(element.querySelectorAll('pre:not(.frontmatter)'));
}
} else if (editingEmbeds && !specific)
codeblockPreElements = Array.from(element.querySelectorAll('.markdown-source-view .cm-embed-block pre:not(.frontmatter)'));
else
codeblockPreElements = [];
const codeblockPreElements: Array<HTMLElement> = await getCodeblockPreElements(element,specific,editingEmbeds);
if (codeblockPreElements.length === 0 && !(editingEmbeds && specific))
return;
const codeblockSectionInfo: MarkdownSectionInformation | null = getSectionInfo(codeblockPreElements[0]);
if (codeblockSectionInfo && specific && !editingEmbeds)
renderSpecificReadingSection(codeblockPreElements,sourcePath,codeblockSectionInfo,plugin);
else if (specific && !printing) {
await sleep(50);
editingEmbeds = editingEmbeds || Boolean(element.matchParent(".cm-embed-block"));
if (editingEmbeds || !element.classList.contains("admonition-content")) {
let contentEl = element.matchParent('.view-content') as HTMLElement;
await readingViewCodeblockDecoratingPostProcessor(contentEl?contentEl:(element.matchParent('div.print') as HTMLElement),{sourcePath,getSectionInfo,frontmatter},plugin,editingEmbeds); // Re-render whole document
}
}
await renderSpecificReadingSection(codeblockPreElements,sourcePath,codeblockSectionInfo,plugin);
else if (specific && !printing)
await retriggerProcessor(element,{sourcePath,getSectionInfo,frontmatter},plugin,editingEmbeds);
else
renderDocument(codeblockPreElements,sourcePath,cache,editingEmbeds,printing,plugin);
await renderDocument(codeblockPreElements,sourcePath,cache,editingEmbeds,printing,plugin);
}
async function renderSpecificReadingSection(codeblockPreElements: Array<HTMLElement>, sourcePath: string, codeblockSectionInfo: MarkdownSectionInformation, plugin: CodeStylerPlugin): Promise<void> {
const codeblocksParameters = (await parseCodeblockSource(Array.from({length: codeblockSectionInfo.lineEnd-codeblockSectionInfo.lineStart+1}, (_,num) => num + codeblockSectionInfo.lineStart).map((lineNumber)=>codeblockSectionInfo.text.split('\n')[lineNumber]),sourcePath,plugin)).codeblocksParameters;
if (codeblockPreElements.length !== codeblocksParameters.length)
return;
for (let [key,codeblockPreElement] of codeblockPreElements.entries()) {
let codeblockParameters = codeblocksParameters[key];
let codeblockCodeElement = codeblockPreElement.querySelector('pre > code');
if (!codeblockCodeElement)
return;
if (Array.from(codeblockCodeElement.classList).some(className => /^language-\S+/.test(className)))
while(!codeblockCodeElement.classList.contains("is-loaded"))
await sleep(2);
if (isExcluded(codeblockParameters.language,plugin.settings.excludedLanguages) || codeblockParameters.ignore)
continue;
await remakeCodeblock(codeblockCodeElement as HTMLElement,codeblockPreElement,codeblockParameters,true,plugin);
}
}
async function renderDocument(codeblockPreElements: Array<HTMLElement>, sourcePath: string, cache: CachedMetadata | null, editingEmbeds: boolean, printing: boolean, plugin: CodeStylerPlugin) {
const fileContentLines = await getFileContentLines(sourcePath,plugin);
if (!fileContentLines)
return;
let codeblocksParameters: Array<CodeblockParameters> = [];
if (typeof cache?.sections !== 'undefined') {
for (const section of cache.sections) {
if (!editingEmbeds || section.type === 'code' || section.type === 'callout') {
const parsedCodeblocksParameters = await parseCodeblockSource(fileContentLines.slice(section.position.start.line,section.position.end.line+1),sourcePath,plugin);
if (!editingEmbeds || parsedCodeblocksParameters.nested)
codeblocksParameters = codeblocksParameters.concat(parsedCodeblocksParameters.codeblocksParameters);
}
}
} else {
console.error(`Metadata cache not found for file: ${sourcePath}`);
return;
}
if (codeblockPreElements.length !== codeblocksParameters.length)
return;
try {
for (let [key,codeblockPreElement] of Array.from(codeblockPreElements).entries()) {
let codeblockParameters = codeblocksParameters[key];
let codeblockCodeElement: HTMLPreElement | null = codeblockPreElement.querySelector("pre > code");
if (!codeblockCodeElement)
return;
if (Array.from(codeblockCodeElement.classList).some(className => /^language-\S+/.test(className)))
while(!codeblockCodeElement.classList.contains("is-loaded"))
await sleep(2);
if (codeblockCodeElement.querySelector("code [class*='code-styler-line']"))
continue;
if (isExcluded(codeblockParameters.language,plugin.settings.excludedLanguages) || codeblockParameters.ignore)
continue;
await remakeCodeblock(codeblockCodeElement,codeblockPreElement,codeblockParameters,!printing,plugin);
}
} catch (error) {
console.error(`Error rendering document: ${error.message}`);
return;
}
}
export async function readingViewInlineDecoratingPostProcessor(element: HTMLElement, {sourcePath,getSectionInfo,frontmatter}: {sourcePath: string, getSectionInfo: (element: HTMLElement) => MarkdownSectionInformation | null, frontmatter: FrontMatterCache | undefined}, plugin: CodeStylerPlugin) {
export async function readingViewInlineDecoratingPostProcessor(element: HTMLElement, {sourcePath}: {sourcePath: string, getSectionInfo: (element: HTMLElement) => MarkdownSectionInformation | null, frontmatter: FrontMatterCache | undefined}, plugin: CodeStylerPlugin) {
if (!sourcePath || !element)
return;
for (let inlineCodeElement of Array.from(element.querySelectorAll(':not(pre) > code'))) {
if (inlineCodeElement.classList.contains('code-styler-highlighted') || inlineCodeElement.classList.contains('code-styler-highlight-ignore'))
return;
let tempRenderContainer = createDiv();
let renderedCodeElement: HTMLElement | null;
let inlineCodeText = (inlineCodeElement as HTMLElement).innerText;
let {parameters,text} = parseInlineCode(inlineCodeText);
if (!parameters) {
if (!text)
return;
MarkdownRenderer.renderMarkdown('`'.repeat(20)+text+'`'.repeat(20),tempRenderContainer,'',new Component());
renderedCodeElement = tempRenderContainer.querySelector('code');
if (!renderedCodeElement)
return;
inlineCodeElement.innerHTML = renderedCodeElement.innerHTML;
inlineCodeElement.classList.add('code-styler-highlight-ignore');
} else {
MarkdownRenderer.renderMarkdown(['```',plugin.settings.currentTheme.settings.inline.syntaxHighlight?parameters.language:'','\n',text,'\n','```'].join(''),tempRenderContainer,'',new Component());
renderedCodeElement = tempRenderContainer.querySelector('pre > code');
if (!renderedCodeElement)
return;
while(plugin.settings.currentTheme.settings.inline.syntaxHighlight && !renderedCodeElement.classList.contains("is-loaded"))
await sleep(2);
inlineCodeElement.innerHTML = renderedCodeElement.innerHTML;
inlineCodeElement.classList.add('code-styler-highlighted');
inlineCodeElement.setAttribute("parameters",inlineCodeText.substring(0,inlineCodeText.lastIndexOf(text)));
if (parameters.icon || parameters.title)
inlineCodeElement.insertBefore(createInlineOpener(parameters,plugin.languageIcons),inlineCodeElement.childNodes[0]);
}
for (const inlineCodeElement of Array.from(element.querySelectorAll(':not(pre) > code'))) {
await remakeInlineCode(inlineCodeElement as HTMLElement,plugin);
}
}
export function destroyReadingModeElements(): void {
document.querySelectorAll(".code-styler-pre-parent").forEach(codeblockPreParent => {
codeblockPreParent.classList.remove('code-styler-pre-parent');
@ -185,102 +80,203 @@ export function destroyReadingModeElements(): void {
});
}
async function remakeCodeblock(codeblockCodeElement: HTMLElement, codeblockPreElement: HTMLElement, codeblockParameters: CodeblockParameters, dynamic: boolean, plugin: CodeStylerPlugin) {
function escapeHTML(plaintext: string) {
return plaintext.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
async function renderSpecificReadingSection(codeblockPreElements: Array<HTMLElement>, sourcePath: string, codeblockSectionInfo: MarkdownSectionInformation, plugin: CodeStylerPlugin): Promise<void> {
const codeblocksParameters = (await parseCodeblockSource(Array.from({length: codeblockSectionInfo.lineEnd-codeblockSectionInfo.lineStart+1}, (_,num) => num + codeblockSectionInfo.lineStart).map((lineNumber)=>codeblockSectionInfo.text.split('\n')[lineNumber]),sourcePath,plugin)).codeblocksParameters;
if (codeblockPreElements.length !== codeblocksParameters.length)
return;
for (const [key,codeblockPreElement] of codeblockPreElements.entries()) {
const codeblockParameters = codeblocksParameters[key];
const codeblockCodeElement = codeblockPreElement.querySelector('pre > code');
if (!codeblockCodeElement)
return;
if (Array.from(codeblockCodeElement.classList).some(className => /^language-\S+/.test(className)))
while(!codeblockCodeElement.classList.contains("is-loaded"))
await sleep(2);
if (isExcluded(codeblockParameters.language,plugin.settings.excludedLanguages) || codeblockParameters.ignore)
continue;
await remakeCodeblock(codeblockCodeElement as HTMLElement,codeblockPreElement,codeblockParameters,true,plugin);
}
// Add Execute Code Observer
if (dynamic) {
plugin.executeCodeMutationObserver.observe(codeblockPreElement,{
childList: true,
subtree: true,
attributes: true,
characterData: true,
});
}
async function retriggerProcessor(element: HTMLElement, context: {sourcePath: string, getSectionInfo: (element: HTMLElement) => MarkdownSectionInformation | null, frontmatter: FrontMatterCache | undefined}, plugin: CodeStylerPlugin, editingEmbeds: boolean) {
await sleep(50);
editingEmbeds = editingEmbeds || Boolean(element.matchParent(".cm-embed-block"));
if (editingEmbeds || !element.classList.contains("admonition-content")) {
const contentEl = element.matchParent('.view-content') as HTMLElement;
await readingViewCodeblockDecoratingPostProcessor(contentEl || (element.matchParent('div.print') as HTMLElement),context,plugin,editingEmbeds); // Re-render whole document
}
// Add Parent Classes
codeblockPreElement.classList.add(`code-styler-pre`);
if (codeblockParameters.language)
codeblockPreElement.classList.add(`language-${codeblockParameters.language}`);
if (codeblockPreElement.parentElement)
codeblockPreElement.parentElement.classList.add(`code-styler-pre-parent`);
// Create Header
const headerContainer = createHeader(codeblockParameters, plugin.settings.currentTheme.settings,plugin.languageIcons);
codeblockPreElement.insertBefore(headerContainer,codeblockPreElement.childNodes[0]);
if (dynamic) {
// Add listener for header collapsing on click
headerContainer.addEventListener("click",()=>toggleFold(codeblockPreElement));
if (codeblockParameters.fold.enabled)
codeblockPreElement.classList.add("code-styler-collapsed");
// Line Wrapping Classes //TODO (@mayurankv) Turn these into classes (ideally phase out codeblockCodeElement)
if (codeblockParameters.lineUnwrap.alwaysEnabled) {
codeblockCodeElement.style.setProperty('--line-wrapping','pre');
if (codeblockParameters.lineUnwrap.activeWrap)
codeblockCodeElement.style.setProperty('--line-active-wrapping','pre-wrap');
else
codeblockCodeElement.style.setProperty('--line-active-wrapping','pre');
} else if (codeblockParameters.lineUnwrap.alwaysDisabled)
codeblockCodeElement.style.setProperty('--line-wrapping','pre-wrap');
} else if (codeblockParameters.fold.enabled) //TODO (@mayurankv) Can this be removed? Is it just folding codeblocks in exports?
codeblockPreElement.classList.add("code-styler-collapsed");
// Ignore styled lines
if (codeblockCodeElement.querySelector("code [class*='code-styler-line']"))
}
async function renderDocument(codeblockPreElements: Array<HTMLElement>, sourcePath: string, cache: CachedMetadata | null, editingEmbeds: boolean, printing: boolean, plugin: CodeStylerPlugin) {
const codeblocksParameters: Array<CodeblockParameters> = await getCodeblocksParameters(sourcePath,cache,plugin,editingEmbeds);
if (codeblocksParameters.length !== codeblockPreElements.length)
return;
// Add line numbers
let tree = unified().use(remarkParse).parse(codeblockCodeElement.innerHTML.replace(/\n/g,'<br>'));
let stack: Array<string> = [];
let codeblockHTML: string = '';
codeblockCodeElement.innerHTML = "";
visit(tree,(node)=>{
if (node.type === 'html' || node.type === 'text') {
if (node.type === 'html' && node.value !== '<br>') {
if (node.value.startsWith('<span'))
stack.push(node.value);
else if (node.value.startsWith('</span'))
stack.pop();
} else if (node.type === 'html' && node.value === '<br>')
node.value = '</span>'.repeat(stack.length)+'<br>'+stack.join('');
else
node.value = escapeHTML(node.value);
codeblockHTML += node.value;
try {
for (const [key,codeblockPreElement] of Array.from(codeblockPreElements).entries()) {
const codeblockParameters = codeblocksParameters[key];
const codeblockCodeElement: HTMLPreElement | null = codeblockPreElement.querySelector("pre > code");
if (!codeblockCodeElement)
return;
if (Array.from(codeblockCodeElement.classList).some(className => /^language-\S+/.test(className)))
while(!codeblockCodeElement.classList.contains("is-loaded"))
await sleep(2);
if (codeblockCodeElement.querySelector("code [class*='code-styler-line']"))
continue;
if (isExcluded(codeblockParameters.language,plugin.settings.excludedLanguages) || codeblockParameters.ignore)
continue;
await remakeCodeblock(codeblockCodeElement,codeblockPreElement,codeblockParameters,!printing,plugin);
}
});
let codeblockLines = codeblockHTML.split('<br>');
if (codeblockLines.length == 1)
codeblockLines = ['',''];
codeblockLines.forEach((line,index) => {
if (index === codeblockLines.length-1)
return;
const lineNumber = index + 1;
const lineWrapper = document.createElement("div");
getLineClass(codeblockParameters,lineNumber,line).forEach((lineClass) => {
lineWrapper.classList.add(lineClass);
});
codeblockCodeElement.appendChild(lineWrapper);
let lineNumberDisplay = '';
if (!codeblockParameters.lineNumbers.alwaysEnabled && codeblockParameters.lineNumbers.alwaysDisabled)
lineNumberDisplay = '-hide';
else if (codeblockParameters.lineNumbers.alwaysEnabled && !codeblockParameters.lineNumbers.alwaysDisabled)
lineNumberDisplay = '-specific';
lineWrapper.appendChild(createDiv({cls: `code-styler-line-number${lineNumberDisplay}`, text: (lineNumber+codeblockParameters.lineNumbers.offset).toString()}));
lineWrapper.appendChild(createDiv({cls: `code-styler-line-text`, text: sanitizeHTMLToDom(line !== "" ? line : "<br>")}));
});
} catch (error) {
console.error(`Error rendering document: ${error.message}`);
return;
}
}
async function toggleFold(codeblockPreElement: HTMLElement) {
async function remakeCodeblock(codeblockCodeElement: HTMLElement, codeblockPreElement: HTMLElement, codeblockParameters: CodeblockParameters, dynamic: boolean, plugin: CodeStylerPlugin) {
if (dynamic)
plugin.executeCodeMutationObserver.observe(codeblockPreElement,{childList: true,subtree: true,attributes: true,characterData: true}); // Add Execute Code Observer
insertHeader(codeblockPreElement,codeblockParameters,plugin,dynamic);
codeblockPreElement.classList.add(...getPreClasses(codeblockParameters,dynamic));
if (codeblockPreElement.parentElement)
codeblockPreElement.parentElement.classList.add(`code-styler-pre-parent`);
if (!codeblockCodeElement.querySelector("code [class*='code-styler-line']")) // Ignore styled lines
decorateCodeblockLines(codeblockCodeElement,codeblockParameters)
}
async function remakeInlineCode(inlineCodeElement: HTMLElement, plugin: CodeStylerPlugin): Promise<void> {
if (inlineCodeElement.classList.contains('code-styler-inline'))
return;
const inlineCodeText = inlineCodeElement.innerText;
const {parameters,text} = parseInlineCode(inlineCodeText);
if (parameters && plugin.settings.currentTheme.settings.inline.syntaxHighlight)
inlineCodeElement.innerHTML = await getHighlightedHTML(parameters,text,plugin);
else if ((!parameters && text) || (parameters && !plugin.settings.currentTheme.settings.inline.syntaxHighlight))
inlineCodeElement.innerHTML = text;
else
return;
inlineCodeElement.classList.add('code-styler-inline');
if (parameters) {
const parameterString = inlineCodeText.substring(0,inlineCodeText.lastIndexOf(text));
inlineCodeElement.setAttribute("parameters",parameterString); // Store parameter string as attribute so original text can be restored on plugin removal
if (parameters.icon || parameters.title)
inlineCodeElement.insertBefore(createInlineOpener(parameters,plugin.languageIcons),inlineCodeElement.childNodes[0]);
}
}
async function getCodeblockPreElements(element: HTMLElement, specific: boolean,editingEmbeds: boolean): Promise<Array<HTMLElement>> {
let codeblockPreElements: Array<HTMLElement>;
if (!editingEmbeds && !specific)
codeblockPreElements = Array.from(element.querySelectorAll('.markdown-reading-view pre:not(.frontmatter)'));
else if (editingEmbeds && !specific)
codeblockPreElements = Array.from(element.querySelectorAll('.markdown-source-view .cm-embed-block pre:not(.frontmatter)'));
else if (!editingEmbeds && specific) {
codeblockPreElements = Array.from(element.querySelectorAll('pre:not(.frontmatter)'));
const admonitionCodeElement = codeblockPreElements?.[0]?.querySelector('pre:not([class]) > code[class*="language-ad-"]');
if (admonitionCodeElement) {
await sleep(50);
codeblockPreElements = Array.from(element.querySelectorAll('pre:not(.frontmatter)'));
}
} else
codeblockPreElements = [];
return codeblockPreElements
}
async function getCodeblocksParameters(sourcePath: string, cache: CachedMetadata | null, plugin: CodeStylerPlugin, editingEmbeds: boolean): Promise<Array<CodeblockParameters>> {
let codeblocksParameters: Array<CodeblockParameters> = [];
const fileContentLines = await getFileContentLines(sourcePath,plugin);
if (!fileContentLines)
return [];
if (typeof cache?.sections !== 'undefined') {
for (const section of cache.sections) {
if (!editingEmbeds || section.type === 'code' || section.type === 'callout') {
const parsedCodeblocksParameters = await parseCodeblockSource(fileContentLines.slice(section.position.start.line,section.position.end.line+1),sourcePath,plugin);
if (!editingEmbeds || parsedCodeblocksParameters.nested)
codeblocksParameters = codeblocksParameters.concat(parsedCodeblocksParameters.codeblocksParameters);
}
}
} else
console.error(`Metadata cache not found for file: ${sourcePath}`);
return codeblocksParameters;
}
async function toggleFold(codeblockPreElement: HTMLElement): Promise<void> {
codeblockPreElement.querySelectorAll('pre > code').forEach((codeblockCodeElement: HTMLElement)=>codeblockCodeElement.style.setProperty('max-height',`calc(${Math.ceil(codeblockCodeElement.scrollHeight+0.01)}px + var(--code-padding) * ${codeblockCodeElement.classList.contains('execute-code-output')?'3.5 + var(--header-separator-width)':'2'})`));
await sleep(1);
codeblockPreElement.classList.toggle("code-styler-collapsed");
await sleep(TRANSITION_LENGTH);
codeblockPreElement.querySelectorAll('pre > code').forEach((codeblockCodeElement: HTMLElement)=>codeblockCodeElement.style.removeProperty('max-height'));
}
function insertHeader(codeblockPreElement: HTMLElement, codeblockParameters: CodeblockParameters, plugin: CodeStylerPlugin, dynamic: boolean): void {
const headerContainer = createHeader(codeblockParameters, plugin.settings.currentTheme.settings,plugin.languageIcons);
if (dynamic)
headerContainer.addEventListener("click",()=>{toggleFold(codeblockPreElement)}); // Add listener for header collapsing on click
codeblockPreElement.insertBefore(headerContainer,codeblockPreElement.childNodes[0]);
}
function getPreClasses(codeblockParameters: CodeblockParameters, dynamic: boolean): Array<string> {
const preClassList = ['code-styler-pre'];
if (codeblockParameters.language)
preClassList.push(`language-${codeblockParameters.language}`);
if (dynamic) {
if (codeblockParameters.fold.enabled)
preClassList.push("code-styler-collapsed");
if (codeblockParameters.lineUnwrap.alwaysEnabled)
preClassList.push(codeblockParameters.lineUnwrap.activeWrap?'unwrapped-inactive':'unwrapped');
else if (codeblockParameters.lineUnwrap.alwaysDisabled)
preClassList.push('unwrapped');
}
return preClassList;
}
function decorateCodeblockLines(codeblockCodeElement: HTMLElement, codeblockParameters: CodeblockParameters): void {
getCodeblockLines(codeblockCodeElement).forEach((line,index,codeblockLines) => {
if (index !== codeblockLines.length-1)
insertLineWrapper(codeblockCodeElement,codeblockParameters,index+1,line);
});
}
function getCodeblockLines(codeblockCodeElement: HTMLElement): Array<string> {
function escapeHTML(plaintext: string) {
return plaintext.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
const tree = unified().use(remarkParse).parse(codeblockCodeElement.innerHTML.replace(/\n/g,'<br>'));
const stack: Array<string> = [];
let codeblockHTML = '';
visit(tree,['text','html'],(node)=>{
if (node.type === 'html' && node.value !== '<br>') {
if (node.value.startsWith('<span'))
stack.push(node.value);
else if (node.value.startsWith('</span'))
stack.pop();
} else if (node.type === 'html' && node.value === '<br>')
node.value = '</span>'.repeat(stack.length)+'<br>'+stack.join('');
else if (node.type === 'text')
node.value = escapeHTML(node.value);
if ('value' in node)
codeblockHTML += node.value;
});
let codeblockLines = codeblockHTML.split('<br>');
if (codeblockLines.length == 1)
codeblockLines = ['',''];
codeblockCodeElement.innerHTML = "";
return codeblockLines;
}
function insertLineWrapper(codeblockCodeElement: HTMLElement, codeblockParameters: CodeblockParameters, lineNumber: number, line: string): void {
const lineWrapper = document.createElement("div");
codeblockCodeElement.appendChild(lineWrapper);
getLineClasses(codeblockParameters,lineNumber,line).forEach((lineClass) => lineWrapper.classList.add(lineClass));
lineWrapper.appendChild(createDiv({cls: `code-styler-line-number${getLineNumberDisplay(codeblockParameters)}`, text: (lineNumber+codeblockParameters.lineNumbers.offset).toString()}));
lineWrapper.appendChild(createDiv({cls: `code-styler-line-text`, text: sanitizeHTMLToDom(line !== "" ? line : "<br>")}));
}
async function getHighlightedHTML(parameters: InlineCodeParameters, text: string, plugin: CodeStylerPlugin): Promise<string> {
const temporaryRenderingContainer = createDiv();
MarkdownRenderer.renderMarkdown(['```',parameters.language,'\n',text,'\n','```'].join(''),temporaryRenderingContainer,'',new Component());
const renderedCodeElement = temporaryRenderingContainer.querySelector('code');
if (!renderedCodeElement)
return 'ERROR: Could not render highlighted code';
while(plugin.settings.currentTheme.settings.inline.syntaxHighlight && !renderedCodeElement.classList.contains("is-loaded"))
await sleep(2);
return renderedCodeElement.innerHTML;
}
export const executeCodeMutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation: MutationRecord) => {

View file

@ -4,7 +4,6 @@ import { ColorTranslator } from "colortranslator";
import CodeStylerPlugin from "./main";
import { Colour, CSS, HEX, Display, CodeStylerSettings, CodeStylerThemeColours, PARAMETERS, DEFAULT_SETTINGS, LANGUAGE_NAMES, LANGUAGE_ICONS_DATA } from './Settings';
import { todo } from "node:test";
const DISPLAY_OPTIONS: Record<Display,string> = {
"none": "Never",
@ -261,7 +260,7 @@ export class SettingsTab extends PluginSettingTab {
new Setting(containerEl)
.setName('Highlight Line Numbers')
.setDesc('If enabled, highlights will also highlight the line numbers.')
.addToggle(toggle => {let highlightLineNumbersToggle = toggle
.addToggle(toggle => {const highlightLineNumbersToggle = toggle
.setValue(this.plugin.settings.currentTheme.settings.gutter.highlight)
.setDisabled(!this.plugin.settings.currentTheme.settings.codeblock.lineNumbers)
.onChange((value) => {
@ -274,7 +273,7 @@ export class SettingsTab extends PluginSettingTab {
.setName('Indicate Current Line Number')
.setDesc('If enabled, the current line number in codeblocks will be indicated with a separate colour.')
.setClass('code-styler-spaced')
.addToggle(toggle => {let indicateCurrentLineNumberToggle = toggle
.addToggle(toggle => {const indicateCurrentLineNumberToggle = toggle
.setValue(this.plugin.settings.currentTheme.settings.gutter.activeLine)
.setDisabled(!this.plugin.settings.currentTheme.settings.codeblock.lineNumbers)
.onChange((value) => {
@ -908,13 +907,7 @@ export class SettingsTab extends PluginSettingTab {
} else {
try {
this.plugin.settings.redirectLanguages = JSON.parse(value);
Object.entries(this.plugin.settings.redirectLanguages).forEach(([languageName, languageSettings]: [string, {colour?: Colour, icon?: string}])=>{
if ('icon' in languageSettings) {
if (LANGUAGE_NAMES[languageName] in this.plugin.languageIcons)
URL.revokeObjectURL(this.plugin.languageIcons[LANGUAGE_NAMES[languageName]]);
this.plugin.languageIcons[LANGUAGE_NAMES[languageName]] = URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">${languageSettings.icon}</svg>`], { type: "image/svg+xml" }));
}
});
this.redirectLanguages();
(async () => {await this.plugin.saveSettings()})();
} catch {
new Notice('Invalid JSON'); //NOSONAR
@ -933,9 +926,20 @@ export class SettingsTab extends PluginSettingTab {
donationDiv.appendChild(donationButton);
}
// Setting Parsing
redirectLanguages() {
Object.entries(this.plugin.settings.redirectLanguages).forEach(([languageName, languageSettings]: [string, {colour?: Colour, icon?: string}])=>{
if ('icon' in languageSettings) {
if (LANGUAGE_NAMES[languageName] in this.plugin.languageIcons)
URL.revokeObjectURL(this.plugin.languageIcons[LANGUAGE_NAMES[languageName]]);
this.plugin.languageIcons[LANGUAGE_NAMES[languageName]] = URL.createObjectURL(new Blob([`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">${languageSettings.icon}</svg>`], { type: "image/svg+xml" }));
}
});
}
// Setting Creation
createPickr(plugin: CodeStylerPlugin, containerEl: HTMLElement, setting: Setting, id: string, getRelevantThemeColour: (relevantThemeColours: CodeStylerThemeColours)=>Colour, saveRelevantThemeColour: (relevantThemeColours: CodeStylerThemeColours, saveColour: Colour)=>void, disabled?: ()=>boolean) {
let pickr: PickrResettable = new PickrResettable(plugin,containerEl,setting,getRelevantThemeColour,saveRelevantThemeColour);
const pickr: PickrResettable = new PickrResettable(plugin,containerEl,setting,getRelevantThemeColour,saveRelevantThemeColour);
pickr
.on('show', (colour: Pickr.HSVaColor, instance: Pickr) => {
if (typeof disabled !== 'undefined' && disabled())
@ -958,7 +962,7 @@ export class SettingsTab extends PluginSettingTab {
this.pickrs[id]=pickr;
}
// Setting Updates
// Setting Tab Updates
updateDropdown(dropdown: DropdownComponent, settings: CodeStylerSettings) {
dropdown.selectEl.empty();
Object.keys(settings.themes).forEach((theme_name: string) => {
@ -1068,7 +1072,7 @@ function calc(calcString: string): string {
return calcString;
}
function getCssVariable(cssVariable: CSS): HEX {
let variableValue = window.getComputedStyle(document.body).getPropertyValue(cssVariable).trim();
const variableValue = window.getComputedStyle(document.body).getPropertyValue(cssVariable).trim();
if (typeof variableValue === "string" && variableValue.trim().startsWith('#'))
return `#${variableValue.trim().substring(1)}`;
else if (variableValue.startsWith('rgb'))
@ -1080,7 +1084,7 @@ function getCssVariable(cssVariable: CSS): HEX {
return `#${ColorTranslator.toHEXA(variableValue).substring(1)}`;
}
export function getColour(themeColour: Colour): Colour {
return isCss(themeColour)?getCssVariable(themeColour):themeColour;;
return isCss(themeColour)?getCssVariable(themeColour):themeColour;
}
function getCurrentMode() {

View file

@ -34,6 +34,17 @@
overflow-x: overlay;
white-space: var(--line-wrapping);
}
.code-styler pre.code-styler-pre.wrapped > code {
--line-wrapping: pre-wrap;
}
.code-styler pre.code-styler-pre.unwrapped > code {
--line-wrapping: pre;
--line-active-wrapping: pre;
}
.code-styler pre.code-styler-pre.unwrapped-inactive > code {
--line-wrapping: pre;
--line-active-wrapping: pre-wrap;
}
.code-styler pre.code-styler-pre code:not(:has( > input[style*="display: inline;"])):active {
--line-wrapping: var(--line-active-wrapping) !important;
}

View file

@ -42,7 +42,7 @@ export default class CodeStylerPlugin extends Plugin {
let zoomTimeout: NodeJS.Timeout = setTimeout(()=>{});
this.registerEvent(this.app.workspace.on('css-change',()=>{
updateStyling(this.settings,this.app); // Update styling on css changes
let currentFontSize = document.body.getCssPropertyValue('--font-text-size');
const currentFontSize = document.body.getCssPropertyValue('--font-text-size');
if (this.sizes.font !== currentFontSize) {
this.sizes.font = currentFontSize;
clearTimeout(zoomTimeout);
@ -52,7 +52,7 @@ export default class CodeStylerPlugin extends Plugin {
}
},this));
this.registerEvent(this.app.workspace.on('resize',()=>{
let currentZoomSize = document.body.getCssPropertyValue('--zoom-factor');
const currentZoomSize = document.body.getCssPropertyValue('--zoom-factor');
if (this.sizes.zoom !== currentZoomSize) {
this.sizes.zoom = currentZoomSize;
clearTimeout(zoomTimeout);

File diff suppressed because one or more lines are too long