Refactor to support partial highlighting of matches

This commit is contained in:
Adam Fletcher 2025-04-14 10:28:48 +01:00
parent d284b0aa0d
commit c852a68ecb
2 changed files with 208 additions and 192 deletions

160
main.ts
View file

@ -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<string, string> = {
"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

View file

@ -5,88 +5,160 @@ export interface Rulesets {
redundancies: Record<string, string>;
}
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;
}
};
}
export class Rules {
rules: Record<string, Rule> = {};
customRules: Record<string, Rule> = {};
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);;
}
}