From 7c420b0b0b8bf66fa42c116add59aa583a686a67 Mon Sep 17 00:00:00 2001 From: Luke Leppan Date: Wed, 7 Jul 2021 14:34:39 +0200 Subject: [PATCH] getting closer --- .vscode/settings.json | 3 + README.md | 8 +- package.json | 2 + presets/default.md | 6 +- src/data/collector.ts | 32 ++++--- src/data/manager.ts | 6 +- src/main.ts | 162 ++++++----------------------------- src/settings/settings-tab.ts | 144 +++++++++++++------------------ src/settings/settings.ts | 55 +++++++++--- src/status/manager.ts | 107 ++++++++++++++++++++++- src/status/parse.ts | 52 +++++++++-- 11 files changed, 320 insertions(+), 257 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..59f21fa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "window.title": "${dirty} ${rootName} 🦄" +} \ No newline at end of file diff --git a/README.md b/README.md index 65efa7d..f9e0aa3 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ All Optional: Works with all languages. -#### Coming soon: +#### TODO: -- Complete Customization -- Customization presets -- More stats +- [ ] add total counts back +- [ ] more statistic display +- [ ] add more statistics (make suggestions) ### Contributors diff --git a/package.json b/package.json index c444c31..71e4efb 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@tsconfig/svelte": "^1.0.13", "@types/moment": "^2.13.0", "@types/node": "^14.17.3", + "@types/parsimmon": "^1.10.6", "obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master", "rollup": "^2.32.1", "rollup-plugin-copy": "^3.3.0", @@ -36,6 +37,7 @@ "typescript": "^4.0.3" }, "dependencies": { + "parsimmon": "^1.18.0", "svelte": "^3.38.3" } } diff --git a/presets/default.md b/presets/default.md index d48a36e..1d04638 100644 --- a/presets/default.md +++ b/presets/default.md @@ -1 +1,5 @@ -%word_count% words %character_count% characters +Query: +{word_count} words {character_count} characters + +Alt Query: +{files} files {total_words} words {total_characters} characters diff --git a/src/data/collector.ts b/src/data/collector.ts index e0b71c2..12d6802 100644 --- a/src/data/collector.ts +++ b/src/data/collector.ts @@ -8,25 +8,33 @@ export class DataCollector { this.vault = vault; } - async getTotalCount(): Promise { - let allWords: number; + getTotalWordCount() { + let allWords: number = 0; + console.log(allWords); + } + + async getTotalCharacterCount() { let allCharacters: number; - let allSentences: number; - let allFiles: number; for (const f of this.vault.getFiles()) { let fileContents = await this.vault.cachedRead(f); - allWords += this.getWordCount(fileContents); allCharacters += this.getCharacterCount(fileContents); + } + + return allCharacters; + } + + async getTotalSentenceCount() { + let allSentences: number; + for (const f of this.vault.getFiles()) { + let fileContents = await this.vault.cachedRead(f); allSentences += this.getSentenceCount(fileContents); } - allFiles = this.vault.getFiles().length; - return { - words: allWords, - characters: allCharacters, - sentences: allSentences, - files: allFiles, - }; + return allSentences; + } + + async getTotalFileCount() { + return this.vault.getFiles().length; } getWordCount(text: string): number { diff --git a/src/data/manager.ts b/src/data/manager.ts index 71d56b9..8967509 100644 --- a/src/data/manager.ts +++ b/src/data/manager.ts @@ -23,7 +23,6 @@ export class DataManager { } this.stats = JSON.parse(await this.vault.adapter.read(".vault-stats")); - console.log(this.stats); this.getTodayIndex(); this.update(); @@ -74,14 +73,13 @@ export class DataManager { } onVaultModify(file: TAbstractFile) { - console.log(this.stats); if (!this.stats.history[this.index].modifiedFiles.includes(file.name)) { - console.log(this.stats); - this.stats.history[this.index].modifiedFiles.push(file.name); this.update(); } } + change(cm: CodeMirror.Editor) {} + setTotalStats() {} } diff --git a/src/main.ts b/src/main.ts index df64924..2485e93 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import { MarkdownView, Plugin, TFile, addIcon, WorkspaceLeaf } from "obsidian"; import { BetterWordCountSettingsTab } from "./settings/settings-tab"; -import { BetterWordCountSettings } from "./settings/settings"; +import { BetterWordCountSettings, DEFAULT_SETTINGS } from "./settings/settings"; import { getWordCount, getCharacterCount, @@ -11,14 +11,17 @@ import { StatusBar } from "./status/bar"; import { STATS_ICON, STATS_ICON_NAME, VIEW_TYPE_STATS } from "./constants"; import StatsView from "./view/view"; import { DataManager } from "./data/manager"; +import { BarManager } from "./status/manager"; +import type CodeMirror from "codemirror"; +import { parse } from "./status/parse"; export default class BetterWordCount extends Plugin { - public recentlyTyped: boolean; public statusBar: StatusBar; public currentFile: TFile; public settings: BetterWordCountSettings; public view: StatsView; public dataManager: DataManager; + public barManager: BarManager; onunload(): void { this.app.workspace @@ -27,26 +30,22 @@ export default class BetterWordCount extends Plugin { } async onload() { - this.dataManager = new DataManager(this.app.vault); + this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()); + this.saveData(this.settings); + this.addSettingTab(new BetterWordCountSettingsTab(this.app, this)); let statusBarEl = this.addStatusBarItem(); this.statusBar = new StatusBar(statusBarEl); - this.recentlyTyped = false; - - this.settings = (await this.loadData()) || new BetterWordCountSettings(); - this.addSettingTab(new BetterWordCountSettingsTab(this.app, this)); - - addIcon(STATS_ICON_NAME, STATS_ICON); - - this.updateAltCount(); - - this.registerEvent( - this.app.workspace.on("file-open", this.onFileOpen, this) + this.dataManager = new DataManager(this.app.vault); + this.barManager = new BarManager( + this.statusBar, + this.settings, + this.app.vault ); this.registerEvent( - this.app.workspace.on("quick-preview", this.onQuickPreview, this) + this.app.workspace.on("active-leaf-change", this.activeLeafChange, this) ); this.registerEvent( @@ -57,9 +56,17 @@ export default class BetterWordCount extends Plugin { ) ); + this.registerCodeMirror((cm: CodeMirror.Editor) => { + cm.on("cursorActivity", (cm: CodeMirror.Editor) => + this.barManager.cursorActivity(cm) + ); + }); + + addIcon(STATS_ICON_NAME, STATS_ICON); + this.addCommand({ id: "show-vault-stats-view", - name: "Open view", + name: "Open Statistics", checkCallback: (checking: boolean) => { if (checking) { return this.app.workspace.getLeavesOfType("vault-stats").length === 0; @@ -73,132 +80,19 @@ export default class BetterWordCount extends Plugin { (leaf: WorkspaceLeaf) => (this.view = new StatsView(leaf)) ); - this.registerInterval( - window.setInterval(async () => { - let activeLeaf = this.app.workspace.activeLeaf; - - if (!activeLeaf || !(activeLeaf.view instanceof MarkdownView)) { - return; - } - - let editor = activeLeaf.view.sourceMode.cmEditor; - if (editor.somethingSelected()) { - let content: string = editor.getSelection(); - this.updateWordCount(content); - this.recentlyTyped = false; - } else if ( - this.currentFile && - this.currentFile.extension === "md" && - !this.recentlyTyped - ) { - const contents = await this.app.vault.cachedRead(this.currentFile); - this.updateWordCount(contents); - } else if (!this.recentlyTyped) { - this.updateWordCount(""); - } - }, 500) - ); - - let activeLeaf = this.app.workspace.activeLeaf; - let files: TFile[] = this.app.vault.getMarkdownFiles(); - - files.forEach((file) => { - if (file.basename === activeLeaf.getDisplayText()) { - this.onFileOpen(file); - } - }); - if (this.app.workspace.layoutReady) { this.initLeaf(); } } - async onFileOpen(file: TFile) { - this.currentFile = file; - if (file && file.extension === "md") { - const contents = await this.app.vault.cachedRead(file); - this.recentlyTyped = true; - this.updateWordCount(contents); - } else { - this.updateAltCount(); + activeLeafChange(leaf: WorkspaceLeaf) { + if (!(leaf.view.getViewType() === "markdown")) { + this.barManager.updateAltStatusBar(); } } - onQuickPreview(file: TFile, contents: string) { - this.currentFile = file; - const leaf = this.app.workspace.activeLeaf; - - if (leaf && leaf.view.getViewType() === "markdown") { - this.recentlyTyped = true; - this.updateWordCount(contents); - } - } - - async updateAltCount() { - const files = getFilesCount(this.app.vault.getFiles()); - - let displayText: string = `${files} files `; - let allWords = 0; - let allCharacters = 0; - let allSentences = 0; - - for (const f of this.app.vault.getMarkdownFiles()) { - let fileContents = await this.app.vault.cachedRead(f); - allWords += getWordCount(fileContents); - allCharacters += getCharacterCount(fileContents); - allSentences += getSentenceCount(fileContents); - } - - if (this.settings.showWords) { - displayText = - displayText + - this.settings.wordsPrefix + - allWords + - this.settings.wordsSuffix; - } - if (this.settings.showCharacters) { - displayText = - displayText + - this.settings.charactersPrefix + - allCharacters + - this.settings.charactersSuffix; - } - if (this.settings.showSentences) { - displayText = - displayText + - this.settings.sentencesPrefix + - allSentences + - this.settings.sentencesSuffix; - } - - this.statusBar.displayText(displayText); - } - - updateWordCount(text: string) { - let displayText: string = ""; - if (this.settings.showWords) { - displayText = - displayText + - this.settings.wordsPrefix + - getWordCount(text) + - this.settings.wordsSuffix; - } - if (this.settings.showCharacters) { - displayText = - displayText + - this.settings.charactersPrefix + - getCharacterCount(text) + - this.settings.charactersSuffix; - } - if (this.settings.showSentences) { - displayText = - displayText + - this.settings.sentencesPrefix + - getSentenceCount(text) + - this.settings.sentencesSuffix; - } - - this.statusBar.displayText(displayText); + async saveSettings(): Promise { + await this.saveData(this.settings); } initLeaf(): void { diff --git a/src/settings/settings-tab.ts b/src/settings/settings-tab.ts index 271f2bf..32eff48 100644 --- a/src/settings/settings-tab.ts +++ b/src/settings/settings-tab.ts @@ -1,106 +1,78 @@ -import { App, PluginSettingTab, Setting } from "obsidian"; +import { + App, + DropdownComponent, + PluginSettingTab, + Setting, + TextAreaComponent, + ToggleComponent, +} from "obsidian"; import type BetterWordCount from "src/main"; +import { PRESETS, PresetOption } from "../settings/settings"; export class BetterWordCountSettingsTab extends PluginSettingTab { - constructor(app: App, plugin: BetterWordCount) { + private disableTextAreas: boolean; + + constructor(app: App, private plugin: BetterWordCount) { super(app, plugin); + this.disableTextAreas = + this.plugin.settings.preset.name === "custom" ? false : true; } display(): void { let { containerEl } = this; - const plugin: BetterWordCount = (this as any).plugin; containerEl.empty(); containerEl.createEl("h2", { text: "Better Word Count Settings" }); - // Word Count Settings - containerEl.createEl("h3", { text: "Word Count Settings" }); + // General Settings + containerEl.createEl("h3", { text: "General Settings" }); new Setting(containerEl) - .setName("Show Word Count") - .setDesc("Enable this to show the word count.") - .addToggle((boolean) => - boolean.setValue(plugin.settings.showWords).onChange((value) => { - plugin.settings.showWords = value; - plugin.saveData(plugin.settings); - }) - ); - new Setting(containerEl) - .setName("Word Count Prefix") - .setDesc("This changes the text in front of the word count number.") - .addText((text) => - text.setValue(plugin.settings.wordsPrefix).onChange((value) => { - plugin.settings.wordsPrefix = value; - plugin.saveData(plugin.settings); - }) - ); - new Setting(containerEl) - .setName("Word Count Suffix") - .setDesc("This changes the text after of the word count number.") - .addText((text) => - text.setValue(plugin.settings.wordsSuffix).onChange((value) => { - plugin.settings.wordsSuffix = value; - plugin.saveData(plugin.settings); - }) - ); + .setName("Collect Statistics") + .setDesc( + "Turn on to start collecting daily statistics of your writing. Stored in the .vault-stats file in the root of your vault." + ) + .addToggle((cb: ToggleComponent) => { + cb.setValue(this.plugin.settings.collectStats); + cb.onChange(async (value: boolean) => { + this.plugin.settings.collectStats = value; + await this.plugin.saveSettings(); + }); + }); - // Character Count Settings - containerEl.createEl("h3", { text: "Character Count Settings" }); + // Status Bar Settings + containerEl.createEl("h3", { text: "Status Bar Settings" }); new Setting(containerEl) - .setName("Show Character Count") - .setDesc("Enable this to show the character count.") - .addToggle((boolean) => - boolean.setValue(plugin.settings.showCharacters).onChange((value) => { - plugin.settings.showCharacters = value; - plugin.saveData(plugin.settings); - }) - ); - new Setting(containerEl) - .setName("Character Count Prefix") - .setDesc("This changes the text in front of the character count number.") - .addText((text) => - text.setValue(plugin.settings.charactersPrefix).onChange((value) => { - plugin.settings.charactersPrefix = value; - plugin.saveData(plugin.settings); - }) - ); - new Setting(containerEl) - .setName("Character Count Suffix") - .setDesc("This changes the text after of the character count number.") - .addText((text) => - text.setValue(plugin.settings.charactersSuffix).onChange((value) => { - plugin.settings.charactersSuffix = value; - plugin.saveData(plugin.settings); - }) - ); + .setName("Select a Preset") + .setDesc( + "Presets are premade status bar expressions. Overides status bar settings." + ) + .addDropdown((cb: DropdownComponent) => { + PRESETS.forEach((preset: PresetOption) => { + cb.addOption(preset.name, preset.name); + }); + cb.setValue(this.plugin.settings.preset.name); - // Sentence Count Settings - containerEl.createEl("h3", { text: "Sentence Count Settings" }); + cb.onChange(async (value: string) => { + let newPreset = PRESETS.find((preset) => preset.name === value); + this.plugin.settings.preset = newPreset; + this.plugin.settings.statusBarQuery = newPreset.statusBarQuery; + this.plugin.settings.statusBarAltQuery = newPreset.statusBarAltQuery; + await this.plugin.saveSettings(); + }); + }); new Setting(containerEl) - .setName("Show Sentence Count") - .setDesc("Enable this to show the sentence count.") - .addToggle((boolean) => - boolean.setValue(plugin.settings.showSentences).onChange((value) => { - plugin.settings.showSentences = value; - plugin.saveData(plugin.settings); - }) - ); + .setName("Status Bar Text") + .setDesc("Customize the Status Bar text with this.") + .addTextArea((cb: TextAreaComponent) => { + cb.setPlaceholder("Enter an expression..."); + cb.setValue(this.plugin.settings.statusBarQuery); + }); new Setting(containerEl) - .setName("Sentence Count Prefix") - .setDesc("This changes the text in front of the sentence count number.") - .addText((text) => - text.setValue(plugin.settings.sentencesPrefix).onChange((value) => { - plugin.settings.sentencesPrefix = value; - plugin.saveData(plugin.settings); - }) - ); - new Setting(containerEl) - .setName("Sentence Count Suffix") - .setDesc("This changes the text after of the sentence count number.") - .addText((text) => - text.setValue(plugin.settings.sentencesSuffix).onChange((value) => { - plugin.settings.sentencesSuffix = value; - plugin.saveData(plugin.settings); - }) - ); + .setName("Alternative Status Bar Text") + .setDesc("Customize the Alternative Status Bar text with this.") + .addTextArea((cb: TextAreaComponent) => { + cb.setPlaceholder("Enter an expression..."); + cb.setValue(this.plugin.settings.statusBarAltQuery); + }); } } diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 4d3ef8d..bb55090 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -1,11 +1,46 @@ -export class BetterWordCountSettings { - showWords: boolean = true; - wordsPrefix: string = ""; - wordsSuffix: string = " words "; - showCharacters: boolean = true; - charactersPrefix: string = ""; - charactersSuffix: string = " characters "; - showSentences: boolean = false; - sentencesPrefix: string = ""; - sentencesSuffix: string = " sentences"; +export const DEFAULT_SETTINGS: BetterWordCountSettings = { + preset: { + name: "default", + statusBarQuery: "{word_count} words {character_count} characters", + statusBarAltQuery: + "{files} files {total_words} words {total_characters} characters", + }, + statusBarQuery: "{word_count} words {character_count} characters", + statusBarAltQuery: + "{files} files {total_words} words {total_characters} characters", + countComments: false, + collectStats: false, +}; + +export const PRESETS: PresetOption[] = [ + { + name: "default", + statusBarQuery: "{word_count} words {character_count} characters", + statusBarAltQuery: + "{files} files {total_words} words {total_characters} characters", + }, + { + name: "minimal", + statusBarQuery: "w: {word_count} c: {character_count}", + statusBarAltQuery: "f: {files} tw: {total_words} tc: {total_characters}", + }, + { + name: "custom", + statusBarQuery: "", + statusBarAltQuery: "", + }, +]; + +export interface BetterWordCountSettings { + preset: PresetOption; + statusBarQuery: string; + statusBarAltQuery: string; + countComments: boolean; + collectStats: boolean; +} + +export interface PresetOption { + name: string; + statusBarQuery: string; + statusBarAltQuery: string; } diff --git a/src/status/manager.ts b/src/status/manager.ts index bad1631..9af517b 100644 --- a/src/status/manager.ts +++ b/src/status/manager.ts @@ -1,7 +1,112 @@ +import type { Vault } from "obsidian"; +import { DataCollector } from "src/data/collector"; +import type { BetterWordCountSettings } from "src/settings/settings"; +import { + getWordCount, + getCharacterCount, + getSentenceCount, +} from "../data/stats"; import type { StatusBar } from "./bar"; +import { Expression, parse } from "./parse"; export class BarManager { private statusBar: StatusBar; + private settings: BetterWordCountSettings; + private dataCollector: DataCollector; - constructor(statusBar: StatusBar) {} + constructor( + statusBar: StatusBar, + settings: BetterWordCountSettings, + vault: Vault + ) { + this.statusBar = statusBar; + this.settings = settings; + this.dataCollector = new DataCollector(vault); + } + + async updateStatusBar(text: string): Promise { + let newText = ""; + const expression: Expression = parse(this.settings.statusBarQuery); + + let varsIndex = 0; + expression.parsed.forEach((value: string) => { + newText = newText + value; + switch (expression.vars[varsIndex]) { + case 0: + newText = newText + getWordCount(text); + break; + case 1: + newText = newText + getCharacterCount(text); + break; + case 2: + newText = newText + getSentenceCount(text); + break; + case 3: + newText = newText + this.dataCollector.getTotalWordCount(); + break; + case 4: + newText = newText + this.dataCollector.getTotalCharacterCount(); + break; + case 5: + newText = newText + this.dataCollector.getTotalSentenceCount(); + break; + case 6: + newText = newText + this.dataCollector.getTotalFileCount(); + break; + } + varsIndex++; + }); + + this.statusBar.displayText(newText); + } + + async updateAltStatusBar(): Promise { + let newText = ""; + const expression: Expression = parse(this.settings.statusBarAltQuery); + + let varsIndex = 0; + expression.parsed.forEach(async (value: string) => { + newText = newText + value; + switch (expression.vars[varsIndex]) { + case 0: + newText = newText + getWordCount(""); + break; + case 1: + newText = newText + getCharacterCount(""); + break; + case 2: + newText = newText + getSentenceCount(""); + break; + case 3: + newText = newText + (await this.dataCollector.getTotalWordCount()); + break; + case 4: + newText = + newText + (await this.dataCollector.getTotalCharacterCount()); + break; + case 5: + newText = + newText + (await this.dataCollector.getTotalSentenceCount()); + break; + case 6: + newText = newText + (await this.dataCollector.getTotalFileCount()); + break; + } + varsIndex++; + }); + + this.statusBar.displayText(newText); + } + + cursorActivity(cm: CodeMirror.Editor) { + if (cm.somethingSelected()) { + this.updateStatusBar(cm.getSelection()); + } else { + this.updateStatusBar(cm.getValue()); + } + } + + // change(cm: CodeMirror.Editor, changeObj: CodeMirror.EditorChangeLinkedList) { + // this.updateStatusBar(cm.getValue()); + // } } diff --git a/src/status/parse.ts b/src/status/parse.ts index e18114d..0c83e82 100644 --- a/src/status/parse.ts +++ b/src/status/parse.ts @@ -1,14 +1,56 @@ +const REGEX: RegExp = /{(.*?)}/g; export interface Expression { - parsedText: string; + parsed: string[]; vars: number[]; } -export function parse(text: string): Expression { - let parsedText: string; - let vars: number[]; +// Could be done smarter but I'm tired +export function parse(query: string): Expression { + let parsed: string[] = []; + let vars: number[] = []; + + query.split(REGEX).forEach((s) => { + switch (s) { + case "word_count": + vars.push(0); + break; + case "character_count": + vars.push(1); + break; + case "sentence_count": + vars.push(2); + break; + case "total_word_count": + vars.push(3); + break; + case "total_character_count": + vars.push(4); + break; + case "total_sentence_count": + vars.push(5); + break; + case "file_count": + vars.push(6); + break; + + default: + parsed.push(s); + break; + } + }); return { - parsedText: parsedText, + parsed: parsed, vars: vars, }; } + +const varNames = { + word_count: 0, + charater_count: 1, + sentence_count: 2, + total_word_count: 3, + total_charater_count: 4, + total_sentence_count: 5, + file_count: 6, +};