This commit is contained in:
Mayuran Visakan 2024-02-13 03:50:57 +00:00
parent a6bdc18e1d
commit 8e4e3e730f
13 changed files with 317 additions and 59 deletions

View file

@ -1,2 +1,3 @@
node_modules/
main.js
*.md

11
.todo
View file

@ -1,8 +1,14 @@
Features:
Code Referencing:
☐ Local file reference functionality
☐ Local file reference functionality `file`
☐ Replace dependence on
☐ Github/Gitlab/Bitbucket code reference functionality
☐ Regex finding lines
☐ Path by markdown links
☐ Adjust line number in codeblock parsing for this codeblock as with other plugins
☐ Return line number at start of file for this
☐ Also keep repository for title
☐ File watcher?
☐ Github/Gitlab/Bitbucket code reference functionality `repository`
☐ https://forum.obsidian.md/t/reference-a-git-repo/73697 (Do 3rd implementation)
☐ `site` and `tree path` or just `file path`
☐ `lines`
@ -73,6 +79,7 @@ Testing:
Documentation:
☐ Proper documentation #14
☐ `throw Error` s instead of `return`
☐ Use docstrings and better commenting #119
☐ Add test vault #15
☐ Notes for each feature

View file

@ -312,7 +312,7 @@ This plugin is also compatible with the following obsidian plugins out of the bo
[![Execute Code Plugin](https://github-readme-stats.vercel.app/api/pin/?username=twibiral&repo=obsidian-execute-code&show_owner=true&bg_color=00000010&border_color=e3e7ef)](https://github.com/twibiral/obsidian-execute-code)
[![Code Preview Plugin](https://github-readme-stats.vercel.app/api/pin/?username=zjhcn&repo=obsidian-code-preview&show_owner=true&bg_color=00000010&border_color=e3e7ef)](https://github.com/zjhcn/obsidian-code-preview)
[![Code Preview Plugin](https://github-readme-stats.vercel.app/api/pin/?username=tillahoffmann&repo=obsidian-file-include&show_owner=true&bg_color=00000010&border_color=e3e7ef)](https://github.com/tillahoffmann/obsidian-file-include)
[![File Include Plugin](https://github-readme-stats.vercel.app/api/pin/?username=tillahoffmann&repo=obsidian-file-include&show_owner=true&bg_color=00000010&border_color=e3e7ef)](https://github.com/tillahoffmann/obsidian-file-include)
[![Admonitions Plugin](https://github-readme-stats.vercel.app/api/pin/?username=javalent&repo=admonitions&show_owner=true&bg_color=00000010&border_color=e3e7ef)](https://github.com/javalent/admonitions)
## Roadmap

141
main.js

File diff suppressed because one or more lines are too long

View file

@ -55,11 +55,11 @@ export async function parseCodeblockSource(codeSection: Array<string>, plugin: C
function parseCodeblockSection(codeSection: Array<string>): void {
if (codeSection.length === 0)
return;
const openingCodeblockLine = getOpeningLine(codeSection);
if (!openingCodeblockLine)
return;
const openDelimiter = /^\s*(?:>\s*)*((?:```+|~~~+)).*$/.exec(openingCodeblockLine)?.[1];
if (!openDelimiter)
return;
@ -70,7 +70,7 @@ export async function parseCodeblockSource(codeSection: Array<string>, plugin: C
codeblocks.push(codeSection.slice(0,openDelimiterIndex+2+closeDelimiterIndex));
else
parseCodeblockSection(codeSection.slice(openDelimiterIndex+1,openDelimiterIndex+1+closeDelimiterIndex));
parseCodeblockSection(codeSection.slice(openDelimiterIndex+1+closeDelimiterIndex+1));
}
parseCodeblockSection(codeSection);
@ -91,7 +91,7 @@ async function parseCodeblock(codeblockLines: Array<string>, plugin: CodeStylerP
if (!parameterLine)
return null;
const codeblockParameters = parseCodeblockParameters(parameterLine,plugin.settings.currentTheme);
if (isCodeblockIgnored(codeblockParameters.language,plugin.settings.processedCodeblocksWhitelist))
return null;
@ -149,7 +149,7 @@ export function parseCodeblockParameters(parameterLine: string, theme: CodeStyle
const parameterStrings = parameterLine.match(/(?:(?:ref|reference|title):(?:\[\[.*?\]\]|\[.*?\]\(.+\))|[^\s"']+|"[^"]*"|'[^']*')+/g);
if (!parameterStrings)
return codeblockParameters;
parameterStrings.forEach((parameterString) => parseCodeblockParameterString(parameterString.replace(/(?:^,|,$)/g, ""),codeblockParameters,theme));
return codeblockParameters;
}
@ -348,19 +348,18 @@ function parseHighlightedLines(highlightedLinesString: string): Highlights {
Array.from({length:end-start+1}, (_,num) => num + start).forEach(lineNumber => lineNumbers.add(lineNumber));
} else if (/^\/(.*)\/$/.test(highlightRule)) { // Regex
try {
regularExpressions.add(new RegExp(highlightRule.replace(/^\/(.*)\/$/,"$1")));
regularExpressions.add(new RegExp(highlightRule.replace(/^\/(.*)\/$/, "$1")));
} catch {
//pass
}
} else if (/".*"/.test(highlightRule)) { // Plain Text
} else if (/".*"/.test(highlightRule)) // Plain Text
plainText.add(highlightRule.substring(1,highlightRule.length-1));
} else if (/'.*'/.test(highlightRule)) { // Plain Text
else if (/'.*'/.test(highlightRule)) // Plain Text
plainText.add(highlightRule.substring(1,highlightRule.length-1));
} else if (/\D/.test(highlightRule)) { // Plain Text
else if (/\D/.test(highlightRule)) // Plain Text //TODO (@mayurankv) Should this be \D+ ??
plainText.add(highlightRule);
} else if (/\d+/.test(highlightRule)) { // Plain Number
else if (/\d+/.test(highlightRule)) // Plain Number
lineNumbers.add(parseInt(highlightRule));
}
});
return {
lineNumbers: [...lineNumbers],
@ -379,7 +378,7 @@ function parseRegexExcludedLanguages(excludedLanguagesString: string): Array<Reg
return excludedLanguagesString.split(",").map(regexLanguage => new RegExp(`^${regexLanguage.trim().replace(/\*/g,".+")}$`,"i"));
}
export function getParameterLine(codeblockLines: Array<string>): string | undefined {
function getParameterLine(codeblockLines: Array<string>): string | undefined {
let openingCodeblockLine = getOpeningLine(codeblockLines);
if (openingCodeblockLine && (openingCodeblockLine !== codeblockLines[0] || />\s*(?:[`~])/.test(openingCodeblockLine)))
openingCodeblockLine = cleanParameterLine(openingCodeblockLine);

View file

@ -0,0 +1,66 @@
import { parseYaml } from "obsidian";
export interface ReferenceParameters {
filePath: string;
language: string;
repository?: string;
start?: string | number | RegExp;
end?: string | number | RegExp;
}
interface PassedParameters {
filePath?: string;
file?: string;
path?: string;
link?: string;
repository?: string;
repo?: string;
language?: string;
lang?: string;
start?: string | number;
end?: string | number;
}
export function parseReferenceParameters(source: string): ReferenceParameters {
source = source.replace(/^([^:]+):(.+)\n/, "$1: $2\n");
const passedParameters: PassedParameters = parseYaml(source);
if (passedParameters === source)
throw Error("YAML Parse Error");
const filePath = passedParameters?.filePath ?? passedParameters?.file ?? passedParameters?.path ?? passedParameters?.link;
if (filePath === undefined)
throw Error("No file specified");
const referenceParameters: ReferenceParameters = {filePath: filePath, language: passedParameters?.language ?? passedParameters?.lang ?? getLanguage(filePath)};
const repository = passedParameters?.repository ?? passedParameters?.repo;
if (repository !== undefined)
referenceParameters.repository = repository;
const start = getLineIdentifier(String(passedParameters.start));
if (start !== undefined)
referenceParameters.start = start;
const end = getLineIdentifier(String(passedParameters.end));
if (end !== undefined)
referenceParameters.end = end;
return referenceParameters;
}
function getLineIdentifier(lineIdentifier: string | undefined): RegExp | string | number | undefined {
if (lineIdentifier === undefined)
return undefined;
else if (/^\/(.*)\/$/.test(lineIdentifier)) { // Regex
try {
return new RegExp(lineIdentifier.replace(/^\/(.*)\/$/, "$1"));
} catch {
throw Error("Invalid Regular Expression");
}
} else if (/".*"/.test(lineIdentifier)) // Plain Text
return lineIdentifier.substring(1,lineIdentifier.length-1);
else if (/'.*'/.test(lineIdentifier)) // Plain Text
return lineIdentifier.substring(1,lineIdentifier.length-1);
else if (/\D/.test(lineIdentifier)) // Plain Text //TODO (@mayurankv) Should this be \D+ ??
return lineIdentifier;
else if (/\d+/.test(lineIdentifier)) // Plain Number
return parseInt(lineIdentifier);
}
function getLanguage(filePath: string): string {
return filePath.slice((filePath.lastIndexOf(".") - 1 >>> 0) + 2);
}

View file

@ -130,14 +130,14 @@ async function remakeCodeblocks(codeblockPreElements: Array<HTMLElement>, codebl
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,sourcePath,plugin,dynamic);
codeblockPreElement.classList.add(...getPreClasses(codeblockParameters,dynamic));
codeblockPreElement.setAttribute("defaultFold",codeblockParameters.fold.enabled.toString());
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,sourcePath,plugin);
}
@ -187,7 +187,7 @@ async function getCodeblocksParameters(sourcePath: string, cache: CachedMetadata
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),plugin,sourcePath);
if (!editingEmbeds || parsedCodeblocksParameters.nested)
if (!editingEmbeds || (editingEmbeds && parsedCodeblocksParameters?.codeblocksParameters?.[0]?.language === "reference") || parsedCodeblocksParameters.nested)
codeblocksParameters = codeblocksParameters.concat(parsedCodeblocksParameters.codeblocksParameters);
}
}
@ -244,7 +244,7 @@ function decorateCodeblockLines(codeblockCodeElement: HTMLElement, codeblockPara
//TODO (@mayurankv) Add fold to previous point
indentation = currentIndentation;
} else if (currentIndentation < indentation) {
//TODO (@mayurankv) Close all folds to level of currentIndenation
//TODO (@mayurankv) Close all folds to level of current indentation
indentation = currentIndentation;
}
if (currentIndentation > 0) {
@ -298,7 +298,7 @@ function convertCommentLinks(result: Array<ElementContent>, commentText: string,
result.push({type: "text",value: ending});
const linkText = commentText.slice(linkMatch.index, linkMatch.index + linkMatch[0].length);
const linkContainer = createDiv();
MarkdownRenderer.render(plugin.app,linkText,linkContainer,sourcePath,plugin);
MarkdownRenderer.render(plugin.app, linkText, linkContainer, sourcePath, plugin);
const linkChild = (fromHtml(linkContainer.innerHTML,{fragment: true})?.children?.[0] as Element)?.children?.[0];
result.push(linkChild);
commentText = commentText.slice(0, linkMatch.index);

62
src/Referencing.ts Normal file
View file

@ -0,0 +1,62 @@
import { MarkdownPostProcessorContext, MarkdownRenderer, MarkdownSectionInformation, TFile, normalizePath } from "obsidian";
import { ReferenceParameters, parseReferenceParameters } from "src/Parsing/ReferenceParsing";
import { LOCAL_PREFIX, REFERENCE_CODEBLOCK } from "src/Settings";
import CodeStylerPlugin from "src/main";
export async function referenceCodeblockProcessor(source: string, codeblockElement: HTMLElement, context: MarkdownPostProcessorContext, plugin: CodeStylerPlugin) {
const codeblockSectionInfo: MarkdownSectionInformation | null = context.getSectionInfo(codeblockElement);
if (codeblockSectionInfo === null)
throw Error("Could not retrieve codeblock information");
const referenceParameters = parseReferenceParameters(source);
if (referenceParameters?.repository !== undefined) {
// Get github file
// Store in settings folder
const storePath = "";
referenceParameters.filePath = storePath;
}
renderLocalFile(referenceParameters, codeblockElement, context, codeblockSectionInfo, plugin);
}
export async function renderLocalFile(referenceParameters: ReferenceParameters, codeblockElement: HTMLElement, context: MarkdownPostProcessorContext, codeblockSectionInfo: MarkdownSectionInformation, plugin: CodeStylerPlugin) {
let codeblockContent: string;
try {
const vaultPath = getPath(normalizePath(referenceParameters.filePath), context.sourcePath);
const vaultFile = this.app.vault.getAbstractFileByPath(vaultPath);
if (!(vaultFile instanceof TFile))
throw Error(`${vaultPath} is not a file`);
const codeContent = (await this.app.vault.read(vaultFile)).trim();
const parameterLine = codeblockSectionInfo.text.split("\n")[codeblockSectionInfo.lineStart].substring(REFERENCE_CODEBLOCK.length).trim();
codeblockContent = ["```", referenceParameters.language," ",parameterLine, "\n", codeContent, "\n", "```"].join("");
} catch (error) {
codeblockContent = `> [!error] ${(error instanceof Error) ? error.message : String(error)}`;
}
MarkdownRenderer.render(plugin.app, codeblockContent, codeblockElement, context.sourcePath, plugin);
}
function getPath(filePath: string, sourcePath: string): string {
filePath = filePath.trim().replace("\\", "/");
if (filePath.startsWith(LOCAL_PREFIX))
return filePath.substring(2);
else if (filePath.startsWith("./") || /^[^<:"/\\>?|*]/.test(filePath)) {
if (!sourcePath)
throw Error("Cannot resolve relative path because the source path is missing");
return getRelativePath(filePath, sourcePath.trim());
} else if (filePath.startsWith("/"))
throw Error(`Path should not start with "/", use "${LOCAL_PREFIX}" to reference a path relative to the vault root folder`);
else
throw Error("Cannot resolve path");
}
function getRelativePath(filePath: string, sourcePath: string) {
if (filePath.startsWith("./"))
filePath = filePath.substring(2);
const vaultDirs = sourcePath.split("/");
vaultDirs.pop();
while (filePath.startsWith("../"))
if (vaultDirs.pop() !== undefined)
filePath = filePath.substring(3);
else
throw Error("Path references outside vault, too many \"../\"s used");
return normalizePath([...vaultDirs, filePath].join("/"));
}

View file

@ -130,7 +130,7 @@ export interface Language {
continuation?: string; // Default: None
multiline?: string; // Default: true
}>;
}
}
// Theme Defaults
const THEME_DEFAULT_SETTINGS: CodeStylerThemeSettings = {
@ -586,7 +586,7 @@ export const EXAMPLE_CODEBLOCK_PARAMETERS = "python title:foo";
export const EXAMPLE_CODEBLOCK_CONTENT = "print(\"This line is very long and should be used as an example for how the plugin deals with wrapping and unwrapping very long lines given the choice of codeblock parameters and settings.\")\nprint(\"This line is highlighted.\")";
export const EXAMPLE_INLINE_CODE = "{python icon title:foo} print(\"This is inline code\")";
export const EXCLUDED_LANGUAGES = "ad-*";
export const WHITELIST_CODEBLOCKS = "run-*";
export const WHITELIST_CODEBLOCKS = "run-*, reference, include";
// Plugin default settings
export const DEFAULT_SETTINGS: CodeStylerSettings = {
@ -667,8 +667,10 @@ const settingsUpdaters: Record<string,(settings: CodeStylerSettings)=>CodeStyler
export const FOLD_PLACEHOLDER = "Folded Code";
export const PARAMETERS = ["title","fold","ln","wrap","unwrap","ignore"];
export const TRANSITION_LENGTH = 240; // 240ms
export const SPECIAL_LANGUAGES = ["^preview$","^include$","^output$","^run-.+$"];
export const SPECIAL_LANGUAGES = ["^reference$","^preview$","^include$","^output$","^run-.+$"];
export const SETTINGS_SOURCEPATH_PREFIX = "@Code-Styler-Settings:";
export const LOCAL_PREFIX = "@/";
export const REFERENCE_CODEBLOCK = "reference";
const PRISM_LANGUAGES: {[key: string]: string} = { // Prism Languages: https://prismjs.com/plugins/show-language/
// "none": "Plain text", // NOTE: Obsidian uses this for codeblocks without language names
@ -945,7 +947,7 @@ const PRISM_LANGUAGES: {[key: string]: string} = { // Prism Languages: https://p
"yaml": "YAML",
"yml": "YAML",
"yang": "YANG",
};
};
const MANUAL_PRISM_LANGUAGES: {[key: string]: string} = { // Manually generated list from https://prismjs.com/ - 297 languages
"css":"CSS",
"clike":"C-like",

View file

@ -105,7 +105,8 @@ body {
content: none !important;
}
.markdown-source-view.mod-cm6 .cm-embed-block pre.code-styler-pre {
margin: 1em 0px;
margin: 0em 0px;
/* margin: 1em 0px; */
}
/** Editing Mode Body */
@ -142,7 +143,7 @@ body.code-styler .markdown-source-view :not(pre.code-styler-pre) > .code-styler-
body .code-styler-header-container-hidden {
display: none !important;
}
.code-styler-header-container {
.code-styler-header-container {
display: flex !important;
overflow: visible;
height: var(--container-height);

View file

@ -117,24 +117,29 @@ pre.code-styler-pre div.code-block-highlight-wrap {
}
.markdown-source-view.mod-cm6 .cm-embed-block:not(:hover) .code-styler-pre-parent + .edit-block-button {
opacity: 0;
visibility: hidden
visibility: hidden;
}
.markdown-source-view.mod-cm6 .cm-embed-block .code-styler-pre-parent + .edit-block-button {
.markdown-source-view.mod-cm6 .cm-embed-block>.code-styler-pre-parent + .edit-block-button {
--copy-code-header-right-margin: calc(2.2 * var(--header-button-spacing));
--dent-difference: 0px;
--polygon-in: polygon(0 0, 18px 0, 18px 36px, 0 36px);
z-index: 10;
top: max(calc(0.5 * var(--container-height) * 1),calc(0.5 * var(--container-min-height) * 1));
padding: 0;
padding: 0 !important;
margin: 0;
margin-right: calc(var(--copy-code-header-right-margin) - var(--dent-difference));
background-color: transparent !important;
clip-path: var(--polygon-in);
opacity: 1;
transform: translateY(-50%);
transition: visibility var(--duration-button), opacity var(--duration-button);
transition: visibility var(--duration-button), opacity var(--duration-button) !important;
visibility: visible;
will-change: margin-right, clip-path;
}
.markdown-source-view.mod-cm6.is-live-preview.is-readable-line-width .cm-embed-block>.code-styler-pre-parent+.edit-block-button {
width: unset !important;
padding-left: 0 !important;
}
body .cm-embed-block .code-styler-pre-parent:has( pre.code-styler-pre .code-styler-header-container-hidden) + .edit-block-button {
--copy-code-header-right-margin: 4px;
top: calc(0.5 * var(--font-text-size) * 0.875 * var(--line-height-normal));
@ -147,7 +152,7 @@ body .cm-embed-block .code-styler-pre-parent:has( pre.code-styler-pre .code-styl
}
.markdown-source-view.mod-cm6 .cm-embed-block:hover .code-styler-pre-parent + .edit-block-button:active {
scale: 0.95;
transition: scale var(--duration-button) cubic-bezier(0.4, 0.14, 0.3, 1);
transition: scale var(--duration-button) cubic-bezier(0.4, 0.14, 0.3, 1) !important;
}
/** File Include Plugin */

View file

@ -1,10 +1,11 @@
import { Plugin, MarkdownView, WorkspaceLeaf } from "obsidian";
import { convertSettings, DEFAULT_SETTINGS, LANGUAGES, CodeStylerSettings } from "./Settings";
import { convertSettings, DEFAULT_SETTINGS, LANGUAGES, CodeStylerSettings, REFERENCE_CODEBLOCK } from "./Settings";
import { SettingsTab } from "./SettingsTab";
import { removeStylesAndClasses, updateStyling } from "./ApplyStyling";
import { createCodeblockCodeMirrorExtensions, editingDocumentFold } from "./EditingView";
import { destroyReadingModeElements, readingDocumentFold, executeCodeMutationObserver, readingViewCodeblockDecoratingPostProcessor, readingViewInlineDecoratingPostProcessor } from "./ReadingView";
import { referenceCodeblockProcessor } from "./Referencing";
export default class CodeStylerPlugin extends Plugin {
settings: CodeStylerSettings;
@ -14,7 +15,7 @@ export default class CodeStylerPlugin extends Plugin {
font: string;
zoom: string;
};
async onload() {
await this.loadSettings(); // Load Settings
const settingsTab = new SettingsTab(this.app,this);
@ -34,7 +35,9 @@ export default class CodeStylerPlugin extends Plugin {
};
this.executeCodeMutationObserver = executeCodeMutationObserver; // Add execute code mutation observer
this.registerMarkdownCodeBlockProcessor(REFERENCE_CODEBLOCK, async (source, el, ctx) => { await referenceCodeblockProcessor(source, el, ctx, this);});
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
@ -98,7 +101,7 @@ export default class CodeStylerPlugin extends Plugin {
console.log("Loaded plugin: Code Styler");
}
onunload() {
this.executeCodeMutationObserver.disconnect();
removeStylesAndClasses();

File diff suppressed because one or more lines are too long