Skip to content

Commit

Permalink
Added link parsing and rmd parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mayurankv committed Nov 26, 2023
1 parent 771c7c1 commit 0ebda45
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 100 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
125 changes: 73 additions & 52 deletions main.js

Large diffs are not rendered by default.

26 changes: 16 additions & 10 deletions src/CodeblockDecorating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
42 changes: 25 additions & 17 deletions src/EditingView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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?)
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 &&
Expand All @@ -246,41 +250,45 @@ 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 {
return (
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) => {
Expand All @@ -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}));
}
}
}
Expand Down Expand Up @@ -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);
}
Expand Down
26 changes: 18 additions & 8 deletions src/Parsing/CodeblockParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -216,19 +223,22 @@ 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();
}
}
}
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") {
Expand Down
18 changes: 9 additions & 9 deletions src/ReadingView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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()) {
Expand All @@ -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());
Expand Down Expand Up @@ -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]);
Expand Down
Loading

0 comments on commit 0ebda45

Please sign in to comment.