diff --git a/src/appContext.ts b/helpers/appContext.ts similarity index 100% rename from src/appContext.ts rename to helpers/appContext.ts diff --git a/src/declarations.d.ts b/helpers/declarations.d.ts similarity index 100% rename from src/declarations.d.ts rename to helpers/declarations.d.ts diff --git a/src/graphs.ts b/helpers/graphs.ts similarity index 100% rename from src/graphs.ts rename to helpers/graphs.ts diff --git a/src/parser.ts b/helpers/parser.ts similarity index 98% rename from src/parser.ts rename to helpers/parser.ts index 8349841..87dec3b 100644 --- a/src/parser.ts +++ b/helpers/parser.ts @@ -4,13 +4,14 @@ import {create , all} from "mathjs"; import type {ChartOptions, ChartConfiguration} from "chart.js/auto"; import { Notice, App, TFile} from "obsidian"; -import { getApp } from "appContext"; +import { getApp } from "./appContext"; import { customNotice,isTuple } from "main"; import { validLineProperties } from "./graphs"; import { min } from "mathjs"; import { string } from "mathjs"; import { isArray } from "chart.js/dist/helpers/helpers.core"; +import {validPlotProperties,validRoots} from "./plotProperties" const math = create(all); math.import({ // Created an alias so the user can write the more "normal" ln(x) and Mathjs wont hate me. @@ -66,42 +67,8 @@ export type parsedText = { const builtInConstants = ["e","E","pi","PI"]; -export const validPlotProperties = [ - "type", - "min", - "max", - "display", - "position", - "text", - "color", - "stepSize", - "beginAtZero", - "suggestedMin", - "suggestedMax", - "lineWidth", - "drawBorder", - "drawOnChartArea", - "drawTicks", - "tickLength", - "mode", - "intersect", - "enabled", - "borderColor", - "backgroundColor", - "borderWidth", - "pointRadius", - "tension", - "fill", - "hidden" -]; -const validRoots = [ - "plugins", - "scales", - "elements", - "interaction", - "animation" -] + diff --git a/helpers/plotProperties.ts b/helpers/plotProperties.ts new file mode 100644 index 0000000..804bbde --- /dev/null +++ b/helpers/plotProperties.ts @@ -0,0 +1,131 @@ + +import { CompletionContext } from "@codemirror/autocomplete"; +import { PlotPluginSettings } from "settings"; +import { getDefaultPlotProperties } from "main"; +import LinearScaleBase from "chart.js/dist/scales/scale.linearbase"; + + +export const myCompletions = { + "obj.": [ + {label: "plugins", type: "property", info: "Chart plugins"}, + {label: "scales", type: "property", info: "Axis settings"}, + {label: "elements",type: "property",info: "Line settings"}, + {label: "animations",type: "property",info: "Animation settings"}, + {label: "interaction",type: "property",info: "Interaction settings"} + ], +} + + +export const validRoots = [ + "plugins", + "scales", + "elements", + "interaction", + "animation" +] + +export const validPlotProperties = [ + "type", + "min", + "max", + "display", + "position", + "text", + "color", + "stepSize", + "beginAtZero", + "suggestedMin", + "suggestedMax", + "lineWidth", + "drawBorder", + "drawOnChartArea", + "drawTicks", + "tickLength", + "mode", + "intersect", + "enabled", + "borderColor", + "backgroundColor", + "borderWidth", + "pointRadius", + "tension", + "fill", + "hidden" +]; + +type TreeNode = { + [key: string]: TreeNode; +}; + +export function linePlotCompletionSource(settings: PlotPluginSettings) { + const chartTree = objectToTree(getDefaultPlotProperties(settings)); // Get default properties from main and create a tree object to travers + return function(context: CompletionContext) { // function that autocompletion accepts + + if (!inLineplotBlock(context)) return null; // if the text doesnt have ```lineplot at some point before, dont autocomplete. Couldnt get syntaxTree to work + const match = context.matchBefore(/[a-zA-Z0-9_.()]*/); // Matches text before cursor stop + if (!match) return null; + + if (match.text.startsWith("obj.")) { + const pathText = match.text.slice(4); // Remove obj. + const parts = pathText.split("."); // Gets path scales.x + + const partial = parts.pop() ?? ""; // Removes what is being typed + + let node: any = chartTree; // Traversal starts at root + + for (const part of parts) { // Walk through path + if (!part) continue; + + if (!(part in node)) return null; // If part not a valid jey in nodes (chartTree) stop suggesting + + node = node[part]; // the node becomes the next object inside part so node was scales.x is now x. -> options for x + } + return { + from: context.pos - partial.length, // we only replace the property being written not the whole obj. -> path + options: Object.keys(node).filter(key => key.startsWith(partial)).map( key => ({ + label: key, + type: "property" + })) + }; + } + + return null; + } +} + +function inLineplotBlock(context: CompletionContext): boolean { + let line = context.state.doc.lineAt(context.pos); + + let foundStart = false; + + for (let n = line.number; n >= 1; n--) { + const txt = context.state.doc.line(n).text.trim(); + + if (txt.startsWith("```")) { + if (txt === "```lineplot") { + foundStart = true; + } + break; + } + } + + return foundStart; +} + + + +export function objectToTree(obj: any): TreeNode { + const tree: TreeNode = {}; + + for (const key in obj) { + const value = obj[key]; + + if (value !== null && typeof value === "object" && !Array.isArray(value)) { + tree[key] = objectToTree(value); + } else { + tree[key] = {}; + } + } + + return tree; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d28ad79..06b1765 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "0-BSD", "dependencies": { + "@codemirror/autocomplete": "^6.20.1", "chart.js": "^4.5.1", "chartjs-plugin-zoom": "^2.2.0", "mathjs": "^15.1.1", @@ -35,12 +36,37 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, "node_modules/@codemirror/state": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", "license": "MIT", - "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -50,7 +76,6 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -713,12 +738,35 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@microsoft/eslint-plugin-sdl": { "version": "1.1.0", @@ -1561,8 +1609,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -4762,8 +4809,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/supports-color": { "version": "7.2.0", @@ -5093,8 +5139,7 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/which": { "version": "2.0.2", diff --git a/package.json b/package.json index 61f3fcf..bfba245 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "typescript-eslint": "8.35.1" }, "dependencies": { + "@codemirror/autocomplete": "^6.20.1", "chart.js": "^4.5.1", "chartjs-plugin-zoom": "^2.2.0", "mathjs": "^15.1.1", diff --git a/src/main.ts b/src/main.ts index 8d7b34b..b03dd02 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,14 @@ -import {handleMarkdown, handleGlobalOptions, evaluateExpressions, PlotData, parsedText, handleTableData} from "./parser" -import { createPlot, buildDatasets} from "./graphs"; +import {handleMarkdown, handleGlobalOptions, evaluateExpressions, PlotData, parsedText, handleTableData} from "../helpers/parser" +import { createPlot, buildDatasets} from "../helpers/graphs"; import {Notice, Plugin} from "obsidian"; import { apply } from "mathjs"; -import { setApp } from "appContext"; +import { setApp } from "../helpers/appContext"; import { PlotPluginSettings, DEFAULT_SETTINGS, PlotSettingTab } from "settings"; import { ChartConfiguration } from "chart.js/auto"; - - +import {autocompletion} from "@codemirror/autocomplete"; +import {EditorView} from "@codemirror/view"; +import { linePlotCompletionSource } from "../helpers/plotProperties"; export default class PlotPlugin extends Plugin { settings!: PlotPluginSettings; @@ -17,12 +18,19 @@ export default class PlotPlugin extends Plugin { await this.loadSettings(); this.addSettingTab(new PlotSettingTab(this.app,this)); + this.registerEditorExtension([ + autocompletion({ + override: [linePlotCompletionSource(this.settings)], + activateOnTyping: true, + })] + ) + setApp(this.app); // Sets current app as the working app to use globally. this.registerMarkdownCodeBlockProcessor("lineplot", async (source: string, el: HTMLElement) => { // Runs whenever a codeblock with the "lineplot" identifier is edited. - const defaultProperties: ChartConfiguration<"line">["options"] = this.getDefaultPlotProperties(); + const defaultProperties: ChartConfiguration<"line">["options"] = getDefaultPlotProperties(this.settings); const sourcePaths = getSourcePaths(source); // Gets any source:: item from path PATHS. Stores them for comparisons later. if (defaultProperties === undefined) return; let cachedParsedText = await handleMarkdown(source,defaultProperties); // Evaluates all the markdown in the codeblock and creates a ParsedText type object. @@ -60,6 +68,7 @@ export default class PlotPlugin extends Plugin { if (!chartInstance) { // If the chart doesnt exist yet. Create it const wrapper = el.createDiv("plot-wrapper"); + wrapper.setCssProps({ "--wrapper-height": `${this.settings.canvasHeight}px`, "--wrapper-padding": `${this.settings.canvasPadding}px`, @@ -136,126 +145,11 @@ export default class PlotPlugin extends Plugin { refreshPlots() { this.app.workspace.updateOptions(); } - - - getDefaultPlotProperties(): ChartConfiguration<"line">["options"] { - const style = getComputedStyle(document.body); - const fontFamily = style.getPropertyValue("--font-interface").trim() || "sans-serif"; - return { - responsive: true, - maintainAspectRatio: false, - animation: false, - interaction: { - mode: "nearest", - intersect: false - }, - layout: { - padding: 8 - }, - plugins: { - legend: { - display: this.settings.legendStatus, - position: "right", - labels: { - boxWidth: 5, - boxHeight: 5, - padding: 14, - usePointStyle: true, - font: { - family: fontFamily, - size: 12 - } - } - }, - zoom: { - - pan: { - enabled: this.settings.zoomStatus ? false : true, - mode: "xy" - }, - zoom: { - wheel: { - enabled: this.settings.zoomStatus ? false : true, - }, - pinch: { - enabled: this.settings.zoomStatus ? false : true, - }, - mode: "xy" - } - }, - title: { - display: this.settings.titleStatus, - font: { - family: fontFamily, - size: 13, - weight: 600 - } - }, - tooltip: { - enabled: true, - padding: 10, - cornerRadius: 8 - }, - - }, - elements: { - line: { - borderWidth: this.settings.EborderWidth, - tension: 0.15, - fill: false, - }, - - point: { - radius: this.settings.pointRadius, - hoverRadius: 4, - hitRadius: 6, - pointStyle: "circle" - } - }, - - scales: { - x: { - type: this.settings.xScalesType, - - display: true, - - grid: { - display: true - }, - - ticks: { - display: true, - font: { - family: fontFamily, - size: 11 - } - }, - - title: { - display: this.settings.titleStatus, - text: "" - } - }, - - y: { - type: this.settings.yScalesType, - display: true, - - grid: { - display: true - }, - - ticks: { - display: true - }, - - title: { - display: this.settings.titleStatus, - text: "" - }}}}; - - } + + + + } @@ -265,25 +159,7 @@ export default class PlotPlugin extends Plugin { export function isTuple(arr: number[]): arr is [number, number, number] { return arr.length === 3 && arr.every(n => typeof n === "number"); } -/* -function declareScalesType(data: PlotData[], scale: string) { - let sample = data[0]?.data[0]?.x; - switch (scale) { - case "x": - sample = data[0].data[0].x; - case "y": - sample = data[1]?.data[1]?.y; - } - console.log(sample); - - if (typeof sample === "number") return "linear"; - - if (typeof sample === "string") return "category"; - - return "linear"; -} - */ function getSourcePaths(src: string) { const paths: string[] = []; @@ -333,3 +209,121 @@ export function customNotice(msg: string, cls = "", timeout = 4000) { }); } +export function getDefaultPlotProperties(settings: PlotPluginSettings): ChartConfiguration<"line">["options"] { + const style = getComputedStyle(document.body); + const fontFamily = style.getPropertyValue("--font-interface").trim() || "sans-serif"; + return { + responsive: true, + maintainAspectRatio: false, + animation: false, + interaction: { + mode: "nearest", + intersect: false + }, + layout: { + padding: 8 + }, + plugins: { + legend: { + display: settings.legendStatus, + position: "right", + labels: { + boxWidth: 5, + boxHeight: 5, + padding: 14, + usePointStyle: true, + font: { + family: fontFamily, + size: 12 + } + } + }, + zoom: { + + pan: { + enabled: settings.zoomStatus ? false : true, + mode: "xy" + }, + zoom: { + wheel: { + enabled: settings.zoomStatus ? false : true, + }, + pinch: { + enabled: settings.zoomStatus ? false : true, + }, + mode: "xy" + } + }, + title: { + display: settings.titleStatus, + font: { + family: fontFamily, + size: 13, + weight: 600 + } + }, + tooltip: { + enabled: true, + padding: 10, + cornerRadius: 8 + }, + + }, + elements: { + line: { + borderWidth: settings.EborderWidth, + tension: 0.15, + fill: false, + }, + + point: { + radius: settings.pointRadius, + hoverRadius: 4, + hitRadius: 6, + pointStyle: "circle" + } + }, + + scales: { + x: { + type: settings.xScalesType, + + display: true, + + grid: { + display: true + }, + + ticks: { + display: true, + font: { + family: fontFamily, + size: 11 + } + }, + + title: { + display: settings.titleStatus, + text: "" + } + }, + + y: { + type: settings.yScalesType, + display: true, + + grid: { + display: true + }, + + ticks: { + display: true + }, + + title: { + display: settings.titleStatus, + text: "" + }}}}; + + + } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index bd944a1..c0e0803 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,5 +27,5 @@ }, "include": [ "src/**/*.ts" - ] +, "helpers/graphs.ts", "helpers/parser.ts", "helpers/appContext.ts", "helpers/declarations.d.ts" ] }