Skip to content

Commit

Permalink
add settings
Browse files Browse the repository at this point in the history
  • Loading branch information
mProjectsCode committed Mar 30, 2024
1 parent d65debb commit 7465950
Show file tree
Hide file tree
Showing 12 changed files with 330 additions and 27 deletions.
6 changes: 6 additions & 0 deletions exampleVault/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,10 @@ echo "Hello"
+ this line will be marked as inserted
- this line will be marked as deleted
this is a regular line
```

```mermaid
pie title NETFLIX
"Time spent looking for movie" : 90
"Time spent watching it" : 10
```
35 changes: 35 additions & 0 deletions src/LoadedLanguage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type BundledLanguage, type SpecialLanguage } from 'shiki';

export class LoadedLanguage {
alias: string;
defaultLanguage: BundledLanguage | SpecialLanguage | undefined;
languages: (BundledLanguage | SpecialLanguage)[];

constructor(alias: string) {
this.alias = alias;
this.defaultLanguage = undefined;
this.languages = [];
}

addLanguage(language: BundledLanguage | SpecialLanguage): void {
if (!this.languages.includes(language)) {
this.languages.push(language);
}
}

setDefaultLanguage(language: BundledLanguage | SpecialLanguage): void {
if (!this.languages.includes(language)) {
throw new Error(`Language ${language} is not included in the loaded languages for ${this.alias}`);
}

this.defaultLanguage = language;
}

getDefaultLanguage(): BundledLanguage | SpecialLanguage {
if (this.defaultLanguage === undefined) {
throw new Error(`No default language set for ${this.alias}`);
}

return this.defaultLanguage;
}
}
122 changes: 122 additions & 0 deletions src/PrismPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* eslint-disable */

/*
* Taken from https://github.com/PrismJS/prism/blob/master/plugins/filter-highlight-all/prism-filter-highlight-all.js
*/

export function filterHighlightAllPlugin(Prism: any): void {
if (typeof Prism === 'undefined' || typeof document === 'undefined') {
return;
}

const script = Prism.util.currentScript();

/**
* @type {Array<(element: HTMLElement) => boolean>}
*/
const filters: any[] = [];

const config: any = (Prism.plugins.filterHighlightAll = {
/**
* Adds a new filter for the elements of `highlightAll` and `highlightAllUnder` such that only elements for
* which the given function returns `true` will be highlighted.
*
* @param {(value: { element: HTMLElement, language: string }) => boolean} condition
*/
add: function (condition: any): void {
filters.push(function (element: any) {
return condition({
element: element,
language: Prism.util.getLanguage(element),
});
});
},

/**
* Adds a new filter for the elements of `highlightAll` and `highlightAllUnder` such that only elements that
* match the given CSS selection will be highlighted.
*
* @param {string} selector
*/
addSelector: function (selector: any): void {
filters.push(function (element: any) {
return element.matches(selector);
});
},

reject: {
/**
* Adds a new filter for the elements of `highlightAll` and `highlightAllUnder` such that only elements for
* which the given function returns `false` will be highlighted.
*
* @param {(value: { element: HTMLElement, language: string }) => boolean} condition
*/
add: function (condition: any): void {
filters.push(function (element: any) {
return !condition({
element: element,
language: Prism.util.getLanguage(element),
});
});
},

/**
* Adds a new filter for the elements of `highlightAll` and `highlightAllUnder` such that only elements that do
* not match the given CSS selection will be highlighted.
*
* @param {string} selector
*/
addSelector: function (selector: any): void {
filters.push(function (element: any) {
return !element.matches(selector);
});
},
},

/**
* Filters the elements of `highlightAll` and `highlightAllUnder` such that only elements with a known language
* will be highlighted. All elements with an unset or unknown language will be ignored.
*
* __Note:__ This will effectively disable the AutoLoader plugin.
*
* @type {boolean}
*/
filterKnown: !!script && script.hasAttribute('data-filter-known'),
});

config.add(function filterKnown(env: any) {
return !config.filterKnown || typeof Prism.languages[env.language] === 'object';
});

if (script) {
let attr;
attr = script.getAttribute('data-filter-selector');
if (attr) {
config.addSelector(attr);
}
attr = script.getAttribute('data-reject-selector');
if (attr) {
config.reject.addSelector(attr);
}
}

/**
* Applies all filters to the given element and returns true if and only if every filter returned true on the
* given element.
*
* @param {HTMLElement} element
* @returns {boolean}
*/
function combinedFilter(element: any): boolean {
for (let i: number = 0, l = filters.length; i < l; i++) {
if (!filters[i](element)) {
return false;
}
}
return true;
}

Prism.hooks.add('before-all-elements-highlight', function (env: any) {
env.elements = env.elements.filter(combinedFilter);
});
}
File renamed without changes.
2 changes: 1 addition & 1 deletion src/Cm6_ViewPlugin.ts → src/codemirror/Cm6_ViewPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Decoration, type DecorationSet, type EditorView, ViewPlugin, type ViewU
import { type Range } from '@codemirror/state';
import { type SyntaxNode } from '@lezer/common';
import { syntaxTree } from '@codemirror/language';
import { Cm6_Util } from './Cm6_Util';
import { Cm6_Util } from 'src/codemirror/Cm6_Util';
import { type ThemedToken } from 'shiki';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
113 changes: 88 additions & 25 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { Plugin, TFile } from 'obsidian';
import { type BundledLanguage, bundledLanguages, getHighlighter, type Highlighter, type TokensResult } from 'shiki';
import { loadPrism, Plugin, TFile } from 'obsidian';
import { bundledLanguages, getHighlighter, type Highlighter, type TokensResult } from 'shiki';
import { ExpressiveCodeEngine, ExpressiveCodeTheme } from '@expressive-code/core';
import { pluginShiki } from '@expressive-code/plugin-shiki';
import { pluginTextMarkers } from '@expressive-code/plugin-text-markers';
import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections';
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers';
import { pluginFrames } from '@expressive-code/plugin-frames';
import { ThemeMapper } from 'src/ThemeMapper';
import { EC_THEME } from 'src/ECTheme';
import { ThemeMapper } from 'src/themes/ThemeMapper';
import { EC_THEME } from 'src/themes/ECTheme';
import { CodeBlock } from 'src/CodeBlock';
import { OBSIDIAN_THEME } from 'src/ObsidianTheme';
import { createCm6Plugin } from 'src/Cm6_ViewPlugin';
import { OBSIDIAN_THEME } from 'src/themes/ObsidianTheme';
import { createCm6Plugin } from 'src/codemirror/Cm6_ViewPlugin';
import { DEFAULT_SETTINGS, type Settings } from 'src/settings/Settings';
import { ShikiSettingsTab } from 'src/settings/SettingsTab';
import { filterHighlightAllPlugin } from 'src/PrismPlugin';
import { LoadedLanguage } from 'src/LoadedLanguage';

// some languages break obsidian's `registerMarkdownCodeBlockProcessor`, so we blacklist them
const languageNameBlacklist = new Set(['c++', 'c#', 'f#']);
const languageNameBlacklist = new Set(['c++', 'c#', 'f#', 'mermaid']);

export default class ShikiPlugin extends Plugin {
// @ts-expect-error TS2564
Expand All @@ -25,23 +29,25 @@ export default class ShikiPlugin extends Plugin {
// @ts-expect-error TS2564
activeCodeBlocks: Map<string, CodeBlock[]>;
// @ts-expect-error TS2564
loadedLanguages: Map<string, string>;
loadedLanguages: Map<string, LoadedLanguage>;
// @ts-expect-error TS2564
shiki: Highlighter;
// @ts-expect-error TS2564
settings: Settings;

async onload(): Promise<void> {
await this.loadSettings();

this.addSettingTab(new ShikiSettingsTab(this));

this.themeMapper = new ThemeMapper();
this.activeCodeBlocks = new Map();
this.loadedLanguages = new Map();

await this.loadLanguages();

this.shiki = await getHighlighter({
themes: [OBSIDIAN_THEME],
langs: Object.keys(bundledLanguages),
});

await this.loadEC();
await this.loadShiki();

this.registerCodeBlockProcessors();

Expand Down Expand Up @@ -69,17 +75,45 @@ export default class ShikiPlugin extends Plugin {
for (const [shikiLanguage, registration] of Object.entries(bundledLanguages)) {
// the last element of the array is seemingly the most recent version of the language
const language = (await registration()).default.at(-1);
const shikiLanguageName = shikiLanguage as keyof typeof bundledLanguages;

if (language === undefined) {
continue;
}

for (const alias of [language.name, ...(language.aliases ?? [])]) {
if (!this.loadedLanguages.has(alias) && !languageNameBlacklist.has(alias)) {
this.loadedLanguages.set(alias, shikiLanguage);
if (languageNameBlacklist.has(alias)) {
continue;
}

if (!this.loadedLanguages.has(alias)) {
const newLanguage = new LoadedLanguage(alias);
newLanguage.addLanguage(shikiLanguageName);

this.loadedLanguages.set(alias, newLanguage);
}

this.loadedLanguages.get(alias)!.addLanguage(shikiLanguageName);
}
}

for (const [alias, language] of this.loadedLanguages) {
if (language.languages.length === 1) {
language.setDefaultLanguage(language.languages[0]);
} else {
const defaultLanguage = language.languages.find(lang => lang === alias);
if (defaultLanguage !== undefined) {
language.setDefaultLanguage(defaultLanguage);
} else {
console.warn(`No default language found for ${alias}, using the first language in the list`);
language.setDefaultLanguage(language.languages[0]);
}
}
}

for (const disabledLanguage of this.settings.disabledLanguages) {
this.loadedLanguages.delete(disabledLanguage);
}
}

async loadEC(): Promise<void> {
Expand Down Expand Up @@ -113,17 +147,38 @@ export default class ShikiPlugin extends Plugin {
}
}

async loadShiki(): Promise<void> {
this.shiki = await getHighlighter({
themes: [OBSIDIAN_THEME],
langs: Object.keys(bundledLanguages),
});
}

async registerPrismPlugin(): Promise<void> {
/* eslint-disable */

await loadPrism();

const prism = await loadPrism();
filterHighlightAllPlugin(prism);
prism.plugins.filterHighlightAll.reject.addSelector('div.expressive-code pre code');
}

registerCodeBlockProcessors(): void {
for (const [alias, language] of this.loadedLanguages) {
this.registerMarkdownCodeBlockProcessor(
alias,
async (source, el, ctx) => {
const codeBlock = new CodeBlock(this, el, source, language, alias, ctx);

ctx.addChild(codeBlock);
},
-1,
);
try {
this.registerMarkdownCodeBlockProcessor(
alias,
async (source, el, ctx) => {
const codeBlock = new CodeBlock(this, el, source, language.getDefaultLanguage(), alias, ctx);

ctx.addChild(codeBlock);
},
-1,
);
} catch (e) {
console.warn(`Failed to register code block processor for ${alias}`, e);
}
}
}

Expand Down Expand Up @@ -167,8 +222,16 @@ export default class ShikiPlugin extends Plugin {
}

return this.shiki.codeToTokens(code, {
lang: shikiLanguage as BundledLanguage,
lang: shikiLanguage.getDefaultLanguage(),
theme: 'obsidian-theme',
});
}

async loadSettings(): Promise<void> {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) as Settings;
}

async saveSettings(): Promise<void> {
await this.saveData(this.settings);
}
}
7 changes: 7 additions & 0 deletions src/settings/Settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Settings {
disabledLanguages: string[];
}

export const DEFAULT_SETTINGS: Settings = {
disabledLanguages: [],
};
Loading

0 comments on commit 7465950

Please sign in to comment.