From c852a68ecbe7a82ec6f81f4ff121f20a358862dd Mon Sep 17 00:00:00 2001 From: Adam Fletcher Date: Mon, 14 Apr 2025 10:28:48 +0100 Subject: [PATCH] Refactor to support partial highlighting of matches --- main.ts | 160 ++++++++++++----------------------- rulesets.ts | 240 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 208 insertions(+), 192 deletions(-) diff --git a/main.ts b/main.ts index 3ac2210..4a26466 100644 --- a/main.ts +++ b/main.ts @@ -10,7 +10,7 @@ import { import { RangeSetBuilder } from '@codemirror/state'; import { syntaxTree } from '@codemirror/language'; -import { Rulesets, RULESETS } from './rulesets'; +import { Rules, Rule, RuleType } from './rulesets'; interface CutTheFluffPluginSettings { @@ -41,12 +41,17 @@ const DEFAULT_SETTINGS: CutTheFluffPluginSettings = { export default class CutTheFluffPlugin extends Plugin { settings: CutTheFluffPluginSettings; forceViewUpdate: boolean; - regex: RegExp; + regex: RegExp | null; + rules: Rules; async onload() { + + this.rules = new Rules(); + await this.loadSettings(); this.buildRegex(); + this.addCommand({ id: 'toggle', name: 'Toggle highlighting', @@ -66,78 +71,38 @@ export default class CutTheFluffPlugin extends Plugin { } buildRegex() { - /*const words = [ - 'basically', - 'just', - 'basically', - 'actually', - 'actual', - 'literally', - 'really', - 'very', - 'quite', - 'honestly', - 'simply', - 'like', - 'combine together' - ];*/ - const customRules: string[] = []; + + const enabledRuleTypes: RuleType[] = [ + ...(this.settings.enableRulesetWeakQualifiers ? [RuleType.WeakQualifier] : []), + ...(this.settings.enableRulesetJargon ? [RuleType.Jargon] : []), + ...(this.settings.enableRulesetComplexity ? [RuleType.Complexity] : []), + ...(this.settings.enableRulesetRedudancies ? [RuleType.Redundancy] : []), + ]; + + this.rules.resetCustomRules(); const customExceptions: string[] = []; - - - this.settings.customWordList.trim().split(/\r?\n/).forEach(str => { - if (str.startsWith('-')) { - customExceptions.push(str.substring(1)); - } else { - customRules.push(str); - } - }); - - - //const filteredArray = stringArray.filter(str => str.startsWith('-')); - - let words: string[] = []; - - const rulesetSettingMap: Record = { - "enableRulesetWeakQualifiers": 'weakQualifiers', - "enableRulesetJargon": 'jargon', - "enableRulesetComplexity": 'complexity', - "enableRulesetRedudancies": 'redundancies' - } - - - - - - for (let key in rulesetSettingMap) { - let rulesetToggleSettingKey = key as keyof CutTheFluffPluginSettings; - if (typeof this.settings[rulesetToggleSettingKey] === 'boolean') { - if (this.settings[rulesetToggleSettingKey]) { - let typedKey = rulesetSettingMap[key] as keyof Rulesets; - - words = words.concat(Object.keys(RULESETS[typedKey]).filter(value => { - return !customExceptions.includes(value); - })); + if(this.settings.customWordList.trim().length > 0) { + this.settings.customWordList.trim().split(/\r?\n/).forEach(str => { + if (str.startsWith('-')) { + customExceptions.push(str.substring(1)); + } else { + this.rules.addCustomRule(str); } - } + }); } - - if(this.settings.customWordList.length > 0) { - words = words.concat(customRules.map( - line => line.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - )); + let words: string[] = this.rules.getMatchStrings(enabledRuleTypes, customExceptions); + + if(words.length > 0) { + const pattern = `\\b(?:${words.join("|")})\\b` + this.regex = new RegExp(pattern, 'gi'); + } else { + this.regex = null; } - - - //const words = this.settings.wordlist.trim().split(/\r?\n/).map(line => line.trim()); - - const pattern = `\\b(?:${words.join("|")})\\b` - this.regex = new RegExp(pattern, 'gi'); } async loadSettings() { @@ -148,7 +113,7 @@ export default class CutTheFluffPlugin extends Plugin { await this.saveData(this.settings); this.buildRegex(); this.forceViewUpdate = true; - document.body.classList.toggle('sentence-length-highlighting-active', this.settings.enabled); + document.body.classList.toggle('cut-the-fluff-active', this.settings.enabled); this.app.workspace.updateOptions(); } @@ -165,12 +130,13 @@ export default class CutTheFluffPlugin extends Plugin { update(update: ViewUpdate) { if (update.docChanged || update.viewportChanged || plugin.forceViewUpdate) { this.decorations = this.buildDecorations(update.view); + plugin.forceViewUpdate = false; } } buildDecorations(view: EditorView): DecorationSet { - if (!plugin.settings.enabled) { + if (!plugin.settings.enabled || plugin.regex == null) { return Decoration.none; } @@ -210,17 +176,15 @@ export default class CutTheFluffPlugin extends Plugin { }); } - //const words = ["just", "sample", "is"]; - - //const sentenceRegex = /\bbasically\b|\btext\b/gi + let match; for (const { from, to } of view.visibleRanges) { - // IMPORTANT: Reset lastIndex for global regexes when searching new strings/slices - plugin.regex.lastIndex = 0; + + plugin.regex.lastIndex = 0; // Important const doc = view.state.doc @@ -228,13 +192,17 @@ export default class CutTheFluffPlugin extends Plugin { while ((match = plugin.regex.exec(visibleText)) !== null) { - //console.log(text.length); + const matchingRule: Rule = plugin.rules.getRuleByMatchString(match[0]); let start: number; + start = match.index + from + matchingRule.highlightOffset; - start = match.index + from; - - const end = start + match[0].length; + let end: number; + if (matchingRule.highlightLength != null) { + end = start + matchingRule.highlightLength - matchingRule.highlightOffset + } else { + end = start + match[0].length - matchingRule.highlightOffset; + } if (skipRanges.some(range => start >= range.min && start <= range.max)) { continue; @@ -243,11 +211,11 @@ export default class CutTheFluffPlugin extends Plugin { - if(plugin.settings.enableTooltips) { + if (plugin.settings.enableTooltips) { builder.add(start, end, Decoration.mark({ class: `fluff${formattingClass}`, attributes: { - 'aria-label': "Much longer \nworld", + 'aria-label': matchingRule.match, 'data-tooltip-position': "bottom" } })); @@ -258,36 +226,13 @@ export default class CutTheFluffPlugin extends Plugin { } - + } } - /* - while ((match = sentenceRegex.exec(text)) !== null) { - - let start: number; - - start = match.index; - - const end = start + match[0].length; - - if (skipRanges.some(range => start >= range.min && start <= range.max)) { - continue; - } - - - - - - - - builder.add(start, end, Decoration.mark({ - class: 'fluff', - })); - } - */ + return builder.finish(); } @@ -341,12 +286,11 @@ class CutTheFluggSettingsTab extends PluginSettingTab { dropdown.onChange(async (newValue) => { this.plugin.settings.highlightStyle = newValue; - // Save the updated settings await this.plugin.saveSettings(); }); }); - - /* + + new Setting(containerEl) .setName('Enable tooltips') @@ -356,7 +300,7 @@ class CutTheFluggSettingsTab extends PluginSettingTab { this.plugin.settings.enableTooltips = value; await this.plugin.saveSettings(); })); - */ + new Setting(containerEl).setName('Built-in rulesets').setHeading(); @@ -391,7 +335,7 @@ class CutTheFluggSettingsTab extends PluginSettingTab { })); new Setting(containerEl) - .setName('Redudancies') + .setName('Redundancies') .setDesc('eg. combine together, basic fundamentals, critically important, final conclusion') .addToggle(toggle => toggle .setValue(this.plugin.settings.enableRulesetRedudancies) // Set the initial state of the toggle from loaded settings diff --git a/rulesets.ts b/rulesets.ts index f3f7d8b..1c6dd10 100644 --- a/rulesets.ts +++ b/rulesets.ts @@ -5,88 +5,160 @@ export interface Rulesets { redundancies: Record; } -export const RULESETS: Rulesets = { - weakQualifiers:{ - "a bit": "", - "a little": "", - "actual": "", - "actually": "", - "basically": "", - "just": "", - "kind of": "", - "like": "", - "literally": "", - "quite": "", - "really": "", - "simply": "", - "sort of": "", - "very": "", - "somewhat": "", - }, - jargon: { - "at the end of the day": "", - "boil the ocean": "", - "double-edged sword": "", - "going forward": "", - "I beg to differ": "", - "ideate": "", - "move the needle": "", - "moving forward": "", - "paradigm": "", - "paradigm shift": "", - "rightsizing": "", - "singing from the same hymn sheet": "", - "touch base": "", - "unpack": "", - }, - complexity: { - "as of yet": "", - "at the present time": "", - "close proximity": "", - "commence": "", - "deem": "", - "dialogue": "", - "due to the fact that": "", - "endeavor": "", - "endeavour": "", - "enumerate": "", - "equitable": "", - "erroneous": "", - "facilitate": "", - "For all intents and purposes": "", - "henceforth": "", - "herewith": "", - "honestly": "", - "in a sense": "", - "in lieu of": "", - "in my humble opinion": "", - "in order to": "", - "in regard to": "", - "in relation to": "", - "in the event of": "", - "inception": "", - "leverage": "", - "numerous": "", - "per se": "", - "pertaining to": "", - "prior to": "", - "subsequently": "", - "utilise": "", - "utilize": "", - "of the opinion that": "", - "with reference to": "", - "with the exception of": "", - "i might add": "", - "it is interesting to note": "", - }, - redundancies: { - "combine together": "", - "successfully complete": "", - "basic fundamentals": "", - "each and every": "", - "in the event that": "", - "one and the same": "", - "critically important": "", - "final conclusion": "", +export const enum RuleType { + WeakQualifier = "WeakQualifier", + Jargon = "Jaron", + Complexity = "Complexity", + Redundancy = "Redudancy", + Custom = "Custom" +} + +export class Rule { + match: string; + //alternatives: string[]; + highlightLength: number | null; + highlightOffset: number; + type: RuleType; + + constructor(type: RuleType, match: string, highLightOffset: number = 0, highlightLength: number | null = null) { + this.type = type; + this.match = match.toLowerCase(); + this.highlightOffset = highLightOffset; + this.highlightLength = highlightLength; } -}; \ No newline at end of file +} + +export class Rules { + rules: Record = {}; + customRules: Record = {}; + + constructor() { + + this.addRule(new Rule(RuleType.WeakQualifier, "a bit")); + this.addRule(new Rule(RuleType.WeakQualifier, "a little")); + this.addRule(new Rule(RuleType.WeakQualifier, "actual")); + this.addRule(new Rule(RuleType.WeakQualifier, "actually")); + this.addRule(new Rule(RuleType.WeakQualifier, "basically")); + this.addRule(new Rule(RuleType.WeakQualifier, "just")); + this.addRule(new Rule(RuleType.WeakQualifier, "kind of")); + this.addRule(new Rule(RuleType.WeakQualifier, "like")); + this.addRule(new Rule(RuleType.WeakQualifier, "literally")); + this.addRule(new Rule(RuleType.WeakQualifier, "quite")); + this.addRule(new Rule(RuleType.WeakQualifier, "really")); + this.addRule(new Rule(RuleType.WeakQualifier, "simply")); + this.addRule(new Rule(RuleType.WeakQualifier, "sort of")); + this.addRule(new Rule(RuleType.WeakQualifier, "very")); + this.addRule(new Rule(RuleType.WeakQualifier, "somewhat")); + + this.addRule(new Rule(RuleType.Jargon, "at the end of the day")); + this.addRule(new Rule(RuleType.Jargon, "boil the ocean")); + this.addRule(new Rule(RuleType.Jargon, "double-edged sword")); + this.addRule(new Rule(RuleType.Jargon, "going forward")); + this.addRule(new Rule(RuleType.Jargon, "i beg to differ")); + this.addRule(new Rule(RuleType.Jargon, "ideate")); + this.addRule(new Rule(RuleType.Jargon, "move the needle")); + this.addRule(new Rule(RuleType.Jargon, "moving forward")); + this.addRule(new Rule(RuleType.Jargon, "paradigm")); + this.addRule(new Rule(RuleType.Jargon, "paradigm shift")); + this.addRule(new Rule(RuleType.Jargon, "rightsizing")); + this.addRule(new Rule(RuleType.Jargon, "singing from the same hymn sheet")); + this.addRule(new Rule(RuleType.Jargon, "touch base")); + this.addRule(new Rule(RuleType.Jargon, "unpack")); + this.addRule(new Rule(RuleType.Jargon, "north star")); + + + + this.addRule(new Rule(RuleType.Complexity, "as of yet")); + this.addRule(new Rule(RuleType.Complexity, "at the present time")); + this.addRule(new Rule(RuleType.Complexity, "close proximity")); + this.addRule(new Rule(RuleType.Complexity, "commence")); + this.addRule(new Rule(RuleType.Complexity, "deem")); + this.addRule(new Rule(RuleType.Complexity, "dialogue")); + this.addRule(new Rule(RuleType.Complexity, "due to the fact that")); + this.addRule(new Rule(RuleType.Complexity, "endeavor")); + this.addRule(new Rule(RuleType.Complexity, "endeavour")); + this.addRule(new Rule(RuleType.Complexity, "enumerate")); + this.addRule(new Rule(RuleType.Complexity, "equitable")); + this.addRule(new Rule(RuleType.Complexity, "erroneous")); + this.addRule(new Rule(RuleType.Complexity, "facilitate")); + this.addRule(new Rule(RuleType.Complexity, "For all intents and purposes")); + this.addRule(new Rule(RuleType.Complexity, "henceforth")); + this.addRule(new Rule(RuleType.Complexity, "herewith")); + this.addRule(new Rule(RuleType.Complexity, "honestly")); + this.addRule(new Rule(RuleType.Complexity, "in a sense")); + this.addRule(new Rule(RuleType.Complexity, "in lieu of")); + this.addRule(new Rule(RuleType.Complexity, "in my humble opinion")); + this.addRule(new Rule(RuleType.Complexity, "in order to")); + this.addRule(new Rule(RuleType.Complexity, "in regard to")); + this.addRule(new Rule(RuleType.Complexity, "in relation to")); + this.addRule(new Rule(RuleType.Complexity, "in the event of")); + this.addRule(new Rule(RuleType.Complexity, "inception")); + this.addRule(new Rule(RuleType.Complexity, "leverage")); + this.addRule(new Rule(RuleType.Complexity, "numerous")); + this.addRule(new Rule(RuleType.Complexity, "per se")); + this.addRule(new Rule(RuleType.Complexity, "pertaining to")); + this.addRule(new Rule(RuleType.Complexity, "prior to")); + this.addRule(new Rule(RuleType.Complexity, "subsequently")); + this.addRule(new Rule(RuleType.Complexity, "utilise")); + this.addRule(new Rule(RuleType.Complexity, "utilize")); + this.addRule(new Rule(RuleType.Complexity, "of the opinion that")); + this.addRule(new Rule(RuleType.Complexity, "with reference to")); + this.addRule(new Rule(RuleType.Complexity, "with the exception of")); + this.addRule(new Rule(RuleType.Complexity, "i might add")); + this.addRule(new Rule(RuleType.Complexity, "it is interesting to note")); + + this.addRule(new Rule(RuleType.Redundancy, "combine together", 7)); + this.addRule(new Rule(RuleType.Redundancy, "critically important", 8)); + this.addRule(new Rule(RuleType.Redundancy, "each and every", 0, 9)); + + + this.addRule(new Rule(RuleType.Redundancy, "successfully complete", 0, 13)); + this.addRule(new Rule(RuleType.Redundancy, "basic fundamentals")); + this.addRule(new Rule(RuleType.Redundancy, "in the event that")); + this.addRule(new Rule(RuleType.Redundancy, "one and the same", 0, 8)); + this.addRule(new Rule(RuleType.Redundancy, "final conclusion", 0, 6)); + + } + + private addRule(rule: Rule) : void { + this.rules[rule.match] = rule; + } + + public resetCustomRules() : void { + this.customRules = {}; + } + + public addCustomRule(match: string) : void { + this.customRules[match] = new Rule(RuleType.Custom, match); + } + + public getRuleByMatchString(match: string): Rule { + + match = match.toLowerCase(); + + if(match in this.rules) { + return this.rules[match]; + } else { + return this.customRules[match]; + } + + } + + public getMatchStrings(types: RuleType[], exclusions: string[]): string[] { + let toReturn: string[] = []; + + for (let key in this.rules) { + if (types.includes(this.rules[key].type) && !exclusions.includes(key)) { + toReturn.push(key); + } + } + + for (let key in this.customRules) { + toReturn.push(key); + } + + // Sort by length so more precise matches take priority + return toReturn.sort((a, b) => b.length - a.length);; + } + +} \ No newline at end of file