Skip to content

Commit

Permalink
fix: Have the completion provider auto fill ignore, words, and dictio…
Browse files Browse the repository at this point in the history
…naries (#1255)

* fix: Have the completion provider auto fill `ignore` and `words`
* fix: improve the word and dictionary suggestions.
  • Loading branch information
Jason3S authored Sep 2, 2021
1 parent 21f1266 commit 98f17f2
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 15 deletions.
6 changes: 6 additions & 0 deletions fixtures/workspaces/jupyter/getting-started/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# Sample Jupyter Notebook

Thesse are wordz to livve by.

Whaat do youu thinkk?

How about trying it agaain?
4 changes: 2 additions & 2 deletions packages/client/src/autocomplete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const mockedRegisterCompletionItemProvider = mocked(languages.registerCompletion
describe('autocomplete', () => {
test('registerCspellInlineCompletionProviders', () => {
const disposables = registerCspellInlineCompletionProviders();
expect(mockedRegisterCompletionItemProvider).toHaveBeenCalledTimes(2);
expect(disposables).toHaveLength(2);
expect(mockedRegisterCompletionItemProvider).toHaveBeenCalledTimes(4);
expect(disposables).toHaveLength(4);
});
});
143 changes: 130 additions & 13 deletions packages/client/src/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
import * as vscode from 'vscode';
import { getCSpellDiags } from './diags';
import * as di from './di';

export function registerCspellInlineCompletionProviders(): vscode.Disposable[] {
return [
vscode.languages.registerCompletionItemProvider(selector, cspellTriggerCompletionProvider, 'p'),
vscode.languages.registerCompletionItemProvider(selector, cspellInlineCompletionProvider, ':'),
vscode.languages.registerCompletionItemProvider({ language: '*', scheme: '*' }, cspellTriggerCompletionProvider, 'c', 's', 'p'),
vscode.languages.registerCompletionItemProvider('*', cspellInlineCompletionProvider, ':'),
vscode.languages.registerCompletionItemProvider('*', cspellInlineDictionaryNameCompletionProvider, ' '),
vscode.languages.registerCompletionItemProvider('*', cspellInlineIssuesCompletionProvider, ' '),
];
}

const selector = '*'; // [{ scheme: 'file', language: 'markdown' }, { scheme: 'undefined', language: '*' }, '*'];

const cspellInlineCompletionProvider: vscode.CompletionItemProvider = {
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
const diags = findNearestDiags(getCSpellDiags(document.uri), position, 1);
// get all text until the `position` and check if it reads `cspell.`
const linePrefix = document.lineAt(position).text.substr(0, position.character);
if (!linePrefix.endsWith('spell:')) {
return undefined;
}

return completions.map(genCompletionItem);
const context: CompletionContext = {
words: diags.map((d) => document.getText(d.range)),
};

return completions.map((c) => (typeof c === 'function' ? c(context) : c)).map(genCompletionItem);
},
resolveCompletionItem(item: vscode.CompletionItem) {
return item;
},
};

function genCompletionItem(c: Completion): vscode.CompletionItem {
const item = new vscode.CompletionItem(c.label, vscode.CompletionItemKind.Text);
const item = new vscode.CompletionItem(c.label, c.kind || vscode.CompletionItemKind.Text);
item.insertText = new vscode.SnippetString(c.insertText);
item.documentation = c.description;
item.sortText = c.sortText;
item.commitCharacters = c.commitCharacters || [' '];
return item;
}

Expand All @@ -34,20 +45,30 @@ interface Completion {
insertText: string;
description: string;
sortText?: string;
kind?: vscode.CompletionItemKind;
commitCharacters?: string[];
}

const completions: Completion[] = [
interface CompletionContext {
words: string[];
}

type CompletionFn = (context: CompletionContext) => Completion;

const completions: (Completion | CompletionFn)[] = [
{
label: 'words',
insertText: 'words ${1:word}',
insertText: 'words',
description: 'Words to be allowed in the document',
sortText: '1',
kind: vscode.CompletionItemKind.Snippet,
},
{
label: 'ignore words',
insertText: 'ignore ${1:word}',
insertText: 'ignore',
description: 'Words to be ignored in the document',
sortText: '2',
kind: vscode.CompletionItemKind.Snippet,
},
{
label: 'ignoreRegExp',
Expand Down Expand Up @@ -77,22 +98,36 @@ const completions: Completion[] = [
},
{
label: 'dictionaries',
insertText: 'dictionaries ${1:dictionary_name}',
insertText: 'dictionaries',
commitCharacters: [' '],
description: 'Add dictionaries to be used in this document.',
kind: vscode.CompletionItemKind.Snippet,
},
{
label: 'locale',
insertText: 'locale ${1:en}',
description: 'Set the language locale to be used in this document. (i.e. fr,en)',
kind: vscode.CompletionItemKind.Snippet,
},
{
label: 'disable compound words',
insertText: 'disableCompoundWords',
description: 'Turn OFF Allow Compound Words.',
},
{
label: 'enable compound words',
insertText: 'enableCompoundWords',
description: 'Turn ON Allow Compound Words.',
},
];

// a completion item that can be accepted by a commit character,
// the `commitCharacters`-property is set which means that the completion will
// be inserted and then the character will be typed.
const triggerCSpellCompletion = new vscode.CompletionItem('cspell');
const triggerText = 'cspell';
const triggerCSpellCompletion = new vscode.CompletionItem('cspell', vscode.CompletionItemKind.Snippet);
triggerCSpellCompletion.commitCharacters = [':'];
triggerCSpellCompletion.insertText = 'cspell';
triggerCSpellCompletion.insertText = new vscode.SnippetString('${LINE_COMMENT} cspell');
triggerCSpellCompletion.documentation = new vscode.MarkdownString('Press `:` to get `cspell` options');

const cspellTriggerCompletionProvider: vscode.CompletionItemProvider = {
Expand All @@ -103,11 +138,93 @@ const cspellTriggerCompletionProvider: vscode.CompletionItemProvider = {
_context: vscode.CompletionContext
) {
const linePrefix = document.lineAt(position).text.substr(0, position.character);
if (!linePrefix.endsWith('csp')) {
const lastWord = linePrefix.split(/\s/g).pop();
if (!lastWord || lastWord.length < 3 || !triggerText.startsWith(lastWord)) {
return undefined;
}

// return all completion items as array
return [triggerCSpellCompletion];
},
resolveCompletionItem(item: vscode.CompletionItem) {
return item;
},
};

// function flatten<T>(nested: T[][]): T[] {
// function* _flatten<T>(nested: T[][]) {
// for (const a of nested) {
// yield* a;
// }
// }

// return [..._flatten(nested)];
// }

function findNearestDiags(diags: vscode.Diagnostic[], position: vscode.Position, count: number): vscode.Diagnostic[] {
/** A Simple distance calculation weighted towards lines over characters while trying to preserve order. */
function dist(diag: vscode.Diagnostic) {
const p0 = diag.range.start;
const deltaLine = Math.abs(p0.line - position.line);
return deltaLine * 1000 + p0.character;
}

const sorted = [...diags].sort((a, b) => dist(a) - dist(b));
return sorted.slice(0, count);
}

const cspellInlineDictionaryNameCompletionProvider: vscode.CompletionItemProvider = {
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
// get all text until the `position` and check if it reads `cspell.`
const linePrefix = document.lineAt(position).text.substr(0, position.character);

const isDictionaryRequest = /cspell:dictionaries/;

if (!isDictionaryRequest.test(linePrefix)) {
return undefined;
}

const settings = await di.get('client').getConfigurationForDocument(document);
const docSettings = settings.docSettings || settings.settings;
if (!docSettings) return undefined;

const enabledDicts = new Set(docSettings.dictionaries || []);
const dicts = (docSettings.dictionaryDefinitions || [])
.map((def) => def.name)
.filter((a) => !!a)
.filter((name) => !enabledDicts.has(name));

return dicts.map((d) => new vscode.CompletionItem(d, vscode.CompletionItemKind.Text)).map((c) => ((c.commitCharacters = [' ']), c));
},
resolveCompletionItem(item: vscode.CompletionItem) {
return item;
},
};

const cspellInlineIssuesCompletionProvider: vscode.CompletionItemProvider = {
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
// get all text until the `position` and check if it reads `cspell.`
const line = document.lineAt(position);
const linePrefix = line.text.substr(0, position.character);
const isDictionaryRequest = /cspell:(words|ignore)/;

if (!isDictionaryRequest.test(linePrefix)) {
return undefined;
}
const wordsOnline = new Set(line.text.split(/[\s:.]/));
const allDiags = getCSpellDiags(document.uri).filter((d) => !wordsOnline.has(document.getText(d.range)));

const diags = findNearestDiags(allDiags, position, 10);

if (!diags.length) return undefined;

const words = [...new Set(diags.map((d) => document.getText(d.range)))].sort();

return words
.map((word) => new vscode.CompletionItem(word, vscode.CompletionItemKind.Text))
.map((c) => ((c.commitCharacters = [' ']), c));
},
resolveCompletionItem(item: vscode.CompletionItem) {
return item;
},
};

0 comments on commit 98f17f2

Please sign in to comment.