Skip to content

Commit

Permalink
Merge pull request #1132 from pjkaufman/custom-auto-corrections
Browse files Browse the repository at this point in the history
Add Custom Auto-Correct Options
  • Loading branch information
pjkaufman authored Aug 11, 2024
2 parents 616a83b + 358b8bb commit 229a6aa
Show file tree
Hide file tree
Showing 17 changed files with 492 additions and 11 deletions.
3 changes: 3 additions & 0 deletions __mocks__/obsidian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
import moment from 'moment';

export {moment as moment};

// Needed to make sure that auto-correct tests work due to the auto-correct option using a modal in some scenarios
export class Modal {}
15 changes: 15 additions & 0 deletions __tests__/auto-correct-common-misspellings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,20 @@ ruleTest({
être
`,
},
{
testName: 'Custom replacements should work on file content',
before: dedent`
The cartt is over theree.
`,
after: dedent`
The cart is over there.
`,
options: {
extraAutoCorrectFiles: [{
filePath: 'file_path',
customReplacements: new Map<string, string>([['cartt', 'cart'], ['theree', 'there']]),
}],
},
},
],
});
77 changes: 77 additions & 0 deletions __tests__/parse-custom-replacements.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {parseCustomReplacements} from '../src/utils/strings';
import dedent from 'ts-dedent';

type parseCustomReplacementsTestCase = {
name: string,
text: string,
expectedCustomReplacements: Map<string, string>
};

const getTablesInTextTestCases: parseCustomReplacementsTestCase[] = [
{
name: 'parsing a file with a single table with two columns gets the correct amount of parsed out entries',
text: dedent`
Here is some text
| column1 | column2 |
| ------- | ------- |
| replaceme | withme |
| replace | with |
`,
expectedCustomReplacements: new Map<string, string>([['replaceme', 'withme'], ['replace', 'with']]),
},
{
name: 'parsing a file with a single table with three columns gets no entries parsed out',
text: dedent`
Here is some text
| column1 | column2 | column3 |
| ------- | ------- | ---- |
| replaceme | withme | me |
| replace | with | me2 |
`,
expectedCustomReplacements: new Map<string, string>(),
},
{
name: 'parsing a file with two tables with two columns gets the correct amount of parsed out entries',
text: dedent`
Here is some text
| column1 | column2 |
| ------- | ------- |
| replaceme | withme |
| replace | with |
The following table also has some replacements
| header1 | header2 |
| ------- | ------- |
| var | variablE |
| hack | hacker |
`,
expectedCustomReplacements: new Map<string, string>([['replaceme', 'withme'], ['replace', 'with'], ['var', 'variablE'], ['hack', 'hacker']]),
},
{
name: 'parsing a file with no tables gets no parsed out entries',
text: dedent`
Here is some text without any tables in it.
`,
expectedCustomReplacements: new Map<string, string>(),
},
{
name: 'parsing a file with a single table with two columns gets no entries parsed out when each line is missing a pipe (i.e. |)',
text: dedent`
Here is some text
| column1 | column2 |
| ------- | ------- |
| replaceme | withme
replace | with |
`,
expectedCustomReplacements: new Map<string, string>(),
},
];

describe('Get All Tables in Text', () => {
for (const testCase of getTablesInTextTestCases) {
it(testCase.name, () => {
const customReplacements = parseCustomReplacements(testCase.text);

expect(customReplacements).toEqual(testCase.expectedCustomReplacements);
});
}
});
32 changes: 32 additions & 0 deletions docs/additional-info/rules/auto-correct-common-misspellings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#### How to Use Custom Misspellings

There is a default list of common misspellings that is used as the base for how this rule works.
However, there may be instances where the user may want to add their own list of misspellings to handle.
In those scenarios, they can add files to the list of files that have custom misspellings in them.

##### Format

A file that has custom misspellings in them can have any content in them. But the only content that will
be parsed as custom misspellings should be found in a two column table. For example the following table
will result in `th` being replaced with `the` and `tht` being replaced with `that`:

``` markdown
The following is a table with custom misspellings:
| Replace | With |
| ------- | ---- |
| th | the |
| tht | that |
```

!!! Note
The first two lines of the table are skipped (the header and separator) and all rows after that
must start and end with a pipe (`|`). If any do not start or end with a pipe or they have more
than 2 columns, then they will be skipped.

##### Current Limitations


- The list of custom replacements is only loaded when the plugin initially loads or when the file is added to the list of files that include custom misspellings
* This means that making a change to a file that is already in the list of custom misspelling files will not work unless the Linter is reloaded or the file is removed and re-added to the list of custom misspelling files
- There is no way to specify that a word is to always be capitalized
- This is due to how the auto-correct rule was designed as it sets the first letter of the replacement word to the case of the first letter of the word being replaced
36 changes: 36 additions & 0 deletions docs/docs/settings/content-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,43 @@ Uses a dictionary of common misspellings to automatically convert them to their
| Name | Description | List Items | Default Value |
| ---- | ----------- | ---------- | ------------- |
| `Ignore Words` | A comma separated list of lowercased words to ignore when auto-correcting | N/A | |
| `Extra Auto-Correct Source Files` | These are files that have a markdown table in them that have the initial word and the word to correct it to (these are case insensitive corrections). **Note: the tables used should have the starting and ending `|` indicators present for each line.** | N/A | |

### Additional Info


#### How to Use Custom Misspellings

There is a default list of common misspellings that is used as the base for how this rule works.
However, there may be instances where the user may want to add their own list of misspellings to handle.
In those scenarios, they can add files to the list of files that have custom misspellings in them.

##### Format

A file that has custom misspellings in them can have any content in them. But the only content that will
be parsed as custom misspellings should be found in a two column table. For example the following table
will result in `th` being replaced with `the` and `tht` being replaced with `that`:

``` markdown
The following is a table with custom misspellings:
| Replace | With |
| ------- | ---- |
| th | the |
| tht | that |
```

!!! Note
The first two lines of the table are skipped (the header and separator) and all rows after that
must start and end with a pipe (`|`). If any do not start or end with a pipe or they have more
than 2 columns, then they will be skipped.

##### Current Limitations


- The list of custom replacements is only loaded when the plugin initially loads or when the file is added to the list of files that include custom misspellings
* This means that making a change to a file that is already in the list of custom misspelling files will not work unless the Linter is reloaded or the file is removed and re-added to the list of custom misspelling files
- There is no way to specify that a word is to always be capitalized
- This is due to how the auto-correct rule was designed as it sets the first letter of the replacement word to the case of the first letter of the word being replaced


### Examples
Expand Down
10 changes: 10 additions & 0 deletions esbuild.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ const mockedPlugins = [replace({
'import {Setting} from \'obsidian\';': '',
// remove the use of obsidian in settings helper to allow for docs.js to run
'import {Component, MarkdownRenderer} from \'obsidian\';': '',
// remove the use of obsidian in the auto-correct files picker to allow for docs.js to run
'import {Setting, Component, App, TFile, normalizePath, ExtraButtonComponent} from \'obsidian\';': '',
// remove the use of obsidian in add custom row to allow for docs.js to run
'import {Component, Setting} from \'obsidian\';': '',
// remove the use of obsidian in suggest to allow for docs.js to run
'import {App, ISuggestOwner, Scope} from \'obsidian\';': '',
// remove the use of obsidian in md file suggester to allow for docs.js to run
'import {App, TFile} from \'obsidian\';': '',
// remove the use of obsidian in parse results modal to allow for docs.js to run
'import {Modal, App} from \'obsidian\';': 'class Modal {}',
},
delimiters: ['', ''],
})];
Expand Down
20 changes: 20 additions & 0 deletions src/lang/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,18 @@ export default {
'warning-text': 'Warning',
'file-backup-text': 'Make sure you have backed up your files.',
'custom-command-warning': 'Linting multiple files with custom commands enabled is a slow process that requires the ability to open panes in the side panel. It is noticeably slower than running without custom commands enabled. Please proceed with caution.',
'cancel-button-text': 'Cancel',

'copy-aria-label': 'Copy',

// parse-results-modal.ts
'parse-results-heading-text': 'Custom Parse Values',
'file-parse-description-text': 'The following is the list of custom replacements found in {FILE}.',
'no-parsed-values-found-text': 'There were no custom replacements found in {FILE}. Please make sure that all tables with custom replacements in {FILE} only have two columns and all rows start and end with a pipe (i.e. |).',
'find-header-text': 'Word to Find',
'replace-header-text': 'Replacement Word',
'close-button-text': 'Close',

'tabs': {
'names': {
// tab.ts
Expand Down Expand Up @@ -231,6 +240,13 @@ export default {
'move-down-tooltip': 'Move down',
'delete-tooltip': 'Delete',
},
'custom-auto-correct': {
'delete-tooltip': 'Delete',
'show-parsed-contents-tooltip': 'View parsed replacements',
'custom-row-parse-warning': '"{ROW}" is not a valid row with custom replacements. It must have only 2 columns.',
'file-search-placeholder-text': 'File name',
'add-input-button-text': 'Add another custom file',
},
},

// rules
Expand All @@ -243,6 +259,10 @@ export default {
'name': 'Ignore Words',
'description': 'A comma separated list of lowercased words to ignore when auto-correcting',
},
'extra-auto-correct-files': {
'name': 'Extra Auto-Correct Source Files',
'description': 'These are files that have a markdown table in them that have the initial word and the word to correct it to (these are case insensitive corrections). **Note: the tables used should have the starting and ending `|` indicators present for each line.**',
},
},
// add-blank-line-after-yaml.ts
'add-blank-line-after-yaml': {
Expand Down
24 changes: 22 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {App, Editor, EventRef, MarkdownView, Menu, Notice, Plugin, TAbstractFile, TFile, TFolder, addIcon, htmlToMarkdown, EditorSelection, EditorChange, MarkdownViewModeType, getFrontMatterInfo, FileView} from 'obsidian';
import {App, Editor, EventRef, MarkdownView, Menu, Notice, Plugin, TAbstractFile, TFile, TFolder, addIcon, htmlToMarkdown, EditorSelection, EditorChange, MarkdownViewModeType, getFrontMatterInfo, FileView, normalizePath} from 'obsidian';
import {Options, RuleType, ruleTypeToRules, rules} from './rules';
import DiffMatchPatch from 'diff-match-patch';
import dedent from 'ts-dedent';
import {stripCr} from './utils/strings';
import {parseCustomReplacements, stripCr} from './utils/strings';
import {logInfo, logError, logDebug, setLogLevel, logWarn, setCollectLogs, clearLogs, convertNumberToLogLevel} from './utils/logger';
import {moment} from 'obsidian';
import './rules-registry';
Expand All @@ -17,6 +17,7 @@ import {RuleAliasSuggest} from './cm6/rule-alias-suggester';
import {DEFAULT_SETTINGS, LinterSettings} from './settings-data';
import AsyncLock from 'async-lock';
import {warn} from 'loglevel';
import {CustomAutoCorrectContent} from './ui/linter-components/auto-correct-files-picker-option';

// https://github.com/liamcain/obsidian-calendar-ui/blob/03ceecbf6d88ef260dadf223ee5e483d98d24ffc/src/localization.ts#L20-L43
const langToMomentLocale = {
Expand Down Expand Up @@ -138,6 +139,16 @@ export default class LinterPlugin extends Plugin {
}
}

// load the custom replacements since they are not stored in the data.json when the settings data is saved
for (const replacementFileInfo of this.settings.ruleConfigs['auto-correct-common-misspellings']['extra-auto-correct-files'] as CustomAutoCorrectContent[]) {
if (replacementFileInfo.filePath != '') {
const file = this.getFileFromPath(replacementFileInfo.filePath);
if (file) {
replacementFileInfo.customReplacements = parseCustomReplacements(stripCr(await this.app.vault.cachedRead(file)));
}
}
}

this.updatePasteOverrideStatus();
this.updateHasCustomCommandStatus();
}
Expand Down Expand Up @@ -869,4 +880,13 @@ export default class LinterPlugin extends Plugin {
const lines = doc.split('\n');
return {line: lines.length - 1, ch: lines[lines.length - 1].length};
}

private getFileFromPath(filePath: string): TFile {
const file = this.app.vault.getAbstractFileByPath(normalizePath(filePath));
if (file instanceof TFile) {
return file;
}

return null;
}
}
16 changes: 16 additions & 0 deletions src/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {getTextInLanguage, LanguageStringKey} from './lang/helpers';
import LinterPlugin from './main';
import {parseTextToHTMLWithoutOuterParagraph} from './ui/helpers';
import {LinterSettings} from './settings-data';
import {AutoCorrectFilesPickerOption} from './ui/linter-components/auto-correct-files-picker-option';

export type SearchOptionInfo = {name: string, description: string, options?: DropdownRecord[]}

Expand Down Expand Up @@ -171,3 +172,18 @@ export class DropdownOption extends Option {
this.parseNameAndDescriptionAndRemoveSettingBorder(setting, plugin);
}
}


export class MdFilePickerOption extends Option {
constructor(configKey: string, nameKey: LanguageStringKey, descriptionKey: LanguageStringKey, ruleAlias?: string | null) {
super(configKey, nameKey, descriptionKey, [], ruleAlias);
}

public display(containerEl: HTMLElement, settings: LinterSettings, plugin: LinterPlugin): void {
settings.ruleConfigs[this.ruleAlias][this.configKey] = settings.ruleConfigs[this.ruleAlias][this.configKey] ?? [];

new AutoCorrectFilesPickerOption(containerEl, plugin.settingsTab.component, settings.ruleConfigs[this.ruleAlias][this.configKey], plugin.app, () => {
plugin.saveSettings();
}, this.nameKey, this.descriptionKey);
}
}
35 changes: 29 additions & 6 deletions src/rules/auto-correct-common-misspellings.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {IgnoreTypes} from '../utils/ignore-types';
import {Options, RuleType} from '../rules';
import RuleBuilder, {ExampleBuilder, OptionBuilderBase, TextAreaOptionBuilder} from './rule-builder';
import RuleBuilder, {ExampleBuilder, MdFilePickerOptionBuilder, OptionBuilderBase, TextAreaOptionBuilder} from './rule-builder';
import dedent from 'ts-dedent';
import {misspellingToCorrection} from '../utils/auto-correct-misspellings';
import {wordRegex, wordSplitterRegex} from '../utils/regex';
import {CustomAutoCorrectContent} from '../ui/linter-components/auto-correct-files-picker-option';

class AutoCorrectCommonMisspellingsOptions implements Options {
ignoreWords?: string[] = [];
extraAutoCorrectFiles?: CustomAutoCorrectContent[] = [];
}

@RuleBuilder.register
Expand All @@ -27,16 +29,30 @@ export default class AutoCorrectCommonMisspellings extends RuleBuilder<AutoCorre
}
replaceWordWithCorrectCasing(word: string, options: AutoCorrectCommonMisspellingsOptions): string {
const lowercasedWord = word.toLowerCase();
if (!misspellingToCorrection.has(lowercasedWord) || options.ignoreWords.includes(lowercasedWord)) {
if (options.ignoreWords.includes(lowercasedWord)) {
return word;
}

let correctedWord = misspellingToCorrection.get(lowercasedWord);
if (word.charAt(0) == word.charAt(0).toUpperCase()) {
correctedWord = correctedWord.charAt(0).toUpperCase() + correctedWord.substring(1);
if (misspellingToCorrection.has(lowercasedWord)) {
return this.determineCorrectedWord(word, misspellingToCorrection.get(lowercasedWord));
}

return correctedWord;
if (options.extraAutoCorrectFiles) {
for (let i = 0; i < options.extraAutoCorrectFiles.length; i++) {
if (options.extraAutoCorrectFiles[i].customReplacements?.has(lowercasedWord)) {
return this.determineCorrectedWord(word, options.extraAutoCorrectFiles[i].customReplacements?.get(lowercasedWord));
}
}
}

return word;
}
determineCorrectedWord(originalWord: string, replacement: string): string {
if (originalWord.charAt(0) == originalWord.charAt(0).toUpperCase()) {
replacement = replacement.charAt(0).toUpperCase() + replacement.substring(1);
}

return replacement;
}
get exampleBuilders(): ExampleBuilder<AutoCorrectCommonMisspellingsOptions>[] {
return [
Expand Down Expand Up @@ -115,6 +131,13 @@ export default class AutoCorrectCommonMisspellings extends RuleBuilder<AutoCorre
splitter: wordSplitterRegex,
separator: ', ',
}),
new MdFilePickerOptionBuilder({
OptionsClass: AutoCorrectCommonMisspellingsOptions,
nameKey: 'rules.auto-correct-common-misspellings.extra-auto-correct-files.name',
descriptionKey: 'rules.auto-correct-common-misspellings.extra-auto-correct-files.description',
// @ts-expect-error since it looks like there is an issue with the types here
optionsKey: 'extraAutoCorrectFiles',
}),
];
}
}
Loading

0 comments on commit 229a6aa

Please sign in to comment.