Added link parsing and rmd parsing

This commit is contained in:
Mayuran Visakan 2023-11-26 22:08:30 +00:00
parent 771c7c1df9
commit 0ebda454d7
11 changed files with 162 additions and 100 deletions

View file

@ -24,6 +24,20 @@ See this project's [releases](/../../../releases).
- Added swapping `:` with `=` for parameter setting for compatibility with similar plugins
- Added utilising `{1,3,5-7}` syntax for default highlighting line numbers - note this has no ability to highlight with regex or by word
- Added parsing of rmarkdown codeblock formats
- Added links to codeblock headers via the `ref` parameter or using a wikilink in the `title` parameter
### Changed
- Changed how `charWidth` is calculated
### Fixed
- Scrolling long codeblocks no longer glitches the margin due to `charWidth` changes
### Notes
- Links do not show up in metadata (i.e. graphs or backlinks), this is an upstream issue with adding links via plugins
## [1.0.11] - 2023-09-02

125
main.js

File diff suppressed because one or more lines are too long

View file

@ -2,29 +2,35 @@ import { LANGUAGE_NAMES, CodeStylerThemeSettings, FOLD_PLACEHOLDER } from "./Set
import { CodeblockParameters, Highlights } from "./Parsing/CodeblockParsing";
import { InlineCodeParameters } from "./Parsing/InlineCodeParsing";
import { Component, MarkdownRenderer } from "obsidian";
import CodeStylerPlugin from "./main";
export function createHeader(codeblockParameters: CodeblockParameters, themeSettings: CodeStylerThemeSettings, languageIcons: Record<string,string>): HTMLElement {
export function createHeader(codeblockParameters: CodeblockParameters, themeSettings: CodeStylerThemeSettings, sourcePath: string, plugin: CodeStylerPlugin): HTMLElement {
const headerContainer = createDiv();
const iconURL = codeblockParameters.language?getLanguageIcon(codeblockParameters.language,languageIcons):undefined;
const iconURL = codeblockParameters.language?getLanguageIcon(codeblockParameters.language,plugin.languageIcons):undefined;
if (!isHeaderHidden(codeblockParameters,themeSettings,iconURL)) {
headerContainer.classList.add("code-styler-header-container");
if (codeblockParameters.language !== "") {
if (isLanguageIconShown(codeblockParameters,themeSettings,iconURL))
headerContainer.appendChild(createImageWrapper(iconURL as string,createDiv()));
if (isLanguageTagShown(codeblockParameters,themeSettings))
headerContainer.appendChild(createDiv({cls: "code-styler-header-language-tag", text: getLanguageTag(codeblockParameters.language)})); //TODO (@mayurankv) Can I remove the language? Is this info elsewhere?
headerContainer.appendChild(createDiv({cls: "code-styler-header-language-tag", text: getLanguageTag(codeblockParameters.language)}));
}
// headerContainer.appendChild(createDiv({cls: "code-styler-header-text", text: codeblockParameters.title || (codeblockParameters.fold.enabled?(codeblockParameters.fold.placeholder || themeSettings.header.foldPlaceholder || FOLD_PLACEHOLDER):"")}));
const titleContainer = createDiv({cls: "code-styler-header-text"});
const title = codeblockParameters.title || (codeblockParameters.fold.enabled?(codeblockParameters.fold.placeholder || themeSettings.header.foldPlaceholder || FOLD_PLACEHOLDER):"");
if (codeblockParameters.reference === "")
titleContainer.innerText = title;
else
MarkdownRenderer.render(app,`[[${title}|${codeblockParameters.reference}]]`,titleContainer,"",new Component());
const titleContainer = createTitleContainer(codeblockParameters, themeSettings, sourcePath, plugin);
headerContainer.appendChild(titleContainer);
} else
headerContainer.classList.add("code-styler-header-container-hidden");
return headerContainer;
}
function createTitleContainer(codeblockParameters: CodeblockParameters, themeSettings: CodeStylerThemeSettings, sourcePath: string, plugin: CodeStylerPlugin): HTMLElement {
const titleContainer = createDiv({cls: "code-styler-header-text"});
const title = codeblockParameters.title || (codeblockParameters.fold.enabled?(codeblockParameters.fold.placeholder || themeSettings.header.foldPlaceholder || FOLD_PLACEHOLDER):"");
if (codeblockParameters.reference === "")
titleContainer.innerText = title;
else {
MarkdownRenderer.render(plugin.app,`[[${codeblockParameters.reference}|${title}]]`,titleContainer,sourcePath,new Component()); //TODO (@mayurankv) Add links to metadata cache properly
}
return titleContainer;
}
export function createInlineOpener(inlineCodeParameters: InlineCodeParameters, languageIcons: Record<string,string>, containerClasses: Array<string> = ["code-styler-inline-opener"]): HTMLElement {
const openerContainer = createSpan({cls: containerClasses.join(" ")});
if (inlineCodeParameters.icon) {

View file

@ -8,13 +8,14 @@ import { CodeStylerSettings, CodeStylerThemeSettings, SPECIAL_LANGUAGES } from "
import { CodeblockParameters, parseCodeblockParameters, testOpeningLine, trimParameterLine, isCodeblockIgnored, isLanguageIgnored } from "./Parsing/CodeblockParsing";
import { InlineCodeParameters, parseInlineCode } from "./Parsing/InlineCodeParsing";
import { createHeader, createInlineOpener, getLanguageIcon, getLineClass, isHeaderHidden } from "./CodeblockDecorating";
import CodeStylerPlugin from "./main";
interface SettingsState {
excludedLanguages: string;
processedCodeblocksWhitelist: string;
}
export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings, languageIcons: Record<string,string>) {
export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings, plugin: CodeStylerPlugin) {
const livePreviewCompartment = new Compartment;
const ignoreCompartment = new Compartment;
@ -55,10 +56,10 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
});
const charWidthState = StateField.define<number>({ //TODO (@mayurankv) Improve implementation
create(state: EditorState): number {
return(state.field(editorEditorField).defaultCharacterWidth * 1.1);
return(state.field(editorEditorField).defaultCharacterWidth * 1.105);
},
update(value: number, transaction: Transaction): number {
return(transaction.state.field(editorEditorField).defaultCharacterWidth * 1.1);
return(transaction.state.field(editorEditorField).defaultCharacterWidth * 1.105);
}
});
const headerDecorations = StateField.define<DecorationSet>({ //TODO (@mayurankv) Update (does this need to be updated in this manner?)
@ -216,17 +217,19 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
class HeaderWidget extends WidgetType {
codeblockParameters: CodeblockParameters;
themeSettings: CodeStylerThemeSettings;
languageIcons: Record<string,string>;
sourcePath: string;
plugin: CodeStylerPlugin;
iconURL: string | undefined;
folded: boolean;
hidden: boolean;
constructor(codeblockParameters: CodeblockParameters, folded: boolean, themeSettings: CodeStylerThemeSettings, languageIcons: Record<string,string>) {
constructor(codeblockParameters: CodeblockParameters, folded: boolean, themeSettings: CodeStylerThemeSettings, sourcePath: string, plugin: CodeStylerPlugin) {
super();
this.codeblockParameters = structuredClone(codeblockParameters);
this.themeSettings = structuredClone(themeSettings);
this.languageIcons = languageIcons;
this.iconURL = getLanguageIcon(this.codeblockParameters.language,languageIcons);
this.sourcePath = sourcePath;
this.plugin = plugin;
this.iconURL = getLanguageIcon(this.codeblockParameters.language,this.plugin.languageIcons);
this.folded = folded;
this.hidden = isHeaderHidden(this.codeblockParameters,this.themeSettings,this.iconURL);
}
@ -235,6 +238,7 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
return (
this.codeblockParameters.language === other.codeblockParameters.language &&
this.codeblockParameters.title === other.codeblockParameters.title &&
this.codeblockParameters.reference === other.codeblockParameters.reference &&
this.codeblockParameters.fold.enabled === other.codeblockParameters.fold.enabled &&
this.codeblockParameters.fold.placeholder === other.codeblockParameters.fold.placeholder &&
this.themeSettings.header.foldPlaceholder === other.themeSettings.header.foldPlaceholder &&
@ -246,23 +250,26 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
}
toDOM(view: EditorView): HTMLElement {
const headerContainer = createHeader(this.codeblockParameters,this.themeSettings,this.languageIcons,);
const headerContainer = createHeader(this.codeblockParameters,this.themeSettings,this.sourcePath,this.plugin);
if (this.codeblockParameters.language!=="")
headerContainer.classList.add(`language-${this.codeblockParameters.language}`);
if (this.folded)
headerContainer.classList.add("code-styler-header-folded");
headerContainer.onclick = () => {foldOnClick(view,headerContainer,this.folded,this.codeblockParameters.language);};
headerContainer.onclick = (event) => {
if (!(event.target as HTMLElement)?.classList?.contains("internal-link"))
foldOnClick(view,headerContainer,this.folded,this.codeblockParameters.language);
};
return headerContainer;
}
}
class OpenerWidget extends WidgetType {
inlineCodeParameters: InlineCodeParameters;
languageIcons: Record<string,string>;
plugin: CodeStylerPlugin;
constructor (inlineCodeParameters: InlineCodeParameters, languageIcons: Record<string,string>) {
constructor (inlineCodeParameters: InlineCodeParameters, plugin: CodeStylerPlugin) {
super();
this.inlineCodeParameters = inlineCodeParameters;
this.languageIcons = languageIcons;
this.plugin = plugin;
}
eq(other: OpenerWidget): boolean {
@ -270,17 +277,18 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
this.inlineCodeParameters.language == other.inlineCodeParameters.language &&
this.inlineCodeParameters.title == other.inlineCodeParameters.title &&
this.inlineCodeParameters.icon == other.inlineCodeParameters.icon &&
getLanguageIcon(this.inlineCodeParameters.language,this.languageIcons) == getLanguageIcon(other.inlineCodeParameters.language,other.languageIcons)
getLanguageIcon(this.inlineCodeParameters.language,this.plugin.languageIcons) == getLanguageIcon(other.inlineCodeParameters.language,other.plugin.languageIcons)
);
}
toDOM(): HTMLElement {
return createInlineOpener(this.inlineCodeParameters,this.languageIcons,["code-styler-inline-opener","cm-inline-code"]);
return createInlineOpener(this.inlineCodeParameters,this.plugin.languageIcons,["code-styler-inline-opener","cm-inline-code"]);
}
}
function buildHeaderDecorations(state: EditorState, foldValue: (position: number, defaultFold: boolean)=>boolean = (position,defaultFold)=>defaultFold) {
const builder = new RangeSetBuilder<Decoration>();
const sourcePath = state.field(editorInfoField)?.file?.path ?? "";
let codeblockParameters: CodeblockParameters;
syntaxTree(state).iterate({
enter: (syntaxNode) => {
@ -289,7 +297,7 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
codeblockParameters = parseCodeblockParameters(trimParameterLine(startLine.text.toString()),settings.currentTheme);
if (!isLanguageIgnored(codeblockParameters.language,settings.excludedLanguages) && !isCodeblockIgnored(codeblockParameters.language,settings.processedCodeblocksWhitelist) && !codeblockParameters.ignore) {
if (!SPECIAL_LANGUAGES.some(regExp => new RegExp(regExp).test(codeblockParameters.language)))
builder.add(startLine.from,startLine.from,Decoration.widget({widget: new HeaderWidget(codeblockParameters,foldValue(startLine.from,codeblockParameters.fold.enabled),settings.currentTheme.settings,languageIcons), block: true, side: -1}));
builder.add(startLine.from,startLine.from,Decoration.widget({widget: new HeaderWidget(codeblockParameters,foldValue(startLine.from,codeblockParameters.fold.enabled),settings.currentTheme.settings,sourcePath,plugin), block: true, side: -1}));
}
}
}
@ -357,8 +365,8 @@ export function createCodeblockCodeMirrorExtensions(settings: CodeStylerSettings
builder.add(parameters.from, parameters.to, Decoration.mark({class: "code-styler-inline-parameters"}));
else {
builder.add(parameters.from, parameters.to, Decoration.replace({}));
if (parameters.value?.title || (parameters.value?.icon && getLanguageIcon(parameters.value.language,languageIcons)))
builder.add(parameters.from, parameters.from, Decoration.replace({widget: new OpenerWidget(parameters.value,languageIcons)}));
if (parameters.value?.title || (parameters.value?.icon && getLanguageIcon(parameters.value.language,plugin.languageIcons)))
builder.add(parameters.from, parameters.from, Decoration.replace({widget: new OpenerWidget(parameters.value,plugin)}));
}
modeHighlight({start: parameters.to, text: text.value, language: parameters.value.language},builder);
}

View file

@ -134,12 +134,19 @@ export function parseCodeblockParameters(parameterLine: string, theme: CodeStyle
else
return codeblockParameters;
const rmdMatch = /^\{(.+)\} *$/.exec(parameterLine);
if (rmdMatch)
parameterLine = rmdMatch[1];
const languageBreak = parameterLine.indexOf(" ");
codeblockParameters.language = parameterLine.slice(0,languageBreak !== -1?languageBreak:parameterLine.length).toLowerCase();
codeblockParameters.language = parameterLine.slice(0,(languageBreak !== -1)?languageBreak:parameterLine.length).toLowerCase();
if (languageBreak === -1)
return codeblockParameters;
parameterLine = parameterLine.slice(languageBreak+1);
if (rmdMatch)
parameterLine = "title:" + parameterLine;
const parameterStrings = parameterLine.slice(languageBreak+1).match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
const parameterStrings = parameterLine.match(/(?:[^\s,"']+|"[^"]*"|'[^']*')+/g);
if (!parameterStrings)
return codeblockParameters;
@ -216,9 +223,9 @@ function manageTitle(parameterString: string, codeblockParameters: CodeblockPara
codeblockParameters.title = titleMatch[2].trim();
const refTitleMatch = /\[\[([^|]*?)\|?([^|]*?)\]\]/.exec(parameterString.slice("title:".length));
if (refTitleMatch) {
if (refTitleMatch[2] === "") {
codeblockParameters.title = refTitleMatch[1].trim();
codeblockParameters.reference = refTitleMatch[1].trim();
if (refTitleMatch[1] === "") {
codeblockParameters.title = refTitleMatch[2].trim();
codeblockParameters.reference = refTitleMatch[2].trim();
} else {
codeblockParameters.title = refTitleMatch[2].trim();
codeblockParameters.reference = refTitleMatch[1].trim();
@ -226,9 +233,12 @@ function manageTitle(parameterString: string, codeblockParameters: CodeblockPara
}
}
function manageReference(parameterString: string, codeblockParameters: CodeblockParameters) {
const refMatch = /(["']?)([^\1]+)\1/.exec(parameterString.slice("ref:".length));
if (refMatch)
codeblockParameters.reference = refMatch[2].trim();
const refMatch = /\[\[([^|]*?)(?:\|[^|]*?)?\]\]/.exec(parameterString.slice("ref:".length));
if (refMatch) {
codeblockParameters.reference = refMatch[1].trim();
if (codeblockParameters.title === "")
codeblockParameters.title = refMatch[1].trim();
}
}
function manageFolding(parameterString: string, codeblockParameters: CodeblockParameters) {
if (parameterString === "fold") {

View file

@ -85,15 +85,15 @@ export function destroyReadingModeElements(): void {
async function renderSpecificReadingSection(codeblockPreElements: Array<HTMLElement>, sourcePath: string, codeblockSectionInfo: MarkdownSectionInformation, plugin: CodeStylerPlugin) {
const codeblocksParameters = (await parseCodeblockSource(Array.from({length: codeblockSectionInfo.lineEnd-codeblockSectionInfo.lineStart+1}, (_,num) => num + codeblockSectionInfo.lineStart).map((lineNumber)=>codeblockSectionInfo.text.split("\n")[lineNumber]),plugin,sourcePath)).codeblocksParameters;
await remakeCodeblocks(codeblockPreElements,codeblocksParameters,true,false,plugin);
await remakeCodeblocks(codeblockPreElements,codeblocksParameters,sourcePath,true,false,plugin);
}
async function renderSettings(codeblockPreElements: Array<HTMLElement>, sourcePath: string, plugin: CodeStylerPlugin) {
const codeblocksParameters = (await parseCodeblockSource(sourcePath.substring(SETTINGS_SOURCEPATH_PREFIX.length).split("\n"),plugin)).codeblocksParameters;
await remakeCodeblocks(codeblockPreElements,codeblocksParameters,true,false,plugin);
await remakeCodeblocks(codeblockPreElements,codeblocksParameters,sourcePath,true,false,plugin);
}
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);
await remakeCodeblocks(codeblockPreElements,codeblocksParameters,!printing,true,plugin);
await remakeCodeblocks(codeblockPreElements,codeblocksParameters,sourcePath,!printing,true,plugin);
}
async function retriggerProcessor(element: HTMLElement, context: {sourcePath: string, getSectionInfo: (element: HTMLElement) => MarkdownSectionInformation | null, frontmatter: FrontMatterCache | undefined}, plugin: CodeStylerPlugin, editingEmbeds: boolean) {
if (element.matchParent("div.block-language-dataviewjs") && isCodeblockIgnored("dataviewjs",plugin.settings.processedCodeblocksWhitelist))
@ -107,7 +107,7 @@ async function retriggerProcessor(element: HTMLElement, context: {sourcePath: st
}
}
async function remakeCodeblocks(codeblockPreElements: Array<HTMLElement>, codeblocksParameters: Array<CodeblockParameters>, dynamic: boolean, skipStyled: boolean, plugin: CodeStylerPlugin) {
async function remakeCodeblocks(codeblockPreElements: Array<HTMLElement>, codeblocksParameters: Array<CodeblockParameters>, sourcePath: string, dynamic: boolean, skipStyled: boolean, plugin: CodeStylerPlugin) {
if (codeblockPreElements.length !== codeblocksParameters.length)
return;
for (const [key,codeblockPreElement] of Array.from(codeblockPreElements).entries()) {
@ -122,15 +122,15 @@ async function remakeCodeblocks(codeblockPreElements: Array<HTMLElement>, codebl
continue;
if (isLanguageIgnored(codeblockParameters.language,plugin.settings.excludedLanguages) || codeblockParameters.ignore)
continue;
await remakeCodeblock(codeblockCodeElement,codeblockPreElement,codeblockParameters,dynamic,plugin);
await remakeCodeblock(codeblockCodeElement,codeblockPreElement,codeblockParameters,sourcePath,dynamic,plugin);
}
}
async function remakeCodeblock(codeblockCodeElement: HTMLElement, codeblockPreElement: HTMLElement, codeblockParameters: CodeblockParameters, dynamic: boolean, plugin: CodeStylerPlugin) {
async function remakeCodeblock(codeblockCodeElement: HTMLElement, codeblockPreElement: HTMLElement, codeblockParameters: CodeblockParameters, sourcePath: string, 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);
insertHeader(codeblockPreElement,codeblockParameters,sourcePath,plugin,dynamic);
codeblockPreElement.classList.add(...getPreClasses(codeblockParameters,dynamic));
codeblockPreElement.setAttribute("defaultFold",codeblockParameters.fold.enabled.toString());
@ -193,8 +193,8 @@ async function getCodeblocksParameters(sourcePath: string, cache: CachedMetadata
console.error(`Metadata cache not found for file: ${sourcePath}`);
return codeblocksParameters;
}
function insertHeader(codeblockPreElement: HTMLElement, codeblockParameters: CodeblockParameters, plugin: CodeStylerPlugin, dynamic: boolean): void {
const headerContainer = createHeader(codeblockParameters, plugin.settings.currentTheme.settings,plugin.languageIcons);
function insertHeader(codeblockPreElement: HTMLElement, codeblockParameters: CodeblockParameters, sourcePath: string, plugin: CodeStylerPlugin, dynamic: boolean): void {
const headerContainer = createHeader(codeblockParameters, plugin.settings.currentTheme.settings, sourcePath, plugin);
if (dynamic)
headerContainer.addEventListener("click",()=>{toggleFold(codeblockPreElement);}); // Add listener for header folding on click
codeblockPreElement.insertBefore(headerContainer,codeblockPreElement.childNodes[0]);

View file

@ -644,6 +644,7 @@ const settingsUpdaters: Record<string,(settings: CodeStylerSettings)=>CodeStyler
return settings;
}),
"1.0.11": settingsPreserve,
"1.0.12": settingsPreserve,
};
// Constants

View file

@ -1165,7 +1165,7 @@ export class SettingsTab extends PluginSettingTab {
dropdown.setValue(settings.selectedTheme);
}
updateAlternativeHighlights() {
if (this.page !== "codeblock")
if (this.page !== "codeblock" || this.codeblockPage !== "highlight")
return;
this.alternativeHighlightsContainer.empty();
Object.keys(this.plugin.settings.currentTheme.colours.light.highlights.alternativeHighlights).forEach((alternativeHighlightName) => {

View file

@ -280,6 +280,7 @@ pre.code-styler-pre [class^='code-styler-line-highlighted'] > .code-styler-line-
}
pre.code-styler-pre div > [class*='code-styler-line-number'],
.markdown-source-view div[class*="HyperMD-codeblock"] > [class*='code-styler-line-number'] {
width: var(--line-number-gutter-width);
min-width: var(--line-number-gutter-min-width);
padding-right: 8px;
padding-left: calc(4px + var(--language-border-width));

View file

@ -37,7 +37,7 @@ export default class CodeStylerPlugin extends Plugin {
this.registerMarkdownPostProcessor(async (el,ctx) => {await readingViewCodeblockDecoratingPostProcessor(el,ctx,this);}); // Add codeblock decorating markdownPostProcessor
this.registerMarkdownPostProcessor(async (el,ctx) => {await readingViewInlineDecoratingPostProcessor(el,ctx,this);}); // Add inline code decorating markdownPostProcessor
this.registerEditorExtension(createCodeblockCodeMirrorExtensions(this.settings,this.languageIcons)); // Add codemirror extensions
this.registerEditorExtension(createCodeblockCodeMirrorExtensions(this.settings,this)); // Add codemirror extensions
let zoomTimeout: NodeJS.Timeout = setTimeout(()=>{});
this.registerEvent(this.app.workspace.on("css-change",()=>{
@ -108,7 +108,7 @@ export default class CodeStylerPlugin extends Plugin {
}
async loadSettings() {
this.settings = Object.assign({},structuredClone(DEFAULT_SETTINGS),convertSettings(await this.loadData()));
this.settings = {...structuredClone(DEFAULT_SETTINGS), ...convertSettings(await this.loadData())};
}
async saveSettings() {

File diff suppressed because one or more lines are too long