refactor: Use multi-file to organize

This commit is contained in:
Yaotian-Liu 2023-05-21 16:57:23 +08:00
parent f4a09bbaff
commit 4cab4a02c4
No known key found for this signature in database
GPG key ID: 160A5464FF2FBA1F
4 changed files with 382 additions and 358 deletions

387
main.ts
View file

@ -1,61 +1,17 @@
import { Editor, MarkdownView, Plugin, Notice } from "obsidian";
import { PseudocodeSettingTab } from "src/setting_tab";
import { PseudocodeSuggestor } from "src/auto_complete";
import {
App,
Editor,
EditorPosition,
EditorSuggest,
EditorSuggestContext,
EditorSuggestTriggerInfo,
MarkdownView,
Plugin,
PluginSettingTab,
Setting,
TFile,
Notice,
} from "obsidian";
PseudocodeSettings,
DEFAULT_SETTINGS,
PseudocodeBlockInit,
BLOCK_NAME,
} from "src/setting";
import * as pseudocode from "pseudocode";
import * as katex from "katex";
// This is the setting for pseudocode.js
interface PseudocodeJsSettings {
indentSize: string;
commentDelimiter: string;
lineNumber: boolean;
lineNumberPunc: string;
noEnd: boolean;
captionCount: undefined;
}
// Setting for this plugin
interface PseudocodeSettings {
blockSize: number;
preamblePath: string;
preambleLoadedNotice: boolean;
jsSettings: PseudocodeJsSettings;
}
const DEFAULT_SETTINGS: PseudocodeSettings = {
blockSize: 99,
preamblePath: "preamble.sty",
preambleLoadedNotice: false,
jsSettings: {
indentSize: "1.2em",
commentDelimiter: "//",
lineNumber: false,
lineNumberPunc: ":",
noEnd: false,
captionCount: undefined,
}
};
const PseudocodeBlockInit =
"```pseudo\n" +
"\t\\begin{algorithm}\n\t\\caption{Algo Caption}\n\t\\begin{algorithmic}" +
"\n\n\t\\end{algorithmic}\n\t\\end{algorithm}" +
"\n```";
const BLOCK_NAME = "pseudo";
export default class PseudocodePlugin extends Plugin {
settings: PseudocodeSettings;
preamble: string;
@ -73,7 +29,7 @@ export default class PseudocodePlugin extends Plugin {
// TODO: Might be able to optimize.
const mathRegex = /\$(.*?)\$/g;
source = source.replace(mathRegex, (match, group1) => {
return '$' + this.preamble + group1 + '$';
return "$" + this.preamble + group1 + "$";
});
const preEl = blockDiv.createEl("pre", { cls: "code", text: source });
@ -82,7 +38,9 @@ export default class PseudocodePlugin extends Plugin {
pseudocode.renderElement(preEl, this.settings.jsSettings);
} catch (error) {
console.log(error);
const errorSpan = blockDiv.createEl("span", { text: "\u274C " + error.message });
const errorSpan = blockDiv.createEl("span", {
text: "\u274C " + error.message,
});
errorSpan.classList.add("error-message");
blockDiv.empty();
blockDiv.appendChild(errorSpan);
@ -114,15 +72,22 @@ export default class PseudocodePlugin extends Plugin {
});
}
onunload() { }
onunload() {}
async loadPreamble() {
try { this.preamble = await this.app.vault.adapter.read(this.settings.preamblePath); }
catch (error) {
try {
this.preamble = await this.app.vault.adapter.read(
this.settings.preamblePath
);
} catch (error) {
console.log(error);
// Extract the search path from the error message.
const searchPath = error.message.match(/'(.*?)'/g)[0];
new Notice("Pseudocode Plugin: Preamble file not found at " + searchPath + ".");
new Notice(
"Pseudocode Plugin: Preamble file not found at " +
searchPath +
"."
);
this.preamble = "";
return;
}
@ -133,11 +98,12 @@ export default class PseudocodePlugin extends Plugin {
if (this.settings.preambleLoadedNotice) {
new Notice("Pseudocode Plugin: Preamble file loaded.");
}
}
catch (error) {
} catch (error) {
console.log(error);
new Notice("Pseudocode Plugin: Preamble file contains invalid LaTeX. " +
"Please refer to console for details.");
new Notice(
"Pseudocode Plugin: Preamble file contains invalid LaTeX. " +
"Please refer to console for details."
);
this.preamble = "";
}
}
@ -154,298 +120,3 @@ export default class PseudocodePlugin extends Plugin {
await this.saveData(this.settings);
}
}
class PseudocodeSettingTab extends PluginSettingTab {
plugin: PseudocodePlugin;
constructor(app: App, plugin: PseudocodePlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h1", { text: "Pseudocode Plugin Settings" });
containerEl.createEl("h2", { text: "Render Behevior" });
// Instantiate Block Size setting
new Setting(containerEl)
.setName("Block Size")
.setDesc(
"The width of the pseudocode block. The unit is 'em'." +
" The default value is 99, which will work as the max width of the editor." +
" '30' looks good for me."
)
.addText((text) =>
text
.setValue(this.plugin.settings.blockSize.toString())
.onChange(async (value) => {
this.plugin.settings.blockSize = Number(value);
await this.plugin.saveSettings();
})
);
// Instantiate Indent Size setting
new Setting(containerEl)
.setName("Indent Size")
.setDesc(
"The indent size of inside a control block, e.g. if, for, etc. The unit must be in 'em'."
)
.addText((text) =>
text
.setValue(this.plugin.settings.jsSettings.indentSize)
.onChange(async (value) => {
this.plugin.settings.jsSettings.indentSize = value;
await this.plugin.saveSettings();
})
);
// Instantiate Comment Delimiter setting
new Setting(containerEl)
.setName("Comment Delimiter")
.setDesc("The string used to indicate a comment in the pseudocode.")
.addText((text) =>
text
.setValue(this.plugin.settings.jsSettings.commentDelimiter)
.onChange(async (value) => {
this.plugin.settings.jsSettings.commentDelimiter = value;
await this.plugin.saveSettings();
})
);
// Instantiate Show Line Numbers setting
new Setting(containerEl)
.setName("Show Line Numbers")
.setDesc("Whether line numbering is enabled.")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.jsSettings.lineNumber)
.onChange(async (value) => {
this.plugin.settings.jsSettings.lineNumber = value;
await this.plugin.saveSettings();
})
);
// Instantiate Line Number Punctuation setting
new Setting(containerEl)
.setName("Line Number Punctuation")
.setDesc(
"The punctuation used to separate the line number from the pseudocode."
)
.addText((text) =>
text
.setValue(this.plugin.settings.jsSettings.lineNumberPunc)
.onChange(async (value) => {
this.plugin.settings.jsSettings.lineNumberPunc = value;
await this.plugin.saveSettings();
})
);
// Instantiate No End setting
new Setting(containerEl)
.setName("No End")
.setDesc(
"If enabled, pseudocode blocks will not have an 'end' statement."
)
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.jsSettings.noEnd)
.onChange(async (value) => {
this.plugin.settings.jsSettings.noEnd = value;
await this.plugin.saveSettings();
})
);
containerEl.createEl("h2", { text: "Preamble Settings" });
// Instantiate Preamble Path setting
new Setting(containerEl)
.setName("Preamble Path")
.setDesc(
"The path to the preamble file. The path is relative to the vault root."
)
.addText((text) =>
text
// .setValue(this.plugin.settings.preamblePath)
.setValue(this.plugin.settings.preamblePath)
.onChange(async (value) => {
this.plugin.settings.preamblePath = value;
await this.plugin.saveSettings();
})
);
// Instantiate Preamble Load Sign setting
new Setting(containerEl)
.setName("Preamble Loaded Notice")
.setDesc("Whether to show a notice everytime the preamble is loaded.")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.preambleLoadedNotice)
.onChange(async (value) => {
this.plugin.settings.preambleLoadedNotice = value;
await this.plugin.saveSettings();
})
);
}
}
class PseudocodeSuggestor extends EditorSuggest<string> {
plugin: PseudocodePlugin;
constructor(plugin: PseudocodePlugin) {
super(plugin.app);
this.plugin = plugin;
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo | null {
// perf: Use the "\" to tell whether to return.
const currentLineToCursor = editor
.getLine(cursor.line)
.slice(0, cursor.ch);
const currentLineLastWordStart = currentLineToCursor.lastIndexOf("\\");
// if there is no word, return null
if (currentLineLastWordStart === -1) return null;
// If is within a LaTeX $$ wrap, return null
const currentLineLastMoneyMark = currentLineToCursor.lastIndexOf("$");
if (currentLineLastMoneyMark != -1) return null;
const currentFileToCursor = editor.getRange({ line: 0, ch: 0 }, cursor);
const indexOfLastCodeBlockStart =
currentFileToCursor.lastIndexOf("```");
// check if this is a pseudocode block
const isPseudocode =
currentFileToCursor.slice(
indexOfLastCodeBlockStart + 3,
indexOfLastCodeBlockStart + 3 + BLOCK_NAME.length
) == BLOCK_NAME;
if (!isPseudocode) return null;
return {
start: { line: cursor.line, ch: currentLineLastWordStart },
end: cursor,
query: currentLineToCursor.slice(currentLineLastWordStart),
};
}
getSuggestions(
context: EditorSuggestContext
): string[] | Promise<string[]> {
const query = context.query;
const suggestions = this.pseudocodeKeywords.filter((value) =>
value.toLowerCase().startsWith(query.toLowerCase())
);
return suggestions;
}
renderSuggestion(value: string, el: HTMLElement): void {
el.addClass("suggestion");
const suggestContent = el.createDiv({ cls: "suggestion-content" });
suggestContent.setText(value);
}
selectSuggestion(value: string, evt: MouseEvent | KeyboardEvent): void {
if (this.context) {
const editor = this.context.editor;
const suggestion = value;
const start = this.context.start;
const end = editor.getCursor();
editor.replaceRange(suggestion, start, end);
const newCursor = end;
newCursor.ch = start.ch + suggestion.length;
editor.setCursor(newCursor);
this.close();
}
}
private pseudocodeKeywords: string[] = [
"\\begin{algorithmic}",
"\\begin{algorithm}",
"\\end{algorithmic}",
"\\end{algorithm}",
"\\caption{}",
"\\Procedure{}{}",
"\\EndProcedure",
"\\Function{}{}",
"\\EndFunction",
"\\Require",
"\\Ensure",
"\\Input",
"\\Output",
"\\State",
"\\Return",
"\\Print",
"\\For{}",
"\\EndFor",
"\\If{}",
"\\Elif{}",
"\\EndIf",
"\\While{}",
"\\EndWhile",
"\\Repeat",
"\\Until{}",
"\\Comment{}",
"\\{",
"\\}",
"\\$",
"\\&",
"\\#",
"\\%",
"\\_",
"\\gets",
"\\Call{}{}",
"\\And",
"\\Or",
"\\Xor",
"\\Not",
"\\To",
"\\DownTo",
"\\True",
"\\False",
"\\tiny",
"\\scriptsize",
"\\footnotesize",
"\\small",
"\\normalsize",
"\\Large",
"\\Huge",
"\\rmfamily",
"\\sffamily",
"\\ttfamily",
"\\upshape",
"\\itshape",
"\\slshape",
"\\scshape",
"\\bfseries",
"\\mdseries",
"\\lfseries",
"\\textnormal{}",
"\\textrm{}",
"\\textsf{}",
"\\texttt{}",
"\\textup{}",
"\\textit{}",
"\\textsl{}",
"\\textsc{}",
"\\uppercase{}",
"\\lowercase{}",
"\\textbf{}",
"\\textmd{}",
"\\textlf{}",
];
}

169
src/auto_complete.ts Normal file
View file

@ -0,0 +1,169 @@
import {
Editor,
EditorPosition,
EditorSuggest,
EditorSuggestContext,
EditorSuggestTriggerInfo,
TFile,
} from "obsidian";
import { BLOCK_NAME } from "src/setting";
import PseudocodePlugin from "main";
export class PseudocodeSuggestor extends EditorSuggest<string> {
plugin: PseudocodePlugin;
constructor(plugin: PseudocodePlugin) {
super(plugin.app);
this.plugin = plugin;
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
file: TFile
): EditorSuggestTriggerInfo | null {
// perf: Use the "\" to tell whether to return.
const currentLineToCursor = editor
.getLine(cursor.line)
.slice(0, cursor.ch);
const currentLineLastWordStart = currentLineToCursor.lastIndexOf("\\");
// if there is no word, return null
if (currentLineLastWordStart === -1) return null;
// If is within a LaTeX $$ wrap, return null
const currentLineLastMoneyMark = currentLineToCursor.lastIndexOf("$");
if (currentLineLastMoneyMark != -1) return null;
const currentFileToCursor = editor.getRange({ line: 0, ch: 0 }, cursor);
const indexOfLastCodeBlockStart =
currentFileToCursor.lastIndexOf("```");
// check if this is a pseudocode block
const isPseudocode =
currentFileToCursor.slice(
indexOfLastCodeBlockStart + 3,
indexOfLastCodeBlockStart + 3 + BLOCK_NAME.length
) == BLOCK_NAME;
if (!isPseudocode) return null;
return {
start: { line: cursor.line, ch: currentLineLastWordStart },
end: cursor,
query: currentLineToCursor.slice(currentLineLastWordStart),
};
}
getSuggestions(
context: EditorSuggestContext
): string[] | Promise<string[]> {
const query = context.query;
const suggestions = this.pseudocodeKeywords.filter((value) =>
value.toLowerCase().startsWith(query.toLowerCase())
);
return suggestions;
}
renderSuggestion(value: string, el: HTMLElement): void {
el.addClass("suggestion");
const suggestContent = el.createDiv({ cls: "suggestion-content" });
suggestContent.setText(value);
}
selectSuggestion(value: string, evt: MouseEvent | KeyboardEvent): void {
if (this.context) {
const editor = this.context.editor;
const suggestion = value;
const start = this.context.start;
const end = editor.getCursor();
editor.replaceRange(suggestion, start, end);
const newCursor = end;
newCursor.ch = start.ch + suggestion.length;
editor.setCursor(newCursor);
this.close();
}
}
private pseudocodeKeywords: string[] = [
"\\begin{algorithmic}",
"\\begin{algorithm}",
"\\end{algorithmic}",
"\\end{algorithm}",
"\\caption{}",
"\\Procedure{}{}",
"\\EndProcedure",
"\\Function{}{}",
"\\EndFunction",
"\\Require",
"\\Ensure",
"\\Input",
"\\Output",
"\\State",
"\\Return",
"\\Print",
"\\For{}",
"\\EndFor",
"\\If{}",
"\\Elif{}",
"\\EndIf",
"\\While{}",
"\\EndWhile",
"\\Repeat",
"\\Until{}",
"\\Comment{}",
"\\{",
"\\}",
"\\$",
"\\&",
"\\#",
"\\%",
"\\_",
"\\gets",
"\\Call{}{}",
"\\And",
"\\Or",
"\\Xor",
"\\Not",
"\\To",
"\\DownTo",
"\\True",
"\\False",
"\\tiny",
"\\scriptsize",
"\\footnotesize",
"\\small",
"\\normalsize",
"\\Large",
"\\Huge",
"\\rmfamily",
"\\sffamily",
"\\ttfamily",
"\\upshape",
"\\itshape",
"\\slshape",
"\\scshape",
"\\bfseries",
"\\mdseries",
"\\lfseries",
"\\textnormal{}",
"\\textrm{}",
"\\textsf{}",
"\\texttt{}",
"\\textup{}",
"\\textit{}",
"\\textsl{}",
"\\textsc{}",
"\\uppercase{}",
"\\lowercase{}",
"\\textbf{}",
"\\textmd{}",
"\\textlf{}",
];
}

39
src/setting.ts Normal file
View file

@ -0,0 +1,39 @@
// This is the setting for pseudocode.js
export interface PseudocodeJsSettings {
indentSize: string;
commentDelimiter: string;
lineNumber: boolean;
lineNumberPunc: string;
noEnd: boolean;
captionCount: undefined;
}
// Setting for this plugin
export interface PseudocodeSettings {
blockSize: number;
preamblePath: string;
preambleLoadedNotice: boolean;
jsSettings: PseudocodeJsSettings;
}
export const DEFAULT_SETTINGS: PseudocodeSettings = {
blockSize: 99,
preamblePath: "preamble.sty",
preambleLoadedNotice: false,
jsSettings: {
indentSize: "1.2em",
commentDelimiter: "//",
lineNumber: false,
lineNumberPunc: ":",
noEnd: false,
captionCount: undefined,
}
};
export const PseudocodeBlockInit =
"```pseudo\n" +
"\t\\begin{algorithm}\n\t\\caption{Algo Caption}\n\t\\begin{algorithmic}" +
"\n\n\t\\end{algorithmic}\n\t\\end{algorithm}" +
"\n```";
export const BLOCK_NAME = "pseudo";

145
src/setting_tab.ts Normal file
View file

@ -0,0 +1,145 @@
import {
App,
PluginSettingTab,
Setting,
} from "obsidian";
import PseudocodePlugin from "main";
export class PseudocodeSettingTab extends PluginSettingTab {
plugin: PseudocodePlugin;
constructor(app: App, plugin: PseudocodePlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h1", { text: "Pseudocode Plugin Settings" });
containerEl.createEl("h2", { text: "Render Behevior" });
// Instantiate Block Size setting
new Setting(containerEl)
.setName("Block Size")
.setDesc(
"The width of the pseudocode block. The unit is 'em'." +
" The default value is 99, which will work as the max width of the editor." +
" '30' looks good for me."
)
.addText((text) =>
text
.setValue(this.plugin.settings.blockSize.toString())
.onChange(async (value) => {
this.plugin.settings.blockSize = Number(value);
await this.plugin.saveSettings();
})
);
// Instantiate Indent Size setting
new Setting(containerEl)
.setName("Indent Size")
.setDesc(
"The indent size of inside a control block, e.g. if, for, etc. The unit must be in 'em'."
)
.addText((text) =>
text
.setValue(this.plugin.settings.jsSettings.indentSize)
.onChange(async (value) => {
this.plugin.settings.jsSettings.indentSize = value;
await this.plugin.saveSettings();
})
);
// Instantiate Comment Delimiter setting
new Setting(containerEl)
.setName("Comment Delimiter")
.setDesc("The string used to indicate a comment in the pseudocode.")
.addText((text) =>
text
.setValue(this.plugin.settings.jsSettings.commentDelimiter)
.onChange(async (value) => {
this.plugin.settings.jsSettings.commentDelimiter = value;
await this.plugin.saveSettings();
})
);
// Instantiate Show Line Numbers setting
new Setting(containerEl)
.setName("Show Line Numbers")
.setDesc("Whether line numbering is enabled.")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.jsSettings.lineNumber)
.onChange(async (value) => {
this.plugin.settings.jsSettings.lineNumber = value;
await this.plugin.saveSettings();
})
);
// Instantiate Line Number Punctuation setting
new Setting(containerEl)
.setName("Line Number Punctuation")
.setDesc(
"The punctuation used to separate the line number from the pseudocode."
)
.addText((text) =>
text
.setValue(this.plugin.settings.jsSettings.lineNumberPunc)
.onChange(async (value) => {
this.plugin.settings.jsSettings.lineNumberPunc = value;
await this.plugin.saveSettings();
})
);
// Instantiate No End setting
new Setting(containerEl)
.setName("No End")
.setDesc(
"If enabled, pseudocode blocks will not have an 'end' statement."
)
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.jsSettings.noEnd)
.onChange(async (value) => {
this.plugin.settings.jsSettings.noEnd = value;
await this.plugin.saveSettings();
})
);
containerEl.createEl("h2", { text: "Preamble Settings" });
// Instantiate Preamble Path setting
new Setting(containerEl)
.setName("Preamble Path")
.setDesc(
"The path to the preamble file. The path is relative to the vault root."
)
.addText((text) =>
text
// .setValue(this.plugin.settings.preamblePath)
.setValue(this.plugin.settings.preamblePath)
.onChange(async (value) => {
this.plugin.settings.preamblePath = value;
await this.plugin.saveSettings();
})
);
// Instantiate Preamble Load Sign setting
new Setting(containerEl)
.setName("Preamble Loaded Notice")
.setDesc("Whether to show a notice everytime the preamble is loaded.")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.preambleLoadedNotice)
.onChange(async (value) => {
this.plugin.settings.preambleLoadedNotice = value;
await this.plugin.saveSettings();
})
);
}
}